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

如何在JavaScript中实现俄罗斯方块图形的90°顺时针旋转?

嘿,我懂你这种硬编码旋转映射的痛苦——每次加新形状都要写一堆重复代码,简直是折磨!其实咱们完全可以用坐标变换的思路,实现一个通用的90°顺时针旋转函数,再也不用依赖硬编码了,也没必要切换到二维矩阵。

核心思路

俄罗斯方块的旋转本质是围绕一个锚点做坐标变换:先把所有点转换为相对于锚点的局部坐标,应用旋转公式后再转回到全局坐标。关键是选对旋转锚点(比如官方俄块里不同形状有固定的锚点),以及适配屏幕坐标系(y轴向下)的旋转公式。

通用实现方案

1. 带指定锚点的旋转函数(精准匹配需求)

先给你一个最直接的实现,支持手动指定旋转锚点——刚好能完美匹配你给出的S形旋转例子:

// 传入形状坐标数组和旋转锚点,返回旋转后的坐标数组
function rotateShapeClockwise(shape, anchor) {
  return shape.map(point => {
    // 第一步:转成相对于锚点的局部坐标
    const localX = point.x - anchor.x;
    const localY = point.y - anchor.y;
    
    // 第二步:应用屏幕坐标系下的90°顺时针旋转公式
    // (数学坐标系顺时针公式是(x,y)→(y,-x),屏幕y轴向下,公式调整为(x,y)→(-y,x))
    const newLocalX = -localY;
    const newLocalY = localX;
    
    // 第三步:转回全局坐标
    return {
      x: newLocalX + anchor.x,
      y: newLocalY + anchor.y
    };
  });
}

用你的S形测试一下:

const sShape = [ {x:0, y:2}, {x:0, y:1}, {x:1, y:1}, {x:1, y:0} ];
// S形的旋转锚点选中间的(1,1)
const rotatedSShape = rotateShapeClockwise(sShape, {x:1, y:1});
console.log(rotatedSShape);
// 输出完全符合你的需求:
// [ {x:0,y:0}, {x:1,y:0}, {x:1,y:1}, {x:2,y:1} ]

2. 自动计算锚点的通用函数(适配所有形状)

如果不想手动给每个形状指定锚点,可以通过包围盒自动计算锚点,再结合位置调整保证旋转后形状不偏移:

// 自动计算形状的旋转锚点(默认取包围盒中心)
function getRotationAnchor(shape) {
  const xs = shape.map(p => p.x);
  const ys = shape.map(p => p.y);
  const minX = Math.min(...xs);
  const maxX = Math.max(...xs);
  const minY = Math.min(...ys);
  const maxY = Math.max(...ys);
  
  // 返回包围盒中心(整数坐标,适配网格)
  return {
    x: Math.floor((minX + maxX) / 2),
    y: Math.floor((minY + maxY) / 2)
  };
}

// 带自动锚点和位置校准的通用旋转函数
function rotateShapeClockwiseAuto(shape) {
  const anchor = getRotationAnchor(shape);
  let rotated = rotateShapeClockwise(shape, anchor);
  
  // 校准位置:让旋转前后的形状中心保持一致,避免偏移
  const oldCenter = {
    x: (Math.min(...shape.map(p=>p.x)) + Math.max(...shape.map(p=>p.x))) / 2,
    y: (Math.min(...shape.map(p=>p.y)) + Math.max(...shape.map(p=>p.y))) / 2
  };
  const newCenter = {
    x: (Math.min(...rotated.map(p=>p.x)) + Math.max(...rotated.map(p=>p.x))) / 2,
    y: (Math.min(...rotated.map(p=>p.y)) + Math.max(...rotated.map(p=>p.y))) / 2
  };
  
  // 计算偏移量并取整,保证坐标是网格上的整数
  const offsetX = Math.round(oldCenter.x - newCenter.x);
  const offsetY = Math.round(oldCenter.y - newCenter.y);
  
  return rotated.map(p => ({
    x: p.x + offsetX,
    y: p.y + offsetY
  }));
}

比如测试直线形:

const lineShape = [ {x:0,y:0}, {x:0,y:1}, {x:0,y:2}, {x:0,y:3} ];
const rotatedLine = rotateShapeClockwiseAuto(lineShape);
// 旋转后会得到横向的直线:[ {x:0,y:1}, {x:1,y:1}, {x:2,y:1}, {x:3,y:1} ]

扩展优化

如果要完全贴合官方俄罗斯方块的旋转规则,可以给每个形状的定义加上专属锚点:

const shapes = {
  S: {
    coords: [ {x:0, y:2}, {x:0, y:1}, {x:1, y:1}, {x:1, y:0} ],
    anchor: {x:1, y:1}
  },
  Line: {
    coords: [ {x:0,y:0}, {x:0,y:1}, {x:0,y:2}, {x:0,y:3} ],
    anchor: {x:0, y:1.5} // 用浮点锚点,最后取整即可
  },
  Square: {
    coords: [ {x:0,y:0}, {x:0,y:1}, {x:1,y:0}, {x:1,y:1} ],
    anchor: {x:0.5, y:0.5}
  }
};

旋转时直接调用rotateShapeClockwise(shapes.S.coords, shapes.S.anchor)就行,完全不用硬编码旋转映射。

为什么这个方案更好?

  • 无冗余代码:新增形状只需要定义初始坐标和锚点,不用写旋转后的坐标
  • 灵活可控:可以随时调整锚点和位置校准规则,适配不同的游戏逻辑
  • 逻辑清晰:基于坐标变换的数学原理,比硬编码更容易维护和调试

内容的提问来源于stack exchange,提问作者Adelin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:20:48