GML How to create Game of Life in GMS1.4 / GMS2

CONWAY'S GAME OF LIFE algorithm in GML

GM Version
: Studio 1.4 / 2
Target Platform: All
Download: see code below
Summary: A nice Game of Life algorithm using GML.
This method uses the GML data structure called ds_grid to make the algorithm run faster.
We will make two grids: one for the real world and one for the calculations.
After we have made all the calculations and put into the calculation grid, we will copy the results into the real world grid. If we made everything in one grid the results would be incorrect, because each game rule would prevail over the next ones as they would be called sequentially.
The code is full of comments in order to make the game logic clearer.
I'm sorry if the tutorial might look too long but I will hopefully make a YouTube tutorial video.

Here are the game rules:
  1. Any live cell with fewer than two live neighbours dies, as if by underpopulation.
  2. Any live cell with two or three live neighbours lives on to the next generation.
  3. Any live cell with more than three live neighbours dies, as if by overpopulation.
  4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

Screenshot.png

1) First, let's create a new sprite 16x16 wide and make two image frames, one for the DEAD cell and one for the ALIVE cell.

Tutorial - 1.png

2) Let's create a new script and write some macros:
Code:
#macro TILE_SIZE 16                // Size of each individual cell
#macro DEAD 0                    // The cell dead state
#macro ALIVE 1                    // The cell alive state
#macro ONE_SECOND room_speed    // One game second
3) Let's create a new object called oController. This object will hold all the game logic and draw the game grid:

Tutorial - 3.png

4) Add a new Create Event and initialize the oController object:
Code:
/// Init controller

// Create the world grid and the calculation grid
worldWidth  = (room_width div TILE_SIZE);                // How many horizontal cells?
worldHeight = (room_height div TILE_SIZE);                // How many vertical cells?
worldGrid   = ds_grid_create(worldWidth, worldHeight);  // Create a grid 40x40 (cells) wide
calculationGrid = ds_grid_create(worldWidth, worldHeight);  // Create a grid 40x40 (cells) wide

// Clear the world grid and the calculation grid - all the cells are marked as DEAD cells
ds_grid_clear(worldGrid, DEAD);
ds_grid_clear(calculationGrid, DEAD);


// Init world grid -
startingPopoulation = 50;    // How many cells are ALIVE at the start of the game
initWorld(worldGrid, worldWidth, worldHeight, startingPopoulation); // Create population

// Update time - how fast the cells update their state
updateTime = ONE_SECOND/6;

// Calculate Popoulation - keep track of how many cells are ALIVE
population  = calculatePopolation(worldGrid, worldWidth, worldHeight);

// Whether the game is paused or not
gamePaused = false;
5) Create a new script and rename it initWorld; this will initialize the world grid generating random ALIVE cells along the whole grid.

Code:
///initWorld(grid, w, h, max_population);

var grid = argument0;                // The world grid
var w = argument1;                    // The world grid width
var h = argument2;                    // The world grid height
var maxPopulation = argument3;        // Max number of ALIVE cells at the start of the game

var counter = 0;                    // Keep track of how many ALIVE cells are being created

// Loop through the world grid
for (var i = 0; i < w; i++) {
    for (var j = 0; j < h; j++) {
        // Reached max population - break the loop
        if (counter > maxPopulation) break;
        // Chance to create ALIVE cell
        if (irandom(10) < 1) {
            // Set the world grid cell to ALIVE state
            grid[# i, j] = ALIVE;
            // Increase counter - a new ALIVE cell is being created
            counter++;
        }
    }
}
6) Create a new script and rename it calculatePopulation. This script will return the number of ALIVE cells.

Code:
///calculatePopulation(grid, w, h);

var grid = argument0;            // The world grid
var w = argument1;                // The world grid width
var h = argument2;                // The world grid height

var counter = 0;                // Keep track of how many ALIVE cells are in the world grid

// Loop through the grid
for (var i = 0; i < w; i++) {
    for (var j = 0; j < h; j++) {
        // An ALIVE cell is being found - increase the population counter
        if (grid[# i, j] == ALIVE) counter++;
    }
}

// Return the number of ALIVE cells
return counter;
7) Let's create a new Draw Event in the oController objects and write some code:

Code:
/// Draw world

drawWorld(worldGrid, worldWidth, worldHeight);

// The colour of the text
var textColour = c_red;

// Draw Population counter
draw_set_colour(textColour);
draw_text(32, 32, "Population: " + string(population));
draw_set_colour(c_white);

// Draw world speed
draw_set_colour(textColour);
draw_text(32, 64, "World Speed: " + string(updateTime));
draw_set_colour(c_white);

// Draw game state - game is currently paused
if (gamePaused) {
    draw_set_colour(textColour);
    draw_text(32, 96, "Game Paused");
    draw_set_colour(c_white);
}

8) Let's add a new script and rename it drawWorld. This script will loop though the world grid and draw all the cells. The cells will be white (image_index = 0) if the cell is DEAD and blue (image_index = 1) if the cell is ALIVE.

Code:
///drawWorld(grid, w, h);

var grid = argument0;           // The world grid
var w = argument1;                // The world grid width
var h = argument2;                // The world grid height

// Loop through the grid
for (var i = 0; i < w; i++) {
    for (var j = 0; j < h; j++) {
        // Get x and y world position
        var xPos = i * TILE_SIZE;
        var yPos = j * TILE_SIZE;
        // Get the subimage of the cell sprite - DEAD (0) or ALIVE (1)
        var subImage = grid[# i, j];
        // Draw the cell sprite at the given position and with the right subimage
        draw_sprite(sCell, subImage, xPos, yPos);
    }
}
9) Let's add a Step Event in the oController object and write some code:

Code:
/// Update world
if (alarm[0] == -1 && !gamePaused) {
    alarm[0] = updateTime;
}


// Increase / decrease update speed
speedFactor = 0.1;                    // The speed increase/decrease factor
maxSpeed = ONE_SECOND * 2;            // Max speed limit
minSpeed = ONE_SECOND / 20;            // Min speed limit


if (keyboard_check_pressed(vk_down)) {
    // Increase update speed
    updateTime = min(updateTime + speedFactor, maxSpeed);
} else if (keyboard_check_pressed(vk_up)) {
    // Decrease update speed
    updateTime = max(updateTime - speedFactor, minSpeed);
}

// Pause/unpause the game
if (keyboard_check_pressed(vk_space)) {
    // Resume the game
    if (!gamePaused) alarm[0] = updateTime;
    // Pause / unpause the game
    gamePaused = !gamePaused;
}


// Create ALIVE cell at mouse position
if (mouse_check_button_pressed(mb_left)) {
    // Get world grid position
    var xGrid = mouse_x div TILE_SIZE;
    var yGrid = mouse_y div TILE_SIZE;
 
    worldGrid[# xGrid, yGrid] = ALIVE;
 
    // Pause the game - avoid cells update during creation
    gamePaused = true;
}

10) Let's add a new Alarm0 event in the oController object:

Code:
/// Update World
updateWorld(worldGrid, worldWidth, worldHeight);

// Update real World (copy the calcuaztio grid values into the real world grid
for (var i = 0; i < worldWidth; i++) {
    for (var j = 0; j < worldHeight; j++) {
        // Copy calculation grid to world grid
        worldGrid[# i, j] = calculationGrid[# i, j];
        // Reset calculation grid
        calculationGrid[# i, j] = DEAD;
    }
}
// Calculate Population
population  = calculatePopolation(worldGrid, worldWidth, worldHeight);
11) Let's add a new script and rename it updateWorld(), this script will update all the world grid. As it is called in the alarm[0] of the oController object, it will not run each steps.

Code:
///updateWorld(grid, width, height);


var grid = argument0;            // The world grid
var w = argument1;                // The world grid width
var h = argument2;                // The world grid height


// Loop through the grid
for (var i = 0; i < w; i++) {
    for (var j = 0; j < h; j++) {
        // Check all cases
        checkAdjacent0(grid, i, j);  // Cell dies if there are less than 2 adjacent ALIVE cells
        checkAdjacent1(grid, i, j);  // Cell survive if there are 2 or 3 adjacent ALIVE cells
        checkAdjacent2(grid, i, j);  // Cell dies if there are more than 3 adjacent ALIVE cells
        checkAdjacent3(grid, i, j);  // Dead cell with exactly 3 adjacent ALIVE cells, becomes ALIVE
    }
}
11) Now here it comes the most important part of the algorithm.
We will create 4 scripts, one for each game rule:
1 - Cell dies if there are less than 2 adjacent ALIVE cells
2 - Cell survive if there are 2 or 3 adjacent ALIVE cells
3 - Cell dies if there are more than 3 adjacent ALIVE cells
4 - Dead cell with exactly 3 adjacent ALIVE cells, becomes ALIVE

IMPORTANT: All these scripts will check the world grid and put the given results into the calculation grid.

Code:
//checkAdjacent0(grid, x, y);

var grid = argument0;
var xGrid = argument1;
var yGrid = argument2;

// Count adjacent ALIVE cells
var counter = 0;

// RULE: Cell dies if there are less than 2 adjacent ALIVE cells

// Check if the cell is ALIVE
if (grid[# xGrid, yGrid] == ALIVE) {
    // Check all adjacent cells:
 
    // Top-left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top
    if (yGrid != 0 && grid[# xGrid, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top-right
    if (xGrid != worldWidth && yGrid != 0 && grid[# xGrid + 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Right
    if (xGrid != worldWidth && grid[# xGrid + 1, yGrid] == ALIVE) {
        counter++;
    }
    // Bottom-right
    if (xGrid != worldWidth && yGrid != worldHeight && grid[# xGrid + 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom
    if (yGrid != worldHeight && grid[# xGrid, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom-left
    if (xGrid != 0 && yGrid != worldHeight && grid[# xGrid - 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid] == ALIVE) {
        counter++;
    }
 
    // There are less than 2 ALIVE cells so current cell becomes a DEAD cell
    if (counter < 2) {
        calculationGrid[# xGrid, yGrid] = DEAD;
    }
}

exit;
Code:
//checkAdjacent1(grid, x, y);

var grid = argument0;
var xGrid = argument1;
var yGrid = argument2;

// Count adjacent ALIVE cells
var counter = 0;

// RULE: Cell survives if there are 2 or 3 adjacent ALIVE cells

if (grid[# xGrid, yGrid] == ALIVE) {
    // Check all adjacent cells:
 
    // Top-left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top
    if (yGrid != 0 && grid[# xGrid, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top-right
    if (xGrid != worldWidth && yGrid != 0 && grid[# xGrid + 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Right
    if (xGrid != worldWidth && grid[# xGrid + 1, yGrid] == ALIVE) {
        counter++;
    }
    // Bottom-right
    if (xGrid != worldWidth && yGrid != worldHeight && grid[# xGrid + 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom
    if (yGrid != worldHeight && grid[# xGrid, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom-left
    if (xGrid != 0 && yGrid != worldHeight && grid[# xGrid - 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid] == ALIVE) {
        counter++;
    }
 
    // Cell survives if there are 2 or 3 adjacent ALIVE cells
    if (counter == 2 || counter == 3) {
        calculationGrid[# xGrid, yGrid] = ALIVE;
    }
}

exit;
Code:
//checkAdjacent2(grid, x, y);

var grid = argument0;
var xGrid = argument1;
var yGrid = argument2;

// Count adjacent ALIVE cells
var counter = 0;

// RULE: Cell dies if there are more than 3 adjacent ALIVE cells

// Check if the cell is ALIVE
if (grid[# xGrid, yGrid] == ALIVE) {
    // Check all adjacent cells:
 
    // Top-left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top
    if (yGrid != 0 && grid[# xGrid, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top-right
    if (xGrid != worldWidth && yGrid != 0 && grid[# xGrid + 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Right
    if (xGrid != worldWidth && grid[# xGrid + 1, yGrid] == ALIVE) {
        counter++;
    }
    // Bottom-right
    if (xGrid != worldWidth && yGrid != worldHeight && grid[# xGrid + 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom
    if (yGrid != worldHeight && grid[# xGrid, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom-left
    if (xGrid != 0 && yGrid != worldHeight && grid[# xGrid - 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid] == ALIVE) {
        counter++;
    }
 
    // There are more than 3 adjacent ALIVE cells - current cell becomes a DEAD cell
    if (counter > 3) {
        calculationGrid[# xGrid, yGrid] = DEAD;
    }
}

exit;
Code:
//checkAdjacent3(grid, x, y);

var grid = argument0;
var xGrid = argument1;
var yGrid = argument2;

// Count adjacent ALIVE cells
var counter = 0;

// RULE: DEAD cell with exactly 3 adjacent ALIVE cells, becomes ALIVE

// Check if the cell is ALIVE
if (grid[# xGrid, yGrid] == DEAD) {
    // Check all adjacent cells:
 
    // Top-left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top
    if (yGrid != 0 && grid[# xGrid, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Top-right
    if (xGrid != worldWidth && yGrid != 0 && grid[# xGrid + 1, yGrid - 1] == ALIVE) {
        counter++;
    }
    // Right
    if (xGrid != worldWidth && grid[# xGrid + 1, yGrid] == ALIVE) {
        counter++;
    }
    // Bottom-right
    if (xGrid != worldWidth && yGrid != worldHeight && grid[# xGrid + 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom
    if (yGrid != worldHeight && grid[# xGrid, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Bottom-left
    if (xGrid != 0 && yGrid != worldHeight && grid[# xGrid - 1, yGrid + 1] == ALIVE) {
        counter++;
    }
    // Left
    if (xGrid != 0 && grid[# xGrid - 1, yGrid] == ALIVE) {
        counter++;
    }
 
    // There are 3 adjacent ALIVE cells, current cells becomes an ALIVE cell
    if (counter == 3) {
        calculationGrid[# xGrid, yGrid] = ALIVE;
    }
}

exit;
That's it!
Now, the Game of Life should work fine.
If you have any doubts or suggestions, please leave a comment down below.
 
Last edited:
Top