简体   繁体   中英

Maze drawing with canvas

As a side project I've created a maze generator using a graph. The logic for the generation works fine but I'm encountering some issues with rendering it. In my logic every cell is represented as an array of 4 edges where the first edge is the top wall, the second one is the right wall and so on in a clockwise direction. If there is an edge(anything different from -1) there should be a wall, if there is -1 that side should be open.

For attempting to follow this logic I've created the following Render 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();
    });
  }
}

which on a first glance seem's to work fine, except for the fact that it renders "ghost walls"(ligh grey strokes) like in this image 5X5迷宫

If i glance at the edges i can see that for example the cell [3][3] is supposed to have only the top and left wall because it's edges are [23, -1, -1, 32] . I'm convinced that the error is in how i move the point's but I cannot quite pin down the issue.

minimal example: https://stackblitz.com/edit/js-ys9a1j

in the minimal example the graph is not randomized, but is designed in a way that the result should consist into all blocks to have only the bottom and left wall ([-1,-1, 1, 1])

You have a logic problem here: you are defining the four edges of every cells independently.

If for instance we have the cell B2 being -1,-1,-1,-1 (all open) and C2 being 1,1,1,1 (all closed), the edge between these two cells will be closed by C2 declaration, even though B2 declarations says it should be open.

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

To avoid that, you need to rethink your logic so that you either check if said wall wasn't declared before, or even better, so that you store it only once.

Two ways I can think of, though untested:

- Generate a graph. From the entrance of your maze, you generate a node, which will have four properties: N,S,W,E each pointing to an other node if open, or to null if there should be a wall. After testing, I found that the graph idea actually needs to also have a lookup method, or it can only go in two directions...

  • 2 2D arrays, one only for the walls on the columns, one only for the ones on the rows.

 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> 

I've found the issue, reviewing the code with a friend we noticed that the case 3 of the render cell was drawing the wrong line. This is how the code has been fixed.

Thanks to Kaiido, I resolved even the issue with the lighter grey strokes with his advice to offset by 0.5 the coordinates.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM