| import gradio as gr |
| from squid_game_core import ( |
| parse_tier_map, |
| get_expected_value, |
| compute_ev_win_lose_two_extremes, |
| finite_squid_game_probabilities, |
| dp_ev, |
| infinite_squid_game_expected_counts_partial, |
| ) |
| from typing import List, Tuple |
|
|
| def validate_distribution(dist_str: str) -> Tuple[bool, str, List[int]]: |
| """Validate the distribution string and convert to list of integers""" |
| try: |
| dist = [int(x.strip()) for x in dist_str.split(',')] |
| if any(x < 0 for x in dist): |
| return False, "Distribution cannot contain negative numbers", [] |
| return True, "", dist |
| except ValueError: |
| return False, "Distribution must be comma-separated integers (e.g., '0,1,2')", [] |
|
|
| def validate_tier_map(tier_str: str) -> Tuple[bool, str]: |
| """Validate the tier map string format""" |
| try: |
| lines = tier_str.strip().splitlines() |
| for line in lines: |
| if ':' not in line: |
| return False, "Each line must contain a colon (e.g., '1-2:1.5')" |
| range_part, mult_part = line.split(':') |
| float(mult_part.strip()) |
| if '-' in range_part: |
| low_str, high_str = range_part.split('-') |
| int(low_str), int(high_str) |
| else: |
| int(range_part.strip()) |
| return True, "" |
| except ValueError: |
| return False, "Invalid format. Example: '1:1.0\\n2-4:2.0\\n5-6:3.0'" |
|
|
| def solve_game(distribution: str, total_squids: int, tier_map_str: str) -> str: |
| """Main function to solve the game and return formatted results""" |
| |
| valid_dist, error_msg, dist = validate_distribution(distribution) |
| if not valid_dist: |
| return error_msg |
|
|
| |
| valid_tier, error_msg = validate_tier_map(tier_map_str) |
| if not valid_tier: |
| return error_msg |
|
|
| |
| try: |
| X = int(total_squids) |
| if X < 0: |
| return "Total squids cannot be negative" |
| if X < sum(dist): |
| return "Total squids cannot be less than current distribution sum" |
| except ValueError: |
| return "Total squids must be an integer" |
|
|
| |
| try: |
| tier_map = parse_tier_map(tier_map_str) |
| tier_map_tuple = tuple((a, b, c) for a, b, c in tier_map) |
|
|
| |
| remaining = X - sum(dist) |
|
|
| |
| get_expected_value.cache_clear() |
| unforced_ev = get_expected_value(tuple(dist), remaining, tier_map_tuple) |
|
|
| result = "Unforced Expected Values:\n" |
| for i, ev in enumerate(unforced_ev): |
| result += f"Player {i+1}: {ev:.3f}\n" |
|
|
| |
| win_lose_results = compute_ev_win_lose_two_extremes(tuple(dist), remaining, tier_map_tuple) |
|
|
| result += "\nForced Win/Lose Results:\n" |
| for r in win_lose_results: |
| result += (f"Player {r['player']+1}: forcedWinEV = {r['forcedWinEV']:.3f}, " |
| f"forcedLoseEV = {r['forcedLoseEV']:.3f}, Diff = {r['difference']:.3f}\n") |
|
|
| |
| result += "\nTier Map Interpretation:\n" |
| for low, high, mult in tier_map: |
| if low == high: |
| result += f"• {low} squid(s): multiplier = {mult:.1f}\n" |
| else: |
| result += f"• {low}-{high} squids: multiplier = {mult:.1f}\n" |
|
|
| return result |
|
|
| except Exception as e: |
| return f"Error occurred: {str(e)}" |
|
|
| def solve_finite_game(distribution: str, total_squids: int) -> str: |
| """Calculate the probability of each player getting a squid in the finite variant""" |
| |
| valid_dist, error_msg, dist = validate_distribution(distribution) |
| if not valid_dist: |
| return error_msg |
|
|
| |
| if any(x > 1 for x in dist): |
| return "Error: In the finite variant, players can only have 0 or 1 squid. Please adjust your input." |
|
|
| |
| try: |
| X = int(total_squids) |
| if X < 0: |
| return "Total squids cannot be negative" |
| |
| |
| if X > len(dist): |
| return f"Error: In the finite variant, total squids cannot exceed the number of players ({len(dist)})" |
| |
| |
| current_sum = sum(dist) |
| if current_sum + (X - current_sum) > len(dist): |
| return f"Error: Total squids to distribute ({X}) plus already distributed squids ({current_sum}) cannot exceed the number of players ({len(dist)})" |
| except ValueError: |
| return "Total squids must be an integer" |
|
|
| try: |
| |
| dp_ev.cache_clear() |
| |
| |
| n = len(dist) |
| |
| |
| remaining = X - sum(dist) |
| |
| |
| bitmask_u = (1 << n) - 1 |
| for i, val in enumerate(dist): |
| if val == 1: |
| bitmask_u ^= (1 << i) |
| |
| |
| probs = dp_ev(n, bitmask_u, remaining) |
| |
| result = "Finite Squid Game Probabilities:\n" |
| result += "(Probability of each player getting a squid)\n\n" |
| |
| for i, prob in enumerate(probs): |
| result += f"Player {i+1}: {prob:.4f}\n" |
| |
| return result |
| |
| except Exception as e: |
| return f"Error occurred: {str(e)}" |
|
|
| def solve_infinite_game(distribution: str) -> str: |
| """Calculate the expected final squid count for each player in the infinite variant""" |
| |
| valid_dist, error_msg, dist = validate_distribution(distribution) |
| if not valid_dist: |
| return error_msg |
|
|
| try: |
| |
| expected_counts = infinite_squid_game_expected_counts_partial(dist) |
| |
| result = "Infinite Squid Game Expected Final Counts:\n" |
| result += "(Expected number of squids each player will have at the end)\n\n" |
| |
| for i, count in enumerate(expected_counts): |
| if count == float('inf'): |
| result += f"Player {i+1}: ∞ (infinite)\n" |
| else: |
| result += f"Player {i+1}: {count:.4f}\n" |
| |
| |
| zero_count = sum(1 for x in dist if x == 0) |
| if zero_count == 1: |
| result += "\nExplanation: Game ends immediately as there is exactly one player with 0 squids." |
| elif zero_count == 0: |
| result += "\nExplanation: Game never ends (infinite loop) as there are no players with 0 squids." |
| else: |
| H_z = sum(1.0 / k for k in range(1, zero_count+1)) |
| increment = (H_z - 1.0) |
| result += f"\nExplanation: Each player is expected to receive {increment:.4f} additional squids before the game ends." |
| |
| return result |
| |
| except Exception as e: |
| return f"Error occurred: {str(e)}" |
|
|
| |
| DEFAULT_TIER_MAP = """0-0:0 |
| 1-2:1 |
| 3-4:2 |
| 5-6:4 |
| 7-7:8 |
| 8-8:16 |
| 9-9:32 |
| 10-100:64""" |
|
|
| with gr.Blocks(title="Squid Game Calculator") as iface: |
| gr.Markdown(""" |
| # Squid Game Expected Value Calculator |
| |
| Calculate the expected payoff for each player in the Squid Game. |
| |
| **Classic Variant Rules:** |
| 1. Players take turns collecting squids randomly. |
| 2. The game ends when either: |
| - Exactly one player has 0 squids, OR |
| - There are no squids left to distribute. |
| |
| **Finite Variant Rules:** |
| 1. Players take turns collecting squids randomly. |
| 2. Once a player gets a squid, they can't get another one. |
| 3. The game ends when all squids are distributed or only one player remains without a squid. |
| |
| **Infinite Variant Rules:** |
| 1. Players take turns collecting squids randomly (unlimited supply). |
| 2. Players accumulate squids over time. |
| 3. The game ends only when exactly one player has 0 squids. |
| """) |
| |
| with gr.Row(): |
| with gr.Column(): |
| distribution_input = gr.Textbox( |
| label="Players' Current Squids", |
| placeholder="0,0", |
| value="0,0", |
| info="""Enter each player's current squids, separated by commas. |
| Example: '1,0,1,2,0' represents: |
| - Player 1 has 1 squid |
| - Player 2 has 0 squids |
| - Player 3 has 1 squid |
| - Player 4 has 2 squids |
| - Player 5 has 0 squids""" |
| ) |
| total_squids_input = gr.Number( |
| label="Total Squids in Game (Classic & Finite Variants Only)", |
| value=9, |
| minimum=0, |
| step=1, |
| precision=0, |
| info="The total number of squids to be distributed (must be ≥ sum of current squids for classic variant)" |
| ) |
| tier_map_input = gr.Textbox( |
| label="Squid Value Tiers (Classic Variant Only)", |
| placeholder=DEFAULT_TIER_MAP, |
| value=DEFAULT_TIER_MAP, |
| lines=8, |
| info="""Define the value tiers for squids. |
| Format: range:multiplier (one per line) |
| Example: |
| 0-0:0 |
| 1-2:1 |
| 3-4:2 |
| 5-6:4 |
| 7-7:8 |
| 8-8:16 |
| 9-9:32 |
| 10-100:64""" |
| ) |
| |
| with gr.Column(): |
| results_output = gr.Textbox(label="Results", lines=15) |
| |
| with gr.Row(): |
| classic_btn = gr.Button("Calculate Classic Variant", variant="primary") |
| finite_btn = gr.Button("Calculate Finite Variant", variant="stop") |
| infinite_btn = gr.Button("Calculate Infinite Variant", variant="secondary") |
| |
| gr.Examples( |
| examples=[ |
| ["0,0", 9, DEFAULT_TIER_MAP], |
| ["1,0,1", 12, DEFAULT_TIER_MAP], |
| ["2,0,2,0", 14, DEFAULT_TIER_MAP], |
| ], |
| inputs=[distribution_input, total_squids_input, tier_map_input], |
| ) |
| |
| classic_btn.click( |
| fn=solve_game, |
| inputs=[distribution_input, total_squids_input, tier_map_input], |
| outputs=results_output |
| ) |
| |
| finite_btn.click( |
| fn=solve_finite_game, |
| inputs=[distribution_input, total_squids_input], |
| outputs=results_output |
| ) |
| |
| infinite_btn.click( |
| fn=solve_infinite_game, |
| inputs=[distribution_input], |
| outputs=results_output |
| ) |
|
|
| if __name__ == "__main__": |
| iface.launch() |