diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 5008ddf..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa8981f --- /dev/null +++ b/.gitignore @@ -0,0 +1,138 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +.DS_Store diff --git a/Week4/prep-exercises/2-game-of-life/Cell.js b/Week4/prep-exercises/2-game-of-life/Cell.js new file mode 100644 index 0000000..cac08da --- /dev/null +++ b/Week4/prep-exercises/2-game-of-life/Cell.js @@ -0,0 +1,57 @@ +/** + * @typedef {Object} GridCell + * @property {number} x + * @property {number} y + * @property {boolean} alive + * @property {boolean} [nextAlive] + */ + +export default class Cell { + static size; + + constructor(x, y) { + this.x = x; + this.y = y; + this.alive = Math.random() > 0.5; + this.nextAlive = false; + } + + draw(context) { + // Draw this background + context.fillStyle = '#303030'; + context.fillRect( + this.x * Cell.size, + this.y * Cell.size, + Cell.size, + Cell.size + ); + + if (this.alive) { + // Draw living this inside background + context.fillStyle = `rgb(24, 215, 236)`; + context.fillRect( + this.x * Cell.size + 1, + this.y * Cell.size + 1, + Cell.size - 2, + Cell.size - 2 + ); + } + } + + liveAndLetDie(aliveNeighbors) { + if (aliveNeighbors === 2) { + // Living cell remains living, dead cell remains dead + this.nextAlive = this.alive; + } else if (aliveNeighbors === 3) { + // Dead cell becomes living, living cell remains living + this.nextAlive = true; + } else { + // Living cell dies, dead cell remains dead + this.nextAlive = false; + } + } + + update() { + this.alive = this.nextAlive; + } +} diff --git a/Week4/prep-exercises/2-game-of-life/Game.js b/Week4/prep-exercises/2-game-of-life/Game.js new file mode 100644 index 0000000..697d15e --- /dev/null +++ b/Week4/prep-exercises/2-game-of-life/Game.js @@ -0,0 +1,35 @@ +import Grid from './Grid.js'; + +const CELL_SIZE = 10; +const NUM_COLUMNS = 75; +const NUM_ROWS = 40; + +export default class Game { + constructor(canvas) { + // Resize the canvas to accommodate the desired number of cell rows and + // columns + canvas.height = NUM_ROWS * CELL_SIZE; + canvas.width = NUM_COLUMNS * CELL_SIZE; + + // Obtain a context that is needed to draw on the canvas + this.context = canvas.getContext('2d'); + if (!(this.context instanceof CanvasRenderingContext2D)) { + throw new Error('Context not found'); + } + + this.grid = new Grid(NUM_ROWS, NUM_COLUMNS, CELL_SIZE); + } + + gameLoop() { + this.grid.render(this.context); + this.grid.update(); + + setTimeout(() => { + window.requestAnimationFrame(() => this.gameLoop()); + }, 200); + } + + start() { + window.requestAnimationFrame(() => this.gameLoop()); + } +} diff --git a/Week4/prep-exercises/2-game-of-life/Grid.js b/Week4/prep-exercises/2-game-of-life/Grid.js new file mode 100644 index 0000000..3157730 --- /dev/null +++ b/Week4/prep-exercises/2-game-of-life/Grid.js @@ -0,0 +1,65 @@ +import Cell from './Cell.js'; + +export default class Grid { + rows = []; + + constructor(numRows, numColumns, cellSize) { + this.numRows = numRows; + this.numColumns = numColumns; + + Cell.size = cellSize; + + // Create the grid as a two-dimensional array (i.e. an array of arrays) + for (let y = 0; y < numRows; y++) { + const row = []; + for (let x = 0; x < numColumns; x++) { + const cell = new Cell(x, y); + row.push(cell); + } + this.rows.push(row); + } + } + + isAlive(x, y) { + // Out-of-border cells are presumed dead + if (x < 0 || x >= this.numColumns || y < 0 || y >= this.numRows) { + return 0; + } + + const cell = this.rows[y][x]; + return cell.alive ? 1 : 0; + } + + countLivingNeighbors(cell) { + const { x, y } = cell; + return ( + this.isAlive(x - 1, y - 1) + + this.isAlive(x, y - 1) + + this.isAlive(x + 1, y - 1) + + this.isAlive(x - 1, y) + + this.isAlive(x + 1, y) + + this.isAlive(x - 1, y + 1) + + this.isAlive(x, y + 1) + + this.isAlive(x + 1, y + 1) + ); + } + + forEachCell(callback) { + this.rows.forEach((row) => { + row.forEach((cell) => callback(cell)); + }); + } + + update() { + this.forEachCell((cell) => { + const aliveNeighbors = this.countLivingNeighbors(cell); + cell.liveAndLetDie(aliveNeighbors); + }); + + this.forEachCell((cell) => cell.update()); + } + + render(context) { + this.forEachCell((cell) => cell.draw(context)); + } +} diff --git a/Week4/prep-exercises/2-game-of-life/README.md b/Week4/prep-exercises/2-game-of-life/README.md new file mode 100644 index 0000000..b0eb941 --- /dev/null +++ b/Week4/prep-exercises/2-game-of-life/README.md @@ -0,0 +1,102 @@ +# Prep exercise: Conway's Game of Life + +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). + +From Wikipedia: + +> 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. + +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 follows: + +1. Any live cell with two or three live neighbors survives. +2. Any dead cell with three live neighbors becomes a live cell. +3. All other live cells die in the next generation. Similarly, all other dead cells stay dead. + +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. + +![Game of Life changing](../../../assets/game-of-life-1.gif) + +## Code walk-through + +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. + +### `class Cell` + +This class represents a single cell. + + +| Methods | Description | +|----------|-------------| +| 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 its next `alive` state. | +| draw() | Draws the cell on the canvas. The visual representation depends on whether the cell is alive or dead. | +| liveAndLetDie() | Determines the next state (alive or dead) depending on the number of living neighbors of the cell, by applying the rules of the Game Of Life. | +| update() | Updates the state of the cell (alive or dead) as set previously by `liveAndLetDie()`. + +### `class Grid` + +This class manages the game grid, made up of cells. + + +| Methods | Description | +|----------|-------------| +| `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 to the canvas context unto which the cells will be drawn. | +| `forEachCell()` | Executes a callback function for each cell in the two-dimensional grid array, passing the cell as a parameter to the callback. | +| `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 to be dead. The function returns one if the given cell is alive or zero if its dead. | +| `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. | +| `update()` | Iterates through all cells of the grid and computes the new state of each cell. | +| `render()` | Iterates through all cells of the grid and draws each cell onto the canvas. | + +### `class Game` + + +| Methods | Description | +|----------|-------------| +| `gameLoop()` | Executes one life cycle of the game (i.e., `grid.render()` followed by `grid.update()`) and then reschedules itself to run again after a delay. | +| `start()` | The `start()` method calls `gameLoop()` to kickstart the game. | + +The `main()` function gets a reference to the `canvas` element hard-coded in the `index.html` file and instantiates a `Game` object, passing the canvas reference to the `Game` constructor. + +The diagram below visualizes the overall call hierarchy of the various classes and methods. + +![Game of Life Call Graph](../../../assets/GameOfLife.png) + +The `gameLoop()` method calls `grid.update()` to update (each cell of) the grid according to the game rules (see above) and 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. + +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. + +### Exercise + +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: + +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. + +2. In `draw` method of the `Cell` class, replace [`rgb()`]() with [`rgba()`]() that adds a fourth parameter indicating `opacity` to the `rgb` value like this: + + ```js + context.fillStyle = `rgba(24, 215, 236, ${opacity})`; + ``` + + The `opacity` of each rendered cell should depend on the cell's `lifeTime` property, as specified in this table: + + | lifeTime | opacity | + | :------: | :-----: | + | 1 | 0.25 | + | 2 | 0.5 | + | 3 | 0.75 | + | 4+ | 1 | + +3. In the `liveAndLetDie()` method of the `Cell` class add code to update the `lifeTime` value of each cell: + + - A living cell that remains living should have its `lifeTime` incremented by one. + - A living cell that dies should have its `lifeTime` reset to zero. + - A dead cell that is brought to life should have its `lifeTime` reset to one. + +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). + +![Game of Life stable](../../../assets/game-of-life-2.gif) + +- Cells in a still life pattern remain living indefinitely and should therefore stabilize at the highest opacity. + +- 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. + +_Have fun!_ diff --git a/Week4/prep-exercises/2-game-of-life/app.js b/Week4/prep-exercises/2-game-of-life/app.js new file mode 100644 index 0000000..db55856 --- /dev/null +++ b/Week4/prep-exercises/2-game-of-life/app.js @@ -0,0 +1,16 @@ +import Game from './Game.js'; + +function main() { + const canvas = document.getElementById('canvas'); + if (!(canvas instanceof HTMLCanvasElement)) { + throw new Error('Canvas element not found'); + } + + // Create the game "engine" + const game = new Game(canvas); + + // Start the game + game.start(); +} + +window.addEventListener('load', main); diff --git a/Week4/prep-exercises/2-game-of-life/index.html b/Week4/prep-exercises/2-game-of-life/index.html new file mode 100644 index 0000000..1281dfb --- /dev/null +++ b/Week4/prep-exercises/2-game-of-life/index.html @@ -0,0 +1,38 @@ + + + + + Codestin Search App + + + + + + + + + diff --git a/Week4/prep-exercises/2-game-of-life/style.css b/Week4/prep-exercises/2-game-of-life/style.css new file mode 100644 index 0000000..f8ae468 --- /dev/null +++ b/Week4/prep-exercises/2-game-of-life/style.css @@ -0,0 +1,18 @@ +canvas { + border: 1px solid lightgrey; + border-radius: 6px; +} + +.markdown-body { + box-sizing: border-box; + min-width: 200px; + max-width: 980px; + margin: 0 auto; + padding: 45px; +} + +@media (max-width: 767px) { + .markdown-body { + padding: 15px; + } +} diff --git a/assets/GameOfLife.drawio b/assets/GameOfLife.drawio new file mode 100644 index 0000000..0aba370 --- /dev/null +++ b/assets/GameOfLife.drawio @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/GameOfLife.png b/assets/GameOfLife.png new file mode 100644 index 0000000..d14c6b5 Binary files /dev/null and b/assets/GameOfLife.png differ diff --git a/assets/game-of-life-1.gif b/assets/game-of-life-1.gif new file mode 100644 index 0000000..aa77cd0 Binary files /dev/null and b/assets/game-of-life-1.gif differ diff --git a/assets/game-of-life-2.gif b/assets/game-of-life-2.gif new file mode 100644 index 0000000..7d53440 Binary files /dev/null and b/assets/game-of-life-2.gif differ diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..0704fb8 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,15 @@ +module.exports = [ + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + modules: true, + }, + }, + }, + rules: { + semi: 'error', + 'prefer-const': 'error', + }, + }, +];