Spaces:
Running
Running
import gradio as gr | |
import pixeltable as pxt | |
import numpy as np | |
from datetime import datetime | |
from pixeltable.functions.huggingface import sentence_transformer | |
from pixeltable.functions import openai | |
import os | |
import getpass | |
import re | |
import random | |
# Set up OpenAI API key | |
if 'OPENAI_API_KEY' not in os.environ: | |
os.environ['OPENAI_API_KEY'] = getpass.getpass('Enter your OpenAI API key: ') | |
# Initialize Pixeltable | |
pxt.drop_dir('ai_rpg', force=True) | |
pxt.create_dir('ai_rpg') | |
def generate_messages(genre: str, player_name: str, initial_scenario: str, player_input: str, turn_number: int, stats: str) -> list[dict]: | |
return [ | |
{ | |
'role': 'system', | |
'content': f"""๋ฐ๋์ ํ๊ตญ์ด(ํ๊ธ)๋ก ์์ฑํ๋ผ. You are the game master for a {genre} RPG. The player's name is {player_name}. | |
๊ด๋ฆฌํด์ผ ํ ํ๋ ์ด์ด ์คํฏ: {stats} | |
๋น์ ์ ํ๋ ์ด์ด์ ์ ํ์ ๋ฐ๋ผ ์คํ ๋ฆฌ๋ฅผ ์์ํ๊ฒ ์ ๊ฐํ๋ ๊ฒ์ ๋ง์คํฐ์ ๋๋ค. | |
์์ธํ ์ค๋ช ๊ณผ ๊ฐ๊ฐ์ ์ธ ๋ฌ์ฌ๋ฅผ ํตํด ํ๋ ์ด์ด๊ฐ ๊ฒ์ ์ ์ธ๊ณ์ ๋ชฐ์ ํ ์ ์๋๋ก ํ์ธ์. | |
ํ๋ ์ด์ด์ ์ ํ์ ๋ฐ๋ผ ์คํฏ์ด ๋ณํ๋ ๊ฒฝ์ฐ ์ด๋ฅผ ์คํ ๋ฆฌ์ ๋ฐ์ํ์ธ์. | |
์ํํ ์ํฉ, ๋์ , ๋ณด์, ์ฐ์ฐํ ๋ง๋จ์ด ํฌํจ๋ ํฅ๋ฏธ๋ก์ด ์คํ ๋ฆฌ๋ฅผ ๋ง๋์ธ์. | |
Provide your response in three clearly separated sections using exactly this format: | |
๐ **STORY**: [Your engaging narrative response to the player's action with vivid descriptions] | |
๐ **STATS UPDATE**: [Brief update on any changes to player stats based on their actions] | |
๐ฏ **OPTIONS**: | |
1. [A dialogue option with potential consequences] | |
2. [An action they could take with different outcomes] | |
3. [A unique or unexpected choice that might lead to adventure] | |
4. [A risky but potentially rewarding option]""" | |
}, | |
{ | |
'role': 'user', | |
'content': f"Current scenario: {initial_scenario}\n" | |
f"Player's action: {player_input}\n" | |
f"Turn number: {turn_number}\n" | |
f"Current player stats: {stats}\n\n" | |
"Provide the story response, stats update, and options:" | |
} | |
] | |
def get_story(response: str) -> str: | |
"""Extract just the story part from the response""" | |
match = re.search(r'๐\s*\*\*STORY\*\*:\s*(.*?)(?=๐\s*\*\*STATS|$)', response, re.DOTALL) | |
if match: | |
return match.group(1).strip() | |
parts = response.split("STATS UPDATE:") | |
if len(parts) > 1: | |
story_part = parts[0].replace("STORY:", "").replace("๐", "").replace("**STORY**:", "").strip() | |
return story_part | |
return response | |
def get_stats_update(response: str) -> str: | |
"""Extract the stats update from the response""" | |
match = re.search(r'๐\s*\*\*STATS UPDATE\*\*:\s*(.*?)(?=๐ฏ\s*\*\*OPTIONS\*\*|$)', response, re.DOTALL) | |
if match: | |
return match.group(1).strip() | |
parts = response.split("STATS UPDATE:") | |
if len(parts) > 1: | |
stats_part = parts[1].split("OPTIONS:")[0].strip() | |
return stats_part | |
return "์คํฏ ๋ณํ ์์" | |
def get_options(response: str) -> list[str]: | |
"""Extract the options from the response""" | |
match = re.search(r'๐ฏ\s*\*\*OPTIONS\*\*:\s*(.*?)(?=$)', response, re.DOTALL) | |
if match: | |
options_text = match.group(1) | |
options = re.findall(r'\d+\.\s*(.*?)(?=\d+\.|$)', options_text, re.DOTALL) | |
options = [opt.strip() for opt in options if opt.strip()] | |
while len(options) < 4: | |
options.append("๋ค๋ฅธ ํ๋ ์๋...") | |
return options[:4] | |
parts = response.split("OPTIONS:") | |
if len(parts) > 1: | |
options = re.findall(r'\d+\.\s*(.*?)(?=\d+\.|$)', parts[1], re.DOTALL) | |
options = [opt.strip() for opt in options if opt.strip()] | |
while len(options) < 4: | |
options.append("๋ค๋ฅธ ํ๋ ์๋...") | |
return options[:4] | |
return ["๊ณ์ํ๊ธฐ...", "๋ค๋ฅธ ํ๋ ์ทจํ๊ธฐ", "๋ญ๊ฐ ์๋ก์ด ์๋ํ๊ธฐ", "์ฃผ๋ณ ํ์ํ๊ธฐ"] | |
def initialize_stats(genre: str) -> str: | |
"""Initialize player stats based on the selected genre""" | |
base_stats = { | |
"๐งโโ๏ธ Fantasy": "์ฒด๋ ฅ: 100, ๋ง๋: 80, ํ: 7, ์ง๋ฅ: 8, ๋ฏผ์ฒฉ: 6, ์์ง๊ธ: 50๊ณจ๋", | |
"๐ Sci-Fi": "์ฒด๋ ฅ: 100, ์๋์ง: 90, ๊ธฐ์ ๋ ฅ: 8, ์ง๋ฅ: 9, ๋ฏผ์ฒฉ: 6, ํฌ๋ ๋ง: 500", | |
"๐ป Horror": "์ฒด๋ ฅ: 80, ์ ์ ๋ ฅ: 100, ํ: 6, ์ง๋ฅ: 7, ๋ฏผ์ฒฉ: 8, ์์งํ: ์์ ๋ฑ, ๊ธฐ๋ณธ ์ฝํ", | |
"๐ Mystery": "์ฒด๋ ฅ: 90, ์ง์ค๋ ฅ: 100, ๊ด์ฐฐ๋ ฅ: 9, ์ง๋ฅ: 8, ์นด๋ฆฌ์ค๋ง: 7, ๋จ์: 0", | |
"๐ Post-Apocalyptic": "์ฒด๋ ฅ: 95, ๋ฐฉ์ฌ๋ฅ ์ ํญ: 75, ํ: 8, ์์กด๋ ฅ: 9, ๋ฌผ์: ์ ํ๋จ", | |
"๐ค Cyberpunk": "์ฒด๋ ฅ: 90, ์ฌ์ด๋ฒ์จ์ด: 85%, ํดํน: 8, ๊ฑฐ๋ฆฌ ์ ์ฉ๋: 6, ์ฃ์ง: 7, ๋์: 1000", | |
"โ๏ธ Steampunk": "์ฒด๋ ฅ: 95, ์ฆ๊ธฐ๋ ฅ: 85, ๊ธฐ๊ณ๊ณตํ: 8, ์์ ์ฑ: 7, ์ฌ๊ต์ฑ: 6, ์ค๋ง: 200" | |
} | |
if genre in base_stats: | |
return base_stats[genre] | |
else: | |
# Default stats if genre not found | |
return "์ฒด๋ ฅ: 100, ์๋์ง: 100, ํ: 7, ์ง๋ฅ: 7, ๋ฏผ์ฒฉ: 7, ์์ง๊ธ: 100" | |
def generate_random_event(turn_number: int) -> str: | |
"""Generate a random event based on turn number""" | |
if turn_number % 3 == 0 and turn_number > 0: # Every 3rd turn | |
events = [ | |
"๊ฐ์๊ธฐ ๋ถ๊ทผ์์ ์ด์ํ ์๋ฆฌ๊ฐ ๋ค๋ฆฝ๋๋ค", | |
"๋ฏ์ ์ฌํ์๊ฐ ๋น์ ์ ๋ฐ๋ผ๋ณด๊ณ ์์ต๋๋ค", | |
"์ง๋ฉด์ด ๋ฏธ์ธํ๊ฒ ์ง๋ํ๊ธฐ ์์ํฉ๋๋ค", | |
"์ฃผ๋จธ๋์์ ๋ฌด์ธ๊ฐ๊ฐ ๋น๋ฉ๋๋ค", | |
"๋ฉ๋ฆฌ์ ๋ฌด์ธ๊ฐ๊ฐ ๋น์ ์ ํฅํด ๋ค๊ฐ์ค๊ณ ์์ต๋๋ค", | |
"๊ฐ์๊ธฐ ๋ ์จ๊ฐ ๋ณํ๊ธฐ ์์ํฉ๋๋ค", | |
"์ฃผ๋ณ์ ์จ๊ฒจ์ง ํต๋ก๋ฅผ ๋ฐ๊ฒฌํฉ๋๋ค" | |
] | |
return random.choice(events) | |
return "" | |
# Create a single table for all game data | |
interactions = pxt.create_table( | |
'ai_rpg.interactions', | |
{ | |
'session_id': pxt.String, | |
'player_name': pxt.String, | |
'genre': pxt.String, | |
'initial_scenario': pxt.String, | |
'turn_number': pxt.Int, | |
'player_input': pxt.String, | |
'timestamp': pxt.Timestamp, | |
'player_stats': pxt.String, | |
'random_event': pxt.String | |
} | |
) | |
# Add computed columns for AI responses | |
interactions.add_computed_column(messages=generate_messages( | |
interactions.genre, | |
interactions.player_name, | |
interactions.initial_scenario, | |
interactions.player_input, | |
interactions.turn_number, | |
interactions.player_stats | |
)) | |
interactions.add_computed_column(ai_response=openai.chat_completions( | |
messages=interactions.messages, | |
model='gpt-4.1-mini', | |
max_tokens=800, | |
temperature=0.8 | |
)) | |
interactions.add_computed_column(full_response=interactions.ai_response.choices[0].message.content) | |
interactions.add_computed_column(story_text=get_story(interactions.full_response)) | |
interactions.add_computed_column(stats_update=get_stats_update(interactions.full_response)) | |
interactions.add_computed_column(options=get_options(interactions.full_response)) | |
class RPGGame: | |
def __init__(self): | |
self.current_session_id = None | |
self.turn_number = 0 | |
self.current_stats = "" | |
def start_game(self, player_name: str, genre: str, scenario: str) -> tuple[str, str, str, list[str]]: | |
session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{player_name}" | |
self.current_session_id = session_id | |
self.turn_number = 0 | |
# ํจ์ ํธ์ถ ๊ฒฐ๊ณผ๋ฅผ ๋จผ์ ๋ณ์์ ์ ์ฅ | |
self.current_stats = initialize_stats(genre) | |
interactions.insert([{ | |
'session_id': session_id, | |
'player_name': player_name, | |
'genre': genre, | |
'initial_scenario': scenario, | |
'turn_number': 0, | |
'player_input': "Game starts", | |
'timestamp': datetime.now(), | |
'player_stats': self.current_stats, # ๋ฌธ์์ด ๊ฐ์ ์ ์ฅ | |
'random_event': "" | |
}]) | |
result = interactions.select( | |
interactions.story_text, | |
interactions.stats_update, | |
interactions.options | |
).where( | |
(interactions.session_id == session_id) & | |
(interactions.turn_number == 0) | |
).collect() | |
return session_id, result['story_text'][0], result['stats_update'][0], result['options'][0] | |
def process_action(self, action: str) -> tuple[str, str, list[str]]: | |
if not self.current_session_id: | |
return "๊ฒ์ ์ธ์ ์ด ํ์ฑํ๋์ง ์์์ต๋๋ค. ์ ๊ฒ์์ ์์ํ์ธ์.", "์คํฏ ์์", [] | |
self.turn_number += 1 | |
prev_turn = interactions.select( | |
interactions.player_name, | |
interactions.genre, | |
interactions.initial_scenario, | |
interactions.player_stats | |
).where( | |
(interactions.session_id == self.current_session_id) & | |
(interactions.turn_number == self.turn_number - 1) | |
).collect() | |
self.current_stats = prev_turn['player_stats'][0] | |
random_event = generate_random_event(self.turn_number) | |
if random_event: | |
action = f"{action} ({random_event})" | |
interactions.insert([{ | |
'session_id': self.current_session_id, | |
'player_name': prev_turn['player_name'][0], | |
'genre': prev_turn['genre'][0], | |
'initial_scenario': prev_turn['initial_scenario'][0], | |
'turn_number': self.turn_number, | |
'player_input': action, | |
'timestamp': datetime.now(), | |
'player_stats': self.current_stats, | |
'random_event': random_event | |
}]) | |
result = interactions.select( | |
interactions.story_text, | |
interactions.stats_update, | |
interactions.options | |
).where( | |
(interactions.session_id == self.current_session_id) & | |
(interactions.turn_number == self.turn_number) | |
).collect() | |
# Update stats for next turn | |
self.current_stats = result['stats_update'][0] | |
return result['story_text'][0], result['stats_update'][0], result['options'][0] | |
def create_interface(): | |
game = RPGGame() | |
# Custom CSS for improved visuals | |
custom_css = """ | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
} | |
.title-container { | |
background: linear-gradient(135deg, #6e48aa 0%, #9c27b0 100%); | |
color: white; | |
padding: 20px; | |
border-radius: 15px; | |
margin-bottom: 20px; | |
text-align: center; | |
box-shadow: 0 4px 15px rgba(0,0,0,0.2); | |
} | |
.story-container { | |
background: #f8f9fa; | |
border-left: 5px solid #9c27b0; | |
padding: 15px; | |
border-radius: 10px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
margin-bottom: 20px; | |
font-family: 'Noto Sans KR', sans-serif; | |
} | |
.stats-container { | |
background: #e8f5e9; | |
border-left: 5px solid #4caf50; | |
padding: 15px; | |
border-radius: 10px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
margin-bottom: 20px; | |
} | |
.options-container { | |
background: #e3f2fd; | |
border-left: 5px solid #2196f3; | |
padding: 15px; | |
border-radius: 10px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
margin-bottom: 20px; | |
} | |
.action-button { | |
background: linear-gradient(135deg, #6e48aa 0%, #9c27b0 100%); | |
color: white; | |
border: none; | |
padding: 10px 20px; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
} | |
.action-button:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 4px 10px rgba(0,0,0,0.2); | |
} | |
.history-container { | |
background: #fff8e1; | |
border-left: 5px solid #ffc107; | |
padding: 15px; | |
border-radius: 10px; | |
box-shadow: 0 2px 10px rgba(0,0,0,0.1); | |
margin-top: 20px; | |
} | |
""" | |
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo: | |
gr.HTML( | |
""" | |
<div class="title-container"> | |
<h1 style="margin-bottom: 0.5em; font-size: 2.5em;">๐ฒ AI RPG ์ด๋๋ฒค์ฒ</h1> | |
<p style="font-size: 1.2em;">Pixeltable๊ณผ OpenAI๋ก ๊ตฌํ๋ ๋ชฐ์ ํ ๋กคํ๋ ์ ๊ฒ์ ๊ฒฝํ!</p> | |
</div> | |
""" | |
) | |
with gr.Row(): | |
with gr.Column(scale=1): | |
with gr.Accordion("๐ฏ ์ด ์ฑ์ ๋ฌด์์ธ๊ฐ์?", open=False): | |
gr.HTML( | |
""" | |
<div style="padding: 15px;"> | |
<h3>AI RPG ์ด๋๋ฒค์ฒ๋ ๋ค์ ๊ธฐ๋ฅ์ ๋ณด์ฌ์ค๋๋ค:</h3> | |
<ul style="list-style-type: none; padding-left: 5px;"> | |
<li>๐ฎ <b>๋์ ์คํ ๋ฆฌํ ๋ง:</b> AI๊ฐ ์์ฑํ๋ ๋ชฐ์ ํ ์ด์ผ๊ธฐ ๊ฒฝํ</li> | |
<li>๐ <b>๊ฒ์ ์ํ ๊ด๋ฆฌ:</b> Pixeltable๋ก ๊ฒ์ ์ํ์ ๊ธฐ๋ก ์ถ์ </li> | |
<li>๐ญ <b>์ปจํ ์คํธ ๊ธฐ๋ฐ ์ ํ์ง:</b> ํ๋ ์ด์ด ํ๋์ ๋ฐ๋ฅธ ๋ง์ถคํ ์ต์ </li> | |
<li>๐ค <b>AI ์คํ ๋ฆฌํ ๋ง:</b> ์์ํ ๋ด๋ฌํฐ๋ธ ์์ฑ</li> | |
<li>๐ <b>์บ๋ฆญํฐ ์ํ ์ถ์ :</b> ๊ฒ์ ์งํ์ ๋ฐ๋ฅธ ์คํฏ ๋ณํ</li> | |
</ul> | |
</div> | |
""" | |
) | |
with gr.Accordion("๐จ ์บ๋ฆญํฐ ์์ฑ", open=True): | |
player_name = gr.Textbox( | |
label="๐ค ์บ๋ฆญํฐ ์ด๋ฆ", | |
placeholder="๋น์ ์ ์บ๋ฆญํฐ ์ด๋ฆ์ ์ ๋ ฅํ์ธ์...", | |
container=False | |
) | |
genre = gr.Dropdown( | |
choices=[ | |
"๐งโโ๏ธ Fantasy", | |
"๐ Sci-Fi", | |
"๐ป Horror", | |
"๐ Mystery", | |
"๐ Post-Apocalyptic", | |
"๐ค Cyberpunk", | |
"โ๏ธ Steampunk" | |
], | |
label="๐ญ ์ฅ๋ฅด ์ ํ", | |
container=False, | |
value="๐งโโ๏ธ Fantasy" | |
) | |
scenario = gr.Textbox( | |
label="๐ ์์ ์๋๋ฆฌ์ค", | |
lines=3, | |
placeholder="์ด๊ธฐ ์ค์ ๊ณผ ์ํฉ์ ์ค๋ช ํ์ธ์...", | |
container=False | |
) | |
start_button = gr.Button("๐ฎ ๋ชจํ ์์!", variant="primary") | |
with gr.Column(scale=2): | |
story_display = gr.Markdown( | |
label="๐ ์คํ ๋ฆฌ", | |
value="<div class='story-container'>๋ชจํ์ ์์ํ๋ ค๋ฉด ์บ๋ฆญํฐ๋ฅผ ์์ฑํ๊ณ '๋ชจํ ์์!' ๋ฒํผ์ ํด๋ฆญํ์ธ์.</div>", | |
show_label=False | |
) | |
stats_display = gr.Markdown( | |
label="๐ ์บ๋ฆญํฐ ์คํฏ", | |
value="<div class='stats-container'>๋ชจํ์ ์์ํ๋ฉด ์บ๋ฆญํฐ ์คํฏ์ด ์ด๊ณณ์ ํ์๋ฉ๋๋ค.</div>", | |
show_label=False | |
) | |
gr.HTML("<div class='options-container'><h3>๐ฏ ๋ค์ ํ๋ ์ ํ</h3></div>") | |
action_input = gr.Radio( | |
choices=[], | |
label="", | |
interactive=True | |
) | |
submit_action = gr.Button("โก ํ๋ ์คํ", variant="primary") | |
with gr.Row(): | |
with gr.Column(): | |
gr.HTML("<div class='history-container'><h3>๐ซ ์ด๋๋ฒค์ฒ ์์</h3></div>") | |
gr.Examples( | |
examples=[ | |
["์ด์์ ", "๐งโโ๏ธ Fantasy", "๋น์ ์ ์ํ์ง ์ ๋นํ ์ฒ์ ๊ฐ์ฅ์๋ฆฌ์์ ๋์ ๋น๋๋ค. ๋ฉ๋ฆฌ์ ์ฑ์ ์ฒจํ์ด ๋ณด์ด๊ณ , ๋น์ ์ ๋จธ๋ฆฌ์์๋ ์๊ตญ์ ์ํํ๋ ๊ณ ๋ ๋ง๋ฒ์ ๊ดํ ๋จ์๋ง์ด ๋จ์์์ต๋๋ค. ๊ฐ์๊ธฐ ์ฒ์์ ์ด์ํ ๋น์ด ๋ณด์ ๋๋ค..."], | |
["๊น์ง์", "๐ Sci-Fi", "์ฐ์ฃผ์ 'ํธ๋ผ์ด์ฆ'์ ํญํด์ฌ๋ก์, ๋น์ ์ ๋ฏธ์ง์ ํ์ฑ ํ์ฌ ์ค ๋น์ ์๋์ ๊นจ์ด๋ฉ๋๋ค. ์ ์ฅ๊ณผ ์ฐ๋ฝ์ด ๋์ ๋์๊ณ , ์๋ช ์ ์ง ์์คํ ์ด ์ ์ฐจ ์คํจํ๊ณ ์์ต๋๋ค. ์กฐ์ฉํ ์ ๋ด์์ ์ด์ํ ๋ฐ๊ฑธ์ ์๋ฆฌ๊ฐ ๋ค๋ฆฝ๋๋ค..."], | |
["์ผ๋ก ๋จธ์คํฌ", "๐ค Cyberpunk", "2077๋ ์์ธ, ๋น์ ์ ๋ด๋ด๋งํฌ ์ธ๋์คํธ๋ฆฌ์ CEO์ ๋๋ค. ๋น์ ์ ์ต์ ๋-์ปดํจํฐ ์ธํฐํ์ด์ค ๊ธฐ์ ์ด ์ฌ์ฉ์๋ค์๊ฒ ์์์น ๋ชปํ ๋ฅ๋ ฅ์ ๋ถ์ฌํ๊ธฐ ์์ํ์ต๋๋ค - ๊ทธ๋ค์ ์ง์ ์๋ฌผ๊ณผ ๊ต๊ฐํ ์ ์๊ฒ ๋ ๊ฒ์ ๋๋ค. ์ฃผ์ ํฌ์์ ํ๋ ์ ํ ์ด์ ์ ์ค๋นํ๋ ๋์, AI ๋น์๊ฐ ํ ์คํธ ์ฐธ๊ฐ์๋ค์ด ๋ถ๊ฐ์ฌ์ํ '์๋ฌผ ํ๋ช '์ ์กฐ์งํ๊ณ ์๋ค๋ ๋ณด๊ณ ๋ฅผ ์ ํฉ๋๋ค..."], | |
["๊ณ ๋ ๋จ์ง", "๐ Post-Apocalyptic", "๋น์ ์ ๋ด ์์ธ์ ๋ง์ง๋ง ๋ง์คํฐ ์ ฐํ๋ก, ์ ๋ญ์ ๋ฆฌ ํธํ ํํ์์ ์งํ ๋ ์คํ ๋์ ์ด์ํ๊ณ ์์ต๋๋ค. ๋น์ ์ ์๊ทธ๋์ฒ ์๋ฆฌ๋ ์ํํ ๋ฐฉ์ฌ๋ฅ ๊ตฌ์ญ์์๋ง ์๋ผ๋ ํฌ๊ท ๋ฒ์ฏ์ด ํ์ํฉ๋๋ค. ์ค๋ ๋ฐค์ ๋น๋ฐ ๋ชจ์์ ์ค๋นํ๋ ์ค, ์ ์ฐฐ๋ณ์ด ์ฃผ๋ณ ์ง์ญ์ ๊ฒฝ์ ์๋ฆฌ์ฌ ๊ฐฑ๋จ์ ๊ดํ ๋ถ๊ธธํ ์์์ ๊ฐ์ง๊ณ ๋์์ต๋๋ค..."], | |
["์ ์ ํธ", "๐ป Horror", "๋น์ ์ ์น๊ตฌ์ ์ด๋๋ก ์ผ๋ฆผ ์ ์ธ๋ด ๋ณ์ฅ์ ์ฃผ๋ง์ ๋ณด๋ด๋ฌ ์์ต๋๋ค. ์ฒซ๋ ๋ฐค, ์ฐฝ๋ฐ์ผ๋ก ๋ณด์ด๋ ๊ธฐ์ดํ ๋น์ ์ด๋๋ ค ์ฒ์ผ๋ก ๋ค์ด๊ฐ๊ฒ ๋ฉ๋๋ค. ๋์์ค๋ ๊ธธ์ ์ฐพ์ผ๋ ค ํ์ง๋ง, ๋ณ์ฅ์ด ๋ณด์ด์ง ์๊ณ ๋ฏ์ ์๊ฐ๊ฐ ์ ์ ์ง์ด์ง๋๋ค. ๋ฉ๋ฆฌ์ ๋๊ตฐ๊ฐโ์๋, ๋ฌด์ธ๊ฐ๊ฐ ๋น์ ์ ๋ถ๋ฅด๋ ์๋ฆฌ๊ฐ ๋ค๋ฆฝ๋๋ค..."], | |
["๋ฐ์งํ", "๐ Mystery", "์ ์ ํ์ฌ๋ก์ ๋น์ ์ ์ฒซ ์ฌ๊ฑด์ ๋์ ์ต๊ณ ์ ๊ธฐ์ ๊ธฐ์ CEO์ ์๋ฌธ์ ์ค์ข ์ ๋๋ค. ๊ทธ์ ์ฌ๋ฌด์ค์๋ ํํ์ด ์๊ณ , ์ ์ผํ ๋จ์๋ ์ฑ ์ ์์ ๋์ธ ์ํธํ๋ ๋ฉ๋ชจ์ ๊บผ์ ธ์๋ ๊ทธ์ ์ต์ฒจ๋จ AI ๋น์๋ฟ์ ๋๋ค. ์กฐ์ฌ๋ฅผ ์์ํ์๋ง์, ๋น์ ์ CEO๊ฐ ๋ง์ง๋ง์ผ๋ก ์์ ํ๋ ๋น๋ฐ ํ๋ก์ ํธ์ ๊ดํ ์ด์ผ๊ธฐ๋ฅผ ๋ฃ๊ฒ ๋ฉ๋๋ค..."], | |
["์ด๋ฏผ์", "โ๏ธ Steampunk", "์ฆ๊ธฐ์ ๊ธฐ์ด๋ก ๊ฐ๋ํ ๋ด ์กฐ์ ์์, ๋น์ ์ ํ์ ์ ์ธ ๋นํ์ ์ค๊ณ์์ ๋๋ค. ๋น์ ์ ์ต์ ๋ฐ๋ช ํ ์์ฐ ์ค, ์ ๋ถ์ ๋น๋ฐ ์์์ด ์ ๊ทผํด ์ํ์ ์ฒํ ํฉ์ค ๊ฐ์กฑ์ ์ํ ๋น๋ฐ ์๋ฌด๋ฅผ ์ ์ํฉ๋๋ค. ์งํ ๋จ์ฒด๋ค์ด ์์ข๋ฅผ ์ํํ๊ณ ์์ผ๋ฉฐ, ๋น์ ์ ๋ฐ๋ช ํ์ด ์๊ฐ์ ์ ์ผํ ํฌ๋ง์ด๋ผ๊ณ ํฉ๋๋ค..."] | |
], | |
inputs=[player_name, genre, scenario] | |
) | |
with gr.Column(): | |
history_df = gr.Dataframe( | |
headers=["๐ ํด", "๐ฏ ํ๋ ์ด์ด ํ๋", "๐ฌ ๊ฒ์ ๋ฐ์"], | |
label="๐ ๋ชจํ ์ญ์ฌ", | |
wrap=True, | |
row_count=5, | |
col_count=(3, "fixed") | |
) | |
def start_new_game(name, genre_choice, scenario_text): | |
if not name or not genre_choice or not scenario_text: | |
return ( | |
"<div class='story-container'>๋ชจ๋ ํ๋๋ฅผ ์ ๋ ฅํ ํ ์์ํ์ธ์.</div>", | |
"<div class='stats-container'>์คํฏ ์ ๋ณด ์์</div>", | |
[], | |
[] | |
) | |
try: | |
_, initial_story, initial_stats, initial_options = game.start_game(name, genre_choice, scenario_text) | |
history_df = interactions.select( | |
turn=interactions.turn_number, | |
action=interactions.player_input, | |
response=interactions.story_text | |
).where( | |
interactions.session_id == game.current_session_id | |
).order_by( | |
interactions.turn_number | |
).collect().to_pandas() | |
history_data = [ | |
[str(row['turn']), row['action'], row['response']] | |
for _, row in history_df.iterrows() | |
] | |
story_html = f"<div class='story-container'>{initial_story}</div>" | |
stats_html = f"<div class='stats-container'><h3>๐ ์บ๋ฆญํฐ ์ํ</h3>{initial_stats}</div>" | |
return story_html, stats_html, gr.Radio(choices=initial_options, interactive=True), history_data | |
except Exception as e: | |
return ( | |
f"<div class='story-container'>๊ฒ์ ์์ ์ค๋ฅ: {str(e)}</div>", | |
"<div class='stats-container'>์คํฏ ์ ๋ณด ์์</div>", | |
[], | |
[] | |
) | |
def process_player_action(action_choice): | |
try: | |
if not action_choice: | |
return ( | |
"<div class='story-container'>๊ณ์ํ๋ ค๋ฉด ํ๋์ ์ ํํ์ธ์.</div>", | |
"<div class='stats-container'>์คํฏ ์ ๋ณด ์์</div>", | |
[], | |
[] | |
) | |
story, stats, options = game.process_action(action_choice) | |
history_df = interactions.select( | |
turn=interactions.turn_number, | |
action=interactions.player_input, | |
response=interactions.story_text | |
).where( | |
interactions.session_id == game.current_session_id | |
).order_by( | |
interactions.turn_number | |
).collect().to_pandas() | |
history_data = [ | |
[str(row['turn']), row['action'], row['response']] | |
for _, row in history_df.iterrows() | |
] | |
story_html = f"<div class='story-container'>{story}</div>" | |
stats_html = f"<div class='stats-container'><h3>๐ ์บ๋ฆญํฐ ์ํ</h3>{stats}</div>" | |
return story_html, stats_html, gr.Radio(choices=options, interactive=True), history_data | |
except Exception as e: | |
return ( | |
f"<div class='story-container'>์ค๋ฅ: {str(e)}</div>", | |
"<div class='stats-container'>์คํฏ ์ ๋ณด ์์</div>", | |
[], | |
[] | |
) | |
start_button.click( | |
start_new_game, | |
inputs=[player_name, genre, scenario], | |
outputs=[story_display, stats_display, action_input, history_df] | |
) | |
submit_action.click( | |
process_player_action, | |
inputs=[action_input], | |
outputs=[story_display, stats_display, action_input, history_df] | |
) | |
gr.HTML(""" | |
<div style="text-align: center; margin-top: 30px; padding: 20px; background: #f5f5f5; border-radius: 10px;"> | |
<h3>๐ AI RPG ์ด๋๋ฒค์ฒ - Pixeltable๋ก ์ ์๋ ๋ชฐ์ ํ ๋กคํ๋ ์ ๊ฒฝํ</h3> | |
<p>์์ ๋ง์ ์บ๋ฆญํฐ๋ฅผ ๋ง๋ค๊ณ , ์ ํํ ์ฅ๋ฅด์ ์ธ๊ณ์์ ๋ชจํ์ ์ฆ๊ธฐ์ธ์. ๋น์ ์ ์ ํ์ด ์คํ ๋ฆฌ๋ฅผ ํ์ฑํฉ๋๋ค!</p> | |
</div> | |
""") | |
return demo | |
if __name__ == "__main__": | |
demo = create_interface() | |
demo.launch() |