Update app.py
Browse files
app.py
CHANGED
@@ -8,6 +8,7 @@ import os
|
|
8 |
import getpass
|
9 |
import re
|
10 |
import random
|
|
|
11 |
|
12 |
# Set up OpenAI API key
|
13 |
if 'OPENAI_API_KEY' not in os.environ:
|
@@ -17,55 +18,39 @@ if 'OPENAI_API_KEY' not in os.environ:
|
|
17 |
pxt.drop_dir('ai_rpg', force=True)
|
18 |
pxt.create_dir('ai_rpg')
|
19 |
|
20 |
-
#
|
21 |
def initialize_stats(genre: str) -> str:
|
22 |
"""Initialize player stats based on the selected genre"""
|
23 |
base_stats = {
|
24 |
-
"๐งโโ๏ธ Fantasy": "
|
25 |
-
"๐ Sci-Fi": "
|
26 |
-
"๐ป Horror": "
|
27 |
-
"๐ Mystery": "
|
28 |
-
"๐ Post-Apocalyptic": "
|
29 |
-
"๐ค Cyberpunk": "
|
30 |
-
"โ๏ธ Steampunk": "
|
31 |
}
|
32 |
|
33 |
if genre in base_stats:
|
34 |
return base_stats[genre]
|
35 |
else:
|
36 |
# Default stats if genre not found
|
37 |
-
return "
|
38 |
-
|
39 |
-
@pxt.udf
|
40 |
-
def generate_random_event(turn_number: int) -> str:
|
41 |
-
"""Generate a random event based on turn number"""
|
42 |
-
if turn_number % 3 == 0 and turn_number > 0: # Every 3rd turn
|
43 |
-
events = [
|
44 |
-
"๊ฐ์๊ธฐ ๋ถ๊ทผ์์ ์ด์ํ ์๋ฆฌ๊ฐ ๋ค๋ฆฝ๋๋ค",
|
45 |
-
"๋ฏ์ ์ฌํ์๊ฐ ๋น์ ์ ๋ฐ๋ผ๋ณด๊ณ ์์ต๋๋ค",
|
46 |
-
"์ง๋ฉด์ด ๋ฏธ์ธํ๊ฒ ์ง๋ํ๊ธฐ ์์ํฉ๋๋ค",
|
47 |
-
"์ฃผ๋จธ๋์์ ๋ฌด์ธ๊ฐ๊ฐ ๋น๋ฉ๋๋ค",
|
48 |
-
"๋ฉ๋ฆฌ์ ๋ฌด์ธ๊ฐ๊ฐ ๋น์ ์ ํฅํด ๋ค๊ฐ์ค๊ณ ์์ต๋๋ค",
|
49 |
-
"๊ฐ์๊ธฐ ๋ ์จ๊ฐ ๋ณํ๊ธฐ ์์ํฉ๋๋ค",
|
50 |
-
"์ฃผ๋ณ์ ์จ๊ฒจ์ง ํต๋ก๋ฅผ ๋ฐ๊ฒฌํฉ๋๋ค"
|
51 |
-
]
|
52 |
-
return random.choice(events)
|
53 |
-
return ""
|
54 |
|
55 |
@pxt.udf
|
56 |
def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int, stats: str) -> list[dict]:
|
57 |
return [
|
58 |
{
|
59 |
'role': 'system',
|
60 |
-
'content': f"""
|
61 |
|
62 |
-
|
63 |
|
64 |
-
|
65 |
-
|
66 |
|
67 |
-
|
68 |
-
|
69 |
|
70 |
Provide your response in three clearly separated sections using exactly this format:
|
71 |
|
@@ -89,6 +74,30 @@ def generate_messages(genre: str, player_name: str, initial_scenario: str, playe
|
|
89 |
}
|
90 |
]
|
91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
@pxt.udf
|
93 |
def get_story(response: str) -> str:
|
94 |
"""Extract just the story part from the response"""
|
@@ -111,7 +120,7 @@ def get_stats_update(response: str) -> str:
|
|
111 |
if len(parts) > 1:
|
112 |
stats_part = parts[1].split("OPTIONS:")[0].strip()
|
113 |
return stats_part
|
114 |
-
return "
|
115 |
|
116 |
@pxt.udf
|
117 |
def get_options(response: str) -> list[str]:
|
@@ -122,7 +131,7 @@ def get_options(response: str) -> list[str]:
|
|
122 |
options = re.findall(r'\d+\.\s*(.*?)(?=\d+\.|$)', options_text, re.DOTALL)
|
123 |
options = [opt.strip() for opt in options if opt.strip()]
|
124 |
while len(options) < 4:
|
125 |
-
options.append("
|
126 |
return options[:4]
|
127 |
|
128 |
parts = response.split("OPTIONS:")
|
@@ -130,10 +139,10 @@ def get_options(response: str) -> list[str]:
|
|
130 |
options = re.findall(r'\d+\.\s*(.*?)(?=\d+\.|$)', parts[1], re.DOTALL)
|
131 |
options = [opt.strip() for opt in options if opt.strip()]
|
132 |
while len(options) < 4:
|
133 |
-
options.append("
|
134 |
return options[:4]
|
135 |
|
136 |
-
return ["
|
137 |
|
138 |
# Create a single table for all game data
|
139 |
interactions = pxt.create_table(
|
@@ -161,9 +170,10 @@ interactions.add_computed_column(messages=generate_messages(
|
|
161 |
interactions.player_stats
|
162 |
))
|
163 |
|
|
|
164 |
interactions.add_computed_column(ai_response=openai.chat_completions(
|
165 |
messages=interactions.messages,
|
166 |
-
model='gpt-
|
167 |
max_tokens=800,
|
168 |
temperature=0.8
|
169 |
))
|
@@ -184,93 +194,125 @@ class RPGGame:
|
|
184 |
self.current_session_id = session_id
|
185 |
self.turn_number = 0
|
186 |
|
187 |
-
#
|
188 |
initial_stats = initialize_stats(genre)
|
189 |
self.current_stats = initial_stats
|
190 |
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
|
214 |
def process_action(self, action: str) -> tuple[str, str, list[str]]:
|
215 |
if not self.current_session_id:
|
216 |
-
return "
|
217 |
|
218 |
self.turn_number += 1
|
219 |
|
220 |
-
|
221 |
-
interactions.
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
(
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
274 |
|
275 |
def create_interface():
|
276 |
game = RPGGame()
|
@@ -299,7 +341,7 @@ def create_interface():
|
|
299 |
border-radius: 10px;
|
300 |
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
301 |
margin-bottom: 20px;
|
302 |
-
font-family: '
|
303 |
}
|
304 |
|
305 |
.stats-container {
|
@@ -349,34 +391,34 @@ def create_interface():
|
|
349 |
gr.HTML(
|
350 |
"""
|
351 |
<div class="title-container">
|
352 |
-
<h1 style="margin-bottom: 0.5em; font-size: 2.5em;">๐ฒ AI RPG
|
353 |
-
<p style="font-size: 1.2em;">
|
354 |
</div>
|
355 |
"""
|
356 |
)
|
357 |
|
358 |
with gr.Row():
|
359 |
with gr.Column(scale=1):
|
360 |
-
with gr.Accordion("๐ฏ
|
361 |
gr.HTML(
|
362 |
"""
|
363 |
<div style="padding: 15px;">
|
364 |
-
<h3>AI RPG
|
365 |
<ul style="list-style-type: none; padding-left: 5px;">
|
366 |
-
<li>๐ฎ <b
|
367 |
-
<li>๐ <b
|
368 |
-
<li>๐ญ <b
|
369 |
-
<li>๐ค <b>AI
|
370 |
-
<li>๐ <b
|
371 |
</ul>
|
372 |
</div>
|
373 |
"""
|
374 |
)
|
375 |
|
376 |
-
with gr.Accordion("๐จ
|
377 |
player_name = gr.Textbox(
|
378 |
-
label="๐ค
|
379 |
-
placeholder="
|
380 |
container=False
|
381 |
)
|
382 |
genre = gr.Dropdown(
|
@@ -389,60 +431,60 @@ def create_interface():
|
|
389 |
"๐ค Cyberpunk",
|
390 |
"โ๏ธ Steampunk"
|
391 |
],
|
392 |
-
label="๐ญ
|
393 |
container=False,
|
394 |
value="๐งโโ๏ธ Fantasy"
|
395 |
)
|
396 |
scenario = gr.Textbox(
|
397 |
-
label="๐
|
398 |
lines=3,
|
399 |
-
placeholder="
|
400 |
container=False
|
401 |
)
|
402 |
-
start_button = gr.Button("๐ฎ
|
403 |
|
404 |
with gr.Column(scale=2):
|
405 |
story_display = gr.Markdown(
|
406 |
-
label="๐
|
407 |
-
value="<div class='story-container'
|
408 |
show_label=False
|
409 |
)
|
410 |
|
411 |
stats_display = gr.Markdown(
|
412 |
-
label="๐
|
413 |
-
value="<div class='stats-container'
|
414 |
show_label=False
|
415 |
)
|
416 |
|
417 |
-
gr.HTML("<div class='options-container'><h3>๐ฏ
|
418 |
|
419 |
action_input = gr.Radio(
|
420 |
choices=[],
|
421 |
label="",
|
422 |
interactive=True
|
423 |
)
|
424 |
-
submit_action = gr.Button("โก
|
425 |
|
426 |
with gr.Row():
|
427 |
with gr.Column():
|
428 |
-
gr.HTML("<div class='history-container'><h3>๐ซ
|
429 |
gr.Examples(
|
430 |
examples=[
|
431 |
-
["
|
432 |
-
["
|
433 |
-
["
|
434 |
-
["
|
435 |
-
["
|
436 |
-
["
|
437 |
-
["
|
438 |
],
|
439 |
inputs=[player_name, genre, scenario]
|
440 |
)
|
441 |
|
442 |
with gr.Column():
|
443 |
history_df = gr.Dataframe(
|
444 |
-
headers=["๐
|
445 |
-
label="๐
|
446 |
wrap=True,
|
447 |
row_count=5,
|
448 |
col_count=(3, "fixed")
|
@@ -451,8 +493,8 @@ def create_interface():
|
|
451 |
def start_new_game(name, genre_choice, scenario_text):
|
452 |
if not name or not genre_choice or not scenario_text:
|
453 |
return (
|
454 |
-
"<div class='story-container'
|
455 |
-
"<div class='stats-container'
|
456 |
[],
|
457 |
[]
|
458 |
)
|
@@ -476,13 +518,13 @@ def create_interface():
|
|
476 |
]
|
477 |
|
478 |
story_html = f"<div class='story-container'>{initial_story}</div>"
|
479 |
-
stats_html = f"<div class='stats-container'><h3>๐
|
480 |
|
481 |
return story_html, stats_html, gr.Radio(choices=initial_options, interactive=True), history_data
|
482 |
except Exception as e:
|
483 |
return (
|
484 |
-
f"<div class='story-container'
|
485 |
-
"<div class='stats-container'
|
486 |
[],
|
487 |
[]
|
488 |
)
|
@@ -491,8 +533,8 @@ def create_interface():
|
|
491 |
try:
|
492 |
if not action_choice:
|
493 |
return (
|
494 |
-
"<div class='story-container'
|
495 |
-
"<div class='stats-container'
|
496 |
[],
|
497 |
[]
|
498 |
)
|
@@ -515,13 +557,13 @@ def create_interface():
|
|
515 |
]
|
516 |
|
517 |
story_html = f"<div class='story-container'>{story}</div>"
|
518 |
-
stats_html = f"<div class='stats-container'><h3>๐
|
519 |
|
520 |
return story_html, stats_html, gr.Radio(choices=options, interactive=True), history_data
|
521 |
except Exception as e:
|
522 |
return (
|
523 |
-
f"<div class='story-container'
|
524 |
-
"<div class='stats-container'
|
525 |
[],
|
526 |
[]
|
527 |
)
|
@@ -540,8 +582,8 @@ def create_interface():
|
|
540 |
|
541 |
gr.HTML("""
|
542 |
<div style="text-align: center; margin-top: 30px; padding: 20px; background: #f5f5f5; border-radius: 10px;">
|
543 |
-
<h3>๐ AI RPG
|
544 |
-
<p
|
545 |
</div>
|
546 |
""")
|
547 |
|
|
|
8 |
import getpass
|
9 |
import re
|
10 |
import random
|
11 |
+
import time
|
12 |
|
13 |
# Set up OpenAI API key
|
14 |
if 'OPENAI_API_KEY' not in os.environ:
|
|
|
18 |
pxt.drop_dir('ai_rpg', force=True)
|
19 |
pxt.create_dir('ai_rpg')
|
20 |
|
21 |
+
# Regular function (not UDF)
|
22 |
def initialize_stats(genre: str) -> str:
|
23 |
"""Initialize player stats based on the selected genre"""
|
24 |
base_stats = {
|
25 |
+
"๐งโโ๏ธ Fantasy": "Health: 100, Mana: 80, Strength: 7, Intelligence: 8, Agility: 6, Gold: 50",
|
26 |
+
"๐ Sci-Fi": "Health: 100, Energy: 90, Tech: 8, Intelligence: 9, Agility: 6, Credits: 500",
|
27 |
+
"๐ป Horror": "Health: 80, Sanity: 100, Strength: 6, Intelligence: 7, Agility: 8, Items: Flashlight, First Aid",
|
28 |
+
"๐ Mystery": "Health: 90, Focus: 100, Observation: 9, Intelligence: 8, Charisma: 7, Clues: 0",
|
29 |
+
"๐ Post-Apocalyptic": "Health: 95, Radiation Resistance: 75, Strength: 8, Survival: 9, Supplies: Limited",
|
30 |
+
"๐ค Cyberpunk": "Health: 90, Cyberware: 85%, Hacking: 8, Street Cred: 6, Edge: 7, Nuyen: 1000",
|
31 |
+
"โ๏ธ Steampunk": "Health: 95, Steam Power: 85, Engineering: 8, Artistry: 7, Social: 6, Shillings: 200"
|
32 |
}
|
33 |
|
34 |
if genre in base_stats:
|
35 |
return base_stats[genre]
|
36 |
else:
|
37 |
# Default stats if genre not found
|
38 |
+
return "Health: 100, Energy: 100, Strength: 7, Intelligence: 7, Agility: 7, Money: 100"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
@pxt.udf
|
41 |
def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int, stats: str) -> list[dict]:
|
42 |
return [
|
43 |
{
|
44 |
'role': 'system',
|
45 |
+
'content': f"""You are the game master for a {genre} RPG. The player's name is {player_name}.
|
46 |
|
47 |
+
Player stats to manage: {stats}
|
48 |
|
49 |
+
You are a game master who vividly develops stories based on the player's choices.
|
50 |
+
Use detailed descriptions and sensory details to help the player immerse themselves in the game world.
|
51 |
|
52 |
+
When player stats change based on their choices, reflect this in the story.
|
53 |
+
Create interesting stories that include dangerous situations, challenges, rewards, and chance encounters.
|
54 |
|
55 |
Provide your response in three clearly separated sections using exactly this format:
|
56 |
|
|
|
74 |
}
|
75 |
]
|
76 |
|
77 |
+
@pxt.udf
|
78 |
+
def fallback_response(player_input: str, turn_number: int) -> str:
|
79 |
+
"""Generate a fallback response when API fails"""
|
80 |
+
if turn_number == 0:
|
81 |
+
return """๐ **STORY**: Your adventure begins as you assess your surroundings. The world around you seems full of possibilities and dangers alike. What will you do next?
|
82 |
+
|
83 |
+
๐ **STATS UPDATE**: Your current stats remain unchanged.
|
84 |
+
|
85 |
+
๐ฏ **OPTIONS**:
|
86 |
+
1. Explore the area carefully
|
87 |
+
2. Look for signs of civilization
|
88 |
+
3. Check your inventory
|
89 |
+
4. Rest and gather your thoughts"""
|
90 |
+
else:
|
91 |
+
return f"""๐ **STORY**: You decide to {player_input}. As you do, you notice new details about your environment and feel yourself making progress in your quest.
|
92 |
+
|
93 |
+
๐ **STATS UPDATE**: Your actions have slightly improved your experience.
|
94 |
+
|
95 |
+
๐ฏ **OPTIONS**:
|
96 |
+
1. Continue on your current path
|
97 |
+
2. Try a different approach
|
98 |
+
3. Investigate something suspicious
|
99 |
+
4. Take a moment to strategize"""
|
100 |
+
|
101 |
@pxt.udf
|
102 |
def get_story(response: str) -> str:
|
103 |
"""Extract just the story part from the response"""
|
|
|
120 |
if len(parts) > 1:
|
121 |
stats_part = parts[1].split("OPTIONS:")[0].strip()
|
122 |
return stats_part
|
123 |
+
return "No stat changes"
|
124 |
|
125 |
@pxt.udf
|
126 |
def get_options(response: str) -> list[str]:
|
|
|
131 |
options = re.findall(r'\d+\.\s*(.*?)(?=\d+\.|$)', options_text, re.DOTALL)
|
132 |
options = [opt.strip() for opt in options if opt.strip()]
|
133 |
while len(options) < 4:
|
134 |
+
options.append("Try something else...")
|
135 |
return options[:4]
|
136 |
|
137 |
parts = response.split("OPTIONS:")
|
|
|
139 |
options = re.findall(r'\d+\.\s*(.*?)(?=\d+\.|$)', parts[1], re.DOTALL)
|
140 |
options = [opt.strip() for opt in options if opt.strip()]
|
141 |
while len(options) < 4:
|
142 |
+
options.append("Try something else...")
|
143 |
return options[:4]
|
144 |
|
145 |
+
return ["Continue...", "Take a different action", "Try something new", "Explore surroundings"]
|
146 |
|
147 |
# Create a single table for all game data
|
148 |
interactions = pxt.create_table(
|
|
|
170 |
interactions.player_stats
|
171 |
))
|
172 |
|
173 |
+
# Changed from gpt-4.1-mini to gpt-3.5-turbo for better compatibility
|
174 |
interactions.add_computed_column(ai_response=openai.chat_completions(
|
175 |
messages=interactions.messages,
|
176 |
+
model='gpt-3.5-turbo', # Changed to a more widely available model
|
177 |
max_tokens=800,
|
178 |
temperature=0.8
|
179 |
))
|
|
|
194 |
self.current_session_id = session_id
|
195 |
self.turn_number = 0
|
196 |
|
197 |
+
# Get initial stats as a string
|
198 |
initial_stats = initialize_stats(genre)
|
199 |
self.current_stats = initial_stats
|
200 |
|
201 |
+
try:
|
202 |
+
interactions.insert([{
|
203 |
+
'session_id': session_id,
|
204 |
+
'player_name': player_name,
|
205 |
+
'genre': genre,
|
206 |
+
'initial_scenario': scenario,
|
207 |
+
'turn_number': 0,
|
208 |
+
'player_input': "Game starts",
|
209 |
+
'timestamp': datetime.now(),
|
210 |
+
'player_stats': initial_stats, # Store string result
|
211 |
+
'random_event': ""
|
212 |
+
}])
|
213 |
+
|
214 |
+
# Added retry logic for API calls
|
215 |
+
max_retries = 3
|
216 |
+
for attempt in range(max_retries):
|
217 |
+
try:
|
218 |
+
result = interactions.select(
|
219 |
+
interactions.story_text,
|
220 |
+
interactions.stats_update,
|
221 |
+
interactions.options
|
222 |
+
).where(
|
223 |
+
(interactions.session_id == session_id) &
|
224 |
+
(interactions.turn_number == 0)
|
225 |
+
).collect()
|
226 |
+
|
227 |
+
return session_id, result['story_text'][0], result['stats_update'][0], result['options'][0]
|
228 |
+
except Exception as e:
|
229 |
+
if attempt < max_retries - 1:
|
230 |
+
time.sleep(2) # Wait before retrying
|
231 |
+
else:
|
232 |
+
# Use fallback response if all retries fail
|
233 |
+
fallback = fallback_response("Game starts", 0)
|
234 |
+
return session_id, get_story(fallback), get_stats_update(fallback), get_options(fallback)
|
235 |
+
except Exception as e:
|
236 |
+
# Use fallback if insert fails
|
237 |
+
fallback = fallback_response("Game starts", 0)
|
238 |
+
return session_id, get_story(fallback), get_stats_update(fallback), get_options(fallback)
|
239 |
|
240 |
def process_action(self, action: str) -> tuple[str, str, list[str]]:
|
241 |
if not self.current_session_id:
|
242 |
+
return "No active game session. Please start a new game.", "No stats", []
|
243 |
|
244 |
self.turn_number += 1
|
245 |
|
246 |
+
try:
|
247 |
+
prev_turn = interactions.select(
|
248 |
+
interactions.player_name,
|
249 |
+
interactions.genre,
|
250 |
+
interactions.initial_scenario,
|
251 |
+
interactions.player_stats
|
252 |
+
).where(
|
253 |
+
(interactions.session_id == self.current_session_id) &
|
254 |
+
(interactions.turn_number == self.turn_number - 1)
|
255 |
+
).collect()
|
256 |
+
|
257 |
+
self.current_stats = prev_turn['player_stats'][0]
|
258 |
+
|
259 |
+
# Generate random event directly
|
260 |
+
random_event_val = ""
|
261 |
+
if self.turn_number % 3 == 0 and self.turn_number > 0:
|
262 |
+
events = [
|
263 |
+
"Suddenly, you hear a strange sound nearby",
|
264 |
+
"A mysterious traveler is watching you",
|
265 |
+
"The ground begins to vibrate slightly",
|
266 |
+
"Something in your pocket starts to glow",
|
267 |
+
"Something is approaching you from the distance",
|
268 |
+
"The weather suddenly begins to change",
|
269 |
+
"You discover a hidden passage nearby"
|
270 |
+
]
|
271 |
+
random_event_val = random.choice(events)
|
272 |
+
|
273 |
+
if random_event_val:
|
274 |
+
action = f"{action} ({random_event_val})"
|
275 |
+
|
276 |
+
interactions.insert([{
|
277 |
+
'session_id': self.current_session_id,
|
278 |
+
'player_name': prev_turn['player_name'][0],
|
279 |
+
'genre': prev_turn['genre'][0],
|
280 |
+
'initial_scenario': prev_turn['initial_scenario'][0],
|
281 |
+
'turn_number': self.turn_number,
|
282 |
+
'player_input': action,
|
283 |
+
'timestamp': datetime.now(),
|
284 |
+
'player_stats': self.current_stats,
|
285 |
+
'random_event': random_event_val
|
286 |
+
}])
|
287 |
+
|
288 |
+
# Added retry logic for API calls
|
289 |
+
max_retries = 3
|
290 |
+
for attempt in range(max_retries):
|
291 |
+
try:
|
292 |
+
result = interactions.select(
|
293 |
+
interactions.story_text,
|
294 |
+
interactions.stats_update,
|
295 |
+
interactions.options
|
296 |
+
).where(
|
297 |
+
(interactions.session_id == self.current_session_id) &
|
298 |
+
(interactions.turn_number == self.turn_number)
|
299 |
+
).collect()
|
300 |
+
|
301 |
+
# Update stats for next turn
|
302 |
+
self.current_stats = result['stats_update'][0]
|
303 |
+
|
304 |
+
return result['story_text'][0], result['stats_update'][0], result['options'][0]
|
305 |
+
except Exception as e:
|
306 |
+
if attempt < max_retries - 1:
|
307 |
+
time.sleep(2) # Wait before retrying
|
308 |
+
else:
|
309 |
+
# Use fallback response if all retries fail
|
310 |
+
fallback = fallback_response(action, self.turn_number)
|
311 |
+
return get_story(fallback), get_stats_update(fallback), get_options(fallback)
|
312 |
+
except Exception as e:
|
313 |
+
# Use fallback if something fails
|
314 |
+
fallback = fallback_response(action, self.turn_number)
|
315 |
+
return get_story(fallback), get_stats_update(fallback), get_options(fallback)
|
316 |
|
317 |
def create_interface():
|
318 |
game = RPGGame()
|
|
|
341 |
border-radius: 10px;
|
342 |
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
343 |
margin-bottom: 20px;
|
344 |
+
font-family: 'Roboto', sans-serif;
|
345 |
}
|
346 |
|
347 |
.stats-container {
|
|
|
391 |
gr.HTML(
|
392 |
"""
|
393 |
<div class="title-container">
|
394 |
+
<h1 style="margin-bottom: 0.5em; font-size: 2.5em;">๐ฒ AI RPG Adventure</h1>
|
395 |
+
<p style="font-size: 1.2em;">An immersive roleplaying experience powered by Pixeltable and OpenAI!</p>
|
396 |
</div>
|
397 |
"""
|
398 |
)
|
399 |
|
400 |
with gr.Row():
|
401 |
with gr.Column(scale=1):
|
402 |
+
with gr.Accordion("๐ฏ What is this app?", open=False):
|
403 |
gr.HTML(
|
404 |
"""
|
405 |
<div style="padding: 15px;">
|
406 |
+
<h3>AI RPG Adventure showcases:</h3>
|
407 |
<ul style="list-style-type: none; padding-left: 5px;">
|
408 |
+
<li>๐ฎ <b>Dynamic Storytelling:</b> AI-generated immersive stories</li>
|
409 |
+
<li>๐ <b>Game State Management:</b> Tracking game state with Pixeltable</li>
|
410 |
+
<li>๐ญ <b>Contextual Choices:</b> Custom options based on player actions</li>
|
411 |
+
<li>๐ค <b>AI Narratives:</b> Vivid storytelling</li>
|
412 |
+
<li>๐ <b>Character Status Tracking:</b> Stat changes as you play</li>
|
413 |
</ul>
|
414 |
</div>
|
415 |
"""
|
416 |
)
|
417 |
|
418 |
+
with gr.Accordion("๐จ Create Character", open=True):
|
419 |
player_name = gr.Textbox(
|
420 |
+
label="๐ค Character Name",
|
421 |
+
placeholder="Enter your character's name...",
|
422 |
container=False
|
423 |
)
|
424 |
genre = gr.Dropdown(
|
|
|
431 |
"๐ค Cyberpunk",
|
432 |
"โ๏ธ Steampunk"
|
433 |
],
|
434 |
+
label="๐ญ Choose Genre",
|
435 |
container=False,
|
436 |
value="๐งโโ๏ธ Fantasy"
|
437 |
)
|
438 |
scenario = gr.Textbox(
|
439 |
+
label="๐ Starting Scenario",
|
440 |
lines=3,
|
441 |
+
placeholder="Describe the initial setting and situation...",
|
442 |
container=False
|
443 |
)
|
444 |
+
start_button = gr.Button("๐ฎ Begin Adventure!", variant="primary")
|
445 |
|
446 |
with gr.Column(scale=2):
|
447 |
story_display = gr.Markdown(
|
448 |
+
label="๐ Story",
|
449 |
+
value="<div class='story-container'>Create a character and click 'Begin Adventure!' to start your journey.</div>",
|
450 |
show_label=False
|
451 |
)
|
452 |
|
453 |
stats_display = gr.Markdown(
|
454 |
+
label="๐ Character Stats",
|
455 |
+
value="<div class='stats-container'>Character stats will appear here when you start your adventure.</div>",
|
456 |
show_label=False
|
457 |
)
|
458 |
|
459 |
+
gr.HTML("<div class='options-container'><h3>๐ฏ Choose Your Next Action</h3></div>")
|
460 |
|
461 |
action_input = gr.Radio(
|
462 |
choices=[],
|
463 |
label="",
|
464 |
interactive=True
|
465 |
)
|
466 |
+
submit_action = gr.Button("โก Take Action", variant="primary")
|
467 |
|
468 |
with gr.Row():
|
469 |
with gr.Column():
|
470 |
+
gr.HTML("<div class='history-container'><h3>๐ซ Adventure Examples</h3></div>")
|
471 |
gr.Examples(
|
472 |
examples=[
|
473 |
+
["Admiral Yi", "๐งโโ๏ธ Fantasy", "You wake up at the edge of a forgotten mystical forest. In the distance, you can see castle spires, and in your mind remains only the clue about ancient magic threatening the kingdom. Suddenly, you see a strange light coming from the forest..."],
|
474 |
+
["Ji-Young Kim", "๐ Sci-Fi", "As the navigator of the starship 'Horizon', you wake up to an emergency alarm during the exploration of an unknown planet. Contact with the captain has been lost, and life support systems are gradually failing. You hear strange footsteps in the quiet ship..."],
|
475 |
+
["Elon Musk", "๐ค Cyberpunk", "Seoul, 2077. You are the CEO of Neural Link Industries. Your latest brain-computer interface technology has started giving users an unexpected ability - they can communicate with their houseplants. As you prepare for a major investor presentation, your AI assistant reports that test participants are organizing a mysterious 'plant revolution'..."],
|
476 |
+
["Gordon Ramsay", "๐ Post-Apocalyptic", "You are the last master chef in New Seoul, running an underground restaurant in the ruins of an old luxury hotel. Your signature dish requires a rare mushroom that only grows in dangerous radioactive zones. While preparing for tonight's secret gathering, your scout returns with ominous news about rival chef gangs in the area..."],
|
477 |
+
["Jung-Ho Yoo", "๐ป Horror", "You came to a secluded cabin in the forest for the weekend at your friend's invitation. On the first night, you are drawn into the forest by a strange light visible through the window. As you try to find your way back, you can't see the cabin, and unfamiliar fog grows thicker. In the distance, you hear someoneโno, something calling you..."],
|
478 |
+
["Detective Park", "๐ Mystery", "As a rookie detective, your first case is the mysterious disappearance of the CEO of the city's top tech company. There's no blood in his office, and the only clues are an encrypted note on the desk and his powered-down cutting-edge AI assistant. As you begin your investigation, you hear about a secret project the CEO was working on..."],
|
479 |
+
["Min-Su Lee", "โ๏ธ Steampunk", "In New Joseon, filled with steam and gears, you are an innovative airship designer. During a demonstration of your latest invention, a government secret agent approaches with a secret mission for the imperial family in danger. Underground groups are threatening the throne, and your invention is the royal family's only hope..."]
|
480 |
],
|
481 |
inputs=[player_name, genre, scenario]
|
482 |
)
|
483 |
|
484 |
with gr.Column():
|
485 |
history_df = gr.Dataframe(
|
486 |
+
headers=["๐
Turn", "๐ฏ Player Action", "๐ฌ Game Response"],
|
487 |
+
label="๐ Adventure History",
|
488 |
wrap=True,
|
489 |
row_count=5,
|
490 |
col_count=(3, "fixed")
|
|
|
493 |
def start_new_game(name, genre_choice, scenario_text):
|
494 |
if not name or not genre_choice or not scenario_text:
|
495 |
return (
|
496 |
+
"<div class='story-container'>Please fill in all fields before starting.</div>",
|
497 |
+
"<div class='stats-container'>No stat information</div>",
|
498 |
[],
|
499 |
[]
|
500 |
)
|
|
|
518 |
]
|
519 |
|
520 |
story_html = f"<div class='story-container'>{initial_story}</div>"
|
521 |
+
stats_html = f"<div class='stats-container'><h3>๐ Character Status</h3>{initial_stats}</div>"
|
522 |
|
523 |
return story_html, stats_html, gr.Radio(choices=initial_options, interactive=True), history_data
|
524 |
except Exception as e:
|
525 |
return (
|
526 |
+
f"<div class='story-container'>Game start error: {str(e)}</div>",
|
527 |
+
"<div class='stats-container'>No stat information</div>",
|
528 |
[],
|
529 |
[]
|
530 |
)
|
|
|
533 |
try:
|
534 |
if not action_choice:
|
535 |
return (
|
536 |
+
"<div class='story-container'>Please select an action to continue.</div>",
|
537 |
+
"<div class='stats-container'>No stat information</div>",
|
538 |
[],
|
539 |
[]
|
540 |
)
|
|
|
557 |
]
|
558 |
|
559 |
story_html = f"<div class='story-container'>{story}</div>"
|
560 |
+
stats_html = f"<div class='stats-container'><h3>๐ Character Status</h3>{stats}</div>"
|
561 |
|
562 |
return story_html, stats_html, gr.Radio(choices=options, interactive=True), history_data
|
563 |
except Exception as e:
|
564 |
return (
|
565 |
+
f"<div class='story-container'>Error: {str(e)}</div>",
|
566 |
+
"<div class='stats-container'>No stat information</div>",
|
567 |
[],
|
568 |
[]
|
569 |
)
|
|
|
582 |
|
583 |
gr.HTML("""
|
584 |
<div style="text-align: center; margin-top: 30px; padding: 20px; background: #f5f5f5; border-radius: 10px;">
|
585 |
+
<h3>๐ AI RPG Adventure - An Immersive Roleplaying Experience Powered by Pixeltable</h3>
|
586 |
+
<p>Create your own character and embark on an adventure in a world of your chosen genre. Your choices shape the story!</p>
|
587 |
</div>
|
588 |
""")
|
589 |
|