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 ๐