|
import streamlit as st |
|
import pandas as pd |
|
import plotly.express as px |
|
import random |
|
import json |
|
import csv |
|
import base64 |
|
import uuid |
|
from io import StringIO |
|
from datetime import datetime |
|
from streamlit_flow import streamlit_flow |
|
from streamlit_flow.elements import StreamlitFlowNode, StreamlitFlowEdge |
|
from streamlit_flow.layouts import TreeLayout |
|
|
|
|
|
SITUATIONS = [ |
|
{ |
|
"id": "village_peril", |
|
"name": "The Village in Peril", |
|
"description": "A once-peaceful village is shrouded in dark miasma. Villagers cower in fear as shadowy figures lurk in the mist.", |
|
"emoji": "ποΈ" |
|
}, |
|
{ |
|
"id": "cursed_artifact", |
|
"name": "The Cursed Artifact", |
|
"description": "Deep in an ancient tomb, a glowing artifact pulses with malevolent energy, its whispers echoing in your mind.", |
|
"emoji": "πΊ" |
|
}, |
|
{ |
|
"id": "sacred_pact", |
|
"name": "The Sacred Pact", |
|
"description": "At a moonlit shrine, spirits of the land gather, awaiting a mediator to forge a new covenant between realms.", |
|
"emoji": "π" |
|
}, |
|
{ |
|
"id": "shapeshifter_challenge", |
|
"name": "The Shapeshifter's Challenge", |
|
"description": "A rival kitsune issues a challenge, their forms flickering between human and fox. The air crackles with competitive energy.", |
|
"emoji": "π¦" |
|
}, |
|
{ |
|
"id": "fox_fire_trial", |
|
"name": "The Fox Fire Trial", |
|
"description": "Sacred blue flames dance before you, a test of your mastery over kitsune magic. The heat is both inviting and intimidating.", |
|
"emoji": "π₯" |
|
} |
|
] |
|
|
|
ACTIONS = [ |
|
{ |
|
"id": "fox_fire", |
|
"name": "Use Fox Fire", |
|
"description": "Summon mystical flames to illuminate the darkness or ward off evil spirits.", |
|
"emoji": "π₯" |
|
}, |
|
{ |
|
"id": "shapeshift", |
|
"name": "Shapeshift", |
|
"description": "Transform your appearance to blend in or deceive others.", |
|
"emoji": "π¦" |
|
}, |
|
{ |
|
"id": "possess_object", |
|
"name": "Possess an Object", |
|
"description": "Infuse your spirit into an inanimate object to manipulate or gather information.", |
|
"emoji": "π»" |
|
}, |
|
{ |
|
"id": "call_spirits", |
|
"name": "Call Upon Ancient Spirits", |
|
"description": "Invoke the wisdom and power of your ancestors to guide you.", |
|
"emoji": "π" |
|
}, |
|
{ |
|
"id": "use_artifact", |
|
"name": "Use Mystical Artifact", |
|
"description": "Activate a powerful magical item to unleash its effects.", |
|
"emoji": "π‘οΈ" |
|
} |
|
] |
|
|
|
def generate_situation(): |
|
return random.choice(SITUATIONS) |
|
|
|
def generate_actions(): |
|
return random.sample(ACTIONS, 3) |
|
|
|
def evaluate_action(action, power_level, mystical_energy, history): |
|
base_success_chance = (power_level + mystical_energy) / 2 |
|
if action['id'] in history: |
|
success_chance = base_success_chance + (history[action['id']] * 2) |
|
else: |
|
success_chance = base_success_chance |
|
outcome = random.randint(1, 100) <= success_chance |
|
return outcome, success_chance |
|
|
|
def create_graph_from_history(history_df): |
|
nodes = [] |
|
edges = [] |
|
for index, row in history_df.iterrows(): |
|
node_id = f"{index}-{row['situation_id']}-{row['action_id']}" |
|
content = f"{row['situation_emoji']} {row['situation_name']}\n{row['action_emoji']} {row['action_name']}\nOutcome: {'β
Success' if row['outcome'] else 'β Failure'}\nπ {row['timestamp']}" |
|
new_node = StreamlitFlowNode(node_id, (0, 0), {'content': content}, 'output', 'bottom', 'top') |
|
nodes.append(new_node) |
|
|
|
if index > 0: |
|
prev_node_id = f"{index-1}-{history_df.iloc[index-1]['situation_id']}-{history_df.iloc[index-1]['action_id']}" |
|
new_edge = StreamlitFlowEdge(f"{prev_node_id}-{node_id}", prev_node_id, node_id, animated=True, dashed=True) |
|
edges.append(new_edge) |
|
|
|
return nodes, edges |
|
|
|
def save_history_to_csv(history_df): |
|
csv_buffer = StringIO() |
|
history_df.to_csv(csv_buffer, index=False) |
|
csv_string = csv_buffer.getvalue() |
|
b64 = base64.b64encode(csv_string.encode()).decode() |
|
return b64 |
|
|
|
def load_history_from_csv(csv_string): |
|
csv_buffer = StringIO(csv_string) |
|
history_df = pd.read_csv(csv_buffer) |
|
return history_df |
|
|
|
def app(): |
|
st.markdown("# π¦ Kitsune - The Mystical Shape-Shifter Game π¦") |
|
|
|
if 'user_id' not in st.session_state: |
|
st.session_state.user_id = str(uuid.uuid4()) |
|
|
|
if 'game_state' not in st.session_state: |
|
st.session_state.game_state = { |
|
'score': 0, |
|
'history': {}, |
|
'power_level': 50, |
|
'mystical_energy': 75, |
|
'history_df': pd.DataFrame(columns=['user_id', 'timestamp', 'situation_id', 'situation_name', 'situation_emoji', 'action_id', 'action_name', 'action_emoji', 'outcome', 'score']) |
|
} |
|
|
|
|
|
st.sidebar.markdown("## π Game Stats") |
|
st.sidebar.markdown(f"**Score:** {st.session_state.game_state['score']}") |
|
|
|
|
|
power_level = st.sidebar.slider('Power Level β‘', 0, 100, st.session_state.game_state['power_level']) |
|
mystical_energy = st.sidebar.slider('Mystical Energy β¨', 0, 100, st.session_state.game_state['mystical_energy']) |
|
|
|
|
|
situation = generate_situation() |
|
actions = generate_actions() |
|
|
|
st.markdown(f"## {situation['emoji']} Current Situation: {situation['name']}") |
|
st.markdown(situation['description']) |
|
st.markdown("### π Choose your action:") |
|
|
|
cols = st.columns(3) |
|
for i, action in enumerate(actions): |
|
if cols[i].button(f"{action['emoji']} {action['name']}"): |
|
outcome, success_chance = evaluate_action(action, power_level, mystical_energy, st.session_state.game_state['history']) |
|
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
|
|
|
st.markdown(f"You decided to: **{action['name']}**") |
|
st.markdown(action['description']) |
|
st.markdown(f"**Outcome:** {'β
Success!' if outcome else 'β Failure.'}") |
|
st.markdown(f"**Success Chance:** {success_chance:.2f}%") |
|
|
|
if outcome: |
|
st.session_state.game_state['score'] += 1 |
|
|
|
|
|
if action['id'] in st.session_state.game_state['history']: |
|
st.session_state.game_state['history'][action['id']] += 1 if outcome else -1 |
|
else: |
|
st.session_state.game_state['history'][action['id']] = 1 if outcome else -1 |
|
|
|
|
|
new_record = pd.DataFrame({ |
|
'user_id': [st.session_state.user_id], |
|
'timestamp': [timestamp], |
|
'situation_id': [situation['id']], |
|
'situation_name': [situation['name']], |
|
'situation_emoji': [situation['emoji']], |
|
'action_id': [action['id']], |
|
'action_name': [action['name']], |
|
'action_emoji': [action['emoji']], |
|
'outcome': [outcome], |
|
'score': [st.session_state.game_state['score']] |
|
}) |
|
st.session_state.game_state['history_df'] = pd.concat([st.session_state.game_state['history_df'], new_record], ignore_index=True) |
|
|
|
|
|
csv_b64 = save_history_to_csv(st.session_state.game_state['history_df']) |
|
href = f'<a href="data:file/csv;base64,{csv_b64}" download="kitsune_game_history.csv">Download Game History</a>' |
|
st.markdown(href, unsafe_allow_html=True) |
|
|
|
|
|
if not st.session_state.game_state['history_df'].empty: |
|
st.markdown("## π³ Your Journey") |
|
nodes, edges = create_graph_from_history(st.session_state.game_state['history_df']) |
|
streamlit_flow('kitsune_game_flow', |
|
nodes, |
|
edges, |
|
layout=TreeLayout(direction='down'), |
|
fit_view=True, |
|
height=600) |
|
|
|
|
|
data = {"Stat": ["Power Level β‘", "Mystical Energy β¨"], |
|
"Value": [power_level, mystical_energy]} |
|
df = pd.DataFrame(data) |
|
fig = px.bar(df, x='Stat', y='Value', title="Kitsune Stats π") |
|
st.plotly_chart(fig) |
|
|
|
|
|
st.markdown("## πΎ Load Game") |
|
uploaded_file = st.file_uploader("Load Game History", type="csv") |
|
if uploaded_file is not None: |
|
csv_string = uploaded_file.getvalue().decode() |
|
loaded_history_df = load_history_from_csv(csv_string) |
|
st.session_state.game_state['history_df'] = loaded_history_df |
|
st.session_state.game_state['score'] = loaded_history_df['score'].iloc[-1] |
|
st.session_state.user_id = loaded_history_df['user_id'].iloc[0] |
|
st.success("Game history loaded successfully!") |
|
|
|
if __name__ == "__main__": |
|
app() |