Created default build up

- next make grid be a own component with a lot of callbacks
- after this start the game implementtion
This commit is contained in:
2026-02-06 11:52:17 +01:00
parent da43213808
commit a22dd17869
8 changed files with 214 additions and 32 deletions

View File

@@ -4,5 +4,37 @@
</mat-card-header>
<mat-card-content>
<app-information [algorithmInformation]="algoInformation"/>
<div class="controls-panel">
<button mat-raised-button >
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
</button>
</div>
<div class="grid-size">
<mat-form-field appearance="outline" class="grid-field">
<mat-label>{{ 'PATHFINDING.GRID_HEIGHT' | translate }}</mat-label>
<input
matInput
type="number"
[(ngModel)]="gridRows"
[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>{{ 'PATHFINDING.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>
</div>
<canvas #gridCanvas></canvas>
</mat-card-content>
</mat-card>

View File

@@ -0,0 +1,11 @@
export interface Node {
row: number;
col: number;
}
export const DEFAULT_GRID_ROWS = 100;
export const DEFAULT_GRID_COLS = 100;
export const MIN_GRID_SIZE = 20;
export const MAX_GRID_SIZE = 200;
export const MAX_GRID_PX = 1000;

View File

@@ -1,9 +1,14 @@
import { Component } from '@angular/core';
import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
import {TranslatePipe} from "@ngx-translate/core";
import {UrlConstants} from '../../../constants/UrlConstants';
import {Information} from '../information/information';
import {AlgorithmInformation} from '../information/information.models';
import {MatButton} from '@angular/material/button';
import {MatIcon} from '@angular/material/icon';
import {MatFormField, MatInput, MatLabel} from '@angular/material/input';
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';
@Component({
selector: 'app-conway-gol',
@@ -13,31 +18,136 @@ import {AlgorithmInformation} from '../information/information.models';
MatCardHeader,
MatCardTitle,
TranslatePipe,
Information
Information,
MatButton,
MatIcon,
MatFormField,
MatInput,
MatLabel,
ReactiveFormsModule,
FormsModule
],
templateUrl: './conway-gol.html',
styleUrl: './conway-gol.scss',
})
export class ConwayGol {
protected readonly UrlConstants = UrlConstants;
export class ConwayGol implements AfterViewInit {
algoInformation: AlgorithmInformation = {
title: 'PATHFINDING.EXPLANATION.TITLE',
title: 'GOL.EXPLANATION.TITLE',
entries: [
{
name: 'Dijkstra',
description: 'PATHFINDING.EXPLANATION.DIJKSTRA_EXPLANATION',
link: UrlConstants.DIJKSTRA_WIKI
},
{
name: 'A*',
description: 'PATHFINDING.EXPLANATION.ASTAR_EXPLANATION',
link: UrlConstants.ASTAR_WIKI
name: '',
description: 'GOL.EXPLANATION.EXPLANATION',
link: UrlConstants.CONWAYS_WIKI
}
],
disclaimer: 'PATHFINDING.EXPLANATION.DISCLAIMER',
disclaimer: 'GOL.EXPLANATION.DISCLAIMER',
disclaimerBottom: '',
disclaimerListEntry: []
disclaimerListEntry: ['GOL.EXPLANATION.DISCLAIMER_1', 'GOL.EXPLANATION.DISCLAIMER_2', 'GOL.EXPLANATION.DISCLAIMER_3', 'GOL.EXPLANATION.DISCLAIMER_4']
};
protected gridCols = DEFAULT_GRID_COLS;
protected gridRows = DEFAULT_GRID_ROWS;
protected readonly MIN_GRID_SIZE = MIN_GRID_SIZE;
protected readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
nodeSize = 10;
grid: Node[][] = [];
@ViewChild('gridCanvas', { static: true })
canvas!: ElementRef<HTMLCanvasElement>;
private ctx!: CanvasRenderingContext2D;
ngAfterViewInit(): void {
this.ctx = this.getContextOrThrow();
this.applyGridSize();
}
applyGridSize(): void {
this.gridRows = this.clampGridSize(this.gridRows, DEFAULT_GRID_ROWS);
this.gridCols = this.clampGridSize(this.gridCols, DEFAULT_GRID_COLS);
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();
}
private initializeGrid(): void {
this.grid = this.createEmptyGrid();
this.drawGrid();
}
private createEmptyGrid(): Node[][] {
const grid: Node[][] = [];
for (let row = 0; row < this.gridRows; row++) {
const currentRow: Node[] = [];
for (let col = 0; col < this.gridCols; col++) {
currentRow.push(this.createNode(row, col));
}
grid.push(currentRow);
}
return grid;
}
private createNode(row: number, col: number): Node {
return {
row,
col
};
}
private 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]);
}
}
}
private drawNode(node: Node): void {
this.ctx.fillStyle = this.getNodeColor();
this.ctx.fillRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
this.ctx.strokeStyle = '#ccc';
this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
}
private getNodeColor(): string {
return 'lightgray';
}
private getContextOrThrow(): CanvasRenderingContext2D {
const ctx = this.canvas.nativeElement.getContext('2d');
if (!ctx) {
throw new Error('CanvasRenderingContext2D not available.');
}
return ctx;
}
private clampGridSize(value: number, fallback: number): number {
const parsed = Math.floor(Number(value));
const safe = Number.isFinite(parsed) ? parsed : fallback;
return Math.min(Math.max(MIN_GRID_SIZE, safe), MAX_GRID_SIZE);
}
private computeNodeSize(rows: number, cols: number): number {
const sizeByWidth = Math.floor(MAX_GRID_PX / cols);
const sizeByHeight = Math.floor(MAX_GRID_PX / 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;
}
}

View File

@@ -71,4 +71,5 @@
</div>
<canvas #gridCanvas></canvas>
</mat-card-content>
</mat-card-content>
</mat-card>

View File

@@ -21,13 +21,6 @@
}
}
.grid-size {
display: flex;
gap: 0.75rem;
align-items: center;
flex-wrap: wrap;
}
.grid-field {
width: 150px;
}
@@ -54,9 +47,3 @@
&.path { background-color: gold; }
}
}
canvas {
border: 1px solid #ccc;
display: block;
max-width: 100%;
}

View File

@@ -343,7 +343,17 @@
}
},
"GOL": {
"TITLE": "Conway's Spiel des Lebens"
"TITLE": "Conway's Spiel des Lebens",
"START": "Starten",
"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",

View File

@@ -342,7 +342,17 @@
}
},
"GOL": {
"TITLE": "Conway's Game of Life"
"TITLE": "Conway's Game of Life",
"START": "Start",
"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",

View File

@@ -231,3 +231,24 @@ a {
margin-left: 0.25rem;
}
}
.controls-panel {
display: flex;
gap: 10px;
margin-bottom: 20px;
align-items: center;
flex-wrap: wrap;
}
.grid-size {
display: flex;
gap: 0.75rem;
align-items: center;
flex-wrap: wrap;
}
canvas {
border: 1px solid #ccc;
display: block;
max-width: 100%;
}