diff --git a/eslint.config.js b/eslint.config.js index c6c231f..7c67f09 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -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", { diff --git a/package.json b/package.json index 2d81c4a..984652d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "playground-frontend", - "version": "0.2.0", + "version": "1.0.0", "scripts": { "ng": "ng", "start": "ng serve", diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 33423b1..d739666 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -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} ]; diff --git a/src/app/constants/RouterConstants.ts b/src/app/constants/RouterConstants.ts index 30122d5..05f9d04 100644 --- a/src/app/constants/RouterConstants.ts +++ b/src/app/constants/RouterConstants.ts @@ -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 = { diff --git a/src/app/constants/UrlConstants.ts b/src/app/constants/UrlConstants.ts index b71c228..87164ee 100644 --- a/src/app/constants/UrlConstants.ts +++ b/src/app/constants/UrlConstants.ts @@ -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' } diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.html b/src/app/pages/algorithms/conway-gol/conway-gol.component.html similarity index 100% rename from src/app/pages/algorithms/conway-gol/conway-gol.html rename to src/app/pages/algorithms/conway-gol/conway-gol.component.html diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.ts b/src/app/pages/algorithms/conway-gol/conway-gol.component.ts similarity index 98% rename from src/app/pages/algorithms/conway-gol/conway-gol.ts rename to src/app/pages/algorithms/conway-gol/conway-gol.component.ts index 743965b..ea54f8a 100644 --- a/src/app/pages/algorithms/conway-gol/conway-gol.ts +++ b/src/app/pages/algorithms/conway-gol/conway-gol.component.ts @@ -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', diff --git a/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html new file mode 100644 index 0000000..c001fd1 --- /dev/null +++ b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.html @@ -0,0 +1,41 @@ + + + {{ 'LABYRINTH.TITLE' | translate }} + + + +
+
+ + +
+
+ +
+ +
+ {{ 'PATHFINDING.START_NODE' | translate }} + {{ 'PATHFINDING.END_NODE' | translate }} + {{ 'PATHFINDING.WALL' | translate }} + {{ 'PATHFINDING.VISITED' | translate }} + {{ 'PATHFINDING.PATH' | translate }} +
+
+

{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}

+

{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms

+
+
+ +
+
diff --git a/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.scss b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.ts b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.ts new file mode 100644 index 0000000..082d205 --- /dev/null +++ b/src/app/pages/algorithms/pathfinding/labyrinth/labyrinth.component.ts @@ -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: 'Prim’s', + description: 'LABYRINTH.EXPLANATION.PRIM_EXPLANATION', + link: UrlConstants.PRIMS_WIKI + }, + { + name: 'Kruskal’s', + 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; + }; +} diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts index 98e99ca..66a7fe4 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts @@ -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); - } } diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.models.ts b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts index 7d34f94..8776882 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.models.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts @@ -7,7 +7,7 @@ export interface Node { isVisited: boolean; isPath: boolean; distance: number; - previousNode: Node | null; + linkedNode: Node | null; fScore: number; hScore: number; } diff --git a/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts b/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts index 9519c84..063199a 100644 --- a/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts +++ b/src/app/pages/algorithms/pathfinding/service/pathfinding.service.ts @@ -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); diff --git a/src/app/pages/algorithms/service/algorithms.service.ts b/src/app/pages/algorithms/service/algorithms.service.ts index f834c16..8643b1b 100644 --- a/src/app/pages/algorithms/service/algorithms.service.ts +++ b/src/app/pages/algorithms/service/algorithms.service.ts @@ -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 } ]; diff --git a/src/app/shared/SharedFunctions.ts b/src/app/shared/SharedFunctions.ts index c54b1e5..1a4ae7a 100644 --- a/src/app/shared/SharedFunctions.ts +++ b/src/app/shared/SharedFunctions.ts @@ -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; + } } diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 1a69b8f..cc65eed 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -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" diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 8de833b..2597079 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -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"