Update app.py
Browse files
app.py
CHANGED
@@ -18,7 +18,7 @@ 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 = {
|
@@ -37,6 +37,34 @@ def initialize_stats(genre: str) -> str:
|
|
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 [
|
@@ -74,30 +102,6 @@ def generate_messages(genre: str, player_name: str, initial_scenario: str, playe
|
|
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"""
|
@@ -156,7 +160,11 @@ interactions = pxt.create_table(
|
|
156 |
'player_input': pxt.String,
|
157 |
'timestamp': pxt.Timestamp,
|
158 |
'player_stats': pxt.String,
|
159 |
-
'random_event': pxt.String
|
|
|
|
|
|
|
|
|
160 |
}
|
161 |
)
|
162 |
|
@@ -170,10 +178,10 @@ interactions.add_computed_column(messages=generate_messages(
|
|
170 |
interactions.player_stats
|
171 |
))
|
172 |
|
173 |
-
# Changed
|
174 |
interactions.add_computed_column(ai_response=openai.chat_completions(
|
175 |
messages=interactions.messages,
|
176 |
-
model='gpt-3.5-turbo',
|
177 |
max_tokens=800,
|
178 |
temperature=0.8
|
179 |
))
|
@@ -189,7 +197,7 @@ class RPGGame:
|
|
189 |
self.turn_number = 0
|
190 |
self.current_stats = ""
|
191 |
|
192 |
-
def start_game(self, player_name: str, genre: str, scenario: str) -> tuple[str, str,
|
193 |
session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{player_name}"
|
194 |
self.current_session_id = session_id
|
195 |
self.turn_number = 0
|
@@ -198,7 +206,11 @@ class RPGGame:
|
|
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,
|
@@ -207,39 +219,37 @@ class RPGGame:
|
|
207 |
'turn_number': 0,
|
208 |
'player_input': "Game starts",
|
209 |
'timestamp': datetime.now(),
|
210 |
-
'player_stats': initial_stats,
|
211 |
-
'random_event': ""
|
|
|
|
|
|
|
|
|
212 |
}])
|
213 |
|
214 |
-
#
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
)
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
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 |
-
#
|
237 |
-
fallback
|
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 |
|
@@ -273,6 +283,9 @@ class RPGGame:
|
|
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],
|
@@ -282,37 +295,35 @@ class RPGGame:
|
|
282 |
'player_input': action,
|
283 |
'timestamp': datetime.now(),
|
284 |
'player_stats': self.current_stats,
|
285 |
-
'random_event': random_event_val
|
|
|
|
|
|
|
|
|
286 |
}])
|
287 |
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
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 |
-
#
|
314 |
-
fallback =
|
315 |
-
return
|
316 |
|
317 |
def create_interface():
|
318 |
game = RPGGame()
|
@@ -500,33 +511,39 @@ def create_interface():
|
|
500 |
)
|
501 |
|
502 |
try:
|
503 |
-
|
504 |
-
|
505 |
-
history_df = interactions.select(
|
506 |
-
turn=interactions.turn_number,
|
507 |
-
action=interactions.player_input,
|
508 |
-
response=interactions.story_text
|
509 |
-
).where(
|
510 |
-
interactions.session_id == game.current_session_id
|
511 |
-
).order_by(
|
512 |
-
interactions.turn_number
|
513 |
-
).collect().to_pandas()
|
514 |
|
515 |
-
|
516 |
-
|
517 |
-
|
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'>
|
527 |
-
"<div class='stats-container'
|
528 |
-
[],
|
529 |
-
[]
|
530 |
)
|
531 |
|
532 |
def process_player_action(action_choice):
|
@@ -541,31 +558,38 @@ def create_interface():
|
|
541 |
|
542 |
story, stats, options = game.process_action(action_choice)
|
543 |
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
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'>
|
566 |
-
"<div class='stats-container'
|
567 |
-
[],
|
568 |
-
[]
|
569 |
)
|
570 |
|
571 |
start_button.click(
|
|
|
18 |
pxt.drop_dir('ai_rpg', force=True)
|
19 |
pxt.create_dir('ai_rpg')
|
20 |
|
21 |
+
# Regular function (not UDF) for initializing stats
|
22 |
def initialize_stats(genre: str) -> str:
|
23 |
"""Initialize player stats based on the selected genre"""
|
24 |
base_stats = {
|
|
|
37 |
# Default stats if genre not found
|
38 |
return "Health: 100, Energy: 100, Strength: 7, Intelligence: 7, Agility: 7, Money: 100"
|
39 |
|
40 |
+
# Regular Python function for fallback responses (not a UDF)
|
41 |
+
def create_fallback_response(player_input: str, turn_number: int) -> dict:
|
42 |
+
"""Generate a fallback response when API fails"""
|
43 |
+
if turn_number == 0:
|
44 |
+
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?"
|
45 |
+
stats = "Your current stats remain unchanged."
|
46 |
+
options = [
|
47 |
+
"Explore the area carefully",
|
48 |
+
"Look for signs of civilization",
|
49 |
+
"Check your inventory",
|
50 |
+
"Rest and gather your thoughts"
|
51 |
+
]
|
52 |
+
else:
|
53 |
+
story = f"You decide to {player_input}. As you do, you notice new details about your environment and feel yourself making progress in your quest."
|
54 |
+
stats = "Your actions have slightly improved your experience."
|
55 |
+
options = [
|
56 |
+
"Continue on your current path",
|
57 |
+
"Try a different approach",
|
58 |
+
"Investigate something suspicious",
|
59 |
+
"Take a moment to strategize"
|
60 |
+
]
|
61 |
+
|
62 |
+
return {
|
63 |
+
"story": story,
|
64 |
+
"stats": stats,
|
65 |
+
"options": options
|
66 |
+
}
|
67 |
+
|
68 |
@pxt.udf
|
69 |
def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int, stats: str) -> list[dict]:
|
70 |
return [
|
|
|
102 |
}
|
103 |
]
|
104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
@pxt.udf
|
106 |
def get_story(response: str) -> str:
|
107 |
"""Extract just the story part from the response"""
|
|
|
160 |
'player_input': pxt.String,
|
161 |
'timestamp': pxt.Timestamp,
|
162 |
'player_stats': pxt.String,
|
163 |
+
'random_event': pxt.String,
|
164 |
+
'use_fallback': pxt.Boolean,
|
165 |
+
'fallback_story': pxt.String,
|
166 |
+
'fallback_stats': pxt.String,
|
167 |
+
'fallback_options': pxt.String
|
168 |
}
|
169 |
)
|
170 |
|
|
|
178 |
interactions.player_stats
|
179 |
))
|
180 |
|
181 |
+
# Changed to gpt-3.5-turbo for better compatibility
|
182 |
interactions.add_computed_column(ai_response=openai.chat_completions(
|
183 |
messages=interactions.messages,
|
184 |
+
model='gpt-3.5-turbo',
|
185 |
max_tokens=800,
|
186 |
temperature=0.8
|
187 |
))
|
|
|
197 |
self.turn_number = 0
|
198 |
self.current_stats = ""
|
199 |
|
200 |
+
def start_game(self, player_name: str, genre: str, scenario: str) -> tuple[str, str, list[str]]:
|
201 |
session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{player_name}"
|
202 |
self.current_session_id = session_id
|
203 |
self.turn_number = 0
|
|
|
206 |
initial_stats = initialize_stats(genre)
|
207 |
self.current_stats = initial_stats
|
208 |
|
209 |
+
# Create fallback content just in case
|
210 |
+
fallback = create_fallback_response("Game starts", 0)
|
211 |
+
|
212 |
try:
|
213 |
+
# First try without fallback
|
214 |
interactions.insert([{
|
215 |
'session_id': session_id,
|
216 |
'player_name': player_name,
|
|
|
219 |
'turn_number': 0,
|
220 |
'player_input': "Game starts",
|
221 |
'timestamp': datetime.now(),
|
222 |
+
'player_stats': initial_stats,
|
223 |
+
'random_event': "",
|
224 |
+
'use_fallback': False,
|
225 |
+
'fallback_story': fallback["story"],
|
226 |
+
'fallback_stats': fallback["stats"],
|
227 |
+
'fallback_options': ",".join(fallback["options"])
|
228 |
}])
|
229 |
|
230 |
+
# Try to get response
|
231 |
+
try:
|
232 |
+
result = interactions.select(
|
233 |
+
interactions.story_text,
|
234 |
+
interactions.stats_update,
|
235 |
+
interactions.options
|
236 |
+
).where(
|
237 |
+
(interactions.session_id == session_id) &
|
238 |
+
(interactions.turn_number == 0)
|
239 |
+
).collect()
|
240 |
+
|
241 |
+
return result['story_text'][0], result['stats_update'][0], result['options'][0]
|
242 |
+
except Exception as e:
|
243 |
+
# If OpenAI fails, use the fallback values
|
244 |
+
return fallback["story"], fallback["stats"], fallback["options"]
|
245 |
+
|
|
|
|
|
|
|
|
|
|
|
246 |
except Exception as e:
|
247 |
+
# If everything fails, return fallback
|
248 |
+
return fallback["story"], fallback["stats"], fallback["options"]
|
|
|
249 |
|
250 |
def process_action(self, action: str) -> tuple[str, str, list[str]]:
|
251 |
if not self.current_session_id:
|
252 |
+
return "No active game session. Please start a new game.", "No stats", ["Start a new game"]
|
253 |
|
254 |
self.turn_number += 1
|
255 |
|
|
|
283 |
if random_event_val:
|
284 |
action = f"{action} ({random_event_val})"
|
285 |
|
286 |
+
# Create fallback content
|
287 |
+
fallback = create_fallback_response(action, self.turn_number)
|
288 |
+
|
289 |
interactions.insert([{
|
290 |
'session_id': self.current_session_id,
|
291 |
'player_name': prev_turn['player_name'][0],
|
|
|
295 |
'player_input': action,
|
296 |
'timestamp': datetime.now(),
|
297 |
'player_stats': self.current_stats,
|
298 |
+
'random_event': random_event_val,
|
299 |
+
'use_fallback': False,
|
300 |
+
'fallback_story': fallback["story"],
|
301 |
+
'fallback_stats': fallback["stats"],
|
302 |
+
'fallback_options': ",".join(fallback["options"])
|
303 |
}])
|
304 |
|
305 |
+
try:
|
306 |
+
result = interactions.select(
|
307 |
+
interactions.story_text,
|
308 |
+
interactions.stats_update,
|
309 |
+
interactions.options
|
310 |
+
).where(
|
311 |
+
(interactions.session_id == self.current_session_id) &
|
312 |
+
(interactions.turn_number == self.turn_number)
|
313 |
+
).collect()
|
314 |
+
|
315 |
+
# Update stats for next turn
|
316 |
+
self.current_stats = result['stats_update'][0]
|
317 |
+
|
318 |
+
return result['story_text'][0], result['stats_update'][0], result['options'][0]
|
319 |
+
except Exception as e:
|
320 |
+
# If OpenAI fails, use the fallback values
|
321 |
+
return fallback["story"], fallback["stats"], fallback["options"]
|
322 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
except Exception as e:
|
324 |
+
# If everything fails, use fallback
|
325 |
+
fallback = create_fallback_response(action, self.turn_number)
|
326 |
+
return fallback["story"], fallback["stats"], fallback["options"]
|
327 |
|
328 |
def create_interface():
|
329 |
game = RPGGame()
|
|
|
511 |
)
|
512 |
|
513 |
try:
|
514 |
+
initial_story, initial_stats, initial_options = game.start_game(name, genre_choice, scenario_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
515 |
|
516 |
+
# Try to get history, but if it fails, create empty history
|
517 |
+
try:
|
518 |
+
history_df = interactions.select(
|
519 |
+
turn=interactions.turn_number,
|
520 |
+
action=interactions.player_input,
|
521 |
+
response=interactions.story_text
|
522 |
+
).where(
|
523 |
+
(interactions.session_id == game.current_session_id)
|
524 |
+
).order_by(
|
525 |
+
interactions.turn_number
|
526 |
+
).collect().to_pandas()
|
527 |
+
|
528 |
+
history_data = [
|
529 |
+
[str(row['turn']), row['action'], row['response']]
|
530 |
+
for _, row in history_df.iterrows()
|
531 |
+
]
|
532 |
+
except Exception as e:
|
533 |
+
history_data = [["0", "Game starts", initial_story]]
|
534 |
|
535 |
story_html = f"<div class='story-container'>{initial_story}</div>"
|
536 |
stats_html = f"<div class='stats-container'><h3>π Character Status</h3>{initial_stats}</div>"
|
537 |
|
538 |
return story_html, stats_html, gr.Radio(choices=initial_options, interactive=True), history_data
|
539 |
except Exception as e:
|
540 |
+
# Handle any other unexpected errors
|
541 |
+
fallback = create_fallback_response("Game starts", 0)
|
542 |
return (
|
543 |
+
f"<div class='story-container'>{fallback['story']}</div>",
|
544 |
+
f"<div class='stats-container'><h3>π Character Status</h3>{fallback['stats']}</div>",
|
545 |
+
gr.Radio(choices=fallback['options'], interactive=True),
|
546 |
+
[["0", "Game starts", fallback['story']]]
|
547 |
)
|
548 |
|
549 |
def process_player_action(action_choice):
|
|
|
558 |
|
559 |
story, stats, options = game.process_action(action_choice)
|
560 |
|
561 |
+
# Try to get history, but if it fails, create minimal history
|
562 |
+
try:
|
563 |
+
history_df = interactions.select(
|
564 |
+
turn=interactions.turn_number,
|
565 |
+
action=interactions.player_input,
|
566 |
+
response=interactions.story_text
|
567 |
+
).where(
|
568 |
+
(interactions.session_id == game.current_session_id)
|
569 |
+
).order_by(
|
570 |
+
interactions.turn_number
|
571 |
+
).collect().to_pandas()
|
572 |
+
|
573 |
+
history_data = [
|
574 |
+
[str(row['turn']), row['action'], row['response']]
|
575 |
+
for _, row in history_df.iterrows()
|
576 |
+
]
|
577 |
+
except Exception as e:
|
578 |
+
# If history retrieval fails, create minimal history
|
579 |
+
history_data = [[str(game.turn_number), action_choice, story]]
|
580 |
|
581 |
story_html = f"<div class='story-container'>{story}</div>"
|
582 |
stats_html = f"<div class='stats-container'><h3>π Character Status</h3>{stats}</div>"
|
583 |
|
584 |
return story_html, stats_html, gr.Radio(choices=options, interactive=True), history_data
|
585 |
except Exception as e:
|
586 |
+
# Handle any other unexpected errors
|
587 |
+
fallback = create_fallback_response(action_choice, 1)
|
588 |
return (
|
589 |
+
f"<div class='story-container'>{fallback['story']}</div>",
|
590 |
+
f"<div class='stats-container'><h3>π Character Status</h3>{fallback['stats']}</div>",
|
591 |
+
gr.Radio(choices=fallback['options'], interactive=True),
|
592 |
+
[[str(game.turn_number), action_choice, fallback['story']]]
|
593 |
)
|
594 |
|
595 |
start_button.click(
|