Spaces:
Running
feat: Add character-aware gameplay and Gradio web UI (Phase 8)
Browse filesMajor Features:
- 🌐 Gradio web interface (app_gradio.py) for browser-based gameplay
- 🎭 Character-aware dialogue system (play_with_character.py)
- 🎲 Pre-made characters: Thorin Stormshield (Fighter) & Elara Moonwhisper (Wizard)
- 📊 Live character stats displayed during gameplay
- ⚡ Command system: /stats, /context, /rag, /character
Technical Improvements:
- Suppressed tokenizer parallelism warning
- Fixed first/second person context ("The player is X" → AI uses "you")
- Dynamic character support (load from JSON or create new)
- Three play modes: Web UI, CLI, or legacy GM dialogue
Documentation Updates:
- README: Comprehensive gameplay instructions for Thorin and Elara
- README: Full commands reference (/context, /rag, /stats)
- README: Gradio and CLI interface documentation
- plan_progress.md: Added Phase 8 (Game Mechanics Engine)
- plan_progress.md: Documented hybrid AI + Rules Engine architecture (Option B)
- requirements.txt: Added gradio>=4.0.0
Architecture Notes (Phase 8):
- Discovered AI reliability issues (ignores valid spells, allows invalid ones)
- Selected hybrid approach: Rules engine intercepts actions before AI narration
- Future work: Spell validation, HP tracking, combat mechanics
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- README.md +163 -39
- app_gradio.py +328 -0
- plan_progress.md +118 -7
- play_with_character.py +269 -0
- requirements.txt +3 -0
|
@@ -196,66 +196,188 @@ Sage | Neutral Good
|
|
| 196 |
|
| 197 |
#### Step 4: Play D&D with AI Game Master
|
| 198 |
|
| 199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 200 |
|
| 201 |
```bash
|
| 202 |
-
python
|
| 203 |
```
|
| 204 |
|
| 205 |
-
|
| 206 |
-
- Install Ollama: https://ollama.ai
|
| 207 |
-
- Download RPG model: `ollama pull hf.co/Chun121/Qwen3-4B-RPG-Roleplay-V2:Q4_K_M`
|
| 208 |
|
| 209 |
-
**
|
| 210 |
-
-
|
| 211 |
-
-
|
| 212 |
-
-
|
| 213 |
-
-
|
| 214 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
|
| 216 |
-
**Example
|
| 217 |
```
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
Model: hf.co/Chun121/Qwen3-4B-RPG-Roleplay-V2:Q4_K_M
|
| 220 |
|
| 221 |
-
Type /help for commands or
|
| 222 |
======================================================================
|
| 223 |
|
| 224 |
-
🎲
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
-
|
| 227 |
-
throw from each goblin. Roll 8d6 for fire damage. Each goblin in the
|
| 228 |
-
20-foot radius must make their save - on a success, they take half damage.
|
| 229 |
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
-
|
| 233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
```
|
| 235 |
|
| 236 |
-
**
|
| 237 |
```
|
| 238 |
-
/help - Show available commands
|
| 239 |
-
/context <text> - Set the current scene/context
|
| 240 |
/history - Show conversation history
|
| 241 |
-
/rag <query> - Test RAG search (see what the GM knows)
|
| 242 |
/save <file> - Save session to JSON
|
| 243 |
/quit - Exit the game
|
| 244 |
```
|
| 245 |
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
-
**
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
#### Step 5: Run Interactive Searches (Optional)
|
| 261 |
|
|
@@ -364,8 +486,10 @@ You should see the Qwen3-4B-RPG model in the list.
|
|
| 364 |
│
|
| 365 |
├── chromadb/ # Vector database (created on init)
|
| 366 |
├── initialize_rag.py # Main initialization script ⭐
|
| 367 |
-
├──
|
| 368 |
-
├──
|
|
|
|
|
|
|
| 369 |
├── test_spell_search.py # Manual search testing
|
| 370 |
├── create_character.py # Character creator launcher
|
| 371 |
├── run_gm_dialogue.py # AI GM dialogue launcher
|
|
|
|
| 196 |
|
| 197 |
#### Step 4: Play D&D with AI Game Master
|
| 198 |
|
| 199 |
+
**Prerequisites:**
|
| 200 |
+
- Install Ollama: https://ollama.ai
|
| 201 |
+
- Download RPG model: `ollama pull hf.co/Chun121/Qwen3-4B-RPG-Roleplay-V2:Q4_K_M`
|
| 202 |
+
|
| 203 |
+
You have **two ways** to play:
|
| 204 |
+
|
| 205 |
+
---
|
| 206 |
+
|
| 207 |
+
### Option A: 🌐 Web Interface (Gradio) **⭐ RECOMMENDED**
|
| 208 |
+
|
| 209 |
+
Launch the web UI for the best experience:
|
| 210 |
|
| 211 |
```bash
|
| 212 |
+
python app_gradio.py
|
| 213 |
```
|
| 214 |
|
| 215 |
+
Then open http://localhost:7860 in your browser.
|
|
|
|
|
|
|
| 216 |
|
| 217 |
+
**Features:**
|
| 218 |
+
- 🎭 **Pre-made Characters**: Play as Thorin Stormshield (Dwarf Fighter) or Elara Moonwhisper (Elf Wizard)
|
| 219 |
+
- 💬 **Chat Interface**: Clean conversation view with the AI GM
|
| 220 |
+
- 📊 **Character Sheet**: Live character stats displayed in sidebar
|
| 221 |
+
- ⚡ **Quick Commands**: Built-in buttons for common actions
|
| 222 |
+
- 🎲 **RAG Search**: Test spell/monster lookups directly in the UI
|
| 223 |
+
|
| 224 |
+
**Quick Start:**
|
| 225 |
+
1. Select a character from dropdown (Thorin or Elara)
|
| 226 |
+
2. Click "Load Character"
|
| 227 |
+
3. Type your action in the chat box
|
| 228 |
+
4. The GM responds with RAG-enhanced D&D rules!
|
| 229 |
|
| 230 |
+
**Example Actions:**
|
| 231 |
```
|
| 232 |
+
# As Thorin (Fighter):
|
| 233 |
+
I draw my longsword and look for enemies
|
| 234 |
+
I attack with my weapon
|
| 235 |
+
/stats
|
| 236 |
+
|
| 237 |
+
# As Elara (Wizard):
|
| 238 |
+
I cast Magic Missile at the goblin
|
| 239 |
+
I look through my spellbook
|
| 240 |
+
/rag Magic Missile
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
### Option B: 💻 Command Line Interface
|
| 246 |
+
|
| 247 |
+
For terminal lovers, use the CLI version:
|
| 248 |
+
|
| 249 |
+
```bash
|
| 250 |
+
python play_with_character.py
|
| 251 |
+
```
|
| 252 |
+
|
| 253 |
+
**Character Selection:**
|
| 254 |
+
```
|
| 255 |
+
1. Create new character (full interactive creation)
|
| 256 |
+
2. Load existing character (from JSON file)
|
| 257 |
+
3. Use test character (quick start with Thorin)
|
| 258 |
+
```
|
| 259 |
+
|
| 260 |
+
**Available Characters:**
|
| 261 |
+
|
| 262 |
+
**Thorin Stormshield** - Level 3 Dwarf Fighter
|
| 263 |
+
- HP: 28 | AC: 18 | Proficiency: +2
|
| 264 |
+
- STR 16 (+3) | DEX 12 (+1) | CON 16 (+3)
|
| 265 |
+
- Equipment: Longsword, Shield, Plate Armor
|
| 266 |
+
- Perfect for: Melee combat, tanking, straightforward gameplay
|
| 267 |
+
|
| 268 |
+
**Elara Moonwhisper** - Level 2 Elf Wizard
|
| 269 |
+
- HP: 14 | AC: 12 | Proficiency: +2
|
| 270 |
+
- INT 17 (+3) | DEX 14 (+2) | CON 12 (+1)
|
| 271 |
+
- Spells: Fire Bolt, Mage Hand, Magic Missile, Shield
|
| 272 |
+
- Equipment: Quarterstaff, Spellbook, Component Pouch
|
| 273 |
+
- Perfect for: Spellcasting, strategic gameplay, testing RAG
|
| 274 |
+
|
| 275 |
+
**Example Session:**
|
| 276 |
+
```
|
| 277 |
+
🎲 D&D GAME SESSION - Playing as Elara Moonwhisper
|
| 278 |
+
======================================================================
|
| 279 |
+
Character: Elara Moonwhisper (Elf Wizard)
|
| 280 |
Model: hf.co/Chun121/Qwen3-4B-RPG-Roleplay-V2:Q4_K_M
|
| 281 |
|
| 282 |
+
Type /help for commands or start playing!
|
| 283 |
======================================================================
|
| 284 |
|
| 285 |
+
🎲 Elara Moonwhisper: I look around the tavern
|
| 286 |
+
|
| 287 |
+
🎭 GM: The tavern is dimly lit by flickering torches. You notice a group
|
| 288 |
+
of rough-looking adventurers in the corner, and a hooded figure sitting
|
| 289 |
+
alone by the fireplace...
|
| 290 |
+
|
| 291 |
+
🎲 Elara Moonwhisper: /stats
|
| 292 |
+
|
| 293 |
+
📊 Elara Moonwhisper | HP: 14 | AC: 12 | Prof: +2
|
| 294 |
+
STR -1 | DEX +2 | CON +1 | INT +3 | WIS +1 | CHA +0
|
| 295 |
+
```
|
| 296 |
+
|
| 297 |
+
---
|
| 298 |
|
| 299 |
+
### 🎮 Commands Reference
|
|
|
|
|
|
|
| 300 |
|
| 301 |
+
**Character Commands:**
|
| 302 |
+
```
|
| 303 |
+
/character - Show full character sheet
|
| 304 |
+
/stats - Quick stats view (HP, AC, modifiers)
|
| 305 |
+
/context - View current scene and character context
|
| 306 |
+
```
|
| 307 |
|
| 308 |
+
**RAG Commands:**
|
| 309 |
+
```
|
| 310 |
+
/rag <query> - Search D&D knowledge base
|
| 311 |
+
Examples:
|
| 312 |
+
/rag Magic Missile - Look up spell details
|
| 313 |
+
/rag Goblin - Look up monster stats
|
| 314 |
+
/rag Fighter - Look up class features
|
| 315 |
+
/rag Elf - Look up race traits
|
| 316 |
```
|
| 317 |
|
| 318 |
+
**Session Commands:**
|
| 319 |
```
|
| 320 |
+
/help - Show all available commands
|
|
|
|
| 321 |
/history - Show conversation history
|
|
|
|
| 322 |
/save <file> - Save session to JSON
|
| 323 |
/quit - Exit the game
|
| 324 |
```
|
| 325 |
|
| 326 |
+
---
|
| 327 |
+
|
| 328 |
+
### 💡 How It Works
|
| 329 |
+
|
| 330 |
+
**Character-Aware Gameplay:**
|
| 331 |
+
1. Select or create a character (Thorin, Elara, or custom)
|
| 332 |
+
2. Character stats, spells, and equipment are passed to the GM
|
| 333 |
+
3. GM knows YOUR character and references it in responses
|
| 334 |
+
|
| 335 |
+
**RAG Integration:**
|
| 336 |
+
1. You type an action (e.g., "I cast Magic Missile")
|
| 337 |
+
2. System searches ChromaDB for relevant spells/monsters/rules
|
| 338 |
+
3. RAG results are injected into the AI GM's context
|
| 339 |
+
4. GM generates response using accurate D&D 5e rules
|
| 340 |
+
5. Response references your character's abilities
|
| 341 |
+
|
| 342 |
+
**Example RAG Flow:**
|
| 343 |
+
```
|
| 344 |
+
Input: "I cast Magic Missile at the goblin"
|
| 345 |
+
↓
|
| 346 |
+
RAG Search: Finds "Magic Missile" spell
|
| 347 |
+
↓
|
| 348 |
+
Context: "Elara (Wizard) has Magic Missile in spell list"
|
| 349 |
+
↓
|
| 350 |
+
Context: "Magic Missile: 1st-level, 3 darts, 1d4+1 force damage each"
|
| 351 |
+
↓
|
| 352 |
+
AI GM Response: Uses accurate spell mechanics + your character's stats
|
| 353 |
+
```
|
| 354 |
+
|
| 355 |
+
---
|
| 356 |
+
|
| 357 |
+
### 🎯 Gameplay Tips
|
| 358 |
+
|
| 359 |
+
**For Best Results:**
|
| 360 |
+
- **Be Specific**: "I cast Fire Bolt at the closest goblin" vs "I attack"
|
| 361 |
+
- **Use RAG**: Test `/rag <spell>` before casting to see what the GM knows
|
| 362 |
+
- **Check Context**: Use `/context` to see what the GM knows about your character
|
| 363 |
+
- **View Stats**: Use `/stats` during combat to remember your modifiers
|
| 364 |
+
- **Roleplay**: The GM responds to narrative actions ("I cautiously enter the room")
|
| 365 |
|
| 366 |
+
**Testing Spells with Elara:**
|
| 367 |
+
```
|
| 368 |
+
/rag Magic Missile # See full spell description
|
| 369 |
+
I cast Magic Missile # GM should apply 1st-level spell rules
|
| 370 |
+
/rag Fireball # Search for a spell Elara doesn't know
|
| 371 |
+
I cast Fireball # GM behavior (should it allow this?)
|
| 372 |
+
```
|
| 373 |
+
|
| 374 |
+
**Combat with Thorin:**
|
| 375 |
+
```
|
| 376 |
+
/stats # Check your attack bonus
|
| 377 |
+
I attack with my longsword # GM will ask for d20 roll
|
| 378 |
+
I use my shield # GM applies AC bonus
|
| 379 |
+
I use Second Wind # GM applies fighter class feature
|
| 380 |
+
```
|
| 381 |
|
| 382 |
#### Step 5: Run Interactive Searches (Optional)
|
| 383 |
|
|
|
|
| 486 |
│
|
| 487 |
├── chromadb/ # Vector database (created on init)
|
| 488 |
├── initialize_rag.py # Main initialization script ⭐
|
| 489 |
+
├── app_gradio.py # Gradio web UI ⭐ NEW!
|
| 490 |
+
├── play_with_character.py # Character-aware gameplay CLI ⭐ NEW!
|
| 491 |
+
├── query_rag.py # Interactive query CLI ⭐
|
| 492 |
+
├── test_all_collections.py # Comprehensive test suite ⭐
|
| 493 |
├── test_spell_search.py # Manual search testing
|
| 494 |
├── create_character.py # Character creator launcher
|
| 495 |
├── run_gm_dialogue.py # AI GM dialogue launcher
|
|
@@ -0,0 +1,328 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
D&D Character-Aware Game - Gradio Web Interface
|
| 4 |
+
|
| 5 |
+
Web UI for playing D&D with RAG-enhanced AI GM and character tracking.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
# Suppress tokenizer warning
|
| 10 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 11 |
+
|
| 12 |
+
import gradio as gr
|
| 13 |
+
import json
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
# Add project to path
|
| 17 |
+
import sys
|
| 18 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 19 |
+
|
| 20 |
+
from dnd_rag_system.core.chroma_manager import ChromaDBManager
|
| 21 |
+
from dnd_rag_system.systems.character_creator import Character
|
| 22 |
+
from dnd_rag_system.systems.gm_dialogue import GameMaster
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Initialize system
|
| 26 |
+
print("🎲 Initializing D&D RAG System...")
|
| 27 |
+
db = ChromaDBManager()
|
| 28 |
+
gm = GameMaster(db)
|
| 29 |
+
|
| 30 |
+
# Pre-made characters
|
| 31 |
+
THORIN = Character(
|
| 32 |
+
name="Thorin Stormshield",
|
| 33 |
+
race="Dwarf",
|
| 34 |
+
character_class="Fighter",
|
| 35 |
+
level=3,
|
| 36 |
+
strength=16,
|
| 37 |
+
dexterity=12,
|
| 38 |
+
constitution=16,
|
| 39 |
+
intelligence=10,
|
| 40 |
+
wisdom=13,
|
| 41 |
+
charisma=8,
|
| 42 |
+
hit_points=28,
|
| 43 |
+
armor_class=18,
|
| 44 |
+
proficiency_bonus=2,
|
| 45 |
+
background="Soldier",
|
| 46 |
+
alignment="Lawful Good",
|
| 47 |
+
race_traits=["Dwarven Resilience", "Darkvision"],
|
| 48 |
+
class_features=["Fighting Style: Defense", "Second Wind", "Action Surge"],
|
| 49 |
+
proficiencies=["All armor", "All weapons", "Shields"],
|
| 50 |
+
equipment=["Longsword", "Shield", "Plate Armor", "Backpack", "50 GP"],
|
| 51 |
+
spells=[]
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
ELARA = Character(
|
| 55 |
+
name="Elara Moonwhisper",
|
| 56 |
+
race="Elf",
|
| 57 |
+
character_class="Wizard",
|
| 58 |
+
level=2,
|
| 59 |
+
strength=8,
|
| 60 |
+
dexterity=14,
|
| 61 |
+
constitution=12,
|
| 62 |
+
intelligence=17,
|
| 63 |
+
wisdom=13,
|
| 64 |
+
charisma=10,
|
| 65 |
+
hit_points=14,
|
| 66 |
+
armor_class=12,
|
| 67 |
+
proficiency_bonus=2,
|
| 68 |
+
background="Sage",
|
| 69 |
+
alignment="Neutral Good",
|
| 70 |
+
race_traits=["Darkvision", "Fey Ancestry"],
|
| 71 |
+
class_features=["Spellcasting", "Arcane Recovery"],
|
| 72 |
+
proficiencies=["Daggers", "Quarterstaffs", "Light crossbows"],
|
| 73 |
+
equipment=["Quarterstaff", "Spellbook", "Component Pouch", "Scholar's Pack"],
|
| 74 |
+
spells=["Fire Bolt", "Mage Hand", "Magic Missile", "Shield"]
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
# Global state
|
| 78 |
+
current_character = None
|
| 79 |
+
conversation_history = []
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def load_character(character_choice):
|
| 83 |
+
"""Load selected character and update context."""
|
| 84 |
+
global current_character, conversation_history
|
| 85 |
+
|
| 86 |
+
conversation_history = []
|
| 87 |
+
|
| 88 |
+
if character_choice == "Thorin Stormshield (Dwarf Fighter)":
|
| 89 |
+
current_character = THORIN
|
| 90 |
+
else:
|
| 91 |
+
current_character = ELARA
|
| 92 |
+
|
| 93 |
+
# Set GM context
|
| 94 |
+
char = current_character
|
| 95 |
+
mods = char.get_modifiers()
|
| 96 |
+
|
| 97 |
+
context = f"""The player is {char.name}, a level {char.level} {char.race} {char.character_class}.
|
| 98 |
+
|
| 99 |
+
PLAYER CHARACTER STATS:
|
| 100 |
+
- HP: {char.hit_points}/{char.hit_points} | AC: {char.armor_class} | Prof Bonus: +{char.proficiency_bonus}
|
| 101 |
+
- STR: {char.strength} ({mods['strength']:+d}) | DEX: {char.dexterity} ({mods['dexterity']:+d}) | CON: {char.constitution} ({mods['constitution']:+d})
|
| 102 |
+
- INT: {char.intelligence} ({mods['intelligence']:+d}) | WIS: {char.wisdom} ({mods['wisdom']:+d}) | CHA: {char.charisma} ({mods['charisma']:+d})
|
| 103 |
+
|
| 104 |
+
EQUIPMENT: {', '.join(char.equipment[:5])}
|
| 105 |
+
"""
|
| 106 |
+
|
| 107 |
+
if char.spells:
|
| 108 |
+
context += f"\nSPELLS: {', '.join(char.spells[:5])}"
|
| 109 |
+
|
| 110 |
+
gm.set_context(context)
|
| 111 |
+
|
| 112 |
+
# Return character sheet
|
| 113 |
+
return format_character_sheet(), "", []
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def format_character_sheet():
|
| 117 |
+
"""Format character sheet for display."""
|
| 118 |
+
if not current_character:
|
| 119 |
+
return "No character selected"
|
| 120 |
+
|
| 121 |
+
char = current_character
|
| 122 |
+
mods = char.get_modifiers()
|
| 123 |
+
|
| 124 |
+
sheet = f"""# {char.name}
|
| 125 |
+
**{char.race} {char.character_class}, Level {char.level}**
|
| 126 |
+
*{char.background} | {char.alignment}*
|
| 127 |
+
|
| 128 |
+
---
|
| 129 |
+
|
| 130 |
+
### Combat Stats
|
| 131 |
+
- **HP**: {char.hit_points}
|
| 132 |
+
- **AC**: {char.armor_class}
|
| 133 |
+
- **Proficiency Bonus**: +{char.proficiency_bonus}
|
| 134 |
+
|
| 135 |
+
### Ability Scores
|
| 136 |
+
| Ability | Score | Modifier |
|
| 137 |
+
|---------|-------|----------|
|
| 138 |
+
| STR | {char.strength} | {mods['strength']:+d} |
|
| 139 |
+
| DEX | {char.dexterity} | {mods['dexterity']:+d} |
|
| 140 |
+
| CON | {char.constitution} | {mods['constitution']:+d} |
|
| 141 |
+
| INT | {char.intelligence} | {mods['intelligence']:+d} |
|
| 142 |
+
| WIS | {char.wisdom} | {mods['wisdom']:+d} |
|
| 143 |
+
| CHA | {char.charisma} | {mods['charisma']:+d} |
|
| 144 |
+
|
| 145 |
+
### Equipment
|
| 146 |
+
{chr(10).join('- ' + item for item in char.equipment)}
|
| 147 |
+
|
| 148 |
+
"""
|
| 149 |
+
|
| 150 |
+
if char.spells:
|
| 151 |
+
sheet += f"""### Spells
|
| 152 |
+
{chr(10).join('- ' + spell for spell in char.spells)}
|
| 153 |
+
"""
|
| 154 |
+
|
| 155 |
+
return sheet
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def chat(message, history):
|
| 159 |
+
"""Handle chat messages."""
|
| 160 |
+
global conversation_history
|
| 161 |
+
|
| 162 |
+
if not current_character:
|
| 163 |
+
return history + [("Please select a character first!", "")]
|
| 164 |
+
|
| 165 |
+
if not message.strip():
|
| 166 |
+
return history
|
| 167 |
+
|
| 168 |
+
# Handle special commands
|
| 169 |
+
if message.startswith("/"):
|
| 170 |
+
cmd = message.lower().strip()
|
| 171 |
+
|
| 172 |
+
if cmd == "/help":
|
| 173 |
+
help_text = """**Available Commands:**
|
| 174 |
+
- `/help` - Show this help
|
| 175 |
+
- `/context` - Show current scene context
|
| 176 |
+
- `/stats` - Show character stats
|
| 177 |
+
- `/rag <query>` - Search D&D rules (e.g., `/rag fireball`)
|
| 178 |
+
|
| 179 |
+
Otherwise, just type your action and press Enter!"""
|
| 180 |
+
return history + [(message, help_text)]
|
| 181 |
+
|
| 182 |
+
elif cmd == "/context":
|
| 183 |
+
context_text = gm.session.context
|
| 184 |
+
return history + [(message, f"**Current Context:**\n\n{context_text}")]
|
| 185 |
+
|
| 186 |
+
elif cmd == "/stats":
|
| 187 |
+
stats = format_character_sheet()
|
| 188 |
+
return history + [(message, stats)]
|
| 189 |
+
|
| 190 |
+
elif cmd.startswith("/rag "):
|
| 191 |
+
query = cmd[5:].strip()
|
| 192 |
+
if query:
|
| 193 |
+
results = gm.search_rag(query, n_results=2)
|
| 194 |
+
formatted = gm.format_rag_context(results)
|
| 195 |
+
return history + [(message, f"**RAG Search Results:**\n\n{formatted}")]
|
| 196 |
+
else:
|
| 197 |
+
return history + [(message, "Usage: `/rag <query>` (e.g., `/rag magic missile`)")]
|
| 198 |
+
|
| 199 |
+
else:
|
| 200 |
+
return history + [(message, f"Unknown command: {cmd}\nType `/help` for available commands")]
|
| 201 |
+
|
| 202 |
+
# Generate GM response
|
| 203 |
+
try:
|
| 204 |
+
response = gm.generate_response(message, use_rag=True)
|
| 205 |
+
conversation_history.append((message, response))
|
| 206 |
+
return history + [(message, response)]
|
| 207 |
+
except Exception as e:
|
| 208 |
+
error_msg = f"Error: {str(e)}\n\nMake sure Ollama is running and the model is installed:\n`ollama pull hf.co/Chun121/Qwen3-4B-RPG-Roleplay-V2:Q4_K_M`"
|
| 209 |
+
return history + [(message, error_msg)]
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def clear_history():
|
| 213 |
+
"""Clear conversation history."""
|
| 214 |
+
global conversation_history
|
| 215 |
+
conversation_history = []
|
| 216 |
+
return []
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
# Create Gradio interface
|
| 220 |
+
with gr.Blocks(title="D&D RAG Game Master") as demo:
|
| 221 |
+
gr.Markdown("""
|
| 222 |
+
# 🎲 D&D Character-Aware Game Master
|
| 223 |
+
|
| 224 |
+
Play D&D with an AI Game Master powered by RAG (Retrieval-Augmented Generation) and the Qwen3-4B-RPG-Roleplay model.
|
| 225 |
+
|
| 226 |
+
**How to Play:**
|
| 227 |
+
1. Select a character
|
| 228 |
+
2. Type your actions (e.g., "I look around", "I cast Magic Missile at the goblin")
|
| 229 |
+
3. Use `/help` to see available commands
|
| 230 |
+
""")
|
| 231 |
+
|
| 232 |
+
with gr.Row():
|
| 233 |
+
with gr.Column(scale=1):
|
| 234 |
+
gr.Markdown("## Character Selection")
|
| 235 |
+
|
| 236 |
+
character_dropdown = gr.Dropdown(
|
| 237 |
+
choices=["Thorin Stormshield (Dwarf Fighter)", "Elara Moonwhisper (Elf Wizard)"],
|
| 238 |
+
value="Thorin Stormshield (Dwarf Fighter)",
|
| 239 |
+
label="Choose Your Character"
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
load_btn = gr.Button("Load Character", variant="primary")
|
| 243 |
+
|
| 244 |
+
gr.Markdown("---")
|
| 245 |
+
|
| 246 |
+
character_sheet = gr.Markdown(format_character_sheet())
|
| 247 |
+
|
| 248 |
+
with gr.Column(scale=2):
|
| 249 |
+
gr.Markdown("## Game Session")
|
| 250 |
+
|
| 251 |
+
chatbot = gr.Chatbot(
|
| 252 |
+
height=500,
|
| 253 |
+
label="Game Master",
|
| 254 |
+
show_label=True,
|
| 255 |
+
avatar_images=(None, "🎭")
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
with gr.Row():
|
| 259 |
+
msg_input = gr.Textbox(
|
| 260 |
+
placeholder="Type your action or command here... (e.g., 'I look around' or '/help')",
|
| 261 |
+
label="Your Action",
|
| 262 |
+
show_label=False,
|
| 263 |
+
scale=4
|
| 264 |
+
)
|
| 265 |
+
submit_btn = gr.Button("Send", variant="primary", scale=1)
|
| 266 |
+
|
| 267 |
+
with gr.Row():
|
| 268 |
+
clear_btn = gr.Button("Clear History")
|
| 269 |
+
gr.Markdown("**Quick Commands:** `/help` | `/context` | `/stats` | `/rag <query>`")
|
| 270 |
+
|
| 271 |
+
gr.Markdown("""
|
| 272 |
+
---
|
| 273 |
+
|
| 274 |
+
### 📖 Example Actions
|
| 275 |
+
- "I look around and check my surroundings"
|
| 276 |
+
- "I draw my weapon and prepare for combat"
|
| 277 |
+
- "I cast Magic Missile at the goblin" (Elara)
|
| 278 |
+
- "I attack with my longsword" (Thorin)
|
| 279 |
+
|
| 280 |
+
### 🔍 RAG Commands
|
| 281 |
+
- `/rag Magic Missile` - Look up spell details
|
| 282 |
+
- `/rag Goblin` - Look up monster stats
|
| 283 |
+
- `/rag Fighter` - Look up class features
|
| 284 |
+
|
| 285 |
+
**Powered by:** ChromaDB RAG + Ollama (Qwen3-4B-RPG-Roleplay-V2)
|
| 286 |
+
""")
|
| 287 |
+
|
| 288 |
+
# Event handlers
|
| 289 |
+
load_btn.click(
|
| 290 |
+
load_character,
|
| 291 |
+
inputs=[character_dropdown],
|
| 292 |
+
outputs=[character_sheet, msg_input, chatbot]
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
submit_btn.click(
|
| 296 |
+
chat,
|
| 297 |
+
inputs=[msg_input, chatbot],
|
| 298 |
+
outputs=[chatbot]
|
| 299 |
+
).then(
|
| 300 |
+
lambda: "",
|
| 301 |
+
outputs=[msg_input]
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
msg_input.submit(
|
| 305 |
+
chat,
|
| 306 |
+
inputs=[msg_input, chatbot],
|
| 307 |
+
outputs=[chatbot]
|
| 308 |
+
).then(
|
| 309 |
+
lambda: "",
|
| 310 |
+
outputs=[msg_input]
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
clear_btn.click(
|
| 314 |
+
clear_history,
|
| 315 |
+
outputs=[chatbot]
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
if __name__ == "__main__":
|
| 320 |
+
# Load default character
|
| 321 |
+
load_character("Thorin Stormshield (Dwarf Fighter)")
|
| 322 |
+
|
| 323 |
+
# Launch
|
| 324 |
+
demo.launch(
|
| 325 |
+
server_name="0.0.0.0",
|
| 326 |
+
server_port=7860,
|
| 327 |
+
share=False
|
| 328 |
+
)
|
|
@@ -17,6 +17,7 @@
|
|
| 17 |
| **Phase 5: GM Dialogue** | ✅ Complete | 2/2 | RAG-enhanced AI GM working |
|
| 18 |
| **Phase 6: Character Creation** | ✅ Complete | 2/2 | Full character creator with RAG |
|
| 19 |
| **Phase 7: Testing & Validation** | ✅ Complete | 3/3 | 26+ comprehensive tests passing |
|
|
|
|
| 20 |
|
| 21 |
**Legend**: ✅ Complete | 🚧 In Progress | ⏳ Pending | ❌ Blocked
|
| 22 |
|
|
@@ -223,6 +224,97 @@ python query_rag.py --monster "dragon" # Search monsters
|
|
| 223 |
|
| 224 |
---
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
## 📦 Supporting Files ✅ COMPLETE
|
| 227 |
|
| 228 |
### ✅ Dependencies
|
|
@@ -295,10 +387,20 @@ python query_rag.py --monster "dragon" # Search monsters
|
|
| 295 |
5. ✅ **Interactive Query Tool** - New CLI for exploring the RAG system
|
| 296 |
6. ✅ **Comprehensive Tests** - 26+ automated tests validating all functionality
|
| 297 |
|
| 298 |
-
### Known Issues
|
| 299 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
|
| 301 |
-
### Future Enhancements
|
| 302 |
- ⏳ **Subrace Support**: High Elf, Mountain Dwarf, etc. with specific abilities
|
| 303 |
- ⏳ **Advanced Filtering**: Search by CR range, spell level range, class, type
|
| 304 |
- ⏳ **Web UI**: Web interface for GM dialogue
|
|
@@ -318,7 +420,10 @@ python query_rag.py --monster "dragon" # Search monsters
|
|
| 318 |
| 2024-11-06 15:00 | Phase 3-6 complete (initialization, query, GM, character creator) |
|
| 319 |
| 2024-11-06 18:00 | **Major upgrades**: Name weighting, race extraction, comprehensive tests |
|
| 320 |
| 2024-11-06 20:00 | **Phase 7 complete**: All tests passing, documentation updated |
|
| 321 |
-
| 2024-11-06 21:00 | **
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
---
|
| 324 |
|
|
@@ -354,9 +459,15 @@ python query_rag.py --monster "dragon" # Search monsters
|
|
| 354 |
|
| 355 |
---
|
| 356 |
|
| 357 |
-
**Status**:
|
| 358 |
-
**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
|
| 360 |
---
|
| 361 |
|
| 362 |
-
**Last Updated**:
|
|
|
|
| 17 |
| **Phase 5: GM Dialogue** | ✅ Complete | 2/2 | RAG-enhanced AI GM working |
|
| 18 |
| **Phase 6: Character Creation** | ✅ Complete | 2/2 | Full character creator with RAG |
|
| 19 |
| **Phase 7: Testing & Validation** | ✅ Complete | 3/3 | 26+ comprehensive tests passing |
|
| 20 |
+
| **Phase 8: Game Mechanics Engine** | 🚧 In Progress | 0/5 | Character-aware gameplay enhancements |
|
| 21 |
|
| 22 |
**Legend**: ✅ Complete | 🚧 In Progress | ⏳ Pending | ❌ Blocked
|
| 23 |
|
|
|
|
| 224 |
|
| 225 |
---
|
| 226 |
|
| 227 |
+
## 🎮 Phase 8: Game Mechanics Engine 🚧 IN PROGRESS
|
| 228 |
+
|
| 229 |
+
**Goal**: Transform AI from rule-maker to narrator by implementing programmatic game mechanics
|
| 230 |
+
|
| 231 |
+
### ✅ 8.0 Character-Aware Dialogue System **⭐ NEW!**
|
| 232 |
+
**File**: `play_with_character.py`
|
| 233 |
+
- [x] Load or create characters for gameplay
|
| 234 |
+
- [x] Character context passed to GM (stats, equipment, spells)
|
| 235 |
+
- [x] Three character modes: Create new, Load JSON, Quick test
|
| 236 |
+
- [x] Commands: `/character`, `/stats`, `/context` for character info
|
| 237 |
+
- [x] Fixed tokenizer warning suppression
|
| 238 |
+
- [x] Dynamic character support (not hardcoded to one character)
|
| 239 |
+
- [x] Proper first/second person context ("The player is X" → AI uses "you")
|
| 240 |
+
- [x] Integration testing completed (Dec 1, 2024)
|
| 241 |
+
|
| 242 |
+
### 🚧 8.1 Spell System Enhancement
|
| 243 |
+
**File**: `play_with_character.py` (to be refactored)
|
| 244 |
+
- [ ] Programmatic spell validation (check if player owns spell)
|
| 245 |
+
- [ ] Spell slot tracking by level (1st: 3 slots, 2nd: 2 slots, etc.)
|
| 246 |
+
- [ ] Auto-decrement slots when casting
|
| 247 |
+
- [ ] Rest mechanic to restore slots
|
| 248 |
+
- [ ] Spell lookup from RAG before AI narration
|
| 249 |
+
|
| 250 |
+
### 🚧 8.2 Combat Mechanics
|
| 251 |
+
**Status**: ⏳ Pending
|
| 252 |
+
- [ ] HP tracking and damage application
|
| 253 |
+
- [ ] Attack roll automation (d20 + modifiers)
|
| 254 |
+
- [ ] Damage roll automation (weapon dice + STR/DEX)
|
| 255 |
+
- [ ] AC checks (hit/miss determination)
|
| 256 |
+
- [ ] Death saves and unconsciousness
|
| 257 |
+
|
| 258 |
+
### 🚧 8.3 Turn & Initiative System
|
| 259 |
+
**Status**: ⏳ Pending
|
| 260 |
+
- [ ] Initiative roller (d20 + DEX modifier)
|
| 261 |
+
- [ ] Turn order tracking
|
| 262 |
+
- [ ] Action economy (action, bonus action, movement, reaction)
|
| 263 |
+
- [ ] Combat state management
|
| 264 |
+
|
| 265 |
+
### 🚧 8.4 Inventory System
|
| 266 |
+
**Status**: ⏳ Pending
|
| 267 |
+
- [ ] Add/remove items programmatically
|
| 268 |
+
- [ ] Equipment weight tracking
|
| 269 |
+
- [ ] Item usage validation
|
| 270 |
+
- [ ] Gold/currency management
|
| 271 |
+
|
| 272 |
+
### 🚧 8.5 Integration & Testing
|
| 273 |
+
**Status**: ⏳ Pending
|
| 274 |
+
- [ ] Test spell casting flow end-to-end
|
| 275 |
+
- [ ] Test combat scenarios
|
| 276 |
+
- [ ] Test inventory management
|
| 277 |
+
- [ ] Update documentation
|
| 278 |
+
|
| 279 |
+
---
|
| 280 |
+
|
| 281 |
+
## 🏗️ Phase 8 Architecture Notes
|
| 282 |
+
|
| 283 |
+
### Option B: Hybrid AI + Rules Engine (SELECTED)
|
| 284 |
+
|
| 285 |
+
**Problem**: AI is unreliable at following D&D rules consistently
|
| 286 |
+
- Ignores spells player casts
|
| 287 |
+
- Allows spells player doesn't know
|
| 288 |
+
- Doesn't track resources (HP, spell slots)
|
| 289 |
+
- Makes up mechanics on the fly
|
| 290 |
+
|
| 291 |
+
**Solution**: Intercept player actions BEFORE AI sees them
|
| 292 |
+
|
| 293 |
+
**Flow**:
|
| 294 |
+
1. **Player Input**: "I cast Magic Missile at the goblin"
|
| 295 |
+
2. **Rules Engine** (Python code):
|
| 296 |
+
- Parse: Detect spell casting intent
|
| 297 |
+
- Validate: Check if player owns "Magic Missile" ✓
|
| 298 |
+
- Validate: Check if player has 1st-level spell slot ✓
|
| 299 |
+
- Retrieve: Get spell details from RAG (3 darts, 1d4+1 each)
|
| 300 |
+
- Roll: 3d4+3 = 11 damage (programmatically)
|
| 301 |
+
- Deduct: Spell slot consumed
|
| 302 |
+
- Update: Target HP reduced by 11
|
| 303 |
+
3. **AI Prompt**: "You successfully cast Magic Missile dealing 11 force damage to the goblin. The goblin now has 5 HP remaining. Describe the magical missiles striking the goblin."
|
| 304 |
+
4. **AI Response**: (Just narrates the flavor, mechanics already handled)
|
| 305 |
+
|
| 306 |
+
**Benefits**:
|
| 307 |
+
- AI becomes a **narrator**, not a **rules engine**
|
| 308 |
+
- Mechanics are deterministic and accurate
|
| 309 |
+
- AI can focus on storytelling
|
| 310 |
+
- Players can trust the rules
|
| 311 |
+
|
| 312 |
+
**Alternatives Rejected**:
|
| 313 |
+
- **Option A** (Pure AI): Too unreliable, tested and failed
|
| 314 |
+
- **Option C** (Post-process AI): Too hard to fix bad outputs
|
| 315 |
+
|
| 316 |
+
---
|
| 317 |
+
|
| 318 |
## 📦 Supporting Files ✅ COMPLETE
|
| 319 |
|
| 320 |
### ✅ Dependencies
|
|
|
|
| 387 |
5. ✅ **Interactive Query Tool** - New CLI for exploring the RAG system
|
| 388 |
6. ✅ **Comprehensive Tests** - 26+ automated tests validating all functionality
|
| 389 |
|
| 390 |
+
### Known Issues (Phase 8 Discovery - Dec 1, 2024)
|
| 391 |
+
- **AI Unreliability**: Pure AI approach fails to consistently enforce D&D rules
|
| 392 |
+
- Ignores valid spell casts (Magic Missile cast was turned into melee combat)
|
| 393 |
+
- Allows invalid spells (Let Elara cast Fireball, which she doesn't know)
|
| 394 |
+
- No resource tracking (spell slots, HP, gold)
|
| 395 |
+
- **Solution**: Moving to Hybrid Architecture (Option B) with programmatic rules engine
|
| 396 |
+
|
| 397 |
+
### Current Work (Phase 8)
|
| 398 |
+
- 🚧 **Spell Validation System**: Programmatically check spell ownership before AI generation
|
| 399 |
+
- 🚧 **Resource Tracking**: HP, spell slots, inventory management
|
| 400 |
+
- 🚧 **Combat Mechanics**: Attack/damage rolls, initiative, turn tracking
|
| 401 |
+
- 🚧 **Rules Engine**: Intercept player actions, apply mechanics, then AI narrates
|
| 402 |
|
| 403 |
+
### Future Enhancements (Post-Phase 8)
|
| 404 |
- ⏳ **Subrace Support**: High Elf, Mountain Dwarf, etc. with specific abilities
|
| 405 |
- ⏳ **Advanced Filtering**: Search by CR range, spell level range, class, type
|
| 406 |
- ⏳ **Web UI**: Web interface for GM dialogue
|
|
|
|
| 420 |
| 2024-11-06 15:00 | Phase 3-6 complete (initialization, query, GM, character creator) |
|
| 421 |
| 2024-11-06 18:00 | **Major upgrades**: Name weighting, race extraction, comprehensive tests |
|
| 422 |
| 2024-11-06 20:00 | **Phase 7 complete**: All tests passing, documentation updated |
|
| 423 |
+
| 2024-11-06 21:00 | **V1.0 COMPLETE** - Production ready! |
|
| 424 |
+
| 2024-12-01 18:00 | **Phase 8 started**: Character-aware dialogue system created |
|
| 425 |
+
| 2024-12-01 19:00 | Testing reveals AI reliability issues with game mechanics |
|
| 426 |
+
| 2024-12-01 19:30 | **Architecture decision**: Option B (Hybrid Rules Engine) selected |
|
| 427 |
|
| 428 |
---
|
| 429 |
|
|
|
|
| 459 |
|
| 460 |
---
|
| 461 |
|
| 462 |
+
**Status**: 🚧 **V2.0 IN DEVELOPMENT** (Phase 8: Game Mechanics Engine)
|
| 463 |
+
**Current Focus**: Implementing hybrid AI + Rules Engine architecture
|
| 464 |
+
**Next Steps**:
|
| 465 |
+
1. Spell validation system
|
| 466 |
+
2. Spell slot tracking
|
| 467 |
+
3. HP and damage mechanics
|
| 468 |
+
4. Combat turn system
|
| 469 |
+
5. Inventory management
|
| 470 |
|
| 471 |
---
|
| 472 |
|
| 473 |
+
**Last Updated**: December 1, 2024 19:30
|
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
D&D Character-Aware Game Session
|
| 4 |
+
|
| 5 |
+
Integrated test script that:
|
| 6 |
+
1. Creates or loads a character
|
| 7 |
+
2. Starts a GM dialogue session with character context
|
| 8 |
+
3. Allows playing with character stats visible to GM
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import os
|
| 12 |
+
# Suppress tokenizer warning when forking subprocess
|
| 13 |
+
os.environ['TOKENIZERS_PARALLELISM'] = 'false'
|
| 14 |
+
|
| 15 |
+
import sys
|
| 16 |
+
import json
|
| 17 |
+
from pathlib import Path
|
| 18 |
+
|
| 19 |
+
# Add project to path
|
| 20 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
| 21 |
+
|
| 22 |
+
from dnd_rag_system.core.chroma_manager import ChromaDBManager
|
| 23 |
+
from dnd_rag_system.systems.character_creator import CharacterCreator, Character
|
| 24 |
+
from dnd_rag_system.systems.gm_dialogue import GameMaster, InteractiveGM
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class CharacterAwareGM(InteractiveGM):
|
| 28 |
+
"""
|
| 29 |
+
Enhanced GM that knows about the player's character.
|
| 30 |
+
|
| 31 |
+
Extends InteractiveGM to include character information in prompts.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(self, gm: GameMaster, character: Character):
|
| 35 |
+
"""Initialize with character context."""
|
| 36 |
+
super().__init__(gm)
|
| 37 |
+
self.character = character
|
| 38 |
+
|
| 39 |
+
# Set initial scene with character info
|
| 40 |
+
self._initialize_character_context()
|
| 41 |
+
|
| 42 |
+
def _initialize_character_context(self):
|
| 43 |
+
"""Add character to GM's context."""
|
| 44 |
+
char = self.character
|
| 45 |
+
mods = char.get_modifiers()
|
| 46 |
+
|
| 47 |
+
context = f"""The player is {char.name}, a level {char.level} {char.race} {char.character_class}.
|
| 48 |
+
|
| 49 |
+
PLAYER CHARACTER STATS:
|
| 50 |
+
- HP: {char.hit_points}/{char.hit_points} | AC: {char.armor_class} | Prof Bonus: +{char.proficiency_bonus}
|
| 51 |
+
- STR: {char.strength} ({mods['strength']:+d}) | DEX: {char.dexterity} ({mods['dexterity']:+d}) | CON: {char.constitution} ({mods['constitution']:+d})
|
| 52 |
+
- INT: {char.intelligence} ({mods['intelligence']:+d}) | WIS: {char.wisdom} ({mods['wisdom']:+d}) | CHA: {char.charisma} ({mods['charisma']:+d})
|
| 53 |
+
|
| 54 |
+
EQUIPMENT: {', '.join(char.equipment[:5])}
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
if char.spells:
|
| 58 |
+
context += f"\nSPELLS: {', '.join(char.spells[:5])}"
|
| 59 |
+
|
| 60 |
+
self.gm.set_context(context)
|
| 61 |
+
|
| 62 |
+
def print_help(self):
|
| 63 |
+
"""Override help to add character commands."""
|
| 64 |
+
super().print_help()
|
| 65 |
+
print("\nCHARACTER COMMANDS:")
|
| 66 |
+
print(" /character - Show character sheet")
|
| 67 |
+
print(" /stats - Quick stats view")
|
| 68 |
+
print()
|
| 69 |
+
|
| 70 |
+
def run(self):
|
| 71 |
+
"""Override run to handle character commands."""
|
| 72 |
+
print("\n" + "="*70)
|
| 73 |
+
print(f"🎲 D&D GAME SESSION - Playing as {self.character.name}")
|
| 74 |
+
print("="*70)
|
| 75 |
+
print(f"Character: {self.character.name} ({self.character.race} {self.character.character_class})")
|
| 76 |
+
print(f"Model: {self.gm.model_name}")
|
| 77 |
+
print(f"Database: {self.gm.db.persist_dir}")
|
| 78 |
+
print("\nType /help for commands or start playing!")
|
| 79 |
+
print("="*70)
|
| 80 |
+
|
| 81 |
+
self.running = True
|
| 82 |
+
use_rag_next = True
|
| 83 |
+
|
| 84 |
+
while self.running:
|
| 85 |
+
try:
|
| 86 |
+
# Get player input
|
| 87 |
+
player_input = input(f"\n🎲 {self.character.name}: ").strip()
|
| 88 |
+
|
| 89 |
+
if not player_input:
|
| 90 |
+
continue
|
| 91 |
+
|
| 92 |
+
# Handle commands
|
| 93 |
+
if player_input.startswith('/'):
|
| 94 |
+
# Check for character-specific commands first
|
| 95 |
+
if player_input.lower() == '/character':
|
| 96 |
+
self._show_character_sheet()
|
| 97 |
+
continue
|
| 98 |
+
elif player_input.lower() == '/stats':
|
| 99 |
+
self._show_quick_stats()
|
| 100 |
+
continue
|
| 101 |
+
else:
|
| 102 |
+
# Use parent's command handler
|
| 103 |
+
self._handle_command(player_input)
|
| 104 |
+
continue
|
| 105 |
+
|
| 106 |
+
# Generate GM response
|
| 107 |
+
print("\n🎭 GM: ", end="", flush=True)
|
| 108 |
+
response = self.gm.generate_response(player_input, use_rag=use_rag_next)
|
| 109 |
+
print(response)
|
| 110 |
+
|
| 111 |
+
# Reset RAG flag
|
| 112 |
+
use_rag_next = True
|
| 113 |
+
|
| 114 |
+
except KeyboardInterrupt:
|
| 115 |
+
print("\n\n👋 Game interrupted. Type /quit to exit or continue playing.")
|
| 116 |
+
except Exception as e:
|
| 117 |
+
print(f"\n❌ Error: {e}")
|
| 118 |
+
|
| 119 |
+
def _show_character_sheet(self):
|
| 120 |
+
"""Display full character sheet."""
|
| 121 |
+
char = self.character
|
| 122 |
+
mods = char.get_modifiers()
|
| 123 |
+
|
| 124 |
+
print("\n" + "="*70)
|
| 125 |
+
print(f"CHARACTER SHEET - {char.name}")
|
| 126 |
+
print("="*70)
|
| 127 |
+
print(f"{char.race} {char.character_class}, Level {char.level}")
|
| 128 |
+
print(f"{char.background} | {char.alignment}")
|
| 129 |
+
print("\n" + "─"*70)
|
| 130 |
+
|
| 131 |
+
print("\nABILITY SCORES:")
|
| 132 |
+
print(f" STR: {char.strength:2d} ({mods['strength']:+d}) | INT: {char.intelligence:2d} ({mods['intelligence']:+d})")
|
| 133 |
+
print(f" DEX: {char.dexterity:2d} ({mods['dexterity']:+d}) | WIS: {char.wisdom:2d} ({mods['wisdom']:+d})")
|
| 134 |
+
print(f" CON: {char.constitution:2d} ({mods['constitution']:+d}) | CHA: {char.charisma:2d} ({mods['charisma']:+d})")
|
| 135 |
+
|
| 136 |
+
print(f"\nCOMBAT STATS:")
|
| 137 |
+
print(f" HP: {char.hit_points} | AC: {char.armor_class} | Proficiency: +{char.proficiency_bonus}")
|
| 138 |
+
|
| 139 |
+
print(f"\nEQUIPMENT:")
|
| 140 |
+
for item in char.equipment:
|
| 141 |
+
print(f" - {item}")
|
| 142 |
+
|
| 143 |
+
if char.spells:
|
| 144 |
+
print(f"\nSPELLS:")
|
| 145 |
+
for spell in char.spells:
|
| 146 |
+
print(f" - {spell}")
|
| 147 |
+
|
| 148 |
+
print("="*70)
|
| 149 |
+
|
| 150 |
+
def _show_quick_stats(self):
|
| 151 |
+
"""Show condensed stats."""
|
| 152 |
+
char = self.character
|
| 153 |
+
mods = char.get_modifiers()
|
| 154 |
+
|
| 155 |
+
print(f"\n📊 {char.name} | HP: {char.hit_points} | AC: {char.armor_class} | Prof: +{char.proficiency_bonus}")
|
| 156 |
+
print(f" STR {mods['strength']:+d} | DEX {mods['dexterity']:+d} | CON {mods['constitution']:+d} | INT {mods['intelligence']:+d} | WIS {mods['wisdom']:+d} | CHA {mods['charisma']:+d}")
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def load_character_from_json(filepath: str) -> Character:
|
| 160 |
+
"""Load character from JSON file."""
|
| 161 |
+
with open(filepath, 'r') as f:
|
| 162 |
+
data = json.load(f)
|
| 163 |
+
|
| 164 |
+
# Create Character object from dict
|
| 165 |
+
char = Character()
|
| 166 |
+
for key, value in data.items():
|
| 167 |
+
if hasattr(char, key):
|
| 168 |
+
setattr(char, key, value)
|
| 169 |
+
|
| 170 |
+
return char
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def main():
|
| 174 |
+
"""Main entry point for character-aware game session."""
|
| 175 |
+
print("\n" + "="*70)
|
| 176 |
+
print("🎲 D&D CHARACTER-AWARE GAME SESSION")
|
| 177 |
+
print("="*70)
|
| 178 |
+
|
| 179 |
+
try:
|
| 180 |
+
# Initialize ChromaDB
|
| 181 |
+
print("\n📚 Connecting to D&D knowledge base...")
|
| 182 |
+
db = ChromaDBManager()
|
| 183 |
+
|
| 184 |
+
# Character selection
|
| 185 |
+
print("\n" + "─"*70)
|
| 186 |
+
print("CHARACTER SELECTION")
|
| 187 |
+
print("─"*70)
|
| 188 |
+
print("\n1. Create new character")
|
| 189 |
+
print("2. Load existing character")
|
| 190 |
+
print("3. Use test character (quick start)")
|
| 191 |
+
|
| 192 |
+
choice = input("\nSelect option (1-3): ").strip()
|
| 193 |
+
|
| 194 |
+
if choice == "1":
|
| 195 |
+
# Create new character
|
| 196 |
+
print("\n🎭 Starting character creator...")
|
| 197 |
+
creator = CharacterCreator(db)
|
| 198 |
+
character = creator.create_character_interactive()
|
| 199 |
+
|
| 200 |
+
# Save option
|
| 201 |
+
save = input("\nSave character? (y/n): ").strip().lower()
|
| 202 |
+
if save == 'y':
|
| 203 |
+
filename = input("Filename (e.g., aragorn.json): ").strip()
|
| 204 |
+
if not filename.endswith('.json'):
|
| 205 |
+
filename += '.json'
|
| 206 |
+
character.to_json(filename)
|
| 207 |
+
|
| 208 |
+
elif choice == "2":
|
| 209 |
+
# Load existing character
|
| 210 |
+
filename = input("\nCharacter filename: ").strip()
|
| 211 |
+
if not filename.endswith('.json'):
|
| 212 |
+
filename += '.json'
|
| 213 |
+
|
| 214 |
+
character = load_character_from_json(filename)
|
| 215 |
+
print(f"\n✅ Loaded: {character.name} ({character.race} {character.character_class})")
|
| 216 |
+
|
| 217 |
+
else:
|
| 218 |
+
# Quick test character
|
| 219 |
+
print("\n⚡ Creating test character...")
|
| 220 |
+
character = Character(
|
| 221 |
+
name="Thorin Stormshield",
|
| 222 |
+
race="Dwarf",
|
| 223 |
+
character_class="Fighter",
|
| 224 |
+
level=3,
|
| 225 |
+
strength=16,
|
| 226 |
+
dexterity=12,
|
| 227 |
+
constitution=16,
|
| 228 |
+
intelligence=10,
|
| 229 |
+
wisdom=13,
|
| 230 |
+
charisma=8,
|
| 231 |
+
hit_points=28,
|
| 232 |
+
armor_class=18,
|
| 233 |
+
proficiency_bonus=2,
|
| 234 |
+
background="Soldier",
|
| 235 |
+
alignment="Lawful Good",
|
| 236 |
+
race_traits=["Dwarven Resilience", "Darkvision"],
|
| 237 |
+
class_features=["Fighting Style: Defense", "Second Wind", "Action Surge"],
|
| 238 |
+
proficiencies=["All armor", "All weapons", "Shields"],
|
| 239 |
+
equipment=["Longsword", "Shield", "Plate Armor", "Backpack", "50 GP"],
|
| 240 |
+
spells=[]
|
| 241 |
+
)
|
| 242 |
+
print(f"✅ Created: {character.name} - a battle-ready dwarf fighter!")
|
| 243 |
+
|
| 244 |
+
# Initialize Game Master with character
|
| 245 |
+
print("\n🎭 Initializing AI Game Master...")
|
| 246 |
+
gm = GameMaster(db)
|
| 247 |
+
|
| 248 |
+
print("✅ Game Master ready!")
|
| 249 |
+
|
| 250 |
+
# Start character-aware session
|
| 251 |
+
interactive = CharacterAwareGM(gm, character)
|
| 252 |
+
interactive.run()
|
| 253 |
+
|
| 254 |
+
except FileNotFoundError as e:
|
| 255 |
+
print(f"\n❌ Character file not found: {e}")
|
| 256 |
+
return 1
|
| 257 |
+
except Exception as e:
|
| 258 |
+
print(f"\n❌ Failed to initialize: {e}")
|
| 259 |
+
print("\nTroubleshooting:")
|
| 260 |
+
print(" 1. Run: python initialize_rag.py")
|
| 261 |
+
print(" 2. Install Ollama: https://ollama.ai")
|
| 262 |
+
print(" 3. Download model: ollama pull hf.co/Chun121/Qwen3-4B-RPG-Roleplay-V2:Q4_K_M")
|
| 263 |
+
return 1
|
| 264 |
+
|
| 265 |
+
return 0
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
if __name__ == '__main__':
|
| 269 |
+
sys.exit(main())
|
|
@@ -9,6 +9,9 @@ pdfplumber>=0.10.0
|
|
| 9 |
# Ollama Python client
|
| 10 |
ollama>=0.1.0
|
| 11 |
|
|
|
|
|
|
|
|
|
|
| 12 |
# Rich console output
|
| 13 |
rich>=13.0.0
|
| 14 |
|
|
|
|
| 9 |
# Ollama Python client
|
| 10 |
ollama>=0.1.0
|
| 11 |
|
| 12 |
+
# Web UI
|
| 13 |
+
gradio>=4.0.0
|
| 14 |
+
|
| 15 |
# Rich console output
|
| 16 |
rich>=13.0.0
|
| 17 |
|