aicodingfun commited on
Commit
b331c31
·
verified ·
1 Parent(s): 3aaa571

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +847 -0
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)