Initial commit
This commit is contained in:
774
skills/badger-app-creator/SKILL.md
Normal file
774
skills/badger-app-creator/SKILL.md
Normal file
@@ -0,0 +1,774 @@
|
||||
---
|
||||
name: badger-app-creator
|
||||
description: Create MicroPython applications for Universe 2025 (Tufty) Badge including display graphics, button handling, and MonaOS app structure. Use when building badge apps, creating interactive displays, or developing MicroPython programs.
|
||||
---
|
||||
|
||||
# Universe 2025 Badge App Creator
|
||||
|
||||
Create well-structured MicroPython applications for the **Universe 2025 (Tufty) Badge** with MonaOS integration, display graphics, button handling, and proper app architecture.
|
||||
|
||||
## Important: MonaOS App Structure
|
||||
|
||||
**Critical**: MonaOS apps follow a specific structure. Each app is a directory in `/system/apps/` containing:
|
||||
|
||||
```
|
||||
/system/apps/my_app/
|
||||
├── icon.png # 24x24 PNG icon
|
||||
├── __init__.py # Entry point with update() function
|
||||
└── assets/ # Optional: app assets (auto-added to path)
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Required Functions
|
||||
|
||||
Your `__init__.py` must implement:
|
||||
|
||||
**`update()`** - Required, called every frame by MonaOS:
|
||||
```python
|
||||
def update():
|
||||
# Called every frame
|
||||
# Draw your UI, handle input, update state
|
||||
pass
|
||||
```
|
||||
|
||||
**`init()`** - Optional, called once when app launches:
|
||||
```python
|
||||
def init():
|
||||
# Initialize app state, load resources
|
||||
pass
|
||||
```
|
||||
|
||||
**`on_exit()`** - Optional, called when HOME button pressed:
|
||||
```python
|
||||
def on_exit():
|
||||
# Save state, cleanup resources
|
||||
pass
|
||||
```
|
||||
|
||||
## MonaOS App Template
|
||||
|
||||
```python
|
||||
# __init__.py - MonaOS app template
|
||||
from badgeware import screen, brushes, shapes, io, PixelFont, Image
|
||||
|
||||
# App state
|
||||
app_state = {
|
||||
"counter": 0,
|
||||
"color": (255, 255, 255)
|
||||
}
|
||||
|
||||
def init():
|
||||
"""Called once when app launches"""
|
||||
# Load font
|
||||
screen.font = PixelFont.load("nope.ppf")
|
||||
|
||||
# Load saved state if exists
|
||||
try:
|
||||
with open("/storage/myapp_state.txt", "r") as f:
|
||||
app_state["counter"] = int(f.read())
|
||||
except:
|
||||
pass
|
||||
|
||||
print("App initialized!")
|
||||
|
||||
def update():
|
||||
"""Called every frame by MonaOS"""
|
||||
# Clear screen
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw UI
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("My App", 10, 10)
|
||||
screen.text(f"Count: {app_state['counter']}", 10, 30)
|
||||
|
||||
# Handle buttons (checked every frame)
|
||||
if io.BUTTON_A in io.pressed:
|
||||
app_state["counter"] += 1
|
||||
|
||||
if io.BUTTON_B in io.pressed:
|
||||
app_state["counter"] = 0
|
||||
|
||||
# HOME button exits automatically
|
||||
|
||||
def on_exit():
|
||||
"""Called when returning to MonaOS menu"""
|
||||
# Save state
|
||||
with open("/storage/myapp_state.txt", "w") as f:
|
||||
f.write(str(app_state["counter"]))
|
||||
|
||||
print("App exiting!")
|
||||
```
|
||||
|
||||
## Display API (badgeware)
|
||||
|
||||
### Import Modules
|
||||
|
||||
```python
|
||||
from badgeware import screen, brushes, shapes, Image, PixelFont, Matrix, io
|
||||
```
|
||||
|
||||
### Screen Drawing (160x120 framebuffer)
|
||||
|
||||
The screen is a 160×120 RGB framebuffer that MonaOS automatically pixel-doubles to 320×240.
|
||||
|
||||
**Basic Drawing**:
|
||||
```python
|
||||
# Set brush color (RGB 0-255)
|
||||
screen.brush = brushes.color(r, g, b)
|
||||
|
||||
# Clear screen
|
||||
screen.clear()
|
||||
|
||||
# Draw text
|
||||
screen.text("Hello", x, y)
|
||||
|
||||
# Draw shapes
|
||||
screen.draw(shapes.rectangle(x, y, width, height))
|
||||
screen.draw(shapes.circle(x, y, radius))
|
||||
screen.draw(shapes.line(x1, y1, x2, y2))
|
||||
screen.draw(shapes.arc(x, y, radius, start_angle, end_angle))
|
||||
screen.draw(shapes.pie(x, y, radius, start_angle, end_angle))
|
||||
```
|
||||
|
||||
**Antialiasing** (smooth edges):
|
||||
```python
|
||||
screen.antialias = Image.X4 # Enable 4x antialiasing
|
||||
screen.antialias = Image.NONE # Disable
|
||||
```
|
||||
|
||||
**No Manual Update Needed**: MonaOS automatically updates the display after each `update()` call.
|
||||
|
||||
### Shapes Module
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/badgerware/shapes.md
|
||||
|
||||
**Available Shapes**:
|
||||
```python
|
||||
from badgeware import shapes
|
||||
|
||||
# Rectangle
|
||||
shapes.rectangle(x, y, width, height)
|
||||
|
||||
# Circle
|
||||
shapes.circle(x, y, radius)
|
||||
|
||||
# Line
|
||||
shapes.line(x1, y1, x2, y2)
|
||||
|
||||
# Arc (portion of circle outline)
|
||||
shapes.arc(x, y, radius, start_angle, end_angle)
|
||||
|
||||
# Pie (filled circle segment)
|
||||
shapes.pie(x, y, radius, start_angle, end_angle)
|
||||
|
||||
# Rounded rectangle
|
||||
shapes.rounded_rectangle(x, y, width, height, radius)
|
||||
|
||||
# Regular polygon (pentagon, hexagon, etc.)
|
||||
shapes.regular_polygon(x, y, sides, radius)
|
||||
|
||||
# Squircle (smooth rectangle-circle hybrid)
|
||||
shapes.squircle(x, y, width, height)
|
||||
```
|
||||
|
||||
**Transformations**:
|
||||
```python
|
||||
from badgeware import Matrix
|
||||
|
||||
# Create shape
|
||||
rect = shapes.rectangle(-1, -1, 2, 2)
|
||||
|
||||
# Apply transformation
|
||||
rect.transform = Matrix() \
|
||||
.translate(80, 60) \ # Move to center
|
||||
.scale(20, 20) \ # Scale up
|
||||
.rotate(io.ticks / 100) # Animated rotation
|
||||
|
||||
screen.draw(rect)
|
||||
```
|
||||
|
||||
### Brushes Module
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/badgerware/brushes.md
|
||||
|
||||
**Solid Colors**:
|
||||
```python
|
||||
from badgeware import brushes
|
||||
|
||||
# RGB color (0-255 per channel)
|
||||
screen.brush = brushes.color(r, g, b)
|
||||
|
||||
# Examples
|
||||
screen.brush = brushes.color(255, 0, 0) # Red
|
||||
screen.brush = brushes.color(0, 255, 0) # Green
|
||||
screen.brush = brushes.color(0, 0, 255) # Blue
|
||||
screen.brush = brushes.color(255, 255, 255) # White
|
||||
screen.brush = brushes.color(0, 0, 0) # Black
|
||||
```
|
||||
|
||||
### Fonts
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/PixelFont.md
|
||||
|
||||
**30 Licensed Pixel Fonts Included**:
|
||||
```python
|
||||
from badgeware import PixelFont
|
||||
|
||||
# Load font
|
||||
screen.font = PixelFont.load("nope.ppf")
|
||||
|
||||
# Draw text with loaded font
|
||||
screen.text("Styled text", x, y)
|
||||
|
||||
# Measure text width
|
||||
width = screen.font.measure("text to measure")
|
||||
|
||||
# Reset to default font
|
||||
screen.font = None
|
||||
```
|
||||
|
||||
### Images & Sprites
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/badgerware/Image.md
|
||||
|
||||
**Loading Images**:
|
||||
```python
|
||||
from badgeware import Image
|
||||
|
||||
# Load PNG image
|
||||
img = Image.load("sprite.png")
|
||||
|
||||
# Blit to screen
|
||||
screen.blit(img, x, y)
|
||||
|
||||
# Scaled blit
|
||||
screen.scale_blit(img, x, y, width, height)
|
||||
```
|
||||
|
||||
**Sprite Sheets**:
|
||||
```python
|
||||
# Using SpriteSheet helper (from examples)
|
||||
from lib import SpriteSheet
|
||||
|
||||
# Load sprite sheet (7 columns, 1 row)
|
||||
sprites = SpriteSheet("assets/mona-sprites.png", 7, 1)
|
||||
|
||||
# Blit specific sprite (column 0, row 0)
|
||||
screen.blit(sprites.sprite(0, 0), x, y)
|
||||
|
||||
# Scaled sprite
|
||||
screen.scale_blit(sprites.sprite(3, 0), x, y, 30, 30)
|
||||
```
|
||||
|
||||
## Button Handling (io module)
|
||||
|
||||
Full documentation: https://github.com/badger/home/blob/main/badgerware/io.md
|
||||
|
||||
### Button Constants
|
||||
|
||||
```python
|
||||
from badgeware import io
|
||||
|
||||
# Available buttons
|
||||
io.BUTTON_A # Left button
|
||||
io.BUTTON_B # Middle button
|
||||
io.BUTTON_C # Right button
|
||||
io.BUTTON_UP # Up button
|
||||
io.BUTTON_DOWN # Down button
|
||||
io.BUTTON_HOME # HOME button (exits to MonaOS)
|
||||
```
|
||||
|
||||
### Button States
|
||||
|
||||
Check button states within your `update()` function:
|
||||
|
||||
```python
|
||||
def update():
|
||||
# Button just pressed this frame
|
||||
if io.BUTTON_A in io.pressed:
|
||||
print("A was just pressed")
|
||||
|
||||
# Button just released this frame
|
||||
if io.BUTTON_B in io.released:
|
||||
print("B was just released")
|
||||
|
||||
# Button currently held down
|
||||
if io.BUTTON_C in io.held:
|
||||
print("C is being held")
|
||||
|
||||
# Button state changed this frame (pressed or released)
|
||||
if io.BUTTON_UP in io.changed:
|
||||
print("UP state changed")
|
||||
```
|
||||
|
||||
**No Debouncing Needed**: The io module handles button debouncing automatically.
|
||||
|
||||
### Menu Navigation Example
|
||||
|
||||
```python
|
||||
menu_items = ["Option 1", "Option 2", "Option 3", "Option 4"]
|
||||
selected = 0
|
||||
|
||||
def update():
|
||||
global selected
|
||||
|
||||
# Clear screen
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw title
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("Menu", 10, 5)
|
||||
|
||||
# Draw menu items
|
||||
y = 30
|
||||
for i, item in enumerate(menu_items):
|
||||
if i == selected:
|
||||
# Highlight selected item
|
||||
screen.brush = brushes.color(255, 255, 0)
|
||||
screen.text("> " + item, 10, y)
|
||||
else:
|
||||
screen.brush = brushes.color(200, 200, 200)
|
||||
screen.text(" " + item, 10, y)
|
||||
y += 20
|
||||
|
||||
# Handle navigation
|
||||
if io.BUTTON_UP in io.pressed:
|
||||
selected = (selected - 1) % len(menu_items)
|
||||
|
||||
if io.BUTTON_DOWN in io.pressed:
|
||||
selected = (selected + 1) % len(menu_items)
|
||||
|
||||
if io.BUTTON_A in io.pressed:
|
||||
print(f"Selected: {menu_items[selected]}")
|
||||
```
|
||||
|
||||
## Animation & Timing
|
||||
|
||||
### Using io.ticks
|
||||
|
||||
```python
|
||||
from badgeware import io
|
||||
import math
|
||||
|
||||
def update():
|
||||
# io.ticks increments every frame
|
||||
# Use for smooth animations
|
||||
|
||||
# Oscillating value
|
||||
y = (math.sin(io.ticks / 100) * 30) + 60
|
||||
|
||||
# Rotating shape
|
||||
angle = io.ticks / 50
|
||||
rect = shapes.rectangle(-1, -1, 2, 2)
|
||||
rect.transform = Matrix().translate(80, 60).rotate(angle)
|
||||
screen.draw(rect)
|
||||
|
||||
# Pulsing size
|
||||
scale = (math.sin(io.ticks / 60) * 10) + 20
|
||||
circle = shapes.circle(80, 60, scale)
|
||||
screen.draw(circle)
|
||||
```
|
||||
|
||||
## State Management
|
||||
|
||||
### Persistent Storage
|
||||
|
||||
Store app data in the writable LittleFS partition at `/storage/`:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
CONFIG_FILE = "/storage/myapp_config.json"
|
||||
|
||||
def save_config(data):
|
||||
"""Save configuration to persistent storage"""
|
||||
try:
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
json.dump(data, f)
|
||||
print("Config saved!")
|
||||
except Exception as e:
|
||||
print(f"Save failed: {e}")
|
||||
|
||||
def load_config():
|
||||
"""Load configuration from persistent storage"""
|
||||
try:
|
||||
with open(CONFIG_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
# Return defaults if file doesn't exist
|
||||
return {
|
||||
"name": "Badge User",
|
||||
"theme": "light",
|
||||
"counter": 0
|
||||
}
|
||||
|
||||
# Usage in app
|
||||
config = {}
|
||||
|
||||
def init():
|
||||
global config
|
||||
config = load_config()
|
||||
print(f"Loaded: {config}")
|
||||
|
||||
def on_exit():
|
||||
save_config(config)
|
||||
```
|
||||
|
||||
### State Machine Pattern
|
||||
|
||||
```python
|
||||
class AppState:
|
||||
MENU = 0
|
||||
GAME = 1
|
||||
SETTINGS = 2
|
||||
GAME_OVER = 3
|
||||
|
||||
state = AppState.MENU
|
||||
game_data = {"score": 0, "level": 1}
|
||||
|
||||
def update():
|
||||
global state
|
||||
|
||||
if state == AppState.MENU:
|
||||
draw_menu()
|
||||
if io.BUTTON_A in io.pressed:
|
||||
state = AppState.GAME
|
||||
|
||||
elif state == AppState.GAME:
|
||||
update_game()
|
||||
draw_game()
|
||||
if game_data["score"] < 0:
|
||||
state = AppState.GAME_OVER
|
||||
|
||||
elif state == AppState.SETTINGS:
|
||||
draw_settings()
|
||||
if io.BUTTON_B in io.pressed:
|
||||
state = AppState.MENU
|
||||
|
||||
elif state == AppState.GAME_OVER:
|
||||
draw_game_over()
|
||||
if io.BUTTON_A in io.pressed:
|
||||
state = AppState.MENU
|
||||
game_data = {"score": 0, "level": 1}
|
||||
|
||||
def draw_menu():
|
||||
screen.brush = brushes.color(0, 0, 0)
|
||||
screen.clear()
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("MAIN MENU", 40, 50)
|
||||
screen.text("Press A to start", 30, 70)
|
||||
|
||||
def update_game():
|
||||
# Game logic
|
||||
game_data["score"] += 1
|
||||
|
||||
def draw_game():
|
||||
screen.brush = brushes.color(0, 0, 0)
|
||||
screen.clear()
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text(f"Score: {game_data['score']}", 10, 10)
|
||||
|
||||
def draw_settings():
|
||||
# Settings UI
|
||||
pass
|
||||
|
||||
def draw_game_over():
|
||||
screen.brush = brushes.color(0, 0, 0)
|
||||
screen.clear()
|
||||
screen.brush = brushes.color(255, 0, 0)
|
||||
screen.text("GAME OVER", 40, 50)
|
||||
screen.text(f"Score: {game_data['score']}", 40, 70)
|
||||
```
|
||||
|
||||
## WiFi Integration
|
||||
|
||||
Use standard MicroPython network module:
|
||||
|
||||
```python
|
||||
import network
|
||||
import time
|
||||
|
||||
def connect_wifi(ssid, password):
|
||||
"""Connect to WiFi network"""
|
||||
wlan = network.WLAN(network.STA_IF)
|
||||
wlan.active(True)
|
||||
|
||||
if wlan.isconnected():
|
||||
print("Already connected:", wlan.ifconfig()[0])
|
||||
return True
|
||||
|
||||
print(f"Connecting to {ssid}...")
|
||||
wlan.connect(ssid, password)
|
||||
|
||||
# Wait for connection (with timeout)
|
||||
timeout = 10
|
||||
while not wlan.isconnected() and timeout > 0:
|
||||
time.sleep(1)
|
||||
timeout -= 1
|
||||
|
||||
if wlan.isconnected():
|
||||
print("Connected:", wlan.ifconfig()[0])
|
||||
return True
|
||||
else:
|
||||
print("Connection failed")
|
||||
return False
|
||||
|
||||
def fetch_data(url):
|
||||
"""Fetch data from URL"""
|
||||
try:
|
||||
import urequests
|
||||
response = urequests.get(url)
|
||||
data = response.json()
|
||||
response.close()
|
||||
return data
|
||||
except Exception as e:
|
||||
print(f"Error fetching data: {e}")
|
||||
return None
|
||||
|
||||
# Usage in app
|
||||
def init():
|
||||
if connect_wifi("MyWiFi", "password123"):
|
||||
data = fetch_data("https://api.example.com/data")
|
||||
if data:
|
||||
print("Got data:", data)
|
||||
```
|
||||
|
||||
Full WiFi docs: https://docs.micropython.org/en/latest/rp2/quickref.html#wlan
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Reduce Draw Calls
|
||||
|
||||
```python
|
||||
# Bad - many individual draws
|
||||
def update():
|
||||
for i in range(100):
|
||||
screen.draw(shapes.rectangle(i, i, 2, 2))
|
||||
|
||||
# Better - batch or optimize
|
||||
def update():
|
||||
# Draw fewer, larger shapes
|
||||
screen.draw(shapes.rectangle(0, 0, 100, 100))
|
||||
```
|
||||
|
||||
### Cache Computed Values
|
||||
|
||||
```python
|
||||
# Cache expensive calculations
|
||||
_cached_sprites = None
|
||||
|
||||
def get_sprites():
|
||||
global _cached_sprites
|
||||
if _cached_sprites is None:
|
||||
_cached_sprites = SpriteSheet("sprites.png", 8, 8)
|
||||
return _cached_sprites
|
||||
|
||||
def update():
|
||||
sprites = get_sprites() # Fast after first call
|
||||
screen.blit(sprites.sprite(0, 0), 10, 10)
|
||||
```
|
||||
|
||||
### Minimize Memory Allocation
|
||||
|
||||
```python
|
||||
# Bad - creates new lists every frame
|
||||
def update():
|
||||
items = [1, 2, 3, 4, 5] # Don't do this in update()
|
||||
for item in items:
|
||||
process(item)
|
||||
|
||||
# Good - create once, reuse
|
||||
items = [1, 2, 3, 4, 5] # Module level
|
||||
|
||||
def update():
|
||||
for item in items: # Reuse existing list
|
||||
process(item)
|
||||
```
|
||||
|
||||
## Project Structure Best Practices
|
||||
|
||||
### Simple App (Single File)
|
||||
|
||||
```
|
||||
my_app/
|
||||
├── icon.png
|
||||
└── __init__.py
|
||||
```
|
||||
|
||||
### Complex App (Multiple Files)
|
||||
|
||||
```
|
||||
my_app/
|
||||
├── icon.png
|
||||
├── __init__.py # Entry point
|
||||
└── assets/
|
||||
├── sprites.png
|
||||
├── font.ppf
|
||||
└── config.json
|
||||
```
|
||||
|
||||
Access assets using relative paths (assets/ is auto-added to sys.path):
|
||||
|
||||
```python
|
||||
# In __init__.py
|
||||
from badgeware import Image
|
||||
|
||||
# Load from assets/
|
||||
sprite = Image.load("assets/sprites.png")
|
||||
|
||||
# Or if assets/ in path:
|
||||
sprite = Image.load("sprites.png")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
```python
|
||||
def update():
|
||||
"""Update with error handling"""
|
||||
try:
|
||||
# Your update code
|
||||
draw_ui()
|
||||
handle_input()
|
||||
except Exception as e:
|
||||
# Show error on screen
|
||||
screen.brush = brushes.color(255, 0, 0)
|
||||
screen.clear()
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("Error:", 10, 10)
|
||||
screen.text(str(e)[:30], 10, 30)
|
||||
|
||||
# Log to console for debugging
|
||||
import sys
|
||||
sys.print_exception(e)
|
||||
```
|
||||
|
||||
## Testing & Debugging
|
||||
|
||||
### Test Locally
|
||||
|
||||
```bash
|
||||
# Run app temporarily without installing
|
||||
mpremote run my_app/__init__.py
|
||||
```
|
||||
|
||||
### REPL Debugging
|
||||
|
||||
```bash
|
||||
# Connect to REPL
|
||||
mpremote
|
||||
|
||||
# Test imports
|
||||
>>> from badgeware import screen, brushes
|
||||
>>> screen.brush = brushes.color(255, 0, 0)
|
||||
>>> screen.clear()
|
||||
```
|
||||
|
||||
### Print Debugging
|
||||
|
||||
```python
|
||||
def update():
|
||||
# Print statements appear in REPL/serial console
|
||||
print(f"State: {state}, Counter: {counter}")
|
||||
|
||||
# Draw debug info on screen
|
||||
screen.text(f"Debug: {value}", 0, 110)
|
||||
```
|
||||
|
||||
## Official Examples
|
||||
|
||||
Study official examples: https://github.com/badger/home/tree/main/badge/apps
|
||||
|
||||
Key examples:
|
||||
- **Commits Game**: Sprite animations, collision detection
|
||||
- **Snake Game**: Grid-based movement, state management
|
||||
- **Menu System**: Navigation, app launching
|
||||
|
||||
## Official API Documentation
|
||||
|
||||
- **Image class**: https://github.com/badger/home/blob/main/badgerware/Image.md
|
||||
- **shapes module**: https://github.com/badger/home/blob/main/badgerware/shapes.md
|
||||
- **brushes module**: https://github.com/badger/home/blob/main/badgerware/brushes.md
|
||||
- **PixelFont class**: https://github.com/badger/home/blob/main/PixelFont.md
|
||||
- **Matrix class**: https://github.com/badger/home/blob/main/Matrix.md
|
||||
- **io module**: https://github.com/badger/home/blob/main/badgerware/io.md
|
||||
|
||||
## Complete Example App
|
||||
|
||||
```python
|
||||
# __init__.py - Complete counter app with persistence
|
||||
from badgeware import screen, brushes, shapes, io, PixelFont
|
||||
import json
|
||||
|
||||
# State
|
||||
counter = 0
|
||||
high_score = 0
|
||||
|
||||
def init():
|
||||
"""Load saved state"""
|
||||
global counter, high_score
|
||||
|
||||
screen.font = PixelFont.load("nope.ppf")
|
||||
|
||||
try:
|
||||
with open("/storage/counter_state.json", "r") as f:
|
||||
data = json.load(f)
|
||||
counter = data.get("counter", 0)
|
||||
high_score = data.get("high_score", 0)
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f"Counter initialized: {counter}, High: {high_score}")
|
||||
|
||||
def update():
|
||||
"""Update every frame"""
|
||||
global counter, high_score
|
||||
|
||||
# Clear screen
|
||||
screen.brush = brushes.color(20, 40, 60)
|
||||
screen.clear()
|
||||
|
||||
# Draw title
|
||||
screen.brush = brushes.color(255, 255, 255)
|
||||
screen.text("COUNTER APP", 30, 10)
|
||||
|
||||
# Draw counter (large)
|
||||
screen.text(f"{counter}", 60, 40, scale=3)
|
||||
|
||||
# Draw high score
|
||||
screen.text(f"High: {high_score}", 40, 80)
|
||||
|
||||
# Draw instructions
|
||||
screen.text("A: +1 B: Reset", 20, 105)
|
||||
|
||||
# Handle buttons
|
||||
if io.BUTTON_A in io.pressed:
|
||||
counter += 1
|
||||
if counter > high_score:
|
||||
high_score = counter
|
||||
|
||||
if io.BUTTON_B in io.pressed:
|
||||
counter = 0
|
||||
|
||||
def on_exit():
|
||||
"""Save state before exit"""
|
||||
try:
|
||||
with open("/storage/counter_state.json", "w") as f:
|
||||
json.dump({
|
||||
"counter": counter,
|
||||
"high_score": high_score
|
||||
}, f)
|
||||
print("State saved!")
|
||||
except Exception as e:
|
||||
print(f"Save failed: {e}")
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
- **See Official Hacks**: https://badger.github.io/hacks/
|
||||
- **Explore Badge Hardware**: Use `badger-hardware` skill
|
||||
- **WiFi & Bluetooth**: See MicroPython docs
|
||||
- **Deploy Your App**: Use `badger-deploy` skill
|
||||
|
||||
Happy coding! 🦡
|
||||
Reference in New Issue
Block a user