diff --git a/index.html b/index.html
index e0d1c84..05c8697 100644
--- a/index.html
+++ b/index.html
@@ -2,9 +2,9 @@
-
+
- Vite + React + TS
+ Game of Life
diff --git a/public/conway-life-glider.svg b/public/conway-life-glider.svg
new file mode 100644
index 0000000..97c6d1e
--- /dev/null
+++ b/public/conway-life-glider.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/game/Game.tsx b/src/components/game/Game.tsx
index 9d682ba..f5e115e 100644
--- a/src/components/game/Game.tsx
+++ b/src/components/game/Game.tsx
@@ -1,5 +1,5 @@
import CellContainer from './CellContainer';
-import {useCallback, useEffect, useState} from 'react';
+import {useCallback, useEffect, useRef, useState} from 'react';
import '../../styles/game.less';
import ControlPanel from './ControlPanel';
@@ -13,12 +13,20 @@ export enum GameState {
Running
}
+interface GameRules {
+ dead: number[];
+ alive: number[];
+ spawn: number[];
+}
+
export interface GameOptions {
gameState: GameState;
width: number;
height: number;
speed: number;
minSpeed: number;
+ randomizerDensity: number;
+ gameRules: GameRules;
}
const defaultGameOptions: GameOptions = {
@@ -26,13 +34,30 @@ const defaultGameOptions: GameOptions = {
width: 25,
height: 25,
speed: 1000,
- minSpeed: 100
+ minSpeed: 10,
+ randomizerDensity: 0.3,
+ gameRules: {
+ dead: [0,1,4,5,6,7,8],
+ alive: [2],
+ spawn: [3]
+ }
}
const Game = ({}: GameProps) => {
const [gameOptions, setGameOptions] = useState(defaultGameOptions);
const [cells, setCells] = useState([]);
+ const intervalRef = useRef(null);
+
+ const {
+ randomizerDensity,
+ width,
+ height,
+ speed,
+ gameState,
+ gameRules
+ } = gameOptions;
+
const handleStart = useCallback(() => {
setGameOptions(prev => {
prev.gameState = GameState.Running;
@@ -49,25 +74,25 @@ const Game = ({}: GameProps) => {
const handleRandomFill = useCallback(() => {
const newCells: boolean[] = [];
- const totalCells = gameOptions.width * gameOptions.height;
+ const totalCells = width * height;
for(let i = 0; i < totalCells; i++) {
- newCells.push(Math.random() < 0.5);
+ newCells.push(Math.random() < randomizerDensity);
}
setCells(newCells);
- }, []);
+ }, [randomizerDensity, width, height]);
const handleClear = useCallback(() => {
const newCells: boolean[] = [];
- const totalCells = gameOptions.width * gameOptions.height;
+ const totalCells = width * height;
for(let i = 0; i < totalCells; i++) {
newCells.push(false);
}
setCells(newCells);
- }, []);
+ }, [width, height]);
const handleCellUpdate = useCallback((newVal: boolean, position: number) => {
setCells(prev => {
@@ -87,12 +112,69 @@ const Game = ({}: GameProps) => {
})
}, [gameOptions]);
+ const countNeighbours = useCallback((x: number, y: number) => {
+ let cnt = 0;
+
+ for(let xMod = -1; xMod <= 1; xMod++) {
+ for(let yMod = -1; yMod <= 1; yMod++) {
+ const currX = x + xMod;
+ const currY = y + yMod;
+
+ if(
+ (xMod === 0 && yMod === 0) ||
+ (currX < 0 || currX >= width) ||
+ (currY < 0 || currX >= height)
+ ) {
+ continue;
+ }
+
+ if(cells[currY * width + currX]) cnt++;
+ }
+ }
+
+ return cnt;
+ }, [cells]);
+
+ const calcNextIteration = useCallback(() => {
+ const newCells: boolean[] = [];
+
+ for(let y = 0; y < height; y++) {
+ for(let x = 0; x < width; x++) {
+ const neighbourCnt = countNeighbours(x, y);
+
+
+ if(gameRules.dead.includes(neighbourCnt)) {
+ newCells[y * width + x] = false;
+ } else if(gameRules.alive.includes(neighbourCnt)) {
+ newCells[y * width + x] = cells[y * width + x];
+ } else if(gameRules.spawn.includes(neighbourCnt)) {
+ newCells[y * width + x] = true;
+ }
+ }
+ }
+
+ setCells(newCells);
+ }, [cells, width, height]);
+
useEffect(() => {
handleClear();
}, [handleClear]);
+ useEffect(() => {
+ if(intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+
+ if(gameState !== GameState.Running) return;
+
+ intervalRef.current = setInterval(() => {
+ calcNextIteration();
+ }, speed);
+ }, [gameState, speed, intervalRef, calcNextIteration]);
+
return (
+
Game of Life