如何获取数组中的周边对象?及P5.js实现Connect4游戏四子连线获胜机制的问题排查
嘿,我来帮你搞定这两个技术问题!
1. 如何获取数组中的周边对象?
首先得明确,“周边对象”一般指二维数组中某个元素的8邻域元素(上下左右+四个对角线),当然你也可以根据需求只取4邻域(上下左右)。这里给你一套通用的实现思路:
- 先处理边界判断:避免访问数组越界,比如要获取
grid[y][x]的周边元素,得先确认y-1 >=0(上方存在)、y+1 < 总行数(下方存在)、x-1 >=0(左方存在)、x+1 < 总列数(右方存在),对角线则是这些条件的组合。 - 用方向数组简化代码:把所有可能的方向写成一个数组,遍历这个数组就能一次性收集所有有效周边元素,不用写一堆if判断。
举个实用的代码例子:
function getSurroundingElements(grid, targetY, targetX) { const totalRows = grid.length; const totalCols = grid[0].length; // 定义8个方向:[y偏移量, x偏移量] const directions = [[-1,-1], [-1,0], [-1,1], [0,-1], [0,1], [1,-1], [1,0], [1,1]]; const surroundingItems = []; for (const [dy, dx] of directions) { const newY = targetY + dy; const newX = targetX + dx; // 检查坐标是否在数组有效范围内 if (newY >= 0 && newY < totalRows && newX >= 0 && newX < totalCols) { surroundingItems.push(grid[newY][newX]); } } return surroundingItems; }
调用这个函数就能拿到目标位置所有有效的周边元素啦。如果只需要4邻域,删掉方向数组里的对角线方向就行。
2. Connect4游戏四子连线获胜判定问题
先分析你当前代码的几个核心问题:
- 重复计数逻辑混乱:你同时检查左右、上下的正反方向,导致同一条连线被多次累加计数,比如水平方向的棋子会被左右两边的计数重复加,导致数值不准。
- 状态未重置:
connectedBlocksX/Y这些计数变量没有在每次检查前重置,会一直累积之前的数值,后续判断完全失效。 - 递归落子的冗余检查:你在
drop的递归过程中多次调用checkWin,其实只需要在棋子最终落定的位置检查一次就够了。
改进后的实现方案
核心思路是:每次落子后,从落定的位置出发,沿着4组对立方向(水平、垂直、两个对角线)分别遍历,统计同色棋子的总长度,只要任意一组达到4就判定获胜。
步骤1:替换checkWin函数
把你原来的checkWin换成这个更清晰的版本:
function checkWin(x, y) { const currentColor = grid[y][x].color; if (currentColor === "gray") return; // 空位置不用检查 // 定义4组对立方向:每组包含两个相反的方向 const directionPairs = [ [[0, -1], [0, 1]], // 左 ↔ 右(水平) [[-1, 0], [1, 0]], // 上 ↔ 下(垂直) [[-1, -1], [1, 1]], // 左上 ↔ 右下 [[-1, 1], [1, -1]] // 右上 ↔ 左下 ]; for (const [dirA, dirB] of directionPairs) { let count = 1; // 初始计数为当前棋子本身 // 遍历第一个方向,统计同色棋子数 let nextY = y + dirA[0]; let nextX = x + dirA[1]; while (nextY >= 0 && nextY < rows && nextX >= 0 && nextX < cols && grid[nextY][nextX].color === currentColor) { count++; nextY += dirA[0]; nextX += dirA[1]; } // 遍历对立方向,继续统计同色棋子数 nextY = y + dirB[0]; nextX = x + dirB[1]; while (nextY >= 0 && nextY < rows && nextX >= 0 && nextX < cols && grid[nextY][nextX].color === currentColor) { count++; nextY += dirB[0]; nextX += dirB[1]; } // 只要任意一组方向的总长度≥4,就判定获胜 if (count >= 4) { winner = currentColor; return; // 找到获胜条件后直接返回,不用继续检查 } } }
步骤2:简化drop函数的落子逻辑
你原来的递归落子逻辑太复杂,改成直接找到当前列最底部的空位置,落子后只检查一次获胜:
drop(){ const colIndex = (this.x - 5) / 50; let targetRow = null; // 从底部往上找当前列第一个空位置 for(let y = rows - 1; y >= 0; y--){ if(!grid[y][colIndex].isFilled){ targetRow = y; break; } } if(targetRow === null) return; // 当前列已满,无法落子 // 设置落子的状态和颜色 grid[targetRow][colIndex].isFilled = true; grid[targetRow][colIndex].color = turn === 0 ? "red" : "yellow"; // 只在落定的位置检查一次获胜 checkWin(colIndex, targetRow); // 切换玩家回合 turn = turn === 0 ? 1 : 0; }
额外优化:简化网格初始化
你原来手动写6行grid[y] = [0,...]可以改成一行:
var grid = Array(rows).fill().map(() => Array(cols).fill(0));
这样修改后,获胜判定逻辑就准确啦,而且代码可读性和维护性都提升了不少。
内容的提问来源于stack exchange,提问作者Maddox Fox
相关产品推荐
相关产品推荐

