You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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___

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 06:36:25