用 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;
}
作为一个附带项目,我使用图表创建了一个迷宫生成器。这一代的逻辑工作正常,但我在渲染它时遇到了一些问题。在我的逻辑中,每个单元格都表示为 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 每个节点如果打开则指向另一个节点,或者如果应该有墙则指向 经过测试发现图思路其实也需要有lookup的方法,不然只能往两个方向走...null
。
- 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;
}