适用于转换为2D坐标的数据结构及棋盘possibleCells数组格式化咨询
嘿,作为JS新手能想到优化数据结构来提升维护性,这点超棒!我来给你捋捋怎么解决这个问题~
一、优化possibleCells数组的格式化方案
核心思路:按攻击模式分组存储
把零散的相对坐标按攻击规则类型归类,比如直线攻击、斜线攻击、跳跃攻击(像国际象棋马的走法),每个类型用对象描述规则,这样一眼就能看懂棋子的攻击逻辑,维护起来也方便。
具体格式示例
根据不同棋子的攻击逻辑,我们可以把possibleCells设计成对象数组,每个对象对应一种攻击模式:
1. 直线/斜线无限攻击(比如车、象)
适合攻击范围沿固定方向延伸,直到棋盘边界或被阻挡的棋子:
// 车的攻击范围(上下左右直线无限步) const rookPossibleCells = [ { type: "straight", // 攻击类型:直线 directions: [[-1, 0], [1, 0], [0, -1], [0, 1]], // 四个方向的单位步长 maxSteps: Infinity // 最大步数:无限(直到边界) } ]; // 象的攻击范围(四条斜线无限步) const bishopPossibleCells = [ { type: "diagonal", // 攻击类型:斜线 directions: [[-1, -1], [-1, 1], [1, -1], [1, 1]], maxSteps: Infinity } ];
2. 固定跳跃式攻击(比如马、兵)
适合攻击位置是离散的固定相对坐标,没有连续延伸的情况:
// 马的攻击范围(8种跳法) const knightPossibleCells = [ { type: "jump", // 攻击类型:跳跃 positions: [[-2, -1], [-2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2], [2, -1], [2, 1]] // 所有相对坐标 } ]; // 兵的攻击范围(假设只能斜向前1步攻击) const pawnPossibleCells = [ { type: "jump", positions: [[1, -1], [1, 1]] // 假设y轴向下为正,兵在上方,攻击斜下方 } ];
3. 混合攻击模式(比如后,兼具车和象的攻击)
直接把两种模式的对象放进数组就行,扩展性拉满:
// 后的攻击范围(直线+斜线) const queenPossibleCells = [ { type: "straight", directions: [[-1,0], [1,0], [0,-1], [0,1]], maxSteps: Infinity }, { type: "diagonal", directions: [[-1,-1], [-1,1], [1,-1], [1,1]], maxSteps: Infinity } ];
为什么这样格式化更实用?
- 可读性强:不用盯着一堆零散坐标猜逻辑,看
type和参数就知道棋子怎么攻击 - 维护方便:要修改攻击范围,直接改对应模式的参数就行,比如把兵的攻击步数改成2,只需要调整
positions数组 - 转换函数复用:可以写一个通用的转换函数,根据不同的
type自动处理攻击范围计算(后面会给例子)
二、适合2D坐标的优质数据结构(JS新手友好)
作为新手,优先选容易理解、操作简单的结构,下面三种最常用:
1. 二维数组(Matrix)
最直观的棋盘结构,直接对应行列坐标,比如board[row][col],每个元素存储单元格的状态(比如是否有棋子、棋子类型):
// 初始化一个8x8的棋盘 const board = Array(8).fill(null).map(() => Array(8).fill(null)); // 给(2,3)位置放一个车 board[2][3] = { type: "rook", color: "white" }; // 获取(2,3)的棋子 const piece = board[2][3];
优点:新手容易理解,坐标访问速度快,适合棋盘大小固定的情况。
2. 对象映射(Object Map)
用字符串格式的坐标作为键,存储有棋子的位置,适合稀疏棋盘(大部分格子是空的):
const board = {}; // 给(2,3)位置放车 board["2,3"] = { type: "rook", color: "white" }; // 获取(2,3)的棋子 const piece = board["2,3"]; // 遍历所有有棋子的位置 for (const coord in board) { const [x, y] = coord.split(",").map(Number); console.log(`位置(${x},${y})有棋子:`, board[coord]); }
优点:不用初始化整个棋盘,节省内存,适合大棋盘场景。
3. ES6 Map数据结构
和对象映射类似,但更灵活,支持迭代和复杂键(不过通常还是用字符串坐标):
const board = new Map(); // 给(2,3)位置放车 board.set("2,3", { type: "rook", color: "white" }); // 获取(2,3)的棋子 const piece = board.get("2,3"); // 遍历所有有棋子的位置 for (const [coord, piece] of board) { const [x, y] = coord.split(",").map(Number); console.log(`位置(${x},${y})有棋子:`, piece); }
优点:比普通对象更安全(不会有原型链污染问题),迭代更方便,适合需要频繁添加/删除棋子的场景。
三、通用转换函数示例(把possibleCells转成绝对攻击坐标)
基于上面格式化的possibleCells,写一个通用函数,输入当前坐标、possibleCells和棋盘大小,就能算出所有攻击位置:
function getAttackPositions(currentX, currentY, possibleCells, boardSize) { const attackPositions = []; const { width, height } = boardSize; for (const pattern of possibleCells) { if (pattern.type === "jump") { // 处理跳跃式攻击:直接计算每个相对坐标的绝对位置 for (const [dx, dy] of pattern.positions) { const newX = currentX + dx; const newY = currentY + dy; // 检查是否在棋盘范围内 if (newX >= 0 && newX < width && newY >= 0 && newY < height) { attackPositions.push([newX, newY]); } } } else if (pattern.type === "straight" || pattern.type === "diagonal") { // 处理直线/斜线攻击:沿方向一步步延伸,直到边界 for (const [dx, dy] of pattern.directions) { let step = 1; while (step <= pattern.maxSteps) { const newX = currentX + dx * step; const newY = currentY + dy * step; // 超出棋盘就停止当前方向 if (newX < 0 || newX >= width || newY < 0 || newY >= height) break; // 添加攻击位置 attackPositions.push([newX, newY]); // 如果需要考虑棋子阻挡(比如国际象棋规则),可以在这里加判断: // if (board[newY][newX] !== null) break; step++; } } } } return attackPositions; } // 用法示例:计算车在(3,3)位置的攻击范围(8x8棋盘) const attackPositions = getAttackPositions(3, 3, rookPossibleCells, { width: 8, height: 8 }); console.log(attackPositions); // 输出所有车能攻击到的坐标
这样一来,不管是什么棋子,只要按格式定义好possibleCells,就能用这个函数算出攻击范围,完全不用重复写逻辑~
内容的提问来源于stack exchange,提问作者Gallaxhar
相关产品推荐
相关产品推荐

