Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bf17199

Browse files
remarcmijstasel
authored andcommitted
Reorganize into multiple classes
1 parent 1734726 commit bf17199

File tree

12 files changed

+334
-475
lines changed

12 files changed

+334
-475
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* @typedef {Object} GridCell
3+
* @property {number} x
4+
* @property {number} y
5+
* @property {boolean} alive
6+
* @property {boolean} [nextAlive]
7+
*/
8+
9+
export default class Cell {
10+
static size;
11+
12+
constructor(x, y) {
13+
this.x = x;
14+
this.y = y;
15+
this.alive = Math.random() > 0.5;
16+
this.nextAlive = false;
17+
}
18+
19+
draw(context) {
20+
// Draw this background
21+
context.fillStyle = '#303030';
22+
context.fillRect(
23+
this.x * Cell.size,
24+
this.y * Cell.size,
25+
Cell.size,
26+
Cell.size
27+
);
28+
29+
if (this.alive) {
30+
// Draw living this inside background
31+
context.fillStyle = `rgb(24, 215, 236)`;
32+
context.fillRect(
33+
this.x * Cell.size + 1,
34+
this.y * Cell.size + 1,
35+
Cell.size - 2,
36+
Cell.size - 2
37+
);
38+
}
39+
}
40+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import Grid from './Grid.js';
2+
3+
const CELL_SIZE = 10;
4+
const NUM_COLUMNS = 75;
5+
const NUM_ROWS = 40;
6+
7+
export default class Game {
8+
constructor(canvas) {
9+
// Resize the canvas to accommodate the desired number of cell rows and
10+
// columns
11+
canvas.height = NUM_ROWS * CELL_SIZE;
12+
canvas.width = NUM_COLUMNS * CELL_SIZE;
13+
14+
// Obtain a context that is needed to draw on the canvas
15+
this.context = canvas.getContext('2d');
16+
if (!(this.context instanceof CanvasRenderingContext2D)) {
17+
throw new Error('Context not found');
18+
}
19+
20+
this.grid = new Grid(NUM_ROWS, NUM_COLUMNS, CELL_SIZE);
21+
}
22+
23+
gameLoop() {
24+
this.grid.render(this.context);
25+
this.grid.update();
26+
27+
setTimeout(() => {
28+
window.requestAnimationFrame(() => this.gameLoop());
29+
}, 200);
30+
}
31+
32+
start() {
33+
window.requestAnimationFrame(() => this.gameLoop());
34+
}
35+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import Cell from './Cell.js';
2+
3+
export default class Grid {
4+
rows = [];
5+
6+
constructor(numRows, numColumns, cellSize) {
7+
this.numRows = numRows;
8+
this.numColumns = numColumns;
9+
10+
Cell.size = cellSize;
11+
12+
// Create the grid as a two-dimensional array (i.e. an array of arrays)
13+
for (let y = 0; y < numRows; y++) {
14+
const row = [];
15+
for (let x = 0; x < numColumns; x++) {
16+
const cell = new Cell(x, y);
17+
row.push(cell);
18+
}
19+
this.rows.push(row);
20+
}
21+
}
22+
23+
isAlive(x, y) {
24+
// Out-of-border cells are presumed dead
25+
if (x < 0 || x >= this.numColumns || y < 0 || y >= this.numRows) {
26+
return 0;
27+
}
28+
29+
const cell = this.rows[y][x];
30+
return cell.alive ? 1 : 0;
31+
}
32+
33+
countLivingNeighbors(cell) {
34+
const { x, y } = cell;
35+
return (
36+
this.isAlive(x - 1, y - 1) +
37+
this.isAlive(x, y - 1) +
38+
this.isAlive(x + 1, y - 1) +
39+
this.isAlive(x - 1, y) +
40+
this.isAlive(x + 1, y) +
41+
this.isAlive(x - 1, y + 1) +
42+
this.isAlive(x, y + 1) +
43+
this.isAlive(x + 1, y + 1)
44+
);
45+
}
46+
47+
forEachCell(callback) {
48+
this.rows.forEach((row) => {
49+
row.forEach((cell) => callback(cell));
50+
});
51+
}
52+
53+
update() {
54+
this.forEachCell((cell) => {
55+
const numAlive = this.countLivingNeighbors(cell);
56+
57+
if (numAlive === 2) {
58+
// Living cell remains living, dead cell remains dead
59+
cell.nextAlive = cell.alive;
60+
} else if (numAlive === 3) {
61+
// Dead cell becomes living, living cell remains living
62+
cell.nextAlive = true;
63+
} else {
64+
// Living cell dies, dead cell remains dead
65+
cell.nextAlive = false;
66+
}
67+
});
68+
69+
this.forEachCell((cell) => {
70+
cell.alive = cell.nextAlive;
71+
});
72+
}
73+
74+
render(context) {
75+
this.forEachCell((cell) => cell.draw(context));
76+
}
77+
}

Week4/prep-exercises/2-game-of-life/README.md

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,59 @@ In the exercise code a new generation of cells replaces the previous one every 2
1818

1919
## Code walk-through
2020

21+
The JavaScript code is made up of four files, three of which contain JavaScript classes (one class per file) and a file containing a `main()` function.
22+
23+
### `class Cell`
24+
25+
This class represents a single cell.
26+
27+
<!--prettier-ignore-->
28+
| Methods | Description |
29+
|----------|-------------|
30+
| constructor() | Initializes the cell's `x` and `y` coordinates from arguments passed to the constructor. It randomly sets the initial `alive` boolean state of the cell and initializes it next `alive` state. |
31+
| draw() | Draws the cell on the canvas. The visual representation depends on whether the cell is alive or dead. |
32+
33+
### `class Grid`
34+
35+
This class manages the game grid, made up of cells.
36+
2137
<!--prettier-ignore-->
22-
| Methods | Description |
38+
| Methods | Description |
2339
|----------|-------------|
24-
| `constructor()` | Creates a two-dimensional array (i.e., an array of arrays) that represents a grid of cells that evolve over time. |
25-
| `createCell()` | (`static` method) Creates a JavaScript object representing a cell with `x` (column number) and `y` (row number) properties and a boolean `aLive` property that is randomly initialized to `true` or `false`. |
40+
| `constructor()` | Creates a two-dimensional array (i.e., an array of arrays) that represents a grid of cells that evolve over time. It keeps a reference the to the canvas context unto which cells will be drawn. |
2641
| `forEachCell()` | Executes a callback function for each cell in the two-dimensional grid array, passing the cell as parameter to the callback. |
27-
| `drawCell()` | Takes a cell object as a parameter and draws the cell on the canvas. The visual representation depends on whether the cell is alive or dead. |
2842
| `isAlive()` | Determines whether a cell at the given coordinates is alive or dead. The coordinates could potentially be off-grid. Off-grid cells are presumed dead. The function returns one if the given cell is alive or zero if its dead. |
2943
| `countLivingNeighbors()` | Counts the number of living neighbors for a given cell. Each cell has eight neighbors, some of which may be off-grid if the cell is located at an edge or a corner of the grid. |
30-
| `updateGrid()` | Iterates through all cells of the grid and computes the new state of each cell by applying the rules of the Game Of Life. |
31-
| `renderGrid()` | Iterates through all cells of the grid and draws each cell onto the canvas. |
44+
| `update()` | Iterates through all cells of the grid and computes the new state of each cell by applying the rules of the Game Of Life. |
45+
| `render()` | Iterates through all cells of the grid and draws each cell onto the canvas. |
46+
47+
### `class Game`
48+
49+
<!--prettier-ignore-->
50+
| Methods | Description |
51+
|----------|-------------|
3252
| `gameLoop()` | Executes one life cycle of the game (i.e., `updateGrid()` followed by `renderGrid()`) and then reschedules itself to run again after a delay. |
3353
| `start()` | The `start()` function creates the initial grid, renders it to the web page by calling `renderGrid()` and calls `gameLoop()` to kickstart the game. |
3454

35-
The `main()` function gets a reference to the `canvas` element hard-coded in the `index.html` file and resizes the canvas to the desired size. It then instantiates a GameOfLife object and starts the game engine. The function `main()` itself is executed when the browser has finished loading the page.
55+
The `main()` function gets a reference to the `canvas` element hard-coded in the `index.html` file and instantiates a `Game` object.
3656

37-
The diagram below visualizes the overall call hierarchy of the various functions. The `main()` function calls `createGame()`, which in turn creates a closure enclosing the `grid` array and a couple of functions that operate on that `grid`. Then, `main()` calls the `start()` function to start the game.
57+
The diagram below visualizes the overall call hierarchy of the various classes and methods.
3858

39-
The `start()` method creates the initial grid, renders it to the web page by calling `renderGrid()` and calls `gameLoop()` to kickstart the game.
59+
![Game of Life Call Graph](../../../assets/GameOfLife.png)
4060

41-
The `gameLoop()` method calls `updateGrid()` to update (each cell of) the grid according to the game rules (see above) and the calls `renderGrid()` to render the updated grid to the web page. It then schedules a call to itself using `setTimeout()`. This causes the game to keep evolving the grid according to the game rules every 200ms until the page is closed.
61+
The `start()` method creates the initial grid and calls `gameLoop()` to kickstart the game.
4262

43-
Note: The use of [`window.requestAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) is not essential for the functioning of the game but helps to avoid screen flicker.
63+
The `gameLoop()` method calls `grid.update()` to update (each cell of) the grid according to the game rules (see above) and the calls `grid.render()` to render the updated grid to the web page. It then schedules a call to itself using `setTimeout()`. This causes the game to keep evolving the grid according to the game rules every 200ms until the page is closed.
4464

45-
![Game of Life Call Graph](../../../assets/game-of-life-call-graph.png)
65+
Note: The use of [`window.requestAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) is not essential for the functioning of the game but helps to avoid screen flicker.
4666

4767
### Exercise
4868

49-
In the supplied JavaScript code (file: `index.js`) the color of all living cells is a single shade of blue. This is in contrast to the illustration above where living cells have different shades of blue, depending on their life time. Your job is as follows:
69+
In the supplied JavaScript code the color of all living cells is a single shade of blue. This is in contrast to the illustration above where living cells have different shades of blue, depending on their life time. Your job is as follows:
5070

51-
1. In function `createCell()`, add a numeric `lifeTime` property to the object and assign it the value of one if the cell is initially alive or zero if it is initially dead.
71+
1. In the constructor of the `Cell` class, add a numeric `lifeTime` property to the object and assign it the value of `1` if the cell is initially alive or `0` if it is initially dead.
5272

53-
2. In function `drawCell()`, replace [`rgb()`](<https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb()>) with [`rgba()`](<https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgba()>) that adds a fourth parameter indicating `opacity` to the `rgb` value like this:
73+
2. In `draw` method of the `Cell` class, replace [`rgb()`](<https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb()>) with [`rgba()`](<https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgba()>) that adds a fourth parameter indicating `opacity` to the `rgb` value like this:
5474

5575
```js
5676
context.fillStyle = `rgba(24, 215, 236, ${opacity})`;
@@ -65,7 +85,7 @@ In the supplied JavaScript code (file: `index.js`) the color of all living cells
6585
| 3 | 0.75 |
6686
| 4+ | 1 |
6787

68-
3. In function `updateGrid()` add code to update the `lifeTime` value of each cell:
88+
3. In `update` method of the `Grid` class add code to update the `lifeTime` value of each cell:
6989

7090
- A living cell that remains living should have its `lifeTime` incremented by one.
7191
- A living cell that dies should have its `lifeTime` reset to zero.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Game from './Game.js';
2+
3+
function main() {
4+
const canvas = document.getElementById('canvas');
5+
if (!(canvas instanceof HTMLCanvasElement)) {
6+
throw new Error('Canvas element not found');
7+
}
8+
9+
// Create the game "engine"
10+
const game = new Game(canvas);
11+
12+
// Start the game
13+
game.start();
14+
}
15+
16+
window.addEventListener('load', main);

0 commit comments

Comments
 (0)