.gitignore | ||
00thefool.aseprite | ||
00TheFool.inc | ||
01themagician.aseprite | ||
01themagician.asm | ||
01TheMagician.inc | ||
02thehighpriestess.aseprite | ||
02thehighpriestess.asm | ||
02TheHighPriestess.inc | ||
03theempress.aseprite | ||
03theempress.asm | ||
03TheEmpress.inc | ||
04theemperor.aseprite | ||
04theemperor.asm | ||
04TheEmperor.inc | ||
05thehierophant.aseprite | ||
05thehierophant.asm | ||
05TheHierophant.inc | ||
06thelovers.aseprite | ||
06thelovers.asm | ||
06TheLovers.inc | ||
07thechariot.aseprite | ||
07thechariot.asm | ||
07TheChariot.inc | ||
08strength.aseprite | ||
08strength.asm | ||
08Strength.inc | ||
09thehermit.aseprite | ||
09thehermit.asm | ||
09TheHermit.inc | ||
10wheeloffortune.aseprite | ||
10wheeloffortune.asm | ||
10WheelOfFortune.inc | ||
11justice.aseprite | ||
11justice.asm | ||
11Justice.inc | ||
12thehangedman.aseprite | ||
12thehangedman.asm | ||
12TheHangedMan.inc | ||
13death.aseprite | ||
13death.asm | ||
13Death.inc | ||
14temperance.aseprite | ||
14temperance.asm | ||
14Temperance.inc | ||
15thedevil.aseprite | ||
15thedevil.asm | ||
15TheDevil.inc | ||
16thetower.aseprite | ||
16thetower.asm | ||
16TheTower.inc | ||
17thestar.aseprite | ||
17thestar.asm | ||
17TheStar.inc | ||
18themoon.aseprite | ||
18themoon.asm | ||
18TheMoon.inc | ||
19thesun.aseprite | ||
19thesun.asm | ||
19TheSun.inc | ||
20judgement.aseprite | ||
20judgement.asm | ||
20Judgement.inc | ||
21theworld.aseprite | ||
21theworld.asm | ||
21TheWorld.inc | ||
Async.inc | ||
CardHelpers.inc | ||
CardLibrary.inc | ||
CopyRangeSafe.inc | ||
CopyTilesSafe.inc | ||
curve_authoring.html | ||
gb-export.lua | ||
generate_animation_circling.py | ||
hardware.inc | ||
justice.asm | ||
KeyArtTiles.asm | ||
main.asm | ||
misc.py | ||
Random.inc | ||
readme.md | ||
ScreenCardBrowse.inc | ||
ScreenCardRead.inc | ||
screendesigns.aseprite | ||
screendesigns.asm | ||
ScreenMainMenu.inc | ||
ScreenShuffle.inc | ||
ScreenSpreadSelect.inc | ||
source.zip | ||
Sprites.asm | ||
SpritesLayer.asm | ||
SpriteTiles.asm |
This is a tarot deck for the Nintendo Game Boy (1989). I hand-coded it in assembly using gbdev.io's live rgbds interface. I made the art in Aseprite, using a script I heavily modified to export the data in a gameboy-compatible format for inclusion in the assembly source.
The entry point for this software is main.asm. I would love to have properly separated all the functionality into different asm files, but rgbds-live seems not to be able to link them together. So I used INCLUDE
directives to split it up instead. Each file beginning wih Screen
describes one screen of the app. Simple enough. I tend to organize apps into screens or scenes, with the main loop repeatedly calling the appropriate update
and draw
functions on a scene object, and a helper function to change what scene is loaded (and call setup
and teardown
, if applicable). It seemed natural enough. It doesn't lend itself to fancy scene-changing animations, but... I'm working with hand-coded assembly on the Game Boy (1989). Forgive me for not putting enough delight and pop! in the UI of my tarot project.
What is there to say about this project? The hardest part by far has been the art. I get into flow states programming really easily - major thanks to my spouse for keeping me from burning out on this project by enforcing working hours. The art has been a struggle. I did not realize how many drawings 22 pieces of art is. It's twenty two! That's a lot! I vaguely dread the idea of doing a whole deck. I'm going to avoid it for now.
The part of this project I'm most proud is the Async subsystem, which you can find defined, unsurprisingly, in Async.inc
. It's a fully functioning multithreaded programming system, powered by the game boy's LYC interrupt - a "safe" time period is defined, as a subset of the vblank period, and whenever the LY counter reaches that region, the interrupt is used to switch to a second stack and resume execution of the second thread. The upshot of this is you can write totally normal-looking code, and if you call Async_Spawn_HL
with the appropriate argument, it will then run that code only during the "safe" time period. This went through a lot of changes in development, initially without a second stack, so the second thread had to be written with weird compromises like disabling interrupts around function calls. But now it's proper, fully functional context switching!
Let me rephrase: you can have two simultaneous threads of execution, with their own stacks, and it will jump between them so that the second thread only executes during a safe subset of vblank. So you can write totally normal code that copies data to vram, and you don't have to keep track of when it's safe to write.
I'm really proud of some of the art. Especially the sun, the meteor, death, and strength.
Ah, I mentioned The Meteor! I changed Judgement to The Meteor to make the final six cards form a neat sequence of astral bodies. The meteor is depicted streaking down towards Earth, bringing destruction, devastation, a total reset, a game over. It's more oblique than Judgement but honestly, I'm the artist and I get to make the decisions. Maybe I just wanted to draw a meteor. Maybe it's a little bit of a Homestuck reference.