add game logic

This commit is contained in:
sschwei1 2022-10-25 00:28:48 +02:00
parent 591ed62ccd
commit fbd1e0d932
4 changed files with 96 additions and 9 deletions

View File

@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/conway-life-glider.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Game of Life</title>
</head>
<body>
<div id="root"></div>

View File

@ -0,0 +1 @@
<svg width="512px" height="512px" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path fill="#000" d="M19.51 19.51V492.3H492.3V19.51H19.51zm18 18H171.1V171.1H37.51V37.51zm151.59 0h133.6V171.1H189.1V37.51zm151.6 0h133.6V171.1H340.7V37.51zm-84.8 9.94a56.85 56.85 0 0 0-56.8 56.85 56.85 56.85 0 0 0 56.8 56.9 56.85 56.85 0 0 0 56.9-56.9 56.85 56.85 0 0 0-56.9-56.85zM37.51 189.1H171.1v133.6H37.51V189.1zm151.59 0h133.6v133.6H189.1V189.1zm151.6 0h133.6v133.6H340.7V189.1zm66.9 10a56.85 56.85 0 0 0-56.8 56.8 56.85 56.85 0 0 0 56.8 56.9 56.85 56.85 0 0 0 56.8-56.9 56.85 56.85 0 0 0-56.8-56.8zM37.51 340.7H171.1v133.6H37.51V340.7zm151.59 0h133.6v133.6H189.1V340.7zm151.6 0h133.6v133.6H340.7V340.7zm-236.4 10.1a56.85 56.85 0 0 0-56.85 56.8 56.85 56.85 0 0 0 56.85 56.8 56.85 56.85 0 0 0 56.9-56.8 56.85 56.85 0 0 0-56.9-56.8zm151.6 0a56.85 56.85 0 0 0-56.8 56.8 56.85 56.85 0 0 0 56.8 56.8 56.85 56.85 0 0 0 56.9-56.8 56.85 56.85 0 0 0-56.9-56.8zm151.7 0a56.85 56.85 0 0 0-56.8 56.8 56.85 56.85 0 0 0 56.8 56.8 56.85 56.85 0 0 0 56.8-56.8 56.85 56.85 0 0 0-56.8-56.8z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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<GameOptions>(defaultGameOptions);
const [cells, setCells] = useState<boolean[]>([]);
const intervalRef = useRef<number|null>(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 (
<div className='game-container'>
<h1 className='title'>Game of Life</h1>
<ControlPanel
gameOptions={gameOptions}
handleStart={handleStart}

View File

@ -4,6 +4,10 @@
display: flex;
flex-direction: column;
.title {
text-align: center;
}
.control-panel {
display: flex;
justify-content: space-between;