flowchart LR
input[/sequential input data W/] --> state_logic
input -- sometimes --> output_logic
subgraph "Finite State Machine"
current_state[/current state Q/]
--> state_logic["state transition logic"]
--> next_state[/next state Q'/]
-. "becomes on next clock cycle" .-> current_state
--> output_logic[output logic]
end
output_logic --> output[/sequential output data Z/]
Stratagem Devlog, Part 1
State against the machine
After five years of being abandoned on my computer, Stratagem is finally ready for public release. I’ve rewritten the original code I made in 2019 from scratch using the tools and knowledge that I learned since then.
As of writing this article, you can play the most recent version of Stratagem on your computer, smartphone, or tablet by visiting its Lexaloffle BBS page. You can view its source code directly on its GitHub repository and help me develop it by reporting issues or contributing code.
This article is part of a series called “Stratagem Devlog”.
Logic Updates
As I described before in a previous article, a finite state machine (FSM) is an abstract computing model that can be in one of a finite number of internal states at a time. In hardware design, FSMs are implemented as synchronous sequential circuits: they are synchronous because they update periodically on clock cycles, and sequential because they operate on sequential data like streams of ones and zeros or button presses over time.1
Finite state machines are not limited to just hardware: my video game is also represented as one, too. In this case, The game’s sequential input data is the series of button controls the player presses over time, the sequential output data is the what the game displays over time, the states describe what actions the player can do, and the state transitions describe the rules of the game that link those actions together. The game’s internal state dictates what is being displayed on the screen, and controls what actions the player can or can’t perform to move to a different state.2
Here is a simplified version of Stratagem v0.1.1’s state diagram. I’ve grouped all of the states relating to the actual gameplay loop into its own sub-state machine for clarity.
stateDiagram-v2
[*] --> title_screen: initialize
title_screen --> gameplay: player wants to play game
title_screen --> high_scores: player wants to see high scores
state gameplay {
[*] --> game_init
game_init --> generate_board: game is finished initializing
generate_board --> game_idle: game is finished generating board
game_idle --> level_up: player reached level score threshold
game_idle --> game_over: player ran out of chances
game_idle --> swap_select: player wants to swap gems
swap_select --> game_idle: player doesn't want to swap gems
swap_select --> player_matching: player chose gems to swap
update_board --> player_matching: board is full and may have matches
player_matching --> game_idle: board is full and has no matches
player_matching --> update_board: board has holes from cleared gems
level_up --> generate_board: player is ready for the next level
game_over --> [*]: player did not get a high score
game_over --> enter_high_score: player got a high score
enter_high_score --> [*]: player finished entering high score
}
gameplay --> high_scores
high_scores --> title_screen: player wants to play again
The PICO-8 engine contains three special functions called _init(), _draw(), and _update() that correspond to initializing the program, drawing on the screen, and updating once every frame. When modeling a PICO-8 program as a state machine, I used the _update() function for state transition logic and the _draw() function defining output logic. Large and/or reusable parts of the program’s code are split off into their own functions for easier readability.
Viewing a video game as a finite state machine helped me make parts of my code work in parallel by only executing parts of code loops that would take too long to complete within a single frame (1/30 of a second). The main example for this is how the interaction between the player_matching state and the update_board state work. These states provide the logic behind clearing matches on the gem grid and dropping new gems down from the top, respectively. In my original code for Stratagem v0.0.3, this equivalent logic was executed continuously inside a match-and-combo-calculating while-loop until the grid was refilled with gems. While this process was functionally identical to how the state diagram worked, it blocked any other operation from happening (e.g., letting the animated background update) since no other code was being executed within the while-loop. To circumvent these issues, the logic for checking state machine transitions was made fast enough to be completed within the span of a single frame.

I also added an arcade-like high-score leaderboard using the PICO-8’s persistent data storage that tracks the top ten best (local) games played.

Art Updates
My original code had unused sprite art for both a logo and smaller versions of the gems, but was missing a real title screen. So, I updated the logo to reflect the game’s new name and used the smaller gems to decorate the non-gameplay screens. I took inspiration from another PICO-8 puzzle game called Pushamo to add a simple dynamic background based on a wobbly function.3

Music Updates
The two music tracks in Stratagem’s original version were called “Space Puzzle” and “Breakage”; they were arrangements of homework assignments I made for a college music class. I scrapped both of these songs because I didn’t like their melodies. These songs also used all four of the PICO-8’s sound channels, so they clashed with the game’s sound effects.
I wrote three new songs for Stratagem: “Breakage 2”, “Calm (P8 Cover)”, and “Eight-Four”. “Breakage 2” recycled parts from “Breakage” and added both a new chord progression and a new melody. I’ve already written about “Calm” on my music page; this cover compresses it to fit on only three sound channels. “Eight-Four” is an 8/4 arrangement of an unreleased song I wrote in 7/4 un-creatively named “Seven-Four”.
I still have space to fit one more song in the cartridge, but that’s a task for another day.
Workflow Updates
Even though the PICO-8’s limited resolution is part of its design philosophy as a fantasy console, I absolutely can’t stand reading blocks of text in its 3×5 pixel font for more than ten minutes. Since the .p8 cartridge file format that it uses for describing carts can be viewed as raw text, I can use third-party tools like pico-tool to develop the game art separately from the Lua code. Splitting the code from the art lets me check the code for errors with Selene, add type annotations for Lua LS, and format it with Stylua without having to deal with how the .p8 cartridge format is organized. It wasn’t too difficult to configure these tools to work with PICO-8’s Lua dialect “P8 Lua”.
In Stratagem v0.1.0, I hastily removed pico-tool from my build scripts because I was concerned that it would make the cart-building process less portable. I instead resorted to using shell commands like cat and sed to assemble the cart since I didn’t need to use any of pico-tool’s code-shrinking tools just yet. However, I’d like to add it back to the build process since it can be easily installed in a Python virtual environment.
Conclusion
I’ve been on cloud nine since releasing Stratagem to the public. I’ve said previously that solo game development is super difficult, but I haven’t mentioned how rewarding it is. I can’t help but smile when seeing how people interact with a game I made by myself.
References
Footnotes
Brown and Vranesic, Fundamentals of Digital Logic with Verilog Design, ch. 6.↩︎
If you more information about finite state machines geared towards game developers, I suggest looking at the “State” chapter of the book “Game Programming Patterns” by Robert Nystrom.↩︎
Astute viewers of my site would recognize wobbly functions from prompt 13 of Genuary 2024.↩︎