Spaces:
Running
Running
Update index.html
Browse files- 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 =
|
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 |
-
|
|
|
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 |
-
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
# Check if this option requires an item
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
|
127 |
# Process special items to collect
|
128 |
-
|
129 |
-
|
|
|
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
|
|
|
135 |
|
136 |
# Update journey progress based on page transitions
|
137 |
-
game_state.journey_progress += 5
|
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 |
-
|
143 |
-
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
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 |
-
|
157 |
-
|
158 |
-
|
|
|
159 |
game_state.stats[stat] += amount
|
160 |
|
161 |
# Process HP loss
|
162 |
-
|
163 |
-
|
|
|
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 |
-
|
171 |
-
|
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
|
182 |
-
|
183 |
-
|
|
|
|
|
|
|
184 |
|
185 |
-
# Handle game over
|
186 |
-
if "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 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
if item in game_state.inventory:
|
210 |
-
|
211 |
-
|
212 |
-
if
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
if
|
221 |
-
options.append(
|
222 |
-
|
223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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(),
|
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"
|
239 |
if hp_percent < 30:
|
240 |
-
hp_color = "#F44336"
|
241 |
elif hp_percent < 70:
|
242 |
-
hp_color = "#FFC107"
|
243 |
|
244 |
-
|
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
|
248 |
</div>
|
249 |
<div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
|
250 |
-
<strong>Wisdom:</strong> {game_state.stats
|
251 |
</div>
|
252 |
<div style='background: #f0f0f0; padding: 5px 10px; border-radius: 5px;'>
|
253 |
-
<strong>Strength:</strong> {game_state.stats
|
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 |
-
|
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
|
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 |
-
|
290 |
-
|
291 |
-
|
292 |
-
bg_color = "#
|
293 |
-
elif
|
294 |
-
bg_color = "#
|
295 |
-
elif
|
296 |
-
bg_color = "#
|
|
|
|
|
297 |
|
298 |
inventory_html += f"""
|
299 |
<div style='background: {bg_color}; padding: 5px 10px; border-radius: 5px; position: relative;'
|
300 |
-
title="{item_data
|
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 |
-
|
|
|
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]
|
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,
|
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 |
-
|
348 |
-
|
349 |
|
350 |
-
|
|
|
|
|
|
|
|
|
|
|
351 |
for item in game_state.inventory:
|
352 |
item_data = items_data.get(item, {})
|
353 |
if "attackBonus" in item_data:
|
354 |
-
|
|
|
|
|
355 |
if "defenseBonus" in item_data:
|
356 |
-
|
357 |
-
|
|
|
|
|
|
|
|
|
|
|
358 |
enemy_attack = enemy["attack"]
|
359 |
enemy_defense = enemy["defense"]
|
360 |
|
|
|
|
|
361 |
# Simple turn-based combat
|
362 |
-
|
|
|
|
|
363 |
# Player attacks
|
364 |
-
|
365 |
-
enemy_hp -=
|
|
|
366 |
|
367 |
if enemy_hp <= 0:
|
|
|
368 |
break
|
369 |
|
370 |
# Enemy attacks
|
371 |
-
|
372 |
-
player_hp -=
|
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 |
-
|
|
|
|
|
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 |
-
#
|
424 |
-
choice.change(
|
425 |
-
|
426 |
-
|
427 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
465 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
{
|
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":
|
692 |
],
|
693 |
-
|
|
|
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 |
-
|
|
|
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 |
-
|
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":
|
761 |
-
{ "text": "Remain captive to get inside, then escape", "next":
|
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":
|
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 |
-
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
|
924 |
-
|
925 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
926 |
},
|
927 |
-
|
928 |
-
|
|
|
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":
|
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":
|
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":
|
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":
|
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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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" 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" 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>
|