fesature/maze-gen #16

Merged
lobo merged 2 commits from fesature/maze-gen into main 2026-02-09 14:57:36 +01:00
17 changed files with 444 additions and 23 deletions
Showing only changes of commit bbec113f5d - Show all commits

View File

@@ -17,6 +17,7 @@ module.exports = defineConfig([
rules: {
"@typescript-eslint/no-inferrable-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/prefer-for-of": "off",
"@angular-eslint/directive-selector": [
"error",
{

View File

@@ -1,6 +1,6 @@
{
"name": "playground-frontend",
"version": "0.2.0",
"version": "1.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",

View File

@@ -10,6 +10,7 @@ export const routes: Routes = [
{ path: RouterConstants.PATHFINDING.PATH, component: RouterConstants.PATHFINDING.COMPONENT},
{ path: RouterConstants.SORTING.PATH, component: RouterConstants.SORTING.COMPONENT},
{ path: RouterConstants.IMPRINT.PATH, component: RouterConstants.IMPRINT.COMPONENT},
{ path: RouterConstants.GOL.PATH, component: RouterConstants.GOL.COMPONENT}
{ path: RouterConstants.GOL.PATH, component: RouterConstants.GOL.COMPONENT},
{ path: RouterConstants.LABYRINTH.PATH, component: RouterConstants.LABYRINTH.COMPONENT}
];

View File

@@ -4,7 +4,8 @@ import {ImprintComponent} from '../pages/imprint/imprint.component';
import {AlgorithmsComponent} from '../pages/algorithms/algorithms.component';
import {PathfindingComponent} from '../pages/algorithms/pathfinding/pathfinding.component';
import {SortingComponent} from '../pages/algorithms/sorting/sorting.component';
import {ConwayGol} from '../pages/algorithms/conway-gol/conway-gol';
import {ConwayGolComponent} from '../pages/algorithms/conway-gol/conway-gol.component';
import {LabyrinthComponent} from '../pages/algorithms/pathfinding/labyrinth/labyrinth.component';
export class RouterConstants {
@@ -41,7 +42,13 @@ export class RouterConstants {
static readonly GOL = {
PATH: 'algorithms/gol',
LINK: '/algorithms/gol',
COMPONENT: ConwayGol
COMPONENT: ConwayGolComponent
};
static readonly LABYRINTH = {
PATH: 'algorithms/labyrinth',
LINK: '/algorithms/labyrinth',
COMPONENT: LabyrinthComponent
};
static readonly IMPRINT = {

View File

@@ -8,4 +8,6 @@
static readonly HEAP_SORT_WIKI = 'https://de.wikipedia.org/wiki/Heapsort'
static readonly SHAKE_SORT_WIKI = 'https://de.wikipedia.org/wiki/Shakersort'
static readonly CONWAYS_WIKI = 'https://de.wikipedia.org/wiki/Conways_Spiel_des_Lebens'
static readonly PRIMS_WIKI = 'https://de.wikipedia.org/wiki/Algorithmus_von_Prim'
static readonly KRUSKAL_WIKI = 'https://de.wikipedia.org/wiki/Algorithmus_von_Kruskal'
}

View File

@@ -29,9 +29,9 @@ import {GenericGridComponent, GridPos} from '../../../shared/components/generic-
FormsModule,
GenericGridComponent
],
templateUrl: './conway-gol.html',
templateUrl: './conway-gol.component.html',
})
export class ConwayGol implements AfterViewInit {
export class ConwayGolComponent implements AfterViewInit {
algoInformation: AlgorithmInformation = {
title: 'GOL.EXPLANATION.TITLE',

View File

@@ -0,0 +1,41 @@
<mat-card class="container">
<mat-card-header>
<mat-card-title>{{ 'LABYRINTH.TITLE' | translate }}</mat-card-title>
</mat-card-header>
<mat-card-content>
<app-information [algorithmInformation]="algoInformation"/>
<div class="controls-container">
<div class="controls-panel">
<button matButton="filled" (click)="visualize('dijkstra')">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
<button matButton="filled" (click)="visualize('astar')">{{ 'PATHFINDING.ASTAR' | translate }}</button>
</div>
<div class="controls-panel">
<button matButton="filled" (click)="createRandom()">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
</div>
<div class="legend">
<span><span class="legend-color start"></span> {{ 'PATHFINDING.START_NODE' | translate }}</span>
<span><span class="legend-color end"></span> {{ 'PATHFINDING.END_NODE' | translate }}</span>
<span><span class="legend-color wall"></span> {{ 'PATHFINDING.WALL' | translate }}</span>
<span><span class="legend-color visited"></span> {{ 'PATHFINDING.VISITED' | translate }}</span>
<span><span class="legend-color path"></span> {{ 'PATHFINDING.PATH' | translate }}</span>
</div>
<div class="controls-panel">
<p>{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}</p>
<p>{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms</p>
</div>
</div>
<app-generic-grid
[gridRows]="gridRows"
[gridCols]="gridCols"
[minGridSize]="MIN_GRID_SIZE"
[maxGridSize]="MAX_GRID_SIZE"
[maxGridPx]="MAX_GRID_PX"
[createNodeFn]="createMazeNode"
[getNodeColorFn]="getMazeColor"
[applySelectionFn]="applyNoSelection"
[backgroundColor]="'lightgray'"
(gridChange)="grid = $event"
></app-generic-grid>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,325 @@
import {AfterViewInit, Component, inject, ViewChild} from '@angular/core';
import {Information} from '../../information/information';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card';
import {TranslatePipe} from '@ngx-translate/core';
import {GenericGridComponent, GridPos} from '../../../../shared/components/generic-grid/generic-grid';
import {AlgorithmInformation} from '../../information/information.models';
import {UrlConstants} from '../../../../constants/UrlConstants';
import {Node} from '../pathfinding.models';
import {SharedFunctions} from '../../../../shared/SharedFunctions';
import {MatButton} from '@angular/material/button';
import {DecimalPipe} from '@angular/common';
import {PathfindingService} from '../service/pathfinding.service';
@Component({
selector: 'app-labyrinth',
imports: [
Information,
MatCard,
MatCardContent,
MatCardHeader,
MatCardTitle,
TranslatePipe,
GenericGridComponent,
MatButton,
DecimalPipe
],
templateUrl: './labyrinth.component.html',
styleUrl: './labyrinth.component.scss',
})
export class LabyrinthComponent implements AfterViewInit {
protected readonly gridRows = 101;
protected readonly gridCols = 101;
protected readonly MAX_GRID_SIZE = 101;
protected readonly MAX_GRID_PX = 1000;
protected readonly MIN_GRID_SIZE = 101;
private readonly pathfindingService = inject(PathfindingService);
algoInformation: AlgorithmInformation = {
title: 'LABYRINTH.EXPLANATION.TITLE',
entries: [
{
name: 'Prims',
description: 'LABYRINTH.EXPLANATION.PRIM_EXPLANATION',
link: UrlConstants.PRIMS_WIKI
},
{
name: 'Kruskals',
description: 'LABYRINTH.EXPLANATION.KRUSKAL_EXPLANATION',
link: UrlConstants.KRUSKAL_WIKI
}
],
disclaimer: 'LABYRINTH.EXPLANATION.DISCLAIMER',
disclaimerBottom: '',
disclaimerListEntry: ['LABYRINTH.EXPLANATION.DISCLAIMER_1', 'LABYRINTH.EXPLANATION.DISCLAIMER_2', 'LABYRINTH.EXPLANATION.DISCLAIMER_3', 'LABYRINTH.EXPLANATION.DISCLAIMER_4']
};
@ViewChild(GenericGridComponent) genericGridComponent!: GenericGridComponent;
grid: Node[][] = [];
startNode: Node | null = null;
endNode: Node | null = null;
animationSpeed = 3;
pathLength = "0";
executionTime = 0;
private timeoutIds: number[] = [];
ngAfterViewInit(): void {
if (this.genericGridComponent) {
this.genericGridComponent.initializationFn = this.initializeMazeGrid;
this.genericGridComponent.createNodeFn = this.createMazeNode;
this.genericGridComponent.getNodeColorFn = this.getMazeColor;
this.genericGridComponent.applySelectionFn = this.applyNoSelection;
this.genericGridComponent.gridRows = this.gridRows;
this.genericGridComponent.gridCols = this.gridCols;
this.genericGridComponent.minGridSize = this.MIN_GRID_SIZE;
this.genericGridComponent.maxGridSize = this.MAX_GRID_SIZE;
this.genericGridComponent.maxGridPx = 1000;
this.genericGridComponent.applyGridSize();
this.genericGridComponent.initializeGrid();
}
}
initializeMazeGrid = (grid: Node[][]): void => {
this.grid = grid;
this.createRandom();
};
createRandom(): void {
this.stopAnimations();
this.clearPath();
this.startNode = null;
this.endNode = null;
for (let row = 0; row < this.grid.length; row++) {
for (let col = 0; col < this.grid[row].length; col++) {
this.grid[row][col].isWall = true;
this.grid[row][col].isStart = 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() {
for (let row = 0; row < this.grid.length; row++) {
for (let col = 0; col < this.grid[row].length; col++) {
this.grid[row][col].isVisited = false;
this.grid[row][col].linkedNode = null;
}
}
}
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() {
const startRow: number = SharedFunctions.randomEventIntFromInterval(this.gridRows - 1);
const startCol: number = SharedFunctions.randomEventIntFromInterval(this.gridCols - 1);
const startNode = this.grid[startRow][startCol];
startNode.isWall = false;
startNode.isStart = true;
startNode.isVisited = true;
return {startRow, startCol, startNode};
}
visualize(algorithm: string): void {
this.stopAnimations();
this.clearPath();
const startTime = performance.now();
let result;
switch (algorithm) {
case 'dijkstra': result = this.pathfindingService.dijkstra(
this.grid,
this.grid[this.startNode!.row][this.startNode!.col],
this.grid[this.endNode!.row][this.endNode!.col]
);
break;
case 'astar': result = this.pathfindingService.aStar(
this.grid,
this.grid[this.startNode!.row][this.startNode!.col],
this.grid[this.endNode!.row][this.endNode!.col]
);
break;
}
if (!result)
{
return;
}
const endTime = performance.now();
const lengthOfShortestPath = result.nodesInShortestPathOrder.length;
if (lengthOfShortestPath === 0)
{
this.pathLength = "∞"
}
else
{
this.pathLength = result.nodesInShortestPathOrder.length + "";
}
this.executionTime = endTime - startTime;
this.animateAlgorithm(result.visitedNodesInOrder, result.nodesInShortestPathOrder);
}
createMazeNode = (row: number, col: number): Node => {
return {
row,
col,
isStart: false,
isEnd: false,
isWall: false,
isVisited: false,
isPath: false,
distance: Infinity,
linkedNode: null,
hScore: 0,
fScore: Infinity,
};
};
getMazeColor = (node: Node): string => {
if (node.isStart) return 'green';
if (node.isEnd) return 'red';
if (node.isPath) return 'gold';
if (node.isVisited) return 'skyblue';
if (node.isWall) return 'black';
return 'lightgray';
};
applyNoSelection = (pos: GridPos, grid: Node[][]): void => {
this.grid = grid;
//dont need a selection for the maze case
}
// --- Animation (adapted to use genericGridComponent for redraw) ---
private stopAnimations(): void {
for (const id of this.timeoutIds) {
clearTimeout(id);
}
this.timeoutIds = [];
}
private clearPath(): void {
for (let row = 0; row < this.gridRows; row++) {
for (let col = 0; col < this.gridCols; col++) {
const node = this.grid[row][col];
node.isVisited = false;
node.isPath = false;
node.distance = Infinity;
node.linkedNode = null;
}
}
this.genericGridComponent?.drawGrid(); // Redraw the grid via generic grid component
}
private animateAlgorithm(visited: Node[], path: Node[]): void {
for (let i = 0; i <= visited.length; i++) {
if (i === visited.length) {
const id = globalThis.setTimeout(() => this.animateShortestPath(path), this.animationSpeed * i);
this.timeoutIds.push(id);
return;
}
const node = visited[i];
const id = globalThis.setTimeout(() => {
if (!node.isStart && !node.isEnd) {
node.isVisited = true;
this.genericGridComponent?.drawNode(node); // Redraw single node
}
}, this.animationSpeed * i);
this.timeoutIds.push(id);
}
}
private animateShortestPath(path: Node[]): void {
for (let i = 0; i < path.length; i++) {
const node = path[i];
const id = globalThis.setTimeout(() => {
if (!node.isStart && !node.isEnd) {
node.isPath = true;
this.genericGridComponent?.drawNode(node); // Redraw single node
}
}, this.animationSpeed * i);
this.timeoutIds.push(id);
}
}
//utility
private getNeighborWalls(row: number, col: number, frontier: Node[]): void{
const directions = [
[0, 2], [0, -2], [2, 0], [-2, 0]
];
for (const [dr, dc] of directions) {
const nextRow = row + dr;
const nextCol = col + dc;
if (this.isValid(nextRow, nextCol) && this.grid[nextRow][nextCol].isWall && !this.grid[nextRow][nextCol].isVisited) {
const wallRow = row + dr / 2;
const wallCol = col + dc / 2;
const node = this.grid[wallRow][wallCol];
node.linkedNode = this.grid[nextRow][nextCol];
frontier.push(node);
}
}
}
isValid = (row: number, col: number): boolean => {
return row >= 0 && row < this.gridRows && col >= 0 && col < this.gridCols;
};
}

View File

@@ -16,6 +16,7 @@ import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/mat
import {Information} from '../information/information';
import {AlgorithmInformation} from '../information/information.models';
import {GenericGridComponent, GridPos} from '../../../shared/components/generic-grid/generic-grid';
import {SharedFunctions} from '../../../shared/SharedFunctions';
enum NodeType {
Start = 'start',
@@ -119,7 +120,7 @@ export class PathfindingComponent implements AfterViewInit {
isVisited: false,
isPath: false,
distance: Infinity,
previousNode: null,
linkedNode: null,
hScore: 0,
fScore: Infinity,
};
@@ -326,16 +327,16 @@ export class PathfindingComponent implements AfterViewInit {
private createRandomStartEndPosition(): { start: GridPos; end: GridPos } {
const midCol = Math.floor(this.gridCols / 2);
const startRow: number = this.randomIntFromInterval(0, this.gridRows - 1);
const startCol: number = this.randomIntFromInterval(0, this.gridCols - 1);
const startRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
const startCol: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1);
const endRow: number = this.randomIntFromInterval(0, this.gridRows - 1);
const endRow: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
let endCol: number;
if (startCol <= midCol) {
endCol = this.randomIntFromInterval(midCol + 1, this.gridCols - 1);
endCol = SharedFunctions.randomIntFromInterval(midCol + 1, this.gridCols - 1);
} else {
endCol = this.randomIntFromInterval(0, midCol);
endCol = SharedFunctions.randomIntFromInterval(0, midCol);
}
return {
@@ -359,8 +360,8 @@ export class PathfindingComponent implements AfterViewInit {
for (let wall = 0; wall < maxNumberOfWalls; wall++) {
const row: number = this.randomIntFromInterval(0, this.gridRows - 1);
const col: number = this.randomIntFromInterval(0, this.gridCols - 1);
const row: number = SharedFunctions.randomIntFromInterval(0, this.gridRows - 1);
const col: number = SharedFunctions.randomIntFromInterval(0, this.gridCols - 1);
if (!this.grid[row][col]) { // Use the grid passed from GenericGrid
wall--;
@@ -436,7 +437,7 @@ export class PathfindingComponent implements AfterViewInit {
node.isVisited = false;
node.isPath = false;
node.distance = Infinity;
node.previousNode = null;
node.linkedNode = null;
}
}
this.genericGridComponent?.drawGrid(); // Redraw the grid via generic grid component
@@ -486,8 +487,4 @@ export class PathfindingComponent implements AfterViewInit {
return false;
}
// --- Utility ---
private randomIntFromInterval(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1) + min);
}
}

View File

@@ -7,7 +7,7 @@ export interface Node {
isVisited: boolean;
isPath: boolean;
distance: number;
previousNode: Node | null;
linkedNode: Node | null;
fScore: number;
hScore: number;
}

View File

@@ -25,7 +25,7 @@ export class PathfindingService {
let currentNode: Node | null = endNode;
while (currentNode !== null) {
shortestPathNodes.unshift(currentNode);
currentNode = currentNode.previousNode;
currentNode = currentNode.linkedNode;
}
return shortestPathNodes;
}
@@ -72,7 +72,7 @@ export class PathfindingService {
const unvisitedNeighbors = this.getUnvisitedNeighbors(node, grid);
for (const neighbor of unvisitedNeighbors) {
neighbor.distance = node.distance + 1;
neighbor.previousNode = node;
neighbor.linkedNode = node;
}
}
@@ -136,7 +136,7 @@ export class PathfindingService {
}
private updateNeighborNode(neighbor: Node, currentNode: Node, tentativeGScore: number, endNode: Node, openSet: Node[]) {
neighbor.previousNode = currentNode;
neighbor.linkedNode = currentNode;
neighbor.distance = tentativeGScore;
neighbor['distance'] = this.calculateHeuristic(neighbor, endNode);
neighbor['hScore'] = this.calculateHeuristic(neighbor, endNode);

View File

@@ -26,6 +26,12 @@ export class AlgorithmsService {
title: 'ALGORITHM.GOL.TITLE',
description: 'ALGORITHM.GOL.DESCRIPTION',
routerLink: RouterConstants.GOL.LINK
},
{
id: 'labyrinth',
title: 'ALGORITHM.LABYRINTH.TITLE',
description: 'ALGORITHM.LABYRINTH.DESCRIPTION',
routerLink: RouterConstants.LABYRINTH.LINK
}
];

View File

@@ -7,4 +7,11 @@ export class SharedFunctions {
globalThis.location.href = `mailto:${user}@${domain}`;
}
static randomIntFromInterval(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1) + min);
}
static randomEventIntFromInterval(interval: number): number {
return Math.floor(Math.random() * (interval / 2)) * 2;
}
}

View File

@@ -362,6 +362,19 @@
"DISCLAIMER_4": " Eine tote Zelle bleibt tot, wenn sie nicht genau drei lebende Nachbarn hat."
}
},
"LABYRINTH": {
"TITLE": "Labyrinth-Erzeugung",
"EXPLANATION": {
"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.",
"KRUSKAL_EXPLANATION": "betrachtet alle Wände des Gitters als potenzielle Wege. Er wählt zufällig Wände aus und öffnet sie nur dann, wenn die beiden angrenzenden Zellen noch nicht miteinander verbunden sind (verhindert Kreise). Vorteil: Erzeugt ein sehr komplexes Labyrinth mit vielen langen, verwinkelten Pfaden. Visuell ist es spannend, da das Labyrinth an vielen Stellen gleichzeitig entsteht und am Ende zu einem Ganzen verschmilzt.",
"DISCLAIMER": "Beide Algorithmen basieren auf dem Prinzip des 'Minimal Spanning Tree' (Minimaler Spannbaum). Das bedeutet für dein Labyrinth:",
"DISCLAIMER_1": "Perfektes Labyrinth: Es gibt keine geschlossenen Kreise (Loops) jeder Punkt ist erreichbar, aber es gibt immer nur genau einen Weg zwischen zwei Punkten.",
"DISCLAIMER_2": "Erreichbarkeit: Da es ein Spannbaum ist, wird garantiert jede Zelle des Gitters Teil des Labyrinths, es gibt keine isolierten Bereiche.",
"DISCLAIMER_3": "Zufälligkeit: Durch die Gewichtung der Kanten mit Zufallswerten entstehen bei jedem Durchlauf völlig neue, einzigartige Strukturen.",
"DISCLAIMER_4": "Anwendung: Solche Labyrinthe sind die perfekte Testumgebung für Pfadfindungsalgorithmen wie Dijkstra oder A*."
}
},
"ALGORITHM": {
"TITLE": "Algorithmen",
"PATHFINDING": {
@@ -376,6 +389,10 @@
"TITLE": "Conway's Game of Life",
"DESCRIPTION": "Das 'Spiel des Lebens' ist ein vom Mathematiker John Horton Conway 1970 entworfenes Spiel."
},
"LABYRINTH": {
"TITLE": "Labyrinth-Erzeugung",
"DESCRIPTION": "Visualisierung verschiedener Laybrinth-Erzeugungs-Algorithmen."
},
"NOTE": "HINWEIS",
"GRID_HEIGHT": "Höhe",
"GRID_WIDTH": "Beite"

View File

@@ -361,6 +361,19 @@
"DISCLAIMER_4": "A dead cell remains dead if it does not have exactly three living neighbors."
}
},
"LABYRINTH": {
"TITLE": "Labyrinth Generation",
"EXPLANATION": {
"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.",
"KRUSKAL_EXPLANATION": "considers all walls of the grid as potential paths. It randomly selects walls and opens them only if the two adjacent cells are not yet connected (preventing cycles). Advantage: Produces a very complex labyrinth with many long, winding paths. Visually, it is engaging because the labyrinth emerges simultaneously in many places and eventually merges into a whole.",
"DISCLAIMER": "Both algorithms are based on the principle of the 'Minimum Spanning Tree'. This means for your labyrinth:",
"DISCLAIMER_1": "Perfect labyrinth: There are no closed loops every point is reachable, but there is always exactly one path between any two points.",
"DISCLAIMER_2": "Reachability: Since it is a spanning tree, every cell in the grid is guaranteed to be part of the labyrinth; there are no isolated areas.",
"DISCLAIMER_3": "Randomness: By weighting the edges with random values, each run produces completely new, unique structures.",
"DISCLAIMER_4": "Application: Such labyrinths are the perfect test environment for pathfinding algorithms such as Dijkstra or A*."
}
},
"ALGORITHM": {
"TITLE": "Algorithms",
"PATHFINDING": {
@@ -375,6 +388,10 @@
"TITLE": "Conway's Game of Life",
"DESCRIPTION": "The Game of Life is a cellular automaton devised by the British mathematician John Horton Conway in 1970."
},
"LABYRINTH": {
"TITLE": "Maze Generation",
"DESCRIPTION": "Visualizing various maze generation algorithms."
},
"NOTE": "Note",
"GRID_HEIGHT": "Height",
"GRID_WIDTH": "Width"