diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.html b/src/app/pages/algorithms/conway-gol/conway-gol.html
index 7352516..7bb078b 100644
--- a/src/app/pages/algorithms/conway-gol/conway-gol.html
+++ b/src/app/pages/algorithms/conway-gol/conway-gol.html
@@ -33,6 +33,7 @@
play_arrow {{ 'GOL.START' | translate }}
}
+
{{ 'SORTING.EXECUTION_TIME' | translate }}: {{ executionTime }} ms
@@ -69,6 +70,7 @@
(keyup.enter)="applySpeed()"
/>
+
{{ 'GOL.ALIVE' | translate }}
@@ -84,7 +86,8 @@
[createNodeFn]="createConwayNode"
[getNodeColorFn]="getConwayNodeColor"
[applySelectionFn]="applyConwaySelection"
- (gridChange)="grid = $event"
+ [backgroundColor]="'lightgray'"
+ (gridChange)="readGrid = $event"
>
diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.models.ts b/src/app/pages/algorithms/conway-gol/conway-gol.models.ts
index 9902429..f6ce9e4 100644
--- a/src/app/pages/algorithms/conway-gol/conway-gol.models.ts
+++ b/src/app/pages/algorithms/conway-gol/conway-gol.models.ts
@@ -16,7 +16,7 @@ export const DEFAULT_GRID_ROWS = 50;
export const DEFAULT_GRID_COLS = 50;
export const MIN_GRID_SIZE = 20;
-export const MAX_GRID_SIZE = 100;
+export const MAX_GRID_SIZE = 200;
export const DEFAULT_TIME_PER_GENERATION = 30;
export const MIN_TIME_PER_GENERATION = 20;
diff --git a/src/app/pages/algorithms/conway-gol/conway-gol.ts b/src/app/pages/algorithms/conway-gol/conway-gol.ts
index 9190bed..743965b 100644
--- a/src/app/pages/algorithms/conway-gol/conway-gol.ts
+++ b/src/app/pages/algorithms/conway-gol/conway-gol.ts
@@ -54,7 +54,9 @@ export class ConwayGol implements AfterViewInit {
protected readonly MAX_GRID_SIZE = MAX_GRID_SIZE;
protected readonly MAX_GRID_PX = MAX_GRID_PX;
- grid: Node[][] = [];
+ readGrid: Node[][] = [];
+ writeGrid: Node[][] = [];
+ executionTime = 0;
currentScenario: Scenario = Scenario.SIMPLE;
readonly gameStarted = signal(false);
@@ -96,45 +98,42 @@ export class ConwayGol implements AfterViewInit {
};
getConwayNodeColor = (node: Node): string => {
- if (node.alive) {
- return 'black';
- }
- return 'lightgray';
+ return node.alive ? 'black' : 'lightgray';
};
applyConwaySelection = (pos: GridPos, grid: Node[][]): void => {
- this.grid = grid; // Keep internal grid in sync
+ this.readGrid = grid; // Keep internal grid in sync
const node = grid[pos.row][pos.col];
node.alive = !node.alive; // Toggle alive status
};
initializeConwayGrid = (grid: Node[][]): void => {
this.gameStarted.set(false);
- this.grid = grid;
-
+ this.readGrid = grid;
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;
}
+ this.writeGrid = structuredClone(this.readGrid);
};
// --- Conway-specific logic (kept local) ---
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;
+ this.readGrid[row][col].alive = Math.random() <= LIVE_SPAWN_PROBABILITY;
}
}
}
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;
+ this.readGrid[3][4].alive = true;
+ this.readGrid[4][5].alive = true;
+ this.readGrid[5][3].alive = true;
+ this.readGrid[5][4].alive = true;
+ this.readGrid[5][5].alive = true;
}
setupPulsar(): void {
@@ -181,39 +180,44 @@ export class ConwayGol implements AfterViewInit {
this.gameStarted.set(true);
let lifeIsDead = false;
while (this.gameStarted()){
- let gridClone = structuredClone(this.grid);
+ const startTime = performance.now();
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);
+ lifeIsDead = this.checkLifeRules(row, col, this.writeGrid) && lifeIsDead;
}
}
- this.swapGrid(gridClone);
+ this.swapGrids();
+ const endTime = performance.now();
+ this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));
if (lifeIsDead){
this.gameStarted.set(false);
}
- await this.delay(this.lifeSpeed);
+ const delta = Math.max(this.lifeSpeed - this.executionTime, 0);
+ await this.delay(delta);
}
+ this.executionTime = 0;
}
- 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 checkLifeRules(row: number, col: number, writeGrid: Node[][]): boolean {
+ const currentCell = this.readGrid[row][col];
+ const aliveNeighbors = this.howManyNeighborsAreLiving(row, col);
+ const oldLifeState = currentCell.alive;
+
+ const nextStateAlive = (currentCell.alive && (aliveNeighbors === 2 || aliveNeighbors === 3)) || (!currentCell.alive && aliveNeighbors === 3);
+ writeGrid[row][col].alive = nextStateAlive;
+
+ //only if at least one cell changes the game is still alive
+ return (nextStateAlive == oldLifeState);
}
- private swapGrid(gridClone: Node[][]) {
- this.grid = gridClone;
+ private swapGrids() {
+ const tmp = this.readGrid;
+ this.readGrid = this.writeGrid;
+ this.writeGrid = tmp;
if (this.genericGridComponent) {
- this.genericGridComponent.grid = this.grid;
+ this.genericGridComponent.grid = this.readGrid;
this.genericGridComponent.drawGrid();
}
}
@@ -231,7 +235,7 @@ export class ConwayGol implements AfterViewInit {
if (nRow == row && nCol == col) {
continue;
}
- if (this.grid[nRow][nCol].alive) {
+ if (this.readGrid[nRow][nCol].alive) {
aliveNeighborCount++;
}
}
@@ -250,7 +254,7 @@ export class ConwayGol implements AfterViewInit {
private setAlive(r: number, c: number): void {
if (r >= 0 && r < this.gridRows && c >= 0 && c < this.gridCols) {
- this.grid[r][c].alive = true;
+ this.readGrid[r][c].alive = true;
}
}
}
diff --git a/src/app/pages/algorithms/pathfinding/pathfinding.component.html b/src/app/pages/algorithms/pathfinding/pathfinding.component.html
index 36d9ccd..9110a47 100644
--- a/src/app/pages/algorithms/pathfinding/pathfinding.component.html
+++ b/src/app/pages/algorithms/pathfinding/pathfinding.component.html
@@ -73,6 +73,7 @@
[createNodeFn]="createPathfindingNode"
[getNodeColorFn]="getPathfindingNodeColor"
[applySelectionFn]="applyPathfindingSelection"
+ [backgroundColor]="'lightgray'"
(gridChange)="grid = $event"
>
diff --git a/src/app/pages/algorithms/sorting/sorting.component.ts b/src/app/pages/algorithms/sorting/sorting.component.ts
index 569c037..440f2d9 100644
--- a/src/app/pages/algorithms/sorting/sorting.component.ts
+++ b/src/app/pages/algorithms/sorting/sorting.component.ts
@@ -136,7 +136,6 @@ export class SortingComponent implements OnInit {
const endTime = performance.now();
this.executionTime = Number.parseFloat((endTime - startTime).toFixed(4));
- console.log(snapshots.length);
this.animateSorting(snapshots);
}
diff --git a/src/app/shared/components/generic-grid/generic-grid.ts b/src/app/shared/components/generic-grid/generic-grid.ts
index 55c8035..26791a9 100644
--- a/src/app/shared/components/generic-grid/generic-grid.ts
+++ b/src/app/shared/components/generic-grid/generic-grid.ts
@@ -21,6 +21,7 @@ export class GenericGridComponent implements AfterViewInit {
@Input() minGridSize: number = 5;
@Input() maxGridSize: number = 50;
@Input() drawNodeBorderColor: string = '#ccc';
+ @Input() backgroundColor: string = 'lightgray';
// Callbacks from parent component
@Input() createNodeFn!: (row: number, col: number) => any;
@@ -99,19 +100,70 @@ export class GenericGridComponent implements AfterViewInit {
}
drawGrid(): void {
- this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height);
+ if (!this.ctx || !this.grid.length) return;
+
+ const width = this.canvas.nativeElement.width;
+ const height = this.canvas.nativeElement.height;
+ const size = this.nodeSize;
+
+ this.ctx.fillStyle = this.backgroundColor;
+ this.ctx.fillRect(0, 0, width, height);
+ this.ctx.fillStyle = 'black';
+
for (let row = 0; row < this.gridRows; row++) {
for (let col = 0; col < this.gridCols; col++) {
- this.drawNode(this.grid[row][col]);
+ const node = this.grid[row][col];
+
+ const color = this.getNodeColorFn(node);
+
+ if (color !== this.backgroundColor) {
+ if (this.ctx.fillStyle !== color) {
+ this.ctx.fillStyle = color;
+ }
+
+ const x = col * this.nodeSize;
+ const y = row * this.nodeSize;
+
+ this.ctx.fillRect(x, y, this.nodeSize, this.nodeSize);
+
+ }
}
}
+
+ if (size > 2) {
+ this.drawGridLines(width, height);
+ }
+
+ }
+
+ private drawGridLines(width: number, height: number): void {
+ this.ctx.beginPath();
+ this.ctx.strokeStyle = this.drawNodeBorderColor;
+ this.ctx.lineWidth = 1;
+
+ for (let col = 0; col <= this.gridCols; col++) {
+ const x = col * this.nodeSize;
+ this.ctx.moveTo(x, 0);
+ this.ctx.lineTo(x, height);
+ }
+
+ for (let row = 0; row <= this.gridRows; row++) {
+ const y = row * this.nodeSize;
+ this.ctx.moveTo(0, y);
+ this.ctx.lineTo(width, y);
+ }
+
+ this.ctx.stroke();
}
drawNode(node: any): void {
this.ctx.fillStyle = this.getNodeColorFn(node);
this.ctx.fillRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
- this.ctx.strokeStyle = this.drawNodeBorderColor;
- this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
+
+ if (this.nodeSize > 4) {
+ this.ctx.strokeStyle = this.drawNodeBorderColor;
+ this.ctx.strokeRect(node.col * this.nodeSize, node.row * this.nodeSize, this.nodeSize, this.nodeSize);
+ }
}
private getContextOrThrow(): CanvasRenderingContext2D {