Hi, last night I was watching video on YouTube with title "Math Has a Fatal Flaw". I saw Conway's Game of Life there.
I think every programmer should write the game, but for 4 years of programming experience I have never written this 😔.
The Post about How did I write Conway's Game of Life on JavaScript.
Last night I thought that I cannot write this game and It was sad 😵💫 but I was able.
For start I define constants.
const START_NUMBERS_OF_CELL = 2000
const CELL_SIZE = 10
const LIFE_WIDTH = document.documentElement.offsetWidth
const LIFE_HEIGHT = document.documentElement.offsetHeight
const GAME_BOARD_BACKGROUND_COLOR = "#000000";
I am using the screen size of the user's device for the size of the game board. I defined START_NUMBERS_OF_CELL
, CELL_SIZE
and GAME_BOARD_BACKGROUND_COLOR
too so I can to configuration my game.
Cell's Class
I am using ECMAScript classes in my JavaScript code and canvas for drawing game for users.
I wanna start for Cell's class because this class is very simple class.
In order to draw Cell on canvas I need canvas context and x
and y
coordinates
class Cell {
//...
constructor(ctx, x, y) {
this.ctx = ctx
this.x = x
this.y = y
}
//...
}
I know that I should kill cell If cell has not 2 or 3 neighbors so I need draw and dead methods.
class Cell {
//...
get position() {
return [
this.x * CELL_SIZE,
this.y * CELL_SIZE,
CELL_SIZE,
CELL_SIZE,
]
}
draw(color = "#ffffff") {
this.ctx.fillStyle = color
this.ctx.fillRect(...this.position)
}
dead() {
this.ctx.fillStyle = GAME_BOARD_BACKGROUND_COLOR
this.ctx.fillRect(...this.position)
}
//...
}
I defined neighbors variable like privet variable and did setter and getter methods for work with it.
class Cell {
#neighbors = 0
//...
set neighbors(neighbors) {
this.#neighbors = neighbors
}
get neighbors() {
return this.#neighbors
}
}
Life's Class
Let's start Life class.
In Life class' constructor I passed HTMLCanvasElement
and define canvas context, draw background and define array of cell. I have array of arrays so that I filled this.cells
a empty items.
class Life {
constructor(canvas) {
this.canvas = canvas
this.canvasWidth = LIFE_WIDTH / CELL_SIZE
this.canvasHeight = LIFE_HEIGHT / CELL_SIZE
this.canvas.width = LIFE_WIDTH
this.canvas.height = LIFE_HEIGHT
this.ctx = this.canvas.getContext("2d")
this.ctx.fillStyle = GAME_BOARD_BACKGROUND_COLOR
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height)
this.cells = []
for (let i = 0; i < this.canvasWidth; i++) {
this.cells[i] = []
for (let j = 0; j < this.canvasHeight; j++) {
this.cells[i][j] = undefined
}
}
//...
}
//...
}
After that I did cycle from 0 to our START_NUMBERS_OF_CELL constant so that I fill of cells the game board. I generate random random position for cells and check If the cell is not in this.cells
I create new a cell and drawing it. After that I need run the game. I am using requestAnimationFrame
.
class Life {
constructor(canvas) {
//...
for (let i = 0; i < START_NUMBERS_OF_CELL; i++) {
const cellXPosition = Math.floor(Math.random() * this.canvasWidth)
const cellYPosition = Math.floor(Math.random() * this.canvasHeight)
if (!this.cells[cellXPosition][cellYPosition]) {
this.cells[cellXPosition][cellYPosition] = new Cell(this.ctx, cellXPosition, cellYPosition, false)
this.cells[cellXPosition][cellYPosition].draw()
}
}
this.deadWave = this.deadWave.bind(this)
requestAnimationFrame(this.deadWave)
}
deadWave() {
//...
}
}
After initialization the game board I have left write rules of the game in deadWave
method:
- Any live cell with fewer than two live neighbours dies, as if by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
In deadWave
method I have cycle for check cell neighbor and boring new cells using rules and cycle for dead cells
Cycles start like that
//...
deadWave() {
for (let i = 0; i < this.canvasWidth; i++) {
for (let j = 0; j < this.canvasHeight; j++) {
//...
}
}
for (let i = 0; i < this.canvasWidth; i++) {
for (let j = 0; j < this.canvasHeight; j++) {
//...
}
}
}
//...
In first cycle in start of iteration I check that cell by i, j
address is exist and if it is I set neighbor
of cell is 0.
Next I check every neighbor cell (in total is 8) and If neighbor cell is exist I count it.
In the end of cycle I check that cell is exist and if it is I set count of neighbors to cell If cell is not exist I do boring of cell and set neighbor is 2 because next cycle If cell have not neighbor cell will be dead.
//...
deadWave() {
for (let i = 0; i < this.canvasWidth; i++) {
for (let j = 0; j < this.canvasHeight; j++) {
if (this.cells[i][j]) {
this.cells[i][j].neighbors = 0
}
let countAroundCells = 0
//...
if (this.cells[i][j]) {
this.cells[i][j].neighbors = countAroundCells
} else if (countAroundCells === 3) {
this.cells[i][j] = new Cell(this.ctx, i, j)
this.cells[i][j].draw(this.randomColor)
}
}
}
//...
}
//...
Next cycle if a cell is exist I check that the cell is newborn and if is it I set newborn false
value. If the cell is not newborn I kill the cell.
//...
deadWave() {
//...
for (let i = 0; i < this.canvasWidth; i++) {
for (let j = 0; j < this.canvasHeight; j++) {
if (this.cells[i][j]) {
if (this.cells[i][j].newborn) {
this.cells[i][j].newborn = false;
} else if (this.cells[i][j].neighbors !== 2 && this.cells[i][j].neighbors !== 3) {
this.cells[i][j].dead()
this.cells[i][j] = undefined
}
}
}
}
}
//...
Finally I should call deadWave method again and again so that I call requestAnimationFrame(this.deadWave)
in the end of the method.
Thank you for reading the post ☺️
Full code you can see in GitHub repository or live demo right now
Top comments (2)
Very cool
Thank u 😌