awacke1 commited on
Commit
2703c4f
·
verified ·
1 Parent(s): 7346308

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +405 -316
index.html CHANGED
@@ -13,13 +13,13 @@
13
  margin: 0;
14
  padding: 20px;
15
  }
16
-
17
  h1 {
18
  color: #c00;
19
  text-align: center;
20
  margin-bottom: 20px;
21
  }
22
-
23
  .container {
24
  max-width: 900px;
25
  margin: 0 auto;
@@ -29,7 +29,7 @@
29
  padding: 20px;
30
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
31
  }
32
-
33
  .game-description {
34
  text-align: center;
35
  margin-bottom: 20px;
@@ -45,7 +45,7 @@
45
  <p>An interactive text adventure game powered by Gradio-lite</p>
46
  <p><em>Can you defeat the Evil Power Master and save the land?</em></p>
47
  </div>
48
-
49
  <gradio-lite theme="light">
50
  <gradio-file name="app.py" entrypoint>
51
  import gradio as gr
@@ -60,61 +60,84 @@ def initialize_game():
60
  game_state = GameState()
61
  current_page = 1
62
  page_data = game_data[str(current_page)]
63
-
64
  # Create the SVG illustration
65
- svg_content = create_svg_illustration(page_data["illustration"])
66
-
67
  # Build page content
68
- content = f"<h2>{page_data['title']}</h2>"
69
- content += page_data["content"]
70
-
71
  # Build options
72
  options = []
73
- for opt in page_data["options"]:
74
- options.append(opt["text"])
75
-
 
 
 
 
 
76
  # Update game statistics display
77
  stats = generate_stats_display(game_state) # Use the function here
78
-
79
- # Initialize inventory as empty
80
  inventory = generate_inventory_display(game_state) # Use the function here
81
-
82
  # Set story path
83
  story_path = "You are at the beginning of your adventure."
84
-
85
- return svg_content, content, gr.Dropdown(choices=options, label="What will you do?"), game_state.to_json(), stats, inventory, story_path
86
 
87
  def update_game(choice, game_state_json):
88
  """Update game based on player choice"""
 
 
 
89
  # Parse game state from JSON
90
  game_state_dict = json.loads(game_state_json)
91
  game_state = GameState.from_dict(game_state_dict)
92
-
93
  # Get current page data
94
  current_page = game_state.current_page
95
  # Ensure current_page is always a string for dictionary keys
96
- page_data = game_data[str(current_page)]
97
-
 
 
 
 
 
 
 
98
  # Find the selected option
99
  selected_option = None
100
- for i, opt in enumerate(page_data["options"]):
101
- if opt["text"] == choice:
102
- selected_option = opt
103
- break
104
-
 
 
 
 
 
 
 
105
  if selected_option is None:
106
  # Handle case where choice might be "Restart" or invalid
107
  if choice == "Restart":
108
  return initialize_game()
109
  else:
110
- # Stay on the same page if the choice wasn't found (shouldn't normally happen)
111
- options = [opt["text"] for opt in page_data["options"]]
112
- svg_content = create_svg_illustration(page_data["illustration"])
113
- content = f"<h2>{page_data['title']}</h2>" + page_data["content"] + "<p><i>Invalid choice selected. Please try again.</i></p>"
 
114
  stats_display = generate_stats_display(game_state)
115
  inventory_display = generate_inventory_display(game_state)
116
- story_path_display = f"You remain on page {current_page}: {page_data['title']}"
117
- return svg_content, content, gr.Dropdown(choices=options), game_state.to_json(), stats_display, inventory_display, story_path_display
118
 
119
  # Check if this option requires an item
120
  item_required = selected_option.get("requireItem")
@@ -126,7 +149,7 @@ def update_game(choice, game_state_json):
126
  stats_display = generate_stats_display(game_state)
127
  inventory_display = generate_inventory_display(game_state)
128
  story_path_display = f"You remain on page {current_page}: {page_data['title']}"
129
- return svg_content, content, gr.Dropdown(choices=options), game_state.to_json(), stats_display, inventory_display, story_path_display
130
 
131
  # Check if option requires any item from a list
132
  any_item_required = selected_option.get("requireAnyItem")
@@ -141,48 +164,68 @@ def update_game(choice, game_state_json):
141
  stats_display = generate_stats_display(game_state)
142
  inventory_display = generate_inventory_display(game_state)
143
  story_path_display = f"You remain on page {current_page}: {page_data['title']}"
144
- return svg_content, content, gr.Dropdown(choices=options), game_state.to_json(), stats_display, inventory_display, story_path_display
145
-
146
  # Process special items to collect
147
  item_to_add = selected_option.get("addItem")
148
  if item_to_add and item_to_add not in game_state.inventory:
149
  game_state.inventory.append(item_to_add)
150
-
 
 
 
 
 
 
151
  # Move to the next page
152
  next_page = selected_option["next"]
153
  game_state.current_page = next_page
 
 
 
 
 
 
 
 
154
  if next_page not in game_state.visited_pages: # Avoid duplicates if revisiting
155
  game_state.visited_pages.append(next_page)
156
-
157
  # Update journey progress based on page transitions
158
  game_state.journey_progress += 5 # Increment progress with each decision
159
  if game_state.journey_progress > 100:
160
  game_state.journey_progress = 100
161
-
162
  battle_occurred = False
 
 
 
163
  # Check for random battle (20% chance if page has randomBattle flag)
164
- current_page_data = game_data[str(current_page)] # Data for the page *before* potential challenge/next page logic
165
- if current_page_data.get("randomBattle", False) and random.random() < 0.2:
166
  battle_result, battle_log = simulate_battle(game_state)
167
  battle_occurred = True
168
  if not battle_result:
169
  # Player died in battle
170
  content = "<h2>Game Over</h2><p>You have been defeated in battle!</p>" + battle_log
171
- return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"], label="Game Over"), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), "Game Over - You were defeated in battle."
 
 
172
  else:
173
  # Player survived battle, add battle log to the next page content
174
  battle_message = f"<div style='border: 1px solid green; padding: 10px; margin-top: 10px; background-color: #e8f5e9;'><strong>Battle Won!</strong>{battle_log}</div>"
175
 
176
  # Get new page data (for the destination page 'next_page')
177
- page_data = game_data[str(next_page)]
178
-
179
  # Process page stat effects
180
  stat_increase = page_data.get("statIncrease")
181
  if stat_increase:
182
  stat = stat_increase["stat"]
183
  amount = stat_increase["amount"]
184
- game_state.stats[stat] += amount
185
-
 
186
  # Process HP loss
187
  hp_loss = page_data.get("hpLoss")
188
  if hp_loss:
@@ -190,99 +233,122 @@ def update_game(choice, game_state_json):
190
  if game_state.current_hp <= 0:
191
  game_state.current_hp = 0
192
  content = "<h2>Game Over</h2><p>You have died from your wounds!</p>"
193
- return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"], label="Game Over"), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), "Game Over - You died from your wounds."
194
-
 
 
195
  challenge_log = ""
 
196
  # Check if this is a challenge page
197
  challenge = page_data.get("challenge")
198
  if challenge:
 
199
  success, roll, total = perform_challenge(game_state, challenge)
200
  challenge_log = f"<div style='border: 1px solid #ccc; padding: 10px; margin-top: 10px; background-color: #eee;'>"
201
  challenge_log += f"<strong>Challenge: {challenge.get('title', 'Skill Check')}</strong><br>"
202
  challenge_log += f"Target Stat: {challenge['stat']}, Difficulty: {challenge['difficulty']}<br>"
203
- challenge_log += f"You rolled a {roll} + {game_state.stats[challenge['stat']] - (roll - total)} ({challenge['stat']}) = {total}<br>"
204
-
205
  # Update story based on challenge result
206
  if success:
207
  challenge_log += "<strong style='color: green;'>Success!</strong></div>"
208
  next_page = challenge["success"]
209
  else:
210
- challenge_log += "<strong style='color: red;'>Failure!</strong></div>"
 
211
  next_page = challenge["failure"]
212
-
 
213
  game_state.current_page = next_page
 
 
 
 
 
 
 
 
214
  if next_page not in game_state.visited_pages:
215
  game_state.visited_pages.append(next_page)
216
- page_data = game_data[str(next_page)] # Load data for the page determined by the challenge outcome
217
-
218
  # Create the SVG illustration for the final destination page
219
  svg_content = create_svg_illustration(page_data.get("illustration", "default"))
220
-
221
  # Handle game over on the destination page
222
  if page_data.get("gameOver", False):
223
  content = f"<h2>{page_data['title']}</h2>"
224
  content += page_data["content"]
225
  if battle_occurred and battle_result: content += battle_message # Add battle log if survived
226
- if challenge: content += challenge_log # Add challenge log
227
  if "ending" in page_data:
228
  content += f"<div style='text-align: center; margin-top: 20px; font-weight: bold; color: #c00;'>THE END</div>"
229
  content += f"<p style='font-style: italic;'>{page_data['ending']}</p>"
230
-
231
- return svg_content, content, gr.Dropdown(choices=["Restart"], label="Game Over"), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), "Game Over - " + page_data['title']
232
-
 
 
233
  # Build page content for the final destination page
234
- content = f"<h2>{page_data['title']}</h2>"
235
- content += page_data["content"]
236
  if battle_occurred and battle_result: content += battle_message # Add battle log if survived
237
- if challenge: content += challenge_log # Add challenge log
238
-
239
  # Build options for the final destination page
240
  options = []
241
- if "options" in page_data: # Check if options exist (Game Over pages might not have them)
 
242
  for opt in page_data["options"]:
243
  option_available = True
244
  # Check if option requires an item
245
- if "requireItem" in opt and opt["requireItem"] not in game_state.inventory:
 
246
  option_available = False
247
  # Check if option requires any item from a list
248
- if option_available and "requireAnyItem" in opt:
249
- if not any(item in game_state.inventory for item in opt["requireAnyItem"]):
 
250
  option_available = False
251
-
252
  if option_available:
253
  options.append(opt["text"])
254
-
255
- # Handle alternative option if necessary (only if no primary options are available)
256
  alt_opt_data = page_data.get("alternativeOption")
257
  if alt_opt_data and not options:
258
  alt_show_if = alt_opt_data.get("showIf")
259
  # Show alternative if no condition or condition met
260
- if not alt_show_if or any(item in game_state.inventory for item in alt_show_if):
261
  options.append(alt_opt_data["text"])
262
- # Add the alternative option details to page_data["options"] temporarily
263
- # so the logic finds it if selected next turn
264
  if "options" not in page_data: page_data["options"] = []
265
- page_data["options"].append(alt_opt_data)
 
 
266
 
267
 
268
  if not options: # If still no options (dead end maybe?)
269
- options = ["Restart"]
270
  content += "<p><i>There are no further actions you can take from here.</i></p>"
271
 
272
  # Update game progress display text
273
- story_path = f"You are on page {next_page}: {page_data['title']}"
274
  if game_state.journey_progress >= 80:
275
  story_path += " (Nearing the conclusion)"
276
  elif game_state.journey_progress >= 50:
277
  story_path += " (Middle of your journey)"
278
  elif game_state.journey_progress >= 25:
279
  story_path += " (Adventure beginning)"
280
-
281
  # Generate final displays
282
  stats_display = generate_stats_display(game_state)
283
  inventory_display = generate_inventory_display(game_state)
284
-
285
- return svg_content, content, gr.Dropdown(choices=options, label="What will you do?"), game_state.to_json(), stats_display, inventory_display, story_path
 
 
286
 
287
  def generate_stats_display(game_state):
288
  """Generate HTML for displaying player stats"""
@@ -293,7 +359,7 @@ def generate_stats_display(game_state):
293
  hp_color = "#F44336" # Red
294
  elif hp_percent < 70:
295
  hp_color = "#FFC107" # Yellow
296
-
297
  stats_html = f"""
298
  <div style='display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;'>
299
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
@@ -313,7 +379,7 @@ def generate_stats_display(game_state):
313
  </div>
314
  </div>
315
  """
316
-
317
  # Add journey progress
318
  stats_html += f"""
319
  <div style='margin-top: 10px;'>
@@ -326,20 +392,21 @@ def generate_stats_display(game_state):
326
  </div>
327
  </div>
328
  """
329
-
330
  return stats_html
331
 
332
  def generate_inventory_display(game_state):
333
  """Generate HTML for displaying player inventory"""
334
  if not game_state.inventory:
335
  return "<em>Your inventory is empty.</em>"
336
-
337
- inventory_html = "<div style='display: flex; flex-wrap: wrap; gap: 10px;'>"
338
-
339
  for item in game_state.inventory:
340
  item_data = items_data.get(item, {"type": "unknown", "description": "A mysterious item."})
341
  bg_color = "#e0e0e0" # Default grey
342
  item_type = item_data.get("type", "unknown")
 
343
 
344
  if item_type == "weapon":
345
  bg_color = "#ffcdd2" # Light red
@@ -349,14 +416,16 @@ def generate_inventory_display(game_state):
349
  bg_color = "#bbdefb" # Light blue
350
  elif item_type == "quest":
351
  bg_color = "#fff9c4" # Light yellow
352
-
 
 
353
  inventory_html += f"""
354
- <div style='background: {bg_color}; padding: 5px 10px; border-radius: 5px; position: relative;'
355
- title="{item_data.get('description', 'No description available.')}">
356
  {item}
357
  </div>
358
  """
359
-
360
  inventory_html += "</div>"
361
  return inventory_html
362
 
@@ -364,54 +433,56 @@ def perform_challenge(game_state, challenge):
364
  """Perform a skill challenge and determine success. Returns (success_bool, roll, total)"""
365
  stat = challenge["stat"]
366
  difficulty = challenge["difficulty"]
367
-
368
  # Roll dice (1-6) and add stat
369
  roll = random.randint(1, 6)
370
  player_stat_value = game_state.stats.get(stat, 0) # Default to 0 if stat doesn't exist
371
  total = roll + player_stat_value
372
-
373
  # Determine if successful
374
  success = total >= difficulty
375
-
376
  # Bonus for great success
377
  if success and total >= difficulty + 3:
378
  stat_increase = random.randint(1, 2)
379
- game_state.stats[stat] = player_stat_value + stat_increase
380
-
 
381
  # Penalty for bad failure
382
  if not success and total <= difficulty - 3:
383
  stat_decrease = random.randint(1, 2)
384
- game_state.stats[stat] = max(1, player_stat_value - stat_decrease) # Stat cannot go below 1
385
-
 
386
  # Record challenge outcome
387
  if success:
388
  game_state.challenges_won += 1
389
-
390
  return success, roll, total
391
 
392
  def simulate_battle(game_state):
393
  """Simulate a battle with a random enemy. Returns (player_won_bool, battle_log_html)"""
394
  battle_log = "<div style='font-size: 0.9em; line-height: 1.4;'>"
395
-
396
  # Select a random enemy type
397
  enemy_types = list(enemies_data.keys())
398
  if not enemy_types:
399
  return True, "<p>No enemies defined for battle!</p>" # Avoid error if no enemies exist
400
  enemy_type = random.choice(enemy_types)
401
  enemy = enemies_data[enemy_type].copy() # Copy data to modify HP
402
-
403
  battle_log += f"<p>A wild <strong>{enemy_type}</strong> appears!</p>"
404
-
405
  # Simple battle simulation
406
  player_hp = game_state.current_hp
407
  enemy_hp = enemy["hp"]
408
-
409
  player_base_attack = game_state.stats.get("strength", 1)
410
  player_base_defense = 1 # Base defense
411
-
412
  player_attack = player_base_attack
413
  player_defense = player_base_defense
414
-
415
  # Add weapon/armor bonuses if the player has relevant items
416
  attack_bonuses = []
417
  defense_bonuses = []
@@ -431,9 +502,9 @@ def simulate_battle(game_state):
431
 
432
  enemy_attack = enemy["attack"]
433
  enemy_defense = enemy["defense"]
434
-
435
  battle_log += f"<p>Player HP: {player_hp}, Enemy HP: {enemy_hp}</p><hr>"
436
-
437
  # Simple turn-based combat
438
  turn = 1
439
  while player_hp > 0 and enemy_hp > 0 and turn < 20: # Add turn limit to prevent infinite loops
@@ -442,31 +513,32 @@ def simulate_battle(game_state):
442
  damage_to_enemy = max(1, player_attack - enemy_defense)
443
  enemy_hp -= damage_to_enemy
444
  battle_log += f"&nbsp;&nbsp;You attack the {enemy_type} for {damage_to_enemy} damage. (Enemy HP: {max(0, enemy_hp)})<br>"
445
-
446
  if enemy_hp <= 0:
447
  battle_log += f"<p><strong>You defeated the {enemy_type}!</strong></p>"
 
448
  break
449
-
450
  # Enemy attacks
451
  damage_to_player = max(1, enemy_attack - player_defense)
452
  player_hp -= damage_to_player
453
  battle_log += f"&nbsp;&nbsp;The {enemy_type} attacks you for {damage_to_player} damage. (Your HP: {max(0, player_hp)})"
454
-
455
  if player_hp <= 0:
456
  battle_log += f"<p><strong>The {enemy_type} defeated you!</strong></p>"
457
  break
458
-
459
  battle_log += "<hr>"
460
  turn += 1
461
 
462
- if turn >= 20:
463
- battle_log += "<p>The battle drags on too long and ends inconclusively.</p>"
464
 
465
  # Update player HP after battle
466
- game_state.current_hp = player_hp
467
-
468
  # Return True if player won (or survived), False if player lost
469
- player_won = player_hp > 0
470
  battle_log += "</div>"
471
  return player_won, battle_log
472
 
@@ -475,44 +547,46 @@ def simulate_battle(game_state):
475
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
476
  gr.Markdown("# Choose Your Own Adventure")
477
  gr.Markdown("### Can you defeat the Evil Power Master?")
478
-
479
  with gr.Row():
480
  with gr.Column(scale=2):
481
  illustration = gr.HTML(label="Scene")
482
  content = gr.HTML(label="Story")
483
- choice = gr.Dropdown(label="What will you do?")
484
-
485
  # Hidden field to store game state as JSON
486
  game_state = gr.Textbox(label="Game State JSON", visible=False) # Changed label, still hidden
487
-
488
  with gr.Column(scale=1):
489
  story_path = gr.Markdown(label="Your Path")
490
  stats = gr.HTML(label="Character Stats")
491
  inventory = gr.HTML(label="Inventory")
492
- # map_btn = gr.Button("View Story Map") # Removed button for simplicity, can add back later
493
 
494
  # Event listener for player choices
495
  choice.change(
496
- update_game,
497
- inputs=[choice, game_state],
498
  outputs=[illustration, content, choice, game_state, stats, inventory, story_path]
499
  )
500
-
501
  # Initialize game on load
502
  demo.load(
503
- initialize_game,
504
- inputs=[],
505
  outputs=[illustration, content, choice, game_state, stats, inventory, story_path]
506
  )
507
 
 
508
  demo.launch()
 
509
  </gradio-file>
510
 
511
  <gradio-file name="game_engine.py">
512
  import json
513
  import random
514
  # Import illustrations from game_data for use in create_svg_illustration
515
- from game_data import illustrations
516
 
517
  class GameState:
518
  """Class to manage game state"""
@@ -533,11 +607,13 @@ class GameState:
533
  self.challenges_won = 0
534
  self.current_path = "main"
535
  self.journey_progress = 0 # Initialize progress
536
-
537
  def to_json(self):
538
  """Convert game state to JSON string"""
539
- return json.dumps(self.__dict__)
540
-
 
 
541
  @classmethod
542
  def from_dict(cls, data):
543
  """Create a GameState object from a dictionary"""
@@ -545,23 +621,28 @@ class GameState:
545
  # Make sure default values are preserved if not in data
546
  for key, value in game_state.__dict__.items():
547
  if key in data:
548
- setattr(game_state, key, data[key])
549
- # Overwrite specific complex types if they exist in data
550
- if 'stats' in data: game_state.stats = data['stats']
551
- if 'inventory' in data: game_state.inventory = data['inventory']
552
- if 'visited_pages' in data: game_state.visited_pages = data['visited_pages']
553
-
 
 
554
  return game_state
555
 
556
  def create_svg_illustration(illustration_key):
557
  """Return SVG illustration based on the illustration key"""
558
  # If illustration is not found, use a default illustration
559
  svg_code = illustrations.get(illustration_key, illustrations.get("default", "<svg viewBox='0 0 100 100'><rect width='100' height='100' fill='grey'/><text x='10' y='50' fill='white'>No Image</text></svg>"))
560
-
561
  # Wrap the SVG in a container div with appropriate styling
 
562
  return f"""
563
- <div style="width: 100%; max-height: 250px; border: 2px solid #999; border-radius: 5px; overflow: hidden; display: flex; justify-content: center; align-items: center; background-color: #eee;">
564
- {svg_code}
 
 
565
  </div>
566
  """
567
  </gradio-file>
@@ -582,7 +663,7 @@ game_data = {
582
  ],
583
  "illustration": "city-gates"
584
  },
585
-
586
  "2": {
587
  "title": "The Weaponsmith",
588
  "content": """<p>The city's renowned weaponsmith, Gorn, welcomes you to his forge. Weapons of all kinds line the walls, from simple daggers to exotic blades that pulse with magic.</p>
@@ -595,7 +676,7 @@ game_data = {
595
  ],
596
  "illustration": "weaponsmith"
597
  },
598
-
599
  "3": {
600
  "title": "The Ancient Temple",
601
  "content": """<p>The Temple of Eternal Light stands at the heart of Silverhold. Inside, the air is thick with incense, and soft chanting echoes through the chambers.</p>
@@ -608,7 +689,7 @@ game_data = {
608
  ],
609
  "illustration": "temple"
610
  },
611
-
612
  "4": {
613
  "title": "The Resistance Leader",
614
  "content": """<p>In a secluded tavern, you meet Lyra, leader of the resistance against the Evil Power Master. Battle-scarred but determined, she unfurls a map across the table.</p>
@@ -621,7 +702,7 @@ game_data = {
621
  ],
622
  "illustration": "resistance-meeting"
623
  },
624
-
625
  "5": {
626
  "title": "The Journey Begins",
627
  "content": """<p>Prepared as best you can be, you leave Silverhold behind. The path to the Evil Power Master's fortress takes you through the Shadowwood Forest, a place once beautiful but now corrupted by dark magic.</p>
@@ -634,7 +715,7 @@ game_data = {
634
  "randomBattle": True,
635
  "illustration": "shadowwood-forest"
636
  },
637
-
638
  "6": {
639
  "title": "Ambush on the Road",
640
  "content": """<p>The main road through Shadowwood is overgrown but still visible. As you make your way forward, the trees suddenly rustle with movement.</p>
@@ -650,7 +731,7 @@ game_data = {
650
  },
651
  "illustration": "road-ambush"
652
  },
653
-
654
  "7": {
655
  "title": "The Mist-Shrouded River",
656
  "content": """<p>The river path is longer but less traveled. Thick mist clings to the water's surface, reducing visibility but potentially hiding you from enemies.</p>
@@ -666,7 +747,7 @@ game_data = {
666
  },
667
  "illustration": "river-spirit"
668
  },
669
-
670
  "8": {
671
  "title": "The Forgotten Ruins",
672
  "content": """<p>The ancient ruins rise from the forest floor like the bones of a forgotten civilization. Moss-covered stones and crumbling arches create a labyrinth of passages and dead ends.</p>
@@ -682,7 +763,7 @@ game_data = {
682
  },
683
  "illustration": "ancient-ruins"
684
  },
685
-
686
  "9": {
687
  "title": "Breaking Through",
688
  "content": """<p>With courage and quick thinking, you manage to fight your way through the ambush. Several scouts fall to your attacks, and the rest scatter into the forest.</p>
@@ -696,7 +777,7 @@ game_data = {
696
  "randomBattle": True,
697
  "illustration": "forest-edge"
698
  },
699
-
700
  "10": {
701
  "title": "Captured!",
702
  "content": """<p>Despite your best efforts, the scouts overwhelm you. Disarmed and bound, you're dragged through the forest to a small outpost.</p>
@@ -710,7 +791,7 @@ game_data = {
710
  "hpLoss": 5,
711
  "illustration": "prisoner-cell"
712
  },
713
-
714
  "11": {
715
  "title": "The Spirit's Blessing",
716
  "content": """<p>"Wisdom flows in you like the river itself," the spirit says, impressed by your answer. The mist swirls around you, and you feel a surge of energy.</p>
@@ -725,7 +806,7 @@ game_data = {
725
  "statIncrease": { "stat": "wisdom", "amount": 2 },
726
  "illustration": "spirit-blessing"
727
  },
728
-
729
  "12": {
730
  "title": "The Spirit's Wrath",
731
  "content": """<p>"Incorrect," the spirit hisses, its melodic voice turning harsh. "The river does not suffer fools!"</p>
@@ -739,7 +820,7 @@ game_data = {
739
  "hpLoss": 8,
740
  "illustration": "river-danger"
741
  },
742
-
743
  "13": {
744
  "title": "Ancient Allies",
745
  "content": """<p>Your wisdom guides you through the ruins' labyrinth. As you navigate the crumbling corridors, the whispers change from threatening to curious.</p>
@@ -750,9 +831,10 @@ game_data = {
750
  { "text": "Ask for knowledge about the fortress", "next": 16 },
751
  { "text": "Request they guide you through secret paths", "next": 17 }
752
  ],
 
753
  "illustration": "ancient-spirits"
754
  },
755
-
756
  "14": {
757
  "title": "Lost in Time",
758
  "content": """<p>The ruins become an impossible maze. Each turn leads to unfamiliar passages, and the whispers grow louder, disorienting you further.</p>
@@ -764,24 +846,25 @@ game_data = {
764
  { "text": "Find a local guide in a nearby village", "next": 17 }
765
  ],
766
  "hpLoss": 10,
 
767
  "illustration": "lost-ruins"
768
  },
769
-
770
  "15": {
771
  "title": "The Looming Fortress",
772
  "content": """<p>The Evil Power Master's fortress dominates the landscape - a massive structure of black stone with towers that seem to pierce the clouds.</p>
773
  <p>Lightning crackles around its highest spires, and dark shapes patrol the battlements. The main gate is heavily guarded, but there must be other ways in.</p>
774
  <p>As you survey the imposing structure, you consider your options for infiltration.</p>""",
775
  "options": [
776
- { "text": "Approach the main gate in disguise", "next": 21 },
777
- { "text": "Scale the outer wall under cover of darkness", "next": 22 }, # Leads to Discovered!
778
- { "text": "Look for the secret tunnel (requires Secret Tunnel Map)", "next": 24, "requireItem": "Secret Tunnel Map" } # Changed from 23 to 24
779
  ],
780
- # Alternative option should only appear if the primary one is blocked
781
- "alternativeOption": { "text": "Search for another entrance (if no map)", "next": 21, "showIf": ["Secret Tunnel Map"] }, # ShowIf logic inverted here, should be 'showIf *not* having map' but easier to just let it appear if map required option fails
782
  "illustration": "evil-fortress"
783
  },
784
-
785
  "16": {
786
  "title": "Strategic Approach",
787
  "content": """<p>You take time to observe the fortress from a distance. Patrols move in predictable patterns, and supply wagons come and go at regular intervals.</p>
@@ -789,14 +872,19 @@ game_data = {
789
  <p>You identify several possible entry points, each with its own risks and advantages.</p>""",
790
  "options": [
791
  { "text": "Infiltrate with an incoming supply wagon", "next": 21 },
792
- { "text": "Use magic to create a distraction (requires any spell)", "next": 22, "requireAnyItem": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing"] }, # Leads to Discovered!
793
- { "text": "Bribe a guard to let you in (requires special item)", "next": 23, "requireAnyItem": ["Ancient Amulet", "Poison Daggers", "Master Key"] } # Leads to Secret Resistance
794
  ],
795
  # If no required items for specific options, provide a fallback
796
- "alternativeOption": { "text": "Attempt to sneak in without a specific plan", "next": 21, "showIf": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing", "Ancient Amulet", "Poison Daggers", "Master Key"] },
 
 
 
 
 
797
  "illustration": "fortress-observation"
798
  },
799
-
800
  "17": {
801
  "title": "The Hidden Way",
802
  "content": """<p>Your search reveals an unexpected approach to the fortress - an ancient maintenance tunnel that runs beneath the barren plains, likely forgotten by the Evil Power Master.</p>
@@ -805,11 +893,11 @@ game_data = {
805
  "options": [
806
  { "text": "Enter the tunnel immediately", "next": 24 },
807
  { "text": "Hide and wait for the patrol to pass", "next": 21 },
808
- { "text": "Set a trap for the patrol", "next": 22 } # Leads to Discovered!
809
  ],
810
  "illustration": "hidden-tunnel"
811
  },
812
-
813
  "18": {
814
  "title": "Night Escape",
815
  "content": """<p>You wait until the dead of night, when most guards are dozing at their posts. Using a loose stone you discovered in your cell, you work on the rusted lock.</p>
@@ -820,12 +908,12 @@ game_data = {
820
  "description": "Slip past the sleeping guards without alerting them.",
821
  "stat": "wisdom",
822
  "difficulty": 6,
823
- "success": 16, # Escaped successfully, continue observing
824
- "failure": 10 # Recaptured! Back to cell
825
  },
826
  "illustration": "night-escape"
827
  },
828
-
829
  "19": {
830
  "title": "Unexpected Ally",
831
  "content": """<p>When the guard passes your cell alone, you whisper, asking why they seemed sympathetic. The guard looks around nervously before responding.</p>
@@ -834,26 +922,25 @@ game_data = {
834
  "options": [
835
  { "text": "Accept the guard's help (gain disguise, proceed)", "next": 16, "addItem": "Guard Disguise"}, # Gain disguise, proceed to strategic approach
836
  { "text": "Decline - you don't want to put them at risk", "next": 18 }, # Try night escape instead
837
- { "text": "Convince them to join your cause", "next": 20 } # Maybe they arrange transport?
838
  ],
839
- # Removed addItem from here, added to option
840
  "illustration": "guard-ally"
841
  },
842
-
843
  "20": {
844
  "title": "Journey to the Fortress",
845
  "content": """<p>At dawn, you're bound and loaded onto a prison wagon with other captured travelers. The journey to the fortress is uncomfortable, but you use the time to observe and plan.</p>
846
  <p>The other prisoners share what they know about the fortress. One mentions rumors of a resistance operating within the Evil Power Master's ranks.</p>
847
  <p>As the wagon approaches the massive gates, you begin to formulate a plan.</p>""",
848
  "options": [
849
- { "text": "Look for an opportunity to escape during transfer", "next": 22 }, # High risk, likely leads to discovered
850
  { "text": "Remain captive to get inside, then escape", "next": 21 }, # Get inside, then try to move
851
- { "text": "Try to contact the internal resistance (requires clue)", "next": 23, "requireAnyItem": ["Guard Disguise"]} # Need something to signal resistance
852
  ],
853
- "alternativeOption": { "text": "Remain captive with no plan", "next": 28, "showIf": ["Guard Disguise"] }, # If no clue for resistance
854
  "illustration": "prison-wagon"
855
  },
856
-
857
  "21": {
858
  "title": "Infiltration",
859
  "content": """<p>Using your skills and preparation, you manage to infiltrate the outer perimeter of the fortress. The black stone walls loom overhead, even more intimidating up close.</p>
@@ -864,10 +951,10 @@ game_data = {
864
  { "text": "Search for the dungeons", "next": 26 },
865
  { "text": "Follow a group of mages", "next": 27 }
866
  ],
867
- "randomBattle": True, # Corrected typo
868
  "illustration": "fortress-interior"
869
  },
870
-
871
  "22": {
872
  "title": "Discovered!",
873
  "content": """<p>As you make your way through the fortress, an alarm suddenly sounds! Your presence has been detected, and you hear the heavy footsteps of guards approaching.</p>
@@ -876,14 +963,14 @@ game_data = {
876
  "challenge": {
877
  "title": "Escape Detection",
878
  "description": "You need to escape from the approaching guards before they trap you.",
879
- "stat": "courage", # Changed to courage for variety
880
  "difficulty": 7,
881
  "success": 25, # Managed to evade, head to tower
882
  "failure": 28 # Captured Again
883
  },
884
  "illustration": "fortress-alarm"
885
  },
886
-
887
  "23": {
888
  "title": "Secret Resistance",
889
  "content": """<p>Through luck or skill (or maybe a bribe or disguise), you make contact with members of the secret resistance operating within the fortress. They're skeptical of you at first, but your actions have convinced them of your intentions.</p>
@@ -896,7 +983,7 @@ game_data = {
896
  ],
897
  "illustration": "secret-meeting"
898
  },
899
-
900
  "24": {
901
  "title": "Underground Passage",
902
  "content": """<p>The ancient tunnel is damp and narrow, forcing you to crouch as you make your way forward. Roots hang from the ceiling, and the air feels thick with age.</p>
@@ -909,21 +996,21 @@ game_data = {
909
  ],
910
  "illustration": "underground-passage"
911
  },
912
-
913
  "25": {
914
  "title": "The Central Tower",
915
  "content": """<p>The central tower rises from the heart of the fortress like a black spear. As you get closer, you can feel powerful magic emanating from its peak.</p>
916
  <p>Guards are more numerous here, and you spot several dark mages performing rituals in alcoves along the walls. Whatever the Evil Power Master is planning, it seems to be approaching its climax.</p>
917
  <p>A massive door blocks the entrance to the tower, engraved with strange symbols.</p>""",
918
  "options": [
919
- { "text": "Try to decipher the symbols", "next": 35 },
920
  { "text": "Look for another entrance", "next": 36 },
921
- { "text": "Use magic to open the door (requires any spell)", "next": 37, "requireAnyItem": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing"] }
922
  ],
923
- "alternativeOption": { "text": "Wait and observe the mages", "next": 36, "showIf": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing"] },
924
  "illustration": "central-tower"
925
  },
926
-
927
  "26": {
928
  "title": "The Dungeons",
929
  "content": """<p>The dungeons are a maze of dark, damp corridors lined with cells. Moans and whispers echo off the stone walls, creating an eerie atmosphere of despair.</p>
@@ -936,7 +1023,7 @@ game_data = {
936
  ],
937
  "illustration": "dungeon-corridors"
938
  },
939
-
940
  "27": {
941
  "title": "The Ritual Chamber",
942
  "content": """<p>Following the group of mages leads you to a large circular chamber. Hidden in the shadows, you observe as they prepare for what appears to be an important ritual.</p>
@@ -949,20 +1036,20 @@ game_data = {
949
  ],
950
  "illustration": "ritual-chamber"
951
  },
952
-
953
  "28": {
954
  "title": "Captured Again",
955
  "content": """<p>The guards overwhelm you, and this time, there's no easy escape. You're brought before a high-ranking officer, who smiles coldly.</p>
956
  <p>"The Master will be most pleased," he says. "He so rarely gets to meet those foolish enough to challenge him directly."</p>
957
  <p>You're escorted under heavy guard toward the central tower, where the Evil Power Master awaits.</p>""",
958
  "options": [
959
- { "text": "Look for another chance to escape", "next": 44 },
960
  { "text": "Prepare yourself mentally to face the Power Master", "next": 45 },
961
  { "text": "Try to gather information from the guards", "next": 46 }
962
  ],
963
  "illustration": "prisoner-escort"
964
  },
965
-
966
  "29": {
967
  "title": "The Uprising",
968
  "content": """<p>The resistance has planned carefully. When the signal is given, chaos erupts throughout the fortress. Guards find themselves fighting servants, and magical wards fail as saboteurs destroy key components.</p>
@@ -975,7 +1062,7 @@ game_data = {
975
  ],
976
  "illustration": "fortress-uprising"
977
  },
978
-
979
  "30": {
980
  "title": "The Final Approach",
981
  "content": """<p>With the resistance's help, you navigate through secret passages unknown to most of the fortress inhabitants. These narrow corridors, built during the fortress's construction, lead directly to the upper levels.</p>
@@ -988,7 +1075,7 @@ game_data = {
988
  ],
989
  "illustration": "secret-passage"
990
  },
991
-
992
  "31": {
993
  "title": "Knowledge Exchange",
994
  "content": """<p>You share everything you've learned on your journey - about the river spirit, the ancient ruins, and any magical artifacts you've collected. In return, the resistance provides crucial information.</p>
@@ -999,57 +1086,45 @@ game_data = {
999
  { "text": "Find out when the alignment will occur", "next": 54 },
1000
  { "text": "Learn if there are any weapons that can harm the crystal", "next": 55 }
1001
  ],
 
1002
  "illustration": "knowledge-sharing"
1003
  },
1004
 
1005
  # --- Placeholder Pages ---
1006
- "32": { "title": "Storeroom Search", "content": "<p>You search the dusty storeroom. Mostly old crates and broken tools, but you find a Healing Potion tucked away!</p>", "options": [{ "text": "Find a way up", "next": 33 }], "addItem": "Healing Potion", "illustration": "underground-passage" },
1007
- "33": { "title": "Ascending", "content": "<p>You find a loose grate leading to the fortress kitchens. Smells better than the tunnel.</p>", "options": [{ "text": "Sneak through the kitchens", "next": 21 }], "illustration": "fortress-interior" },
1008
- "34": { "title": "Listening", "content": "<p>You hear guards marching above, and the clang of metal. Sounds like standard patrols, nothing urgent.</p>", "options": [{ "text": "Explore the storeroom", "next": 32 }, { "text": "Try to find a way up", "next": 33 }], "illustration": "underground-passage" },
1009
- "35": { "title": "Deciphering Symbols", "content": "<p>The symbols pulse with dark energy. Your wisdom tells you they form a powerful ward, requiring a specific magical key or immense force.</p>", "options": [{ "text": "Look for another entrance", "next": 36 }, { "text": "Use magic (if you have it)", "next": 37, "requireAnyItem": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing", "Master Key"] }], "illustration": "central-tower" },
1010
- "36": { "title": "Searching for Entry", "content": "<p>You circle the base of the tower. High up, you spot a less guarded balcony. Scaling the tower might be possible, but dangerous.</p>", "options": [{ "text": "Attempt to climb", "next": 22 }, {"text": "Return to the main door", "next": 25}], "illustration": "central-tower" },
1011
- "37": { "title": "Magical Entry", "content": "<p>You channel your magic (or use the Master Key). The symbols flare, and the massive door grinds open! You step inside the tower.</p>", "options": [{ "text": "Ascend the tower", "next": 48 }], "illustration": "final-confrontation" }, # Straight to the top
1012
- "38": { "title": "Causing Distraction", "content": "<p>You manage to unlock a few cells. The freed prisoners, though weak, cause a commotion, drawing guards away.</p>", "options": [{ "text": "Investigate the guarded cell", "next": 39 }, {"text": "Look for an exit upwards", "next": 40}], "illustration": "dungeon-corridors" },
1013
- "39": { "title": "Guarded Cell", "content": "<p>Inside the cell is a captured resistance mage! They recognize your intent and offer to help you reach the Master if freed.</p>", "options": [{ "text": "Free the mage (requires Master Key or skill)", "next": 30, "requireAnyItem": ["Master Key", "Ancient Amulet"] }, {"text": "Leave them for now", "next": 40}], "illustration": "dungeon-corridors" },
1014
- "40": { "title": "Finding Passage", "content": "<p>You find a hidden staircase leading up, likely towards the main fortress levels.</p>", "options": [{ "text": "Take the stairs", "next": 21 }], "illustration": "fortress-interior" },
1015
- "41": { "title": "Disrupting the Ritual", "content": "<p>You burst in! The mages are startled. You attack, disrupting their concentration. The crystal flares erratically.</p>", "options": [{ "text": "Fight the mages", "next": 22 }], "illustration": "ritual-chamber" }, # Likely leads to being discovered
1016
- "42": { "title": "Observing the Ritual", "content": "<p>You watch as the mages chant. The crystal seems to be drawing power from somewhere deep within the fortress, focusing it for the Master.</p>", "options": [{ "text": "Try to disrupt it now", "next": 41 }, {"text": "Try to steal the crystal", "next": 43}, {"text": "Leave and head to the tower", "next": 25}], "illustration": "ritual-chamber" },
1017
- "43": { "title": "Stealing the Crystal", "content": "<p>You attempt to snatch the crystal. It burns with cold energy! The mages react instantly, attacking you.</p>", "options": [{ "text": "Fight back!", "next": 22 }], "illustration": "ritual-chamber" }, # Leads to being discovered
1018
- "44": { "title": "Escape Attempt", "content": "<p>You look for a chance, but the guards are too vigilant. No escape seems possible right now.</p>", "options": [{ "text": "Prepare mentally", "next": 45 }, {"text": "Gather info", "next": 46}], "illustration": "prisoner-escort" },
1019
- "45": { "title": "Mental Preparation", "content": "<p>You focus your mind, recalling your training and purpose. You are ready to face the Evil Power Master.</p>", "options": [{ "text": "Continue to the Master's chamber", "next": 48 }], "illustration": "final-confrontation" }, # Directly to final battle
1020
- "46": { "title": "Gathering Information", "content": "<p>You try to subtly listen to the guards. They mention the Master is 'nearly ready' and the 'crystal is at peak power'. Time is short.</p>", "options": [{ "text": "Prepare mentally", "next": 45 }, {"text": "Look for escape again", "next": 44}], "illustration": "prisoner-escort" },
1021
- "47": { "title": "Leading the Charge", "content": "<p>You lead the resistance fighters bravely, cutting through the Master's guards. You push towards the tower entrance.</p>", "options": [{ "text": "Breach the tower door", "next": 25 }], "illustration": "fortress-uprising" },
1022
  # Page 48 is the final confrontation
1023
- "49": { "title": "Sneaking Ahead", "content": "<p>While the uprising rages, you slip past the guards and find a less-guarded entrance to the tower.</p>", "options": [{ "text": "Enter the tower", "next": 50 }], "illustration": "secret-passage" },
1024
- "50": { "title": "Cautious Entry", "content": "<p>You enter the tower base. Stairs spiral upwards into darkness. The air hums with power.</p>", "options": [{ "text": "Ascend carefully", "next": 48 }], "illustration": "final-confrontation" }, # Head to final battle
1025
- "51": { "title": "Ritual Information", "content": "<p>Your guide explains the Master is performing a ritual tied to a cosmic alignment to achieve near-godhood. The crystal is key.</p>", "options": [{ "text": "Enter the tower", "next": 50 }, {"text": "Ask for more help", "next": 52}], "illustration": "secret-passage" },
1026
- "52": { "title": "Requesting Help", "content": "<p>The resistance offers a brave warrior to accompany you, providing backup in the final fight.</p>", "options": [{ "text": "Accept the help and enter", "next": 48 }], "addItem": "Resistance Ally", "illustration": "secret-passage" }, # Add ally and go to fight
1027
- "53": { "title": "Crystal Location", "content": "<p>The crystal is kept in the Master's main chamber, at the very top of the central tower. It floats above his throne.</p>", "options": [{ "text": "Head to the tower", "next": 48 }], "illustration": "knowledge-sharing" }, # Go to final battle
1028
- "54": { "title": "Alignment Timing", "content": "<p>The alignment is imminent! Perhaps only minutes away. You must hurry!</p>", "options": [{ "text": "Head to the tower immediately", "next": 48 }], "illustration": "knowledge-sharing" }, # Go to final battle
1029
- "55": { "title": "Crystal Weakness", "content": "<p>They mention rumors that pure light or holy energy can disrupt the crystal. Perhaps your Healing Light or Shield of Faith spells?</p>", "options": [{ "text": "Head to the tower", "next": 48 }], "illustration": "knowledge-sharing" }, # Go to final battle
1030
-
1031
- "58": {
1032
- "title": "Attempt to Reason",
1033
- "content": """<p>You try to speak to the Evil Power Master, appealing to any shred of humanity left. He laughs mockingly.</p>
1034
- <p>"Humanity? Reason? Such petty concerns! Power is the only truth!" he snarls, launching a powerful attack.</p>
1035
- <p>It seems negotiation is impossible.</p>""",
1036
- "options": [
1037
- { "text": "Attack the Evil Power Master directly", "next": 56 },
1038
- { "text": "Try to destroy the crystal", "next": 57 }
1039
- ],
1040
- "illustration": "final-confrontation"
1041
- },
1042
 
1043
- # --- Endings (already defined) ---
1044
  "56": { # Direct Confrontation Battle
1045
  "title": "Direct Confrontation",
1046
- "content": """<p>You charge at the Evil Power Master, drawing on all your courage and strength. He meets your attack with dark magic, the air between you distorting with energy.</p>
1047
- <p>The battle is intense, pushing you to your limits. Each blow you land seems to be absorbed by his power, while his attacks grow increasingly dangerous.</p>
1048
- <p>As the fight continues, you realize that as long as the crystal empowers him, this might be an unwinnable battle.</p>""",
1049
  "challenge": {
1050
  "title": "Battle with the Evil Power Master",
1051
- "description": "You must overcome his dark magic through sheer determination and skill.",
1052
- "stat": "strength", # Strength check
1053
  "difficulty": 9, # High difficulty
1054
  "success": 59, # Hero's Victory
1055
  "failure": 60 # Darkness Prevails
@@ -1058,77 +1133,89 @@ game_data = {
1058
  },
1059
  "57": { # Crystal Destruction Attempt
1060
  "title": "Crystal Destruction",
1061
- "content": """<p>Recognizing the true source of his power, you ignore the Evil Power Master and lunge toward the floating crystal. He shouts in alarm, desperately trying to stop you.</p>
1062
- <p>"No! Stay away from that, you fool! You have no idea what forces you're tampering with!"</p>
1063
- <p>As you get closer to the crystal, its energy intensifies, creating a barrier of dark magic that you must break through.</p>""",
1064
  "challenge": {
1065
  "title": "Breaking the Crystal Barrier",
1066
- "description": "You must overcome the crystal's protective magic to reach and destroy it.",
1067
- "stat": "wisdom", # Wisdom check
1068
  "difficulty": 8, # Slightly lower difficulty than direct fight
1069
  "success": 61, # The Crystal Shatters
1070
  "failure": 62 # A Desperate Gambit (leads to sacrifice ending)
1071
  },
1072
  "illustration": "crystal-barrier"
1073
  },
 
 
 
 
 
 
 
 
 
 
 
1074
  "59": {
1075
  "title": "A Hero's Victory",
1076
- "content": """<p>Through sheer determination and skill, you manage to overcome the Evil Power Master's defenses. As he staggers backwards from your decisive blow, his connection to the crystal weakens momentarily.</p>
1077
- <p>Seizing the opportunity, you strike the crystal with all your remaining strength. It cracks, then shatters in a blinding flash of light and energy.</p>
1078
- <p>The Evil Power Master screams as his power dissipates. "Impossible! I was so close to ultimate power!"</p>
1079
- <p>As the light fades, you stand victorious. The land will heal from his corruption, and the people are free once more.</p>""",
1080
  "gameOver": True,
1081
- "ending": "You've defeated the Evil Power Master and destroyed the source of his power. Songs will be sung of your bravery for generations to come. The land begins to heal, and you are hailed as a hero throughout the realm.",
1082
  "illustration": "hero-victory"
1083
  },
1084
  "60": {
1085
  "title": "Darkness Prevails",
1086
- "content": """<p>Despite your best efforts, the Evil Power Master's power is too great. His dark magic overwhelms your defenses, bringing you to your knees.</p>
1087
- <p>"Valiant, but futile," he says, looking down at you. "You could have joined me, you know. Now you'll witness my ascension to godhood before your end."</p>
1088
- <p>As your vision fades, you see the crystal's glow intensifying. You've failed, and darkness will soon cover the land.</p>""",
1089
  "gameOver": True,
1090
- "ending": "The Evil Power Master was too powerful, and your quest ends in defeat. Darkness spreads across the land as he completes his ritual and ascends to even greater power. Perhaps another hero will rise in the future to challenge his reign.",
1091
  "illustration": "dark-victory"
1092
  },
1093
  "61": {
1094
  "title": "The Crystal Shatters",
1095
- "content": """<p>Drawing on all your wisdom and willpower, you break through the crystal's barrier. Reaching out, you touch its surface, which burns cold against your skin.</p>
1096
- <p>The Evil Power Master shrieks in desperation, "Stop! You'll doom us all!"</p>
1097
- <p>With a final effort, you channel your energy into the crystal. Cracks appear across its surface, spreading rapidly until it shatters with a deafening explosion of light and sound.</p>
1098
- <p>When your vision clears, the Evil Power Master lies defeated, his power broken along with the crystal. Peace can now return to the land.</p>""",
1099
  "gameOver": True,
1100
- "ending": "By destroying the source of the Evil Power Master's strength, you've saved the realm from his tyranny. The fortress begins to crumble as its dark magic fades, but you escape to tell the tale. Your name becomes legend throughout the land.",
1101
  "illustration": "crystal-destroyed"
1102
  },
1103
  "62": {
1104
  "title": "A Desperate Gambit",
1105
- "content": """<p>The crystal's barrier proves too strong, repelling your attempts to break through. As you struggle against it, the Evil Power Master regains his composure and approaches.</p>
1106
- <p>"Did you really think it would be that easy?" he mocks. "The crystal has existed for millennia. It cannot be destroyed by someone like you."</p>
1107
- <p>In a final, desperate move, you use all your remaining strength to hurl your most powerful weapon or spell at the crystal.</p>
1108
- <p>To your surprise and the Evil Power Master's horror, this creates a resonance effect. The crystal begins to vibrate violently, its energy becoming unstable.</p>
1109
- <p>"What have you done?" he cries as the crystal's energy spirals out of control, engulfing both of you in blinding light.</p>""",
1110
  "gameOver": True,
1111
- "ending": "Your desperate attack destabilized the crystal, causing a catastrophic release of energy that destroyed the Evil Power Master and his fortress. Though you didn't survive, your sacrifice saved the realm from darkness. Bards sing of your heroism for generations to come.",
1112
  "illustration": "heroic-sacrifice"
1113
  }
1114
  }
1115
 
 
1116
  # --- Item Data ---
1117
  items_data = {
1118
- "Flaming Sword": {"type": "weapon", "description": "A sword wreathed in magical fire. +3 Attack.", "attackBonus": 3},
1119
- "Whispering Bow": {"type": "weapon", "description": "A bow that fires silent arrows. +2 Attack.", "attackBonus": 2},
1120
- "Guardian Shield": {"type": "armor", "description": "A sturdy shield blessed with protective magic. +2 Defense.", "defenseBonus": 2},
1121
- "Healing Light Spell": {"type": "spell", "description": "A spell that can mend minor wounds."},
1122
- "Shield of Faith Spell": {"type": "spell", "description": "A spell that creates a temporary magical shield."},
1123
- "Binding Runes Scroll": {"type": "spell", "description": "A scroll containing runes to temporarily bind an enemy."},
1124
- "Secret Tunnel Map": {"type": "quest", "description": "A map marking a hidden entrance to the fortress."},
1125
- "Poison Daggers": {"type": "weapon", "description": "Daggers coated with a fast-acting poison. +1 Attack.", "attackBonus": 1},
1126
- "Master Key": {"type": "quest", "description": "A key said to unlock any non-magical lock."},
1127
- "Water Spirit's Blessing": {"type": "spell", "description": "A blessing from the river spirit, enhancing wisdom."},
1128
- "Ancient Amulet": {"type": "quest", "description": "An amulet radiating ancient protective magic. +1 Defense.", "defenseBonus": 1},
1129
- "Guard Disguise": {"type": "quest", "description": "A uniform worn by the fortress guards."},
1130
- "Healing Potion": {"type": "consumable", "description": "A potion that restores some health."}, # Placeholder
1131
- "Resistance Ally": {"type": "quest", "description": "A brave resistance fighter accompanying you."}, # Placeholder
1132
  }
1133
 
1134
  # --- Enemy Data ---
@@ -1136,69 +1223,72 @@ enemies_data = {
1136
  "Scout": {"hp": 15, "attack": 4, "defense": 1},
1137
  "Dark Mage": {"hp": 20, "attack": 6, "defense": 2},
1138
  "Fortress Guard": {"hp": 25, "attack": 5, "defense": 3},
1139
- # Add more enemies as needed
 
1140
  }
1141
 
1142
 
1143
  # --- SVG templates for illustrations ---
1144
  # Helper function for placeholder SVGs
1145
  def create_placeholder_svg(text="Placeholder", color="#cccccc"):
 
1146
  return f"""<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
1147
- <rect width="200" height="100" fill="{color}" />
1148
  <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="monospace" font-size="14px" fill="#000">{text}</text>
 
1149
  </svg>"""
1150
 
1151
  illustrations = {
1152
  "default": create_placeholder_svg("Default Scene", "#aaaaaa"),
1153
  "game-over": create_placeholder_svg("Game Over", "#ffaaaa"),
1154
  "city-gates": """
1155
- <svg viewBox="0 0 400 250" xmlns="http://www.w3.org/2000/svg">
1156
- <rect x="0" y="0" width="400" height="150" fill="#6b88a2" />
1157
- <rect x="0" y="150" width="400" height="100" fill="#7d6c54" />
1158
- <rect x="50" y="50" width="300" height="100" fill="#c9c0a8" />
1159
- <rect x="150" y="80" width="100" height="120" fill="#5a3c28" />
1160
- <rect x="195" y="80" width="10" height="120" fill="#3a281e" />
1161
- <rect x="50" y="30" width="40" height="120" fill="#d6cdb7" />
1162
- <rect x="310" y="30" width="40" height="120" fill="#d6cdb7" />
1163
- <polygon points="50,30 70,10 90,30" fill="#bf9969" />
1164
- <polygon points="310,30 330,10 350,30" fill="#bf9969" />
1165
- <path d="M150,250 C 170 220, 230 220, 250 250 Z" fill="#a89783" />
1166
- <line x1="200" y1="200" x2="200" y2="250" stroke="#a89783" stroke-width="100" />
1167
- <rect x="70" y="10" width="2" height="20" fill="#222" />
1168
- <rect x="72" y="10" width="10" height="5" fill="#bf2121" />
1169
- <rect x="330" y="10" width="2" height="20" fill="#222" />
1170
- <rect x="332" y="10" width="10" height="5" fill="#bf2121" />
1171
- </svg>
 
 
 
 
1172
  """,
1173
  "weaponsmith": """
1174
- <svg viewBox="0 0 400 250" xmlns="http://www.w3.org/2000/svg">
1175
- <rect x="0" y="0" width="400" height="250" fill="#3d3123" />
1176
- <rect x="250" y="100" width="100" height="70" fill="#8c5b3f" />
1177
- <rect x="270" y="120" width="60" height="30" fill="#e63d16" />
1178
- <ellipse cx="300" cy="135" rx="25" ry="10" fill="#f7d046" />
1179
- <rect x="180" y="160" width="40" height="20" fill="#888888" />
1180
- <rect x="190" y="150" width="20" height="10" fill="#999999" />
1181
- <rect x="195" y="140" width="10" height="10" fill="#aaaaaa" />
1182
- <line x1="50" y1="60" x2="90" y2="70" stroke="#c0c0c0" stroke-width="4" />
1183
- <line x1="120" y1="50" x2="150" y2="70" stroke="#c0c0c0" stroke-width="3" />
1184
- <rect x="60" y="90" width="30" height="80" fill="#814d2b" />
1185
- <rect x="65" y="90" width="20" height="5" fill="#c0c0c0" />
1186
- <rect x="140" y="90" width="5" height="100" fill="#814d2b" />
1187
- <ellipse cx="142.5" cy="90" rx="15" ry="3" fill="#c0c0c0" />
1188
- <rect x="30" y="180" width="120" height="10" fill="#8c5b3f" />
1189
- <circle cx="220" cy="120" r="25" fill="#d8a77a" />
1190
- <rect x="210" y="145" width="20" height="35" fill="#603813" />
1191
- <rect x="205" y="110" width="30" height="20" fill="#d8a77a" />
1192
- <circle cx="213" cy="115" r="3" fill="#000" />
1193
- <circle cx="227" cy="115" r="3" fill="#000" />
1194
- <path d="M215,125 Q220,130 225,125" fill="none" stroke="#000" stroke-width="1" />
1195
- <rect x="60" y="60" width="10" height="30" fill="#603813" />
1196
- <rect x="62" y="30" width="6" height="30" fill="#ff5722" opacity="0.7">
1197
- <animate attributeName="opacity" values="0.7;1;0.7" dur="2s" repeatCount="indefinite" />
1198
- </rect>
1199
- <rect x="130" y="50" width="6" height="40" fill="#603813" />
1200
- <ellipse cx="133" cy="50" rx="15" ry="3" fill="#c0c0c0" />
1201
- </svg>
1202
  """,
1203
  # --- Add other placeholders ---
1204
  "temple": create_placeholder_svg("Ancient Temple", "#fffde7"),
@@ -1239,7 +1329,6 @@ illustrations = {
1239
  "heroic-sacrifice": create_placeholder_svg("Heroic Sacrifice", "#ffcc80"),
1240
 
1241
  }
1242
-
1243
  </gradio-file>
1244
 
1245
  </gradio-lite>
 
13
  margin: 0;
14
  padding: 20px;
15
  }
16
+
17
  h1 {
18
  color: #c00;
19
  text-align: center;
20
  margin-bottom: 20px;
21
  }
22
+
23
  .container {
24
  max-width: 900px;
25
  margin: 0 auto;
 
29
  padding: 20px;
30
  box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
31
  }
32
+
33
  .game-description {
34
  text-align: center;
35
  margin-bottom: 20px;
 
45
  <p>An interactive text adventure game powered by Gradio-lite</p>
46
  <p><em>Can you defeat the Evil Power Master and save the land?</em></p>
47
  </div>
48
+
49
  <gradio-lite theme="light">
50
  <gradio-file name="app.py" entrypoint>
51
  import gradio as gr
 
60
  game_state = GameState()
61
  current_page = 1
62
  page_data = game_data[str(current_page)]
63
+
64
  # Create the SVG illustration
65
+ svg_content = create_svg_illustration(page_data.get("illustration", "default"))
66
+
67
  # Build page content
68
+ content = f"<h2>{page_data.get('title', 'Untitled Page')}</h2>"
69
+ content += page_data.get("content", "<p>No content for this page.</p>")
70
+
71
  # Build options
72
  options = []
73
+ if "options" in page_data:
74
+ for opt in page_data["options"]:
75
+ options.append(opt["text"])
76
+ if not options: # Fallback if no options defined
77
+ options = ["Restart"]
78
+ content += "<p><i>There are no actions defined here.</i></p>"
79
+
80
+
81
  # Update game statistics display
82
  stats = generate_stats_display(game_state) # Use the function here
83
+
84
+ # Initialize inventory display
85
  inventory = generate_inventory_display(game_state) # Use the function here
86
+
87
  # Set story path
88
  story_path = "You are at the beginning of your adventure."
89
+
90
+ return svg_content, content, gr.Dropdown(choices=options, label="What will you do?", value=None), game_state.to_json(), stats, inventory, story_path
91
 
92
  def update_game(choice, game_state_json):
93
  """Update game based on player choice"""
94
+ if not choice: # Handle empty choice if dropdown value is cleared
95
+ return gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
96
+
97
  # Parse game state from JSON
98
  game_state_dict = json.loads(game_state_json)
99
  game_state = GameState.from_dict(game_state_dict)
100
+
101
  # Get current page data
102
  current_page = game_state.current_page
103
  # Ensure current_page is always a string for dictionary keys
104
+ current_page_str = str(current_page)
105
+ if current_page_str not in game_data:
106
+ # Handle invalid page number - maybe reset?
107
+ error_content = f"<p>Error: Reached invalid page number {current_page}. Resetting game.</p>"
108
+ svg_error = create_svg_illustration("default")
109
+ return svg_error, error_content, gr.Dropdown(choices=["Restart"], label="Error"), GameState().to_json(), generate_stats_display(GameState()), generate_inventory_display(GameState()), "Error - Resetting"
110
+
111
+ page_data = game_data[current_page_str]
112
+
113
  # Find the selected option
114
  selected_option = None
115
+ if "options" in page_data:
116
+ for i, opt in enumerate(page_data["options"]):
117
+ if opt["text"] == choice:
118
+ selected_option = opt
119
+ break
120
+
121
+ # Also check alternative option if it was displayed and chosen
122
+ alt_opt_data = page_data.get("alternativeOption")
123
+ if not selected_option and alt_opt_data and alt_opt_data["text"] == choice:
124
+ selected_option = alt_opt_data
125
+
126
+
127
  if selected_option is None:
128
  # Handle case where choice might be "Restart" or invalid
129
  if choice == "Restart":
130
  return initialize_game()
131
  else:
132
+ # Stay on the same page if the choice wasn't found
133
+ options = [opt["text"] for opt in page_data.get("options", [])]
134
+ if not options: options = ["Restart"]
135
+ svg_content = create_svg_illustration(page_data.get("illustration", "default"))
136
+ content = f"<h2>{page_data.get('title', 'Error')}</h2>" + page_data.get("content", "") + "<p><i>Invalid choice selected. Please try again.</i></p>"
137
  stats_display = generate_stats_display(game_state)
138
  inventory_display = generate_inventory_display(game_state)
139
+ story_path_display = f"You remain on page {current_page}: {page_data.get('title', 'Error')}"
140
+ return svg_content, content, gr.Dropdown(choices=options, value=None), game_state.to_json(), stats_display, inventory_display, story_path_display
141
 
142
  # Check if this option requires an item
143
  item_required = selected_option.get("requireItem")
 
149
  stats_display = generate_stats_display(game_state)
150
  inventory_display = generate_inventory_display(game_state)
151
  story_path_display = f"You remain on page {current_page}: {page_data['title']}"
152
+ return svg_content, content, gr.Dropdown(choices=options, value=None), game_state.to_json(), stats_display, inventory_display, story_path_display
153
 
154
  # Check if option requires any item from a list
155
  any_item_required = selected_option.get("requireAnyItem")
 
164
  stats_display = generate_stats_display(game_state)
165
  inventory_display = generate_inventory_display(game_state)
166
  story_path_display = f"You remain on page {current_page}: {page_data['title']}"
167
+ return svg_content, content, gr.Dropdown(choices=options, value=None), game_state.to_json(), stats_display, inventory_display, story_path_display
168
+
169
  # Process special items to collect
170
  item_to_add = selected_option.get("addItem")
171
  if item_to_add and item_to_add not in game_state.inventory:
172
  game_state.inventory.append(item_to_add)
173
+ # Optionally remove item if it's consumable (needs item_data lookup)
174
+ item_data = items_data.get(item_to_add, {})
175
+ if item_data.get("type") == "consumable" and item_data.get("useOnAdd"): # e.g. a potion
176
+ if "hpRestore" in item_data:
177
+ game_state.current_hp = min(game_state.max_hp, game_state.current_hp + item_data["hpRestore"])
178
+ # Don't add consumable to inventory if used immediately? Or remove after use? Let's keep it simple and add it.
179
+
180
  # Move to the next page
181
  next_page = selected_option["next"]
182
  game_state.current_page = next_page
183
+ next_page_str = str(next_page) # Use string for keys
184
+
185
+ if next_page_str not in game_data:
186
+ # Handle invalid destination page
187
+ error_content = f"<p>Error: Option leads to invalid page number {next_page}. Resetting game.</p>"
188
+ svg_error = create_svg_illustration("default")
189
+ return svg_error, error_content, gr.Dropdown(choices=["Restart"], label="Error"), GameState().to_json(), generate_stats_display(GameState()), generate_inventory_display(GameState()), "Error - Resetting"
190
+
191
  if next_page not in game_state.visited_pages: # Avoid duplicates if revisiting
192
  game_state.visited_pages.append(next_page)
193
+
194
  # Update journey progress based on page transitions
195
  game_state.journey_progress += 5 # Increment progress with each decision
196
  if game_state.journey_progress > 100:
197
  game_state.journey_progress = 100
198
+
199
  battle_occurred = False
200
+ battle_message = "" # Initialize battle message
201
+ battle_result = True # Assume survival unless battle happens and player loses
202
+
203
  # Check for random battle (20% chance if page has randomBattle flag)
204
+ # Check the *current* page data before moving
205
+ if page_data.get("randomBattle", False) and random.random() < 0.2:
206
  battle_result, battle_log = simulate_battle(game_state)
207
  battle_occurred = True
208
  if not battle_result:
209
  # Player died in battle
210
  content = "<h2>Game Over</h2><p>You have been defeated in battle!</p>" + battle_log
211
+ stats_display = generate_stats_display(game_state) # Show final stats
212
+ inventory_display = generate_inventory_display(game_state) # Show final inventory
213
+ return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"], label="Game Over", value=None), game_state.to_json(), stats_display, inventory_display, "Game Over - You were defeated in battle."
214
  else:
215
  # Player survived battle, add battle log to the next page content
216
  battle_message = f"<div style='border: 1px solid green; padding: 10px; margin-top: 10px; background-color: #e8f5e9;'><strong>Battle Won!</strong>{battle_log}</div>"
217
 
218
  # Get new page data (for the destination page 'next_page')
219
+ page_data = game_data[next_page_str]
220
+
221
  # Process page stat effects
222
  stat_increase = page_data.get("statIncrease")
223
  if stat_increase:
224
  stat = stat_increase["stat"]
225
  amount = stat_increase["amount"]
226
+ if stat in game_state.stats:
227
+ game_state.stats[stat] += amount
228
+
229
  # Process HP loss
230
  hp_loss = page_data.get("hpLoss")
231
  if hp_loss:
 
233
  if game_state.current_hp <= 0:
234
  game_state.current_hp = 0
235
  content = "<h2>Game Over</h2><p>You have died from your wounds!</p>"
236
+ stats_display = generate_stats_display(game_state) # Show final stats
237
+ inventory_display = generate_inventory_display(game_state) # Show final inventory
238
+ return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"], label="Game Over", value=None), game_state.to_json(), stats_display, inventory_display, "Game Over - You died from your wounds."
239
+
240
  challenge_log = ""
241
+ challenge_occurred = False
242
  # Check if this is a challenge page
243
  challenge = page_data.get("challenge")
244
  if challenge:
245
+ challenge_occurred = True
246
  success, roll, total = perform_challenge(game_state, challenge)
247
  challenge_log = f"<div style='border: 1px solid #ccc; padding: 10px; margin-top: 10px; background-color: #eee;'>"
248
  challenge_log += f"<strong>Challenge: {challenge.get('title', 'Skill Check')}</strong><br>"
249
  challenge_log += f"Target Stat: {challenge['stat']}, Difficulty: {challenge['difficulty']}<br>"
250
+ challenge_log += f"You rolled a {roll} + ({game_state.stats.get(challenge['stat'], 0)} {challenge['stat']}) = {total}<br>" # Corrected stat display
251
+
252
  # Update story based on challenge result
253
  if success:
254
  challenge_log += "<strong style='color: green;'>Success!</strong></div>"
255
  next_page = challenge["success"]
256
  else:
257
+ # *** THIS IS THE CORRECTED LINE ***
258
+ challenge_log += "<strong style='color: red;'>Failure!</strong></div>"
259
  next_page = challenge["failure"]
260
+
261
+ # Update game state to reflect the page change from the challenge outcome
262
  game_state.current_page = next_page
263
+ next_page_str = str(next_page) # Use string for keys
264
+
265
+ if next_page_str not in game_data:
266
+ # Handle invalid page number from challenge outcome
267
+ error_content = f"<p>Error: Challenge outcome leads to invalid page number {next_page}. Resetting game.</p>"
268
+ svg_error = create_svg_illustration("default")
269
+ return svg_error, error_content, gr.Dropdown(choices=["Restart"], label="Error"), GameState().to_json(), generate_stats_display(GameState()), generate_inventory_display(GameState()), "Error - Resetting"
270
+
271
  if next_page not in game_state.visited_pages:
272
  game_state.visited_pages.append(next_page)
273
+ page_data = game_data[next_page_str] # Load data for the page determined by the challenge outcome
274
+
275
  # Create the SVG illustration for the final destination page
276
  svg_content = create_svg_illustration(page_data.get("illustration", "default"))
277
+
278
  # Handle game over on the destination page
279
  if page_data.get("gameOver", False):
280
  content = f"<h2>{page_data['title']}</h2>"
281
  content += page_data["content"]
282
  if battle_occurred and battle_result: content += battle_message # Add battle log if survived
283
+ if challenge_occurred: content += challenge_log # Add challenge log
284
  if "ending" in page_data:
285
  content += f"<div style='text-align: center; margin-top: 20px; font-weight: bold; color: #c00;'>THE END</div>"
286
  content += f"<p style='font-style: italic;'>{page_data['ending']}</p>"
287
+
288
+ stats_display = generate_stats_display(game_state) # Show final stats
289
+ inventory_display = generate_inventory_display(game_state) # Show final inventory
290
+ return svg_content, content, gr.Dropdown(choices=["Restart"], label="Game Over", value=None), game_state.to_json(), stats_display, inventory_display, "Game Over - " + page_data.get('title', 'Untitled')
291
+
292
  # Build page content for the final destination page
293
+ content = f"<h2>{page_data.get('title', 'Untitled Page')}</h2>"
294
+ content += page_data.get("content", "<p>No content.</p>")
295
  if battle_occurred and battle_result: content += battle_message # Add battle log if survived
296
+ if challenge_occurred: content += challenge_log # Add challenge log
297
+
298
  # Build options for the final destination page
299
  options = []
300
+ current_inventory = game_state.inventory # Cache inventory for checks
301
+ if "options" in page_data: # Check if options exist
302
  for opt in page_data["options"]:
303
  option_available = True
304
  # Check if option requires an item
305
+ req_item = opt.get("requireItem")
306
+ if req_item and req_item not in current_inventory:
307
  option_available = False
308
  # Check if option requires any item from a list
309
+ req_any_item = opt.get("requireAnyItem")
310
+ if option_available and req_any_item:
311
+ if not any(item in current_inventory for item in req_any_item):
312
  option_available = False
313
+
314
  if option_available:
315
  options.append(opt["text"])
316
+
317
+ # Handle alternative option if necessary (only if no primary options are available/visible)
318
  alt_opt_data = page_data.get("alternativeOption")
319
  if alt_opt_data and not options:
320
  alt_show_if = alt_opt_data.get("showIf")
321
  # Show alternative if no condition or condition met
322
+ if not alt_show_if or any(item in current_inventory for item in alt_show_if):
323
  options.append(alt_opt_data["text"])
324
+ # Temporarily add the alternative option details to the current page's options
325
+ # This ensures it can be selected on the next turn if it was the only one shown
326
  if "options" not in page_data: page_data["options"] = []
327
+ # Avoid adding duplicate if somehow already there
328
+ if alt_opt_data not in page_data["options"]:
329
+ page_data["options"].append(alt_opt_data)
330
 
331
 
332
  if not options: # If still no options (dead end maybe?)
333
+ options = ["Restart"]
334
  content += "<p><i>There are no further actions you can take from here.</i></p>"
335
 
336
  # Update game progress display text
337
+ story_path = f"You are on page {next_page}: {page_data.get('title', 'Untitled')}"
338
  if game_state.journey_progress >= 80:
339
  story_path += " (Nearing the conclusion)"
340
  elif game_state.journey_progress >= 50:
341
  story_path += " (Middle of your journey)"
342
  elif game_state.journey_progress >= 25:
343
  story_path += " (Adventure beginning)"
344
+
345
  # Generate final displays
346
  stats_display = generate_stats_display(game_state)
347
  inventory_display = generate_inventory_display(game_state)
348
+
349
+ # Ensure dropdown value is reset so change event fires even if same choice is valid again
350
+ return svg_content, content, gr.Dropdown(choices=options, label="What will you do?", value=None), game_state.to_json(), stats_display, inventory_display, story_path
351
+
352
 
353
  def generate_stats_display(game_state):
354
  """Generate HTML for displaying player stats"""
 
359
  hp_color = "#F44336" # Red
360
  elif hp_percent < 70:
361
  hp_color = "#FFC107" # Yellow
362
+
363
  stats_html = f"""
364
  <div style='display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;'>
365
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
 
379
  </div>
380
  </div>
381
  """
382
+
383
  # Add journey progress
384
  stats_html += f"""
385
  <div style='margin-top: 10px;'>
 
392
  </div>
393
  </div>
394
  """
395
+
396
  return stats_html
397
 
398
  def generate_inventory_display(game_state):
399
  """Generate HTML for displaying player inventory"""
400
  if not game_state.inventory:
401
  return "<em>Your inventory is empty.</em>"
402
+
403
+ inventory_html = "<div style='display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px;'>" # Added margin-top
404
+
405
  for item in game_state.inventory:
406
  item_data = items_data.get(item, {"type": "unknown", "description": "A mysterious item."})
407
  bg_color = "#e0e0e0" # Default grey
408
  item_type = item_data.get("type", "unknown")
409
+ description = item_data.get('description', 'No description available.')
410
 
411
  if item_type == "weapon":
412
  bg_color = "#ffcdd2" # Light red
 
416
  bg_color = "#bbdefb" # Light blue
417
  elif item_type == "quest":
418
  bg_color = "#fff9c4" # Light yellow
419
+ elif item_type == "consumable":
420
+ bg_color = "#d1c4e9" # Light purple
421
+
422
  inventory_html += f"""
423
+ <div style='background: {bg_color}; padding: 5px 10px; border-radius: 5px; position: relative; cursor: help;'
424
+ title="{description}">
425
  {item}
426
  </div>
427
  """
428
+
429
  inventory_html += "</div>"
430
  return inventory_html
431
 
 
433
  """Perform a skill challenge and determine success. Returns (success_bool, roll, total)"""
434
  stat = challenge["stat"]
435
  difficulty = challenge["difficulty"]
436
+
437
  # Roll dice (1-6) and add stat
438
  roll = random.randint(1, 6)
439
  player_stat_value = game_state.stats.get(stat, 0) # Default to 0 if stat doesn't exist
440
  total = roll + player_stat_value
441
+
442
  # Determine if successful
443
  success = total >= difficulty
444
+
445
  # Bonus for great success
446
  if success and total >= difficulty + 3:
447
  stat_increase = random.randint(1, 2)
448
+ if stat in game_state.stats:
449
+ game_state.stats[stat] = player_stat_value + stat_increase
450
+
451
  # Penalty for bad failure
452
  if not success and total <= difficulty - 3:
453
  stat_decrease = random.randint(1, 2)
454
+ if stat in game_state.stats:
455
+ game_state.stats[stat] = max(1, player_stat_value - stat_decrease) # Stat cannot go below 1
456
+
457
  # Record challenge outcome
458
  if success:
459
  game_state.challenges_won += 1
460
+
461
  return success, roll, total
462
 
463
  def simulate_battle(game_state):
464
  """Simulate a battle with a random enemy. Returns (player_won_bool, battle_log_html)"""
465
  battle_log = "<div style='font-size: 0.9em; line-height: 1.4;'>"
466
+
467
  # Select a random enemy type
468
  enemy_types = list(enemies_data.keys())
469
  if not enemy_types:
470
  return True, "<p>No enemies defined for battle!</p>" # Avoid error if no enemies exist
471
  enemy_type = random.choice(enemy_types)
472
  enemy = enemies_data[enemy_type].copy() # Copy data to modify HP
473
+
474
  battle_log += f"<p>A wild <strong>{enemy_type}</strong> appears!</p>"
475
+
476
  # Simple battle simulation
477
  player_hp = game_state.current_hp
478
  enemy_hp = enemy["hp"]
479
+
480
  player_base_attack = game_state.stats.get("strength", 1)
481
  player_base_defense = 1 # Base defense
482
+
483
  player_attack = player_base_attack
484
  player_defense = player_base_defense
485
+
486
  # Add weapon/armor bonuses if the player has relevant items
487
  attack_bonuses = []
488
  defense_bonuses = []
 
502
 
503
  enemy_attack = enemy["attack"]
504
  enemy_defense = enemy["defense"]
505
+
506
  battle_log += f"<p>Player HP: {player_hp}, Enemy HP: {enemy_hp}</p><hr>"
507
+
508
  # Simple turn-based combat
509
  turn = 1
510
  while player_hp > 0 and enemy_hp > 0 and turn < 20: # Add turn limit to prevent infinite loops
 
513
  damage_to_enemy = max(1, player_attack - enemy_defense)
514
  enemy_hp -= damage_to_enemy
515
  battle_log += f"&nbsp;&nbsp;You attack the {enemy_type} for {damage_to_enemy} damage. (Enemy HP: {max(0, enemy_hp)})<br>"
516
+
517
  if enemy_hp <= 0:
518
  battle_log += f"<p><strong>You defeated the {enemy_type}!</strong></p>"
519
+ game_state.enemies_defeated += 1 # Increment defeated count
520
  break
521
+
522
  # Enemy attacks
523
  damage_to_player = max(1, enemy_attack - player_defense)
524
  player_hp -= damage_to_player
525
  battle_log += f"&nbsp;&nbsp;The {enemy_type} attacks you for {damage_to_player} damage. (Your HP: {max(0, player_hp)})"
526
+
527
  if player_hp <= 0:
528
  battle_log += f"<p><strong>The {enemy_type} defeated you!</strong></p>"
529
  break
530
+
531
  battle_log += "<hr>"
532
  turn += 1
533
 
534
+ if turn >= 20 and player_hp > 0 and enemy_hp > 0: # Check if loop ended due to turn limit
535
+ battle_log += "<p>The battle drags on too long and ends inconclusively. The enemy retreats!</p>"
536
 
537
  # Update player HP after battle
538
+ game_state.current_hp = max(0, player_hp) # Ensure HP doesn't go below 0
539
+
540
  # Return True if player won (or survived), False if player lost
541
+ player_won = game_state.current_hp > 0
542
  battle_log += "</div>"
543
  return player_won, battle_log
544
 
 
547
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
548
  gr.Markdown("# Choose Your Own Adventure")
549
  gr.Markdown("### Can you defeat the Evil Power Master?")
550
+
551
  with gr.Row():
552
  with gr.Column(scale=2):
553
  illustration = gr.HTML(label="Scene")
554
  content = gr.HTML(label="Story")
555
+ choice = gr.Dropdown(label="What will you do?", allow_custom_value=False) # Prevent custom values
556
+
557
  # Hidden field to store game state as JSON
558
  game_state = gr.Textbox(label="Game State JSON", visible=False) # Changed label, still hidden
559
+
560
  with gr.Column(scale=1):
561
  story_path = gr.Markdown(label="Your Path")
562
  stats = gr.HTML(label="Character Stats")
563
  inventory = gr.HTML(label="Inventory")
564
+ # map_btn = gr.Button("View Story Map") # Removed button for simplicity
565
 
566
  # Event listener for player choices
567
  choice.change(
568
+ update_game,
569
+ inputs=[choice, game_state],
570
  outputs=[illustration, content, choice, game_state, stats, inventory, story_path]
571
  )
572
+
573
  # Initialize game on load
574
  demo.load(
575
+ initialize_game,
576
+ inputs=[],
577
  outputs=[illustration, content, choice, game_state, stats, inventory, story_path]
578
  )
579
 
580
+ # Launch the Gradio app
581
  demo.launch()
582
+
583
  </gradio-file>
584
 
585
  <gradio-file name="game_engine.py">
586
  import json
587
  import random
588
  # Import illustrations from game_data for use in create_svg_illustration
589
+ from game_data import illustrations
590
 
591
  class GameState:
592
  """Class to manage game state"""
 
607
  self.challenges_won = 0
608
  self.current_path = "main"
609
  self.journey_progress = 0 # Initialize progress
610
+
611
  def to_json(self):
612
  """Convert game state to JSON string"""
613
+ # Ensure stats is part of the dict being dumped
614
+ state_dict = self.__dict__.copy()
615
+ return json.dumps(state_dict)
616
+
617
  @classmethod
618
  def from_dict(cls, data):
619
  """Create a GameState object from a dictionary"""
 
621
  # Make sure default values are preserved if not in data
622
  for key, value in game_state.__dict__.items():
623
  if key in data:
624
+ # Special handling for stats dictionary to avoid losing default keys
625
+ if key == 'stats' and isinstance(data[key], dict):
626
+ # Update default stats with loaded stats
627
+ default_stats = getattr(game_state, key)
628
+ default_stats.update(data[key])
629
+ setattr(game_state, key, default_stats)
630
+ else:
631
+ setattr(game_state, key, data[key])
632
  return game_state
633
 
634
  def create_svg_illustration(illustration_key):
635
  """Return SVG illustration based on the illustration key"""
636
  # If illustration is not found, use a default illustration
637
  svg_code = illustrations.get(illustration_key, illustrations.get("default", "<svg viewBox='0 0 100 100'><rect width='100' height='100' fill='grey'/><text x='10' y='50' fill='white'>No Image</text></svg>"))
638
+
639
  # Wrap the SVG in a container div with appropriate styling
640
+ # Ensure SVG scales correctly within the container
641
  return f"""
642
+ <div style="width: 100%; height: 250px; border: 2px solid #999; border-radius: 5px; overflow: hidden; display: flex; justify-content: center; align-items: center; background-color: #eee;">
643
+ <div style="width: 100%; height: 100%; display: flex; justify-content: center; align-items: center;">
644
+ {svg_code}
645
+ </div>
646
  </div>
647
  """
648
  </gradio-file>
 
663
  ],
664
  "illustration": "city-gates"
665
  },
666
+
667
  "2": {
668
  "title": "The Weaponsmith",
669
  "content": """<p>The city's renowned weaponsmith, Gorn, welcomes you to his forge. Weapons of all kinds line the walls, from simple daggers to exotic blades that pulse with magic.</p>
 
676
  ],
677
  "illustration": "weaponsmith"
678
  },
679
+
680
  "3": {
681
  "title": "The Ancient Temple",
682
  "content": """<p>The Temple of Eternal Light stands at the heart of Silverhold. Inside, the air is thick with incense, and soft chanting echoes through the chambers.</p>
 
689
  ],
690
  "illustration": "temple"
691
  },
692
+
693
  "4": {
694
  "title": "The Resistance Leader",
695
  "content": """<p>In a secluded tavern, you meet Lyra, leader of the resistance against the Evil Power Master. Battle-scarred but determined, she unfurls a map across the table.</p>
 
702
  ],
703
  "illustration": "resistance-meeting"
704
  },
705
+
706
  "5": {
707
  "title": "The Journey Begins",
708
  "content": """<p>Prepared as best you can be, you leave Silverhold behind. The path to the Evil Power Master's fortress takes you through the Shadowwood Forest, a place once beautiful but now corrupted by dark magic.</p>
 
715
  "randomBattle": True,
716
  "illustration": "shadowwood-forest"
717
  },
718
+
719
  "6": {
720
  "title": "Ambush on the Road",
721
  "content": """<p>The main road through Shadowwood is overgrown but still visible. As you make your way forward, the trees suddenly rustle with movement.</p>
 
731
  },
732
  "illustration": "road-ambush"
733
  },
734
+
735
  "7": {
736
  "title": "The Mist-Shrouded River",
737
  "content": """<p>The river path is longer but less traveled. Thick mist clings to the water's surface, reducing visibility but potentially hiding you from enemies.</p>
 
747
  },
748
  "illustration": "river-spirit"
749
  },
750
+
751
  "8": {
752
  "title": "The Forgotten Ruins",
753
  "content": """<p>The ancient ruins rise from the forest floor like the bones of a forgotten civilization. Moss-covered stones and crumbling arches create a labyrinth of passages and dead ends.</p>
 
763
  },
764
  "illustration": "ancient-ruins"
765
  },
766
+
767
  "9": {
768
  "title": "Breaking Through",
769
  "content": """<p>With courage and quick thinking, you manage to fight your way through the ambush. Several scouts fall to your attacks, and the rest scatter into the forest.</p>
 
777
  "randomBattle": True,
778
  "illustration": "forest-edge"
779
  },
780
+
781
  "10": {
782
  "title": "Captured!",
783
  "content": """<p>Despite your best efforts, the scouts overwhelm you. Disarmed and bound, you're dragged through the forest to a small outpost.</p>
 
791
  "hpLoss": 5,
792
  "illustration": "prisoner-cell"
793
  },
794
+
795
  "11": {
796
  "title": "The Spirit's Blessing",
797
  "content": """<p>"Wisdom flows in you like the river itself," the spirit says, impressed by your answer. The mist swirls around you, and you feel a surge of energy.</p>
 
806
  "statIncrease": { "stat": "wisdom", "amount": 2 },
807
  "illustration": "spirit-blessing"
808
  },
809
+
810
  "12": {
811
  "title": "The Spirit's Wrath",
812
  "content": """<p>"Incorrect," the spirit hisses, its melodic voice turning harsh. "The river does not suffer fools!"</p>
 
820
  "hpLoss": 8,
821
  "illustration": "river-danger"
822
  },
823
+
824
  "13": {
825
  "title": "Ancient Allies",
826
  "content": """<p>Your wisdom guides you through the ruins' labyrinth. As you navigate the crumbling corridors, the whispers change from threatening to curious.</p>
 
831
  { "text": "Ask for knowledge about the fortress", "next": 16 },
832
  { "text": "Request they guide you through secret paths", "next": 17 }
833
  ],
834
+ "statIncrease": { "stat": "wisdom", "amount": 1 }, # Reward for passing challenge implicitly
835
  "illustration": "ancient-spirits"
836
  },
837
+
838
  "14": {
839
  "title": "Lost in Time",
840
  "content": """<p>The ruins become an impossible maze. Each turn leads to unfamiliar passages, and the whispers grow louder, disorienting you further.</p>
 
846
  { "text": "Find a local guide in a nearby village", "next": 17 }
847
  ],
848
  "hpLoss": 10,
849
+ "statIncrease": { "stat": "luck", "amount": -1 }, # Penalty for failing challenge implicitly
850
  "illustration": "lost-ruins"
851
  },
852
+
853
  "15": {
854
  "title": "The Looming Fortress",
855
  "content": """<p>The Evil Power Master's fortress dominates the landscape - a massive structure of black stone with towers that seem to pierce the clouds.</p>
856
  <p>Lightning crackles around its highest spires, and dark shapes patrol the battlements. The main gate is heavily guarded, but there must be other ways in.</p>
857
  <p>As you survey the imposing structure, you consider your options for infiltration.</p>""",
858
  "options": [
859
+ { "text": "Approach the main gate (needs disguise)", "next": 21, "requireItem": "Guard Disguise" },
860
+ { "text": "Scale the outer wall under cover of darkness", "next": 22 }, # Leads to Discovered! challenge
861
+ { "text": "Look for the secret tunnel (requires Secret Tunnel Map)", "next": 24, "requireItem": "Secret Tunnel Map" }
862
  ],
863
+ # Alternative option shows if neither disguise nor map is present
864
+ "alternativeOption": { "text": "Search for another entrance (risky)", "next": 21, "showIf": ["Guard Disguise", "Secret Tunnel Map"] }, # Show if *missing* items
865
  "illustration": "evil-fortress"
866
  },
867
+
868
  "16": {
869
  "title": "Strategic Approach",
870
  "content": """<p>You take time to observe the fortress from a distance. Patrols move in predictable patterns, and supply wagons come and go at regular intervals.</p>
 
872
  <p>You identify several possible entry points, each with its own risks and advantages.</p>""",
873
  "options": [
874
  { "text": "Infiltrate with an incoming supply wagon", "next": 21 },
875
+ { "text": "Use magic to create a distraction (requires any spell)", "next": 22, "requireAnyItem": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing"] }, # Leads to Discovered! challenge
876
+ { "text": "Bribe a guard to let you in (requires special item)", "next": 23, "requireAnyItem": ["Ancient Amulet", "Master Key"] } # Leads to Secret Resistance
877
  ],
878
  # If no required items for specific options, provide a fallback
879
+ "alternativeOption": {
880
+ "text": "Attempt to sneak in using shadows",
881
+ "next": 21,
882
+ "showIf": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing", "Ancient Amulet", "Master Key"] # Show if missing required items
883
+ },
884
+ "statIncrease": { "stat": "wisdom", "amount": 1 }, # Reward for observation
885
  "illustration": "fortress-observation"
886
  },
887
+
888
  "17": {
889
  "title": "The Hidden Way",
890
  "content": """<p>Your search reveals an unexpected approach to the fortress - an ancient maintenance tunnel that runs beneath the barren plains, likely forgotten by the Evil Power Master.</p>
 
893
  "options": [
894
  { "text": "Enter the tunnel immediately", "next": 24 },
895
  { "text": "Hide and wait for the patrol to pass", "next": 21 },
896
+ { "text": "Set a trap for the patrol", "next": 22 } # Leads to Discovered! challenge
897
  ],
898
  "illustration": "hidden-tunnel"
899
  },
900
+
901
  "18": {
902
  "title": "Night Escape",
903
  "content": """<p>You wait until the dead of night, when most guards are dozing at their posts. Using a loose stone you discovered in your cell, you work on the rusted lock.</p>
 
908
  "description": "Slip past the sleeping guards without alerting them.",
909
  "stat": "wisdom",
910
  "difficulty": 6,
911
+ "success": 16, # Escaped successfully, back to observing
912
+ "failure": 10 # Recaptured! Back to cell (loop)
913
  },
914
  "illustration": "night-escape"
915
  },
916
+
917
  "19": {
918
  "title": "Unexpected Ally",
919
  "content": """<p>When the guard passes your cell alone, you whisper, asking why they seemed sympathetic. The guard looks around nervously before responding.</p>
 
922
  "options": [
923
  { "text": "Accept the guard's help (gain disguise, proceed)", "next": 16, "addItem": "Guard Disguise"}, # Gain disguise, proceed to strategic approach
924
  { "text": "Decline - you don't want to put them at risk", "next": 18 }, # Try night escape instead
925
+ { "text": "Convince them to join your cause", "next": 20 } # They arrange transport?
926
  ],
 
927
  "illustration": "guard-ally"
928
  },
929
+
930
  "20": {
931
  "title": "Journey to the Fortress",
932
  "content": """<p>At dawn, you're bound and loaded onto a prison wagon with other captured travelers. The journey to the fortress is uncomfortable, but you use the time to observe and plan.</p>
933
  <p>The other prisoners share what they know about the fortress. One mentions rumors of a resistance operating within the Evil Power Master's ranks.</p>
934
  <p>As the wagon approaches the massive gates, you begin to formulate a plan.</p>""",
935
  "options": [
936
+ { "text": "Look for an opportunity to escape during transfer", "next": 22 }, # High risk, likely leads to discovered challenge
937
  { "text": "Remain captive to get inside, then escape", "next": 21 }, # Get inside, then try to move
938
+ { "text": "Try to contact the internal resistance (needs clue/item)", "next": 23, "requireAnyItem": ["Guard Disguise", "Ancient Amulet"]} # Signal resistance
939
  ],
940
+ "alternativeOption": { "text": "Remain captive with no plan (leads to capture)", "next": 28, "showIf": ["Guard Disguise", "Ancient Amulet"] }, # If no clue/item
941
  "illustration": "prison-wagon"
942
  },
943
+
944
  "21": {
945
  "title": "Infiltration",
946
  "content": """<p>Using your skills and preparation, you manage to infiltrate the outer perimeter of the fortress. The black stone walls loom overhead, even more intimidating up close.</p>
 
951
  { "text": "Search for the dungeons", "next": 26 },
952
  { "text": "Follow a group of mages", "next": 27 }
953
  ],
954
+ "randomBattle": True,
955
  "illustration": "fortress-interior"
956
  },
957
+
958
  "22": {
959
  "title": "Discovered!",
960
  "content": """<p>As you make your way through the fortress, an alarm suddenly sounds! Your presence has been detected, and you hear the heavy footsteps of guards approaching.</p>
 
963
  "challenge": {
964
  "title": "Escape Detection",
965
  "description": "You need to escape from the approaching guards before they trap you.",
966
+ "stat": "courage",
967
  "difficulty": 7,
968
  "success": 25, # Managed to evade, head to tower
969
  "failure": 28 # Captured Again
970
  },
971
  "illustration": "fortress-alarm"
972
  },
973
+
974
  "23": {
975
  "title": "Secret Resistance",
976
  "content": """<p>Through luck or skill (or maybe a bribe or disguise), you make contact with members of the secret resistance operating within the fortress. They're skeptical of you at first, but your actions have convinced them of your intentions.</p>
 
983
  ],
984
  "illustration": "secret-meeting"
985
  },
986
+
987
  "24": {
988
  "title": "Underground Passage",
989
  "content": """<p>The ancient tunnel is damp and narrow, forcing you to crouch as you make your way forward. Roots hang from the ceiling, and the air feels thick with age.</p>
 
996
  ],
997
  "illustration": "underground-passage"
998
  },
999
+
1000
  "25": {
1001
  "title": "The Central Tower",
1002
  "content": """<p>The central tower rises from the heart of the fortress like a black spear. As you get closer, you can feel powerful magic emanating from its peak.</p>
1003
  <p>Guards are more numerous here, and you spot several dark mages performing rituals in alcoves along the walls. Whatever the Evil Power Master is planning, it seems to be approaching its climax.</p>
1004
  <p>A massive door blocks the entrance to the tower, engraved with strange symbols.</p>""",
1005
  "options": [
1006
+ { "text": "Try to decipher the symbols (needs wisdom)", "next": 35 },
1007
  { "text": "Look for another entrance", "next": 36 },
1008
+ { "text": "Use magic/key to open the door", "next": 37, "requireAnyItem": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing", "Master Key"] }
1009
  ],
1010
+ "alternativeOption": { "text": "Wait and observe the mages", "next": 36, "showIf": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing", "Master Key"] },
1011
  "illustration": "central-tower"
1012
  },
1013
+
1014
  "26": {
1015
  "title": "The Dungeons",
1016
  "content": """<p>The dungeons are a maze of dark, damp corridors lined with cells. Moans and whispers echo off the stone walls, creating an eerie atmosphere of despair.</p>
 
1023
  ],
1024
  "illustration": "dungeon-corridors"
1025
  },
1026
+
1027
  "27": {
1028
  "title": "The Ritual Chamber",
1029
  "content": """<p>Following the group of mages leads you to a large circular chamber. Hidden in the shadows, you observe as they prepare for what appears to be an important ritual.</p>
 
1036
  ],
1037
  "illustration": "ritual-chamber"
1038
  },
1039
+
1040
  "28": {
1041
  "title": "Captured Again",
1042
  "content": """<p>The guards overwhelm you, and this time, there's no easy escape. You're brought before a high-ranking officer, who smiles coldly.</p>
1043
  <p>"The Master will be most pleased," he says. "He so rarely gets to meet those foolish enough to challenge him directly."</p>
1044
  <p>You're escorted under heavy guard toward the central tower, where the Evil Power Master awaits.</p>""",
1045
  "options": [
1046
+ { "text": "Look for another chance to escape (low odds)", "next": 44 },
1047
  { "text": "Prepare yourself mentally to face the Power Master", "next": 45 },
1048
  { "text": "Try to gather information from the guards", "next": 46 }
1049
  ],
1050
  "illustration": "prisoner-escort"
1051
  },
1052
+
1053
  "29": {
1054
  "title": "The Uprising",
1055
  "content": """<p>The resistance has planned carefully. When the signal is given, chaos erupts throughout the fortress. Guards find themselves fighting servants, and magical wards fail as saboteurs destroy key components.</p>
 
1062
  ],
1063
  "illustration": "fortress-uprising"
1064
  },
1065
+
1066
  "30": {
1067
  "title": "The Final Approach",
1068
  "content": """<p>With the resistance's help, you navigate through secret passages unknown to most of the fortress inhabitants. These narrow corridors, built during the fortress's construction, lead directly to the upper levels.</p>
 
1075
  ],
1076
  "illustration": "secret-passage"
1077
  },
1078
+
1079
  "31": {
1080
  "title": "Knowledge Exchange",
1081
  "content": """<p>You share everything you've learned on your journey - about the river spirit, the ancient ruins, and any magical artifacts you've collected. In return, the resistance provides crucial information.</p>
 
1086
  { "text": "Find out when the alignment will occur", "next": 54 },
1087
  { "text": "Learn if there are any weapons that can harm the crystal", "next": 55 }
1088
  ],
1089
+ "statIncrease": { "stat": "wisdom", "amount": 1 }, # Reward for sharing info
1090
  "illustration": "knowledge-sharing"
1091
  },
1092
 
1093
  # --- Placeholder Pages ---
1094
+ "32": { "title": "Storeroom Search", "content": "<p>You search the dusty storeroom. Mostly old crates and broken tools, but you find a forgotten Healing Potion tucked away!</p>", "options": [{ "text": "Drink Potion and find way up", "next": 33, "addItem": "Healing Potion" }], "illustration": "underground-passage" }, # Add Potion
1095
+ "33": { "title": "Ascending", "content": "<p>You find a loose grate leading to the fortress kitchens. Smells marginally better than the tunnel. You emerge cautiously.</p>", "options": [{ "text": "Sneak through the kitchens", "next": 21 }], "illustration": "fortress-interior" },
1096
+ "34": { "title": "Listening", "content": "<p>You hear the rhythmic marching of guards above, and the distant clang of metal from the armory. Sounds like standard patrols, nothing immediately alarming.</p>", "options": [{ "text": "Explore the storeroom", "next": 32 }, { "text": "Try to find a way up", "next": 33 }], "illustration": "underground-passage" },
1097
+ "35": { "title": "Deciphering Symbols", "content": "<p>The symbols pulse with dark energy. Your wisdom tells you they form a powerful ward, possibly tied to the Master's life force. It requires a specific magical resonance or immense force to bypass.</p>", "options": [{ "text": "Look for another entrance", "next": 36 }, { "text": "Use magic/key (if you have it)", "next": 37, "requireAnyItem": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing", "Master Key", "Ancient Amulet"] }], "statIncrease": {"stat":"wisdom", "amount":1}, "illustration": "central-tower" },
1098
+ "36": { "title": "Searching for Entry", "content": "<p>You circle the base of the tower. High up, you spot a less guarded balcony. Scaling the sheer, dark stone might be possible for someone with exceptional strength or courage, but it's extremely dangerous.</p>", "options": [{ "text": "Attempt to climb (Strength/Courage Challenge)", "next": 22 }, {"text": "Return to the main door", "next": 25}], "illustration": "central-tower" }, # Leads to challenge
1099
+ "37": { "title": "Magical Entry", "content": "<p>You focus your magical item (or the Master Key). The symbols flare violently, then fade as the massive door groans open! You step into the oppressive darkness of the tower base.</p>", "options": [{ "text": "Ascend the tower", "next": 48 }], "illustration": "final-confrontation" }, # Straight to the top
1100
+ "38": { "title": "Causing Distraction", "content": "<p>Using your lockpicking skills (or the Master Key), you manage to unlock a few cells. The freed prisoners, though weak, immediately cause a commotion, shouting and banging, drawing several guards away from their posts.</p>", "options": [{ "text": "Investigate the guarded cell", "next": 39 }, {"text": "Look for an exit upwards", "next": 40}], "requireAnyItem": ["Master Key"], "illustration": "dungeon-corridors" }, # Added item requirement
1101
+ "39": { "title": "Guarded Cell", "content": "<p>Inside the heavily barred cell is a frail figure clad in mage robes likely a captured resistance member or someone who defied the Master. They look up weakly as you approach the bars.</p>", "options": [{ "text": "Attempt to free the mage (needs Master Key/Runes)", "next": 30, "requireAnyItem": ["Master Key", "Binding Runes Scroll"] }, {"text": "Leave them for now", "next": 40}], "illustration": "dungeon-corridors" }, # Specified required items
1102
+ "40": { "title": "Finding Passage", "content": "<p>Behind a loose tapestry depicting the Master's grim visage, you find a hidden spiral staircase leading up, likely towards the main fortress levels above the dungeons.</p>", "options": [{ "text": "Take the stairs", "next": 21 }], "illustration": "fortress-interior" },
1103
+ "41": { "title": "Disrupting the Ritual", "content": "<p>You burst into the chamber! The mages are startled, their chant faltering. You launch an attack, disrupting their concentration entirely. The central crystal flares erratically, and raw magic arcs through the room.</p>", "options": [{ "text": "Fight the enraged mages!", "next": 22 }], "illustration": "ritual-chamber" }, # Likely leads to being discovered challenge
1104
+ "42": { "title": "Observing the Ritual", "content": "<p>You watch intently as the mages chant in unison. The crystal seems to be drawing power from ley lines deep within the fortress, focusing it towards the tower's peak for the Master's final empowerment during the alignment.</p>", "options": [{ "text": "Try to disrupt it now", "next": 41 }, {"text": "Try to steal the crystal (very risky)", "next": 43}, {"text": "Leave and head to the tower", "next": 25}], "statIncrease": {"stat": "wisdom", "amount": 1}, "illustration": "ritual-chamber" },
1105
+ "43": { "title": "Stealing the Crystal", "content": "<p>As the mages are deep in their chant, you attempt to swiftly snatch the pulsing crystal. It burns with raw, cold energy! They immediately sense the disturbance and react with fury, turning to attack you.</p>", "options": [{ "text": "Fight back!", "next": 22 }], "illustration": "ritual-chamber" }, # Leads to being discovered challenge
1106
+ "44": { "title": "Escape Attempt", "content": "<p>You scan your surroundings, looking for any weakness, any chance. But the guards are professionals, their grip like iron, their eyes vigilant. No escape seems possible right now.</p>", "options": [{ "text": "Focus and prepare mentally", "next": 45 }, {"text": "Try to glean info from guards", "next": 46}], "illustration": "prisoner-escort" },
1107
+ "45": { "title": "Mental Preparation", "content": "<p>You close your eyes briefly, focusing your mind. You recall your training, your purpose, the faces of those suffering under the Master's rule. You are ready. Let him come.</p>", "options": [{ "text": "Continue to the Master's chamber", "next": 48 }], "statIncrease": {"stat": "courage", "amount": 1}, "illustration": "final-confrontation" }, # Directly to final battle
1108
+ "46": { "title": "Gathering Information", "content": "<p>You try to subtly listen to the guards' chatter. They mutter about the Master being 'nearly ready' and the 'alignment is minutes away'. One nervously mentions the crystal is 'unstable' at peak power. Time is critically short.</p>", "options": [{ "text": "Focus and prepare mentally", "next": 45 }, {"text": "Look desperately for escape again", "next": 44}], "statIncrease": {"stat": "wisdom", "amount": 1}, "illustration": "prisoner-escort" },
1109
+ "47": { "title": "Leading the Charge", "content": "<p>With a battle cry, you lead the resistance fighters forward, cutting through the surprised ranks of the Master's personal guard. Steel clashes against steel as you push relentlessly towards the main tower entrance.</p>", "options": [{ "text": "Breach the tower door", "next": 25 }], "illustration": "fortress-uprising" },
1110
  # Page 48 is the final confrontation
1111
+ "49": { "title": "Sneaking Ahead", "content": "<p>While the main uprising creates chaos as a diversion, you slip through the shadows, bypassing the main battle. You find a less-guarded servant's entrance to the central tower.</p>", "options": [{ "text": "Enter the tower stealthily", "next": 50 }], "illustration": "secret-passage" },
1112
+ "50": { "title": "Cautious Entry", "content": "<p>You ease open the hidden door and step into the base of the central tower. A grand staircase spirals upwards into oppressive darkness. The very air hums with palpable dark energy.</p>", "options": [{ "text": "Ascend the tower carefully", "next": 48 }], "illustration": "final-confrontation" }, # Head to final battle
1113
+ "51": { "title": "Ritual Information", "content": "<p>Your resistance guide explains the Master is performing a ritual tied to a rare cosmic alignment to achieve immense power, possibly immortality. The dark crystal in his chamber is the focal point.</p>", "options": [{ "text": "Enter the tower now", "next": 50 }, {"text": "Ask for additional backup", "next": 52}], "illustration": "secret-passage" },
1114
+ "52": { "title": "Requesting Help", "content": "<p>The resistance leader nods grimly. 'Take Kaelen,' she says, gesturing to a scarred warrior armed with a blessed axe. 'May their strength aid yours.' Kaelen gives a curt nod.</p>", "options": [{ "text": "Accept Kaelen's help and enter", "next": 48 }], "addItem": "Resistance Ally", "illustration": "secret-passage" }, # Add ally and go to fight
1115
+ "53": { "title": "Crystal Location", "content": "<p>The resistance sage confirms the crystal is kept in the Master's main audience chamber, at the very top of the central tower. It reputedly floats above his throne, radiating immense power.</p>", "options": [{ "text": "Prepare and head to the tower", "next": 48 }], "illustration": "knowledge-sharing" }, # Go to final battle
1116
+ "54": { "title": "Alignment Timing", "content": "<p>'The alignment is upon us!' the sage gasps, pointing to strange lights in the sky visible even through the fortress windows. 'It may already be too late! You must hurry!'</p>", "options": [{ "text": "Race to the tower immediately!", "next": 48 }], "illustration": "knowledge-sharing" }, # Go to final battle
1117
+ "55": { "title": "Crystal Weakness", "content": "<p>They mention ancient texts hinting that the crystal, forged in darkness, is vulnerable to sources of pure light or concentrated holy energy. Perhaps your Healing Light spell, if focused, or the Shield of Faith could disrupt it?</p>", "options": [{ "text": "Remember this and head to the tower", "next": 48 }], "illustration": "knowledge-sharing" }, # Go to final battle
 
 
 
 
 
 
 
 
 
 
 
 
1118
 
 
1119
  "56": { # Direct Confrontation Battle
1120
  "title": "Direct Confrontation",
1121
+ "content": """<p>You charge at the Evil Power Master, drawing on all your courage and strength. He meets your attack with blasts of dark magic, the air between you crackling and distorting with raw energy.</p>
1122
+ <p>The battle is fierce, pushing you to your absolute limits. Each blow you land seems partially absorbed by the dark energy surrounding him, while his counter-attacks grow increasingly powerful and difficult to evade.</p>
1123
+ <p>As the fight rages, you realize that as long as the dark crystal pulses behind him, this might be an unwinnable war of attrition.</p>""",
1124
  "challenge": {
1125
  "title": "Battle with the Evil Power Master",
1126
+ "description": "You must overcome his dark magic through sheer determination, skill, and perhaps exploiting a weakness.",
1127
+ "stat": "strength", # Primary check is Strength
1128
  "difficulty": 9, # High difficulty
1129
  "success": 59, # Hero's Victory
1130
  "failure": 60 # Darkness Prevails
 
1133
  },
1134
  "57": { # Crystal Destruction Attempt
1135
  "title": "Crystal Destruction",
1136
+ "content": """<p>Recognizing the crystal as the true source of his overwhelming power, you feint an attack and then lunge towards the malevolently pulsing gem floating above the throne. The Evil Power Master shouts in alarm and fury, desperately trying to intercept you.</p>
1137
+ <p>"No! Stay away from that, you insignificant fool! You have no conception of the forces you're tampering with!"</p>
1138
+ <p>As you get closer, the crystal's energy intensifies dramatically, throwing up a shimmering barrier of pure dark magic that you must break through to reach it.</p>""",
1139
  "challenge": {
1140
  "title": "Breaking the Crystal Barrier",
1141
+ "description": "You must overcome the crystal's protective magic using wit, willpower, or perhaps a specific counter-energy.",
1142
+ "stat": "wisdom", # Primary check is Wisdom
1143
  "difficulty": 8, # Slightly lower difficulty than direct fight
1144
  "success": 61, # The Crystal Shatters
1145
  "failure": 62 # A Desperate Gambit (leads to sacrifice ending)
1146
  },
1147
  "illustration": "crystal-barrier"
1148
  },
1149
+ "58": {
1150
+ "title": "Attempt to Reason",
1151
+ "content": """<p>You hold up a hand, trying to speak to the Evil Power Master, appealing to any possible reason or shred of humanity that might remain within the darkness. He pauses, then throws back his head and laughs – a chilling, hollow sound.</p>
1152
+ <p>"Humanity? Reason? Such fleeting, petty concerns! Power is the only truth, the only reason! And soon, I shall have ultimate power!" he snarls, launching a devastating bolt of dark energy directly at you.</p>
1153
+ <p>Clearly, negotiation was never an option.</p>""",
1154
+ "options": [
1155
+ { "text": "Attack the Evil Power Master directly", "next": 56 },
1156
+ { "text": "Try to destroy the crystal", "next": 57 }
1157
+ ],
1158
+ "illustration": "final-confrontation"
1159
+ },
1160
  "59": {
1161
  "title": "A Hero's Victory",
1162
+ "content": """<p>Through sheer determination, skill, and perhaps exploiting a momentary weakness, you manage to overcome the Evil Power Master's formidable defenses. As he staggers backwards from your decisive blow, his concentration wavers, and his connection to the crystal weakens for a critical instant.</p>
1163
+ <p>Seizing the opportunity, you strike the dark crystal with all your remaining might (or perhaps channel pure energy into it). It cracks visibly, then shatters in a blinding flash of light and concussive energy that throws you back.</p>
1164
+ <p>The Evil Power Master screams a final, agonized cry as his borrowed power dissipates like smoke. "Impossible! I was... so close... to ultimate power!"</p>
1165
+ <p>As the blinding light fades, you stand victorious amidst the crumbling chamber. The dark magic recedes from the land, and the people are free once more.</p>""",
1166
  "gameOver": True,
1167
+ "ending": "You have defeated the Evil Power Master and destroyed the source of his terrible power. Songs will be sung of your bravery and skill for generations to come. The land slowly begins to heal from his corruption, and you are hailed as the Hero of Silverhold throughout the recovering realm.",
1168
  "illustration": "hero-victory"
1169
  },
1170
  "60": {
1171
  "title": "Darkness Prevails",
1172
+ "content": """<p>Despite your bravery and best efforts, the Evil Power Master's command over dark magic, amplified by the crystal, is simply too great. His relentless attacks overwhelm your defenses, shattering your weapon or shield, and bringing you crashing to your knees.</p>
1173
+ <p>"Valiant, perhaps, but ultimately futile," he says, looking down at you with cold contempt. "You could have served me, you know. Gained power beyond your imagining. Now... you will simply serve as a warning." He raises a hand, dark energy coalescing.</p>
1174
+ <p>As your vision fades into darkness, you see the crystal's evil glow intensifying, the alignment reaching its peak. You have failed. Darkness will cover the land, and the Master's reign is absolute.</p>""",
1175
  "gameOver": True,
1176
+ "ending": "The Evil Power Master proved too powerful, and your heroic quest ends in tragic defeat. Darkness spreads unchallenged across the land as he completes his ritual and ascends to even greater, terrifying power. Hope fades, but perhaps, someday, another hero will rise to challenge his eternal reign.",
1177
  "illustration": "dark-victory"
1178
  },
1179
  "61": {
1180
  "title": "The Crystal Shatters",
1181
+ "content": """<p>Drawing on all your wisdom and inner willpower (or perhaps using a specific magical counter), you manage to push through the crystal's defensive barrier. Reaching out, you touch its vibrating surface, which burns with an unnatural cold against your skin.</p>
1182
+ <p>The Evil Power Master shrieks in mingled fury and desperation, "Stop! Stop, you fool! You'll doom us all!"</p>
1183
+ <p>With a final, concentrated effort, you channel your own life force or focused energy into the dark gem. Cracks spiderweb across its obsidian surface, spreading rapidly until it detonates with a deafening implosion of light and sound, sucking the dark energy from the room.</p>
1184
+ <p>When your vision clears, the Evil Power Master lies broken and powerless on the floor, his power utterly broken along with the crystal. The oppressive atmosphere lifts, and peace can finally begin to return to the land.</p>""",
1185
  "gameOver": True,
1186
+ "ending": "By cleverly targeting and destroying the source of the Evil Power Master's augmented strength, you have saved the realm from his tyranny. The fortress itself begins to shake and crumble as its dark magic foundations unravel. You escape just in time, leaving the ruin behind. Your name becomes a legend associated with wisdom and decisive action.",
1187
  "illustration": "crystal-destroyed"
1188
  },
1189
  "62": {
1190
  "title": "A Desperate Gambit",
1191
+ "content": """<p>The crystal's protective barrier proves too strong, violently repelling your attempts to break through. As you struggle against its crushing force, the Evil Power Master regains his composure and approaches, a cruel smile playing on his lips.</p>
1192
+ <p>"Did you truly think it would be that easy?" he mocks, preparing a final, devastating spell. "This crystal has endured for millennia. It cannot be destroyed by a pathetic mortal like you."</p>
1193
+ <p>Seeing no other option, in a final, desperate move, you gather all your remaining strength and hurl your most powerful weapon or focus your strongest spell directly at the unstable crystal.</p>
1194
+ <p>To your surprise and the Evil Power Master's horror, this doesn't break the barrier but instead creates a catastrophic resonance effect within the overloaded gem. The crystal begins to vibrate violently, its contained energy spiraling wildly out of control.</p>
1195
+ <p>"What have you done?!" the Master cries in terror as the crystal's energy erupts outwards, engulfing both of you, the chamber, and the entire tower top in blinding, destructive light.</p>""",
1196
  "gameOver": True,
1197
+ "ending": "Your desperate, final attack destabilized the dark crystal, causing a catastrophic release of uncontrolled energy that utterly annihilated the Evil Power Master, his chamber, and tragically, yourself. Though you did not survive, your ultimate sacrifice shattered his power and saved the realm from imminent darkness. Bards will forever sing of your selfless heroism.",
1198
  "illustration": "heroic-sacrifice"
1199
  }
1200
  }
1201
 
1202
+
1203
  # --- Item Data ---
1204
  items_data = {
1205
+ "Flaming Sword": {"type": "weapon", "description": "A sword wreathed in magical fire. Looks dangerous to friend and foe. +3 Attack.", "attackBonus": 3},
1206
+ "Whispering Bow": {"type": "weapon", "description": "A finely crafted bow enchanted for silence. Favored by assassins. +2 Attack.", "attackBonus": 2},
1207
+ "Guardian Shield": {"type": "armor", "description": "A sturdy shield blessed with protective magic. Glows faintly. +2 Defense.", "defenseBonus": 2},
1208
+ "Healing Light Spell": {"type": "spell", "description": "A basic clerical spell that can mend minor wounds."},
1209
+ "Shield of Faith Spell": {"type": "spell", "description": "A protective spell that creates a temporary shimmering barrier."},
1210
+ "Binding Runes Scroll": {"type": "spell", "description": "A scroll containing complex runes to temporarily immobilize an enemy."},
1211
+ "Secret Tunnel Map": {"type": "quest", "description": "A crudely drawn map marking a hidden tunnel entrance near the fortress's north wall."},
1212
+ "Poison Daggers": {"type": "weapon", "description": "A pair of wicked-looking daggers coated with a fast-acting paralytic poison. +1 Attack.", "attackBonus": 1},
1213
+ "Master Key": {"type": "quest", "description": "A heavy iron key with intricate wards. Said to unlock any non-magical lock."},
1214
+ "Water Spirit's Blessing": {"type": "spell", "description": "A lingering blessing from the river spirit, enhancing clarity and insight."},
1215
+ "Ancient Amulet": {"type": "quest", "description": "A heavy stone amulet radiating ancient protective magic. Seems to resist dark energy. +1 Defense.", "defenseBonus": 1},
1216
+ "Guard Disguise": {"type": "quest", "description": "A slightly ill-fitting uniform worn by the fortress guards."},
1217
+ "Healing Potion": {"type": "consumable", "description": "A vial containing a sparkling red liquid. Restores 10 HP.", "hpRestore": 10, "useOnAdd": True}, # Added HP restore value and use flag
1218
+ "Resistance Ally": {"type": "quest", "description": "Kaelen, a grim resistance fighter armed with a blessed axe, fights alongside you."}, # Added name
1219
  }
1220
 
1221
  # --- Enemy Data ---
 
1223
  "Scout": {"hp": 15, "attack": 4, "defense": 1},
1224
  "Dark Mage": {"hp": 20, "attack": 6, "defense": 2},
1225
  "Fortress Guard": {"hp": 25, "attack": 5, "defense": 3},
1226
+ "Shadow Hound": {"hp": 18, "attack": 5, "defense": 2},
1227
+ "Animated Armor": {"hp": 30, "attack": 4, "defense": 4},
1228
  }
1229
 
1230
 
1231
  # --- SVG templates for illustrations ---
1232
  # Helper function for placeholder SVGs
1233
  def create_placeholder_svg(text="Placeholder", color="#cccccc"):
1234
+ # Basic SVG with a rectangle and centered text
1235
  return f"""<svg viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
1236
+ <rect width="200" height="100" fill="{color}" rx="5" ry="5"/>
1237
  <text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-family="monospace" font-size="14px" fill="#000">{text}</text>
1238
+ Sorry, your browser does not support inline SVG.
1239
  </svg>"""
1240
 
1241
  illustrations = {
1242
  "default": create_placeholder_svg("Default Scene", "#aaaaaa"),
1243
  "game-over": create_placeholder_svg("Game Over", "#ffaaaa"),
1244
  "city-gates": """
1245
+ <svg viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
1246
+ <rect width="200" height="75" fill="#87CEEB" />
1247
+ <rect y="75" width="200" height="50" fill="#8B4513" />
1248
+ <rect x="25" y="25" width="150" height="50" fill="#A9A9A9" stroke="#696969" stroke-width="1"/>
1249
+ <rect x="25" y="15" width="20" height="60" fill="#A9A9A9" stroke="#696969" stroke-width="1"/>
1250
+ <rect x="155" y="15" width="20" height="60" fill="#A9A9A9" stroke="#696969" stroke-width="1"/>
1251
+ <path d="M 25 25 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 Z" fill="#A9A9A9" stroke="#696969" stroke-width="0.5"/>
1252
+ <path d="M 25 15 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 Z" fill="#A9A9A9" stroke="#696969" stroke-width="0.5"/>
1253
+ <path d="M 155 15 h 5 v -5 h 5 v 5 h 5 v -5 h 5 v 5 Z" fill="#A9A9A9" stroke="#696969" stroke-width="0.5"/>
1254
+ <rect x="75" y="40" width="50" height="60" fill="#654321" />
1255
+ <path d="M 75 40 Q 100 30 125 40 Z" fill="#654321"/>
1256
+ <rect x="98" y="40" width="4" height="60" fill="#402000" />
1257
+ <circle cx="90" cy="70" r="2" fill="#402000" />
1258
+ <circle cx="110" cy="70" r="2" fill="#402000" />
1259
+ <path d="M 75 125 Q 100 100 125 125 Z" fill="#A0522D" />
1260
+ <path d="M 75 100 L 125 100 L 100 75 Z" fill="#A0522D" opacity="0.5"/>
1261
+ <line x1="35" y1="0" x2="35" y2="15" stroke="black" stroke-width="1"/>
1262
+ <polygon points="35,0 45,5 35,10" fill="#DC143C"/>
1263
+ <line x1="165" y1="0" x2="165" y2="15" stroke="black" stroke-width="1"/>
1264
+ <polygon points="165,0 175,5 165,10" fill="#DC143C"/>
1265
+ </svg>
1266
  """,
1267
  "weaponsmith": """
1268
+ <svg viewBox="0 0 200 125" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
1269
+ <rect width="200" height="125" fill="#6B4226"/>
1270
+ <rect x="125" y="50" width="50" height="35" fill="#8B4513"/>
1271
+ <rect x="135" y="60" width="30" height="15" fill="#FF4500"/>
1272
+ <ellipse cx="150" cy="67.5" rx="12.5" ry="5" fill="#FFD700" opacity="0.8"/>
1273
+ <path d="M 90 80 L 110 80 L 115 75 L 105 70 L 95 70 L 85 75 Z" fill="#708090"/>
1274
+ <rect x="95" y="80" width="10" height="10" fill="#778899"/>
1275
+ <line x1="25" y1="30" x2="45" y2="35" stroke="#C0C0C0" stroke-width="2"/> <line x1="60" y1="25" x2="75" y2="35" stroke="#A0522D" stroke-width="1.5"/> <polygon points="75,35 78,33 80,35 78,37" fill="#C0C0C0"/> <path d="M 30 45 C 35 40, 45 40, 50 45 L 50 85 L 30 85 Z" fill="#808080"/> <circle cx="40" cy="65" r="3" fill="#696969"/> <line x1="70" y1="45" x2="70" y2="85" stroke="#A0522D" stroke-width="1.5"/>
1276
+ <path d="M 65 45 Q 70 40, 75 45 Q 80 55, 70 60 Z" fill="#C0C0C0"/>
1277
+ <rect x="15" y="90" width="60" height="5" fill="#8B4513"/>
1278
+ <rect x="20" y="95" width="5" height="10" fill="#8B4513"/>
1279
+ <rect x="65" y="95" width="5" height="10" fill="#8B4513"/>
1280
+ <circle cx="110" cy="60" r="12.5" fill="#DEB887"/> <rect x="100" y="72.5" width="20" height="17.5" fill="#A0522D"/> <rect x="95" y="70" width="30" height="10" fill="#D2691E"/> <circle cx="106.5" cy="57.5" r="1.5" fill="#000"/> <circle cx="113.5" cy="57.5" r="1.5" fill="#000"/> <path d="M 107.5 62.5 Q 110 65 112.5 62.5" fill="none" stroke="#000" stroke-width="0.5"/> <rect x="115" y="70" width="10" height="3" fill="#808090"/>
1281
+ <rect x="118" y="73" width="4" height="15" fill="#A0522D"/>
1282
+ <line x1="20" y1="90" x2="20" y2="75" stroke="#603813" stroke-width="2"/>
1283
+ <line x1="18" y1="75" x2="22" y2="75" stroke="#603813" stroke-width="1"/>
1284
+ <path d="M 20 75 L 19 70 L 20 65 L 21 70 Z" fill="#FF4500" opacity="0.7">
1285
+ <animate attributeName="opacity" values="0.7;1;0.7" dur="1s" repeatCount="indefinite" />
1286
+ </path>
1287
+ <path d="M 40 90 C 35 80, 35 70, 40 60" stroke="#556B2F" stroke-width="1.5" fill="none"/>
1288
+ <line x1="40" y1="90" x2="40" y2="60" stroke="#8FBC8F" stroke-width="0.5"/>
1289
+ <path d="M 55 90 C 58 85, 62 85, 65 90 L 65 80 L 55 80 Z" fill="#B0C4DE" stroke="#708090" stroke-width="1"/>
1290
+ <circle cx="60" cy="85" r="2" fill="#F0F8FF"/>
1291
+ </svg>
 
 
 
 
1292
  """,
1293
  # --- Add other placeholders ---
1294
  "temple": create_placeholder_svg("Ancient Temple", "#fffde7"),
 
1329
  "heroic-sacrifice": create_placeholder_svg("Heroic Sacrifice", "#ffcc80"),
1330
 
1331
  }
 
1332
  </gradio-file>
1333
 
1334
  </gradio-lite>