Enhance Conway's Game of Life UI & interaction
Add interactive controls and drawing support for Conway's Game of Life: introduce Node.alive, Scenario enum, spawn/speed/time constants, random/empty generation, and mouse/touch drawing (click-drag/touch to toggle cells). Update template to include control buttons, speed input, legend, and expose Scenario constants. Implement grid initialization, random seeding, grid position mapping, and optimized node drawing/color logic. Also update i18n (de/en) with GOL strings and move GRID label keys to ALGORITHM, switch some label usages accordingly. Move generic container/legend styles into global styles.scss (adjust canvas border color), and simplify component SCSS files. Change CONWAYS_WIKI URL to German wiki and remove now-unused UrlConstants references from components.
This commit is contained in:
@@ -7,5 +7,5 @@
|
|||||||
static readonly QUICK_SORT_WIKI = 'https://de.wikipedia.org/wiki/Quicksort'
|
static readonly QUICK_SORT_WIKI = 'https://de.wikipedia.org/wiki/Quicksort'
|
||||||
static readonly HEAP_SORT_WIKI = 'https://de.wikipedia.org/wiki/Heapsort'
|
static readonly HEAP_SORT_WIKI = 'https://de.wikipedia.org/wiki/Heapsort'
|
||||||
static readonly SHAKE_SORT_WIKI = 'https://de.wikipedia.org/wiki/Shakersort'
|
static readonly SHAKE_SORT_WIKI = 'https://de.wikipedia.org/wiki/Shakersort'
|
||||||
static readonly CONWAYS_WIKI = 'https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life'
|
static readonly CONWAYS_WIKI = 'https://de.wikipedia.org/wiki/Conways_Spiel_des_Lebens'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,36 +4,68 @@
|
|||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<app-information [algorithmInformation]="algoInformation"/>
|
||||||
<div class="controls-panel">
|
<div class="controls-container">
|
||||||
<button mat-raised-button >
|
<div class="controls-panel">
|
||||||
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
<button mat-raised-button (click)="generate(Scenario.RANDOM)">
|
||||||
</button>
|
<mat-icon>shuffle</mat-icon> {{ 'GOL.RANDOM_SCENE' | translate }}
|
||||||
</div>
|
</button>
|
||||||
<div class="grid-size">
|
<button mat-raised-button (click)="generate(Scenario.EMPTY)">
|
||||||
<mat-form-field appearance="outline" class="grid-field">
|
<mat-icon>check_box_outline_blank</mat-icon> {{ 'GOL.EMPTY_SCENE' | translate }}
|
||||||
<mat-label>{{ 'PATHFINDING.GRID_HEIGHT' | translate }}</mat-label>
|
</button>
|
||||||
<input
|
<button mat-raised-button >
|
||||||
matInput
|
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||||
type="number"
|
</button>
|
||||||
[(ngModel)]="gridRows"
|
<button mat-raised-button >
|
||||||
[min]="MIN_GRID_SIZE"
|
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||||
[max]="MAX_GRID_SIZE"
|
</button>
|
||||||
(blur)="applyGridSize()"
|
</div>
|
||||||
(keyup.enter)="applyGridSize()"
|
<div class="controls-panel">
|
||||||
/>
|
<button mat-raised-button >
|
||||||
</mat-form-field>
|
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||||
<mat-form-field appearance="outline" class="grid-field">
|
</button>
|
||||||
<mat-label>{{ 'PATHFINDING.GRID_WIDTH' | translate }}</mat-label>
|
</div>
|
||||||
<input
|
<div class="grid-size">
|
||||||
matInput
|
<mat-form-field appearance="outline" class="grid-field">
|
||||||
type="number"
|
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
||||||
[(ngModel)]="gridCols"
|
<input
|
||||||
[min]="MIN_GRID_SIZE"
|
matInput
|
||||||
[max]="MAX_GRID_SIZE"
|
type="number"
|
||||||
(blur)="applyGridSize()"
|
[(ngModel)]="gridRows"
|
||||||
(keyup.enter)="applyGridSize()"
|
[min]="MIN_GRID_SIZE"
|
||||||
/>
|
[max]="MAX_GRID_SIZE"
|
||||||
</mat-form-field>
|
(blur)="applyGridSize()"
|
||||||
|
(keyup.enter)="applyGridSize()"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline" class="grid-field">
|
||||||
|
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="gridCols"
|
||||||
|
[min]="MIN_GRID_SIZE"
|
||||||
|
[max]="MAX_GRID_SIZE"
|
||||||
|
(blur)="applyGridSize()"
|
||||||
|
(keyup.enter)="applyGridSize()"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-form-field appearance="outline" class="grid-field">
|
||||||
|
<mat-label>{{ 'GOL.SPEED' | translate }}</mat-label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
type="number"
|
||||||
|
[(ngModel)]="lifeSpeed"
|
||||||
|
[min]="MIN_TIME_PER_GENERATION"
|
||||||
|
[max]="MAX_TIME_PER_GENERATION"
|
||||||
|
(blur)="applySpeed()"
|
||||||
|
(keyup.enter)="applySpeed()"
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="legend">
|
||||||
|
<span><span class="legend-color alive"></span> {{ 'GOL.ALIVE' | translate }}</span>
|
||||||
|
<span><span class="legend-color empty"></span> {{ 'GOL.DEAD' | translate }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<canvas #gridCanvas></canvas>
|
<canvas #gridCanvas></canvas>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
export interface Node {
|
export interface Node {
|
||||||
row: number;
|
row: number;
|
||||||
col: number;
|
col: number;
|
||||||
|
alive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Scenario {
|
||||||
|
RANDOM = 0,
|
||||||
|
EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_GRID_ROWS = 100;
|
export const DEFAULT_GRID_ROWS = 100;
|
||||||
@@ -8,4 +14,10 @@ export const DEFAULT_GRID_COLS = 100;
|
|||||||
|
|
||||||
export const MIN_GRID_SIZE = 20;
|
export const MIN_GRID_SIZE = 20;
|
||||||
export const MAX_GRID_SIZE = 200;
|
export const MAX_GRID_SIZE = 200;
|
||||||
|
export const DEFAULT_TIME_PER_GENERATION = 50;
|
||||||
|
|
||||||
|
export const MIN_TIME_PER_GENERATION = 20;
|
||||||
|
export const MAX_TIME_PER_GENERATION = 200;
|
||||||
|
|
||||||
export const MAX_GRID_PX = 1000;
|
export const MAX_GRID_PX = 1000;
|
||||||
|
export const LIVE_SPAWN_PROBABILITY = 0.37;
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import {MatButton} from '@angular/material/button';
|
|||||||
import {MatIcon} from '@angular/material/icon';
|
import {MatIcon} from '@angular/material/icon';
|
||||||
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
|
||||||
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
|
||||||
import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, MAX_GRID_SIZE, MIN_GRID_SIZE, MAX_GRID_PX, Node} from './conway-gol.models';
|
import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, MAX_GRID_SIZE, MIN_GRID_SIZE, MAX_GRID_PX, Node, LIVE_SPAWN_PROBABILITY, Scenario, MAX_TIME_PER_GENERATION, MIN_TIME_PER_GENERATION, DEFAULT_TIME_PER_GENERATION} from './conway-gol.models';
|
||||||
|
|
||||||
|
interface GridPos { row: number; col: number }
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-conway-gol',
|
selector: 'app-conway-gol',
|
||||||
@@ -45,20 +47,57 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
disclaimerBottom: '',
|
disclaimerBottom: '',
|
||||||
disclaimerListEntry: ['GOL.EXPLANATION.DISCLAIMER_1', 'GOL.EXPLANATION.DISCLAIMER_2', 'GOL.EXPLANATION.DISCLAIMER_3', 'GOL.EXPLANATION.DISCLAIMER_4']
|
disclaimerListEntry: ['GOL.EXPLANATION.DISCLAIMER_1', 'GOL.EXPLANATION.DISCLAIMER_2', 'GOL.EXPLANATION.DISCLAIMER_3', 'GOL.EXPLANATION.DISCLAIMER_4']
|
||||||
};
|
};
|
||||||
|
|
||||||
protected gridCols = DEFAULT_GRID_COLS;
|
protected gridCols = DEFAULT_GRID_COLS;
|
||||||
protected gridRows = DEFAULT_GRID_ROWS;
|
protected gridRows = DEFAULT_GRID_ROWS;
|
||||||
|
protected lifeSpeed = DEFAULT_TIME_PER_GENERATION;
|
||||||
protected readonly MIN_GRID_SIZE = MIN_GRID_SIZE;
|
protected readonly MIN_GRID_SIZE = MIN_GRID_SIZE;
|
||||||
protected readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
|
protected readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
|
||||||
nodeSize = 10;
|
nodeSize = 10;
|
||||||
grid: Node[][] = [];
|
grid: Node[][] = [];
|
||||||
|
currentScenario: Scenario = 0;
|
||||||
|
|
||||||
@ViewChild('gridCanvas', { static: true })
|
@ViewChild('gridCanvas', { static: true })
|
||||||
canvas!: ElementRef<HTMLCanvasElement>;
|
canvas!: ElementRef<HTMLCanvasElement>;
|
||||||
private ctx!: CanvasRenderingContext2D;
|
private ctx!: CanvasRenderingContext2D;
|
||||||
|
private lastCell: GridPos | null = null;
|
||||||
|
isDrawing = false;
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.ctx = this.getContextOrThrow();
|
this.ctx = this.getContextOrThrow();
|
||||||
this.applyGridSize();
|
this.applyGridSize();
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
generate(scene: Scenario): void {
|
||||||
|
this.currentScenario = scene;
|
||||||
|
this.initializeGrid();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupRandomLives(): void {
|
||||||
|
for (let row = 0; row < this.gridRows; row++) {
|
||||||
|
for (let col = 0; col < this.gridCols; col++) {
|
||||||
|
this.grid[row][col].alive = Math.random() <= LIVE_SPAWN_PROBABILITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
applyGridSize(): void {
|
applyGridSize(): void {
|
||||||
@@ -73,11 +112,19 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.initializeGrid();
|
this.initializeGrid();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applySpeed(): void {
|
||||||
|
this.lifeSpeed = Math.min(Math.max(this.lifeSpeed, MIN_TIME_PER_GENERATION), MAX_TIME_PER_GENERATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private initializeGrid(): void {
|
private initializeGrid(): void {
|
||||||
this.grid = this.createEmptyGrid();
|
this.grid = this.createEmptyGrid();
|
||||||
|
if (this.currentScenario === Scenario.RANDOM) {
|
||||||
|
this.setupRandomLives();
|
||||||
|
}
|
||||||
|
|
||||||
this.drawGrid();
|
this.drawGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +134,7 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
for (let row = 0; row < this.gridRows; row++) {
|
for (let row = 0; row < this.gridRows; row++) {
|
||||||
const currentRow: Node[] = [];
|
const currentRow: Node[] = [];
|
||||||
for (let col = 0; col < this.gridCols; col++) {
|
for (let col = 0; col < this.gridCols; col++) {
|
||||||
currentRow.push(this.createNode(row, col));
|
currentRow.push(this.createNode(row, col, false));
|
||||||
}
|
}
|
||||||
grid.push(currentRow);
|
grid.push(currentRow);
|
||||||
}
|
}
|
||||||
@@ -95,10 +142,11 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
return grid;
|
return grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createNode(row: number, col: number): Node {
|
private createNode(row: number, col: number, alive: boolean): Node {
|
||||||
return {
|
return {
|
||||||
row,
|
row,
|
||||||
col
|
col,
|
||||||
|
alive
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,14 +161,18 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private drawNode(node: Node): void {
|
private drawNode(node: Node): void {
|
||||||
this.ctx.fillStyle = this.getNodeColor();
|
this.ctx.fillStyle = this.getNodeColor(node);
|
||||||
this.ctx.fillRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
this.ctx.fillRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
||||||
|
|
||||||
this.ctx.strokeStyle = '#ccc';
|
this.ctx.strokeStyle = '#ccc';
|
||||||
this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNodeColor(): string {
|
private getNodeColor(node: Node): string {
|
||||||
|
if (node.alive)
|
||||||
|
{
|
||||||
|
return 'black';
|
||||||
|
}
|
||||||
return 'lightgray';
|
return 'lightgray';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,4 +202,88 @@ export class ConwayGol implements AfterViewInit {
|
|||||||
el.height = this.gridRows * this.nodeSize;
|
el.height = this.gridRows * this.nodeSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//mouse listener
|
||||||
|
private onMouseDown(event: MouseEvent): void {
|
||||||
|
const pos = this.getGridPosition(event);
|
||||||
|
if (!pos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDrawing = true;
|
||||||
|
this.lastCell = null;
|
||||||
|
this.applySelectionAt(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMouseMove(event: MouseEvent): void {
|
||||||
|
if (!this.isDrawing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos = this.getGridPosition(event);
|
||||||
|
if (!pos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isSameCell(pos, this.lastCell)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.applySelectionAt(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMouseUp(): void {
|
||||||
|
this.isDrawing = false;
|
||||||
|
this.lastCell = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mouse -> grid cell
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private applySelectionAt(pos: GridPos): void {
|
||||||
|
const node = this.grid[pos.row][pos.col];
|
||||||
|
node.alive = !node.alive;
|
||||||
|
this.lastCell = pos;
|
||||||
|
this.drawNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly Scenario = Scenario;
|
||||||
|
protected readonly MIN_TIME_PER_GENERATION = MIN_TIME_PER_GENERATION;
|
||||||
|
protected readonly MAX_TIME_PER_GENERATION = MAX_TIME_PER_GENERATION;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,4 @@ import {AlgorithmInformation} from './information.models';
|
|||||||
export class Information {
|
export class Information {
|
||||||
|
|
||||||
@Input({ required: true }) algorithmInformation!: AlgorithmInformation;
|
@Input({ required: true }) algorithmInformation!: AlgorithmInformation;
|
||||||
|
|
||||||
protected readonly UrlConstants = UrlConstants;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<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">
|
||||||
<mat-label>{{ 'PATHFINDING.GRID_HEIGHT' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_HEIGHT' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
type="number"
|
type="number"
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="grid-field">
|
<mat-form-field appearance="outline" class="grid-field">
|
||||||
<mat-label>{{ 'PATHFINDING.GRID_WIDTH' | translate }}</mat-label>
|
<mat-label>{{ 'ALGORITHM.GRID_WIDTH' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -1,13 +1,3 @@
|
|||||||
.container {
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -24,26 +14,3 @@
|
|||||||
.grid-field {
|
.grid-field {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.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 #ccc;
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -664,6 +664,4 @@ export class PathfindingComponent implements AfterViewInit {
|
|||||||
private randomIntFromInterval(min: number, max: number): number {
|
private randomIntFromInterval(min: number, max: number): number {
|
||||||
return Math.floor(Math.random() * (max - min + 1) + min);
|
return Math.floor(Math.random() * (max - min + 1) + min);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly UrlConstants = UrlConstants;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="sorting-container">
|
<div class="container">
|
||||||
<mat-card class="sorting-card">
|
<mat-card class="sorting-card">
|
||||||
<mat-card-header>
|
<mat-card-header>
|
||||||
<mat-card-title>{{ 'SORTING.TITLE' | translate }}</mat-card-title>
|
<mat-card-title>{{ 'SORTING.TITLE' | translate }}</mat-card-title>
|
||||||
|
|||||||
@@ -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 {
|
.sorting-card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
@@ -59,4 +51,3 @@
|
|||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -171,6 +171,4 @@ export class SortingComponent implements OnInit {
|
|||||||
this.stopAnimations();
|
this.stopAnimations();
|
||||||
this.resetSortState();
|
this.resetSortState();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly UrlConstants = UrlConstants;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,9 +317,7 @@
|
|||||||
},
|
},
|
||||||
"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."
|
||||||
},
|
}
|
||||||
"GRID_HEIGHT": "Höhe",
|
|
||||||
"GRID_WIDTH": "Beite"
|
|
||||||
},
|
},
|
||||||
"SORTING": {
|
"SORTING": {
|
||||||
"TITLE": "Sortieralgorithmen",
|
"TITLE": "Sortieralgorithmen",
|
||||||
@@ -345,6 +343,11 @@
|
|||||||
"GOL": {
|
"GOL": {
|
||||||
"TITLE": "Conway's Spiel des Lebens",
|
"TITLE": "Conway's Spiel des Lebens",
|
||||||
"START": "Starten",
|
"START": "Starten",
|
||||||
|
"RANDOM_SCENE": "Zufällig",
|
||||||
|
"EMPTY_SCENE": "Leer",
|
||||||
|
"ALIVE": "Lebend",
|
||||||
|
"DEAD": "Leer",
|
||||||
|
"SPEED": "Zeit pro Generation",
|
||||||
"EXPLANATION": {
|
"EXPLANATION": {
|
||||||
"TITLE": "Erklärung",
|
"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).",
|
"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).",
|
||||||
@@ -369,6 +372,8 @@
|
|||||||
"TITLE": "Conway's Game of Life",
|
"TITLE": "Conway's Game of Life",
|
||||||
"DESCRIPTION": "Das 'Spiel des Lebens' ist ein vom Mathematiker John Horton Conway 1970 entworfenes Spiel."
|
"DESCRIPTION": "Das 'Spiel des Lebens' ist ein vom Mathematiker John Horton Conway 1970 entworfenes Spiel."
|
||||||
},
|
},
|
||||||
"NOTE": "HINWEIS"
|
"NOTE": "HINWEIS",
|
||||||
|
"GRID_HEIGHT": "Höhe",
|
||||||
|
"GRID_WIDTH": "Beite"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,9 +317,7 @@
|
|||||||
},
|
},
|
||||||
"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."
|
||||||
},
|
}
|
||||||
"GRID_HEIGHT": "Height",
|
|
||||||
"GRID_WIDTH": "Width"
|
|
||||||
},
|
},
|
||||||
"SORTING": {
|
"SORTING": {
|
||||||
"TITLE": "Sorting Algorithms",
|
"TITLE": "Sorting Algorithms",
|
||||||
@@ -344,6 +342,11 @@
|
|||||||
"GOL": {
|
"GOL": {
|
||||||
"TITLE": "Conway's Game of Life",
|
"TITLE": "Conway's Game of Life",
|
||||||
"START": "Start",
|
"START": "Start",
|
||||||
|
"RANDOM_SCENE": "Random",
|
||||||
|
"EMPTY_SCENE": "Empty",
|
||||||
|
"ALIVE": "Alive",
|
||||||
|
"DEAD": "Empty",
|
||||||
|
"SPEED": "Time per Generation",
|
||||||
"EXPLANATION": {
|
"EXPLANATION": {
|
||||||
"TITLE": "Erklärung",
|
"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).",
|
"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).",
|
||||||
@@ -368,6 +371,8 @@
|
|||||||
"TITLE:": "Conway's Game of Life",
|
"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."
|
"DESCRIPTION": "The Game of Life is a cellular automaton devised by the British mathematician John Horton Conway in 1970."
|
||||||
},
|
},
|
||||||
"NOTE": "Note"
|
"NOTE": "Note",
|
||||||
|
"GRID_HEIGHT": "Height",
|
||||||
|
"GRID_WIDTH": "Width"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -213,6 +213,11 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// algos
|
// algos
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.algo-info {
|
.algo-info {
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
@@ -248,7 +253,38 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid lightgray;
|
||||||
display: block;
|
display: block;
|
||||||
max-width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user