俄罗斯方块方块旋转算法

51

如何表示和旋转俄罗斯方块游戏的方块是最佳算法(及其解释)?我经常发现方块的旋转和表示方案很难理解。

大多数俄罗斯方块游戏似乎在每次旋转时都使用天真的“重新制作方块数组”:

http://www.codeplex.com/Project/ProjectDirectory.aspx?ProjectSearchText=tetris

但是,有些使用预先构建的编码数字和位移来表示每个方块:

http://www.codeplex.com/wintris

是否有一种方法可以使用数学方法进行操作(不确定它是否适用于基于单元格的面板)?


参见:https://gamedev.stackexchange.com/questions/17974/how-to-rotate-blocks-in-tetris - gmarmstrong
这是一篇不错的文章:"俄罗斯方块旋转算法"。_Baeldung_。Sryheni, S. 2023年6月9日。已存档 - undefined
16个回答

33
当我在尝试弄清楚如何在我的俄罗斯方块游戏中实现旋转时,这是我在stackoverflow上找到的第一个问题。虽然这个问题很久了,但我认为我的回答会对那些试图通过算法解决这个问题的人有所帮助。首先,我不同意硬编码每个方块和旋转会更容易。Gamecat的答案是正确的,但我想进一步解释一下。以下是我在Java中解决旋转问题时使用的步骤。
对于每个形状,确定它的原点位置。我使用this page上的图表中的点来分配我的原点位置。请记住,根据您的实现方式,每次用户移动该块时,您可能需要修改原点。
旋转假定原点位于点(0,0),因此您必须在旋转之前将每个块进行平移。例如,假设您的原点当前位于点(4,5)。这意味着在形状旋转之前,必须将每个块在x坐标上平移-4,在y坐标上平移-5,以相对于(0,0)。
在Java中,典型的坐标平面从左上角的点(0,0)开始,然后向右和向下增加。为了在我的实现中进行补偿,我在旋转之前将每个点乘以-1。
以下是我用来计算逆时针旋转后新的x和y坐标的公式。有关更多信息,请查看维基百科上的Rotation Matrix页面。x'和y'是新坐标:
x' = x * cos(PI/2) - y * sin(PI/2) and y' = x * sin(PI/2) + y * cos(PI/2)
最后一步,我只是按相反的顺序执行步骤2和3。所以我再次将结果乘以-1,然后将块平移到它们的原始坐标。

这是我用Java编写的代码,可以帮助你了解如何在你的语言中实现:

public synchronized void rotateLeft(){

    Point[] rotatedCoordinates = new Point[MAX_COORDINATES];

    for(int i = 0; i < MAX_COORDINATES; i++){

        // Translates current coordinate to be relative to (0,0)
        Point translationCoordinate = new Point(coordinates[i].x - origin.x, coordinates[i].y - origin.y);

        // Java coordinates start at 0 and increase as a point moves down, so
        // multiply by -1 to reverse
        translationCoordinate.y *= -1;

        // Clone coordinates, so I can use translation coordinates
        // in upcoming calculation
        rotatedCoordinates[i] = (Point)translationCoordinate.clone();

        // May need to round results after rotation
        rotatedCoordinates[i].x = (int)Math.round(translationCoordinate.x * Math.cos(Math.PI/2) - translationCoordinate.y * Math.sin(Math.PI/2)); 
        rotatedCoordinates[i].y = (int)Math.round(translationCoordinate.x * Math.sin(Math.PI/2) + translationCoordinate.y * Math.cos(Math.PI/2));

        // Multiply y-coordinate by -1 again
        rotatedCoordinates[i].y *= -1;

        // Translate to get new coordinates relative to
        // original origin
        rotatedCoordinates[i].x += origin.x;
        rotatedCoordinates[i].y += origin.y;

        // Erase the old coordinates by making them black
        matrix.fillCell(coordinates[i].x, coordinates[i].y, Color.black);

    }
    // Set new coordinates to be drawn on screen
    setCoordinates(rotatedCoordinates.clone());
}

这个方法就足以将你的形状向左旋转,这比为每个形状定义每个旋转要简单得多(取决于你使用的语言)。


3
优化:sin(Pi/2)=1,cos(Pi/2)=0。由此得到旋转矩阵[[0,-1],[1,0]](逆时针旋转)。 - jojonas
2
对于俄罗斯方块使用的简单旋转,不需要进行矩阵乘法等复杂计算。请参考此答案以了解简单的实现方式:https://dev59.com/3XVD5IYBdhLWcg3wO5ED#8664879 - Thomas Eding

32

形状数量有限,因此我建议使用固定表格而不进行计算。这样可以节省时间。

但是有旋转算法。

选择中心点并旋转π/2。

如果一块图形的起始位置为(1,2),则顺时针移动到(2,-1)、(-1,-2)和(-1,2)。将此应用于每个块,即可旋转该图形。

每个x值都是前一个y值,每个y值都是前一个x值。得到以下矩阵:

[  0   1 ]
[ -1   0 ]

逆时针旋转请使用:

[  0  -1 ]
[  1   0 ]

12
我同意这是一个正确的答案,但我认为它没有详细说明如何通过算法实现。请看下面我的回答,其中包含更多适合不太擅长数学的人的信息。 - Jason Rae

12

这是我最近在一个基于jQuery/CSS的俄罗斯方块游戏中所做的。

计算方块的中心(作为旋转点),即方块形状的中心。 将其称为(px, py)。

组成方块形状的每个小方块都将围绕该点旋转。 对于每个小方块,可以应用以下计算...

其中每个小方块的宽度和高度为q,小方块的当前位置(左上角)为(x1, y1),新的小方块位置为(x2, y2):

x2 = (y1 + px - py)

y2 = (px + py - x1 - q)

要逆向旋转:

x2 = (px + py - y1 - q)

y2 = (x1 + py - px)

这个计算是基于2D仿射矩阵变换的。如果您对我是如何得出这个结果感兴趣,请告诉我。


难道不应该是 x2 = (y1 + px - py) y2 = (x1 + py - px) 吗? - Joji

11

就我个人而言,我一直都是手动表示旋转的 - 对于很少的几种形状,这种方式很容易编码。基本上我有以下伪代码:

class Shape
{
    Color color;
    ShapeRotation[] rotations;
}

class ShapeRotation
{
    Point[4] points;
}

class Point
{
    int x, y;
}

从概念上说,一个直接在形状中的多维点数组也可以解决问题 :)


9

您可以通过在矩阵上应用数学运算来旋转它。如果您有一个矩阵,比如:

Mat A = [1,1,1]
        [0,0,1]
        [0,0,0]

要旋转它,将其乘以其转置,然后再乘以此矩阵([I]身份[H]水平[M]镜像):

IHM(A) = [0,0,1]
         [0,1,0]
         [1,0,0]

然后您将拥有:
Mat Rotation = Trn(A)*IHM(A) = [1,0,0]*[0,0,1] = [0,0,1]
                               [1,0,0] [0,1,0] = [0,0,1]
                               [1,1,0] [1,0,0] = [0,1,1]

注意:旋转中心将是矩阵的中心,在此情况下为(2,2)。

8

表示

将每个方块表示为最小矩阵,其中1表示俄罗斯方块所占据的空间,0表示空白区域。例如:

originalMatrix = 
[0,   0,   <b>1</b>]
[<b>1</b>,   <b>1</b>,   <b>1</b>]

旋转公式

enter image description here

旋转公式

clockwise90DegreesRotatedMatrix = reverseTheOrderOfColumns(Transpose(originalMatrix))

anticlockwise90DegreesRotatedMatrix = reverseTheOrderOfRows(Transpose(originalMatrix))

插图

originalMatrix = 
  x    y    z
a[0,   0,   <b>1</b>]
b[<b>1</b>,   <b>1</b>,   <b>1</b>]

transposed = transpose(originalMatrix)
  a   b
x[0,  <b>1</b>]
y[0,  <b>1</b>]
z[<b>1</b>,  <b>1</b>]

counterClockwise90DegreesRotated = reverseTheOrderOfRows(transposed)
  a   b
z[<b>1</b>,  <b>1</b>]
y[0,  <b>1</b>]
x[0,  <b>1</b>]

enter image description here

clockwise90DegreesRotated = reverseTheOrderOfColumns(transposed)
  b   a
x[<b>1</b>,  0]
y[<b>1</b>,  0]
z[<b>1</b>,  <b>1</b>]

enter image description here


7

由于每个形状只有4种可能的方向,为什么不使用一个状态数组来表示该形状,然后通过顺时针或逆时针旋转来增加或减少形状状态的索引(对于索引进行环绕)?我认为这可能比执行旋转计算等更快。


找出每个形状和块的状态并不容易。应该有更优雅的方法。 - Arnis Lapsa
8
对于俄罗斯方块游戏,很容易。这是我最喜欢的玩法。 - Nosredna
这正是我在Commodore 64的俄罗斯方块克隆中处理它的方式。请参见http://sblox.codeplex.com/SourceControl/changeset/view/97448#2376111以查看形状表的示例。 - Payton Byrd

3

假设我们假定俄罗斯方块中央方块的坐标为(x0,y0),并保持不变,则Java中其他3个方块的旋转将如下所示:

private void rotateClockwise()
{
    if(rotatable > 0)   //We don't rotate tetromino O. It doesn't have central square.
    {
        int i = y1 - y0;
        y1 = (y0 + x1) - x0;
        x1 = x0 - i;
        i = y2 - y0;
        y2 = (y0 + x2) - x0;
        x2 = x0 - i;
        i = y3 - y0;
        y3 = (y0 + x3) - x0;
        x3 = x0 - i;  
    }
}

private void rotateCounterClockwise()
{
    if(rotatable > 0)
    {
        int i = y1 - y0;
        y1 = (y0 - x1) + x0;
        x1 = x0 + i;
        i = y2 - y0;
        y2 = (y0 - x2) + x0;
        x2 = x0 + i;
        i = y3 - y0;
        y3 = (y0 - x3) + x0;
        x3 = x0 + i;
    }
}

3

我从这里的矩阵旋转中推导出一种旋转算法。简而言之:如果你有一个由所有组成块的单元格的坐标列表,例如[(0, 1), (1, 1), (2, 1), (3, 1)]或[(1, 0), (0, 1), (1, 1), (2, 1)]:

 0123       012
0....      0.#.
1####  or  1###
2....      2...
3....

您可以使用以下方式计算新的坐标:

x_new = y_old
y_new = 1 - (x_old - (me - 2))

顺时针旋转

x_new = 1 - (y_old - (me - 2))
y_new = x_old

表示逆时针旋转。 me 是方块的最大范围,即对于I型方块为4,对于O型方块为2,对于其他所有方块为3


3

如果您使用Python,基于单元格而不是坐标对来旋转嵌套列表非常简单。

rotate = lambda tetrad: zip(*tetrad[::-1])

# S Tetrad
tetrad = rotate([[0,0,0,0], [0,0,0,0], [0,1,1,0], [1,1,0,0]])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接