Discovering the most efficient route between two locations within a grid of values

I'm currently working on a game where I need to find the shortest route between two points.

https://i.sstatic.net/jBnEd.png

In my map, I have a 2D array called matrix: Node[][],

class Node{
   index: {
     x: number,
     y: number
   },
   isAvailable: boolean
}

The goal is to calculate the shortest path taking node availability into consideration.

For example, nodes that are trees are marked as unavailable node.isAvailable = false

However, I've hit a roadblock when trying to implement the algorithm for this matrix.

I attempted to use Dijkstra's algorithm from here, but struggled with applying it. Here's what I tried:

const graph = new Dijkstra();

//convert the matrix (2d array) to graph
matrix.map((row) => {
  row.map((node: Node) => {
    let x = node.index.x;
    let y = node.index.y;
    graph.addVertex(x + ":" + y, {x: x, y: y});

  });
});

console.log(graph.shortestPath('0:0', '5:5'));
//the output was ['0:0'] (definitely not the answer)

Can anyone provide guidance on how to properly apply the algorithm to this matrix?

By the way, here's my full code

Answer №1

I recently completed the implementation of the A* algorithm

https://i.sstatic.net/PNwre.gif

export class PathFinder {

  grid: Tile[][];
  gridHeight: number;
  gridWidth: number;
  startTile: Tile;
  endTile: Tile;

  /** Array containing checked tiles. */
  closedList: List<Tile> = new List<Tile>();
  openList: List<Tile> = new List<Tile>();

  constructor(grid: Tile[][], gridHeight: number, gridWidth: number) {

    this.grid = grid;
    this.gridHeight = gridHeight;
    this.gridWidth = gridWidth;
  }

  searchPath(start: Tile, end: Tile): Tile[] {
    this.startTile = start;
    this.endTile = end;

    /** Validate the path */
    if (!start.walkable) {
      console.log('The starting tile is not walkable, please choose a different tile than', start.index);
      return [];
    }
    if (!end.walkable) {
      console.log('The ending tile is not walkable, please choose a different tile than', end.index);
      return [];
    }
    /** Start executing the A* Algorithm */

    /** Add the starting tile to the openList */
    this.openList.push(start);
    let currentTile;

    /** Loop until the openList is empty */
    while (this.openList.length) {
      //current node = node for open list with the lowest cost.
      currentTile = this.getTileWithLowestTotal(this.openList);

      //if the currentTile is the endTile, then stop searching
      if(JSON.stringify(currentTile.index) === JSON.stringify(end.index)){

        this.startTile.setBackgroundColor("rgba(255, 45, 45, .8)");
        this.endTile.setBackgroundColor("rgba(255, 45, 45, .8)");
        return this.shortestPath();
      }
      else {
        //move the current tile to the closed list and remove it from the open list.
        this.openList.remove(currentTile);
        this.closedList.push(currentTile);

        //Get all adjacent Tiles
        let adjacentTiles = this.getAdjacentTiles(currentTile);

        for (let adjacentTile of adjacentTiles) {
          //Check if tile is not in the open list
          if (!this.openList.contains(adjacentTile)) {
            //Check if tile is not in the closed list
            if (!this.closedList.contains(adjacentTile)) {
              //Add it to the open list and calculate cost
              this.openList.push(adjacentTile);

              //calculate the cost
              adjacentTile.cost = currentTile.cost + 1;

              //calculate the manhattan distance
              adjacentTile.heuristic = this.manhattanDistance(adjacentTile);

              // calculate the total amount
              adjacentTile.total = adjacentTile.cost + adjacentTile.heuristic;

              currentTile.setBackgroundColor('rgba(0, 181, 93, 0.8)');
            }
          }
        }
      }
    }
  }

  getTileWithLowestTotal(openList: Tile[]): Tile {
    let tileWithLowestTotal = new Tile();
    let lowestTotal: number = 999999999;
    /** Search open tiles and get the tile with the lowest total cost */
    for (let openTile of openList) {
      if (openTile.total <= lowestTotal) {
        //clone lowestTotal
        lowestTotal = openTile.total;
        tileWithLowestTotal = openTile;
      }
    }
    return tileWithLowestTotal;
  }

  getAdjacentTiles(current: Tile): Tile[] {
    let adjacentTiles: Tile[] = [];
    let adjacentTile: Tile;

    //Tile to left
    if (current.index.x - 1 >= 0) {
      adjacentTile = this.grid[current.index.x - 1][current.index.y];
      if (adjacentTile && adjacentTile.walkable) {
        adjacentTiles.push(adjacentTile);
      }
    }

    //Tile to right
    if (current.index.x + 1 < this.gridWidth) {
      adjacentTile = this.grid[current.index.x + 1][current.index.y];
      if (adjacentTile && adjacentTile.walkable) {
        adjacentTiles.push(adjacentTile);
      }
    }

    //Tile below
    if (current.index.y + 1 < this.gridHeight) {
      adjacentTile = this.grid[current.index.x][current.index.y + 1];
      if (adjacentTile && adjacentTile.walkable) {
        adjacentTiles.push(adjacentTile);
      }
    }

    //Tile above
    if (current.index.y - 1 >= 0) {
      adjacentTile = this.grid[current.index.x][current.index.y - 1];
      if (adjacentTile && adjacentTile.walkable) {
        adjacentTiles.push(adjacentTile);
      }
    }
    /** TODO: Implement diagonal movements */
    return adjacentTiles;
  }

  /** Calculate the manhattan distance */
  manhattanDistance(adjacentTile: Tile): number {
    return Math.abs((this.endTile.index.x - adjacentTile.index.x) +
      (this.endTile.index.y - adjacentTile.index.y));
  }

  shortestPath() {
    let startFound: boolean = false;
    let currentTile = this.endTile;
    let pathTiles = [];

    //includes the end tile in the path
    pathTiles.push(this.endTile);
    this.endTile.ball = true;

    while (!startFound) {
      let adjacentTiles = this.getAdjacentTiles(currentTile);

      //check to see what newest current tile.
      for (let adjacentTile of adjacentTiles) {
        //check if it is the start tile
        if (JSON.stringify(adjacentTile.index) === JSON.stringify(this.startTile.index)){
          return pathTiles;
        }

        //it has to be inside the closedList or openList
        if (this.closedList.contains(adjacentTile) || this.openList.contains(adjacentTile)) {
          if (adjacentTile.cost <= currentTile.cost && adjacentTile.cost > 0) {
            //change the current tile.
            currentTile = adjacentTile;
            //Add this adjacentTile to the path list
            pathTiles.push(adjacentTile);
            //highlight way with yellow balls
            adjacentTile.ball = true;
            break;
          }
        }
      }
    }
  }
}

Answer №2

My approach can be likened to spilling paint on the target:

First, I mark the initial square with 0 and then begin traversing the neighboring squares, marking them with 1 to indicate their distance from the target. This process continues as I move on to the neighbors of neighbors, creating a path for my character to follow. All that's left is for the character to navigate to the squares with the lowest values.

Things get more interesting when multiple characters are involved, maneuvering around each other while trying to reach their destinations.

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

Combining Vue-Test-Utils with TypeScript typings for wrapper.vm

So, I ran into an interesting situation. Has anyone ever worked with typescript + vue-test-utils and attempted to change a value for testing purposes like this: wrapper.vm.aCoolRefValueToManipulate = 'something much cooler'? I gave it a shot, a ...

The argument provided is of type 'string | null' which cannot be assigned to a parameter expecting only 'string' type. The value of 'null' is not compatible with the type 'string' in Angular 12

When trying to parse the stored session data using JSON.parse(sessionStorage.getItem('owner')), we may encounter an error stating: Argument of type 'string | null' is not assignable to parameter of type 'string'. This is becau ...

A step-by-step guide on updating a deprecated typings package manually

Currently, I am developing a NodeJS application using TypeScript and incorporating numerous Node packages. However, not all of these packages come with TypeScript definitions, so Typings is utilized to obtain separate definition files. During the deployme ...

A guide to troubleshooting and resolving the elusive 500 Server Error in Next JS API

Currently, I am in the process of developing a chat bot using the innovative OPEN AI GPT 4 Model within NextJS. However, upon sending a POST request to http://localhost:3001/api/generate, I am faced with a Response displaying a status code of 500 along wit ...

Firebase Functions Project encountering a "Cannot find module" error in VS Code

While working on a firebase functions project in Visual Studio Code, I encountered an issue inside the index.ts file. The imported modules were not being recognized even though autocomplete showed that the modules exist. When attempting to import them, I k ...

What is the best way to pass the username and token data from a child component to its parent component

Hey, I'm currently working on a login app in React where the login component is a child of the app. My goal is to send back the username and token to the parent component once a user is logged in, so that I can then pass this information to other chil ...

Tips for transfering variables from an electron application to the backend of an Angular project

My goal is to develop a website and desktop application using the same code base. However, due to some minor differences between the two platforms, I need a way for my Angular app to distinguish whether it has been called from the web or from Electron. I& ...

The issue arises when attempting to drop elements from two lists with incorrect positions and mismatched coordinates

Angular 9 had a working version of this, which you can find here: https://stackblitz.com/edit/two-drop-list-problem-zp556d?file=package.json Now in the new Angular 14 version: https://stackblitz.com/edit/angular-ivy-1jvbnn?file=src%2Fapp%2Fapp.component ...

Encountering a problem while attempting to host an Angular application on localhost:4200

After executing the ng serve command, I encountered an issue in the browser: An error occurred while trying to resolve "localhost:4200" ("") for "10.238.0.0": rpc error: code = Unknown desc = no such record I apologize if this question seems basic, as I ...

Retrieve files by utilizing the find() function in couchdb-nano

As CouchDB doesn't have collections, I decided to add a custom type property to my entities. Now, I want to filter all the entities based on that property, for example, retrieve all users with {type:'user'}. In the CouchDB documentation, I c ...

Integrate child component properties within the parent component

Looking to create a wrapper component that selects specific props from an inner component and includes additional custom props. The issue is that using pick will generate a type rather than an interface, limiting the ability to add more keys. How can I wor ...

Find the object in the array that has a name that is a combination of

I am facing an issue in implementing TypeScript validation for filtering an array. I have a specific array of actions and I want to filter out internal actions from it. Despite my efforts, I am unable to properly communicate to TypeScript that the filtered ...

Error: SvelteKit server-side rendering encountered a TypeError when trying to fetch data. Unfortunately, Express is not providing a clear TypeScript stack trace

I've been monitoring the logs of the SvelteKit SSR server using adapter-node. After customizing the server.js to utilize Express instead of Polka, I noticed some errors occurring, particularly when the fetch() function attempts to retrieve data from ...

The development mode of NextJS is experiencing issues, however, the build and start commands are functioning normally

Just finished creating a brand new Next app (version: 12.0.7) using Typescript and Storybook. Everything seems to be working fine - I can successfully build and start the server. However, when I try to run dev mode and make a request, I encounter the follo ...

Issue: Pipe 'AsyncPipe' received an invalid argument '[object Object]'

I’m encountering an issue while attempting to replicate the steps from a specific YouTube tutorial. At the 8:22 mark of this video, I’m facing the following error: Error: InvalidPipeArgument: '[object Object]' for pipe 'AsyncPipe&apos ...

Using act() in React/Jest/MSW causes errors when waiting for a response

As I delve into learning how to unit test with React, my focus has shifted towards using TypeScript. Unfortunately, the course I am taking does not cover most errors related to TypeScript. In my testing journey, I have set up a simple testing function with ...

Steps for setting up a project to compile for ES6 syntax:

Working on a project using Angular 2 + PrimeNG, I encountered an issue with TypeScript compilation while trying to use Git. The solution involved adjusting the package.json file. "dependencies": { "@angular/common": "2.4.2", // List of dependencies goes ...

Utilize TypeScript Compiler (tsc) without the need for global installation via

Currently, I am tackling a project that needs to be delivered to a group of individuals. This project is written in TypeScript, requiring them to execute the command tsc for compilation. The issue arises when I run this command following the execution of ...

Exploring nested contexts in react testing library with renderHook

This is a sample code snippet in TypeScript that uses React and Testing Library: import React, { FC, useEffect, useMemo, useState } from 'react'; import { renderHook, waitFor } from '@testing-library/react'; interface PropsCtx { inpu ...

What is the best way to distinguish shared services from other services in Angular 6 and above?

There was a time when I frequently heard the term "shared services" in relation to sharing data between unrelated components in Angular. However, I started questioning whether every service is actually classified as a shared service or not. If it is cons ...