From fbd1e0d932f5cb1dc5d173357d4e3412e30a87bf Mon Sep 17 00:00:00 2001 From: sschwei1 <24sebastian05@gmail.com> Date: Tue, 25 Oct 2022 00:28:48 +0200 Subject: [PATCH] add game logic --- index.html | 4 +- public/conway-life-glider.svg | 1 + src/components/game/Game.tsx | 96 ++++++++++++++++++++++++++++++++--- src/styles/game.less | 4 ++ 4 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 public/conway-life-glider.svg 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