Skip to content

Add Pokedex app — Gen 1 Pokemon viewer with shiny sprites#93

Open
keith-oak wants to merge 1 commit into
badger:mainfrom
keith-oak:add-pokedex-app
Open

Add Pokedex app — Gen 1 Pokemon viewer with shiny sprites#93
keith-oak wants to merge 1 commit into
badger:mainfrom
keith-oak:add-pokedex-app

Conversation

@keith-oak

Copy link
Copy Markdown

Summary

  • Gen 1 Pokedex viewer with all 151 Pokemon + a hidden easter egg (#000)
  • Local gen1.json database (12KB) — instant data loading, no WiFi needed for stats
  • 80x80 sprites fetched via fetch_sprites.py (not bundled — Nintendo copyright)
  • Shiny sprite toggle with animated sparkle effects
  • Classic Pokedex-inspired red header/footer design

Controls

Button Action
A Previous Pokemon
C Next Pokemon
B Random Pokemon
UP Toggle shiny/normal
A+C hold Force refresh sprite

Setup

  1. Copy app files to badge
  2. Run python3 fetch_sprites.py /Volumes/BADGER to download sprites
  3. Sprites are cached on badge — only needed once

Screenshots

Charmander (#4)

Charmander

Pikachu (#25)

Pikachu

Shiny Pikachu

Shiny Pikachu

Mewtwo (#150)

Mewtwo

Gen 1 Pokedex with all 151 Pokemon and local gen1.json database.
Sprites fetched via fetch_sprites.py (not bundled due to copyright).
Shiny toggle with sparkle effects, Pokedex-inspired UI design.
Copilot AI review requested due to automatic review settings March 31, 2026 10:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Gen 1 Pokédex app for the Universe badge, including a local Pokémon database and sprite download tooling.

Changes:

  • Added pokedex app runtime (__init__.py) with navigation, shiny toggle, and sprite caching/fetching.
  • Added a compact local Gen 1 database (gen1.json) and bundled app icon/easter-egg assets.
  • Added a desktop helper script (fetch_sprites.py) plus several screenshot PNGs.

Reviewed changes

Copilot reviewed 3 out of 10 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
badge/apps/pokedex/init.py Main Pokédex app logic: UI, input handling, DB load, sprite load/cache/fetch, shiny effects
badge/apps/pokedex/gen1.json Local Gen 1 dataset (151 entries + #000 easter egg)
badge/apps/pokedex/fetch_sprites.py Desktop script to download normal/shiny sprites to the badge filesystem
badge/apps/pokedex/icon.png App icon for launcher grid
badge/apps/pokedex/keefymon.png Easter egg sprite asset (normal)
badge/apps/pokedex/keefymon_shiny.png Easter egg sprite asset (shiny)
badge/apps/pokedex/screenshot_charmander.png Screenshot artifact (docs)
badge/apps/pokedex/screenshot_pikachu.png Screenshot artifact (docs)
badge/apps/pokedex/screenshot_shiny_pikachu.png Screenshot artifact (docs)
badge/apps/pokedex/screenshot_mewtwo.png Screenshot artifact (docs)

Comment on lines +120 to +121
return "/system/shiny/{}.png".format(pokemon_id)
return "/system/sprites/{}.png".format(pokemon_id)

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sprite_path() points at /system/sprites and /system/shiny, but fetch_sprites.py (and the PR setup instructions) download into sprites/ and shiny/ at the badge root. As-is, the app won't find pre-fetched sprites and will always try WiFi. Align these paths (and consider using the writable root filesystem rather than /system/, since cached sprites are user data).

Suggested change
return "/system/shiny/{}.png".format(pokemon_id)
return "/system/sprites/{}.png".format(pokemon_id)
return "/shiny/{}.png".format(pokemon_id)
return "/sprites/{}.png".format(pokemon_id)

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +122
def sprite_path(pokemon_id, is_shiny=False):
if is_shiny:
return "/system/shiny/{}.png".format(pokemon_id)
return "/system/sprites/{}.png".format(pokemon_id)

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hidden easter egg #000 can't load its bundled sprites: the repo includes keefymon.png / keefymon_shiny.png, but sprite_path() only ever looks for {id}.png in the sprite cache and will attempt to download id=0 from PokeAPI (likely 404). Add a special-case mapping for pokemon_id == 0 to these local PNGs (or ship them as 0.png in the cache directories).

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +91
try:
sys.path.insert(0, "/")
import secrets
sys.path.pop(0)
WIFI_SSID = getattr(secrets, "WIFI_SSID", None)
WIFI_PASSWORD = getattr(secrets, "WIFI_PASSWORD", None)
del secrets
except ImportError:
WIFI_PASSWORD = None
WIFI_SSID = None

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_wifi_details() inserts "/" into sys.path but only pops it on the success path; if secrets.py exists but raises a non-ImportError (e.g., syntax error), the path entry will be left behind and can affect later imports. Use a try/finally to ensure sys.path.pop(0) always happens after sys.path.insert(0, "/").

Suggested change
try:
sys.path.insert(0, "/")
import secrets
sys.path.pop(0)
WIFI_SSID = getattr(secrets, "WIFI_SSID", None)
WIFI_PASSWORD = getattr(secrets, "WIFI_PASSWORD", None)
del secrets
except ImportError:
WIFI_PASSWORD = None
WIFI_SSID = None
sys.path.insert(0, "/")
try:
import secrets
WIFI_SSID = getattr(secrets, "WIFI_SSID", None)
WIFI_PASSWORD = getattr(secrets, "WIFI_PASSWORD", None)
del secrets
except ImportError:
WIFI_PASSWORD = None
WIFI_SSID = None
finally:
sys.path.pop(0)

Copilot uses AI. Check for mistakes.

# Clear screen
screen.brush = dark_bg
screen.draw(shapes.rectangle(0, 0, 160, 120))

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clearing the frame by drawing a full-screen rectangle is noticeably slower than using screen.clear() on the badge. Set screen.brush = dark_bg and call screen.clear() here (this pattern is used elsewhere in the codebase and avoids extra shape work each frame).

Suggested change
screen.draw(shapes.rectangle(0, 0, 160, 120))
screen.clear()

Copilot uses AI. Check for mistakes.
Comment on lines +377 to +384
# Force sprite refresh on A+C hold
if io.BUTTON_A in io.held and io.BUTTON_C in io.held:
changed = True
try:
os.remove(sprite_path(current_id, shiny))
except:
pass

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The A+C “force refresh” runs every frame while the buttons are held, repeatedly deleting the file and triggering full reload + gc.collect() in the same frame loop. This can cause stutter and makes it hard to complete a refresh. Gate this action to a single edge (e.g., track a refresh_held flag, or trigger when the combo becomes held for the first time).

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +148
def fetch_sprite(pokemon_id, is_shiny=False):
path = sprite_path(pokemon_id, is_shiny)
if file_exists(path):
return
try:
url_path = "shiny/" if is_shiny else ""
response = urlopen(SPRITE_URL.format(id=pokemon_id, path=url_path), headers={"User-Agent": "Pokedex Badge"})
data = bytearray(512)
with open(path, "wb") as f:
while True:
length = response.readinto(data)
if length == 0:
break
f.write(data[:length])
yield
del data
del response
except Exception as e:
try:
os.remove(path)
except:
pass
raise RuntimeError("Sprite fetch failed: {}".format(e))
finally:
gc.collect()

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fetch_sprite() can block indefinitely in urlopen() / readinto() if the network stalls; since this runs inside update(), it can freeze the UI. Consider adding an io.ticks-based timeout (similar to async_fetch_to_disk() in badge/apps/badge/__init__.py) and ensure partial files are cleaned up on timeout/failure.

Copilot uses AI. Check for mistakes.
Comment on lines +186 to +200
# Fixed bar layout
BAR_X = 97
BAR_W = 36

def draw_stat_bar(name, value, x, y, max_val=154):
fill = min(int((value / max_val) * BAR_W), BAR_W)

screen.font = small_font
screen.brush = faded
screen.text(name, x, y)

# Bar background
screen.brush = brushes.color(40, 40, 50)
screen.draw(shapes.rounded_rectangle(BAR_X, y + 2, BAR_W, 5, 2))

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

draw_stat_bar() takes an x argument but the bar itself is always drawn at the global BAR_X (only the label uses x). Either use x for the bar geometry too, or remove the parameter to avoid misleading callers.

Copilot uses AI. Check for mistakes.
Comment on lines +36 to +42
result = subprocess.run(
["curl", "-sf", url, "-o", out_path],
capture_output=True, timeout=30,
)
return pokemon_id, shiny, result.returncode == 0
except Exception:
return pokemon_id, shiny, False

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This script shells out to curl, which isn't available by default on all platforms (notably some Windows setups) and makes error handling harder. Consider using Python's standard library (urllib.request) for downloads so the setup flow works anywhere python3 runs.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants