|
|
|
"""
|
|
Tag Collector Game
|
|
A gamified version of the Image Tagger application where users collect tags
|
|
to earn currency that can be used to lower thresholds and discover rarer tags.
|
|
"""
|
|
|
|
import streamlit as st
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import math
|
|
import json
|
|
import random
|
|
from PIL import Image
|
|
from collections import Counter
|
|
import torch
|
|
import importlib
|
|
import traceback
|
|
|
|
|
|
import tag_storage
|
|
import state_manager
|
|
|
|
|
|
from game_constants import (
|
|
TAG_CURRENCY_NAME, TAG_ANIMATIONS, TAG_POWER_BONUSES, ENKEPHALIN_CURRENCY_NAME, ENKEPHALIN_ICON, RARITY_LEVELS, ACHIEVEMENTS,
|
|
STARTING_THRESHOLD, MIN_THRESHOLD,
|
|
THRESHOLD_UPGRADES
|
|
)
|
|
from tag_categories import (
|
|
TAG_CATEGORIES, initialize_progression_system, get_unlocked_categories,
|
|
get_max_detectable_tags, get_collection_power_level
|
|
)
|
|
from tag_mosaic import display_tag_mosaic, RevealMosaic
|
|
from series_mosaics import display_series_mosaics
|
|
from scan_handler import enhanced_scan_button_handler
|
|
from library_system import initialize_library_system, display_library_extraction
|
|
from dev_tools import display_dev_tools
|
|
|
|
|
|
|
|
|
|
st.set_page_config(layout="wide", page_title="Camie Collector", page_icon="🎮")
|
|
|
|
|
|
|
|
|
|
|
|
@st.cache_data
|
|
def load_tag_rarity_metadata():
|
|
"""
|
|
Load the tag rarity metadata from the JSON file.
|
|
Returns the tag_rarity dictionary or None if the file doesn't exist.
|
|
|
|
Returns:
|
|
dict: Tag rarity metadata or None if not found
|
|
"""
|
|
try:
|
|
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
metadata_path = os.path.join(parent_dir, "model", "tag_rarity_metadata.json")
|
|
if os.path.exists(metadata_path):
|
|
with open(metadata_path, 'r') as f:
|
|
metadata = json.load(f)
|
|
print(f"Loaded tag rarity metadata for {len(metadata['tag_rarity'])} tags")
|
|
return metadata["tag_rarity"]
|
|
else:
|
|
print(f"Warning: {metadata_path} not found.")
|
|
print("No metadata file found.")
|
|
return None
|
|
except Exception as e:
|
|
print(f"Error loading tag rarity metadata: {str(e)}")
|
|
traceback.print_exc()
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def is_windows():
|
|
"""Check if the system is Windows"""
|
|
import platform
|
|
return platform.system() == "Windows"
|
|
|
|
def load_model():
|
|
"""
|
|
Load the image tagger model for the game
|
|
|
|
Returns:
|
|
tuple: (model, thresholds, metadata)
|
|
"""
|
|
|
|
if '.' not in sys.path:
|
|
sys.path.append('.')
|
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
parent_dir = os.path.dirname(current_dir)
|
|
|
|
print(parent_dir)
|
|
|
|
model_dir = os.path.join(parent_dir)
|
|
|
|
|
|
if not model_dir:
|
|
st.error("Model directory not found. Searched in:")
|
|
st.write(f"- {os.path.abspath(model_dir)}")
|
|
st.info("Please make sure you've exported the model first.")
|
|
st.stop()
|
|
|
|
|
|
model_type = "camie-tagger-v2"
|
|
|
|
try:
|
|
|
|
with st.spinner(f"Loading model for Tag Collector Game..."):
|
|
model, thresholds, metadata = load_exported_model(model_dir, model_type)
|
|
return model, thresholds, metadata
|
|
|
|
except Exception as e:
|
|
st.error(f"Error loading model: {str(e)}")
|
|
st.code(traceback.format_exc())
|
|
st.stop()
|
|
|
|
def load_exported_model(model_dir, model_type):
|
|
"""
|
|
Load the exported model from the given directory.
|
|
|
|
Args:
|
|
model_dir: Path to the model directory
|
|
model_type: Type of model to load ('initial_only' or 'full')
|
|
|
|
Returns:
|
|
tuple: (model, thresholds, metadata)
|
|
"""
|
|
import torch
|
|
import json
|
|
import sys
|
|
import importlib.util
|
|
|
|
|
|
if model_dir not in sys.path:
|
|
sys.path.append(model_dir)
|
|
|
|
|
|
with open(model_dir+ "\camie-tagger-v2-metadata.json") as f:
|
|
metadata = json.load(f)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
model_code_path = os.path.join(model_dir, "app\\utils\\model_loader.py")
|
|
if os.path.exists(model_code_path):
|
|
|
|
spec = importlib.util.spec_from_file_location("model_code", model_code_path)
|
|
model_code = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(model_code)
|
|
|
|
|
|
class ModelDataset:
|
|
def __init__(self, metadata):
|
|
self.total_tags = metadata['total_tags']
|
|
self.idx_to_tag = {int(k): v for k, v in metadata['idx_to_tag'].items()}
|
|
self.tag_to_category = metadata.get('tag_to_category', {})
|
|
|
|
def get_tag_info(self, idx):
|
|
tag = self.idx_to_tag.get(idx, f"unknown_{idx}")
|
|
category = self.tag_to_category.get(tag, "general")
|
|
return tag, category
|
|
|
|
|
|
dataset = ModelDataset(metadata)
|
|
|
|
|
|
if model_type == "initial_only":
|
|
|
|
if os.path.exists(os.path.join(model_dir, "model_initial_only.pt")):
|
|
model_path = os.path.join(model_dir, "model_initial_only.pt")
|
|
else:
|
|
model_path = os.path.join(model_dir, "model_initial.pt")
|
|
|
|
|
|
if os.path.exists(os.path.join(model_dir, "model_info_initial_only.json")):
|
|
with open(os.path.join(model_dir, "model_info_initial_only.json"), "r") as f:
|
|
model_info = json.load(f)
|
|
else:
|
|
model_info = {"tag_context_size": 256, "num_heads": 16}
|
|
|
|
|
|
if hasattr(model_code, 'InitialOnlyImageTagger'):
|
|
model = model_code.InitialOnlyImageTagger(
|
|
total_tags=metadata['total_tags'],
|
|
dataset=dataset,
|
|
pretrained=False
|
|
)
|
|
else:
|
|
|
|
model = model_code.ImageTagger(
|
|
total_tags=metadata['total_tags'],
|
|
dataset=dataset,
|
|
pretrained=False,
|
|
tag_context_size=model_info.get('tag_context_size', 256),
|
|
num_heads=model_info.get('num_heads', 16)
|
|
)
|
|
else:
|
|
|
|
if os.path.exists(os.path.join(model_dir, "model_refined.pt")):
|
|
model_path = os.path.join(model_dir, "model_refined.pt")
|
|
else:
|
|
model_path = os.path.join(model_dir, "model.pt")
|
|
|
|
|
|
if os.path.exists(os.path.join(model_dir, "model_info.json")):
|
|
with open(os.path.join(model_dir, "model_info.json"), "r") as f:
|
|
model_info = json.load(f)
|
|
else:
|
|
model_info = {"tag_context_size": 256, "num_heads": 16}
|
|
|
|
|
|
model = model_code.ImageTagger(
|
|
total_tags=metadata['total_tags'],
|
|
dataset=dataset,
|
|
pretrained=False,
|
|
tag_context_size=model_info.get('tag_context_size', 256),
|
|
num_heads=model_info.get('num_heads', 16)
|
|
)
|
|
|
|
|
|
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
|
state_dict = torch.load(model_path, map_location=device)
|
|
model.load_state_dict(state_dict, strict=False)
|
|
|
|
|
|
model.to(device)
|
|
model.eval()
|
|
|
|
|
|
model.dataset = dataset
|
|
|
|
thresholds = []
|
|
|
|
return model, thresholds, metadata
|
|
else:
|
|
st.error(f"model_code.py not found at {model_code_path}")
|
|
st.stop()
|
|
|
|
|
|
|
|
|
|
|
|
def initialize_game_state():
|
|
"""Initialize the game state in session state if not already present."""
|
|
if 'game_initialized' not in st.session_state:
|
|
st.session_state.game_initialized = True
|
|
|
|
|
|
st.session_state.state_version = 0
|
|
|
|
|
|
loaded_tags, tag_history, _ = tag_storage.load_tag_collection()
|
|
|
|
if loaded_tags:
|
|
|
|
st.session_state.collected_tags = loaded_tags
|
|
st.session_state.tag_history = tag_history
|
|
else:
|
|
|
|
st.session_state.collected_tags = {}
|
|
st.session_state.tag_history = []
|
|
|
|
|
|
initialize_default_state()
|
|
|
|
if 'tag_rarity_metadata' not in st.session_state:
|
|
st.session_state.tag_rarity_metadata = load_tag_rarity_metadata()
|
|
|
|
def initialize_default_state():
|
|
"""Initialize default state variables"""
|
|
|
|
st.session_state.threshold = STARTING_THRESHOLD
|
|
st.session_state.tag_currency = 0
|
|
st.session_state.enkephalin = 0
|
|
st.session_state.purchased_upgrades = []
|
|
st.session_state.achievements = set()
|
|
st.session_state.current_scan = None
|
|
|
|
|
|
st.session_state.game_stats = {
|
|
"images_processed": 0,
|
|
"total_tags_found": 0,
|
|
"total_currency_earned": 0,
|
|
"currency_spent": 0,
|
|
"enkephalin_generated": 0,
|
|
"enkephalin_spent": 0,
|
|
"tags_sacrificed": 0,
|
|
"essences_generated": 0
|
|
}
|
|
|
|
|
|
st.session_state.tag_power_bonus = 0
|
|
st.session_state.coin_multiplier = 1.0
|
|
st.session_state.unlocked_combinations = set()
|
|
st.session_state.combination_bonuses = {"threshold_reduction": 0, "coin_bonus": 0}
|
|
|
|
|
|
st.session_state.sacrificed_tags = {}
|
|
|
|
def save_game():
|
|
"""Save the game state to files using the enhanced tag storage system."""
|
|
try:
|
|
|
|
import tag_storage
|
|
success = tag_storage.save_game(st.session_state)
|
|
return success
|
|
except Exception as e:
|
|
st.error(f"Failed to save game: {str(e)}")
|
|
return False
|
|
|
|
def load_game():
|
|
"""
|
|
Load the game state from files using the enhanced tag storage system.
|
|
|
|
Returns:
|
|
bool: True if load was successful, False otherwise
|
|
"""
|
|
try:
|
|
|
|
import tag_storage
|
|
success = tag_storage.load_game(st.session_state)
|
|
return success
|
|
except Exception as e:
|
|
st.error(f"Failed to load game: {str(e)}")
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def get_discovered_rarities():
|
|
"""
|
|
Helper function to get a set of all rarities the player has discovered
|
|
Returns:
|
|
set: Set of discovered rarity names
|
|
"""
|
|
|
|
discovered_rarities = set(["Canard"])
|
|
|
|
|
|
if hasattr(st.session_state, 'collected_tags') and st.session_state.collected_tags:
|
|
for tag_info in st.session_state.collected_tags.values():
|
|
if "rarity" in tag_info and tag_info["rarity"]:
|
|
discovered_rarities.add(tag_info["rarity"])
|
|
|
|
return discovered_rarities
|
|
|
|
def apply_tag_animations():
|
|
"""Apply CSS animations for special tag rarities from the game constants"""
|
|
|
|
css = ""
|
|
|
|
|
|
for rarity, animation_info in TAG_ANIMATIONS.items():
|
|
css += animation_info["animation"]
|
|
|
|
|
|
st.markdown(f"<style>{css}</style>", unsafe_allow_html=True)
|
|
|
|
def create_sidebar():
|
|
"""Create a simplified sidebar without game stats"""
|
|
with st.sidebar:
|
|
|
|
display_save_load_controls()
|
|
|
|
|
|
display_rarity_legend()
|
|
|
|
|
|
|
|
|
|
|
|
display_support_info()
|
|
|
|
def display_save_load_controls():
|
|
"""Display save/load game controls"""
|
|
st.subheader("Save/Load Game")
|
|
save_col, load_col = st.columns(2)
|
|
with save_col:
|
|
if st.button("Save Game"):
|
|
if save_game():
|
|
st.success("Game saved!")
|
|
|
|
st.rerun()
|
|
with load_col:
|
|
if st.button("Load Game"):
|
|
if load_game():
|
|
st.success("Game loaded!")
|
|
|
|
st.rerun()
|
|
else:
|
|
st.info("No saved game found.")
|
|
|
|
def display_rarity_legend():
|
|
"""Display a legend explaining the rarity levels with TagCoins and Enkephalin rewards, with undiscovered rarities blacked out and special effects for rare rarities"""
|
|
st.sidebar.subheader("Tag Rarity Legend")
|
|
|
|
|
|
st.sidebar.markdown("""
|
|
<style>
|
|
/* Star of the City animation */
|
|
@keyframes sidebar-glow {
|
|
0% { text-shadow: 0 0 2px gold; }
|
|
50% { text-shadow: 0 0 6px gold; }
|
|
100% { text-shadow: 0 0 2px gold; }
|
|
}
|
|
|
|
.sidebar-star {
|
|
animation: sidebar-glow 2s infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Impuritas Civitas animation */
|
|
@keyframes sidebar-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; }
|
|
}
|
|
|
|
.sidebar-impuritas {
|
|
animation: sidebar-rainbow 4s linear infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Urban Nightmare animation */
|
|
@keyframes sidebar-pulse {
|
|
0% { opacity: 0.8; }
|
|
50% { opacity: 1; }
|
|
100% { opacity: 0.8; }
|
|
}
|
|
|
|
.sidebar-nightmare {
|
|
animation: sidebar-pulse 3s infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Urban Plague subtle effect */
|
|
.sidebar-plague {
|
|
text-shadow: 0 0 2px #9C27B0;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Special Effect for Enkephalin */
|
|
@keyframes enkephalin-glow {
|
|
0% { text-shadow: 0 0 2px cyan; }
|
|
50% { text-shadow: 0 0 5px cyan; }
|
|
100% { text-shadow: 0 0 2px cyan; }
|
|
}
|
|
|
|
.enkephalin-value {
|
|
color: #00BCD4;
|
|
animation: enkephalin-glow 2s infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
/* Reward row styling */
|
|
.rewards-row {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 8px;
|
|
padding: 5px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.rewards-row:hover {
|
|
background-color: rgba(0,0,0,0.05);
|
|
}
|
|
|
|
.rarity-name {
|
|
flex: 0 0 35%;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.coin-value {
|
|
flex: 0 0 30%;
|
|
color: #FFD700;
|
|
}
|
|
|
|
.enkephalin-value {
|
|
flex: 0 0 35%;
|
|
color: #00BCD4;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
discovered_rarities = get_discovered_rarities()
|
|
|
|
|
|
st.sidebar.markdown("""
|
|
<div style="display: flex; margin-bottom: 10px; font-weight: bold;">
|
|
<div style="flex: 0 0 35%;">Rarity</div>
|
|
<div style="flex: 0 0 30%;">TagCoins</div>
|
|
<div style="flex: 0 0 35%;">Enkephalin</div>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
for rarity, info in RARITY_LEVELS.items():
|
|
|
|
enkephalin_reward = TAG_POWER_BONUSES.get(rarity, {}).get("enkephalin_reward", 0)
|
|
|
|
|
|
if rarity in discovered_rarities:
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
|
|
st.sidebar.markdown(
|
|
f"""
|
|
<div class="rewards-row">
|
|
<div class="rarity-name">
|
|
<span class='sidebar-impuritas'>{rarity}</span>
|
|
</div>
|
|
<div class="coin-value">
|
|
{info['value']} {TAG_CURRENCY_NAME}
|
|
</div>
|
|
<div class="enkephalin-value">
|
|
+{enkephalin_reward} {ENKEPHALIN_ICON}
|
|
</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
elif rarity == "Star of the City":
|
|
|
|
st.sidebar.markdown(
|
|
f"""
|
|
<div class="rewards-row">
|
|
<div class="rarity-name">
|
|
<span class='sidebar-star' style='color:{info['color']}'>{rarity}</span>
|
|
</div>
|
|
<div class="coin-value">
|
|
{info['value']} {TAG_CURRENCY_NAME}
|
|
</div>
|
|
<div class="enkephalin-value">
|
|
+{enkephalin_reward} {ENKEPHALIN_ICON}
|
|
</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
elif rarity == "Urban Nightmare":
|
|
|
|
st.sidebar.markdown(
|
|
f"""
|
|
<div class="rewards-row">
|
|
<div class="rarity-name">
|
|
<span class='sidebar-nightmare' style='color:{info['color']}'>{rarity}</span>
|
|
</div>
|
|
<div class="coin-value">
|
|
{info['value']} {TAG_CURRENCY_NAME}
|
|
</div>
|
|
<div class="enkephalin-value">
|
|
+{enkephalin_reward} {ENKEPHALIN_ICON}
|
|
</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
elif rarity == "Urban Plague":
|
|
|
|
st.sidebar.markdown(
|
|
f"""
|
|
<div class="rewards-row">
|
|
<div class="rarity-name">
|
|
<span class='sidebar-plague' style='color:{info['color']}'>{rarity}</span>
|
|
</div>
|
|
<div class="coin-value">
|
|
{info['value']} {TAG_CURRENCY_NAME}
|
|
</div>
|
|
<div class="enkephalin-value">
|
|
+{enkephalin_reward} {ENKEPHALIN_ICON}
|
|
</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
else:
|
|
|
|
st.sidebar.markdown(
|
|
f"""
|
|
<div class="rewards-row">
|
|
<div class="rarity-name">
|
|
<span style='color:{info['color']}'>{rarity}</span>
|
|
</div>
|
|
<div class="coin-value">
|
|
{info['value']} {TAG_CURRENCY_NAME}
|
|
</div>
|
|
<div class="enkephalin-value">
|
|
{'+' + str(enkephalin_reward) + ' ' + ENKEPHALIN_ICON if enkephalin_reward > 0 else '-'}
|
|
</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
else:
|
|
|
|
st.sidebar.markdown(
|
|
f"""
|
|
<div class="rewards-row">
|
|
<div class="rarity-name">
|
|
<span style='color:#333333;'>?????</span>
|
|
</div>
|
|
<div class="coin-value">
|
|
??? {TAG_CURRENCY_NAME}
|
|
</div>
|
|
<div class="enkephalin-value">
|
|
??? {ENKEPHALIN_ICON}
|
|
</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def display_support_info():
|
|
|
|
st.markdown("---")
|
|
|
|
|
|
st.subheader("💡 Notes")
|
|
|
|
st.markdown("""
|
|
This tagger was trained on a subset of the available data and for limited epochs due to hardware limitations.
|
|
|
|
A more comprehensive model trained on the full 3+ million image dataset and many more epochs would provide:
|
|
- More recent characters and tags.
|
|
- Improved accuracy.
|
|
|
|
If you find this tool useful and would like to support future development:
|
|
""")
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes coffee-button-glow {
|
|
0% { box-shadow: 0 0 5px #FFD700; }
|
|
50% { box-shadow: 0 0 15px #FFD700; }
|
|
100% { box-shadow: 0 0 5px #FFD700; }
|
|
}
|
|
|
|
.coffee-button {
|
|
display: inline-block;
|
|
animation: coffee-button-glow 2s infinite;
|
|
border-radius: 5px;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
.coffee-button:hover {
|
|
transform: scale(1.05);
|
|
}
|
|
</style>
|
|
|
|
<a href="https://ko-fi.com/camais" target="_blank" class="coffee-button">
|
|
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png"
|
|
alt="Buy Me A Coffee"
|
|
style="height: 45px; width: 162px; border-radius: 5px;" />
|
|
</a>
|
|
""", unsafe_allow_html=True)
|
|
|
|
st.markdown("""
|
|
Your support helps with:
|
|
- GPU costs for training
|
|
- Storage for larger datasets
|
|
- Development of new features
|
|
- Future projects
|
|
|
|
Thank you! 🙏
|
|
|
|
Full Details: https://huggingface.co/Camais03/camie-tagger
|
|
""")
|
|
|
|
def display_scan_interface():
|
|
"""Display the scan interface tab"""
|
|
st.subheader("Scan Images for Tags")
|
|
|
|
|
|
col1, col2 = st.columns([1, 1])
|
|
|
|
with col1:
|
|
|
|
uploaded_file = st.file_uploader("Choose an image...", type=["jpg", "jpeg", "png"])
|
|
|
|
image_path = None
|
|
if uploaded_file:
|
|
|
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as tmp_file:
|
|
tmp_file.write(uploaded_file.getvalue())
|
|
image_path = tmp_file.name
|
|
|
|
|
|
image = Image.open(uploaded_file)
|
|
st.image(image, use_container_width=True)
|
|
|
|
|
|
button_key = f"scan_btn_{st.session_state.get('state_version', 0)}"
|
|
if st.button("Scan for Tags", key=button_key):
|
|
|
|
with st.spinner("Scanning image..."):
|
|
success = enhanced_scan_button_handler(image_path)
|
|
if success:
|
|
|
|
import tag_storage
|
|
tag_storage.update_tag_storage_from_session(st.session_state)
|
|
|
|
|
|
st.rerun()
|
|
|
|
with col2:
|
|
display_scanner_settings()
|
|
|
|
|
|
if hasattr(st.session_state, 'current_scan') and st.session_state.current_scan:
|
|
display_scan_results(st.session_state.current_scan)
|
|
|
|
def display_scan_results(scan_data):
|
|
"""Display scan results from session state with improved tag categorization and special effects for rare tags"""
|
|
if not scan_data:
|
|
return
|
|
|
|
found_tags = scan_data.get("found_tags", [])
|
|
all_tags = scan_data.get("all_tags", {})
|
|
total_currency_earned = scan_data.get("total_currency_earned", 0)
|
|
total_enkephalin_earned = scan_data.get("total_enkephalin_earned", 0)
|
|
new_tag_count = scan_data.get("new_tag_count", 0)
|
|
|
|
|
|
new_rare_tags = [t for t in found_tags if t.get("is_new", False) and
|
|
t.get("rarity") in ["Star of the City", "Impuritas Civitas"]]
|
|
|
|
if new_rare_tags:
|
|
|
|
rarity_order = ["Impuritas Civitas", "Star of the City"]
|
|
new_rare_tags.sort(key=lambda x: rarity_order.index(x["rarity"]) if x["rarity"] in rarity_order else 999)
|
|
|
|
|
|
rarest_tag = new_rare_tags[0]
|
|
if rarest_tag["rarity"] == "Impuritas Civitas":
|
|
st.markdown(f"""
|
|
<style>
|
|
@keyframes rainbow-bg {{
|
|
0% {{ background-color: rgba(255,0,0,0.1); }}
|
|
14% {{ background-color: rgba(255,165,0,0.1); }}
|
|
28% {{ background-color: rgba(255,255,0,0.1); }}
|
|
42% {{ background-color: rgba(0,128,0,0.1); }}
|
|
57% {{ background-color: rgba(0,0,255,0.1); }}
|
|
71% {{ background-color: rgba(75,0,130,0.1); }}
|
|
85% {{ background-color: rgba(238,130,238,0.1); }}
|
|
100% {{ background-color: rgba(255,0,0,0.1); }}
|
|
}}
|
|
|
|
.rare-discovery-banner {{
|
|
background-color: rgba(0,0,0,0.1);
|
|
border: 3px solid red;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
text-align: center;
|
|
animation: rainbow-bg 4s linear infinite;
|
|
}}
|
|
</style>
|
|
|
|
<div class="rare-discovery-banner">
|
|
<h2>✨ EXTRAORDINARY DISCOVERY! ✨</h2>
|
|
<h3>You found the ultra-rare Impuritas Civitas tag</h3>
|
|
<p>This is one of the rarest tags in the game!</p>
|
|
<p>Rewards: +{RARITY_LEVELS[rarest_tag["rarity"]]["value"]} {TAG_CURRENCY_NAME} and +25 {ENKEPHALIN_ICON}</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
elif rarest_tag["rarity"] == "Star of the City":
|
|
st.markdown(f"""
|
|
<style>
|
|
@keyframes glow-bg {{
|
|
0% {{ box-shadow: 0 0 10px #FFD700; }}
|
|
50% {{ box-shadow: 0 0 30px #FFD700; }}
|
|
100% {{ box-shadow: 0 0 10px #FFD700; }}
|
|
}}
|
|
|
|
.star-discovery-banner {{
|
|
background-color: rgba(255,215,0,0.1);
|
|
border: 2px solid gold;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin: 20px 0;
|
|
text-align: center;
|
|
box-shadow: 0 0 10px #FFD700;
|
|
animation: glow-bg 2s infinite;
|
|
}}
|
|
</style>
|
|
|
|
<div class="star-discovery-banner">
|
|
<h2>🌟 EXCEPTIONAL DISCOVERY! 🌟</h2>
|
|
<h3>You found a Star of the City tag</h3>
|
|
<p>This is a very rare and valuable tag!</p>
|
|
<p>Rewards: +{RARITY_LEVELS[rarest_tag["rarity"]]["value"]} {TAG_CURRENCY_NAME} and +10 {ENKEPHALIN_ICON}</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
if found_tags:
|
|
st.success(f"Found {len(found_tags)} tags! {f'({new_tag_count} new discoveries)' if new_tag_count > 0 else ''}")
|
|
|
|
|
|
rarity_counts = {}
|
|
for tag_info in found_tags:
|
|
rarity = tag_info.get("rarity", "Unknown")
|
|
is_new = tag_info.get("is_new", False)
|
|
|
|
if rarity not in rarity_counts:
|
|
rarity_counts[rarity] = {"total": 0, "new": 0}
|
|
|
|
rarity_counts[rarity]["total"] += 1
|
|
if is_new:
|
|
rarity_counts[rarity]["new"] += 1
|
|
|
|
|
|
rarity_order = list(RARITY_LEVELS.keys())
|
|
|
|
|
|
for rarity, counts in sorted(rarity_counts.items(),
|
|
key=lambda x: rarity_order.index(x[0]) if x[0] in rarity_order else 999):
|
|
color = RARITY_LEVELS.get(rarity, {}).get("color", "#AAAAAA")
|
|
total = counts["total"]
|
|
new_count = counts["new"]
|
|
|
|
|
|
col1, col2 = st.columns([3, 1])
|
|
with col1:
|
|
st.markdown(f"<span style='color:{color};font-weight:bold;'>{rarity}:</span> {total} tags", unsafe_allow_html=True)
|
|
with col2:
|
|
if new_count > 0:
|
|
st.write(f"({new_count} new)")
|
|
|
|
st.divider()
|
|
|
|
|
|
col1, col2, col3 = st.columns([1, 1, 1])
|
|
with col1:
|
|
st.write("**Earned:**")
|
|
with col2:
|
|
st.write(f"**{total_currency_earned} {TAG_CURRENCY_NAME}**")
|
|
with col3:
|
|
if total_enkephalin_earned > 0:
|
|
st.write(f"**{total_enkephalin_earned} {ENKEPHALIN_ICON}**")
|
|
|
|
|
|
st.divider()
|
|
col1, col2 = st.columns(2)
|
|
with col1:
|
|
st.write("**Images Processed:**")
|
|
st.write("**Total Tags Found:**")
|
|
st.write("**Current TagCoins:**")
|
|
st.write(f"**Current {ENKEPHALIN_CURRENCY_NAME}:**")
|
|
with col2:
|
|
st.write(f"**{st.session_state.game_stats['images_processed']}**")
|
|
st.write(f"**{st.session_state.game_stats['total_tags_found']}**")
|
|
st.write(f"**{st.session_state.tag_currency}**")
|
|
st.write(f"**{st.session_state.enkephalin} {ENKEPHALIN_ICON}**")
|
|
|
|
|
|
st.subheader("Found Tags by Category")
|
|
|
|
|
|
rare_rarities = ["Urban Legend", "Urban Plague", "Urban Nightmare", "Star of the City", "Impuritas Civitas"]
|
|
has_rare_tags = any(tag_info.get("rarity") in rare_rarities for tag_info in found_tags)
|
|
|
|
|
|
for category, tags in all_tags.items():
|
|
|
|
collected_tags = [(tag, prob, rarity, any(t["tag"] == tag and t["is_new"] for t in found_tags))
|
|
for tag, prob, rarity, status in tags if status == "collected"]
|
|
|
|
if not collected_tags:
|
|
continue
|
|
|
|
|
|
new_tags = [(tag, prob, rarity, True) for tag, prob, rarity, is_new in collected_tags if is_new]
|
|
existing_tags = [(tag, prob, rarity, False) for tag, prob, rarity, is_new in collected_tags if not is_new]
|
|
|
|
|
|
new_tags.sort(key=lambda x: rarity_order.index(x[2]) if x[2] in rarity_order else 999, reverse=True)
|
|
existing_tags.sort(key=lambda x: rarity_order.index(x[2]) if x[2] in rarity_order else 999, reverse=True)
|
|
|
|
|
|
ordered_tags = new_tags + existing_tags
|
|
|
|
|
|
category_has_rare = any(rarity in rare_rarities for _, _, rarity, _ in collected_tags)
|
|
|
|
|
|
default_expanded = category == None or (category_has_rare and has_rare_tags) or len(new_tags) > 0
|
|
|
|
|
|
category_title = f"{category.capitalize()} ({len(collected_tags)} tags)"
|
|
if new_tags:
|
|
category_title += f" 🆕 {len(new_tags)} new!"
|
|
|
|
|
|
if any(tag[2] in ["Star of the City", "Impuritas Civitas"] for tag in ordered_tags):
|
|
category_title += " ✨ RARE!"
|
|
|
|
with st.expander(category_title, expanded=default_expanded):
|
|
|
|
if new_tags:
|
|
st.markdown("### ✨ New Discoveries")
|
|
for tag, prob, rarity, _ in new_tags:
|
|
|
|
tag_info = next((t for t in found_tags if t["tag"] == tag), None)
|
|
enkephalin_reward = tag_info.get("enkephalin", 0) if tag_info else 0
|
|
|
|
|
|
if enkephalin_reward == 0 and rarity in TAG_POWER_BONUSES:
|
|
enkephalin_reward = TAG_POWER_BONUSES[rarity]["enkephalin_reward"]
|
|
|
|
display_tag_with_effects(tag, prob, rarity, is_new=True, enkephalin=enkephalin_reward)
|
|
|
|
|
|
if existing_tags:
|
|
st.markdown("---")
|
|
|
|
|
|
if existing_tags:
|
|
if new_tags:
|
|
st.markdown("### Previously Discovered")
|
|
for tag, prob, rarity, _ in existing_tags:
|
|
display_tag_with_effects(tag, prob, rarity, is_new=False)
|
|
else:
|
|
st.warning("No tags found above the current threshold.")
|
|
|
|
def display_tag_with_effects(tag, prob, rarity, is_new=False, enkephalin=0):
|
|
"""
|
|
Display a tag with special effects based on rarity, including animations for the rarest tags,
|
|
sample count, and both TagCoins and Enkephalin rewards for new discoveries.
|
|
"""
|
|
if rarity is None:
|
|
rarity = "Unknown"
|
|
|
|
|
|
color = RARITY_LEVELS.get(rarity, {}).get("color", "#AAAAAA")
|
|
|
|
|
|
coin_value = RARITY_LEVELS.get(rarity, {}).get("value", 0)
|
|
|
|
|
|
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"]
|
|
|
|
|
|
sample_display = ""
|
|
if sample_count is not None:
|
|
if sample_count >= 1000000:
|
|
sample_display = f"({sample_count/1000000:.1f}M samples)"
|
|
elif sample_count >= 1000:
|
|
sample_display = f"({sample_count/1000:.1f}K samples)"
|
|
else:
|
|
sample_display = f"({sample_count} samples)"
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes glowing {
|
|
0% { box-shadow: 0 0 5px #FFD700; }
|
|
50% { box-shadow: 0 0 20px #FFD700; }
|
|
100% { box-shadow: 0 0 5px #FFD700; }
|
|
}
|
|
|
|
@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 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 pulse-border {
|
|
0% { border-color: #FF9800; }
|
|
50% { border-color: #FF5722; }
|
|
100% { border-color: #FF9800; }
|
|
}
|
|
|
|
@keyframes enkephalin-glow {
|
|
0% { text-shadow: 0 0 2px cyan; }
|
|
50% { text-shadow: 0 0 5px cyan; }
|
|
100% { text-shadow: 0 0 2px cyan; }
|
|
}
|
|
|
|
@keyframes coin-glow {
|
|
0% { text-shadow: 0 0 2px gold; }
|
|
50% { text-shadow: 0 0 5px gold; }
|
|
100% { text-shadow: 0 0 2px gold; }
|
|
}
|
|
|
|
.star-of-city {
|
|
background-color: rgba(255, 215, 0, 0.2);
|
|
padding: 8px;
|
|
border-radius: 5px;
|
|
border: 2px solid gold;
|
|
animation: glowing 2s infinite;
|
|
}
|
|
|
|
.impuritas-civitas {
|
|
background-color: rgba(0, 0, 0, 0.1);
|
|
padding: 10px;
|
|
border-radius: 5px;
|
|
border: 3px solid red;
|
|
animation: rainbow-border 4s linear infinite;
|
|
}
|
|
|
|
.impuritas-text {
|
|
font-weight: bold;
|
|
animation: rainbow-text 4s linear infinite;
|
|
}
|
|
|
|
.urban-nightmare {
|
|
background-color: rgba(255, 152, 0, 0.1);
|
|
padding: 6px;
|
|
border-radius: 5px;
|
|
border: 2px solid #FF9800;
|
|
animation: pulse-border 3s infinite;
|
|
}
|
|
|
|
.urban-plague {
|
|
background-color: rgba(156, 39, 176, 0.08);
|
|
padding: 5px;
|
|
border-radius: 5px;
|
|
border: 1px solid #9C27B0;
|
|
box-shadow: 0 0 3px #9C27B0;
|
|
}
|
|
|
|
.sample-count {
|
|
font-size: 0.8em;
|
|
color: #666;
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.enkephalin-reward {
|
|
font-size: 0.9em;
|
|
color: #00BCD4;
|
|
margin-left: 5px;
|
|
animation: enkephalin-glow 2s infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.coin-reward {
|
|
font-size: 0.9em;
|
|
color: #FFD700;
|
|
margin-left: 5px;
|
|
animation: coin-glow 2s infinite;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.rewards-container {
|
|
display: inline-block;
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
border-radius: 4px;
|
|
padding: 2px 6px;
|
|
margin-left: 5px;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
rare_rarities = {
|
|
"Urban Legend": {"style": "background-color:rgba(33, 150, 243, 0.1); padding:5px; border-radius:5px;", "emoji": "🔮"},
|
|
"Urban Plague": {"style": "class='urban-plague'", "emoji": "⚔️"},
|
|
"Urban Nightmare": {"style": "class='urban-nightmare'", "emoji": "👑"},
|
|
"Star of the City": {"style": "class='star-of-city'", "emoji": "🌟"},
|
|
"Impuritas Civitas": {"style": "class='impuritas-civitas'", "emoji": "✨"}
|
|
}
|
|
|
|
|
|
is_rare = rarity in rare_rarities
|
|
style = ""
|
|
emoji_prefix = ""
|
|
|
|
if is_rare:
|
|
style = rare_rarities[rarity]["style"]
|
|
emoji_prefix = rare_rarities[rarity]["emoji"] + " "
|
|
|
|
|
|
rewards_display = ""
|
|
if is_new:
|
|
coin_display = f"<span class='coin-reward'>+{coin_value} {TAG_CURRENCY_NAME}</span>"
|
|
enkephalin_display = ""
|
|
if enkephalin > 0:
|
|
enkephalin_display = f"<span class='enkephalin-reward'>+{enkephalin} {ENKEPHALIN_ICON}</span>"
|
|
|
|
if enkephalin > 0:
|
|
rewards_display = f"<span class='rewards-container'>{coin_display} {enkephalin_display}</span>"
|
|
else:
|
|
rewards_display = f"<span class='rewards-container'>{coin_display}</span>"
|
|
|
|
|
|
if is_new:
|
|
if rarity == "Impuritas Civitas":
|
|
tag_html = f"""
|
|
<div {style}>
|
|
{emoji_prefix}<span class="impuritas-text">{tag}</span> -
|
|
<span style="color:{color};font-weight:bold;">{rarity}</span> ({prob:.2f})
|
|
<span style="background-color:#4CAF50;color:white;padding:2px 6px;border-radius:10px;font-size:0.8em;">NEW</span>
|
|
{rewards_display}
|
|
<span class="sample-count">{sample_display}</span>
|
|
</div>
|
|
"""
|
|
else:
|
|
tag_html = f"""
|
|
<div {style}>
|
|
{emoji_prefix}<strong>{tag}</strong> -
|
|
<span style="color:{color};font-weight:bold;">{rarity}</span> ({prob:.2f})
|
|
<span style="background-color:#4CAF50;color:white;padding:2px 6px;border-radius:10px;font-size:0.8em;">NEW</span>
|
|
{rewards_display}
|
|
<span class="sample-count">{sample_display}</span>
|
|
</div>
|
|
"""
|
|
else:
|
|
if rarity == "Impuritas Civitas":
|
|
tag_html = f"""
|
|
<div {style}>
|
|
{emoji_prefix}<span class="impuritas-text">{tag}</span> -
|
|
<span style="color:{color};font-weight:bold;">{rarity}</span> ({prob:.2f})
|
|
<span class="sample-count">{sample_display}</span>
|
|
</div>
|
|
"""
|
|
else:
|
|
tag_html = f"""
|
|
<div {style}>
|
|
{emoji_prefix}{tag} -
|
|
<span style="color:{color};font-weight:bold;">{rarity}</span> ({prob:.2f})
|
|
<span class="sample-count">{sample_display}</span>
|
|
</div>
|
|
"""
|
|
|
|
|
|
if is_new and rarity in ["Star of the City", "Impuritas Civitas"]:
|
|
|
|
|
|
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
st.markdown(f"""
|
|
<div style="border:3px solid red; padding:10px; margin:10px 0; text-align:center; background-color:rgba(0,0,0,0.1); animation:rainbow-border 4s linear infinite;">
|
|
<h3>🎉 EXTRAORDINARY DISCOVERY! 🎉</h3>
|
|
<p>You found an ultra-rare Impuritas Civitas tag!</p>
|
|
<p>Rewards: +{RARITY_LEVELS[rarity]["value"]} {TAG_CURRENCY_NAME} and +25 {ENKEPHALIN_ICON}</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
elif rarity == "Star of the City":
|
|
st.markdown(f"""
|
|
<div style="border:2px solid gold; padding:10px; margin:10px 0; text-align:center; background-color:rgba(255,215,0,0.2); animation:glowing 2s infinite;">
|
|
<h3>🌟 EXCEPTIONAL DISCOVERY! 🌟</h3>
|
|
<p>You found a very rare Star of the City tag!</p>
|
|
<p>Rewards: +{RARITY_LEVELS[rarity]["value"]} {TAG_CURRENCY_NAME} and +10 {ENKEPHALIN_ICON}</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
st.markdown(tag_html, unsafe_allow_html=True)
|
|
|
|
def display_scanner_settings():
|
|
"""Display scanner settings and help"""
|
|
st.write("### Scanner Settings")
|
|
|
|
|
|
base_threshold = st.session_state.threshold
|
|
tag_power = st.session_state.tag_power_bonus if hasattr(st.session_state, 'tag_power_bonus') else 0
|
|
effective_threshold = max(MIN_THRESHOLD, base_threshold - tag_power)
|
|
|
|
|
|
if tag_power > 0:
|
|
st.write(f"Base Threshold: **{base_threshold:.3f}**")
|
|
st.write(f"Tag Power Bonus: **-{tag_power:.4f}**")
|
|
st.write(f"Effective Threshold: **{effective_threshold:.3f}**")
|
|
else:
|
|
st.write(f"Current Threshold: **{base_threshold:.3f}**")
|
|
|
|
if hasattr(st.session_state, 'coin_multiplier') and st.session_state.coin_multiplier > 1.0:
|
|
st.info(f"Your Tag Power gives a coin multiplier of {st.session_state.coin_multiplier:.2f}x for new discoveries!")
|
|
|
|
|
|
st.markdown(
|
|
"""
|
|
### How to Play
|
|
1. Upload an image
|
|
2. Scan for tags to discover them
|
|
3. Earn TagCoins for new discoveries
|
|
4. Spend TagCoins on upgrades to lower the threshold
|
|
5. Lower thresholds reveal rarer tags!
|
|
6. Collect sets of related tags for bonuses and reveal unique mosaics!
|
|
7. Visit the Library System to discover unique tags (not collect)
|
|
8. Use collected tags to either inspire new searches or generate essence
|
|
9. Use Enkephalin to generate Tag Essences
|
|
10. Use the Tag Essence Generator to collect the tag and related tags to it.
|
|
"""
|
|
)
|
|
|
|
def display_game_stats_panel():
|
|
"""Display a prominent game stats panel at the top of the main content area"""
|
|
|
|
tag_currency = st.session_state.tag_currency
|
|
enkephalin = st.session_state.enkephalin
|
|
images_processed = st.session_state.game_stats.get('images_processed', 0)
|
|
total_tags_found = st.session_state.game_stats.get('total_tags_found', 0)
|
|
total_currency_earned = st.session_state.game_stats.get('total_currency_earned', 0)
|
|
unique_tags = len(st.session_state.collected_tags) if hasattr(st.session_state, 'collected_tags') else 0
|
|
|
|
|
|
base_threshold = st.session_state.threshold
|
|
tag_power = st.session_state.tag_power_bonus if hasattr(st.session_state, 'tag_power_bonus') else 0
|
|
effective_threshold = max(MIN_THRESHOLD, base_threshold - tag_power)
|
|
|
|
|
|
st.markdown('<div class="stats-panel">', unsafe_allow_html=True)
|
|
|
|
|
|
cols = st.columns([1, 1, 1, 1])
|
|
|
|
with cols[0]:
|
|
st.markdown(f'<div class="stats-value">{tag_currency}</div>', unsafe_allow_html=True)
|
|
st.markdown(f'<div class="stats-label">{TAG_CURRENCY_NAME}</div>', unsafe_allow_html=True)
|
|
|
|
with cols[1]:
|
|
st.markdown(f'<div class="stats-value">{enkephalin} {ENKEPHALIN_ICON}</div>', unsafe_allow_html=True)
|
|
st.markdown(f'<div class="stats-label">{ENKEPHALIN_CURRENCY_NAME}</div>', unsafe_allow_html=True)
|
|
|
|
with cols[2]:
|
|
if tag_power > 0:
|
|
st.markdown(f'<div class="stats-value">{effective_threshold:.3f}</div>', unsafe_allow_html=True)
|
|
st.markdown(f'<div class="stats-label">Effective Threshold (Base: {base_threshold:.3f})</div>', unsafe_allow_html=True)
|
|
else:
|
|
st.markdown(f'<div class="stats-value">{base_threshold:.3f}</div>', unsafe_allow_html=True)
|
|
st.markdown(f'<div class="stats-label">Threshold</div>', unsafe_allow_html=True)
|
|
|
|
with cols[3]:
|
|
st.markdown(f'<div class="stats-value">{unique_tags}</div>', unsafe_allow_html=True)
|
|
st.markdown(f'<div class="stats-label">Unique Tags</div>', unsafe_allow_html=True)
|
|
|
|
|
|
with st.expander("More Game Stats"):
|
|
cols2 = st.columns(3)
|
|
|
|
with cols2[0]:
|
|
st.markdown("### Collection Stats")
|
|
st.write(f"Images Processed: **{images_processed}**")
|
|
st.write(f"Total Tags Found: **{total_tags_found}**")
|
|
st.write(f"Unique Tags: **{unique_tags}**")
|
|
|
|
with cols2[1]:
|
|
st.markdown("### Economy Stats")
|
|
st.write(f"Total Earned: **{total_currency_earned}** {TAG_CURRENCY_NAME}")
|
|
if hasattr(st.session_state.game_stats, 'currency_spent'):
|
|
st.write(f"Currency Spent: **{st.session_state.game_stats.get('currency_spent', 0)}** {TAG_CURRENCY_NAME}")
|
|
if hasattr(st.session_state.game_stats, 'enkephalin_generated'):
|
|
st.write(f"Enkephalin Generated: **{st.session_state.game_stats.get('enkephalin_generated', 0)}**")
|
|
|
|
with cols2[2]:
|
|
st.markdown("### Bonuses")
|
|
if hasattr(st.session_state, 'tag_power_bonus') and st.session_state.tag_power_bonus > 0:
|
|
st.write(f"Tag Power Bonus: **-{st.session_state.tag_power_bonus:.4f}**")
|
|
if hasattr(st.session_state, 'coin_multiplier') and st.session_state.coin_multiplier > 1.0:
|
|
st.write(f"Coin Multiplier: **{st.session_state.coin_multiplier:.2f}x**")
|
|
if hasattr(st.session_state, 'unlocked_combinations'):
|
|
st.write(f"Unlocked Combinations: **{len(st.session_state.unlocked_combinations)}**")
|
|
|
|
|
|
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
def display_tag_collection():
|
|
"""Display the user's tag collection with stats and analysis."""
|
|
st.subheader("Your Tag Collection")
|
|
|
|
|
|
list_tab, stats_tab, analysis_tab, mosaic_tab = st.tabs([
|
|
"Tag List",
|
|
"Collection Stats",
|
|
"Category Analysis",
|
|
"Collection Mosaic"
|
|
])
|
|
|
|
with list_tab:
|
|
display_tag_list()
|
|
|
|
with stats_tab:
|
|
display_collection_stats()
|
|
|
|
with analysis_tab:
|
|
display_category_stats()
|
|
|
|
with mosaic_tab:
|
|
|
|
display_tag_mosaic()
|
|
|
|
def display_tag_list():
|
|
"""Display a simple list view of tags with improved sorting options"""
|
|
|
|
unique_tags = len(st.session_state.collected_tags)
|
|
st.write(f"You have collected {unique_tags} unique tags.")
|
|
|
|
|
|
rarity_counts = get_rarity_counts()
|
|
|
|
|
|
active_rarities = {r: c for r, c in rarity_counts.items() if c > 0}
|
|
|
|
|
|
if active_rarities:
|
|
display_rarity_distribution(active_rarities)
|
|
|
|
|
|
sort_options = ["Category (rarest first)", "Rarity"]
|
|
selected_sort = st.selectbox("Sort tags by:", sort_options)
|
|
|
|
|
|
if selected_sort == "Category (rarest first)":
|
|
categories = group_tags_by_category()
|
|
|
|
|
|
for category, tags in sorted(categories.items()):
|
|
|
|
rarity_order = list(RARITY_LEVELS.keys())
|
|
|
|
|
|
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)
|
|
|
|
|
|
has_rare_tags = any(info.get("rarity") in ["Impuritas Civitas", "Star of the City"]
|
|
for _, info in sorted_tags)
|
|
|
|
|
|
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>"
|
|
|
|
|
|
header = f"{category_display} ({len(tags)} tags)"
|
|
if has_rare_tags:
|
|
header += " ✨ Contains rare tags!"
|
|
|
|
|
|
st.markdown(header, unsafe_allow_html=True)
|
|
with st.expander("Show/Hide"):
|
|
|
|
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))
|
|
|
|
|
|
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"]
|
|
|
|
|
|
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_tag_grid(tags_in_rarity)
|
|
st.markdown("---")
|
|
|
|
elif selected_sort == "Rarity":
|
|
|
|
rarity_groups = {}
|
|
for tag, info in st.session_state.collected_tags.items():
|
|
rarity = info.get("rarity", "Unknown")
|
|
if rarity not in rarity_groups:
|
|
rarity_groups[rarity] = []
|
|
rarity_groups[rarity].append((tag, info))
|
|
|
|
|
|
ordered_rarities = list(RARITY_LEVELS.keys())
|
|
ordered_rarities.reverse()
|
|
|
|
|
|
for rarity in ordered_rarities:
|
|
if rarity in rarity_groups:
|
|
tags = rarity_groups[rarity]
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
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>"
|
|
|
|
|
|
st.markdown(f"### {rarity_html} ({len(tags)} tags)", unsafe_allow_html=True)
|
|
with st.expander("Show/Hide"):
|
|
|
|
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))
|
|
|
|
|
|
for category, category_tags in sorted(category_groups.items()):
|
|
|
|
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_tag_grid(category_tags)
|
|
st.markdown("---")
|
|
|
|
def get_rarity_counts():
|
|
"""Count tags by rarity levels"""
|
|
rarity_counts = {
|
|
"Canard": 0,
|
|
"Urban Myth": 0,
|
|
"Urban Legend": 0,
|
|
"Urban Plague": 0,
|
|
"Urban Nightmare": 0,
|
|
"Star of the City": 0,
|
|
"Impuritas Civitas": 0,
|
|
"Unknown": 0
|
|
}
|
|
|
|
for tag_info in st.session_state.collected_tags.values():
|
|
rarity = tag_info.get("rarity")
|
|
if rarity is None:
|
|
rarity = "Unknown"
|
|
|
|
if rarity in rarity_counts:
|
|
rarity_counts[rarity] += 1
|
|
else:
|
|
|
|
rarity_counts["Unknown"] += 1
|
|
|
|
return rarity_counts
|
|
|
|
def display_rarity_distribution(active_rarities):
|
|
"""Display distribution of tags by rarity with themed animations for rare tags"""
|
|
|
|
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]:
|
|
|
|
color = RARITY_LEVELS.get(rarity, {}).get("color", "#888888")
|
|
|
|
|
|
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 group_tags_by_category():
|
|
"""Group tags by their categories"""
|
|
categories = {}
|
|
for tag, info in st.session_state.collected_tags.items():
|
|
category = info.get("category", "unknown")
|
|
if category not in categories:
|
|
categories[category] = []
|
|
categories[category].append((tag, info))
|
|
return categories
|
|
|
|
def display_tag_grid(tags):
|
|
"""Display tags in a grid layout with sample count information"""
|
|
|
|
cols = st.columns(3)
|
|
for i, (tag, info) in enumerate(sorted(tags)):
|
|
col_idx = i % 3
|
|
with cols[col_idx]:
|
|
rarity = info.get("rarity")
|
|
if rarity is None:
|
|
rarity = "Unknown"
|
|
|
|
count = info.get("count", 1)
|
|
color = RARITY_LEVELS.get(rarity, {}).get("color", "#888888")
|
|
|
|
|
|
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"]
|
|
|
|
|
|
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>"
|
|
|
|
|
|
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>"
|
|
|
|
|
|
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> (×{count}) {sample_display}",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
def display_collection_stats():
|
|
"""Display overall collection statistics with themed animations"""
|
|
st.subheader("Collection Overview")
|
|
|
|
|
|
if not hasattr(st.session_state, 'collected_tags') or not st.session_state.collected_tags:
|
|
st.info("Start scanning images to collect tags!")
|
|
return
|
|
|
|
|
|
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; }
|
|
}
|
|
|
|
@keyframes gradient-shift {
|
|
0% { background-position: 0% 50%; }
|
|
50% { background-position: 100% 50%; }
|
|
100% { background-position: 0% 50%; }
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* Custom styles for collection metrics */
|
|
.metric-container {
|
|
background-color: #f8f9fa;
|
|
border-radius: 5px;
|
|
padding: 10px;
|
|
text-align: center;
|
|
border: 1px solid #dee2e6;
|
|
}
|
|
|
|
.metric-value {
|
|
font-size: 24px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.metric-label {
|
|
font-size: 14px;
|
|
color: #6c757d;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
display_rarity_breakdown()
|
|
|
|
|
|
display_common_tags()
|
|
|
|
|
|
display_recent_discoveries()
|
|
|
|
def display_rarity_breakdown():
|
|
"""Display rarity breakdown with progress bar visualization showing collected vs total tags"""
|
|
st.subheader("Rarity Collection Progress")
|
|
|
|
|
|
collected_counts = {}
|
|
for tag_info in st.session_state.collected_tags.values():
|
|
rarity = tag_info["rarity"]
|
|
if rarity not in collected_counts:
|
|
collected_counts[rarity] = 0
|
|
collected_counts[rarity] += 1
|
|
|
|
|
|
total_counts = {}
|
|
if hasattr(st.session_state, 'tag_rarity_metadata') and st.session_state.tag_rarity_metadata:
|
|
for tag, info in st.session_state.tag_rarity_metadata.items():
|
|
if isinstance(info, dict) and "rarity" in info:
|
|
rarity = info["rarity"]
|
|
if rarity not in total_counts:
|
|
total_counts[rarity] = 0
|
|
total_counts[rarity] += 1
|
|
|
|
|
|
ordered_rarities = list(RARITY_LEVELS.keys())
|
|
|
|
|
|
for rarity in ordered_rarities:
|
|
if rarity in collected_counts:
|
|
collected = collected_counts[rarity]
|
|
total = total_counts.get(rarity, 0)
|
|
|
|
|
|
if total <= 0:
|
|
total = collected
|
|
|
|
|
|
percentage = (collected / total) * 100 if total > 0 else 0
|
|
|
|
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
rarity_span = f"<span style='color:{color};font-weight:bold;'>{rarity}</span>"
|
|
bar_style = f"background-color: {color};"
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
rarity_span = f"<span class='grid-impuritas' style='font-weight:bold;'>{rarity}</span>"
|
|
bar_style = "background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); background-size: 400% 400%; animation: gradient-shift 4s linear infinite;"
|
|
elif rarity == "Star of the City":
|
|
rarity_span = f"<span class='grid-star' style='color:{color};font-weight:bold;'>{rarity}</span>"
|
|
bar_style = f"background-color: {color}; box-shadow: 0 0 5px #FFD700;"
|
|
elif rarity == "Urban Nightmare":
|
|
rarity_span = f"<span class='grid-nightmare' style='color:{color};font-weight:bold;'>{rarity}</span>"
|
|
bar_style = f"background-color: {color}; animation: grid-pulse 3s infinite;"
|
|
elif rarity == "Urban Plague":
|
|
rarity_span = f"<span class='grid-plague' style='color:{color};font-weight:bold;'>{rarity}</span>"
|
|
|
|
|
|
st.markdown(
|
|
f"""
|
|
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
<div style="width: 150px;">{rarity_span}</div>
|
|
<div style="flex-grow: 1; background-color: #f0f0f0; border-radius: 5px; height: 20px;">
|
|
<div style="width: {percentage}%; {bar_style} height: 20px; border-radius: 5px;"></div>
|
|
</div>
|
|
<div style="width: 120px; text-align: right;">{collected}/{total} ({percentage:.1f}%)</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes gradient-shift {
|
|
0% { background-position: 0% 50%; }
|
|
50% { background-position: 100% 50%; }
|
|
100% { background-position: 0% 50%; }
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
def display_common_tags():
|
|
"""Display the most common tags with themed animations"""
|
|
st.subheader("Most Common Tags")
|
|
|
|
|
|
sorted_tags = sorted(
|
|
st.session_state.collected_tags.items(),
|
|
key=lambda x: x[1]["count"],
|
|
reverse=True
|
|
)
|
|
|
|
|
|
for i, (tag, info) in enumerate(sorted_tags[:10]):
|
|
rarity = info["rarity"]
|
|
count = info["count"]
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
rarity_span = f"<span style='color:{color};'>{rarity}</span>"
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
rarity_span = f"<span class='grid-impuritas'>{rarity}</span>"
|
|
elif rarity == "Star of the City":
|
|
rarity_span = f"<span class='grid-star' style='color:{color};'>{rarity}</span>"
|
|
elif rarity == "Urban Nightmare":
|
|
rarity_span = f"<span class='grid-nightmare' style='color:{color};'>{rarity}</span>"
|
|
elif rarity == "Urban Plague":
|
|
rarity_span = f"<span class='grid-plague' style='color:{color};'>{rarity}</span>"
|
|
|
|
|
|
sample_display = ""
|
|
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"]
|
|
if sample_count >= 1000000:
|
|
sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count/1000000:.1f}M samples)</span>"
|
|
elif sample_count >= 1000:
|
|
sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count/1000:.1f}K samples)</span>"
|
|
else:
|
|
sample_display = f"<span style='font-size:0.8em;color:#666;'>({sample_count} samples)</span>"
|
|
|
|
st.markdown(
|
|
f"**{i+1}.** {tag} - {rarity_span} ({count} occurrences) {sample_display}",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
def display_recent_discoveries():
|
|
"""Display recently discovered tags with themed animations"""
|
|
st.subheader("Recent Discoveries")
|
|
|
|
|
|
if hasattr(st.session_state, 'tag_history') and st.session_state.tag_history:
|
|
|
|
new_discoveries = [entry for entry in st.session_state.tag_history if entry.get("is_new", False)]
|
|
for entry in new_discoveries[-10:]:
|
|
tag = entry["tag"]
|
|
rarity = entry["rarity"]
|
|
time = entry["time"]
|
|
color = RARITY_LEVELS[rarity]["color"]
|
|
|
|
|
|
rarity_span = f"<span style='color:{color};'>{rarity}</span>"
|
|
|
|
if rarity == "Impuritas Civitas":
|
|
rarity_span = f"<span class='grid-impuritas'>{rarity}</span>"
|
|
elif rarity == "Star of the City":
|
|
rarity_span = f"<span class='grid-star' style='color:{color};'>{rarity}</span>"
|
|
elif rarity == "Urban Nightmare":
|
|
rarity_span = f"<span class='grid-nightmare' style='color:{color};'>{rarity}</span>"
|
|
elif rarity == "Urban Plague":
|
|
rarity_span = f"<span class='grid-plague' style='color:{color};'>{rarity}</span>"
|
|
|
|
st.markdown(
|
|
f"{tag} - {rarity_span} (at {time})",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
def display_category_stats():
|
|
"""Display stats and details about tag categories"""
|
|
st.subheader("Category Analysis")
|
|
|
|
|
|
if not hasattr(st.session_state, 'collected_tags') or not st.session_state.collected_tags:
|
|
st.info("Start scanning images to collect tags!")
|
|
return
|
|
|
|
|
|
category_counts = {}
|
|
for tag, info in st.session_state.collected_tags.items():
|
|
category = info.get("category", "general")
|
|
if category not in category_counts:
|
|
category_counts[category] = {"count": 0, "tags": []}
|
|
category_counts[category]["count"] += 1
|
|
category_counts[category]["tags"].append(tag)
|
|
|
|
|
|
display_category_bars(category_counts)
|
|
|
|
st.divider()
|
|
|
|
def display_category_bars(category_counts):
|
|
"""Display category distribution with bar visualization"""
|
|
total_tags = len(st.session_state.collected_tags)
|
|
st.subheader("Category Distribution")
|
|
|
|
|
|
sorted_categories = sorted(
|
|
category_counts.items(),
|
|
key=lambda x: x[1]["count"],
|
|
reverse=True
|
|
)
|
|
|
|
max_count = max(cat_info["count"] for _, cat_info in sorted_categories) if sorted_categories else 1
|
|
|
|
for category, cat_info in sorted_categories:
|
|
count = cat_info["count"]
|
|
percentage = (count / total_tags) * 100
|
|
|
|
if category in TAG_CATEGORIES:
|
|
color = TAG_CATEGORIES[category]["color"]
|
|
icon = TAG_CATEGORIES[category]["icon"]
|
|
name = TAG_CATEGORIES[category]["name"]
|
|
else:
|
|
color = "#888888"
|
|
icon = "❓"
|
|
name = category.capitalize()
|
|
|
|
|
|
st.markdown(
|
|
f"""
|
|
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
|
<div style="width: 150px;">{icon} <span style="color:{color};font-weight:bold;">{name}</span></div>
|
|
<div style="flex-grow: 1; background-color: #f0f0f0; border-radius: 5px; height: 20px;">
|
|
<div style="width: {(count/max_count)*100}%; background-color: {color}; height: 20px; border-radius: 5px;"></div>
|
|
</div>
|
|
<div style="width: 100px; text-align: right;">{count} ({percentage:.1f}%)</div>
|
|
</div>
|
|
""",
|
|
unsafe_allow_html=True
|
|
)
|
|
|
|
def display_achievements_tab():
|
|
"""Display the user's achievements in a dedicated tab"""
|
|
st.subheader("🏆 Achievements")
|
|
|
|
|
|
unlocked_count = len(st.session_state.achievements)
|
|
total_count = len(ACHIEVEMENTS)
|
|
|
|
|
|
progress_percentage = unlocked_count / total_count
|
|
st.progress(progress_percentage, text=f"Unlocked {unlocked_count} of {total_count} achievements ({int(progress_percentage * 100)}%)")
|
|
|
|
|
|
achievement_categories = group_achievements_by_category()
|
|
|
|
|
|
category_tabs = st.tabs(list(achievement_categories.keys()))
|
|
|
|
|
|
for i, (category, tab) in enumerate(zip(achievement_categories.keys(), category_tabs)):
|
|
with tab:
|
|
display_category_achievements(category, achievement_categories[category])
|
|
|
|
def group_achievements_by_category():
|
|
"""Group achievements into categories"""
|
|
achievement_categories = {
|
|
"Collection": [],
|
|
"Rarity": [],
|
|
"Progression": [],
|
|
"Special": []
|
|
}
|
|
|
|
|
|
for achievement_id, achievement in ACHIEVEMENTS.items():
|
|
if "collector" in achievement_id or "Collector" in achievement.get("name", ""):
|
|
category = "Collection"
|
|
elif any(rarity in achievement_id for rarity in ["canard", "myth", "legend", "plague", "nightmare", "star", "impuritas"]):
|
|
category = "Rarity"
|
|
elif any(term in achievement_id for term in ["milestone", "master", "threshold"]):
|
|
category = "Progression"
|
|
else:
|
|
category = "Special"
|
|
|
|
achievement_categories[category].append((achievement_id, achievement))
|
|
|
|
return achievement_categories
|
|
|
|
def display_category_achievements(category, achievements_in_category):
|
|
"""Display achievements for a specific category"""
|
|
|
|
if not achievements_in_category:
|
|
st.info(f"No {category} achievements available yet.")
|
|
return
|
|
|
|
|
|
cols_per_row = 3
|
|
for j in range(0, len(achievements_in_category), cols_per_row):
|
|
cols = st.columns(cols_per_row)
|
|
|
|
for k in range(cols_per_row):
|
|
idx = j + k
|
|
if idx < len(achievements_in_category):
|
|
achievement_id, achievement = achievements_in_category[idx]
|
|
with cols[k]:
|
|
display_achievement_card(achievement_id, achievement)
|
|
|
|
def display_achievement_card(achievement_id, achievement):
|
|
"""Display a single achievement card"""
|
|
|
|
is_unlocked = achievement_id in st.session_state.achievements
|
|
|
|
|
|
if is_unlocked:
|
|
card_style = "border:2px solid #4CAF50; border-radius:10px; padding:10px; margin:5px; background-color:rgba(76, 175, 80, 0.1);"
|
|
icon = "✅"
|
|
else:
|
|
card_style = "border:2px solid #cccccc; border-radius:10px; padding:10px; margin:5px; background-color:rgba(0, 0, 0, 0.05);"
|
|
icon = "🔒"
|
|
|
|
|
|
st.markdown(f"""
|
|
<div style="{card_style}">
|
|
<p style="font-size:18px; margin:0;">{icon} <b>{achievement.get('name', 'Unknown')}</b></p>
|
|
<p style="font-size:14px; margin:5px 0;">{achievement.get('description', '')}</p>
|
|
{f"<p style='font-size:12px; color:#666;'>Requires: {achievement.get('requirement', '')}</p>" if 'requirement' in achievement else ""}
|
|
{f"<p style='font-size:12px; color:#4CAF50;'>Reward: {', '.join([f'{k}: {v}' for k, v in achievement.get('reward', {}).items()])}</p>" if 'reward' in achievement and is_unlocked else ""}
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
def check_achievements(session_state):
|
|
"""
|
|
Check for achievement completion based on the current game state.
|
|
Adds newly completed achievements to the session_state.achievements set
|
|
and applies their rewards.
|
|
|
|
Args:
|
|
session_state: Streamlit session state containing game data
|
|
|
|
Returns:
|
|
list: List of newly unlocked achievement names (empty if none)
|
|
"""
|
|
from game_constants import ACHIEVEMENTS, RARITY_LEVELS
|
|
|
|
|
|
if not hasattr(session_state, 'achievements'):
|
|
session_state.achievements = set()
|
|
|
|
newly_unlocked = []
|
|
|
|
|
|
def apply_reward(achievement_id):
|
|
reward = ACHIEVEMENTS[achievement_id].get("reward", {})
|
|
|
|
|
|
if "tagcoins" in reward:
|
|
session_state.tag_currency += reward["tagcoins"]
|
|
|
|
|
|
if "coin_bonus" in reward:
|
|
if not hasattr(session_state, 'achievement_coin_bonus'):
|
|
session_state.achievement_coin_bonus = 0
|
|
session_state.achievement_coin_bonus += reward["coin_bonus"]
|
|
|
|
|
|
if "enkephalin" in reward:
|
|
session_state.enkephalin += reward["enkephalin"]
|
|
|
|
|
|
if "essence_cost_reduction" in reward:
|
|
if not hasattr(session_state, 'essence_cost_reduction'):
|
|
session_state.essence_cost_reduction = 0
|
|
session_state.essence_cost_reduction += reward["essence_cost_reduction"]
|
|
|
|
|
|
if "library_cost_reduction" in reward:
|
|
if not hasattr(session_state, 'library_cost_reduction'):
|
|
session_state.library_cost_reduction = 0
|
|
session_state.library_cost_reduction += reward["library_cost_reduction"]
|
|
|
|
|
|
if "enkephalin_bonus" in reward:
|
|
if not hasattr(session_state, 'enkephalin_bonus'):
|
|
session_state.enkephalin_bonus = 0
|
|
session_state.enkephalin_bonus += reward["enkephalin_bonus"]
|
|
|
|
|
|
tag_count = len(session_state.collected_tags) if hasattr(session_state, 'collected_tags') else 0
|
|
|
|
for achievement_id, achievement in ACHIEVEMENTS.items():
|
|
|
|
if achievement_id in session_state.achievements:
|
|
continue
|
|
|
|
|
|
if achievement_id.startswith("tag_collector_") or achievement_id.startswith("collection_milestone_") or achievement_id == "tag_master":
|
|
requirement = achievement.get("requirement", 0)
|
|
if tag_count >= requirement:
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id.endswith("_collector") or achievement_id == "legendary_hunter" or achievement_id == "multi_legendary":
|
|
|
|
rarity_counts = {}
|
|
for tag_info in session_state.collected_tags.values():
|
|
rarity = tag_info.get("rarity", "Unknown")
|
|
if rarity not in rarity_counts:
|
|
rarity_counts[rarity] = 0
|
|
rarity_counts[rarity] += 1
|
|
|
|
|
|
if achievement_id == "canard_collector" and rarity_counts.get("Canard", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "urban_myth_collector" and rarity_counts.get("Urban Myth", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "urban_legend_collector" and rarity_counts.get("Urban Legend", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "urban_plague_collector" and rarity_counts.get("Urban Plague", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "urban_nightmare_collector" and rarity_counts.get("Urban Nightmare", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "star_collector" and rarity_counts.get("Star of the City", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "impuritas_collector" and rarity_counts.get("Impuritas Civitas", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "legendary_hunter" and rarity_counts.get("Impuritas Civitas", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "multi_legendary" and rarity_counts.get("Impuritas Civitas", 0) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "perfect_scanner":
|
|
effective_threshold = session_state.threshold
|
|
if hasattr(session_state, 'tag_power_bonus'):
|
|
effective_threshold -= session_state.tag_power_bonus
|
|
if effective_threshold <= 0.1:
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
elif achievement_id == "optimal_threshold":
|
|
if abs(session_state.threshold - 0.32857141) < 0.001:
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "essence_creator" or achievement_id == "essence_master":
|
|
essence_count = session_state.game_stats.get("essences_generated", 0)
|
|
if essence_count >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "tag_explorer":
|
|
explored_tiers = session_state.explored_library_tiers if hasattr(session_state, 'explored_library_tiers') else set()
|
|
if len(explored_tiers) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "enkephalin_master" or achievement_id == "enkephalin_harvester":
|
|
enkephalin_generated = session_state.game_stats.get("enkephalin_generated", 0)
|
|
if enkephalin_generated >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "sacrifice_devotee":
|
|
tags_sacrificed = session_state.game_stats.get("tags_sacrificed", 0)
|
|
if tags_sacrificed >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "category_explorer":
|
|
|
|
categories = set()
|
|
for tag_info in session_state.collected_tags.values():
|
|
category = tag_info.get("category", "unknown")
|
|
categories.add(category)
|
|
|
|
if len(categories) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "series_collector":
|
|
completed_series = session_state.completed_series if hasattr(session_state, 'completed_series') else set()
|
|
if len(completed_series) >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "rapid_tagger":
|
|
images_processed = session_state.game_stats.get("images_processed", 0)
|
|
if images_processed >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "library_scholar":
|
|
extracted_tags = session_state.tags_extracted if hasattr(session_state, 'tags_extracted') else 0
|
|
if extracted_tags >= achievement.get("requirement", 0):
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "rarity_hunter":
|
|
|
|
rarity_counts = {}
|
|
for tag_info in session_state.collected_tags.values():
|
|
rarity = tag_info.get("rarity", "Unknown")
|
|
if rarity not in rarity_counts:
|
|
rarity_counts[rarity] = 0
|
|
rarity_counts[rarity] += 1
|
|
|
|
|
|
has_all_rarities = all(rarity in rarity_counts and rarity_counts[rarity] > 0
|
|
for rarity in RARITY_LEVELS.keys())
|
|
|
|
if has_all_rarities:
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
|
|
elif achievement_id == "legendary_librarian":
|
|
extracted_legendary = session_state.extracted_impuritas if hasattr(session_state, 'extracted_impuritas') else False
|
|
if extracted_legendary:
|
|
session_state.achievements.add(achievement_id)
|
|
apply_reward(achievement_id)
|
|
newly_unlocked.append(achievement["name"])
|
|
|
|
return newly_unlocked
|
|
|
|
|
|
def display_achievement_notifications(newly_unlocked):
|
|
"""
|
|
Display notifications for newly unlocked achievements.
|
|
|
|
Args:
|
|
newly_unlocked: List of newly unlocked achievement names
|
|
"""
|
|
import streamlit as st
|
|
|
|
if not newly_unlocked:
|
|
return
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes slide-in {
|
|
0% { transform: translateX(100%); opacity: 0; }
|
|
10% { transform: translateX(0); opacity: 1; }
|
|
90% { transform: translateX(0); opacity: 1; }
|
|
100% { transform: translateX(100%); opacity: 0; }
|
|
}
|
|
|
|
.achievement-notification {
|
|
position: fixed;
|
|
top: 70px;
|
|
right: 20px;
|
|
background-color: #4CAF50;
|
|
color: white;
|
|
padding: 15px;
|
|
border-radius: 5px;
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
|
|
z-index: 9999;
|
|
animation: slide-in 5s ease-in-out;
|
|
animation-fill-mode: forwards;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
for i, achievement_name in enumerate(newly_unlocked):
|
|
delay = i * 0.5
|
|
notification_html = f"""
|
|
<div class="achievement-notification" style="animation-delay: {delay}s; top: {70 + i*70}px;">
|
|
<div>🏆 Achievement Unlocked!</div>
|
|
<div><strong>{achievement_name}</strong></div>
|
|
</div>
|
|
"""
|
|
st.markdown(notification_html, unsafe_allow_html=True)
|
|
|
|
|
|
def update_tag_power_bonuses():
|
|
"""
|
|
Update tag power bonuses based on the player's collection.
|
|
This affects threshold reduction and coin multiplier.
|
|
"""
|
|
import streamlit as st
|
|
from game_constants import TAG_POWER_BONUSES, RARITY_LEVELS
|
|
|
|
|
|
if not hasattr(st.session_state, 'tag_power_bonus'):
|
|
st.session_state.tag_power_bonus = 0
|
|
|
|
if not hasattr(st.session_state, 'coin_multiplier'):
|
|
st.session_state.coin_multiplier = 1.0
|
|
|
|
|
|
st.session_state.tag_power_bonus = 0
|
|
coin_multiplier_bonus = 0
|
|
|
|
|
|
if hasattr(st.session_state, 'collected_tags'):
|
|
for tag, info in st.session_state.collected_tags.items():
|
|
rarity = info.get("rarity")
|
|
if rarity in TAG_POWER_BONUSES:
|
|
bonus = TAG_POWER_BONUSES[rarity]
|
|
|
|
|
|
coin_multiplier_bonus += bonus["coin_multiplier"]
|
|
|
|
|
|
st.session_state.coin_multiplier = 1.0 + coin_multiplier_bonus
|
|
|
|
|
|
if hasattr(st.session_state, 'achievement_coin_bonus'):
|
|
st.session_state.coin_multiplier += st.session_state.achievement_coin_bonus
|
|
|
|
return st.session_state.tag_power_bonus, st.session_state.coin_multiplier
|
|
|
|
def display_upgrade_shop():
|
|
"""Display the upgrade shop with preset threshold levels and visual enhancements"""
|
|
|
|
st.markdown("""
|
|
<style>
|
|
.upgrade-shop-header {
|
|
background: linear-gradient(90deg, #0d6efd, #6610f2);
|
|
color: white;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.currency-display {
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
border-radius: 8px;
|
|
padding: 8px 15px;
|
|
margin-top: 10px;
|
|
font-size: 1.2em;
|
|
}
|
|
</style>
|
|
|
|
<div class="upgrade-shop-header">
|
|
<h2>🧪 Neural Scanner Upgrade Lab 🧪</h2>
|
|
<div>Enhance your scanner's capabilities to discover rarer tags!</div>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
purchased = set(st.session_state.purchased_upgrades) if hasattr(st.session_state, 'purchased_upgrades') else set()
|
|
|
|
|
|
base_threshold = st.session_state.threshold
|
|
tag_power_bonus = st.session_state.tag_power_bonus if hasattr(st.session_state, 'tag_power_bonus') else 0
|
|
effective_threshold = max(MIN_THRESHOLD, base_threshold - tag_power_bonus)
|
|
|
|
st.divider()
|
|
|
|
|
|
display_available_upgrades(base_threshold, tag_power_bonus, purchased)
|
|
|
|
def display_available_upgrades(base_threshold, tag_power_bonus, purchased):
|
|
"""Display available upgrade options with progressive unlocking and visual effects"""
|
|
|
|
st.markdown("""
|
|
<style>
|
|
@keyframes glow-effect {
|
|
0% { box-shadow: 0 0 5px rgba(0,123,255,0.5); }
|
|
50% { box-shadow: 0 0 15px rgba(0,123,255,0.8); }
|
|
100% { box-shadow: 0 0 5px rgba(0,123,255,0.5); }
|
|
}
|
|
|
|
.upgrade-card {
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 8px;
|
|
padding: 15px;
|
|
margin-bottom: 15px;
|
|
background-color: #f8f9fa;
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.upgrade-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
/* Rarity-based animations - matching the tag rarities */
|
|
@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 20px #FFD700; }
|
|
100% { box-shadow: 0 0 5px #FFD700; }
|
|
}
|
|
|
|
@keyframes nightmare-pulse {
|
|
0% { border-color: #FF9800; }
|
|
50% { border-color: #FF5722; }
|
|
100% { border-color: #FF9800; }
|
|
}
|
|
|
|
/* Upgrade card variants */
|
|
.upgrade-card-available {
|
|
border: 1px solid #2196F3; /* Urban Legend blue */
|
|
background-color: rgba(33, 150, 243, 0.05);
|
|
}
|
|
|
|
.upgrade-card-affordable {
|
|
border: 2px solid #9C27B0; /* Urban Plague purple */
|
|
background-color: rgba(156, 39, 176, 0.08);
|
|
box-shadow: 0 0 3px #9C27B0;
|
|
}
|
|
|
|
.upgrade-card-purchased {
|
|
border: 2px solid #5D9C59; /* Urban Myth green */
|
|
background-color: rgba(93, 156, 89, 0.08);
|
|
}
|
|
|
|
.upgrade-card-locked {
|
|
border: 1px solid #AAAAAA; /* Canard gray */
|
|
background-color: rgba(170, 170, 170, 0.05);
|
|
filter: grayscale(100%);
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* Special cards for high-tier upgrades */
|
|
.upgrade-card-nightmare {
|
|
border: 2px solid #FF9800; /* Urban Nightmare orange */
|
|
background-color: rgba(255, 152, 0, 0.08);
|
|
animation: nightmare-pulse 3s infinite;
|
|
}
|
|
|
|
.upgrade-card-star {
|
|
border: 2px solid #FFEB3B; /* Star of the City yellow/gold */
|
|
background-color: rgba(255, 235, 59, 0.08);
|
|
animation: star-glow 2s infinite;
|
|
}
|
|
|
|
.upgrade-card-impuritas {
|
|
border: 3px solid red; /* Impuritas Civitas red/rainbow */
|
|
background-color: rgba(0, 0, 0, 0.1);
|
|
animation: rainbow-border 4s linear infinite;
|
|
}
|
|
|
|
.upgrade-name {
|
|
font-weight: bold;
|
|
font-size: 1.1em;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.level-indicator {
|
|
display: flex;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.level-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 50%;
|
|
margin-right: 5px;
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.level-dot-filled {
|
|
background-color: #0d6efd;
|
|
}
|
|
|
|
.level-dot-current {
|
|
background-color: #28a745;
|
|
box-shadow: 0 0 5px #28a745;
|
|
}
|
|
|
|
.locked-overlay {
|
|
font-size: 1.2em;
|
|
color: #6c757d;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
if purchased:
|
|
with st.expander("Your Purchased Upgrades", expanded=False):
|
|
for i, upgrade in enumerate(THRESHOLD_UPGRADES):
|
|
if upgrade["name"] in purchased:
|
|
|
|
card_class = "upgrade-card-purchased"
|
|
|
|
|
|
if i >= 6:
|
|
card_class = "upgrade-card-impuritas"
|
|
elif i >= 5:
|
|
card_class = "upgrade-card-star"
|
|
elif i >= 4:
|
|
card_class = "upgrade-card-nightmare"
|
|
|
|
upgrade_html = f"""
|
|
<div class="upgrade-card {card_class}">
|
|
<div class="level-indicator">
|
|
{create_level_dots(i, len(THRESHOLD_UPGRADES), purchased)}
|
|
</div>
|
|
<div class="upgrade-name">{upgrade["name"]} ✓</div>
|
|
<p>{upgrade["description"]}</p>
|
|
<p><strong>Threshold setting:</strong> {upgrade["threshold_setting"]:.4f}</p>
|
|
</div>
|
|
"""
|
|
st.markdown(upgrade_html, unsafe_allow_html=True)
|
|
|
|
|
|
next_available_index = None
|
|
for i, upgrade in enumerate(THRESHOLD_UPGRADES):
|
|
if upgrade["name"] not in purchased:
|
|
next_available_index = i
|
|
break
|
|
|
|
|
|
if next_available_index is None:
|
|
st.success("🎉 Congratulations! You've unlocked all available scanner upgrades!")
|
|
return
|
|
|
|
|
|
st.subheader("Available Upgrades")
|
|
|
|
|
|
for i, upgrade in enumerate(THRESHOLD_UPGRADES):
|
|
|
|
if upgrade["name"] in purchased:
|
|
continue
|
|
|
|
|
|
if i > next_available_index + 1:
|
|
continue
|
|
|
|
|
|
is_next_upgrade = (i == next_available_index)
|
|
can_afford = st.session_state.tag_currency >= upgrade["cost"]
|
|
|
|
if is_next_upgrade:
|
|
|
|
with st.container():
|
|
|
|
new_threshold = upgrade["threshold_setting"]
|
|
|
|
|
|
threshold_change = abs(new_threshold - base_threshold)
|
|
if new_threshold < base_threshold:
|
|
change_text = f"Lowers threshold to {new_threshold:.4f} (↓ {threshold_change:.4f})"
|
|
change_emoji = "⬇️"
|
|
else:
|
|
change_text = f"Raises threshold to {new_threshold:.4f} (↑ {threshold_change:.4f})"
|
|
change_emoji = "⬆️"
|
|
|
|
|
|
card_class = ""
|
|
|
|
|
|
base_class = "upgrade-card-affordable" if can_afford else "upgrade-card-available"
|
|
|
|
|
|
if i >= 6:
|
|
card_class = "upgrade-card-impuritas"
|
|
elif i >= 5:
|
|
card_class = "upgrade-card-star"
|
|
elif i >= 4:
|
|
card_class = "upgrade-card-nightmare"
|
|
else:
|
|
card_class = base_class
|
|
|
|
|
|
upgrade_html = f"""
|
|
<div class="upgrade-card {card_class}">
|
|
<div class="level-indicator">
|
|
{create_level_dots(i, len(THRESHOLD_UPGRADES), purchased)}
|
|
</div>
|
|
<div class="upgrade-name">{upgrade["name"]}</div>
|
|
<p>{upgrade["description"]}</p>
|
|
<p><strong>{change_emoji} {change_text}</strong></p>
|
|
</div>
|
|
"""
|
|
st.markdown(upgrade_html, unsafe_allow_html=True)
|
|
|
|
|
|
if tag_power_bonus > 0:
|
|
effective_upgrade_threshold = max(MIN_THRESHOLD, new_threshold - tag_power_bonus)
|
|
st.info(f"Effective threshold: {effective_upgrade_threshold:.4f} with your Tag Power bonus of {tag_power_bonus:.4f}")
|
|
|
|
|
|
col1, col2 = st.columns([3, 1])
|
|
with col1:
|
|
st.write(f"**Cost:** {upgrade['cost']} {TAG_CURRENCY_NAME}")
|
|
with col2:
|
|
if st.button("Purchase", key=f"upgrade_{i}", disabled=not can_afford, use_container_width=True):
|
|
purchase_upgrade(i)
|
|
else:
|
|
|
|
|
|
locked_class = "upgrade-card-locked"
|
|
rarity_hint = ""
|
|
|
|
if i >= 6:
|
|
rarity_hint = f'<div style="height: 3px; width: 50px; background: linear-gradient(to right, red, orange, yellow, green, blue, indigo, violet); margin: 5px 0;"></div>'
|
|
elif i >= 5:
|
|
rarity_hint = f'<div style="height: 3px; width: 50px; background: #FFEB3B; box-shadow: 0 0 3px #FFD700; margin: 5px 0;"></div>'
|
|
elif i >= 4:
|
|
rarity_hint = f'<div style="height: 3px; width: 50px; background: #FF9800; margin: 5px 0;"></div>'
|
|
|
|
locked_html = f"""
|
|
<div class="upgrade-card {locked_class}">
|
|
<div class="level-indicator">
|
|
{create_level_dots(i, len(THRESHOLD_UPGRADES), purchased)}
|
|
</div>
|
|
<div class="upgrade-name">{upgrade["name"]}</div>
|
|
{rarity_hint}
|
|
<p style="color: #6c757d;">Complete previous upgrade to unlock</p>
|
|
<p><strong>Cost:</strong> {upgrade['cost']} {TAG_CURRENCY_NAME}</p>
|
|
</div>
|
|
"""
|
|
st.markdown(locked_html, unsafe_allow_html=True)
|
|
|
|
|
|
if next_available_index + 2 < len(THRESHOLD_UPGRADES):
|
|
remaining = len(THRESHOLD_UPGRADES) - (next_available_index + 2)
|
|
st.markdown(f"<div style='text-align:center;color:#6c757d;'>{remaining} more upgrades will be revealed as you progress</div>", unsafe_allow_html=True)
|
|
|
|
def create_level_dots(current_level, total_levels, purchased):
|
|
"""Create HTML for level indicator dots"""
|
|
dots_html = ""
|
|
for i in range(total_levels):
|
|
if f"Pattern Recognition Module" in purchased and i == 0:
|
|
|
|
dot_class = "level-dot level-dot-filled"
|
|
elif i < current_level:
|
|
|
|
dot_class = "level-dot level-dot-filled"
|
|
elif i == current_level:
|
|
|
|
dot_class = "level-dot level-dot-current"
|
|
else:
|
|
|
|
dot_class = "level-dot"
|
|
|
|
dots_html += f'<div class="{dot_class}"></div>'
|
|
|
|
return dots_html
|
|
|
|
def purchase_upgrade(upgrade_index):
|
|
"""Purchase a threshold upgrade"""
|
|
upgrade = THRESHOLD_UPGRADES[upgrade_index]
|
|
|
|
|
|
if st.session_state.tag_currency >= upgrade["cost"]:
|
|
|
|
st.session_state.tag_currency -= upgrade["cost"]
|
|
st.session_state.game_stats["currency_spent"] += upgrade["cost"]
|
|
|
|
|
|
st.session_state.threshold = upgrade["threshold_setting"]
|
|
|
|
|
|
st.session_state.purchased_upgrades.append(upgrade["name"])
|
|
|
|
|
|
save_game()
|
|
|
|
|
|
if 'state_version' in st.session_state:
|
|
st.session_state.state_version += 1
|
|
|
|
|
|
st.rerun()
|
|
else:
|
|
st.error("Not enough currency!")
|
|
|
|
def display_neural_specialization():
|
|
"""Display neural specialization grid"""
|
|
st.subheader("Neural Specialization")
|
|
|
|
|
|
categories_count = {}
|
|
if hasattr(st.session_state, 'collected_tags'):
|
|
for tag, info in st.session_state.collected_tags.items():
|
|
category = info.get("category", "general")
|
|
if category not in categories_count:
|
|
categories_count[category] = 0
|
|
categories_count[category] += 1
|
|
|
|
|
|
mastered = st.session_state.mastered_categories if hasattr(st.session_state, 'mastered_categories') else {}
|
|
|
|
|
|
cols = st.columns(3)
|
|
col_idx = 0
|
|
|
|
for category, info in TAG_CATEGORIES.items():
|
|
with cols[col_idx]:
|
|
count = categories_count.get(category, 0)
|
|
is_mastered = category in mastered
|
|
|
|
|
|
if is_mastered:
|
|
color = info['color']
|
|
status = "MASTERED"
|
|
else:
|
|
color = "#AAAAAA"
|
|
status = "Active"
|
|
|
|
|
|
st.markdown(f"""
|
|
<div style="border: 1px solid {color}; border-radius: 5px; padding: 10px; margin-bottom: 10px;">
|
|
<p style="margin: 0; font-weight: bold;">{info['icon']} {info['name']}</p>
|
|
<p style="margin: 0; font-size: 0.9em;">Tags: {count}</p>
|
|
<p style="margin: 0; font-size: 0.9em; color: {color};">{status}</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
col_idx = (col_idx + 1) % 3
|
|
|
|
def display_discovery_tips():
|
|
"""Display tag discovery tips"""
|
|
with st.expander("💡 Tag Discovery Tips"):
|
|
st.write("""
|
|
**How to improve your model's cognitive abilities:**
|
|
|
|
1. **Collect diverse tags** across many categories to increase your collection level
|
|
2. **Master categories** by collecting many tags within each category
|
|
3. **Find tag combinations** by collecting specific sets of related tags
|
|
4. **Keep rare tags** in your collection to maintain tag power
|
|
5. **Extract tags** from the Library System to discover new tags
|
|
6. **Generate tag essences** to gain insight into what the AI recognizes
|
|
""")
|
|
|
|
|
|
|
|
|
|
|
|
def create_app_tabs():
|
|
tab1, tab2, tab3, tab4, tab5, tab6 = st.tabs([
|
|
"Scan Images",
|
|
"Tag Collection",
|
|
"Series Collections",
|
|
"Upgrade Shop",
|
|
"Library",
|
|
"Essence Generator"
|
|
])
|
|
|
|
return tab1, tab2, tab3, tab4, tab5, tab6
|
|
|
|
def main():
|
|
"""Main application function."""
|
|
|
|
st.title("🎮 Tag Collector Game")
|
|
|
|
|
|
apply_tag_animations()
|
|
|
|
load_game()
|
|
|
|
|
|
initialize_game_state()
|
|
initialize_library_system()
|
|
|
|
|
|
if 'model' not in st.session_state:
|
|
try:
|
|
model, thresholds, metadata = load_model()
|
|
st.session_state.model = model
|
|
st.session_state.thresholds = thresholds
|
|
st.session_state.metadata = metadata
|
|
except Exception as e:
|
|
st.error(f"Error loading model: {str(e)}")
|
|
st.info("Please make sure the model is properly exported.")
|
|
st.stop()
|
|
|
|
|
|
display_game_stats_panel()
|
|
|
|
|
|
create_sidebar()
|
|
|
|
|
|
tab1, tab2, tab3, tab4, tab5, tab6 = create_app_tabs()
|
|
|
|
|
|
with tab1:
|
|
display_scan_interface()
|
|
|
|
with tab2:
|
|
display_tag_collection()
|
|
|
|
with tab3:
|
|
display_series_mosaics()
|
|
|
|
with tab4:
|
|
display_upgrade_shop()
|
|
|
|
with tab5:
|
|
display_library_extraction()
|
|
|
|
with tab6:
|
|
|
|
from essence_generator import display_essence_generator
|
|
display_essence_generator()
|
|
|
|
if __name__ == "__main__":
|
|
main() |