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

Is it possible for moduleNameMapper to exclude imports made by a module located within node_modules directory?

I am trying to figure out how to make my moduleNameMapper ignore imports from the node_modules directory. The issue is that one of the dependencies, @sendgrid/mail, uses imports starting with ./src/ which causes problems when importing into Jest. My curre ...

Can someone assist me in creating a clickable link that opens a menu in HTML when clicked?

I have been attempting for the past few days to open the megamenu by clicking on a link, but despite my efforts, I have not been successful. After reviewing some code, I discovered a clue in the CSS. It seems that setting the visibility value to visible wi ...

What is the best way to rerun a script without having to manually refresh the entire webpage?

If you visit greenb.byethost12.com, you can check out the progress I've made so far. Currently, when you click on the correct answer, it triggers document.location.reload(index.php), causing the entire page to refresh. What I aim for is to have only ...

Is it possible to nullify an object and utilize nullish coalescing for handling potentially undefined constants?

In my development work with React, I often utilize a props object structured like this: const props: { id: number, name?: string} = { id: 1 }; // 'name' property not defined const { id, name } = props; // the 'name' constant is now fore ...

javascript loop that runs on every second element only

After completing an ajax query, the following JavaScript code is executed. All of my images are named "pic". <script type="text/javascript> function done() { var e = document.getElementsByName("pic"); alert(e.length); for (var i = 0; ...

Jquery failing to append existing values into the DOM

I'm currently attempting to enhance an existing jQuery table structure with the following code snippet: function GetViewData(data) { $.ajax({ type: "GET", url: "/Services/Configuration/ViewServices. ...

Change the spread operator in JavaScript to TypeScript functions

I'm struggling to convert a piece of code from Javascript to Typescript. The main issue lies in converting the spread operator. function calculateCombinations(first, next, ...rest) { if (rest.length) { next = calculateCombinations(next, ...res ...

Exploring the best way to access ViewContainerRef: ViewChild vs Directive

While researching, I came across a recommendation in the Angular Docs that suggests using a directive to access the ViewContainerRef for creating dynamic components. Here is an example of such a directive: import { Directive, ViewContainerRef } from &apos ...

Every time I try to access Heroku, I encounter an issue with Strapi and the H10 error code

Upon opening Heroku and running the command "heroku logs --tail", my app encountered a crash and I can't seem to locate my Strapi application in Heroku. 2020-05-04T19:05:38.602418+00:00 heroku[router]: at=error code=H10 desc="App crashed" method=GE ...

What is the reason for Next.js and Gatsby.js necessitating the installation of Node.js, while React.js does not have that requirement?

Have you ever thought about the connection between Next.js and Gatsby.js, both being built on React.js? Could it be that the development environments are Node applications themselves? Or maybe there's something else I'm not seeing? While I have ...

Use ajax, javascript, and php to insert information into a database

Issue at Hand: I am trying to extract the subject name from the admin and store it in the database. Following that, I aim to display the query result on the same webpage without refreshing it using Ajax. However, the current code is yielding incorrect outp ...

Typescript subtraction operation may result in Undefined

I am a beginner in the world of TypeScript and I'm currently struggling with running this code snippet: class TestClass { public t: number = 10; constructor() { this.t = this.t - 1; console.log(this.t); } } var obj = new TestClass(); ...

AngularJS is unable to locate the $locationProvider module

I'm encountering an error message every time I attempt to utilize $locationProvider. Is there a specific way I should be importing the module? Error: $injector:unpr Unknown Provider Unknown provider: $locationProviderProvider <- $locationProvider ...

The inner workings of v8's fast object storage method

After exploring the answer to whether v8 rehashes when an object grows, I am intrigued by how v8 manages to store "fast" objects. According to the response: Fast mode for property access is significantly faster, but it requires knowledge of the object&ap ...

Verify whether the input field contains a value in order to change certain classes

My meteor-app includes an input field that dynamically changes position based on whether it contains content or not. When a user begins typing, with at least one character, the input field moves to the top of the page. In my current approach, I am using a ...

Issues with clicking on the ion-tab icon in AngularJS are hindering the functionality

I'm currently facing a challenge with an ion-tab icon in my AngularJS project. I've been attempting to add a click action using the code below, but unfortunately, nothing is displaying as expected. HTML <ion-tab title="Share" icon="icon ion- ...

Renaming errors within a project with a complex nested structure using npm

I am encountering an issue in my NodeJS project which consists of nested subprojects with their own package.json files. Whenever I make changes to dependencies in the subprojects, I encounter errors similar to the one below: npm ERR! code ENOENT npm ERR! ...

I am looking for an image search API that supports JSONP so that users can easily search for images on my website

I am currently in the process of creating a blog platform. My goal is to allow users to input keywords on my site and search for images directly within the website. This way, I can easily retrieve the URL of the desired image. ...

"Typescript: Unraveling the Depths of Nested

Having trouble looping through nested arrays in a function that returns a statement. selectInputFilter(enteredText, filter) { if (this.searchType === 3) { return (enteredText['actors'][0]['surname'].toLocaleLowerCase().ind ...

Cookie set upon clicking a particular element exclusively

Looking to toggle a class when clicking an element and preserve it post page load using jQuery or JavaScript. Here's what I currently have, successfully adding the class upon click but not retaining it after reload: jQuery("a.action.towishlist").clic ...