All Projects

Game Boy Advance Snake Game

Individual Project · March 2025

Computer Organization and Programming

Overview

A fully functional snake game written in C for the Game Boy Advance, running on an emulator and talking directly to the hardware through memory-mapped registers.

Highlights

  • Individually designed and implemented software for a fully functional snake game to run on a Game Boy Advance emulator.
  • Wrote and tested over 500 lines of C code, including game state management, display interface, and other memory register mapped functionality.
  • Debugged and solved many issues within the code, including tracing memory-related issues using gdb.
Title screen of the Game Boy Advance snake game, with a brick wall background and a Press START prompt

The Assignment

This was the final homework in Georgia Tech’s CS 2110, Computer Organization and Programming: write an interactive program in C that runs on a Game Boy Advance emulator. There’s no operating system or graphics library on the GBA. The screen, the buttons, and the DMA controller are all reached through memory-mapped registers at fixed addresses, so the program works directly with the hardware.

The requirements were a title screen, images, text, button input, collision detection, and no screen tearing, all built on Mode 3 video, DMA, and VBlank timing. I chose to make Snake.

The game's title screen: a brick wall background with a blinking Press START prompt
The title screen

Talking to the Hardware

Before writing the game, I finished the course’s starter library in gba.c and gba.h. In Mode 3 the screen is a 240×160 bitmap, with each pixel stored as a 16-bit color in video memory starting at 0x6000000. Copying pixels one at a time from the CPU is too slow, so images and rectangles go through DMA channel 3: write a source address, a destination address, and a control word, and the hardware copies a whole row at once.

To keep the picture from tearing, drawing waits for VBlank, the short gap after the screen finishes a frame. waitForVBlank polls the scanline counter register to find it. I also wrote a KEY_JUST_PRESSED macro that uses bitmasks to compare this frame’s button register with the last one, so holding a button counts as a single press.

void waitForVBlank(void) {
  while (SCANLINECOUNTER > HEIGHT);   // wait until we're out of the current VBlank
  while (SCANLINECOUNTER < HEIGHT);   // then wait for the next one to start
  vBlankCounter++;
}

void drawImageDMA(int row, int col, int width, int height, const u16 *image) {
  for (int r = 0; r < height; r++) {  // one DMA transfer per row
    DMA[DMA_CHANNEL_3].src = image + OFFSET(r, 0, width);
    DMA[DMA_CHANNEL_3].dst = videoBuffer + OFFSET(row + r, col, WIDTH);
    DMA[DMA_CHANNEL_3].cnt = width | DMA_ON | DMA_SOURCE_INCREMENT;
  }
}
Two of the library functions from gba.c

The Game

The board is a 30×18 grid of 8-pixel cells, with the score in a bar along the bottom. The snake moves one cell every 8 frames, about 7.5 cells per second.

  • The snake is a doubly linked list. Each tick adds a new head, and the tail is removed unless the snake just ate.
  • Turns pressed between ticks go into a queue, so two quick presses (like a tight U-turn) both register. Presses that would reverse the snake into itself are ignored.
  • Two power-ups are on the board at a time. When one is eaten, it respawns on a random cell that isn’t covered by the snake or the other power-up.
  • Hitting a wall or your own body loses; a score of 100 wins. SELECT returns to the title screen at any point.
  • The head has eyes and a forked tongue that turn with it, and on a loss the eyes become X’s.
Gameplay: a green snake on a dark grid, with power-ups on the board and the score along the bottom
Gameplay in the mGBA emulator

Drawing Without Tearing

The GBA is slow enough that redrawing the full screen on every move would tear. So each tick only redraws what changed. The new head cell is drawn, and the old tail cell is erased by copying the matching piece of the background image back over it. The previous tongue is cleared, and the score bar is redrawn only when the score changes. All of this happens right after a single waitForVBlank call, inside the blank period.

The title screen, grid background, power-up sprite, and end-of-game card are my own artwork (the title’s brick texture is by Gregory Ligman). I converted them into C arrays with nin10kit.