import streamlit as st import random import time from datetime import datetime import pandas as pd # import numpy as np # Not explicitly used, can be removed if not needed for other things import altair as alt # Set page configuration (should be the first Streamlit command) st.set_page_config( page_title="Number Master Challenge", page_icon="🎯", layout="centered", initial_sidebar_state="expanded" ) # Apply custom CSS for better styling st.markdown(""" """, unsafe_allow_html=True) # --- Session State Initialization --- # Using a helper to make it slightly cleaner def init_session_state_key(key, value): if key not in st.session_state: st.session_state[key] = value init_session_state_key('number_to_guess', random.randint(1, 100)) # Default to Medium on first ever load init_session_state_key('attempts', 0) init_session_state_key('total_games', 0) init_session_state_key('total_guesses', 0) init_session_state_key('wins', 0) init_session_state_key('best_score', float('inf')) init_session_state_key('game_active', True) init_session_state_key('difficulty', 'Medium') # Default difficulty init_session_state_key('max_attempts', 10) # Default for Medium init_session_state_key('range_min', 1) # Default for Medium init_session_state_key('range_max', 100) # Default for Medium init_session_state_key('history', []) init_session_state_key('game_start_time', time.time()) init_session_state_key('feedback', "Select your difficulty and make your first guess!") init_session_state_key('hint_used', False) init_session_state_key('guess_history', []) # --- Game Logic Functions --- DIFFICULTY_SETTINGS = { 'Easy': {'min': 1, 'max': 50, 'attempts': 15}, 'Medium': {'min': 1, 'max': 100, 'attempts': 10}, 'Hard': {'min': 1, 'max': 200, 'attempts': 8}, 'Expert': {'min': 1, 'max': 500, 'attempts': 6} } def reset_game(difficulty_changed=False): settings = DIFFICULTY_SETTINGS[st.session_state.difficulty] st.session_state.range_min = settings['min'] st.session_state.range_max = settings['max'] st.session_state.max_attempts = settings['attempts'] st.session_state.number_to_guess = random.randint(st.session_state.range_min, st.session_state.range_max) st.session_state.attempts = 0 st.session_state.game_active = True if difficulty_changed: st.session_state.feedback = f"Difficulty set to {st.session_state.difficulty}. New game started!" else: st.session_state.feedback = "New game started! Make your guess." st.session_state.hint_used = False st.session_state.guess_history = [] st.session_state.game_start_time = time.time() # No need to manage input widget keys explicitly if using st.form and relying on Streamlit's default keying or explicit keys for inputs. def update_stats(won): st.session_state.total_games += 1 st.session_state.total_guesses += st.session_state.attempts if won: st.session_state.wins += 1 if st.session_state.attempts < st.session_state.best_score: st.session_state.best_score = st.session_state.attempts game_duration = round(time.time() - st.session_state.game_start_time, 1) st.session_state.history.append({ 'date': datetime.now().strftime("%Y-%m-%d %H:%M"), 'difficulty': st.session_state.difficulty, 'attempts': st.session_state.attempts, 'max_attempts': st.session_state.max_attempts, 'won': won, 'duration': game_duration, 'number': st.session_state.number_to_guess # Store the number for history }) def evaluate_guess(guess_value): # Renamed param to avoid conflict # Ensure guess is an integer try: guess_value = int(guess_value) except (ValueError, TypeError): st.session_state.feedback = "Invalid guess. Please enter a whole number." return # Don't proceed with evaluation st.session_state.attempts += 1 st.session_state.guess_history.append(guess_value) if guess_value == st.session_state.number_to_guess: st.session_state.feedback = f"🎉 CORRECT! You found the number {st.session_state.number_to_guess} in {st.session_state.attempts} attempts!" st.session_state.game_active = False update_stats(True) st.balloons() # Add some celebration elif st.session_state.attempts >= st.session_state.max_attempts: st.session_state.feedback = f"😔 Game over! You've used all {st.session_state.max_attempts} attempts. The number was {st.session_state.number_to_guess}." st.session_state.game_active = False update_stats(False) elif guess_value < st.session_state.number_to_guess: st.session_state.feedback = "📈 Too low! Aim higher." else: # guess_value > st.session_state.number_to_guess st.session_state.feedback = "📉 Too high! Aim lower." def give_hint(): if not st.session_state.game_active: return "Game is over. Start a new game to get hints!" if st.session_state.hint_used: return "You've already used your hint for this game!" target = st.session_state.number_to_guess hints = [] if target % 2 == 0: hints.append("The number is even.") else: hints.append("The number is odd.") if st.session_state.difficulty in ['Easy', 'Medium'] and len(str(target)) > 1 : # More complex hints for easier levels digit_sum = sum(int(digit) for digit in str(target)) hints.append(f"The sum of its digits is {digit_sum}.") if st.session_state.difficulty == 'Easy': if target > st.session_state.range_min + (st.session_state.range_max - st.session_state.range_min) / 2: hints.append("It's in the upper half of the range.") else: hints.append("It's in the lower half of the range.") st.session_state.hint_used = True # Mark hint as used # st.session_state.attempts += 1 # Optional: Penalize hint usage by an attempt # if st.session_state.attempts >= st.session_state.max_attempts: # st.session_state.feedback = f"😔 Game over! You've used all {st.session_state.max_attempts} attempts. The number was {st.session_state.number_to_guess}." # st.session_state.game_active = False # update_stats(False) print(hints) # debug return " ".join(hints) if hints else "Hmm, I'm feeling shy with hints right now." @st.cache_data # Cache the fun fact for the session def get_number_fun_fact_cached(): fun_facts = [ "The fear of numbers is called 'arithmophobia'.", "Zero (0) was not recognized as a number in Europe until the 13th century.", "7 is often considered the world's most common lucky number.", "The Mayans and Babylonians independently developed the concept of zero.", "142857 is a cyclic number: 142857 × 2 = 285714, 142857 × 3 = 428571, etc.", "There are 293 ways to make change for a US dollar using standard coins.", "A googol is 1 followed by 100 zeros. A googolplex is 10 to the power of a googol.", "In many East Asian cultures, the number 4 is unlucky as it sounds like 'death'.", "Ancient Greeks considered 10 to be a perfect number (tetractys).", "A 'perfect number' is a positive integer that is equal to the sum of its proper positive divisors. E.g., 6 (1+2+3).", "No number from 1 to 999 contains the letter 'a' when spelled out in English.", "The number 8 is considered very lucky in Chinese culture, sounding like 'wealth' or 'prosper'.", "The sum of the first 10 Lucas numbers (starting L0=2, L1=1) is 123 + 1 = 124. L10 is 123.", # Corrected fact "Euler's number (e) is an irrational number approximately equal to 2.71828." ] return random.choice(fun_facts) # --- Sidebar --- with st.sidebar: st.markdown("## Game Settings") difficulty_options = list(DIFFICULTY_SETTINGS.keys()) current_difficulty_index = difficulty_options.index(st.session_state.difficulty) new_difficulty = st.selectbox("Select Difficulty", difficulty_options, index=current_difficulty_index, key="difficulty_selector") if new_difficulty != st.session_state.difficulty: st.session_state.difficulty = new_difficulty reset_game(difficulty_changed=True) st.rerun() # Explicitly rerun to update UI based on new difficulty st.markdown("---") st.markdown("## Game Statistics") col1, col2 = st.columns(2) with col1: st.metric("Games Played", st.session_state.total_games) st.metric("Wins", st.session_state.wins) with col2: st.metric("Best Score (Attempts)", st.session_state.best_score if st.session_state.best_score != float('inf') else "N/A") win_rate_val = (st.session_state.wins / st.session_state.total_games * 100) if st.session_state.total_games > 0 else 0 st.metric("Win Rate", f"{win_rate_val:.1f}%") avg_attempts_val = (st.session_state.total_guesses / st.session_state.total_games) if st.session_state.total_games > 0 else 0 st.metric("Avg. Attempts per Game", f"{avg_attempts_val:.1f}" if st.session_state.total_games > 0 else "N/A", help="Average attempts across all completed games.") st.markdown("---") st.markdown("## Did You Know?") st.markdown(f"*{get_number_fun_fact_cached()}*") # Use cached version st.markdown("---") if st.button("Reset All Stats", type="secondary"): # Clear specific stats, keep difficulty init_session_state_key('total_games', 0) # Re-init to 0 st.session_state.total_games = 0 st.session_state.total_guesses = 0 st.session_state.wins = 0 st.session_state.best_score = float('inf') st.session_state.history = [] # reset_game() # Don't reset the current game, just the stats st.success("All game statistics have been reset!") time.sleep(1.5) # Give user time to see message st.rerun() # --- Main Game Area --- st.markdown('
Number Master Challenge by Valentino Phiri
", unsafe_allow_html=True) st.markdown(f'Guess the number between {st.session_state.range_min} and {st.session_state.range_max}!
', unsafe_allow_html=True) # Game info card with st.container(border=False): # Use border=True for themed container st.markdown('{random.choice(encouragements)}
", unsafe_allow_html=True) if st.button("Start New Game", key="new_game_button", type="primary"): reset_game() st.rerun() st.markdown('Number Master Challenge by Valentino Phiri
", unsafe_allow_html=True)