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

Discussion in 'Tutorials' started by AlexDerFerri, Dec 22, 2019.

  1. AlexDerFerri

    AlexDerFerri Member

    Joined:
    Aug 24, 2017
    Posts:
    100
    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: Dec 23, 2019
    Escaliburn and Liam Jacobs like this.

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice