A gen-1-Pokémon-inspired tamagotchi for the Waveshare ESP32-S3-Touch-AMOLED-1.75 (round 466×466 AMOLED, CO5300 driver over QSPI, CST9217 touch over I2C). Raise any of the 151, evolve it, train it and complete them all (shinies included).
Personal, non-commercial fan project. Code is MIT; the sprites are from PMD SpriteCollab (CC BY-NC, Pokémon © Nintendo/Game Freak), and the 3D case is CC BY-NC-SA. See License and Credits.
🔴 3D-printed Pokéball case + print profiles → on MakerWorld · flash it in your browser → web installer
Running on hardware. Implemented: the 151 + shinies animated from microSD, full life cycle (egg by rarity → evolution → farewell/release/runaway, each gated behind a decision dialog), bred-Pokédex with gallery, battle stats (genes + training), retention hooks (streak / bond / medals / name), biome + real-time backgrounds, ball minigame, training bag, animated bath, RTC with offline progression, battery (AXP2101) and PWR button, anti-burn-in dimming, sound (ES8311), 8 UI languages (English default), starter choice on first run, and a one-click web installer.
Pending: wild encounters / battle (designed, not implemented), 3D case, soak test. See Roadmap.
A quick reference to how the game really works (values straight from the code).
Needs: FOOD, JOY, ENE (energy), HYG (hygiene). Start 80 / 80 / 80 / 100. While awake, per minute:
| Stat | Drain/min | Notes |
|---|---|---|
| FOOD | −2 | |
| ENE | −1 | −1 extra if overweight (weight > 50 → sluggish) |
| HYG | −1 | −4 more per poop on screen (max 3 poops) |
| JOY | −1 | −2 extra if FOOD < 30, −2 extra if HYG < 30 |
| Tier | Base chance | After a proper goodbye | # species |
|---|---|---|---|
| ✨ Legendary | ~3 %* | ~10 % | 5 |
| 🔵 Rare | ~27 % | ~45 % | 27 |
| ⚪ Common | the rest | the rest | 47 |
* Legendaries only start appearing once you've registered ≥ 25 Pokémon. - A daily streak and high bond push rare/legendary odds higher. - A clean goodbye blesses the next egg; a run-away curses it (forces Common). - Within a tier it favors species whose evolution line you haven't finished (so all 151 are completable). - Shiny: base 1 / 48 (→ 1 / 24 right after a goodbye), improved by streak/bond down to a best of 1 / 8. Tracked separately in the dex. - Every hatch rolls unique genes (90–110 % per stat) — no two are identical.
After any ending, a new egg appears.
ATK / DEF / SPD = real Gen-1 base × genes + level + training (STRENGTH ← bag, SPEED ← minigame, DEFENSE ← 12 h of unbroken good care). (Battles: on the roadmap.)
pin_config.h)| Library | Author | Use |
|---|---|---|
GFX Library for Arduino (Arduino_GFX) |
moononournation | CO5300 over QSPI + framebuffer in PSRAM |
| SensorLib | Lewis He | CST9217 touch + PCF85063 RTC |
| XPowersLib | Lewis He | AXP2101 PMU (battery, brightness, PWR button) |
| U8g2 | olikraus | CJK glyphs (unifont_t_japanese3, the only Japanese subset that also carries !?。、「」; unifont_t_korean2 for Hangul, 2350 syllables against korean1's 478); only the font data is used, not its display driver |
| ESP_I2S (bundled in the ESP32 core) | Espressif | I2S to the ES8311 codec |
16M Flash (3MB APP/9MB FATFS)) ·
USB CDC On Boot EnabledFQBN="esp32:esp32:esp32s3:CDCOnBoot=cdc,FlashSize=16M,PSRAM=opi,PartitionScheme=app3M_fat9M_16MB"
arduino-cli compile --fqbn "$FQBN" .
arduino-cli upload -p /dev/cu.usbmodemXXXX --fqbn "$FQBN" .
web/index.html flashes the firmware (ESP Web Tools) and pushes the sprites to
the SD over Web Serial, no Arduino needed. Serve it over HTTPS or localhost
(secure context) and open it in Chrome/Edge. See web/README.md.
All sprites come from PMD SpriteCollab (CC BY-NC). You can regenerate the whole set and load it onto your board with the pipeline below — the firmware accepts files over USB (PUT protocol with per-block ACK), so you don't have to remove the card (it formats the SD to FAT if needed).
python3 tools/pack_pmd.py # fetch + pack PMD sprites: the 151 + shiny -> tools/sdcard/mons/p[s]NNN.bin
python3 tools/make_thumbs.py # Pokédex thumbnails (from the PMD sprites) -> thumbs.bin
python3 tools/send_sd.py # send tools/sdcard/mons/* to the board's SD over USB
To make the one-click web-installer bundle instead of sending over USB:
python3 tools/pack_bundle.py # bundle tools/sdcard/mons/* into web/sprites.pak
Then load it from the web installer's "Load sprites" button (or send_sd.py
above). pack_pmd.py also takes individual dex numbers, e.g. pack_pmd.py 7 25.
(~40 MB total, all PMD. Versioned under tools/sdcard/.)
On first run you choose a starter (Bulbasaur / Charmander / Squirtle). After that you start with an egg. Tap it 3 times or wait and it hatches. From then on, care for your companion:
Four stats that decay: FOOD, JOY, ENE (energy), HYG (hygiene). If one bottoms out it counts as a slip-up.
Buttons (bottom arc, icons): - 🍎 Feed → food menu: 3 berries (each species has a hidden favourite that gives a bonus) and a candy (+happiness but it fattens; weight makes it sluggish). - ⚽ Play → the pokeball minigame (trains SPEED). - 🌙 Light → sleep/wake (recovers energy, dims the screen). While asleep, needs decay much slower (rest). - 🫧 Bath → a foam scene that cleans up the poops.
Touch gestures: - Tap the creature = pet it (+happiness, bond). - Horizontal swipe = open the Pokédex / gallery. - Vertical swipe up = open the stat card (4 pages: Profile / Battle / Medals / Progress; swipe between them; tap the name on Profile to rename; on Battle the "Train strength" button opens the bag). - Swipe down = set the clock and pick the language + sound on/off. - Long press (3 s) on the creature = release dialog.
Physical PWR button: short = screen on/off · long (4 s) = full power-off (the RTC stays alive, so time passes even while it's off).
The three life-cycle endings and evolution don't happen on their own — when the conditions are met a button appears and you tap it (so you're present to witness it), each opening a two-option dialog:
tools/pack_pmd.py packs
actions (Idle, Walk L/R, Sleep, Eat, Hurt, Attack, Pose, Nod, DeepBreath) into
the multi-action TPK2 format (/mons/pNNN.bin). The engine in TamaPoke.ino
makes the creature wander, gesture, curl up to sleep, chew and wince. Anchored by
the feet (lowest content row), not the canvas. The Pokédex thumbnails
(thumbs.bin, TPTH) are derived from these by tools/make_thumbs.py.tools/sprites.py): 9 primitive-drawn sprites as a
no-SD fallback + the UI icons. Generates species.h. Preview in
tools/sheet.png, emit with python3 tools/sprites.py emit.sdmon.h/.cpp loads the PMD sprites into PSRAM (PmdMon for TPK2) plus the
thumbnails (SdThumbs). SdMon (TPK1) remains as a dormant legacy fallback only.
tools/dex_data.py is the single source: name, slug, type (accent colour +
background biome), evolution line with gen-1 levels, rarities and starters.
tools/dex_stats.py has the real base stats (from PokéAPI). tools/gen_names.py
pulls the official localized names from PokéAPI into tools/dex_names.py
(only French and German differ in gen 1; Spanish, Italian and Portuguese use the
English ones). gen_dex.py emits dex.h (the DEX_TBL[152] table plus the
per-language name tables and the dexName() accessor). The pet's identity is its
Pokédex number (persisted in NVS).
Each creature has ATK/DEF/SPD = real gen-1 base × genes (90–110 %, rolled at hatch) + level + training: - SPEED ← the minigame - DEFENSE ← sustained good care (12 h with no slip-ups) - STRENGTH ← the training bag (whacking)
Shown on the Battle page of the stat card. The (hidden) weight goes up with candy and burns off with training.
High streak and bond improve the egg roll (rarity and shiny): caring well always pays off.
The life cycle lasts 3 days of play. Three endings (all leave a new egg): farewell (final form + 3 days), release (long press), runaway (all 4 bars at zero for 1 h). Each bred species is recorded in the bred Pokédex (normal and shiny separately).
The egg rolls rarity over the ~79 base forms (47 common / 27 rare / 5 legendary), biased towards the lines you're missing (all 151 are completable), blessed by a farewell and punished by a runaway. Legendaries only with 25+ registered. Shiny 1/48 (better with streak/bond/farewell).
Languages: the UI ships in 8 languages — English (default), Spanish, French, German, Italian, Portuguese, Japanese and Korean — switchable from the settings screen (swipe down). Pokémon names are localized too: French, German, Japanese and Korean show the official names (Bulbizarre, Bisasam, フシギダネ, 이상해씨...). Spanish, Italian and Portuguese use the English ones, which is what those regions officially use for gen 1. Nidoran♀/♂ read ニドランF/M in Japanese and 니드런 암/수 in Korean: no U8g2 unifont subset carries ♀ or ♂, so each language picks its own substitute.
The idle screen paints the sky from the RTC's real time (dawn / day / dusk / night with moon and stars) and the ground from the type's biome (meadow, beach, forest, volcano, mountain, snow). Sleeping forces night.
TamaPoke.ino — init, game loop, render of every screen, gestures, serial console, audiopet.h / pet.cpp — pet state and logic (stats, evolution, life cycle, streak/bond/medals, NVS)sdmon.h / sdmon.cpp — TPK1 (animated) and TPK2 (PMD) sprites + thumbnails, and file reception over USB (PUT/LS)rtcbat.h / rtcbat.cpp — PCF85063 RTC + AXP2101 PMU (battery, brightness, PWR button)audio.h / audio.cpp — ES8311 + I2S + Game-Boy-style tone synth (non-blocking task)i18n.h / i18n.cpp — the 8-language string tablesdex.h — GENERATED (gen_dex.py): the 151 tablespecies.h — GENERATED (sprites.py): fallback sprites, UI icons, colourspin_config.h — the board's official pinstools/ — pipeline: dex_data.py (data), dex_stats.py, dex_names.py +
gen_names.py (localized names), gen_dex.py,
sprites.py (workshop), pack_pmd.py / make_thumbs.py
(packers), pack_bundle.py (web bundle), send_sd.py (SD upload), touch_log.pytools/sdcard/mons/ — the generated .bin files (animated, shiny, PMD, thumbnails)web/ — the browser installer (ESP Web Tools + Web Serial sprite loader)The percentage on screen comes from a typical single-cell LiPo discharge curve (batcurve.h) over a smoothed
voltage, and only goes down while discharging; on USB or while charging the PMU's own gauge is used. When the
smoothed voltage stays under 3.30 V for 30 s the pet is saved and the PMU powers off (not in the first 30 s
after boot); the PMU itself is set to cut at 3.1 V as a backstop.
What the firmware does to save power (the policy is in power.h, covered by test/test_power.cpp):
STATS (full state) · SPEC <dex> (change species) · LVL <n> · HATCH ·
SHINY · NICK <x> · BYE / RUN (farewell / runaway) · ABANDON (force the
runaway-ready state) · WIPE (factory reset → new game) · BEEP (audio test) ·
REG (Pokédex) · EGGS (simulate 20 eggs) · GAL (gallery) · CAREDAY ·
TIME <epoch> / RTCSET <epoch> · HEALTH (uptime + heap for the soak test) ·
LS / PUT (SD files).
To test fast: lower PET_TICK_MS, MINUTES_PER_LEVEL and FAREWELL_AGE_MIN in pet.h.
HEALTH command/heartbeat).(Done: 3D-printed case published on MakerWorld; repo public with the browser installer + one-click sprite bundle.)
All sprites: PMD SpriteCollab
(community, CC BY-NC). Base stats: PokéAPI. Pokémon is a ™ of
Nintendo / Game Freak / The Pokémon Company. Non-commercial, personal-use project.
Full list in CREDITS.md. Version history in
CHANGELOG.md — most of the fixes there came from people
who built one and reported what they found.
This is an unofficial fan project, not affiliated with or endorsed by Nintendo.
| 日期 | 榜单 | 排名 | 周期新增 | 总 STAR |
|---|