Add Prim/Kruskal maze gen & nodeData refactor
Implement Prim and Kruskal maze generation in the Labyrinth component with animated generation and UI guards. Introduces isAnimationRunning signal, mazeAnimationSpeed, maze node order tracking and animateMazeGeneration; createRandom(now takes a boolean) triggers either Prim or Kruskal flow, sets random start/end, and animates. Refactor Node.distance -> nodeData across models, components and the PathfindingService (Dijkstra/A*) to use nodeData for g-scores/ids. Add SharedFunctions.shuffleArray utility and update i18n (EN/DE) with labels for Prim/Kruskal. Misc: minor cleanup/init changes and drawing logic adjustments to support the new maze flows.
This commit is contained in:
@@ -6,11 +6,12 @@
|
|||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-container">
|
<div class="controls-container">
|
||||||
<div class="controls-panel">
|
<div class="controls-panel">
|
||||||
<button matButton="filled" (click)="visualize('dijkstra')">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="visualize('dijkstra')">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
||||||
<button matButton="filled" (click)="visualize('astar')">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="visualize('astar')">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls-panel">
|
<div class="controls-panel">
|
||||||
<button matButton="filled" (click)="createRandom()">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="createRandom(true)">{{ 'LABYRINTH.PRIM' | translate }}</button>
|
||||||
|
<button matButton="filled" [disabled]="isAnimationRunning()" (click)="createRandom(false)">{{ 'LABYRINTH.KRUSKAL' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="legend">
|
<div class="legend">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {AfterViewInit, Component, inject, ViewChild} from '@angular/core';
|
import {AfterViewInit, Component, inject, signal, ViewChild} from '@angular/core';
|
||||||
import {Information} from '../../information/information';
|
import {Information} from '../../information/information';
|
||||||
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
|
||||||
import {TranslatePipe} from '@ngx-translate/core';
|
import {TranslatePipe} from '@ngx-translate/core';
|
||||||
@@ -62,10 +62,13 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
startNode: Node | null = null;
|
startNode: Node | null = null;
|
||||||
endNode: Node | null = null;
|
endNode: Node | null = null;
|
||||||
animationSpeed = 3;
|
animationSpeed = 3;
|
||||||
|
mazeAnimationSpeed = 1;
|
||||||
pathLength = "0";
|
pathLength = "0";
|
||||||
executionTime = 0;
|
executionTime = 0;
|
||||||
|
|
||||||
private timeoutIds: number[] = [];
|
private timeoutIds: number[] = [];
|
||||||
|
protected mazeNodesInOrder: Node[] = [];
|
||||||
|
readonly isAnimationRunning = signal(false);
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
if (this.genericGridComponent) {
|
if (this.genericGridComponent) {
|
||||||
@@ -85,14 +88,167 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
|
|
||||||
initializeMazeGrid = (grid: Node[][]): void => {
|
initializeMazeGrid = (grid: Node[][]): void => {
|
||||||
this.grid = grid;
|
this.grid = grid;
|
||||||
this.createRandom();
|
this.createRandom(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
createRandom(): void {
|
createRandom(prim: boolean): void {
|
||||||
|
this.isAnimationRunning.set(true);
|
||||||
this.stopAnimations();
|
this.stopAnimations();
|
||||||
this.clearPath();
|
this.clearPath();
|
||||||
this.startNode = null;
|
this.startNode = null;
|
||||||
this.endNode = null;
|
this.endNode = null;
|
||||||
|
if (prim)
|
||||||
|
{
|
||||||
|
this.createPrimMaze();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
this.createKruskalMaze();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cleanupGrid();
|
||||||
|
this.genericGridComponent.drawGrid();
|
||||||
|
}
|
||||||
|
// ------- Kuskal -------
|
||||||
|
private createKruskalMaze(): void {
|
||||||
|
this.initKuskal();
|
||||||
|
this.mazeNodesInOrder = [];
|
||||||
|
const walls = this.findWallsWithADistanceOfTwoRooms();
|
||||||
|
SharedFunctions.shuffleArray(walls);
|
||||||
|
for (const wallInfo of walls) {
|
||||||
|
const { row, col, roomA, roomB } = wallInfo;
|
||||||
|
|
||||||
|
if (roomA.nodeData !== roomB.nodeData) {
|
||||||
|
const wallNode = this.grid[row][col];
|
||||||
|
wallNode.isWall = true;
|
||||||
|
this.mazeNodesInOrder.push(wallNode);
|
||||||
|
|
||||||
|
const oldId = roomB.nodeData;
|
||||||
|
const newId = roomA.nodeData;
|
||||||
|
|
||||||
|
this.mergeSets(oldId, newId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.setRandomStartAndEnd();
|
||||||
|
this.animateMazeGeneration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initKuskal() {
|
||||||
|
let roomId = 0;
|
||||||
|
|
||||||
|
for (let row = 0; row < this.gridRows; row++) {
|
||||||
|
for (let col = 0; col < this.gridCols; col++) {
|
||||||
|
const node = this.grid[row][col];
|
||||||
|
node.isStart = false;
|
||||||
|
node.isEnd = false;
|
||||||
|
|
||||||
|
if (row % 2 === 0 && col % 2 === 0) {
|
||||||
|
node.isWall = false;
|
||||||
|
node.nodeData = roomId++;
|
||||||
|
} else {
|
||||||
|
node.isWall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mergeSets(oldId: number, newId: number): void {
|
||||||
|
for (let r = 0; r < this.gridRows; r += 2) {
|
||||||
|
for (let c = 0; c < this.gridCols; c += 2) {
|
||||||
|
if (this.grid[r][c].nodeData === oldId) {
|
||||||
|
this.grid[r][c].nodeData = newId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findWallsWithADistanceOfTwoRooms() {
|
||||||
|
const walls: { row: number, col: number, roomA: Node, roomB: Node }[] = [];
|
||||||
|
for (let row = 0; row < this.gridRows; row++) {
|
||||||
|
for (let col = 0; col < this.gridCols; col++) {
|
||||||
|
if (row % 2 === 0 && col % 2 !== 0 && col > 0 && col < this.gridCols - 1) {
|
||||||
|
walls.push({
|
||||||
|
row, col,
|
||||||
|
roomA: this.grid[row][col - 1],
|
||||||
|
roomB: this.grid[row][col + 1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (row % 2 !== 0 && col % 2 === 0 && row > 0 && row < this.gridRows - 1) {
|
||||||
|
walls.push({
|
||||||
|
row, col,
|
||||||
|
roomA: this.grid[row - 1][col],
|
||||||
|
roomB: this.grid[row + 1][col]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return walls;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setRandomStartAndEnd(): void {
|
||||||
|
const lastRow = Math.floor((this.gridRows - 1) / 2) * 2;
|
||||||
|
const lastCol = Math.floor((this.gridCols - 1) / 2) * 2;
|
||||||
|
|
||||||
|
const corners = [
|
||||||
|
{ r: 0, c: 0 },
|
||||||
|
{ r: 0, c: lastCol },
|
||||||
|
{ r: lastRow, c: 0 },
|
||||||
|
{ r: lastRow, c: lastCol }
|
||||||
|
];
|
||||||
|
|
||||||
|
const startIndex = Math.floor(Math.random() * corners.length);
|
||||||
|
let endIndex = Math.floor(Math.random() * corners.length);
|
||||||
|
while (endIndex === startIndex) {
|
||||||
|
endIndex = Math.floor(Math.random() * corners.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = corners[startIndex];
|
||||||
|
const end = corners[endIndex];
|
||||||
|
|
||||||
|
this.startNode = this.grid[start.r][start.c];
|
||||||
|
this.startNode.isStart = true;
|
||||||
|
this.startNode.isWall = false;
|
||||||
|
|
||||||
|
this.endNode =this.grid[end.r][end.c];
|
||||||
|
this.endNode.isEnd = true;
|
||||||
|
this.endNode.isWall = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------- PRIM -------
|
||||||
|
private createPrimMaze(): void {
|
||||||
|
this.initPrim();
|
||||||
|
this.mazeNodesInOrder = [];
|
||||||
|
const frontier: Node[] = [];
|
||||||
|
|
||||||
|
const {startRow, startCol, startNode} = this.findStartNode();
|
||||||
|
this.mazeNodesInOrder.push(startNode);
|
||||||
|
|
||||||
|
this.getNeighborWalls(startRow, startCol, frontier);
|
||||||
|
|
||||||
|
while (frontier.length > 0) {
|
||||||
|
const randomIndex = SharedFunctions.randomIntFromInterval(0, frontier.length - 1);
|
||||||
|
const lastIndex = frontier.length - 1;
|
||||||
|
[frontier[randomIndex], frontier[lastIndex]] = [frontier[lastIndex], frontier[randomIndex]];
|
||||||
|
|
||||||
|
const wallNode = frontier.pop()!;
|
||||||
|
const target = wallNode.linkedNode;
|
||||||
|
|
||||||
|
if (!target || target.isVisited) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
wallNode.isVisited = true;
|
||||||
|
target.isVisited = true;
|
||||||
|
|
||||||
|
this.mazeNodesInOrder.push(wallNode, target);
|
||||||
|
|
||||||
|
this.getNeighborWalls(target.row, target.col, frontier);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setRandomStartAndEnd();
|
||||||
|
this.animateMazeGeneration();
|
||||||
|
}
|
||||||
|
|
||||||
|
private initPrim() {
|
||||||
for (let row = 0; row < this.grid.length; row++) {
|
for (let row = 0; row < this.grid.length; row++) {
|
||||||
for (let col = 0; col < this.grid[row].length; col++) {
|
for (let col = 0; col < this.grid[row].length; col++) {
|
||||||
this.grid[row][col].isWall = true;
|
this.grid[row][col].isWall = true;
|
||||||
@@ -100,34 +256,6 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
this.grid[row][col].isEnd = false;
|
this.grid[row][col].isEnd = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const frontier: Node[] = [];
|
|
||||||
|
|
||||||
const {startRow, startCol, startNode} = this.findStartNode();
|
|
||||||
this.startNode = startNode;
|
|
||||||
this.getNeighborWalls(startRow, startCol, frontier);
|
|
||||||
while (frontier.length > 0) {
|
|
||||||
const randomIndex = SharedFunctions.randomIntFromInterval(0, frontier.length - 1);
|
|
||||||
|
|
||||||
//swap and pop from array
|
|
||||||
const lastIndex = frontier.length - 1;
|
|
||||||
[frontier[randomIndex], frontier[lastIndex]] = [frontier[lastIndex], frontier[randomIndex]];
|
|
||||||
const wallFromFrontierList = frontier.pop()!;
|
|
||||||
const target = wallFromFrontierList.linkedNode;
|
|
||||||
|
|
||||||
if (!target || target.isVisited) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
wallFromFrontierList.isWall = false;
|
|
||||||
wallFromFrontierList.isVisited = true;
|
|
||||||
target.isWall = false;
|
|
||||||
target.isVisited = true;
|
|
||||||
this.getNeighborWalls(target.row, target.col, frontier);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.findEndNode(startNode);
|
|
||||||
this.cleanupGrid();
|
|
||||||
|
|
||||||
this.genericGridComponent.drawGrid();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanupGrid() {
|
private cleanupGrid() {
|
||||||
@@ -139,32 +267,12 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private findEndNode(startNode: Node) {
|
|
||||||
let endFound = false;
|
|
||||||
while (!endFound) {
|
|
||||||
const endRow: number = SharedFunctions.randomEventIntFromInterval(this.gridRows - 1);
|
|
||||||
const endCol: number = SharedFunctions.randomEventIntFromInterval(this.gridCols - 1);
|
|
||||||
|
|
||||||
const endNode = this.grid[endRow][endCol];
|
|
||||||
|
|
||||||
if (endNode != startNode && !endNode.isWall) {
|
|
||||||
endNode.isWall = false;
|
|
||||||
endNode.isEnd = true;
|
|
||||||
endNode.isVisited = true;
|
|
||||||
this.endNode = endNode;
|
|
||||||
endFound = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private findStartNode() {
|
private findStartNode() {
|
||||||
const startRow: number = SharedFunctions.randomEventIntFromInterval(this.gridRows - 1);
|
const startRow: number = SharedFunctions.randomEventIntFromInterval(this.gridRows - 1);
|
||||||
const startCol: number = SharedFunctions.randomEventIntFromInterval(this.gridCols - 1);
|
const startCol: number = SharedFunctions.randomEventIntFromInterval(this.gridCols - 1);
|
||||||
|
|
||||||
const startNode = this.grid[startRow][startCol];
|
const startNode = this.grid[startRow][startCol];
|
||||||
startNode.isWall = false;
|
startNode.isWall = false;
|
||||||
startNode.isStart = true;
|
|
||||||
startNode.isVisited = true;
|
startNode.isVisited = true;
|
||||||
return {startRow, startCol, startNode};
|
return {startRow, startCol, startNode};
|
||||||
}
|
}
|
||||||
@@ -220,7 +328,7 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
isWall: false,
|
isWall: false,
|
||||||
isVisited: false,
|
isVisited: false,
|
||||||
isPath: false,
|
isPath: false,
|
||||||
distance: Infinity,
|
nodeData: Infinity,
|
||||||
linkedNode: null,
|
linkedNode: null,
|
||||||
hScore: 0,
|
hScore: 0,
|
||||||
fScore: Infinity,
|
fScore: Infinity,
|
||||||
@@ -255,7 +363,7 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
const node = this.grid[row][col];
|
const node = this.grid[row][col];
|
||||||
node.isVisited = false;
|
node.isVisited = false;
|
||||||
node.isPath = false;
|
node.isPath = false;
|
||||||
node.distance = Infinity;
|
node.nodeData = Infinity;
|
||||||
node.linkedNode = null;
|
node.linkedNode = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -274,7 +382,7 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
const id = globalThis.setTimeout(() => {
|
const id = globalThis.setTimeout(() => {
|
||||||
if (!node.isStart && !node.isEnd) {
|
if (!node.isStart && !node.isEnd) {
|
||||||
node.isVisited = true;
|
node.isVisited = true;
|
||||||
this.genericGridComponent?.drawNode(node); // Redraw single node
|
this.genericGridComponent?.drawNode(node);
|
||||||
}
|
}
|
||||||
}, this.animationSpeed * i);
|
}, this.animationSpeed * i);
|
||||||
|
|
||||||
@@ -295,6 +403,31 @@ export class LabyrinthComponent implements AfterViewInit {
|
|||||||
this.timeoutIds.push(id);
|
this.timeoutIds.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private animateMazeGeneration(): void {
|
||||||
|
for (let i = 0; i < this.mazeNodesInOrder.length; i++) {
|
||||||
|
const id = globalThis.setTimeout(() => {
|
||||||
|
const node = this.mazeNodesInOrder[i];
|
||||||
|
node.isWall = false;
|
||||||
|
this.genericGridComponent?.drawNode(node);
|
||||||
|
if (i === this.mazeNodesInOrder.length - 1) {
|
||||||
|
this.cleanupGrid();
|
||||||
|
if (this.startNode) {
|
||||||
|
this.genericGridComponent?.drawNode(this.startNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.endNode) {
|
||||||
|
this.genericGridComponent?.drawNode(this.endNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == this.mazeNodesInOrder.length - 1) {
|
||||||
|
this.isAnimationRunning.set(false);
|
||||||
|
}
|
||||||
|
}, this.mazeAnimationSpeed * i);
|
||||||
|
|
||||||
|
this.timeoutIds.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//utility
|
//utility
|
||||||
private getNeighborWalls(row: number, col: number, frontier: Node[]): void{
|
private getNeighborWalls(row: number, col: number, frontier: Node[]): void{
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
isWall: false,
|
isWall: false,
|
||||||
isVisited: false,
|
isVisited: false,
|
||||||
isPath: false,
|
isPath: false,
|
||||||
distance: Infinity,
|
nodeData: Infinity,
|
||||||
linkedNode: null,
|
linkedNode: null,
|
||||||
hScore: 0,
|
hScore: 0,
|
||||||
fScore: Infinity,
|
fScore: Infinity,
|
||||||
@@ -436,7 +436,7 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
const node = this.grid[row][col];
|
const node = this.grid[row][col];
|
||||||
node.isVisited = false;
|
node.isVisited = false;
|
||||||
node.isPath = false;
|
node.isPath = false;
|
||||||
node.distance = Infinity;
|
node.nodeData = Infinity;
|
||||||
node.linkedNode = null;
|
node.linkedNode = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export interface Node {
|
|||||||
isWall: boolean;
|
isWall: boolean;
|
||||||
isVisited: boolean;
|
isVisited: boolean;
|
||||||
isPath: boolean;
|
isPath: boolean;
|
||||||
distance: number;
|
nodeData: number; //can be used as distance or id or something
|
||||||
linkedNode: Node | null;
|
linkedNode: Node | null;
|
||||||
fScore: number;
|
fScore: number;
|
||||||
hScore: number;
|
hScore: number;
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class PathfindingService {
|
|||||||
// Dijkstra's Algorithm
|
// Dijkstra's Algorithm
|
||||||
dijkstra(grid: Node[][], startNode: Node, endNode: Node): { visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[] } {
|
dijkstra(grid: Node[][], startNode: Node, endNode: Node): { visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[] } {
|
||||||
const visitedNodesInOrder: Node[] = [];
|
const visitedNodesInOrder: Node[] = [];
|
||||||
startNode.distance = 0;
|
startNode.nodeData = 0;
|
||||||
const unvisitedNodes: Node[] = this.getAllNodes(grid);
|
const unvisitedNodes: Node[] = this.getAllNodes(grid);
|
||||||
|
|
||||||
while (unvisitedNodes.length > 0) {
|
while (unvisitedNodes.length > 0) {
|
||||||
@@ -44,7 +44,7 @@ export class PathfindingService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTrapped = closestNode.distance === Infinity;
|
const isTrapped = closestNode.nodeData === Infinity;
|
||||||
if (isTrapped)
|
if (isTrapped)
|
||||||
{
|
{
|
||||||
return { visitedNodesInOrder, nodesInShortestPathOrder: [] };
|
return { visitedNodesInOrder, nodesInShortestPathOrder: [] };
|
||||||
@@ -65,13 +65,13 @@ export class PathfindingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private sortNodesByDistance(unvisitedNodes: Node[]): void {
|
private sortNodesByDistance(unvisitedNodes: Node[]): void {
|
||||||
unvisitedNodes.sort((nodeA, nodeB) => nodeA.distance - nodeB.distance);
|
unvisitedNodes.sort((nodeA, nodeB) => nodeA.nodeData - nodeB.nodeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateUnvisitedNeighbors(node: Node, grid: Node[][]): void {
|
private updateUnvisitedNeighbors(node: Node, grid: Node[][]): void {
|
||||||
const unvisitedNeighbors = this.getUnvisitedNeighbors(node, grid);
|
const unvisitedNeighbors = this.getUnvisitedNeighbors(node, grid);
|
||||||
for (const neighbor of unvisitedNeighbors) {
|
for (const neighbor of unvisitedNeighbors) {
|
||||||
neighbor.distance = node.distance + 1;
|
neighbor.nodeData = node.nodeData + 1;
|
||||||
neighbor.linkedNode = node;
|
neighbor.linkedNode = node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,10 +79,10 @@ export class PathfindingService {
|
|||||||
// A* Search Algorithm
|
// A* Search Algorithm
|
||||||
aStar(grid: Node[][], startNode: Node, endNode: Node): { visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[] } {
|
aStar(grid: Node[][], startNode: Node, endNode: Node): { visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[] } {
|
||||||
const visitedNodesInOrder: Node[] = [];
|
const visitedNodesInOrder: Node[] = [];
|
||||||
startNode.distance = 0;
|
startNode.nodeData = 0;
|
||||||
startNode['hScore'] = this.calculateHeuristic(startNode, endNode);
|
startNode['hScore'] = this.calculateHeuristic(startNode, endNode);
|
||||||
// fScore = gScore + hScore
|
// fScore = gScore + hScore
|
||||||
startNode['fScore'] = startNode.distance + startNode['hScore'];
|
startNode['fScore'] = startNode.nodeData + startNode['hScore'];
|
||||||
|
|
||||||
const openSet: Node[] = [startNode];
|
const openSet: Node[] = [startNode];
|
||||||
const allNodes = this.getAllNodes(grid);
|
const allNodes = this.getAllNodes(grid);
|
||||||
@@ -97,7 +97,7 @@ export class PathfindingService {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTrapped = currentNode.distance === Infinity;
|
const isTrapped = currentNode.nodeData === Infinity;
|
||||||
if (isTrapped)
|
if (isTrapped)
|
||||||
{
|
{
|
||||||
return {visitedNodesInOrder, nodesInShortestPathOrder: []};
|
return {visitedNodesInOrder, nodesInShortestPathOrder: []};
|
||||||
@@ -114,9 +114,9 @@ export class PathfindingService {
|
|||||||
|
|
||||||
const neighbors = this.getUnvisitedNeighbors(currentNode, grid);
|
const neighbors = this.getUnvisitedNeighbors(currentNode, grid);
|
||||||
for (const neighbor of neighbors) {
|
for (const neighbor of neighbors) {
|
||||||
const tentativeGScore = currentNode.distance + 1; // Distance from start to neighbor
|
const tentativeGScore = currentNode.nodeData + 1; // Distance from start to neighbor
|
||||||
|
|
||||||
if (tentativeGScore < neighbor.distance) {
|
if (tentativeGScore < neighbor.nodeData) {
|
||||||
this.updateNeighborNode(neighbor, currentNode, tentativeGScore, endNode, openSet);
|
this.updateNeighborNode(neighbor, currentNode, tentativeGScore, endNode, openSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,10 +137,10 @@ export class PathfindingService {
|
|||||||
|
|
||||||
private updateNeighborNode(neighbor: Node, currentNode: Node, tentativeGScore: number, endNode: Node, openSet: Node[]) {
|
private updateNeighborNode(neighbor: Node, currentNode: Node, tentativeGScore: number, endNode: Node, openSet: Node[]) {
|
||||||
neighbor.linkedNode = currentNode;
|
neighbor.linkedNode = currentNode;
|
||||||
neighbor.distance = tentativeGScore;
|
neighbor.nodeData = tentativeGScore;
|
||||||
neighbor['distance'] = this.calculateHeuristic(neighbor, endNode);
|
neighbor['nodeData'] = this.calculateHeuristic(neighbor, endNode);
|
||||||
neighbor['hScore'] = this.calculateHeuristic(neighbor, endNode);
|
neighbor['hScore'] = this.calculateHeuristic(neighbor, endNode);
|
||||||
neighbor['fScore'] = neighbor.distance + neighbor['hScore'];
|
neighbor['fScore'] = neighbor.nodeData + neighbor['hScore'];
|
||||||
|
|
||||||
if (!openSet.includes(neighbor)) {
|
if (!openSet.includes(neighbor)) {
|
||||||
openSet.push(neighbor);
|
openSet.push(neighbor);
|
||||||
@@ -151,7 +151,7 @@ export class PathfindingService {
|
|||||||
for (const node of allNodes) {
|
for (const node of allNodes) {
|
||||||
if (node !== startNode) {
|
if (node !== startNode) {
|
||||||
node['fScore'] = Infinity;
|
node['fScore'] = Infinity;
|
||||||
node.distance = Infinity; // gScore
|
node.nodeData = Infinity; // gScore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,4 +14,12 @@ export class SharedFunctions {
|
|||||||
static randomEventIntFromInterval(interval: number): number {
|
static randomEventIntFromInterval(interval: number): number {
|
||||||
return Math.floor(Math.random() * (interval / 2)) * 2;
|
return Math.floor(Math.random() * (interval / 2)) * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static shuffleArray<T>(array: T[]): T[] {
|
||||||
|
for (let i = array.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[array[i], array[j]] = [array[j], array[i]];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,6 +364,8 @@
|
|||||||
},
|
},
|
||||||
"LABYRINTH": {
|
"LABYRINTH": {
|
||||||
"TITLE": "Labyrinth-Erzeugung",
|
"TITLE": "Labyrinth-Erzeugung",
|
||||||
|
"PRIM": "Erzeuge Prim's Labyrinth",
|
||||||
|
"KRUSKAL": "Erzeuge Kruskal's Labyrinth",
|
||||||
"EXPLANATION": {
|
"EXPLANATION": {
|
||||||
"TITLE": "Algorithmen",
|
"TITLE": "Algorithmen",
|
||||||
"PRIM_EXPLANATION": "startet an einem zufälligen Punkt und erweitert das Labyrinth, indem er immer eine zufällige benachbarte Wand zu einer bereits besuchten Zelle auswählt und diese öffnet. Vorteil: Erzeugt sehr gleichmäßige, natürlich wirkende Labyrinthe mit vielen kurzen Sackgassen. Visuell wirkt es wie ein organisches Wachstum von einem Zentrum aus.",
|
"PRIM_EXPLANATION": "startet an einem zufälligen Punkt und erweitert das Labyrinth, indem er immer eine zufällige benachbarte Wand zu einer bereits besuchten Zelle auswählt und diese öffnet. Vorteil: Erzeugt sehr gleichmäßige, natürlich wirkende Labyrinthe mit vielen kurzen Sackgassen. Visuell wirkt es wie ein organisches Wachstum von einem Zentrum aus.",
|
||||||
|
|||||||
@@ -363,6 +363,8 @@
|
|||||||
},
|
},
|
||||||
"LABYRINTH": {
|
"LABYRINTH": {
|
||||||
"TITLE": "Labyrinth Generation",
|
"TITLE": "Labyrinth Generation",
|
||||||
|
"PRIM": "Generate Prim's Labyrinth",
|
||||||
|
"KRUSKAL": "Generate Kruskal's Labyrinth",
|
||||||
"EXPLANATION": {
|
"EXPLANATION": {
|
||||||
"TITLE": "Algorithms",
|
"TITLE": "Algorithms",
|
||||||
"PRIM_EXPLANATION": "starts at a random point and expands the labyrinth by always selecting a random neighboring wall of an already visited cell and opening it. Advantage: Produces very uniform, natural-looking labyrinths with many short dead ends. Visually, it appears like organic growth from a central point.",
|
"PRIM_EXPLANATION": "starts at a random point and expands the labyrinth by always selecting a random neighboring wall of an already visited cell and opening it. Advantage: Produces very uniform, natural-looking labyrinths with many short dead ends. Visually, it appears like organic growth from a central point.",
|
||||||
|
|||||||
Reference in New Issue
Block a user