用 canvas 画迷宫

Maze drawing with canvas

作为一个附带项目,我使用图表创建了一个迷宫生成器。这一代的逻辑工作正常,但我在渲染它时遇到了一些问题。在我的逻辑中,每个单元格都表示为 4 条边的数组,其中第一条边是顶墙,第二条边是右墙,依顺时针方向依次类推。如果有边(与-1 不同的任何东西),则应该有一堵墙,如果有-1,则该侧应该是开放的。

为了尝试遵循这个逻辑,我创建了以下渲染图 class

export class Renderer {
  private _context: CanvasRenderingContext2D;
  private _y: number;
  private _x: number;

  constructor(canvas: HTMLCanvasElement, xSize: number, ySize: number) {
    this._context = canvas.getContext('2d') as CanvasRenderingContext2D;
    this._x = xSize;
    this._y = ySize
  }


  public render(graphAdjacencyList: Array<Vertex>): void {
    for (let i = 0; i < this._x; i++) {
      for (let j = 0; j < this._y; j++) {
        const codedIndex: number = parseInt(i.toString() + j.toString());
        this._renderCell({ x: 20 * j, y: 20 * i }, graphAdjacencyList[codedIndex].getEdges(), 20)
      }
    }
  }


  private _renderCell(coords: Record<'x' | 'y', number>, cellWalls: Array<number>, size: number) {
    cellWalls.forEach((w: number, index: number) => {
      this._context.beginPath();
      switch (index) {
        case 0:
          this._context.moveTo(coords.x, coords.y);
          (w !== -1) ? this._context.lineTo(coords.x + size, coords.y) : null;
          break;
        case 1:
          this._context.moveTo(coords.x + size, coords.y);
          (w !== -1) ? this._context.lineTo(coords.x + size, coords.y + size) : null;
          break;
        case 2:
          this._context.moveTo(coords.x + size, coords.y + size);
          (w !== -1) ? this._context.lineTo(coords.x, coords.y + size) : null;
          break;
        case 3:
          this._context.moveTo(coords.x, coords.y + size);
          (w !== -1) ? this._context.lineTo(coords.x, coords.y - size) : this._context.moveTo(coords.x, coords.y - size);
          break;
      }

      this._context.closePath();
      this._context.stroke();
    });
  }
}

乍一看似乎工作正常,除了它渲染 "ghost walls"(浅灰色笔触)就像这张图片

如果我看一下边缘,我可以看到例如单元格 [3][3] 应该只有顶部和左侧的墙,因为它的边缘是 [23, -1, -1, 32]。我确信错误在于我移动点的方式,但我不能完全确定问题。

最小示例:https://stackblitz.com/edit/js-ys9a1j

在最小的例子中,图形不是随机的,但设计的结果应该包含在所有块中,只有底部和左边的墙 ([-1,-1, 1, 1])

你这里有一个逻辑问题:你在独立定义每个单元格的四个边。

例如,如果单元格 B2 为 -1,-1,-1,-1(全开),C2 为 1,1,1,1(全闭),则这两个单元格之间的边将由 C2 声明关闭,即使 B2 声明说它应该打开。

 _A_|_B_|_C_|_D_|
|        
1        
|    ooo|‾‾‾|
2    o o|   |                – -> closed (wall)
|    ooo|___|                o -> open (no wall)

为避免这种情况,您需要重新考虑您的逻辑,以便检查之前是否未声明过该墙,或者甚至更好,以便您只存储一次。

我能想到的两种方法,虽然未经测试:

- 生成图表。从迷宫的入口处,您生成一个节点,该节点将具有四个属性:N、S、W、E 每个节点如果打开则指向另一个节点,或者如果应该有墙则指向 null 经过测试发现图思路其实也需要有lookup的方法,不然只能往两个方向走...

  • 2 个二维数组,一个仅用于列上的墙,一个仅用于行上的墙。

const cellWidth = 20;
const maze = generateMaze(12, 12);
const ctx = canvas.getContext('2d');
ctx.translate(cellWidth + 0.5, cellWidth + 0.5);
ctx.beginPath();
drawCols(maze.cols);
drawRows(maze.rows);
ctx.stroke();

function generateMaze(width, height) {
  const rows = generateCells(width, height);
  const cols = generateCells(height, width);
  return { cols, rows};

  function generateCells(a, b) {
    return Array.from({ length: a })
      .map(_ => Array.from({ length: b })
        .map(_ => Math.random() < 0.5)
      );
  }

}

function drawCols(list) {
  list.forEach((arr, x) => {
    arr.forEach((bool, y) => {
      if (bool) {
        ctx.moveTo(x * cellWidth, y * cellWidth);
        ctx.lineTo(x * cellWidth, y * cellWidth + cellWidth);
      }
    });
  });
}

function drawRows(list) {
  list.forEach((arr, y) => {
    arr.forEach((bool, x) => {
      if (bool) {
        ctx.moveTo(x * cellWidth, y * cellWidth);
        ctx.lineTo(x * cellWidth + cellWidth, y * cellWidth);
      }
    });
  });
}
<canvas id="canvas" width="300" height="300"></canvas>

我发现了问题,与朋友一起检查代码时我们注意到渲染单元格的案例 3 绘制了错误的线。代码就是这样修复的。

多亏了 Kaiido,我什至解决了浅灰色笔画的问题,他建议我将坐标偏移 0.5。

  case 3:
          this._adjustedMoveTo(coords.x, coords.y + size);
          (w !== -1) ? this._adjustedLineTo(coords.x, coords.y) : null
          break;
      }