import React, { useEffect, useRef, useReducer, useState } from "react";
import { IoIosPlay, IoIosPause } from "react-icons/io";
import getWidth from "../../helpers/getWidth";
import styled from "styled-components";
import { primary } from "../../styles/fonts";
const StyledCanvas = styled.canvas`
border: 1px solid black;
`;
const ControlBar = styled.div`
height: 24,
display: "flex",
width: "100%",
justifyContent: "space-around",
alignItems: "center"
`;
const StyledPauseIcon = styled(IoIosPause)`
color: black;
cursor: pointer;
font-size: 28px;
&:hover {
color: ${primary};
}
`;
const StyledPlayIcon = styled(IoIosPlay)`
color: black;
cursor: pointer;
font-size: 28px;
&:hover {
color: ${primary};
}
`;
const RATIO = 2;
let width = getWidth();
let height = width / RATIO;
const verticalLines = 150;
let cellWidth = height / verticalLines;
let horizontalLines = Math.floor(width / cellWidth);
let canvasCtx;
const actions = {
PLAYING: "PLAYING",
UPDATE_CELLS: "UPDATE_CELLS"
};
function generateInitialCells() {
let row = [];
let allCells = [];
for (let i = 0; i < verticalLines; i++) {
for (let j = 0; j < horizontalLines; j++) {
if (Math.floor(Math.random() * 100) < 45) {
row.push(1);
} else {
row.push(0);
}
}
allCells.push(row);
row = [];
}
return allCells;
}
function draw(currentCells) {
canvasCtx.fillStyle = "black";
currentCells.forEach((row, rowIndex) => {
row.forEach((cell, cellIndex) => {
if (cell === 1) {
canvasCtx.fillRect(
cellIndex * cellWidth,
rowIndex * cellWidth,
cellWidth,
cellWidth
);
}
});
});
}
function generateNext(rowIndex, cellIndex, numOfCells, currentCells) {
const numOfRows = currentCells.length;
const neighbors = [
{ row: rowIndex - 1, cell: cellIndex - 1 },
{ row: rowIndex - 1, cell: cellIndex },
{ row: rowIndex - 1, cell: cellIndex + 1 },
{ row: rowIndex, cell: cellIndex - 1 },
{ row: rowIndex, cell: cellIndex + 1 },
{ row: rowIndex + 1, cell: cellIndex - 1 },
{ row: rowIndex + 1, cell: cellIndex },
{ row: rowIndex + 1, cell: cellIndex + 1 }
];
let liveNeighborCount = 0;
neighbors.forEach(neighbor => {
if (
neighbor.row > -1 &&
neighbor.row < numOfRows &&
neighbor.cell > -1 &&
neighbor.cell < numOfCells &&
currentCells[neighbor.row][neighbor.cell] === 1
) {
liveNeighborCount++;
}
});
if (currentCells[rowIndex][cellIndex] === 1 && liveNeighborCount < 2) {
return 0;
} else if (currentCells[rowIndex][cellIndex] === 1 && liveNeighborCount > 3) {
return 0;
} else if (
currentCells[rowIndex][cellIndex] === 0 &&
liveNeighborCount === 3
) {
return 1;
} else if (
currentCells[rowIndex][cellIndex] === 1 &&
(liveNeighborCount === 3 || liveNeighborCount === 2)
) {
return 1;
} else {
return 0;
}
}
function updateAndDraw(state) {
const { current } = state;
const currentCopy = [...current];
let tempRow = [];
let allCells = [];
if (!currentCopy.length) {
const initCells = generateInitialCells();
draw(initCells);
return initCells;
}
current.forEach((row, rowIndex) => {
row.forEach((cell, cellIndex) => {
tempRow.push(generateNext(rowIndex, cellIndex, row.length, currentCopy));
});
allCells.push(tempRow);
tempRow = [];
});
canvasCtx.clearRect(0, 0, width, height);
draw(allCells);
return allCells;
}
function GameOfLife() {
const [stateWidth, setWidth] = useState(getWidth());
const canvasRef = useRef(null);
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
canvasCtx = canvasRef.current.getContext("2d");
if (state.playing) {
const id = setInterval(() => {
requestAnimationFrame(() => {
dispatch({ type: actions.UPDATE_CELLS });
});
}, 1000 / 10);
return () => {
clearInterval(id);
};
}
}, [state.playing]);
useEffect(() => {
function handleResize() {
setWidth(getWidth());
width = getWidth();
height = width / RATIO;
cellWidth = height / verticalLines;
}
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return (
<>
<StyledCanvas
ref={canvasRef}
width={width}
height={width / RATIO}
></StyledCanvas>
<ControlBar>
{state.playing ? (
<StyledPauseIcon
onClick={() => {
dispatch({ type: actions.PLAYING, playing: false });
}}
/>
) : (
<StyledPlayIcon
onClick={() => dispatch({ type: actions.PLAYING, playing: true })}
/>
)}
</ControlBar>
</>
);
}
export default GameOfLife;
const initialState = {
current: [],
playing: true
};
function reducer(state, action) {
if (action.type === actions.UPDATE_CELLS) {
return {
...state,
current: updateAndDraw(state)
};
} else if (action.type === actions.PLAYING) {
return {
...state,
playing: action.playing
};
} else {
return state;
}
}