Build Your Own Game Boy Game, Part 4: Taking Control
- Marcel Pflug
- May 3
- 6 min read
Updated: Jul 4
We have an arena with walls, but it is completely still. In this part we give it a star: a snake that the player can steer around the board. To get there we need three things. We need a way to remember where the snake is, a way to read the buttons, and a loop that ties it all together and runs the game. Let us take them one at a time.

Remembering the Snake
A snake is really just a chain of body segments, each sitting in one grid cell. The neat way to store that is two lists: one holding the column of every segment, and one holding the row. We also remember how long the snake currently is, and which direction it is travelling. Position zero in the lists is always the head.
#define MAX_LEN 128 // the longest the snake can ever get
uint8_t snake_x[MAX_LEN]; // column of each segment
uint8_t snake_y[MAX_LEN]; // row of each segment
uint8_t length; // how many segments right now
int8_t dir_x, dir_y; // current direction (-1, 0 or 1)These few lines are just labelled boxes to remember things in. The define line fixes MAX_LEN, the longest the snake can ever grow, at 128. snake_x and snake_y are two lists (programmers call them arrays), each with room for 128 numbers; together they store the column and row of every body segment, with position 0 always being the head. length holds how many segments exist right now, and dir_x and dir_y hold the current direction as a pair of small numbers, each being minus one, zero or plus one. For example, dir_x of 1 with dir_y of 0 means moving right.
Now we set the snake up at the start of a game. We place a short, three-segment snake in the middle of the board and point it to the right.
void init_snake(void) {
uint8_t i;
length = 3;
for (i = 0; i < length; i++) {
snake_x[i] = 10 - i; // head at 10, body trailing left
snake_y[i] = 9;
set_bkg_tile_xy(snake_x[i], snake_y[i], T_BODY);
}
dir_x = 1; // moving right
dir_y = 0;
}init_snake lays down the starting snake. length = 3 says three segments to begin with. The for loop then runs i from 0 to 2 and, for each segment, sets its column to 10 - i (so the head sits at column 10 and the body trails off to the left), sets its row to 9, and draws a body tile there. Finally dir_x = 1 and dir_y = 0 point it to the right. A short snake, ready to go.
Reading the Game Boy Controls
GBDK-2020 gives us a joypad function that tells us which buttons are currently held. We check the four direction buttons and update the snake's direction. There is one classic rule in Snake: you cannot instantly turn back on yourself, so we only allow an up or down turn when the snake is moving horizontally, and a left or right turn when it is moving vertically.
void read_input(void) {
uint8_t keys = joypad();
if ((keys & J_UP) && dir_y == 0) { dir_x = 0; dir_y = -1; }
else if ((keys & J_DOWN) && dir_y == 0) { dir_x = 0; dir_y = 1; }
else if ((keys & J_LEFT) && dir_x == 0) { dir_x = -1; dir_y = 0; }
else if ((keys & J_RIGHT) && dir_x == 0) { dir_x = 1; dir_y = 0; }
}The one new symbol here is the & in keys & J_UP. joypad() hands back all the buttons squashed into a single number, and & is how we ask whether one particular button is pressed, so (keys & J_UP) means the up button is down. The extra check && dir_y == 0 enforces the classic Snake rule: you may only turn up or down while moving sideways, which stops the snake instantly doubling back on itself. The else if chain means only the first matching direction is taken each time.
Making the Snake Move
Moving the snake is the clever bit, and it is simpler than it looks. We work out where the new head will be, then shuffle every segment one place along the chain so the body follows the head. We draw a body tile at the new head, and clear the tile where the tail used to be. Because we only ever change two tiles per step, the head and the old tail, the game stays fast even when the snake grows long.
void move_snake(void) {
int8_t i;
uint8_t nx = snake_x[0] + dir_x; // new head column
uint8_t ny = snake_y[0] + dir_y; // new head row
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); // draw new head
set_bkg_tile_xy(tail_x, tail_y, T_EMPTY); // erase old tail
}This is the heart of the movement, so let us take it slowly. First we work out the new head position, nx and ny, by adding the direction to the old head (position 0). We also make a note of where the tail currently is. Then comes the clever part: the for loop runs backwards, from the last segment to the second, and copies each segment into the position of the one in front of it, so the whole body shuffles forward by one and follows the head. We then place the new head into position 0, draw a body tile there, and rub out the tile where the tail used to be. Because only two tiles ever change, the head and the old tail, the game stays fast no matter how long the snake grows.
The Game Loop
Every game has a heartbeat: a loop that repeats forever, reading input and updating the screen. On the Game Boy we pace that loop with wait_vbl_done, which pauses until the console has finished drawing the current frame. That happens about 60 times a second, which would make our snake far too fast, so we use a small counter and only move the snake every sixth frame. That gives a comfortable, classic Snake speed while still reading the buttons smoothly.
void main(void) {
uint8_t timer = 0;
set_bkg_data(0, 4, tiles);
SHOW_BKG;
DISPLAY_ON;
draw_walls();
init_snake();
while (1) {
wait_vbl_done(); // wait one frame (~1/60 s)
read_input(); // check the buttons every frame
if (++timer >= 6) { // but only move every 6th frame
timer = 0;
move_snake();
}
}
}The main function is the whole game in miniature. It loads the tiles, shows the background, turns on the screen, draws the walls and places the snake, exactly the pieces from Part 3. Then while (1) starts a loop that runs forever. Each time round, wait_vbl_done pauses until the console has finished drawing one frame (about a sixtieth of a second), and read_input checks the buttons. The line if (++timer >= 6) counts those frames and only lets the snake move on every sixth one; without it the snake would lurch sixty times a second, far too fast to play. So the buttons stay responsive while the snake glides at a comfortable pace.
Build the ROM and you will have a snake gliding around the arena, turning crisply as you press the direction pad. It does not eat, grow or die yet, but it is unmistakably a game now, and it responds to your hands.
Coming Up in Part 5
In Part 5 we turn this into a real, winnable and losable game. We will scatter food for the snake to eat, make it grow with every bite, and add the rules that end the game when it hits a wall or its own tail. By the end you will have a complete, playable Snake.










Comments