テトリスゲームのピースを表現および回転させるための最良のアルゴリズム(および説明)は何ですか?ピースの回転と表現のスキームはいつも混乱します。
ほとんどのテトリスゲームは、各回転で単純な「ブロックの配列を作り直す」を使用するようです。
http://www.codeplex.com/Project/ProjectDirectory.aspx?ProjectSearchText=tetris
ただし、一部のものは、事前に構築されたエンコードされた数字とビットシフトを使用して、各ピースを表します。
http://www.codeplex.com/wintris
数学を使用してこれを行う方法はありますか(セルベースのボードで動作するかどうかはわかりません)?
形状の量は限られているため、固定テーブルを使用し、計算は使用しません。それは時間を節約します。
しかし、回転アルゴリズムがあります。
中心点を選択し、pi/2を回転します。
ピースのブロックが(1,2)で始まる場合、時計回りに(2、-1)と(-1、-2)と(-1、2)に移動します。これを各ブロックに適用すると、ピースが回転します。
各xは前のyであり、各yは前のxです。次のマトリックスが得られます。
[ 0 1 ]
[ -1 0 ]
反時計回りに回転するには、次を使用します。
[ 0 -1 ]
[ 1 0 ]
テトリスゲームで回転がどのように機能するかを理解しようとしたとき、これがスタックオーバーフローで最初に見つかった質問でした。この質問は古いものですが、私の入力は、他の人がこれをアルゴリズム的に理解しようとするのに役立つと思います。まず、各ピースとローテーションのハードコーディングが簡単になることに同意しません。 Gamecatの答えは正しいのですが、詳しく説明したかったのです。 Javaの回転の問題を解決するために使用した手順を次に示します。
各形状について、その原点がどこにあるかを決定します。 このページ のダイアグラム上のポイントを使用して、Originポイントを割り当てました。実装によっては、ユーザーがピースを移動するたびにOriginを変更する必要がある場合があることに注意してください。
回転では、原点がポイント(0,0)にあると想定されるため、各ブロックを回転させる前に各ブロックを平行移動する必要があります。たとえば、Originが現在ポイント(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)および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());
}
図形を左に回転させるために必要なのはこの方法だけです。これは、すべての図形の各回転を定義するよりもはるかに小さくなります(言語によって異なります)。
これは、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アフィン行列変換に基づいています。あなたが私がこれに到達した方法に興味があれば私に知らせてください。
個人的には、常に手で回転を表現してきました-形がほとんどなく、そのようにコーディングするのは簡単です。基本的に私は(擬似コードとして)持っていた
class Shape
{
Color color;
ShapeRotation[] rotations;
}
class ShapeRotation
{
Point[4] points;
}
class Point
{
int x, y;
}
少なくとも概念的には、形状の点の多次元配列は、トリックも行います:)
行列は、数学演算を適用することによってのみ回転できます。マトリックスがある場合は、次のように言います。
Mat A = [1,1,1]
[0,0,1]
[0,0,0]
回転するには、転置してからこの行列を乗算します([I] dentity [H] orizontaly [M] irrored):
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)になります。
各シェイプに可能な方向は4つしかないので、シェイプの状態の配列を使用し、CWまたはCCWを回転させるだけでシェイプステートのインデックスをインクリメントまたはデクリメントします(インデックスのラップアラウンドを使用)。回転計算などを実行するよりも速くなると思います。
表現
1はテトリミノが占める空間を表し、0は空の空間を表す最小行列の各部分を表します。例:
originalMatrix =
[0, 0, 1]
[1, 1, 1]
回転式
clockwise90DegreesRotatedMatrix = reverseTheOrderOfColumns(Transpose(originalMatrix))
anticlockwise90DegreesRotatedMatrix = reverseTheOrderOfRows(Transpose(originalMatrix))
イラスト
originalMatrix =
x y z
a[0, 0, 1]
b[1, 1, 1]
transposed = transpose(originalMatrix)
a b
x[0, 1]
y[0, 1]
z[1, 1]
counterClockwise90DegreesRotated = reverseTheOrderOfRows(transposed)
a b
z[1, 1]
y[0, 1]
x[0, 1]
clockwise90DegreesRotated = reverseTheOrderOfColumns(transposed)
b a
x[1, 0]
y[1, 0]
z[1, 1]
マトリックスの回転から回転アルゴリズムを導き出しました here 。まとめると:ブロックを構成するすべてのセルの座標のリストがある場合、たとえば[(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
はブロックの最大範囲、つまり4
Iブロックの場合、2
Oブロックおよび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]])
Tetrominoの中央の正方形の座標(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;
}
}
3x3サイズのテトリスの作品では、作品のxとyを反転し、外側の列を入れ替えます
少なくとも、Rubyでは、実際に行列を使用できます。 [[0,1]、[0,2]、[0,3]]のような配列のネストされた配列としてピース形状を表します
require 'matrix'
shape = shape.map{|arr|(Matrix[arr] * Matrix[[0,-1],[1,0]]).to_a.flatten}
ただし、各= 28行に7つの図形と4つの状態があり、それ以上になることはないため、図形のハードコーディングは実行可能であることに同意します。
詳細については、私のブログ投稿 https://content.pivotal.io/blog/the-simplest-thing-that-could-possibly-work-in-tetris と完全に機能する実装を参照してください。 (マイナーなバグあり) https://github.com/andrewfader/Tetronimo
シェイプの位置と、すべてのシェイプの4つのポイントの4つの座標のセットを使用しました。 2D空間にあるため、ポイントに2D回転行列を簡単に適用できます。
ポイントはdivなので、cssクラスはオフからオンに変わります。 (これは、最後のターンの場所のcssクラスをクリアした後です。)
配列サイズが3 * 3の場合、たとえば反時計回りに回転させる最も簡単な方法は次のとおりです。
oldShapeMap[3][3] = {{1,1,0},
{0,1,0},
{0,1,1}};
bool newShapeMap[3][3] = {0};
int gridSize = 3;
for(int i=0;i<gridSize;i++)
for(int j=0;j<gridSize;j++)
newShapeMap[i][j] = oldShapeMap[j][(gridSize-1) - i];
/*newShapeMap now contain:
{{0,0,1},
{1,1,1},
{1,0,0}};
*/
Python:
pieces = [
[(0,0),(0,1),(0,2),(0,3)],
[(0,0),(0,1),(1,0),(1,1)],
[(1,0),(0,1),(1,1),(1,2)],
[(0,0),(0,1),(1,0),(2,0)],
[(0,0),(0,1),(1,1),(2,1)],
[(0,1),(1,0),(1,1),(2,0)]
]
def get_piece_dimensions(piece):
max_r = max_c = 0
for point in piece:
max_r = max(max_r, point[0])
max_c = max(max_c, point[1])
return max_r, max_c
def rotate_piece(piece):
max_r, max_c = get_piece_dimensions(piece)
new_piece = []
for r in range(max_r+1):
for c in range(max_c+1):
if (r,c) in piece:
new_piece.append((c, max_r-r))
return new_piece