GameLink: multiplayer gaming with TinyGo Keeb

![gamelink.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3960226/c6cc3017-9e6e-5504-83ea-7cf0929d93a1.jpeg)

# What is a GameLink!?
If you grew up in the 80s and 90s like me, you might remember the tech wonder that [Nintendo Game Boy Game Link Cable](https://en.wikipedia.org/wiki/Game_Link_Cable) was, a cable that allowed you to connect two Game Boy systems for multiplayer gaming, way before online gaming was a thing. As a kid, it was like magic, so as soon as I played a little with [TinyGo Keeb zero-kb02](https://github.com/sago35/keyboards/tree/main/zero-kb02), I decided I needed to make one for them.

### TinyGo, TinyGo Keeb and zero-kb02

So the logical next question is, what does it mean any of those words?
- **TinyGo** is a project to bring the Go programming language to microcontrollers and modern web browsers by creating a new compiler based on LLVM
- **TinyGo Keeb** is how I referred to customs keyboards that have a [TinyGo](https://tinygo.org/) firmware, the series of workshops called *TinyGo Keeb Tour* and the most popular model *zero-kb02*. (it should be noted that the official repository is called [tinygo-keyboard](https://github.com/sago35/tinygo-keyboard))
- **zero-kb02** is probably the most popular of such keyboards (or keebs as called in jargon). Both TinyGo Keeb and zero-kb02 is the work of @sago35, none of this would be possible without him
- **TinyGo Keeb Tour 2024 & 2025** is a series of workshops held in several cities of Japan where the attendees solder and build the keyboard zero-kb02 and runs a few example programs on them

![keeb.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3960226/b0ba0cdc-4a2b-8548-dcbc-7f6b5903b0d3.jpeg)

> *TinyGo Keeb zero-kb02*

## Connecting two devices
We are going to use the [I²C](https://en.wikipedia.org/wiki/I%C2%B2C) interface of the *zero-kb02* to talk to each other. To make everything easier we are going to use a Waveshare zero RP2040 (it could be another board, but those are very cheap and I have  many) to handle all the messages from one device to another. Connect one I²C port to each different device, like shown in the image below.

![circuit.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3960226/b0a47de8-d951-6159-1942-e7d7db160a4c.png)

 >\**the 5V/VCC>red wire is not needed*

![i2c_detail.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3960226/5515fe04-e2f0-6851-065f-f3e772ae0991.jpeg)

> *shown in detail the I²C connector of zero-kb02 below the USB-C*


The *firmware* of the *GameLink* is very simple too. Two ring buffers that can holds several *"messages"*, *device A* writes a message in *buffer A*, then *device B* read the message from *buffer A* and viceversa. The magic of it resides in the **I2C.Listen(port)** func, which configures to port to listen to incomming I²C messages. Then it's a matter to reply with an appropriate response: 

```go
for {
    evt, n, err := ports[port].WaitForEvent(buf)
    if err != nil {
    }

    switch evt {
    case machine.I2CReceive: // store received message
        if n > dataSize {
            n = dataSize
        }
        stacks[port].writePtr = (stacks[port].writePtr + 1) % stackSize
        for o := 0; o < n; o++ {
            stacks[port].stack[stacks[port].writePtr].data[o] = buf[o]
        }
        for o := n; o < dataSize; o++ {
            stacks[port].stack[stacks[port].writePtr].data[o] = 0
        }
        stacks[port].stack[stacks[port].writePtr].read = false

    case machine.I2CRequest: // return the oldest unread message
        portClient := (port + 1) % 2
        ptr := (stacks[portClient].readPtr + 1) % stackSize
        if stacks[portClient].stack[ptr].read {
            ports[port].Reply([]byte{0})
            continue
        }
        stacks[portClient].readPtr = ptr
        ports[port].Reply(stacks[portClient].stack[ptr].data[:])
        stacks[portClient].stack[ptr].read = true

    case machine.I2CFinish:

    default:
    }
}
 ```


## GameLink driver
And finally our *TinyGo Keebs* need a driver to talk to the *GameLink*. The driver itself will be very simple for this Proof of Concept 

```go
// Write sends a message to the GameLink to be read later by the other device connected to
func (d *Device) Write(data []uint8) error {
	return d.bus.Tx(d.Address, data, nil)
}

// Read returns a message from the GameLink sent by the other device connected to
func (d *Device) Read() ([]uint8, error) {
	data := make([]uint8, 10)
	err := d.bus.Tx(d.Address, nil, data)
	return data, err
}
```

![gamelink_keebs.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3960226/03261bfc-f2c7-4d64-cdf7-e96db003e558.jpeg)

> *two zero-kb02 connected via a GameLink*


## Tic-tac-four?
To showcase the *GameLink* I created a small *Proof of Concept* game of *tic-tac-toe* played on a 4x3 matrix, you can see and download the code in the [official github repository](https://github.com/conejoninja/gamelink/blob/main/main.go). Please, be aware this is a *PoC* and as any software in the early stages, it contains multiple errors, lacks features and a lot of wrong decissions were made during its making.



![tic-tac-four](https://github.com/conejoninja/gamelink/blob/main/assets/tic-tac-four.gif?raw=true)

> *demo of a tic-tac-four gameplay*




This article is part of [TinyGo - Qiita Advent Calendar 2024](https://qiita.com/advent-calendar/2024/tinygo)