Skip to content

Commit 14d8946

Browse files
rodillo69claude
andcommitted
Add local development version with CLAUDE.md documentation
- Added comprehensive CLAUDE.md file for future Claude Code instances - Added .gitignore for MicroPython/ESP32 project - Local working version of PSIC-O-TRONIC game πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
0 parents  commit 14d8946

21 files changed

+16170
-0
lines changed

β€Ž.gitignoreβ€Ž

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# MicroPython / ESP32
2+
*.pyc
3+
__pycache__/
4+
.DS_Store
5+
6+
# Configuration files with sensitive data (stored on device)
7+
config.json
8+
stats.json
9+
10+
# Local development
11+
.vscode/
12+
.idea/
13+
*.swp
14+
*.swo
15+
*~
16+
17+
# Logs and temporary files
18+
*.log
19+
tmp/
20+
temp/
21+
22+
# Mac specific
23+
.DS_Store
24+
.AppleDouble
25+
.LSOverride
26+
27+
# Credentials and secrets (never commit these)
28+
credentials.json
29+
secrets.py
30+
*.key
31+
*.pem

β€ŽCLAUDE.mdβ€Ž

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
**PSIC-O-TRONIC** is a dark humor psychology game running on ESP32-S3 hardware with MicroPython. Players receive fictional clinical cases via AI (Google Gemini) and must choose the "most psychotic" response as an unethical psychologist. The game features an LCD display, physical buttons, LEDs, and audio feedback.
8+
9+
**Hardware Platform:**
10+
- ESP32-S3 microcontroller
11+
- 20x4 I2C LCD display
12+
- 3 input buttons (Up, Down, Select) with 3 corresponding LEDs
13+
- 1 notification LED
14+
- Piezo speaker on GPIO 9
15+
- I2C on GPIO 1 (SDA) and GPIO 2 (SCL)
16+
17+
**Key Technologies:**
18+
- MicroPython (not standard Python - limited stdlib, ujson instead of json, urequests instead of requests)
19+
- Google Gemini AI API for dynamic case generation
20+
- WiFi connectivity for API access
21+
- OTA updates from GitHub
22+
- Persistent storage in ESP32 flash
23+
24+
## Architecture
25+
26+
### Main Game Loop
27+
`main.py` contains the `PsicOTronic` class which implements a state machine architecture. All game states are defined in the `State` class (lines 56-86). The main loop runs at ~12.5 FPS (`FRAME_DELAY = 0.08`).
28+
29+
**State Flow:**
30+
1. `BOOT` β†’ `WIFI_CHECK` β†’ `WIFI_PORTAL` (if needed) β†’ `INTRO` β†’ `MENU`
31+
2. From `MENU`, players can enter: game modes, stats, settings, OTA updates, credits
32+
3. Game flow: `MODE_SELECT` β†’ `PLAYER_SELECT` β†’ `QUOTA_SELECT` β†’ `PASS_DEVICE` β†’ `FETCHING` β†’ `MESSAGE_ANIM` β†’ `READING` β†’ `CHOOSING` β†’ `FEEDBACK`
33+
4. Special states: `PAUSE` (triggered by UP+DOWN simultaneously), `ERROR`, `OTA_*` states
34+
35+
### LCD Buffer System
36+
To prevent flickering, the game uses a double-buffer approach:
37+
- `lcd_buffer`: Working buffer where content is written
38+
- `lcd_shadow`: Shadow buffer tracking LCD physical state
39+
- `_lcd_render()`: Only writes changed characters to physical LCD (lines 268-274)
40+
41+
**Important:** Always write to buffer first (`_lcd_put`, `_lcd_centered`, `_lcd_clear_buffer`), then call `_lcd_render()` to update display.
42+
43+
### Game Modes Architecture
44+
45+
Three game modes share common base (`GameSession` in `game_modes.py`):
46+
47+
1. **Classic Mode** (`MODE_CLASSIC`): Multiplayer (1-4 players), solve N cases with 3 lives per player
48+
2. **Survival Mode** (`MODE_SURVIVAL`): Single player, survive as long as possible, leaderboard tracking
49+
3. **Career Mode** ("Mi Consulta"): Complex simulation mode with its own state machine in `career_mode.py`
50+
51+
Career mode is significantly more complex with:
52+
- Patient management system (`career_patients.py`)
53+
- Daily scheduling and time simulation (`career_scheduler.py`)
54+
- Achievement/upgrade/mission systems (`career_systems.py`)
55+
- Persistent career state (`career_data.py`)
56+
- Economy, inventory, reputation, crafting, tournaments, etc.
57+
58+
### AI Integration
59+
60+
`gemini_api.py` handles all Gemini API communication:
61+
- `GeminiOracle.get_scenario()`: Generates clinical cases
62+
- Uses `PROMPT_BASE` to define game's dark humor style
63+
- History tracking prevents topic repetition (last 5 themes)
64+
- Cleanses response text to remove leading punctuation
65+
- Error scenarios returned when API fails
66+
67+
**API Configuration:**
68+
- Default API key in `config.py`: `DEFAULT_API_KEY`
69+
- Model: `gemini-2.0-flash` (configurable)
70+
- Prompts are cleaned to remove accents/Γ± before sending (MicroPython JSON limitation)
71+
72+
### Configuration & Persistence
73+
74+
`config.py` manages two JSON files in flash:
75+
- `/config.json`: WiFi credentials, API key/model, settings
76+
- `/stats.json`: Game statistics, records, career progress
77+
78+
**Memory Safety:** All file operations wrapped in try/except with error reporting to `error_handler.py`
79+
80+
### WiFi & Connectivity
81+
82+
WiFi managed by `wifi_portal.py`:
83+
1. If no saved credentials, creates AP named "PSIC-O-TRONIC" (no password)
84+
2. Serves captive portal web interface at 192.168.4.1
85+
3. User configures WiFi + optional API key via mobile
86+
4. Credentials saved to `/config.json`
87+
88+
**Portal Features:**
89+
- Responsive CSS design for mobile
90+
- WiFi scanning and selection
91+
- API key configuration
92+
- Career mode progress viewing
93+
- Cancel by holding UP+SELECT buttons
94+
95+
### OTA Updates
96+
97+
`ota_update.py` implements GitHub-based OTA:
98+
- Repository: `rodillo69/psic-o-tronic` (main branch)
99+
- Version tracked in `/version.json`
100+
- Downloads files listed in remote `version.json`
101+
- Accessible from main menu if updates detected
102+
- Can force update or skip
103+
104+
### Error Handling
105+
106+
`error_handler.py` provides centralized error management:
107+
- HTTP error mapping (400, 401, 429, etc.)
108+
- Memory monitoring and warnings
109+
- Error reporting with context
110+
- Persistent error log (optional)
111+
112+
## Development Commands
113+
114+
### Deploying to ESP32
115+
116+
**Prerequisites:**
117+
- Install `ampy` or `mpremote` for file transfer
118+
- Connect ESP32 via USB
119+
120+
**Using ampy:**
121+
```bash
122+
# Upload single file
123+
ampy --port /dev/ttyUSB0 put main.py
124+
125+
# Upload all Python files
126+
for f in *.py; do ampy --port /dev/ttyUSB0 put "$f"; done
127+
128+
# Run without saving
129+
ampy --port /dev/ttyUSB0 run main.py
130+
```
131+
132+
**Using mpremote:**
133+
```bash
134+
# Copy all files
135+
mpremote connect /dev/ttyUSB0 cp *.py :
136+
137+
# Run
138+
mpremote connect /dev/ttyUSB0 run main.py
139+
```
140+
141+
### Testing & Debugging
142+
143+
**REPL Access:**
144+
```bash
145+
# Screen (macOS/Linux)
146+
screen /dev/ttyUSB0 115200
147+
148+
# Minicom
149+
minicom -D /dev/ttyUSB0 -b 115200
150+
```
151+
152+
**Standalone Module Tests:**
153+
Many modules have `if __name__ == "__main__"` blocks for testing:
154+
```python
155+
# Test config system
156+
python config.py # Won't work - must run on ESP32
157+
158+
# On ESP32 REPL:
159+
import config
160+
config.load_stats()
161+
```
162+
163+
**Memory Monitoring:**
164+
```python
165+
import gc
166+
gc.collect()
167+
gc.mem_free() # Check available RAM
168+
```
169+
170+
### OTA Update Workflow
171+
172+
1. Update `version.json` with new version number and file list
173+
2. Push changes to `rodillo69/psic-o-tronic` main branch
174+
3. Device checks GitHub for `version.json`
175+
4. Downloads changed files from raw.githubusercontent.com
176+
5. Prompts user to reboot
177+
178+
## Code Style & Conventions
179+
180+
### MicroPython Constraints
181+
182+
**DO:**
183+
- Use `ujson` instead of `json`
184+
- Use `urequests` instead of `requests`
185+
- Use `os.stat()` to check file existence (no `os.path.exists()`)
186+
- Call `gc.collect()` before large operations
187+
- Use simple loops instead of comprehensions when memory-critical
188+
189+
**DON'T:**
190+
- Use f-strings extensively (prefer format() or %)
191+
- Import large modules at global scope
192+
- Create large lists/dicts in memory
193+
- Use standard library modules unavailable in MicroPython
194+
195+
### Text Handling
196+
197+
Custom character mapping in `lcd_chars.py`:
198+
- Custom LCD chars 0-7 defined (heart, empty heart, etc.)
199+
- `convert_text()`: Maps Spanish characters to LCD-safe equivalents
200+
- Special characters: `chr(0)` = filled heart, `chr(1)` = empty heart
201+
202+
### Input Debouncing
203+
204+
Button reads use time-based debouncing (`DEBOUNCE_MS = 280`):
205+
```python
206+
def _get_input(self):
207+
now = time.ticks_ms()
208+
if time.ticks_diff(now, self._last_btn_time) < DEBOUNCE_MS:
209+
return None
210+
# ... check buttons
211+
```
212+
213+
### Audio System
214+
215+
`audio.py` provides sound effects via PWM:
216+
- `init_audio(pin)`: Initialize speaker
217+
- `play(sound_name)`: Play predefined sound ('boot', 'click', 'beep', 'mensaje', 'correcto', 'incorrecto', 'victoria', 'game_over')
218+
- Non-blocking playback
219+
220+
## Important Patterns
221+
222+
### Adding New Game States
223+
224+
1. Add state constant to `State` class in `main.py`
225+
2. Create handler method: `def _update_<state_name>(self, key):`
226+
3. Register in `state_handlers` dict in `run()` method
227+
4. Implement LCD rendering with buffer system
228+
5. Handle input (UP/DOWN/SELECT) in handler
229+
6. Set `self.state` to transition
230+
231+
### Career Mode Extensions
232+
233+
Career mode is modular - systems are defined in `career_systems.py`:
234+
- Achievements: `LOGROS` dict
235+
- Upgrades: `MEJORAS` dict
236+
- Missions: `MISIONES_DIARIAS`, `MISIONES_SEMANALES`
237+
- Events: `EVENTOS` dict
238+
- Recipes: `RECETAS` dict
239+
240+
Add new content by extending these dicts and implementing corresponding logic.
241+
242+
### AI Prompt Modification
243+
244+
Modify `PROMPT_BASE` in `gemini_api.py` to change case generation behavior:
245+
- Style defined in "ESTILO DE HUMOR" section
246+
- Categories in "CATEGORIAS TEMATICAS"
247+
- Difficulty rules in "REGLAS DE DIFICULTAD"
248+
249+
**Critical:** Response must be valid JSON matching `JSON_TEMPLATE`. The AI generates: theme, sender name, message, 4 options, correct index (0-3), win/lose feedback.
250+
251+
## Hardware Pin Configuration
252+
253+
Defined in `main.py` lines 37-46:
254+
```python
255+
PIN_BTN_UP = 4
256+
PIN_BTN_SELECT = 5
257+
PIN_BTN_DOWN = 6
258+
PIN_LED_UP = 7
259+
PIN_LED_SELECT = 15
260+
PIN_LED_DOWN = 16
261+
PIN_LED_NOTIFY = 17
262+
PIN_SPEAKER = 9
263+
PIN_I2C_SDA = 1
264+
PIN_I2C_SCL = 2
265+
```
266+
267+
### LCD Communication
268+
269+
I2C LCD library: `i2c_lcd.py` (standard MicroPython LCD driver)
270+
- Default address: Auto-detected via `i2c.scan()`
271+
- 400kHz I2C frequency
272+
- 4x20 character display
273+
274+
## Common Issues
275+
276+
**Memory Errors:**
277+
- Call `gc.collect()` before API requests
278+
- Limit string operations in loops
279+
- Close `urequests` responses: `res.close()`
280+
281+
**WiFi Issues:**
282+
- Portal may timeout if no interaction
283+
- Check saved credentials with `config.get_wifi_config()`
284+
- Clear config: `config.clear_wifi_config()`
285+
286+
**API Errors:**
287+
- Default API key may hit rate limits
288+
- Users should configure their own Gemini key via portal
289+
- Check `gemini_api.py` error handling for HTTP status codes
290+
291+
**OTA Failures:**
292+
- Requires stable WiFi during download
293+
- Large files may cause memory issues
294+
- Version format must be semver: "X.Y.Z"
295+
296+
## Repository Structure
297+
298+
```
299+
/
300+
β”œβ”€β”€ main.py # Main game engine & state machine
301+
β”œβ”€β”€ config.py # Configuration & stats persistence
302+
β”œβ”€β”€ game_modes.py # Classic & Survival mode logic
303+
β”œβ”€β”€ career_mode.py # Career mode main loop (3600+ lines)
304+
β”œβ”€β”€ career_data.py # Career persistent data management
305+
β”œβ”€β”€ career_patients.py # Patient generation & AI prompts
306+
β”œβ”€β”€ career_scheduler.py # Time simulation & scheduling
307+
β”œβ”€β”€ career_systems.py # Achievements/upgrades/missions/etc
308+
β”œβ”€β”€ gemini_api.py # Gemini AI integration
309+
β”œβ”€β”€ wifi_portal.py # Captive portal for setup
310+
β”œβ”€β”€ ota_update.py # Over-the-air update system
311+
β”œβ”€β”€ error_handler.py # Centralized error management
312+
β”œβ”€β”€ audio.py # Sound effects via PWM
313+
β”œβ”€β”€ ui_renderer.py # UI helper functions
314+
β”œβ”€β”€ lcd_chars.py # LCD character mapping
315+
β”œβ”€β”€ lcd_api.py # LCD low-level API
316+
β”œβ”€β”€ i2c_lcd.py # I2C LCD driver
317+
β”œβ”€β”€ ntp_time.py # NTP time synchronization
318+
└── version.json # Version info for OTA
319+
```
320+
321+
## Testing Notes
322+
323+
**Standalone Testing:**
324+
Most modules cannot run on desktop Python due to MicroPython-specific imports. Test on actual ESP32 hardware or use MicroPython Unix port.
325+
326+
**WiFi Testing:**
327+
Portal can be tested by monitoring serial output while connecting phone to "PSIC-O-TRONIC" AP.
328+
329+
**API Testing:**
330+
Run `gemini_api.py` standalone on ESP32 REPL after WiFi configured.

0 commit comments

Comments
Β (0)