canvas坐标对齐需用margin+col×gridSize+gridSize/2;落子校验行列有效性;localStorage存取须JSON序列化并try-catch;悔棋用history.push避免断链。

canvas 绘制棋盘和落子为什么总对不齐
坐标算错是新手最常遇到的问题。canvas 的 fillRect 和 arc 默认以左上角为原点,而五子棋逻辑里习惯用“格子中心”定位。直接用行列号 × 格宽,会把棋子画在格子左上角,看起来偏移明显。
- 棋盘格宽建议设为固定值(如
40),起始偏移留出边距(如margin = 40),真实坐标 =margin + col * gridSize + gridSize / 2 - 绘制黑子白子时,别用
fillStyle = "#000"硬编码,改用变量currentPlayer === 1 ? "#000" : "#fff",方便后续扩展 - Canvas 渲染前务必调用
clearRect(0, 0, canvas.width, canvas.height),否则旧棋子残留,尤其在悔棋或重绘时容易误判视觉状态
判断五连珠用 for 循环还是方向数组
硬写八个方向的嵌套 for 容易漏边界、难维护;但全靠方向数组又可能绕晕自己。实际推荐折中:用方向数组存增量,外层循环遍历四个主方向(横、竖、正斜、反斜),每个方向内用 while 向两端延伸计数。
- 方向数组定义成:
const dirs = [[0, 1], [1, 0], [1, 1], [1, -1]],避免重复检查对称方向 - 每次落子后,只检查以该点为中心的四个方向,不用扫全盘——性能差一个数量级
- 注意数组越界:
row + dr * i和col + dc * i每次都要校验是否在[0, 14]范围内(15×15棋盘) - 别用
==判断棋子颜色,用board[row][col] === currentPlayer,防止0(空位)被误认为黑子(若用1/2表示)
鼠标点击坐标转棋盘行列总偏差一格
根本原因是没考虑 canvas 的 CSS 显示尺寸和内部绘图尺寸不一致。比如 HTML 中设了 style="width:600px",但没设置 canvas.width,浏览器会拉伸 canvas,导致 getBoundingClientRect() 返回的位置除以 clientWidth 后映射到错误格子。
- 必须显式设置 canvas 的
width和height属性(不是 style):如canvas.width = 600; canvas.height = 600; - 获取点击位置时,用
rect = canvas.getBoundingClientRect(),再算相对偏移:const x = e.clientX - rect.left,然后除以单格像素宽 - 计算行列用
Math.round((x - margin) / gridSize),而不是Math.floor——否则靠近格线时总归到左边/上边格子 - 点击后先验证行列是否有效:
if (row 14 || col 14 || board[row][col] !== 0) return;
本地存储悔棋记录为什么 reload 后消失
localStorage 存的是字符串,直接塞数组或对象会变 "[object Object]",取出来就废了。而且五子棋的悔棋不是只存最后一步,得存完整棋局快照,否则无法回退多步。
立即学习“前端免费学习笔记(深入)”;
- 存的时候用
JSON.stringify({ board, history }),history 推荐存每步的{ row, col, player }对象数组 - 读的时候加
try...catch,因为用户可能手动删过 localStorage 或数据损坏:try { data = JSON.parse(stored) } catch(e) { data = null; } - 别在每次落子后都覆盖整个 state,用
history.push(step)再存,否则撤销后无法继续下新棋(历史链断了) - localStorage 有大小限制(通常 5MB),但五子棋 15×15 最多 225 步,JSON 化后远小于 1KB,放心用
事情说清了就结束。真正卡住的往往不是算法,而是 canvas 坐标映射、localStorage 序列化、以及点击事件里那几行没校验的边界条件。











