From a22dd178692a57421daa2993fddb05ef8416930e Mon Sep 17 00:00:00 2001 From: LoboTheDark Date: Fri, 6 Feb 2026 11:52:17 +0100 Subject: [PATCH] Created default build up - next make grid be a own component with a lot of callbacks - after this start the game implementtion --- .../algorithms/conway-gol/conway-gol.html | 32 ++++ .../conway-gol/conway-gol.models.ts | 11 ++ .../pages/algorithms/conway-gol/conway-gol.ts | 142 ++++++++++++++++-- .../pathfinding/pathfinding.component.html | 3 +- .../pathfinding/pathfinding.component.scss | 13 -- src/assets/i18n/de.json | 12 +- src/assets/i18n/en.json | 12 +- src/styles.scss | 21 +++ 8 files changed, 214 insertions(+), 32 deletions(-) create mode 100644 src/app/pages/algorithms/conway-gol/conway-gol.models.ts diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.html b/src/app/pages/algorithms/conway-gol/conway-gol.html index 43c212e..228e8f7 100644 --- a/src/app/pages/algorithms/conway-gol/conway-gol.html +++ b/src/app/pages/algorithms/conway-gol/conway-gol.html @@ -4,5 +4,37 @@ +
+ +
+
+ + {{ 'PATHFINDING.GRID_HEIGHT' | translate }} + + + + {{ 'PATHFINDING.GRID_WIDTH' | translate }} + + +
+
diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.models.ts b/src/app/pages/algorithms/conway-gol/conway-gol.models.ts new file mode 100644 index 0000000..a3300a6 --- /dev/null +++ b/src/app/pages/algorithms/conway-gol/conway-gol.models.ts @@ -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; diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.ts b/src/app/pages/algorithms/conway-gol/conway-gol.ts index b0ecc02..a80abc4 100644 --- a/src/app/pages/algorithms/conway-gol/conway-gol.ts +++ b/src/app/pages/algorithms/conway-gol/conway-gol.ts @@ -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; + 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; + } + } diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.html b/src/app/pages/algorithms/pathfinding/pathfinding.component.html index 660789a..6f3a695 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.html +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.html @@ -71,4 +71,5 @@ - + + diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.scss b/src/app/pages/algorithms/pathfinding/pathfinding.component.scss index 3e30d18..fdfd40e 100644 --- a/src/app/pages/algorithms/pathfinding/pathfinding.component.scss +++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.scss @@ -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%; -} diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index 9c28066..3dacb72 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -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", diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index feeea1f..b560660 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -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", diff --git a/src/styles.scss b/src/styles.scss index 3a12a96..3321d1d 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -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%; +}