RPG_GAME-EN / app.py
Gileskk's picture
Upload app.py
ec3d3a8 verified
import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
import hashlib
import uuid
import time
from config import APP_TITLE, APP_DESCRIPTION, GAME_CONFIG
from database.nocodb import nocodb_client
from llm.together import together_client
# Page Configuration
st.set_page_config(
page_title=APP_TITLE,
page_icon="⚔️",
layout="wide",
initial_sidebar_state="expanded"
)
# Custom CSS Styles
st.markdown("""
<style>
.main {
background-color: #f0f2f6;
}
.stButton>button {
background-color: #4CAF50;
color: white;
border-radius: 5px;
padding: 10px 20px;
font-weight: bold;
transition: all 0.3s ease;
}
.stButton>button:hover {
background-color: #45a049;
transform: translateY(-2px);
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.character-card {
background-color: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.character-card:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
}
.battle-log {
background-color: #fff3cd;
padding: 15px;
border-radius: 5px;
margin: 10px 0;
border-left: 5px solid #ffc107;
}
.success-message {
color: #28a745;
font-weight: bold;
padding: 10px;
border-radius: 5px;
background-color: #d4edda;
}
.error-message {
color: #dc3545;
font-weight: bold;
padding: 10px;
border-radius: 5px;
background-color: #f8d7da;
}
.sidebar-content {
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
margin-bottom: 20px;
}
.tab-content {
padding: 20px;
background-color: white;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.stat-value {
font-size: 1.2em;
font-weight: bold;
color: #2c3e50;
}
.class-badge {
padding: 5px 10px;
border-radius: 15px;
font-size: 0.9em;
font-weight: bold;
}
.warrior { background-color: #e74c3c; color: white; }
.mage { background-color: #3498db; color: white; }
.rogue { background-color: #2ecc71; color: white; }
.priest { background-color: #9b59b6; color: white; }
.archer { background-color: #f1c40f; color: white; }
</style>
""", unsafe_allow_html=True)
# Initialize Session State
if 'user_id' not in st.session_state:
st.session_state.user_id = None
if 'character' not in st.session_state:
st.session_state.character = None
if 'characters' not in st.session_state:
st.session_state.characters = []
if 'is_logged_in' not in st.session_state:
st.session_state.is_logged_in = False
if 'current_tab' not in st.session_state:
st.session_state.current_tab = "Character Info"
def hash_password(password: str) -> str:
"""Hash password using SHA256"""
return hashlib.sha256(password.encode()).hexdigest()
def login_user(username: str, password: str) -> bool:
"""User login"""
try:
users = nocodb_client.get_records("users", {
"where": f"(username,eq,{username})"
})
if users and users[0]["password"] == hash_password(password):
st.session_state.user_id = users[0]["Id"]
st.session_state.is_logged_in = True
# Load user's characters
characters = nocodb_client.get_records("characters", {
"where": f"(user_id,eq,{users[0]['Id']})"
})
st.session_state.characters = characters
return True
except Exception as e:
st.error(f"Login failed: {str(e)}")
return False
def register_user(username: str, password: str) -> bool:
"""User registration"""
try:
# Check if username exists
existing_users = nocodb_client.get_records("users", {
"where": f"(username,eq,{username})"
})
if existing_users:
st.error("Username already exists")
return False
# Create new user
user = {
"username": username,
"password": hash_password(password),
"created_time": datetime.now().isoformat()
}
result = nocodb_client.create_record("users", user)
return True
except Exception as e:
st.error(f"Registration failed: {str(e)}")
return False
def load_character(character_id: str) -> None:
"""Load specified character"""
try:
characters = nocodb_client.get_records("characters", {
"where": f"(Id,eq,{character_id})"
})
if characters:
# Ensure all required attributes exist
character = characters[0]
required_fields = {
'level': 1,
'EXP': 0,
'ATK': GAME_CONFIG["base_stats"]["ATK"],
'DEF': GAME_CONFIG["base_stats"]["DEF"],
'MAG': GAME_CONFIG["base_stats"]["MAG"],
'DEX': GAME_CONFIG["base_stats"]["DEX"],
'LUK': GAME_CONFIG["base_stats"]["LUK"]
}
# Use default values for missing fields
for field, default_value in required_fields.items():
if field not in character:
character[field] = default_value
# Update database record
nocodb_client.update_record("characters", character_id, {field: default_value})
st.session_state.character = character
st.rerun()
except Exception as e:
st.error(f"Failed to load character: {str(e)}")
def create_character(name: str, character_class: str) -> bool:
"""Create new character"""
try:
# Get class base attributes
class_stats = GAME_CONFIG["class_base_stats"][character_class]
# Ensure all required fields have default values
character = {
"user_id": int(st.session_state.user_id),
"name": name,
"class": character_class,
"level": 1,
"EXP": 0,
"ATK": class_stats["ATK"],
"DEF": class_stats["DEF"],
"MAG": class_stats["MAG"],
"DEX": class_stats["DEX"],
"LUK": class_stats["LUK"],
"created_time": datetime.now().isoformat(),
"last_login": datetime.now().isoformat()
}
result = nocodb_client.create_record("characters", character)
if result:
# Ensure returned result contains all required fields
for field, value in character.items():
if field not in result:
result[field] = value
st.session_state.character = result
return True
return False
except Exception as e:
st.error(f"Failed to create character: {str(e)}")
return False
def update_character_stats(character_id: str, stats: dict) -> bool:
"""Update character attributes"""
try:
nocodb_client.update_record("characters", character_id, stats)
return True
except Exception as e:
st.error(f"Failed to update attributes: {str(e)}")
return False
def get_class_badge(character_class: str) -> str:
"""Get class badge style"""
class_styles = {
"Warrior": "warrior",
"Mage": "mage",
"Rogue": "rogue",
"Priest": "priest",
"Archer": "archer"
}
return f'<span class="class-badge {class_styles.get(character_class, "")}">{character_class}</span>'
def create_character_stats_chart(character):
"""Create character attributes radar chart"""
stats = {
'Attributes': ['Attack', 'Defense', 'Magic', 'Dexterity', 'Luck'],
'Values': [
character['ATK'],
character['DEF'],
character['MAG'],
character['DEX'],
character['LUK']
]
}
fig = go.Figure()
fig.add_trace(go.Scatterpolar(
r=stats['Values'],
theta=stats['Attributes'],
fill='toself',
name='Current Stats'
))
# Add class baseline
class_stats = GAME_CONFIG["class_base_stats"][character['class']]
base_stats = [
class_stats['ATK'],
class_stats['DEF'],
class_stats['MAG'],
class_stats['DEX'],
class_stats['LUK']
]
fig.add_trace(go.Scatterpolar(
r=base_stats,
theta=stats['Attributes'],
fill='toself',
name='Class Baseline'
))
fig.update_layout(
polar=dict(
radialaxis=dict(
visible=True,
range=[0, max(max(stats['Values']), max(base_stats)) * 1.2]
)),
showlegend=True,
title='Character Attributes Radar Chart'
)
return fig
def create_level_progress_chart(character):
"""Create level progress gauge"""
# Calculate required experience (example: 1000 exp per level)
exp_needed = character['level'] * 1000
progress = (character['EXP'] / exp_needed) * 100
fig = go.Figure(go.Indicator(
mode="gauge+number",
value=progress,
title={'text': f"Level Progress (Lv.{character['level']})"},
gauge={
'axis': {'range': [0, 100]},
'bar': {'color': "darkblue"},
'steps': [
{'range': [0, 100], 'color': "lightgray"}
],
'threshold': {
'line': {'color': "red", 'width': 4},
'thickness': 0.75,
'value': 100
}
}
))
fig.update_layout(
height=200,
margin=dict(l=20, r=20, t=50, b=20)
)
return fig
def create_friend_ranking_table(character, available_opponents):
"""Create friend ranking table"""
try:
# Ensure current character has all attributes
current_char = {
'name': character['name'],
'level': character.get('level', 1),
'ATK': character.get('ATK', 10),
'DEF': character.get('DEF', 10),
'MAG': character.get('MAG', 10),
'DEX': character.get('DEX', 10),
'LUK': character.get('LUK', 10),
'class': character['class'],
'username': 'You'
}
current_char['total_stats'] = current_char['ATK'] + current_char['DEF'] + current_char['MAG'] + current_char['DEX'] + current_char['LUK']
# Ensure all opponents have attributes
for opp in available_opponents:
opp['ATK'] = opp.get('ATK', 10)
opp['DEF'] = opp.get('DEF', 10)
opp['MAG'] = opp.get('MAG', 10)
opp['DEX'] = opp.get('DEX', 10)
opp['LUK'] = opp.get('LUK', 10)
opp['level'] = opp.get('level', 1)
opp['total_stats'] = opp['ATK'] + opp['DEF'] + opp['MAG'] + opp['DEX'] + opp['LUK']
all_chars = available_opponents + [current_char]
# Sort by total stats
sorted_chars = sorted(all_chars, key=lambda x: x['total_stats'], reverse=True)
# Create table data
table_data = []
for i, char in enumerate(sorted_chars, 1):
table_data.append({
"Rank": i,
"Character": char['name'],
"Class": char['class'],
"Level": char['level'],
"Attack": char['ATK'],
"Defense": char['DEF'],
"Magic": char['MAG'],
"Dexterity": char['DEX'],
"Luck": char['LUK'],
"Total Stats": char['total_stats'],
"Player": char['username']
})
# Create DataFrame
df = pd.DataFrame(table_data)
return df
except Exception as e:
st.error(f"Failed to create ranking table: {str(e)}")
return None
# Add rate limit decorator
def rate_limit(max_requests=10, time_window=60):
"""Rate limit decorator"""
last_reset = time.time()
request_count = 0
def decorator(func):
def wrapper(*args, **kwargs):
nonlocal last_reset, request_count
current_time = time.time()
# Reset counter
if current_time - last_reset >= time_window:
last_reset = current_time
request_count = 0
# Check limit
if request_count >= max_requests:
time.sleep(time_window - (current_time - last_reset))
last_reset = time.time()
request_count = 0
request_count += 1
return func(*args, **kwargs)
return wrapper
return decorator
# Add retry mechanism
def retry_on_error(max_retries=3, delay=1):
"""Retry decorator"""
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
if "429" in str(e):
if attempt < max_retries - 1:
time.sleep(delay * (attempt + 1))
continue
raise e
return func(*args, **kwargs)
return wrapper
return decorator
# Modify get available opponents function
@rate_limit(max_requests=10, time_window=60)
@retry_on_error(max_retries=3, delay=2)
def get_available_opponents(current_user_id):
"""Get available opponents list"""
try:
# Use cache mechanism to get all users and character data
users = nocodb_client.get_records("users")
if not users:
return []
# Get all character data (one-time fetch)
all_characters = nocodb_client.get_records("characters")
# Filter and process data in memory
available_opponents = []
for user in users:
if user["Id"] != current_user_id: # Exclude self
# Filter user's characters in memory
user_characters = [char for char in all_characters if char.get("user_id") == user["Id"]]
if user_characters:
for char in user_characters:
available_opponents.append({
"id": char["Id"],
"name": char["name"],
"class": char["class"],
"level": char.get("level", 1),
"username": user["username"],
"ATK": char.get("ATK", 10),
"DEF": char.get("DEF", 10),
"MAG": char.get("MAG", 10),
"DEX": char.get("DEX", 10),
"LUK": char.get("LUK", 10)
})
return available_opponents
except Exception as e:
st.error(f"Failed to get friend list: {str(e)}")
return []
# Modify battle record retrieval logic
def get_battle_records(character_id):
"""Get battle records (with cache)"""
try:
# Add error handling and retry logic
max_retries = 3
retry_delay = 2
for attempt in range(max_retries):
try:
battles = nocodb_client.get_records("battles", {
"where": f"(attacker,eq,{character_id})"
})
return battles[-5:] if battles else [] # Return last 5 battles
except Exception as e:
if "429" in str(e) and attempt < max_retries - 1:
time.sleep(retry_delay * (attempt + 1))
continue
raise e
except Exception as e:
st.error(f"Failed to get battle records: {str(e)}")
return []
# Modify Boss challenge record retrieval logic
def get_boss_challenge_records(character_id):
"""Get Boss challenge records (with cache)"""
try:
# Add error handling and retry logic
max_retries = 3
retry_delay = 2
for attempt in range(max_retries):
try:
challenges = nocodb_client.get_records("boss_challenges", {
"where": f"(team,like,{character_id})"
})
return challenges[-5:] if challenges else [] # Return last 5 challenges
except Exception as e:
if "429" in str(e) and attempt < max_retries - 1:
time.sleep(retry_delay * (attempt + 1))
continue
raise e
except Exception as e:
st.error(f"Failed to get challenge records: {str(e)}")
return []
# Sidebar
with st.sidebar:
st.title("⚔️ RPG Adventure Game")
if not st.session_state.is_logged_in:
with st.container():
st.markdown('<div class="sidebar-content">', unsafe_allow_html=True)
tab1, tab2 = st.tabs(["Login", "Register"])
with tab1:
with st.form("login_form"):
login_username = st.text_input("Username")
login_password = st.text_input("Password", type="password")
if st.form_submit_button("Login", use_container_width=True):
if login_user(login_username, login_password):
st.success("Login successful!")
st.rerun()
with tab2:
with st.form("register_form"):
reg_username = st.text_input("New Username")
reg_password = st.text_input("New Password", type="password")
reg_confirm = st.text_input("Confirm Password", type="password")
if st.form_submit_button("Register", use_container_width=True):
if reg_password == reg_confirm:
if register_user(reg_username, reg_password):
st.success("Registration successful! Please login")
else:
st.error("Passwords do not match")
st.markdown('</div>', unsafe_allow_html=True)
else:
with st.container():
st.markdown('<div class="sidebar-content">', unsafe_allow_html=True)
st.success(f"Welcome back!")
# Character selection
if st.session_state.characters:
st.markdown("### Select Character")
character_names = [f"{char['name']} ({char['class']})" for char in st.session_state.characters]
selected_character = st.selectbox(
"Select character to use",
character_names,
index=0 if not st.session_state.character else character_names.index(f"{st.session_state.character['name']} ({st.session_state.character['class']})")
)
if selected_character:
selected_id = st.session_state.characters[character_names.index(selected_character)]["Id"]
if not st.session_state.character or st.session_state.character["Id"] != selected_id:
load_character(selected_id)
# Navigation
st.markdown("### Navigation")
tabs = ["Character Info", "Character Development", "PVP Battle", "Boss Challenge"]
selected_tab = st.radio("Select function", tabs, index=tabs.index(st.session_state.current_tab))
st.session_state.current_tab = selected_tab
if st.button("Logout", use_container_width=True):
st.session_state.is_logged_in = False
st.session_state.user_id = None
st.session_state.character = None
st.session_state.characters = []
st.rerun()
st.markdown('</div>', unsafe_allow_html=True)
# Model selection
with st.container():
st.markdown('<div class="sidebar-content">', unsafe_allow_html=True)
st.markdown("### AI Model Settings")
try:
models = together_client.get_available_models()
if models:
model_names = [model["name"] for model in models]
selected_model = st.selectbox(
"Select AI model",
model_names,
index=0
)
together_client.set_model(selected_model)
except Exception as e:
st.warning(f"Failed to get model list: {str(e)}")
st.markdown('</div>', unsafe_allow_html=True)
# Main interface
if not st.session_state.is_logged_in:
st.markdown("""
<div style='text-align: center; padding: 50px;'>
<h1>Welcome to RPG Adventure Game</h1>
<p>Please login or register to start the game</p>
</div>
""", unsafe_allow_html=True)
else:
# Check NocoDB connection
try:
nocodb_client.get_records("characters")
except Exception as e:
st.error(f"Unable to connect to NocoDB database: {str(e)}")
st.stop()
# Display content based on selected tab
if st.session_state.current_tab == "Character Info":
if not st.session_state.character:
st.markdown("""
<div style='text-align: center;'>
<h2>Create Your Character</h2>
<p>Choose a class and start your adventure!</p>
</div>
""", unsafe_allow_html=True)
with st.form("character_creation"):
col1, col2 = st.columns(2)
with col1:
name = st.text_input("Character Name")
character_class = st.selectbox(
"Choose Class",
GAME_CONFIG["classes"]
)
with col2:
st.markdown("### Class Description")
class_descriptions = {
"Warrior": "Excels in melee combat with high attack and defense",
"Mage": "Masters magic with powerful magical attacks",
"Rogue": "Agile and flexible, skilled in critical hits and evasion",
"Priest": "Expert in healing and support magic",
"Archer": "Ranged attack specialist with high accuracy"
}
st.write(class_descriptions[character_class])
if st.form_submit_button("Create Character", use_container_width=True):
if name:
if create_character(name, character_class):
st.success("Character created successfully!")
st.rerun()
else:
st.warning("Please enter character name")
else:
# Character basic info card
st.markdown(f"""
<div class='character-card'>
<h2>{st.session_state.character['name']} {get_class_badge(st.session_state.character['class'])}</h2>
<div style='display: flex; justify-content: space-between;'>
<div>
<p>ID:<span class='stat-value'>{st.session_state.character['Id']}</span></p>
<p>Level:<span class='stat-value'>{st.session_state.character['level']}</span></p>
<p>EXP:<span class='stat-value'>{st.session_state.character['EXP']}</span></p>
</div>
<div>
<p>Attack:<span class='stat-value'>{st.session_state.character['ATK']}</span></p>
<p>Defense:<span class='stat-value'>{st.session_state.character['DEF']}</span></p>
<p>Magic:<span class='stat-value'>{st.session_state.character['MAG']}</span></p>
<p>Dexterity:<span class='stat-value'>{st.session_state.character['DEX']}</span></p>
<p>Luck:<span class='stat-value'>{st.session_state.character['LUK']}</span></p>
</div>
</div>
</div>
""", unsafe_allow_html=True)
# Add attribute radar chart
st.plotly_chart(create_character_stats_chart(st.session_state.character), use_container_width=True)
# Add level progress gauge
st.plotly_chart(create_level_progress_chart(st.session_state.character), use_container_width=True)
# Add class description
st.markdown("### Class Features")
class_descriptions = {
"Warrior": "Excels in melee combat with high attack and defense, serving as the main damage dealer and tank in the team.",
"Mage": "Masters magic with powerful magical attacks, but has weak defense and needs team protection.",
"Rogue": "Agile and flexible, skilled in critical hits and evasion, serving as the team's striker.",
"Priest": "Expert in healing and support magic, playing a crucial support role in the team.",
"Archer": "Ranged attack specialist with high accuracy, suitable for kiting tactics."
}
st.info(class_descriptions[st.session_state.character['class']])
elif st.session_state.current_tab == "Character Development":
if not st.session_state.character:
st.warning("Please create a character first")
else:
st.markdown("""
<div style='text-align: center;'>
<h2>Character Development</h2>
<p>Enhance your character through daily login and attribute point allocation!</p>
</div>
""", unsafe_allow_html=True)
try:
# Check daily login reward
last_login = st.session_state.character.get("last_login")
if last_login:
try:
# Try to parse datetime string
if isinstance(last_login, str):
last_login_dt = datetime.fromisoformat(last_login)
if datetime.now() - last_login_dt > timedelta(days=1):
# Update level and experience
new_level = st.session_state.character["level"] + 1
new_exp = st.session_state.character["EXP"] + 100 # Add 100 exp per level
# Update character data
update_data = {
"level": new_level,
"EXP": new_exp,
"last_login": datetime.now().isoformat()
}
if update_character_stats(st.session_state.character["Id"], update_data):
st.session_state.character["level"] = new_level
st.session_state.character["EXP"] = new_exp
st.session_state.character["last_login"] = datetime.now().isoformat()
st.markdown(f"""
<div class='success-message'>
Daily login reward received:
<br>Level up: {new_level-1}{new_level}
<br>Experience gained: +100
<br>Received 5 attribute points!
</div>
""", unsafe_allow_html=True)
except (ValueError, TypeError):
# Ignore date check if parsing fails
pass
# Attribute point allocation
st.markdown("### Attribute Point Allocation")
col1, col2 = st.columns(2)
with col1:
atk_points = st.number_input("Attack", min_value=0, value=0)
def_points = st.number_input("Defense", min_value=0, value=0)
mag_points = st.number_input("Magic", min_value=0, value=0)
with col2:
dex_points = st.number_input("Dexterity", min_value=0, value=0)
luk_points = st.number_input("Luck", min_value=0, value=0)
if st.button("Confirm Allocation", use_container_width=True):
total_points = atk_points + def_points + mag_points + dex_points + luk_points
if total_points <= GAME_CONFIG["max_daily_points"]:
# Update character attributes
st.session_state.character["ATK"] += atk_points
st.session_state.character["DEF"] += def_points
st.session_state.character["MAG"] += mag_points
st.session_state.character["DEX"] += dex_points
st.session_state.character["LUK"] += luk_points
if update_character_stats(st.session_state.character["Id"], {
"ATK": st.session_state.character["ATK"],
"DEF": st.session_state.character["DEF"],
"MAG": st.session_state.character["MAG"],
"DEX": st.session_state.character["DEX"],
"LUK": st.session_state.character["LUK"]
}):
st.success("Attribute points allocated successfully!")
else:
st.warning(f"Maximum {GAME_CONFIG['max_daily_points']} attribute points per day")
except Exception as e:
st.error(f"Character development system error: {str(e)}")
elif st.session_state.current_tab == "PVP Battle":
if not st.session_state.character:
st.warning("Please create a character first")
else:
st.markdown("""
<div style='text-align: center;'>
<h2>PVP Battle</h2>
<p>Battle with other players and show your strength!</p>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
# Add friend challenge option
st.markdown("### Friend Challenge")
try:
# Use new opponent retrieval function
available_opponents = get_available_opponents(st.session_state.user_id)
if available_opponents:
# Display rankings
st.markdown("### Friend Rankings")
ranking_table = create_friend_ranking_table(st.session_state.character, available_opponents)
if ranking_table is not None:
# Use Streamlit's table display
st.dataframe(
ranking_table,
use_container_width=True,
hide_index=True,
column_config={
"Rank": st.column_config.NumberColumn("Rank", width="small"),
"Character": st.column_config.TextColumn("Character", width="medium"),
"Class": st.column_config.TextColumn("Class", width="small"),
"Level": st.column_config.NumberColumn("Level", width="small"),
"Attack": st.column_config.NumberColumn("Attack", width="small"),
"Defense": st.column_config.NumberColumn("Defense", width="small"),
"Magic": st.column_config.NumberColumn("Magic", width="small"),
"Dexterity": st.column_config.NumberColumn("Dexterity", width="small"),
"Luck": st.column_config.NumberColumn("Luck", width="small"),
"Total Stats": st.column_config.NumberColumn("Total Stats", width="small"),
"Player": st.column_config.TextColumn("Player", width="medium")
}
)
# Highlight current player's row
current_player_row = ranking_table[ranking_table['Character'] == st.session_state.character['name']]
if not current_player_row.empty:
st.markdown(f"""
<div style='background-color: #FFD700; padding: 10px; border-radius: 5px; margin-top: 10px;'>
<strong>Your Rank: #{current_player_row['Rank'].iloc[0]}</strong>
</div>
""", unsafe_allow_html=True)
# Create selection box
opponent_options = [f"{opp['name']} ({opp['class']}) - Level {opp['level']} - Player: {opp['username']}" for opp in available_opponents]
selected_opponent = st.selectbox(
"Select friend to challenge",
opponent_options,
index=0
)
if st.button("Challenge Friend", use_container_width=True):
selected_id = available_opponents[opponent_options.index(selected_opponent)]["id"]
try:
# Get opponent information
opponents = nocodb_client.get_records("characters", {
"where": f"(Id,eq,{selected_id})"
})
if opponents:
opponent = opponents[0]
# Generate battle story
battle_story = together_client.generate_battle_story(
st.session_state.character,
opponent
)
# Save battle record
battle_record = {
"attacker": st.session_state.character["Id"],
"defender": opponent["Id"],
"battle_story": battle_story,
"created_time": datetime.now().isoformat()
}
nocodb_client.create_record("battles", battle_record)
# Display battle story
st.markdown("### Battle Story")
st.markdown(f"""
<div class='battle-log'>
{battle_story}
</div>
""", unsafe_allow_html=True)
except Exception as e:
st.error(f"Battle failed: {str(e)}")
else:
st.info("No friends available for challenge")
except Exception as e:
st.error(f"Failed to get friend list: {str(e)}")
st.markdown("---")
st.markdown("### Direct Challenge")
opponent_id = st.text_input("Enter opponent's ID")
if st.button("Start Battle", use_container_width=True):
if opponent_id:
try:
# Get opponent information
opponents = nocodb_client.get_records("characters", {
"where": f"(Id,eq,{opponent_id})"
})
if opponents:
opponent = opponents[0]
# Generate battle story
battle_story = together_client.generate_battle_story(
st.session_state.character,
opponent
)
# Save battle record
battle_record = {
"attacker": st.session_state.character["Id"],
"defender": opponent["Id"],
"battle_story": battle_story,
"created_time": datetime.now().isoformat()
}
nocodb_client.create_record("battles", battle_record)
# Display battle story
st.markdown("### Battle Story")
st.markdown(f"""
<div class='battle-log'>
{battle_story}
</div>
""", unsafe_allow_html=True)
else:
st.warning("Opponent not found")
except Exception as e:
st.error(f"Battle failed: {str(e)}")
else:
st.warning("Please enter opponent's ID")
with col2:
st.markdown("### Recent Battle Records")
try:
battles = get_battle_records(st.session_state.character["Id"])
if battles:
for battle in battles: # Show last 5 battles
st.markdown(f"""
<div class='battle-log'>
<p>Battle Time: {battle['created_time']}</p>
<p>Battle Story: {battle['battle_story'][:100]}...</p>
</div>
""", unsafe_allow_html=True)
else:
st.info("No battle records")
except Exception as e:
st.error(f"Failed to get battle records: {str(e)}")
elif st.session_state.current_tab == "Boss Challenge":
if not st.session_state.character:
st.warning("Please create a character first")
else:
st.markdown("""
<div style='text-align: center;'>
<h2>Boss Challenge</h2>
<p>Form a team and challenge powerful Bosses for great rewards!</p>
</div>
""", unsafe_allow_html=True)
col1, col2 = st.columns(2)
with col1:
# Display current Boss information
st.markdown("### Current Boss")
for boss in GAME_CONFIG["bosses"]:
st.markdown(f"""
<div class='character-card'>
<h3>{boss['name']}</h3>
<p>Attack: <span class='stat-value'>{boss['ATK']}</span></p>
<p>Defense: <span class='stat-value'>{boss['DEF']}</span></p>
<p>Magic: <span class='stat-value'>{boss['MAG']}</span></p>
<p>Dexterity: <span class='stat-value'>{boss['DEX']}</span></p>
<p>Luck: <span class='stat-value'>{boss['LUK']}</span></p>
</div>
""", unsafe_allow_html=True)
# Team challenge
team_member_ids = st.text_area("Enter teammate IDs (one per line)")
if st.button("Start Challenge", use_container_width=True):
if team_member_ids:
try:
# Get teammate information
team = [st.session_state.character]
for id in team_member_ids.strip().split("\n"):
members = nocodb_client.get_records("characters", {
"where": f"(Id,eq,{id})"
})
if members:
team.append(members[0])
if len(team) > 1:
# Generate Boss battle story
boss_story = together_client.generate_boss_story(team, GAME_CONFIG["bosses"][0])
# Save challenge record
challenge_record = {
"team": ",".join([str(member["Id"]) for member in team]),
"boss_id": 1,
"story": boss_story,
"created_time": datetime.now().isoformat()
}
nocodb_client.create_record("boss_challenges", challenge_record)
# Display challenge story
st.markdown("### Challenge Story")
st.markdown(f"""
<div class='battle-log'>
{boss_story}
</div>
""", unsafe_allow_html=True)
else:
st.warning("Need at least one teammate")
except Exception as e:
st.error(f"Challenge failed: {str(e)}")
else:
st.warning("Please enter teammate IDs")
with col2:
st.markdown("### Recent Challenge Records")
try:
challenges = get_boss_challenge_records(st.session_state.character["Id"])
if challenges:
for challenge in challenges: # Show last 5 challenges
st.markdown(f"""
<div class='battle-log'>
<p>Challenge Time: {challenge['created_time']}</p>
<p>Challenge Story: {challenge['story'][:100]}...</p>
</div>
""", unsafe_allow_html=True)
else:
st.info("No challenge records")
except Exception as e:
st.error(f"Failed to get challenge records: {str(e)}")
# Footer
st.markdown("---")
st.markdown("""
<div style='text-align: center;'>
<p>Powered by Together AI, NocoDB, and Hugging Face Spaces</p>
</div>
""", unsafe_allow_html=True)