Learning Go by examples: part 5 - Create a Game Boy Advance (GBA) game in Go

aurelievache

Aurélie Vache

Posted on August 11, 2021

Learning Go by examples: part 5 - Create a Game Boy Advance (GBA) game in Go

After created an HTTP REST API server, our first CLI (Command Line Interface) application in Go and our Bot for Discord, what can we do now?

What about coding your own Game Boy Advance game?!

Why a Game Boy Advance app/game?

As you may have already noticed in my "Understanding Kubernetes in a visual way" video serie on YouTube (in the background) I am a retrogamer fan.

Understanding Kubernetes in a visual wa

I am a Developer (and Ops) for more than 15 years, I love retrogaming and I love to play with Go.

But we can only code a game for GBA in C, isn't it? Well, it's not perfectly true, with TinyGo we can code in Go and build for a lot of microcontrollers, WebAssembly... and Game Boy Advance (GBA) machine! Go is really cool, we can do many things with this language!

And, for fun!

So what about creating a game for one of my favorite portable console?

Game Boy Advance

Game Boy Advance

The Game Boy Advance (GBA) was one of the handheld video games console produced by Nintendo released in 2001. In this console you can find a 240x160 (3:2 aspect ratio) 15-bit color LCD display, a multidirectional pad (D-pad), "A", "B", "L", "R", "START" & "SELECT" buttons for actions.

The GBA CPU is based on a 32-bit ARM7TDMI core with embedded memory.

One of the strength of this console is to be backward compatible with Game Boy and Game Boy Color games.

GBA games are loaded in a game cartridge:

GBA game

If you are interested about the technical specification of this console, you can find a lot of useful information in this article.

Initialization

We created our Git repository in the previous article, so now we just have to retrieve it locally:



$ git clone https://github.com/scraly/learning-go-by-examples.git
$ cd learning-go-by-examples


Enter fullscreen mode Exit fullscreen mode

We will create a folder go-gopher-gba for our CLI application and go into it:



$ mkdir go-gopher-gba
$ cd go-gopher-gba


Enter fullscreen mode Exit fullscreen mode

Now, we have to initialize Go modules (dependency management):



$ go mod init github.com/scraly/learning-go-by-examples/go-gopher-gba
go: creating new go.mod: module github.com/scraly/learning-go-by-examples/go-gopher-gba


Enter fullscreen mode Exit fullscreen mode

This will create a go.mod file like this:



module github.com/scraly/learning-go-by-examples/go-gopher-gba

go 1.16


Enter fullscreen mode Exit fullscreen mode

Before to start our super Game Boy Advance (GBA) application, as good practice, we will create a simple code organization.

Create the following folders organization:



.
├── README.md
├── bin
├── go.mod


Enter fullscreen mode Exit fullscreen mode

That's it? Yes, the rest of our code organization will be created shortly ;-).

TinyGo

TinyGo

TinyGo is a Go compiler for embedded systems and to the modern web.
You can compile and run TinyGo programs on a lot of microcontroller boards such as the BBC micro:bit, the Arduino Uno, the Nintendo Switch and the Game Boy Advance ;-).

TinyGo can also produce WebAssembly (WASM) code which is very compact in size. You can compile programs for web browsers, as well as for server and edge computing environments that support the WebAssembly System Interface (WASI) family of interfaces.

When I discovered TinyGo in the begining of 2020 I was just: WoW, it's awesome!!!!

First, we will install TinyGo. If you have a MacOS:



$ brew tap tinygo-org/tools
$ brew install tinygo


Enter fullscreen mode Exit fullscreen mode

Check TinyGo is correctly installed:



$ tinygo version
tinygo version 0.19.0 darwin/amd64 (using go version go1.16.5 and LLVM version 11.0.0)


Enter fullscreen mode Exit fullscreen mode

Let's create our app!

Gopher Park

What do we want?

This simple Game Boy Advance app/game should:

  • Display a screen with "Gopher" text and "Press START button"
  • Display two gophers
  • When you press START button: your Gopher player just appear
  • With multidirectional arrows you can move your Gopher at left, right, top or bottom
  • When you press A button: your Gopher jump :-D
  • When you press SELECT button, you go back to "Start" screen

It seems to be cool for a first start, let's do it!

OK, so let's start!

As we can see in the TinyGo website, the Game Boy Advance is a handheld videogame platform based on the ARM7TDMI microcontroller. It's a useful information and we can find in this page the link to the machine package for GBA... and that's all, we will talk about the lack of real documentation later :-).

Nevertheless, a lack of documentation and up-to-date working example will not stop us. So I will show you the code and explain to you step by step.

As we will display text messages in our app, we will create a fonts folder and put inside the fonts in Go that we want to use:



fonts
├── freesansbold24pt7b.go
└── gophers58pt.go


Enter fullscreen mode Exit fullscreen mode

You can find theses fonts in my GitHub repository.

After this prerequisite, we will create the code of our app. In order to do this, we will create a gopher.go file and copy/paste the following code into it.

First, Go code is organized into packages. So, we initialize the package, called main, and all dependencies/librairies we need to import and use in our main file:



package main

import (
    "image/color"
    "machine"
    "runtime/interrupt"
    "runtime/volatile"
    "unsafe"

    "github.com/scraly/learning-go-by-examples/go-gopher-gba/fonts"
    "tinygo.org/x/tinydraw"
    "tinygo.org/x/tinyfont"
)


Enter fullscreen mode Exit fullscreen mode

Then, we initialize a list of variables:



var (
    //KeyCodes / Buttons
    keyDOWN      = uint16(895)
    keyUP        = uint16(959)
    keyLEFT      = uint16(991)
    keyRIGHT     = uint16(1007)
    keyLSHOULDER = uint16(511)
    keyRSHOULDER = uint16(767)
    keyA         = uint16(1022)
    keyB         = uint16(1021)
    keySTART     = uint16(1015)
    keySELECT    = uint16(1019)

    // Register display
    regDISPSTAT = (*volatile.Register16)(unsafe.Pointer(uintptr(0x4000004)))

    // Register keypad
    regKEYPAD = (*volatile.Register16)(unsafe.Pointer(uintptr(0x04000130)))

    // Display from machine
    display = machine.Display

    // Screen resolution
    screenWidth, screenHeight = display.Size()

    // Colors
    black = color.RGBA{}
    white = color.RGBA{255, 255, 255, 255}
    green = color.RGBA{0, 255, 0, 255}
    red   = color.RGBA{255, 0, 0, 255}

    // Google colors
    gBlue   = color.RGBA{66, 163, 244, 255}
    gRed    = color.RGBA{219, 68, 55, 255}
    gYellow = color.RGBA{244, 160, 0, 255}
    gGreen  = color.RGBA{15, 157, 88, 255}

    // Coordinates
    x int16 = 100 //TODO: horizontally center
    y int16 = 100 //TODO: vertically center
)


Enter fullscreen mode Exit fullscreen mode

As you can see, we define several variables that we will use in our code, in order to avoid a code complex to read and in order to not copy/paste RGB colors code in several pieces of code.

Then, we define our main() function that:

  • configure and register the display (the console's screen)
  • call the function that will draw our texts and Gophers
  • plug an update() function in an interrupt signal
  • define an infinite loop to avoid exiting the application


func main() {
    // Set up the display
    display.Configure()

    // Register display status
    regDISPSTAT.SetBits(1<<3 | 1<<4)

    // Display Gopher text message and draw our Gophers
    drawGophers()

    // Creates an interrupt that will call the "update" fonction below, hardware way to display things on the screen
    interrupt.New(machine.IRQ_VBLANK, update).Enable()

    // Infinite loop to avoid exiting the application
    for {
    }
}


Enter fullscreen mode Exit fullscreen mode

Let's implement our drawGophers() function:



func drawGophers() {

    // Display a textual message "Gopher" with Google colors
    tinyfont.DrawChar(display, &fonts.Bold24pt7b, 36, 60, 'G', gBlue)
    tinyfont.DrawChar(display, &fonts.Bold24pt7b, 71, 60, 'o', gRed)
    tinyfont.DrawChar(display, &fonts.Bold24pt7b, 98, 60, 'p', gYellow)
    tinyfont.DrawChar(display, &fonts.Bold24pt7b, 126, 60, 'h', gGreen)
    tinyfont.DrawChar(display, &fonts.Bold24pt7b, 154, 60, 'e', gBlue)
    tinyfont.DrawChar(display, &fonts.Bold24pt7b, 180, 60, 'r', gRed)

    // Display a "press START button" message - center
    tinyfont.WriteLine(display, &tinyfont.TomThumb, 85, 90, "Press START button", white)

    // Display two gophers
    tinyfont.DrawChar(display, &fonts.Regular58pt, 5, 140, 'B', green)
    tinyfont.DrawChar(display, &fonts.Regular58pt, 195, 140, 'X', red)
}


Enter fullscreen mode Exit fullscreen mode

As I want to display text messages, I used TinyFont package with fonts we put in fonts folder... and I used it to display our Gophers too!
For the moment we can't display an image and load a sprite so I had to find a way to display our cute Gophers ;-).

Then, we need to implement our update() function:



func update(interrupt.Interrupt) {

    // Read uint16 from register regKEYPAD that represents the state of current buttons pressed
    // and compares it against the defined values for each button on the Gameboy Advance
    switch keyValue := regKEYPAD.Get(); keyValue {
    // Start the "game"
    case keySTART:
        // Clear display
        clearScreen()
        // Display gopher
        tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
    // Go back to Menu
    case keySELECT:
        clearScreen()
        drawGophers()
    // Gopher go to the right
    case keyRIGHT:
        // Clear display
        clearScreen()
        x = x + 10
        // display gopher at right
        tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
    // Gopher go to the left
    case keyLEFT:
        // Clear display
        clearScreen()
        x = x - 10
        // display gopher at right
        tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
    // Gopher go to the down
    case keyDOWN:
        // Clear display
        clearScreen()
        y = y + 10
        tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
    // Gopher go to the up
    case keyUP:
        // Clear display
        clearScreen()
        y = y - 10
        tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
    // Gopher jump
    case keyA:
        // Clear display
        clearScreen()
        // Display the gopher up
        y = y - 20
        tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
        // Clear the display
        clearScreen()
        // Display the gopher down
        y = y + 20
        tinyfont.DrawChar(display, &fonts.Regular58pt, x, y, 'B', green)
    }
}


Enter fullscreen mode Exit fullscreen mode

Hum... Aurélie, is it possible to explain to us what you have done in this function?

In fact, previously we registered to KeyPad so everytime a button and/or a multidirectional arrow is pressed, the update() function is called so into it. Then I create a switch case that allow me to execute code depending on the button pressed.

And, finally, I don't find any existing method that clear screen, or that fill a color in the entire screen, GBA machine TinyGo implementation don't have the needed function. So I use a trick: I create a small function that draw a rectangle with the full size of the screen in black (as black is the background color of the display screen).



func clearScreen() {
    tinydraw.FilledRectangle(
        display,
        int16(0), int16(0),
        screenWidth, screenHeight,
        black,
    )
}


Enter fullscreen mode Exit fullscreen mode

Install needed dependencies

We use external packages/dependencies in gopher.go file, so we have to install them:



$ go get tinygo.org/x/tinydraw
$ go get tinygo.org/x/tinyfont


Enter fullscreen mode Exit fullscreen mode

TinyFont

TinyGo is not a standalone way to build Go app for microcontrollers and lightweight hardware but the main GitHub repository contains also several useful tools like TinyFont.

TinyFont

TinyFont is afont/text package for TinyGo displays (based on Adafruit's GFX library).

TinyFont repository contains several fonts you can use in your apps and import like this:



import (
    "tinygo.org/x/tinyfont/freemono"
    "tinygo.org/x/tinyfont/freesans"
    "tinygo.org/x/tinyfont/freeserif"
)


Enter fullscreen mode Exit fullscreen mode

If you want to use your personal fonts, you can convert it from BDF format to "TinyGo" compatible format with tinyfontgen tool.

Usage example:



$ tinyfontgen --package my-font --fontname MyFontRegular12pt MyFont-Regular-12pt.bdf --output MyFont-Regular-12pt.go --all


Enter fullscreen mode Exit fullscreen mode

TinyDraw

TinyDraw is an useful tool that allow you to draw geometrics figures (based on Adafruit GFX library).

TinyDraw

Thanks to TinyDraw you can easily draw geometrics forms:



    white = color.RGBA{255, 255, 255, 255}
    green = color.RGBA{0, 255, 0, 255}
    red   = color.RGBA{255, 0, 0, 255}

    // ...

    tinydraw.Line(&display, 100, 100, 40, 100, red)

    tinydraw.Rectangle(&display, 30, 106, 120, 20, white)
    tinydraw.FilledRectangle(&display, 34, 110, 112, 12, green)

    tinydraw.Circle(&display, 120, 30, 20, white)
    tinydraw.FilledCircle(&display, 120, 30, 16, red)

    tinydraw.Triangle(&display, 120, 102, 100, 80, 152, 46, white)
    tinydraw.FilledTriangle(&display, 120, 98, 104, 80, 144, 54, green)


Enter fullscreen mode Exit fullscreen mode

Gopher font

As we can't display images and sprites for the moment in GBA hardware with TinyFont, I needed to find a way to display our favorite Gophers, the trick is to use an awesome existing Gopher font created by Jaana Dogan (@rakyll), thanks!!

Gopher font

And you can even play with this font in the 2ttf playground.

2ttf playground

GBA emulator

It's time to test our app, but if you don't have a real Game Boy Advance (GBA) retro portable videogame console, it's not a problem because several emulator exists.

TinyGo recommend, and use, mGBA, a Game Boy Advance software emulator so we will install it to test our apps.

First, download mGBA, untar it and then copy it in your PATH:



$ cp ./bin/mgba /usr/local/bin/mgba


Enter fullscreen mode Exit fullscreen mode

As usual, check the executable is working correctly:



$ mgba --version
mgba 0.9.2 (f6d5f51d231053cc8a1778b7a139096d2bcf7324)


Enter fullscreen mode Exit fullscreen mode

Cool!

You can run mGBA with the command line tool mgba, (located in bin folder), but you have also the possibility to run the visual application (located in Applications):

mGBA app

By default, controls are mapped to the keyboard like this:



    A: X
    B: Z
    L: A
    R: S
    Start: Enter
    Select: Backspace


Enter fullscreen mode Exit fullscreen mode

Test it!

It's time to test our little game!



$ tinygo run -target=gameboy-advance gopher.go
tinygo:ld.lld: warning: lld uses blx instruction, no object with architecture supporting feature detected


Enter fullscreen mode Exit fullscreen mode

This command will compile the code, execute mGBA emulator and load your app.

Start menu

Awesome!

Now, press the "START" button (the ENTER touch in your keyboard), you should see a green Gopher appear at the center of the screen.

green Gopher

With multidirectional arrows you can move the Gopher at left, right, bottom and top.

And if you press the "A" button (X in the keyboard by default), Gopher should do a jump :-).

You can watch the following video with the demo:

Build it!

Your application is ready, you can build it.
For that, like the previous articles, we will use Taskfile in order to automate our common tasks.

So, for this app too, I created a Taskfile.yml file with this content:



version: "3"

tasks:

    run: 
        desc: Run the app
        cmds:
        - GOFLAGS=-mod=mod tinygo run -target=gameboy-advance gopher.go

    build:
        desc: Build the GBA app
        cmds:
        - GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.gba -target=gameboy-advance gopher.go

    build-mgba:
        desc: Build the GBA app for mGBA
        cmds:
        - GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.elf -target=gameboy-advance gopher.go
        - mv bin/gopher.elf bin/gopher.gba

    mgba:
        desc: Load the game
        cmds:
        - mgba bin/gopher.gba


Enter fullscreen mode Exit fullscreen mode

Thanks to this, we can build our app easily:



$ task build
task: [build] GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.gba -target=gameboy-advance gopher.go
tinygo:ld.lld: warning: lld uses blx instruction, no object with architecture supporting feature detected
   code    data     bss |   flash     ram
   4128   28536    3116 |   32664   31652

$ ll bin/gopher.gba
-rwxr-xr-x  1 aurelievache  staff   229K  7 aoû 18:53 bin/gopher.gba


Enter fullscreen mode Exit fullscreen mode

Tips and tricks



    build-mgba:
        desc: Build the GBA app for mGBA
        cmds:
        - GOFLAGS=-mod=mod tinygo build -size short -o bin/gopher.elf -target=gameboy-advance gopher.go
        - mv bin/gopher.elf bin/gopher.gba


Enter fullscreen mode Exit fullscreen mode

Wait a minute, what is this strange tricks in the build-mgba task?

Currently, there is a bug with mGBA: tinygo build command don't build a correct GBA ROM that mGBA can load and run correctly.



$ tinygo build -target=gameboy-advance -o bin/gba-display.gba gba-display.go 
tinygo:ld.lld: warning: lld uses blx instruction, no object with architecture supporting feature detected

$ file bin/gba-display.gba
bin/gba-display.gba: data


Enter fullscreen mode Exit fullscreen mode

When you run it through mGBA emulator, the game crash:



$ mgba bin/gba-display.gba
The game crashed!


Enter fullscreen mode Exit fullscreen mode

Game crashed

So after having a discussion with TinyGo contributors, if I build to an .elf file and then copy it to a .gba file, it should working... and it's true.

So if you have the same bug like me, you know the trick ;-).

If you want to load it through VisualBoyAdvance app, another GBA emulator, you don't need the trick, the following command will run a working format:



$ tinygo build -size short -o bin/gopher.gba -target=gameboy-advance gopher.go

Enter fullscreen mode Exit fullscreen mode




Test on a real handset!

I don't like emulator, I prefer "physical" retrogaming console so I wanted to test with a real handset. I've got the chance to have one GBA and one GBA SP console in working condition, but how can I transfer my gopher.gba file into my console?

In order to do that you can use a "linker", like the "EZ Flash Omega" I bought. It allow you to copy your .gba files into a microSD card you put in this particular GBA cartridge.

It's time to test ... crossed fingers!

Gopher on GBA SP

The little Gopher app is working in the Game Boy Advance SP! :-)

Easy! Isn't it?

Well ... If I tell you it was very easy to create this game (this simple app with cute Gophers), it's a lie ^^.

TinyGo is a really good tool but one of the pain point is the documentation and concrete up-to-date and working examples :-(.

I will be totally honest to you. I knew & know nothing about microcontrollers, electronics and hardware so without concrete examples, without tutorials, without a good documentation, it was not easy to create this app but I'm very happy to created it and I hope with this article will motivate you to test TinyGo in your side :-).

If you want to test TinyGo, you can play with it through the existing playground.

Moreover, with TitiMoby, we created a fresh new GitLab repository tinygo-examples containing several working and concrete examples in TinyGo with Adafruit PyGamer, GBA... and soon WebAssembly.
Don't hesitate to add your own TinyGo examples! :-)

TinyGo examples

Conclusion

As you have seen in this article and previous articles, it's possible to create applications in Go: CLI, REST API, Bot for Discord... and also apps for microcontrollers and retro consoles! :-)

All the code of our little Gopher GBA app in Go is available in: https://github.com/scraly/learning-go-by-examples/tree/main/go-gopher-gba

In the following articles we will create others kind/types of applications in Go.

Hope you'll like it.

💖 💪 🙅 🚩
aurelievache
Aurélie Vache

Posted on August 11, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related