俄罗斯方块2维数组逻辑

6
我正在尝试使用矩阵而不是精灵在JS中编写俄罗斯方块游戏,以便更好地可视化2D数组。
我通过转置其矩阵数据然后反转行来旋转一个块。 但由于块的宽度和高度不能完全填充这个4x4的矩阵, 旋转导致块移动,而不是在原地旋转。
我无法解决这个问题,已经花了两天以上的时间尝试让这个简单的游戏工作,并重新开始了几次。 我需要帮助,我真的想学会编程游戏,唯一能成功的就是井字棋,但我花在上面的时间比应该多得多。
以下是完整的JS代码。单击画布旋转方块。

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
canvas.width = 400;
canvas.height = 600;

// game object
var G = {};
var current = 0;
var x = 0;
var y = 0;

//GRID
G.grid = [];
G.gridColumns = 10;
G.gridRows = 15;
for (var i = 0; i < G.gridColumns; i++) {
  G.grid[i] = [];
  for (var j = 0; j < G.gridRows; j++) {
    G.grid[i][j] = 0;
  }
}

// Array with all different blocks
G.blocks = [];
//block constructor
function block() {};
G.blocks[0] = new block();
G.blocks[0].matrix = [
  [1, 0, 0, 0],
  [1, 1, 0, 0],
  [0, 1, 0, 0],
  [0, 0, 0, 0]
];
G.blocks[0].width = 2;
G.blocks[0].height = 3;

function transpose(m) {
  // dont understand this completely, but works because j<i
  for (var i = 0; i < m.matrix.length; i++) {
    for (var j = 0; j < i; j++) {
      var temp = m.matrix[i][j];
      m.matrix[i][j] = m.matrix[j][i];
      m.matrix[j][i] = temp;
    }
  }
}

function reverseRows(m) {
  for (var i = 0; i < m.matrix.length; i++) {
    m.matrix[i].reverse();
  }
}

function rotate(m) {
  transpose(m);
  reverseRows(m);
}

function add(m1, m2) {
  for (var i = 0; i < m1.matrix.length; i++) {
    for (var j = 0; j < m1.matrix[i].length; j++) {
      m2[i + x][j + y] = m1.matrix[i][j];
    }
  }
}

function draw(matrix) {
  for (var i = 0; i < matrix.length; i++) {
    for (var j = 0; j < matrix[i].length; j++) {
      if (matrix[i][j] === 1) {
        ctx.fillRect(j * 20, i * 20, 19, 19);
      }
    }
  }
  ctx.strokeRect(0, 0, G.gridColumns * 20, G.gridRows * 20);
}


window.addEventListener("click", function(e) {
  rotate(G.blocks[current]);
});

function tick() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  add(G.blocks[current], G.grid);
  draw(G.grid);
}
setInterval(tick, 1000 / 30);
<canvas id="c"></canvas>

请忽略我代码中的小问题,我是自学编程的。 提前感谢您的帮助 :)

到底是什么出了问题? - zero298
为什么不使用3x3矩阵而是4x4矩阵? - Vanojx1
2
@Vanojx1 一些俄罗斯方块的形状可能有4个方格的长度/宽度。 - zero298
@Vanojx1 把容器的大小设置为最大元素的大小,在我的脑海中是有意义的。我不是最聪明的人 :) - onlyme0349
2个回答

6

旋转

实际旋转存在一个问题,即使矩阵的宽度被考虑在内,有些旋转看起来并不完美。让我们看一下对字母 I 进行旋转会发生什么:

. X . .        . . . .        . . X .        . . . .
. X . .   =>   X X X X   =>   . . X .   =>   . . . .
. X . .        . . . .        . . X .        X X X X
. X . .        . . . .        . . X .        . . . .

从游戏玩法的角度来看,你会期望第三个和第四个形状分别与第一个和第二个形状相同。然而,使用通用旋转算法并不能得到这样的结果。你可以通过使用非方形矩阵(5x4)来解决上述问题,但算法将比最初预期的更加复杂。
实际上,我愿意打赌大多数俄罗斯方块的实现都不会费心地用程序设计旋转,并且只是在一种看起来尽可能好和公平的方式下硬编码所有不同可能的方块形状。美妙之处在于,您不再需要担心它们的大小。您可以将它们全部存储为4x4。
正如我们将在这里看到的那样,这可以以非常紧凑的格式完成。
将俄罗斯方块作为位掩码进行编码
因为俄罗斯方块基本上是一组可以是开或者关的“大像素”,所以将其表示为位掩码而不是整数矩阵非常适合和高效。
让我们看看如何将S形状的两种不同旋转编码:
X . . .     1 0 0 0
X X . .  =  1 1 0 0  =  1000110001000000 (in binary)  =  0x8C40 (in hexadecimal)
. X . .     0 1 0 0
. . . .     0 0 0 0

. X X .     0 1 1 0
X X . .  =  1 1 0 0  =  0110110000000000 (in binary)  =  0x6C00 (in hexadecimal)
. . . .     0 0 0 0
. . . .     0 0 0 0

对于这个形状,其余两个旋转是相同的。因此,我们可以用以下方式完全定义我们的S形状:

[ 0x8C40, 0x6C00, 0x8C40, 0x6C00 ]

对于每个形状和每个旋转,我们都做了同样的事情,最终得到了以下内容:

var shape = [
  [ 0x4640, 0x0E40, 0x4C40, 0x4E00 ], // 'T'
  [ 0x8C40, 0x6C00, 0x8C40, 0x6C00 ], // 'S'
  [ 0x4C80, 0xC600, 0x4C80, 0xC600 ], // 'Z'
  [ 0x4444, 0x0F00, 0x4444, 0x0F00 ], // 'I'
  [ 0x44C0, 0x8E00, 0xC880, 0xE200 ], // 'J'
  [ 0x88C0, 0xE800, 0xC440, 0x2E00 ], // 'L'
  [ 0xCC00, 0xCC00, 0xCC00, 0xCC00 ]  // 'O'
];

绘制它们

现在,我们要如何用这种新格式来绘制一个俄罗斯方块?与其使用matrix[y][x]访问矩阵中的值,我们将测试位掩码中相关的位:

for (var y = 0; y < 4; y++) {
  for (var x = 0; x < 4; x++) {
    if (shape[s][r] & (0x8000 >> (y * 4 + x))) {
      ctx.fillRect(x * 20, y * 20, 19, 19);
    }
  }
}

演示

下面是使用此方法的一些演示代码。

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
canvas.width = 100;
canvas.height = 100;

var shape = [
  [ 0x4640, 0x0E40, 0x4C40, 0x4E00 ], // 'T'
  [ 0x8C40, 0x6C00, 0x8C40, 0x6C00 ], // 'S'
  [ 0x4C80, 0xC600, 0x4C80, 0xC600 ], // 'Z'
  [ 0x4444, 0x0F00, 0x4444, 0x0F00 ], // 'I'
  [ 0x44C0, 0x8E00, 0xC880, 0xE200 ], // 'J'
  [ 0x88C0, 0xE800, 0xC440, 0x2E00 ], // 'L'
  [ 0xCC00, 0xCC00, 0xCC00, 0xCC00 ]  // 'O'
];

var curShape = 0, curRotation = 0;
draw(curShape, curRotation);

function draw(s, r) {
  ctx.fillStyle = 'white';
  ctx.fillRect(0, 0, 100, 100);
  ctx.fillStyle = 'black';

  for (var y = 0; y < 4; y++) {
    for (var x = 0; x < 4; x++) {
      if (shape[s][r] & (0x8000 >> (y * 4 + x))) {
        ctx.fillRect(x * 20, y * 20, 19, 19);
      }
    }
  }
}

function next() {
  curShape = (curShape + 1) % 7;
  draw(curShape, curRotation);
}

function rotate() {
  curRotation = (curRotation + 1) % 4;
  draw(curShape, curRotation);
}
<canvas id="c"></canvas>
<button onclick="rotate()">Rotate</button>
<button onclick="next()">Next shape</button>


2
首先,为什么button标签的onclick参数对我不起作用?我一直不得不在我的js代码中使用eventlistener。其次,你能告诉我这个"(0x8000 >> (y * 4 + x))"代码片段是做什么的吗?我甚至没有在Javascript中看到过那个运算符。谢谢,我以前没听说过位掩码,学会使用它们可能会非常有用。 - onlyme0349
2
@onlyme0349 这个运算符是“按位右移运算符”。有关按位运算符的良好介绍在这里 - Mark
1
即使有这个,理解正在发生的事情仍然非常困难,特别是迭代。不过还是谢谢,至少我现在知道它叫什么了。 :) - onlyme0349
2
基本上,我们从位掩码的最高位(0x8000 = 第16位)开始,根据 x 和 y 将其向右移动 0 到 15 个位置(>>),然后使用按位与(&)运算符与俄罗斯方块位掩码进行运算,以确定该单元格是否已设置。 - Arnauld
1
@Arnould 我明白了,谢谢。最后一个问题,关于“0x”(标签?)是什么?它像我们在CSS中使用的“#”标签吗? - onlyme0349
2
0x是十六进制表示法的前缀。您可以在此处了解更多有关数字字面量的信息(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Numbers_and_dates)。 - Arnauld

3
我认为你的问题在于你总是假设你的方块宽度为4个格子。你可能希望将矩阵缩小到最小的正方形空间。 对于Z / S块,它应该是3x3。然后,你的旋转中心将正确运作。
你现在的问题是旋转有点正确,但是你的方块中心在单元格(2,2)而不是(1,1)(假设基数为0)。C是应用旋转的参考框架。
[x][ ][ ][ ][ ]      [ ][ ][X][X][ ]
[X][X][ ][ ][ ]      [ ][X][X][ ][ ]
[ ][X][C][ ][ ]  =>  [ ][ ][C][ ][ ]
[ ][ ][ ][ ][ ]      [ ][ ][ ][ ][ ]
[ ][ ][ ][ ][ ]      [ ][ ][ ][ ][ ]

如果你能将你的形状缩小成一团,你就可以应用旋转并实现以下效果:
[x][ ][ ]      [ ][X][X]
[X][C][ ]  =>  [X][C][ ]
[ ][X][ ]      [ ][ ][ ]

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - onlyme0349

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