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.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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. const INITIAL_STATE: State = {
  13. grid: [...INITIAL_GRID],
  14. running: false,
  15. generations: 0,
  16. cellSize: DEFAULT_CELL_SIZE,
  17. speed: DEFAULT_SPEED,
  18. width: DEFAULT_WIDTH,
  19. height: DEFAULT_HEIGHT,
  20. };
  21. interface State {
  22. grid: boolean[][];
  23. running: boolean;
  24. cellSize: number;
  25. speed: number;
  26. width: number;
  27. height: number;
  28. generations: number;
  29. }
  30. class Board extends React.Component<{}, State> {
  31. timer: NodeJS.Timeout | null;
  32. constructor(props: {}) {
  33. super(props);
  34. this.state = INITIAL_STATE;
  35. this.timer = null;
  36. }
  37. start = () => {
  38. this.timer = setInterval(() => {
  39. const grid = generateNewGrid(this.state.grid);
  40. this.setState({ grid, generations: this.state.generations + 1 });
  41. }, this.state.speed);
  42. };
  43. stop = () => {
  44. if (this.timer) clearInterval(this.timer);
  45. };
  46. reset = () => {
  47. if (window.confirm('Are you sure?')) {
  48. this.stop();
  49. this.setState(INITIAL_STATE);
  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 {
  118. cellSize,
  119. generations,
  120. grid,
  121. height,
  122. running,
  123. speed,
  124. width,
  125. } = this.state;
  126. return (
  127. <div className="container">
  128. <Controls
  129. running={running}
  130. speed={speed}
  131. width={width}
  132. height={height}
  133. toggle={this.toggleRunning}
  134. updateCellSize={this.updateCellSize}
  135. updateDimensions={this.updateDimensions}
  136. updateSpeed={this.updateSpeed}
  137. reset={this.reset}
  138. download={this.download}
  139. upload={this.upload}
  140. />
  141. <div className="board-container">
  142. <div className="board">
  143. {grid.map((row, y) => (
  144. <div className="row" key={y} style={{ height: cellSize }}>
  145. {row.map((cell, x) => (
  146. <Cell
  147. disabled={running}
  148. x={x}
  149. y={y}
  150. cellSize={cellSize}
  151. alive={cell}
  152. toggle={this.toggleCell}
  153. key={x}
  154. />
  155. ))}
  156. </div>
  157. ))}
  158. </div>
  159. </div>
  160. <div className="counter">
  161. <span>
  162. {generations} generation{generations === 1 ? '' : 's'}
  163. </span>
  164. </div>
  165. </div>
  166. );
  167. }
  168. }
  169. export default Board;