top of page

Build Your Own Game Boy Game, Part 5: Food, Growth and Game Over

  • Writer: Marcel Pflug
    Marcel Pflug
  • May 1
  • 6 min read

Updated: Jul 4


Our snake glides around the arena, but it has nothing to do and no way to lose. In this final building part we add the three things that turn a moving line into a real game: food to chase, growth as a reward, and rules that end the run when you crash. After this, you will have a complete Snake that you can lose, swear at, and immediately play again.

Scattering the Food

Food should appear at a random empty spot inside the walls. GBDK-2020 provides a random number generator, which we use to pick a column and a row. If the spot we picked is already occupied, by the snake or by a wall, we simply try again until we find an empty cell, then place the food tile there.

#include <rand.h>

void place_food(void) {
    uint8_t fx, fy;
    do {
        fx = 1 + (rand() % (GRID_W - 2));   // somewhere inside the walls
        fy = 1 + (rand() % (GRID_H - 2));
    } while (get_bkg_tile_xy(fx, fy) != T_EMPTY);
    set_bkg_tile_xy(fx, fy, T_FOOD);
}

This little function drops food somewhere valid. rand() asks GBDK for a random number, and the % (remainder) trims it down into range, so rand() % (GRID_W - 2) lands somewhere between the side walls; adding 1 keeps it off the wall itself. The do ... while wrapper means try this, and if the chosen spot is not empty (because the snake or a wall is already there) try again, repeating until it finds a free cell. Only then does it place the food tile. It is a tidy way of saying keep guessing until you find an empty square.

Eating, Growing and Crashing

Now we upgrade the move function from Part 4 into a full game step. Before moving, we look at the tile the head is about to enter. If it is a wall or part of the snake, the game is over and we report it. If it is food, the snake grows by keeping its old tail instead of erasing it, and we drop a fresh piece of food. Otherwise it is a normal move and we clear the tail as before.

// returns 1 if the snake died this step
uint8_t step_game(void) {
    int8_t i;
    uint8_t nx = snake_x[0] + dir_x;
    uint8_t ny = snake_y[0] + dir_y;
    uint8_t hit = get_bkg_tile_xy(nx, ny);

    if (hit == T_WALL || hit == T_BODY)
        return 1;                       // crashed: game over

    uint8_t tail_x = snake_x[length - 1];
    uint8_t tail_y = snake_y[length - 1];

    for (i = length - 1; i > 0; i--) {  // body follows the head
        snake_x[i] = snake_x[i - 1];
        snake_y[i] = snake_y[i - 1];
    }
    snake_x[0] = nx;
    snake_y[0] = ny;
    set_bkg_tile_xy(nx, ny, T_BODY);

    if (hit == T_FOOD) {                // ate food: grow
        if (length < MAX_LEN) {
            snake_x[length] = tail_x;
            snake_y[length] = tail_y;
            length++;
        }
        place_food();
    } else {                           // normal move: clear tail
        set_bkg_tile_xy(tail_x, tail_y, T_EMPTY);
    }
    return 0;
}

step_game is the Part 4 move function with the rules added, so read it in three beats. First it works out the square the head is about to enter and looks at what is already there, storing that in hit. If hit is a wall or the snake's own body, it returns 1, our agreed signal for game over. If the snake survived, the middle section shuffles the body forward and draws the new head, exactly as before. Then the decision: if hit was food, the snake grows by keeping its old tail segment instead of erasing it, adds one to length, and drops a new piece of food; otherwise it was an ordinary move, so we rub out the tail as usual. Returning 0 at the end means the snake is still alive. That single returned number, 1 or 0, is how the game loop will know whether to carry on.

Starting and Restarting

We want each game to begin fresh, so we gather the setup into one function that clears the arena, places a new three-segment snake and drops the first food. This is the Part 4 setup with a clean-up sweep and a piece of food added.

void init_game(void) {
    uint8_t x, y, i;
    for (y = 1; y < GRID_H - 1; y++)        // clear the arena
        for (x = 1; x < GRID_W - 1; x++)
            set_bkg_tile_xy(x, y, T_EMPTY);

    length = 3;
    for (i = 0; i < length; i++) {
        snake_x[i] = 10 - i;
        snake_y[i] = 9;
        set_bkg_tile_xy(snake_x[i], snake_y[i], T_BODY);
    }
    dir_x = 1;
    dir_y = 0;
    place_food();
}

init_game simply resets everything for a fresh run. The two stacked for loops together sweep across every cell inside the walls (one loop for the rows, one nested inside it for the columns) and set each to empty, wiping the old game away. Then, exactly as in Part 4, it lays down a new three-segment snake pointing right, and finally drops the first piece of food with place_food.

One small but important detail: a random generator needs a seed, or it produces the same food pattern every time. A neat trick is to wait for the player to press START, count rapidly while the button is held, and use that count as the seed. Because no two people hold the button for exactly the same time, the food lands differently every game.

void main(void) {
    uint8_t timer;
    uint16_t seed = 0;
    set_bkg_data(0, 4, tiles);
    SHOW_BKG;
    DISPLAY_ON;

    waitpad(J_START);                   // wait for the player
    while (joypad() & J_START) seed++;   // count while held
    initrand(seed);                     // seed the randomness

    while (1) {                         // one game per outer loop
        draw_walls();
        init_game();
        timer = 0;
        while (1) {                     // the game loop
            wait_vbl_done();
            read_input();
            if (++timer >= 6) {
                timer = 0;
                if (step_game()) break;  // died: leave the loop
            }
        }
        waitpad(J_START);               // game over: press START
        waitpadup();                    // wait for release, then restart
    }
}

The main function now runs whole games back to back. The first lines load the tiles and switch the screen on. Then comes the seeding trick: waitpad(J_START) freezes until START is pressed, while (joypad() & J_START) seed++ counts rapidly for as long as it is held, and initrand(seed) uses that count so the food falls differently each time. After that, the outer while (1) plays one game per lap: it draws the walls, sets up a fresh board, and runs the inner game loop. That inner loop moves the snake every sixth frame and watches step_game; the moment step_game reports a crash, break jumps out of it. We then wait for START and its release before looping back to start another game. Two loops, one nested inside the other: the inner one is a single game, the outer one lets you play again and again.

Finishing the Snake Game

Put all the pieces together, the tiles and defines from Part 3, the data and input from Part 4, and the food, growth and game-over logic from this part, and you have a complete Snake game in roughly 150 lines of C. Build it, hold START to begin, and play. The snake eats, grows longer with every bite, and the run ends the moment you clip a wall or your own tail. Press START again and you are straight into a fresh game.

Take a moment to appreciate what you have done. You designed graphics, talked to real Game Boy hardware, handled input and wrote game logic, and the result is a genuine cartridge-ready game you made yourself. That is the same craft, in miniature, that built the library this whole collection celebrates.


Build your own Game Boy game - Let's play
Let's play - the final snake game for your Game Boy

Building Your snake.gb File

Throughout this series you have built each step the same way you built your very first ROM back in Part 1: with the lcc tool that ships with GBDK-2020. To turn the finished game into a playable file, save the complete program as snake.c, open a terminal in that folder, and run a single command. Adjust the path so that it points at the lcc program inside your GBDK bin folder.

lcc -o snake.gb snake.c

That produces snake.gb, a real Game Boy ROM. Open it in an emulator such as Emulicious, BGB or SameBoy to play right away, or copy it onto a flash cartridge to run it on a genuine DMG-01. We look at that last step, playing on real hardware, in more detail in Part 6.

Download the Game Files

Want to skip ahead, or check your work against a finished version? Grab the complete project here: the full snake.c source code, ready to build, together with the compiled snake.gb ROM so you can play it straight away.



Coming Up in Part 6

So far we have played in an emulator. In the final part we take the leap that makes it all real: running your game on an actual Game Boy, plus where to go next if you have caught the bug, from friendlier tools to deeper ones and the welcoming community behind them.


The Full Series


Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page