PierreBrunelle commited on
Commit
fb4c328
·
verified ·
1 Parent(s): ad043e2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -92
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 an engaging response to the player's action and present exactly 3 numbered options for their next move:
27
- 1. A dialogue option (saying something)
28
- 2. A random action they could take
29
- 3. A unique or unexpected choice
30
-
31
- Format each option with a number (1., 2., 3.) followed by the action description."""
 
 
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 a response and 3 options:"
39
  }
40
  ]
41
 
42
  @pxt.udf
43
- def extract_options(story_text: str) -> list[str]:
44
- """Extract the three options from the story text"""
45
- # Look for numbered options (1., 2., 3.) and grab the text after them
46
- options = re.findall(r'\d\.\s*(.*?)(?=\d\.|$)', story_text, re.DOTALL)
47
- # Clean up the options and ensure we have exactly 3
 
 
 
 
 
 
 
 
 
 
 
 
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.StringType(),
58
- 'player_name': pxt.StringType(),
59
- 'genre': pxt.StringType(),
60
- 'initial_scenario': pxt.StringType(),
61
- 'turn_number': pxt.IntType(),
62
- 'player_input': pxt.StringType(),
63
- 'timestamp': pxt.TimestampType(),
64
  }
65
  )
66
 
@@ -80,20 +95,20 @@ interactions['ai_response'] = openai.chat_completions(
80
  temperature=0.8
81
  )
82
 
83
- interactions['story_text'] = interactions.ai_response.choices[0].message.content
84
- interactions['options'] = extract_options(interactions.story_text)
 
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
- # Get initial story and options
108
- initial_response = interactions.select(interactions.story_text).where(
 
 
109
  (interactions.session_id == session_id) &
110
  (interactions.turn_number == 0)
111
- ).collect()['story_text'][0]
112
 
113
- return session_id, initial_response
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
- # Get AI response
143
- response = interactions.select(interactions.story_text).where(
 
 
144
  (interactions.session_id == self.current_session_id) &
145
  (interactions.turn_number == self.turn_number)
146
- ).collect()['story_text'][0]
147
 
148
- return response
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 inputs and generates
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
- with gr.Row():
240
- with gr.Column():
241
- action_input = gr.Radio(
242
- choices=[],
243
- label="🎲 Select an action or write your own below:",
244
- interactive=True
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
- _, initial_response = game.start_game(name, genre_choice, scenario_text)
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 initial_response, gr.Radio(choices=options, interactive=True), "", history_data
302
  except Exception as e:
303
- return f"Error starting game: {str(e)}", [], "", []
304
-
305
- def process_player_action(action_choice, custom_action):
306
  try:
307
- # Use custom action if provided, otherwise use selected choice
308
- action = custom_action if custom_action else action_choice
309
- if not action:
310
- return "Please either select an action or write your own.", [], "", []
311
 
312
- response = game.process_action(action)
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 response, gr.Radio(choices=options, interactive=True), "", history_data
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, custom_action, history_df]
345
  )
346
 
347
- # Single action submit button
348
  submit_action.click(
349
  process_player_action,
350
- inputs=[action_input, custom_action],
351
- outputs=[story_display, action_input, custom_action, history_df]
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;">