1
\$\begingroup\$

This is my first React project. I've included the main files that don't just contain boilerplate. This is a visualization of Conway's Game of Life, with a few toggleable features like starting/stopping and changing the speed.

Game.tsx

import React from "react"
import { Grid } from "./Grid";
import { randomProbability, wait } from "./utils";

type Props = {};
type State = {
    grid: boolean[][]
    running: boolean,
    timeout: number
};

const HEIGHT = 30;
const WIDTH = 60;
const PROBABILITY_OF_ALIVE = 0.5;

export default class Game extends React.Component<Props, State> {
    constructor(props: Props) {
        super(props);
        this.state = {
            grid: this.generateRandomGrid(),
            running: false,
            timeout: 10
        };
    }

    async runSimulation() {
        await this.setState({ running: true });

        while (this.state.running) {
            await this.simulateOneStep();
        }
    }

    async simulateOneStep() {
        const newGrid = this.nextGeneration();

        await wait(this.state.timeout);

        await this.setState({ grid: newGrid });
    }

    renderStopStartButtons() {
        if (this.state.running) {
            return (
                <button className="menu-button" onClick={() => this.stop()}>
                    Stop
                </button>
            );
        } else {
            return (
                <button className="menu-button" onClick={() => this.runSimulation()}>
                    Start
                </button>
            );
        }
    }

    renderStepButton() {
        if (this.state.running) {
            return (
                <button className="empty-menu-button">
                    Run One Step
                </button>
            );
        } else {
            return (
                <button className="menu-button" onClick={() => this.simulateOneStep()}>
                    Run One Step
                </button>
            );
        }
    }

    renderResetButton() {
        return (
            <button className="menu-button" onClick={() => window.location.reload()}>
                Reset
            </button>
        )
    }

    renderSpeedSlider() {
        return (
            <span>
                <label className="menu-text">Toggle Speed</label>
                <input type="range" id="toggle-speed" min="5" max="1000"
                    onChange={event => this.setState({ timeout: parseInt(event.target.value) })}>
                </input>
            </span>
        )
    }

    renderMenu() {
        return (
            <header>
                <nav>
                    <ul id="menu">
                        <span className="menu-segment">
                            {this.renderResetButton()}
                        </span>

                        <span className="menu-segment">
                            {this.renderStopStartButtons()}
                        </span>

                        <span className="menu-segment">
                            {this.renderStepButton()}
                        </span>

                        <span className="menu-segment">
                            {this.renderSpeedSlider()}
                        </span>
                    </ul>
                </nav>
            </header>
        )
    }

    stop() {
        this.setState({ running: false });
    }

    start() {
        this.setState({ running: true });
    }

    render() {
        return (
            <div id="page">
                {this.renderMenu()}

                <Grid dimensions={this.state.grid} />
            </div>
        )
    }

    generateRandomGrid() {
        const grid: boolean[][] = [];

        for (let row = 0; row < HEIGHT; row++) {
            grid.push([]);
            for (let col = 0; col < WIDTH; col++) {
                grid[row].push(randomProbability(PROBABILITY_OF_ALIVE));
            }
        }

        return grid;
    }

    nextGeneration() {
        const newGrid = this.state.grid.slice();

        for (let row = 0; row < HEIGHT; row++) {
            for (let col = 0; col < WIDTH; col++) {
                if (this.getAliveNeighbourCount(row, col) === 3) {
                    this.setAlive(newGrid, row, col);
                } else if (this.isAlive(row, col) && this.getAliveNeighbourCount(row, col) === 2) {
                    this.setAlive(newGrid, row, col);
                } else {
                    this.setDead(newGrid, row, col);
                }
            }
        }

        return newGrid;
    }

    setAlive(grid: boolean[][], row: number, col: number) {
        grid[row][col] = true;
    }

    setDead(grid: boolean[][], row: number, col: number) {
        grid[row][col] = false;
    }

    getAliveNeighbourCount(row: number, col: number) {
        let neighbourCount = 0;

        for (let i = -1; i < 2; i++) {
            for (let j = -1; j < 2; j++) {
                if (i === 0 && j === 0) {
                    continue;
                }
                if (this.isOnGrid(row + i, col + j) && this.isAlive(row + i, col + j)) {
                    neighbourCount++;
                }
            }
        }

        return neighbourCount;
    }

    isAlive(row: number, col: number) {
        return this.state.grid[row][col];
    }

    isOnGrid(row: number, col: number) {
        return row >= 0 && row < HEIGHT && col >= 0 && col < WIDTH;
    }
}

Grid.tsx

import React from "react"
import { Tile } from "./Tile";

type Props = {
  dimensions: boolean[][]
}

export const Grid = ({ dimensions }: Props) => {
  const renderTile = (tile: boolean, index: number) => {
    return <Tile isAlive={tile} key={index.toString()} />
  }

  const renderRow = (row: boolean[], index: number) => {
    return (
      <tr key={index.toString()}>
        {row.map((tile, index) => {
          return renderTile(tile, index);
        })}
      </tr>
    )
  }

  return (
    <table id="main-grid">
      <tbody>
        {dimensions.map((row, index) => {
          return renderRow(row, index);
        })}
      </tbody>
    </table>
  );
}

Tile.tsx

import React from "react"

type Props = {
    isAlive: boolean,
}

export const Tile = ({ isAlive }: Props) => {
    const aliveStyle = isAlive ? "alive" : "";

    return (
        <td className={aliveStyle}>

        </td>
    );
}

\$\endgroup\$
1
  • 4
    \$\begingroup\$ This question is incomplete. To help reviewers give you better answers, please add sufficient context to your question. The more you tell us about what your code does and what the purpose of doing that is, the easier it will be for reviewers to help you. Questions should include a description of what the code does \$\endgroup\$
    – Edward
    Dec 19, 2020 at 18:41

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.