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

如何使用Vanilla JavaScript获取二维数组中的相邻单元格?

解决网格单元格获取相邻单元格的问题

你的代码核心问题在于Cell对象只保存了像素坐标,没有记录它在网格中的**行(row)和列(col)**位置,导致无法通过网格的二维数组索引定位相邻单元格。下面一步步修正并实现邻居获取逻辑:

步骤1:修改Cell构造函数,添加行列属性

给每个Cell绑定网格内的行列索引,同时直接通过行列计算像素坐标,简化后续代码:

function Cell(row, col) {
  // 网格内的行列位置(关键!)
  this.row = row;
  this.col = col;
  // 像素坐标直接通过行列计算
  this.x = col * cellSize;
  this.y = row * cellSize;
  this.f = 0;
  this.g = 0;
  this.h = 0;
  // 记录父节点,用于后续路径回溯
  this.parent = null;

  this.show = function(color = 'gray') {
    ctx.fillStyle = color;
    ctx.fillRect(this.x, this.y, cellSize, cellSize);
    ctx.strokeStyle = 'white';
    ctx.strokeRect(this.x, this.y, cellSize, cellSize);
  }
}

步骤2:实现获取相邻单元格的方法

给Cell添加原型方法getNeighbors,遍历上下左右四个方向(可扩展对角线),同时做边界检查避免超出网格范围:

Cell.prototype.getNeighbors = function() {
  const neighbors = [];
  // 上下左右四个方向的偏移量
  const directions = [
    [-1, 0], // 上
    [1, 0],  // 下
    [0, -1], // 左
    [0, 1]   // 右
    // 需要对角线邻居的话,取消注释下面的行
    // [-1, -1], [-1, 1], [1, -1], [1, 1]
  ];

  for (const [rowOffset, colOffset] of directions) {
    const newRow = this.row + rowOffset;
    const newCol = this.col + colOffset;
    // 检查是否在网格有效范围内
    if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) {
      neighbors.push(grid[newRow][newCol]);
    }
  }
  return neighbors;
}

步骤3:修正createGrid函数

创建Cell时直接传入行列索引,简化像素坐标的赋值逻辑:

function createGrid() {
  for (let i = 0; i < rows; i++) {
    grid[i] = new Array(cols);
  }
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      grid[i][j] = new Cell(i, j);
      grid[i][j].show();
    }
  }
}

步骤4:在update中结合A*逻辑使用邻居方法

完善update函数,加入A*算法的核心流程,其中就会用到获取邻居的逻辑:

// 把interval移到全局,方便后续停止动画
let interval;

function update() {
  // open列表为空,说明无路径
  if (open.length === 0) {
    console.log('No path found');
    clearInterval(interval);
    return;
  }

  // 找到open列表中f值最小的节点
  let currentIndex = 0;
  for (let i = 0; i < open.length; i++) {
    if (open[i].f < open[currentIndex].f) {
      currentIndex = i;
    }
  }
  const current = open[currentIndex];

  // 到达终点,回溯路径并停止
  if (current === end) {
    console.log('Path found!');
    let path = [];
    let temp = current;
    while (temp.parent) {
      path.push(temp);
      temp = temp.parent;
    }
    path.push(start);
    // 绘制路径
    path.forEach(cell => cell.show('blue'));
    clearInterval(interval);
    return;
  }

  // 将当前节点从open移到closed
  removeArray(open, current);
  closed.push(current);

  // 遍历当前节点的所有邻居
  const neighbors = current.getNeighbors();
  for (const neighbor of neighbors) {
    // 邻居在closed列表中,跳过
    if (closed.includes(neighbor)) continue;

    const tempG = current.g + 1; // 每步代价设为1
    let newPath = false;

    // 邻居不在open列表,添加进去
    if (!open.includes(neighbor)) {
      open.push(neighbor);
      newPath = true;
    } else if (tempG < neighbor.g) {
      // 当前路径到邻居的代价更小,更新路径
      newPath = true;
    }

    if (newPath) {
      neighbor.parent = current;
      neighbor.g = tempG;
      // 曼哈顿距离计算h值(适合网格场景)
      neighbor.h = Math.abs(neighbor.col - end.col) + Math.abs(neighbor.row - end.row);
      neighbor.f = neighbor.g + neighbor.h;
    }
  }

  // 重绘网格避免颜色重叠
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      grid[i][j].show();
    }
  }
  // 重新标记起点终点
  start.show('yellow');
  end.show('purple');
  
  // 绘制open和closed列表
  for (let i = 0; i < open.length; i++) {
    open[i].show('green');
  }
  for (let i = 0; i < closed.length; i++) {
    closed[i].show('red');
  }
}

完整修改后的代码

//GLOBAL VARIABLES 
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const wh = 600;
const cellSize = 30;
const rows = 20;
const cols = 20;
const grid = new Array(rows);
const open = [];
const closed = [];
let start;
let end;
let interval;

//FUNCTIONS 
(function() {
  setup();
})();

function Cell(row, col) {
  this.row = row;
  this.col = col;
  this.x = col * cellSize;
  this.y = row * cellSize;
  this.f = 0;
  this.g = 0;
  this.h = 0;
  this.parent = null;

  this.show = function(color = 'gray') {
    ctx.fillStyle = color;
    ctx.fillRect(this.x, this.y, cellSize, cellSize);
    ctx.strokeStyle = 'white';
    ctx.strokeRect(this.x, this.y, cellSize, cellSize);
  }
}

Cell.prototype.getNeighbors = function() {
  const neighbors = [];
  const directions = [
    [-1, 0],
    [1, 0],
    [0, -1],
    [0, 1]
    // [-1, -1], [-1, 1], [1, -1], [1, 1]
  ];

  for (const [rowOffset, colOffset] of directions) {
    const newRow = this.row + rowOffset;
    const newCol = this.col + colOffset;
    if (newRow >= 0 && newRow < rows && newCol >=0 && newCol < cols) {
      neighbors.push(grid[newRow][newCol]);
    }
  }
  return neighbors;
}

function setup() {
  interval = setInterval(update, 120);
  canvas.setAttribute('width', wh);
  canvas.setAttribute('height', wh);
  document.body.insertBefore(canvas, document.body.childNodes[0]);
  createGrid();
  setStartEnd();
}

function createGrid() {
  for (let i = 0; i < rows; i++) {
    grid[i] = new Array(cols);
  }
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      grid[i][j] = new Cell(i, j);
      grid[i][j].show();
    }
  }
}

function setStartEnd() {
  start = grid[0][0];
  end = grid[cols - 1][rows - 1];
  open.push(start);
  start.show('yellow');
  end.show('purple');
}

function removeArray(arr, e) {
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] === e) {
      arr.splice(i, 1);
      break;
    }
  }
}

function update() {
  if (open.length === 0) {
    console.log('No path found');
    clearInterval(interval);
    return;
  }

  let currentIndex = 0;
  for (let i = 0; i < open.length; i++) {
    if (open[i].f < open[currentIndex].f) {
      currentIndex = i;
    }
  }
  const current = open[currentIndex];

  if (current === end) {
    console.log('Path found!');
    let path = [];
    let temp = current;
    while (temp.parent) {
      path.push(temp);
      temp = temp.parent;
    }
    path.push(start);
    path.forEach(cell => cell.show('blue'));
    clearInterval(interval);
    return;
  }

  removeArray(open, current);
  closed.push(current);

  const neighbors = current.getNeighbors();
  for (const neighbor of neighbors) {
    if (closed.includes(neighbor)) continue;

    const tempG = current.g + 1;
    let newPath = false;

    if (!open.includes(neighbor)) {
      open.push(neighbor);
      newPath = true;
    } else if (tempG < neighbor.g) {
      newPath = true;
    }

    if (newPath) {
      neighbor.parent = current;
      neighbor.g = tempG;
      neighbor.h = Math.abs(neighbor.col - end.col) + Math.abs(neighbor.row - end.row);
      neighbor.f = neighbor.g + neighbor.h;
    }
  }

  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      grid[i][j].show();
    }
  }
  start.show('yellow');
  end.show('purple');
  
  for (let i = 0; i < open.length; i++) {
    open[i].show('green');
  }
  for (let i = 0; i < closed.length; i++) {
    closed[i].show('red');
  }
}

关键说明

  • 给Cell绑定行列索引是获取邻居的核心前提,通过二维数组索引就能快速定位相邻单元格
  • getNeighbors方法通过方向偏移量+边界检查,确保只获取有效邻居
  • 完善后的update函数实现了A*算法的基础流程,你可以继续扩展障碍物、对角线移动等功能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 18:48:38