Spaces:
Running
Running
#!/usr/bin/env python3 | |
""" | |
Developer Tools for Tag Collector Game | |
A hidden panel with tools for testing and debugging game features. | |
""" | |
import streamlit as st | |
import random | |
import time | |
from game_constants import ( | |
TAG_CURRENCY_NAME, | |
RARITY_LEVELS, | |
ACHIEVEMENTS, | |
) | |
from tag_categories import ( | |
TAG_CATEGORIES, | |
TAG_DETECTOR_UPGRADES, | |
PROGRESSION_ACHIEVEMENTS | |
) | |
def display_dev_tools(): | |
""" | |
Display the developer tools interface | |
This should be hidden in production builds or behind a developer toggle | |
""" | |
st.title("🛠️ Developer Tools") | |
st.warning("These tools are for testing and debugging only. They can break game balance!") | |
# Create tabs for different tool categories | |
resource_tab, tag_tab, progression_tab, mosaic_tab, reset_tab = st.tabs([ | |
"Resources", "Tag Management", "Progression", "Mosaic Tools", "Reset Tools" | |
]) | |
with resource_tab: | |
display_resource_tools() | |
with tag_tab: | |
display_tag_tools() | |
with progression_tab: | |
display_progression_tools() | |
with mosaic_tab: | |
display_mosaic_tools() | |
with reset_tab: | |
display_reset_tools() | |
def display_resource_tools(): | |
"""Display tools for managing game resources""" | |
st.subheader("Currency and Resources") | |
# Add TagCoins | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
amount = st.number_input("Amount of TagCoins to add:", min_value=0, max_value=1000000, value=1000, step=100) | |
with col2: | |
if st.button("Add TagCoins", key="add_currency"): | |
st.session_state.tag_currency += amount | |
st.session_state.game_stats["total_currency_earned"] += amount | |
st.success(f"Added {amount} {TAG_CURRENCY_NAME}!") | |
# Set threshold directly | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
threshold = st.slider("Set threshold value:", min_value=0.0, max_value=1.0, value=st.session_state.threshold, step=0.01) | |
with col2: | |
if st.button("Set Threshold", key="set_threshold"): | |
st.session_state.threshold = threshold | |
st.success(f"Set threshold to {threshold:.2f}") | |
# Add tag power bonuses | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
power = st.number_input("Add tag power bonus:", min_value=0.0, max_value=0.1, value=0.01, step=0.001, format="%.3f") | |
with col2: | |
if st.button("Add Power", key="add_power"): | |
if not hasattr(st.session_state, 'tag_power_bonus'): | |
st.session_state.tag_power_bonus = 0 | |
st.session_state.tag_power_bonus += power | |
st.success(f"Added {power:.3f} tag power!") | |
def display_tag_tools(): | |
"""Display tools for managing tags""" | |
st.subheader("Tag Management") | |
# Add specific tag | |
with st.expander("Add Specific Tag", expanded=True): | |
col1, col2, col3 = st.columns([4, 2, 1]) | |
with col1: | |
tag_name = st.text_input("Tag name:", value="custom_tag") | |
with col2: | |
rarities = list(RARITY_LEVELS.keys()) | |
rarity = st.selectbox("Rarity:", rarities) | |
with col3: | |
# Get categories from session state or fallback to general | |
categories = ["general", "character", "copyright", "meta", "rating", "artist", "year"] | |
category = st.selectbox("Category:", categories) | |
if st.button("Add Tag", key="add_specific_tag"): | |
# Check if tag already exists | |
is_new = tag_name not in st.session_state.collected_tags | |
# Add tag to collection | |
st.session_state.collected_tags[tag_name] = { | |
"count": 1, | |
"rarity": rarity, | |
"category": category, | |
"discovery_time": time.strftime("%Y-%m-%d %H:%M:%S") | |
} | |
# Show confirmation | |
if is_new: | |
st.success(f"Added new tag '{tag_name}' ({rarity}, {category})") | |
else: | |
st.session_state.collected_tags[tag_name]["count"] += 1 | |
st.info(f"Incremented count for existing tag '{tag_name}'") | |
# Generate random tags | |
with st.expander("Generate Random Tags", expanded=False): | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
num_tags = st.number_input("Number of random tags to generate:", min_value=1, max_value=1000, value=10) | |
# Options for distribution | |
advanced = st.checkbox("Advanced options") | |
if advanced: | |
st.write("Rarity distribution:") | |
common_pct = st.slider("Common tags %:", 0, 100, 70) | |
uncommon_pct = st.slider("Uncommon tags %:", 0, 100, 20) | |
rare_pct = st.slider("Rare tags %:", 0, 100, 8) | |
super_rare_pct = st.slider("Super rare tags %:", 0, 100, 2) | |
# Ensure total is 100% | |
total = common_pct + uncommon_pct + rare_pct + super_rare_pct | |
if total != 100: | |
st.warning(f"Distribution totals {total}%, should be 100%") | |
with col2: | |
generate_button = st.button("Generate", key="generate_random_tags") | |
if generate_button: | |
generated_count = 0 | |
# Determine distribution of rarities | |
if advanced and total == 100: | |
# Custom distribution | |
rarity_weights = { | |
"Whispered Word": common_pct / 100, | |
"Common Canard": uncommon_pct / 100 * 0.6, | |
"Urban Footnote": uncommon_pct / 100 * 0.4, | |
"Urban Myth": rare_pct / 100 * 0.5, | |
"Urban Legend": rare_pct / 100 * 0.5, | |
"Urban Nightmare": super_rare_pct / 100 * 0.8, | |
"Impuritas Civitas": super_rare_pct / 100 * 0.2 | |
} | |
else: | |
# Default distribution | |
rarity_weights = { | |
"Whispered Word": 0.70, | |
"Common Canard": 0.15, | |
"Urban Footnote": 0.08, | |
"Urban Myth": 0.04, | |
"Urban Legend": 0.02, | |
"Urban Nightmare": 0.008, | |
"Impuritas Civitas": 0.002 | |
} | |
# Generate the tags | |
for i in range(num_tags): | |
# Create a random tag name if we don't have metadata | |
tag_name = f"random_tag_{int(time.time() % 10000)}_{i}" | |
# Determine rarity | |
rarity = random.choices( | |
list(rarity_weights.keys()), | |
weights=list(rarity_weights.values()), | |
k=1 | |
)[0] | |
# Determine category | |
categories = list(TAG_CATEGORIES.keys()) | |
category = random.choice(categories) | |
# Add to collection | |
timestamp = time.strftime("%Y-%m-%d %H:%M:%S") | |
# Check if this is a new tag | |
is_new = tag_name not in st.session_state.collected_tags | |
if is_new: | |
st.session_state.collected_tags[tag_name] = { | |
"count": 1, | |
"rarity": rarity, | |
"category": category, | |
"discovery_time": timestamp | |
} | |
generated_count += 1 | |
else: | |
# Increment count if already exists | |
st.session_state.collected_tags[tag_name]["count"] += 1 | |
# Show confirmation | |
st.success(f"Generated {generated_count} new random tags!") | |
def display_progression_tools(): | |
"""Display tools for managing progression""" | |
st.subheader("Progression System Tools") | |
# Unlock categories | |
with st.expander("Unlock Categories", expanded=True): | |
st.write("Select categories to unlock:") | |
# Get currently unlocked categories | |
unlocked = [] | |
if hasattr(st.session_state, 'unlocked_tag_categories'): | |
unlocked = st.session_state.unlocked_tag_categories | |
# Display each category with a checkbox | |
category_checkboxes = {} | |
for category, info in TAG_CATEGORIES.items(): | |
# Skip default unlocked | |
if info["unlocked_by_default"]: | |
continue | |
# Check if already unlocked | |
is_unlocked = category in unlocked | |
category_checkboxes[category] = st.checkbox( | |
f"{info['name']} ({info['cost']} {TAG_CURRENCY_NAME})", | |
value=is_unlocked, | |
key=f"cat_{category}" | |
) | |
# Button to apply changes | |
if st.button("Apply Category Changes", key="apply_categories"): | |
# Initialize if needed | |
if not hasattr(st.session_state, 'unlocked_tag_categories'): | |
st.session_state.unlocked_tag_categories = [] | |
# Add default unlocked | |
for cat, info in TAG_CATEGORIES.items(): | |
if info["unlocked_by_default"]: | |
st.session_state.unlocked_tag_categories.append(cat) | |
# Update unlocked categories | |
for category, checked in category_checkboxes.items(): | |
# If checked but not unlocked, add it | |
if checked and category not in st.session_state.unlocked_tag_categories: | |
st.session_state.unlocked_tag_categories.append(category) | |
st.success(f"Unlocked {TAG_CATEGORIES[category]['name']}!") | |
# If unchecked but unlocked, remove it | |
elif not checked and category in st.session_state.unlocked_tag_categories: | |
st.session_state.unlocked_tag_categories.remove(category) | |
st.info(f"Locked {TAG_CATEGORIES[category]['name']}") | |
# Upgrade detector level | |
with st.expander("Set Detector Level", expanded=False): | |
# Get current level | |
current_level = 0 | |
if hasattr(st.session_state, 'detector_level'): | |
current_level = st.session_state.detector_level | |
# Display slider for detector level | |
new_level = st.slider( | |
"Detector Level:", | |
min_value=0, | |
max_value=len(TAG_DETECTOR_UPGRADES) - 1, | |
value=current_level | |
) | |
# Show info about selected level | |
upgrade = TAG_DETECTOR_UPGRADES[new_level] | |
max_tags = upgrade["max_tags"] | |
if max_tags == 0: | |
st.write(f"Selected: {upgrade['name']} (Unlimited tags)") | |
else: | |
st.write(f"Selected: {upgrade['name']} ({max_tags} tags)") | |
# Button to apply changes | |
if st.button("Set Detector Level", key="set_detector_level"): | |
st.session_state.detector_level = new_level | |
st.success(f"Set detector level to {new_level} ({upgrade['name']})") | |
# Unlock achievements | |
with st.expander("Manage Achievements", expanded=False): | |
# Combine standard and progression achievements | |
all_achievements = {**ACHIEVEMENTS, **PROGRESSION_ACHIEVEMENTS} | |
# Initialize achievements if needed | |
if not hasattr(st.session_state, 'achievements'): | |
st.session_state.achievements = set() | |
# Create tabs for unlocked and locked | |
unlocked_tab, locked_tab = st.tabs(["Unlocked", "Locked"]) | |
with unlocked_tab: | |
st.write("Currently unlocked achievements:") | |
# Show unlocked achievements with option to remove | |
for achievement_id in sorted(st.session_state.achievements): | |
if achievement_id in all_achievements: | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
achievement = all_achievements[achievement_id] | |
st.write(f"**{achievement['name']}**: {achievement['description']}") | |
with col2: | |
if st.button("Remove", key=f"remove_{achievement_id}"): | |
st.session_state.achievements.remove(achievement_id) | |
st.info(f"Removed achievement: {achievement['name']}") | |
st.rerun() | |
with locked_tab: | |
st.write("Currently locked achievements:") | |
# Show locked achievements with option to add | |
locked_achievements = [a for a in all_achievements if a not in st.session_state.achievements] | |
for achievement_id in sorted(locked_achievements): | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
achievement = all_achievements[achievement_id] | |
st.write(f"**{achievement['name']}**: {achievement['description']}") | |
with col2: | |
if st.button("Unlock", key=f"unlock_{achievement_id}"): | |
st.session_state.achievements.add(achievement_id) | |
# Apply rewards if applicable | |
if "reward" in achievement: | |
from scan_handler import apply_achievement_reward | |
apply_achievement_reward(achievement_id, achievement["reward"]) | |
st.success(f"Unlocked achievement: {achievement['name']}") | |
st.rerun() | |
def display_mosaic_tools(): | |
"""Display tools for managing the tag mosaic""" | |
st.subheader("Tag Mosaic Tools") | |
# Check if mosaic exists | |
has_mosaic = hasattr(st.session_state, 'tag_mosaic') | |
if not has_mosaic: | |
st.warning("Tag Mosaic not initialized yet. Visit the Tag Collection tab first.") | |
return | |
# Fill random portions of the mosaic | |
with st.expander("Fill Random Portions", expanded=True): | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
fill_percentage = st.slider( | |
"Percentage to fill:", | |
min_value=0, | |
max_value=100, | |
value=10, | |
step=1 | |
) | |
# Options for distribution | |
st.write("Fill with tags of rarity:") | |
fill_rarities = {} | |
for rarity in RARITY_LEVELS: | |
fill_rarities[rarity] = st.checkbox(rarity, value=True, key=f"fill_{rarity}") | |
with col2: | |
fill_button = st.button("Fill Mosaic", key="fill_mosaic") | |
if fill_button: | |
# Get the mosaic from session state | |
mosaic = st.session_state.tag_mosaic | |
# Calculate how many cells to fill | |
total_cells = mosaic.total_cells | |
existing_filled = len(mosaic.filled_cells) | |
target_filled = int(total_cells * fill_percentage / 100) | |
cells_to_add = max(0, target_filled - existing_filled) | |
# Get active rarities | |
active_rarities = [r for r, checked in fill_rarities.items() if checked] | |
if not active_rarities: | |
st.error("Select at least one rarity to fill with") | |
return | |
# Create artificial tags and add them | |
added_count = 0 | |
added_tags = {} | |
# Generate random positions | |
all_positions = [(x, y) for x in range(mosaic.grid_width) for y in range(mosaic.grid_height)] | |
# Remove already filled positions | |
available_positions = [pos for pos in all_positions if pos not in mosaic.filled_cells] | |
# If we need more than available, just use what's available | |
cells_to_add = min(cells_to_add, len(available_positions)) | |
# Randomly select positions | |
selected_positions = random.sample(available_positions, cells_to_add) | |
# Create tags for each position | |
for pos in selected_positions: | |
x, y = pos | |
# Create a tag name | |
tag_name = f"mosaic_fill_{x}_{y}_{int(time.time() % 10000)}" | |
# Select a random rarity from active rarities | |
rarity = random.choice(active_rarities) | |
# Add to tags dictionary (this won't be saved to session_state) | |
added_tags[tag_name] = { | |
"count": 1, | |
"rarity": rarity, | |
"category": "general" | |
} | |
added_count += 1 | |
# Update the mosaic (this does save to disk) | |
if added_count > 0: | |
mosaic.update_with_tags(added_tags) | |
st.success(f"Added {added_count} random cells to the mosaic!") | |
# Show updated stats | |
stats = mosaic.get_stats() | |
st.write(f"New completion: {stats['completion_percentage']:.2f}%") | |
st.write(f"Emerging pattern: {stats['completion_pattern']}") | |
# Show image | |
mosaic_img = mosaic.get_image(show_highlights=True) | |
st.image(mosaic_img, caption="Updated Mosaic", width=400) | |
else: | |
st.info("No new cells added. Mosaic may already be filled to the requested level.") | |
# Reset mosaic without affecting collection | |
with st.expander("Reset Mosaic", expanded=False): | |
if st.button("Reset Mosaic", key="reset_mosaic"): | |
# Confirm | |
confirm = st.checkbox("I understand this will clear the mosaic visualization (not your collection)") | |
if confirm: | |
# Get the mosaic from session state | |
mosaic = st.session_state.tag_mosaic | |
# Reset the mosaic by creating a new one | |
from tag_mosaic import TagMosaic | |
st.session_state.tag_mosaic = TagMosaic() | |
# Delete the mosaic save file | |
import os | |
if os.path.exists("tag_mosaic.png"): | |
try: | |
os.remove("tag_mosaic.png") | |
except Exception as e: | |
st.error(f"Error removing mosaic file: {e}") | |
st.success("Mosaic has been reset!") | |
def display_reset_tools(): | |
"""Display tools for resetting the game""" | |
st.subheader("Reset Tools") | |
st.warning("These tools will reset parts of your game progress. Use with caution!") | |
# Reset currency | |
with st.expander("Reset Currency", expanded=False): | |
col1, col2 = st.columns([3, 1]) | |
with col1: | |
new_amount = st.number_input("Set currency to:", min_value=0, value=0) | |
with col2: | |
if st.button("Reset Currency", key="reset_currency"): | |
st.session_state.tag_currency = new_amount | |
st.success(f"Reset currency to {new_amount} {TAG_CURRENCY_NAME}") | |
# Reset collection | |
with st.expander("Reset Collection", expanded=False): | |
st.write("This will remove all collected tags or specific rarities.") | |
# Options to keep certain rarities | |
st.write("Keep tags with these rarities:") | |
keep_rarities = {} | |
for rarity in RARITY_LEVELS: | |
keep_rarities[rarity] = st.checkbox(rarity, value=False, key=f"keep_{rarity}") | |
if st.button("Reset Collection", key="reset_collection"): | |
# Confirm | |
confirm = st.checkbox("I understand this will delete collected tags") | |
if confirm: | |
# Get rarities to keep | |
rarities_to_keep = [r for r, checked in keep_rarities.items() if checked] | |
# If keeping some rarities, filter the collection | |
if rarities_to_keep: | |
# Create a new collection with only the kept rarities | |
kept_tags = {} | |
for tag, info in st.session_state.collected_tags.items(): | |
if info.get("rarity") in rarities_to_keep: | |
kept_tags[tag] = info | |
# Replace the collection | |
removed_count = len(st.session_state.collected_tags) - len(kept_tags) | |
st.session_state.collected_tags = kept_tags | |
st.success(f"Removed {removed_count} tags. Kept {len(kept_tags)} tags with rarities: {', '.join(rarities_to_keep)}") | |
else: | |
# Remove all tags | |
removed_count = len(st.session_state.collected_tags) | |
st.session_state.collected_tags = {} | |
st.success(f"Removed all {removed_count} tags from your collection") | |
# Reset complete game | |
with st.expander("Reset ENTIRE Game", expanded=False): | |
st.error("This will reset ALL game progress including collection, currency, achievements, and upgrades.") | |
if st.button("Reset EVERYTHING", key="reset_everything"): | |
# Double confirm | |
confirm1 = st.checkbox("I understand ALL progress will be lost") | |
confirm2 = st.checkbox("This cannot be undone") | |
if confirm1 and confirm2: | |
# Reset everything | |
st.session_state.threshold = 0.25 # Default starting threshold | |
st.session_state.tag_currency = 0 | |
st.session_state.collected_tags = {} | |
st.session_state.purchased_upgrades = [] | |
st.session_state.achievements = set() | |
st.session_state.tag_history = [] | |
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 | |
} | |
# Reset progression | |
if hasattr(st.session_state, 'unlocked_tag_categories'): | |
st.session_state.unlocked_tag_categories = [] | |
# Add default unlocked categories | |
for cat, info in TAG_CATEGORIES.items(): | |
if info["unlocked_by_default"]: | |
st.session_state.unlocked_tag_categories.append(cat) | |
if hasattr(st.session_state, 'detector_level'): | |
st.session_state.detector_level = 0 | |
if hasattr(st.session_state, 'tag_power_bonus'): | |
st.session_state.tag_power_bonus = 0 | |
if hasattr(st.session_state, 'coin_multiplier'): | |
st.session_state.coin_multiplier = 1.0 | |
if hasattr(st.session_state, 'essence_generator_count'): | |
st.session_state.essence_generator_count = 0 | |
# Reset mosaic | |
import os | |
if os.path.exists("tag_mosaic.png"): | |
try: | |
os.remove("tag_mosaic.png") | |
except Exception as e: | |
st.error(f"Error removing mosaic file: {e}") | |
if hasattr(st.session_state, 'tag_mosaic'): | |
from tag_mosaic import TagMosaic | |
st.session_state.tag_mosaic = TagMosaic() | |
st.success("Game completely reset to initial state!") | |
st.info("Refresh the page to see changes take effect") |