From 72b10d87ae8279746aef131b9d311a29adebe0d0 Mon Sep 17 00:00:00 2001 From: Erzangel Date: Fri, 29 May 2026 11:11:44 +0200 Subject: [PATCH] feat: init commit --- .idea/.gitignore | 5 + .idea/copilotDiffState.xml | 27 + .idea/inspectionProfiles/Project_Default.xml | 6 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/web-adventure.iml | 10 + INDEX.md | 310 +++++ PROJECT_SUMMARY.md | 297 +++++ README.md | 156 +++ SETUP.md | 305 +++++ __pycache__/backend.cpython-314.pyc | Bin 0 -> 37254 bytes backend.py | 602 +++++++++ index.html | 1120 +++++++++++++++++ launcher.py | 65 + main.py | 16 + quickstart.py | 92 ++ requirements.txt | 11 + start.sh | 25 + verify.py | 97 ++ 20 files changed, 3165 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/copilotDiffState.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/web-adventure.iml create mode 100644 INDEX.md create mode 100644 PROJECT_SUMMARY.md create mode 100644 README.md create mode 100644 SETUP.md create mode 100644 __pycache__/backend.cpython-314.pyc create mode 100644 backend.py create mode 100644 index.html create mode 100755 launcher.py create mode 100644 main.py create mode 100644 quickstart.py create mode 100644 requirements.txt create mode 100755 start.sh create mode 100644 verify.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/copilotDiffState.xml b/.idea/copilotDiffState.xml new file mode 100644 index 0000000..47e624d --- /dev/null +++ b/.idea/copilotDiffState.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..fff029c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..55b85b5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/web-adventure.iml b/.idea/web-adventure.iml new file mode 100644 index 0000000..537283c --- /dev/null +++ b/.idea/web-adventure.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/INDEX.md b/INDEX.md new file mode 100644 index 0000000..d39a58c --- /dev/null +++ b/INDEX.md @@ -0,0 +1,310 @@ +# šŸŽ® Web Adventure - Complete Documentation Index + +## šŸ“– Start Here + +If you're new to Web Adventure, start with: +1. **This file** - You're reading it! šŸ“ +2. **SETUP.md** - Comprehensive setup and gameplay guide +3. **quickstart.py** - Run this for an interactive start +4. **launcher.py** - Use this to start playing + +## šŸ“š Documentation Files + +### For Getting Started +- **quickstart.py** - Interactive quick start guide + - Run: `python quickstart.py` + - Launches the game automatically + +- **verify.py** - Verification script + - Run: `python verify.py` + - Checks all components are installed + - Good for troubleshooting + +### For Setup & Installation +- **SETUP.md** - Complete setup guide (⭐ Most detailed) + - Installation steps + - How to play guide + - Control summary + - Troubleshooting + - Customization options + +- **README.md** - User documentation + - Features overview + - Installation + - How to play basics + - File structure + +- **PROJECT_SUMMARY.md** - Implementation overview + - What's been built + - Technical architecture + - Customization examples + - Testing checklist + +## šŸš€ How to Run + +### Method 1: Quickstart (🌟 Recommended) +```bash +cd /Users/luka/dev/me/web-adventure +python quickstart.py +``` +- Interactive guide +- Auto-launches game + +### Method 2: Launcher (Simple) +```bash +cd /Users/luka/dev/me/web-adventure +python launcher.py +``` +- Starts backend +- Opens browser automatically +- One command solution + +### Method 3: Manual (Advanced) +```bash +# Terminal 1 - Start server +cd /Users/luka/dev/me/web-adventure +python -m uvicorn backend:app --reload --host 0.0.0.0 --port 8000 + +# Terminal 2 - Open browser +# Open: file:///Users/luka/dev/me/web-adventure/index.html +``` + +### Method 4: Shell Script +```bash +cd /Users/luka/dev/me/web-adventure +./start.sh +``` + +## šŸŽ® Game Features Overview + +### Player Actions +- **Move** - Navigate the world (4 directions) +- **Attack** - Fight monsters and boss +- **Gather** - Collect resources +- **Build** - Place structures + +### Game Mechanics +- **Multiplayer** - Play with friends in real-time +- **Leveling** - Gain experience, become stronger +- **Combat** - Fight monsters, attack boss +- **Resources** - Gather wood and stone +- **Structures** - Build 3 types (house, farm, tower) +- **Boss Battle** - 30-minute challenge +- **Reset** - World resets if time runs out + +## šŸ“‚ Project Structure + +``` +web-adventure/ +│ +ā”œā”€ā”€ šŸŽ® GAME FILES +│ ā”œā”€ā”€ backend.py - FastAPI server (game logic) +│ ā”œā”€ā”€ index.html - Frontend (3D game + UI) +│ +ā”œā”€ā”€ šŸš€ STARTUP SCRIPTS +│ ā”œā”€ā”€ launcher.py - Auto-start (recommended) +│ ā”œā”€ā”€ quickstart.py - Interactive guide +│ ā”œā”€ā”€ start.sh - Shell script +│ ā”œā”€ā”€ verify.py - Verification tool +│ +ā”œā”€ā”€ šŸ“š DOCUMENTATION +│ ā”œā”€ā”€ README.md - Main documentation +│ ā”œā”€ā”€ SETUP.md - Complete setup guide +│ ā”œā”€ā”€ PROJECT_SUMMARY.md - Implementation details +│ ā”œā”€ā”€ INDEX.md - This file +│ +ā”œā”€ā”€ āš™ļø CONFIGURATION +│ └── requirements.txt - Python dependencies +│ +└── šŸ“„ OTHER + └── main.py - Old placeholder (unused) +``` + +## šŸŽÆ Quick Reference + +### Common Commands +```bash +# Check if everything is installed +python verify.py + +# Read complete setup guide +cat SETUP.md + +# Read implementation details +cat PROJECT_SUMMARY.md + +# Start the game (recommended) +python launcher.py + +# Start with interactive guide +python quickstart.py +``` + +### Configuration + +#### Game Duration (in backend.py) +```python +GAME_DURATION = 30 * 60 # Change to 15 * 60 for 15 minutes +``` + +#### Player Colors (in index.html) +```javascript +const colors = ['#FF6B6B', '#4ECDC4', ...]; // Add more colors here +``` + +#### Monster Spawn Rate (in backend.py) +```python +MONSTER_SPAWN_RATE = 0.1 # Increase for more monsters +``` + +## šŸ› Troubleshooting + +### Issue: Port 8000 already in use +```bash +# Find and kill process +lsof -i :8000 +kill -9 +``` + +### Issue: Modules not installed +```bash +pip install -r requirements.txt +``` + +### Issue: Browser won't load +- Check backend is running: http://localhost:8000/docs +- Clear cache and refresh +- Try different browser + +### Issue: Game lags +- Fewer players online +- Close other browser tabs +- Check internet connection + +## šŸŽ“ Learning Path + +1. **New to the project?** + - Read SETUP.md for overview + - Run quickstart.py + - Play the game! + +2. **Want to understand the code?** + - Read PROJECT_SUMMARY.md for architecture + - Check backend.py for game logic + - Check index.html for frontend + +3. **Want to customize?** + - See customization section in SETUP.md + - Edit constants in backend.py + - Modify colors in index.html + +4. **Want to extend?** + - Add new structures in backend.py + - Add new resources + - Create new monsters + - Add new game mechanics + +## šŸ“Š Game Statistics + +- **Grid Size**: 500x500 tiles +- **Game Duration**: 30 minutes +- **Starting Level**: 1 +- **Starting Health**: 100 +- **Starting Action Points**: 20 +- **Max Monsters**: 50 on map +- **Player Colors**: 8 default (customize for more) +- **Structures**: 3 types (house, farm, tower) +- **Resources**: 2 types (wood, stone) +- **Real-time Update Rate**: 10 Hz + +## šŸ” System Requirements + +- **Python**: 3.8 or higher +- **Browser**: Modern (Chrome, Firefox, Safari, Edge) +- **OS**: macOS, Linux, or Windows +- **RAM**: 512 MB minimum +- **Disk**: 50 MB for project + dependencies +- **Internet**: Not required (local multiplayer) + +## šŸŽ‰ What You Have + +āœ… **Complete Backend** +- FastAPI server +- WebSocket real-time updates +- Complete game logic +- Player management +- Monster spawning +- Combat system +- Leveling system + +āœ… **Complete Frontend** +- 3D graphics (Three.js) +- Login system +- Game UI +- Control panels +- Real-time synchronization +- Game over screen + +āœ… **Complete Game** +- Multiplayer gameplay +- Resource gathering +- Structure building +- Combat and leveling +- Time-based challenges +- Boss battle system + +āœ… **Documentation** +- Setup guides +- Gameplay guides +- API documentation +- Customization examples +- Architecture overview + +## šŸŽ® Ready to Play? + +1. Run `python launcher.py` +2. Enter your username +3. Choose your color +4. Click "Enter the World" +5. Start exploring and fighting! + +## šŸ“ž Quick Help + +**Something not working?** +1. Check SETUP.md troubleshooting section +2. Run `python verify.py` to check installation +3. Review error messages in browser console (F12) +4. Check terminal output from backend + +**Want to customize?** +- See customization section in SETUP.md +- Edit constants in backend.py +- Modify themes in index.html + +**Want to learn more?** +- Read PROJECT_SUMMARY.md for full details +- Review backend.py for game logic (well-commented) +- Review index.html for frontend code (well-commented) + +## 🌟 Tips for Playing + +1. **Early Game**: Gather resources and build structures +2. **Mid Game**: Kill monsters to gain levels +3. **Late Game**: Focus on boss with other players +4. **Team Strategy**: Place farms for faster action point regeneration +5. **Timing**: Watch the timer and coordinate final boss push + +## šŸš€ Let's Go! + +You're all set! Start with: +```bash +python launcher.py +``` + +Good luck, adventurer! šŸ—”ļøāš”ļøšŸ›”ļø + +--- + +*Last Updated: May 28, 2026* +*Web Adventure v1.0 - Community RPG* + diff --git a/PROJECT_SUMMARY.md b/PROJECT_SUMMARY.md new file mode 100644 index 0000000..1200d59 --- /dev/null +++ b/PROJECT_SUMMARY.md @@ -0,0 +1,297 @@ +# šŸŽ® Web Adventure - Implementation Summary + +## āœ… What's Been Built + +Your community-based multiplayer RPG web application is complete with all core features implemented! + +### Backend Features ✨ +- āœ… FastAPI server with WebSocket support for real-time multiplayer +- āœ… Complete game world state management +- āœ… Player authentication and session management +- āœ… Movement system with action point cost +- āœ… Monster spawning and combat system +- āœ… Boss monster with health tracking and progress display +- āœ… Resource gathering (wood/stone from trees/mountains) +- āœ… Structure building system (houses, farms, guard towers) +- āœ… Experience and leveling system +- āœ… Action point regeneration with farm bonuses +- āœ… 30-minute game rounds with automatic reset +- āœ… Real-time game tick updates (10 Hz) + +### Frontend Features ✨ +- āœ… Beautiful login screen with color selection +- āœ… 3D game world using Three.js WebGL rendering +- āœ… Real-time player rendering (colored spheres) +- āœ… Monster visualization (red cubes) +- āœ… Boss visualization (large red cube with special styling) +- āœ… Structure rendering (houses, farms, towers) +- āœ… Resource rendering (trees, mountains) +- āœ… Dynamic UI panels with player stats +- āœ… Inventory display +- āœ… Action buttons for all game actions +- āœ… Game timer and status display +- āœ… Boss health bar visualization +- āœ… Game over screen with final stats +- āœ… Responsive design + +### Game Mechanics ✨ + +**Player Progression** +- Level system (1-based, increases with experience) +- Experience gained from defeating monsters +- Attack/Defense stats +- Movement capacity (tiles per action) +- Gathering capacity (resources per action) +- Max health, current health tracking +- Action points system (20 start, regenerate 1/min) + +**Combat** +- Monster spawning with varied levels (1-5) +- Random monster placement on map +- Attack/defense based damage calculation +- Boss monster with high health pool +- Boss appears when level 10+ players present +- Victory condition: boss defeated before time expires + +**Resources & Building** +- Two resource types: Wood, Stone +- 50 randomized resources on map +- Three structure types: + - **House**: +20 max health + - **Farm**: +2 action regeneration + - **Guard Tower**: +2 defense +- Nearby structures provide bonuses to players +- Resource costs for each structure type + +**Multiplayer** +- All players share the same game world +- Real-time position updates +- WebSocket-based live synchronization +- Player color selection for identification +- Shared resources and structures +- Collaborative boss fight system + +**Time Management** +- 30-minute game rounds +- Global timer visible to all players +- Automatic world reset on timer expiration +- Player stats reset with world +- Continuous action point regeneration + +## šŸ“ Project Files + +``` +web-adventure/ +ā”œā”€ā”€ backend.py # Main game server (450+ lines) +│ # - FastAPI app +│ # - Game world state +│ # - WebSocket handler +│ # - REST API endpoints +│ # - Game logic & mechanics +│ +ā”œā”€ā”€ index.html # Complete frontend (600+ lines) +│ # - Login interface +│ # - 3D game rendering with Three.js +│ # - UI panels and controls +│ # - WebSocket client +│ # - API communication +│ +ā”œā”€ā”€ launcher.py # Easy startup script +│ # - Auto-starts server +│ # - Opens game in browser +│ +ā”œā”€ā”€ start.sh # Bash startup script +│ # - Alternative launcher +│ +ā”œā”€ā”€ requirements.txt # Python dependencies +│ # - FastAPI +│ # - Uvicorn +│ # - WebSockets +│ # - PyJWT +│ +ā”œā”€ā”€ README.md # User documentation +ā”œā”€ā”€ SETUP.md # Comprehensive setup guide +└── PROJECT_SUMMARY.md # This file +``` + +## šŸš€ Running the Game + +### Preferred Method +```bash +cd /Users/luka/dev/me/web-adventure +python launcher.py +``` +*This automatically starts the backend and opens the game in your browser.* + +### Manual Method +```bash +# Terminal 1 +cd /Users/luka/dev/me/web-adventure +python -m uvicorn backend:app --reload --host 0.0.0.0 --port 8000 + +# Terminal 2 +# Open file:///Users/luka/dev/me/web-adventure/index.html in browser +``` + +## šŸŽ® Game Flow + +1. **Login Screen** + - Enter username + - Select color + - Click "Enter the World" + +2. **Initialization** + - Player spawns at random location + - WebSocket connection established + - Real-time game updates begin + - Can see other players, monsters, structures + +3. **Gameplay Loop** + - Move around the map + - Gather resources from trees/mountains + - Build structures for team bonuses + - Fight monsters to level up + - Gain experience points + - Coordinate with other players + +4. **Boss Phase** + - When level 10+ players join, boss spawns + - Large red cube appears on map + - Boss health bar displays at top center + - Players attack to damage boss + - Boss health decreases as players attack + +5. **Victory/Defeat** + - **Victory**: Boss defeated before timer = 0 + - Large experience bonuses + - Level up rewards + - Game ends successfully + - **Defeat**: Timer reaches 0 + - All players return to level 1 + - Inventory clears + - World resets + - New round begins + +## šŸ“Š Key Configurations + +**Game Duration**: 30 minutes (1800 seconds) +**Grid Size**: 500x500 tiles +**Action Points**: 20 max, regenerate 1/minute +**Monster Spawn**: 10% chance per tick +**Boss Difficulty**: Based on average player level +**Minimum Boss Level**: 10 + +## šŸ”§ Customization Examples + +### Change Game Duration to 15 Minutes +In `backend.py`, line ~8: +```python +GAME_DURATION = 15 * 60 # was 30 * 60 +``` + +### Add More Colors +In `index.html`, around line 600: +```javascript +const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', + '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E2', + '#FF69B4', '#00CED1']; // Added more colors +``` + +### Increase Monster Spawn Rate +In `backend.py`, line ~13: +```python +MONSTER_SPAWN_RATE = 0.2 # was 0.1 (double spawn rate) +``` + +### Make Boss Easier or Harder +In `backend.py`, line ~15: +```python +BOSS_HEALTH_BASE = 500 # was 1000 (easier) +BOSS_HEALTH_BASE = 2000 # was 1000 (harder) +``` + +## šŸŽÆ Gameplay Tips for Players + +1. **Early Game**: Gather resources and build structures near your spawn +2. **Mid Game**: Level up by defeating monsters, coordinate with teammates +3. **Late Game**: Focus fire on boss with other players before timer runs out +4. **Strategy**: Farm placement = faster action point regeneration = more actions +5. **Teamwork**: Multiple players attacking boss = victory + +## šŸ“ˆ Scalability Features + +The game is designed to handle: +- Multiple concurrent players (tested with many) +- Real-time WebSocket synchronization +- Dynamic resource generation +- Monster spawning and culling +- Structure building by multiple players +- Efficient game state broadcasting + +## šŸ” Architecture Highlights + +**Backend Design** +- Single `GameWorld` instance manages all state +- Dataclasses for clean data structures +- Async WebSocket handling for real-time updates +- State updates at 10 Hz game tick +- REST API for one-time actions +- WebSocket for continuous updates + +**Frontend Design** +- Three.js 3D scene with optimized rendering +- Real-time UI updates from WebSocket +- Separate render loop from update loop +- Event-based button controls +- Responsive layout using CSS Grid + +**Communication** +- Login: REST POST +- Game State: WebSocket (continuous) +- Actions: REST POST +- Updates: WebSocket (10x per second) + +## šŸŽ‰ What's Next? + +Optional enhancements you can add: +1. **Database Integration**: Persistent leaderboards +2. **Authentication**: Proper user accounts +3. **More Content**: Additional structures, items, NPCs +4. **PvP**: Player vs Player combat zones +5. **Guilds**: Team/clan system +6. **Mobile**: Responsive mobile UI +7. **Sounds**: Audio effects and music +8. **Animations**: Smoother movements and attacks + +## āœ… Testing Checklist + +Things verified working: +- Backend starts without errors āœ… +- Frontend loads correctly āœ… +- Login system functional āœ… +- Player movement works āœ… +- Combat damage calculation works āœ… +- Experience/leveling works āœ… +- Resource gathering works āœ… +- Structure building works āœ… +- Boss spawning works āœ… +- Timer counts down āœ… +- WebSocket real-time updates āœ… +- Multiple players can join āœ… +- Game reset works āœ… + +## šŸŽŠ Conclusion + +You now have a **fully functional multiplayer web-based RPG game**! + +The game features: +- ✨ Complete gameplay loop +- ✨ Real-time multiplayer synchronization +- ✨ 3D graphics rendering +- ✨ Rich game mechanics +- ✨ Scalable architecture + +Simply run `python launcher.py` and start playing with friends! + +Good luck defeating the boss! šŸ—”ļøāš”ļøšŸ›”ļø + diff --git a/README.md b/README.md new file mode 100644 index 0000000..efde3f6 --- /dev/null +++ b/README.md @@ -0,0 +1,156 @@ +# Web Adventure - Community RPG + +A multiplayer web-based RPG where players collaborate to defeat a boss monster within a time limit before the world resets. + +## Features + +### Core Gameplay +- **Multiplayer World**: All logged-in players share the same game world on a large grid +- **Player Attributes**: Level, Health, Experience, Action Points, Attack, Defense, Movement Range +- **Inventory System**: Gather wood and stone from resources +- **Structure Building**: Place houses, farms, and guard towers for bonuses +- **Combat System**: Fight monsters and bosses with attack/defense mechanics +- **Time-Limited Rounds**: 30-minute game sessions with boss as final objective +- **Action Point System**: Limited actions per minute (starts at 20, regenerates 1 per minute) + +### Game Mechanics + +**Player Progression** +- Gain experience by defeating monsters +- Level up to increase max action points and strength +- Objective: Reach sufficient level to help defeat the boss + +**Monster System** +- Monsters spawn randomly on the map +- Each has health, attack, and defense stats +- Varied difficulty levels +- Boss appears when conditions are met (high player levels, enough players online) + +**Building System** +- **House**: Basic structure, provides max health bonus +- **Farm**: Increases action point regeneration rate +- **Guard Tower**: Provides defense bonus to nearby players + +**Resource Gathering** +- Trees provide wood +- Mountains provide stone +- Limited gathering capacity per action +- Resources regenerate over time + +**Victory Condition** +- Players must defeat the boss before 30 minutes elapse +- Requires coordination, high levels, and strategic structures +- On victory: Players gain major experience rewards +- On failure: World resets, players return to level 1 + +## Installation + +### Prerequisites +- Python 3.8+ +- pip + +### Setup + +1. Navigate to the project directory: +```bash +cd /Users/luka/dev/me/web-adventure +``` + +2. Install dependencies: +```bash +pip install -r requirements.txt +``` + +3. Start the backend server: +```bash +python -m uvicorn backend:app --reload --host 0.0.0.0 --port 8000 +``` + +4. Open a browser and navigate to: +``` +file:///Users/luka/dev/me/web-adventure/index.html +``` + +## How to Play + +### Login +1. Enter your username (1-30 characters) +2. Choose your player color +3. Click "Enter the World" + +### Movement +- Use the directional buttons (⬆ ⬇ ⬅ āž”) to move up to 5 tiles +- Each move costs 1 action point + +### Gathering +- Click the 🌳 Gather button to collect resources from nearby trees/mountains +- Gathered items go into your inventory + +### Combat +- Click the āš”ļø Attack button to attack nearby monsters or the boss +- Damage = your attack - monster's defense + random variance +- Each attack costs 1 action point + +### Building +- Click šŸ  House, 🌾 Farm, or šŸ›”ļø Tower to build structures +- Each structure requires resources and 1 action point +- Structures provide bonuses: + - **House** (+20 max health): Increases survivability + - **Farm** (+2 action regeneration): Generates action points faster + - **Guard Tower** (+2 defense): Improves defense stats + +### Action Points +- Start with 20 action points per session +- Regenerate 1 per minute naturally +- Farms nearby increase regeneration rate +- Every action (move, attack, gather, build) costs 1 action point +- Higher levels increase your max action points + +### Winning +- Reach the boss level (requires level 10+ by default) +- Coordinate with other players to attack the boss +- Defeat the boss before time runs out +- Earn major experience rewards + +## Game Configuration + +Edit the constants in `backend.py` to customize: +- `GAME_DURATION`: Game round length (seconds) +- `GRID_SIZE`: Map dimensions (tiles) +- `ACTION_POINTS_MAX`: Starting action points +- `BOSS_SPAWN_DISTANCE`: Distance boss spawns from players +- `MONSTER_SPAWN_RATE`: Frequency of monster spawning + +## Architecture + +### Backend (FastAPI + WebSockets) +- RESTful API for login, movement, and actions +- WebSocket connection for real-time game state updates +- In-memory game world state +- Automatic game tick system for respawning and regeneration + +### Frontend (HTML5 + Three.js) +- 3D game world visualization using Three.js +- Real-time UI showing player stats and game status +- Responsive control panel for all game actions +- Smooth camera following player movement + +## Files + +- `backend.py`: FastAPI server with game logic +- `index.html`: Complete frontend with 3D rendering +- `requirements.txt`: Python dependencies +- `main.py`: Placeholder (not used) + +## Future Enhancements + +- Persistent player profiles and statistics +- More structure types with unique abilities +- Trading between players +- PvP combat +- Quests and achievements +- More sophisticated AI for monsters +- Player guilds/teams +- Seasonal leaderboards +- Mobile app version + diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..e599565 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,305 @@ +# šŸŽ® Web Adventure - Community RPG + +A multiplayer browser-based RPG where players collaborate to defeat a boss monster before time runs out! + +## šŸš€ Quick Start + +### Option 1: Easy Launcher (Recommended) +```bash +cd /Users/luka/dev/me/web-adventure +python launcher.py +``` +This will automatically start the backend server and open the game in your browser. + +### Option 2: Manual Start +```bash +# Terminal 1 - Start backend +cd /Users/luka/dev/me/web-adventure +python -m uvicorn backend:app --reload --host 0.0.0.0 --port 8000 + +# Terminal 2 - Open frontend +# Open file:///Users/luka/dev/me/web-adventure/index.html in your browser +``` + +### Option 3: Shell Script +```bash +/Users/luka/dev/me/web-adventure/start.sh +``` + +## šŸ“‹ Requirements + +- Python 3.8+ +- Modern web browser (Chrome, Firefox, Safari, Edge) +- No additional Python packages needed (all included in requirements.txt) + +## āš™ļø Installation + +### First Time Setup + +1. **Clone/Navigate to Project** + ```bash + cd /Users/luka/dev/me/web-adventure + ``` + +2. **Install Dependencies** (one-time only) + ```bash + pip install -r requirements.txt + ``` + +3. **Run the Game** + ```bash + python launcher.py + ``` + +That's it! The game should automatically open in your browser. + +## šŸŽÆ How to Play + +### Game Objective +**Defeat the boss monster before time runs out!** The game lasts 30 minutes. If you succeed, you win big rewards. If you fail, the world resets. + +### Getting Started +1. Enter your username (1-30 characters) +2. Choose your player color +3. Click "Enter the World" +4. You spawn in a random location on the map + +### Player Mechanics + +**Stats** +- **Level**: Increases as you gain experience (exp) +- **Health**: Decreases when attacked, recovers slowly +- **Action Points**: Limited actions per minute (start with 20) + - Each action (move, attack, gather, build) costs 1 AP + - Regenerates 1 per minute naturally + - Farms increase regeneration rate +- **Attack/Defense**: Combat stats affecting damage and damage taken +- **Movement Capacity**: How far you can move per action + +### Movement +- Use directional buttons: ⬆ ⬇ ⬅ āž” +- Move up to 5 tiles per action +- Follow other players to find resources and monsters + +### Gathering Resources +- Click 🌳 **Gather** button near Trees or Mountains +- Gain **Wood** or **Stone** +- Resources are used to build structures +- Limited range (must be close to resource) + +### Combat System +- Click āš”ļø **Attack** button to attack nearby enemies +- **Damage = Your Attack - Enemy Defense + random variance** +- Each attack costs 1 action point +- Defeat monsters to: + - Gain experience points + - Level up (every 100 exp = +1 level) + - Increase your stats + +**Boss Monster** +- Appears when players reach level 10+ +- Significantly stronger than regular monsters +- Requires coordination with other players +- Defeating it = **Victory!** (before timer runs out) + +### Building Structures +Three types of structures, each costing different resources: + +**šŸ  House** (20 Wood, 10 Stone) +- Provides +20 max health bonus +- Makes you more durable +- Helpful for survivability + +**🌾 Farm** (15 Wood, 5 Stone) +- Increases action point regeneration nearby +- Generates action points faster for nearby players +- Great for team bonuses + +**šŸ›”ļø Guard Tower** (30 Wood, 20 Stone) +- Provides +2 defense bonus +- Reduces damage taken +- Helps defend against monsters + +**Structure Benefits** +- Structures provide bonuses to all nearby players (radius ~10 tiles) +- Multiple structures stack their bonuses +- Permanent until world resets + +### Resource Types +- **Wood**: From trees (green cones) - Used in all structures +- **Stone**: From mountains (gray cones) - Used in all structures + +### Winning the Game +1. **Level Up** to at least level 10 +2. **Boss Appears** automatically when conditions are met +3. **Coordinate Attacks** with other players +4. **Defeat the Boss** before the 30-minute timer ends +5. **Victory!** All players receive major experience rewards + +### Game Over Conditions +- **Victory**: Boss defeated before time runs out +- **Defeat**: Timer reaches 0:00 +- World resets and player stats return to level 1 + +## šŸŽ® Control Summary + +| Action | Button | Cost | Effect | +|--------|--------|------|--------| +| Move Up | ⬆ | 1 AP | Move 5 tiles north | +| Move Down | ⬇ | 1 AP | Move 5 tiles south | +| Move Left | ⬅ | 1 AP | Move 5 tiles west | +| Move Right | āž” | 1 AP | Move 5 tiles east | +| Gather | 🌳 | 1 AP | Collect nearby resource | +| Attack | āš”ļø | 1 AP | Damage nearby monster/boss | +| Build House | šŸ  | 1 AP | Place house structure | +| Build Farm | 🌾 | 1 AP | Place farm structure | +| Build Tower | šŸ›”ļø | 1 AP | Place tower structure | + +## šŸ—ļø Project Structure + +``` +web-adventure/ +ā”œā”€ā”€ backend.py # FastAPI server with game logic +ā”œā”€ā”€ index.html # Complete frontend with 3D rendering +ā”œā”€ā”€ launcher.py # Easy startup script +ā”œā”€ā”€ start.sh # Bash startup script +ā”œā”€ā”€ requirements.txt # Python dependencies +ā”œā”€ā”€ README.md # This file +└── SETUP.md # Detailed setup guide +``` + +## šŸ”§ Backend API + +### REST Endpoints + +**POST /api/login** +- Login and create player +- Body: `{"username": "name", "color": "#FF6B6B"}` +- Returns: Player data, session ID, game status + +**GET /api/game/state** +- Get full game state (players, monsters, structures, resources) +- Returns: Complete game world data + +**POST /api/player/{player_id}/move** +- Move player +- Body: `{"dx": 5, "dy": 0}` +- Returns: New position, action points + +**POST /api/player/{player_id}/action** +- Perform action (attack, gather, build) +- Body: `{"action_type": "attack", "target_id": "monster_123"}` +- Returns: Action result + +**POST /api/game/start** +- Start/restart the game round +- Returns: Game status + +**GET /api/health** +- Server health check +- Returns: `{"status": "ok"}` + +### WebSocket Connection + +**WS /ws/{player_id}** +- Real-time game updates (10 updates/second) +- Receive: Player positions, monster data, boss status, timer +- Automatic game tick updates + +## šŸ“Š Game Configuration + +Edit constants in `backend.py` to customize: + +```python +GAME_DURATION = 30 * 60 # 30 minutes +GRID_SIZE = 500 # 500x500 tile map +ACTION_POINTS_MAX = 20 # Starting action points +ACTION_POINTS_REGEN_INTERVAL = 60 # 1 minute regeneration +MONSTER_SPAWN_RATE = 0.1 # Spawn rate per tick +MAX_MONSTERS = 50 # Max monsters on map +BOSS_HEALTH_BASE = 1000 # Boss starting health +MIN_LEVEL_FOR_BOSS = 10 # Minimum level to spawn boss +``` + +## šŸŽØ Customization + +### Colors +Edit the color array in `index.html` to add/change player colors: +```javascript +const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', ...]; +``` + +### Game Duration +Change `GAME_DURATION` in `backend.py`: +```python +GAME_DURATION = 15 * 60 # 15 minutes instead of 30 +``` + +### Difficulty +Adjust these values in `backend.py`: +- Increase `BOSS_HEALTH_BASE` for harder boss +- Increase `MONSTER_SPAWN_RATE` for more monsters +- Change `MIN_LEVEL_FOR_BOSS` for earlier/later boss appearance + +## šŸ› Troubleshooting + +### Port 8000 Already in Use +```bash +# Find process using port 8000 and kill it +lsof -i :8000 +kill -9 + +# Or use different port +python -m uvicorn backend:app --host 0.0.0.0 --port 8001 +``` + +### Browser Won't Load Game +- Make sure backend is running: http://localhost:8000/docs +- Try refreshing the page +- Clear browser cache +- Try a different browser + +### Game Lags +- Reduce player count (have fewer people logged in) +- Lower monitor refresh rate +- Close other browser tabs + +### Action Points Not Regenerating +- Game must be running (wait for "Game Started" message) +- Action points regenerate every 60 seconds +- Farm structures nearby increase regeneration + +## šŸ“ Technical Stack + +- **Backend**: FastAPI + Uvicorn + WebSockets +- **Frontend**: HTML5 + Three.js (WebGL) +- **Real-time**: WebSocket protocol for live updates +- **Language**: Python 3.8+, JavaScript + +## šŸš€ Future Features + +- Persistent database for leaderboards +- Player guilds/teams +- More structure types +- Trading between players +- PvP combat zones +- Quests and achievements +- Mobile app version +- Seasonal resets with new content +- Boss tier progression +- Special events and limited-time challenges + +## šŸ“ž Support + +If you encounter issues: +1. Check error messages in browser console (F12) +2. Check terminal output for server errors +3. Verify all dependencies installed: `pip list` +4. Try restarting the server + +## šŸŽ‰ Enjoy! + +You now have a working multiplayer RPG! Invite friends, explore, and defeat the boss together! + +Good luck, adventurer! šŸ—”ļøāš”ļøšŸ›”ļø + diff --git a/__pycache__/backend.cpython-314.pyc b/__pycache__/backend.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e2da17d40023bf53506acf0b3f9e923712f1497 GIT binary patch literal 37254 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 player_id + self.generation = 0 + self._init_resources() + + def _init_resources(self): + """Initialize static resources on the map""" + for _ in range(50): + resource_id = f"resource_{len(self.resources)}" + x = random.randint(-GRID_SIZE // 2, GRID_SIZE // 2) + y = random.randint(-GRID_SIZE // 2, GRID_SIZE // 2) + resource_type = random.choice(["tree", "mountain"]) + self.resources[resource_id] = Resource( + id=resource_id, + position=Position(x, y, 0), + resource_type=resource_type, + amount=random.randint(50, 200), + ) + + def start_game(self): + """Start or restart the game""" + self.game_start_time = datetime.now().timestamp() + self.is_game_active = True + self.boss = None + self.monsters.clear() + self.generation += 1 + # Keep players but reset their state + for player in self.players.values(): + if not player.active: + continue + player.level = 1 + player.exp = 0 + player.health = player.max_health + player.action_points = ACTION_POINTS_MAX + player.inventory = {"wood": 0, "stone": 0} + player.position = Position( + random.randint(-PLAYER_START_SPAWN_RANGE, PLAYER_START_SPAWN_RANGE), + random.randint(-PLAYER_START_SPAWN_RANGE, PLAYER_START_SPAWN_RANGE), + 0, + ) + + def get_elapsed_time(self) -> float: + if not self.is_game_active: + return 0 + return datetime.now().timestamp() - self.game_start_time + + def check_game_over(self) -> bool: + if not self.is_game_active: + return False + elapsed = self.get_elapsed_time() + if elapsed >= GAME_DURATION: + self.is_game_active = False + return True + return False + + def update_tick(self): + """Called periodically to update game state""" + current_time = datetime.now().timestamp() + + # Spawn boss if conditions are met + if self.boss is None and len(self.players) > 0: + active_players = [p for p in self.players.values() if p.active] + if active_players and any(p.level >= MIN_LEVEL_FOR_BOSS for p in active_players): + self._spawn_boss() + + # Spawn monsters + if random.random() < MONSTER_SPAWN_RATE and len(self.monsters) < MAX_MONSTERS: + self._spawn_monster() + + # Regenerate player action points + for player in self.players.values(): + if not player.active: + continue + if current_time - player.last_action_regen >= ACTION_POINTS_REGEN_INTERVAL: + regen_amount = 1 + nearby_bonuses = self._get_nearby_structure_bonuses(player.position) + if "action_regen" in nearby_bonuses: + regen_amount = nearby_bonuses["action_regen"] + player.action_points = min( + player.max_action_points, + player.action_points + regen_amount, + ) + player.last_action_regen = current_time + + def _spawn_monster(self): + """Spawn a random monster on the map""" + monster_id = f"monster_{len(self.monsters)}_{self.generation}" + level = random.randint(1, 5) + x = random.randint(-GRID_SIZE // 2, GRID_SIZE // 2) + y = random.randint(-GRID_SIZE // 2, GRID_SIZE // 2) + health = 20 + level * 10 + self.monsters[monster_id] = Monster( + id=monster_id, + position=Position(x, y, 0), + health=health, + max_health=health, + level=level, + attack=3 + level, + is_boss=False, + ) + + def _spawn_boss(self): + """Spawn the boss monster""" + boss_id = f"boss_{self.generation}" + active_players = [p for p in self.players.values() if p.active] + avg_level = sum(p.level for p in active_players) / len(active_players) + health = int(BOSS_HEALTH_BASE + avg_level * 500) + + # Spawn boss at a distance from players + if active_players: + player_pos = active_players[0].position + angle = random.random() * 2 * 3.14159 + x = player_pos.x + BOSS_SPAWN_DISTANCE * (angle ** 0.5) * (1 if random.random() > 0.5 else -1) + y = player_pos.y + BOSS_SPAWN_DISTANCE * (1 - angle ** 0.5) * (1 if random.random() > 0.5 else -1) + else: + x = y = 0 + + self.boss = Monster( + id=boss_id, + position=Position(x, y, 0), + health=health, + max_health=health, + level=int(avg_level) + 5, + attack=15 + int(avg_level), + is_boss=True, + ) + + def _get_nearby_structure_bonuses(self, position: Position, radius: int = 10) -> Dict: + bonuses = {} + for structure in self.structures.values(): + dist = ((structure.position.x - position.x) ** 2 + (structure.position.y - position.y) ** 2) ** 0.5 + if dist <= radius: + for key, value in structure.bonuses.items(): + bonuses[key] = bonuses.get(key, 0) + value + return bonuses + +# Use plain dicts instead of Pydantic models + +# Global game state +game_world = GameWorld() +print("DEBUG: GameWorld initialized") + +app = FastAPI() +print("DEBUG: FastAPI app created") + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# ==================== API ENDPOINTS ==================== + +@app.post("/api/login") +async def login(request: Request): + """Login user and create/get player""" + print("===== LOGIN START =====", flush=True) + try: + print("[1] Parsing JSON...", flush=True) + body = await request.json() + print(f"[2] Got body: {body}", flush=True) + except Exception as e: + print(f"[X] Failed to parse JSON: {e}", flush=True) + raise HTTPException(status_code=400, detail="Invalid JSON") + + print("[3] Extracting fields...", flush=True) + username = body.get("username", "").strip() + color = body.get("color", "") + print(f"[4] Got username={username}, color={color}", flush=True) + + if len(username) < 1 or len(username) > 30: + print("[5a] Invalid username length", flush=True) + raise HTTPException(status_code=400, detail="Invalid username length") + + print("[5b] Generating player ID...", flush=True) + player_id = hashlib.md5(f"{username}_{game_world.generation}".encode()).hexdigest()[:12] + print(f"[6] Player_id={player_id}", flush=True) + + print("[7] Checking if player exists...", flush=True) + if player_id not in game_world.players: + print("[8] Creating new player...", flush=True) + player = Player( + id=player_id, + username=username, + color=color, + position=Position( + random.randint(-PLAYER_START_SPAWN_RANGE, PLAYER_START_SPAWN_RANGE), + random.randint(-PLAYER_START_SPAWN_RANGE, PLAYER_START_SPAWN_RANGE), + 0, + ), + ) + print("[9] Adding player to world...", flush=True) + game_world.players[player_id] = player + print("[10] Player added", flush=True) + + print("[11] Getting player from world...", flush=True) + player = game_world.players[player_id] + player.active = True + print("[12] Creating session...", flush=True) + session_id = hashlib.md5(f"{player_id}_{datetime.now().timestamp()}".encode()).hexdigest()[:16] + player.session_id = session_id + game_world.connected_sessions[session_id] = player_id + print("[13] Session created", flush=True) + + print("[14] Converting player to dict...", flush=True) + player_dict = player.to_dict() + print(f"[15] Player dict has {len(player_dict)} keys", flush=True) + + print("[16] Building response...", flush=True) + response = { + "player_id": player_id, + "session_id": session_id, + "player": player_dict, + "game_active": game_world.is_game_active, + "time_remaining": max(0, GAME_DURATION - game_world.get_elapsed_time()), + } + print("[17] Response built, returning...", flush=True) + return response + +@app.get("/api/game/state") +async def get_game_state(): + """Get full game state""" + return { + "grid_size": GRID_SIZE, + "players": {pid: p.to_dict() for pid, p in game_world.players.items()}, + "monsters": {mid: m.to_dict() for mid, m in game_world.monsters.items()}, + "boss": game_world.boss.to_dict() if game_world.boss else None, + "structures": {sid: s.to_dict() for sid, s in game_world.structures.items()}, + "resources": {rid: r.to_dict() for rid, r in game_world.resources.items()}, + "game_active": game_world.is_game_active, + "time_remaining": max(0, GAME_DURATION - game_world.get_elapsed_time()), + } + +@app.post("/api/player/{player_id}/move") +async def move_player(player_id: str, request: Request): + """Move player""" + if player_id not in game_world.players: + raise HTTPException(status_code=404, detail="Player not found") + + body = await request.json() + dx = body.get("dx", 0) + dy = body.get("dy", 0) + + player = game_world.players[player_id] + if not player.active: + raise HTTPException(status_code=400, detail="Player not active") + + if player.action_points < 1: + raise HTTPException(status_code=400, detail="Insufficient action points") + + # Calculate distance + distance = (dx ** 2 + dy ** 2) ** 0.5 + if distance > player.movement_capacity: + raise HTTPException(status_code=400, detail="Movement exceeds capacity") + + player.position.x += dx + player.position.y += dy + player.position.x = max(-GRID_SIZE // 2, min(GRID_SIZE // 2, player.position.x)) + player.position.y = max(-GRID_SIZE // 2, min(GRID_SIZE // 2, player.position.y)) + player.action_points -= 1 + + return {"position": player.position.to_dict(), "action_points": player.action_points} + +@app.post("/api/player/{player_id}/action") +async def player_action(player_id: str, request: Request): + """Player performs an action""" + if player_id not in game_world.players: + raise HTTPException(status_code=404, detail="Player not found") + + body = await request.json() + player = game_world.players[player_id] + if player.action_points < 1: + raise HTTPException(status_code=400, detail="Insufficient action points") + + action_type = body.get("action_type") + if action_type == "attack": + result = _handle_attack(player, body.get("target_id")) + elif action_type == "gather": + result = _handle_gather(player, body.get("target_id")) + elif action_type == "build": + result = _handle_build(player, body.get("tx"), body.get("ty"), body.get("structure_type")) + else: + return {"action": action_type, "success": False, "reason": "Unknown action"} + + # Only consume AP if the action actually succeeded + if result.get("success"): + player.action_points -= 1 + + return result + +def _handle_attack(player: Player, target_id: str) -> Dict: + """Handle player attack""" + if target_id in game_world.monsters: + monster = game_world.monsters[target_id] + dist = ((monster.position.x - player.position.x) ** 2 + (monster.position.y - player.position.y) ** 2) ** 0.5 + + if dist > 5: + return {"success": False, "reason": "Target too far"} + + damage = max(1, player.attack + random.randint(-2, 2) - monster.defense) + monster.health -= damage + + if monster.health <= 0: + del game_world.monsters[target_id] + player.exp += monster.level * 10 + player.level = 1 + int(player.exp / 100) + return {"success": True, "damage": damage, "exp": monster.level * 10, "monster_killed": True} + + return {"success": True, "damage": damage, "monster_health_remaining": monster.health} + + elif game_world.boss and target_id == game_world.boss.id: + monster = game_world.boss + dist = ((monster.position.x - player.position.x) ** 2 + (monster.position.y - player.position.y) ** 2) ** 0.5 + + if dist > 5: + return {"success": False, "reason": "Boss too far"} + + damage = max(1, player.attack + random.randint(-2, 2) - monster.defense) + monster.health -= damage + old_progress = monster.boss_progress + monster.boss_progress = (monster.max_health - monster.health) / monster.max_health * 100 + + if monster.health <= 0: + # Victory! + for p in game_world.players.values(): + if p.active: + p.level += 5 + p.exp += 500 + game_world.boss = None + return {"success": True, "damage": damage, "boss_killed": True} + + return {"success": True, "damage": damage, "boss_health_remaining": monster.health} + + return {"success": False, "reason": "Target not found"} + +def _handle_gather(player: Player, target_id: str) -> Dict: + """Gather from all resources within GATHER_RADIUS of the player (target_id is ignored).""" + GATHER_RADIUS = 5 + gathered_total = {"wood": 0, "stone": 0} + depleted = [] + remaining_capacity = player.gathering_capacity + + for rid, resource in list(game_world.resources.items()): + if remaining_capacity <= 0: + break + dist = ((resource.position.x - player.position.x) ** 2 + + (resource.position.y - player.position.y) ** 2) ** 0.5 + if dist <= GATHER_RADIUS: + amount = min(remaining_capacity, resource.amount) + resource.amount -= amount + gathered_total[resource.resource_type] = gathered_total.get(resource.resource_type, 0) + amount + remaining_capacity -= amount + if resource.amount <= 0: + depleted.append(rid) + + for rid in depleted: + del game_world.resources[rid] + + total_gathered = sum(gathered_total.values()) + if total_gathered == 0: + return {"success": False, "reason": "No resources within reach (radius 5)"} + + for rtype, amt in gathered_total.items(): + player.inventory[rtype] = player.inventory.get(rtype, 0) + amt + + return {"success": True, "gathered": gathered_total, "inventory": player.inventory} + +def _handle_build(player: Player, tx: float, ty: float, structure_type: str) -> Dict: + """Handle structure building""" + if structure_type not in ["house", "farm", "guard_tower"]: + return {"success": False, "reason": "Invalid structure type"} + + costs = {"house": {"wood": 20, "stone": 10}, "farm": {"wood": 15, "stone": 5}, "guard_tower": {"wood": 30, "stone": 20}} + cost = costs[structure_type] + + for material, amount in cost.items(): + if player.inventory.get(material, 0) < amount: + return {"success": False, "reason": f"Insufficient {material}"} + + # Deduct cost + for material, amount in cost.items(): + player.inventory[material] -= amount + + # Create structure + structure_id = f"struct_{len(game_world.structures)}" + bonuses = {} + if structure_type == "farm": + bonuses["action_regen"] = 2 + elif structure_type == "guard_tower": + bonuses["defense"] = 2 + elif structure_type == "house": + bonuses["max_health"] = 20 + + game_world.structures[structure_id] = Structure( + id=structure_id, + position=Position(tx, ty, 0), + structure_type=structure_type, + owner_id=player.id, + health=100, + bonuses=bonuses, + ) + + return {"success": True, "structure_id": structure_id, "inventory": player.inventory} + +@app.websocket("/ws/{player_id}") +async def websocket_endpoint(websocket: WebSocket, player_id: str): + """WebSocket for real-time game updates""" + if player_id not in game_world.players: + await websocket.close(code=4004, reason="Player not found") + return + + player = game_world.players[player_id] + await websocket.accept() + + try: + while True: + # Send game state updates every 100ms + await asyncio.sleep(0.1) + game_world.update_tick() + + if game_world.check_game_over(): + await websocket.send_json({"type": "game_over", "time_remaining": 0}) + break + + state = { + "type": "state_update", + "players": {pid: p.to_dict() for pid, p in game_world.players.items()}, + "monsters": {mid: m.to_dict() for mid, m in game_world.monsters.items()}, + "boss": game_world.boss.to_dict() if game_world.boss else None, + "structures": {sid: s.to_dict() for sid, s in game_world.structures.items()}, + "resources": {rid: r.to_dict() for rid, r in game_world.resources.items()}, + "time_remaining": max(0, GAME_DURATION - game_world.get_elapsed_time()), + "elapsed_time": game_world.get_elapsed_time(), + } + await websocket.send_json(state) + except Exception as e: + print(f"WebSocket error: {e}") + finally: + player.active = False + +@app.post("/api/game/start") +async def start_game(): + """Start a new game""" + game_world.start_game() + return {"game_active": True, "time_remaining": GAME_DURATION} + +@app.get("/api/health") +async def health(): + """Health check""" + return {"status": "ok"} + + diff --git a/index.html b/index.html new file mode 100644 index 0000000..ae48310 --- /dev/null +++ b/index.html @@ -0,0 +1,1120 @@ + + + + + + Web Adventure - Community RPG + + + + + +
+ + +
+
Player Stats
+
+
+ Level: + 1 +
+
+ Experience: + 0 +
+
+ Health: + 100/100 +
+
+
+
+
+ Action Points: + 20 +
+
+
+
+
+ Attack: + 5 +
+
+ Defense: + 2 +
+
+ Move Range: + 5 +
+
+
+
Inventory
+
+ Wood: + 0 +
+
+ Stone: + 0 +
+
+
+ +
+
Game Status
+
+ Time Remaining: + 30:00 +
+
+ Players Online: + 1 +
+
+ Monsters: + 0 +
+
+ +
+
āš”ļø BOSS APPEARED āš”ļø
+
+
+
+
+ Loading... +
+
+ + +
+
šŸ“· CAMERA
+
+ +
+
+ + + +
+
+ +
+
+ + +
+
+ +
+
Controls
+
Each action costs 1 AP. Hover buttons to see details.
+
+ + + + +
+
+ + +
+
+ + + +
+
Ready for adventure!
+
+
+ +
+
+

GAME OVER

+

The timer has run out!

+

+ +
+
+ + + + + + diff --git a/launcher.py b/launcher.py new file mode 100755 index 0000000..9983734 --- /dev/null +++ b/launcher.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +""" +Web Adventure - Community RPG Launcher +Simple script to start both backend and frontend +""" + +import subprocess +import time +import sys +import os +import signal +import webbrowser +from pathlib import Path + +def main(): + print("\n" + "="*60) + print("šŸŽ® WEB ADVENTURE - Community RPG") + print("="*60 + "\n") + + # Get the directory of this script + script_dir = Path(__file__).parent.resolve() + os.chdir(script_dir) + + # Start the backend + print("šŸš€ Starting backend server...") + backend_process = subprocess.Popen( + [sys.executable, "-m", "uvicorn", "backend:app", "--host", "0.0.0.0", "--port", "8000"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + preexec_fn=os.setsid + ) + + # Wait for backend to start + time.sleep(2) + + if backend_process.poll() is not None: + print("āŒ Backend failed to start!") + sys.exit(1) + + print("āœ… Backend running at http://localhost:8000") + print("\n" + "="*60) + print("šŸŒ Opening game in browser...") + print("="*60 + "\n") + + # Open the frontend in the browser + html_path = (script_dir / "index.html").as_uri() + webbrowser.open(html_path) + + print(f"\nšŸ“– API Documentation: http://localhost:8000/docs") + print(f"šŸŽ® Game: {html_path}\n") + + print("Press Ctrl+C to stop the server\n") + + try: + backend_process.wait() + except KeyboardInterrupt: + print("\n\nShutting down...") + os.killpg(os.getpgid(backend_process.pid), signal.SIGTERM) + backend_process.wait() + print("āœ… Server stopped") + +if __name__ == "__main__": + main() + diff --git a/main.py b/main.py new file mode 100644 index 0000000..94e3a87 --- /dev/null +++ b/main.py @@ -0,0 +1,16 @@ +# This is a sample Python script. + +# Press ⌃R to execute it or replace it with your code. +# Press Double ⇧ to search everywhere for classes, files, tool windows, actions, and settings. + + +def print_hi(name): + # Use a breakpoint in the code line below to debug your script. + print(f'Hi, {name}') # Press ⌘F8 to toggle the breakpoint. + + +# Press the green button in the gutter to run the script. +if __name__ == '__main__': + print_hi('PyCharm') + +# See PyCharm help at https://www.jetbrains.com/help/pycharm/ diff --git a/quickstart.py b/quickstart.py new file mode 100644 index 0000000..cfb7323 --- /dev/null +++ b/quickstart.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +""" +šŸŽ® WEB ADVENTURE - QUICK START +Your multiplayer RPG is ready to play! +""" + +import subprocess +import sys + +print(""" +╔════════════════════════════════════════════════════════════╗ +ā•‘ ā•‘ +ā•‘ šŸŽ® WEB ADVENTURE - Community RPG šŸŽ® ā•‘ +ā•‘ ā•‘ +ā•‘ A Multiplayer Browser-Based RPG Game ā•‘ +ā•‘ ā•‘ +ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā• + +šŸ“‹ Your game is ready to play! + +Quick Start Options: + +1. EASIEST: Run launcher.py + ================================ + $ python launcher.py + + ✨ This will: + • Start the backend server + • Automatically open the game in your browser + • Everything happens automatically! + +2. MANUAL: Start server and open browser yourself + ================================ + Terminal 1: + $ python -m uvicorn backend:app --reload \\ + --host 0.0.0.0 --port 8000 + + Terminal 2: + • Open file:///Users/luka/dev/me/web-adventure/index.html + in your web browser + +3. CHECK: Run verification script + ================================ + $ python verify.py + + Verifies all components are set up correctly + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +šŸ“š DOCUMENTATION: + +• README.md ............. Complete user guide +• SETUP.md .............. Detailed setup instructions +• PROJECT_SUMMARY.md .... Implementation overview + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +šŸŽ® GAME FEATURES: + +āœ… Multiplayer World - Play with friends online +āœ… 3D Graphics - Beautiful Three.js rendering +āœ… Combat System - Fight monsters and boss +āœ… Leveling System - Gain experience, items +āœ… Building Structures - Create houses, farms, towers +āœ… Resource Gathering - Collect wood and stone +āœ… Boss Mechanic - 30-minute challenge to defeat boss +āœ… Real-time Updates - Live multiplayer synchronization +āœ… Team Bonuses - Structures help nearby players + +━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +šŸš€ LET'S PLAY! + +Starting in 3 seconds... (or press Ctrl+C to cancel) + +""") + +import time +time.sleep(3) + +# Run launcher +try: + from pathlib import Path + import os + os.chdir(Path(__file__).parent) + subprocess.run([sys.executable, "launcher.py"]) +except KeyboardInterrupt: + print("\n\nšŸ‘‹ See you next time, adventurer!") +except Exception as e: + print(f"\nāŒ Error: {e}") + print("\nTry running: python launcher.py") + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..31db696 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +fastapi==0.109.0 +uvicorn==0.27.0 +websockets==12.0 +python-multipart==0.0.6 +python-dotenv==1.0.0 +PyJWT==2.8.0 + + + + + diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..3cc50ce --- /dev/null +++ b/start.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# Web Adventure - Startup Script + +echo "šŸŽ® Web Adventure - Community RPG" +echo "==================================" +echo "" +echo "Starting backend server..." + +# Check if pip packages are installed +if ! python -c "import fastapi" 2>/dev/null; then + echo "Installing dependencies..." + pip install -r requirements.txt +fi + +# Start the server +echo "" +echo "Backend running at: http://localhost:8000" +echo "Game frontend at: file://$(pwd)/index.html" +echo "" +echo "Press Ctrl+C to stop the server" +echo "" + +python -m uvicorn backend:app --reload --host 0.0.0.0 --port 8000 + diff --git a/verify.py b/verify.py new file mode 100644 index 0000000..f4909e3 --- /dev/null +++ b/verify.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +Web Adventure - Quick Verification Script +Checks that all components are properly set up +""" + +import sys +import os +from pathlib import Path + +def check_file_exists(path, description): + if path.exists(): + size = path.stat().st_size + print(f"āœ… {description}: {path.name} ({size:,} bytes)") + return True + else: + print(f"āŒ {description}: {path.name} NOT FOUND") + return False + +def check_imports(): + """Check that all required Python packages are installed""" + packages = { + 'fastapi': 'FastAPI', + 'uvicorn': 'Uvicorn', + 'websockets': 'WebSockets', + 'pydantic': 'Pydantic', + } + + all_ok = True + for module_name, display_name in packages.items(): + try: + __import__(module_name) + print(f"āœ… {display_name} installed") + except ImportError: + print(f"āŒ {display_name} NOT installed") + all_ok = False + + return all_ok + +def verify_python_syntax(filepath): + """Verify Python file has valid syntax""" + try: + with open(filepath, 'r') as f: + compile(f.read(), filepath, 'exec') + return True + except SyntaxError as e: + print(f" Syntax error: {e}") + return False + +def main(): + print("\n" + "="*60) + print("šŸ” WEB ADVENTURE - Verification Script") + print("="*60 + "\n") + + script_dir = Path(__file__).parent.resolve() + os.chdir(script_dir) + + all_checks = [] + + # Check files + print("šŸ“ Checking files...") + all_checks.append(check_file_exists(script_dir / "backend.py", "Backend")) + all_checks.append(check_file_exists(script_dir / "index.html", "Frontend")) + all_checks.append(check_file_exists(script_dir / "requirements.txt", "Dependencies")) + all_checks.append(check_file_exists(script_dir / "launcher.py", "Launcher")) + all_checks.append(check_file_exists(script_dir / "README.md", "Readme")) + all_checks.append(check_file_exists(script_dir / "SETUP.md", "Setup Guide")) + all_checks.append(check_file_exists(script_dir / "PROJECT_SUMMARY.md", "Project Summary")) + + print("\nšŸ“¦ Checking Python packages...") + all_checks.append(check_imports()) + + print("\nšŸ Checking Python syntax...") + for pyfile in [script_dir / "backend.py", script_dir / "launcher.py"]: + print(f" Checking {pyfile.name}...", end=" ") + if verify_python_syntax(pyfile): + print("āœ…") + all_checks.append(True) + else: + print("āŒ") + all_checks.append(False) + + print("\n" + "="*60) + if all(all_checks): + print("āœ… All checks passed! Ready to play!") + print("\nTo start the game, run:") + print(" python launcher.py") + print("\n" + "="*60 + "\n") + return 0 + else: + print("āŒ Some checks failed. Please review above.") + print("\n" + "="*60 + "\n") + return 1 + +if __name__ == "__main__": + sys.exit(main()) +