Adjusted small things, norhting serious

This commit is contained in:
2026-02-02 10:44:13 +01:00
parent ac937eb2fb
commit 668640dcc6
6 changed files with 46 additions and 21 deletions

View File

@@ -13,9 +13,23 @@
<strong>A*</strong> {{ 'PATHFINDING.EXPLANATION.ASTAR_EXPLANATION' | translate}} <strong>A*</strong> {{ 'PATHFINDING.EXPLANATION.ASTAR_EXPLANATION' | translate}}
<a href="{{UrlConstants.ASTAR_WIKI}}" target="_blank" rel="noopener noreferrer">Wikipedia</a> <a href="{{UrlConstants.ASTAR_WIKI}}" target="_blank" rel="noopener noreferrer">Wikipedia</a>
</p> </p>
<p>
<strong>{{ 'PATHFINDING.EXPLANATION.NOTE' | translate}}</strong> {{ 'PATHFINDING.EXPLANATION.DISCLAIMER' | translate}}
</p>
</div> </div>
<div class="controls-container"> <div class="controls-container">
<div class="controls">
<button matButton="filled" (click)="visualizeDijkstra()">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
<button matButton="filled" (click)="visualizeAStar()">{{ 'PATHFINDING.ASTAR' | translate }}</button>
</div>
<div class="controls">
<button matButton="filled" (click)="normalCase()">{{ 'PATHFINDING.NORMAL_CASE' | translate }}</button>
<button matButton="filled" (click)="edgeCase()">{{ 'PATHFINDING.EDGE_CASE' | translate }}</button>
<button matButton="filled" (click)="clearBoard()">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
</div>
<div class="controls"> <div class="controls">
<mat-button-toggle-group [(ngModel)]="selectedNodeType" aria-label="Node Type Selection"> <mat-button-toggle-group [(ngModel)]="selectedNodeType" aria-label="Node Type Selection">
<mat-button-toggle [value]="NodeType.Start">{{ 'PATHFINDING.START_NODE' | translate }}</mat-button-toggle> <mat-button-toggle [value]="NodeType.Start">{{ 'PATHFINDING.START_NODE' | translate }}</mat-button-toggle>
@@ -25,14 +39,6 @@
</mat-button-toggle-group> </mat-button-toggle-group>
</div> </div>
<div class="controls">
<button matButton="filled" (click)="visualizeDijkstra()">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
<button matButton="filled" (click)="visualizeAStar()">{{ 'PATHFINDING.ASTAR' | translate }}</button>
<button matButton="filled" (click)="normalCase()">{{ 'PATHFINDING.NORMAL_CASE' | translate }}</button>
<button matButton="filled" (click)="edgeCase()">{{ 'PATHFINDING.EDGE_CASE' | translate }}</button>
<button matButton="filled" (click)="clearBoard()">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
</div>
<div class="controls"> <div class="controls">
<div class="grid-size"> <div class="grid-size">
<mat-form-field appearance="outline" class="grid-field"> <mat-form-field appearance="outline" class="grid-field">

View File

@@ -163,6 +163,8 @@ export class PathfindingComponent implements AfterViewInit {
// Mouse interactions // Mouse interactions
private onMouseDown(event: MouseEvent): void { private onMouseDown(event: MouseEvent): void {
this.stopAnimations();
this.clearPath();
const pos = this.getGridPosition(event); const pos = this.getGridPosition(event);
if (!pos) { if (!pos) {
return; return;
@@ -263,7 +265,8 @@ export class PathfindingComponent implements AfterViewInit {
isPath: false, isPath: false,
distance: Infinity, distance: Infinity,
previousNode: null, previousNode: null,
fScore: 0 hScore: 0,
fScore: Infinity,
}; };
} }

View File

@@ -9,6 +9,7 @@ export interface Node {
distance: number; distance: number;
previousNode: Node | null; previousNode: Node | null;
fScore: number; fScore: number;
hScore: number;
} }
export const DEFAULT_GRID_ROWS = 50; export const DEFAULT_GRID_ROWS = 50;

View File

@@ -80,9 +80,9 @@ export class PathfindingService {
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.distance = 0;
startNode['distance'] = this.calculateHeuristic(startNode, endNode); startNode['hScore'] = this.calculateHeuristic(startNode, endNode);
// fScore = gScore + hScore // fScore = gScore + hScore
startNode['fScore'] = startNode.distance + startNode['distance']; startNode['fScore'] = startNode.distance + startNode['hScore'];
const openSet: Node[] = [startNode]; const openSet: Node[] = [startNode];
const allNodes = this.getAllNodes(grid); const allNodes = this.getAllNodes(grid);
@@ -90,7 +90,7 @@ export class PathfindingService {
this.initNodesForAStar(allNodes, startNode); this.initNodesForAStar(allNodes, startNode);
while (openSet.length > 0) { while (openSet.length > 0) {
openSet.sort((nodeA, nodeB) => nodeA['fScore'] - nodeB['fScore']); this.sortOpenSet(openSet);
const currentNode = openSet.shift() as Node; const currentNode = openSet.shift() as Node;
if (currentNode.isWall) { if (currentNode.isWall) {
@@ -125,11 +125,22 @@ export class PathfindingService {
return { visitedNodesInOrder, nodesInShortestPathOrder: [] }; return { visitedNodesInOrder, nodesInShortestPathOrder: [] };
} }
private sortOpenSet(openSet: Node[]) {
openSet.sort((a, b) => {
const f = a['fScore'] - b['fScore'];
if (f !== 0) return f;
// tie-break: smaller heuristic first (more goal-directed)
return (a['hScore'] ?? 0) - (b['hScore'] ?? 0);
});
}
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.previousNode = currentNode; neighbor.previousNode = currentNode;
neighbor.distance = tentativeGScore; neighbor.distance = tentativeGScore;
neighbor['distance'] = this.calculateHeuristic(neighbor, endNode); neighbor['distance'] = this.calculateHeuristic(neighbor, endNode);
neighbor['fScore'] = neighbor.distance + neighbor['distance']; neighbor['hScore'] = this.calculateHeuristic(neighbor, endNode);
neighbor['fScore'] = neighbor.distance + neighbor['hScore'];
if (!openSet.includes(neighbor)) { if (!openSet.includes(neighbor)) {
openSet.push(neighbor); openSet.push(neighbor);

View File

@@ -299,10 +299,10 @@
"END_NODE": "Endknoten", "END_NODE": "Endknoten",
"WALL": "Wand", "WALL": "Wand",
"CLEAR_NODE": "Löschen", "CLEAR_NODE": "Löschen",
"DIJKSTRA": "Dijkstra", "DIJKSTRA": "Start Dijkstra",
"ASTAR": "A*", "ASTAR": "Start A*",
"NORMAL_CASE": "Testaufbau", "NORMAL_CASE": "Testaufbau",
"EDGE_CASE": "A* Grenzfall", "EDGE_CASE": "A* Grenzfall-Aufbau",
"CLEAR_BOARD": "Board leeren", "CLEAR_BOARD": "Board leeren",
"VISITED": "Besucht", "VISITED": "Besucht",
"PATH": "Pfad", "PATH": "Pfad",
@@ -311,7 +311,9 @@
"EXPLANATION": { "EXPLANATION": {
"TITLE": "Algorithmen", "TITLE": "Algorithmen",
"DIJKSTRA_EXPLANATION": " findet garantiert den kürzesten Weg, wenn alle Kantenkosten nicht-negativ sind. Vorteil: optimal und ohne Heuristik. Nachteil: besucht oft sehr viele Knoten (kann bei großen Grids langsamer wirken).", "DIJKSTRA_EXPLANATION": " findet garantiert den kürzesten Weg, wenn alle Kantenkosten nicht-negativ sind. Vorteil: optimal und ohne Heuristik. Nachteil: besucht oft sehr viele Knoten (kann bei großen Grids langsamer wirken).",
"ASTAR_EXPLANATION": " erweitert Dijkstra um eine Heuristik (z.B. Manhattan-Distanz) und kann dadurch wesentlich zielgerichteter suchen. Vorteil: oft deutlich schneller bei guter Heuristik; bei zulässiger Heuristik bleibt der Weg optimal. Nachteil: hängt stark von der Heuristik ab (schlechte Heuristik ≈ Dijkstra)." "ASTAR_EXPLANATION": " erweitert Dijkstra um eine Heuristik (z.B. Manhattan-Distanz) und kann dadurch wesentlich zielgerichteter suchen. Vorteil: oft deutlich schneller bei guter Heuristik; bei zulässiger Heuristik bleibt der Weg optimal. Nachteil: hängt stark von der Heuristik ab (schlechte Heuristik ≈ Dijkstra).",
"NOTE": "HINWEIS",
"DISCLAIMER": "Diese A*-Implementierung ist bewusst einfach gehalten. Es wird nur in vier Richtungen gegangen und jeder Schritt kostet 1. Die Heuristik ist minimal und dient nur dazu, das Prinzip von A* gegenüber Dijkstra zu demonstrieren. Ziel ist nicht ein optimaler oder produktionsreifer A*-Algorithmus, sondern eine anschauliche Visualisierung, wie Heuristiken die Suche beschleunigen können."
}, },
"ALERT": { "ALERT": {
"START_END_NODES": "Bitte wählen Sie einen Start- und Endknoten aus, bevor Sie den Algorithmus starten." "START_END_NODES": "Bitte wählen Sie einen Start- und Endknoten aus, bevor Sie den Algorithmus starten."

View File

@@ -299,10 +299,10 @@
"END_NODE": "End Node", "END_NODE": "End Node",
"WALL": "Wall", "WALL": "Wall",
"CLEAR_NODE": "Clear", "CLEAR_NODE": "Clear",
"DIJKSTRA": "Dijkstra", "DIJKSTRA": "Start Dijkstra",
"ASTAR": "A*", "ASTAR": "Start A*",
"NORMAL_CASE": "Test Scenario", "NORMAL_CASE": "Test Scenario",
"EDGE_CASE": "A* Edge Case", "EDGE_CASE": "A* Edge Case Scenario",
"CLEAR_BOARD": "Clear Board", "CLEAR_BOARD": "Clear Board",
"VISITED": "Visited", "VISITED": "Visited",
"PATH": "Path", "PATH": "Path",
@@ -311,7 +311,9 @@
"EXPLANATION": { "EXPLANATION": {
"TITLE": "Algorithms", "TITLE": "Algorithms",
"DIJKSTRA_EXPLANATION": " is guaranteed to find the shortest path if all edge costs are non-negative. Advantage: optimal and without heuristics. Disadvantage: often visits a large number of nodes (can be slower for large grids).", "DIJKSTRA_EXPLANATION": " is guaranteed to find the shortest path if all edge costs are non-negative. Advantage: optimal and without heuristics. Disadvantage: often visits a large number of nodes (can be slower for large grids).",
"ASTAR_EXPLANATION": " extends Dijkstra with a heuristic (e.g. Manhattan distance) and can therefore search in a much more targeted manner. Advantage: often significantly faster with good heuristics; with permissible heuristics, the path remains optimal. Disadvantage: highly dependent on heuristics (poor heuristics ≈ Dijkstra)." "ASTAR_EXPLANATION": " extends Dijkstra with a heuristic (e.g. Manhattan distance) and can therefore search in a much more targeted manner. Advantage: often significantly faster with good heuristics; with permissible heuristics, the path remains optimal. Disadvantage: highly dependent on heuristics (poor heuristics ≈ Dijkstra).",
"NOTE": "Note",
"DISCLAIMER": "This A* implementation is deliberately kept simple. It only moves in four directions and each step costs 1. The heuristic is minimal and only serves to demonstrate the principle of A* compared to Dijkstra. The goal is not an optimal or production-ready A* algorithm, but a clear visualisation of how heuristics can speed up the search."
}, },
"ALERT": { "ALERT": {
"START_END_NODES": "Please select a start and end node before running the algorithm." "START_END_NODES": "Please select a start and end node before running the algorithm."