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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +415 -248
index.html CHANGED
@@ -51,6 +51,7 @@
51
  import gradio as gr
52
  import random
53
  import json
 
54
  from game_data import game_data, illustrations, enemies_data, items_data
55
  from game_engine import GameState, create_svg_illustration
56
 
@@ -73,25 +74,10 @@ def initialize_game():
73
  options.append(opt["text"])
74
 
75
  # Update game statistics display
76
- stats = f"""
77
- <div style='display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;'>
78
- <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
79
- <strong>Courage:</strong> {game_state.stats['courage']}
80
- </div>
81
- <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
82
- <strong>Wisdom:</strong> {game_state.stats['wisdom']}
83
- </div>
84
- <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
85
- <strong>Strength:</strong> {game_state.stats['strength']}
86
- </div>
87
- <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
88
- <strong>HP:</strong> {game_state.current_hp}/{game_state.max_hp}
89
- </div>
90
- </div>
91
- """
92
 
93
  # Initialize inventory as empty
94
- inventory = ""
95
 
96
  # Set story path
97
  story_path = "You are at the beginning of your adventure."
@@ -106,7 +92,8 @@ def update_game(choice, game_state_json):
106
 
107
  # Get current page data
108
  current_page = game_state.current_page
109
- page_data = game_data[str(current_page)]
 
110
 
111
  # Find the selected option
112
  selected_option = None
@@ -116,111 +103,173 @@ def update_game(choice, game_state_json):
116
  break
117
 
118
  if selected_option is None:
119
- return "Error: Option not found", "Please try again", gr.Dropdown(choices=["Restart"]), game_state.to_json(), "", "", ""
120
-
 
 
 
 
 
 
 
 
 
 
 
121
  # Check if this option requires an item
122
- if "requireItem" in selected_option and selected_option["requireItem"] not in game_state.inventory:
123
- content = f"<p>You need the <strong>{selected_option['requireItem']}</strong> for this option, but you don't have it.</p>"
124
- options = [opt["text"] for opt in page_data["options"]]
125
- return create_svg_illustration(page_data["illustration"]), content, gr.Dropdown(choices=options), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), f"You remain on page {current_page}."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
  # Process special items to collect
128
- if "addItem" in selected_option and selected_option["addItem"] not in game_state.inventory:
129
- game_state.inventory.append(selected_option["addItem"])
 
130
 
131
  # Move to the next page
132
  next_page = selected_option["next"]
133
  game_state.current_page = next_page
134
- game_state.visited_pages.append(next_page)
 
135
 
136
  # Update journey progress based on page transitions
137
- game_state.journey_progress += 5 # Increment progress with each decision
138
  if game_state.journey_progress > 100:
139
  game_state.journey_progress = 100
140
-
 
141
  # Check for random battle (20% chance if page has randomBattle flag)
142
- if "randomBattle" in page_data and page_data["randomBattle"] and random.random() < 0.2:
143
- battle_result = simulate_battle(game_state)
 
 
144
  if not battle_result:
145
  # Player died in battle
146
- content = "<h2>Game Over</h2><p>You have been defeated in battle!</p>"
147
- return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"]), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), "Game Over - You were defeated in battle."
148
-
149
- # Get new page data
 
 
 
150
  page_data = game_data[str(next_page)]
151
 
152
- # Create the SVG illustration
153
- svg_content = create_svg_illustration(page_data["illustration"])
154
-
155
  # Process page stat effects
156
- if "statIncrease" in page_data:
157
- stat = page_data["statIncrease"]["stat"]
158
- amount = page_data["statIncrease"]["amount"]
 
159
  game_state.stats[stat] += amount
160
 
161
  # Process HP loss
162
- if "hpLoss" in page_data:
163
- game_state.current_hp -= page_data["hpLoss"]
 
164
  if game_state.current_hp <= 0:
165
  game_state.current_hp = 0
166
  content = "<h2>Game Over</h2><p>You have died from your wounds!</p>"
167
- return create_svg_illustration("game-over"), content, gr.Dropdown(choices=["Restart"]), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), "Game Over - You died from your wounds."
168
 
 
169
  # Check if this is a challenge page
170
- if "challenge" in page_data:
171
- challenge = page_data["challenge"]
172
- success = perform_challenge(game_state, challenge)
 
 
 
 
173
 
174
  # Update story based on challenge result
175
  if success:
 
176
  next_page = challenge["success"]
177
  else:
 
178
  next_page = challenge["failure"]
179
 
180
  game_state.current_page = next_page
181
- game_state.visited_pages.append(next_page)
182
- page_data = game_data[str(next_page)]
183
- svg_content = create_svg_illustration(page_data["illustration"])
 
 
 
184
 
185
- # Handle game over
186
- if "gameOver" in page_data and page_data["gameOver"]:
187
  content = f"<h2>{page_data['title']}</h2>"
188
  content += page_data["content"]
 
 
189
  if "ending" in page_data:
190
  content += f"<div style='text-align: center; margin-top: 20px; font-weight: bold; color: #c00;'>THE END</div>"
191
  content += f"<p style='font-style: italic;'>{page_data['ending']}</p>"
192
 
193
- return svg_content, content, gr.Dropdown(choices=["Restart"]), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), "Game Over - " + page_data['title']
194
 
195
- # Build page content
196
  content = f"<h2>{page_data['title']}</h2>"
197
  content += page_data["content"]
 
 
198
 
199
- # Build options
200
  options = []
201
- for opt in page_data["options"]:
202
- # Check if option requires an item
203
- if "requireItem" in opt and opt["requireItem"] not in game_state.inventory:
204
- continue
205
- # Check if option requires any item from a list
206
- if "requireAnyItem" in opt:
207
- has_required_item = False
208
- for item in opt["requireAnyItem"]:
209
- if item in game_state.inventory:
210
- has_required_item = True
211
- break
212
- if not has_required_item:
213
- continue
214
-
215
- options.append(opt["text"])
216
-
217
- # Handle alternative option if necessary
218
- if "alternativeOption" in page_data and not options:
219
- alt_opt = page_data["alternativeOption"]
220
- if "showIf" not in alt_opt or any(item in game_state.inventory for item in alt_opt["showIf"]):
221
- options.append(alt_opt["text"])
222
-
223
- # Update game progress
 
 
 
 
 
 
 
 
 
224
  story_path = f"You are on page {next_page}: {page_data['title']}"
225
  if game_state.journey_progress >= 80:
226
  story_path += " (Nearing the conclusion)"
@@ -228,29 +277,33 @@ def update_game(choice, game_state_json):
228
  story_path += " (Middle of your journey)"
229
  elif game_state.journey_progress >= 25:
230
  story_path += " (Adventure beginning)"
 
 
 
 
231
 
232
- return svg_content, content, gr.Dropdown(choices=options), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), story_path
233
 
234
  def generate_stats_display(game_state):
235
  """Generate HTML for displaying player stats"""
236
  # Calculate HP percentage for the progress bar
237
- hp_percent = (game_state.current_hp / game_state.max_hp) * 100
238
- hp_color = "#4CAF50" # Green
239
  if hp_percent < 30:
240
- hp_color = "#F44336" # Red
241
  elif hp_percent < 70:
242
- hp_color = "#FFC107" # Yellow
243
 
244
- stats = f"""
245
  <div style='display: flex; flex-wrap: wrap; gap: 10px; margin-top: 15px;'>
246
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
247
- <strong>Courage:</strong> {game_state.stats['courage']}
248
  </div>
249
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
250
- <strong>Wisdom:</strong> {game_state.stats['wisdom']}
251
  </div>
252
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
253
- <strong>Strength:</strong> {game_state.stats['strength']}
254
  </div>
255
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px; position: relative;'>
256
  <strong>HP:</strong> {game_state.current_hp}/{game_state.max_hp}
@@ -262,7 +315,7 @@ def generate_stats_display(game_state):
262
  """
263
 
264
  # Add journey progress
265
- stats += f"""
266
  <div style='margin-top: 10px;'>
267
  <div style='display: flex; justify-content: space-between; font-size: 12px;'>
268
  <span>Journey Progress:</span>
@@ -274,7 +327,7 @@ def generate_stats_display(game_state):
274
  </div>
275
  """
276
 
277
- return stats
278
 
279
  def generate_inventory_display(game_state):
280
  """Generate HTML for displaying player inventory"""
@@ -285,19 +338,21 @@ def generate_inventory_display(game_state):
285
 
286
  for item in game_state.inventory:
287
  item_data = items_data.get(item, {"type": "unknown", "description": "A mysterious item."})
288
- bg_color = "#e0e0e0"
289
- if item_data["type"] == "weapon":
290
- bg_color = "#ffcdd2"
291
- elif item_data["type"] == "armor":
292
- bg_color = "#c8e6c9"
293
- elif item_data["type"] == "spell":
294
- bg_color = "#bbdefb"
295
- elif item_data["type"] == "quest":
296
- bg_color = "#fff9c4"
 
 
297
 
298
  inventory_html += f"""
299
  <div style='background: {bg_color}; padding: 5px 10px; border-radius: 5px; position: relative;'
300
- title="{item_data['description']}">
301
  {item}
302
  </div>
303
  """
@@ -306,13 +361,14 @@ def generate_inventory_display(game_state):
306
  return inventory_html
307
 
308
  def perform_challenge(game_state, challenge):
309
- """Perform a skill challenge and determine success"""
310
  stat = challenge["stat"]
311
  difficulty = challenge["difficulty"]
312
 
313
  # Roll dice (1-6) and add stat
314
  roll = random.randint(1, 6)
315
- total = roll + game_state.stats[stat]
 
316
 
317
  # Determine if successful
318
  success = total >= difficulty
@@ -320,85 +376,100 @@ def perform_challenge(game_state, challenge):
320
  # Bonus for great success
321
  if success and total >= difficulty + 3:
322
  stat_increase = random.randint(1, 2)
323
- game_state.stats[stat] += stat_increase
324
 
325
  # Penalty for bad failure
326
  if not success and total <= difficulty - 3:
327
  stat_decrease = random.randint(1, 2)
328
- game_state.stats[stat] = max(1, game_state.stats[stat] - stat_decrease)
329
 
330
  # Record challenge outcome
331
  if success:
332
  game_state.challenges_won += 1
333
-
334
- return success
335
 
336
  def simulate_battle(game_state):
337
- """Simulate a battle with a random enemy"""
 
 
338
  # Select a random enemy type
339
  enemy_types = list(enemies_data.keys())
 
 
340
  enemy_type = random.choice(enemy_types)
341
- enemy = enemies_data[enemy_type]
 
 
342
 
343
  # Simple battle simulation
344
  player_hp = game_state.current_hp
345
  enemy_hp = enemy["hp"]
346
 
347
- player_attack = game_state.stats["strength"]
348
- player_defense = 1 # Base defense
349
 
350
- # Add weapon bonus if the player has a weapon
 
 
 
 
 
351
  for item in game_state.inventory:
352
  item_data = items_data.get(item, {})
353
  if "attackBonus" in item_data:
354
- player_attack += item_data["attackBonus"]
 
 
355
  if "defenseBonus" in item_data:
356
- player_defense += item_data["defenseBonus"]
357
-
 
 
 
 
 
358
  enemy_attack = enemy["attack"]
359
  enemy_defense = enemy["defense"]
360
 
 
 
361
  # Simple turn-based combat
362
- while player_hp > 0 and enemy_hp > 0:
 
 
363
  # Player attacks
364
- damage = max(1, player_attack - enemy_defense)
365
- enemy_hp -= damage
 
366
 
367
  if enemy_hp <= 0:
 
368
  break
369
 
370
  # Enemy attacks
371
- damage = max(1, enemy_attack - player_defense)
372
- player_hp -= damage
373
-
 
 
 
 
 
 
 
 
 
 
 
374
  # Update player HP after battle
375
  game_state.current_hp = player_hp
376
 
377
- # Return True if player won, False if player lost
378
- return player_hp > 0
 
 
379
 
380
- def restart_game(choice, game_state_json):
381
- """Restart the game when the player chooses to restart"""
382
- if choice == "Restart":
383
- return initialize_game()
384
- else:
385
- # Parse existing game state
386
- game_state_dict = json.loads(game_state_json)
387
- game_state = GameState.from_dict(game_state_dict)
388
-
389
- # Return current game state
390
- current_page = game_state.current_page
391
- page_data = game_data[str(current_page)]
392
-
393
- svg_content = create_svg_illustration(page_data["illustration"])
394
- content = f"<h2>{page_data['title']}</h2>"
395
- content += page_data["content"]
396
-
397
- options = []
398
- for opt in page_data["options"]:
399
- options.append(opt["text"])
400
-
401
- return svg_content, content, gr.Dropdown(choices=options), game_state.to_json(), generate_stats_display(game_state), generate_inventory_display(game_state), f"You are on page {current_page}: {page_data['title']}"
402
 
403
  # Create Gradio interface
404
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
@@ -412,19 +483,27 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
412
  choice = gr.Dropdown(label="What will you do?")
413
 
414
  # Hidden field to store game state as JSON
415
- game_state = gr.Textbox(visible=False)
416
 
417
  with gr.Column(scale=1):
418
  story_path = gr.Markdown(label="Your Path")
419
  stats = gr.HTML(label="Character Stats")
420
  inventory = gr.HTML(label="Inventory")
421
- map_btn = gr.Button("View Story Map")
422
-
423
- # Initialize or restart the game
424
- choice.change(update_game, [choice, game_state], [illustration, content, choice, game_state, stats, inventory, story_path])
425
-
426
- # Handle restart
427
- demo.load(initialize_game, [], [illustration, content, choice, game_state, stats, inventory, story_path])
 
 
 
 
 
 
 
 
428
 
429
  demo.launch()
430
  </gradio-file>
@@ -432,6 +511,8 @@ demo.launch()
432
  <gradio-file name="game_engine.py">
433
  import json
434
  import random
 
 
435
 
436
  class GameState:
437
  """Class to manage game state"""
@@ -445,13 +526,13 @@ class GameState:
445
  "courage": 7,
446
  "wisdom": 5,
447
  "strength": 6,
448
- "luck": 4
449
  }
450
  self.status_effects = []
451
  self.enemies_defeated = 0
452
  self.challenges_won = 0
453
  self.current_path = "main"
454
- self.journey_progress = 0
455
 
456
  def to_json(self):
457
  """Convert game state to JSON string"""
@@ -461,20 +542,26 @@ class GameState:
461
  def from_dict(cls, data):
462
  """Create a GameState object from a dictionary"""
463
  game_state = cls()
464
- for key, value in data.items():
465
- setattr(game_state, key, value)
 
 
 
 
 
 
 
466
  return game_state
467
 
468
  def create_svg_illustration(illustration_key):
469
  """Return SVG illustration based on the illustration key"""
470
  # If illustration is not found, use a default illustration
471
- if illustration_key not in illustrations:
472
- illustration_key = "default"
473
 
474
  # Wrap the SVG in a container div with appropriate styling
475
  return f"""
476
- <div style="width: 100%; height: 250px; border: 2px solid #999; border-radius: 5px; overflow: hidden;">
477
- {illustrations[illustration_key]}
478
  </div>
479
  """
480
  </gradio-file>
@@ -557,7 +644,7 @@ game_data = {
557
  "title": "Escape the Ambush",
558
  "description": "You need to either fight your way through or find an escape route.",
559
  "stat": "courage",
560
- "difficulty": 5,
561
  "success": 9,
562
  "failure": 10
563
  },
@@ -573,7 +660,7 @@ game_data = {
573
  "title": "The River Spirit's Riddle",
574
  "description": "Answer correctly to gain passage and a blessing. Answer wrongly and face the river's peril.",
575
  "stat": "wisdom",
576
- "difficulty": 6,
577
  "success": 11,
578
  "failure": 12
579
  },
@@ -589,7 +676,7 @@ game_data = {
589
  "title": "Navigate the Haunted Ruins",
590
  "description": "Find the correct path through the ruins while avoiding the malevolent spirits.",
591
  "stat": "wisdom",
592
- "difficulty": 5,
593
  "success": 13,
594
  "failure": 14
595
  },
@@ -687,10 +774,11 @@ game_data = {
687
  <p>As you survey the imposing structure, you consider your options for infiltration.</p>""",
688
  "options": [
689
  { "text": "Approach the main gate in disguise", "next": 21 },
690
- { "text": "Scale the outer wall under cover of darkness", "next": 22 },
691
- { "text": "Look for the secret tunnel (requires Secret Tunnel Map)", "next": 23, "requireItem": "Secret Tunnel Map" }
692
  ],
693
- "alternativeOption": { "text": "Search for another entrance", "next": 21, "showIf": ["Secret Tunnel Map"] },
 
694
  "illustration": "evil-fortress"
695
  },
696
 
@@ -701,10 +789,11 @@ game_data = {
701
  <p>You identify several possible entry points, each with its own risks and advantages.</p>""",
702
  "options": [
703
  { "text": "Infiltrate with an incoming supply wagon", "next": 21 },
704
- { "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"] },
705
- { "text": "Bribe a guard to let you in (requires special item)", "next": 23, "requireAnyItem": ["Ancient Amulet", "Poison Daggers", "Master Key"] }
706
  ],
707
- "alternativeOption": { "text": "Wait for another opportunity", "next": 21, "showIf": ["Healing Light Spell", "Shield of Faith Spell", "Binding Runes Scroll", "Water Spirit's Blessing", "Ancient Amulet", "Poison Daggers", "Master Key"] },
 
708
  "illustration": "fortress-observation"
709
  },
710
 
@@ -716,7 +805,7 @@ game_data = {
716
  "options": [
717
  { "text": "Enter the tunnel immediately", "next": 24 },
718
  { "text": "Hide and wait for the patrol to pass", "next": 21 },
719
- { "text": "Set a trap for the patrol", "next": 22 }
720
  ],
721
  "illustration": "hidden-tunnel"
722
  },
@@ -731,8 +820,8 @@ game_data = {
731
  "description": "Slip past the sleeping guards without alerting them.",
732
  "stat": "wisdom",
733
  "difficulty": 6,
734
- "success": 16,
735
- "failure": 10
736
  },
737
  "illustration": "night-escape"
738
  },
@@ -743,11 +832,11 @@ game_data = {
743
  <p>"My village was enslaved by the Evil Power Master. My family serves him under threat of death, but many of us secretly hope for his downfall."</p>
744
  <p>The guard offers to help you escape, but warns there will be a price for this betrayal if discovered.</p>""",
745
  "options": [
746
- { "text": "Accept the guard's help", "next": 16 },
747
- { "text": "Decline - you don't want to put them at risk", "next": 18 },
748
- { "text": "Convince them to join your cause", "next": 20 }
749
  ],
750
- "addItem": "Guard Disguise",
751
  "illustration": "guard-ally"
752
  },
753
 
@@ -757,10 +846,11 @@ game_data = {
757
  <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>
758
  <p>As the wagon approaches the massive gates, you begin to formulate a plan.</p>""",
759
  "options": [
760
- { "text": "Look for an opportunity to escape during transfer", "next": 21 },
761
- { "text": "Remain captive to get inside, then escape", "next": 22 },
762
- { "text": "Try to contact the internal resistance", "next": 23 }
763
  ],
 
764
  "illustration": "prison-wagon"
765
  },
766
 
@@ -774,7 +864,7 @@ game_data = {
774
  { "text": "Search for the dungeons", "next": 26 },
775
  { "text": "Follow a group of mages", "next": 27 }
776
  ],
777
- "randomBattle": true,
778
  "illustration": "fortress-interior"
779
  },
780
 
@@ -786,17 +876,17 @@ game_data = {
786
  "challenge": {
787
  "title": "Escape Detection",
788
  "description": "You need to escape from the approaching guards before they trap you.",
789
- "stat": "courage",
790
  "difficulty": 7,
791
- "success": 25,
792
- "failure": 28
793
  },
794
  "illustration": "fortress-alarm"
795
  },
796
 
797
  "23": {
798
  "title": "Secret Resistance",
799
- "content": """<p>Through luck or skill, 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>
800
  <p>"We've been working to undermine the Evil Power Master for years," their leader whispers. "Now with your help, we might finally have a chance to end his reign."</p>
801
  <p>They share crucial information about the fortress layout and the Evil Power Master's weaknesses.</p>""",
802
  "options": [
@@ -880,7 +970,7 @@ game_data = {
880
  <p>"This is our only chance," the resistance leader whispers. "We must reach him before he realizes what's happening."</p>""",
881
  "options": [
882
  { "text": "Lead the charge directly", "next": 47 },
883
- { "text": "Split up to create more distractions", "next": 48 },
884
  { "text": "Sneak ahead while the others draw attention", "next": 49 }
885
  ],
886
  "illustration": "fortress-uprising"
@@ -911,21 +1001,47 @@ game_data = {
911
  ],
912
  "illustration": "knowledge-sharing"
913
  },
914
-
915
- "48": {
916
- "title": "The Final Confrontation",
917
- "content": """<p>At last, you stand before the Evil Power Master in his chamber at the top of the central tower. Dark energy crackles around him as he turns to face you, his eyes gleaming with malice.</p>
918
- <p>"So, you're the one who's caused so much trouble," he says, his voice echoing with unnatural power. "I must admit, I'm impressed you made it this far. But your journey ends here."</p>
919
- <p>The dark crystal hovers behind him, pulsing with energy. This is the moment all your choices have led to.</p>""",
920
- "options": [
921
- { "text": "Attack the Evil Power Master directly", "next": 56 },
922
- { "text": "Try to destroy the crystal", "next": 57 },
923
- { "text": "Attempt to reason with him", "next": 58 }
924
- ],
925
- "illustration": "final-confrontation"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
  },
927
-
928
- "56": {
 
929
  "title": "Direct Confrontation",
930
  "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>
931
  <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>
@@ -933,15 +1049,14 @@ game_data = {
933
  "challenge": {
934
  "title": "Battle with the Evil Power Master",
935
  "description": "You must overcome his dark magic through sheer determination and skill.",
936
- "stat": "strength",
937
- "difficulty": 9,
938
- "success": 59,
939
- "failure": 60
940
  },
941
  "illustration": "power-battle"
942
  },
943
-
944
- "57": {
945
  "title": "Crystal Destruction",
946
  "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>
947
  <p>"No! Stay away from that, you fool! You have no idea what forces you're tampering with!"</p>
@@ -949,46 +1064,42 @@ game_data = {
949
  "challenge": {
950
  "title": "Breaking the Crystal Barrier",
951
  "description": "You must overcome the crystal's protective magic to reach and destroy it.",
952
- "stat": "wisdom",
953
- "difficulty": 8,
954
- "success": 61,
955
- "failure": 62
956
  },
957
  "illustration": "crystal-barrier"
958
  },
959
-
960
  "59": {
961
  "title": "A Hero's Victory",
962
  "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>
963
  <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>
964
  <p>The Evil Power Master screams as his power dissipates. "Impossible! I was so close to ultimate power!"</p>
965
  <p>As the light fades, you stand victorious. The land will heal from his corruption, and the people are free once more.</p>""",
966
- "gameOver": true,
967
  "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.",
968
  "illustration": "hero-victory"
969
  },
970
-
971
  "60": {
972
  "title": "Darkness Prevails",
973
  "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>
974
  <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>
975
  <p>As your vision fades, you see the crystal's glow intensifying. You've failed, and darkness will soon cover the land.</p>""",
976
- "gameOver": true,
977
  "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.",
978
  "illustration": "dark-victory"
979
  },
980
-
981
  "61": {
982
  "title": "The Crystal Shatters",
983
  "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>
984
  <p>The Evil Power Master shrieks in desperation, "Stop! You'll doom us all!"</p>
985
  <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>
986
  <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>""",
987
- "gameOver": true,
988
  "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.",
989
  "illustration": "crystal-destroyed"
990
  },
991
-
992
  "62": {
993
  "title": "A Desperate Gambit",
994
  "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>
@@ -996,41 +1107,63 @@ game_data = {
996
  <p>In a final, desperate move, you use all your remaining strength to hurl your most powerful weapon or spell at the crystal.</p>
997
  <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>
998
  <p>"What have you done?" he cries as the crystal's energy spirals out of control, engulfing both of you in blinding light.</p>""",
999
- "gameOver": true,
1000
  "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.",
1001
  "illustration": "heroic-sacrifice"
1002
  }
1003
  }
1004
 
1005
- # SVG templates for illustrations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1006
  illustrations = {
 
 
1007
  "city-gates": """
1008
  <svg viewBox="0 0 400 250" xmlns="http://www.w3.org/2000/svg">
1009
- <!-- Sky -->
1010
  <rect x="0" y="0" width="400" height="150" fill="#6b88a2" />
1011
-
1012
- <!-- Ground -->
1013
  <rect x="0" y="150" width="400" height="100" fill="#7d6c54" />
1014
-
1015
- <!-- City walls -->
1016
  <rect x="50" y="50" width="300" height="100" fill="#c9c0a8" />
1017
-
1018
- <!-- Gate -->
1019
  <rect x="150" y="80" width="100" height="120" fill="#5a3c28" />
1020
  <rect x="195" y="80" width="10" height="120" fill="#3a281e" />
1021
-
1022
- <!-- Towers -->
1023
  <rect x="50" y="30" width="40" height="120" fill="#d6cdb7" />
1024
  <rect x="310" y="30" width="40" height="120" fill="#d6cdb7" />
1025
-
1026
- <!-- Tower tops -->
1027
  <polygon points="50,30 70,10 90,30" fill="#bf9969" />
1028
  <polygon points="310,30 330,10 350,30" fill="#bf9969" />
1029
-
1030
- <!-- Path -->
1031
- <path d="M150,250 L250,250 L250,200 L150,200 Z" fill="#a89783" />
1032
-
1033
- <!-- Flags -->
1034
  <rect x="70" y="10" width="2" height="20" fill="#222" />
1035
  <rect x="72" y="10" width="10" height="5" fill="#bf2121" />
1036
  <rect x="330" y="10" width="2" height="20" fill="#222" />
@@ -1039,43 +1172,77 @@ illustrations = {
1039
  """,
1040
  "weaponsmith": """
1041
  <svg viewBox="0 0 400 250" xmlns="http://www.w3.org/2000/svg">
1042
- <!-- Background -->
1043
  <rect x="0" y="0" width="400" height="250" fill="#3d3123" />
1044
-
1045
- <!-- Forge -->
1046
  <rect x="250" y="100" width="100" height="70" fill="#8c5b3f" />
1047
  <rect x="270" y="120" width="60" height="30" fill="#e63d16" />
1048
  <ellipse cx="300" cy="135" rx="25" ry="10" fill="#f7d046" />
1049
-
1050
- <!-- Anvil -->
1051
  <rect x="180" y="160" width="40" height="20" fill="#888888" />
1052
  <rect x="190" y="150" width="20" height="10" fill="#999999" />
1053
  <rect x="195" y="140" width="10" height="10" fill="#aaaaaa" />
1054
-
1055
- <!-- Weapons on wall -->
1056
  <line x1="50" y1="60" x2="90" y2="70" stroke="#c0c0c0" stroke-width="4" />
1057
  <line x1="120" y1="50" x2="150" y2="70" stroke="#c0c0c0" stroke-width="3" />
1058
  <rect x="60" y="90" width="30" height="80" fill="#814d2b" />
1059
  <rect x="65" y="90" width="20" height="5" fill="#c0c0c0" />
1060
  <rect x="140" y="90" width="5" height="100" fill="#814d2b" />
1061
  <ellipse cx="142.5" cy="90" rx="15" ry="3" fill="#c0c0c0" />
1062
-
1063
- <!-- Table -->
1064
  <rect x="30" y="180" width="120" height="10" fill="#8c5b3f" />
1065
-
1066
- <!-- Weaponsmith -->
1067
  <circle cx="220" cy="120" r="25" fill="#d8a77a" />
1068
  <rect x="210" y="145" width="20" height="35" fill="#603813" />
1069
  <rect x="205" y="110" width="30" height="20" fill="#d8a77a" />
1070
  <circle cx="213" cy="115" r="3" fill="#000" />
1071
  <circle cx="227" cy="115" r="3" fill="#000" />
1072
  <path d="M215,125 Q220,130 225,125" fill="none" stroke="#000" stroke-width="1" />
1073
-
1074
- <!-- Special weapons -->
1075
  <rect x="60" y="60" width="10" height="30" fill="#603813" />
1076
  <rect x="62" y="30" width="6" height="30" fill="#ff5722" opacity="0.7">
1077
  <animate attributeName="opacity" values="0.7;1;0.7" dur="2s" repeatCount="indefinite" />
1078
  </rect>
1079
-
1080
  <rect x="130" y="50" width="6" height="40" fill="#603813" />
1081
- <ellipse cx="133" cy="50"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  import gradio as gr
52
  import random
53
  import json
54
+ # Make sure to import everything needed from the other files
55
  from game_data import game_data, illustrations, enemies_data, items_data
56
  from game_engine import GameState, create_svg_illustration
57
 
 
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."
 
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
 
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")
121
+ if item_required and item_required not in game_state.inventory:
122
+ content = f"<h2>{page_data['title']}</h2>" + page_data["content"] # Show original content
123
+ content += f"<p style='color: red;'>You need the <strong>{item_required}</strong> for this option, but you don't have it.</p>"
124
+ options = [opt["text"] for opt in page_data["options"]] # Keep current options
125
+ svg_content = create_svg_illustration(page_data["illustration"])
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")
133
+ if any_item_required:
134
+ has_required = any(item in game_state.inventory for item in any_item_required)
135
+ if not has_required:
136
+ item_list = ", ".join(f"'{item}'" for item in any_item_required)
137
+ content = f"<h2>{page_data['title']}</h2>" + page_data["content"] # Show original content
138
+ content += f"<p style='color: red;'>You need one of the following items for this option: <strong>{item_list}</strong>, but you don't have any.</p>"
139
+ options = [opt["text"] for opt in page_data["options"]] # Keep current options
140
+ svg_content = create_svg_illustration(page_data["illustration"])
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:
189
+ game_state.current_hp -= hp_loss
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)"
 
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"""
289
  # Calculate HP percentage for the progress bar
290
+ hp_percent = (game_state.current_hp / game_state.max_hp) * 100 if game_state.max_hp > 0 else 0
291
+ hp_color = "#4CAF50" # Green
292
  if hp_percent < 30:
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;'>
300
+ <strong>Courage:</strong> {game_state.stats.get('courage', 'N/A')}
301
  </div>
302
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
303
+ <strong>Wisdom:</strong> {game_state.stats.get('wisdom', 'N/A')}
304
  </div>
305
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
306
+ <strong>Strength:</strong> {game_state.stats.get('strength', 'N/A')}
307
  </div>
308
  <div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px; position: relative;'>
309
  <strong>HP:</strong> {game_state.current_hp}/{game_state.max_hp}
 
315
  """
316
 
317
  # Add journey progress
318
+ stats_html += f"""
319
  <div style='margin-top: 10px;'>
320
  <div style='display: flex; justify-content: space-between; font-size: 12px;'>
321
  <span>Journey Progress:</span>
 
327
  </div>
328
  """
329
 
330
+ return stats_html
331
 
332
  def generate_inventory_display(game_state):
333
  """Generate HTML for displaying player inventory"""
 
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
346
+ elif item_type == "armor":
347
+ bg_color = "#c8e6c9" # Light green
348
+ elif item_type == "spell":
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
  """
 
361
  return inventory_html
362
 
363
  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
 
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 = []
418
  for item in game_state.inventory:
419
  item_data = items_data.get(item, {})
420
  if "attackBonus" in item_data:
421
+ bonus = item_data["attackBonus"]
422
+ player_attack += bonus
423
+ attack_bonuses.append(f"{item} (+{bonus})")
424
  if "defenseBonus" in item_data:
425
+ bonus = item_data["defenseBonus"]
426
+ player_defense += bonus
427
+ defense_bonuses.append(f"{item} (+{bonus})")
428
+
429
+ if attack_bonuses: battle_log += f"<p>Your attack ({player_base_attack}) is boosted by: {', '.join(attack_bonuses)} = {player_attack} total.</p>"
430
+ if defense_bonuses: battle_log += f"<p>Your defense ({player_base_defense}) is boosted by: {', '.join(defense_bonuses)} = {player_defense} total.</p>"
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
440
+ battle_log += f"<p><strong>Turn {turn}:</strong></p>"
441
  # Player attacks
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
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
  # Create Gradio interface
475
  with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
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>
 
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"""
 
526
  "courage": 7,
527
  "wisdom": 5,
528
  "strength": 6,
529
+ "luck": 4 # Luck isn't used currently, but kept it
530
  }
531
  self.status_effects = []
532
  self.enemies_defeated = 0
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"""
 
542
  def from_dict(cls, data):
543
  """Create a GameState object from a dictionary"""
544
  game_state = cls()
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>
 
644
  "title": "Escape the Ambush",
645
  "description": "You need to either fight your way through or find an escape route.",
646
  "stat": "courage",
647
+ "difficulty": 5, # Adjusted difficulty for demo
648
  "success": 9,
649
  "failure": 10
650
  },
 
660
  "title": "The River Spirit's Riddle",
661
  "description": "Answer correctly to gain passage and a blessing. Answer wrongly and face the river's peril.",
662
  "stat": "wisdom",
663
+ "difficulty": 6, # Adjusted difficulty for demo
664
  "success": 11,
665
  "failure": 12
666
  },
 
676
  "title": "Navigate the Haunted Ruins",
677
  "description": "Find the correct path through the ruins while avoiding the malevolent spirits.",
678
  "stat": "wisdom",
679
+ "difficulty": 5, # Adjusted difficulty for demo
680
  "success": 13,
681
  "failure": 14
682
  },
 
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
 
 
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
 
 
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
  },
 
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
  },
 
832
  <p>"My village was enslaved by the Evil Power Master. My family serves him under threat of death, but many of us secretly hope for his downfall."</p>
833
  <p>The guard offers to help you escape, but warns there will be a price for this betrayal if discovered.</p>""",
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
 
 
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
 
 
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
 
 
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>
890
  <p>"We've been working to undermine the Evil Power Master for years," their leader whispers. "Now with your help, we might finally have a chance to end his reign."</p>
891
  <p>They share crucial information about the fortress layout and the Evil Power Master's weaknesses.</p>""",
892
  "options": [
 
970
  <p>"This is our only chance," the resistance leader whispers. "We must reach him before he realizes what's happening."</p>""",
971
  "options": [
972
  { "text": "Lead the charge directly", "next": 47 },
973
+ { "text": "Split up to create more distractions", "next": 48 }, # Send player to final battle
974
  { "text": "Sneak ahead while the others draw attention", "next": 49 }
975
  ],
976
  "illustration": "fortress-uprising"
 
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>
 
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
1056
  },
1057
  "illustration": "power-battle"
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>
 
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>
 
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 ---
1135
+ 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" />
 
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"),
1205
+ "resistance-meeting": create_placeholder_svg("Resistance Meeting", "#efebe9"),
1206
+ "shadowwood-forest": create_placeholder_svg("Shadowwood Forest", "#38502e"),
1207
+ "road-ambush": create_placeholder_svg("Road Ambush", "#795548"),
1208
+ "river-spirit": create_placeholder_svg("River Spirit", "#b3e5fc"),
1209
+ "ancient-ruins": create_placeholder_svg("Ancient Ruins", "#bdbdbd"),
1210
+ "forest-edge": create_placeholder_svg("Forest Edge", "#a5d6a7"),
1211
+ "prisoner-cell": create_placeholder_svg("Prisoner Cell", "#616161"),
1212
+ "spirit-blessing": create_placeholder_svg("Spirit Blessing", "#e1f5fe"),
1213
+ "river-danger": create_placeholder_svg("River Danger", "#42a5f5"),
1214
+ "ancient-spirits": create_placeholder_svg("Ancient Spirits", "#e0e0e0"),
1215
+ "lost-ruins": create_placeholder_svg("Lost in Ruins", "#9e9e9e"),
1216
+ "evil-fortress": create_placeholder_svg("Evil Fortress", "#212121"),
1217
+ "fortress-observation": create_placeholder_svg("Observing Fortress", "#424242"),
1218
+ "hidden-tunnel": create_placeholder_svg("Hidden Tunnel", "#757575"),
1219
+ "night-escape": create_placeholder_svg("Night Escape", "#37474f"),
1220
+ "guard-ally": create_placeholder_svg("Guard Ally", "#bcaaa4"),
1221
+ "prison-wagon": create_placeholder_svg("Prison Wagon", "#8d6e63"),
1222
+ "fortress-interior": create_placeholder_svg("Fortress Interior", "#616161"),
1223
+ "fortress-alarm": create_placeholder_svg("Fortress Alarm", "#f44336"),
1224
+ "secret-meeting": create_placeholder_svg("Secret Meeting", "#4e342e"),
1225
+ "underground-passage": create_placeholder_svg("Underground Passage", "#6d4c41"),
1226
+ "central-tower": create_placeholder_svg("Central Tower", "#3e2723"),
1227
+ "dungeon-corridors": create_placeholder_svg("Dungeon Corridors", "#5d4037"),
1228
+ "ritual-chamber": create_placeholder_svg("Ritual Chamber", "#4a148c"),
1229
+ "prisoner-escort": create_placeholder_svg("Prisoner Escort", "#757575"),
1230
+ "fortress-uprising": create_placeholder_svg("Fortress Uprising", "#ff7043"),
1231
+ "secret-passage": create_placeholder_svg("Secret Passage", "#a1887f"),
1232
+ "knowledge-sharing": create_placeholder_svg("Knowledge Sharing", "#fff176"),
1233
+ "final-confrontation": create_placeholder_svg("Final Confrontation", "#d32f2f"),
1234
+ "power-battle": create_placeholder_svg("Power Battle", "#ef5350"),
1235
+ "crystal-barrier": create_placeholder_svg("Crystal Barrier", "#ab47bc"),
1236
+ "hero-victory": create_placeholder_svg("Hero's Victory", "#ffee58"),
1237
+ "dark-victory": create_placeholder_svg("Darkness Prevails", "#263238"),
1238
+ "crystal-destroyed": create_placeholder_svg("Crystal Destroyed", "#81d4fa"),
1239
+ "heroic-sacrifice": create_placeholder_svg("Heroic Sacrifice", "#ffcc80"),
1240
+
1241
+ }
1242
+
1243
+ </gradio-file>
1244
+
1245
+ </gradio-lite>
1246
+ </div>
1247
+ </body>
1248
+ </html>