Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,226 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import plotly.express as px
|
4 |
+
import random
|
5 |
+
import json
|
6 |
+
import csv
|
7 |
+
import base64
|
8 |
+
import uuid
|
9 |
+
from io import StringIO
|
10 |
+
from datetime import datetime
|
11 |
+
from streamlit_flow import streamlit_flow
|
12 |
+
from streamlit_flow.elements import StreamlitFlowNode, StreamlitFlowEdge
|
13 |
+
from streamlit_flow.layouts import TreeLayout
|
14 |
+
|
15 |
+
# Rich data structures for game content
|
16 |
+
SITUATIONS = [
|
17 |
+
{
|
18 |
+
"id": "village_peril",
|
19 |
+
"name": "The Village in Peril",
|
20 |
+
"description": "A once-peaceful village is shrouded in dark miasma. Villagers cower in fear as shadowy figures lurk in the mist.",
|
21 |
+
"emoji": "ποΈ"
|
22 |
+
},
|
23 |
+
{
|
24 |
+
"id": "cursed_artifact",
|
25 |
+
"name": "The Cursed Artifact",
|
26 |
+
"description": "Deep in an ancient tomb, a glowing artifact pulses with malevolent energy, its whispers echoing in your mind.",
|
27 |
+
"emoji": "πΊ"
|
28 |
+
},
|
29 |
+
{
|
30 |
+
"id": "sacred_pact",
|
31 |
+
"name": "The Sacred Pact",
|
32 |
+
"description": "At a moonlit shrine, spirits of the land gather, awaiting a mediator to forge a new covenant between realms.",
|
33 |
+
"emoji": "π"
|
34 |
+
},
|
35 |
+
{
|
36 |
+
"id": "shapeshifter_challenge",
|
37 |
+
"name": "The Shapeshifter's Challenge",
|
38 |
+
"description": "A rival kitsune issues a challenge, their forms flickering between human and fox. The air crackles with competitive energy.",
|
39 |
+
"emoji": "π¦"
|
40 |
+
},
|
41 |
+
{
|
42 |
+
"id": "fox_fire_trial",
|
43 |
+
"name": "The Fox Fire Trial",
|
44 |
+
"description": "Sacred blue flames dance before you, a test of your mastery over kitsune magic. The heat is both inviting and intimidating.",
|
45 |
+
"emoji": "π₯"
|
46 |
+
}
|
47 |
+
]
|
48 |
+
|
49 |
+
ACTIONS = [
|
50 |
+
{
|
51 |
+
"id": "fox_fire",
|
52 |
+
"name": "Use Fox Fire",
|
53 |
+
"description": "Summon mystical flames to illuminate the darkness or ward off evil spirits.",
|
54 |
+
"emoji": "π₯"
|
55 |
+
},
|
56 |
+
{
|
57 |
+
"id": "shapeshift",
|
58 |
+
"name": "Shapeshift",
|
59 |
+
"description": "Transform your appearance to blend in or deceive others.",
|
60 |
+
"emoji": "π¦"
|
61 |
+
},
|
62 |
+
{
|
63 |
+
"id": "possess_object",
|
64 |
+
"name": "Possess an Object",
|
65 |
+
"description": "Infuse your spirit into an inanimate object to manipulate or gather information.",
|
66 |
+
"emoji": "π»"
|
67 |
+
},
|
68 |
+
{
|
69 |
+
"id": "call_spirits",
|
70 |
+
"name": "Call Upon Ancient Spirits",
|
71 |
+
"description": "Invoke the wisdom and power of your ancestors to guide you.",
|
72 |
+
"emoji": "π"
|
73 |
+
},
|
74 |
+
{
|
75 |
+
"id": "use_artifact",
|
76 |
+
"name": "Use Mystical Artifact",
|
77 |
+
"description": "Activate a powerful magical item to unleash its effects.",
|
78 |
+
"emoji": "π‘οΈ"
|
79 |
+
}
|
80 |
+
]
|
81 |
+
|
82 |
+
def generate_situation():
|
83 |
+
return random.choice(SITUATIONS)
|
84 |
+
|
85 |
+
def generate_actions():
|
86 |
+
return random.sample(ACTIONS, 3)
|
87 |
+
|
88 |
+
def evaluate_action(action, power_level, mystical_energy, history):
|
89 |
+
base_success_chance = (power_level + mystical_energy) / 2
|
90 |
+
if action['id'] in history:
|
91 |
+
success_chance = base_success_chance + (history[action['id']] * 2)
|
92 |
+
else:
|
93 |
+
success_chance = base_success_chance
|
94 |
+
outcome = random.randint(1, 100) <= success_chance
|
95 |
+
return outcome, success_chance
|
96 |
+
|
97 |
+
def create_graph_from_history(history_df):
|
98 |
+
nodes = []
|
99 |
+
edges = []
|
100 |
+
for index, row in history_df.iterrows():
|
101 |
+
node_id = f"{index}-{row['situation_id']}-{row['action_id']}"
|
102 |
+
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']}"
|
103 |
+
new_node = StreamlitFlowNode(node_id, (0, 0), {'content': content}, 'output', 'bottom', 'top')
|
104 |
+
nodes.append(new_node)
|
105 |
+
|
106 |
+
if index > 0:
|
107 |
+
prev_node_id = f"{index-1}-{history_df.iloc[index-1]['situation_id']}-{history_df.iloc[index-1]['action_id']}"
|
108 |
+
new_edge = StreamlitFlowEdge(f"{prev_node_id}-{node_id}", prev_node_id, node_id, animated=True, dashed=True)
|
109 |
+
edges.append(new_edge)
|
110 |
+
|
111 |
+
return nodes, edges
|
112 |
+
|
113 |
+
def save_history_to_csv(history_df):
|
114 |
+
csv_buffer = StringIO()
|
115 |
+
history_df.to_csv(csv_buffer, index=False)
|
116 |
+
csv_string = csv_buffer.getvalue()
|
117 |
+
b64 = base64.b64encode(csv_string.encode()).decode()
|
118 |
+
return b64
|
119 |
+
|
120 |
+
def load_history_from_csv(csv_string):
|
121 |
+
csv_buffer = StringIO(csv_string)
|
122 |
+
history_df = pd.read_csv(csv_buffer)
|
123 |
+
return history_df
|
124 |
+
|
125 |
+
def app():
|
126 |
+
st.markdown("# π¦ Kitsune - The Mystical Shape-Shifter Game π¦")
|
127 |
+
|
128 |
+
if 'user_id' not in st.session_state:
|
129 |
+
st.session_state.user_id = str(uuid.uuid4())
|
130 |
+
|
131 |
+
if 'game_state' not in st.session_state:
|
132 |
+
st.session_state.game_state = {
|
133 |
+
'score': 0,
|
134 |
+
'history': {},
|
135 |
+
'power_level': 50,
|
136 |
+
'mystical_energy': 75,
|
137 |
+
'history_df': pd.DataFrame(columns=['user_id', 'timestamp', 'situation_id', 'situation_name', 'situation_emoji', 'action_id', 'action_name', 'action_emoji', 'outcome', 'score'])
|
138 |
+
}
|
139 |
+
|
140 |
+
# Game Stats
|
141 |
+
st.sidebar.markdown("## π Game Stats")
|
142 |
+
st.sidebar.markdown(f"**Score:** {st.session_state.game_state['score']}")
|
143 |
+
|
144 |
+
# Character Stats
|
145 |
+
power_level = st.sidebar.slider('Power Level β‘', 0, 100, st.session_state.game_state['power_level'])
|
146 |
+
mystical_energy = st.sidebar.slider('Mystical Energy β¨', 0, 100, st.session_state.game_state['mystical_energy'])
|
147 |
+
|
148 |
+
# Game Loop
|
149 |
+
situation = generate_situation()
|
150 |
+
actions = generate_actions()
|
151 |
+
|
152 |
+
st.markdown(f"## {situation['emoji']} Current Situation: {situation['name']}")
|
153 |
+
st.markdown(situation['description'])
|
154 |
+
st.markdown("### π Choose your action:")
|
155 |
+
|
156 |
+
cols = st.columns(3)
|
157 |
+
for i, action in enumerate(actions):
|
158 |
+
if cols[i].button(f"{action['emoji']} {action['name']}"):
|
159 |
+
outcome, success_chance = evaluate_action(action, power_level, mystical_energy, st.session_state.game_state['history'])
|
160 |
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
161 |
+
|
162 |
+
st.markdown(f"You decided to: **{action['name']}**")
|
163 |
+
st.markdown(action['description'])
|
164 |
+
st.markdown(f"**Outcome:** {'β
Success!' if outcome else 'β Failure.'}")
|
165 |
+
st.markdown(f"**Success Chance:** {success_chance:.2f}%")
|
166 |
+
|
167 |
+
if outcome:
|
168 |
+
st.session_state.game_state['score'] += 1
|
169 |
+
|
170 |
+
# Update history
|
171 |
+
if action['id'] in st.session_state.game_state['history']:
|
172 |
+
st.session_state.game_state['history'][action['id']] += 1 if outcome else -1
|
173 |
+
else:
|
174 |
+
st.session_state.game_state['history'][action['id']] = 1 if outcome else -1
|
175 |
+
|
176 |
+
# Add new record to history DataFrame
|
177 |
+
new_record = pd.DataFrame({
|
178 |
+
'user_id': [st.session_state.user_id],
|
179 |
+
'timestamp': [timestamp],
|
180 |
+
'situation_id': [situation['id']],
|
181 |
+
'situation_name': [situation['name']],
|
182 |
+
'situation_emoji': [situation['emoji']],
|
183 |
+
'action_id': [action['id']],
|
184 |
+
'action_name': [action['name']],
|
185 |
+
'action_emoji': [action['emoji']],
|
186 |
+
'outcome': [outcome],
|
187 |
+
'score': [st.session_state.game_state['score']]
|
188 |
+
})
|
189 |
+
st.session_state.game_state['history_df'] = pd.concat([st.session_state.game_state['history_df'], new_record], ignore_index=True)
|
190 |
+
|
191 |
+
# Save history to CSV and provide download link
|
192 |
+
csv_b64 = save_history_to_csv(st.session_state.game_state['history_df'])
|
193 |
+
href = f'<a href="data:file/csv;base64,{csv_b64}" download="kitsune_game_history.csv">Download Game History</a>'
|
194 |
+
st.markdown(href, unsafe_allow_html=True)
|
195 |
+
|
196 |
+
# Display Graph
|
197 |
+
if not st.session_state.game_state['history_df'].empty:
|
198 |
+
st.markdown("## π³ Your Journey")
|
199 |
+
nodes, edges = create_graph_from_history(st.session_state.game_state['history_df'])
|
200 |
+
streamlit_flow('kitsune_game_flow',
|
201 |
+
nodes,
|
202 |
+
edges,
|
203 |
+
layout=TreeLayout(direction='down'),
|
204 |
+
fit_view=True,
|
205 |
+
height=600)
|
206 |
+
|
207 |
+
# Character Stats Visualization
|
208 |
+
data = {"Stat": ["Power Level β‘", "Mystical Energy β¨"],
|
209 |
+
"Value": [power_level, mystical_energy]}
|
210 |
+
df = pd.DataFrame(data)
|
211 |
+
fig = px.bar(df, x='Stat', y='Value', title="Kitsune Stats π")
|
212 |
+
st.plotly_chart(fig)
|
213 |
+
|
214 |
+
# Load Game State
|
215 |
+
st.markdown("## πΎ Load Game")
|
216 |
+
uploaded_file = st.file_uploader("Load Game History", type="csv")
|
217 |
+
if uploaded_file is not None:
|
218 |
+
csv_string = uploaded_file.getvalue().decode()
|
219 |
+
loaded_history_df = load_history_from_csv(csv_string)
|
220 |
+
st.session_state.game_state['history_df'] = loaded_history_df
|
221 |
+
st.session_state.game_state['score'] = loaded_history_df['score'].iloc[-1]
|
222 |
+
st.session_state.user_id = loaded_history_df['user_id'].iloc[0]
|
223 |
+
st.success("Game history loaded successfully!")
|
224 |
+
|
225 |
+
if __name__ == "__main__":
|
226 |
+
app()
|