Creating intricate mazes using canvas drawing techniques

I recently developed a maze generator as a personal project utilizing a graph. While the generation logic works perfectly, I am facing challenges when it comes to rendering the maze. In my approach, each cell is represented by an array of 4 edges where the first edge corresponds to the top wall, the second one to the right wall, and so forth in a clockwise direction. If there is a value other than -1 in an edge, it indicates the presence of a wall; if the value is -1, that side should remain open.

To align with this logic, I constructed 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();
    });
  }
}

Initially, this renderer seems to work fine, except for the occurrence of "ghost walls" (light grey strokes) as displayed in this image https://i.stack.imgur.com/fL0AV.png

Upon inspecting the edges, I observed that, for instance, the cell at position [3][3] should only have the top and left walls due to its edges being [23, -1, -1, 32]. I suspect the issue lies in how I handle point movements, but I haven't been able to identify the exact problem.

For a minimal demonstration, you can refer to this example: https://stackblitz.com/edit/js-ys9a1j

In the provided example, although the graph isn't randomized, the result should ideally feature all blocks with only bottom and left walls ([-1,-1, 1, 1]).

Answer №1

This situation presents a logic challenge: the cells' edges are being defined independently, leading to potential conflicts.

For example, if cell B2 is marked as open on all four sides (-1,-1,-1,-1) while cell C2 is closed on all sides (1,1,1,1), the edge between these two cells will be closed based on C2's declaration, despite B2 indicating it should be open.

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

To address this issue, it's crucial to revisit the logic behind your approach. One solution could involve checking if a wall has already been declared before assigning its status, or better yet, storing the information only once.

I can propose two untested methods:

  • Utilizing two 2D arrays, one dedicated to column walls and the other to row walls.

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>

Answer №2

After a thorough review, I discovered the root cause of the problem when going over the code with a colleague. It turned out that case 3 in the render cell function was incorrectly drawing a line. We were able to rectify this issue promptly.

I want to extend my gratitude to Kaiido for pointing out how offsetting the coordinates by 0.5 resolved the problem with lighter grey strokes.

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using prevState in setState is not allowed by TypeScript

Currently, I am tackling the complexities of learning TypeScipt and have hit a roadblock where TS is preventing me from progressing further. To give some context, I have defined my interfaces as follows: export interface Test { id: number; date: Date; ...

Error encountered: Element cannot be clicked on at specified coordinates - Angular/Protractor

Recently, I have been testing out CRUD functionality in an Angular app using Protractor. One recurring issue I've encountered is with the create/edit buttons, which all open the same modal regardless of the page you're on. The frustrating part i ...

Tips on retrieving Bootstrap selectpicker value by using loops in Jquery

I am attempting to perform a basic validation using jQuery, where I need to iterate through all elements to check for the existence of values. The validation script is working well except for the jQuery selectpicker functionality. Here's what I have t ...

Oops! Looks like there's an issue with reading the property 'value' of undefined in React-typeahead

Having some issues with setting the state for user selection in a dropdown menu using the react-bootstrap-typeahead component. Any guidance or resources on this topic would be highly appreciated. Details can be found here. The function handleAddTask is su ...

I currently have a set of 6 popovers that all open simultaneously, but I am looking to make them open sequentially, one after

My component generates a div with 6 different experiences, each with its own popover. However, when I click on an experience to open its popover, all 6 popovers appear. How can I assign a unique ID to each popover? This is the code for my experience compo ...

What is the process of invoking a JavaScript function from Selenium?

How can I trigger a JavaScript function from Selenium WebDriver when using Firefox? Whenever I am logged into my website, I typically utilize this command in Firebug's Command Editor to launch a file upload application: infoPanel.applicationManager. ...

Oops! There seems to be a syntax error near "NOT" in TypeORM

I am currently developing an app using NestJs with a Postgres database and TypeOrm as the ORM. I have created my migration file, configured the package.json file, but when I try to run yarn typeorm migration:run, I encounter the following error: query fail ...

Angular FormData fails to append and upload files

I am attempting to use FormData in order to upload a file through an HTTP Request. Here is the HTML code: <ng-template #displayApp> <div class="display flex justify-content-center"> <div > <p-fileUploa ...

Navigating through cors in next.js

Currently, I have set up my front end using Netlify and my backend using Heroku with Next.js For the fetch request on the front end, here is an example: fetch(`https://backendname.herokuapp.com/data`, { method: 'POST', headers: { & ...

Clickable link in popup window using fancybox

When I try to use fancybox to open an iframe and scroll to an anchor tag, it works perfectly in IE but not consistently in other browsers. It stops at a different place than the anchor. I suspect the issue may be related to JavaScript based on information ...

Even though my form allows submission of invalid data, my validation process remains effective and accurate

Here is the code I have written: <!doctype html> <html lang="en"> <head> <title>Testing form input</title> <style type="text/css></style> <script type="text/javascript" src="validation.js"></script> &l ...

I am struggling to grasp the flow of this code execution

Hi there, I just started my journey in JavaScript learning about two weeks ago. I would really appreciate it if someone could walk me through the execution steps of the code provided below. function sort(nums) { function minIndex(left, right) { ...

The concept of RxJS's catchError function involves the return of a versatile

It's interesting that catchError is returning an Observable union type as Observable<{} | Page} instead of just Observable<Page>. The error message from the compiler reads: Type 'Observable<{} | Page>' is not assignable to t ...

Preserving the button's state when clicked

Here is my code snippet: <blink> const [thisButtomSelected, setThisButtomSelected] = useState(false); var thisButton = []; const onAttributeClick = (e) => { thisButton[e.currentTarget.value] = { thisID: e.currentTarget.id, thisName: e. ...

JavaScript code to retrieve an image from an <img> tag's source URL that only allows a single request and is tainted due to cross-origin restrictions

I have an image displayed in the HTML DOM. https://i.stack.imgur.com/oRgvF.png This particular image (the one with a green border) is contained within an img tag and has a URL as its source. I attempted to fetch this image using the fetch method, but enc ...

Encountering problems with TypeScript in a Node application when using the package manager

I am facing an issue while trying to package my node app into an exe using "pkg". I work with TypeScript, and when I attempt to run pkg index.ts --debug, an error pops up. Node.js v18.5.0 Warning Failed to make bytecode node18-x64 for file /snapshot/ ...

The combination of Array.pop and Array.indexOf is not functioning as expected

I'm having an issue with using Array.pop(Array.indexOf(value)). It seems to always delete the last element in the index, even if the value of that index is not what I intended. Can someone provide some guidance on how to resolve this? CheckBoxHandle ...

Transforming jQuery into vanilla JavaScript in order to create a customized dropdown select functionality

I am struggling with converting jQuery code to pure JavaScript for a custom select element. https://codepen.io/PeterGeller/pen/wksIF After referencing the CodePen example, I attempted to implement it with 3 select elements. const select = document.get ...

Implementing jQuery form validator post anti-SPAM verification?

I am facing what seems like a straightforward JavaScript issue, but my knowledge in this area is still limited. Following a successful implementation of basic anti-SPAM feature that asks the user to solve a simple math problem, how can I integrate jQuery& ...

How to use the sha512 hash function in Node.js for Angular2 and Ionic2 applications

I'm attempting to generate a SHA512 Hash in Angular2 (Ionic2) that matches the PHP function hash('sha512'). After trying out different modules like crypto-js, crypto, and js-sha512, I keep getting a different Hash compared to PHP. I even a ...