|
|
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() |