Add ESLint integration and Angular linting support
Configured ESLint for the project with Angular and TypeScript support. Added angular-eslint dependencies, updated angular.json to include linting, and created eslint.config.js for lint rules. Updated package.json and package-lock.json with new dev dependencies.
This commit is contained in:
14
angular.json
14
angular.json
@@ -3,7 +3,10 @@
|
|||||||
"version": 1,
|
"version": 1,
|
||||||
"cli": {
|
"cli": {
|
||||||
"packageManager": "npm",
|
"packageManager": "npm",
|
||||||
"analytics": false
|
"analytics": false,
|
||||||
|
"schematicCollections": [
|
||||||
|
"angular-eslint"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"newProjectRoot": "projects",
|
"newProjectRoot": "projects",
|
||||||
"projects": {
|
"projects": {
|
||||||
@@ -95,6 +98,15 @@
|
|||||||
"src/styles.scss"
|
"src/styles.scss"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-eslint/builder:lint",
|
||||||
|
"options": {
|
||||||
|
"lintFilePatterns": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.html"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
44
eslint.config.js
Normal file
44
eslint.config.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// @ts-check
|
||||||
|
const eslint = require("@eslint/js");
|
||||||
|
const { defineConfig } = require("eslint/config");
|
||||||
|
const tseslint = require("typescript-eslint");
|
||||||
|
const angular = require("angular-eslint");
|
||||||
|
|
||||||
|
module.exports = defineConfig([
|
||||||
|
{
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
extends: [
|
||||||
|
eslint.configs.recommended,
|
||||||
|
tseslint.configs.recommended,
|
||||||
|
tseslint.configs.stylistic,
|
||||||
|
angular.configs.tsRecommended,
|
||||||
|
],
|
||||||
|
processor: angular.processInlineTemplates,
|
||||||
|
rules: {
|
||||||
|
"@angular-eslint/directive-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "attribute",
|
||||||
|
prefix: "app",
|
||||||
|
style: "camelCase",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"@angular-eslint/component-selector": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
prefix: "app",
|
||||||
|
style: "kebab-case",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ["**/*.html"],
|
||||||
|
extends: [
|
||||||
|
angular.configs.templateRecommended,
|
||||||
|
angular.configs.templateAccessibility,
|
||||||
|
],
|
||||||
|
rules: {},
|
||||||
|
}
|
||||||
|
]);
|
||||||
1951
package-lock.json
generated
1951
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -6,7 +6,8 @@
|
|||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test"
|
"test": "ng test",
|
||||||
|
"lint": "ng lint"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -35,7 +36,10 @@
|
|||||||
"@angular/compiler-cli": "~21.1.0",
|
"@angular/compiler-cli": "~21.1.0",
|
||||||
"@angular/platform-browser-dynamic": "~21.1.0",
|
"@angular/platform-browser-dynamic": "~21.1.0",
|
||||||
"@types/jasmine": "~5.1.15",
|
"@types/jasmine": "~5.1.15",
|
||||||
|
"angular-eslint": "21.2.0",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
"jasmine-core": "~6.0.1",
|
"jasmine-core": "~6.0.1",
|
||||||
"typescript": "~5.9.3"
|
"typescript": "~5.9.3",
|
||||||
|
"typescript-eslint": "8.50.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Container,
|
Container,
|
||||||
MoveDirection,
|
|
||||||
OutMode,
|
|
||||||
Engine
|
Engine
|
||||||
} from "@tsparticles/engine";
|
} from "@tsparticles/engine";
|
||||||
import {NgParticlesService, NgxParticlesModule} from "@tsparticles/angular";
|
import {NgParticlesService, NgxParticlesModule} from "@tsparticles/angular";
|
||||||
import {Component} from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import {loadFull} from 'tsparticles';
|
import {loadFull} from 'tsparticles';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -18,6 +16,8 @@ import {loadFull} from 'tsparticles';
|
|||||||
styleUrl: './particles-bg.component.scss',
|
styleUrl: './particles-bg.component.scss',
|
||||||
})
|
})
|
||||||
export class ParticlesBgComponent {
|
export class ParticlesBgComponent {
|
||||||
|
private readonly ngParticlesService = inject(NgParticlesService);
|
||||||
|
|
||||||
id = "tsparticles";
|
id = "tsparticles";
|
||||||
|
|
||||||
/* Starting from 1.19.0 you can use a remote url (AJAX request) to a JSON with the configuration */
|
/* Starting from 1.19.0 you can use a remote url (AJAX request) to a JSON with the configuration */
|
||||||
@@ -92,7 +92,7 @@ export class ParticlesBgComponent {
|
|||||||
detectRetina: true,*/
|
detectRetina: true,*/
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(private readonly ngParticlesService: NgParticlesService) {}
|
|
||||||
async particlesInit(engine: Engine): Promise<void> {
|
async particlesInit(engine: Engine): Promise<void> {
|
||||||
await loadFull(engine);
|
await loadFull(engine);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@@ -63,7 +63,10 @@ import { MatIconModule } from '@angular/material/icon';
|
|||||||
`],
|
`],
|
||||||
})
|
})
|
||||||
export class ImageDialogComponent {
|
export class ImageDialogComponent {
|
||||||
constructor(@Inject(MAT_DIALOG_DATA) public data: { title: string; src: string }) {
|
data = inject<{
|
||||||
console.log(data.title);
|
title: string;
|
||||||
}
|
src: string;
|
||||||
|
}>(MAT_DIALOG_DATA);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>Algorithmen</h1>
|
<h1>Algorithmen</h1>
|
||||||
<div class="category-cards">
|
<div class="category-cards">
|
||||||
<mat-card *ngFor="let category of categories$ | async" [routerLink]="[category.routerLink]">
|
@for (category of categories$ | async; track category.id) {
|
||||||
|
<mat-card [routerLink]="[category.routerLink]">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ category.title }}</mat-card-title>
|
<mat-card-title>{{ category.title }}</mat-card-title>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
@@ -9,5 +10,6 @@
|
|||||||
<p>{{ category.description }}</p>
|
<p>{{ category.description }}</p>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, inject } from '@angular/core';
|
||||||
import { AlgorithmsService } from './service/algorithms.service';
|
import { AlgorithmsService } from './service/algorithms.service';
|
||||||
import { AlgorithmCategory } from './models/algorithm-category';
|
import { AlgorithmCategory } from './models/algorithm-category';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
@@ -14,11 +14,12 @@ import { MatCardModule } from '@angular/material/card';
|
|||||||
imports: [CommonModule, RouterLink, MatCardModule],
|
imports: [CommonModule, RouterLink, MatCardModule],
|
||||||
})
|
})
|
||||||
export class AlgorithmsComponent implements OnInit {
|
export class AlgorithmsComponent implements OnInit {
|
||||||
|
private algorithmsService = inject(AlgorithmsService);
|
||||||
|
|
||||||
|
|
||||||
categories$: Observable<AlgorithmCategory[]> | undefined;
|
categories$: Observable<AlgorithmCategory[]> | undefined;
|
||||||
|
|
||||||
constructor(private algorithmsService: AlgorithmsService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.categories$ = this.algorithmsService.getCategories();
|
this.categories$ = this.algorithmsService.getCategories();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
<button mat-raised-button color="primary" (click)="visualizeDijkstra()">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
<button mat-raised-button color="primary" (click)="visualizeDijkstra()">{{ 'PATHFINDING.DIJKSTRA' | translate }}</button>
|
||||||
<button mat-raised-button color="accent" (click)="visualizeAStar()">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
<button mat-raised-button color="accent" (click)="visualizeAStar()">{{ 'PATHFINDING.ASTAR' | translate }}</button>
|
||||||
|
<button mat-raised-button color="warn" (click)="resetBoard()">{{ 'PATHFINDING.RESET_BOARD' | translate }}</button>
|
||||||
<button mat-raised-button color="warn" (click)="clearBoard()">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
|
<button mat-raised-button color="warn" (click)="clearBoard()">{{ 'PATHFINDING.CLEAR_BOARD' | translate }}</button>
|
||||||
<button mat-raised-button color="info" (click)="clearPath()">{{ 'PATHFINDING.CLEAR_PATH' | translate }}</button>
|
<button mat-raised-button color="info" (click)="clearPath()">{{ 'PATHFINDING.CLEAR_PATH' | translate }}</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,4 +27,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<canvas #gridCanvas></canvas>
|
<canvas #gridCanvas></canvas>
|
||||||
|
|
||||||
|
<div class="results-container">
|
||||||
|
<p>{{ 'PATHFINDING.PATH_LENGTH' | translate }}: {{ pathLength }}</p>
|
||||||
|
<p>{{ 'PATHFINDING.EXECUTION_TIME' | translate }}: {{ executionTime | number:'1.2-2' }} ms</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {AfterViewInit, Component, ElementRef, HostListener, ViewChild} from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, ViewChild, inject } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import {GRID_COLS, GRID_ROWS, NODE_SIZE, Node} from './pathfinding.models';
|
import {GRID_COLS, GRID_ROWS, NODE_SIZE, Node} from './pathfinding.models';
|
||||||
@@ -23,6 +23,11 @@ enum NodeType {
|
|||||||
styleUrls: ['./pathfinding.component.scss']
|
styleUrls: ['./pathfinding.component.scss']
|
||||||
})
|
})
|
||||||
export class PathfindingComponent implements AfterViewInit {
|
export class PathfindingComponent implements AfterViewInit {
|
||||||
|
private readonly pathfindingService = inject(PathfindingService);
|
||||||
|
private readonly translate = inject(TranslateService);
|
||||||
|
private lastRow = -1;
|
||||||
|
private lastCol = -1;
|
||||||
|
private timeoutIds: any[] = [];
|
||||||
|
|
||||||
@ViewChild('gridCanvas', { static: true })
|
@ViewChild('gridCanvas', { static: true })
|
||||||
canvas!: ElementRef<HTMLCanvasElement>;
|
canvas!: ElementRef<HTMLCanvasElement>;
|
||||||
@@ -32,21 +37,21 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
startNode: Node | null = null;
|
startNode: Node | null = null;
|
||||||
endNode: Node | null = null;
|
endNode: Node | null = null;
|
||||||
|
|
||||||
isDrawing: boolean = false;
|
isDrawing = false;
|
||||||
|
shouldAddWall = true;
|
||||||
selectedNodeType: NodeType = NodeType.None; // Default to no selection
|
selectedNodeType: NodeType = NodeType.None; // Default to no selection
|
||||||
animationSpeed: number = 1; // milliseconds
|
animationSpeed = 3; // milliseconds
|
||||||
|
pathLength = 0;
|
||||||
|
executionTime = 0;
|
||||||
|
|
||||||
readonly NodeType = NodeType; // Expose enum to template
|
|
||||||
|
|
||||||
constructor(private pathfindingService: PathfindingService,
|
readonly NodeType = NodeType;
|
||||||
private translate: TranslateService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.ctx = this.canvas.nativeElement.getContext('2d') as CanvasRenderingContext2D;
|
this.ctx = this.canvas.nativeElement.getContext('2d') as CanvasRenderingContext2D;
|
||||||
this.canvas.nativeElement.width = GRID_COLS * NODE_SIZE;
|
this.canvas.nativeElement.width = GRID_COLS * NODE_SIZE;
|
||||||
this.canvas.nativeElement.height = GRID_ROWS * NODE_SIZE;
|
this.canvas.nativeElement.height = GRID_ROWS * NODE_SIZE;
|
||||||
this.initializeGrid();
|
this.initializeGrid(true);
|
||||||
this.drawGrid();
|
this.drawGrid();
|
||||||
|
|
||||||
// Add event listeners for mouse interactions
|
// Add event listeners for mouse interactions
|
||||||
@@ -56,7 +61,7 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
this.canvas.nativeElement.addEventListener('mouseleave', this.onMouseUp.bind(this)); // Stop drawing if mouse leaves canvas
|
this.canvas.nativeElement.addEventListener('mouseleave', this.onMouseUp.bind(this)); // Stop drawing if mouse leaves canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeGrid(): void {
|
initializeGrid(withWalls: boolean): void {
|
||||||
this.grid = [];
|
this.grid = [];
|
||||||
for (let row = 0; row < GRID_ROWS; row++) {
|
for (let row = 0; row < GRID_ROWS; row++) {
|
||||||
const currentRow: Node[] = [];
|
const currentRow: Node[] = [];
|
||||||
@@ -78,10 +83,24 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set default start and end nodes
|
// Set default start and end nodes
|
||||||
this.startNode = this.grid[Math.floor(GRID_ROWS / 2)][Math.floor(GRID_COLS / 4)];
|
this.startNode = this.grid[0][Math.floor(GRID_COLS / 2)];
|
||||||
this.startNode.isStart = true;
|
this.startNode.isStart = true;
|
||||||
this.endNode = this.grid[Math.floor(GRID_ROWS / 2)][Math.floor(3 * GRID_COLS / 4)];
|
this.endNode = this.grid[this.grid.length-1][Math.floor(GRID_COLS / 2)];
|
||||||
this.endNode.isEnd = true;
|
this.endNode.isEnd = true;
|
||||||
|
|
||||||
|
if (withWalls)
|
||||||
|
{
|
||||||
|
//setting walls
|
||||||
|
let offset = Math.floor(GRID_COLS / 4);
|
||||||
|
for (let startWall = 0; startWall < Math.floor(GRID_COLS /2 ); startWall++){
|
||||||
|
this.grid[Math.floor(GRID_ROWS / 2)][offset + startWall].isWall = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopAnimations(): void {
|
||||||
|
this.timeoutIds.forEach((id) => clearTimeout(id));
|
||||||
|
this.timeoutIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
drawGrid(): void {
|
drawGrid(): void {
|
||||||
@@ -117,6 +136,13 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown(event: MouseEvent): void {
|
onMouseDown(event: MouseEvent): void {
|
||||||
|
const { row, col } = this.getGridPosition(event);
|
||||||
|
|
||||||
|
if (this.isValidPosition(row, col)) {
|
||||||
|
const node = this.grid[row][col];
|
||||||
|
this.shouldAddWall = !node.isWall;
|
||||||
|
}
|
||||||
|
|
||||||
this.isDrawing = true;
|
this.isDrawing = true;
|
||||||
this.placeNode(event);
|
this.placeNode(event);
|
||||||
}
|
}
|
||||||
@@ -127,8 +153,25 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp(event: MouseEvent): void {
|
getGridPosition(event: MouseEvent): { row: number, col: number } {
|
||||||
|
const rect = this.canvas.nativeElement.getBoundingClientRect();
|
||||||
|
const x = event.clientX - rect.left;
|
||||||
|
const y = event.clientY - rect.top;
|
||||||
|
|
||||||
|
const col = Math.floor(x / NODE_SIZE);
|
||||||
|
const row = Math.floor(y / NODE_SIZE);
|
||||||
|
|
||||||
|
return { row, col };
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidPosition(row: number, col: number): boolean {
|
||||||
|
return row >= 0 && row < GRID_ROWS && col >= 0 && col < GRID_COLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseUp(): void {
|
||||||
this.isDrawing = false;
|
this.isDrawing = false;
|
||||||
|
this.lastRow = -1;
|
||||||
|
this.lastCol = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
placeNode(event: MouseEvent): void {
|
placeNode(event: MouseEvent): void {
|
||||||
@@ -143,6 +186,12 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.lastRow === row && this.lastCol === col) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lastRow = row;
|
||||||
|
this.lastCol = col;
|
||||||
|
|
||||||
const node = this.grid[row][col];
|
const node = this.grid[row][col];
|
||||||
|
|
||||||
switch (this.selectedNodeType) {
|
switch (this.selectedNodeType) {
|
||||||
@@ -150,27 +199,33 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
if (!node.isEnd && !node.isWall) {
|
if (!node.isEnd && !node.isWall) {
|
||||||
if (this.startNode) {
|
if (this.startNode) {
|
||||||
this.startNode.isStart = false;
|
this.startNode.isStart = false;
|
||||||
|
this.drawNode(this.startNode);
|
||||||
}
|
}
|
||||||
node.isStart = true;
|
node.isStart = true;
|
||||||
this.startNode = node;
|
this.startNode = node;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NodeType.End:
|
case NodeType.End:
|
||||||
if (!node.isStart && !node.isWall) {
|
if (!node.isStart && !node.isWall) {
|
||||||
if (this.endNode) {
|
if (this.endNode) {
|
||||||
this.endNode.isEnd = false;
|
this.endNode.isEnd = false;
|
||||||
|
this.drawNode(this.endNode);
|
||||||
}
|
}
|
||||||
node.isEnd = true;
|
node.isEnd = true;
|
||||||
this.endNode = node;
|
this.endNode = node;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NodeType.Wall:
|
case NodeType.Wall:
|
||||||
if (!node.isStart && !node.isEnd) {
|
if (!node.isStart && !node.isEnd) {
|
||||||
node.isWall = !node.isWall;
|
if (node.isWall !== this.shouldAddWall) {
|
||||||
|
node.isWall = this.shouldAddWall;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case NodeType.None:
|
case NodeType.None:
|
||||||
// Clear a node
|
|
||||||
if (node.isStart) {
|
if (node.isStart) {
|
||||||
node.isStart = false;
|
node.isStart = false;
|
||||||
this.startNode = null;
|
this.startNode = null;
|
||||||
@@ -182,71 +237,110 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.drawGrid();
|
|
||||||
|
this.drawNode(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
visualizeDijkstra(): void {
|
visualizeDijkstra(): void {
|
||||||
|
this.stopAnimations();
|
||||||
if (!this.startNode || !this.endNode) {
|
if (!this.startNode || !this.endNode) {
|
||||||
alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES'));
|
alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.clearPath();
|
this.clearPath();
|
||||||
|
const startTime = performance.now();
|
||||||
const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.dijkstra(this.grid,
|
const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.dijkstra(this.grid,
|
||||||
this.grid[this.startNode.row][this.startNode.col],
|
this.grid[this.startNode.row][this.startNode.col],
|
||||||
this.grid[this.endNode.row][this.endNode.col]
|
this.grid[this.endNode.row][this.endNode.col]
|
||||||
);
|
);
|
||||||
|
const endTime = performance.now();
|
||||||
|
this.pathLength = nodesInShortestPathOrder.length;
|
||||||
|
this.executionTime = endTime - startTime;
|
||||||
this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder);
|
this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
visualizeAStar(): void {
|
visualizeAStar(): void {
|
||||||
|
this.stopAnimations();
|
||||||
if (!this.startNode || !this.endNode) {
|
if (!this.startNode || !this.endNode) {
|
||||||
alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES'));
|
alert(this.translate.instant('PATHFINDING.ALERT.START_END_NODES'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.clearPath();
|
this.clearPath();
|
||||||
|
const startTime = performance.now();
|
||||||
const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.aStar(this.grid,
|
const { visitedNodesInOrder, nodesInShortestPathOrder } = this.pathfindingService.aStar(this.grid,
|
||||||
this.grid[this.startNode.row][this.startNode.col],
|
this.grid[this.startNode.row][this.startNode.col],
|
||||||
this.grid[this.endNode.row][this.endNode.col]
|
this.grid[this.endNode.row][this.endNode.col]
|
||||||
);
|
);
|
||||||
|
const endTime = performance.now();
|
||||||
|
this.pathLength = nodesInShortestPathOrder.length;
|
||||||
|
this.executionTime = endTime - startTime;
|
||||||
this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder);
|
this.animateAlgorithm(visitedNodesInOrder, nodesInShortestPathOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
animateAlgorithm(visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[]): void {
|
animateAlgorithm(visitedNodesInOrder: Node[], nodesInShortestPathOrder: Node[]): void {
|
||||||
for (let i = 0; i <= visitedNodesInOrder.length; i++) {
|
for (let i = 0; i <= visitedNodesInOrder.length; i++) {
|
||||||
if (i === visitedNodesInOrder.length) {
|
if (i === visitedNodesInOrder.length) {
|
||||||
setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
this.animateShortestPath(nodesInShortestPathOrder);
|
this.animateShortestPath(nodesInShortestPathOrder);
|
||||||
}, this.animationSpeed * i);
|
}, this.animationSpeed * i);
|
||||||
|
this.timeoutIds.push(timeoutId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = visitedNodesInOrder[i];
|
const node = visitedNodesInOrder[i];
|
||||||
setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
if (!node.isStart && !node.isEnd) {
|
if (!node.isStart && !node.isEnd) {
|
||||||
node.isVisited = true;
|
node.isVisited = true;
|
||||||
this.drawGrid();
|
this.drawNode(node);
|
||||||
}
|
}
|
||||||
}, this.animationSpeed * i);
|
}, this.animationSpeed * i);
|
||||||
|
this.timeoutIds.push(timeoutId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
animateShortestPath(nodesInShortestPathOrder: Node[]): void {
|
animateShortestPath(nodesInShortestPathOrder: Node[]): void {
|
||||||
for (let i = 0; i < nodesInShortestPathOrder.length; i++) {
|
for (let i = 0; i < nodesInShortestPathOrder.length; i++) {
|
||||||
const node = nodesInShortestPathOrder[i];
|
const node = nodesInShortestPathOrder[i];
|
||||||
setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
if (!node.isStart && !node.isEnd) {
|
if (!node.isStart && !node.isEnd) {
|
||||||
node.isPath = true;
|
node.isPath = true;
|
||||||
this.drawGrid();
|
this.drawNode(node);
|
||||||
}
|
}
|
||||||
}, 50 * i); // Speed up path animation
|
}, this.animationSpeed * i);
|
||||||
|
this.timeoutIds.push(timeoutId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawNode(node: Node): void {
|
||||||
|
if (!this.ctx) return;
|
||||||
|
|
||||||
|
let color = 'lightgray';
|
||||||
|
if (node.isStart) color = 'green';
|
||||||
|
else if (node.isEnd) color = 'red';
|
||||||
|
else if (node.isPath) color = 'gold';
|
||||||
|
else if (node.isVisited) color = 'skyblue';
|
||||||
|
else if (node.isWall) color = 'black';
|
||||||
|
|
||||||
|
this.ctx.fillStyle = color;
|
||||||
|
this.ctx.fillRect(node.col * NODE_SIZE, node.row * NODE_SIZE, NODE_SIZE, NODE_SIZE);
|
||||||
|
this.ctx.strokeStyle = '#ccc';
|
||||||
|
this.ctx.strokeRect(node.col * NODE_SIZE, node.row * NODE_SIZE, NODE_SIZE, NODE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
resetBoard(): void {
|
||||||
|
this.stopAnimations();
|
||||||
|
this.initializeGrid(true);
|
||||||
|
this.drawGrid();
|
||||||
|
}
|
||||||
|
|
||||||
clearBoard(): void {
|
clearBoard(): void {
|
||||||
this.initializeGrid();
|
this.stopAnimations();
|
||||||
|
this.initializeGrid(false);
|
||||||
this.drawGrid();
|
this.drawGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearPath(): void {
|
clearPath(): void {
|
||||||
|
this.stopAnimations();
|
||||||
for (let row = 0; row < GRID_ROWS; row++) {
|
for (let row = 0; row < GRID_ROWS; row++) {
|
||||||
for (let col = 0; col < GRID_COLS; col++) {
|
for (let col = 0; col < GRID_COLS; col++) {
|
||||||
const node = this.grid[row][col];
|
const node = this.grid[row][col];
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ export interface Node {
|
|||||||
fScore: number;
|
fScore: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GRID_ROWS = 25;
|
export const GRID_ROWS = 150;
|
||||||
export const GRID_COLS = 50;
|
export const GRID_COLS = 100;
|
||||||
export const NODE_SIZE = 20; // in pixels
|
export const NODE_SIZE = 10; // in pixels
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Node, GRID_ROWS, GRID_COLS } from '../pathfinding.models';
|
|||||||
})
|
})
|
||||||
export class PathfindingService {
|
export class PathfindingService {
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
// Helper function to get all unvisited neighbors of a given node
|
// Helper function to get all unvisited neighbors of a given node
|
||||||
getUnvisitedNeighbors(node: Node, grid: Node[][]): Node[] {
|
getUnvisitedNeighbors(node: Node, grid: Node[][]): Node[] {
|
||||||
@@ -38,7 +38,7 @@ export class PathfindingService {
|
|||||||
startNode.distance = 0;
|
startNode.distance = 0;
|
||||||
const unvisitedNodes: Node[] = this.getAllNodes(grid);
|
const unvisitedNodes: Node[] = this.getAllNodes(grid);
|
||||||
|
|
||||||
while (!!unvisitedNodes.length) {
|
while (unvisitedNodes.length > 0) {
|
||||||
this.sortNodesByDistance(unvisitedNodes);
|
this.sortNodesByDistance(unvisitedNodes);
|
||||||
const closestNode = unvisitedNodes.shift() as Node;
|
const closestNode = unvisitedNodes.shift() as Node;
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ export class LanguageService {
|
|||||||
use(l: Lang) {
|
use(l: Lang) {
|
||||||
this.lang.set(l);
|
this.lang.set(l);
|
||||||
this.translate.use(l);
|
this.translate.use(l);
|
||||||
try { localStorage.setItem(LocalStoreConstants.LANGUAGE_KEY, l); } catch {}
|
try { localStorage.setItem(LocalStoreConstants.LANGUAGE_KEY, l); } catch (e) { void e; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private getInitial(): Lang {
|
private getInitial(): Lang {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(LocalStoreConstants.LANGUAGE_KEY) as Lang | null;
|
const stored = localStorage.getItem(LocalStoreConstants.LANGUAGE_KEY) as Lang | null;
|
||||||
if (stored === 'de' || stored === 'en') return stored;
|
if (stored === 'de' || stored === 'en') return stored;
|
||||||
} catch {}
|
} catch (e) { void e; }
|
||||||
const browser = (navigator.language || 'en').toLowerCase();
|
const browser = (navigator.language || 'en').toLowerCase();
|
||||||
return browser.startsWith('de') ? 'de' : 'en';
|
return browser.startsWith('de') ? 'de' : 'en';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,13 +10,7 @@ export class ReloadService {
|
|||||||
private readonly _languageChangedTick = signal(0);
|
private readonly _languageChangedTick = signal(0);
|
||||||
readonly languageChangedTick = this._languageChangedTick.asReadonly();
|
readonly languageChangedTick = this._languageChangedTick.asReadonly();
|
||||||
|
|
||||||
constructor(zone: NgZone) {
|
|
||||||
zone.runOutsideAngular(() => {
|
|
||||||
globalThis.addEventListener('storage', (e: StorageEvent) => {
|
|
||||||
this.informListeners(e, zone);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private informListeners(e: StorageEvent, zone: NgZone) {
|
private informListeners(e: StorageEvent, zone: NgZone) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class ThemeService {
|
|||||||
body.classList.toggle('dark', isDark);
|
body.classList.toggle('dark', isDark);
|
||||||
overlayEl.classList.toggle('dark', isDark);
|
overlayEl.classList.toggle('dark', isDark);
|
||||||
|
|
||||||
try { localStorage.setItem(LocalStoreConstants.THEME_KEY, this.theme()); } catch {}
|
try { localStorage.setItem(LocalStoreConstants.THEME_KEY, this.theme()); } catch (e) { void e; }
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -29,7 +29,7 @@ export class ThemeService {
|
|||||||
const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null;
|
const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null;
|
||||||
if (!stored) this.setTheme(e.matches ? 'dark' : 'light');
|
if (!stored) this.setTheme(e.matches ? 'dark' : 'light');
|
||||||
});
|
});
|
||||||
} catch {}
|
} catch (e) { void e; }
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle() { this.setTheme(this.theme() === 'dark' ? 'light' : 'dark'); }
|
toggle() { this.setTheme(this.theme() === 'dark' ? 'light' : 'dark'); }
|
||||||
@@ -39,10 +39,10 @@ export class ThemeService {
|
|||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null;
|
const stored = localStorage.getItem(LocalStoreConstants.THEME_KEY) as Theme | null;
|
||||||
if (stored === 'dark' || stored === 'light') return stored;
|
if (stored === 'dark' || stored === 'light') return stored;
|
||||||
} catch {}
|
} catch (e) { void e; }
|
||||||
try {
|
try {
|
||||||
return globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
return globalThis.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||||
} catch {}
|
} catch (e) { void e; }
|
||||||
return 'light';
|
return 'light';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,10 +301,13 @@
|
|||||||
"CLEAR_NODE": "Löschen",
|
"CLEAR_NODE": "Löschen",
|
||||||
"DIJKSTRA": "Dijkstra",
|
"DIJKSTRA": "Dijkstra",
|
||||||
"ASTAR": "A*",
|
"ASTAR": "A*",
|
||||||
|
"RESET_BOARD": "Board zurücksetzten",
|
||||||
"CLEAR_BOARD": "Board leeren",
|
"CLEAR_BOARD": "Board leeren",
|
||||||
"CLEAR_PATH": "Pfad löschen",
|
"CLEAR_PATH": "Pfad löschen",
|
||||||
"VISITED": "Besucht",
|
"VISITED": "Besucht",
|
||||||
"PATH": "Pfad",
|
"PATH": "Pfad",
|
||||||
|
"PATH_LENGTH": "Pfadlänge",
|
||||||
|
"EXECUTION_TIME": "Ausführungszeit",
|
||||||
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -301,10 +301,13 @@
|
|||||||
"CLEAR_NODE": "Clear",
|
"CLEAR_NODE": "Clear",
|
||||||
"DIJKSTRA": "Dijkstra",
|
"DIJKSTRA": "Dijkstra",
|
||||||
"ASTAR": "A*",
|
"ASTAR": "A*",
|
||||||
|
"RESET_BOARD": "Reset Board",
|
||||||
"CLEAR_BOARD": "Clear Board",
|
"CLEAR_BOARD": "Clear Board",
|
||||||
"CLEAR_PATH": "Clear Path",
|
"CLEAR_PATH": "Clear Path",
|
||||||
"VISITED": "Visited",
|
"VISITED": "Visited",
|
||||||
"PATH": "Path",
|
"PATH": "Path",
|
||||||
|
"PATH_LENGTH": "Path length",
|
||||||
|
"EXECUTION_TIME": "Execution Time",
|
||||||
"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."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user