Spaces:
Running
Running
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,847 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import random
|
3 |
+
import json
|
4 |
+
import base64
|
5 |
+
import io
|
6 |
+
import os
|
7 |
+
from PIL import Image, ImageDraw, ImageFont
|
8 |
+
from google import genai
|
9 |
+
from google.genai import types
|
10 |
+
|
11 |
+
API_KEY = os.environ.get("GOOGLE_API_KEY")
|
12 |
+
client = genai.Client(api_key=API_KEY)
|
13 |
+
|
14 |
+
class AIAdventureGame:
|
15 |
+
def __init__(self):
|
16 |
+
"""初始化遊戲系統"""
|
17 |
+
self.player = None
|
18 |
+
self.backpack = []
|
19 |
+
self.game_history = []
|
20 |
+
self.current_scene = ""
|
21 |
+
self.current_riddle = None # 新增:當前謎語
|
22 |
+
self.current_answer = None # 新增:當前謎語答案
|
23 |
+
|
24 |
+
def create_character(self, name):
|
25 |
+
"""創建玩家角色"""
|
26 |
+
if not name.strip():
|
27 |
+
name = "冒險者"
|
28 |
+
|
29 |
+
self.player = {
|
30 |
+
"name": name,
|
31 |
+
"HP": 100,
|
32 |
+
"max_HP": 100,
|
33 |
+
"Attack": 15,
|
34 |
+
"Defense": 10,
|
35 |
+
"Gold": 50,
|
36 |
+
"level": 1,
|
37 |
+
"exp": 0
|
38 |
+
}
|
39 |
+
self.backpack = ["治療藥水"]
|
40 |
+
self.game_history = [f"歡迎,勇敢的{name}!你的冒險即將開始!"]
|
41 |
+
return self.get_status_text(), self.get_history_text()
|
42 |
+
|
43 |
+
def get_status_text(self):
|
44 |
+
"""獲取玩家狀態文字"""
|
45 |
+
if not self.player:
|
46 |
+
return "請先創建角色"
|
47 |
+
|
48 |
+
status = f"""
|
49 |
+
【角色狀態】
|
50 |
+
姓名: {self.player['name']} (等級 {self.player['level']})
|
51 |
+
生命值: {self.player['HP']}/{self.player['max_HP']}
|
52 |
+
攻擊力: {self.player['Attack']}
|
53 |
+
防禦力: {self.player['Defense']}
|
54 |
+
金幣: {self.player['Gold']}
|
55 |
+
經驗值: {self.player['exp']}/100
|
56 |
+
|
57 |
+
【背包物品】
|
58 |
+
{', '.join(self.backpack) if self.backpack else '空的'}
|
59 |
+
"""
|
60 |
+
return status.strip()
|
61 |
+
|
62 |
+
def get_history_text(self):
|
63 |
+
"""獲取遊戲歷史文字"""
|
64 |
+
return "\n".join(self.game_history[-10:]) # 只顯示最近 10 筆記錄
|
65 |
+
|
66 |
+
def generate_ai_scene(self, scene_type="explore"):
|
67 |
+
"""使用AI生成場景描述"""
|
68 |
+
try:
|
69 |
+
prompts = {
|
70 |
+
"explore": f"你是一個RPG遊戲的敘述者。{self.player['name']}正在進行冒險探索。請生成一個有趣的探索場景描述(50-100字),包含環境、氛圍和可能的發現。場景應該適合等級{self.player['level']}的冒險者。",
|
71 |
+
"battle": f"生成一個戰鬥場景的開場描述(30-50字),{self.player['name']}遭遇了危險。",
|
72 |
+
"treasure": f"生成一個發現寶藏的場景描述(30-50字),{self.player['name']}找到了好東西。",
|
73 |
+
"trap": f"生成一個陷阱場景描述(30-50字),{self.player['name']}遇到了危險。"
|
74 |
+
}
|
75 |
+
|
76 |
+
response = client.models.generate_content(
|
77 |
+
model="gemini-2.0-flash-001",
|
78 |
+
contents=prompts.get(scene_type, prompts["explore"]),
|
79 |
+
config=types.GenerateContentConfig(
|
80 |
+
safety_settings=[
|
81 |
+
types.SafetySetting(
|
82 |
+
category=types.HarmCategory.HARM_CATEGORY_HATE_SPEECH,
|
83 |
+
threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
|
84 |
+
),
|
85 |
+
types.SafetySetting(
|
86 |
+
category=types.HarmCategory.HARM_CATEGORY_HARASSMENT,
|
87 |
+
threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
|
88 |
+
),
|
89 |
+
types.SafetySetting(
|
90 |
+
category=types.HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT,
|
91 |
+
threshold=types.HarmBlockThreshold.BLOCK_LOW_AND_ABOVE,
|
92 |
+
),
|
93 |
+
]
|
94 |
+
)
|
95 |
+
)
|
96 |
+
return response.text.strip()
|
97 |
+
except Exception as e:
|
98 |
+
# 如果AI生成失敗,使用預設文字
|
99 |
+
default_scenes = {
|
100 |
+
"explore": "你走在一條蜿蜒的小徑上,陽光透過樹葉灑下斑駁的光影。",
|
101 |
+
"battle": "突然,一個敵人從陰影中跳出來!",
|
102 |
+
"treasure": "你在角落發現了一個閃閃發光的寶箱!",
|
103 |
+
"trap": "糟糕!你踩到了一個隱藏的陷阱!"
|
104 |
+
}
|
105 |
+
return default_scenes.get(scene_type, default_scenes["explore"])
|
106 |
+
|
107 |
+
def generate_ai_monster(self):
|
108 |
+
"""使用AI生成怪物"""
|
109 |
+
try:
|
110 |
+
level = self.player['level']
|
111 |
+
prompt = f"""生成一個適合等級{level}冒險者的RPG怪物,以JSON格式回應:
|
112 |
+
{{
|
113 |
+
"name": "怪物名稱",
|
114 |
+
"description": "30字以內的描述",
|
115 |
+
"HP": 基礎生命值,
|
116 |
+
"Attack": 攻擊力,
|
117 |
+
"Defense": 防禦力,
|
118 |
+
"weakness": "弱點或特殊說明"
|
119 |
+
}}
|
120 |
+
|
121 |
+
怪物強度應該與玩家等級匹配。"""
|
122 |
+
|
123 |
+
response = client.models.generate_content(
|
124 |
+
model="gemini-2.0-flash-001",
|
125 |
+
contents=prompt
|
126 |
+
)
|
127 |
+
monster_data = json.loads(response.text.strip())
|
128 |
+
|
129 |
+
# 根據等級調整數值
|
130 |
+
level_multiplier = 1 + (level - 1) * 0.3
|
131 |
+
monster_data["HP"] = int(monster_data["HP"] * level_multiplier)
|
132 |
+
monster_data["Attack"] = int(monster_data["Attack"] * level_multiplier)
|
133 |
+
monster_data["Defense"] = int(monster_data["Defense"] * level_multiplier)
|
134 |
+
|
135 |
+
return monster_data
|
136 |
+
except Exception as e:
|
137 |
+
# 預設怪物
|
138 |
+
monsters = [
|
139 |
+
{"name": "哥布林", "description": "綠色的小惡魔", "HP": 40, "Attack": 8, "Defense": 3, "weakness": "害怕光亮"},
|
140 |
+
{"name": "骷髏戰士", "description": "不死的戰士", "HP": 60, "Attack": 12, "Defense": 8, "weakness": "神聖攻擊有效"},
|
141 |
+
{"name": "野狼", "description": "兇猛的野獸", "HP": 45, "Attack": 15, "Defense": 5, "weakness": "火焰傷害加倍"}
|
142 |
+
]
|
143 |
+
return random.choice(monsters)
|
144 |
+
|
145 |
+
def generate_scene_image(self, scene_description):
|
146 |
+
"""使用Gemini 2.0 Flash生成場景插畫"""
|
147 |
+
|
148 |
+
try:
|
149 |
+
# 根據場景描述構建詳細的圖像生成提示詞
|
150 |
+
scene_keywords = self.extract_scene_keywords(scene_description)
|
151 |
+
|
152 |
+
image_prompt = f"""
|
153 |
+
請生成一幅精美的奇幻角色扮演遊戲插畫,場景描述:{scene_description}
|
154 |
+
|
155 |
+
畫風與規格:
|
156 |
+
- 奇幻冒險遊戲美術風格
|
157 |
+
- 色彩鮮豔、充滿魔幻氛圍
|
158 |
+
- 動漫/漫畫啟發的插畫手法
|
159 |
+
- 專業級數位繪畫品質
|
160 |
+
- 電影級光影與構圖
|
161 |
+
- 16:9 橫向畫面比例
|
162 |
+
- 具備景深與大氣透視效果
|
163 |
+
|
164 |
+
場景元素(**必須包含**):
|
165 |
+
{scene_keywords}
|
166 |
+
|
167 |
+
情緒設定:充滿冒險與魔法感
|
168 |
+
品質要求:專業遊戲美術插畫
|
169 |
+
|
170 |
+
**嚴格禁止出現**:
|
171 |
+
- 任何文字、字母、單詞、字型
|
172 |
+
- 任何符號、標誌、圖示
|
173 |
+
- 浮水印、簽名、商標、Logo
|
174 |
+
- 標題、橫幅、介面元素及任何文字相關內容
|
175 |
+
"""
|
176 |
+
|
177 |
+
# 使用 Gemini 2.0 Flash 生成圖像
|
178 |
+
print("🎨 正在生成AI插畫...")
|
179 |
+
|
180 |
+
response = client.models.generate_content(
|
181 |
+
model="gemini-2.0-flash-preview-image-generation",
|
182 |
+
contents=image_prompt,
|
183 |
+
config=types.GenerateContentConfig(
|
184 |
+
response_modalities=['TEXT', 'IMAGE']
|
185 |
+
)
|
186 |
+
)
|
187 |
+
# 處理生成的圖像
|
188 |
+
if self.extract_image_from_response(response):
|
189 |
+
print("✅ AI插畫生成成功!")
|
190 |
+
return self.extract_image_from_response(response)
|
191 |
+
else:
|
192 |
+
print("⚠️ 無法從AI回應中提取圖像,使用備用圖像")
|
193 |
+
return self.create_fallback_image(scene_description)
|
194 |
+
|
195 |
+
except Exception as e:
|
196 |
+
print(f"🚫 AI圖像生成失敗: {str(e)}")
|
197 |
+
print("📝 使用手繪風格備用圖像")
|
198 |
+
return self.create_fallback_image(scene_description)
|
199 |
+
|
200 |
+
def extract_scene_keywords(self, scene_description):
|
201 |
+
"""從場景描述中提取關鍵詞來優化圖像生成"""
|
202 |
+
keywords = []
|
203 |
+
|
204 |
+
prompt = f"""你是一位一名遊戲美術設計助理。請根據以下的場景描述,提取所有關鍵的視覺與氛圍特徵,讓它們既具體又豐富,符合奇幻冒險遊戲插畫的需求。
|
205 |
+
- 請至少列出 5~8 個要素,每個要素都要簡潔地點出畫面中可被強化或突出的細節。
|
206 |
+
- 請用「- 要素: 描述」的格式輸出,不要有其他多餘文字或說明。
|
207 |
+
|
208 |
+
場景描述:
|
209 |
+
{scene_description}"""
|
210 |
+
|
211 |
+
response = client.models.generate_content(
|
212 |
+
model="gemini-2.0-flash-001",
|
213 |
+
contents=prompt
|
214 |
+
)
|
215 |
+
|
216 |
+
keywords = response.text
|
217 |
+
|
218 |
+
return keywords
|
219 |
+
|
220 |
+
def extract_image_from_response(self, response):
|
221 |
+
"""從Gemini回應中提取圖像"""
|
222 |
+
try:
|
223 |
+
|
224 |
+
# 檢查回應是否包含圖像數據
|
225 |
+
if hasattr(response, 'parts') and response.parts:
|
226 |
+
for part in response.parts:
|
227 |
+
if hasattr(part, 'inline_data') and part.inline_data:
|
228 |
+
# 解碼base64圖像數據
|
229 |
+
if hasattr(part.inline_data, 'data'):
|
230 |
+
image_data = part.inline_data.data
|
231 |
+
img = Image.open(io.BytesIO(image_data))
|
232 |
+
|
233 |
+
# 調整圖像大小以適合界面
|
234 |
+
img = img.resize((512, 384), Image.Resampling.LANCZOS)
|
235 |
+
return img
|
236 |
+
|
237 |
+
# 嘗試其他可能的圖像數據格式
|
238 |
+
if hasattr(response, 'candidates') and response.candidates:
|
239 |
+
for candidate in response.candidates:
|
240 |
+
if hasattr(candidate, 'content') and candidate.content:
|
241 |
+
for part in candidate.content.parts:
|
242 |
+
if hasattr(part, 'inline_data') and part.inline_data:
|
243 |
+
image_data = part.inline_data.data
|
244 |
+
img = Image.open(io.BytesIO(image_data))
|
245 |
+
img = img.resize((512, 384), Image.Resampling.LANCZOS)
|
246 |
+
return img
|
247 |
+
|
248 |
+
return None
|
249 |
+
|
250 |
+
except Exception as e:
|
251 |
+
print(f"圖像提取錯誤: {str(e)}")
|
252 |
+
return None
|
253 |
+
|
254 |
+
def create_fallback_image(self, scene_description):
|
255 |
+
"""創建備用場景圖片(當AI圖像生成失敗時使用)"""
|
256 |
+
try:
|
257 |
+
# 創建一個更精美的手繪風格場景圖片
|
258 |
+
img = Image.new('RGB', (512, 384), color=(135, 206, 250)) # 天空藍背景
|
259 |
+
draw = ImageDraw.Draw(img)
|
260 |
+
|
261 |
+
# 根據場景描述選擇不同的繪製風格
|
262 |
+
if any(word in scene_description for word in ["森林", "樹", "叢林", "樹木"]):
|
263 |
+
# 森林場景
|
264 |
+
self.draw_forest_scene(draw, img.size)
|
265 |
+
elif any(word in scene_description for word in ["山", "岩石", "峭壁", "高原"]):
|
266 |
+
# 山地場景
|
267 |
+
self.draw_mountain_scene(draw, img.size)
|
268 |
+
elif any(word in scene_description for word in ["洞穴", "地下", "洞窟", "岩洞"]):
|
269 |
+
# 洞穴場景
|
270 |
+
self.draw_cave_scene(draw, img.size)
|
271 |
+
elif any(word in scene_description for word in ["城堡", "建築", "廢墟", "遺跡"]):
|
272 |
+
# 建築場景
|
273 |
+
self.draw_castle_scene(draw, img.size)
|
274 |
+
else:
|
275 |
+
# 默認草原場景
|
276 |
+
self.draw_default_scene(draw, img.size)
|
277 |
+
|
278 |
+
# 添加裝飾性文字
|
279 |
+
try:
|
280 |
+
font = ImageFont.load_default()
|
281 |
+
# 在圖片底部添加半透明背景
|
282 |
+
draw.rectangle([0, 340, 512, 384], fill=(0, 0, 0, 128))
|
283 |
+
draw.text((10, 350), f"🎨 冒險場景: {scene_description[:30]}...",
|
284 |
+
fill=(255, 255, 255), font=font)
|
285 |
+
except:
|
286 |
+
pass
|
287 |
+
|
288 |
+
return img
|
289 |
+
except Exception as e:
|
290 |
+
# 最終備用方案:純色背景
|
291 |
+
img = Image.new('RGB', (512, 384), color=(100, 149, 237))
|
292 |
+
draw = ImageDraw.Draw(img)
|
293 |
+
draw.text((10, 10), "場景生成中...", fill=(255, 255, 255))
|
294 |
+
return img
|
295 |
+
|
296 |
+
def draw_forest_scene(self, draw, size):
|
297 |
+
"""繪製森林場景"""
|
298 |
+
width, height = size
|
299 |
+
|
300 |
+
# 繪製多層樹木
|
301 |
+
tree_positions = [(80, 250), (200, 230), (320, 270), (450, 240)]
|
302 |
+
tree_colors = [(34, 139, 34), (0, 128, 0), (46, 125, 50), (27, 94, 32)]
|
303 |
+
|
304 |
+
for i, (x, y) in enumerate(tree_positions):
|
305 |
+
color = tree_colors[i % len(tree_colors)]
|
306 |
+
# 樹冠
|
307 |
+
draw.ellipse([x-30, y-50, x+30, y], fill=color)
|
308 |
+
# 樹幹
|
309 |
+
draw.rectangle([x-8, y, x+8, y+50], fill=(101, 67, 33))
|
310 |
+
|
311 |
+
# 繪製草地
|
312 |
+
draw.rectangle([0, height-80, width, height], fill=(76, 175, 80))
|
313 |
+
|
314 |
+
# 添加小草效果
|
315 |
+
for i in range(20):
|
316 |
+
x = random.randint(0, width)
|
317 |
+
y = random.randint(height-50, height-10)
|
318 |
+
draw.line([(x, y), (x+2, y-8)], fill=(27, 94, 32), width=2)
|
319 |
+
|
320 |
+
def draw_mountain_scene(self, draw, size):
|
321 |
+
"""繪製山地場景"""
|
322 |
+
width, height = size
|
323 |
+
|
324 |
+
# 繪製遠山
|
325 |
+
draw.polygon([(0, height//2), (width//4, height//4), (width//2, height//3),
|
326 |
+
(3*width//4, height//5), (width, height//3), (width, height), (0, height)],
|
327 |
+
fill=(105, 105, 105))
|
328 |
+
|
329 |
+
# 繪製近山
|
330 |
+
draw.polygon([(0, 2*height//3), (width//3, height//2), (2*width//3, 3*height//5),
|
331 |
+
(width, 2*height//3), (width, height), (0, height)],
|
332 |
+
fill=(69, 90, 100))
|
333 |
+
|
334 |
+
# 山頂積雪
|
335 |
+
draw.polygon([(width//4, height//4), (width//4-20, height//4+20),
|
336 |
+
(width//4+20, height//4+20)], fill=(255, 255, 255))
|
337 |
+
|
338 |
+
def draw_cave_scene(self, draw, size):
|
339 |
+
"""繪製洞穴場景"""
|
340 |
+
width, height = size
|
341 |
+
|
342 |
+
# 黑暗背景
|
343 |
+
draw.rectangle([0, 0, width, height], fill=(30, 30, 30))
|
344 |
+
|
345 |
+
# 洞穴入口
|
346 |
+
draw.ellipse([width//4, height//3, 3*width//4, 2*height//3], fill=(60, 60, 60))
|
347 |
+
draw.ellipse([width//3, height//2-20, 2*width//3, height//2+60], fill=(20, 20, 20))
|
348 |
+
|
349 |
+
# 岩石
|
350 |
+
for i in range(5):
|
351 |
+
x = random.randint(0, width-50)
|
352 |
+
y = random.randint(height-100, height-20)
|
353 |
+
draw.ellipse([x, y, x+40, y+30], fill=(80, 80, 80))
|
354 |
+
|
355 |
+
def draw_castle_scene(self, draw, size):
|
356 |
+
"""繪製城堡場景"""
|
357 |
+
width, height = size
|
358 |
+
|
359 |
+
# 城堡主體
|
360 |
+
castle_x = width//3
|
361 |
+
castle_width = width//3
|
362 |
+
castle_height = height//2
|
363 |
+
|
364 |
+
draw.rectangle([castle_x, height-castle_height, castle_x+castle_width, height],
|
365 |
+
fill=(139, 69, 19))
|
366 |
+
|
367 |
+
# 城堡塔樓
|
368 |
+
for i in range(3):
|
369 |
+
tower_x = castle_x + i * castle_width//3
|
370 |
+
draw.rectangle([tower_x, height-castle_height-40, tower_x+30, height-castle_height],
|
371 |
+
fill=(160, 82, 45))
|
372 |
+
# 塔頂
|
373 |
+
draw.polygon([(tower_x, height-castle_height-40),
|
374 |
+
(tower_x+15, height-castle_height-60),
|
375 |
+
(tower_x+30, height-castle_height-40)], fill=(139, 0, 0))
|
376 |
+
|
377 |
+
def draw_default_scene(self, draw, size):
|
378 |
+
"""繪製默認草原場景"""
|
379 |
+
width, height = size
|
380 |
+
|
381 |
+
# 草地
|
382 |
+
draw.rectangle([0, 2*height//3, width, height], fill=(76, 175, 80))
|
383 |
+
|
384 |
+
# 遠處的丘陵
|
385 |
+
draw.ellipse([-50, height//2, width//3, 2*height//3], fill=(46, 125, 50))
|
386 |
+
draw.ellipse([2*width//3, height//2+20, width+50, 2*height//3+30], fill=(46, 125, 50))
|
387 |
+
|
388 |
+
# 雲朵
|
389 |
+
cloud_positions = [(100, 50), (300, 80), (450, 40)]
|
390 |
+
for x, y in cloud_positions:
|
391 |
+
for i in range(3):
|
392 |
+
draw.ellipse([x+i*15-10, y-5, x+i*15+25, y+15], fill=(255, 255, 255))
|
393 |
+
|
394 |
+
def explore(self):
|
395 |
+
"""探索功能"""
|
396 |
+
if not self.player or self.player['HP'] <= 0:
|
397 |
+
return "請先創建角色或角色已死亡", "", None
|
398 |
+
|
399 |
+
# 生成隨機事件
|
400 |
+
event = random.choice(["battle", "treasure", "trap", "peaceful"])
|
401 |
+
scene_description = self.generate_ai_scene("explore")
|
402 |
+
|
403 |
+
result_text = f"📍 {scene_description}\n\n"
|
404 |
+
|
405 |
+
if event == "battle":
|
406 |
+
monster = self.generate_ai_monster()
|
407 |
+
battle_scene = self.generate_ai_scene("battle")
|
408 |
+
|
409 |
+
scene_description += f"\n\n{battle_scene}"
|
410 |
+
|
411 |
+
result_text += f"⚔️ {battle_scene}\n"
|
412 |
+
result_text += f"你遇到了: {monster['name']}\n"
|
413 |
+
result_text += f"描述: {monster['description']}\n"
|
414 |
+
result_text += f"弱點: {monster['weakness']}\n"
|
415 |
+
result_text += f"生命值: {monster['HP']}, 攻擊: {monster['Attack']}, 防禦: {monster['Defense']}\n\n"
|
416 |
+
|
417 |
+
# 執行戰鬥
|
418 |
+
battle_result = self.battle(monster)
|
419 |
+
result_text += f"戰鬥結果: {battle_result}"
|
420 |
+
|
421 |
+
elif event == "treasure":
|
422 |
+
treasure_scene = self.generate_ai_scene("treasure")
|
423 |
+
|
424 |
+
scene_description += f"\n\n{treasure_scene}"
|
425 |
+
|
426 |
+
result_text += f"💰 {treasure_scene}\n"
|
427 |
+
treasure_type = random.choice(["gold", "healing_potion", "equipment"]) # Use healing_potion key
|
428 |
+
|
429 |
+
if treasure_type == "gold":
|
430 |
+
gold_amount = random.randint(20, 50)
|
431 |
+
self.player["Gold"] += gold_amount
|
432 |
+
result_text += f"你獲得了 {gold_amount} 金幣!"
|
433 |
+
elif treasure_type == "healing_potion": # Use healing_potion key
|
434 |
+
self.backpack.append("治療藥水") # Keep Chinese name in backpack
|
435 |
+
result_text += "你獲得了一瓶治療藥水!"
|
436 |
+
else:
|
437 |
+
if random.choice([True, False]):
|
438 |
+
self.player["Attack"] += random.randint(1, 3)
|
439 |
+
result_text += f"你找到了一把武器!攻擊力增加!"
|
440 |
+
else:
|
441 |
+
self.player["Defense"] += random.randint(1, 2)
|
442 |
+
result_text += f"你找到了一件防具!防禦力增加!"
|
443 |
+
|
444 |
+
elif event == "trap":
|
445 |
+
trap_scene = self.generate_ai_scene("trap")
|
446 |
+
|
447 |
+
scene_description += f"\n\n{trap_scene}"
|
448 |
+
|
449 |
+
result_text += f"💀 {trap_scene}\n"
|
450 |
+
damage = random.randint(10, 25)
|
451 |
+
self.player["HP"] = max(0, self.player["HP"] - damage)
|
452 |
+
result_text += f"你受到了 {damage} 點傷害!"
|
453 |
+
|
454 |
+
else: # peaceful
|
455 |
+
result_text += "🌸 這裡很寧靜,你恢復了一些體力。"
|
456 |
+
self.player["HP"] = min(self.player["max_HP"], self.player["HP"] + 5)
|
457 |
+
|
458 |
+
# 獲得經驗值
|
459 |
+
exp_gain = random.randint(5, 15)
|
460 |
+
self.player["exp"] += exp_gain
|
461 |
+
result_text += f"\n\n✨ 獲得 {exp_gain} 經驗值!"
|
462 |
+
|
463 |
+
# 檢查升級
|
464 |
+
if self.player["exp"] >= 100:
|
465 |
+
self.level_up()
|
466 |
+
result_text += f"\n🎉 恭喜升級!現在是等級 {self.player['level']}!"
|
467 |
+
|
468 |
+
self.game_history.append(result_text)
|
469 |
+
|
470 |
+
# 生成場景圖片
|
471 |
+
scene_image = self.generate_scene_image(scene_description)
|
472 |
+
|
473 |
+
return result_text, self.get_status_text(), scene_image
|
474 |
+
|
475 |
+
def battle(self, monster):
|
476 |
+
"""戰鬥系統"""
|
477 |
+
monster_hp = monster["HP"]
|
478 |
+
result = ""
|
479 |
+
|
480 |
+
while self.player["HP"] > 0 and monster_hp > 0:
|
481 |
+
# 玩家攻擊
|
482 |
+
player_damage = max(self.player["Attack"] - monster["Defense"], 1)
|
483 |
+
monster_hp -= player_damage
|
484 |
+
result += f"你對{monster['name']}造成了{player_damage}點傷害!\n"
|
485 |
+
|
486 |
+
if monster_hp <= 0:
|
487 |
+
gold_reward = random.randint(15, 30)
|
488 |
+
exp_reward = random.randint(20, 40)
|
489 |
+
self.player["Gold"] += gold_reward
|
490 |
+
self.player["exp"] += exp_reward
|
491 |
+
result += f"🎉 你擊敗了{monster['name']}!\n"
|
492 |
+
result += f"獲得 {gold_reward} 金幣和 {exp_reward} 經驗值!"
|
493 |
+
return result
|
494 |
+
|
495 |
+
# 怪物攻擊
|
496 |
+
monster_damage = max(monster["Attack"] - self.player["Defense"], 1)
|
497 |
+
self.player["HP"] -= monster_damage
|
498 |
+
result += f"{monster['name']}對你造成了{monster_damage}點傷害!\n"
|
499 |
+
|
500 |
+
if self.player["HP"] <= 0:
|
501 |
+
result += "💀 你被打敗了!遊戲結束。"
|
502 |
+
return result
|
503 |
+
|
504 |
+
# 簡化戰鬥,避免無限循環
|
505 |
+
if random.random() < 0.3: # 30%機率快速結束戰鬥
|
506 |
+
if random.choice([True, False]):
|
507 |
+
result += "你找到機會逃脫了!"
|
508 |
+
return result
|
509 |
+
|
510 |
+
return result
|
511 |
+
|
512 |
+
def level_up(self):
|
513 |
+
"""升級系統"""
|
514 |
+
self.player["level"] += 1
|
515 |
+
self.player["exp"] = 0
|
516 |
+
self.player["max_HP"] += 20
|
517 |
+
self.player["HP"] = self.player["max_HP"] # 升級時回滿血
|
518 |
+
self.player["Attack"] += random.randint(2, 5)
|
519 |
+
self.player["Defense"] += random.randint(1, 3)
|
520 |
+
|
521 |
+
def use_potion(self):
|
522 |
+
"""使用藥水 (僅處理治療藥水)"""
|
523 |
+
if not self.player:
|
524 |
+
return "請先創建角色", ""
|
525 |
+
|
526 |
+
potion_name_in_backpack = "治療藥水" # 背包中藥水的中文名稱
|
527 |
+
|
528 |
+
if potion_name_in_backpack in self.backpack:
|
529 |
+
heal_amount = 40
|
530 |
+
self.player["HP"] = min(self.player["max_HP"], self.player["HP"] + heal_amount)
|
531 |
+
self.backpack.remove(potion_name_in_backpack)
|
532 |
+
result = f"使用治療藥水,恢復了{heal_amount}點生命值!"
|
533 |
+
self.game_history.append(result)
|
534 |
+
return result, self.get_status_text()
|
535 |
+
else:
|
536 |
+
return "背包中沒有治療藥水!", self.get_status_text()
|
537 |
+
|
538 |
+
def shop(self, item_type):
|
539 |
+
"""商店系統"""
|
540 |
+
if not self.player:
|
541 |
+
return "請先創建角色", ""
|
542 |
+
|
543 |
+
shop_items = {
|
544 |
+
"healing_potion": {"name": "治療藥水", "price": 15, "effect": "恢復40點生命值"},
|
545 |
+
"max_hp_potion": {"name": "生命藥水", "price": 30, "effect": "最大生命值+20"}, # 新增
|
546 |
+
"bomb": {"name": "炸彈", "price": 60, "effect": "攻擊力+5"}, # 新增
|
547 |
+
"shield": {"name": "盾牌", "price": 50, "effect": "防禦力+3"} # 新增
|
548 |
+
}
|
549 |
+
|
550 |
+
if item_type not in shop_items:
|
551 |
+
return "無效的物品選擇", self.get_status_text()
|
552 |
+
|
553 |
+
item = shop_items[item_type]
|
554 |
+
|
555 |
+
if self.player["Gold"] >= item["price"]:
|
556 |
+
self.player["Gold"] -= item["price"]
|
557 |
+
|
558 |
+
result = f"成功購買{item['name']}!花費{item['price']}金幣。"
|
559 |
+
|
560 |
+
if item_type == "healing_potion":
|
561 |
+
self.backpack.append("治療藥水") # 背包中存中文名稱
|
562 |
+
elif item_type == "max_hp_potion":
|
563 |
+
self.player["max_HP"] += 20
|
564 |
+
self.player["HP"] = self.player["max_HP"] # 購買時回滿血到新的最大值
|
565 |
+
result += " 最大生命值提升!"
|
566 |
+
elif item_type == "bomb":
|
567 |
+
self.backpack.append("炸彈")
|
568 |
+
self.player["Attack"] += 5
|
569 |
+
result += " 攻擊力提升!"
|
570 |
+
elif item_type == "shield":
|
571 |
+
self.backpack.append("盾牌")
|
572 |
+
self.player["Defense"] += 3
|
573 |
+
result += " 防禦力提升!"
|
574 |
+
|
575 |
+
self.game_history.append(result)
|
576 |
+
return result, self.get_status_text()
|
577 |
+
else:
|
578 |
+
return f"金幣不足!需要{item['price']}金幣。", self.get_status_text()
|
579 |
+
|
580 |
+
def generate_riddle(self):
|
581 |
+
"""使用AI生成一個謎語及其答案"""
|
582 |
+
if not self.player:
|
583 |
+
# Return Chinese message for game history/result consistency
|
584 |
+
return "請先創建角色"
|
585 |
+
|
586 |
+
try:
|
587 |
+
prompt = """請生成一個適合國小中年級學生、具有奇幻冒險遊戲風格的簡單謎語,並提供其答案。以JSON格式回應:
|
588 |
+
{{
|
589 |
+
"riddle": "謎語內容",
|
590 |
+
"answer": "謎語答案"
|
591 |
+
}}
|
592 |
+
|
593 |
+
謎語應該有趣、簡單易懂,答案是單詞或短語。"""
|
594 |
+
|
595 |
+
response = client.models.generate_content(
|
596 |
+
model="gemini-2.0-flash-001",
|
597 |
+
contents=prompt
|
598 |
+
)
|
599 |
+
raw_text = response.text.strip()
|
600 |
+
# 尋找 JSON 物件的開始和結束位置
|
601 |
+
json_start = raw_text.find('{')
|
602 |
+
json_end = raw_text.rfind('}')
|
603 |
+
|
604 |
+
if json_start != -1 and json_end != -1 and json_end > json_start:
|
605 |
+
# 提取 JSON 字串
|
606 |
+
json_string = raw_text[json_start : json_end + 1]
|
607 |
+
riddle_data = json.loads(json_string)
|
608 |
+
else:
|
609 |
+
# 如果找不到有效的 JSON,拋出錯誤讓外層捕捉
|
610 |
+
print(f"🚫 AI 回應未包含有效的 JSON 物件: {raw_text}")
|
611 |
+
raise ValueError("AI 回應未包含有效的 JSON 物件。")
|
612 |
+
self.current_riddle = riddle_data.get("riddle", "AI未能生成謎語。")
|
613 |
+
self.current_answer = riddle_data.get("answer", "").strip().lower() # 答案轉小寫以便不區分大小寫比較
|
614 |
+
result = f"謎語商人給你出了一個謎語:\n{self.current_riddle}"
|
615 |
+
self.game_history.append(result)
|
616 |
+
return self.current_riddle # 返回謎語文本給UI顯示
|
617 |
+
except Exception as e:
|
618 |
+
print(f"🚫 AI謎語生成失敗: {str(e)}")
|
619 |
+
self.current_riddle = "AI未能生成謎語。請稍後再試。"
|
620 |
+
self.current_answer = None
|
621 |
+
result = "謎語生成失敗。"
|
622 |
+
self.game_history.append(result)
|
623 |
+
return self.current_riddle # 返回錯誤文本給UI顯示
|
624 |
+
|
625 |
+
def solve_riddle(self, user_answer):
|
626 |
+
"""檢查謎語答案並給予獎勵"""
|
627 |
+
if not self.player:
|
628 |
+
return "請先創建角色", "", "" # Return Chinese message for game history/result consistency
|
629 |
+
|
630 |
+
if self.current_riddle is None or self.current_answer is None:
|
631 |
+
result = "目前沒有謎語可供解答。"
|
632 |
+
self.game_history.append(result)
|
633 |
+
return result, self.get_status_text(), self.get_history_text()
|
634 |
+
|
635 |
+
result = ""
|
636 |
+
if user_answer.strip().lower() == self.current_answer:
|
637 |
+
gold_reward = random.randint(30, 70) # 猜對謎語的獎勵金幣
|
638 |
+
self.player["Gold"] += gold_reward
|
639 |
+
result = f"✅ 恭喜你答對了!答案是「{self.current_answer}」。你獲得了 {gold_reward} 金幣!"
|
640 |
+
self.current_riddle = None # 答對後清除當前謎語
|
641 |
+
self.current_answer = None
|
642 |
+
else:
|
643 |
+
result = f"❌ 答案錯誤。正確答案是「{self.current_answer}」。請再試一次或獲取新的謎語。"
|
644 |
+
# 答錯不清除謎語,可以重試
|
645 |
+
|
646 |
+
self.game_history.append(result)
|
647 |
+
return result, self.get_status_text(), self.get_history_text()
|
648 |
+
|
649 |
+
|
650 |
+
# 建立遊戲
|
651 |
+
game = AIAdventureGame()
|
652 |
+
|
653 |
+
# 定義 Gradio 介面 (Translate UI elements)
|
654 |
+
def create_character_ui(name):
|
655 |
+
"""UI handler for character creation"""
|
656 |
+
status, history = game.create_character(name)
|
657 |
+
# The game history and status text content is in Chinese, as per original code style.
|
658 |
+
# Only the UI labels are English.
|
659 |
+
return status, history, None
|
660 |
+
|
661 |
+
def explore_ui():
|
662 |
+
"""UI handler for exploration"""
|
663 |
+
result, status, image = game.explore()
|
664 |
+
history = game.get_history_text()
|
665 |
+
# result, status, history content is in Chinese.
|
666 |
+
return result, status, history, image
|
667 |
+
|
668 |
+
def use_potion_ui():
|
669 |
+
"""UI handler for using potion"""
|
670 |
+
result, status = game.use_potion()
|
671 |
+
history = game.get_history_text()
|
672 |
+
# result, status, history content is in Chinese.
|
673 |
+
return result, status, history
|
674 |
+
|
675 |
+
# UI handlers for shop items (update names and calls)
|
676 |
+
def buy_healing_potion_ui():
|
677 |
+
"""UI handler for buying healing potion"""
|
678 |
+
result, status = game.shop("healing_potion")
|
679 |
+
history = game.get_history_text()
|
680 |
+
return result, status, history
|
681 |
+
|
682 |
+
def buy_bomb_ui():
|
683 |
+
"""UI handler for buying bomb"""
|
684 |
+
result, status = game.shop("bomb")
|
685 |
+
history = game.get_history_text()
|
686 |
+
return result, status, history
|
687 |
+
|
688 |
+
def buy_shield_ui():
|
689 |
+
"""UI handler for buying shield"""
|
690 |
+
result, status = game.shop("shield")
|
691 |
+
history = game.get_history_text()
|
692 |
+
return result, status, history
|
693 |
+
|
694 |
+
def buy_max_hp_potion_ui():
|
695 |
+
"""UI handler for buying max HP potion"""
|
696 |
+
result, status = game.shop("max_hp_potion")
|
697 |
+
history = game.get_history_text()
|
698 |
+
return result, status, history
|
699 |
+
|
700 |
+
# UI handlers for riddle
|
701 |
+
def get_riddle_ui():
|
702 |
+
"""UI handler to get a new riddle"""
|
703 |
+
riddle_text = game.generate_riddle()
|
704 |
+
status = game.get_status_text()
|
705 |
+
history = game.get_history_text()
|
706 |
+
# riddle_text, status, history content is in Chinese.
|
707 |
+
return riddle_text, "", status, history # Output riddle text, clear answer input, update status/history
|
708 |
+
|
709 |
+
def submit_riddle_ui(user_answer):
|
710 |
+
"""UI handler to submit riddle answer"""
|
711 |
+
result, status, history = game.solve_riddle(user_answer)
|
712 |
+
# result, status, history content is in Chinese.
|
713 |
+
return result, "", status, history # Output result, clear answer input, update status/history
|
714 |
+
|
715 |
+
|
716 |
+
# 建立 Gradio 介面 (Translate UI elements and add new ones)
|
717 |
+
with gr.Blocks(title="AI Adventure Game", theme=gr.themes.Soft()) as app: # Translate title
|
718 |
+
gr.Markdown("""
|
719 |
+
# 🎮 AI Adventure Game
|
720 |
+
|
721 |
+
Welcome to the AI-powered adventure game! Here, every exploration is a unique experience.
|
722 |
+
|
723 |
+
**Game Features:**
|
724 |
+
- 🤖 AI-generated dynamic storylines
|
725 |
+
- 🎨 AI-created unique monsters
|
726 |
+
- 🖼️ Automatically generated scene images
|
727 |
+
- ⚔️ Rich combat and leveling system
|
728 |
+
|
729 |
+
**Instructions:**
|
730 |
+
1. Enter your character name to create your adventurer.
|
731 |
+
2. Click "Start Exploring" to begin your adventure.
|
732 |
+
3. Use the shop to buy equipment and supplies.
|
733 |
+
4. Use potions during combat to restore health.
|
734 |
+
""") # Translate Markdown
|
735 |
+
|
736 |
+
with gr.Row():
|
737 |
+
with gr.Column(scale=2):
|
738 |
+
# Character Creation (Translate label and placeholder)
|
739 |
+
gr.Markdown("## 🧙♂️ Character Creation")
|
740 |
+
name_input = gr.Textbox(label="Enter Character Name", placeholder="Enter your adventurer's name")
|
741 |
+
create_btn = gr.Button("Create Character", variant="primary") # Translate button
|
742 |
+
|
743 |
+
# Game Actions (Translate label and buttons)
|
744 |
+
gr.Markdown("## 🎯 Game Actions")
|
745 |
+
with gr.Row():
|
746 |
+
explore_btn = gr.Button("🗺️ Start Exploring", variant="primary")
|
747 |
+
potion_btn = gr.Button("🧪 Use Healing Potion", variant="secondary") # Translate button
|
748 |
+
|
749 |
+
# Shop (Translate label and buttons, add new items)
|
750 |
+
gr.Markdown("## 🛒 Shop")
|
751 |
+
with gr.Row():
|
752 |
+
buy_healing_potion_btn = gr.Button("Buy Healing Potion (15 Gold)") # Translate button
|
753 |
+
buy_max_hp_potion_btn = gr.Button("Buy Max HP Potion (30 Gold)") # New button
|
754 |
+
with gr.Row():
|
755 |
+
buy_bomb_btn = gr.Button("Buy Bomb (60 Gold)") # New button
|
756 |
+
buy_shield_btn = gr.Button("Buy Shield (50 Gold)") # New button
|
757 |
+
|
758 |
+
# Riddle Challenge (New section)
|
759 |
+
gr.Markdown("## 🤔 Riddle Challenge")
|
760 |
+
riddle_text_output = gr.Textbox(label="Riddle", lines=3, interactive=False, placeholder="Click 'Get Riddle' to receive a challenge.") # New textbox
|
761 |
+
riddle_answer_input = gr.Textbox(label="Your Answer", placeholder="Enter your answer here.") # New textbox
|
762 |
+
with gr.Row():
|
763 |
+
get_riddle_btn = gr.Button("❓ Get Riddle") # New button
|
764 |
+
submit_riddle_btn = gr.Button("✅ Submit Answer", variant="primary") # New button
|
765 |
+
|
766 |
+
|
767 |
+
# Scene Image (Translate label)
|
768 |
+
scene_image = gr.Image(label="Adventure Scene", height=300)
|
769 |
+
|
770 |
+
with gr.Column(scale=1):
|
771 |
+
# Character Status (Translate label)
|
772 |
+
status_text = gr.Textbox(
|
773 |
+
label="📊 Character Status",
|
774 |
+
lines=12,
|
775 |
+
interactive=False,
|
776 |
+
value="Please create a character first." # Translate initial value
|
777 |
+
)
|
778 |
+
|
779 |
+
# Game Result and History (Translate labels and placeholders)
|
780 |
+
with gr.Row():
|
781 |
+
with gr.Column():
|
782 |
+
result_text = gr.Textbox(
|
783 |
+
label="🎮 Game Result",
|
784 |
+
lines=8,
|
785 |
+
interactive=False,
|
786 |
+
placeholder="Game results will appear here..."
|
787 |
+
)
|
788 |
+
|
789 |
+
with gr.Column():
|
790 |
+
history_text = gr.Textbox(
|
791 |
+
label="📜 Adventure Log",
|
792 |
+
lines=8,
|
793 |
+
interactive=False,
|
794 |
+
placeholder="Your adventure story will be logged here..."
|
795 |
+
)
|
796 |
+
|
797 |
+
# Bind events (Update button names and add new bindings)
|
798 |
+
create_btn.click(
|
799 |
+
create_character_ui,
|
800 |
+
inputs=[name_input],
|
801 |
+
outputs=[status_text, history_text, scene_image]
|
802 |
+
)
|
803 |
+
|
804 |
+
explore_btn.click(
|
805 |
+
explore_ui,
|
806 |
+
outputs=[result_text, status_text, history_text, scene_image]
|
807 |
+
)
|
808 |
+
|
809 |
+
potion_btn.click(
|
810 |
+
use_potion_ui,
|
811 |
+
outputs=[result_text, status_text, history_text]
|
812 |
+
)
|
813 |
+
|
814 |
+
# Bind new shop buttons
|
815 |
+
buy_healing_potion_btn.click( # Updated button name
|
816 |
+
buy_healing_potion_ui,
|
817 |
+
outputs=[result_text, status_text, history_text]
|
818 |
+
)
|
819 |
+
|
820 |
+
buy_bomb_btn.click( # New binding
|
821 |
+
buy_bomb_ui,
|
822 |
+
outputs=[result_text, status_text, history_text]
|
823 |
+
)
|
824 |
+
|
825 |
+
buy_shield_btn.click( # New binding
|
826 |
+
buy_shield_ui,
|
827 |
+
outputs=[result_text, status_text, history_text]
|
828 |
+
)
|
829 |
+
|
830 |
+
buy_max_hp_potion_btn.click( # New binding
|
831 |
+
buy_max_hp_potion_ui,
|
832 |
+
outputs=[result_text, status_text, history_text]
|
833 |
+
)
|
834 |
+
|
835 |
+
# Bind riddle buttons
|
836 |
+
get_riddle_btn.click( # New binding
|
837 |
+
get_riddle_ui,
|
838 |
+
outputs=[riddle_text_output, riddle_answer_input, status_text, history_text] # Output riddle text, clear answer input, update status/history
|
839 |
+
)
|
840 |
+
|
841 |
+
submit_riddle_btn.click( # New binding
|
842 |
+
submit_riddle_ui,
|
843 |
+
inputs=[riddle_answer_input],
|
844 |
+
outputs=[result_text, riddle_answer_input, status_text, history_text] # Output result, clear answer input, update status/history
|
845 |
+
)
|
846 |
+
|
847 |
+
app.launch(show_error=True)
|