Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Grid class #5

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/Components/Cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ export enum CellState {
class Cell {
constructor(private state: CellState) {}

static aliveRandomProbability = 0.85;

static newRandom(): Cell {
const isAlive = Math.random() > this.aliveRandomProbability;

return new Cell(isAlive ? CellState.ALIVE : CellState.DEAD);
}

askIfAlive(): boolean {
return this.state === CellState.ALIVE;
}

changeState(newState: CellState): void {
this.state = newState;
askIfDead(): boolean {
return this.state === CellState.DEAD;
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/Components/EvolutionStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Cell, { CellState } from './Cell';
import { IEvolutionStrategy } from './IEvolutionStrategy';

class EvolutionStrategy implements IEvolutionStrategy {
calculateState(cell: Cell, livingNeighbors: number): CellState {
if (cell.askIfAlive() && (livingNeighbors === 2 || livingNeighbors === 3)) {
return CellState.ALIVE;
}

if (cell.askIfDead() && livingNeighbors === 3) {
return CellState.ALIVE;
}

return CellState.DEAD;
}
}

export { EvolutionStrategy };
70 changes: 8 additions & 62 deletions src/Components/GameOfLife.tsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,28 @@
import React, { useState, useEffect } from 'react';
import Cell, { CellState } from './Cell';

// Definimos el tamaño de la cuadrícula
const GRID_WIDTH = 40;
const GRID_HEIGHT = 40;

// Definimos las posibles direcciones de los vecinos
const NEIGHBOR_OFFSETS = [
[-1, -1],
[0, -1],
[1, -1],
[-1, 0],
[1, 0],
[-1, 1],
[0, 1],
[1, 1],
];
import React, { useEffect, useRef, useState } from 'react';
import { Grid } from './Grid';

const GameOfLife: React.FC = () => {
const initializeGrid = () => {
return Array.from({ length: GRID_HEIGHT }, () =>
Array.from({ length: GRID_WIDTH }, () => {
const isAlive = Math.random() > 0.85;

return new Cell(isAlive ? CellState.ALIVE : CellState.DEAD);
}),
);
};

const [grid, setGrid] = useState<Cell[][]>(initializeGrid);

const calculateNextGeneration = () => {
const newGrid = grid.map((row, y) =>
row.map((cell, x) => {
const livingNeighbors = NEIGHBOR_OFFSETS.reduce((count, [dx, dy]) => {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < GRID_WIDTH && ny >= 0 && ny < GRID_HEIGHT) {
return count + (grid[ny][nx].askIfAlive() ? 1 : 0);
}
return count;
}, 0);

if (
cell.askIfAlive() &&
(livingNeighbors === 2 || livingNeighbors === 3)
) {
return new Cell(CellState.ALIVE);
}

if (!cell.askIfAlive() && livingNeighbors === 3) {
return new Cell(CellState.ALIVE);
}

return new Cell(CellState.DEAD);
}),
);
setGrid(newGrid);
};

const grid = useRef(Grid.initialize());
const [cells, setCells] = useState(grid.current.getCells());
useEffect(() => {
const intervalId = setInterval(() => {
calculateNextGeneration();
grid.current.calculateNextGeneration();
setCells(grid.current.getCells());
}, 100);
return () => clearInterval(intervalId);
}, [grid]);

return (
<div>
{grid.map((row, y) => (
{cells.map((row, y) => (
<div key={y} style={{ display: 'flex' }}>
{row.map((cell, x) => (
<div
key={x}
style={{
width: 20,
height: 20,
backgroundColor: cell ? 'green' : 'white',
backgroundColor: cell.askIfAlive() ? 'green' : 'white',
border: '1px solid black',
}}
/>
Expand Down
69 changes: 69 additions & 0 deletions src/Components/Grid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import Cell from './Cell';
import { EvolutionStrategy } from './EvolutionStrategy';
import { HighEvolutionStrategy } from './HighEvolutionStrategy';
import { IEvolutionStrategy } from './IEvolutionStrategy';

// Definimos las posibles direcciones de los vecinos
const NEIGHBOR_OFFSETS = [
[-1, -1],
[0, -1],
[1, -1],
[-1, 0],
[1, 0],
[-1, 1],
[0, 1],
[1, 1],
];

export class Grid {
static height = 40;
static width = 40;
private evolutionStrategy: IEvolutionStrategy;
constructor(
private cells: Cell[][],
evolutionStrategy?: IEvolutionStrategy,
) {
this.evolutionStrategy = evolutionStrategy ?? new EvolutionStrategy();
}

static initialize() {
const cells = Array.from({ length: this.height }, () =>
Array.from({ length: this.width }, () => Cell.newRandom()),
);

return new Grid(cells, new HighEvolutionStrategy());
}

getCells(): Cell[][] {
return this.cells;
}

public calculateNextGeneration() {
const newGrid = this.cells.map((row, y) =>
row.map((cell, x) => {
const livingNeighbors = this.getLiveNeighbors(x, y);

const newState = this.evolutionStrategy.calculateState(
cell,
livingNeighbors,
);

return new Cell(newState);
}),
);

this.cells = newGrid;
}

private getLiveNeighbors(x: number, y: number): number {
return NEIGHBOR_OFFSETS.reduce((count, [dx, dy]) => {
const nx = x + dx;
const ny = y + dy;
if (nx >= 0 && nx < Grid.height && ny >= 0 && ny < Grid.width) {
return count + (this.cells[ny][nx].askIfAlive() ? 1 : 0);
}

return count;
}, 0);
}
}
18 changes: 18 additions & 0 deletions src/Components/HighEvolutionStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Cell, { CellState } from './Cell';
import { IEvolutionStrategy } from './IEvolutionStrategy';

class HighEvolutionStrategy implements IEvolutionStrategy {
calculateState(cell: Cell, livingNeighbors: number): CellState {
if (cell.askIfAlive() && (livingNeighbors === 2 || livingNeighbors === 3)) {
return CellState.ALIVE;
}

if (cell.askIfDead() && (livingNeighbors === 3 || livingNeighbors === 6)) {
return CellState.ALIVE;
}

return CellState.DEAD;
}
}

export { HighEvolutionStrategy };
5 changes: 5 additions & 0 deletions src/Components/IEvolutionStrategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Cell, { CellState } from './Cell';

export interface IEvolutionStrategy {
calculateState(cell: Cell, livingNeighbors: number): CellState;
}
10 changes: 1 addition & 9 deletions test/Cell.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,6 @@ describe('Cell class', () => {
test('When create a dead cell, then the cell state should be dead', () => {
const cell = new Cell(CellState.DEAD);

expect(cell.askIfAlive()).toBeFalsy();
});

test('When change a cell state from alive to dead, should be dead', () => {
const cell = new Cell(CellState.ALIVE);

cell.changeState(CellState.DEAD);

expect(cell.askIfAlive()).toBeFalsy();
expect(cell.askIfDead()).toBeTruthy();
});
});
55 changes: 55 additions & 0 deletions test/EvolutionStrategy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { describe, expect, test } from 'vitest';
import Cell, { CellState } from '../src/Components/Cell';
import { EvolutionStrategy } from '../src/Components/EvolutionStrategy';

const strategy = new EvolutionStrategy();

describe('EvolutionStrategy class', () => {
test('When giving alive cell and 2 livingNeighbors it returns an Alive cellState', () => {
const cell = new Cell(CellState.ALIVE);
const livingNeighbors = 2;
const state = strategy.calculateState(cell, livingNeighbors);

expect(state).toBe(CellState.ALIVE);
});

test('When giving alive cell and 3 livingNeighbors it returns an Alive cellState', () => {
const cell = new Cell(CellState.ALIVE);
const livingNeighbors = 3;
const state = strategy.calculateState(cell, livingNeighbors);

expect(state).toBe(CellState.ALIVE);
});

test('When giving alive cell and 4 or more livingNeighbors it returns an Dead cellState', () => {
const cell = new Cell(CellState.ALIVE);
const livingNeighbors = 4;
const state = strategy.calculateState(cell, livingNeighbors);

expect(state).toBe(CellState.DEAD);
});

test('When giving alive cell and 1 or less livingNeighbors it returns an Dead cellState', () => {
const cell = new Cell(CellState.ALIVE);
const livingNeighbors = 1;
const state = strategy.calculateState(cell, livingNeighbors);

expect(state).toBe(CellState.DEAD);
});

test('When giving dead cell and 3 livingNeighbors it returns an Alive cellState', () => {
const cell = new Cell(CellState.DEAD);
const livingNeighbors = 3;
const state = strategy.calculateState(cell, livingNeighbors);

expect(state).toBe(CellState.ALIVE);
});

test('When giving dead cell and different of 3 livingNeighbors it returns an Dead cellState', () => {
const cell = new Cell(CellState.DEAD);
const livingNeighbors = 1;
const state = strategy.calculateState(cell, livingNeighbors);

expect(state).toBe(CellState.DEAD);
});
});
Loading