# Dev Log 5 - Implementing Tarot Spreads

2025-01-04 - [53] 10:20

I'm a bit farther than this post implies, but I figured I'd give an update explaining that I have arbitrary tarot spreads working! At least for now, I have 13 different tarot spreads represented. They are:

- 1 Card
- 3 Card
- 5 Card
- 5 Card Cross
- 6 Card Cross
- Celtic Cross
- Split Path
- Zodiac
- Year
- Week Ahead
- Horseshoe
- Tetraktys
- Tree of Life

All of these tarot spreads fit within 20 by 16 tiles and use fewer than 16 cards, which turns out to be very useful, as I'll explain a little later.

Some spread examples can be seen in the 3 images below.

## 16 Card Limit

So while it's not necessarily a hard limit, I am currently limited to displaying only 16 unique cards in a spread if I don't want to cause some lag while drawing out the cards. This is because each card uses 6 unique tiles along while about 27 different tiles can easily be reused. Adding a blank tile so the background can be blanked makes the total amount of tiles 124 out of the 128 we can easily use. The bottom third of the image below shows the card tiles for the Celtic Cross tarot spread from before.

As mentioned in the previous dev log, a card back slides into position, does a flipping animation, then disappears. As the sprites disappear, card tiles are drawn in place. This is done for all cards of the tarot spread, no matter if there is only 1 card or 16 cards (none of the spreads so far use all 16).

## Card Position

The background consists of 8x8 pixel tiles, as does the window layer. While the background is technically 32x32 tiles in size (256x256 pixels), the screen can only show a rectangle of 160x144 pixels (equivalent to 20x18 tiles). It is important that these cards are aligned to the 8x8 pixel grid. The Celtic Cross spread from before is shown below with the 8x8 pixel grid over it, showing that all 10 cards are aligned to the grid.

A benefit of using 16 cards per spread no matter what is that I can multiply a spread offset by 16 by shifting the bits of the offset left 4 times. In the code shown below, I use 2 bytes per card, a Y and X value, for the offset of each card of each spread. That means a spread could use 32 bytes, which means I can bit shift the spread offset left 5 times to quickly get to the Y value of the first card offset of the spread. From that location, I can take the card offset and bit shift left 1 time to get to the Y value of the specific card offset of the specific spread.

    ; Top-left tile position of small cards in spread
    ; byte 1 = tile Y value
    ; byte 2 = tile X value
    ; pad with $ff bytes
    SpreadCardTileOrigins:
    .1Card:
    	db 6, 8
    	ds 16*2-1*2, $ff
    .3Card:
    	db 6, 3
    	db 6, 8
    	db 6, 13
    	ds 16*2-3*2, $ff
    .5Card:
    	db 6, 0
    	db 6, 4
    	db 6, 8
    	db 6, 12
    	db 6, 16
    	ds 16*2-5*2, $ff
    .5CardCross:
    	db 6, 3
    	db 6, 8
    	db 6, 13
    	db 1, 8
    	db 11, 8
    	ds 16*2-5*2, $ff
    .6CardCross:
    	db 6, 8
    	db 6, 7
    	db 11, 8
    	db 6, 3
    	db 1, 8
    	db 6, 13
    	ds 16*2-6*2, $ff
    .celticCross:
    	db 6, 6
    	db 6, 5
    	db 11, 6
    	db 6, 1
    	db 1, 6
    	db 6, 11
    	db 12, 16
    	db 8, 16
    	db 4, 16
    	db 0, 16
    	ds 16*2-10*2, $ff
    .splitPath:
    	db 0, 8
    	db 4, 8
    	db 4, 4
    	db 8, 4
    	db 12, 4
    	db 4, 12
    	db 8, 12
    	db 12, 12
    	ds 16*2-8*2, $ff
    .zodiac:
    	db 0, 8
    	db 1, 11
    	db 2, 14
    	db 6, 16
    	db 10, 14
    	db 11, 11
    	db 12, 8
    	db 11, 5
    	db 10, 2
    	db 6, 0
    	db 2, 2
    	db 1, 5
    	db 6, 8
    	ds 16*2-13*2, $ff
    .year:
    	db 0, 8
    	db 1, 11
    	db 2, 14
    	db 6, 16
    	db 10, 14
    	db 11, 11
    	db 12, 8
    	db 11, 5
    	db 10, 2
    	db 6, 0
    	db 2, 2
    	db 1, 5
    	db 5, 8
    	db 6, 4
    	db 6, 12
    	ds 16*2-15*2, $ff
    .weekAhead:
    	db 3, 2
    	db 3, 6
    	db 3, 10
    	db 3, 14
    	db 8, 2
    	db 8, 8
    	db 8, 14
    	ds 16*2-7*2, $ff
    .horseshoe:
    	db 2, 2
    	db 6, 2
    	db 9, 5
    	db 11, 8
    	db 9, 11
    	db 6, 14
    	db 2, 14
    	ds 16*2-7*2, $ff
    .tetraktys:
    	db 12, 14
    	db 12, 10
    	db 12, 6
    	db 12, 2
    	db 8, 12
    	db 8, 8
    	db 8, 4
    	db 4, 10
    	db 4, 6
    	db 0, 8
    	ds 16*2-10*2, $ff
    .treeOfLife:
    	db 0, 8
    	db 1, 12
    	db 1, 4
    	db 5, 12
    	db 5, 4
    	db 4, 8
    	db 9, 12
    	db 9, 4
    	db 8, 8
    	db 12, 8
    	ds 16*2-10*2, $ff

As tiles are 8x8 pixels in size, I can get particular start pixel values by bit shifting offsets left 3 times, which multiplies values by 8. The Game Boy uses a lot of powers of 2, which is very convenient, so I only really need to deal with simple bit shifts and simple additions.

## Sprites

When drawing the face down card sprites that move to position, or the cursor that goes over the card, I need to convert the Y and X value offsets to pixel values sprites are good with. Sprites have 2 modes, an 8x8 mode and an 8x16 mode, where 2 tiles are stacked vertically as a single sprite. Because sprites are allowed to be 16 pixels tall and because sprites can be offscreen, either to the left of the screen or above the screen, the Y value of a sprite is not the top left pixel offset of the screen you want the sprite to go to. It's actually 16 pixels more than what you expect. Same thing with the X value, but 8 pixels instead of 16. That means if you want a sprite to be aligned to the top left of the screen, you need to set the Y value to 16 and the X value to 8.

## Conclusion

It's nice to be able to do multiple different tarot spreads and potentially add or change the spreads in the future by just adding or changing a couple values! While not discussed in the dev log, I have the ability currently to set a card vertically, horizontally, or horizontally over a vertical card. So far none of the tarot spreads use a horizontal card that isn't on top of a vertical card, but maybe I'll find a spread soon that can use it.

I'm pretty sure this post is rambly like the last one and pretty sure that will continue to be the case. My apologies for that. I suppose these dev logs are sort of mostly to show progress is being made, as opposed to teaching someone how to make a Game Boy game. Teaching is not the intention of these dev logs. With that said, explaining is at least partially the intention of these dev logs.

The next dev log will probably be about controller input handling, which I already have implemented in the ROM. After that, I'll likely talk about Bank Switching in order to allow more than 32KiB of total storage space for the ROM.

## Contact/Reply

If you would like to reply to this post, feel free to send me an email.

Email: vi@vigrey.com [Email]
PGP Public Key [515F AD67 F931 0A2B 9B93 CE19 814F ECB1 A398 63CE]