diff --git a/src/app/pages/about/about.component.html b/src/app/pages/about/about.component.html index 4259910..86ff8c6 100644 --- a/src/app/pages/about/about.component.html +++ b/src/app/pages/about/about.component.html @@ -66,7 +66,7 @@ -

{{ 'ABOUT.SECTION.EXPERIENCE' | translate }}

+

{{ 'ABOUT.SECTION.EXPERIENCE' | translate }}

@for (entry of xpKeys; track entry.key) {
diff --git a/src/app/pages/about/about.component.scss b/src/app/pages/about/about.component.scss index 3957ca7..50e8d79 100644 --- a/src/app/pages/about/about.component.scss +++ b/src/app/pages/about/about.component.scss @@ -47,7 +47,6 @@ /* Skills block */ .skills { padding: 5px; - h2 { margin-top: .25rem; margin-left: .25rem; } .chip-groups { margin-left: .25rem; @@ -64,11 +63,7 @@ /* Experience block */ .experience { padding: 5px; - h2 { margin-top: .25rem;margin-left: .25rem; } - .xp-list { - margin-left: .25rem; - display: grid; gap: .75rem; - } + h2 { margin-top: .25rem; margin-left: .25rem; } .xp-item { .xp-head { display:flex; align-items:baseline; gap:.5rem; diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.html b/src/app/pages/algorithms/pathfinding/pathfinding.component.html index 931dcf3..e6866d4 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.html +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.html @@ -23,13 +23,14 @@
- - + +
- - - + + + +
diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.ts b/src/app/pages/algorithms/pathfinding/pathfinding.component.ts index c6ed385..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,40 +103,32 @@ 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.edgeCase(); + if (skipReset) { + this.initializeGrid({withWalls: true, scenario: 'normal'}); + this.drawGrid(); + return; + } + + this.createCase({withWalls: true, scenario: 'normal'}); } - // Scenarios (buttons) - normalCase(): void { + createCase({withWalls, scenario}: { withWalls: boolean, scenario: "normal" | "edge" | "random" }): void + { this.stopAnimations(); - this.initializeGrid(true, 'normal'); + this.initializeGrid({withWalls, scenario}); this.drawGrid(); } - edgeCase(): void { - this.stopAnimations(); - this.initializeGrid(true, 'edge'); - this.drawGrid(); - } - - clearBoard(): void { - this.stopAnimations(); - this.initializeGrid(false, 'edge'); - this.drawGrid(); - } - - visualizeDijkstra(): void { + visualize(algorithm: string): void { if (!this.ensureStartAndEnd()) { return; } @@ -145,36 +137,38 @@ export class PathfindingComponent implements AfterViewInit { this.clearPath(); const startTime = performance.now(); - const result = this.pathfindingService.dijkstra( - this.grid, - this.grid[this.startNode!.row][this.startNode!.col], - this.grid[this.endNode!.row][this.endNode!.col] - ); - const endTime = performance.now(); + let result; - this.pathLength = result.nodesInShortestPathOrder.length; - this.executionTime = endTime - startTime; + 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; + } - this.animateAlgorithm(result.visitedNodesInOrder, result.nodesInShortestPathOrder); - } - - visualizeAStar(): void { - if (!this.ensureStartAndEnd()) { + if (!result) + { return; } - this.stopAnimations(); - this.clearPath(); - - const startTime = performance.now(); - const result = this.pathfindingService.aStar( - this.grid, - this.grid[this.startNode!.row][this.startNode!.col], - this.grid[this.endNode!.row][this.endNode!.col] - ); 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); @@ -244,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); @@ -255,7 +249,7 @@ export class PathfindingComponent implements AfterViewInit { this.endNode.isEnd = true; if (withWalls) { - this.placeDefaultDiagonalWall(); + this.placeDefaultDiagonalWall(scenario); } } @@ -289,23 +283,105 @@ 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(): 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() { + const height = this.gridRows; + const startCol = Math.floor(this.gridCols / 2); + + for (let i = 5; i < (height - 5); i++) { + const row = i; + + if (!this.isValidPosition(row, startCol)) { + continue; + } + + const node = this.grid[row][startCol]; + if (node.isStart || node.isEnd) { + continue; + } + + node.isWall = true; + } + + } + + private createDiagonalWall() { // Diagonal-ish wall; avoids start/end const len = Math.min(this.gridRows, this.gridCols); const startCol = Math.floor((this.gridCols - len) / 2); @@ -327,7 +403,7 @@ export class PathfindingComponent implements AfterViewInit { } } - // Path state +// Path state private clearPath(): void { for (let row = 0; row < this.gridRows; row++) { for (let col = 0; col < this.gridCols; col++) { @@ -563,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",