openfree commited on
Commit
5679cde
ยท
verified ยท
1 Parent(s): 8b0b9e4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +201 -159
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
- # ์ผ๋ฐ˜ ํ•จ์ˆ˜๋กœ ๋ณ€๊ฒฝ (UDF๊ฐ€ ์•„๋‹˜)
21
  def initialize_stats(genre: str) -> str:
22
  """Initialize player stats based on the selected genre"""
23
  base_stats = {
24
- "๐Ÿง™โ€โ™‚๏ธ Fantasy": "์ฒด๋ ฅ: 100, ๋งˆ๋‚˜: 80, ํž˜: 7, ์ง€๋Šฅ: 8, ๋ฏผ์ฒฉ: 6, ์†Œ์ง€๊ธˆ: 50๊ณจ๋“œ",
25
- "๐Ÿš€ Sci-Fi": "์ฒด๋ ฅ: 100, ์—๋„ˆ์ง€: 90, ๊ธฐ์ˆ ๋ ฅ: 8, ์ง€๋Šฅ: 9, ๋ฏผ์ฒฉ: 6, ํฌ๋ ˆ๋”ง: 500",
26
- "๐Ÿ‘ป Horror": "์ฒด๋ ฅ: 80, ์ •์‹ ๋ ฅ: 100, ํž˜: 6, ์ง€๋Šฅ: 7, ๋ฏผ์ฒฉ: 8, ์†Œ์ง€ํ’ˆ: ์†์ „๋“ฑ, ๊ธฐ๋ณธ ์•ฝํ’ˆ",
27
- "๐Ÿ” Mystery": "์ฒด๋ ฅ: 90, ์ง‘์ค‘๋ ฅ: 100, ๊ด€์ฐฐ๋ ฅ: 9, ์ง€๋Šฅ: 8, ์นด๋ฆฌ์Šค๋งˆ: 7, ๋‹จ์„œ: 0",
28
- "๐ŸŒ‹ Post-Apocalyptic": "์ฒด๋ ฅ: 95, ๋ฐฉ์‚ฌ๋Šฅ ์ €ํ•ญ: 75, ํž˜: 8, ์ƒ์กด๋ ฅ: 9, ๋ฌผ์ž: ์ œํ•œ๋จ",
29
- "๐Ÿค– Cyberpunk": "์ฒด๋ ฅ: 90, ์‚ฌ์ด๋ฒ„์›จ์–ด: 85%, ํ•ดํ‚น: 8, ๊ฑฐ๋ฆฌ ์‹ ์šฉ๋„: 6, ์—ฃ์ง€: 7, ๋ˆ„์—”: 1000",
30
- "โš™๏ธ Steampunk": "์ฒด๋ ฅ: 95, ์ฆ๊ธฐ๋ ฅ: 85, ๊ธฐ๊ณ„๊ณตํ•™: 8, ์˜ˆ์ˆ ์„ฑ: 7, ์‚ฌ๊ต์„ฑ: 6, ์‹ค๋ง: 200"
31
  }
32
 
33
  if genre in base_stats:
34
  return base_stats[genre]
35
  else:
36
  # Default stats if genre not found
37
- return "์ฒด๋ ฅ: 100, ์—๋„ˆ์ง€: 100, ํž˜: 7, ์ง€๋Šฅ: 7, ๋ฏผ์ฒฉ: 7, ์†Œ์ง€๊ธˆ: 100"
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"""๋ฐ˜๋“œ์‹œ ํ•œ๊ตญ์–ด(ํ•œ๊ธ€)๋กœ ์ž‘์„ฑํ•˜๋ผ. You are the game master for a {genre} RPG. The player's name is {player_name}.
61
 
62
- ๊ด€๋ฆฌํ•ด์•ผ ํ•  ํ”Œ๋ ˆ์ด์–ด ์Šคํƒฏ: {stats}
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-4.1-mini',
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
- interactions.insert([{
192
- 'session_id': session_id,
193
- 'player_name': player_name,
194
- 'genre': genre,
195
- 'initial_scenario': scenario,
196
- 'turn_number': 0,
197
- 'player_input': "Game starts",
198
- 'timestamp': datetime.now(),
199
- 'player_stats': initial_stats, # ๋ฌธ์ž์—ด ๊ฒฐ๊ณผ ์ €์žฅ
200
- 'random_event': ""
201
- }])
202
-
203
- result = interactions.select(
204
- interactions.story_text,
205
- interactions.stats_update,
206
- interactions.options
207
- ).where(
208
- (interactions.session_id == session_id) &
209
- (interactions.turn_number == 0)
210
- ).collect()
211
-
212
- return session_id, result['story_text'][0], result['stats_update'][0], result['options'][0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- prev_turn = interactions.select(
221
- interactions.player_name,
222
- interactions.genre,
223
- interactions.initial_scenario,
224
- interactions.player_stats
225
- ).where(
226
- (interactions.session_id == self.current_session_id) &
227
- (interactions.turn_number == self.turn_number - 1)
228
- ).collect()
229
-
230
- self.current_stats = prev_turn['player_stats'][0]
231
-
232
- # ์ผ๋ฐ˜ ํ•จ์ˆ˜๋กœ ๋ณ€ํ™˜ํ–ˆ์œผ๋ฏ€๋กœ ๊ทธ๋ƒฅ ํ˜ธ์ถœํ•ด์„œ ์‚ฌ์šฉ
233
- random_event_val = ""
234
- if self.turn_number % 3 == 0 and self.turn_number > 0:
235
- events = [
236
- "๊ฐ‘์ž๊ธฐ ๋ถ€๊ทผ์—์„œ ์ด์ƒํ•œ ์†Œ๋ฆฌ๊ฐ€ ๋“ค๋ฆฝ๋‹ˆ๋‹ค",
237
- "๋‚ฏ์„  ์—ฌํ–‰์ž๊ฐ€ ๋‹น์‹ ์„ ๋ฐ”๋ผ๋ณด๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค",
238
- "์ง€๋ฉด์ด ๋ฏธ์„ธํ•˜๊ฒŒ ์ง„๋™ํ•˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค",
239
- "์ฃผ๋จธ๋‹ˆ์—์„œ ๋ฌด์–ธ๊ฐ€๊ฐ€ ๋น›๋‚ฉ๋‹ˆ๋‹ค",
240
- "๋ฉ€๋ฆฌ์„œ ๋ฌด์–ธ๊ฐ€๊ฐ€ ๋‹น์‹ ์„ ํ–ฅํ•ด ๋‹ค๊ฐ€์˜ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค",
241
- "๊ฐ‘์ž๊ธฐ ๋‚ ์”จ๊ฐ€ ๋ณ€ํ•˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค",
242
- "์ฃผ๋ณ€์— ์ˆจ๊ฒจ์ง„ ํ†ต๋กœ๋ฅผ ๋ฐœ๊ฒฌํ•ฉ๋‹ˆ๋‹ค"
243
- ]
244
- random_event_val = random.choice(events)
245
-
246
- if random_event_val:
247
- action = f"{action} ({random_event_val})"
248
-
249
- interactions.insert([{
250
- 'session_id': self.current_session_id,
251
- 'player_name': prev_turn['player_name'][0],
252
- 'genre': prev_turn['genre'][0],
253
- 'initial_scenario': prev_turn['initial_scenario'][0],
254
- 'turn_number': self.turn_number,
255
- 'player_input': action,
256
- 'timestamp': datetime.now(),
257
- 'player_stats': self.current_stats,
258
- 'random_event': random_event_val
259
- }])
260
-
261
- result = interactions.select(
262
- interactions.story_text,
263
- interactions.stats_update,
264
- interactions.options
265
- ).where(
266
- (interactions.session_id == self.current_session_id) &
267
- (interactions.turn_number == self.turn_number)
268
- ).collect()
269
-
270
- # Update stats for next turn
271
- self.current_stats = result['stats_update'][0]
272
-
273
- return result['story_text'][0], result['stats_update'][0], result['options'][0]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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: 'Noto Sans KR', sans-serif;
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 ์–ด๋“œ๋ฒค์ฒ˜</h1>
353
- <p style="font-size: 1.2em;">Pixeltable๊ณผ OpenAI๋กœ ๊ตฌํ˜„๋œ ๋ชฐ์ž…ํ˜• ๋กคํ”Œ๋ ˆ์ž‰ ๊ฒŒ์ž„ ๊ฒฝํ—˜!</p>
354
  </div>
355
  """
356
  )
357
 
358
  with gr.Row():
359
  with gr.Column(scale=1):
360
- with gr.Accordion("๐ŸŽฏ ์ด ์•ฑ์€ ๋ฌด์—‡์ธ๊ฐ€์š”?", open=False):
361
  gr.HTML(
362
  """
363
  <div style="padding: 15px;">
364
- <h3>AI RPG ์–ด๋“œ๋ฒค์ฒ˜๋Š” ๋‹ค์Œ ๊ธฐ๋Šฅ์„ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค:</h3>
365
  <ul style="list-style-type: none; padding-left: 5px;">
366
- <li>๐ŸŽฎ <b>๋™์  ์Šคํ† ๋ฆฌํ…”๋ง:</b> AI๊ฐ€ ์ƒ์„ฑํ•˜๋Š” ๋ชฐ์ž…ํ˜• ์ด์•ผ๊ธฐ ๊ฒฝํ—˜</li>
367
- <li>๐Ÿ”„ <b>๊ฒŒ์ž„ ์ƒํƒœ ๊ด€๋ฆฌ:</b> Pixeltable๋กœ ๊ฒŒ์ž„ ์ƒํƒœ์™€ ๊ธฐ๋ก ์ถ”์ </li>
368
- <li>๐Ÿ’ญ <b>์ปจํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ์„ ํƒ์ง€:</b> ํ”Œ๋ ˆ์ด์–ด ํ–‰๋™์— ๋”ฐ๋ฅธ ๋งž์ถคํ˜• ์˜ต์…˜</li>
369
- <li>๐Ÿค– <b>AI ์Šคํ† ๋ฆฌํ…”๋ง:</b> ์ƒ์ƒํ•œ ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ ์ƒ์„ฑ</li>
370
- <li>๐Ÿ“Š <b>์บ๋ฆญํ„ฐ ์ƒํƒœ ์ถ”์ :</b> ๊ฒŒ์ž„ ์ง„ํ–‰์— ๋”ฐ๋ฅธ ์Šคํƒฏ ๋ณ€ํ™”</li>
371
  </ul>
372
  </div>
373
  """
374
  )
375
 
376
- with gr.Accordion("๐ŸŽจ ์บ๋ฆญํ„ฐ ์ƒ์„ฑ", open=True):
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("๐ŸŽฎ ๋ชจํ—˜ ์‹œ์ž‘!", variant="primary")
403
 
404
  with gr.Column(scale=2):
405
  story_display = gr.Markdown(
406
- label="๐Ÿ“œ ์Šคํ† ๋ฆฌ",
407
- value="<div class='story-container'>๋ชจํ—˜์„ ์‹œ์ž‘ํ•˜๋ ค๋ฉด ์บ๋ฆญํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  '๋ชจํ—˜ ์‹œ์ž‘!' ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์„ธ์š”.</div>",
408
  show_label=False
409
  )
410
 
411
  stats_display = gr.Markdown(
412
- label="๐Ÿ“Š ์บ๋ฆญํ„ฐ ์Šคํƒฏ",
413
- value="<div class='stats-container'>๋ชจํ—˜์„ ์‹œ์ž‘ํ•˜๋ฉด ์บ๋ฆญํ„ฐ ์Šคํƒฏ์ด ์ด๊ณณ์— ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.</div>",
414
  show_label=False
415
  )
416
 
417
- gr.HTML("<div class='options-container'><h3>๐ŸŽฏ ๋‹ค์Œ ํ–‰๋™ ์„ ํƒ</h3></div>")
418
 
419
  action_input = gr.Radio(
420
  choices=[],
421
  label="",
422
  interactive=True
423
  )
424
- submit_action = gr.Button("โšก ํ–‰๋™ ์‹คํ–‰", variant="primary")
425
 
426
  with gr.Row():
427
  with gr.Column():
428
- gr.HTML("<div class='history-container'><h3>๐Ÿ’ซ ์–ด๋“œ๋ฒค์ฒ˜ ์˜ˆ์‹œ</h3></div>")
429
  gr.Examples(
430
  examples=[
431
- ["์ด์ˆœ์‹ ", "๐Ÿง™โ€โ™‚๏ธ Fantasy", "๋‹น์‹ ์€ ์žŠํ˜€์ง„ ์‹ ๋น„ํ•œ ์ˆฒ์˜ ๊ฐ€์žฅ์ž๋ฆฌ์—์„œ ๋ˆˆ์„ ๋œน๋‹ˆ๋‹ค. ๋ฉ€๋ฆฌ์„œ ์„ฑ์˜ ์ฒจํƒ‘์ด ๋ณด์ด๊ณ , ๋‹น์‹ ์˜ ๋จธ๋ฆฌ์†์—๋Š” ์™•๊ตญ์„ ์œ„ํ˜‘ํ•˜๋Š” ๊ณ ๋Œ€ ๋งˆ๋ฒ•์— ๊ด€ํ•œ ๋‹จ์„œ๋งŒ์ด ๋‚จ์•„์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ‘์ž๊ธฐ ์ˆฒ์—์„œ ์ด์ƒํ•œ ๋น›์ด ๋ณด์ž…๋‹ˆ๋‹ค..."],
432
- ["๊น€์ง€์˜", "๐Ÿš€ Sci-Fi", "์šฐ์ฃผ์„  'ํ˜ธ๋ผ์ด์ฆŒ'์˜ ํ•ญํ•ด์‚ฌ๋กœ์„œ, ๋‹น์‹ ์€ ๋ฏธ์ง€์˜ ํ–‰์„ฑ ํƒ์‚ฌ ์ค‘ ๋น„์ƒ ์•Œ๋žŒ์— ๊นจ์–ด๋‚ฉ๋‹ˆ๋‹ค. ์„ ์žฅ๊ณผ ์—ฐ๋ฝ์ด ๋‘์ ˆ๋˜์—ˆ๊ณ , ์ƒ๋ช… ์œ ์ง€ ์‹œ์Šคํ…œ์ด ์ ์ฐจ ์‹คํŒจํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์กฐ์šฉํ•œ ์„ ๋‚ด์—์„œ ์ด์ƒํ•œ ๋ฐœ๊ฑธ์Œ ์†Œ๋ฆฌ๊ฐ€ ๋“ค๋ฆฝ๋‹ˆ๋‹ค..."],
433
- ["์ผ๋ก  ๋จธ์Šคํฌ", "๐Ÿค– Cyberpunk", "2077๋…„ ์„œ์šธ, ๋‹น์‹ ์€ ๋‰ด๋Ÿด๋งํฌ ์ธ๋”์ŠคํŠธ๋ฆฌ์˜ CEO์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์ตœ์‹  ๋‡Œ-์ปดํ“จํ„ฐ ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ์ˆ ์ด ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋Šฅ๋ ฅ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์‹œ์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค - ๊ทธ๋“ค์˜ ์ง‘์•ˆ ์‹๋ฌผ๊ณผ ๊ต๊ฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ฃผ์š” ํˆฌ์ž์ž ํ”„๋ ˆ์  ํ…Œ์ด์…˜์„ ์ค€๋น„ํ•˜๋Š” ๋™์•ˆ, AI ๋น„์„œ๊ฐ€ ํ…Œ์ŠคํŠธ ์ฐธ๊ฐ€์ž๋“ค์ด ๋ถˆ๊ฐ€์‚ฌ์˜ํ•œ '์‹๋ฌผ ํ˜๋ช…'์„ ์กฐ์งํ•˜๊ณ  ์žˆ๋‹ค๋Š” ๋ณด๊ณ ๋ฅผ ์ „ํ•ฉ๋‹ˆ๋‹ค..."],
434
- ["๊ณ ๋“  ๋žจ์ง€", "๐ŸŒ‹ Post-Apocalyptic", "๋‹น์‹ ์€ ๋‰ด ์„œ์šธ์˜ ๋งˆ์ง€๋ง‰ ๋งˆ์Šคํ„ฐ ์…ฐํ”„๋กœ, ์˜› ๋Ÿญ์…”๋ฆฌ ํ˜ธํ…” ํํ—ˆ์—์„œ ์ง€ํ•˜ ๋ ˆ์Šคํ† ๋ž‘์„ ์šด์˜ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์š”๋ฆฌ๋Š” ์œ„ํ—˜ํ•œ ๋ฐฉ์‚ฌ๋Šฅ ๊ตฌ์—ญ์—์„œ๋งŒ ์ž๋ผ๋Š” ํฌ๊ท€ ๋ฒ„์„ฏ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์˜ค๋Š˜ ๋ฐค์˜ ๋น„๋ฐ€ ๋ชจ์ž„์„ ์ค€๋น„ํ•˜๋˜ ์ค‘, ์ •์ฐฐ๋ณ‘์ด ์ฃผ๋ณ€ ์ง€์—ญ์˜ ๊ฒฝ์Ÿ ์š”๋ฆฌ์‚ฌ ๊ฐฑ๋‹จ์— ๊ด€ํ•œ ๋ถˆ๊ธธํ•œ ์†Œ์‹์„ ๊ฐ€์ง€๊ณ  ๋Œ์•„์˜ต๋‹ˆ๋‹ค..."],
435
- ["์œ ์ •ํ˜ธ", "๐Ÿ‘ป Horror", "๋‹น์‹ ์€ ์นœ๊ตฌ์˜ ์ดˆ๋Œ€๋กœ ์‚ผ๋ฆผ ์† ์™ธ๋”ด ๋ณ„์žฅ์— ์ฃผ๋ง์„ ๋ณด๋‚ด๋Ÿฌ ์™”์Šต๋‹ˆ๋‹ค. ์ฒซ๋‚  ๋ฐค, ์ฐฝ๋ฐ–์œผ๋กœ ๋ณด์ด๋Š” ๊ธฐ์ดํ•œ ๋น›์— ์ด๋Œ๋ ค ์ˆฒ์œผ๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋Œ์•„์˜ค๋Š” ๊ธธ์„ ์ฐพ์œผ๋ ค ํ•˜์ง€๋งŒ, ๋ณ„์žฅ์ด ๋ณด์ด์ง€ ์•Š๊ณ  ๋‚ฏ์„  ์•ˆ๊ฐœ๊ฐ€ ์ ์  ์ง™์–ด์ง‘๋‹ˆ๋‹ค. ๋ฉ€๋ฆฌ์„œ ๋ˆ„๊ตฐ๊ฐ€โ€”์•„๋‹ˆ, ๋ฌด์–ธ๊ฐ€๊ฐ€ ๋‹น์‹ ์„ ๋ถ€๋ฅด๋Š” ์†Œ๋ฆฌ๊ฐ€ ๋“ค๋ฆฝ๋‹ˆ๋‹ค..."],
436
- ["๋ฐ•์ง€ํ›ˆ", "๐Ÿ” Mystery", "์‹ ์ž„ ํ˜•์‚ฌ๋กœ์„œ ๋‹น์‹ ์˜ ์ฒซ ์‚ฌ๊ฑด์€ ๋„์‹œ ์ตœ๊ณ ์˜ ๊ธฐ์ˆ  ๊ธฐ์—… CEO์˜ ์˜๋ฌธ์˜ ์‹ค์ข…์ž…๋‹ˆ๋‹ค. ๊ทธ์˜ ์‚ฌ๋ฌด์‹ค์—๋Š” ํ˜ˆํ”์ด ์—†๊ณ , ์œ ์ผํ•œ ๋‹จ์„œ๋Š” ์ฑ…์ƒ ์œ„์— ๋†“์ธ ์•”ํ˜ธํ™”๋œ ๋ฉ”๋ชจ์™€ ๊บผ์ ธ์žˆ๋Š” ๊ทธ์˜ ์ตœ์ฒจ๋‹จ AI ๋น„์„œ๋ฟ์ž…๋‹ˆ๋‹ค. ์กฐ์‚ฌ๋ฅผ ์‹œ์ž‘ํ•˜์ž๋งˆ์ž, ๋‹น์‹ ์€ CEO๊ฐ€ ๋งˆ์ง€๋ง‰์œผ๋กœ ์ž‘์—…ํ•˜๋˜ ๋น„๋ฐ€ ํ”„๋กœ์ ํŠธ์— ๊ด€ํ•œ ์ด์•ผ๊ธฐ๋ฅผ ๋“ฃ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค..."],
437
- ["์ด๋ฏผ์ˆ˜", "โš™๏ธ Steampunk", "์ฆ๊ธฐ์™€ ๊ธฐ์–ด๋กœ ๊ฐ€๋“ํ•œ ๋‰ด ์กฐ์„ ์—์„œ, ๋‹น์‹ ์€ ํ˜์‹ ์ ์ธ ๋น„ํ–‰์„  ์„ค๊ณ„์ž์ž…๋‹ˆ๋‹ค. ๋‹น์‹ ์˜ ์ตœ์‹  ๋ฐœ๋ช…ํ’ˆ ์‹œ์—ฐ ์ค‘, ์ •๋ถ€์˜ ๋น„๋ฐ€ ์š”์›์ด ์ ‘๊ทผํ•ด ์œ„ํ—˜์— ์ฒ˜ํ•œ ํ™ฉ์‹ค ๊ฐ€์กฑ์„ ์œ„ํ•œ ๋น„๋ฐ€ ์ž„๋ฌด๋ฅผ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค. ์ง€ํ•˜ ๋‹จ์ฒด๋“ค์ด ์™•์ขŒ๋ฅผ ์œ„ํ˜‘ํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ๋‹น์‹ ์˜ ๋ฐœ๋ช…ํ’ˆ์ด ์™•๊ฐ€์˜ ์œ ์ผํ•œ ํฌ๋ง์ด๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค..."]
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'>๋ชจ๋“  ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•œ ํ›„ ์‹œ์ž‘ํ•˜์„ธ์š”.</div>",
455
- "<div class='stats-container'>์Šคํƒฏ ์ •๋ณด ์—†์Œ</div>",
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>๐Ÿ“Š ์บ๋ฆญํ„ฐ ์ƒํƒœ</h3>{initial_stats}</div>"
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'>๊ฒŒ์ž„ ์‹œ์ž‘ ์˜ค๋ฅ˜: {str(e)}</div>",
485
- "<div class='stats-container'>์Šคํƒฏ ์ •๋ณด ์—†์Œ</div>",
486
  [],
487
  []
488
  )
@@ -491,8 +533,8 @@ def create_interface():
491
  try:
492
  if not action_choice:
493
  return (
494
- "<div class='story-container'>๊ณ„์†ํ•˜๋ ค๋ฉด ํ–‰๋™์„ ์„ ํƒํ•˜์„ธ์š”.</div>",
495
- "<div class='stats-container'>์Šคํƒฏ ์ •๋ณด ์—†์Œ</div>",
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>๐Ÿ“Š ์บ๋ฆญํ„ฐ ์ƒํƒœ</h3>{stats}</div>"
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'>์˜ค๋ฅ˜: {str(e)}</div>",
524
- "<div class='stats-container'>์Šคํƒฏ ์ •๋ณด ์—†์Œ</div>",
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 ์–ด๋“œ๋ฒค์ฒ˜ - Pixeltable๋กœ ์ œ์ž‘๋œ ๋ชฐ์ž…ํ˜• ๋กคํ”Œ๋ ˆ์ž‰ ๊ฒฝํ—˜</h3>
544
- <p>์ž์‹ ๋งŒ์˜ ์บ๋ฆญํ„ฐ๋ฅผ ๋งŒ๋“ค๊ณ , ์„ ํƒํ•œ ์žฅ๋ฅด์˜ ์„ธ๊ณ„์—์„œ ๋ชจํ—˜์„ ์ฆ๊ธฐ์„ธ์š”. ๋‹น์‹ ์˜ ์„ ํƒ์ด ์Šคํ† ๋ฆฌ๋ฅผ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค!</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