Lots of technical improvements
This commit is contained in:
1 parent
102cdfcb52
commit
a26443af8f
26 files changed
+1864
-430
No files matched your search
@@ -0,0 +1,121 @@
|
||||
import { PathfindingService } from './pathfinding.service';
|
||||
import { Node } from '../pathfinding.models';
|
||||
|
||||
function createNode(row: number, col: number): Node {
|
||||
return {
|
||||
row,
|
||||
col,
|
||||
isStart: false,
|
||||
isEnd: false,
|
||||
isWall: false,
|
||||
isVisited: false,
|
||||
isPath: false,
|
||||
nodeData: Infinity,
|
||||
linkedNode: null,
|
||||
fScore: Infinity,
|
||||
hScore: Infinity,
|
||||
};
|
||||
}
|
||||
|
||||
function createGrid(rows: number, cols: number): Node[][] {
|
||||
const grid: Node[][] = [];
|
||||
for (let row = 0; row < rows; row++) {
|
||||
const currentRow: Node[] = [];
|
||||
for (let col = 0; col < cols; col++) {
|
||||
currentRow.push(createNode(row, col));
|
||||
}
|
||||
grid.push(currentRow);
|
||||
}
|
||||
return grid;
|
||||
}
|
||||
|
||||
describe('PathfindingService', () => {
|
||||
let service: PathfindingService;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new PathfindingService();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('getUnvisitedNeighbors', () => {
|
||||
it('returns the four orthogonal neighbors for an inner node', () => {
|
||||
const grid = createGrid(3, 3);
|
||||
const neighbors = service.getUnvisitedNeighbors(grid[1][1], grid);
|
||||
|
||||
expect(neighbors.length).toBe(4);
|
||||
});
|
||||
|
||||
it('excludes walls and already visited nodes', () => {
|
||||
const grid = createGrid(3, 3);
|
||||
grid[0][1].isWall = true;
|
||||
grid[1][0].isVisited = true;
|
||||
const neighbors = service.getUnvisitedNeighbors(grid[1][1], grid);
|
||||
|
||||
expect(neighbors.length).toBe(2);
|
||||
});
|
||||
|
||||
it('returns only in-bounds neighbors for a corner node', () => {
|
||||
const grid = createGrid(3, 3);
|
||||
const neighbors = service.getUnvisitedNeighbors(grid[0][0], grid);
|
||||
|
||||
expect(neighbors.length).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getNodesInShortestPath', () => {
|
||||
it('reconstructs the path by following linkedNode references', () => {
|
||||
const grid = createGrid(1, 3);
|
||||
grid[0][1].linkedNode = grid[0][0];
|
||||
grid[0][2].linkedNode = grid[0][1];
|
||||
const path = service.getNodesInShortestPath(grid[0][2]);
|
||||
|
||||
expect(path).toEqual([grid[0][0], grid[0][1], grid[0][2]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dijkstra', () => {
|
||||
it('finds the shortest path on an open grid', () => {
|
||||
const grid = createGrid(5, 5);
|
||||
const start = grid[0][0];
|
||||
const end = grid[4][4];
|
||||
|
||||
const { visitedNodesInOrder, nodesInShortestPathOrder } = service.dijkstra(grid, start, end);
|
||||
|
||||
expect(visitedNodesInOrder).toContain(end);
|
||||
// Manhattan distance 8 => 9 nodes including both endpoints.
|
||||
expect(nodesInShortestPathOrder.length).toBe(9);
|
||||
expect(nodesInShortestPathOrder[0]).toBe(start);
|
||||
expect(nodesInShortestPathOrder[nodesInShortestPathOrder.length - 1]).toBe(end);
|
||||
});
|
||||
|
||||
it('returns an empty path when the end node is walled off', () => {
|
||||
const grid = createGrid(3, 3);
|
||||
const start = grid[0][0];
|
||||
const end = grid[2][2];
|
||||
grid[1][2].isWall = true;
|
||||
grid[2][1].isWall = true;
|
||||
|
||||
const { nodesInShortestPathOrder } = service.dijkstra(grid, start, end);
|
||||
|
||||
expect(nodesInShortestPathOrder).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('aStar', () => {
|
||||
it('reaches the end and returns a connected path on an open grid', () => {
|
||||
const grid = createGrid(5, 5);
|
||||
const start = grid[0][0];
|
||||
const end = grid[4][4];
|
||||
|
||||
const { visitedNodesInOrder, nodesInShortestPathOrder } = service.aStar(grid, start, end);
|
||||
|
||||
expect(visitedNodesInOrder).toContain(end);
|
||||
expect(nodesInShortestPathOrder.length).toBeGreaterThan(0);
|
||||
expect(nodesInShortestPathOrder[0]).toBe(start);
|
||||
expect(nodesInShortestPathOrder[nodesInShortestPathOrder.length - 1]).toBe(end);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import { SortingService } from './sorting.service';
|
||||
import { SortData, SortSnapshot } from '../sorting.models';
|
||||
|
||||
function toSortData(values: number[]): SortData[] {
|
||||
return values.map((value) => ({ value, state: 'unsorted' }));
|
||||
}
|
||||
|
||||
function finalValues(snapshots: SortSnapshot[]): number[] {
|
||||
const lastSnapshot = snapshots[snapshots.length - 1];
|
||||
return lastSnapshot.array.map((item) => item.value);
|
||||
}
|
||||
|
||||
function isAscending(values: number[]): boolean {
|
||||
for (let i = 1; i < values.length; i++) {
|
||||
if (values[i - 1] > values[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
describe('SortingService', () => {
|
||||
let service: SortingService;
|
||||
const unsorted = [5, 2, 9, 1, 5, 6, 3, 8, 7, 4];
|
||||
const expectedSorted = [...unsorted].sort((a, b) => a - b);
|
||||
|
||||
beforeEach(() => {
|
||||
service = new SortingService();
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
|
||||
for (const algorithm of [
|
||||
'bubbleSort',
|
||||
'cocktailSort',
|
||||
'quickSort',
|
||||
'heapSort',
|
||||
'timSort',
|
||||
] as const) {
|
||||
it(`${algorithm} should produce a fully sorted final snapshot`, () => {
|
||||
const snapshots = service[algorithm](toSortData(unsorted));
|
||||
const result = finalValues(snapshots);
|
||||
|
||||
expect(result).toEqual(expectedSorted);
|
||||
expect(isAscending(result)).toBeTrue();
|
||||
});
|
||||
|
||||
it(`${algorithm} should not mutate the input array`, () => {
|
||||
const input = toSortData(unsorted);
|
||||
service[algorithm](input);
|
||||
|
||||
expect(input.map((item) => item.value)).toEqual(unsorted);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
<section class="grid gap-fluid-md max-w-app mx-4 mt-auto">
|
||||
<mat-card class="p-fluid-md text-center">
|
||||
<h1 class="m-0 mb-2 text-[clamp(3rem,12vw,6rem)] font-bold opacity-80">404</h1>
|
||||
<h2 class="m-0 mb-4 text-[clamp(1rem,3vw,1.2rem)] font-semibold">{{ 'NOT_FOUND.TITLE' | translate }}</h2>
|
||||
<p class="opacity-80 mb-6">{{ 'NOT_FOUND.MESSAGE' | translate }}</p>
|
||||
<a mat-flat-button color="primary" [routerLink]="RouterConstants.ABOUT.LINK">
|
||||
{{ 'NOT_FOUND.BACK_HOME' | translate }}
|
||||
</a>
|
||||
</mat-card>
|
||||
</section>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterLink } from '@angular/router';
|
||||
import { MatCard } from '@angular/material/card';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { TranslatePipe } from '@ngx-translate/core';
|
||||
import { RouterConstants } from '../../constants/RouterConstants';
|
||||
|
||||
@Component({
|
||||
selector: 'app-not-found',
|
||||
imports: [RouterLink, MatCard, MatButton, TranslatePipe],
|
||||
templateUrl: './not-found.component.html',
|
||||
})
|
||||
export class NotFoundComponent {
|
||||
protected readonly RouterConstants = RouterConstants;
|
||||
}
|
||||
Reference in new issue
Block a user