diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.html b/src/app/pages/algorithms/pathfinding/pathfinding.component.html index 1c1c6b4..e6866d4 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.html +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.html @@ -27,9 +27,10 @@
- - - + + + +
diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts index fcfd433..9f14bb3 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts @@ -9,7 +9,7 @@ import {MatInputModule} from '@angular/material/input'; import {TranslateModule, TranslateService} from '@ngx-translate/core'; -import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, MAX_GRID_PX, MAX_GRID_SIZE, MIN_GRID_SIZE, Node} from './pathfinding.models'; +import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, MAX_GRID_PX, MAX_GRID_SIZE, MAX_RANDOM_WALLS_FACTORS, MIN_GRID_SIZE, Node} from './pathfinding.models'; import {PathfindingService} from './service/pathfinding.service'; import {UrlConstants} from '../../../constants/UrlConstants'; import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; @@ -70,7 +70,7 @@ export class PathfindingComponent implements AfterViewInit { private shouldAddWall = true; animationSpeed = 3; - pathLength = 0; + pathLength = "0"; executionTime = 0; private timeoutIds: number[] = []; @@ -103,35 +103,28 @@ export class PathfindingComponent implements AfterViewInit { applyGridSize(skipReset?: boolean): void { this.gridRows = this.clampGridSize(this.gridRows, DEFAULT_GRID_ROWS); this.gridCols = this.clampGridSize(this.gridCols, DEFAULT_GRID_COLS); - this.nodeSize = this.computeNodeSize(this.gridRows, this.gridCols); this.resizeCanvas(); - if (skipReset) { - this.initializeGrid(true, 'edge'); + if (this.gridRows === this.grid.length && this.gridCols === this.grid[0].length) + { this.drawGrid(); return; } - // Default after size changes: pick one consistent scenario - this.normalCase(); + if (skipReset) { + this.initializeGrid({withWalls: true, scenario: 'normal'}); + this.drawGrid(); + return; + } + + this.createCase({withWalls: true, scenario: 'normal'}); } - normalCase(): void { + createCase({withWalls, scenario}: { withWalls: boolean, scenario: "normal" | "edge" | "random" }): void + { this.stopAnimations(); - this.initializeGrid(true, 'normal'); - this.drawGrid(); - } - - edgeCase(): void { - this.stopAnimations(); - this.initializeGrid(true, 'edge'); - this.drawGrid(); - } - - clearBoard(): void { - this.stopAnimations(); - this.initializeGrid(false, 'edge'); + this.initializeGrid({withWalls, scenario}); this.drawGrid(); } @@ -167,8 +160,15 @@ export class PathfindingComponent implements AfterViewInit { } const endTime = performance.now(); - - this.pathLength = result.nodesInShortestPathOrder.length; + 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); @@ -238,7 +238,7 @@ export class PathfindingComponent implements AfterViewInit { } // Grid init - private initializeGrid(withWalls: boolean, scenario: 'normal' | 'edge'): void { + private initializeGrid({withWalls, scenario}: { withWalls: boolean, scenario: "normal" | "edge" | "random" }): void { this.grid = this.createEmptyGrid(); const { start, end } = this.getScenarioStartEnd(scenario); @@ -283,29 +283,81 @@ export class PathfindingComponent implements AfterViewInit { }; } - private getScenarioStartEnd(scenario: 'normal' | 'edge'): { start: GridPos; end: GridPos } { + private getScenarioStartEnd(scenario: 'normal' | 'edge' | 'random'): { start: GridPos; end: GridPos } { if (scenario === 'edge') { return { start: { row: 0, col: 0 }, end: { row: this.gridRows - 1, col: this.gridCols - 1 } }; } + else if (scenario === 'random') { + return this.createRandomStartEndPosition(); + } + else { + // normal: mid-left -> mid-right + const midRow = Math.floor(this.gridRows / 2); + return { + start: { row: midRow, col: 0 }, + end: { row: midRow, col: this.gridCols - 1 } + }; + } + } + + private createRandomStartEndPosition() { + 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 endRow: number = this.randomIntFromInterval(0, this.gridRows - 1); + let endCol: number; + + if (startCol <= midCol) { + endCol = this.randomIntFromInterval(midCol + 1, this.gridCols - 1); + } else { + endCol = this.randomIntFromInterval(0, midCol); + } - // normal: mid-left -> mid-right - const midRow = Math.floor(this.gridRows / 2); return { - start: { row: midRow, col: 0 }, - end: { row: midRow, col: this.gridCols - 1 } + start: {row: startRow, col: startCol}, + end: {row: endRow, col: endCol} }; } - private placeDefaultDiagonalWall(scenario: 'normal' | 'edge'): void { + private placeDefaultDiagonalWall(scenario: 'normal' | 'edge' | 'random'): void { if (scenario === 'edge') { this.createDiagonalWall(); } else if (scenario === 'normal') { this.createVerticalWall(); } + else if (scenario === 'random') { + this.createRandomWalls(); + } + } + + private createRandomWalls(){ + const maxNumberOfWalls = Math.floor(MAX_RANDOM_WALLS_FACTORS * this.gridCols * this.gridRows); + + 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); + + if (!this.isValidPosition(row, col)) { + wall--; + continue; + } + + const node = this.grid[row][col]; + if (node.isStart || node.isEnd) { + wall--; + continue; + } + + node.isWall = true; + } + } private createVerticalWall() { @@ -587,5 +639,9 @@ export class PathfindingComponent implements AfterViewInit { return ctx; } + private randomIntFromInterval(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1) + min); + } + protected readonly UrlConstants = UrlConstants; } diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.models.ts b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts index b5fb259..7d34f94 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.models.ts +++ b/src/app/pages/algorithms/pathfinding/pathfinding.models.ts @@ -20,3 +20,4 @@ export const MAX_GRID_SIZE = 150; // Canvas max size (px) export const MAX_GRID_PX = 1000; +export const MAX_RANDOM_WALLS_FACTORS = 0.3; diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 11c0298..72d2b97 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -303,7 +303,8 @@ "ASTAR": "Start A*", "NORMAL_CASE": "Testaufbau", "EDGE_CASE": "A* Grenzfall-Aufbau", - "CLEAR_BOARD": "Board leeren", + "RANDOM_CASE": "Zufälliger-Aufbau", + "CLEAR_BOARD": "Leeres Gitter", "VISITED": "Besucht", "PATH": "Pfad", "PATH_LENGTH": "Pfadlänge", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 7e754c2..181aecf 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -303,7 +303,8 @@ "ASTAR": "Start A*", "NORMAL_CASE": "Test Scenario", "EDGE_CASE": "A* Edge Case Scenario", - "CLEAR_BOARD": "Clear Board", + "RANDOM_CASE": "Random Case", + "CLEAR_BOARD": "Empty Board", "VISITED": "Visited", "PATH": "Path", "PATH_LENGTH": "Path length",