{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms
@@ -90,4 +55,3 @@
-
diff --git a/src/app/pages/algorithms/sorting/sorting.component.scss b/src/app/pages/algorithms/sorting/sorting.component.scss
index b911eac..ce0e7a6 100644
--- a/src/app/pages/algorithms/sorting/sorting.component.scss
+++ b/src/app/pages/algorithms/sorting/sorting.component.scss
@@ -1,11 +1,3 @@
-.sorting-container {
- display: flex;
- justify-content: center;
- align-items: flex-start;
- padding: 20px;
- height: 100%;
- box-sizing: border-box;
-
.sorting-card {
width: 100%;
max-width: 1200px;
@@ -59,4 +51,3 @@
color: #FFFFFF;
}
}
-}
diff --git a/src/app/pages/algorithms/sorting/sorting.component.ts b/src/app/pages/algorithms/sorting/sorting.component.ts
index fac1cff..569c037 100644
--- a/src/app/pages/algorithms/sorting/sorting.component.ts
+++ b/src/app/pages/algorithms/sorting/sorting.component.ts
@@ -11,11 +11,12 @@ import {SortData, SortSnapshot} from './sorting.models';
import { FormsModule } from '@angular/forms';
import {MatInput} from '@angular/material/input';
import {UrlConstants} from '../../../constants/UrlConstants';
-import {MIN} from '@angular/forms/signals';
+import {AlgorithmInformation} from '../information/information.models';
+import {Information} from '../information/information';
@Component({
selector: 'app-sorting',
standalone: true,
- imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslateModule, FormsModule, MatInput],
+ imports: [CommonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatButtonModule, MatIconModule, TranslateModule, FormsModule, MatInput, Information],
templateUrl: './sorting.component.html',
styleUrls: ['./sorting.component.scss']
})
@@ -27,21 +28,46 @@ export class SortingComponent implements OnInit {
readonly MAX_ARRAY_SIZE: number = 200;
readonly MIN_ARRAY_SIZE: number = 20;
+ algoInformation: AlgorithmInformation = {
+ title: 'SORTING.EXPLANATION.TITLE',
+ entries: [
+ {
+ name: 'Bubble Sort',
+ description: 'SORTING.EXPLANATION.BUBBLE_SORT_EXPLANATION',
+ link: UrlConstants.BUBBLE_SORT_WIKI
+ },
+ {
+ name: 'Cocktail Sort',
+ description: 'SORTING.EXPLANATION.COCKTAIL_SORT_EXPLANATION',
+ link: UrlConstants.SHAKE_SORT_WIKI
+ },
+ {
+ name: 'Quick Sort',
+ description: 'SORTING.EXPLANATION.QUICK_SORT_EXPLANATION',
+ link: UrlConstants.QUICK_SORT_WIKI
+ },
+ {
+ name: 'Heap Sort',
+ description: 'SORTING.EXPLANATION.HEAP_SORT_EXPLANATION',
+ link: UrlConstants.HEAP_SORT_WIKI
+ }
+ ],
+ disclaimer: 'SORTING.EXPLANATION.DISCLAIMER',
+ disclaimerBottom: 'SORTING.EXPLANATION.DISCLAIMER_4',
+ disclaimerListEntry: [
+ 'SORTING.EXPLANATION.DISCLAIMER_1',
+ 'SORTING.EXPLANATION.DISCLAIMER_2',
+ 'SORTING.EXPLANATION.DISCLAIMER_3'
+ ]
+ };
+
private timeoutIds: number[] = [];
sortArray: SortData[] = [];
unsortedArrayCopy: SortData[] = [];
arraySize = 50;
maxArrayValue = 100;
animationSpeed = 50; // Milliseconds per step
-
- // Placeholder for available sorting algorithms
- availableAlgorithms: { name: string; value: string }[] = [
- { name: 'Bubble Sort', value: 'bubbleSort' },
- { name: 'Cocktail Sort', value: 'cocktailSort' },
- { name: 'Quick Sort', value: 'quickSort' },
- { name: 'Heap Sort', value: 'heapSort' },
- ];
- selectedAlgorithm: string = this.availableAlgorithms[0].value;
+ selectedAlgorithm: string = this.algoInformation.entries[0].name;
executionTime = 0;
ngOnInit(): void {
@@ -93,22 +119,22 @@ export class SortingComponent implements OnInit {
let snapshots: SortSnapshot[] = [];
switch (this.selectedAlgorithm) {
- case 'bubbleSort':
+ case 'Bubble Sort':
snapshots = this.sortingService.bubbleSort(this.sortArray);
break;
- case 'quickSort':
+ case 'Quick Sort':
snapshots = this.sortingService.quickSort(this.sortArray);
break;
- case 'heapSort':
+ case 'Heap Sort':
snapshots = this.sortingService.heapSort(this.sortArray);
break;
- case 'cocktailSort':
+ case 'Cocktail Sort':
snapshots = this.sortingService.cocktailSort(this.sortArray);
break;
}
const endTime = performance.now();
- this.executionTime = parseFloat((endTime - startTime).toFixed(4));
+ this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));
console.log(snapshots.length);
this.animateSorting(snapshots);
@@ -145,6 +171,4 @@ export class SortingComponent implements OnInit {
this.stopAnimations();
this.resetSortState();
}
-
- protected readonly UrlConstants = UrlConstants;
}
diff --git a/src/app/shared/components/generic-grid/generic-grid.html b/src/app/shared/components/generic-grid/generic-grid.html
new file mode 100644
index 0000000..d0e9eb5
--- /dev/null
+++ b/src/app/shared/components/generic-grid/generic-grid.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/shared/components/generic-grid/generic-grid.scss b/src/app/shared/components/generic-grid/generic-grid.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/shared/components/generic-grid/generic-grid.ts b/src/app/shared/components/generic-grid/generic-grid.ts
new file mode 100644
index 0000000..55c8035
--- /dev/null
+++ b/src/app/shared/components/generic-grid/generic-grid.ts
@@ -0,0 +1,213 @@
+import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core';
+import {CommonModule} from '@angular/common';
+
+export interface GridPos { row: number; col: number }
+
+@Component({
+ selector: 'app-generic-grid',
+ standalone: true,
+ imports: [CommonModule],
+ templateUrl: './generic-grid.html',
+ styleUrl: './generic-grid.scss',
+})
+export class GenericGridComponent implements AfterViewInit {
+ @ViewChild('gridCanvas', { static: true })
+ canvas!: ElementRef;
+
+ @Input() gridRows: number = 10;
+ @Input() gridCols: number = 10;
+ @Input() nodeSize: number = 10; // Default node size, can be overridden by computeNodeSize
+ @Input() maxGridPx: number = 500; // Max pixels for grid dimension
+ @Input() minGridSize: number = 5;
+ @Input() maxGridSize: number = 50;
+ @Input() drawNodeBorderColor: string = '#ccc';
+
+ // Callbacks from parent component
+ @Input() createNodeFn!: (row: number, col: number) => any;
+ @Input() getNodeColorFn!: (node: any) => string;
+ @Input() applySelectionFn!: (pos: GridPos, grid: any[][]) => void;
+ @Input() initializationFn!: (grid: any[][]) => void;
+
+ @Output() gridChange = new EventEmitter();
+ @Output() nodeClick = new EventEmitter();
+
+ private ctx!: CanvasRenderingContext2D;
+ grid: any[][] = [];
+
+ isDrawing = false;
+ private lastCell: GridPos | null = null;
+
+ ngAfterViewInit(): void {
+ this.ctx = this.getContextOrThrow();
+ this.setupCanvasListeners();
+ this.applyGridSize();
+ }
+
+ setupCanvasListeners(): void {
+ const el = this.canvas.nativeElement;
+ el.addEventListener('mousedown', (e) => this.onMouseDown(e));
+ el.addEventListener('mousemove', (e) => this.onMouseMove(e));
+ el.addEventListener('mouseup', () => this.onMouseUp());
+ el.addEventListener('mouseleave', () => this.onMouseUp());
+
+ el.addEventListener('touchstart', (e) => {
+ if (e.cancelable) e.preventDefault();
+ this.onMouseDown(e as never);
+ }, { passive: false });
+
+ el.addEventListener('touchmove', (e) => {
+ if (e.cancelable) e.preventDefault();
+ this.onMouseMove(e as never);
+ }, { passive: false });
+
+ el.addEventListener('touchend', () => {
+ this.onMouseUp();
+ });
+ }
+
+ applyGridSize(): void {
+ this.gridRows = this.clampGridSize(this.gridRows);
+ this.gridCols = this.clampGridSize(this.gridCols);
+ this.nodeSize = this.computeNodeSize(this.gridRows, this.gridCols);
+ this.resizeCanvas();
+ if (this.gridRows === this.grid.length && this.gridCols === this.grid[0]?.length) {
+ this.drawGrid();
+ return;
+ }
+ this.initializeGrid();
+ }
+
+ initializeGrid(): void {
+ this.grid = this.createEmptyGrid();
+ if (this.initializationFn) {
+ this.initializationFn(this.grid);
+ }
+ this.drawGrid();
+ this.gridChange.emit(this.grid);
+ }
+
+ createEmptyGrid(): any[][] {
+ const grid: any[][] = [];
+ for (let row = 0; row < this.gridRows; row++) {
+ const currentRow: any[] = [];
+ for (let col = 0; col < this.gridCols; col++) {
+ currentRow.push(this.createNodeFn(row, col));
+ }
+ grid.push(currentRow);
+ }
+ return grid;
+ }
+
+ drawGrid(): void {
+ this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height);
+ for (let row = 0; row < this.gridRows; row++) {
+ for (let col = 0; col < this.gridCols; col++) {
+ this.drawNode(this.grid[row][col]);
+ }
+ }
+ }
+
+ drawNode(node: any): void {
+ this.ctx.fillStyle = this.getNodeColorFn(node);
+ this.ctx.fillRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
+ this.ctx.strokeStyle = this.drawNodeBorderColor;
+ this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
+ }
+
+ private getContextOrThrow(): CanvasRenderingContext2D {
+ const ctx = this.canvas.nativeElement.getContext('2d');
+ if (!ctx) {
+ throw new Error('CanvasRenderingContext2D not available.');
+ }
+ return ctx;
+ }
+
+ private clampGridSize(value: number): number {
+ const parsed = Math.floor(Number(value));
+ const safe = Number.isFinite(parsed) ? parsed : this.minGridSize; // Use minGridSize as fallback
+ return Math.min(Math.max(this.minGridSize, safe), this.maxGridSize);
+ }
+
+ private computeNodeSize(rows: number, cols: number): number {
+ const sizeByWidth = Math.floor(this.maxGridPx / cols);
+ const sizeByHeight = Math.floor(this.maxGridPx / rows);
+ return Math.max(1, Math.min(sizeByWidth, sizeByHeight));
+ }
+
+ private resizeCanvas(): void {
+ const el = this.canvas.nativeElement;
+ el.width = this.gridCols * this.nodeSize;
+ el.height = this.gridRows * this.nodeSize;
+ }
+
+ onMouseDown(event: MouseEvent | TouchEvent): void {
+ this.isDrawing = true;
+ this.lastCell = null;
+ const pos = this.getGridPosition(event);
+ if (pos) {
+ this.handleInteraction(pos);
+ }
+ }
+
+ onMouseMove(event: MouseEvent | TouchEvent): void {
+ if (!this.isDrawing) {
+ return;
+ }
+ const pos = this.getGridPosition(event);
+ if (pos && !this.isSameCell(pos, this.lastCell)) {
+ this.handleInteraction(pos);
+ }
+ }
+
+ onMouseUp(): void {
+ this.isDrawing = false;
+ this.lastCell = null;
+ }
+
+ private handleInteraction(pos: GridPos): void {
+ this.applySelectionFn(pos, this.grid);
+ this.drawNode(this.grid[pos.row][pos.col]);
+ this.lastCell = pos;
+ this.nodeClick.emit(pos);
+ this.gridChange.emit(this.grid);
+ }
+
+ private getGridPosition(event: MouseEvent | TouchEvent): GridPos | null {
+ const canvas = this.canvas.nativeElement;
+ const rect = canvas.getBoundingClientRect();
+
+ let clientX, clientY;
+ if (event instanceof MouseEvent) {
+ clientX = event.clientX;
+ clientY = event.clientY;
+ } else if (event instanceof TouchEvent && event.touches.length > 0) {
+ clientX = event.touches[0].clientX;
+ clientY = event.touches[0].clientY;
+ } else {
+ return null;
+ }
+
+ const scaleX = canvas.width / rect.width;
+ const scaleY = canvas.height / rect.height;
+
+ const x = (clientX - rect.left) * scaleX;
+ const y = (clientY - rect.top) * scaleY;
+
+ const col = Math.floor(x / this.nodeSize);
+ const row = Math.floor(y / this.nodeSize);
+
+ if (!this.isValidPosition(row, col)) {
+ return null;
+ }
+
+ return { row, col };
+ }
+
+ private isValidPosition(row: number, col: number): boolean {
+ return row >= 0 && row < this.gridRows && col >= 0 && col < this.gridCols;
+ }
+
+ private isSameCell(a: GridPos, b: GridPos | null): boolean {
+ return !!b && a.row === b.row && a.col === b.col;
+ }
+}
diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json
index 72d2b97..638d321 100644
--- a/src/assets/i18n/de.json
+++ b/src/assets/i18n/de.json
@@ -313,14 +313,11 @@
"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).",
"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": {
"START_END_NODES": "Bitte wählen Sie einen Start- und Endknoten aus, bevor Sie den Algorithmus starten."
- },
- "GRID_HEIGHT": "Höhe",
- "GRID_WIDTH": "Beite"
+ }
},
"SORTING": {
"TITLE": "Sortieralgorithmen",
@@ -336,7 +333,6 @@
"QUICK_SORT_EXPLANATION": "folgt dem \"Teile und Herrsche\"-Prinzip. Ein \"Pivot\"-Element wird gewählt, und das Array wird in zwei Hälften geteilt: Elemente kleiner als das Pivot und Elemente größer als das Pivot. Vorteil: Im Durchschnitt einer der schnellsten Sortieralgorithmen (O(n log n)); benötigt keinen zusätzlichen Speicher (In-Place). Nachteil: Im schlechtesten Fall (Worst Case) langsam (O(n²)), wenn das Pivot ungünstig gewählt wird. Ist nicht stabil (ändert Reihenfolge gleicher Elemente).",
"HEAP_SORT_EXPLANATION": "organisiert die Daten zunächst in einer speziellen Baumstruktur (Binary Heap). Das größte Element (die Wurzel) wird entnommen und ans Ende sortiert, dann wird der Baum repariert. Vorteil: Garantiert eine schnelle Laufzeit von O(n log n), selbst im schlechtesten Fall. Benötigt fast keinen zusätzlichen Speicher. Nachteil: In der Praxis oft etwas langsamer als Quick Sort, da die Sprünge im Speicher (Heap-Struktur) den CPU-Cache schlechter nutzen.",
"COCKTAIL_SORT_EXPLANATION" : "(auch Shaker Sort) ist eine Erweiterung des Bubble Sort. Statt nur von links nach rechts zu gehen, wechselt er bei jedem Durchlauf die Richtung und schiebt abwechselnd das größte Element nach rechts und das kleinste nach links. Vorteil: Schneller als Bubble Sort, da kleine Elemente am Ende schneller nach vorne wandern (\"Schildkröten-Problem\" gelöst). Nachteil: Bleibt in der Laufzeitklasse O(n²), also für große Datenmengen ineffizient.",
- "NOTE": "HINWEIS",
"DISCLAIMER": "Die Wahl des \"besten\" Sortieralgorithmus hängt stark von den Daten und den Rahmenbedingungen ab. In der Informatik betrachtet man oft drei Szenarien:",
"DISCLAIMER_1": "Best Case: Die Daten sind schon fast sortiert (hier glänzt z.B. Bubble Sort).",
"DISCLAIMER_2": "Average Case: Der statistische Normalfall.",
@@ -344,6 +340,28 @@
"DISCLAIMER_4": "Zusätzlich gibt es fast immer einen Time-Space Trade-off (Zeit-Speicher-Kompromiss): Algorithmen, die extrem schnell sind (wie Merge Sort), benötigen oft viel zusätzlichen Arbeitsspeicher. Algorithmen, die direkt im vorhandenen Speicher arbeiten (wie Heap Sort), sparen Platz, sind aber manchmal komplexer oder minimal langsamer. Es gibt also keine \"One-Size-Fits-All\"-Lösung."
}
},
+ "GOL": {
+ "TITLE": "Conway's Spiel des Lebens",
+ "START": "Starten",
+ "PAUSE": "Pause",
+ "RANDOM_SCENE": "Zufällig",
+ "EMPTY_SCENE": "Leer",
+ "SIMPLE_SCENE": "Simpel",
+ "PULSAR_SCENE": "Pulsar",
+ "GUN_SCENE": "Pistole",
+ "ALIVE": "Lebend",
+ "DEAD": "Leer",
+ "SPEED": "Zeit pro Generation",
+ "EXPLANATION": {
+ "TITLE": "Erklärung",
+ "EXPLANATION" : "Das Spiel läuft schrittweise ab. Zunächst wird eine Anfangsgeneration von lebenden Zellen auf dem Spielfeld definiert. Aus der vorliegenden Generation (dem Gesamtbild des Spielfeldes) wird die Folgegeneration ermittelt. Der Zustand jeder einzelnen Zelle in der Folgegeneration ergibt sich dabei nach einfachen Regeln aus ihrem aktuellen Zustand sowie den aktuellen Zuständen ihrer acht Nachbarzellen (Moore-Nachbarschaft).",
+ "DISCLAIMER": "Nach Conways ursprünglicher Regel lebt eine Zelle in der nächsten Runde, wenn zuvor in ihrer 3x3-Umgebung insgesamt genau drei Zellen leben, wobei sie selbst nur bei Bedarf mitgezählt wird, das heißt:",
+ "DISCLAIMER_1": "Eine lebende Zelle lebt auch in der Folgegeneration, wenn sie entweder zwei oder drei lebende Nachbarn hat.",
+ "DISCLAIMER_2": "Eine tote Zelle „wird geboren“ (lebt in der Folgegeneration), wenn sie genau drei lebende Nachbarn hat.",
+ "DISCLAIMER_3": "Eine lebende Zelle „stirbt“ (ist in der Folgegeneration tot), wenn sie weniger als zwei (Vereinsamung) oder mehr als drei (Übervölkerung) lebende Nachbarn hat.",
+ "DISCLAIMER_4": " Eine tote Zelle bleibt tot, wenn sie nicht genau drei lebende Nachbarn hat."
+ }
+ },
"ALGORITHM": {
"TITLE": "Algorithmen",
"PATHFINDING": {
@@ -353,7 +371,13 @@
"SORTING": {
"TITLE": "Sortierung",
"DESCRIPTION": "Visualisierung verschiedener Sortieralgorithmen."
-
- }
+ },
+ "GOL": {
+ "TITLE": "Conway's Game of Life",
+ "DESCRIPTION": "Das 'Spiel des Lebens' ist ein vom Mathematiker John Horton Conway 1970 entworfenes Spiel."
+ },
+ "NOTE": "HINWEIS",
+ "GRID_HEIGHT": "Höhe",
+ "GRID_WIDTH": "Beite"
}
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 181aecf..30b4f67 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -313,14 +313,11 @@
"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).",
"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": {
"START_END_NODES": "Please select a start and end node before running the algorithm."
- },
- "GRID_HEIGHT": "Height",
- "GRID_WIDTH": "Width"
+ }
},
"SORTING": {
"TITLE": "Sorting Algorithms",
@@ -335,7 +332,6 @@
"BUBBLE_SORT_EXPLANATION": "repeatedly compares adjacent elements and swaps them if they are in the wrong order. The largest element \"bubbles\" to the end of the list like an air bubble. Advantage: Extremely simple to understand and implement; detects already sorted lists very quickly. Disadvantage: Very inefficient for large lists (runtime O(n²)). Rarely used in practice.",
"QUICK_SORT_EXPLANATION": "follows the \"divide and conquer\" principle. A \"pivot\" element is selected, and the array is divided into two halves: elements smaller than the pivot and elements larger than the pivot. Advantage: On average one of the fastest sorting algorithms (O(n log n)); requires no additional memory (in-place). Disadvantage: Slow in the worst case (O(n²)) if the pivot is chosen poorly. Is not stable (changes order of equal elements).",
"HEAP_SORT_EXPLANATION": "organizes the data initially into a special tree structure (Binary Heap). The largest element (the root) is extracted and sorted to the end, then the tree is repaired. Advantage: Guarantees a fast runtime of O(n log n), even in the worst case. Requires almost no additional memory. Disadvantage: Often slightly slower than Quick Sort in practice because the jumps in memory (heap structure) utilize the CPU cache less effectively.",
- "NOTE": "NOTE",
"DISCLAIMER": "The choice of the \"best\" sorting algorithm depends heavily on the data and the constraints. In computer science, three scenarios are often considered:",
"DISCLAIMER_1": "Best Case: The data is already nearly sorted (Bubble Sort shines here, for example).",
"DISCLAIMER_2": "Average Case: The statistical norm.",
@@ -343,6 +339,28 @@
"DISCLAIMER_4": "Additionally, there is almost always a Time-Space Trade-off: Algorithms that are extremely fast (like Merge Sort) often require a lot of additional working memory. Algorithms that work directly in existing memory (like Heap Sort) save space but are sometimes more complex or slightly slower. Thus, there is no \"one-size-fits-all\" solution."
}
},
+ "GOL": {
+ "TITLE": "Conway's Game of Life",
+ "START": "Start",
+ "PAUSE": "Pause",
+ "RANDOM_SCENE": "Random",
+ "EMPTY_SCENE": "Empty",
+ "SIMPLE_SCENE": "Simple",
+ "PULSAR_SCENE": "Pulsar",
+ "GUN_SCENE": "Gun",
+ "ALIVE": "Alive",
+ "DEAD": "Empty",
+ "SPEED": "Time per Generation",
+ "EXPLANATION": {
+ "TITLE": "Erklärung",
+ "EXPLANATION" : "Das Spiel läuft schrittweise ab. Zunächst wird eine Anfangsgeneration von lebenden Zellen auf dem Spielfeld definiert. Aus der vorliegenden Generation (dem Gesamtbild des Spielfeldes) wird die Folgegeneration ermittelt. Der Zustand jeder einzelnen Zelle in der Folgegeneration ergibt sich dabei nach einfachen Regeln aus ihrem aktuellen Zustand sowie den aktuellen Zuständen ihrer acht Nachbarzellen (Moore-Nachbarschaft).",
+ "DISCLAIMER": "Nach Conways ursprünglicher Regel lebt eine Zelle in der nächsten Runde, wenn zuvor in ihrer 3x3-Umgebung insgesamt genau drei Zellen leben, wobei sie selbst nur bei Bedarf mitgezählt wird, das heißt:",
+ "DISCLAIMER_1": "Eine lebende Zelle lebt auch in der Folgegeneration, wenn sie entweder zwei oder drei lebende Nachbarn hat.",
+ "DISCLAIMER_2": "Eine tote Zelle „wird geboren“ (lebt in der Folgegeneration), wenn sie genau drei lebende Nachbarn hat.",
+ "DISCLAIMER_3": "Eine lebende Zelle „stirbt“ (ist in der Folgegeneration tot), wenn sie weniger als zwei (Vereinsamung) oder mehr als drei (Übervölkerung) lebende Nachbarn hat.",
+ "DISCLAIMER_4": " Eine tote Zelle bleibt tot, wenn sie nicht genau drei lebende Nachbarn hat."
+ }
+ },
"ALGORITHM": {
"TITLE": "Algorithms",
"PATHFINDING": {
@@ -352,6 +370,13 @@
"SORTING": {
"TITLE": "Sorting",
"DESCRIPTION": "Visualizing various sorting algorithms."
- }
+ },
+ "GOL": {
+ "TITLE:": "Conway's Game of Life",
+ "DESCRIPTION": "The Game of Life is a cellular automaton devised by the British mathematician John Horton Conway in 1970."
+ },
+ "NOTE": "Note",
+ "GRID_HEIGHT": "Height",
+ "GRID_WIDTH": "Width"
}
}
diff --git a/src/styles.scss b/src/styles.scss
index 3a12a96..fdba66c 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -110,6 +110,12 @@ a {
transition:
box-shadow 200ms ease,
transform 200ms ease;
+
+ &.container {
+ width: 100%;
+ max-width: 1200px;
+ padding: 20px;
+ }
}
.mat-mdc-card::before {
@@ -213,6 +219,11 @@ a {
}
// algos
+
+.container {
+ padding: 2rem;
+}
+
.algo-info {
margin: 0 0 1rem 0;
padding: 0.75rem 1rem;
@@ -231,3 +242,101 @@ a {
margin-left: 0.25rem;
}
}
+
+.controls-panel {
+ display: flex;
+ gap: 1rem;
+ margin-bottom: 1rem;
+ align-items: center;
+ flex-wrap: wrap;
+ margin-top: 10px;
+ font-size: 0.9em;
+
+ mat-button-toggle-group {
+ border-radius: 4px;
+ overflow: hidden;
+ }
+
+ mat-form-field {
+ width: 200px;
+ }
+}
+
+.grid-size {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ flex-wrap: wrap;
+
+ .grid-field {
+ width: 150px;
+ }
+}
+
+canvas {
+ border: 1px solid lightgray;
+ display: block;
+ max-width: 100%;
+}
+
+.legend {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ align-items: center;
+ font-size: 0.9em;
+
+ .legend-color {
+ display: inline-block;
+ width: 15px;
+ height: 15px;
+ border: 1px solid lightgray;
+ vertical-align: middle;
+ margin-right: 5px;
+
+ &.start { background-color: green; }
+ &.end { background-color: red; }
+ &.wall { background-color: black; }
+ &.visited { background-color: skyblue; }
+ &.path { background-color: gold; }
+ &.empty { background-color: lightgray; }
+ &.alive { background-color: black; }
+ }
+}
+
+.controls-container {
+ display: flex;
+ flex-direction: column;
+ margin-bottom: 1rem;
+}
+
+/* Sorting Visualization */
+.sorting-visualization-area {
+ display: flex;
+ align-items: flex-end;
+ height: 300px; /* Max height for bars */
+ border-bottom: 1px solid #ccc;
+ margin-bottom: 20px;
+ gap: 1px;
+ background-color: #f0f0f0;
+
+ .sorting-bar {
+ flex-grow: 1;
+ background-color: #424242; /* Default unsorted color */
+ transition: height 0.05s ease-in-out, background-color 0.05s ease-in-out;
+ width: 10px; /* Default width, flex-grow will adjust */
+ min-width: 1px; /* Ensure bars are always visible */
+
+ &.unsorted {
+ background-color: #424242;
+ }
+
+ &.comparing {
+ background-color: #ffeb3b; /* Yellow for comparing */
+ }
+
+ &.sorted {
+ background-color: #4caf50; /* Green for sorted */
+ }
+ }
+}
\ No newline at end of file