From e787a4264f2f2071708499c02357c9e1815b511e Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Feb 2026 23:39:12 +0000 Subject: [PATCH 1/4] Add 4 pixel game worlds and CLAUDE.md codebase guide - BlackRoad Harvest: Stardew Valley-inspired farming sim with crops, seasons, shop, day/night cycle - BlackRoad Creatures: Pokemon-inspired RPG with 8 creatures, battles, type matchups, capture, leveling - BlackRoad Pets: Webkinz-inspired virtual pet world with 6 pets, 5 rooms, minigame, shop, decor - BlackRoad Runner: Mario-inspired platformer with 3 worlds, physics, enemies, power-ups, coins - Game hub launcher page linking all worlds - Comprehensive CLAUDE.md documenting repo structure, conventions, and workflows https://claude.ai/code/session_016tjFc6t2emynRTw5Mbi9Y9 --- CLAUDE.md | 244 +++++++ blackroad-pixel/games/index.html | 195 ++++++ blackroad-pixel/games/mario/index.html | 803 +++++++++++++++++++++++ blackroad-pixel/games/pokemon/index.html | 658 +++++++++++++++++++ blackroad-pixel/games/stardew/index.html | 627 ++++++++++++++++++ blackroad-pixel/games/webkinz/index.html | 694 ++++++++++++++++++++ 6 files changed, 3221 insertions(+) create mode 100644 CLAUDE.md create mode 100644 blackroad-pixel/games/index.html create mode 100644 blackroad-pixel/games/mario/index.html create mode 100644 blackroad-pixel/games/pokemon/index.html create mode 100644 blackroad-pixel/games/stardew/index.html create mode 100644 blackroad-pixel/games/webkinz/index.html diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..e82b3ab --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,244 @@ +# CLAUDE.md - BlackRoad OS Bridge Repository + +## What This Repository Is + +This is **BlackRoad-OS/.github** ("The Bridge") - the central nervous system for a 15-organization AI routing ecosystem. It coordinates infrastructure, documentation, prototypes, blueprints, and interactive pixel worlds for the BlackRoad platform. + +**Core Thesis:** BlackRoad is a routing company, not an AI company. It routes to intelligence (Claude, GPT, Llama, local models) rather than building its own. + +--- + +## Repository Structure + +``` +.github/ +├── CLAUDE.md # This file - AI assistant guide +├── README.md # Org landing page +├── INDEX.md # Browsable table of contents +├── MEMORY.md # Persistent session memory across conversations +├── SIGNALS.md # Morse-code style coordination protocol +├── STREAMS.md # Data flow patterns (upstream/instream/downstream) +├── REPO_MAP.md # Ecosystem architecture diagram +├── BLACKROAD_ARCHITECTURE.md # Core thesis, business model, infrastructure +├── .STATUS # Real-time ecosystem state beacon +│ +├── CECE_ABILITIES.md # AI partner (Cece/Claude) abilities manifest +├── CECE_PROTOCOLS.md # Decision frameworks, escalation, PCDEL loop +│ +├── blackroad-pixel/ # Interactive pixel art UI & games +│ ├── index.html # Desktop OS simulation (main UI) +│ ├── style.css # Retro pixel styling +│ ├── app.js # Desktop interactivity +│ └── games/ # Pixel game worlds +│ ├── index.html # Game hub / launcher +│ ├── stardew/index.html # BlackRoad Harvest (farming sim) +│ ├── pokemon/index.html # BlackRoad Creatures (RPG) +│ ├── webkinz/index.html # BlackRoad Pets (virtual pet world) +│ └── mario/index.html # BlackRoad Runner (platformer) +│ +├── orgs/ # 15 organization blueprints +│ ├── BlackRoad-OS/ # Core infrastructure +│ ├── BlackRoad-AI/ # Intelligence routing +│ ├── BlackRoad-Cloud/ # Edge compute +│ ├── BlackRoad-Hardware/ # Pi cluster + IoT +│ ├── BlackRoad-Foundation/ # CRM & business +│ ├── BlackRoad-Labs/ # R&D experiments +│ ├── BlackRoad-Security/ # Zero-trust & vault +│ ├── BlackRoad-Media/ # Content & publishing +│ ├── BlackRoad-Interactive/ # Games & metaverse +│ ├── BlackRoad-Education/ # Learning platform +│ ├── BlackRoad-Gov/ # Civic tech +│ ├── BlackRoad-Archive/ # Preservation +│ ├── BlackRoad-Studio/ # Design tools +│ ├── BlackRoad-Ventures/ # Business & investments +│ └── Blackbox-Enterprises/ # Enterprise stealth +│ +├── prototypes/ # Working Python code +│ ├── cece-engine/ # Autonomous task processing (PCDEL loop) +│ ├── operator/ # AI request routing engine +│ ├── metrics/ # KPI dashboard +│ ├── control-plane/ # Unified control interface +│ ├── explorer/ # Ecosystem browser +│ ├── dispatcher/ # Event dispatching +│ ├── mcp-server/ # MCP protocol server +│ └── webhooks/ # Webhook handlers (GitHub, Stripe, etc.) +│ +├── templates/ # Integration templates +│ ├── ai-router/ # Multi-provider AI routing +│ ├── salesforce-sync/ # CRM synchronization +│ ├── stripe-billing/ # Payment processing +│ ├── cloudflare-workers/ # Edge compute +│ ├── gdrive-sync/ # Google Drive +│ ├── github-ecosystem/ # GitHub Actions +│ └── design-tools/ # Figma + Canva +│ +├── nodes/ # Hardware node configurations +│ ├── cecilia.yaml # Mac dev machine +│ ├── lucidia.yaml # Pi 5 + Hailo-8 +│ ├── octavia.yaml # Pi 5 + Hailo-8 +│ ├── aria.yaml # Pi 5 agent orchestration +│ ├── alice.yaml # Pi 400 Kubernetes +│ └── shellfish.yaml # Digital Ocean droplet +│ +├── routes/ # Request routing registry +│ └── registry.yaml # Complete routing rules +│ +├── .github/workflows/ # 13 GitHub Actions workflows +│ ├── ci.yml # Continuous integration +│ ├── cece-auto.yml # Autonomous Cece engine +│ ├── intelligent-auto-pr.yml # Auto PR generation +│ ├── self-healing-master.yml # Error recovery +│ └── ... # Health, triage, deploy, etc. +│ +├── CODE_OF_CONDUCT.md +├── CONTRIBUTING.md +├── SECURITY.md +├── LICENSE # Proprietary BlackRoad OS, Inc. +└── TODO.md +``` + +--- + +## Key Conventions + +### Language & Stack +- **Documentation:** Markdown (majority of repo) +- **Prototypes:** Python (FastAPI-style, CLI tools) +- **UI/Games:** Vanilla HTML5, CSS3, JavaScript (no frameworks) +- **Config:** YAML (node configs, routing registry) +- **CI/CD:** GitHub Actions +- **Fonts:** Press Start 2P (pixel), JetBrains Mono (code) + +### Naming Conventions +- Organization dirs: PascalCase with hyphens (`BlackRoad-AI/`) +- Python files: snake_case (`control_plane/bridge.py`) +- YAML configs: lowercase (`registry.yaml`) +- Markdown docs: UPPER_SNAKE for root docs (`MEMORY.md`), README.md for subdirs +- CSS: BEM-like with kebab-case (`.chat-panel`, `.thumb-item`) + +### Coding Style +- **Python:** Standard lib preferred, CLI via argparse, async where needed +- **JavaScript:** Vanilla JS, no build tools, `const`/`let` only, canvas for games +- **CSS:** CSS custom properties for theming, mobile-responsive, dark theme default +- **Games:** Single HTML files with embedded CSS/JS for portability + +### Design System +``` +Colors: + --bg-dark: #0d0d1a + --bg-panel: #1a1a2e + --accent-pink: #ff6b9d (primary brand) + --accent-purple: #c44dff (secondary) + --accent-cyan: #00d4ff (highlights) + --accent-orange: #f5a623 (warnings/coins) + --accent-green: #7ed321 (success/health) + --accent-red: #d0021b (errors) + +Gradients: + Warm: #ff6b35 → #f7c948 → #ff6b9d → #c44dff + Cool: #4a90d9 → #00d4ff → #7ed321 → #4a90d9 +``` + +--- + +## Development Workflows + +### Adding New Pixel Game Worlds +1. Create directory under `blackroad-pixel/games//` +2. Build as single `index.html` with embedded CSS/JS (canvas-based) +3. Include title screen, HUD, game loop via `requestAnimationFrame` +4. Use BlackRoad color palette and Press Start 2P font +5. Add card entry to `blackroad-pixel/games/index.html` hub + +### Adding New Organization Blueprints +1. Create directory under `orgs//` +2. Include `README.md` (purpose, repos, team), `REPOS.md` (repo list), `SIGNALS.md` (signals) +3. Follow the existing 3-file structure from other orgs + +### Adding New Prototypes +1. Create directory under `prototypes//` +2. Include `README.md`, `__init__.py`, `cli.py` (entry point) +3. Use `requirements.txt` for dependencies +4. Follow the PCDEL pattern if it involves autonomous processing + +### Working with Memory +- `MEMORY.md` persists context across sessions - always read it first +- `.STATUS` is the real-time state beacon - update it after significant changes +- `SIGNALS.md` defines the communication protocol between orgs + +--- + +## AI Assistant Guidelines + +### Session Startup +1. Read `MEMORY.md` for context from previous sessions +2. Read `.STATUS` for current ecosystem state +3. Check `TODO.md` for pending tasks +4. Identify which organization/area the current task belongs to + +### Decision Authority +- **FULL_AUTO:** Code formatting, documentation updates, test runs, CI fixes, routine commits +- **SUGGEST:** Architecture changes, new integrations, prototype design, cross-org coordination +- **ASK_FIRST:** Spending money, external API calls, production deployments, security changes, deleting files + +### Key Relationships +- **Alexa Louise** = founder/CEO +- **Cece/Cecilia** = AI partner (Claude) living in the Bridge +- **12 named agents** in the chat panel each map to different system capabilities +- The **Pi cluster** (lucidia, octavia, aria, alice) are physical hardware nodes + +### What NOT to Do +- Do not modify `LICENSE` without explicit permission +- Do not push to main without review for production-impacting changes +- Do not introduce external JS/CSS frameworks into the pixel UI (vanilla only) +- Do not create files outside the established directory structure +- Do not modify node YAML configs without understanding the hardware topology + +--- + +## Pixel Game Worlds Reference + +| Game | Inspired By | Features | +|------|-------------|----------| +| **BlackRoad Harvest** | Stardew Valley | Farming, seasons, crops, shop, day/night, energy system | +| **BlackRoad Creatures** | Pokemon | 8 creatures, type matchups, battles, capture, leveling, overworld exploration | +| **BlackRoad Pets** | Webkinz | 6 pet types, 5 rooms, feeding/playing/washing, shop, coin minigame, decor | +| **BlackRoad Runner** | Mario | 3 worlds, platforming physics, enemies, power-ups, coins, question blocks | + +All games are self-contained HTML files using Canvas API and can be served from any static host. + +--- + +## Infrastructure Summary + +| Layer | Tech | Cost | +|-------|------|------| +| CDN/Edge | Cloudflare Workers | Free tier | +| CRM | Salesforce | Dev tier | +| Code | GitHub Enterprise | Included | +| Mesh | Tailscale VPN | Free tier | +| Gateway | Digital Ocean droplet | ~$6/mo | +| Hardware | 4x Raspberry Pi + Mac | Owned | +| **Total** | | **~$40/month** | + +--- + +## Quick Commands + +```bash +# Serve pixel UI locally +cd blackroad-pixel && python3 -m http.server 8080 + +# Run a prototype +cd prototypes/operator && python3 -m cli + +# Check ecosystem health +cat .STATUS + +# View routing registry +cat routes/registry.yaml +``` + +--- + +*Last updated: 2026-02-03 | Session: claude/claude-md* diff --git a/blackroad-pixel/games/index.html b/blackroad-pixel/games/index.html new file mode 100644 index 0000000..d9cff9b --- /dev/null +++ b/blackroad-pixel/games/index.html @@ -0,0 +1,195 @@ + + + + + +BlackRoad Pixel Worlds - Game Hub + + + + + + diff --git a/blackroad-pixel/games/mario/index.html b/blackroad-pixel/games/mario/index.html new file mode 100644 index 0000000..ad0d8ee --- /dev/null +++ b/blackroad-pixel/games/mario/index.html @@ -0,0 +1,803 @@ + + + + + +BlackRoad Runner - Pixel Platformer + + + +
+ +
+ COINS: 0 + SCORE: 0 + WORLD 1-1 + LIVES: 3 + TIME: 400 +
+
+
+

GAME OVER

+

Score: 0

+ +
+
+

BLACKROAD RUNNER

+

A Pixel Platformer

+

A/D or LEFT/RIGHT - Move

+

W/UP/SPACE - Jump

+

Stomp enemies, collect coins!

+

Hit ? blocks for power-ups!

+ +
+
+ + + + diff --git a/blackroad-pixel/games/pokemon/index.html b/blackroad-pixel/games/pokemon/index.html new file mode 100644 index 0000000..87175d1 --- /dev/null +++ b/blackroad-pixel/games/pokemon/index.html @@ -0,0 +1,658 @@ + + + + + +BlackRoad Creatures - Pixel RPG + + + +
+ +
+ NEON TOWN + Steps: 0 +
+
+ +
+ +
+
???Lv 5
+
+
??/??
+
+
+
???Lv 5
+
+
??/??
+
EXP: 0
+
+
+
A wild creature appeared!
+
+ + + + +
+
+
+
+
+ +
+

BLACKROAD CREATURES

+

Gotta Route 'Em All

+

WASD / Arrows - Move

+

Walk in tall grass for encounters

+

Collect and battle creatures!

+ +
+
+ + + + diff --git a/blackroad-pixel/games/stardew/index.html b/blackroad-pixel/games/stardew/index.html new file mode 100644 index 0000000..c2aaec2 --- /dev/null +++ b/blackroad-pixel/games/stardew/index.html @@ -0,0 +1,627 @@ + + + + + +BlackRoad Harvest - Pixel Farm + + + +
+ +
+
+
SEASON:SPRING
+
DAY:1
+
TIME:6:00 AM
+
ENERGY:
+
$500
+
+
+
+
+
+
+

BLACKROAD SEED SHOP

+
+ +
+
+

BLACKROAD HARVEST

+

A Pixel Farming World

+

WASD / Arrows - Move

+

SPACE - Use Tool / Interact

+

1-8 - Select Item

+

E - Open Shop (near store)

+ +
+
+ + + + diff --git a/blackroad-pixel/games/webkinz/index.html b/blackroad-pixel/games/webkinz/index.html new file mode 100644 index 0000000..ec0e981 --- /dev/null +++ b/blackroad-pixel/games/webkinz/index.html @@ -0,0 +1,694 @@ + + + + + +BlackRoad Pets - Virtual World + + + +
+ +
+
HAPPY
+
HUNGER
+
ENERGY
+
CLEAN
+ 1000 KinzCoins +
+
+ + + + + +
+
+ + + + + + + +
+
+ +
+

BLACKROAD PET SHOP

+
+ +
+ +
+

CATCH THE TREATS!

+

Use LEFT/RIGHT arrows to catch falling treats

+ +
Score: 0
+ +
+ +
+

CHOOSE YOUR PET

+

Pick your first BlackRoad companion!

+
+
\uD83D\uDC31Neon Cat
+
\uD83D\uDC36Cyber Pup
+
\uD83D\uDC30Pixel Bunny
+
\uD83E\uDD89Code Owl
+
\uD83D\uDC32Data Dragon
+
\uD83E\uDD8AGlitch Fox
+
+
+
+ + + + From 75b232effbd5ec0128ebe4402bb4f49608f8d43e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Feb 2026 00:05:19 +0000 Subject: [PATCH 2/4] Add Games nav link to desktop UI https://claude.ai/code/session_016tjFc6t2emynRTw5Mbi9Y9 --- blackroad-pixel/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/blackroad-pixel/index.html b/blackroad-pixel/index.html index 80a59f2..c6362d8 100644 --- a/blackroad-pixel/index.html +++ b/blackroad-pixel/index.html @@ -25,6 +25,7 @@ Terminal Tools Agents + Games
From 6037636e76a15b95c55a26ef796719f226a3e27f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 4 Feb 2026 00:17:45 +0000 Subject: [PATCH 3/4] Enhance BlackRoad Creatures and Harvest with deep gameplay systems Creatures (Pokemon): Expanded to 50x40 overworld with 5 zones, healing center, Pokedex, 3 trainer battles, item shop, PP system, critical hits, smart AI, zone-specific encounter tables, and level-up animations. Harvest (Stardew): Added weather system (rain/storm/snow), 4 NPCs with relationship/gifting, fishing minigame with 4 fish types, animals (chickens/cows), crop quality system, particle effects, fireflies, and expanded 30x22 world. https://claude.ai/code/session_016tjFc6t2emynRTw5Mbi9Y9 --- blackroad-pixel/games/pokemon/index.html | 1272 ++++++++++++++--- blackroad-pixel/games/stardew/index.html | 1642 +++++++++++++++++----- 2 files changed, 2303 insertions(+), 611 deletions(-) diff --git a/blackroad-pixel/games/pokemon/index.html b/blackroad-pixel/games/pokemon/index.html index 87175d1..bdad23c 100644 --- a/blackroad-pixel/games/pokemon/index.html +++ b/blackroad-pixel/games/pokemon/index.html @@ -10,19 +10,23 @@ body{background:#0d0d1a;display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:'Press Start 2P',monospace;overflow:hidden} #game-wrapper{position:relative;width:800px;height:600px;border:3px solid #2a2a4a;border-radius:8px;overflow:hidden;box-shadow:0 0 40px rgba(144,19,254,0.2)} canvas{display:block;image-rendering:pixelated;image-rendering:crisp-edges} + +/* Battle UI */ #battle-ui{position:absolute;top:0;left:0;width:100%;height:100%;display:none;z-index:20} #battle-bg{width:100%;height:100%;position:absolute} -#enemy-info{position:absolute;top:20px;left:30px;background:rgba(0,0,0,0.8);border:2px solid #4a90d9;border-radius:8px;padding:10px 16px;min-width:220px} -#player-info{position:absolute;bottom:160px;right:30px;background:rgba(0,0,0,0.8);border:2px solid #7ed321;border-radius:8px;padding:10px 16px;min-width:220px} +#enemy-info{position:absolute;top:20px;left:30px;background:rgba(0,0,0,0.85);border:2px solid #4a90d9;border-radius:8px;padding:10px 16px;min-width:240px} +#player-info{position:absolute;bottom:160px;right:30px;background:rgba(0,0,0,0.85);border:2px solid #7ed321;border-radius:8px;padding:10px 16px;min-width:240px} .creature-name{font-size:10px;color:#fff;margin-bottom:6px} .creature-lv{font-size:8px;color:#f5a623;float:right} -.hp-bar-outer{width:160px;height:10px;background:#333;border-radius:5px;border:1px solid #555;overflow:hidden} -.hp-bar-inner{height:100%;background:linear-gradient(90deg,#7ed321,#4caf50);transition:width 0.5s} +.hp-bar-outer{width:160px;height:10px;background:#333;border-radius:5px;border:1px solid #555;overflow:hidden;margin-top:2px} +.hp-bar-inner{height:100%;background:linear-gradient(90deg,#7ed321,#4caf50);transition:width 0.6s ease} .hp-bar-inner.low{background:linear-gradient(90deg,#f44336,#e53935)} .hp-bar-inner.med{background:linear-gradient(90deg,#f5a623,#ff8f00)} .hp-text{font-size:7px;color:#aaa;margin-top:2px;text-align:right} -#battle-menu{position:absolute;bottom:0;left:0;width:100%;height:150px;background:rgba(0,0,0,0.9);border-top:3px solid #ff6b9d;display:flex} -#battle-text{flex:1;padding:16px 20px;font-size:9px;color:#e0e0e0;line-height:1.8;overflow-y:auto} +.exp-bar-outer{width:160px;height:6px;background:#222;border-radius:3px;border:1px solid #444;overflow:hidden;margin-top:3px} +.exp-bar-inner{height:100%;background:linear-gradient(90deg,#4a90d9,#00d4ff);transition:width 0.6s ease} +#battle-menu{position:absolute;bottom:0;left:0;width:100%;height:150px;background:rgba(0,0,0,0.92);border-top:3px solid #ff6b9d;display:flex} +#battle-text{flex:1;padding:14px 18px;font-size:9px;color:#e0e0e0;line-height:1.8;overflow-y:auto;white-space:pre-wrap} #battle-actions{width:240px;display:grid;grid-template-columns:1fr 1fr;gap:6px;padding:12px;border-left:2px solid #2a2a4a} .battle-btn{font-family:'Press Start 2P',monospace;font-size:8px;color:#fff;border:2px solid #2a2a4a;border-radius:6px;cursor:pointer;padding:8px;transition:all 0.15s;text-align:center} .battle-btn:hover{transform:scale(1.03);border-color:#ff6b9d} @@ -34,6 +38,7 @@ .move-btn{font-family:'Press Start 2P',monospace;font-size:8px;color:#fff;border:2px solid #444;border-radius:6px;cursor:pointer;padding:10px;transition:all 0.15s} .move-btn:hover{border-color:#c44dff;transform:scale(1.02)} .move-btn .move-type{font-size:6px;opacity:0.7;margin-top:4px} +.move-btn.no-pp{opacity:0.4;pointer-events:none} .type-fire{background:linear-gradient(135deg,#d32f2f,#b71c1c)} .type-water{background:linear-gradient(135deg,#1565c0,#0d47a1)} .type-grass{background:linear-gradient(135deg,#2e7d32,#1b5e20)} @@ -42,19 +47,78 @@ .type-normal{background:linear-gradient(135deg,#616161,#424242)} .type-cyber{background:linear-gradient(135deg,#00838f,#006064)} .type-psychic{background:linear-gradient(135deg,#ad1457,#880e4f)} -#overworld-hud{position:absolute;top:8px;right:8px;background:rgba(0,0,0,0.7);border:1px solid #2a2a4a;border-radius:6px;padding:8px 12px;z-index:10} +#bag-panel{position:absolute;bottom:0;left:0;width:100%;height:150px;background:rgba(0,0,0,0.95);border-top:3px solid #f5a623;display:none;grid-template-columns:1fr 1fr;gap:8px;padding:16px} +.bag-btn{font-family:'Press Start 2P',monospace;font-size:8px;color:#fff;border:2px solid #444;border-radius:6px;cursor:pointer;padding:10px;transition:all 0.15s;background:linear-gradient(135deg,#616161,#424242)} +.bag-btn:hover{border-color:#f5a623;transform:scale(1.02)} +.bag-btn.empty{opacity:0.4;pointer-events:none} +#party-battle-panel{position:absolute;bottom:0;left:0;width:100%;height:150px;background:rgba(0,0,0,0.95);border-top:3px solid #7ed321;display:none;padding:10px;overflow-y:auto} +.party-btn{font-family:'Press Start 2P',monospace;font-size:8px;color:#fff;border:2px solid #444;border-radius:4px;cursor:pointer;padding:6px 10px;margin:3px;transition:all 0.15s;background:linear-gradient(135deg,#2e7d32,#1b5e20);display:inline-block} +.party-btn:hover{border-color:#7ed321} +.party-btn.fainted{opacity:0.4;background:linear-gradient(135deg,#616161,#424242)} + +/* Overworld HUD */ +#overworld-hud{position:absolute;top:8px;right:8px;background:rgba(0,0,0,0.75);border:1px solid #2a2a4a;border-radius:6px;padding:8px 12px;z-index:10} #overworld-hud span{font-size:7px;color:#aaa;display:block;margin-bottom:4px} #party-display{position:absolute;top:8px;left:8px;z-index:10;display:flex;gap:4px} -.party-icon{width:32px;height:32px;background:rgba(0,0,0,0.7);border:1px solid #2a2a4a;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px} +.party-icon{width:32px;height:32px;background:rgba(0,0,0,0.7);border:1px solid #2a2a4a;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px;position:relative} .party-icon.fainted{opacity:0.3} +.party-icon .party-hp-bar{position:absolute;bottom:1px;left:2px;width:28px;height:3px;background:#333;border-radius:2px;overflow:hidden} +.party-icon .party-hp-fill{height:100%;background:#7ed321;transition:width 0.3s} + +/* Title Screen */ #title-screen{position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(180deg,#0d0d1a 0%,#1a0a3d 40%,#2a1050 100%);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:50} #title-screen h1{font-size:16px;color:#c44dff;text-shadow:0 0 20px rgba(196,77,255,0.5);margin-bottom:8px} #title-screen h2{font-size:8px;color:#00d4ff;margin-bottom:30px} #title-screen p{font-size:7px;color:#8888aa;margin-bottom:4px} #start-btn{font-family:'Press Start 2P',monospace;font-size:10px;color:#fff;background:linear-gradient(135deg,#c44dff,#9013fe);border:none;padding:12px 32px;border-radius:6px;cursor:pointer;margin-top:20px;transition:transform 0.15s} #start-btn:hover{transform:scale(1.05)} + +/* Catch animation */ #catch-anim{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:40px;display:none;z-index:25;animation:shake 0.5s ease-in-out} @keyframes shake{0%,100%{transform:translate(-50%,-50%) rotate(0)}25%{transform:translate(-50%,-50%) rotate(-15deg)}75%{transform:translate(-50%,-50%) rotate(15deg)}} + +/* Level up animation */ +#levelup-anim{position:absolute;top:40%;left:50%;transform:translate(-50%,-50%);font-size:12px;color:#f5a623;text-shadow:0 0 15px rgba(245,166,35,0.8);display:none;z-index:25;animation:lvup 1.5s ease-out forwards} +@keyframes lvup{0%{opacity:0;transform:translate(-50%,-30%) scale(0.5)}20%{opacity:1;transform:translate(-50%,-50%) scale(1.2)}40%{transform:translate(-50%,-50%) scale(1)}100%{opacity:0;transform:translate(-50%,-80%) scale(1)}} + +/* Dialog overlay (healing center, shop, pokedex, bag) */ +#dialog-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.85);display:none;z-index:30;flex-direction:column;align-items:center;justify-content:center} +#dialog-box{background:#1a1a2e;border:3px solid #c44dff;border-radius:10px;padding:20px 28px;max-width:700px;max-height:520px;overflow-y:auto;width:90%} +#dialog-box h2{font-size:12px;color:#ff6b9d;margin-bottom:12px;text-align:center} +#dialog-box p{font-size:8px;color:#ccc;line-height:2;margin-bottom:8px} +#dialog-box .close-hint{font-size:7px;color:#666;text-align:center;margin-top:12px} +.shop-item{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;margin:4px 0;background:rgba(255,255,255,0.05);border:1px solid #2a2a4a;border-radius:6px;cursor:pointer;transition:all 0.15s} +.shop-item:hover{border-color:#ff6b9d;background:rgba(255,107,157,0.1)} +.shop-item span{font-size:8px;color:#ddd} +.shop-item .price{color:#f5a623} +.shop-item .qty{color:#00d4ff;font-size:7px} + +/* Pokedex grid */ +.dex-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin:12px 0} +.dex-entry{background:rgba(255,255,255,0.05);border:2px solid #2a2a4a;border-radius:8px;padding:10px;text-align:center;cursor:pointer;transition:all 0.15s} +.dex-entry:hover{border-color:#c44dff} +.dex-entry.caught{border-color:#7ed321} +.dex-entry.seen{border-color:#f5a623} +.dex-entry .dex-icon{font-size:24px;display:block;margin-bottom:4px} +.dex-entry .dex-name{font-size:6px;color:#aaa;display:block} +.dex-entry.unseen .dex-icon{filter:brightness(0);opacity:0.3} +.dex-entry.unseen .dex-name{color:#444} +.dex-detail{margin-top:12px;padding:12px;background:rgba(255,255,255,0.03);border:1px solid #2a2a4a;border-radius:6px} +.dex-detail h3{font-size:10px;color:#00d4ff;margin-bottom:8px} +.dex-detail p{font-size:7px;color:#bbb;margin-bottom:4px} +.dex-stat{display:flex;justify-content:space-between;padding:2px 0} +.dex-stat span:first-child{font-size:7px;color:#888} +.dex-stat span:last-child{font-size:7px;color:#fff} + +/* Bag overlay */ +.bag-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:10px 0} +.bag-item{display:flex;justify-content:space-between;align-items:center;padding:10px;background:rgba(255,255,255,0.05);border:1px solid #2a2a4a;border-radius:6px} +.bag-item span{font-size:8px;color:#ddd} +.bag-item .bag-qty{color:#00d4ff} + +/* Trainer battle intro */ +#trainer-intro{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);display:none;z-index:19;flex-direction:column;align-items:center;justify-content:center} +#trainer-intro p{font-size:11px;color:#ff6b9d;text-shadow:0 0 10px rgba(255,107,157,0.5);text-align:center;line-height:2} @@ -63,6 +127,7 @@
NEON TOWN Steps: 0 + $500
@@ -77,26 +142,40 @@
???Lv 5
??/??
-
EXP: 0
+
+
EXP: 0/100
A wild creature appeared!
- - + +
+
+
+
LEVEL UP!
+ + +
+

+
+ +
+

BLACKROAD CREATURES

Gotta Route 'Em All

WASD / Arrows - Move

-

Walk in tall grass for encounters

+

SPACE - Interact

+

TAB - Creature Index

+

I - Open Bag

Collect and battle creatures!

@@ -110,21 +189,61 @@

Gotta Route 'Em All

// --- CREATURE DATABASE --- const creatureDB=[ {id:0,name:'Pyrobyte',type:'fire',icon:'\uD83D\uDD25',baseHp:45,baseAtk:52,baseDef:40,baseSpd:60, - moves:[{name:'Ember',type:'fire',power:40,acc:100},{name:'Tackle',type:'normal',power:40,acc:100},{name:'Fire Spin',type:'fire',power:55,acc:85},{name:'Flame Burst',type:'fire',power:70,acc:90}]}, + moves:[ + {name:'Ember',type:'fire',power:40,acc:100,maxPP:25}, + {name:'Tackle',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Fire Spin',type:'fire',power:55,acc:85,maxPP:15}, + {name:'Flame Burst',type:'fire',power:70,acc:90,maxPP:10} + ]}, {id:1,name:'Aquanode',type:'water',icon:'\uD83D\uDCA7',baseHp:50,baseAtk:46,baseDef:50,baseSpd:48, - moves:[{name:'Water Gun',type:'water',power:40,acc:100},{name:'Tackle',type:'normal',power:40,acc:100},{name:'Bubble Beam',type:'water',power:60,acc:100},{name:'Hydro Pump',type:'water',power:80,acc:80}]}, + moves:[ + {name:'Water Gun',type:'water',power:40,acc:100,maxPP:25}, + {name:'Tackle',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Bubble Beam',type:'water',power:60,acc:100,maxPP:15}, + {name:'Hydro Pump',type:'water',power:80,acc:80,maxPP:5} + ]}, {id:2,name:'Chlorobit',type:'grass',icon:'\uD83C\uDF3F',baseHp:48,baseAtk:48,baseDef:52,baseSpd:45, - moves:[{name:'Vine Whip',type:'grass',power:42,acc:100},{name:'Tackle',type:'normal',power:40,acc:100},{name:'Razor Leaf',type:'grass',power:55,acc:95},{name:'Solar Beam',type:'grass',power:85,acc:90}]}, + moves:[ + {name:'Vine Whip',type:'grass',power:42,acc:100,maxPP:25}, + {name:'Tackle',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Razor Leaf',type:'grass',power:55,acc:95,maxPP:15}, + {name:'Solar Beam',type:'grass',power:85,acc:90,maxPP:5} + ]}, {id:3,name:'Voltpulse',type:'electric',icon:'\u26A1',baseHp:40,baseAtk:55,baseDef:35,baseSpd:70, - moves:[{name:'Spark',type:'electric',power:40,acc:100},{name:'Quick Attack',type:'normal',power:40,acc:100},{name:'Thunderbolt',type:'electric',power:65,acc:100},{name:'Thunder',type:'electric',power:90,acc:70}]}, + moves:[ + {name:'Spark',type:'electric',power:40,acc:100,maxPP:25}, + {name:'Quick Attack',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Thunderbolt',type:'electric',power:65,acc:100,maxPP:15}, + {name:'Thunder',type:'electric',power:90,acc:70,maxPP:5} + ]}, {id:4,name:'Shadowram',type:'dark',icon:'\uD83C\uDF11',baseHp:55,baseAtk:60,baseDef:45,baseSpd:50, - moves:[{name:'Bite',type:'dark',power:45,acc:100},{name:'Tackle',type:'normal',power:40,acc:100},{name:'Shadow Claw',type:'dark',power:60,acc:95},{name:'Dark Pulse',type:'dark',power:75,acc:90}]}, + moves:[ + {name:'Bite',type:'dark',power:45,acc:100,maxPP:25}, + {name:'Tackle',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Shadow Claw',type:'dark',power:60,acc:95,maxPP:15}, + {name:'Dark Pulse',type:'dark',power:75,acc:90,maxPP:10} + ]}, {id:5,name:'Cyphera',type:'cyber',icon:'\uD83E\uDD16',baseHp:52,baseAtk:50,baseDef:55,baseSpd:55, - moves:[{name:'Data Spike',type:'cyber',power:45,acc:100},{name:'Tackle',type:'normal',power:40,acc:100},{name:'Code Burst',type:'cyber',power:60,acc:95},{name:'System Crash',type:'cyber',power:80,acc:85}]}, + moves:[ + {name:'Data Spike',type:'cyber',power:45,acc:100,maxPP:25}, + {name:'Tackle',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Code Burst',type:'cyber',power:60,acc:95,maxPP:15}, + {name:'System Crash',type:'cyber',power:80,acc:85,maxPP:5} + ]}, {id:6,name:'Psylink',type:'psychic',icon:'\uD83D\uDD2E',baseHp:42,baseAtk:58,baseDef:38,baseSpd:65, - moves:[{name:'Confusion',type:'psychic',power:42,acc:100},{name:'Tackle',type:'normal',power:40,acc:100},{name:'Psybeam',type:'psychic',power:60,acc:100},{name:'Psychic',type:'psychic',power:80,acc:90}]}, + moves:[ + {name:'Confusion',type:'psychic',power:42,acc:100,maxPP:25}, + {name:'Tackle',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Psybeam',type:'psychic',power:60,acc:100,maxPP:15}, + {name:'Psychic',type:'psychic',power:80,acc:90,maxPP:10} + ]}, {id:7,name:'Terrashell',type:'normal',icon:'\uD83D\uDC22',baseHp:65,baseAtk:40,baseDef:65,baseSpd:25, - moves:[{name:'Tackle',type:'normal',power:40,acc:100},{name:'Headbutt',type:'normal',power:55,acc:100},{name:'Body Slam',type:'normal',power:70,acc:95},{name:'Hyper Beam',type:'normal',power:90,acc:80}]}, + moves:[ + {name:'Tackle',type:'normal',power:40,acc:100,maxPP:35}, + {name:'Headbutt',type:'normal',power:55,acc:100,maxPP:20}, + {name:'Body Slam',type:'normal',power:70,acc:95,maxPP:10}, + {name:'Hyper Beam',type:'normal',power:90,acc:80,maxPP:5} + ]}, ]; const typeChart={ @@ -145,6 +264,8 @@

Gotta Route 'Em All

function makeCreature(dbId,level){ const base=creatureDB[dbId]; const hp=Math.floor(base.baseHp*(1+level*0.1)); + const numMoves=Math.min(4,1+Math.floor(level/5)); + const moves=base.moves.slice(0,numMoves).map(m=>({...m,pp:m.maxPP})); return{ ...base,level, maxHp:hp,hp, @@ -152,93 +273,410 @@

Gotta Route 'Em All

def:Math.floor(base.baseDef*(1+level*0.08)), spd:Math.floor(base.baseSpd*(1+level*0.06)), exp:0,expNext:level*25+50, - availMoves:base.moves.slice(0,Math.min(4,1+Math.floor(level/5))) + availMoves:moves }; } +// --- ENCOUNTER TABLES --- +// {id, weight} - higher weight = more common +const encounterTables={ + route1:[{id:0,w:40},{id:2,w:40},{id:7,w:20}], + route2:[{id:1,w:40},{id:6,w:30},{id:4,w:15}], + route3:[{id:3,w:35},{id:5,w:30},{id:4,w:35}], + lake:[{id:1,w:80},{id:5,w:20}] +}; + +function pickFromTable(table){ + const total=table.reduce((s,e)=>s+e.w,0); + let r=Math.random()*total; + for(const entry of table){ + r-=entry.w; + if(r<=0) return entry.id; + } + return table[0].id; +} + +// --- ITEM / INVENTORY --- +const items={ + catchBall:{name:'Catch Ball',desc:'Catches wild creatures',price:200,icon:'\u26AA'}, + potion:{name:'Potion',desc:'Heals 20 HP',price:100,icon:'\uD83E\uDDF4',heal:20}, + superPotion:{name:'Super Potion',desc:'Heals 50 HP',price:250,icon:'\uD83C\uDF76',heal:50} +}; +let inventory={catchBall:5,potion:3,superPotion:1}; +let money=500; + +// --- POKEDEX --- +let pokedex={}; // id -> {seen:bool, caught:bool} + +function markSeen(id){if(!pokedex[id])pokedex[id]={seen:true,caught:false};else pokedex[id].seen=true;} +function markCaught(id){if(!pokedex[id])pokedex[id]={seen:true,caught:true};else{pokedex[id].seen=true;pokedex[id].caught=true;}} + +// --- TRAINER DATA --- +const trainers=[ + { + id:'bug',name:'Bug Catcher Ben',x:15,y:10,dir:0,defeated:false, + sprite:{hat:'#7ed321',body:'#558b2f',pants:'#33691e'}, + msg:'Bug Catcher Ben wants to battle!', + reward:100, + party:[makeCreature(2,4)] + }, + { + id:'hiker',name:'Hiker Rocky',x:35,y:18,dir:3,defeated:false, + sprite:{hat:'#795548',body:'#5d4037',pants:'#4e342e'}, + msg:'Hiker Rocky wants to battle!', + reward:200, + party:[makeCreature(7,6),makeCreature(3,7)] + }, + { + id:'ace',name:'Ace Trainer Nova',x:25,y:32,dir:2,defeated:false, + sprite:{hat:'#c44dff',body:'#9013fe',pants:'#6a1b9a'}, + msg:'Ace Trainer Nova wants to battle!', + reward:300, + party:[makeCreature(4,8),makeCreature(6,9),makeCreature(0,10)] + } +]; + // --- GAME STATE --- let gameStarted=false; -let state='overworld'; // overworld, battle +let state='overworld'; // overworld, battle, dialog let steps=0; let party=[makeCreature(5,5)]; // Start with Cyphera -let catchBalls=5; +markCaught(5); let battleEnemy=null; -let battleTurn='player'; -let battleAnim=0; -let battleMsg=''; -let battleMsgQueue=[]; +let battleIsTrainer=false; +let battleTrainer=null; +let battleEnemyParty=[]; +let battleEnemyIndex=0; +let battleLocked=false; -// Overworld map -const MCOLS=40,MROWS=30; +// Overworld map - 50x40 +const MCOLS=50,MROWS=40; let mapData=[]; -let player={x:20,y:15,dir:0,moving:false}; +let player={x:25,y:20,dir:0,moving:false,animFrame:0}; let camera={x:0,y:0}; -// Map tiles: 0=grass,1=tall_grass,2=path,3=water,4=building,5=tree,6=ledge,7=sign +// Map tiles: 0=grass,1=tall_grass,2=path,3=water,4=building_heal,5=tree,6=building_shop,7=sign, +// 8=dark_ground,9=dark_tall_grass,10=forest_ground,11=forest_tall_grass,12=house,13=lake_grass,14=lake_tall_grass +const TILE_GRASS=0,TILE_TALL=1,TILE_PATH=2,TILE_WATER=3,TILE_HEAL=4,TILE_TREE=5, + TILE_SHOP=6,TILE_SIGN=7,TILE_CAVE=8,TILE_CAVE_TALL=9,TILE_FOREST=10, + TILE_FOREST_TALL=11,TILE_HOUSE=12,TILE_LAKE_GRASS=13,TILE_LAKE_TALL=14; + function initMap(){ for(let r=0;r0.35) mapData[r][c]=TILE_TALL; + } + for(let c=26;c<36;c++){ + if(mapData[r][c]===TILE_GRASS && Math.random()>0.4) mapData[r][c]=TILE_TALL; + } + } + // Scattered trees route 1 + const r1trees=[[4,16],[6,18],[5,28],[8,30],[11,14],[13,32],[3,22],[10,17]]; + r1trees.forEach(([r,c])=>{if(r>0&&c>0&&r0.45) mapData[r][c]=TILE_FOREST_TALL; + } + } + // Forest trees + for(let i=0;i<20;i++){ + const tr=14+Math.floor(Math.random()*14); + const tc=32+Math.floor(Math.random()*16); + if(mapData[tr][tc]===TILE_FOREST) mapData[tr][tc]=TILE_TREE; + } + + // === ROUTE 3 SOUTH - Cave/Dark (hard) === + for(let r=26;r0.4) mapData[r][c]=TILE_CAVE_TALL; + } + } + // Dark area rocks (use trees) + for(let i=0;i<15;i++){ + const tr=27+Math.floor(Math.random()*11); + const tc=10+Math.floor(Math.random()*30); + if(tr0.5) mapData[r][c]=TILE_LAKE_TALL; } } - // Paths (routes) - for(let c=5;c<35;c++){mapData[15][c]=2;mapData[16][c]=2;} - for(let r=5;r<25;r++){mapData[r][20]=2;mapData[r][21]=2;} - - // Town area (center) - for(let r=12;r<19;r++) for(let c=17;c<25;c++) mapData[r][c]=2; - // Buildings in town - mapData[12][18]=4;mapData[12][19]=4;mapData[13][18]=4;mapData[13][19]=4; // Lab - mapData[12][22]=4;mapData[12][23]=4;mapData[13][22]=4;mapData[13][23]=4; // Center - mapData[17][18]=4;mapData[17][19]=4;mapData[18][18]=4;mapData[18][19]=4; // Shop - - // Tall grass patches (encounter zones) - for(let r=6;r<12;r++) for(let c=8;c<16;c++) if(Math.random()>0.25) mapData[r][c]=1; - for(let r=20;r<26;r++) for(let c=8;c<16;c++) if(Math.random()>0.25) mapData[r][c]=1; - for(let r=6;r<12;r++) for(let c=25;c<34;c++) if(Math.random()>0.3) mapData[r][c]=1; - for(let r=20;r<26;r++) for(let c=25;c<34;c++) if(Math.random()>0.2) mapData[r][c]=1; - - // Water - for(let r=2;r<5;r++) for(let c=2;c<7;c++) mapData[r][c]=3; - for(let r=26;r<29;r++) for(let c=32;c<38;c++) mapData[r][c]=3; - - // Trees (border) - for(let c=0;c16&&tx<25&&ty>11&&ty<20)) mapData[ty][tx]=5; - } - - // Signs - mapData[14][17]=7;mapData[14][24]=7; -} - -// Encounter zones by location -function getWildCreature(){ - const px=player.x,py=player.y; - let pool,lvRange; - if(py<15){pool=[0,1,2,6];lvRange=[3,7];} // North routes - else if(py>16){pool=[3,4,5,7];lvRange=[4,8];} // South routes - else{pool=[0,1,2,3,4,5,6,7];lvRange=[5,10];} // Everywhere rare - const id=pool[Math.floor(Math.random()*pool.length)]; - const lv=lvRange[0]+Math.floor(Math.random()*(lvRange[1]-lvRange[0]+1)); + // Lake trees + const ltrees=[[2,3],[2,12],[7,3],[7,12],[1,7],[1,8]]; + ltrees.forEach(([r,c])=>{if(mapData[r][c]!==TILE_WATER&&mapData[r][c]!==TILE_PATH)mapData[r][c]=TILE_TREE;}); + + // Extra scattered trees in open areas + for(let i=0;i<25;i++){ + const tr=2+Math.floor(Math.random()*(MROWS-4)); + const tc=2+Math.floor(Math.random()*(MCOLS-4)); + if(mapData[tr][tc]===TILE_GRASS) mapData[tr][tc]=TILE_TREE; + } +} + +// --- ZONE DETECTION --- +function getZone(px,py){ + if(px>=20&&px<=30&&py>=16&&py<=24) return 'town'; + if(py<16&&px<14&&py<9) return 'lake'; + if(py<16) return 'route1'; + if(px>31) return 'route2'; + if(py>25) return 'route3'; + return 'route1'; +} + +function getZoneName(zone){ + switch(zone){ + case 'town':return 'NEON TOWN'; + case 'route1':return 'ROUTE 1 - GRASSLANDS'; + case 'route2':return 'ROUTE 2 - FOREST'; + case 'route3':return 'ROUTE 3 - DARK CAVES'; + case 'lake':return 'CYBER LAKE'; + default:return 'WILD ROUTE'; + } +} + +function getWildCreature(px,py){ + const zone=getZone(px,py); + let table,lvMin,lvMax; + switch(zone){ + case 'route1':table=encounterTables.route1;lvMin=3;lvMax=6;break; + case 'route2':table=encounterTables.route2;lvMin=5;lvMax=9;break; + case 'route3':table=encounterTables.route3;lvMin=7;lvMax=12;break; + case 'lake':table=encounterTables.lake;lvMin=4;lvMax=8;break; + default:table=encounterTables.route1;lvMin=3;lvMax=6; + } + const id=pickFromTable(table); + const lv=lvMin+Math.floor(Math.random()*(lvMax-lvMin+1)); return makeCreature(id,lv); } // --- OVERWORLD RENDERING --- +function drawTile(tile,sx,sy,c,r){ + switch(tile){ + case TILE_GRASS: + ctx.fillStyle='#4caf50';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#43a047'; + if((c+r)%2===0){ctx.fillRect(sx+8,sy+12,2,6);ctx.fillRect(sx+22,sy+6,2,5);} + break; + case TILE_TALL: + ctx.fillStyle='#388e3c';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#2e7d32'; + for(let i=0;i<5;i++){const gx=sx+4+i*6,gy=sy+4;ctx.fillRect(gx,gy,3,TILE-8);ctx.fillRect(gx-1,gy,5,2);} + break; + case TILE_PATH: + ctx.fillStyle='#d4a574';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='rgba(0,0,0,0.05)'; + ctx.fillRect(sx+4,sy+4,4,4);ctx.fillRect(sx+20,sy+16,4,4); + break; + case TILE_WATER: + ctx.fillStyle='#0d47a1';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#1565c0';ctx.fillRect(sx+1,sy+1,TILE-2,TILE-2); + ctx.fillStyle='rgba(0,212,255,0.2)'; + ctx.fillRect(sx+Math.sin(Date.now()/600+c)*4+12,sy+10,10,2); + ctx.fillRect(sx+Math.cos(Date.now()/800+r)*6+8,sy+22,8,2); + break; + case TILE_HEAL: + ctx.fillStyle='#d32f2f';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#e53935';ctx.fillRect(sx+2,sy+2,TILE-4,TILE-4); + ctx.fillStyle='#fff';ctx.fillRect(sx+12,sy+6,8,20);ctx.fillRect(sx+6,sy+12,20,8); + break; + case TILE_SHOP: + ctx.fillStyle='#1565c0';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#1976d2';ctx.fillRect(sx+2,sy+2,TILE-4,TILE-4); + ctx.fillStyle='#f5a623';ctx.fillRect(sx+8,sy+8,16,12); + ctx.fillStyle='#fff';ctx.font='8px "Press Start 2P"';ctx.fillText('$',sx+13,sy+17); + break; + case TILE_TREE: + const bgTile=getZone(c,r); + if(bgTile==='route3'||getZone(c,r)==='route3'){ctx.fillStyle='#3e2723';} + else if(bgTile==='lake'){ctx.fillStyle='#1b5e20';} + else{ctx.fillStyle='#4caf50';} + ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#5d4037';ctx.fillRect(sx+12,sy+18,8,14); + if(getZone(c,r)==='route2'){ + ctx.fillStyle='#1b5e20';ctx.beginPath();ctx.moveTo(sx+16,sy);ctx.lineTo(sx+28,sy+20);ctx.lineTo(sx+4,sy+20);ctx.fill(); + ctx.fillStyle='#2e7d32';ctx.beginPath();ctx.moveTo(sx+16,sy+6);ctx.lineTo(sx+26,sy+18);ctx.lineTo(sx+6,sy+18);ctx.fill(); + } else if(getZone(c,r)==='route3'){ + ctx.fillStyle='#4a148c';ctx.beginPath();ctx.arc(sx+16,sy+12,12,0,Math.PI*2);ctx.fill(); + ctx.fillStyle='#6a1b9a';ctx.beginPath();ctx.arc(sx+12,sy+14,8,0,Math.PI*2);ctx.fill(); + } else { + ctx.fillStyle='#2e7d32';ctx.beginPath();ctx.arc(sx+16,sy+12,12,0,Math.PI*2);ctx.fill(); + ctx.fillStyle='#388e3c';ctx.beginPath();ctx.arc(sx+12,sy+14,8,0,Math.PI*2);ctx.fill(); + } + break; + case TILE_SIGN: + ctx.fillStyle='#d4a574';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#5d4037';ctx.fillRect(sx+13,sy+16,6,16); + ctx.fillStyle='#795548';ctx.fillRect(sx+6,sy+6,20,14); + ctx.fillStyle='#e0e0e0';ctx.font='6px "Press Start 2P"';ctx.fillText('!',sx+14,sy+16); + break; + case TILE_CAVE: + ctx.fillStyle='#2c2c3e';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#3a3a50'; + if((c+r)%3===0) ctx.fillRect(sx+6,sy+10,4,4); + if((c+r)%5===0) ctx.fillRect(sx+18,sy+20,5,3); + break; + case TILE_CAVE_TALL: + ctx.fillStyle='#2c2c3e';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#6a1b9a'; + for(let i=0;i<4;i++){const gx=sx+5+i*7,gy=sy+4;ctx.fillRect(gx,gy,3,TILE-8);ctx.fillRect(gx-1,gy,5,2);} + ctx.fillStyle='rgba(196,77,255,0.15)'; + ctx.fillRect(sx,sy,TILE,TILE); + break; + case TILE_FOREST: + ctx.fillStyle='#2e5016';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#3a6b20'; + if((c+r)%2===0){ctx.fillRect(sx+10,sy+14,3,5);ctx.fillRect(sx+24,sy+8,2,4);} + break; + case TILE_FOREST_TALL: + ctx.fillStyle='#2e5016';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#1b5e20'; + for(let i=0;i<5;i++){const gx=sx+3+i*6,gy=sy+3;ctx.fillRect(gx,gy,3,TILE-6);ctx.fillRect(gx-1,gy,5,3);} + ctx.fillStyle='#2e7d32';ctx.fillRect(sx+2,sy+2,3,6); + break; + case TILE_HOUSE: + ctx.fillStyle='#d4a574';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#795548';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#8d6e63';ctx.fillRect(sx+2,sy+2,TILE-4,TILE-4); + ctx.fillStyle='#fff9c4';ctx.fillRect(sx+10,sy+8,12,10); + ctx.fillStyle='rgba(0,0,0,0.2)';ctx.fillRect(sx+15,sy+8,2,10); + break; + case TILE_LAKE_GRASS: + ctx.fillStyle='#1b5e20';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='rgba(0,212,255,0.08)';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#2e7d32'; + if((c+r)%2===0) ctx.fillRect(sx+12,sy+10,2,6); + break; + case TILE_LAKE_TALL: + ctx.fillStyle='#1b5e20';ctx.fillRect(sx,sy,TILE,TILE); + ctx.fillStyle='#00838f'; + for(let i=0;i<4;i++){const gx=sx+5+i*7,gy=sy+5;ctx.fillRect(gx,gy,3,TILE-10);ctx.fillRect(gx-1,gy,5,2);} + ctx.fillStyle='rgba(0,212,255,0.12)';ctx.fillRect(sx,sy,TILE,TILE); + break; + } +} + +function drawSprite(px,py,spriteColors,dir,animFrame){ + // Shadow + ctx.fillStyle='rgba(0,0,0,0.2)'; + ctx.beginPath();ctx.ellipse(px+16,py+30,10,4,0,0,Math.PI*2);ctx.fill(); + // Body + ctx.fillStyle=spriteColors.body||'#c44dff';ctx.fillRect(px+8,py+10,16,14); + // Head + ctx.fillStyle='#ffcc80';ctx.fillRect(px+10,py+2,12,10); + // Hair + ctx.fillStyle='#212121';ctx.fillRect(px+9,py,14,5); + // Hat + ctx.fillStyle=spriteColors.hat||'#d32f2f';ctx.fillRect(px+7,py-2,18,5);ctx.fillRect(px+10,py-4,12,3); + // Eyes + ctx.fillStyle='#1a1a2e'; + if(dir===0){ctx.fillRect(px+13,py+5,2,2);ctx.fillRect(px+18,py+5,2,2);} + else if(dir===1){ctx.fillRect(px+17,py+5,3,2);} + else if(dir===3){ctx.fillRect(px+12,py+5,3,2);} + else{ctx.fillRect(px+14,py+5,4,2);} + // Legs + ctx.fillStyle=spriteColors.pants||'#1565c0'; + const legOff=animFrame%2===0?0:2; + ctx.fillRect(px+10,py+24-legOff,5,8+legOff);ctx.fillRect(px+17,py+24+legOff,5,8-legOff); +} + function drawOverworld(){ - // Camera camera.x=player.x*TILE-W/2+TILE/2; camera.y=player.y*TILE-H/2+TILE/2; camera.x=Math.max(0,Math.min(camera.x,MCOLS*TILE-W)); camera.y=Math.max(0,Math.min(camera.y,MROWS*TILE-H)); - // Sky - ctx.fillStyle='#87CEEB';ctx.fillRect(0,0,W,H); + const zone=getZone(player.x,player.y); + + // Sky based on zone + if(zone==='route3'){ctx.fillStyle='#1a1a2e';} + else if(zone==='lake'){ctx.fillStyle='#0d2137';} + else if(zone==='route2'){ctx.fillStyle='#1a3a1a';} + else{ctx.fillStyle='#87CEEB';} + ctx.fillRect(0,0,W,H); const startC=Math.floor(camera.x/TILE); const startR=Math.floor(camera.y/TILE); @@ -249,112 +687,87 @@

Gotta Route 'Em All

for(let c=startC;c{ + if(t.defeated) return; + const tx=t.x*TILE-camera.x,ty=t.y*TILE-camera.y; + if(tx>-TILE&&tx-TILE&&ty16&&player.x<25&&player.y>11&&player.y<20) loc='NEON TOWN'; - else if(player.y<12) loc='NORTH ROUTE'; - else if(player.y>18) loc='SOUTH ROUTE'; - document.getElementById('location-text').textContent=loc; - document.getElementById('step-text').textContent=`Steps: ${steps} | Balls: ${catchBalls}`; + // Update HUD + document.getElementById('location-text').textContent=getZoneName(zone); + document.getElementById('step-text').textContent=`Steps: ${steps} | Balls: ${inventory.catchBall}`; + document.getElementById('money-text').textContent=`$${money}`; + + // Update party display + const pd=document.getElementById('party-display'); + pd.innerHTML=''; + party.forEach(p=>{ + const d=document.createElement('div'); + d.className='party-icon'+(p.hp<=0?' fainted':''); + d.textContent=p.icon; + const bar=document.createElement('div');bar.className='party-hp-bar'; + const fill=document.createElement('div');fill.className='party-hp-fill'; + fill.style.width=Math.max(0,p.hp/p.maxHp*100)+'%'; + if(p.hp/p.maxHp<0.25) fill.style.background='#d32f2f'; + else if(p.hp/p.maxHp<0.5) fill.style.background='#f5a623'; + bar.appendChild(fill);d.appendChild(bar); + pd.appendChild(d); + }); } // --- BATTLE SYSTEM --- -function startBattle(enemy){ +function startBattle(enemy,isTrainer,trainer){ battleEnemy=enemy; + battleIsTrainer=isTrainer||false; + battleTrainer=trainer||null; + battleLocked=false; + if(isTrainer&&trainer){ + battleEnemyParty=trainer.party.map(p=>({...p,hp:p.maxHp,availMoves:p.availMoves.map(m=>({...m,pp:m.maxPP}))})); + battleEnemyIndex=0; + battleEnemy=battleEnemyParty[0]; + } + // Reset PP for player creatures + party.forEach(p=>{p.availMoves.forEach(m=>{m.pp=m.maxPP;});}); + state='battle'; - battleTurn='player'; - battleMsg=''; - battleMsgQueue=[]; document.getElementById('battle-ui').style.display='block'; document.getElementById('moves-panel').style.display='none'; + document.getElementById('bag-panel').style.display='none'; + document.getElementById('party-battle-panel').style.display='none'; + markSeen(enemy.id); updateBattleUI(); - setBattleText(`A wild ${enemy.name} (Lv ${enemy.level}) appeared!`); + if(isTrainer){ + setBattleText(`${trainer.name} sent out ${enemy.name}!`); + } else { + setBattleText(`A wild ${enemy.name} (Lv ${enemy.level}) appeared!`); + } drawBattleScene(); } function drawBattleScene(){ - // Background const grd=bctx.createLinearGradient(0,0,0,H); grd.addColorStop(0,'#1a0a3d');grd.addColorStop(0.5,'#2a1050');grd.addColorStop(1,'#0d0d1a'); bctx.fillStyle=grd;bctx.fillRect(0,0,W,H); - // Ground + // Ground platforms bctx.fillStyle='#2e7d32'; - bctx.beginPath();bctx.ellipse(600,280,120,30,0,0,Math.PI*2);bctx.fill(); // enemy platform - bctx.beginPath();bctx.ellipse(200,400,120,30,0,0,Math.PI*2);bctx.fill(); // player platform + bctx.beginPath();bctx.ellipse(600,280,120,30,0,0,Math.PI*2);bctx.fill(); + bctx.beginPath();bctx.ellipse(200,400,120,30,0,0,Math.PI*2);bctx.fill(); // Enemy creature if(battleEnemy&&battleEnemy.hp>0){ @@ -371,6 +784,14 @@

Gotta Route 'Em All

bctx.textAlign='left'; } + // Trainer sprite on enemy side + if(battleIsTrainer&&battleTrainer){ + const t=battleTrainer; + bctx.fillStyle=t.sprite.body;bctx.fillRect(680,200,16,14); + bctx.fillStyle='#ffcc80';bctx.fillRect(682,192,12,10); + bctx.fillStyle=t.sprite.hat;bctx.fillRect(679,188,18,5); + } + // Particles bctx.fillStyle='rgba(196,77,255,0.1)'; for(let i=0;i<8;i++){ @@ -398,58 +819,160 @@

Gotta Route 'Em All

pBar.style.width=pHpPct+'%'; pBar.className='hp-bar-inner'+(pHpPct<25?' low':pHpPct<50?' med':''); document.getElementById('player-hp-text').textContent=`${Math.max(0,pc.hp)}/${pc.maxHp}`; - document.getElementById('player-exp').textContent=`${pc.exp}/${pc.expNext}`; - // Party display - const pd=document.getElementById('party-display'); - pd.innerHTML=''; - party.forEach(p=>{ - const d=document.createElement('div'); - d.className='party-icon'+(p.hp<=0?' fainted':''); - d.textContent=p.icon; - pd.appendChild(d); - }); + const expPct=Math.min(100,pc.exp/pc.expNext*100); + document.getElementById('player-exp-bar').style.width=expPct+'%'; + document.getElementById('player-exp-text').textContent=`EXP: ${pc.exp}/${pc.expNext}`; } function setBattleText(text){ document.getElementById('battle-text').textContent=text; } +function showMainBattleActions(){ + document.getElementById('moves-panel').style.display='none'; + document.getElementById('bag-panel').style.display='none'; + document.getElementById('party-battle-panel').style.display='none'; + document.getElementById('battle-actions').style.display='grid'; +} + function showMoves(){ + if(battleLocked) return; const pc=party[0]; const panel=document.getElementById('moves-panel'); panel.style.display='grid'; + document.getElementById('bag-panel').style.display='none'; + document.getElementById('party-battle-panel').style.display='none'; panel.innerHTML=''; pc.availMoves.forEach((m,i)=>{ const btn=document.createElement('button'); - btn.className=`move-btn type-${m.type}`; - btn.innerHTML=`${m.name}
${m.type.toUpperCase()} | PWR: ${m.power}
`; - btn.onclick=()=>playerAttack(i); + const noPP=m.pp<=0; + btn.className=`move-btn type-${m.type}${noPP?' no-pp':''}`; + btn.innerHTML=`${m.name}
${m.type.toUpperCase()} | PWR:${m.power} | PP:${m.pp}/${m.maxPP}
`; + if(!noPP) btn.onclick=()=>playerAttack(i); panel.appendChild(btn); }); - // Back button const back=document.createElement('button'); back.className='move-btn type-normal'; back.textContent='BACK'; - back.onclick=()=>{panel.style.display='none';}; + back.onclick=()=>showMainBattleActions(); + panel.appendChild(back); +} + +function showBattleBag(){ + if(battleLocked) return; + const panel=document.getElementById('bag-panel'); + panel.style.display='grid'; + document.getElementById('moves-panel').style.display='none'; + document.getElementById('party-battle-panel').style.display='none'; + panel.innerHTML=''; + + // Catch Ball + const ballBtn=document.createElement('button'); + ballBtn.className=`bag-btn${inventory.catchBall<=0?' empty':''}`; + ballBtn.innerHTML=`\u26AA Catch Ball
x${inventory.catchBall}
`; + if(!battleIsTrainer&&inventory.catchBall>0) ballBtn.onclick=()=>useBall(); + else if(battleIsTrainer) ballBtn.onclick=()=>{setBattleText("Can't catch a trainer's creature!");}; + panel.appendChild(ballBtn); + + // Potion + const potBtn=document.createElement('button'); + potBtn.className=`bag-btn${inventory.potion<=0?' empty':''}`; + potBtn.innerHTML=`\uD83E\uDDF4 Potion (20HP)
x${inventory.potion}
`; + if(inventory.potion>0) potBtn.onclick=()=>usePotion('potion'); + panel.appendChild(potBtn); + + // Super Potion + const sPotBtn=document.createElement('button'); + sPotBtn.className=`bag-btn${inventory.superPotion<=0?' empty':''}`; + sPotBtn.innerHTML=`\uD83C\uDF76 Super Potion (50HP)
x${inventory.superPotion}
`; + if(inventory.superPotion>0) sPotBtn.onclick=()=>usePotion('superPotion'); + panel.appendChild(sPotBtn); + + const back=document.createElement('button'); + back.className='bag-btn'; + back.textContent='BACK'; + back.onclick=()=>showMainBattleActions(); + panel.appendChild(back); +} + +async function usePotion(type){ + if(battleLocked) return; + battleLocked=true; + document.getElementById('bag-panel').style.display='none'; + const pc=party[0]; + const heal=items[type].heal; + inventory[type]--; + const oldHp=pc.hp; + pc.hp=Math.min(pc.maxHp,pc.hp+heal); + const healed=pc.hp-oldHp; + setBattleText(`Used ${items[type].name}! ${pc.name} recovered ${healed} HP!`); + updateBattleUI();drawBattleScene(); + await delay(1200); + if(battleEnemy.hp>0){ + await enemyTurn(); + checkBattleEnd(); + } + battleLocked=false; +} + +function showPartySwitch(){ + if(battleLocked) return; + const panel=document.getElementById('party-battle-panel'); + panel.style.display='block'; + document.getElementById('moves-panel').style.display='none'; + document.getElementById('bag-panel').style.display='none'; + panel.innerHTML='
SWITCH CREATURE:
'; + party.forEach((p,i)=>{ + const btn=document.createElement('button'); + btn.className='party-btn'+(p.hp<=0?' fainted':''); + btn.textContent=`${p.icon} ${p.name} Lv${p.level} HP:${p.hp}/${p.maxHp}`; + if(i>0&&p.hp>0) btn.onclick=()=>switchCreature(i); + panel.appendChild(btn); + }); + const back=document.createElement('button'); + back.className='party-btn';back.style.background='linear-gradient(135deg,#616161,#424242)'; + back.textContent='BACK'; + back.onclick=()=>showMainBattleActions(); panel.appendChild(back); } +async function switchCreature(idx){ + if(battleLocked) return; + battleLocked=true; + document.getElementById('party-battle-panel').style.display='none'; + [party[0],party[idx]]=[party[idx],party[0]]; + setBattleText(`Go, ${party[0].name}!`); + updateBattleUI();drawBattleScene(); + await delay(1200); + if(battleEnemy.hp>0){ + await enemyTurn(); + checkBattleEnd(); + } + battleLocked=false; +} + function calcDamage(attacker,defender,move){ const eff=getEffectiveness(move.type,defender.type); const base=((2*attacker.level/5+2)*move.power*attacker.atk/defender.def/50+2); const rand=0.85+Math.random()*0.15; const stab=move.type===attacker.type?1.5:1; - return{dmg:Math.max(1,Math.floor(base*rand*stab*eff)),eff}; + const crit=Math.random()<0.1; + const critMult=crit?1.5:1; + return{dmg:Math.max(1,Math.floor(base*rand*stab*eff*critMult)),eff,crit}; } +function delay(ms){return new Promise(r=>setTimeout(r,ms));} + async function playerAttack(moveIdx){ + if(battleLocked) return; + battleLocked=true; document.getElementById('moves-panel').style.display='none'; const pc=party[0]; const en=battleEnemy; const move=pc.availMoves[moveIdx]; + move.pp=Math.max(0,move.pp-1); - // Speed check if(pc.spd>=en.spd){ await doAttack(pc,en,move,'player'); if(en.hp>0) await enemyTurn(); @@ -458,6 +981,7 @@

Gotta Route 'Em All

if(pc.hp>0) await doAttack(pc,en,move,'player'); } checkBattleEnd(); + battleLocked=false; } function doAttack(attacker,defender,move,who){ @@ -467,12 +991,13 @@

Gotta Route 'Em All

setBattleText(`${attacker.name}'s ${move.name} missed!`); setTimeout(resolve,1200);return; } - const{dmg,eff}=calcDamage(attacker,defender,move); + const{dmg,eff,crit}=calcDamage(attacker,defender,move); defender.hp=Math.max(0,defender.hp-dmg); let effText=''; - if(eff>1) effText=" It's super effective!"; - if(eff<1) effText=" It's not very effective..."; - setBattleText(`${attacker.name} used ${move.name}! (-${dmg} HP)${effText}`); + if(eff>1) effText=" Super effective!"; + if(eff<1) effText=" Not very effective..."; + let critText=crit?' CRITICAL HIT!':''; + setBattleText(`${attacker.name} used ${move.name}! (-${dmg} HP)${critText}${effText}`); updateBattleUI(); drawBattleScene(); setTimeout(resolve,1500); @@ -483,9 +1008,24 @@

Gotta Route 'Em All

return new Promise(resolve=>{ const en=battleEnemy; const pc=party[0]; - const move=en.availMoves[Math.floor(Math.random()*en.availMoves.length)]; + // Smart AI: pick move with highest expected damage + let bestMove=en.availMoves[0]; + let bestDmg=0; + en.availMoves.forEach(m=>{ + if(m.pp<=0) return; + const eff=getEffectiveness(m.type,pc.type); + const stab=m.type===en.type?1.5:1; + const expected=m.power*eff*stab*(m.acc/100); + if(expected>bestDmg){bestDmg=expected;bestMove=m;} + }); + // Fallback if all out of PP + if(bestMove.pp<=0){ + bestMove={name:'Struggle',type:'normal',power:30,acc:100,pp:99,maxPP:99}; + } else { + bestMove.pp=Math.max(0,bestMove.pp-1); + } setTimeout(async()=>{ - await doAttack(en,pc,move,'enemy'); + await doAttack(en,pc,bestMove,'enemy'); updateBattleUI(); resolve(); },500); @@ -497,27 +1037,34 @@

Gotta Route 'Em All

const en=battleEnemy; if(en.hp<=0){ - // Victory! + // Check trainer next creature + if(battleIsTrainer){ + battleEnemyIndex++; + if(battleEnemyIndex=pc.expNext){ - pc.exp-=pc.expNext; - pc.level++; - pc.maxHp=Math.floor(creatureDB[pc.id].baseHp*(1+pc.level*0.1)); - pc.hp=pc.maxHp; - pc.atk=Math.floor(creatureDB[pc.id].baseAtk*(1+pc.level*0.08)); - pc.def=Math.floor(creatureDB[pc.id].baseDef*(1+pc.level*0.08)); - pc.spd=Math.floor(creatureDB[pc.id].baseSpd*(1+pc.level*0.06)); - pc.expNext=pc.level*25+50; - pc.availMoves=creatureDB[pc.id].moves.slice(0,Math.min(4,1+Math.floor(pc.level/5))); - setTimeout(()=>setBattleText(`${pc.name} grew to level ${pc.level}!`),1500); + grantExp(pc,expGain); + let rewardText=''; + if(battleIsTrainer&&battleTrainer){ + money+=battleTrainer.reward; + battleTrainer.defeated=true; + rewardText=` Got $${battleTrainer.reward}!`; } + setBattleText(`${en.name} fainted! +${expGain} EXP!${rewardText}`); setTimeout(endBattle,2500); }else if(pc.hp<=0){ setBattleText(`${pc.name} fainted!`); - // Check for next creature const next=party.find(p=>p.hp>0); if(next){ const idx=party.indexOf(next); @@ -531,63 +1078,296 @@

Gotta Route 'Em All

setBattleText('All your creatures fainted! You blacked out...'); setTimeout(()=>{ endBattle(); - party.forEach(p=>{p.hp=Math.floor(p.maxHp*0.5);}); - player.x=20;player.y=15; + party.forEach(p=>{p.hp=Math.floor(p.maxHp*0.5);p.availMoves.forEach(m=>{m.pp=m.maxPP;});}); + player.x=25;player.y=20; },2000); },1500); } } } +function grantExp(creature,amount){ + creature.exp+=amount; + while(creature.exp>=creature.expNext&&creature.level<50){ + creature.exp-=creature.expNext; + creature.level++; + const base=creatureDB[creature.id]; + creature.maxHp=Math.floor(base.baseHp*(1+creature.level*0.1)); + creature.hp=creature.maxHp; + creature.atk=Math.floor(base.baseAtk*(1+creature.level*0.08)); + creature.def=Math.floor(base.baseDef*(1+creature.level*0.08)); + creature.spd=Math.floor(base.baseSpd*(1+creature.level*0.06)); + creature.expNext=creature.level*25+50; + // Learn new moves at every 5 levels + const numMoves=Math.min(4,1+Math.floor(creature.level/5)); + while(creature.availMoves.length{el.style.display='none';},1600); +} + function useBall(){ - if(catchBalls<=0){setBattleText("No catch balls left!");return;} - catchBalls--; + if(battleLocked) return; + if(battleIsTrainer){setBattleText("Can't catch a trainer's creature!");return;} + if(inventory.catchBall<=0){setBattleText("No catch balls left!");return;} + battleLocked=true; + inventory.catchBall--; const en=battleEnemy; const catchRate=Math.max(5,50-en.level*2+(1-en.hp/en.maxHp)*40); const caught=Math.random()*100{ + anim.style.animation='none';anim.offsetHeight;anim.style.animation='shake 0.5s ease-in-out'; + setTimeout(async()=>{ if(caught){ anim.textContent='\u2728'; setBattleText(`Gotcha! ${en.name} was caught!`); + markCaught(en.id); if(party.length<6){ - en.hp=en.maxHp; + en.hp=en.maxHp;en.availMoves.forEach(m=>{m.pp=m.maxPP;}); party.push(en); } - setTimeout(()=>{anim.style.display='none';endBattle();},1500); + setTimeout(()=>{anim.style.display='none';endBattle();battleLocked=false;},1500); }else{ anim.style.display='none'; setBattleText(`Oh no! ${en.name} broke free!`); - setTimeout(()=>enemyTurn().then(()=>checkBattleEnd()),1000); + await delay(800); + await enemyTurn(); + checkBattleEnd(); + battleLocked=false; } },1200); } function tryRun(){ + if(battleLocked) return; + if(battleIsTrainer){setBattleText("Can't run from a trainer battle!");return;} + battleLocked=true; const chance=party[0].spd>battleEnemy.spd?80:50; if(Math.random()*100{endBattle();battleLocked=false;},1000); }else{ setBattleText("Can't escape!"); - setTimeout(()=>enemyTurn().then(()=>checkBattleEnd()),1000); + setTimeout(async()=>{ + await enemyTurn(); + checkBattleEnd(); + battleLocked=false; + },1000); } } -function showPartyInBattle(){ - let text='YOUR PARTY:\n'; - party.forEach((p,i)=>{ - text+=`${i+1}. ${p.icon} ${p.name} Lv${p.level} HP:${p.hp}/${p.maxHp}${p.hp<=0?' [FAINTED]':''}\n`; - }); - setBattleText(text); -} - function endBattle(){ state='overworld'; document.getElementById('battle-ui').style.display='none'; - updateBattleUI(); + document.getElementById('moves-panel').style.display='none'; + document.getElementById('bag-panel').style.display='none'; + document.getElementById('party-battle-panel').style.display='none'; +} + +// --- DIALOG SYSTEM (Heal Center, Shop, Pokedex, Bag) --- +function showDialog(content){ + state='dialog'; + const overlay=document.getElementById('dialog-overlay'); + const box=document.getElementById('dialog-box'); + box.innerHTML=content; + overlay.style.display='flex'; +} + +function closeDialog(){ + state='overworld'; + document.getElementById('dialog-overlay').style.display='none'; +} + +function showHealingCenter(){ + party.forEach(p=>{p.hp=p.maxHp;p.availMoves.forEach(m=>{m.pp=m.maxPP;});}); + let html=`

\u2764\uFE0F HEALING CENTER

`; + html+=`

Nurse: "Welcome! Let me heal your creatures..."

`; + html+=`

...

`; + html+=`

All your creatures have been restored to full health!

`; + html+=`
`; + party.forEach(p=>{ + html+=`
+ ${p.icon} ${p.name} Lv${p.level} + HP: ${p.hp}/${p.maxHp} \u2714 +
`; + }); + html+=`
`; + html+=`

Press SPACE or ESC to leave

`; + showDialog(html); +} + +function showShop(){ + const renderShop=()=>{ + let html=`

\uD83D\uDED2 BLACKROAD MART

`; + html+=`

Your money: $${money}

`; + html+=`
`; + + const shopItems=[ + {key:'catchBall',name:'Catch Ball',price:200,desc:'For catching creatures',icon:'\u26AA'}, + {key:'potion',name:'Potion',price:100,desc:'Heals 20 HP',icon:'\uD83E\uDDF4'}, + {key:'superPotion',name:'Super Potion',price:250,desc:'Heals 50 HP',icon:'\uD83C\uDF76'} + ]; + shopItems.forEach(si=>{ + html+=`
+ ${si.icon} ${si.name} + $${si.price} + Own: ${inventory[si.key]} +
`; + }); + html+=`
`; + html+=`

Click to buy | Press SPACE or ESC to leave

`; + showDialog(html); + }; + renderShop(); +} + +function buyItem(key,price){ + if(money>=price){ + money-=price; + inventory[key]++; + showShop(); // re-render + } else { + // Flash money text + const box=document.getElementById('dialog-box'); + const p=box.querySelector('p'); + if(p) p.style.color='#d32f2f'; + setTimeout(()=>{if(p)p.style.color='#f5a623';},500); + } +} + +function showPokedex(){ + let caught=0,seen=0; + creatureDB.forEach(c=>{ + if(pokedex[c.id]){ + if(pokedex[c.id].caught) caught++; + if(pokedex[c.id].seen) seen++; + } + }); + + let html=`

\uD83D\uDCD6 CREATURE INDEX

`; + html+=`

Caught: ${caught}/${creatureDB.length} | Seen: ${seen}/${creatureDB.length} | ${Math.floor(caught/creatureDB.length*100)}% Complete

`; + html+=`
`; + creatureDB.forEach(c=>{ + const entry=pokedex[c.id]; + let cls='unseen'; + if(entry&&entry.caught) cls='caught'; + else if(entry&&entry.seen) cls='seen'; + html+=`
+ ${cls==='unseen'?'?':c.icon} + ${cls==='unseen'?'???':c.name} +
`; + }); + html+=`
`; + html+=`
`; + html+=`

Click a creature for details | Press TAB or ESC to close

`; + showDialog(html); +} + +function showDexDetail(id){ + const entry=pokedex[id]; + const c=creatureDB[id]; + const area=document.getElementById('dex-detail-area'); + if(!area) return; + + if(!entry||!entry.caught){ + area.innerHTML=`

${entry&&entry.seen?c.name:'???'}

${entry&&entry.seen?'Seen but not yet caught.':'Not yet encountered.'}

`; + return; + } + + let html=`
`; + html+=`

${c.icon} ${c.name}

`; + html+=`
Type${c.type.toUpperCase()}
`; + html+=`
Base HP${c.baseHp}
`; + html+=`
Base ATK${c.baseAtk}
`; + html+=`
Base DEF${c.baseDef}
`; + html+=`
Base SPD${c.baseSpd}
`; + html+=`

Moves:

`; + c.moves.forEach((m,i)=>{ + html+=`
${m.name} (${m.type})PWR:${m.power} ACC:${m.acc} (Lv ${i*5||1})
`; + }); + html+=`
`; + area.innerHTML=html; +} + +function showBagOverworld(){ + let html=`

\uD83C\uDF92 BAG

`; + html+=`

Money: $${money}

`; + html+=`
`; + html+=`
\u26AA Catch Ballx${inventory.catchBall}
`; + html+=`
\uD83E\uDDF4 Potion (20HP)x${inventory.potion}
`; + html+=`
\uD83C\uDF76 Super Potion (50HP)x${inventory.superPotion}
`; + html+=`
`; + + html+=`

PARTY:

`; + party.forEach(p=>{ + html+=`
+ ${p.icon} ${p.name} Lv${p.level} + HP: ${p.hp}/${p.maxHp} +
`; + }); + + html+=`

Press I or ESC to close

`; + showDialog(html); +} + +// --- TRAINER BATTLE TRIGGER --- +function checkTrainerEncounter(){ + trainers.forEach(t=>{ + if(t.defeated) return; + const dist=Math.abs(player.x-t.x)+Math.abs(player.y-t.y); + if(dist<=2){ + // Trigger trainer battle + const intro=document.getElementById('trainer-intro'); + const introText=document.getElementById('trainer-intro-text'); + introText.textContent=t.msg; + intro.style.display='flex'; + setTimeout(()=>{ + intro.style.display='none'; + startBattle(t.party[0],true,t); + },2000); + } + }); +} + +// --- INTERACTION (SPACE key) --- +function interact(){ + if(state!=='overworld') return; + + // Check adjacent tiles for healing center or shop + const dirs=[[0,-1],[0,1],[-1,0],[1,0],[0,0]]; + for(const [dx,dy] of dirs){ + const cx=player.x+dx,cy=player.y+dy; + if(cy>=0&&cy=0&&cx=29) msg='EAST: Deep Forest\nMedium-level creatures roam here.'; + showDialog(`

\uD83D\uDDFA\uFE0F SIGN

${msg}

Press SPACE or ESC to close

`); + return; + } + } + } } // --- OVERWORLD MOVEMENT --- @@ -606,23 +1386,28 @@

Gotta Route 'Em All

const nx=player.x+dx,ny=player.y+dy; if(nx<0||nx>=MCOLS||ny<0||ny>=MROWS)return; const tile=mapData[ny][nx]; - if(tile===5||tile===3||tile===4)return; // blocked + // Block: trees, water, buildings + if(tile===TILE_TREE||tile===TILE_WATER||tile===TILE_HEAL||tile===TILE_SHOP||tile===TILE_HOUSE)return; + + // Check trainer collision + const trainerBlock=trainers.find(t=>!t.defeated&&t.x===nx&&t.y===ny); + if(trainerBlock) return; player.x=nx;player.y=ny; steps++;moveCD=6; + player.animFrame++; - // Wild encounter in tall grass - if(tile===1&&Math.random()<0.15){ - const wild=getWildCreature(); - setTimeout(()=>startBattle(wild),200); + // Wild encounters in tall grass variants + const isGrass=(tile===TILE_TALL||tile===TILE_FOREST_TALL||tile===TILE_CAVE_TALL||tile===TILE_LAKE_TALL); + if(isGrass&&Math.random()<0.15){ + const wild=getWildCreature(nx,ny); + markSeen(wild.id); + setTimeout(()=>startBattle(wild,false,null),200); + return; } - // Heal at town center - if(nx>=22&&nx<=23&&ny>=12&&ny<=13){ - party.forEach(p=>{p.hp=p.maxHp;}); - catchBalls=Math.max(catchBalls,5); - setBattleText(''); - } + // Trainer encounter check + checkTrainerEncounter(); } // --- MAIN LOOP --- @@ -642,7 +1427,30 @@

Gotta Route 'Em All

// --- INPUT --- document.addEventListener('keydown',e=>{ keys[e.key]=true;keys[e.key.toLowerCase()]=true; - if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(e.key)) e.preventDefault(); + if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' ','Tab'].includes(e.key)) e.preventDefault(); + + // SPACE - interact + if(e.key===' '){ + if(state==='dialog'){closeDialog();} + else if(state==='overworld'){interact();} + } + + // ESC - close dialogs + if(e.key==='Escape'){ + if(state==='dialog'){closeDialog();} + } + + // TAB - Pokedex + if(e.key==='Tab'){ + if(state==='dialog'){closeDialog();} + else if(state==='overworld'){showPokedex();} + } + + // I - Bag + if(e.key==='i'||e.key==='I'){ + if(state==='dialog'){closeDialog();} + else if(state==='overworld'){showBagOverworld();} + } }); document.addEventListener('keyup',e=>{keys[e.key]=false;keys[e.key.toLowerCase()]=false;}); diff --git a/blackroad-pixel/games/stardew/index.html b/blackroad-pixel/games/stardew/index.html index c2aaec2..648e891 100644 --- a/blackroad-pixel/games/stardew/index.html +++ b/blackroad-pixel/games/stardew/index.html @@ -8,23 +8,24 @@ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); *{margin:0;padding:0;box-sizing:border-box} body{background:#1a1a2e;display:flex;align-items:center;justify-content:center;min-height:100vh;font-family:'Press Start 2P',monospace;overflow:hidden} -#game-wrapper{position:relative;width:800px;height:600px;border:3px solid #2a2a4a;border-radius:8px;overflow:hidden;box-shadow:0 0 40px rgba(74,144,217,0.2)} +#game-wrapper{position:relative;width:960px;height:704px;border:3px solid #2a2a4a;border-radius:8px;overflow:hidden;box-shadow:0 0 40px rgba(74,144,217,0.2)} canvas{display:block;image-rendering:pixelated;image-rendering:crisp-edges;cursor:crosshair} #ui-overlay{position:absolute;top:0;left:0;width:100%;pointer-events:none;z-index:10} -#top-bar{display:flex;justify-content:space-between;padding:8px 12px;background:rgba(0,0,0,0.7);pointer-events:auto} +#top-bar{display:flex;justify-content:space-between;padding:8px 12px;background:rgba(0,0,0,0.7);pointer-events:auto;flex-wrap:wrap;gap:4px} #top-bar span{font-size:8px;color:#e0e0e0} .bar-label{color:#8888aa!important;font-size:7px!important} -#energy-bar,#money-display,#day-display,#time-display,#season-display{display:flex;align-items:center;gap:4px} +#energy-bar,#money-display,#day-display,#time-display,#season-display,#weather-display{display:flex;align-items:center;gap:4px} #energy-fill{width:80px;height:8px;background:#333;border-radius:4px;overflow:hidden;border:1px solid #555} #energy-fill-inner{height:100%;background:linear-gradient(90deg,#7ed321,#4caf50);transition:width 0.3s} #inventory-bar{position:absolute;bottom:0;left:0;width:100%;background:rgba(0,0,0,0.8);display:flex;justify-content:center;gap:4px;padding:8px;pointer-events:auto} .inv-slot{width:48px;height:48px;background:rgba(255,255,255,0.08);border:2px solid #2a2a4a;border-radius:4px;display:flex;flex-direction:column;align-items:center;justify-content:center;cursor:pointer;position:relative;transition:all 0.15s} .inv-slot:hover{border-color:#00d4ff;background:rgba(0,212,255,0.1)} .inv-slot.selected{border-color:#ff6b9d;box-shadow:0 0 8px rgba(255,107,157,0.4)} -.inv-slot .item-icon{font-size:20px;line-height:1} +.inv-slot .item-icon{font-size:18px;line-height:1} .inv-slot .item-count{font-size:6px;color:#ccc;position:absolute;bottom:2px;right:4px} +.inv-slot .item-quality{font-size:8px;position:absolute;top:1px;left:2px} #tooltip{position:absolute;background:rgba(0,0,0,0.9);border:1px solid #ff6b9d;border-radius:4px;padding:6px 10px;font-size:7px;color:#e0e0e0;pointer-events:none;display:none;z-index:20;white-space:nowrap} -#shop-modal{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;border:3px solid #ff6b9d;border-radius:8px;padding:16px;display:none;z-index:30;pointer-events:auto;min-width:300px} +#shop-modal{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;border:3px solid #ff6b9d;border-radius:8px;padding:16px;display:none;z-index:30;pointer-events:auto;min-width:340px;max-height:500px;overflow-y:auto} #shop-modal h3{font-size:10px;color:#ff6b9d;margin-bottom:12px;text-align:center} .shop-item{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;background:rgba(255,255,255,0.05);border-radius:4px;margin-bottom:4px;cursor:pointer;transition:background 0.15s} .shop-item:hover{background:rgba(255,107,157,0.15)} @@ -32,23 +33,40 @@ .shop-item .price{color:#f5a623} #shop-close{font-size:8px;color:#ff6b9d;background:none;border:1px solid #ff6b9d;padding:4px 12px;border-radius:4px;cursor:pointer;margin-top:8px;display:block;margin-left:auto;margin-right:auto} #shop-close:hover{background:rgba(255,107,157,0.2)} -#msg-box{position:absolute;bottom:70px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);border:2px solid #4a90d9;border-radius:6px;padding:8px 16px;font-size:8px;color:#e0e0e0;display:none;z-index:15;pointer-events:none;text-align:center;max-width:400px} +#msg-box{position:absolute;bottom:76px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.85);border:2px solid #4a90d9;border-radius:6px;padding:8px 16px;font-size:8px;color:#e0e0e0;display:none;z-index:15;pointer-events:none;text-align:center;max-width:500px} +#dialogue-box{position:absolute;bottom:76px;left:50%;transform:translateX(-50%);background:rgba(13,13,26,0.95);border:3px solid #c44dff;border-radius:8px;padding:12px 18px;display:none;z-index:25;pointer-events:none;text-align:left;width:480px} +#dialogue-name{font-size:9px;color:#ff6b9d;margin-bottom:6px} +#dialogue-text{font-size:7px;color:#e0e0e0;line-height:1.6} +#dialogue-hearts{font-size:7px;color:#888;margin-top:6px} +#dialogue-hint{font-size:6px;color:#555;margin-top:4px;text-align:right} +#fishing-overlay{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:rgba(13,13,26,0.95);border:3px solid #00d4ff;border-radius:8px;padding:20px;display:none;z-index:35;pointer-events:auto;text-align:center;width:240px} +#fishing-overlay h3{font-size:9px;color:#00d4ff;margin-bottom:10px} +#fishing-bar-container{width:30px;height:200px;background:#1a1a2e;border:2px solid #2a2a4a;border-radius:4px;margin:0 auto;position:relative;overflow:hidden} +#fishing-target{position:absolute;width:100%;height:40px;background:rgba(0,212,255,0.3);border:1px solid #00d4ff;border-radius:2px;transition:top 0.1s} +#fishing-cursor{position:absolute;width:100%;height:6px;background:#ff6b9d;border-radius:2px;left:0} +#fishing-fish-icon{font-size:16px;position:absolute;right:-30px;top:50%;transform:translateY(-50%)} +#fishing-hint{font-size:7px;color:#888;margin-top:10px} +#fishing-result{font-size:8px;color:#7ed321;margin-top:8px;display:none} +#npc-shop-modal{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#1a1a2e;border:3px solid #c44dff;border-radius:8px;padding:16px;display:none;z-index:30;pointer-events:auto;min-width:340px} +#npc-shop-modal h3{font-size:10px;color:#c44dff;margin-bottom:12px;text-align:center} +#npc-shop-items{} +#npc-shop-close{font-size:8px;color:#c44dff;background:none;border:1px solid #c44dff;padding:4px 12px;border-radius:4px;cursor:pointer;margin-top:8px;display:block;margin-left:auto;margin-right:auto} #title-screen{position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(180deg,#0d1b2a 0%,#1b4332 40%,#2d6a4f 100%);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:50} #title-screen h1{font-size:16px;color:#7ed321;text-shadow:0 0 20px rgba(126,211,33,0.5);margin-bottom:8px} #title-screen h2{font-size:8px;color:#f5a623;margin-bottom:30px} #title-screen p{font-size:7px;color:#8888aa;margin-bottom:4px} #start-btn{font-family:'Press Start 2P',monospace;font-size:10px;color:#fff;background:linear-gradient(135deg,#ff6b9d,#c44dff);border:none;padding:12px 32px;border-radius:6px;cursor:pointer;margin-top:20px;transition:transform 0.15s} #start-btn:hover{transform:scale(1.05)} -.pixel-tree{position:absolute;font-size:40px;opacity:0.3}
- +
SEASON:SPRING
DAY:1
+
WEATHER:SUNNY
TIME:6:00 AM
ENERGY:
$500
@@ -57,18 +75,40 @@
+
+
+
+
+
SPACE to close / Gift selected crop
+

BLACKROAD SEED SHOP

+
+

FARMER SAGE'S ANIMALS

+
+ +
+
+

FISHING!

+
+
+
+
+ +
Press SPACE to catch!
+
+

BLACKROAD HARVEST

A Pixel Farming World

WASD / Arrows - Move

-

SPACE - Use Tool / Interact

-

1-8 - Select Item

-

E - Open Shop (near store)

+

SPACE - Use Tool / Interact / Talk to NPCs

+

1-0 - Select Item (10 slots)

+

E - Open Shop (near store) / NPC shops

+

F - Fish (equip rod near water)

@@ -76,15 +116,15 @@

A Pixel Farming World