Spaces:
Running
Running
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(
|