react-game-of-life
Version:
React game of life component.
193 lines (170 loc) • 4.53 kB
JSX
import React, {Component} from "react";
import PropTypes from "prop-types";
import Cell from "./Cell";
const LENGTH = 20;
/* global requestAnimationFrame cancelAnimationFrame */
class LifeManager extends Component {
/*
Initialize default state.
*/
constructor(props) {
super(props);
let board = {
delay: 1000 / this.props.fps
};
this.svgStyle = {
width: this.props.width * LENGTH,
height: this.props.height * LENGTH,
position: "absolute",
background: "cyan"
};
// Avoid 2d array and use an object instead.
for (let row = 0; row < this.props.width; row++) {
for (let col = 0; col < this.props.height; col++) {
if (Math.random() > 0.5) {
board[[row, col]] = true;
} else {
board[[row, col]] = false;
}
}
}
// Bind functions.
this.mod = this.mod.bind(this);
this.countNeighbors = this.countNeighbors.bind(this);
this.nextGeneration = this.nextGeneration.bind(this);
this.state = board;
}
/*
Perform floor mod.
*/
mod(n, m) {
return ((n % m) + m) % m;
}
/*
Count the neighbors of a given cell.
*/
countNeighbors(row, col) {
let neighbors = 0;
// right
if (this.state[[this.mod(row + 1, this.props.width), col]]) {
neighbors++;
}
// left
if (this.state[[this.mod(row - 1, this.props.width), col]]) {
neighbors++;
}
// down
if (this.state[[row, this.mod(col + 1, this.props.height)]]) {
neighbors++;
}
// up
if (this.state[[row, this.mod(col - 1, this.props.height)]]) {
neighbors++;
}
// up-left
if (this.state[[this.mod(row - 1, this.props.width), this.mod(col - 1, this.props.height)]]) {
neighbors++;
}
// up-right
if (this.state[[this.mod(row - 1, this.props.width), this.mod(col + 1, this.props.height)]]) {
neighbors++;
}
// down-left
if (this.state[[this.mod(row + 1, this.props.width), this.mod(col - 1, this.props.height)]]) {
neighbors++;
}
// down-right
if (this.state[[this.mod(row + 1, this.props.width), this.mod(col + 1, this.props.height)]]) {
neighbors++;
}
return neighbors;
}
/*
Setup the next generation.
*/
nextGeneration() {
let nextState = {};
let then = new Date().getTime();
let now;
let neighbors;
let delta;
for (let row = 0; row < this.props.width; row++) {
for (let col = 0; col < this.props.height; col++) {
neighbors = this.countNeighbors(row, col);
// default: lives on.
nextState[[row, col]] = this.state[[row, col]];
if (neighbors < 2 || neighbors > 3) { // death
nextState[[row, col]] = false;
} else if (neighbors === 3 && !this.state[[row, col]].status) { // birth
nextState[[row, col]] = true;
}
}
}
this.setState(nextState);
// Pseudo 60fps.
now = new Date().getTime();
delta = now - then;
while (delta < this.state.delay) {
now = new Date().getTime();
delta = now - then;
}
this.requestID = requestAnimationFrame(this.nextGeneration);
}
/*
Start the process when mounted.
*/
componentDidMount() {
this.nextGeneration();
}
/*
Cancel animation when unmounted.
*/
componentWillUnmount() {
cancelAnimationFrame(this.requestID);
}
/*
Render the component.
*/
render() {
let cells = [];
for (let row = 0; row < this.props.width; row++) {
for (let col = 0; col < this.props.height; col++) {
cells.push(<Cell status={this.state[[row, col]]}
row={row}
col={col}
key={row + "cell" + col}
cellActiveColor={this.props.cellActiveColor}
cellInActiveColor={this.props.cellInActiveColor}
lineColor={this.props.lineColor}/>);
}
}
return (
<svg style={this.svgStyle}>
{cells}
</svg>
);
}
};
/*
Define prop types.
*/
LifeManager.propTypes = {
width: PropTypes.number,
height: PropTypes.number,
fps: PropTypes.number,
lineColor: PropTypes.string,
cellActiveColor: PropTypes.string,
cellInActiveColor: PropTypes.string
};
/*
Define default prop types.
*/
LifeManager.defaultProps = {
width: 20,
height: 20,
fps: 60,
lineColor: "cyan",
cellActiveColor: "black",
cellInActiveColor: "gray"
};
export default LifeManager;