Download game/library_system.py from Camais03/camie-tagger: direct link, hf CLI and curl.
- Browser
- Download file 82.5 kB
-
https://huggingface.co/Camais03/camie-tagger/resolve/main/game/library_system.py
- Command line
-
hf download hf://Camais03/camie-tagger/game/library_system.py
-
curl -L -o library_system.py https://huggingface.co/Camais03/camie-tagger/resolve/main/game/library_system.py
82.5 kB
| """ | |
| Redesigned Library System with Instant Discovery and Cooldown for Tag Collector Game | |
| """ | |
| import streamlit as st | |
| import random | |
| import time | |
| import math | |
| import pandas as pd | |
| import datetime | |
| from game_constants import ( | |
| TAG_CURRENCY_NAME, | |
| RARITY_LEVELS, | |
| ENKEPHALIN_CURRENCY_NAME, | |
| ENKEPHALIN_ICON, | |
| TAG_POWER_BONUSES | |
| ) | |
| from essence_generator import display_essence_generator | |
| from tag_categories import ( | |
| TAG_CATEGORIES, | |
| get_collection_power_level | |
| ) | |
| import tag_storage | |
| # Define library properties | |
| LIBRARY_INFO = { | |
| "name": "The Library", | |
| "description": "A vast repository of knowledge where tags are discovered through patient exploration and research.", | |
| "color": "#4A148C", # Deep purple | |
| "rarities_available": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City", "Impuritas Civitas"], | |
| "odds_multiplier": 2.0 | |
| } | |
| # Define library floors with their unlocking requirements and rarity boosts | |
| LIBRARY_FLOORS = [ | |
| { | |
| "name": "Floor of General Works", | |
| "description": "The foundation of knowledge. Contains basic tags with limited rarity.", | |
| "required_tags": 0, # Available from the start | |
| "rarity_boost": 0.0, | |
| "color": "#8D99AE", # Light blue-gray | |
| "unlocked": True, # Always unlocked | |
| "rarities": ["Canard", "Urban Myth", "Urban Legend"], | |
| "odds_multiplier": 1.0 # Base odds multiplier | |
| }, | |
| { | |
| "name": "Floor of History", | |
| "description": "Archives of past knowledge. Offers more access to uncommon tags.", | |
| "required_tags": 25, # Unlocked after collecting 25 tags | |
| "rarity_boost": 0.2, | |
| "color": "#457B9D", # Moderate blue | |
| "rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague"], | |
| "odds_multiplier": 1.2 | |
| }, | |
| { | |
| "name": "Floor of Technological Sciences", | |
| "description": "Repository of technical knowledge. Access to rare tags begins here.", | |
| "required_tags": 75, # Unlocked after collecting 75 tags | |
| "rarity_boost": 0.4, | |
| "color": "#2B9348", # Green | |
| "rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague"], | |
| "odds_multiplier": 1.5 | |
| }, | |
| { | |
| "name": "Floor of Literature", | |
| "description": "A vast collection of narrative concepts. Higher chance of rare discoveries.", | |
| "required_tags": 150, # Unlocked after collecting 150 tags | |
| "rarity_boost": 0.6, | |
| "color": "#6A0572", # Purple | |
| "rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare"], | |
| "odds_multiplier": 1.8 | |
| }, | |
| { | |
| "name": "Floor of Art", | |
| "description": "The realm of aesthetic concepts. First access to Urban Nightmare tags.", | |
| "required_tags": 250, # Unlocked after collecting 250 tags | |
| "rarity_boost": 0.8, | |
| "color": "#D90429", # Red | |
| "rarities": ["Canard", "Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare"], | |
| "odds_multiplier": 2.2 | |
| }, | |
| { | |
| "name": "Floor of Natural Sciences", | |
| "description": "Where empirical knowledge is cataloged. Significant chance of very rare tags.", | |
| "required_tags": 500, # Unlocked after collecting 400 tags | |
| "rarity_boost": 1.0, | |
| "color": "#1A759F", # Deep blue | |
| "rarities": ["Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City"], | |
| "odds_multiplier": 2.5 | |
| }, | |
| { | |
| "name": "Floor of Language", | |
| "description": "The domain of linguistic concepts. First glimpse of Star of the City tags.", | |
| "required_tags": 1000, # Unlocked after collecting 600 tags | |
| "rarity_boost": 1.2, | |
| "color": "#FF8C00", # Orange | |
| "rarities": ["Urban Myth", "Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City"], | |
| "odds_multiplier": 3.0 | |
| }, | |
| { | |
| "name": "Floor of Social Sciences", | |
| "description": "Complex social patterns and abstractions. Notable chance of exceptional rarities.", | |
| "required_tags": 2000, # Unlocked after collecting 1000 tags | |
| "rarity_boost": 1.4, | |
| "color": "#76B041", # Brighter green | |
| "rarities": ["Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City"], | |
| "odds_multiplier": 3.5 | |
| }, | |
| { | |
| "name": "Floor of Philosophy", | |
| "description": "The realm of profound thought. First access to the rarest 'Impuritas Civitas' tags.", | |
| "required_tags": 5000, # Unlocked after collecting 1500 tags | |
| "rarity_boost": 1.6, | |
| "color": "#7209B7", # Deep purple | |
| "rarities": ["Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City", "Impuritas Civitas"], | |
| "odds_multiplier": 5.0 | |
| }, | |
| { | |
| "name": "Floor of Religion", | |
| "description": "The ultimate repository of the most profound conceptual territories.", | |
| "required_tags": 10000, | |
| "rarity_boost": 2.0, | |
| "color": "#FFBD00", # Gold | |
| "rarities": ["Urban Plague", "Urban Nightmare", "Star of the City", "Impuritas Civitas"], | |
| "odds_multiplier": 10.0 | |
| } | |
| ] | |
| def start_instant_expedition(): | |
| """ | |
| Start an instant expedition with a cooldown before next expedition. | |
| Returns: | |
| List of discoveries or None if on cooldown | |
| """ | |
| # Check if we're on cooldown | |
| current_time = time.time() | |
| if hasattr(st.session_state, 'last_expedition_time'): | |
| elapsed_time = current_time - st.session_state.last_expedition_time | |
| cooldown_duration = calculate_expedition_duration() | |
| if elapsed_time < cooldown_duration: | |
| # Still on cooldown | |
| time_remaining = cooldown_duration - elapsed_time | |
| minutes, seconds = divmod(int(time_remaining), 60) | |
| st.error(f"Expedition on cooldown. {minutes:02d}:{seconds:02d} remaining.") | |
| return None | |
| # Generate instant discoveries | |
| discoveries = generate_expedition_discoveries() | |
| # Set the cooldown timer | |
| st.session_state.last_expedition_time = current_time | |
| # Save state | |
| tag_storage.save_game(st.session_state) | |
| # Preserve the current tab | |
| if 'library_tab_index' not in st.session_state: | |
| st.session_state.library_tab_index = 0 | |
| return discoveries | |
| def generate_expedition_discoveries(): | |
| """ | |
| Generate expedition discoveries instantly. | |
| Returns: | |
| List of discovered tags and their info | |
| """ | |
| # Get current library floor | |
| current_floor = None | |
| collection_size = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0 | |
| if hasattr(st.session_state, 'library_floors'): | |
| # Find the highest unlocked floor | |
| for floor in reversed(st.session_state.library_floors): | |
| if collection_size >= floor["required_tags"]: | |
| current_floor = floor | |
| break | |
| # Default to first floor if we couldn't find one | |
| if not current_floor: | |
| current_floor = st.session_state.library_floors[0] if hasattr(st.session_state, 'library_floors') else { | |
| "name": "Archival Records", | |
| "rarities": ["Canard", "Urban Myth"], | |
| "rarity_boost": 0.0 | |
| } | |
| # Calculate rarity odds for discoveries | |
| rarity_odds = calculate_rarity_odds() | |
| # Calculate capacity from upgrades | |
| tags_capacity = calculate_expedition_capacity() | |
| # Generate discoveries | |
| discoveries = [] | |
| for _ in range(tags_capacity): | |
| # Select a rarity based on calculated odds | |
| rarities = list(rarity_odds.keys()) | |
| weights = list(rarity_odds.values()) | |
| selected_rarity = random.choices(rarities, weights=weights, k=1)[0] | |
| # Now select a random tag with this rarity that hasn't been discovered yet | |
| possible_tags = [] | |
| # Check if we have tag metadata with rarity info | |
| if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata: | |
| # Find all tags of the selected rarity | |
| for tag, tag_info in st.session_state.tag_rarity_metadata.items(): | |
| # Skip if already discovered | |
| if tag in st.session_state.discovered_tags: | |
| continue | |
| # Handle both formats - new (dict with rarity) and old (just rarity string) | |
| if isinstance(tag_info, dict) and "rarity" in tag_info: | |
| if tag_info["rarity"] == selected_rarity: | |
| possible_tags.append(tag) | |
| elif tag_info == selected_rarity: | |
| possible_tags.append(tag) | |
| # If no undiscovered tags found in the selected rarity, fallback to already discovered tags | |
| if not possible_tags: | |
| if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata: | |
| for tag, tag_info in st.session_state.tag_rarity_metadata.items(): | |
| # Skip if in different rarity | |
| tag_rarity = tag_info.get("rarity", tag_info) if isinstance(tag_info, dict) else tag_info | |
| if tag_rarity != selected_rarity: | |
| continue | |
| possible_tags.append(tag) | |
| # If still no tags found (or no metadata), create a fallback | |
| if not possible_tags: | |
| # If we have the model's full tag list, use it | |
| if hasattr(st.session_state, 'metadata') and 'idx_to_tag' in st.session_state.metadata: | |
| all_tags = list(st.session_state.metadata['idx_to_tag'].values()) | |
| # Just pick a random tag and assign the selected rarity | |
| possible_tags = random.sample(all_tags, min(20, len(all_tags))) | |
| else: | |
| # Complete fallback - use some generic tags | |
| possible_tags = ["portrait", "landscape", "digital_art", "anime", "realistic", | |
| "fantasy", "sci-fi", "city", "nature", "character"] | |
| # If we found possible tags, select one randomly | |
| if possible_tags: | |
| selected_tag = random.choice(possible_tags) | |
| # Get category from metadata if available | |
| category = "unknown" | |
| if hasattr(st.session_state, 'metadata') and 'tag_to_category' in st.session_state.metadata: | |
| if selected_tag in st.session_state.metadata['tag_to_category']: | |
| category = st.session_state.metadata['tag_to_category'][selected_tag] | |
| # Use the enhanced tag storage function to add the discovered tag | |
| is_new = tag_storage.add_discovered_tag( | |
| tag=selected_tag, | |
| rarity=selected_rarity, | |
| session_state=st.session_state, | |
| library_floor=current_floor["name"], | |
| category=category # Pass the category we found | |
| ) | |
| # Record for library growth | |
| st.session_state.library_growth["total_discoveries"] += 1 | |
| st.session_state.library_growth["last_discovery_time"] = time.time() | |
| # Create timestamp for display | |
| timestamp = time.strftime("%Y-%m-%d %H:%M:%S") | |
| # Add to results | |
| discoveries.append({ | |
| "tag": selected_tag, | |
| "rarity": selected_rarity, | |
| "is_new": is_new, | |
| "timestamp": timestamp, | |
| "library": current_floor["name"] | |
| }) | |
| # Save the game state after discoveries | |
| tag_storage.save_library_state(session_state=st.session_state) | |
| tag_storage.save_game(st.session_state) | |
| return discoveries | |
| def update_discovered_tag_categories(): | |
| """Update categories of discovered tags from metadata if they're unknown""" | |
| if not hasattr(st.session_state, 'discovered_tags') or not st.session_state.discovered_tags: | |
| return 0 | |
| updated_count = 0 | |
| # First try from metadata.tag_to_category | |
| if hasattr(st.session_state, 'metadata') and 'tag_to_category' in st.session_state.metadata: | |
| tag_to_category = st.session_state.metadata['tag_to_category'] | |
| for tag, info in st.session_state.discovered_tags.items(): | |
| if info.get('category', 'unknown') == 'unknown' and tag in tag_to_category: | |
| info['category'] = tag_to_category[tag] | |
| updated_count += 1 | |
| # Then try from tag_rarity_metadata | |
| if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata: | |
| for tag, info in st.session_state.discovered_tags.items(): | |
| if info.get('category', 'unknown') == 'unknown' and tag in st.session_state.tag_rarity_metadata: | |
| tag_metadata = st.session_state.tag_rarity_metadata[tag] | |
| if isinstance(tag_metadata, dict) and "category" in tag_metadata: | |
| info['category'] = tag_metadata["category"] | |
| updated_count += 1 | |
| if updated_count > 0: | |
| print(f"Updated categories for {updated_count} discovered tags") | |
| tag_storage.save_library_state(session_state=st.session_state) | |
| return updated_count | |
| def calculate_expedition_duration(): | |
| """ | |
| Calculate the duration of cooldown after an expedition based on upgrades. | |
| Returns: | |
| Duration in seconds for the cooldown | |
| """ | |
| base_duration = 10 # Default to 10 seconds | |
| # Apply speed upgrades if they exist | |
| speed_level = 1 | |
| if hasattr(st.session_state, 'library_upgrades'): | |
| speed_level = st.session_state.library_upgrades.get("speed", 1) | |
| # Each speed level reduces duration by 10% (multiplicative) | |
| duration_multiplier = 0.9 ** (speed_level - 1) | |
| # Calculate final duration (minimum 1 second) | |
| duration = max(1, base_duration * duration_multiplier) | |
| return duration | |
| def calculate_expedition_capacity(): | |
| """ | |
| Calculate how many tags can be discovered in one expedition. | |
| Returns: | |
| Number of tags that can be discovered | |
| """ | |
| base_capacity = 1 # Default to 1 discovery per expedition | |
| # Apply capacity upgrades if they exist | |
| capacity_level = 1 | |
| if hasattr(st.session_state, 'library_upgrades'): | |
| capacity_level = st.session_state.library_upgrades.get("capacity", 1) | |
| # Each capacity level increases discoveries by 1 | |
| capacity = base_capacity + (capacity_level - 1) | |
| return capacity | |
| def calculate_rarity_odds(): | |
| """ | |
| Calculate rarity odds based on library floor level and upgrades. | |
| Returns: | |
| Dictionary of {rarity: probability} for available rarities | |
| """ | |
| # Get current library floor | |
| current_floor = None | |
| if hasattr(st.session_state, 'library_floors'): | |
| collection_size = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0 | |
| # Find the highest unlocked floor | |
| for floor in reversed(st.session_state.library_floors): | |
| if collection_size >= floor["required_tags"]: | |
| current_floor = floor | |
| break | |
| # Default to first floor if we couldn't find one | |
| if not current_floor: | |
| current_floor = st.session_state.library_floors[0] if hasattr(st.session_state, 'library_floors') else { | |
| "rarities": ["Canard", "Urban Myth"], | |
| "rarity_boost": 0.0, | |
| "odds_multiplier": 1.0 | |
| } | |
| # Get available rarities from current floor | |
| available_rarities = current_floor.get("rarities", ["Canard", "Urban Myth"]) | |
| odds_multiplier = current_floor.get("odds_multiplier", 1.0) | |
| # Base weights for each rarity | |
| base_weights = { | |
| "Canard": 70, | |
| "Urban Myth": 20, | |
| "Urban Legend": 7, | |
| "Urban Plague": 2, | |
| "Urban Nightmare": 1, | |
| "Star of the City": 0.1, | |
| "Impuritas Civitas": 0.01 | |
| } | |
| # Apply floor's rarity boost | |
| floor_rarity_boost = current_floor.get("rarity_boost", 0.0) | |
| # Apply rarity upgrades if they exist | |
| rarity_level = 1 | |
| if hasattr(st.session_state, 'library_upgrades'): | |
| rarity_level = st.session_state.library_upgrades.get("rarity", 1) | |
| # Calculate boost based on rarity level | |
| upgrade_rarity_boost = (rarity_level - 1) * 0.2 # Each level gives 20% more chance for rare tags | |
| # Combine boosts | |
| total_boost = floor_rarity_boost + upgrade_rarity_boost | |
| # Adjust weights based on rarity boost | |
| adjusted_weights = {} | |
| for rarity in available_rarities: | |
| if rarity == "Canard": | |
| # Reduce common tag odds as rarity level increases | |
| adjusted_weights[rarity] = base_weights[rarity] * (1.0 - total_boost * 0.7) | |
| elif rarity == "Urban Myth": | |
| # Slight reduction for uncommon as rarity level increases | |
| adjusted_weights[rarity] = base_weights[rarity] * (1.0 - total_boost * 0.3) | |
| else: | |
| # Increase rare tag odds as rarity level increases | |
| rarity_index = list(RARITY_LEVELS.keys()).index(rarity) | |
| # Higher rarities get larger boosts | |
| boost_factor = 1.0 + (total_boost * odds_multiplier * (rarity_index + 1)) | |
| adjusted_weights[rarity] = base_weights[rarity] * boost_factor | |
| # Normalize weights | |
| total = sum(adjusted_weights.values()) | |
| normalized_weights = {r: w/total for r, w in adjusted_weights.items()} | |
| return normalized_weights | |
| def format_time_remaining(seconds): | |
| """ | |
| Format seconds into a human-readable time remaining format. | |
| Args: | |
| seconds: Seconds remaining | |
| Returns: | |
| String with formatted time | |
| """ | |
| if seconds < 60: | |
| return f"{int(seconds)} seconds" | |
| elif seconds < 3600: | |
| minutes = seconds / 60 | |
| return f"{int(minutes)} minutes" | |
| else: | |
| hours = seconds / 3600 | |
| minutes = (seconds % 3600) / 60 | |
| if minutes > 0: | |
| return f"{int(hours)} hours, {int(minutes)} minutes" | |
| else: | |
| return f"{int(hours)} hours" | |
| def display_cooldown_timer(): | |
| """Display a countdown timer until the next expedition is available""" | |
| # Check if on cooldown | |
| current_time = time.time() | |
| cooldown_remaining = 0 | |
| cooldown_duration = calculate_expedition_duration() | |
| if hasattr(st.session_state, 'last_expedition_time'): | |
| elapsed_time = current_time - st.session_state.last_expedition_time | |
| if elapsed_time < cooldown_duration: | |
| cooldown_remaining = cooldown_duration - elapsed_time | |
| # If on cooldown, show timer | |
| if cooldown_remaining > 0: | |
| minutes, seconds = divmod(int(cooldown_remaining), 60) | |
| # Create a timer display with dark-mode styling | |
| st.markdown(""" | |
| <div style="background-color: rgba(255, 152, 0, 0.15); | |
| border: 1px solid #FF9800; | |
| border-radius: 5px; | |
| padding: 10px; | |
| text-align: center; | |
| margin-bottom: 15px; | |
| color: #ffffff;"> | |
| <p style="margin: 0; font-weight: bold;">⏱️ Next expedition available in:</p> | |
| <p style="font-size: 1.2em; margin: 5px 0;">{:02d}:{:02d}</p> | |
| </div> | |
| """.format(minutes, seconds), unsafe_allow_html=True) | |
| # Add refresh button for the timer | |
| if st.button("🔄 Refresh Timer", key="refresh_timer"): | |
| st.rerun() | |
| return True # Still on cooldown | |
| return False # Not on cooldown | |
| def display_library_exploration_interface(): | |
| """Display the unified interface for library exploration using Streamlit elements.""" | |
| # Tag collection progress | |
| tag_count = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0 | |
| # Check if we have tags to start exploring | |
| if not hasattr(st.session_state, 'collected_tags') or not st.session_state.collected_tags: | |
| st.warning("Start scanning images to collect tags first. The library will grow as you collect more tags!") | |
| return | |
| # Get current library floor | |
| current_floor = None | |
| if hasattr(st.session_state, 'library_floors'): | |
| # Find the highest unlocked floor | |
| for floor in reversed(st.session_state.library_floors): | |
| if tag_count >= floor["required_tags"]: | |
| current_floor = floor | |
| break | |
| # Default to first floor if we couldn't find one | |
| if not current_floor: | |
| current_floor = st.session_state.library_floors[0] if hasattr(st.session_state, 'library_floors') else { | |
| "name": "Floor of General Works", | |
| "description": "The foundational level of knowledge.", | |
| "color": "#607D8B", | |
| "rarities": ["Canard", "Urban Myth"] | |
| } | |
| # Library growth progress | |
| total_discoveries = st.session_state.library_growth["total_discoveries"] | |
| # Create container with colored border for current floor | |
| floor_container = st.container() | |
| with floor_container: | |
| # Use a stylized container with dark mode theme | |
| st.markdown(f""" | |
| <div style="border-left: 5px solid {current_floor['color']}; | |
| border-radius: 5px; | |
| background-color: rgba({int(current_floor['color'][1:3], 16)}, | |
| {int(current_floor['color'][3:5], 16)}, | |
| {int(current_floor['color'][5:7], 16)}, 0.15); | |
| padding: 15px 10px 10px 15px; | |
| margin-bottom: 15px; | |
| color: #ffffff;"> | |
| <h3 style="margin-top: 0; color: {current_floor['color']};">{current_floor['name']}</h3> | |
| <p>{current_floor['description']}</p> | |
| <p>Total Discoveries: <strong>{total_discoveries}</strong></p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Create a nice divider for dark theme | |
| st.markdown("<hr style='margin: 20px 0; border: 0; height: 1px; background-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));'>", unsafe_allow_html=True) | |
| # Display expedition details | |
| st.subheader("Expedition Details") | |
| # Calculate capacity | |
| capacity = calculate_expedition_capacity() | |
| # Two columns for expedition stats | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| # Expedition duration/timer | |
| cooldown_duration = calculate_expedition_duration() | |
| st.write(f"📊 Cooldown: {format_time_remaining(cooldown_duration)}") | |
| st.write(f"🔍 Tag Discoveries: {capacity} per expedition") | |
| with col2: | |
| # Calculate and display rarity odds with Streamlit elements | |
| rarity_odds = calculate_rarity_odds() | |
| available_rarities = current_floor.get("rarities", ["Canard", "Urban Myth"]) | |
| # Display rarity chances with dark theme styling | |
| for rarity in available_rarities: | |
| if rarity in rarity_odds: | |
| color = RARITY_LEVELS[rarity]["color"] | |
| percentage = rarity_odds[rarity]*100 | |
| # Custom styling based on rarity | |
| if rarity == "Impuritas Civitas": | |
| st.markdown(f""" | |
| <div style="display: flex; align-items: center; margin-bottom: 5px;"> | |
| <span style="animation: rainbow-text 4s linear infinite; font-weight: bold; width: 140px;">{rarity}:</span> | |
| <div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;"> | |
| <div style="width: {min(percentage*5, 100)}%; background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); height: 10px; border-radius: 5px;"></div> | |
| </div> | |
| <span style="margin-left: 10px;">{percentage:.1f}%</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| elif rarity == "Star of the City": | |
| st.markdown(f""" | |
| <div style="display: flex; align-items: center; margin-bottom: 5px;"> | |
| <span style="color:{color}; text-shadow: 0 0 3px gold; font-weight: bold; width: 140px;">{rarity}:</span> | |
| <div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;"> | |
| <div style="width: {min(percentage*5, 100)}%; background-color: {color}; box-shadow: 0 0 5px gold; height: 10px; border-radius: 5px;"></div> | |
| </div> | |
| <span style="margin-left: 10px;">{percentage:.1f}%</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| elif rarity == "Urban Nightmare": | |
| st.markdown(f""" | |
| <div style="display: flex; align-items: center; margin-bottom: 5px;"> | |
| <span style="color:{color}; text-shadow: 0 0 1px #FF5722; font-weight: bold; width: 140px;">{rarity}:</span> | |
| <div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;"> | |
| <div style="width: {min(percentage*5, 100)}%; background-color: {color}; animation: pulse-bar 3s infinite; height: 10px; border-radius: 5px;"></div> | |
| </div> | |
| <span style="margin-left: 10px;">{percentage:.1f}%</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| else: | |
| st.markdown(f""" | |
| <div style="display: flex; align-items: center; margin-bottom: 5px;"> | |
| <span style="color:{color}; font-weight: bold; width: 140px;">{rarity}:</span> | |
| <div style="flex-grow: 1; background-color: #2c2c2c; border-radius: 5px; height: 10px;"> | |
| <div style="width: {min(percentage*5, 100)}%; background-color: {color}; height: 10px; border-radius: 5px;"></div> | |
| </div> | |
| <span style="margin-left: 10px;">{percentage:.1f}%</span> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Check for cooldown and display timer if needed | |
| on_cooldown = display_cooldown_timer() | |
| # Add button to start expedition regardless of cooldown status | |
| # The button will always be displayed, but if on cooldown, expedition won't start | |
| if st.button("🚀 Start Expedition", key="start_expedition", use_container_width=True, disabled=on_cooldown): | |
| if not on_cooldown: | |
| discoveries = start_instant_expedition() | |
| if discoveries: | |
| # Store discovery results for display | |
| st.session_state.expedition_results = discoveries | |
| # Show success message | |
| st.success(f"Expedition completed! Discovered {len(discoveries)} new tags!") | |
| # Show balloons for celebration | |
| st.balloons() | |
| # Display the results | |
| display_expedition_results(discoveries) | |
| # Save state | |
| tag_storage.save_game(st.session_state) | |
| else: | |
| # This should not be reached due to disabled button, but just in case | |
| st.error("Expedition on cooldown. Please wait until the timer expires.") | |
| # Display library upgrades | |
| display_library_upgrades() | |
| def display_expedition_results(results): | |
| """Display results from completed expeditions using Streamlit elements with enhanced dark-mode visuals.""" | |
| st.subheader("Expedition Discoveries") | |
| # Add animations CSS for dark theme | |
| st.markdown(""" | |
| <style> | |
| @keyframes rainbow-text { | |
| 0% { color: red; } | |
| 14% { color: orange; } | |
| 28% { color: yellow; } | |
| 42% { color: green; } | |
| 57% { color: blue; } | |
| 71% { color: indigo; } | |
| 85% { color: violet; } | |
| 100% { color: red; } | |
| } | |
| @keyframes rainbow-border { | |
| 0% { border-color: red; } | |
| 14% { border-color: orange; } | |
| 28% { border-color: yellow; } | |
| 42% { border-color: green; } | |
| 57% { border-color: blue; } | |
| 71% { border-color: indigo; } | |
| 85% { border-color: violet; } | |
| 100% { border-color: red; } | |
| } | |
| @keyframes star-glow { | |
| 0% { box-shadow: 0 0 5px #FFD700; } | |
| 50% { box-shadow: 0 0 15px #FFD700; } | |
| 100% { box-shadow: 0 0 5px #FFD700; } | |
| } | |
| @keyframes nightmare-pulse { | |
| 0% { border-color: #FF9800; } | |
| 50% { border-color: #FF5722; } | |
| 100% { border-color: #FF9800; } | |
| } | |
| @keyframes pulse-bar { | |
| 0% { opacity: 0.8; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.8; } | |
| } | |
| .expedition-tag-impuritas { | |
| animation: rainbow-text 4s linear infinite; | |
| font-weight: bold; | |
| } | |
| .expedition-card-impuritas { | |
| background-color: rgba(255, 0, 0, 0.15); | |
| border-radius: 8px; | |
| border: 3px solid red; | |
| padding: 12px; | |
| animation: rainbow-border 4s linear infinite; | |
| color: #ffffff; | |
| } | |
| .expedition-card-star { | |
| background-color: rgba(255, 215, 0, 0.15); | |
| border-radius: 8px; | |
| border: 2px solid gold; | |
| padding: 12px; | |
| animation: star-glow 2s infinite; | |
| color: #ffffff; | |
| } | |
| .expedition-card-nightmare { | |
| background-color: rgba(255, 152, 0, 0.15); | |
| border-radius: 8px; | |
| border: 2px solid #FF9800; | |
| padding: 12px; | |
| animation: nightmare-pulse 3s infinite; | |
| color: #ffffff; | |
| } | |
| .expedition-card-plague { | |
| background-color: rgba(156, 39, 176, 0.12); | |
| border-radius: 8px; | |
| border: 1px solid #9C27B0; | |
| padding: 12px; | |
| box-shadow: 0 0 3px #9C27B0; | |
| color: #ffffff; | |
| } | |
| .expedition-card-legend { | |
| background-color: rgba(33, 150, 243, 0.15); | |
| border-radius: 8px; | |
| border: 1px solid #2196F3; | |
| padding: 12px; | |
| color: #ffffff; | |
| } | |
| .expedition-card-myth { | |
| background-color: rgba(76, 175, 80, 0.15); | |
| border-radius: 8px; | |
| border: 1px solid #4CAF50; | |
| padding: 12px; | |
| color: #ffffff; | |
| } | |
| .expedition-card-canard { | |
| background-color: rgba(170, 170, 170, 0.15); | |
| border-radius: 8px; | |
| border: 1px solid #AAAAAA; | |
| padding: 12px; | |
| color: #ffffff; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Group by rarity first for better organization | |
| results_by_rarity = {} | |
| for result in results: | |
| rarity = result["rarity"] | |
| if rarity not in results_by_rarity: | |
| results_by_rarity[rarity] = [] | |
| results_by_rarity[rarity].append(result) | |
| # Get ordered rarities (rarest first) | |
| ordered_rarities = list(RARITY_LEVELS.keys()) | |
| ordered_rarities.reverse() # Reverse to display rarest first | |
| # Display rare discoveries first | |
| for rarity in ordered_rarities: | |
| if rarity not in results_by_rarity: | |
| continue | |
| rarity_results = results_by_rarity[rarity] | |
| if not rarity_results: | |
| continue | |
| color = RARITY_LEVELS[rarity]["color"] | |
| # Special styling for rare discoveries | |
| if rarity == "Impuritas Civitas": | |
| st.markdown(f""" | |
| <div style="background-color: rgba(255, 0, 0, 0.15); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 2px solid red; animation: rainbow-border 4s linear infinite; color: #ffffff;"> | |
| <h3 style="margin-top: 0; animation: rainbow-text 4s linear infinite;">✨ EXTRAORDINARY DISCOVERY!</h3> | |
| <p>You found {len(rarity_results)} Impuritas Civitas tag(s)!</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.balloons() # Add celebration effect | |
| elif rarity == "Star of the City": | |
| st.markdown(f""" | |
| <div style="background-color: rgba(255, 215, 0, 0.15); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 2px solid gold; animation: star-glow 2s infinite; color: #ffffff;"> | |
| <h3 style="margin-top: 0; color: {color}; text-shadow: 0 0 3px gold;">🌟 EXCEPTIONAL DISCOVERY!</h3> | |
| <p>You found {len(rarity_results)} Star of the City tag(s)!</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| elif rarity == "Urban Nightmare": | |
| st.markdown(f""" | |
| <div style="background-color: rgba(255, 152, 0, 0.15); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 2px solid #FF9800; animation: nightmare-pulse 3s infinite; color: #ffffff;"> | |
| <h3 style="margin-top: 0; color: {color}; text-shadow: 0 0 1px #FF5722;">👑 RARE DISCOVERY!</h3> | |
| <p>You found {len(rarity_results)} Urban Nightmare tag(s)!</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| elif rarity == "Urban Plague": | |
| st.markdown(f""" | |
| <div style="background-color: rgba(156, 39, 176, 0.12); border-radius: 10px; padding: 15px; margin-bottom: 20px; border: 1px solid #9C27B0; box-shadow: 0 0 3px #9C27B0; color: #ffffff;"> | |
| <h3 style="margin-top: 0; color: {color}; text-shadow: 0 0 1px #9C27B0;">⚔️ UNCOMMON DISCOVERY!</h3> | |
| <p>You found {len(rarity_results)} Urban Plague tag(s)!</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| else: | |
| st.markdown(f"### {rarity} ({len(rarity_results)} discoveries)") | |
| # Display tags in this rarity | |
| cols = st.columns(3) | |
| for i, result in enumerate(rarity_results): | |
| col_idx = i % 3 | |
| with cols[col_idx]: | |
| tag = result["tag"] | |
| floor_name = result.get("library", "Library") | |
| # Get the appropriate card class based on rarity | |
| rarity_class = rarity.lower().replace(' ', '-') | |
| card_class = f"expedition-card-{rarity_class}" | |
| # Create styled card for each tag | |
| tag_html = f"""<div class="{card_class}">""" | |
| # Special styling for the tag name based on rarity | |
| if rarity == "Impuritas Civitas": | |
| tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span class="expedition-tag-impuritas">✨ {tag}</span></p>""" | |
| elif rarity == "Star of the City": | |
| tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; text-shadow: 0 0 3px gold; font-weight: bold;">🌟 {tag}</span></p>""" | |
| elif rarity == "Urban Nightmare": | |
| tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; text-shadow: 0 0 1px #FF5722; font-weight: bold;">👑 {tag}</span></p>""" | |
| elif rarity == "Urban Plague": | |
| tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; text-shadow: 0 0 1px #9C27B0; font-weight: bold;">⚔️ {tag}</span></p>""" | |
| else: | |
| tag_html += f"""<p style="font-size: 1.1em; margin-bottom: 5px;"><span style="color: {color}; font-weight: bold;">{tag}</span></p>""" | |
| # Mark as new if it is | |
| is_new = result.get("is_new", False) | |
| new_badge = """<span style="background-color: #4CAF50; color: white; padding: 2px 6px; border-radius: 10px; font-size: 0.7em; margin-left: 5px;">NEW</span>""" if is_new else "" | |
| # Add other tag details | |
| tag_html += f""" | |
| <p style="margin: 0; font-size: 0.9em;">Found in: {floor_name} {new_badge}</p> | |
| <p style="margin: 5px 0 0 0; font-size: 0.9em;">Rarity: <span style="color: {color};">{rarity}</span></p> | |
| </div> | |
| """ | |
| st.markdown(tag_html, unsafe_allow_html=True) | |
| # Add separator between rarity groups | |
| st.markdown("<hr style='margin: 20px 0; border: 0; height: 1px; background-image: linear-gradient(to right, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0));'>", unsafe_allow_html=True) | |
| def display_library_building(): | |
| """Display a visual representation of the library building with all floors.""" | |
| st.subheader("The Great Library Building") | |
| # Get collection size | |
| collection_size = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0 | |
| # Determine current floor | |
| current_floor_index = 0 | |
| for i, floor in enumerate(st.session_state.library_floors): | |
| if collection_size >= floor["required_tags"]: | |
| current_floor_index = i | |
| # Create a visual representation of the library building | |
| total_floors = len(st.session_state.library_floors) | |
| # Enhanced CSS for the library building with dark theme | |
| st.markdown(""" | |
| <style> | |
| @keyframes floor-glow { | |
| 0% { box-shadow: 0 0 5px rgba(13, 110, 253, 0.5); } | |
| 50% { box-shadow: 0 0 15px rgba(13, 110, 253, 0.8); } | |
| 100% { box-shadow: 0 0 5px rgba(13, 110, 253, 0.5); } | |
| } | |
| .library-roof { | |
| background: linear-gradient(90deg, #8D6E63, #A1887F); | |
| height: 35px; | |
| width: 90%; | |
| margin: 0 auto; | |
| border-radius: 8px 8px 0 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| font-weight: bold; | |
| box-shadow: 0 -2px 10px rgba(0,0,0,0.4); | |
| } | |
| .library-floor { | |
| height: 65px; | |
| width: 80%; | |
| margin: 0 auto; | |
| border: 1px solid #444; | |
| display: flex; | |
| align-items: center; | |
| padding: 0 20px; | |
| position: relative; | |
| transition: all 0.3s ease; | |
| color: #ffffff; | |
| background-color: #2c2c2c; | |
| } | |
| .library-floor:hover { | |
| transform: translateX(10px); | |
| } | |
| .library-floor.current { | |
| box-shadow: 0 0 10px rgba(13, 110, 253, 0.5); | |
| z-index: 2; | |
| animation: floor-glow 2s infinite; | |
| border-left: 5px solid #0d6efd; | |
| } | |
| .library-floor.locked { | |
| background-color: #1e1e1e; | |
| color: #777; | |
| filter: grayscale(50%); | |
| } | |
| .library-floor-number { | |
| position: absolute; | |
| left: -30px; | |
| width: 25px; | |
| height: 25px; | |
| background-color: #0d6efd; | |
| color: white; | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-weight: bold; | |
| } | |
| .library-floor.locked .library-floor-number { | |
| background-color: #555; | |
| } | |
| .library-entrance { | |
| background: linear-gradient(90deg, #5D4037, #795548); | |
| height: 45px; | |
| width: 35%; | |
| margin: 0 auto; | |
| border-radius: 10px 10px 0 0; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| color: white; | |
| font-weight: bold; | |
| box-shadow: 0 -2px 10px rgba(0,0,0,0.4); | |
| } | |
| .library-floor-details { | |
| flex: 1; | |
| } | |
| .library-floor-name { | |
| font-weight: bold; | |
| margin: 0; | |
| } | |
| .library-floor-description { | |
| font-size: 0.85em; | |
| margin: 3px 0 0 0; | |
| opacity: 0.9; | |
| } | |
| .library-floor-status { | |
| display: flex; | |
| align-items: center; | |
| font-weight: bold; | |
| } | |
| .library-floor-rarities { | |
| font-size: 0.8em; | |
| margin-top: 4px; | |
| } | |
| .rarity-dot { | |
| display: inline-block; | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| margin-right: 3px; | |
| } | |
| /* Special animations for rarer floors */ | |
| .library-floor.star { | |
| background-color: rgba(255, 215, 0, 0.15); | |
| } | |
| .library-floor.impuritas { | |
| background-color: rgba(255, 0, 0, 0.15); | |
| } | |
| @keyframes rainbow-border { | |
| 0% { border-color: red; } | |
| 14% { border-color: orange; } | |
| 28% { border-color: yellow; } | |
| 42% { border-color: green; } | |
| 57% { border-color: blue; } | |
| 71% { border-color: indigo; } | |
| 85% { border-color: violet; } | |
| 100% { border-color: red; } | |
| } | |
| .rainbow-border { | |
| animation: rainbow-border 4s linear infinite; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Roof | |
| st.markdown('<div class="library-roof">🏛️ The Great Library</div>', unsafe_allow_html=True) | |
| # Display floors from top (highest) to bottom | |
| for i in reversed(range(total_floors)): | |
| floor = st.session_state.library_floors[i] | |
| is_current = i == current_floor_index | |
| is_unlocked = collection_size >= floor["required_tags"] | |
| # Style based on floor status | |
| floor_class = "library-floor" | |
| if is_current: | |
| floor_class += " current" | |
| if not is_unlocked: | |
| floor_class += " locked" | |
| # Add special classes for highest floors | |
| if i >= 8 and is_unlocked: # Impuritas Civitas level floors | |
| floor_class += " impuritas" | |
| elif i >= 6 and is_unlocked: # Star of the City level floors | |
| floor_class += " star" | |
| # Determine rarity dots HTML | |
| rarity_dots = "" | |
| for rarity in floor.get("rarities", []): | |
| color = RARITY_LEVELS[rarity]["color"] | |
| rarity_dots += f'<span class="rarity-dot" style="background-color:{color};"></span>' | |
| # Floor style based on color | |
| if is_unlocked: | |
| floor_style = f"background-color: rgba({int(floor['color'][1:3], 16)}, {int(floor['color'][3:5], 16)}, {int(floor['color'][5:7], 16)}, 0.25);" | |
| else: | |
| floor_style = "" | |
| # Special border animation for highest floor | |
| border_class = "" | |
| if i == 9 and is_unlocked: # Top floor | |
| border_class = "rainbow-border" | |
| # Display the floor | |
| floor_content = f""" | |
| <div class="{floor_class} {border_class}" style="{floor_style}"> | |
| <span class="library-floor-number">{i+1}</span> | |
| <div class="library-floor-details"> | |
| <p class="library-floor-name">{floor['name']}</p> | |
| <p class="library-floor-description">{floor['description'] if is_unlocked else 'Locked'}</p> | |
| <div class="library-floor-rarities">{rarity_dots}</div> | |
| </div> | |
| <div class="library-floor-status"> | |
| {"🔓" if is_unlocked else "🔒"} {floor['required_tags']} tags | |
| </div> | |
| </div> | |
| """ | |
| st.markdown(floor_content, unsafe_allow_html=True) | |
| # Entrance | |
| st.markdown('<div class="library-entrance">📚 Entrance</div>', unsafe_allow_html=True) | |
| # Floor details expander | |
| with st.expander("Floor Details", expanded=False): | |
| # Create a table with styled rarities for dark theme | |
| st.markdown(""" | |
| <style> | |
| .floor-details-table { | |
| width: 100%; | |
| border-collapse: collapse; | |
| color: #ffffff; | |
| } | |
| .floor-details-table th { | |
| background-color: #333; | |
| padding: 8px; | |
| text-align: left; | |
| border: 1px solid #444; | |
| } | |
| .floor-details-table td { | |
| padding: 8px; | |
| border: 1px solid #444; | |
| } | |
| .floor-details-table tr:nth-child(even) { | |
| background-color: rgba(255,255,255,0.03); | |
| } | |
| .floor-details-table tr:nth-child(odd) { | |
| background-color: rgba(0,0,0,0.2); | |
| } | |
| .floor-details-table tr:hover { | |
| background-color: rgba(13, 110, 253, 0.1); | |
| } | |
| .current-floor { | |
| background-color: rgba(13, 110, 253, 0.15) !important; | |
| } | |
| </style> | |
| <table class="floor-details-table"> | |
| <tr> | |
| <th>Floor</th> | |
| <th>Name</th> | |
| <th>Status</th> | |
| <th>Req. Tags</th> | |
| <th>Rarities</th> | |
| <th>Rarity Boost</th> | |
| </tr> | |
| """, unsafe_allow_html=True) | |
| # Add each floor to the table | |
| for i, floor in enumerate(st.session_state.library_floors): | |
| is_unlocked = collection_size >= floor["required_tags"] | |
| is_current = i == current_floor_index | |
| # Format rarities with colors | |
| rarity_text = "" | |
| for rarity in floor.get("rarities", []): | |
| color = RARITY_LEVELS[rarity]["color"] | |
| # Special styling based on rarity | |
| if rarity == "Impuritas Civitas": | |
| rarity_text += f"<span style='animation: rainbow-text 4s linear infinite;'>{rarity}</span>, " | |
| elif rarity == "Star of the City": | |
| rarity_text += f"<span style='color:{color}; text-shadow: 0 0 3px gold;'>{rarity}</span>, " | |
| elif rarity == "Urban Nightmare": | |
| rarity_text += f"<span style='color:{color}; text-shadow: 0 0 1px #FF5722;'>{rarity}</span>, " | |
| elif rarity == "Urban Plague": | |
| rarity_text += f"<span style='color:{color}; text-shadow: 0 0 1px #9C27B0;'>{rarity}</span>, " | |
| else: | |
| rarity_text += f"<span style='color:{color};'>{rarity}</span>, " | |
| # Current floor class | |
| row_class = "current-floor" if is_current else "" | |
| # Add the floor row | |
| st.markdown(f""" | |
| <tr class="{row_class}"> | |
| <td>{i+1}</td> | |
| <td>{floor["name"]}</td> | |
| <td>{"🔓 Unlocked" if is_unlocked else "🔒 Locked"}</td> | |
| <td>{floor["required_tags"]}</td> | |
| <td>{rarity_text[:-2] if rarity_text else ""}</td> | |
| <td>+{int(floor.get('rarity_boost', 0) * 100)}%</td> | |
| </tr> | |
| """, unsafe_allow_html=True) | |
| # Close the table | |
| st.markdown("</table>", unsafe_allow_html=True) | |
| def add_discovered_tag(tag, rarity, library_floor=None): | |
| """ | |
| Add a tag to the discovered tags with enriched metadata | |
| Args: | |
| tag: The tag name | |
| rarity: The tag rarity level | |
| library_floor: The library floor where it was discovered (optional) | |
| Returns: | |
| bool: True if it's a new discovery, False if already discovered | |
| """ | |
| is_new = tag not in st.session_state.discovered_tags | |
| # Get current time | |
| timestamp = time.strftime("%Y-%m-%d %H:%M:%S") | |
| # Get tag category if metadata is available | |
| category = "unknown" | |
| if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata: | |
| if tag in st.session_state.tag_rarity_metadata: | |
| tag_info = st.session_state.tag_rarity_metadata[tag] | |
| if isinstance(tag_info, dict) and "category" in tag_info: | |
| category = tag_info["category"] | |
| # Get or update tag info | |
| if is_new: | |
| tag_info = { | |
| "rarity": rarity, | |
| "discovery_time": timestamp, | |
| "category": category, | |
| "discovery_count": 1, | |
| "last_seen": timestamp | |
| } | |
| if library_floor: | |
| tag_info["library_floor"] = library_floor | |
| st.session_state.discovered_tags[tag] = tag_info | |
| # Track exploration of library tiers | |
| if 'explored_library_tiers' not in st.session_state: | |
| st.session_state.explored_library_tiers = set() | |
| if library_floor: | |
| st.session_state.explored_library_tiers.add(library_floor) | |
| else: | |
| # Update existing tag | |
| tag_info = st.session_state.discovered_tags[tag] | |
| tag_info["discovery_count"] = tag_info.get("discovery_count", 1) + 1 | |
| tag_info["last_seen"] = timestamp | |
| # Only update library floor if provided | |
| if library_floor: | |
| tag_info["library_floor"] = library_floor | |
| # Track exploration | |
| if 'explored_library_tiers' not in st.session_state: | |
| st.session_state.explored_library_tiers = set() | |
| st.session_state.explored_library_tiers.add(library_floor) | |
| # Save state after updating | |
| tag_storage.save_library_state(session_state=st.session_state) | |
| return is_new | |
| def calculate_upgrade_cost(upgrade_type, current_level): | |
| base_cost = { | |
| "speed": 50, | |
| "capacity": 100, | |
| "rarity": 150 | |
| } | |
| # Cost increases with each level | |
| return int(base_cost[upgrade_type] * (current_level * 1.5)) | |
| def purchase_library_upgrade(upgrade_type): | |
| """ | |
| Purchase a library upgrade | |
| Args: | |
| upgrade_type: The type of upgrade ("speed", "capacity", or "rarity") | |
| Returns: | |
| bool: True if purchased successfully, False otherwise | |
| """ | |
| # Get current level and calculate cost | |
| current_level = st.session_state.library_upgrades.get(upgrade_type, 1) | |
| cost = calculate_upgrade_cost(upgrade_type, current_level) | |
| # Check if player can afford it | |
| if st.session_state.tag_currency < cost: | |
| return False | |
| # Apply purchase | |
| st.session_state.tag_currency -= cost | |
| st.session_state.library_upgrades[upgrade_type] = current_level + 1 | |
| # Update stats | |
| if hasattr(st.session_state, 'game_stats'): | |
| if "currency_spent" not in st.session_state.game_stats: | |
| st.session_state.game_stats["currency_spent"] = 0 | |
| st.session_state.game_stats["currency_spent"] += cost | |
| # Save state | |
| tag_storage.save_library_state(session_state=st.session_state) | |
| tag_storage.save_game(st.session_state) | |
| return True | |
| def display_library_upgrades(): | |
| """Display and manage upgrades for the library using Streamlit elements with enhanced visuals.""" | |
| st.subheader("Library Upgrades") | |
| # Add styling for upgrade cards with dark theme | |
| st.markdown(""" | |
| <style> | |
| .upgrade-card { | |
| border: 1px solid #444; | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| background-color: #222; | |
| color: #ffffff; | |
| transition: transform 0.2s, box-shadow 0.2s; | |
| } | |
| .upgrade-card:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 8px rgba(0,0,0,0.3); | |
| } | |
| .upgrade-title { | |
| font-size: 1.2em; | |
| font-weight: bold; | |
| margin-bottom: 10px; | |
| color: #ffffff; | |
| } | |
| .upgrade-level { | |
| display: inline-block; | |
| background-color: #0d6efd; | |
| color: white; | |
| padding: 3px 8px; | |
| border-radius: 10px; | |
| font-size: 0.8em; | |
| margin-bottom: 10px; | |
| } | |
| .upgrade-stat { | |
| display: flex; | |
| align-items: center; | |
| margin-bottom: 5px; | |
| } | |
| .upgrade-stat-label { | |
| width: 100px; | |
| font-size: 0.9em; | |
| color: #adb5bd; | |
| } | |
| .upgrade-stat-value { | |
| font-weight: bold; | |
| } | |
| .upgrade-cost { | |
| margin-top: 10px; | |
| font-weight: bold; | |
| color: #6610f2; | |
| } | |
| .level-bar { | |
| height: 6px; | |
| background-color: #333; | |
| border-radius: 3px; | |
| margin-bottom: 10px; | |
| overflow: hidden; | |
| } | |
| .level-progress { | |
| height: 100%; | |
| background-color: #0d6efd; | |
| border-radius: 3px; | |
| } | |
| @keyframes pulse-button { | |
| 0% { transform: scale(1); } | |
| 50% { transform: scale(1.05); } | |
| 100% { transform: scale(1); } | |
| } | |
| .pulse-button { | |
| animation: pulse-button 2s infinite; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| st.write("Improve your expeditions with these upgrades:") | |
| # Create columns for each upgrade type | |
| col1, col2, col3 = st.columns(3) | |
| # Get current upgrade levels | |
| upgrades = st.session_state.library_upgrades | |
| # Speed upgrade | |
| with col1: | |
| speed_level = upgrades.get("speed", 1) | |
| speed_cost = calculate_upgrade_cost("speed", speed_level) | |
| # Calculate max level for progress bar | |
| max_level = 10 | |
| progress_percentage = min(100, (speed_level / max_level) * 100) | |
| # Create an upgrade card with progress bar in dark theme | |
| st.markdown(f""" | |
| <div class="upgrade-card"> | |
| <div class="upgrade-title">⏱️ Speed Upgrade</div> | |
| <div class="upgrade-level">Level {speed_level}</div> | |
| <div class="level-bar"> | |
| <div class="level-progress" style="width: {progress_percentage}%;"></div> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Effect:</span> | |
| <span class="upgrade-stat-value">Reduces cooldown time</span> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Current:</span> | |
| <span class="upgrade-stat-value">{format_time_remaining(calculate_expedition_duration())}</span> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Next Level:</span> | |
| <span class="upgrade-stat-value">{format_time_remaining(calculate_expedition_duration() * 0.9)}</span> | |
| </div> | |
| <div class="upgrade-cost" style="color: #9D4EDD;">Cost: {speed_cost} {TAG_CURRENCY_NAME}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Upgrade button | |
| can_afford = st.session_state.tag_currency >= speed_cost | |
| button_class = "pulse-button" if can_afford else "" | |
| if st.button(f"Upgrade Speed", key=f"upgrade_speed", disabled=not can_afford, use_container_width=True): | |
| if purchase_library_upgrade("speed"): | |
| st.success(f"Speed upgraded to level {speed_level + 1}!") | |
| st.rerun() | |
| else: | |
| st.error(f"Not enough {TAG_CURRENCY_NAME}. Need {speed_cost}.") | |
| # Capacity upgrade | |
| with col2: | |
| capacity_level = upgrades.get("capacity", 1) | |
| capacity_cost = calculate_upgrade_cost("capacity", capacity_level) | |
| # Calculate max level for progress bar | |
| max_level = 10 | |
| progress_percentage = min(100, (capacity_level / max_level) * 100) | |
| # Create an upgrade card with progress bar in dark theme | |
| st.markdown(f""" | |
| <div class="upgrade-card"> | |
| <div class="upgrade-title">🔍 Capacity Upgrade</div> | |
| <div class="upgrade-level">Level {capacity_level}</div> | |
| <div class="level-bar"> | |
| <div class="level-progress" style="width: {progress_percentage}%;"></div> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Effect:</span> | |
| <span class="upgrade-stat-value">Increases tags discovered</span> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Current:</span> | |
| <span class="upgrade-stat-value">{calculate_expedition_capacity()} tags</span> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Next Level:</span> | |
| <span class="upgrade-stat-value">{calculate_expedition_capacity() + 1} tags</span> | |
| </div> | |
| <div class="upgrade-cost" style="color: #9D4EDD;">Cost: {capacity_cost} {TAG_CURRENCY_NAME}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Upgrade button | |
| can_afford = st.session_state.tag_currency >= capacity_cost | |
| button_class = "pulse-button" if can_afford else "" | |
| if st.button(f"Upgrade Capacity", key=f"upgrade_capacity", disabled=not can_afford, use_container_width=True): | |
| if purchase_library_upgrade("capacity"): | |
| st.success(f"Capacity upgraded to level {capacity_level + 1}!") | |
| st.rerun() | |
| else: | |
| st.error(f"Not enough {TAG_CURRENCY_NAME}. Need {capacity_cost}.") | |
| # Rarity upgrade | |
| with col3: | |
| rarity_level = upgrades.get("rarity", 1) | |
| rarity_cost = calculate_upgrade_cost("rarity", rarity_level) | |
| # Calculate max level for progress bar | |
| max_level = 10 | |
| progress_percentage = min(100, (rarity_level / max_level) * 100) | |
| # Create an upgrade card with progress bar in dark theme | |
| st.markdown(f""" | |
| <div class="upgrade-card"> | |
| <div class="upgrade-title">💎 Rarity Upgrade</div> | |
| <div class="upgrade-level">Level {rarity_level}</div> | |
| <div class="level-bar"> | |
| <div class="level-progress" style="width: {progress_percentage}%;"></div> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Effect:</span> | |
| <span class="upgrade-stat-value">Improves rare tag chance</span> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Current:</span> | |
| <span class="upgrade-stat-value">+{(rarity_level - 1) * 20}% boost</span> | |
| </div> | |
| <div class="upgrade-stat"> | |
| <span class="upgrade-stat-label">Next Level:</span> | |
| <span class="upgrade-stat-value">+{rarity_level * 20}% boost</span> | |
| </div> | |
| <div class="upgrade-cost" style="color: #9D4EDD;">Cost: {rarity_cost} {TAG_CURRENCY_NAME}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Upgrade button | |
| can_afford = st.session_state.tag_currency >= rarity_cost | |
| button_class = "pulse-button" if can_afford else "" | |
| if st.button(f"Upgrade Rarity", key=f"upgrade_rarity", disabled=not can_afford, use_container_width=True): | |
| if purchase_library_upgrade("rarity"): | |
| st.success(f"Rapacity upgraded to level {rarity_level + 1}!") | |
| st.rerun() | |
| else: | |
| st.error(f"Not enough {TAG_CURRENCY_NAME}. Need {rarity_cost}.") | |
| # Add a styled info box about library growth with dark theme | |
| st.markdown(""" | |
| <div style="background-color: rgba(13, 110, 253, 0.15); | |
| border-left: 4px solid #0d6efd; | |
| border-radius: 4px; | |
| padding: 15px; | |
| margin-top: 20px; | |
| color: #ffffff;"> | |
| <h4 style="margin-top: 0; color: #6495ED;">📚 Library Growth</h4> | |
| <p style="margin-bottom: 0;"> | |
| Your library will grow as you collect more tags. Each floor of the library unlocks new rarities and | |
| improves your chances of finding rare tags. Continue collecting tags to unlock deeper levels of the library! | |
| </p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| def initialize_library_system(): | |
| """Initialize the library system state in session state if not already present.""" | |
| if 'library_system_initialized' not in st.session_state: | |
| st.session_state.library_system_initialized = True | |
| # Try to load from storage first | |
| library_state = tag_storage.load_library_state(st.session_state) | |
| if library_state: | |
| # We already have the state loaded into session_state by the load function | |
| print("Library system loaded from storage.") | |
| else: | |
| # Initialize with defaults | |
| st.session_state.discovered_tags = {} # {tag_name: {"rarity": str, "discovery_time": timestamp, "category": str}} | |
| st.session_state.library_exploration_history = [] # List of recent library explorations | |
| # Initialize enkephalin if not present | |
| if 'enkephalin' not in st.session_state: | |
| st.session_state.enkephalin = 0 | |
| # For the library interface | |
| st.session_state.expedition_results = [] # Results from completed expeditions | |
| # Library growth system | |
| st.session_state.library_growth = { | |
| "total_discoveries": 0, | |
| "last_discovery_time": time.time() | |
| } | |
| # Upgrade system for library | |
| st.session_state.library_upgrades = { | |
| "speed": 1, # Expedition speed (reduces cooldown time) | |
| "capacity": 1, # Tags discovered per expedition | |
| "rarity": 1 # Rare tag chance | |
| } | |
| # Set of explored library tiers | |
| st.session_state.explored_library_tiers = set() | |
| print("Library system initialized with defaults.") | |
| # Store library floors in session state if not already there | |
| if 'library_floors' not in st.session_state: | |
| st.session_state.library_floors = LIBRARY_FLOORS | |
| # Update categories for any "unknown" category tags | |
| update_discovered_tag_categories() | |
| # Add CSS animations for styling | |
| st.markdown(""" | |
| <style> | |
| /* Star of the City animation */ | |
| @keyframes star-glow { | |
| 0% { text-shadow: 0 0 5px #FFD700; } | |
| 50% { text-shadow: 0 0 15px #FFD700; } | |
| 100% { text-shadow: 0 0 5px #FFD700; } | |
| } | |
| /* Impuritas Civitas animation */ | |
| @keyframes rainbow-text { | |
| 0% { color: red; } | |
| 14% { color: orange; } | |
| 28% { color: yellow; } | |
| 42% { color: green; } | |
| 57% { color: blue; } | |
| 71% { color: indigo; } | |
| 85% { color: violet; } | |
| 100% { color: red; } | |
| } | |
| @keyframes rainbow-border { | |
| 0% { border-color: red; } | |
| 14% { border-color: orange; } | |
| 28% { border-color: yellow; } | |
| 42% { border-color: green; } | |
| 57% { border-color: blue; } | |
| 71% { border-color: indigo; } | |
| 85% { border-color: violet; } | |
| 100% { border-color: red; } | |
| } | |
| /* Urban Nightmare animation */ | |
| @keyframes nightmare-pulse { | |
| 0% { border-color: #FF9800; } | |
| 50% { border-color: #FF5722; } | |
| 100% { border-color: #FF9800; } | |
| } | |
| /* Urban Plague subtle effect */ | |
| .glow-purple { | |
| text-shadow: 0 0 3px #9C27B0; | |
| } | |
| /* Apply the animations to specific rarity classes */ | |
| .star-of-city { | |
| animation: star-glow 2s infinite; | |
| font-weight: bold; | |
| } | |
| .impuritas-civitas { | |
| animation: rainbow-text 4s linear infinite; | |
| font-weight: bold; | |
| } | |
| .urban-nightmare { | |
| animation: nightmare-pulse 3s infinite; | |
| font-weight: bold; | |
| } | |
| .urban-plague { | |
| text-shadow: 0 0 3px #9C27B0; | |
| font-weight: bold; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| def display_library_extraction(): | |
| """Display the library exploration interface.""" | |
| initialize_library_system() | |
| st.title(f"Welcome to {LIBRARY_INFO['name']}") | |
| st.markdown(f""" | |
| <div style="background-color: rgba(74, 20, 140, 0.15); | |
| border-radius: 10px; | |
| padding: 15px; | |
| margin-bottom: 20px; | |
| border-left: 5px solid {LIBRARY_INFO['color']}; | |
| color: #ffffff;"> | |
| <p style="margin: 0;">{LIBRARY_INFO['description']}</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # Create tabs with enhanced styling for dark theme | |
| st.markdown(""" | |
| <style> | |
| /* Custom styling for tabs */ | |
| .stTabs [data-baseweb="tab-list"] { | |
| gap: 2px; | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| border-radius: 5px 5px 0 0; | |
| padding: 10px 16px; | |
| font-weight: 600; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # Store current tab index in session state if not present | |
| if 'library_tab_index' not in st.session_state: | |
| st.session_state.library_tab_index = 0 | |
| # Create expanded tabs - removed the essence tab | |
| explore_tab, discovered_tab, building_tab = st.tabs([ | |
| "📚 Library Exploration", | |
| "🔍 Discovered Tags", | |
| "🏛️ Library Building" | |
| ]) | |
| with explore_tab: | |
| st.session_state.library_tab_index = 0 | |
| display_library_exploration_interface() | |
| with discovered_tab: | |
| st.session_state.library_tab_index = 1 | |
| st.subheader("Your Discovered Tags") | |
| st.write("These are tags you've discovered through the library system. They differ from your collected tags, which are obtained from scanning images.") | |
| # Display discovered tags using our new function | |
| display_discovered_tags() | |
| with building_tab: | |
| st.session_state.library_tab_index = 2 | |
| display_library_building() | |
| def display_discovered_tags(): | |
| """Display the user's discovered tags with the same visual style as the tag collection""" | |
| # Show total unique discovered tags | |
| if not hasattr(st.session_state, 'discovered_tags') or not st.session_state.discovered_tags: | |
| st.info("Explore the library to discover new tags!") | |
| return | |
| unique_tags = len(st.session_state.discovered_tags) | |
| st.write(f"You have discovered {unique_tags} unique tags.") | |
| # Count tags by rarity | |
| rarity_counts = {} | |
| for tag_info in st.session_state.discovered_tags.values(): | |
| rarity = tag_info.get("rarity", "Unknown") | |
| if rarity not in rarity_counts: | |
| rarity_counts[rarity] = 0 | |
| rarity_counts[rarity] += 1 | |
| # Only display rarity categories that have tags | |
| active_rarities = {r: c for r, c in rarity_counts.items() if c > 0} | |
| # If there are active rarities to display | |
| if active_rarities: | |
| display_discovered_rarity_distribution(active_rarities) | |
| # Add a sorting option | |
| sort_options = ["Category (rarest first)", "Rarity", "Discovery Time"] | |
| selected_sort = st.selectbox("Sort tags by:", sort_options, key="discovered_tags_sort") | |
| # Group tags by the selected method | |
| if selected_sort == "Category (rarest first)": | |
| # Group tags by category | |
| categories = {} | |
| for tag, info in st.session_state.discovered_tags.items(): | |
| category = info.get("category", "unknown") | |
| if category not in categories: | |
| categories[category] = [] | |
| categories[category].append((tag, info)) | |
| # Display tags by category in expanders | |
| for category, tags in sorted(categories.items()): | |
| # Get rarity order for sorting | |
| rarity_order = list(RARITY_LEVELS.keys()) | |
| # Sort tags by rarity (rarest first) | |
| def get_rarity_index(tag_tuple): | |
| tag, info = tag_tuple | |
| rarity = info.get("rarity", "Unknown") | |
| if rarity in rarity_order: | |
| return len(rarity_order) - rarity_order.index(rarity) | |
| return 0 | |
| sorted_tags = sorted(tags, key=get_rarity_index, reverse=True) | |
| # Check if category has any rare tags | |
| has_rare_tags = any(info.get("rarity") in ["Impuritas Civitas", "Star of the City"] | |
| for _, info in sorted_tags) | |
| # Get category info if available | |
| category_display = category.capitalize() | |
| if category in TAG_CATEGORIES: | |
| category_info = TAG_CATEGORIES[category] | |
| category_icon = category_info.get("icon", "") | |
| category_color = category_info.get("color", "#888888") | |
| category_display = f"<span style='color:{category_color};'>{category_icon} {category.capitalize()}</span>" | |
| # Create header with information about rare tags if present | |
| header = f"{category_display} ({len(tags)} tags)" | |
| if has_rare_tags: | |
| header += " ✨ Contains rare tags!" | |
| # Display the header and expander | |
| st.markdown(header, unsafe_allow_html=True) | |
| with st.expander("Show/Hide", expanded=has_rare_tags): | |
| # Group by rarity within category | |
| rarity_groups = {} | |
| for tag, info in sorted_tags: | |
| rarity = info.get("rarity", "Unknown") | |
| if rarity not in rarity_groups: | |
| rarity_groups[rarity] = [] | |
| rarity_groups[rarity].append((tag, info)) | |
| # Display each rarity group in order (rarest first) | |
| for rarity in reversed(rarity_order): | |
| if rarity in rarity_groups: | |
| tags_in_rarity = rarity_groups[rarity] | |
| if tags_in_rarity: | |
| color = RARITY_LEVELS[rarity]["color"] | |
| # Special styling for rare rarities | |
| if rarity == "Impuritas Civitas": | |
| rarity_style = f"animation:rainbow-text 4s linear infinite;font-weight:bold;" | |
| elif rarity == "Star of the City": | |
| rarity_style = f"color:{color};text-shadow:0 0 3px gold;font-weight:bold;" | |
| elif rarity == "Urban Nightmare": | |
| rarity_style = f"color:{color};text-shadow:0 0 1px #FF5722;font-weight:bold;" | |
| else: | |
| rarity_style = f"color:{color};font-weight:bold;" | |
| st.markdown(f"<span style='{rarity_style}'>{rarity.capitalize()}</span> ({len(tags_in_rarity)} tags)", unsafe_allow_html=True) | |
| display_discovered_tag_grid(tags_in_rarity) | |
| st.markdown("---") | |
| elif selected_sort == "Rarity": | |
| # Group tags by rarity level | |
| rarity_groups = {} | |
| for tag, info in st.session_state.discovered_tags.items(): | |
| rarity = info.get("rarity", "Unknown") | |
| if rarity not in rarity_groups: | |
| rarity_groups[rarity] = [] | |
| rarity_groups[rarity].append((tag, info)) | |
| # Get ordered rarities (rarest first) | |
| ordered_rarities = list(RARITY_LEVELS.keys()) | |
| ordered_rarities.reverse() # Reverse to show rarest first | |
| # Display tags by rarity | |
| for rarity in ordered_rarities: | |
| if rarity in rarity_groups: | |
| tags = rarity_groups[rarity] | |
| color = RARITY_LEVELS[rarity]["color"] | |
| # Add special styling for rare rarities | |
| rarity_html = f"<span style='color:{color};font-weight:bold;'>{rarity.capitalize()}</span>" | |
| if rarity == "Impuritas Civitas": | |
| rarity_html = f"<span style='animation:rainbow-text 4s linear infinite;font-weight:bold;'>{rarity.capitalize()}</span>" | |
| elif rarity == "Star of the City": | |
| rarity_html = f"<span style='color:{color};text-shadow:0 0 3px gold;font-weight:bold;'>{rarity.capitalize()}</span>" | |
| elif rarity == "Urban Nightmare": | |
| rarity_html = f"<span style='color:{color};text-shadow:0 0 1px #FF5722;font-weight:bold;'>{rarity.capitalize()}</span>" | |
| # First create the title with HTML, then use it in the expander | |
| st.markdown(f"### {rarity_html} ({len(tags)} tags)", unsafe_allow_html=True) | |
| with st.expander("Show/Hide", expanded=rarity in ["Impuritas Civitas", "Star of the City"]): | |
| # Group by category within rarity | |
| category_groups = {} | |
| for tag, info in tags: | |
| category = info.get("category", "unknown") | |
| if category not in category_groups: | |
| category_groups[category] = [] | |
| category_groups[category].append((tag, info)) | |
| # Display each category within this rarity level | |
| for category, category_tags in sorted(category_groups.items()): | |
| # Get category info if available | |
| category_display = category.capitalize() | |
| if category in TAG_CATEGORIES: | |
| category_info = TAG_CATEGORIES[category] | |
| category_icon = category_info.get("icon", "") | |
| category_color = category_info.get("color", "#888888") | |
| category_display = f"<span style='color:{category_color};'>{category_icon} {category.capitalize()}</span>" | |
| st.markdown(f"#### {category_display} ({len(category_tags)} tags)", unsafe_allow_html=True) | |
| display_discovered_tag_grid(category_tags) | |
| st.markdown("---") | |
| elif selected_sort == "Discovery Time": | |
| # Sort all tags by discovery time (newest first) | |
| sorted_tags = [] | |
| for tag, info in st.session_state.discovered_tags.items(): | |
| discovery_time = info.get("discovery_time", "") | |
| sorted_tags.append((tag, info, discovery_time)) | |
| sorted_tags.sort(key=lambda x: x[2], reverse=True) # Sort by time, newest first | |
| # Group by date | |
| date_groups = {} | |
| for tag, info, time_str in sorted_tags: | |
| # Extract just the date part if timestamp has date and time | |
| date = time_str.split()[0] if " " in time_str else time_str | |
| if date not in date_groups: | |
| date_groups[date] = [] | |
| date_groups[date].append((tag, info)) | |
| # Display tags grouped by discovery date | |
| for date, tags in date_groups.items(): | |
| date_display = date if date else "Unknown date" | |
| st.markdown(f"### Discovered on {date_display} ({len(tags)} tags)") | |
| with st.expander("Show/Hide", expanded=date == list(date_groups.keys())[0]): # Expand most recent by default | |
| display_discovered_tag_grid(tags) | |
| st.markdown("---") | |
| def display_discovered_rarity_distribution(active_rarities): | |
| """Display distribution of discovered tags by rarity with themed animations""" | |
| # Add the necessary CSS for animations | |
| st.markdown(""" | |
| <style> | |
| @keyframes grid-glow { | |
| 0% { text-shadow: 0 0 2px gold; } | |
| 50% { text-shadow: 0 0 6px gold; } | |
| 100% { text-shadow: 0 0 2px gold; } | |
| } | |
| @keyframes grid-rainbow { | |
| 0% { color: red; } | |
| 14% { color: orange; } | |
| 28% { color: yellow; } | |
| 42% { color: green; } | |
| 57% { color: blue; } | |
| 71% { color: indigo; } | |
| 85% { color: violet; } | |
| 100% { color: red; } | |
| } | |
| @keyframes grid-pulse { | |
| 0% { opacity: 0.8; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.8; } | |
| } | |
| .grid-star { | |
| text-shadow: 0 0 3px gold; | |
| animation: grid-glow 2s infinite; | |
| } | |
| .grid-impuritas { | |
| animation: grid-rainbow 4s linear infinite; | |
| } | |
| .grid-nightmare { | |
| text-shadow: 0 0 1px #FF5722; | |
| animation: grid-pulse 3s infinite; | |
| } | |
| .grid-plague { | |
| text-shadow: 0 0 1px #9C27B0; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| rarity_cols = st.columns(len(active_rarities)) | |
| for i, (rarity, count) in enumerate(active_rarities.items()): | |
| with rarity_cols[i]: | |
| # Get color with fallback | |
| color = RARITY_LEVELS.get(rarity, {}).get("color", "#888888") | |
| # Apply special styling based on rarity | |
| style = f"color:{color};font-weight:bold;" | |
| class_name = "" | |
| if rarity == "Impuritas Civitas": | |
| class_name = "grid-impuritas" | |
| elif rarity == "Star of the City": | |
| class_name = "grid-star" | |
| elif rarity == "Urban Nightmare": | |
| class_name = "grid-nightmare" | |
| elif rarity == "Urban Plague": | |
| class_name = "grid-plague" | |
| if class_name: | |
| st.markdown( | |
| f"<div style='text-align:center;'><span class='{class_name}' style='font-weight:bold;'>{rarity.capitalize()}</span><br>{count}</div>", | |
| unsafe_allow_html=True | |
| ) | |
| else: | |
| st.markdown( | |
| f"<div style='text-align:center;'><span style='{style}'>{rarity.capitalize()}</span><br>{count}</div>", | |
| unsafe_allow_html=True | |
| ) | |
| def display_discovered_tag_grid(tags): | |
| """Display discovered tags in a grid layout with discovery information""" | |
| # Create a grid layout for tags | |
| cols = st.columns(3) | |
| for i, (tag, info) in enumerate(sorted(tags)): | |
| col_idx = i % 3 | |
| with cols[col_idx]: | |
| rarity = info.get("rarity", "Unknown") | |
| discovery_time = info.get("discovery_time", "") | |
| library_floor = info.get("library_floor", "") | |
| discovery_count = info.get("discovery_count", 1) | |
| color = RARITY_LEVELS.get(rarity, {}).get("color", "#888888") | |
| # Get sample count if available | |
| sample_count = None | |
| if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata: | |
| if tag in st.session_state.tag_rarity_metadata: | |
| tag_info = st.session_state.tag_rarity_metadata[tag] | |
| if isinstance(tag_info, dict) and "sample_count" in tag_info: | |
| sample_count = tag_info["sample_count"] | |
| # Format sample count | |
| sample_display = "" | |
| if sample_count is not None: | |
| if sample_count >= 1000000: | |
| sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count/1000000:.1f}M)</span>" | |
| elif sample_count >= 1000: | |
| sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count/1000:.1f}K)</span>" | |
| else: | |
| sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count})</span>" | |
| # Apply special styling for rare tags | |
| tag_html = tag | |
| if rarity == "Impuritas Civitas": | |
| tag_html = f"<span style='animation: rainbow-text 4s linear infinite;'>{tag}</span>" | |
| elif rarity == "Star of the City": | |
| tag_html = f"<span style='text-shadow: 0 0 3px gold;'>{tag}</span>" | |
| elif rarity == "Urban Nightmare": | |
| tag_html = f"<span style='text-shadow: 0 0 1px #FF9800;'>{tag}</span>" | |
| # Display tag with rarity badge and discovery info | |
| st.markdown( | |
| f"{tag_html} <span style='background-color:{color};color:white;padding:2px 6px;border-radius:10px;font-size:0.8em;'>{rarity.capitalize()}</span> {sample_display}", | |
| unsafe_allow_html=True | |
| ) | |
| # Show discovery details | |
| if library_floor: | |
| st.markdown(f"<span style='font-size:0.85em;'>Found in: {library_floor}</span>", unsafe_allow_html=True) | |
| if discovery_count > 1: | |
| st.markdown(f"<span style='font-size:0.85em;'>Seen {discovery_count} times</span>", unsafe_allow_html=True) |