JavaScript实现俄罗斯方块:无需硬编码的方块旋转算法方案
俄罗斯方块旋转功能实现方案
完全不需要为每种方块硬编码全部旋转状态,用通用的坐标旋转变换就能实现,代码量小还好维护,具体实现逻辑如下:
核心原理
你现在存储的是每个小方块的绝对画布坐标,直接对绝对坐标做变换会出现方块整体飘移的问题,正确做法是先把坐标转换为相对于方块组自身旋转中心的相对坐标,做完90度旋转变换后,再转回绝对画布坐标即可。
因为画布的Y轴方向是向下的(和数学坐标系Y轴向上相反),适配屏幕坐标的90度旋转公式非常简单:
设某小方块相对于旋转中心的网格偏移为
(relX, relY)(以单个方块边长30px为1单位)
- 顺时针旋转90度后新偏移:
newRelX = relY,newRelY = -relX- 逆时针旋转90度后新偏移:
newRelX = -relY,newRelY = relX
所有偏移都是整数个网格单位,计算完不会出现半像素错位的问题。
具体实现步骤
- 给当前正在下落的活动方块组单独存一个旋转中心(锚点)坐标,这个坐标要跟着方块组一起移动:T/J/L/S/Z这几个3格宽的方块,旋转中心选方块组中心格的坐标即可;I型长条的旋转中心选中间两格的交界点;O型方块是2*2正方形,旋转后形态不变,可以直接跳过旋转逻辑。
- 触发旋转时,遍历当前活动方块组的4个小方块,先算出每个块相对于旋转中心的网格偏移值(像素差除以30转成网格单位,避免浮点数误差)。
- 用上面的旋转公式算出新的相对偏移,再乘30转回像素值,加回旋转中心的绝对坐标,就是旋转后每个小方块的新位置。
核心实现代码参考:
const BLOCK_SIZE = 30; /** * 旋转方块组 * @param {Tetromino[]} blocks 当前活动的4个小方块实例 * @param {{x:number, y:number}} pivot 方块组旋转中心的绝对像素坐标 * @param {boolean} isClockwise 是否顺时针旋转 * @returns {Tetromino[]} 旋转后的方块数组 */ function rotateBlocks(blocks, pivot, isClockwise = true) { return blocks.map(block => { // 转换为相对旋转中心的网格坐标 const relX = (block.x - pivot.x) / BLOCK_SIZE; const relY = (block.y - pivot.y) / BLOCK_SIZE; let newRelX, newRelY; if (isClockwise) { newRelX = relY; newRelY = -relX; } else { newRelX = -relY; newRelY = relX; } // 转换回绝对像素坐标更新方块位置 block.x = pivot.x + newRelX * BLOCK_SIZE; block.y = pivot.y + newRelY * BLOCK_SIZE; return block; }); }
注意事项
- I型长条旋转完可能出现半格偏移,只要在旋转后给所有块的坐标统一加15px(半个方块边长)的修正值,或者初始生成I块时就把相对坐标对齐到格点中心即可解决。
- 旋转完成后要做碰撞检测,如果方块超出画布边界、或者和已经落定的方块重叠,不要直接取消旋转,可以给整个方块组做±30px的水平/垂直偏移找合法位置,也就是常说的“墙踢”逻辑,玩起来手感会顺畅很多。
- 不推荐硬编码所有旋转状态:7种方块各4个旋转方向要写28组坐标,后续调整方块大小、改生成位置都要改大量重复代码,通用变换逻辑只需要写一次,后续新增方块类型都不用修改旋转相关代码。
内容的提问来源于stack exchange,提问作者leo___
相关产品推荐
相关产品推荐

