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

Skip to content

Commit 259ed9e

Browse files
remarcmijstasel
authored andcommitted
Add Game Of Life prep exercise
1 parent c4a01c2 commit 259ed9e

File tree

9 files changed

+596
-0
lines changed

9 files changed

+596
-0
lines changed

.DS_Store

0 Bytes
Binary file not shown.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Prep exercise: Conway's Game of Life
2+
3+
In this exercise you will work with existing, working code for which you are asked to implement an enhancement. The application is a JavaScript version of a classic simulation, called [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life).
4+
5+
From Wikipedia:
6+
7+
> The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970. It is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves.
8+
9+
As illustrated in the picture below, the game is a two-dimensional grid where cells come alive and die, depending on certain rules. These rules as summarized in the Wikipedia article as follow:
10+
11+
1. Any live cell with two or three live neighbors survives.
12+
2. Any dead cell with three live neighbors becomes a live cell.
13+
3. All other live cells die in the next generation. Similarly, all other dead cells stay dead.
14+
15+
In the exercise code a new generation of cells replaces the previous one every 200ms. For each cell of the new generation life or death is determined by applying the above rules on the state of that same cell in the current generation.
16+
17+
![Game of Life changing](../../../assets/game-of-life-1.gif)
18+
19+
## Code walk-through
20+
21+
<!--prettier-ignore-->
22+
| Methods | Description |
23+
|----------|-------------|
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`. |
26+
| `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. |
28+
| `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. |
29+
| `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. |
32+
| `gameLoop()` | Executes one life cycle of the game (i.e., `updateGrid()` followed by `renderGrid()`) and then reschedules itself to run again after a delay. |
33+
| `start()` | The `start()` function creates the initial grid, renders it to the web page by calling `renderGrid()` and calls `gameLoop()` to kickstart the game. |
34+
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.
36+
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.
38+
39+
The `start()` method creates the initial grid, renders it to the web page by calling `renderGrid()` and calls `gameLoop()` to kickstart the game.
40+
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.
42+
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.
44+
45+
![Game of Life Call Graph](../../../assets/game-of-life-call-graph.png)
46+
47+
### Exercise
48+
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:
50+
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.
52+
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:
54+
55+
```js
56+
context.fillStyle = `rgba(24, 215, 236, ${opacity})`;
57+
```
58+
59+
The `opacity` of each rendered cell should depend on the cell's `lifeTime` property, as specified in this table:
60+
61+
| lifeTime | opacity |
62+
| :------: | :-----: |
63+
| 1 | 0.25 |
64+
| 2 | 0.5 |
65+
| 3 | 0.75 |
66+
| 4+ | 1 |
67+
68+
3. In function `updateGrid()` add code to update the `lifeTime` value of each cell:
69+
70+
- A living cell that remains living should have its `lifeTime` incremented by one.
71+
- A living cell that dies should have its `lifeTime` reset to zero.
72+
- A dead cell that is brought to life should have its `lifeTime` reset to one.
73+
74+
Here is a visual check that you can use to verify that the life time enhancement is correctly implemented. Most of the time, if you wait long enough, the game will "stabilize" to "still life" and "oscillator" patterns, as shown in the GIF below (see the Wikipedia article for more information about the Game Of Life patterns).
75+
76+
![Game of Life stable](../../../assets/game-of-life-2.gif)
77+
78+
- Cells in a still life pattern remain living indefinitely and should therefore stabilize at the highest opacity.
79+
80+
- The oscillating parts of an oscillator pattern continually switch between life and death and, when briefly living, should have the lowest opacity. The stable parts should be at the highest opacity.
81+
82+
_Have fun!_
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// @ts-check
2+
/*------------------------------------------------------------------------------
3+
Full description at: https://github.com/HackYourFuture/Assignments/tree/main/2-Browsers/Week1#exercise-6-conways-game-of-life
4+
5+
THIS IS A PREP EXERCISE FOR THE Q&A SESSION, IT SHOULD NOT BE PART OF THE ASSIGNMENT
6+
7+
Adapted from: https://spicyyoghurt.com/tutorials/javascript/conways-game-of-life-canvas
8+
Refactored from ES6 Class syntax to regular functions
9+
------------------------------------------------------------------------------*/
10+
const CELL_SIZE = 10;
11+
const NUM_COLUMNS = 75;
12+
const NUM_ROWS = 40;
13+
14+
/**
15+
* @typedef {Object} GridCell
16+
* @property {number} x
17+
* @property {number} y
18+
* @property {boolean} alive
19+
* @property {boolean} [nextAlive]
20+
*/
21+
22+
/** @typedef {GridCell[]} GridRow */
23+
24+
/**
25+
* Create a cell with the given coordinates and randomly assign its begin state:
26+
* life or death
27+
* @param {number} x
28+
* @param {number} y
29+
* @returns {GridCell}
30+
*/
31+
function createCell(x, y) {
32+
const alive = Math.random() > 0.5;
33+
return {
34+
x,
35+
y,
36+
alive,
37+
};
38+
}
39+
40+
/**
41+
* Create the game "engine" with a closure
42+
* @param {CanvasRenderingContext2D} context
43+
* @param {number} numRows
44+
* @param {number} numColumns
45+
* @returns
46+
*/
47+
export function createGame(context, numRows, numColumns) {
48+
/** @type {GridRow[]} */
49+
const grid = [];
50+
51+
// Create the grid as a two-dimensional array (i.e. an array of arrays)
52+
function createGrid() {
53+
for (let y = 0; y < numRows; y++) {
54+
/** @type {GridRow} */
55+
const row = [];
56+
for (let x = 0; x < numColumns; x++) {
57+
const cell = createCell(x, y);
58+
row.push(cell);
59+
}
60+
grid.push(row);
61+
}
62+
}
63+
64+
/**
65+
* Execute a callback for each cell in the grid
66+
* @param {(cell: GridCell) => void} callback
67+
*/
68+
function forEachCell(callback) {
69+
grid.forEach((row) => {
70+
row.forEach((cell) => callback(cell));
71+
});
72+
}
73+
74+
/**
75+
* Draw a cell onto the canvas
76+
* @param {GridCell} cell
77+
*/
78+
function drawCell(cell) {
79+
// Draw cell background
80+
context.fillStyle = '#303030';
81+
context.fillRect(
82+
cell.x * CELL_SIZE,
83+
cell.y * CELL_SIZE,
84+
CELL_SIZE,
85+
CELL_SIZE
86+
);
87+
88+
if (cell.alive) {
89+
// Draw living cell inside background
90+
context.fillStyle = `rgb(24, 215, 236)`;
91+
context.fillRect(
92+
cell.x * CELL_SIZE + 1,
93+
cell.y * CELL_SIZE + 1,
94+
CELL_SIZE - 2,
95+
CELL_SIZE - 2
96+
);
97+
}
98+
}
99+
100+
/**
101+
* Check the state of the cell at the given coordinates
102+
* @param {number} x
103+
* @param {number} y
104+
* @returns {0 | 1}
105+
*/
106+
function isAlive(x, y) {
107+
// Out-of-border cells are presumed dead
108+
if (x < 0 || x >= numColumns || y < 0 || y >= numRows) {
109+
return 0;
110+
}
111+
112+
return grid[y][x].alive ? 1 : 0;
113+
}
114+
115+
/**
116+
* Count the number of living neighboring cells for a given cell
117+
* @param {GridCell} cell
118+
* @returns {number}
119+
*/
120+
function countLivingNeighbors(cell) {
121+
const { x, y } = cell;
122+
return (
123+
isAlive(x - 1, y - 1) +
124+
isAlive(x, y - 1) +
125+
isAlive(x + 1, y - 1) +
126+
isAlive(x - 1, y) +
127+
isAlive(x + 1, y) +
128+
isAlive(x - 1, y + 1) +
129+
isAlive(x, y + 1) +
130+
isAlive(x + 1, y + 1)
131+
);
132+
}
133+
134+
/**
135+
* Update the state of the cells in the grid by applying the Game Of Life
136+
* rules on each cell.
137+
*/
138+
function updateGrid() {
139+
// Loop over all cells to determine their next state.
140+
forEachCell((cell) => {
141+
// Count number of living neighboring cells
142+
const numAlive = countLivingNeighbors(cell);
143+
144+
if (numAlive === 2) {
145+
// Living cell remains living, dead cell remains dead
146+
cell.nextAlive = cell.alive;
147+
} else if (numAlive === 3) {
148+
// Dead cell becomes living, living cell remains living
149+
cell.nextAlive = true;
150+
} else {
151+
// Living cell dies, dead cell remains dead
152+
cell.nextAlive = false;
153+
}
154+
});
155+
156+
// Apply the newly computed state to the cells
157+
forEachCell((cell) => {
158+
cell.alive = cell.nextAlive ?? false;
159+
});
160+
}
161+
162+
//
163+
164+
/**
165+
* Render a visual representation of the grid
166+
*/
167+
function renderGrid() {
168+
// Draw all cells in the grid
169+
forEachCell(drawCell);
170+
}
171+
172+
/**
173+
* Execute one game cycle
174+
*/
175+
function gameLoop() {
176+
// Update the state of cells in the grid
177+
updateGrid();
178+
179+
// Render the updated grid
180+
renderGrid();
181+
182+
// Schedule the next generation
183+
setTimeout(() => {
184+
window.requestAnimationFrame(gameLoop);
185+
}, 200);
186+
}
187+
188+
/**
189+
* Start the game
190+
*/
191+
function start() {
192+
// Create initial grid
193+
createGrid();
194+
195+
// Render the initial generation
196+
renderGrid();
197+
198+
// Kick-start the gameLoop
199+
window.requestAnimationFrame(gameLoop);
200+
}
201+
202+
return { grid, updateGrid, start };
203+
}
204+
205+
function main() {
206+
// Resize the canvas to accommodate the desired number of cell rows and
207+
// columns
208+
const canvas = document.getElementById('canvas');
209+
if (!(canvas instanceof HTMLCanvasElement)) {
210+
throw new Error('Canvas element not found');
211+
}
212+
213+
canvas.height = NUM_ROWS * CELL_SIZE;
214+
canvas.width = NUM_COLUMNS * CELL_SIZE;
215+
216+
// Obtain a context that is needed to draw on the canvas
217+
const context = canvas.getContext('2d');
218+
if (!(context instanceof CanvasRenderingContext2D)) {
219+
throw new Error('Context not found');
220+
}
221+
222+
// Create the game "engine"
223+
const { start } = createGame(context, NUM_ROWS, NUM_COLUMNS);
224+
225+
// Start the game
226+
start();
227+
}
228+
229+
// ! Do not change or remove any code below
230+
try {
231+
window.addEventListener('load', main);
232+
} catch {
233+
// ignore if running in node with jest
234+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Game of Life</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<link href="./style.css" rel="stylesheet" />
8+
<link
9+
rel="stylesheet"
10+
href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css"
11+
integrity="sha512-Oy18vBnbSJkXTndr2n6lDMO5NN31UljR8e/ICzVPrGpSud4Gkckb8yUpqhKuUNoE+o9gAb4O/rAxxw1ojyUVzg=="
12+
crossorigin="anonymous"
13+
/>
14+
</head>
15+
16+
<body>
17+
<article class="markdown-body">
18+
<h1>Conway's Game of Life</h1>
19+
<canvas id="canvas">
20+
Your browser does not support the HTML5 canvas tag.
21+
</canvas>
22+
<h3>
23+
Adapted from:
24+
<a
25+
href="https://spicyyoghurt.com/tutorials/javascript/conways-game-of-life-canvas"
26+
>SPICY YOGHURT - Create Conway's Game of Life in JavaScript
27+
</a>
28+
</h3>
29+
<h3>
30+
See also:
31+
<a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life"
32+
>Wikipedia - Conway's Game of Life</a
33+
>
34+
</h3>
35+
<script src="index.js" type="module"></script>
36+
</article>
37+
</body>
38+
</html>

0 commit comments

Comments
 (0)