CatRider / app.py
awacke1's picture
Update app.py
0f4bb5a verified
raw
history blame
24.1 kB
# 1. Configuration
import streamlit as st
import pandas as pd
import plotly.express as px
import random
import uuid
import os
import re
from datetime import datetime
from streamlit_flow import streamlit_flow
from streamlit_flow.elements import StreamlitFlowNode, StreamlitFlowEdge
from streamlit_flow.layouts import RadialLayout
# Set page configuration to wide mode
Site_Name = '🦁CatRider🐈'
title="🦁CatRider🐈by👤Aaron Wacker"
helpURL='https://huggingface.co/awacke1'
bugURL='https://huggingface.co/spaces/awacke1'
icons='🦁'
useConfig=True
if useConfig:
st.set_page_config(
page_title=title,
page_icon=icons,
layout="wide",
initial_sidebar_state="auto",
menu_items={
'Get Help': helpURL,
'Report a bug': bugURL,
'About': title
}
)
# 🐱 Cat Rider and Gear Data
CAT_RIDERS = [
{"name": "Whiskers", "type": "Speed", "emoji": "🐾", "strength": 3, "skill": 7},
{"name": "Fluffy", "type": "Bravery", "emoji": "🦁", "strength": 5, "skill": 5},
{"name": "Midnight", "type": "Stealth", "emoji": "🌑", "strength": 4, "skill": 6},
{"name": "Bella", "type": "Charm", "emoji": "😺", "strength": 2, "skill": 8},
{"name": "Shadow", "type": "Mystery", "emoji": "👤", "strength": 4, "skill": 6},
{"name": "Simba", "type": "Royalty", "emoji": "🦁", "strength": 5, "skill": 7},
{"name": "Luna", "type": "Magic", "emoji": "🌙", "strength": 3, "skill": 8},
{"name": "Leo", "type": "Courage", "emoji": "🐯", "strength": 6, "skill": 5},
{"name": "Milo", "type": "Playful", "emoji": "😼", "strength": 4, "skill": 7},
{"name": "Nala", "type": "Grace", "emoji": "🐈", "strength": 5, "skill": 6}
]
RIDING_GEAR = [
{"name": "Feathered Boots", "type": "Agility", "strength": 2},
{"name": "Golden Armor", "type": "Defense", "strength": 4},
{"name": "Magic Whisker Wand", "type": "Magic", "strength": 3},
{"name": "Sleek Shadow Cape", "type": "Stealth", "strength": 1}
]
# 🌍 Game World Data (Expanded to 10 Situations)
SITUATIONS = [
{"id": "feline_escape", "name": "The Great Feline Escape", "description": "Your cat rider is trapped in an old mansion...", "emoji": "🚪", "type": "escape", "preferred_action": "agility"},
{"id": "lost_temple", "name": "The Treasure of the Lost Temple", "description": "On a quest to retrieve an ancient artifact...", "emoji": "🏛️", "type": "exploration", "preferred_action": "resourcefulness"},
{"id": "royal_tournament", "name": "The Royal Tournament", "description": "Compete in a grand tournament...", "emoji": "👑", "type": "competition", "preferred_action": "bravery"},
{"id": "sky_race", "name": "The Sky Race", "description": "Compete in the annual Sky Race...", "emoji": "☁️", "type": "competition", "preferred_action": "agility"},
{"id": "cheese_heist", "name": "The Great Cheese Heist", "description": "Your cat rider must sneak into the royal pantry...", "emoji": "🧀", "type": "heist", "preferred_action": "stealth"},
{"id": "pirate_cove", "name": "The Pirate Cove", "description": "Sail the high seas with your trusty crew...", "emoji": "🏴‍☠️", "type": "exploration", "preferred_action": "bravery"},
{"id": "feline_moon_mission", "name": "The Feline Moon Mission", "description": "Blast off into space...", "emoji": "🌕", "type": "exploration", "preferred_action": "resourcefulness"},
{"id": "purr_summit", "name": "The Purr Summit", "description": "Join a secret gathering of the most intellectual cats...", "emoji": "📜", "type": "debate", "preferred_action": "insight"},
{"id": "feline_invasion", "name": "The Feline Invasion", "description": "Aliens have invaded Earth...", "emoji": "👽", "type": "battle", "preferred_action": "strategy"},
{"id": "eternal_catnap", "name": "The Eternal Catnap", "description": "You've entered a sacred temple...", "emoji": "💤", "type": "exploration", "preferred_action": "stealth"}
]
# 🧠 Expanded Actions (10 Actions)
ACTIONS = [
{"id": "stealth", "name": "Use Stealth", "description": "Sneak past obstacles...", "emoji": "🤫", "type": "skill"},
{"id": "agility", "name": "Showcase Agility", "description": "Perform impressive acrobatic maneuvers...", "emoji": "🏃", "type": "physical"},
{"id": "charm", "name": "Charm Others", "description": "Use your cat's natural charisma...", "emoji": "😻", "type": "social"},
{"id": "resourcefulness", "name": "Be Resourceful", "description": "Utilize the environment or items...", "emoji": "🧠", "type": "mental"},
{"id": "bravery", "name": "Show Bravery", "description": "Face dangers head-on...", "emoji": "🦁", "type": "physical"},
{"id": "strategy", "name": "Develop a Strategy", "description": "Use tactical thinking...", "emoji": "🧠", "type": "mental"},
{"id": "speed", "name": "Sprint Away", "description": "Run faster than you've ever run...", "emoji": "🏃‍♀️", "type": "physical"},
{"id": "insight", "name": "Use Insight", "description": "Tap into ancient feline wisdom...", "emoji": "🔮", "type": "mental"},
{"id": "distraction", "name": "Create a Distraction", "description": "Use cunning tricks...", "emoji": "🪄", "type": "mental"},
{"id": "negotiation", "name": "Negotiate", "description": "Use diplomacy and clever negotiation...", "emoji": "💼", "type": "social"}
]
# Expanded conclusions for outcomes - 10 items each for success and failure
SUCCESS_CONCLUSIONS = [
"Your swift paws led you to victory! 🎉",
"You pounced at the perfect moment! 🏆",
"The stars aligned for your cat rider! 🌟",
"You navigated the challenge like a true feline champion! 🐱",
"Victory is sweet, just like a bowl of fresh milk! 🥛",
"Your opponents are left in awe of your skills! 😺",
"You’ve earned the title of Cat Commander! 🏅",
"All the other cats are jealous of your agility! 🏃‍♂️",
"Your strategy was flawless, and the victory is yours! 🎖️",
"Your cat rider is now a legend in the feline world! 👑"
]
FAILURE_CONCLUSIONS = [
"You tried your best, but it just wasn’t enough. 😿",
"Maybe next time, kitty. Keep your tail up! 🐾",
"That didn’t go as planned. Time for a catnap to recover! 💤",
"Even the best cats have their off days. 😔",
"The challenge was too great this time. Better luck next time! 🍀",
"You might need more than nine lives to get through this. 🐈",
"The enemy was too clever for your plan. 🧠",
"You tripped over your own paws! 🐾",
"The cat gods were not in your favor today. 🙀",
"It’s okay, every cat has a learning curve. 📚"
]
# Function to save history to a markdown file
def save_history_to_file(history_df, user_id):
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"history_{user_id}_{timestamp}.md"
markdown, _, _ = process_journey_history(history_df)
with open(filename, 'w', encoding='utf-8') as f:
f.write(markdown)
return filename
# Function to load saved histories and reconstruct game state
def load_saved_histories(user_id):
history_files = [f for f in os.listdir('.') if f.startswith(f'history_{user_id}_') and f.endswith('.md')]
history_files.sort() # Ensure files are sorted by timestamp
combined_history = ''
for history_file in history_files:
with open(history_file, 'r', encoding='utf-8') as f:
combined_history += f.read() + '\n'
return combined_history
# Function to parse markdown history and reconstruct history DataFrame
def parse_history_markdown(markdown_text):
lines = markdown_text.split('\n')
data = []
current_situation = None
for line in lines:
situation_match = re.match(r"### (\S+) \*\*(.+)\*\*", line)
if situation_match:
situation_emoji = situation_match.group(1)
situation_name = situation_match.group(2)
continue
attempt_match = re.match(r"Attempt (\d+): (\S+) \*\*(.+)\*\*: (✅ Success|❌ Failure) (\S*)", line)
if attempt_match:
attempt = int(attempt_match.group(1))
action_emoji = attempt_match.group(2)
action_name = attempt_match.group(3)
outcome_str = attempt_match.group(4)
stars = attempt_match.group(5)
outcome = True if outcome_str == '✅ Success' else False
score = len(stars)
conclusion_line_index = lines.index(line) + 1
conclusion = lines[conclusion_line_index] if conclusion_line_index < len(lines) else ''
data.append({
'situation_name': situation_name,
'situation_emoji': situation_emoji,
'attempt': attempt,
'action_name': action_name,
'action_emoji': action_emoji,
'outcome': outcome,
'score': score,
'conclusion': conclusion
})
history_df = pd.DataFrame(data)
return history_df
# 🧠 Game Mechanics
def generate_situation():
return random.choice(SITUATIONS)
def generate_actions():
return random.sample(ACTIONS, min(3, len(ACTIONS)))
def evaluate_action(situation, action, gear_strength, rider_skill, history):
# Adjusted base success chance to around 50%
base_success_chance = 50
# Adjust success chance based on gear strength and rider skill
stat_modifier = (gear_strength + rider_skill - 10) * 2 # Assuming average stats sum to 10
success_chance = base_success_chance + stat_modifier
# Boost success chance for preferred action
if action['id'] == situation['preferred_action']:
success_chance += 10
# Adjust based on action history
if action['id'] in history:
success_chance += history[action['id']] * 2
# Clamp success chance between 5% and 95%
success_chance = max(5, min(95, success_chance))
outcome = random.randint(1, 100) <= success_chance
return outcome, success_chance
def generate_encounter_conclusion(situation, action, outcome):
if outcome:
return random.choice(SUCCESS_CONCLUSIONS)
else:
return random.choice(FAILURE_CONCLUSIONS)
# 🔄 Update character stats based on outcome
def update_character_stats(game_state, outcome):
if outcome:
# Increase stats on success
game_state['rider_skill'] += 0.5
game_state['gear_strength'] += 0.2
else:
# Decrease stats on failure, but not below 1
game_state['rider_skill'] = max(1, game_state['rider_skill'] - 0.3)
game_state['gear_strength'] = max(1, game_state['gear_strength'] - 0.1)
return game_state
# 🌳 Process Journey History to Create Markdown and Graph Data
def process_journey_history(history_df):
markdown = "## 🌳 Journey Preview\n\n"
nodes = []
edges = []
node_ids = {}
score = 0 # To keep track of the score for success nodes
grouped = history_df.groupby(['situation_name'], sort=False)
# Main node to connect all situations
main_node_id = "main"
main_node = StreamlitFlowNode(
main_node_id,
pos=(0, 0),
data={'content': "# Your Journey"},
type='input',
target_position='bottom',
width=200
)
nodes.append(main_node)
for situation_name, group in grouped:
situation_emoji = group.iloc[0]['situation_emoji']
situation_id = group.iloc[0].get('situation_id', str(uuid.uuid4()))
situation_node_id = f"situation_{situation_id}"
markdown += f"### {situation_emoji} **{situation_name}**\n"
# Create situation node if not already created
if situation_node_id not in node_ids:
situation_node = StreamlitFlowNode(
situation_node_id,
pos=(0, 0),
data={'content': f"### {situation_emoji} {situation_name}"},
type='default',
target_position='top',
source_position='bottom',
width=200
)
nodes.append(situation_node)
node_ids[situation_node_id] = situation_node_id
# Edge from main node to situation node
edges.append(StreamlitFlowEdge(
id=f"edge_{main_node_id}_{situation_node_id}",
source=main_node_id,
target=situation_node_id,
animated=True
))
for idx, row in group.iterrows():
attempt = row['attempt']
action_emoji = row['action_emoji']
action_name = row['action_name']
outcome = row['outcome']
outcome_str = '✅ Success' if outcome else '❌ Failure'
stars = '⭐' * int(row.get('score', 0)) if outcome else ''
conclusion = row.get('conclusion', '')
markdown += f"Attempt {attempt}: {action_emoji} **{action_name}**: {outcome_str} {stars}\n"
markdown += f"{conclusion}\n"
# Create attempt node
attempt_node_id = f"attempt_{situation_id}_{attempt}_{idx}"
attempt_content = f"Attempt {attempt}: {action_emoji} {action_name}\n{outcome_str} {stars}\n{conclusion}"
attempt_node = StreamlitFlowNode(
attempt_node_id,
pos=(0, 0),
data={'content': attempt_content},
type='output',
target_position='top',
source_position='bottom',
width=250
)
nodes.append(attempt_node)
# Edge from situation to attempt
edges.append(StreamlitFlowEdge(
id=f"edge_{situation_node_id}_{attempt_node_id}",
source=situation_node_id,
target=attempt_node_id,
animated=True
))
markdown += "\n"
return markdown, nodes, edges
# 🔄 Update game state with the result of the action
def update_game_state(game_state, situation, action, outcome, timestamp):
# Generate the encounter conclusion (success or failure)
conclusion = generate_encounter_conclusion(situation, action, outcome)
# Update stats based on the outcome
game_state = update_character_stats(game_state, outcome)
# Update attempt count
attempt = game_state['current_attempt']
# Update score
if outcome:
game_state['score'] += 1
# Create a new record for the history
new_record = pd.DataFrame({
'user_id': [game_state['user_id']],
'timestamp': [timestamp],
'situation_id': [situation['id']],
'situation_name': [situation['name']],
'situation_emoji': [situation['emoji']],
'situation_type': [situation['type']],
'attempt': [attempt],
'action_id': [action['id']],
'action_name': [action['name']],
'action_emoji': [action['emoji']],
'action_type': [action['type']],
'outcome': [outcome],
'conclusion': [conclusion],
'gear_strength': [game_state['gear_strength']],
'rider_skill': [game_state['rider_skill']],
'score': [game_state['score']]
})
# Add the new record to the game history DataFrame
game_state['history_df'] = pd.concat([game_state['history_df'], new_record], ignore_index=True)
# Update the history of actions (tracking how many times each action was used)
if action['id'] in game_state['history']:
game_state['history'][action['id']] += 1 if outcome else -1
else:
game_state['history'][action['id']] = 1 if outcome else -1
# Automatically save history after each action
save_history_to_file(game_state['history_df'], game_state['user_id'])
return game_state
# 🏅 Display Scoreboard with Star Emojis and Buckyball Outline
def display_scoreboard(game_state):
# Calculate number of star emojis based on score
score = game_state['score']
stars = '⭐' * int(score)
# Create buckyball style outline (simplified)
outline = ''
if score > 0:
outline = '''
⬡ ⬡ ⬡ ⬡ ⬡
⬡ ⬡ ⬡ ⬡
⬡ ⬡ ⬡ ⬡ ⬡
⬡ ⬡ ⬡ ⬡
⬡ ⬡ ⬡ ⬡ ⬡
'''
else:
outline = 'No successes yet.'
st.markdown("## 🏅 Scoreboard")
st.markdown(f"**Score:** {stars} ({score})")
st.markdown(outline)
# 🎮 Main Game Application
def main():
st.title("🐱 Cat Rider 🏇")
# 📜 Game Rules
st.markdown("""
### 📜 Game Rules
| Step | Description |
|------|-------------|
| 1️⃣ | Choose your Cat Rider |
| 2️⃣ | Select your Riding Gear |
| 3️⃣ | Set off on an Adventure |
| 4️⃣ | Encounter Challenges and Make Decisions |
| 5️⃣ | Complete the Quest and Grow Stronger |
""")
# 🏁 Initialize game state
if 'game_state' not in st.session_state:
st.session_state.game_state = {
'user_id': None,
'score': 0,
'history': {},
'gear_strength': 0,
'rider_skill': 0,
'cat_rider': None,
'riding_gear': None,
'history_df': pd.DataFrame(columns=['user_id', 'timestamp', 'situation_id', 'situation_name', 'situation_emoji', 'situation_type', 'attempt', 'action_id', 'action_name', 'action_emoji', 'action_type', 'outcome', 'conclusion', 'gear_strength', 'rider_skill', 'score']),
'current_situation': None,
'current_attempt': 1,
'actions': [],
'succeeded': False
}
game_state = st.session_state.game_state
# 🐱 Cat Rider Selection or Loading Previous State
if game_state['cat_rider'] is None:
st.markdown("## Choose Your Cat Rider or Load Previous Journey:")
# Check for existing histories
existing_riders = [f.split('_')[1] for f in os.listdir('.') if f.startswith('history_')]
existing_riders = list(set(existing_riders))
cols = st.columns(len(CAT_RIDERS))
for i, rider in enumerate(CAT_RIDERS):
if cols[i].button(f"{rider['emoji']} {rider['name']} ({rider['type']})", key=f"rider_{i}"):
game_state['cat_rider'] = rider
game_state['rider_skill'] = rider['skill']
game_state['user_id'] = rider['name'] # Use rider name as user ID
# Load existing history if available
existing_history = load_saved_histories(game_state['user_id'])
if existing_history:
history_df = parse_history_markdown(existing_history)
game_state['history_df'] = history_df
game_state['score'] = history_df['score'].max() if not history_df.empty else 0
# 🏇 Riding Gear Selection
if game_state['riding_gear'] is None and game_state['cat_rider'] is not None:
st.markdown("## Select Your Riding Gear:")
cols = st.columns(len(RIDING_GEAR))
for i, gear in enumerate(RIDING_GEAR):
if cols[i].button(f"{gear['name']} ({gear['type']})", key=f"gear_{i}"):
game_state['riding_gear'] = gear
game_state['gear_strength'] = gear['strength']
# 🎭 Game Loop
if game_state['cat_rider'] is not None and game_state['riding_gear'] is not None:
# Check if current_situation is None or if the player succeeded in the previous situation
if game_state['current_situation'] is None or game_state['succeeded']:
# Generate a new situation
game_state['current_situation'] = generate_situation()
game_state['current_attempt'] = 1
game_state['succeeded'] = False
# Clear actions for new situation
game_state['actions'] = []
situation = game_state['current_situation']
st.markdown(f"## {situation['emoji']} Current Situation: {situation['name']} ({situation['type']})")
st.markdown(situation['description'])
st.markdown("### 🎭 Choose your action:")
# Generate actions if not already generated
if not game_state['actions']:
game_state['actions'] = generate_actions()
cols = st.columns(3)
action_chosen = False
for i, action in enumerate(game_state['actions']):
if cols[i].button(f"{action['emoji']} {action['name']} ({action['type']})", key=f"action_{i}_{game_state['current_attempt']}"):
outcome, success_chance = evaluate_action(situation, action, game_state['gear_strength'], game_state['rider_skill'], game_state['history'])
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
st.markdown(f"You decided to: **{action['name']}** ({action['type']})")
st.markdown(action['description'])
st.markdown(f"**Outcome:** {'✅ Success!' if outcome else '❌ Failure.'}")
st.markdown(f"**Success Chance:** {success_chance:.2f}%")
if outcome:
game_state['succeeded'] = True
# Clear actions for next situation
game_state['actions'] = []
else:
game_state['current_attempt'] += 1
# Generate new actions for retry
game_state['actions'] = generate_actions()
# 🔄 Update game state
game_state = update_game_state(
game_state,
situation,
action,
outcome,
timestamp
)
# Display conclusion
conclusion = game_state['history_df'].iloc[-1]['conclusion']
st.markdown(f"**Encounter Conclusion:** {conclusion}")
# Display updated stats
st.markdown(f"**Updated Stats:**")
st.markdown(f"💪 Gear Strength: {game_state['gear_strength']:.2f}")
st.markdown(f"🏋️ Rider Skill: {game_state['rider_skill']:.2f}")
# 🏅 Display Scoreboard
display_scoreboard(game_state)
action_chosen = True
break # Exit the loop after action is taken
# If no action was chosen, show a message
if not action_chosen:
st.markdown("Please choose an action to proceed.")
# Integration point for both functions
if not game_state['history_df'].empty:
# 📝 Process Journey History to get markdown and graph data
markdown_preview, nodes, edges = process_journey_history(game_state['history_df'])
# 📝 Display Markdown Preview
st.markdown(markdown_preview)
# 🌳 Display Knowledge Journey Graph
st.markdown("## 🌳 Your Journey (Knowledge Graph)")
try:
streamlit_flow('cat_rider_flow',
nodes,
edges,
layout=RadialLayout(),
fit_view=True,
height=1000)
except Exception as e:
st.error(f"An error occurred while rendering the journey graph: {str(e)}")
st.markdown("Please try refreshing the page if the graph doesn't appear.")
# 📊 Character Stats Visualization
data = {"Stat": ["Gear Strength 🛡️", "Rider Skill 🏇"],
"Value": [game_state['gear_strength'], game_state['rider_skill']]}
df = pd.DataFrame(data)
fig = px.bar(df, x='Stat', y='Value', title="Cat Rider Stats 📊")
st.plotly_chart(fig)
# Automatically load saved history if available
if game_state['user_id'] and not game_state['history_df'].empty:
existing_history = load_saved_histories(game_state['user_id'])
if existing_history:
st.markdown("## 📂 Loaded Journey History")
st.markdown(existing_history)
if __name__ == "__main__":
main()