/*
* Authored by: Gauravjot Garaya
* Date: 2023-12-13
*/
// You may add your patterns to below object
const patterns = {
glider: new Set([
JSON.stringify([11,10]),
JSON.stringify([12,11]),
JSON.stringify([10,12]),
JSON.stringify([11,12]),
JSON.stringify([12,12]),
]),
toad: new Set([
JSON.stringify([11, 11]),
JSON.stringify([12, 11]),
JSON.stringify([13, 11]),
JSON.stringify([10, 12]),
JSON.stringify([11, 12]),
JSON.stringify([12, 12]),
]),
beacon: new Set([
JSON.stringify([10, 10]),
JSON.stringify([11, 10]),
JSON.stringify([10, 11]),
JSON.stringify([13, 12]),
JSON.stringify([12, 13]),
JSON.stringify([13, 13]),
]),
gun: new Set([
JSON.stringify([1, 5]),
JSON.stringify([1, 6]),
JSON.stringify([2, 5]),
JSON.stringify([2, 6]),
JSON.stringify([11, 5]),
JSON.stringify([11, 6]),
JSON.stringify([11, 7]),
JSON.stringify([12, 4]),
JSON.stringify([12, 8]),
JSON.stringify([13, 3]),
JSON.stringify([14, 3]),
JSON.stringify([13, 9]),
JSON.stringify([14, 9]),
JSON.stringify([15, 6]),
JSON.stringify([16, 4]),
JSON.stringify([16, 8]),
JSON.stringify([17, 5]),
JSON.stringify([17, 6]),
JSON.stringify([17, 7]),
JSON.stringify([18, 6]),
JSON.stringify([21, 3]),
JSON.stringify([21, 4]),
JSON.stringify([21, 5]),
JSON.stringify([22, 3]),
JSON.stringify([22, 4]),
JSON.stringify([22, 5]),
JSON.stringify([23, 2]),
JSON.stringify([23, 6]),
JSON.stringify([25, 1]),
JSON.stringify([25, 2]),
JSON.stringify([25, 6]),
JSON.stringify([25, 7]),
JSON.stringify([35, 3]),
JSON.stringify([35, 4]),
JSON.stringify([36, 3]),
JSON.stringify([36, 4]),
]),
}
// Probably no need to change code below as settings are configurable in UI
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Board />
</React.StrictMode>
);
function Board() {
const [width, setWidth] = React.useState(80);
const [height, setHeight] = React.useState(80);
const [speed, setSpeed] = React.useState(101);
const [activeSquares, setActiveSquares] = React.useState(patterns.glider);
const [isDrawMode, setIsDrawMode] = React.useState(false);
let timedLoop;
React.useEffect(()=> {
// Calculate each cell
timedLoop = setTimeout(()=> {
if (!isDrawMode) {
let nextgen = new Set();
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
// Simply check the number of neighbours to this cell
let count = 0;
if (isSquareActive(j-1,i-1)) count++;
if (isSquareActive(j,i-1)) count++;
if (isSquareActive(j+1,i-1)) count++;
if (isSquareActive(j-1,i)) count++;
if (isSquareActive(j+1,i)) count++;
if (isSquareActive(j-1,i+1)) count++;
if (isSquareActive(j,i+1)) count++;
if (isSquareActive(j+1,i+1)) count++;
// Check if cell is alive or not
if (isSquareActive(j,i)) {
// Check if it be dead in next generation
if (count < 2 || count > 3) {
// dies
} else {
// it stays alive
nextgen.add(JSON.stringify([j,i]))
}
} else {
// Check if this square can be brought to life
if (count === 3) {
// born
nextgen.add(JSON.stringify([j,i]))
}
}
}
}
setActiveSquares(nextgen);
}
}, speed);
return () => {
clearTimeout(timedLoop);
}
});
function isSquareActive(x,y) {
return activeSquares.has(JSON.stringify([x,y]))
}
const onOptionChangeHandler = (event) => {
clearTimeout(timedLoop); // Clears any previous pattern calculations in progress
setActiveSquares(patterns[event.target.value]);
};
const onSpeedChangeHandler = (event) => {
// Clears any previous pattern calculations in progress
// since they are set at old speed
clearTimeout(timedLoop);
setSpeed(event.target.value);
}
const setDrawMode = () => {
if (isDrawMode) {
// Play the animation
setIsDrawMode(false);
} else {
// Clear all the cells for drawing
clearTimeout(timedLoop);
setActiveSquares(new Set());
setIsDrawMode(true);
}
}
const cellDraw = (j,i) => {
if (isDrawMode) {
let newBoard = new Set(activeSquares);
if (isSquareActive(j,i)) {
newBoard.delete(JSON.stringify([j,i]));
} else {
newBoard.add(JSON.stringify([j,i]));
}
setActiveSquares(newBoard);
}
};
return <>
<header>
<p>Conway's Game of Life</p>
<div class="opts flex">
<div>Grid : <input type="number" min="30" max="1000" value={width} onChange={(e) => setWidth(e.target.value)} /> X <input type="number" min="30" max="1000" value={height} onChange={(e) => setHeight(e.target.value)} /></div>
<div>Pattern: <select onChange={onOptionChangeHandler}>{Object.keys(patterns).map((k)=><option key={k}>{k}</option>)}</select></div>
<div>Speed: <input type="number" onChange={onSpeedChangeHandler} value={speed} min="1" max="2000" step="10"/> ms (lower is faster)</div>
<button onClick={setDrawMode}>{isDrawMode ? "Play" : "Draw Custom"}</button>
</div>
</header>
{Array.from({ length: height }, (_, i) =>
{
return <div className="flex">
{
Array.from({ length: width }, (_, j) =>
<Cell
alive={activeSquares.has(JSON.stringify([j,i]))}
clickFn={cellDraw}
x={j}
y={i}
isEditable={isDrawMode}
/>)
}
</div>
}
)}</>
}
function Cell({alive, clickFn, x, y, isEditable}) {
return (
<div onClick={()=>clickFn(x,y)} className={(alive ? "alive" : "") + (isEditable ? " editable" : "") + " cell"}></div>
)
}
View Compiled