How to interact with your embedded Pico-8 games in Jekyll
I coded a small Pico-8 game for my two lovely nephews, showcasing my guinea pig Chantal, in an adventure called “Chantal Panic”. The first part of this series, where I explain how to embed the game into HTML is here.
My nephews asked for a high score board, so I had to figure out how to:
- Inject some external data from my jekyll website into the embedded Pico-8 game
- Make the game modify this data and persist it in a website that is normally static
This article will focus on the first question.
The Problem
Pico-8’s HTML export is a very long generated JS file: all the sprites, lua code is included in that file. It is not possible to inject data into that generated file.
The Solution
I had two possibilities to inject external data into the Pico-8 game:
- Use a the Cartridge data feature - You can save and re-load data from a pico-8 cartridge. If the game has been exported to HTML, the data is stored in the browser’s local storage, through the indexedDB API.
The problem: indexedDB is a very low-level API that’s complex to use with JavaScript.
- Use the GPIO feature - The pico-8 API has some functions to interact with GPIO pins. GPIO stands for “General Purpose Input Output”, they are hardware pins that you can use to communicate with the game.
And luckily, pico-8 simulates these GPIO pins in HTML exports using a simple JavaScript array!
Here is what the manual says:
Cartridges exported as HTML / .js use a global array of integers (pico8_gpio) to represent gpio pins. The shell HTML should define the array:
var pico8_gpio = Array(128);
So if we program our game to listen to these virtuals hardware pins, we can use this simple variable to store and / or modify this data that our game listens to! It’s our best bet to achieve our goal.
How It Works
Here’s the data flow:
Jekyll (_data/best_scores.yml)
↓
JavaScript (pico8_gpio array)
↓
Pico-8 Game (peek from GPIO pins)
Step 1: Make your game listen to GPIO pins
In your Pico-8 game, read from the GPIO memory region:
function fetchscores()
-- GPIO pins are mapped to memory addresses 0x5f80 - 0x5fff
-- We're using the first 4 bytes for our high score data
local namea = chr(peek(0x5f80)) -- First letter
local nameb = chr(peek(0x5f81)) -- Second letter
local namec = chr(peek(0x5f82)) -- Third letter
local amount = peek(0x5f83) -- Score amount
local name = namea..nameb..namec
local score = {}
score.name = name
score.amount = amount
return score
endWhat’s happening:
peek(0x5f80)reads from GPIO pin 0chr()converts the byte value to a character- We concatenate the three letters to form a name
Step 2: Inject the data into the Pico-8 game thanks to Javascript
Now that my game is “plugged” to the GPIO, once exported to HTML, I can inject the data I want into the game thanks to Javascript. Isn’t it amazing? I think so too!
I modified _layouts/pico-8.html to allow individual game pages to inject their GPIO setup.
Find the closing </script> tag (after all the Pico-8 player code) and add the content mark right before the <STYLE> section:
This placement is crucial - it ensures pico8_gpio is defined before the game starts running.
Step 3: Inject Data from Jekyll
Now in my game page (_projects/chantal-panic.md), I add JavaScript that reads from Jekyll’s data and writes to the GPIO array:
<script>
// Create the magic array that Pico-8 will read from
var pico8_gpio = new Array(128);
// Fetch score from Jekyll data (with defaults if file doesn't exist yet)
let bestScoreName = "{{ site.data.best_scores.chantalpanic.name | default: 'AAA' }}";
let bestScoreAmount = parseInt("{{ site.data.best_scores.chantalpanic.score | default: 0 }}");
// Convert the name into bytes (ASCII character codes)
pico8_gpio[0] = bestScoreName.charCodeAt(0); // First letter -> GPIO pin 0
pico8_gpio[1] = bestScoreName.charCodeAt(1); // Second letter -> GPIO pin 1
pico8_gpio[2] = bestScoreName.charCodeAt(2); // Third letter -> GPIO pin 2
pico8_gpio[3] = bestScoreAmount; // Score -> GPIO pin 3
</script>How it works:
-
Jekyll processes the template and injects
{{ site.data.best_scores.chantalpanic.name }} - JavaScript converts the name “BOB” to bytes:
[66, 79, 66] - Pico-8 reads these bytes with
peek()and converts them back to text
Next Steps
Now I can read data from Jekyll into Pico-8. But what about the reverse? How do I get high scores out of the game and persist them?
That’s where it gets interesting - and requires a GitHub Actions workflow. Coming in Article 3!
Part 2 of the “Pico-8 and Jekyll” series