Current state

This commit is contained in:
Carl Vargklint 2026-07-01 00:07:44 +02:00
parent 27c866f26b
commit db29d89cbf
12 changed files with 5348 additions and 4488 deletions

74
OPTIMIZATION.md Normal file
View File

@ -0,0 +1,74 @@
# Size optimization backlog
Flash budget is **16 KB**. Read the real usage with:
```bash
riscv-none-elf-size tama.elf # flash = text+data, RAM = data+bss
```
(Ignore `.elf`/`.hex` *file* sizes — they include debug info / ASCII framing and
are not the flashed image. `tama.bin` is the true image.)
Current state (after the printf removal below): **~11.9 KB / 16 KB (~73%)**.
---
## DONE
### ✅ Drop printf/snprintf — saved ~1.4 KB
Replaced every `snprintf` with tiny `s_cat`/`u_cat` string builders (in `tama.c`),
disabled both printf backends in `funconfig.h`
(`FUNCONF_USE_DEBUGPRINTF`/`FUNCONF_USE_UARTPRINTF` = 0), and stubbed the SSD1306
I2C driver's lone `printf` with `#define printf(...) ((void)0)` before its include.
Result: `mini_vpprintf` + `mini_itoa` + `mini_pad` + `_write` fully garbage-collected.
**Readability:** neutral/positive. No behaviour change (the stubbed printf only
logged an I2C timeout we can't recover from anyway).
---
## TODO — ranked by value vs. effort
### 1. Crop sprite frames — ~0.50.7 KB · low risk
Every creature frame is stored as **32×32 = 128 bytes**, but the art only fills
roughly the middle ~28 rows; the top/bottom rows are empty padding. `ssd1306_drawImage`
already takes explicit width/height, so we can store shorter frames.
- **How:** in `sprites_gen.py`, crop each frame to e.g. 32×28 (width must stay a
multiple of 8; height is free). Emit the real height and adjust the draw `y`.
- **Savings:** ~4 rows × 4 bytes × ~18 frames ≈ 0.30.7 KB.
- **Readability:** art stays editable as ASCII; only the generator changes.
### 2. Alias more Panko frames — ~0.130.26 KB · trivial
Shell/Tmux already reuse 4 frames across the 7 expression slots. Panko (the panda)
still has 7 distinct frames. `EXP_BLINK` and `EXP_EAT` could alias `idle`/`happy`
with little visible loss.
- **How:** in the `species_frames[SP_PANKO]` row of `sprites_gen.py`, point the
blink/eat slots at `pana_idle`/`pana_happy` and delete the now-unused frames.
- **Savings:** 128 B per dropped frame (12 frames).
- **Readability:** neutral. (Trade-off: loses the dedicated blink/eat animation.)
### 3. Table-drive the beep jingles — ~0.10.2 KB · low risk
Many multi-call `beep(...) beep(...) beep(...)` sequences inline a lot of immediates.
- **How:** store jingles as small `{freq,ms}` arrays and add a `play(jingle, n)`
helper; replace the inline sequences with one call each.
- **Savings:** modest, scales with number of jingles (~8 today).
- **Readability:** arguably improves it (named jingles).
### 4. Trim the font table — up to ~1.5 KB · NOT recommended
`fontdata` in `ssd1306.h` is a full 256-char × 8-byte table = **2048 B**; we only
use ASCII ~0x200x7A.
- **Why not:** requires editing the *vendored* `ch32v003fun/extralibs/ssd1306.h`,
which is fragile across library updates and hurts ease-of-use. Only worth it if
we're truly out of space. If done, keep a local patched copy and document it.
### 5. RLE-compress sprites — ~1 KB · NOT recommended
Sprites are mostly runs of 0x00/0xFF and would compress well, but this needs a
decoder in the draw path and makes the art opaque (no more ASCII editing).
Net readability loss; only revisit under severe pressure.
---
## Already optimal — don't bother
- **Compiler flags:** already `-Os -flto -ffunction-sections -fdata-sections
-Wl,--gc-sections`. GCC has no `-Oz`.
- **RAM:** ~55% used, not the constraint. The 1 KB `ssd1306_buffer` is mandatory
(full framebuffer); don't touch.

View File

@ -11,7 +11,7 @@ TARGET=tama
# Regenerate sprites.h from the editable art generator (if python3 is present). # Regenerate sprites.h from the editable art generator (if python3 is present).
if command -v python3 >/dev/null 2>&1; then if command -v python3 >/dev/null 2>&1; then
echo ">> Generating sprites.h..." echo ">> Generating sprites.h..."
python3 sprites_gen.py > sprites.h #python3 sprites_gen.py > sprites.h
fi fi
# --- Toolchain (xpack riscv-none-elf-gcc, bundles a ch32fun-compatible newlib) --- # --- Toolchain (xpack riscv-none-elf-gcc, bundles a ch32fun-compatible newlib) ---

View File

@ -4,4 +4,10 @@
// Configuration overrides go here. Defaults in ch32fun/ch32fun.h are fine for blink. // Configuration overrides go here. Defaults in ch32fun/ch32fun.h are fine for blink.
#define FUNCONF_USE_HSI 1 // internal 24 MHz RC oscillator (no external crystal needed) #define FUNCONF_USE_HSI 1 // internal 24 MHz RC oscillator (no external crystal needed)
// We do no console I/O, so disable both printf backends. This drops the
// debug-interface output path; combined with stubbing the I2C driver's lone
// printf (see tama.c) it lets the linker discard the ~1 KB printf formatter.
#define FUNCONF_USE_DEBUGPRINTF 0
#define FUNCONF_USE_UARTPRINTF 0
#endif #endif

675
sprites.h
View File

@ -9,254 +9,108 @@
// pana_idle: 32x32 // pana_idle: 32x32
static const unsigned char pana_idle[] = { static const unsigned char pana_idle[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
0x01, 0x00, 0x00, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
0x0f, 0xe0, 0x07, 0xf0, 0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
0x1f, 0xf0, 0x0f, 0xf8, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFC, 0x3F, 0x80,
0x3f, 0xf8, 0x9f, 0xfc, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
0x1f, 0xff, 0xff, 0xf8, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
0x1f, 0xff, 0xff, 0xf8, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
0x1f, 0xff, 0xff, 0xf8, 0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
0x0f, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xef, 0xfb, 0xf0,
0x0f, 0x83, 0xe0, 0xf8,
0x0f, 0x93, 0xe4, 0xf8,
0x0f, 0x11, 0xc4, 0x78,
0x0f, 0x83, 0xe0, 0xf8,
0x1f, 0x83, 0xe0, 0xfc,
0x0f, 0xef, 0x7b, 0xf8,
0x0f, 0xfc, 0x1f, 0xf8,
0x0f, 0xff, 0x7f, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x07, 0xf8, 0x0f, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x1f, 0xfc, 0x00,
0x00, 0x00, 0x80, 0x00,
}; };
// pana_blink: 32x32 // pana_blink: 32x32
static const unsigned char pana_blink[] = { static const unsigned char pana_blink[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
0x01, 0x00, 0x00, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
0x0f, 0xe0, 0x07, 0xf0, 0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
0x1f, 0xf0, 0x0f, 0xf8, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xC3, 0xC3, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFC, 0x3F, 0x80,
0x3f, 0xf8, 0x9f, 0xfc, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
0x1f, 0xff, 0xff, 0xf8, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
0x1f, 0xff, 0xff, 0xf8, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
0x1f, 0xff, 0xff, 0xf8, 0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
0x0f, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0x01, 0xc0, 0x78,
0x0f, 0xff, 0xff, 0xf8,
0x1f, 0xff, 0xff, 0xfc,
0x0f, 0xff, 0x7f, 0xf8,
0x0f, 0xfc, 0x1f, 0xf8,
0x0f, 0xff, 0x7f, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x07, 0xf8, 0x0f, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x1f, 0xfc, 0x00,
0x00, 0x00, 0x80, 0x00,
}; };
// pana_happy: 32x32 // pana_happy: 32x32
static const unsigned char pana_happy[] = { static const unsigned char pana_happy[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
0x01, 0x00, 0x00, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
0x0f, 0xe0, 0x07, 0xf0, 0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
0x1f, 0xf0, 0x0f, 0xf8, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xEF, 0xF7, 0xC0, 0x03, 0xE7, 0xEB, 0xC0, 0x03, 0xDB, 0xDB, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xF7, 0xEF, 0x80,
0x3f, 0xf8, 0x9f, 0xfc, 0x00, 0xF7, 0xEF, 0x00, 0x00, 0x7B, 0xDE, 0x00, 0x01, 0xFC, 0x3F, 0xC0,
0x1f, 0xff, 0xff, 0xf8, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
0x1f, 0xff, 0xff, 0xf8, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
0x1f, 0xff, 0xff, 0xf8, 0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
0x0f, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xef, 0xfb, 0xf8,
0x0f, 0xd7, 0xf5, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x1f, 0xff, 0xff, 0xfc,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xf8, 0x0f, 0xf8,
0x0f, 0xfb, 0xef, 0xf8,
0x0f, 0xf7, 0xf7, 0xf8,
0x07, 0xfc, 0x1f, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x1f, 0xfc, 0x00,
0x00, 0x00, 0x80, 0x00,
}; };
// pana_sad: 32x32 // pana_sad: 32x32
static const unsigned char pana_sad[] = { static const unsigned char pana_sad[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
0x01, 0x00, 0x00, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
0x0f, 0xe0, 0x07, 0xf0, 0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
0x1f, 0xf0, 0x0f, 0xf8, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0x9F, 0xF9, 0xC0, 0x03, 0xE7, 0xE7, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x80,
0x3f, 0xf8, 0x9f, 0xfc, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x7B, 0xDE, 0x00, 0x01, 0xF7, 0xEF, 0xC0,
0x1f, 0xff, 0xff, 0xf8, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
0x1f, 0xff, 0xff, 0xf8, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
0x1f, 0xff, 0xff, 0xf8, 0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
0x0f, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x0f, 0xef, 0xfb, 0xf8,
0x0f, 0x83, 0xe0, 0xf8,
0x0f, 0x93, 0xe4, 0xf8,
0x0f, 0x11, 0xc4, 0x78,
0x1f, 0x83, 0xe0, 0xfc,
0x0f, 0x83, 0x60, 0xf8,
0x0f, 0xec, 0x1b, 0xf8,
0x0f, 0xff, 0x7f, 0xf8,
0x0f, 0xfc, 0x1f, 0xf8,
0x07, 0xf7, 0xf7, 0xf0,
0x07, 0xfb, 0xef, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x1f, 0xfc, 0x00,
0x00, 0x00, 0x80, 0x00,
}; };
// pana_eat: 32x32 // pana_eat: 32x32
static const unsigned char pana_eat[] = { static const unsigned char pana_eat[] ={
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
0x01, 0x00, 0x00, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
0x0f, 0xe0, 0x07, 0xf0, 0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
0x1f, 0xf0, 0x0f, 0xf8, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFC, 0x3F, 0x80, 0x01, 0xFD, 0x3F, 0x80,
0x3f, 0xf8, 0x9f, 0xfc, 0x00, 0xFC, 0xBF, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
0x1f, 0xff, 0xff, 0xf8, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
0x1f, 0xff, 0xff, 0xf8, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
0x1f, 0xff, 0xff, 0xf8, 0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
0x0f, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x07, 0xef, 0xfb, 0xf0,
0x07, 0x83, 0xe0, 0xf0,
0x0f, 0x93, 0xe4, 0xf8,
0x0f, 0x11, 0xc4, 0x78,
0x0f, 0x83, 0xe0, 0xf8,
0x0f, 0x83, 0xe0, 0xf8,
0x1f, 0xef, 0xfb, 0xfc,
0x0f, 0xff, 0x7f, 0xf8,
0x0f, 0xfc, 0x1f, 0xf8,
0x0f, 0xfc, 0x1f, 0xf8,
0x0f, 0xf8, 0x0f, 0xf8,
0x07, 0xfc, 0x1f, 0xf0,
0x07, 0xfc, 0x1f, 0xf0,
0x03, 0xff, 0x7f, 0xe0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x1f, 0xfc, 0x00,
0x00, 0x00, 0x80, 0x00,
}; };
// pana_sleep: 32x32 // pana_sleep: 32x32
static const unsigned char pana_sleep[] = { static const unsigned char pana_sleep[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0xF0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x8C, 0x20, 0x00, 0x39, 0xDC, 0x40, 0x00, 0x3B, 0xDC, 0xF0,
0x01, 0x00, 0x00, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
0x0f, 0xe0, 0x07, 0xf0, 0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
0x1f, 0xf0, 0x0f, 0xf8, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xC3, 0xC3, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFE, 0x7F, 0x80,
0x3f, 0xf8, 0x9f, 0xfc, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
0x1f, 0xff, 0xff, 0xf8, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
0x1f, 0xff, 0xff, 0xf8, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
0x1f, 0xff, 0xff, 0xf8, 0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
0x0f, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0x01, 0xc0, 0x78,
0x1f, 0xff, 0xff, 0xfc,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xff, 0x7f, 0xf8,
0x0f, 0xfc, 0x1f, 0xf8,
0x0f, 0xff, 0x7f, 0xf8,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x1f, 0xfc, 0x00,
0x00, 0x00, 0x80, 0x00,
}; };
// pana_dead: 32x32 // pana_dead: 32x32
static const unsigned char pana_dead[] = { static const unsigned char pana_dead[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
0x01, 0x00, 0x00, 0x80, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
0x0f, 0xe0, 0x07, 0xf0, 0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
0x1f, 0xf0, 0x0f, 0xf8, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xDB, 0xDB, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0,
0x1f, 0xf0, 0x0f, 0xf8, 0x03, 0xDB, 0xDB, 0xC0, 0x01, 0xFD, 0xDF, 0x80, 0x01, 0xFA, 0xBF, 0x80,
0x3f, 0xf8, 0x9f, 0xfc, 0x00, 0xF7, 0x7F, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
0x1f, 0xff, 0xff, 0xf8, 0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
0x1f, 0xff, 0xff, 0xf8, 0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
0x1f, 0xff, 0xff, 0xf8, 0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
0x0f, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x0f, 0xbb, 0xee, 0xf8,
0x0f, 0xd7, 0xf5, 0xf8,
0x0f, 0xef, 0xfb, 0xf8,
0x0f, 0xd7, 0xf5, 0xf8,
0x1f, 0xbb, 0xee, 0xfc,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xff, 0xff, 0xf8,
0x0f, 0xf0, 0x07, 0xf8,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x1f, 0xfc, 0x00,
0x00, 0x00, 0x80, 0x00,
}; };
enum { EXP_IDLE, EXP_BLINK, EXP_HAPPY, EXP_SAD, EXP_EAT, EXP_SLEEP, EXP_DEAD, EXP_COUNT }; enum { EXP_IDLE, EXP_BLINK, EXP_HAPPY, EXP_SAD, EXP_EAT, EXP_SLEEP, EXP_DEAD, EXP_COUNT };
@ -264,6 +118,305 @@ static const unsigned char *const pana_frames[EXP_COUNT] = {
pana_idle, pana_blink, pana_happy, pana_sad, pana_eat, pana_sleep, pana_dead, pana_idle, pana_blink, pana_happy, pana_sad, pana_eat, pana_sleep, pana_dead,
}; };
// shell_idle: 32x32
static const unsigned char shell_idle[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x07, 0xff, 0xff, 0xe0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xef, 0xf7, 0xf0,
0x0f, 0xc7, 0xe3, 0xf0,
0x0f, 0x83, 0xc1, 0xf0,
0x0f, 0xc7, 0xe3, 0xf0,
0x0f, 0xef, 0xf7, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0x7f, 0xff, 0xf0,
0x0f, 0xbf, 0xff, 0xf0,
0x0f, 0xdf, 0xff, 0xf0,
0x0f, 0xbf, 0xff, 0xf0,
0x0f, 0x78, 0x0f, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xe0,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
};
// shell_happy: 32x32
static const unsigned char shell_happy[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x07, 0xff, 0xff, 0xe0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xdf, 0xef, 0xf0,
0x0f, 0xaf, 0xd7, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xc7, 0xe3, 0xf0,
0x0f, 0xf8, 0x1f, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xe0,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
};
// shell_sad: 32x32
static const unsigned char shell_sad[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x07, 0xff, 0xff, 0xe0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xef, 0xf7, 0xf0,
0x0f, 0xc7, 0xe3, 0xf0,
0x0f, 0x83, 0xc1, 0xf0,
0x0f, 0xc7, 0xe3, 0xf0,
0x0f, 0xef, 0xf7, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xf8, 0x1f, 0xf0,
0x0f, 0xc7, 0xe3, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xe0,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
};
// shell_sleep: 32x32
static const unsigned char shell_sleep[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x07, 0xff, 0xff, 0xe0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0x01, 0x80, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xf8, 0x0f, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x0f, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xe0,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x07, 0xe0, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
0x00, 0x7f, 0xfe, 0x00,
};
// tmux_idle: 32x32
static const unsigned char tmux_idle[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x1f, 0xff, 0xff, 0xf8,
0x3f, 0xff, 0xff, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xbf, 0x7e, 0xfc,
0x3f, 0x1f, 0x7c, 0x7c,
0x3e, 0x0f, 0x78, 0x3c,
0x3f, 0x1f, 0x7c, 0x7c,
0x3f, 0xbf, 0x7e, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3c, 0x07, 0x70, 0x1c,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0xff, 0xfc,
0x20, 0x00, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x20, 0x00, 0x00, 0x04,
0x1f, 0xff, 0xff, 0xf8,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
// tmux_happy: 32x32
static const unsigned char tmux_happy[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x1f, 0xff, 0xff, 0xf8,
0x3f, 0xff, 0xff, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0x5f, 0x7d, 0x7c,
0x3e, 0xaf, 0x7a, 0xbc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3c, 0x03, 0x60, 0x1c,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0xff, 0xfc,
0x20, 0x00, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x20, 0x00, 0x00, 0x04,
0x1f, 0xff, 0xff, 0xf8,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
// tmux_sad: 32x32
static const unsigned char tmux_sad[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x1f, 0xff, 0xff, 0xf8,
0x3f, 0xff, 0xff, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xbf, 0x7e, 0xfc,
0x3f, 0x1f, 0x7c, 0x7c,
0x3e, 0x0f, 0x78, 0x3c,
0x3f, 0x1f, 0x7c, 0x7c,
0x3f, 0xbf, 0x7e, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xbf, 0x7e, 0xfc,
0x3c, 0x07, 0x70, 0x1c,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0xff, 0xfc,
0x20, 0x00, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x20, 0x00, 0x00, 0x04,
0x1f, 0xff, 0xff, 0xf8,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
// tmux_sleep: 32x32
static const unsigned char tmux_sleep[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x1f, 0xff, 0xff, 0xf8,
0x3f, 0xff, 0xff, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3c, 0x07, 0x70, 0x1c,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0x7f, 0xfc,
0x3f, 0xff, 0xff, 0xfc,
0x20, 0x00, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x27, 0xdf, 0x00, 0x04,
0x20, 0x00, 0x00, 0x04,
0x1f, 0xff, 0xff, 0xf8,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
#define SPECIES_COUNT 3
enum { SP_SHELL, SP_PANKO, SP_TMUX };
static const char *const species_names[SPECIES_COUNT] = {
"Shell", "Panko", "Tmux",
};
static const unsigned char *const species_frames[SPECIES_COUNT][EXP_COUNT] = {
{ shell_idle, shell_idle, shell_happy, shell_sad, shell_happy, shell_sleep, shell_sad },
{ pana_idle, pana_blink, pana_happy, pana_sad, pana_eat, pana_sleep, pana_dead },
{ tmux_idle, tmux_idle, tmux_happy, tmux_sad, tmux_happy, tmux_sleep, tmux_sad },
};
// spr_grave: 32x32 // spr_grave: 32x32
static const unsigned char spr_grave[] = { static const unsigned char spr_grave[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -312,6 +465,78 @@ static const unsigned char spr_poop[] = {
0x00, 0x00,
}; };
// spr_claude: 32x32
static const unsigned char spr_claude[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x20,
0x04, 0x00, 0x00, 0xa8,
0x0e, 0x00, 0x00, 0x20,
0x04, 0x00, 0x03, 0xfe,
0x00, 0x00, 0x00, 0x20,
0x00, 0x00, 0x80, 0xa8,
0x00, 0x0f, 0xf8, 0x20,
0x00, 0x3f, 0xfe, 0x20,
0x00, 0x7f, 0xff, 0x00,
0x00, 0xff, 0xff, 0x80,
0x01, 0xff, 0xff, 0xc0,
0x01, 0xff, 0xff, 0xc0,
0x03, 0xff, 0xff, 0xe0,
0x03, 0xf7, 0xf7, 0xe0,
0x03, 0xf7, 0xf7, 0xe0,
0x03, 0xe3, 0xe3, 0xe0,
0x07, 0xf7, 0xf7, 0xf0,
0x03, 0xf7, 0xf7, 0xe0,
0x03, 0xff, 0xff, 0xe0,
0x03, 0xff, 0xff, 0xe0,
0x03, 0xe7, 0xf3, 0xe0,
0x01, 0xf8, 0x0f, 0xc0,
0x01, 0xff, 0xff, 0xc0,
0x00, 0xff, 0xff, 0x80,
0x00, 0x7f, 0xff, 0x00,
0x00, 0x3f, 0xfe, 0x00,
0x00, 0x0f, 0xf8, 0x00,
0x00, 0x00, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00,
};
// spr_glitch: 32x32
static const unsigned char spr_glitch[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x80, 0x00, 0x80, 0x00,
0x00, 0x0f, 0xf8, 0x00,
0x00, 0x15, 0x54, 0x00,
0x00, 0xff, 0xff, 0x80,
0x01, 0xff, 0xff, 0xc0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0x11, 0x44, 0x41,
0x07, 0xd7, 0xf5, 0xf0,
0x07, 0xef, 0xfb, 0xf0,
0x07, 0xd7, 0xf5, 0xf0,
0x05, 0x11, 0x44, 0x50,
0x0f, 0xff, 0xff, 0xf8,
0x07, 0xff, 0xff, 0xf0,
0x07, 0xff, 0xff, 0xf0,
0x05, 0x55, 0x55, 0x50,
0x07, 0xea, 0xab, 0xf0,
0x43, 0xff, 0xff, 0xe0,
0x03, 0xff, 0xff, 0xe0,
0x01, 0x55, 0x55, 0x40,
0x00, 0xff, 0xff, 0x80,
0x00, 0x3f, 0xfe, 0x00,
0x00, 0x0f, 0xf8, 0x04,
0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
// icon_feed: 16x16 // icon_feed: 16x16
static const unsigned char icon_feed[] = { static const unsigned char icon_feed[] = {
0x00, 0x00, 0x00, 0x00,

View File

@ -25,6 +25,11 @@ def fill_ellipse(g, cx, cy, rx, ry, v=1):
if dx * dx + dy * dy <= 1.0: if dx * dx + dy * dy <= 1.0:
g[y][x] = v g[y][x] = v
def fillrect(g, x0, y0, x1, y1, v=1):
for y in range(y0, y1 + 1):
for x in range(x0, x1 + 1):
px(g, x, y, v)
def hline(g, x0, x1, y, v=1): def hline(g, x0, x1, y, v=1):
if x0 > x1: if x0 > x1:
x0, x1 = x1, x0 x0, x1 = x1, x0
@ -153,6 +158,96 @@ def panda_dead():
hline(g, 12, 20, 23, 0) # flat mouth hline(g, 12, 20, 23, 0) # flat mouth
return g return g
# ---------------------------------------------------------------- species: SHELL
# A friendly CRT terminal. Its mouth is a command prompt "> _".
def shell_base():
g = grid(32, 32)
fillrect(g, 4, 3, 27, 25) # monitor screen (lit)
for cx, cy in [(4, 3), (27, 3), (4, 25), (27, 25)]:
px(g, cx, cy, 0) # round the corners
fillrect(g, 13, 26, 18, 28) # neck
fillrect(g, 9, 29, 22, 31) # base
return g
def shell_prompt(g): # the "> _" signature, dark on screen
px(g, 8, 18, 0); px(g, 9, 19, 0); px(g, 10, 20, 0)
px(g, 9, 21, 0); px(g, 8, 22, 0)
hline(g, 13, 19, 22, 0)
def shell_idle():
g = shell_base()
fill_ellipse(g, 11, 11, 2, 2, 0)
fill_ellipse(g, 20, 11, 2, 2, 0)
shell_prompt(g)
return g
def shell_happy():
g = shell_base()
px(g, 9, 11, 0); px(g, 10, 10, 0); px(g, 11, 11, 0) # ^ ^
px(g, 18, 11, 0); px(g, 19, 10, 0); px(g, 20, 11, 0)
for x in range(10, 22):
y = 19 + (1 if 13 <= x <= 18 else 0)
px(g, x, y, 0)
return g
def shell_sad():
g = shell_base()
fill_ellipse(g, 11, 12, 2, 2, 0)
fill_ellipse(g, 20, 12, 2, 2, 0)
for x in range(10, 22):
y = 23 - (1 if 13 <= x <= 18 else 0)
px(g, x, y, 0)
return g
def shell_sleep():
g = shell_base()
hline(g, 8, 14, 12, 0)
hline(g, 17, 23, 12, 0)
hline(g, 13, 19, 20, 0)
return g
# ---------------------------------------------------------------- species: TMUX
# A split terminal window: a central pane divider and a status bar of tabs.
def tmux_base():
g = grid(32, 32)
fillrect(g, 2, 3, 29, 28)
for cx, cy in [(2, 3), (29, 3), (2, 28), (29, 28)]:
px(g, cx, cy, 0)
vline(g, 16, 5, 22, 0) # pane divider
fillrect(g, 3, 24, 28, 27, 0) # dark status strip
fillrect(g, 5, 25, 9, 26, 1) # window tab 0
fillrect(g, 11, 25, 15, 26, 1) # window tab 1
return g
def tmux_idle():
g = tmux_base()
fill_ellipse(g, 9, 12, 2, 2, 0) # one eye per pane
fill_ellipse(g, 23, 12, 2, 2, 0)
hline(g, 6, 12, 18, 0) # mouth, split by divider
hline(g, 20, 26, 18, 0)
return g
def tmux_happy():
g = tmux_base()
px(g, 7, 12, 0); px(g, 8, 11, 0); px(g, 9, 12, 0); px(g, 10, 11, 0); px(g, 11, 12, 0)
px(g, 21, 12, 0); px(g, 22, 11, 0); px(g, 23, 12, 0); px(g, 24, 11, 0); px(g, 25, 12, 0)
hline(g, 6, 13, 19, 0); hline(g, 19, 26, 19, 0)
return g
def tmux_sad():
g = tmux_base()
fill_ellipse(g, 9, 13, 2, 2, 0)
fill_ellipse(g, 23, 13, 2, 2, 0)
hline(g, 6, 12, 20, 0); hline(g, 20, 26, 20, 0)
px(g, 9, 19, 0); px(g, 23, 19, 0)
return g
def tmux_sleep():
g = tmux_base()
hline(g, 6, 12, 13, 0)
hline(g, 20, 26, 13, 0)
return g
# ---------------------------------------------------------------- props # ---------------------------------------------------------------- props
def grave(): def grave():
art = [ art = [
@ -204,6 +299,42 @@ def poop():
] ]
return from_art(art) return from_art(art)
# ---------------------------------------------------------------- evolved forms
# The pet "ascends" once fully grown. Good care -> Claude (a serene sparkle
# being); neglect -> a corrupted glitch. Both are 32x32 like the panda frames.
def claude():
g = grid(32, 32)
fill_ellipse(g, 16, 19, 11, 11) # round, friendly body
fill_ellipse(g, 12, 18, 1, 2, 0) # calm eyes
fill_ellipse(g, 20, 18, 1, 2, 0)
for x in range(11, 22): # serene smile
y = 23 + (1 if 13 <= x <= 19 else 0)
px(g, x, y, 0)
# signature 4-point sparkle, upper-right
sx, sy = 26, 6
vline(g, sx, sy - 4, sy + 4)
hline(g, sx - 4, sx + 4, sy)
px(g, sx - 2, sy - 2); px(g, sx + 2, sy - 2)
px(g, sx - 2, sy + 2); px(g, sx + 2, sy + 2)
# tiny twinkle, upper-left
px(g, 5, 5); px(g, 5, 4); px(g, 5, 6); px(g, 4, 5); px(g, 6, 5)
return g
def glitch():
g = grid(32, 32)
fill_ellipse(g, 16, 19, 12, 11)
for d in range(-2, 3): # X_X eyes
px(g, 11 + d, 16 + d, 0); px(g, 11 + d, 16 - d, 0)
px(g, 21 + d, 16 + d, 0); px(g, 21 + d, 16 - d, 0)
for x in range(10, 23): # jagged mouth
px(g, x, 22 + (x % 2), 0)
for y in range(10, 31, 4): # scanline corruption
for x in range(2, 30, 2):
px(g, x, y, 0)
for (x, y) in [(0, 8), (2, 30), (31, 14), (29, 29), (1, 24), (30, 4)]:
px(g, x, y, 1) # stray noise pixels
return g
# ---------------------------------------------------------------- 16x16 icons # ---------------------------------------------------------------- 16x16 icons
def icon_feed(): # apple def icon_feed(): # apple
g = grid(16, 16) g = grid(16, 16)
@ -295,8 +426,38 @@ def main():
print("};") print("};")
print() print()
# other starter species (Panko = the panda frames above)
species_extra = [
("shell_idle", shell_idle()),
("shell_happy", shell_happy()),
("shell_sad", shell_sad()),
("shell_sleep", shell_sleep()),
("tmux_idle", tmux_idle()),
("tmux_happy", tmux_happy()),
("tmux_sad", tmux_sad()),
("tmux_sleep", tmux_sleep()),
]
for n, g in species_extra:
emit(n, g)
# species frame tables, indexed [species][EXP_*]. Shell/Tmux reuse their
# four frames across the seven expression slots.
print("#define SPECIES_COUNT 3")
print("enum { SP_SHELL, SP_PANKO, SP_TMUX };")
print("static const char *const species_names[SPECIES_COUNT] = {")
print(' "Shell", "Panko", "Tmux",')
print("};")
print("static const unsigned char *const species_frames[SPECIES_COUNT][EXP_COUNT] = {")
print(" { shell_idle, shell_idle, shell_happy, shell_sad, shell_happy, shell_sleep, shell_sad },")
print(" { pana_idle, pana_blink, pana_happy, pana_sad, pana_eat, pana_sleep, pana_dead },")
print(" { tmux_idle, tmux_idle, tmux_happy, tmux_sad, tmux_happy, tmux_sleep, tmux_sad },")
print("};")
print()
emit("spr_grave", grave()) emit("spr_grave", grave())
emit("spr_poop", poop()) emit("spr_poop", poop())
emit("spr_claude", claude())
emit("spr_glitch", glitch())
icons = [ icons = [
("icon_feed", icon_feed()), ("icon_feed", icon_feed()),

BIN
tama.bin

Binary file not shown.

267
tama.c
View File

@ -14,7 +14,12 @@
*/ */
#include "ch32fun.h" #include "ch32fun.h"
#include <stdio.h>
// The bundled I2C driver calls printf() only to log a timeout we can't act on
// (by then the display is dead). Stub it to a no-op so the ~1 KB printf
// formatter is never referenced and the linker discards it. We format our own
// numbers with the tiny s_cat/u_cat helpers below.
#define printf(...) ((void)0)
#define SSD1306_128X64 // 128x64 panel (see pinout.md / OLED size) #define SSD1306_128X64 // 128x64 panel (see pinout.md / OLED size)
#include "ssd1306_i2c.h" #include "ssd1306_i2c.h"
@ -37,7 +42,7 @@
// a need empties in ~20 min, after which health bleeds down over ~10 more. // a need empties in ~20 min, after which health bleeds down over ~10 more.
// ----------------------------------------------------------------- state // ----------------------------------------------------------------- state
enum { SCR_MAIN, SCR_STATS, SCR_DEAD, SCR_SETTINGS }; enum { SCR_SELECT, SCR_MAIN, SCR_STATS, SCR_DEAD, SCR_SETTINGS };
// notification thresholds: gentle chirp at <50%, urgent at <20% // notification thresholds: gentle chirp at <50%, urgent at <20%
enum { N_FOOD, N_FUN, N_ENGY, N_WASH, N_COUNT }; enum { N_FOOD, N_FUN, N_ENGY, N_WASH, N_COUNT };
@ -49,11 +54,26 @@ typedef struct {
int8_t hygiene; // 0..100 int8_t hygiene; // 0..100
int8_t health; // 0..100 int8_t health; // 0..100
uint16_t age_min; // age in game-minutes (one per tick) uint16_t age_min; // age in game-minutes (one per tick)
uint16_t xp; // experience earned by good care
uint8_t species; // SP_* starter type chosen on the welcome screen
uint8_t level; // 1..MAX_LEVEL
uint8_t form; // FORM_* (display + life stage), derived from level
uint8_t ascend; // 0 undecided, 1 Claude, 2 glitch (latched at max lvl)
uint8_t poop; // 0..3 piles on the floor uint8_t poop; // 0..3 piles on the floor
uint8_t sleeping; // 1 while napping uint8_t sleeping; // 1 while napping
uint8_t alive; uint8_t alive;
} Pet; } Pet;
// Hacker privilege-escalation life stages: proc -> user -> root, then the pet
// either ascends to "Claude" (well-tended) or rots into a glitch.
enum { FORM_PROC, FORM_USER, FORM_ROOT, FORM_CLAUDE, FORM_GLITCH, FORM_COUNT };
static const char *const form_names[FORM_COUNT] = {
"proc", "user", "root", "Claude", "glitch",
};
#define MAX_LEVEL 4
// cumulative XP required to reach level (index = level-1)
static const uint16_t level_xp[MAX_LEVEL] = { 0, 45, 120, 240 };
static Pet pet; static Pet pet;
static uint8_t screen; static uint8_t screen;
static uint8_t cursor; // selected menu icon 0..ICON_COUNT-1 static uint8_t cursor; // selected menu icon 0..ICON_COUNT-1
@ -63,11 +83,13 @@ static uint32_t frame; // free-running frame counter
static uint16_t tick_acc; // frames since last stat tick static uint16_t tick_acc; // frames since last stat tick
static uint8_t warn_flag[N_COUNT]; // already chirped a <50% warning static uint8_t warn_flag[N_COUNT]; // already chirped a <50% warning
static uint8_t crit_flag[N_COUNT]; // already chirped a <20% warning static uint8_t crit_flag[N_COUNT]; // already chirped a <20% warning
static uint8_t evo_anim; // frames remaining for an evolution banner
// settings // settings
static uint8_t sound_on = 1; static uint8_t sound_on = 1;
static uint8_t timeout_idx = 1; // index into screen_timeout_opts (default 30 s) static uint8_t timeout_idx = 1; // index into screen_timeout_opts (default 30 s)
static uint8_t set_sel = 0; // highlighted row on the settings screen static uint8_t set_sel = 0; // highlighted row on the settings screen
static uint8_t sel_species = 0; // highlighted species on the welcome screen
// screen-off-on-inactivity options, in frames (0 = never sleep the display) // screen-off-on-inactivity options, in frames (0 = never sleep the display)
static const uint16_t screen_timeout_opts[] = { 250, 500, 1000, 0 }; static const uint16_t screen_timeout_opts[] = { 250, 500, 1000, 0 };
@ -85,6 +107,20 @@ enum { ACT_FEED, ACT_PLAY, ACT_SLEEP, ACT_CLEAN, ACT_HEAL, ACT_STATS, ACT_SETTIN
// ----------------------------------------------------------------- helpers // ----------------------------------------------------------------- helpers
static int8_t clamp100(int v) { return v < 0 ? 0 : (v > 100 ? 100 : v); } static int8_t clamp100(int v) { return v < 0 ? 0 : (v > 100 ? 100 : v); }
// Tiny string builders used instead of snprintf: we only ever paste short
// strings and small unsigned ints, and avoiding <stdio.h> keeps the whole
// printf machinery (~1 KB) out of the image. Each returns the new end pointer
// so calls chain; remember to NUL-terminate the final buffer yourself.
static char *s_cat(char *p, const char *s) { while (*s) *p++ = *s++; return p; }
static char *u_cat(char *p, unsigned v)
{
char tmp[5];
int n = 0;
do { tmp[n++] = '0' + v % 10; v /= 10; } while (v);
while (n) *p++ = tmp[--n];
return p;
}
// short square-wave beep on the passive piezo (blocking, ms-scale only) // short square-wave beep on the passive piezo (blocking, ms-scale only)
static void beep(uint16_t freq, uint16_t ms) static void beep(uint16_t freq, uint16_t ms)
{ {
@ -115,14 +151,53 @@ static void pet_reset(void)
pet.hygiene = 100; pet.hygiene = 100;
pet.health = 100; pet.health = 100;
pet.age_min = 0; pet.age_min = 0;
pet.xp = 0;
pet.species = sel_species;
pet.level = 1;
pet.form = FORM_PROC;
pet.ascend = 0;
pet.poop = 0; pet.poop = 0;
pet.sleeping = 0; pet.sleeping = 0;
pet.alive = 1; pet.alive = 1;
screen = SCR_MAIN; screen = SCR_MAIN;
cursor = 0; cursor = 0;
evo_anim = 0;
for (int i = 0; i < N_COUNT; i++) { warn_flag[i] = 0; crit_flag[i] = 0; } for (int i = 0; i < N_COUNT; i++) { warn_flag[i] = 0; crit_flag[i] = 0; }
} }
// Map the current level (and, at the top, care quality) to a display form.
static void compute_form(void)
{
switch (pet.level) {
case 1: pet.form = FORM_PROC; break;
case 2: pet.form = FORM_USER; break;
case 3: pet.form = FORM_ROOT; break;
default: // max level: branch once, latched
if (!pet.ascend) pet.ascend = (pet.health >= 60) ? 1 : 2;
pet.form = (pet.ascend == 1) ? FORM_CLAUDE : FORM_GLITCH;
break;
}
}
// Award XP; promote (and trigger the evolution banner) when a tier is reached.
static void add_xp(uint8_t n)
{
if (!pet.alive) return;
if (pet.xp < 60000) pet.xp += n;
uint8_t nl = 1;
for (uint8_t i = 1; i < MAX_LEVEL; i++)
if (pet.xp >= level_xp[i]) nl = i + 1;
if (nl > pet.level) {
pet.level = nl;
compute_form();
evo_anim = 30; // ~1.8 s celebratory banner
beep(784, 60); beep(988, 60); beep(1175, 60); beep(1568, 140);
led_blink();
}
}
// Chirp once when a need first drops below 50% (gentle) or 20% (urgent), // Chirp once when a need first drops below 50% (gentle) or 20% (urgent),
// re-arming only after it recovers. Audible even while the screen is off, // re-arming only after it recovers. Audible even while the screen is off,
// so it works as a "tend to me" reminder. // so it works as a "tend to me" reminder.
@ -177,6 +252,7 @@ static void do_action(uint8_t act)
react(EXP_EAT); react(EXP_EAT);
beep(880, 60); beep(1175, 80); beep(880, 60); beep(1175, 80);
led_blink(); led_blink();
add_xp(4);
break; break;
case ACT_PLAY: case ACT_PLAY:
@ -186,6 +262,7 @@ static void do_action(uint8_t act)
react(EXP_HAPPY); react(EXP_HAPPY);
beep(988, 60); beep(1319, 60); beep(1568, 90); beep(988, 60); beep(1319, 60); beep(1568, 90);
led_blink(); led_blink();
add_xp(6);
break; break;
case ACT_SLEEP: case ACT_SLEEP:
@ -198,12 +275,14 @@ static void do_action(uint8_t act)
pet.hygiene = 100; pet.hygiene = 100;
pet.happy = clamp100(pet.happy + 5); pet.happy = clamp100(pet.happy + 5);
beep(1319, 50); beep(1047, 50); beep(1319, 50); beep(1047, 50);
add_xp(3);
break; break;
case ACT_HEAL: case ACT_HEAL:
pet.health = clamp100(pet.health + 35); pet.health = clamp100(pet.health + 35);
beep(784, 60); beep(1047, 60); beep(1319, 90); beep(784, 60); beep(1047, 60); beep(1319, 90);
led_blink(); led_blink();
add_xp(4);
break; break;
case ACT_STATS: case ACT_STATS:
@ -255,6 +334,9 @@ static void game_tick(void)
if (lo < 15) pet.health = clamp100(pet.health - 3); if (lo < 15) pet.health = clamp100(pet.health - 3);
else if (lo > 50) pet.health = clamp100(pet.health + 2); else if (lo > 50) pet.health = clamp100(pet.health + 2);
// a well-kept pet matures: passive XP while every need stays healthy
if (lo > 50 && !pet.sleeping) add_xp(3);
check_notifications(); check_notifications();
if (pet.health <= 0) { if (pet.health <= 0) {
@ -285,14 +367,79 @@ static void draw_menu_bar(void)
} }
} }
// Per-species eye geometry, so the shades land on the right spot.
static const uint8_t eye_cy[SPECIES_COUNT] = { 11, 17, 12 }; // SP_SHELL,PANKO,TMUX
static const uint8_t eye_lx[SPECIES_COUNT] = { 11, 11, 9 };
static const uint8_t eye_rx[SPECIES_COUNT] = { 20, 21, 23 };
// Hacker garb earned as the pet levels up, drawn dark over the lit body so it
// reads as black gear.
static void draw_accessories(int x, int y)
{
uint8_t sp = pet.species;
uint8_t cy = eye_cy[sp], lx = eye_lx[sp], rx = eye_rx[sp];
if (pet.form >= FORM_USER) { // shades
ssd1306_fillRect(x + lx - 3, y + cy - 2, 7, 4, 0);
ssd1306_fillRect(x + rx - 3, y + cy - 2, 7, 4, 0);
if (rx - lx > 7)
ssd1306_drawFastHLine(x + lx + 4, y + cy - 1, rx - lx - 7, 0);
}
if (pet.form >= FORM_ROOT) { // gaming headset + mic boom
ssd1306_drawLine(x + 3, y + 10, x + 9, y + 2, 0);
ssd1306_drawLine(x + 9, y + 2, x + 23, y + 2, 0);
ssd1306_drawLine(x + 23, y + 2, x + 29, y + 10, 0);
ssd1306_fillRect(x + 1, y + 12, 4, 7, 0);
ssd1306_drawLine(x + 3, y + 18, x + 10, y + 24, 0);
}
}
static void draw_creature(int x, int y)
{
const unsigned char *spr;
if (pet.form == FORM_CLAUDE) spr = spr_claude;
else if (pet.form == FORM_GLITCH) spr = spr_glitch;
else spr = species_frames[pet.species][current_expression()];
ssd1306_drawImage(x, y, spr, PANA_W, PANA_H, 0);
// growing forms wear gear; the ascended/glitched forms speak for themselves
if (spr != spr_claude && spr != spr_glitch && !pet.sleeping)
draw_accessories(x, y);
// Claude radiates: a few twinkling pixels that shift each frame
if (pet.form == FORM_CLAUDE) {
ssd1306_drawPixel(x - 2 + (frame & 3), y + 4, 1);
ssd1306_drawPixel(x + PANA_W + (frame & 1), y + 10, 1);
}
}
static void draw_evo_banner(void)
{
ssd1306_fillRect(6, 18, SSD1306_W - 12, 24, 0);
ssd1306_drawRect(6, 18, SSD1306_W - 12, 24, 1);
draw_center_str(21, ">> evolving");
char b[17], *p = b;
p = s_cat(p, "[ ");
p = s_cat(p, form_names[pet.form]);
p = s_cat(p, " ]");
*p = 0;
draw_center_str(31, b);
}
static void draw_main(void) static void draw_main(void)
{ {
ssd1306_setbuf(0); ssd1306_setbuf(0);
// top bar: action label, with a tiny low-health warning // top bar: action label + level badge, with a tiny low-health warning
draw_center_str(0, menu_labels[cursor]); draw_center_str(0, menu_labels[cursor]);
if (pet.alive && pet.health < 30 && (frame & 8)) if (pet.alive && pet.health < 30 && (frame & 8))
ssd1306_drawstr(0, 0, "!", 1); ssd1306_drawstr(0, 0, "!", 1);
char badge[6], *bp = badge;
*bp++ = 'L';
bp = u_cat(bp, pet.level);
*bp = 0;
ssd1306_drawstr(SSD1306_W - 2 * 8, 0, badge, 1);
ssd1306_drawFastHLine(0, 10, SSD1306_W, 1); ssd1306_drawFastHLine(0, 10, SSD1306_W, 1);
// pet: gentle horizontal stroll + vertical bob (still while asleep) // pet: gentle horizontal stroll + vertical bob (still while asleep)
@ -306,8 +453,7 @@ static void draw_main(void)
px = (t < 40) ? 30 + t : 30 + (80 - t); // 30..70..30 px = (t < 40) ? 30 + t : 30 + (80 - t); // 30..70..30
py = 14 + ((frame % 16) < 8 ? 0 : 1); py = 14 + ((frame % 16) < 8 ? 0 : 1);
} }
ssd1306_drawImage(px, py, pana_frames[current_expression()], draw_creature(px, py);
PANA_W, PANA_H, 0);
// floor + poop piles // floor + poop piles
ssd1306_drawFastHLine(0, 46, SSD1306_W, 1); ssd1306_drawFastHLine(0, 46, SSD1306_W, 1);
@ -315,6 +461,7 @@ static void draw_main(void)
ssd1306_drawImage(8 + i * 12, 38, spr_poop, 8, 8, 0); ssd1306_drawImage(8 + i * 12, 38, spr_poop, 8, 8, 0);
draw_menu_bar(); draw_menu_bar();
if (evo_anim) draw_evo_banner(); // celebratory overlay on top
ssd1306_refresh(); ssd1306_refresh();
} }
@ -330,16 +477,26 @@ static void draw_bar(int y, const char *label, int8_t val)
static void draw_stats(void) static void draw_stats(void)
{ {
ssd1306_setbuf(0); ssd1306_setbuf(0);
draw_center_str(0, "-- STATUS --"); draw_center_str(0, "~/pet $ stat");
draw_bar(10, "FOOD", pet.satiety); draw_bar(10, "FOOD", pet.satiety);
draw_bar(19, "FUN ", pet.happy); draw_bar(19, "HAPPY", pet.happy);
draw_bar(28, "ENGY", pet.energy); draw_bar(28, "ENERGY", pet.energy);
draw_bar(37, "WASH", pet.hygiene); draw_bar(37, "CLEAN", pet.hygiene);
draw_bar(46, "HLTH", pet.health); draw_bar(46, "HEALTH", pet.health);
char line[17]; // no form name here: keep room for the XP-to-next-level readout
snprintf(line, sizeof(line), "Age %u %s", pet.age_min, char line[17], *p = line;
pet.sleeping ? "Zzz" : ""); *p++ = 'L';
p = u_cat(p, pet.level);
p = s_cat(p, " xp ");
if (pet.level < MAX_LEVEL) {
p = u_cat(p, pet.xp);
*p++ = '/';
p = u_cat(p, level_xp[pet.level]);
} else {
p = s_cat(p, "MAX");
}
*p = 0;
ssd1306_drawstr(0, 56, line, 1); ssd1306_drawstr(0, 56, line, 1);
ssd1306_refresh(); ssd1306_refresh();
} }
@ -347,11 +504,35 @@ static void draw_stats(void)
static void draw_dead(void) static void draw_dead(void)
{ {
ssd1306_setbuf(0); ssd1306_setbuf(0);
ssd1306_drawImage(48, 8, spr_grave, 32, 32, 0); ssd1306_drawImage(48, 2, spr_grave, 32, 32, 0);
draw_center_str(44, "R.I.P."); draw_center_str(36, pet.ascend == 2 ? "** CORRUPTED **" : "SEGFAULT (11)");
char line[17]; char line[17], *p = line;
snprintf(line, sizeof(line), "lived %u min", pet.age_min); p = s_cat(p, "core dumped L");
draw_center_str(52, line); p = u_cat(p, pet.level);
*p = 0;
draw_center_str(46, line);
draw_center_str(56, "OK: respawn");
ssd1306_refresh();
}
// welcome screen: choose a starter species before booting the pet
static void draw_select(void)
{
ssd1306_setbuf(0);
draw_center_str(0, "~ $ adopt --pet");
ssd1306_drawFastHLine(0, 10, SSD1306_W, 1);
// big idle preview of the highlighted species (no gear at level 1)
ssd1306_drawImage(48, 13, species_frames[sel_species][EXP_IDLE],
PANA_W, PANA_H, 0);
char line[17], *p = line;
p = s_cat(p, "< ");
p = s_cat(p, species_names[sel_species]);
p = s_cat(p, " >");
*p = 0;
draw_center_str(47, line);
draw_center_str(56, "L/R pick OK go");
ssd1306_refresh(); ssd1306_refresh();
} }
@ -362,9 +543,9 @@ static void draw_settings(void)
ssd1306_drawFastHLine(0, 10, SSD1306_W, 1); ssd1306_drawFastHLine(0, 10, SSD1306_W, 1);
char rows[SETTINGS_ROWS][17]; char rows[SETTINGS_ROWS][17];
snprintf(rows[0], 17, "Screen off:%s", screen_timeout_names[timeout_idx]); *s_cat(s_cat(rows[0], "Screen off:"), screen_timeout_names[timeout_idx]) = 0;
snprintf(rows[1], 17, "Sound: %s", sound_on ? "On" : "Off"); *s_cat(s_cat(rows[1], "Sound: "), sound_on ? "On" : "Off") = 0;
snprintf(rows[2], 17, "Back"); *s_cat(rows[2], "Back") = 0;
for (uint8_t r = 0; r < SETTINGS_ROWS; r++) { for (uint8_t r = 0; r < SETTINGS_ROWS; r++) {
int y = 18 + r * 12; int y = 18 + r * 12;
@ -412,13 +593,24 @@ static void gpio_setup(void)
static void splash(void) static void splash(void)
{ {
// fake terminal boot sequence, revealed line by line
static const char *const boot[] = {
"panagotchi v2",
"booting kernel..",
"mount /pet ok",
"spawn shell ok",
"> ./tamagotchi_",
};
ssd1306_setbuf(0); ssd1306_setbuf(0);
draw_center_str(4, "PANAGOTCHI"); for (uint8_t i = 0; i < 5; i++) {
ssd1306_drawImage(48, 16, pana_idle, PANA_W, PANA_H, 0); ssd1306_drawstr(0, i * 10, boot[i], 1);
draw_center_str(52, "press any key");
ssd1306_refresh(); ssd1306_refresh();
beep(1200 + i * 120, 25);
beep(659, 80); beep(784, 80); beep(988, 120); Delay_Ms(220);
}
draw_center_str(54, "press any key");
ssd1306_refresh();
beep(988, 120);
// wait until a button is pressed (or ~4 s timeout) // wait until a button is pressed (or ~4 s timeout)
for (int i = 0; i < 200; i++) { for (int i = 0; i < 200; i++) {
@ -441,8 +633,8 @@ int main(void)
} }
ssd1306_init(); ssd1306_init();
pet_reset();
splash(); splash();
screen = SCR_SELECT; // pick a species before a pet is born
Buttons prev = {0, 0, 0}; Buttons prev = {0, 0, 0};
@ -464,6 +656,22 @@ int main(void)
} }
} else { } else {
switch (screen) { switch (screen) {
case SCR_SELECT:
if (left_edge) {
sel_species = (sel_species + SPECIES_COUNT - 1) % SPECIES_COUNT;
beep(1047, 25);
}
if (right_edge) {
sel_species = (sel_species + 1) % SPECIES_COUNT;
beep(1047, 25);
}
if (ok_edge) { // born! boot the chosen species
pet_reset(); // sets screen = SCR_MAIN, species = sel_species
beep(659, 80); beep(988, 120);
}
draw_select();
break;
case SCR_MAIN: case SCR_MAIN:
if (left_edge) { if (left_edge) {
cursor = (cursor + ICON_COUNT - 1) % ICON_COUNT; cursor = (cursor + ICON_COUNT - 1) % ICON_COUNT;
@ -507,8 +715,8 @@ int main(void)
break; break;
case SCR_DEAD: case SCR_DEAD:
if (ok_edge) { // start a new pet if (ok_edge) { // back to the welcome screen to re-pick
pet_reset(); screen = SCR_SELECT;
beep(659, 80); beep(988, 120); beep(659, 80); beep(988, 120);
} }
draw_dead(); draw_dead();
@ -524,6 +732,7 @@ int main(void)
} }
if (anim_timer) anim_timer--; if (anim_timer) anim_timer--;
if (evo_anim) evo_anim--;
// advance game time independent of screen / display state // advance game time independent of screen / display state
if (pet.alive && ++tick_acc >= TICK_FRAMES) { if (pet.alive && ++tick_acc >= TICK_FRAMES) {

BIN
tama.elf

Binary file not shown.

1374
tama.hex

File diff suppressed because it is too large Load Diff

6405
tama.lst

File diff suppressed because it is too large Load Diff

866
tama.map

File diff suppressed because it is too large Load Diff