#!/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")