|
After Width: | Height: | Size: 271 B |
|
|
@ -0,0 +1,92 @@
|
|||
# Panagotchi
|
||||
|
||||
A Tamagotchi-style virtual pet for the **CH32V003F4P6** badge — a hacker-themed creature that evolves from `proc` → `user` → `root`, then either ascends to **Claude** or degrades into a **glitch** depending on how well you tend to it.
|
||||
|
||||
Fits in ~12 KB of the 16 KB flash budget.
|
||||
|
||||
## Hardware
|
||||
|
||||
| Pin | Function |
|
||||
|------|-----------------|
|
||||
| PA1 | Indicator LED |
|
||||
| PC0 | OK button |
|
||||
| PC1/PC2 | SSD1306 128×64 OLED (I2C1) |
|
||||
| PD2 | Passive piezo buzzer |
|
||||
| PD5 | Right button |
|
||||
| PD6 | Left button |
|
||||
|
||||
Powered by a CR2032 coin cell via a TLV61070A boost converter to 3.3V. See [pinout.md](pinout.md) for the full pin assignment and power architecture.
|
||||
|
||||
## Gameplay
|
||||
|
||||
**Controls:** Left / Right move the menu cursor. OK activates the selected action.
|
||||
|
||||
**Menu actions:**
|
||||
|
||||
| Icon | Action | Effect |
|
||||
|---------|---------|--------|
|
||||
| Apple | FEED | +30 satiety, −5 hygiene |
|
||||
| Ball | PLAY | +25 happy, −12 energy (requires energy ≥ 15) |
|
||||
| Moon | SLEEP | Toggle nap; restores energy |
|
||||
| Drop | CLEAN | Reset hygiene to 100, remove poop piles |
|
||||
| Cross | HEAL | +35 health |
|
||||
| Chart | STATS | Show stat bars + XP readout |
|
||||
| Gear | SETTINGS| Screen timeout, brightness, sound |
|
||||
|
||||
Stats decay on a ~15-second tick. Health responds to overall wellbeing — if any need drops below 15 it bleeds down; if all are above 50 it recovers. Let health reach 0 and the pet dies with a `SEGFAULT (11)`.
|
||||
|
||||
**Evolution** (XP thresholds: 45 / 120 / 240):
|
||||
|
||||
```
|
||||
proc → user → root → Claude (well-kept) / glitch (neglected)
|
||||
```
|
||||
|
||||
Each stage gains a visual accessory: shades at `user`, a gaming headset and mic boom at `root`.
|
||||
|
||||
**Species** (chosen on the welcome screen):
|
||||
|
||||
- **Panko** — the panda; full 7-expression animation set
|
||||
- **Shell** — a friendly CRT terminal with a `> _` prompt mouth
|
||||
- **Tmux** — a split-pane terminal with a status bar
|
||||
|
||||
## Building
|
||||
|
||||
Requires [ch32v003fun](https://github.com/cnlohr/ch32v003fun) checked out as a sibling directory.
|
||||
|
||||
```sh
|
||||
# Generate sprites from source art
|
||||
python3 sprites_gen.py > sprites.h
|
||||
|
||||
# Build and flash
|
||||
make
|
||||
```
|
||||
|
||||
Flash via the `minichlink` or `wch-link` programmer. The Makefile targets `cv_flash` / `cv_clean` from ch32fun.
|
||||
|
||||
Check real flash usage with:
|
||||
|
||||
```sh
|
||||
riscv-none-elf-size tama.elf
|
||||
```
|
||||
|
||||
## Power saving
|
||||
|
||||
The OLED is the biggest power draw. When the screen-off timeout fires, the display is blanked and the MCU enters WFI light-sleep. Two wake sources:
|
||||
|
||||
- **Button press** (EXTI falling edge on PC0/PD5/PD6) — lights the screen back up
|
||||
- **TIM2 overflow** (~once per game tick) — runs a background stat update and low-need chirp, without waking the display
|
||||
|
||||
SRAM and all pet state survive WFI; only the CPU clock is gated.
|
||||
|
||||
Brightness and screen-off timeout are adjustable in the settings menu.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| [tama.c](tama.c) | Main firmware: game logic, rendering, input, low-power |
|
||||
| [sprites_gen.py](sprites_gen.py) | Python sprite generator — edit art here |
|
||||
| [sprites.h](sprites.h) | Auto-generated 1-bpp sprite data (do not edit) |
|
||||
| [funconfig.h](funconfig.h) | ch32fun config overrides (disables printf backends) |
|
||||
| [pinout.md](pinout.md) | Full pin assignment and power architecture notes |
|
||||
| [OPTIMIZATION.md](OPTIMIZATION.md) | Flash size backlog and optimization notes |
|
||||
|
After Width: | Height: | Size: 271 B |
|
After Width: | Height: | Size: 265 B |
|
After Width: | Height: | Size: 278 B |
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 280 B |
|
After Width: | Height: | Size: 258 B |
595
sprites.h
|
|
@ -9,108 +9,254 @@
|
|||
|
||||
// pana_idle: 32x32
|
||||
static const unsigned char pana_idle[] = {
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
|
||||
0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
|
||||
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
|
||||
0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
|
||||
0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFC, 0x3F, 0x80,
|
||||
0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
|
||||
0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x80,
|
||||
0x0f, 0xe0, 0x07, 0xf0,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x3f, 0xf8, 0x9f, 0xfc,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 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
|
||||
static const unsigned char pana_blink[] = {
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
|
||||
0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
|
||||
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
|
||||
0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
|
||||
0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xC3, 0xC3, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFC, 0x3F, 0x80,
|
||||
0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
|
||||
0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x80,
|
||||
0x0f, 0xe0, 0x07, 0xf0,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x3f, 0xf8, 0x9f, 0xfc,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 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
|
||||
static const unsigned char pana_happy[] = {
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
|
||||
0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
|
||||
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
|
||||
0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
|
||||
0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xEF, 0xF7, 0xC0, 0x03, 0xE7, 0xEB, 0xC0, 0x03, 0xDB, 0xDB, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xF7, 0xEF, 0x80,
|
||||
0x00, 0xF7, 0xEF, 0x00, 0x00, 0x7B, 0xDE, 0x00, 0x01, 0xFC, 0x3F, 0xC0,
|
||||
0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x80,
|
||||
0x0f, 0xe0, 0x07, 0xf0,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x3f, 0xf8, 0x9f, 0xfc,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 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
|
||||
static const unsigned char pana_sad[] = {
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
|
||||
0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
|
||||
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
|
||||
0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
|
||||
0x01, 0xFF, 0xFF, 0x80, 0x03, 0x9F, 0xF9, 0xC0, 0x03, 0xE7, 0xE7, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x80,
|
||||
0x00, 0xFC, 0x3F, 0x00, 0x00, 0x7B, 0xDE, 0x00, 0x01, 0xF7, 0xEF, 0xC0,
|
||||
0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x80,
|
||||
0x0f, 0xe0, 0x07, 0xf0,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x3f, 0xf8, 0x9f, 0xfc,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 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
|
||||
static const unsigned char pana_eat[] ={
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
|
||||
0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
|
||||
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
|
||||
0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
|
||||
0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFC, 0x3F, 0x80, 0x01, 0xFD, 0x3F, 0x80,
|
||||
0x00, 0xFC, 0xBF, 0x00, 0x00, 0x7C, 0x3E, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
|
||||
0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
|
||||
static const unsigned char pana_eat[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x80,
|
||||
0x0f, 0xe0, 0x07, 0xf0,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x3f, 0xf8, 0x9f, 0xfc,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 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
|
||||
static const unsigned char pana_sleep[] = {
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0xF0,
|
||||
0x00, 0x31, 0x8C, 0x20, 0x00, 0x39, 0xDC, 0x40, 0x00, 0x3B, 0xDC, 0xF0,
|
||||
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
|
||||
0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
|
||||
0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xC3, 0xC3, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xFF, 0xFF, 0xC0, 0x01, 0xFF, 0xFF, 0x80, 0x01, 0xFE, 0x7F, 0x80,
|
||||
0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
|
||||
0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x80,
|
||||
0x0f, 0xe0, 0x07, 0xf0,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x3f, 0xf8, 0x9f, 0xfc,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 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
|
||||
static const unsigned char pana_dead[] = {
|
||||
0x00, 0x00, 0x80, 0x00, 0x00, 0x10, 0x88, 0x00, 0x00, 0x11, 0x88, 0x00,
|
||||
0x00, 0x31, 0x8C, 0x00, 0x00, 0x39, 0xDC, 0x00, 0x00, 0x3B, 0xDC, 0x00,
|
||||
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x00, 0xF8, 0x3F, 0x00,
|
||||
0x00, 0xE7, 0xC7, 0x00, 0x00, 0xDF, 0xFB, 0x00, 0x01, 0xBF, 0xFD, 0x80,
|
||||
0x01, 0xFF, 0xFF, 0x80, 0x03, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xC0,
|
||||
0x03, 0xDB, 0xDB, 0xC0, 0x03, 0xE7, 0xE7, 0xC0, 0x03, 0xE7, 0xE7, 0xC0,
|
||||
0x03, 0xDB, 0xDB, 0xC0, 0x01, 0xFD, 0xDF, 0x80, 0x01, 0xFA, 0xBF, 0x80,
|
||||
0x00, 0xF7, 0x7F, 0x00, 0x00, 0x7F, 0xFE, 0x00, 0x01, 0xFF, 0xFF, 0xC0,
|
||||
0x01, 0xFF, 0xFF, 0xC0, 0x03, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF8, 0x0F, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x00, 0x00, 0x80,
|
||||
0x0f, 0xe0, 0x07, 0xf0,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x1f, 0xf0, 0x0f, 0xf8,
|
||||
0x3f, 0xf8, 0x9f, 0xfc,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 0xff, 0xff, 0xf8,
|
||||
0x1f, 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 };
|
||||
|
|
@ -406,17 +552,276 @@ static const unsigned char tmux_sleep[] = {
|
|||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
#define SPECIES_COUNT 3
|
||||
enum { SP_SHELL, SP_PANKO, SP_TMUX };
|
||||
// custom_idle: 32x32
|
||||
static const unsigned char custom_idle[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// custom_blink: 32x32
|
||||
static const unsigned char custom_blink[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// custom_happy: 32x32
|
||||
static const unsigned char custom_happy[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// custom_sad: 32x32
|
||||
static const unsigned char custom_sad[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// custom_eat: 32x32
|
||||
static const unsigned char custom_eat[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// custom_sleep: 32x32
|
||||
static const unsigned char custom_sleep[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// custom_dead: 32x32
|
||||
static const unsigned char custom_dead[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
#define SPECIES_COUNT 4
|
||||
enum { SP_SHELL, SP_PANKO, SP_TMUX, SP_CUSTOM };
|
||||
static const char *const species_names[SPECIES_COUNT] = {
|
||||
"Shell", "Panko", "Tmux",
|
||||
"Shell", "Panko", "Tmux", "Custom",
|
||||
};
|
||||
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 },
|
||||
{ custom_idle, custom_blink, custom_happy, custom_sad, custom_eat, custom_sleep, custom_dead },
|
||||
};
|
||||
|
||||
// Eye centre coords for custom species accessories (shades at user, headset at root).
|
||||
// Pixel positions inside the 32x32 sprite -- adjust to match your art.
|
||||
#define CUSTOM_EYE_CY 17 // eye centre row
|
||||
#define CUSTOM_EYE_LX 11 // left eye centre column
|
||||
#define CUSTOM_EYE_RX 21 // right eye centre column
|
||||
|
||||
// spr_grave: 32x32
|
||||
static const unsigned char spr_grave[] = {
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
|
|
@ -677,12 +1082,32 @@ static const unsigned char icon_settings[] = {
|
|||
0x03, 0xc0,
|
||||
};
|
||||
|
||||
#define ICON_COUNT 7
|
||||
// icon_ir: 16x16
|
||||
static const unsigned char icon_ir[] = {
|
||||
0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
0x00, 0x80,
|
||||
0x01, 0x40,
|
||||
0x02, 0x20,
|
||||
0x04, 0x10,
|
||||
0x00, 0x80,
|
||||
0x09, 0x48,
|
||||
0x02, 0x20,
|
||||
0x10, 0x84,
|
||||
0x00, 0x80,
|
||||
0x00, 0x80,
|
||||
0x00, 0x80,
|
||||
0x00, 0x80,
|
||||
0x01, 0xc0,
|
||||
0x00, 0x00,
|
||||
};
|
||||
|
||||
#define ICON_COUNT 8
|
||||
static const unsigned char *const menu_icons[ICON_COUNT] = {
|
||||
icon_feed, icon_play, icon_sleep, icon_clean, icon_heal, icon_stats, icon_settings,
|
||||
icon_feed, icon_play, icon_sleep, icon_clean, icon_heal, icon_stats, icon_settings, icon_ir,
|
||||
};
|
||||
static const char *const menu_labels[ICON_COUNT] = {
|
||||
"FEED", "PLAY", "SLEEP", "CLEAN", "HEAL", "STATS", "SETTINGS",
|
||||
"FEED", "PLAY", "SLEEP", "CLEAN", "HEAL", "STATS", "SETTINGS", "IR BEAM",
|
||||
};
|
||||
|
||||
#endif // _SPRITES_H
|
||||
|
|
|
|||
|
|
@ -52,6 +52,45 @@ def from_art(art):
|
|||
g.append([1 if c in "#X1" else 0 for c in row.ljust(w)])
|
||||
return g
|
||||
|
||||
def from_png(path, threshold=128):
|
||||
"""Load a PNG and convert to a 1-bpp grid (white/bright=lit, dark=off).
|
||||
Width is padded to the nearest multiple of 8. Requires Pillow: pip install pillow"""
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
raise SystemExit("from_png() needs Pillow: pip install pillow")
|
||||
img = Image.open(path).convert("L")
|
||||
w, h = img.size
|
||||
wp = (w + 7) // 8 * 8
|
||||
g = []
|
||||
for y in range(h):
|
||||
row = [1 if x < w and img.getpixel((x, y)) > threshold else 0 for x in range(wp)]
|
||||
g.append(row)
|
||||
return g
|
||||
|
||||
def load_species_pngs(prefix, aliases=None):
|
||||
"""Load the 7 expression PNGs for a species from <prefix>_<expr>.png files.
|
||||
Missing files fall back automatically (blink->idle, eat->happy, dead->sad).
|
||||
Pass aliases={"blink": "idle"} to force a specific reuse.
|
||||
Only custom_idle.png is strictly required; others are optional."""
|
||||
import os
|
||||
EXPRS = ["idle", "blink", "happy", "sad", "eat", "sleep", "dead"]
|
||||
FALLBACK = { "blink": "idle", "eat": "happy", "dead": "sad"}
|
||||
if aliases:
|
||||
FALLBACK.update(aliases)
|
||||
cache = {}
|
||||
result = []
|
||||
for name in EXPRS:
|
||||
src = FALLBACK.get(name, name)
|
||||
if src not in cache:
|
||||
path = f"{prefix}_{src}.png"
|
||||
if not os.path.exists(path):
|
||||
path = f"{prefix}_idle.png"
|
||||
cache[src] = from_png(path)
|
||||
cache[name] = cache[src]
|
||||
result.append(cache[name])
|
||||
return result
|
||||
|
||||
def pack(g):
|
||||
"""Pack to bytes, MSB first, width padded to multiple of 8."""
|
||||
h = len(g)
|
||||
|
|
@ -395,6 +434,23 @@ def icon_settings(): # gear
|
|||
fill_ellipse(g, 8, 8, 2, 2, 0) # hub hole
|
||||
return g
|
||||
|
||||
def icon_ir(): # wifi-style arcs = IR signal
|
||||
g = grid(16, 16)
|
||||
vline(g, 8, 9, 14) # antenna stem
|
||||
px(g, 7, 14); px(g, 9, 14) # base feet
|
||||
# inner arc
|
||||
px(g, 6, 8); px(g, 10, 8)
|
||||
px(g, 7, 7); px(g, 9, 7)
|
||||
px(g, 8, 6)
|
||||
# outer arc
|
||||
px(g, 3, 9); px(g, 13, 9)
|
||||
px(g, 4, 7); px(g, 12, 7)
|
||||
px(g, 5, 5); px(g, 11, 5)
|
||||
px(g, 6, 4); px(g, 10, 4)
|
||||
px(g, 7, 3); px(g, 9, 3)
|
||||
px(g, 8, 2)
|
||||
return g
|
||||
|
||||
# ---------------------------------------------------------------- main
|
||||
def main():
|
||||
print("// Auto-generated by sprites_gen.py -- do not edit by hand.")
|
||||
|
|
@ -442,17 +498,46 @@ def main():
|
|||
|
||||
# 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 };")
|
||||
#
|
||||
# Custom species: drop PNG files named custom_<expr>.png (32x32, white=lit)
|
||||
# next to this script and re-run it. Required: custom_idle.png.
|
||||
# Optional (fall back automatically): custom_blink, custom_happy, custom_sad,
|
||||
# custom_eat, custom_sleep, custom_dead.
|
||||
import os
|
||||
has_custom = os.path.exists("custom_idle.png")
|
||||
if has_custom:
|
||||
EXPRS = ["idle", "blink", "happy", "sad", "eat", "sleep", "dead"]
|
||||
custom_frames = load_species_pngs("custom")
|
||||
for name, g in zip(EXPRS, custom_frames):
|
||||
emit(f"custom_{name}", g)
|
||||
|
||||
species_count = 4 if has_custom else 3
|
||||
print(f"#define SPECIES_COUNT {species_count}")
|
||||
enum_vals = "SP_SHELL, SP_PANKO, SP_TMUX" + (", SP_CUSTOM" if has_custom else "")
|
||||
print(f"enum {{ {enum_vals} }};")
|
||||
print("static const char *const species_names[SPECIES_COUNT] = {")
|
||||
if has_custom:
|
||||
print(' "Shell", "Panko", "Tmux", "Custom",')
|
||||
else:
|
||||
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 },")
|
||||
if has_custom:
|
||||
print(" { custom_idle, custom_blink, custom_happy, custom_sad, custom_eat, custom_sleep, custom_dead },")
|
||||
print("};")
|
||||
print()
|
||||
if has_custom:
|
||||
# Emit eye geometry as defines so tama.c can extend its arrays.
|
||||
# Edit these pixel coords to match where your character's eyes sit.
|
||||
print("// Eye centre coords for custom species accessories (shades at user, headset at root).")
|
||||
print("// Pixel positions inside the 32x32 sprite -- adjust to match your art.")
|
||||
print("#define CUSTOM_EYE_CY 17 // eye centre row")
|
||||
print("#define CUSTOM_EYE_LX 11 // left eye centre column")
|
||||
print("#define CUSTOM_EYE_RX 21 // right eye centre column")
|
||||
print()
|
||||
|
||||
emit("spr_grave", grave())
|
||||
emit("spr_poop", poop())
|
||||
|
|
@ -467,16 +552,17 @@ def main():
|
|||
("icon_heal", icon_heal()),
|
||||
("icon_stats", icon_stats()),
|
||||
("icon_settings", icon_settings()),
|
||||
("icon_ir", icon_ir()),
|
||||
]
|
||||
for n, g in icons:
|
||||
emit(n, g)
|
||||
|
||||
print("#define ICON_COUNT 7")
|
||||
print("#define ICON_COUNT 8")
|
||||
print("static const unsigned char *const menu_icons[ICON_COUNT] = {")
|
||||
print(" icon_feed, icon_play, icon_sleep, icon_clean, icon_heal, icon_stats, icon_settings,")
|
||||
print(" icon_feed, icon_play, icon_sleep, icon_clean, icon_heal, icon_stats, icon_settings, icon_ir,")
|
||||
print("};")
|
||||
print('static const char *const menu_labels[ICON_COUNT] = {')
|
||||
print(' "FEED", "PLAY", "SLEEP", "CLEAN", "HEAL", "STATS", "SETTINGS",')
|
||||
print(' "FEED", "PLAY", "SLEEP", "CLEAN", "HEAL", "STATS", "SETTINGS", "IR BEAM",')
|
||||
print("};")
|
||||
print()
|
||||
print("#endif // _SPRITES_H")
|
||||
|
|
|
|||
345
tama.c
|
|
@ -32,6 +32,9 @@
|
|||
#define BTN_RIGHT PD5
|
||||
#define BTN_LEFT PD6
|
||||
#define BUZZER PD2
|
||||
#define IR_TX PD4 // IR LED, bit-bang 38 kHz carrier
|
||||
#define IR_RX PD3 // IR demodulator data (active-low)
|
||||
#define IR_RX_VCC PC4 // IR receiver power gate (high = on)
|
||||
|
||||
// ----------------------------------------------------------------- tuning
|
||||
#define FRAME_MS 60 // main loop period
|
||||
|
|
@ -43,7 +46,7 @@
|
|||
// a need empties in ~20 min, after which health bleeds down over ~10 more.
|
||||
|
||||
// ----------------------------------------------------------------- state
|
||||
enum { SCR_SELECT, SCR_MAIN, SCR_STATS, SCR_DEAD, SCR_SETTINGS };
|
||||
enum { SCR_SELECT, SCR_MAIN, SCR_STATS, SCR_DEAD, SCR_SETTINGS, SCR_IR, SCR_TV };
|
||||
|
||||
// notification thresholds: gentle chirp at <50%, urgent at <20%
|
||||
enum { N_FOOD, N_FUN, N_ENGY, N_WASH, N_COUNT };
|
||||
|
|
@ -107,6 +110,47 @@ static const char *const screen_timeout_names[] = { "5s", "10s", "15s", "30s", "
|
|||
#define TIMEOUT_OPT_COUNT 6
|
||||
#define SETTINGS_ROWS 4 // screen-off, brightness, sound, back
|
||||
|
||||
// IR screen state
|
||||
// Modes: 0=ZAPP (send ping), 1=RECV (listen), 2=BEAM (send pet data), 3=TV remote, 4=BACK
|
||||
enum { IR_ZAPP, IR_RECV, IR_BEAM, IR_TV, IR_BACK, IR_MODE_COUNT };
|
||||
static uint8_t ir_mode = 0;
|
||||
// ir_status: 0=idle, 1=sent, 2=got signal, 3=nothing, 4=waiting
|
||||
static uint8_t ir_status = 0;
|
||||
static uint8_t ir_stimer = 0; // frames to show a status message
|
||||
|
||||
// NEC protocol constants for badge-to-badge comms
|
||||
#define PANA_ADDR 0x50 // 'P' for Panagotchi
|
||||
|
||||
// ----------------------------------------------------------------- TV remote
|
||||
// Commands fired at every brand in sequence; TV responds to whichever matches.
|
||||
// Order must match tv_cmd_names[]. BACK is a navigation item, not an IR code.
|
||||
#define TV_CMD_COUNT 6
|
||||
#define TV_NAV_COUNT 7 // TV_CMD_COUNT + 1 (BACK entry)
|
||||
#define TV_BRAND_COUNT 5
|
||||
|
||||
static const char *const tv_cmd_names[TV_NAV_COUNT] = {
|
||||
"POWER", "VOL+", "VOL-", "MUTE", "CH+", "CH-", "BACK",
|
||||
};
|
||||
|
||||
// NEC addresses per brand
|
||||
static const uint8_t tv_addr[TV_BRAND_COUNT] = {
|
||||
0x04, // LG
|
||||
0x40, // Panasonic
|
||||
0xA5, // Pioneer
|
||||
0x05, // Mitsubishi
|
||||
0x00, // Generic / CHUNGHWA
|
||||
};
|
||||
// Commands: [brand][POWER, VOL+, VOL-, MUTE, CH+, CH-]
|
||||
static const uint8_t tv_cmds[TV_BRAND_COUNT][TV_CMD_COUNT] = {
|
||||
{ 0x08, 0x02, 0x03, 0x09, 0x00, 0x01 }, // LG
|
||||
{ 0x3D, 0x20, 0x21, 0x04, 0x34, 0x35 }, // Panasonic
|
||||
{ 0x1C, 0x14, 0x15, 0x19, 0x01, 0x02 }, // Pioneer
|
||||
{ 0x18, 0x07, 0x0B, 0x0D, 0x02, 0x06 }, // Mitsubishi
|
||||
{ 0x45, 0x46, 0x15, 0x09, 0x09, 0x15 }, // Generic
|
||||
};
|
||||
|
||||
static uint8_t tv_cmd = 0; // currently selected command (0..TV_NAV_COUNT-1)
|
||||
|
||||
// display power state
|
||||
static uint8_t disp_on = 1;
|
||||
static uint32_t idle_frames = 0; // frames since the last button activity
|
||||
|
|
@ -115,7 +159,7 @@ static uint32_t idle_frames = 0; // frames since the last button activity
|
|||
static uint32_t reset_cause = 0;
|
||||
|
||||
// menu icon order must match sprites.h menu_icons[] / menu_labels[]
|
||||
enum { ACT_FEED, ACT_PLAY, ACT_SLEEP, ACT_CLEAN, ACT_HEAL, ACT_STATS, ACT_SETTINGS };
|
||||
enum { ACT_FEED, ACT_PLAY, ACT_SLEEP, ACT_CLEAN, ACT_HEAL, ACT_STATS, ACT_SETTINGS, ACT_IR };
|
||||
|
||||
// ----------------------------------------------------------------- helpers
|
||||
static int8_t clamp100(int v) { return v < 0 ? 0 : (v > 100 ? 100 : v); }
|
||||
|
|
@ -156,6 +200,90 @@ static void led_blink(void)
|
|||
funDigitalWrite(LED_PIN, FUN_LOW);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------- IR TX
|
||||
// Bit-bang 38 kHz carrier on IR_TX for `us` microseconds (mark).
|
||||
static void ir_mark(uint16_t us)
|
||||
{
|
||||
// half-period ≈ 13 µs → cycles ≈ us/26
|
||||
uint16_t cycles = us / 26u + 1u;
|
||||
for (uint16_t i = 0; i < cycles; i++) {
|
||||
funDigitalWrite(IR_TX, FUN_HIGH);
|
||||
Delay_Us(13);
|
||||
funDigitalWrite(IR_TX, FUN_LOW);
|
||||
Delay_Us(13);
|
||||
}
|
||||
}
|
||||
|
||||
static void ir_space(uint16_t us)
|
||||
{
|
||||
funDigitalWrite(IR_TX, FUN_LOW);
|
||||
Delay_Us(us);
|
||||
}
|
||||
|
||||
// Send one NEC frame: 9ms mark + 4.5ms space + 32 bits + stop.
|
||||
static void ir_send_nec(uint8_t addr, uint8_t cmd)
|
||||
{
|
||||
ir_mark(9000); ir_space(4500);
|
||||
uint8_t bytes[4] = { addr, (uint8_t)~addr, cmd, (uint8_t)~cmd };
|
||||
for (uint8_t b = 0; b < 4; b++) {
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ir_mark(562);
|
||||
ir_space((bytes[b] & 1) ? 1687u : 562u);
|
||||
bytes[b] >>= 1;
|
||||
}
|
||||
}
|
||||
ir_mark(562);
|
||||
funDigitalWrite(IR_TX, FUN_LOW);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------- IR RX
|
||||
// Wait up to `wait_us` for IR_RX to reach `lvl`, then measure how long it
|
||||
// holds (up to `hold_us`). Returns ~duration in µs (10 µs resolution), or 0
|
||||
// on timeout. The IR demodulator output is active-low: LOW = carrier present.
|
||||
static uint32_t ir_pulse(uint8_t lvl, uint32_t wait_us, uint32_t hold_us)
|
||||
{
|
||||
uint32_t t;
|
||||
for (t = 0; t < wait_us; t += 10) {
|
||||
if ((uint8_t)funDigitalRead(IR_RX) == lvl) goto measure;
|
||||
Delay_Us(10);
|
||||
}
|
||||
return 0;
|
||||
measure:;
|
||||
for (t = 0; t < hold_us; t += 10) {
|
||||
if ((uint8_t)funDigitalRead(IR_RX) != lvl) return t;
|
||||
Delay_Us(10);
|
||||
}
|
||||
return 0; // pulse held too long
|
||||
}
|
||||
|
||||
// Try to receive one NEC frame. Blocks up to ~2 s waiting for the leader.
|
||||
// Returns 1 on a valid frame, 0 otherwise. addr/cmd are set on success.
|
||||
static uint8_t ir_recv_nec(uint8_t *addr_out, uint8_t *cmd_out)
|
||||
{
|
||||
// leader mark ~9000 µs LOW; wait up to 2 s
|
||||
uint32_t t = ir_pulse(FUN_LOW, 2000000u, 12000u);
|
||||
if (t < 7000u) return 0;
|
||||
|
||||
// leader space ~4500 µs HIGH
|
||||
t = ir_pulse(FUN_HIGH, 8000u, 6000u);
|
||||
if (t < 3000u) return 0;
|
||||
|
||||
uint32_t bits = 0;
|
||||
for (uint8_t i = 0; i < 32; i++) {
|
||||
if (!ir_pulse(FUN_LOW, 1500u, 1500u)) return 0;
|
||||
t = ir_pulse(FUN_HIGH, 2500u, 2500u);
|
||||
if (!t) return 0;
|
||||
bits >>= 1;
|
||||
if (t > 1000u) bits |= 0x80000000UL;
|
||||
}
|
||||
ir_pulse(FUN_LOW, 1500u, 1500u); // stop bit
|
||||
|
||||
*addr_out = (uint8_t)(bits & 0xFF);
|
||||
*cmd_out = (uint8_t)((bits >> 16) & 0xFF);
|
||||
return ((bits >> 8) & 0xFF) == (uint8_t)(~*addr_out) &&
|
||||
((bits >> 24) & 0xFF) == (uint8_t)(~*cmd_out);
|
||||
}
|
||||
|
||||
// push the currently selected brightness to the panel's contrast register
|
||||
static void apply_brightness(void)
|
||||
{
|
||||
|
|
@ -315,6 +443,14 @@ static void do_action(uint8_t act)
|
|||
set_sel = 0;
|
||||
beep(659, 40);
|
||||
break;
|
||||
|
||||
case ACT_IR:
|
||||
screen = SCR_IR;
|
||||
ir_mode = 0;
|
||||
ir_status = 0;
|
||||
ir_stimer = 0;
|
||||
beep(659, 40);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -378,19 +514,27 @@ static void draw_center_str(int y, const char *s)
|
|||
|
||||
static void draw_menu_bar(void)
|
||||
{
|
||||
// 7 icons across the bottom (18 px slots), selected one boxed
|
||||
// 8 icons across the bottom (16 px slots, no gap), selected one boxed
|
||||
for (uint8_t i = 0; i < ICON_COUNT; i++) {
|
||||
int x = i * 18 + 1;
|
||||
int x = i * 16;
|
||||
ssd1306_drawImage(x, 48, menu_icons[i], ICON_W, ICON_H, 0);
|
||||
if (i == cursor)
|
||||
ssd1306_drawRect(x - 1, 47, ICON_W + 1, ICON_H + 1, 1);
|
||||
ssd1306_drawRect(x, 47, ICON_W, ICON_H + 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// Per-species eye geometry for accessory rendering (shades at user, headset at root).
|
||||
// When a custom species is present, sprites.h emits CUSTOM_EYE_* defines; update
|
||||
// those values to match where the eyes sit in your custom_idle.png.
|
||||
#ifdef CUSTOM_EYE_CY
|
||||
static const uint8_t eye_cy[SPECIES_COUNT] = { 11, 17, 12, CUSTOM_EYE_CY };
|
||||
static const uint8_t eye_lx[SPECIES_COUNT] = { 11, 11, 9, CUSTOM_EYE_LX };
|
||||
static const uint8_t eye_rx[SPECIES_COUNT] = { 20, 21, 23, CUSTOM_EYE_RX };
|
||||
#else
|
||||
static const uint8_t eye_cy[SPECIES_COUNT] = { 11, 17, 12 };
|
||||
static const uint8_t eye_lx[SPECIES_COUNT] = { 11, 11, 9 };
|
||||
static const uint8_t eye_rx[SPECIES_COUNT] = { 20, 21, 23 };
|
||||
#endif
|
||||
|
||||
// Hacker garb earned as the pet levels up, drawn dark over the lit body so it
|
||||
// reads as black gear.
|
||||
|
|
@ -579,6 +723,144 @@ static void draw_settings(void)
|
|||
ssd1306_refresh();
|
||||
}
|
||||
|
||||
static const char *const ir_mode_names[IR_MODE_COUNT] = {
|
||||
"ZAPP", "RECV", "BEAM", "TV", "BACK",
|
||||
};
|
||||
|
||||
static void draw_ir(void)
|
||||
{
|
||||
ssd1306_setbuf(0);
|
||||
draw_center_str(0, "~ IR BEAM ~");
|
||||
ssd1306_drawFastHLine(0, 10, SSD1306_W, 1);
|
||||
|
||||
// mode selector with arrows
|
||||
char mline[17], *p = mline;
|
||||
p = s_cat(p, "< ");
|
||||
p = s_cat(p, ir_mode_names[ir_mode]);
|
||||
p = s_cat(p, " >");
|
||||
*p = 0;
|
||||
draw_center_str(20, mline);
|
||||
|
||||
// status line
|
||||
if (ir_stimer) {
|
||||
const char *msg = "?";
|
||||
if (ir_status == 1) msg = "SENT!";
|
||||
else if (ir_status == 2) msg = "GOT IT! +xp";
|
||||
else if (ir_status == 3) msg = "NOTHING";
|
||||
else if (ir_status == 4) msg = "WAITING...";
|
||||
draw_center_str(35, msg);
|
||||
} else {
|
||||
const char *hint = "?";
|
||||
if (ir_mode == IR_ZAPP) hint = "OK: send ping";
|
||||
else if (ir_mode == IR_RECV) hint = "OK: listen 2s";
|
||||
else if (ir_mode == IR_BEAM) hint = "OK: beam pet";
|
||||
else if (ir_mode == IR_TV) hint = "OK: enter";
|
||||
else if (ir_mode == IR_BACK) hint = "OK: back";
|
||||
draw_center_str(35, hint);
|
||||
}
|
||||
|
||||
draw_center_str(56, "L/R mode OK go");
|
||||
ssd1306_refresh();
|
||||
}
|
||||
|
||||
// Called when OK is pressed on the IR screen.
|
||||
static void do_ir_action(void)
|
||||
{
|
||||
if (ir_mode == IR_BACK) {
|
||||
screen = SCR_MAIN;
|
||||
beep(880, 30);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ir_mode == IR_ZAPP) {
|
||||
ir_send_nec(PANA_ADDR, 0x01); // fixed ping command
|
||||
beep(988, 40); beep(1319, 60);
|
||||
led_blink();
|
||||
ir_status = 1; ir_stimer = 30;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ir_mode == IR_BEAM) {
|
||||
// encode species in high nibble, level in low nibble
|
||||
uint8_t cmd = (uint8_t)((pet.species << 4) | (pet.level & 0x0F));
|
||||
ir_send_nec(PANA_ADDR, cmd);
|
||||
beep(880, 40); beep(1175, 40); beep(1568, 70);
|
||||
led_blink();
|
||||
ir_status = 1; ir_stimer = 30;
|
||||
return;
|
||||
}
|
||||
|
||||
if (ir_mode == IR_TV) {
|
||||
screen = SCR_TV;
|
||||
tv_cmd = 0;
|
||||
beep(659, 40);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ir_mode == IR_RECV) {
|
||||
// show WAITING... while blocking
|
||||
ir_status = 4; ir_stimer = 255;
|
||||
draw_ir();
|
||||
|
||||
// power up the IR receiver
|
||||
funDigitalWrite(IR_RX_VCC, FUN_HIGH);
|
||||
Delay_Ms(5); // stabilise
|
||||
|
||||
uint8_t addr = 0, cmd = 0;
|
||||
uint8_t ok = ir_recv_nec(&addr, &cmd);
|
||||
|
||||
funDigitalWrite(IR_RX_VCC, FUN_LOW); // power down receiver
|
||||
|
||||
if (ok) {
|
||||
if (addr == PANA_ADDR) {
|
||||
if (cmd == 0x01) {
|
||||
// ping from another badge: mood boost
|
||||
pet.happy = clamp100(pet.happy + 10);
|
||||
add_xp(5);
|
||||
} else {
|
||||
// BEAM packet: species in high nibble, level in low nibble
|
||||
uint8_t lvl = cmd & 0x0F;
|
||||
if (lvl > pet.level) add_xp(8); else add_xp(3);
|
||||
pet.happy = clamp100(pet.happy + 8);
|
||||
}
|
||||
} else {
|
||||
// generic NEC signal: small happy bump
|
||||
pet.happy = clamp100(pet.happy + 3);
|
||||
}
|
||||
beep(784, 60); beep(988, 60); beep(1175, 90);
|
||||
led_blink();
|
||||
ir_status = 2;
|
||||
} else {
|
||||
beep(330, 150);
|
||||
ir_status = 3;
|
||||
}
|
||||
ir_stimer = 45;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_tv(void)
|
||||
{
|
||||
ssd1306_setbuf(0);
|
||||
draw_center_str(0, "~ TV REMOTE ~");
|
||||
ssd1306_drawFastHLine(0, 10, SSD1306_W, 1);
|
||||
|
||||
// command selector
|
||||
char line[17], *p = line;
|
||||
p = s_cat(p, "< ");
|
||||
p = s_cat(p, tv_cmd_names[tv_cmd]);
|
||||
p = s_cat(p, " >");
|
||||
*p = 0;
|
||||
draw_center_str(22, line);
|
||||
|
||||
if (tv_cmd < TV_CMD_COUNT)
|
||||
draw_center_str(36, "blast 5 brands");
|
||||
// BACK entry has no sub-label
|
||||
|
||||
ssd1306_drawFastHLine(0, 46, SSD1306_W, 1);
|
||||
draw_center_str(50, "L/R cmd OK go");
|
||||
ssd1306_refresh();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------- input
|
||||
typedef struct { uint8_t ok, left, right; } Buttons;
|
||||
|
||||
|
|
@ -685,6 +967,15 @@ static void gpio_setup(void)
|
|||
funPinMode(BUZZER, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP);
|
||||
funDigitalWrite(BUZZER, FUN_LOW);
|
||||
|
||||
funPinMode(IR_TX, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP);
|
||||
funDigitalWrite(IR_TX, FUN_LOW);
|
||||
|
||||
funPinMode(IR_RX_VCC, GPIO_Speed_10MHz | GPIO_CNF_OUT_PP);
|
||||
funDigitalWrite(IR_RX_VCC, FUN_LOW); // receiver off at boot
|
||||
|
||||
funPinMode(IR_RX, GPIO_CNF_IN_PUPD);
|
||||
funDigitalWrite(IR_RX, FUN_HIGH); // pull-up (idle = HIGH)
|
||||
|
||||
// buttons: input with internal pull-up (ODR high selects pull-up)
|
||||
funPinMode(BTN_OK, GPIO_CNF_IN_PUPD);
|
||||
funPinMode(BTN_LEFT, GPIO_CNF_IN_PUPD);
|
||||
|
|
@ -851,6 +1142,46 @@ int main(void)
|
|||
}
|
||||
draw_dead();
|
||||
break;
|
||||
|
||||
case SCR_IR:
|
||||
if (left_edge) {
|
||||
ir_mode = (ir_mode + IR_MODE_COUNT - 1) % IR_MODE_COUNT;
|
||||
beep(1047, 25);
|
||||
}
|
||||
if (right_edge) {
|
||||
ir_mode = (ir_mode + 1) % IR_MODE_COUNT;
|
||||
beep(1047, 25);
|
||||
}
|
||||
if (ok_edge) do_ir_action();
|
||||
if (ir_stimer) ir_stimer--;
|
||||
draw_ir();
|
||||
break;
|
||||
|
||||
case SCR_TV:
|
||||
if (left_edge) {
|
||||
tv_cmd = (tv_cmd + TV_NAV_COUNT - 1) % TV_NAV_COUNT;
|
||||
beep(1047, 25);
|
||||
}
|
||||
if (right_edge) {
|
||||
tv_cmd = (tv_cmd + 1) % TV_NAV_COUNT;
|
||||
beep(1047, 25);
|
||||
}
|
||||
if (ok_edge) {
|
||||
if (tv_cmd == TV_CMD_COUNT) { // BACK entry
|
||||
screen = SCR_IR;
|
||||
beep(880, 30);
|
||||
} else {
|
||||
// blast selected command at all brands in turn
|
||||
for (uint8_t b = 0; b < TV_BRAND_COUNT; b++) {
|
||||
ir_send_nec(tv_addr[b], tv_cmds[b][tv_cmd]);
|
||||
Delay_Ms(40); // inter-frame gap
|
||||
}
|
||||
beep(988, 40); beep(1319, 80);
|
||||
led_blink();
|
||||
}
|
||||
}
|
||||
draw_tv();
|
||||
break;
|
||||
}
|
||||
|
||||
// sleep the display after the configured idle period
|
||||
|
|
|
|||