Conway's Game of Life as a React web app
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Board.tsx 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import React from 'react';
  2. import Cell from './Cell';
  3. import Controls from './Controls';
  4. import { generateNewGrid, updateGridDimensions } from './lib';
  5. const DEFAULT_WIDTH = 30;
  6. const DEFAULT_HEIGHT = 20;
  7. const DEFAULT_SPEED = 100;
  8. const DEFAULT_CELL_SIZE = 30;
  9. const INITIAL_GRID = Array(DEFAULT_HEIGHT).fill(
  10. Array(DEFAULT_WIDTH).fill(false),
  11. );
  12. interface State {
  13. grid: boolean[][];
  14. running: boolean;
  15. cellSize: number;
  16. speed: number;
  17. width: number;
  18. height: number;
  19. }
  20. class Board extends React.Component<{}, State> {
  21. timer: NodeJS.Timeout | null;
  22. constructor(props: {}) {
  23. super(props);
  24. this.state = {
  25. grid: [...INITIAL_GRID],
  26. running: false,
  27. cellSize: DEFAULT_CELL_SIZE,
  28. speed: DEFAULT_SPEED,
  29. width: DEFAULT_WIDTH,
  30. height: DEFAULT_HEIGHT,
  31. };
  32. this.timer = null;
  33. }
  34. start = () => {
  35. this.timer = setInterval(() => {
  36. const grid = generateNewGrid(this.state.grid);
  37. this.setState({ grid });
  38. }, this.state.speed);
  39. };
  40. stop = () => {
  41. if (this.timer) clearInterval(this.timer);
  42. };
  43. reset = () => {
  44. if (window.confirm('Are you sure?')) {
  45. this.setState({ grid: [...INITIAL_GRID] });
  46. this.stop();
  47. this.setState({ running: false });
  48. this.setState({ cellSize: DEFAULT_CELL_SIZE });
  49. this.setState({ width: DEFAULT_WIDTH, height: DEFAULT_HEIGHT });
  50. }
  51. };
  52. toggleCell = (x: number, y: number) => {
  53. const newGrid = [...this.state.grid];
  54. const newRow = [...newGrid[y]];
  55. newRow[x] = !newGrid[y][x];
  56. newGrid[y] = newRow;
  57. this.setState({ grid: newGrid });
  58. };
  59. toggleRunning = () => {
  60. if (this.state.running) this.stop();
  61. if (!this.state.running) this.start();
  62. this.setState({ running: !this.state.running });
  63. };
  64. updateSpeed = (speed: number) => {
  65. this.setState({ speed });
  66. if (this.state.running) {
  67. this.stop();
  68. this.start();
  69. }
  70. };
  71. updateCellSize = (f: (size: number) => number) => {
  72. this.setState({ cellSize: f(this.state.cellSize) });
  73. };
  74. updateDimensions = (dimensions: { width?: number; height?: number }) => {
  75. if (dimensions.width) {
  76. this.setState({ width: dimensions.width });
  77. }
  78. if (dimensions.height) {
  79. this.setState({ height: dimensions.height });
  80. }
  81. const grid = updateGridDimensions(
  82. { width: this.state.width, height: this.state.height },
  83. dimensions,
  84. this.state.grid,
  85. );
  86. this.setState({ grid });
  87. };
  88. download = () => {
  89. const state = JSON.stringify(this.state);
  90. const element = document.createElement('a');
  91. const file = new Blob([state], { type: 'text/plain' });
  92. element.href = URL.createObjectURL(file);
  93. element.download = 'export.json';
  94. document.body.appendChild(element);
  95. element.click();
  96. };
  97. upload = () => {
  98. if (
  99. window.confirm('This will replace any changes you have made. Continue?')
  100. ) {
  101. const element = document.createElement('input');
  102. element.type = 'file';
  103. element.click();
  104. element.addEventListener('change', (e) => {
  105. const files = (e.currentTarget as HTMLInputElement).files;
  106. if (files && files.length > 0) {
  107. const file = files[0];
  108. file.text().then((text) => {
  109. const newState = JSON.parse(text);
  110. this.setState(newState);
  111. });
  112. }
  113. });
  114. }
  115. };
  116. render() {
  117. const { grid, running, speed, width, height, cellSize } = this.state;
  118. return (
  119. <div className="container">
  120. <Controls
  121. running={running}
  122. speed={speed}
  123. width={width}
  124. height={height}
  125. toggle={this.toggleRunning}
  126. updateCellSize={this.updateCellSize}
  127. updateDimensions={this.updateDimensions}
  128. updateSpeed={this.updateSpeed}
  129. reset={this.reset}
  130. download={this.download}
  131. upload={this.upload}
  132. />
  133. <div className="board-container">
  134. <div className="board">
  135. {grid.map((row, y) => (
  136. <div className="row" key={y} style={{ height: cellSize }}>
  137. {row.map((cell, x) => (
  138. <Cell
  139. disabled={running}
  140. x={x}
  141. y={y}
  142. cellSize={cellSize}
  143. alive={cell}
  144. toggle={this.toggleCell}
  145. key={x}
  146. />
  147. ))}
  148. </div>
  149. ))}
  150. </div>
  151. </div>
  152. </div>
  153. );
  154. }
  155. }
  156. export default Board;