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:
@@ -4,5 +4,37 @@
|
|||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<app-information [algorithmInformation]="algoInformation"/>
|
<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-content>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
|||||||
11
src/app/pages/algorithms/conway-gol/conway-gol.models.ts
Normal file
11
src/app/pages/algorithms/conway-gol/conway-gol.models.ts
Normal 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;
|
||||||
@@ -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 {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
|
||||||
import {TranslatePipe} from "@ngx-translate/core";
|
import {TranslatePipe} from "@ngx-translate/core";
|
||||||
import {UrlConstants} from '../../../constants/UrlConstants';
|
import {UrlConstants} from '../../../constants/UrlConstants';
|
||||||
import {Information} from '../information/information';
|
import {Information} from '../information/information';
|
||||||
import {AlgorithmInformation} from '../information/information.models';
|
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({
|
@Component({
|
||||||
selector: 'app-conway-gol',
|
selector: 'app-conway-gol',
|
||||||
@@ -13,31 +18,136 @@ import {AlgorithmInformation} from '../information/information.models';
|
|||||||
MatCardHeader,
|
MatCardHeader,
|
||||||
MatCardTitle,
|
MatCardTitle,
|
||||||
TranslatePipe,
|
TranslatePipe,
|
||||||
Information
|
Information,
|
||||||
|
MatButton,
|
||||||
|
MatIcon,
|
||||||
|
MatFormField,
|
||||||
|
MatInput,
|
||||||
|
MatLabel,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
FormsModule
|
||||||
],
|
],
|
||||||
templateUrl: './conway-gol.html',
|
templateUrl: './conway-gol.html',
|
||||||
styleUrl: './conway-gol.scss',
|
styleUrl: './conway-gol.scss',
|
||||||
})
|
})
|
||||||
export class ConwayGol {
|
export class ConwayGol implements AfterViewInit {
|
||||||
|
|
||||||
protected readonly UrlConstants = UrlConstants;
|
|
||||||
|
|
||||||
algoInformation: AlgorithmInformation = {
|
algoInformation: AlgorithmInformation = {
|
||||||
title: 'PATHFINDING.EXPLANATION.TITLE',
|
title: 'GOL.EXPLANATION.TITLE',
|
||||||
entries: [
|
entries: [
|
||||||
{
|
{
|
||||||
name: 'Dijkstra',
|
name: '',
|
||||||
description: 'PATHFINDING.EXPLANATION.DIJKSTRA_EXPLANATION',
|
description: 'GOL.EXPLANATION.EXPLANATION',
|
||||||
link: UrlConstants.DIJKSTRA_WIKI
|
link: UrlConstants.CONWAYS_WIKI
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'A*',
|
|
||||||
description: 'PATHFINDING.EXPLANATION.ASTAR_EXPLANATION',
|
|
||||||
link: UrlConstants.ASTAR_WIKI
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
disclaimer: 'PATHFINDING.EXPLANATION.DISCLAIMER',
|
disclaimer: 'GOL.EXPLANATION.DISCLAIMER',
|
||||||
disclaimerBottom: '',
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,4 +71,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<canvas #gridCanvas></canvas>
|
<canvas #gridCanvas></canvas>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|||||||
@@ -21,13 +21,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid-size {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-field {
|
.grid-field {
|
||||||
width: 150px;
|
width: 150px;
|
||||||
}
|
}
|
||||||
@@ -54,9 +47,3 @@
|
|||||||
&.path { background-color: gold; }
|
&.path { background-color: gold; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
display: block;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -343,7 +343,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GOL": {
|
"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": {
|
"ALGORITHM": {
|
||||||
"TITLE": "Algorithmen",
|
"TITLE": "Algorithmen",
|
||||||
|
|||||||
@@ -342,7 +342,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"GOL": {
|
"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": {
|
"ALGORITHM": {
|
||||||
"TITLE": "Algorithms",
|
"TITLE": "Algorithms",
|
||||||
|
|||||||
@@ -231,3 +231,24 @@ a {
|
|||||||
margin-left: 0.25rem;
|
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%;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user