Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -16,51 +16,66 @@ if 'OPENAI_API_KEY' not in os.environ:
|
|
16 |
pxt.drop_dir('ai_rpg', force=True)
|
17 |
pxt.create_dir('ai_rpg')
|
18 |
|
19 |
-
# Create a UDF for generating messages
|
20 |
@pxt.udf
|
21 |
def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int) -> list[dict]:
|
22 |
return [
|
23 |
{
|
24 |
'role': 'system',
|
25 |
-
'content': f"""You are the game master for a {genre} RPG. The player's name is {player_name}.
|
26 |
-
Provide
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
32 |
},
|
33 |
{
|
34 |
'role': 'user',
|
35 |
'content': f"Current scenario: {initial_scenario}\n"
|
36 |
f"Player's action: {player_input}\n"
|
37 |
f"Turn number: {turn_number}\n\n"
|
38 |
-
"Provide
|
39 |
}
|
40 |
]
|
41 |
|
42 |
@pxt.udf
|
43 |
-
def
|
44 |
-
"""Extract the
|
45 |
-
|
46 |
-
|
47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
options = [opt.strip() for opt in options[:3]]
|
|
|
49 |
while len(options) < 3:
|
50 |
options.append("Take another action...")
|
|
|
51 |
return options
|
52 |
|
53 |
# Create a single table for all game data
|
54 |
interactions = pxt.create_table(
|
55 |
'ai_rpg.interactions',
|
56 |
{
|
57 |
-
'session_id': pxt.
|
58 |
-
'player_name': pxt.
|
59 |
-
'genre': pxt.
|
60 |
-
'initial_scenario': pxt.
|
61 |
-
'turn_number': pxt.
|
62 |
-
'player_input': pxt.
|
63 |
-
'timestamp': pxt.
|
64 |
}
|
65 |
)
|
66 |
|
@@ -80,20 +95,20 @@ interactions['ai_response'] = openai.chat_completions(
|
|
80 |
temperature=0.8
|
81 |
)
|
82 |
|
83 |
-
interactions['
|
84 |
-
interactions['
|
|
|
85 |
|
86 |
class RPGGame:
|
87 |
def __init__(self):
|
88 |
self.current_session_id = None
|
89 |
self.turn_number = 0
|
90 |
|
91 |
-
def start_game(self, player_name: str, genre: str, scenario: str) -> str:
|
92 |
session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{player_name}"
|
93 |
self.current_session_id = session_id
|
94 |
self.turn_number = 0
|
95 |
|
96 |
-
# Create initial interaction with all session data
|
97 |
interactions.insert([{
|
98 |
'session_id': session_id,
|
99 |
'player_name': player_name,
|
@@ -104,21 +119,22 @@ class RPGGame:
|
|
104 |
'timestamp': datetime.now()
|
105 |
}])
|
106 |
|
107 |
-
|
108 |
-
|
|
|
|
|
109 |
(interactions.session_id == session_id) &
|
110 |
(interactions.turn_number == 0)
|
111 |
-
).collect()
|
112 |
|
113 |
-
return session_id,
|
114 |
|
115 |
-
def process_action(self, action: str) -> str:
|
116 |
if not self.current_session_id:
|
117 |
-
return "No active game session. Please start a new game."
|
118 |
|
119 |
self.turn_number += 1
|
120 |
|
121 |
-
# Get session info from previous turn
|
122 |
prev_turn = interactions.select(
|
123 |
interactions.player_name,
|
124 |
interactions.genre,
|
@@ -128,7 +144,6 @@ class RPGGame:
|
|
128 |
(interactions.turn_number == 0)
|
129 |
).collect()
|
130 |
|
131 |
-
# Record player action with session data
|
132 |
interactions.insert([{
|
133 |
'session_id': self.current_session_id,
|
134 |
'player_name': prev_turn['player_name'][0],
|
@@ -139,19 +154,20 @@ class RPGGame:
|
|
139 |
'timestamp': datetime.now()
|
140 |
}])
|
141 |
|
142 |
-
|
143 |
-
|
|
|
|
|
144 |
(interactions.session_id == self.current_session_id) &
|
145 |
(interactions.turn_number == self.turn_number)
|
146 |
-
).collect()
|
147 |
|
148 |
-
return
|
149 |
|
150 |
def create_interface():
|
151 |
game = RPGGame()
|
152 |
|
153 |
with gr.Blocks(theme=gr.themes.Base()) as demo:
|
154 |
-
# Header with title and description
|
155 |
gr.Markdown(
|
156 |
"""
|
157 |
<div style="margin-bottom: 20px;">
|
@@ -161,7 +177,6 @@ def create_interface():
|
|
161 |
"""
|
162 |
)
|
163 |
|
164 |
-
# Side-by-side accordions
|
165 |
with gr.Row():
|
166 |
with gr.Column():
|
167 |
with gr.Accordion("🎯 What does it do?", open=False):
|
@@ -171,7 +186,6 @@ def create_interface():
|
|
171 |
- 🎮 Creates dynamic, AI-driven interactive stories
|
172 |
- 🔄 Maintains game state and history using Pixeltable tables
|
173 |
- 💭 Generates contextual options based on player actions
|
174 |
-
- 📝 Processes natural language inputs for custom actions
|
175 |
- 🤖 Uses LLMs to create engaging narratives
|
176 |
- 📊 Tracks and displays game progression
|
177 |
|
@@ -196,12 +210,11 @@ def create_interface():
|
|
196 |
4. ⚙️ **Custom Processing**: Uses Pixeltable UDFs to handle game
|
197 |
logic and AI prompt generation
|
198 |
|
199 |
-
5. 🎯 **Interactive Flow**: Processes player
|
200 |
contextual responses in real-time
|
201 |
""")
|
202 |
|
203 |
with gr.Row():
|
204 |
-
# Setup column
|
205 |
with gr.Column():
|
206 |
player_name = gr.Textbox(
|
207 |
label="👤 Your Character's Name",
|
@@ -226,7 +239,6 @@ def create_interface():
|
|
226 |
)
|
227 |
start_button = gr.Button("🎮 Begin Adventure", variant="primary")
|
228 |
|
229 |
-
# Game interaction column
|
230 |
with gr.Column():
|
231 |
story_display = gr.Textbox(
|
232 |
label="📜 Story",
|
@@ -236,21 +248,13 @@ def create_interface():
|
|
236 |
|
237 |
gr.Markdown("### 🎯 Choose Your Action")
|
238 |
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
custom_action = gr.Textbox(
|
247 |
-
label="✨ Custom Action",
|
248 |
-
placeholder="Write your own action here...",
|
249 |
-
lines=2
|
250 |
-
)
|
251 |
-
submit_action = gr.Button("⚡ Take Action", variant="secondary")
|
252 |
-
|
253 |
-
# Example scenarios
|
254 |
gr.Markdown("### 💫 Example Adventures")
|
255 |
gr.Examples(
|
256 |
examples=[
|
@@ -261,7 +265,6 @@ def create_interface():
|
|
261 |
inputs=[player_name, genre, scenario]
|
262 |
)
|
263 |
|
264 |
-
# History display
|
265 |
history_df = gr.Dataframe(
|
266 |
headers=["📅 Turn", "🎯 Player Action", "💬 Game Response"],
|
267 |
label="📚 Adventure History",
|
@@ -269,20 +272,14 @@ def create_interface():
|
|
269 |
row_count=5,
|
270 |
col_count=(3, "fixed")
|
271 |
)
|
|
|
272 |
def start_new_game(name, genre_choice, scenario_text):
|
273 |
if not name or not genre_choice or not scenario_text:
|
274 |
-
return "Please fill in all fields before starting.", [],
|
275 |
|
276 |
try:
|
277 |
-
_,
|
278 |
-
|
279 |
-
# Get options from the initial response
|
280 |
-
options = interactions.select(interactions.options).where(
|
281 |
-
(interactions.session_id == game.current_session_id) &
|
282 |
-
(interactions.turn_number == 0)
|
283 |
-
).collect()['options'][0]
|
284 |
|
285 |
-
# Get initial history
|
286 |
history_df = interactions.select(
|
287 |
turn=interactions.turn_number,
|
288 |
action=interactions.player_input,
|
@@ -298,26 +295,17 @@ def create_interface():
|
|
298 |
for _, row in history_df.iterrows()
|
299 |
]
|
300 |
|
301 |
-
return
|
302 |
except Exception as e:
|
303 |
-
return f"Error starting game: {str(e)}", [],
|
304 |
-
|
305 |
-
def process_player_action(action_choice
|
306 |
try:
|
307 |
-
|
308 |
-
|
309 |
-
if not action:
|
310 |
-
return "Please either select an action or write your own.", [], "", []
|
311 |
|
312 |
-
|
313 |
-
|
314 |
-
# Get new options
|
315 |
-
options = interactions.select(interactions.options).where(
|
316 |
-
(interactions.session_id == game.current_session_id) &
|
317 |
-
(interactions.turn_number == game.turn_number)
|
318 |
-
).collect()['options'][0]
|
319 |
|
320 |
-
# Get updated history
|
321 |
history_df = interactions.select(
|
322 |
turn=interactions.turn_number,
|
323 |
action=interactions.player_input,
|
@@ -333,25 +321,22 @@ def create_interface():
|
|
333 |
for _, row in history_df.iterrows()
|
334 |
]
|
335 |
|
336 |
-
return
|
337 |
except Exception as e:
|
338 |
-
return f"Error: {str(e)}", [],
|
339 |
|
340 |
-
# Connect the start button
|
341 |
start_button.click(
|
342 |
start_new_game,
|
343 |
inputs=[player_name, genre, scenario],
|
344 |
-
outputs=[story_display, action_input,
|
345 |
)
|
346 |
|
347 |
-
# Single action submit button
|
348 |
submit_action.click(
|
349 |
process_player_action,
|
350 |
-
inputs=[action_input
|
351 |
-
outputs=[story_display, action_input,
|
352 |
)
|
353 |
|
354 |
-
# Footer with links
|
355 |
gr.HTML(
|
356 |
"""
|
357 |
<div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;">
|
|
|
16 |
pxt.drop_dir('ai_rpg', force=True)
|
17 |
pxt.create_dir('ai_rpg')
|
18 |
|
|
|
19 |
@pxt.udf
|
20 |
def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int) -> list[dict]:
|
21 |
return [
|
22 |
{
|
23 |
'role': 'system',
|
24 |
+
'content': f"""You are the game master for a {genre} RPG. The player's name is {player_name}.
|
25 |
+
Provide your response in two clearly separated sections using exactly this format:
|
26 |
+
|
27 |
+
STORY: [Your engaging narrative response to the player's action]
|
28 |
+
|
29 |
+
OPTIONS:
|
30 |
+
1. [A dialogue option]
|
31 |
+
2. [A random action they could take]
|
32 |
+
3. [A unique or unexpected choice]"""
|
33 |
},
|
34 |
{
|
35 |
'role': 'user',
|
36 |
'content': f"Current scenario: {initial_scenario}\n"
|
37 |
f"Player's action: {player_input}\n"
|
38 |
f"Turn number: {turn_number}\n\n"
|
39 |
+
"Provide the story response and options:"
|
40 |
}
|
41 |
]
|
42 |
|
43 |
@pxt.udf
|
44 |
+
def get_story(response: str) -> str:
|
45 |
+
"""Extract just the story part from the response"""
|
46 |
+
parts = response.split("OPTIONS:")
|
47 |
+
if len(parts) != 2:
|
48 |
+
return response
|
49 |
+
|
50 |
+
story = parts[0].replace("STORY:", "").strip()
|
51 |
+
return story
|
52 |
+
|
53 |
+
@pxt.udf
|
54 |
+
def get_options(response: str) -> list[str]:
|
55 |
+
"""Extract the options from the response"""
|
56 |
+
parts = response.split("OPTIONS:")
|
57 |
+
if len(parts) != 2:
|
58 |
+
return ["Continue...", "Take another action", "Try something else"]
|
59 |
+
|
60 |
+
options = re.findall(r'\d\.\s*(.*?)(?=\d\.|$)', parts[1], re.DOTALL)
|
61 |
options = [opt.strip() for opt in options[:3]]
|
62 |
+
|
63 |
while len(options) < 3:
|
64 |
options.append("Take another action...")
|
65 |
+
|
66 |
return options
|
67 |
|
68 |
# Create a single table for all game data
|
69 |
interactions = pxt.create_table(
|
70 |
'ai_rpg.interactions',
|
71 |
{
|
72 |
+
'session_id': pxt.String,
|
73 |
+
'player_name': pxt.String,
|
74 |
+
'genre': pxt.String,
|
75 |
+
'initial_scenario': pxt.String,
|
76 |
+
'turn_number': pxt.Int,
|
77 |
+
'player_input': pxt.String,
|
78 |
+
'timestamp': pxt.Timestamp,
|
79 |
}
|
80 |
)
|
81 |
|
|
|
95 |
temperature=0.8
|
96 |
)
|
97 |
|
98 |
+
interactions['full_response'] = interactions.ai_response.choices[0].message.content
|
99 |
+
interactions['story_text'] = get_story(interactions.full_response)
|
100 |
+
interactions['options'] = get_options(interactions.full_response)
|
101 |
|
102 |
class RPGGame:
|
103 |
def __init__(self):
|
104 |
self.current_session_id = None
|
105 |
self.turn_number = 0
|
106 |
|
107 |
+
def start_game(self, player_name: str, genre: str, scenario: str) -> tuple[str, str, list[str]]:
|
108 |
session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{player_name}"
|
109 |
self.current_session_id = session_id
|
110 |
self.turn_number = 0
|
111 |
|
|
|
112 |
interactions.insert([{
|
113 |
'session_id': session_id,
|
114 |
'player_name': player_name,
|
|
|
119 |
'timestamp': datetime.now()
|
120 |
}])
|
121 |
|
122 |
+
result = interactions.select(
|
123 |
+
interactions.story_text,
|
124 |
+
interactions.options
|
125 |
+
).where(
|
126 |
(interactions.session_id == session_id) &
|
127 |
(interactions.turn_number == 0)
|
128 |
+
).collect()
|
129 |
|
130 |
+
return session_id, result['story_text'][0], result['options'][0]
|
131 |
|
132 |
+
def process_action(self, action: str) -> tuple[str, list[str]]:
|
133 |
if not self.current_session_id:
|
134 |
+
return "No active game session. Please start a new game.", []
|
135 |
|
136 |
self.turn_number += 1
|
137 |
|
|
|
138 |
prev_turn = interactions.select(
|
139 |
interactions.player_name,
|
140 |
interactions.genre,
|
|
|
144 |
(interactions.turn_number == 0)
|
145 |
).collect()
|
146 |
|
|
|
147 |
interactions.insert([{
|
148 |
'session_id': self.current_session_id,
|
149 |
'player_name': prev_turn['player_name'][0],
|
|
|
154 |
'timestamp': datetime.now()
|
155 |
}])
|
156 |
|
157 |
+
result = interactions.select(
|
158 |
+
interactions.story_text,
|
159 |
+
interactions.options
|
160 |
+
).where(
|
161 |
(interactions.session_id == self.current_session_id) &
|
162 |
(interactions.turn_number == self.turn_number)
|
163 |
+
).collect()
|
164 |
|
165 |
+
return result['story_text'][0], result['options'][0]
|
166 |
|
167 |
def create_interface():
|
168 |
game = RPGGame()
|
169 |
|
170 |
with gr.Blocks(theme=gr.themes.Base()) as demo:
|
|
|
171 |
gr.Markdown(
|
172 |
"""
|
173 |
<div style="margin-bottom: 20px;">
|
|
|
177 |
"""
|
178 |
)
|
179 |
|
|
|
180 |
with gr.Row():
|
181 |
with gr.Column():
|
182 |
with gr.Accordion("🎯 What does it do?", open=False):
|
|
|
186 |
- 🎮 Creates dynamic, AI-driven interactive stories
|
187 |
- 🔄 Maintains game state and history using Pixeltable tables
|
188 |
- 💭 Generates contextual options based on player actions
|
|
|
189 |
- 🤖 Uses LLMs to create engaging narratives
|
190 |
- 📊 Tracks and displays game progression
|
191 |
|
|
|
210 |
4. ⚙️ **Custom Processing**: Uses Pixeltable UDFs to handle game
|
211 |
logic and AI prompt generation
|
212 |
|
213 |
+
5. 🎯 **Interactive Flow**: Processes player choices and generates
|
214 |
contextual responses in real-time
|
215 |
""")
|
216 |
|
217 |
with gr.Row():
|
|
|
218 |
with gr.Column():
|
219 |
player_name = gr.Textbox(
|
220 |
label="👤 Your Character's Name",
|
|
|
239 |
)
|
240 |
start_button = gr.Button("🎮 Begin Adventure", variant="primary")
|
241 |
|
|
|
242 |
with gr.Column():
|
243 |
story_display = gr.Textbox(
|
244 |
label="📜 Story",
|
|
|
248 |
|
249 |
gr.Markdown("### 🎯 Choose Your Action")
|
250 |
|
251 |
+
action_input = gr.Radio(
|
252 |
+
choices=[],
|
253 |
+
label="🎲 Select your next action:",
|
254 |
+
interactive=True
|
255 |
+
)
|
256 |
+
submit_action = gr.Button("⚡ Take Action", variant="secondary")
|
257 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
258 |
gr.Markdown("### 💫 Example Adventures")
|
259 |
gr.Examples(
|
260 |
examples=[
|
|
|
265 |
inputs=[player_name, genre, scenario]
|
266 |
)
|
267 |
|
|
|
268 |
history_df = gr.Dataframe(
|
269 |
headers=["📅 Turn", "🎯 Player Action", "💬 Game Response"],
|
270 |
label="📚 Adventure History",
|
|
|
272 |
row_count=5,
|
273 |
col_count=(3, "fixed")
|
274 |
)
|
275 |
+
|
276 |
def start_new_game(name, genre_choice, scenario_text):
|
277 |
if not name or not genre_choice or not scenario_text:
|
278 |
+
return "Please fill in all fields before starting.", [], []
|
279 |
|
280 |
try:
|
281 |
+
_, initial_story, initial_options = game.start_game(name, genre_choice, scenario_text)
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
|
|
|
283 |
history_df = interactions.select(
|
284 |
turn=interactions.turn_number,
|
285 |
action=interactions.player_input,
|
|
|
295 |
for _, row in history_df.iterrows()
|
296 |
]
|
297 |
|
298 |
+
return initial_story, gr.Radio(choices=initial_options, interactive=True), history_data
|
299 |
except Exception as e:
|
300 |
+
return f"Error starting game: {str(e)}", [], []
|
301 |
+
|
302 |
+
def process_player_action(action_choice):
|
303 |
try:
|
304 |
+
if not action_choice:
|
305 |
+
return "Please select an action to continue.", [], []
|
|
|
|
|
306 |
|
307 |
+
story, options = game.process_action(action_choice)
|
|
|
|
|
|
|
|
|
|
|
|
|
308 |
|
|
|
309 |
history_df = interactions.select(
|
310 |
turn=interactions.turn_number,
|
311 |
action=interactions.player_input,
|
|
|
321 |
for _, row in history_df.iterrows()
|
322 |
]
|
323 |
|
324 |
+
return story, gr.Radio(choices=options, interactive=True), history_data
|
325 |
except Exception as e:
|
326 |
+
return f"Error: {str(e)}", [], []
|
327 |
|
|
|
328 |
start_button.click(
|
329 |
start_new_game,
|
330 |
inputs=[player_name, genre, scenario],
|
331 |
+
outputs=[story_display, action_input, history_df]
|
332 |
)
|
333 |
|
|
|
334 |
submit_action.click(
|
335 |
process_player_action,
|
336 |
+
inputs=[action_input],
|
337 |
+
outputs=[story_display, action_input, history_df]
|
338 |
)
|
339 |
|
|
|
340 |
gr.HTML(
|
341 |
"""
|
342 |
<div style="margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;">
|