feature/gameOfLife #12
@@ -6,23 +6,33 @@
|
||||
<app-information [algorithmInformation]="algoInformation"/>
|
||||
<div class="controls-container">
|
||||
<div class="controls-panel">
|
||||
<button mat-raised-button (click)="generate(Scenario.SIMPLE)">
|
||||
<mat-icon>arrow_right</mat-icon> {{ 'GOL.SIMPLE_SCENE' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button (click)="generate(Scenario.PULSAR)">
|
||||
<mat-icon>arrow_right</mat-icon> {{ 'GOL.PULSAR_SCENE' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button (click)="generate(Scenario.GUN)">
|
||||
<mat-icon>arrow_right</mat-icon> {{ 'GOL.GUN_SCENE' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button (click)="generate(Scenario.RANDOM)">
|
||||
<mat-icon>shuffle</mat-icon> {{ 'GOL.RANDOM_SCENE' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button (click)="generate(Scenario.EMPTY)">
|
||||
<mat-icon>check_box_outline_blank</mat-icon> {{ 'GOL.EMPTY_SCENE' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button >
|
||||
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button >
|
||||
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="controls-panel">
|
||||
<button mat-raised-button >
|
||||
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||
</button>
|
||||
@if (gameStarted())
|
||||
{
|
||||
<button mat-raised-button (click)="pauseGame()">
|
||||
<mat-icon>pause</mat-icon> {{ 'GOL.PAUSE' | translate }}
|
||||
</button>
|
||||
} @else {
|
||||
<button mat-raised-button (click)="startGame()">
|
||||
<mat-icon>play_arrow</mat-icon> {{ 'GOL.START' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="grid-size">
|
||||
<mat-form-field appearance="outline" class="grid-field">
|
||||
@@ -33,7 +43,7 @@
|
||||
[(ngModel)]="gridRows"
|
||||
[min]="MIN_GRID_SIZE"
|
||||
[max]="MAX_GRID_SIZE"
|
||||
(ngModelChange)="genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
|
||||
(ngModelChange)="pauseGame(); genericGridComponent.gridRows = gridRows; genericGridComponent.applyGridSize()"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="grid-field">
|
||||
@@ -44,7 +54,7 @@
|
||||
[(ngModel)]="gridCols"
|
||||
[min]="MIN_GRID_SIZE"
|
||||
[max]="MAX_GRID_SIZE"
|
||||
(ngModelChange)="genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()"
|
||||
(ngModelChange)="pauseGame(); genericGridComponent.gridCols = gridCols; genericGridComponent.applyGridSize()"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="grid-field">
|
||||
|
||||
@@ -6,15 +6,18 @@ export interface Node {
|
||||
|
||||
export enum Scenario {
|
||||
RANDOM = 0,
|
||||
EMPTY
|
||||
EMPTY = 1,
|
||||
SIMPLE = 2,
|
||||
PULSAR = 3,
|
||||
GUN = 4
|
||||
}
|
||||
|
||||
export const DEFAULT_GRID_ROWS = 100;
|
||||
export const DEFAULT_GRID_COLS = 100;
|
||||
export const DEFAULT_GRID_ROWS = 40;
|
||||
export const DEFAULT_GRID_COLS = 40;
|
||||
|
||||
export const MIN_GRID_SIZE = 20;
|
||||
export const MAX_GRID_SIZE = 200;
|
||||
export const DEFAULT_TIME_PER_GENERATION = 50;
|
||||
export const MAX_GRID_SIZE = 100;
|
||||
export const DEFAULT_TIME_PER_GENERATION = 30;
|
||||
|
||||
export const MIN_TIME_PER_GENERATION = 20;
|
||||
export const MAX_TIME_PER_GENERATION = 200;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {AfterViewInit, Component, ViewChild} from '@angular/core';
|
||||
import {AfterViewInit, Component, signal, ViewChild} from '@angular/core';
|
||||
import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card";
|
||||
import {TranslatePipe} from "@ngx-translate/core";
|
||||
import {UrlConstants} from '../../../constants/UrlConstants';
|
||||
@@ -8,7 +8,7 @@ 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, LIVE_SPAWN_PROBABILITY, Scenario, MAX_TIME_PER_GENERATION, MIN_TIME_PER_GENERATION, DEFAULT_TIME_PER_GENERATION} from './conway-gol.models';
|
||||
import {DEFAULT_GRID_COLS, DEFAULT_GRID_ROWS, DEFAULT_TIME_PER_GENERATION, LIVE_SPAWN_PROBABILITY, MAX_GRID_PX, MAX_GRID_SIZE, MAX_TIME_PER_GENERATION, MIN_GRID_SIZE, MIN_TIME_PER_GENERATION, Node, Scenario} from './conway-gol.models';
|
||||
import {GenericGridComponent, GridPos} from '../../../shared/components/generic-grid/generic-grid';
|
||||
|
||||
@Component({
|
||||
@@ -55,7 +55,8 @@ export class ConwayGol implements AfterViewInit {
|
||||
protected readonly MAX_GRID_PX = MAX_GRID_PX;
|
||||
|
||||
grid: Node[][] = [];
|
||||
currentScenario: Scenario = 0;
|
||||
currentScenario: Scenario = Scenario.SIMPLE;
|
||||
readonly gameStarted = signal(false);
|
||||
|
||||
@ViewChild(GenericGridComponent) genericGridComponent!: GenericGridComponent;
|
||||
|
||||
@@ -72,6 +73,7 @@ export class ConwayGol implements AfterViewInit {
|
||||
this.genericGridComponent.maxGridPx = this.MAX_GRID_PX;
|
||||
this.genericGridComponent.initializeGrid();
|
||||
}
|
||||
this.gameStarted.set(false);
|
||||
}
|
||||
|
||||
generate(scene: Scenario): void {
|
||||
@@ -107,9 +109,14 @@ export class ConwayGol implements AfterViewInit {
|
||||
};
|
||||
|
||||
initializeConwayGrid = (grid: Node[][]): void => {
|
||||
this.gameStarted.set(false);
|
||||
this.grid = grid;
|
||||
if (this.currentScenario === Scenario.RANDOM) {
|
||||
this.setupRandomLives();
|
||||
|
||||
switch(this.currentScenario) {
|
||||
case Scenario.RANDOM: this.setupRandomLives(); break;
|
||||
case Scenario.SIMPLE: this.setupSimpleLive(); break;
|
||||
case Scenario.PULSAR: this.setupPulsar(); break;
|
||||
case Scenario.GUN: this.setupGliderGun(); break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,8 +129,128 @@ export class ConwayGol implements AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
setupSimpleLive(): void {
|
||||
this.grid[3][4].alive = true;
|
||||
this.grid[4][5].alive = true;
|
||||
this.grid[5][3].alive = true;
|
||||
this.grid[5][4].alive = true;
|
||||
this.grid[5][5].alive = true;
|
||||
}
|
||||
|
||||
setupPulsar(): void {
|
||||
const centerRow = Math.floor(this.gridRows / 2);
|
||||
const centerCol = Math.floor(this.gridCols / 2);
|
||||
|
||||
const rows = [-6, -1, 1, 6];
|
||||
const offsets = [2, 3, 4];
|
||||
|
||||
rows.forEach(r => {
|
||||
offsets.forEach(c => {
|
||||
this.setAlive(centerRow + r, centerCol + c);
|
||||
this.setAlive(centerRow + r, centerCol - c);
|
||||
this.setAlive(centerRow + c, centerCol + r);
|
||||
this.setAlive(centerRow - c, centerCol + r);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setupGliderGun(): void {
|
||||
const r = 5;
|
||||
const c = 5;
|
||||
|
||||
const dots = [
|
||||
[r+4, c], [r+4, c+1], [r+5, c], [r+5, c+1], // Block links
|
||||
[r+4, c+10], [r+5, c+10], [r+6, c+10], [r+3, c+11], [r+7, c+11],
|
||||
[r+2, c+12], [r+8, c+12], [r+2, c+13], [r+8, c+13], [r+5, c+14],
|
||||
[r+3, c+15], [r+7, c+15], [r+4, c+16], [r+5, c+16], [r+6, c+16], [r+5, c+17],
|
||||
[r+2, c+20], [r+3, c+20], [r+4, c+20], [r+2, c+21], [r+3, c+21], [r+4, c+21],
|
||||
[r+1, c+22], [r+5, c+22], [r+0, c+24], [r+1, c+24], [r+5, c+24], [r+6, c+24],
|
||||
[r+2, c+34], [r+3, c+34], [r+2, c+35], [r+3, c+35]
|
||||
];
|
||||
|
||||
dots.forEach(([row, col]) => this.setAlive(row, col));
|
||||
}
|
||||
|
||||
// --- The rules of the game
|
||||
|
||||
pauseGame(): void {
|
||||
this.gameStarted.set(false);
|
||||
}
|
||||
|
||||
async startGame(): Promise<void> {
|
||||
this.gameStarted.set(true);
|
||||
let lifeIsDead = false;
|
||||
while (this.gameStarted()){
|
||||
let gridClone = structuredClone(this.grid);
|
||||
lifeIsDead = true;
|
||||
for (let row = 0; row < this.gridRows; row++) {
|
||||
for (let col = 0; col < this.gridCols; col++) {
|
||||
lifeIsDead = this.checkLifeRules(row, col, gridClone, lifeIsDead);
|
||||
}
|
||||
}
|
||||
|
||||
this.swapGrid(gridClone);
|
||||
if (lifeIsDead){
|
||||
this.gameStarted.set(false);
|
||||
}
|
||||
await this.delay(this.lifeSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
private checkLifeRules(row: number, col: number, gridClone: Node[][], lifeIsDead: boolean) {
|
||||
const itsMe = this.grid[row][col];
|
||||
let aliveNeighbors = this.howManyNeighborsAreLiving(row, col);
|
||||
if (itsMe.alive && (aliveNeighbors < 2 || aliveNeighbors > 3)) {
|
||||
gridClone[row][col].alive = false;
|
||||
lifeIsDead = false;
|
||||
} else if (!itsMe.alive && aliveNeighbors === 3) {
|
||||
gridClone[row][col].alive = true;
|
||||
lifeIsDead = false;
|
||||
}
|
||||
return lifeIsDead;
|
||||
}
|
||||
|
||||
private swapGrid(gridClone: Node[][]) {
|
||||
this.grid = gridClone;
|
||||
if (this.genericGridComponent) {
|
||||
this.genericGridComponent.grid = this.grid;
|
||||
this.genericGridComponent.drawGrid();
|
||||
}
|
||||
}
|
||||
|
||||
private howManyNeighborsAreLiving(row: number, col: number): number {
|
||||
|
||||
let aliveNeighborCount = 0;
|
||||
const minRow = Math.max(row - 1, 0);
|
||||
const minCol = Math.max(col - 1, 0);
|
||||
const maxRow = Math.min(row + 1, this.gridRows - 1);
|
||||
const maxCol = Math.min(col + 1, this.gridCols - 1);
|
||||
|
||||
for (let nRow = minRow; nRow <= maxRow; nRow++) {
|
||||
for (let nCol = minCol; nCol <= maxCol; nCol++) {
|
||||
if (nRow == row && nCol == col) {
|
||||
continue;
|
||||
}
|
||||
if (this.grid[nRow][nCol].alive) {
|
||||
aliveNeighborCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return aliveNeighborCount;
|
||||
}
|
||||
|
||||
// --- Other methods ---
|
||||
protected readonly Scenario = Scenario;
|
||||
protected readonly MIN_TIME_PER_GENERATION = MIN_TIME_PER_GENERATION;
|
||||
protected readonly MAX_TIME_PER_GENERATION = MAX_TIME_PER_GENERATION;
|
||||
|
||||
delay(ms: number) {
|
||||
return new Promise( resolve => setTimeout(resolve, ms) );
|
||||
}
|
||||
|
||||
private setAlive(r: number, c: number): void {
|
||||
if (r >= 0 && r < this.gridRows && c >= 0 && c < this.gridCols) {
|
||||
this.grid[r][c].alive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,8 +343,12 @@
|
||||
"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",
|
||||
|
||||
@@ -342,8 +342,12 @@
|
||||
"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",
|
||||
|
||||
Reference in New Issue
Block a user