sidnvy commited on
Commit
950537f
·
verified ·
1 Parent(s): 4a67ae9

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. squid_game.py +58 -74
  2. squid_game_core.py +83 -37
squid_game.py CHANGED
@@ -1,5 +1,9 @@
1
  import gradio as gr
2
- from squid_game_core import parse_tier_map, get_expected_value, next_squid_gain_for_nonzero, hypothetical_next_round_gain
 
 
 
 
3
  from typing import List, Tuple
4
 
5
  def validate_distribution(dist_str: str) -> Tuple[bool, str, List[int]]:
@@ -21,7 +25,6 @@ def validate_tier_map(tier_str: str) -> Tuple[bool, str]:
21
  return False, "Each line must contain a colon (e.g., '1-2:1.5')"
22
  range_part, mult_part = line.split(':')
23
  float(mult_part.strip()) # Check multiplier is a valid number
24
-
25
  if '-' in range_part:
26
  low_str, high_str = range_part.split('-')
27
  int(low_str), int(high_str)
@@ -37,12 +40,12 @@ def solve_game(distribution: str, total_squids: int, tier_map_str: str) -> str:
37
  valid_dist, error_msg, dist = validate_distribution(distribution)
38
  if not valid_dist:
39
  return error_msg
40
-
41
  # Validate tier map
42
  valid_tier, error_msg = validate_tier_map(tier_map_str)
43
  if not valid_tier:
44
  return error_msg
45
-
46
  # Validate total squids
47
  try:
48
  X = int(total_squids)
@@ -56,54 +59,41 @@ def solve_game(distribution: str, total_squids: int, tier_map_str: str) -> str:
56
  # Parse tier map and convert to tuple for caching
57
  try:
58
  tier_map = parse_tier_map(tier_map_str)
59
- tier_map_tuple = tuple((a,b,c) for a,b,c in tier_map)
60
-
61
- # Calculate remaining squids
62
  remaining = X - sum(dist)
63
-
64
- # Get expected values
65
  get_expected_value.cache_clear()
66
- expected_values = get_expected_value(tuple(dist), remaining, tier_map_tuple)
67
-
68
- # Format results
69
- result = "Expected Values:\n"
70
- for i, ev in enumerate(expected_values):
71
  result += f"Player {i+1}: {ev:.3f}\n"
72
-
73
- # Calculate penalty from expected values of zero-squid players
74
- zero_player_evs = [ev for i, ev in enumerate(expected_values) if dist[i] == 0]
75
- if zero_player_evs:
76
- # Verify all zero-squid players have same expected value
77
- if not all(abs(ev - zero_player_evs[0]) < 1e-6 for ev in zero_player_evs):
78
- return "Error: Zero-squid players have different expected values"
79
- penalty = abs(zero_player_evs[0])
80
- else:
81
- penalty = 0
82
-
83
- # Calculate potential gains using hypothetical_next_round_gain with actual penalty
84
- gains = hypothetical_next_round_gain(dist, tier_map, penalty=penalty)
85
- if gains:
86
- result += "\nPotential Gains from Next Squid:\n"
87
- for player_idx, gain in enumerate(gains):
88
- result += f"Player {player_idx+1}: +{gain:.1f}"
89
- if dist[player_idx] == 0:
90
- result += " (includes avoiding payment share)"
91
- result += "\n"
92
-
93
- # Add tier map interpretation
94
  result += "\nTier Map Interpretation:\n"
95
  for low, high, mult in tier_map:
96
  if low == high:
97
  result += f"• {low} squid(s): multiplier = {mult:.1f}\n"
98
  else:
99
  result += f"• {low}-{high} squids: multiplier = {mult:.1f}\n"
100
-
101
  return result
102
-
103
  except Exception as e:
104
  return f"Error occurred: {str(e)}"
105
 
106
- # Define a default tier map that's commonly used
107
  DEFAULT_TIER_MAP = """0-0:0
108
  1-2:1
109
  3-4:2
@@ -121,16 +111,16 @@ iface = gr.Interface(
121
  placeholder="0,0",
122
  value="0,0",
123
  info="""Enter each player's current squids, separated by commas.
124
- Example: '1,0,1,2,0' means:
125
- - Player 1 has 1 squid
126
- - Player 2 has 0 squids
127
- - Player 3 has 1 squid
128
- - Player 4 has 2 squids
129
- - Player 5 has 0 squids"""
130
  ),
131
  gr.Number(
132
  label="Total Squids in Game",
133
- value=2,
134
  minimum=0,
135
  step=1,
136
  precision=0,
@@ -140,41 +130,35 @@ Example: '1,0,1,2,0' means:
140
  label="Squid Value Tiers",
141
  placeholder=DEFAULT_TIER_MAP,
142
  value=DEFAULT_TIER_MAP,
143
- lines=6,
144
- info="""Define how squids are valued at different quantities.
145
- Format: range:multiplier (one rule per line)
146
-
147
- Examples:
148
- 0-0:0 → 0 squids = 0 points
149
- 1-2:1 → 1,2 squid = 1× points (total = 1)
150
- 3-4:2 → 3,4 squids = 2× points each (total = 4)
151
- 5-6:4 → 5,6 squids = 4× points each
152
- 7-7:8 → 7 squids = 8 points
153
- 8-8:16 → 8 squids = 16 points
154
- 9-9:32 → 9 squids = 32 points
155
- 10-100:64 → 10+ squids = 64× points each
156
-
157
- Edit these values to match your game rules."""
158
  )
159
  ],
160
- outputs=gr.Textbox(label="Results", lines=10),
161
  title="Squid Game Expected Value Calculator",
162
  description="""
163
  Calculate the expected payoff for each player in the Squid Game.
164
-
165
- Game Rules:
166
- 1. Players take turns collecting squids randomly
167
- 2. Game ends when either:
168
- - Exactly one player has 0 squids (they pay the total value of others' squids, winners keep their squids), OR
169
- - No squids remain to distribute:
170
- * If multiple players have 0, each pays the total value and winners get multiplied payouts
171
- * If no one has 0, no payment occurs
172
  """,
173
  examples=[
174
- # Common scenarios with descriptive labels
175
- ["0,0", 2, DEFAULT_TIER_MAP], # Basic 2-player game
176
- ["1,0,1", 4, DEFAULT_TIER_MAP], # 3 players, some squids distributed
177
- ["2,0,2,0", 6, DEFAULT_TIER_MAP], # 4 players, mixed distribution
178
  ]
179
  )
180
 
 
1
  import gradio as gr
2
+ from squid_game_core import (
3
+ parse_tier_map,
4
+ get_expected_value,
5
+ compute_ev_win_lose_two_extremes,
6
+ )
7
  from typing import List, Tuple
8
 
9
  def validate_distribution(dist_str: str) -> Tuple[bool, str, List[int]]:
 
25
  return False, "Each line must contain a colon (e.g., '1-2:1.5')"
26
  range_part, mult_part = line.split(':')
27
  float(mult_part.strip()) # Check multiplier is a valid number
 
28
  if '-' in range_part:
29
  low_str, high_str = range_part.split('-')
30
  int(low_str), int(high_str)
 
40
  valid_dist, error_msg, dist = validate_distribution(distribution)
41
  if not valid_dist:
42
  return error_msg
43
+
44
  # Validate tier map
45
  valid_tier, error_msg = validate_tier_map(tier_map_str)
46
  if not valid_tier:
47
  return error_msg
48
+
49
  # Validate total squids
50
  try:
51
  X = int(total_squids)
 
59
  # Parse tier map and convert to tuple for caching
60
  try:
61
  tier_map = parse_tier_map(tier_map_str)
62
+ tier_map_tuple = tuple((a, b, c) for a, b, c in tier_map)
63
+
64
+ # Calculate remaining squids to distribute
65
  remaining = X - sum(dist)
66
+
67
+ # Get unforced expected values (full random assignment)
68
  get_expected_value.cache_clear()
69
+ unforced_ev = get_expected_value(tuple(dist), remaining, tier_map_tuple)
70
+
71
+ result = "Unforced Expected Values:\n"
72
+ for i, ev in enumerate(unforced_ev):
 
73
  result += f"Player {i+1}: {ev:.3f}\n"
74
+
75
+ # Compute each player's forced win/lose EV extremes:
76
+ win_lose_results = compute_ev_win_lose_two_extremes(tuple(dist), remaining, tier_map_tuple)
77
+
78
+ result += "\nForced Win/Lose Results:\n"
79
+ for r in win_lose_results:
80
+ result += (f"Player {r['player']+1}: forcedWinEV = {r['forcedWinEV']:.3f}, "
81
+ f"forcedLoseEV = {r['forcedLoseEV']:.3f}, Diff = {r['difference']:.3f}\n")
82
+
83
+ # Add a human-friendly interpretation of the tier map
 
 
 
 
 
 
 
 
 
 
 
 
84
  result += "\nTier Map Interpretation:\n"
85
  for low, high, mult in tier_map:
86
  if low == high:
87
  result += f"• {low} squid(s): multiplier = {mult:.1f}\n"
88
  else:
89
  result += f"• {low}-{high} squids: multiplier = {mult:.1f}\n"
90
+
91
  return result
92
+
93
  except Exception as e:
94
  return f"Error occurred: {str(e)}"
95
 
96
+ # Default value for tier map used in interface
97
  DEFAULT_TIER_MAP = """0-0:0
98
  1-2:1
99
  3-4:2
 
111
  placeholder="0,0",
112
  value="0,0",
113
  info="""Enter each player's current squids, separated by commas.
114
+ Example: '1,0,1,2,0' represents:
115
+ - Player 1 has 1 squid
116
+ - Player 2 has 0 squids
117
+ - Player 3 has 1 squid
118
+ - Player 4 has 2 squids
119
+ - Player 5 has 0 squids"""
120
  ),
121
  gr.Number(
122
  label="Total Squids in Game",
123
+ value=9,
124
  minimum=0,
125
  step=1,
126
  precision=0,
 
130
  label="Squid Value Tiers",
131
  placeholder=DEFAULT_TIER_MAP,
132
  value=DEFAULT_TIER_MAP,
133
+ lines=8,
134
+ info="""Define the value tiers for squids.
135
+ Format: range:multiplier (one per line)
136
+ Example:
137
+ 0-0:0
138
+ 1-2:1
139
+ 3-4:2
140
+ 5-6:4
141
+ 7-7:8
142
+ 8-8:16
143
+ 9-9:32
144
+ 10-100:64"""
 
 
 
145
  )
146
  ],
147
+ outputs=gr.Textbox(label="Results", lines=15),
148
  title="Squid Game Expected Value Calculator",
149
  description="""
150
  Calculate the expected payoff for each player in the Squid Game.
151
+
152
+ Rules:
153
+ 1. Players take turns collecting squids randomly.
154
+ 2. The game ends when either:
155
+ - Exactly one player has 0 squids, OR
156
+ - There are no squids left to distribute.
 
 
157
  """,
158
  examples=[
159
+ ["0,0", 9, DEFAULT_TIER_MAP],
160
+ ["1,0,1", 12, DEFAULT_TIER_MAP],
161
+ ["2,0,2,0", 14, DEFAULT_TIER_MAP],
 
162
  ]
163
  )
164
 
squid_game_core.py CHANGED
@@ -112,49 +112,95 @@ def get_expected_value(distribution, remaining, tier_map_tuple):
112
  accumulated[i] /= n
113
  return tuple(accumulated)
114
 
115
- def next_squid_gain_for_nonzero(distribution, tier_map):
 
 
 
 
 
116
  """
117
- Returns a dict: {player_index: gain in tierValue if that player goes from s_i to s_i+1}.
118
- Only for players who currently hold > 0 squids.
 
 
119
  """
120
- results = {}
121
- for i, s in enumerate(distribution):
122
- if s > 0: # Only include non-zero players
123
- curr_val = tierValue(s, tier_map)
124
- next_val = tierValue(s+1, tier_map)
125
- results[i] = next_val - curr_val
126
- return results
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
- def hypothetical_next_round_gain(distribution, tier_map, penalty):
 
 
 
129
  """
130
- Returns a list (or dict) of length N, indicating how much "extra" reward
131
- each player would get if they, individually, are the *sole* winner next round.
 
 
 
 
132
 
133
- - If s[i] > 0:
134
- gain[i] = tierValue(s[i]+1) - tierValue(s[i])
135
- - If s[i] == 0:
136
- gain[i] = tierValue(1) + (1/zero_count)*penalty
137
- (assuming your simplified logic that 1/zero_count is
138
- the chance of "dodging" the final cost of 24 by winning a squid)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
  """
140
  n = len(distribution)
141
- gains = [0.0]*n
142
 
143
- zero_count = sum(1 for x in distribution if x==0)
 
 
144
 
145
- for i, s_i in enumerate(distribution):
146
- if s_i > 0:
147
- current_val = tierValue(s_i, tier_map)
148
- next_val = tierValue(s_i + 1, tier_map)
149
- gains[i] = next_val - current_val
150
- else:
151
- # s_i=0
152
- val_if_win = tierValue(1, tier_map) # from 0 => 1
153
- # plus the "avoid paying 24" portion *if you assume
154
- # it is equally likely you'd be the one stuck paying if you remain at 0
155
- if zero_count > 0:
156
- gains[i] = val_if_win + (penalty / zero_count)
157
- else:
158
- # edge case: if zero_count=0? not possible if s_i=0.
159
- gains[i] = val_if_win
160
- return gains
 
112
  accumulated[i] /= n
113
  return tuple(accumulated)
114
 
115
+ def get_expected_value_forced_win(
116
+ i,
117
+ distribution,
118
+ leftover,
119
+ tier_map_tuple
120
+ ):
121
  """
122
+ 假设下一只乌贼 100% 给玩家 i。
123
+ 则先把 distribution[i] += 1, leftover -=1,
124
+ 然后对 (distribution', leftover') 做完全随机的 get_expected_value(...)。
125
+ 返回:一个长度 N 的 tuple,表示每个玩家在这种强制赢前提下的期望最终收益。
126
  """
127
+ if leftover <= 0:
128
+ # 没乌贼剩了,也可能是某些奇怪边界,直接算终局:
129
+ return get_expected_value(distribution, leftover, tier_map_tuple)
130
+
131
+ dist_forced = list(distribution)
132
+ dist_forced[i] += 1
133
+ new_dist = tuple(dist_forced)
134
+ return get_expected_value(new_dist, leftover - 1, tier_map_tuple)
135
+
136
+
137
+ def get_expected_value_forced_lose(
138
+ i,
139
+ distribution,
140
+ leftover,
141
+ tier_map_tuple
142
+ ):
143
+ """
144
+ 假设下一只乌贼 100% 不会给玩家 i,
145
+ 即本轮发乌贼只在其余 (n-1) 人中随机选 winner,
146
+ 然后后续 (leftover-1) 轮恢复正常 n 人随机。
147
 
148
+ 做法:遍历所有 winner != i (prob=1/(n-1)),发给那个 winner,
149
+ 然后 leftover-1 的状态再用 get_expected_value 完全随机。
150
+
151
+ 返回:一个长度 N 的 tuple (每个玩家最终EV)
152
  """
153
+ n = len(distribution)
154
+ if leftover <= 0:
155
+ return get_expected_value(distribution, leftover, tier_map_tuple)
156
+
157
+ # 如果 n=1,那就无可比了……(此处不太可能)
158
+ # 一般 n>=2, leftover>=1
159
 
160
+ # 假设我们这里显式地做一次 "下一只的发放" 的平均
161
+ # winner只能在 [0..n-1] - {i} 之中。
162
+ # Probability = 1/(n-1)
163
+
164
+ accumulated = [0.0]*n
165
+ valid_winners = [w for w in range(n) if w != i]
166
+
167
+ for w in valid_winners:
168
+ dist_next = list(distribution)
169
+ dist_next[w] += 1
170
+ sub_ev = get_expected_value(tuple(dist_next), leftover-1, tier_map_tuple)
171
+ for p in range(n):
172
+ accumulated[p] += sub_ev[p]
173
+
174
+ # 做平均
175
+ for p in range(n):
176
+ accumulated[p] /= len(valid_winners) # == (n-1)
177
+
178
+ return tuple(accumulated)
179
+
180
+ def compute_ev_win_lose_two_extremes(distribution, leftover, tier_map_tuple):
181
+ """
182
+ 返回一个数据结构,记录每个玩家 i 在:
183
+ - forced_win 时的期望收益
184
+ - forced_lose 时的期望收益
185
+ - difference = forced_win - forced_lose
186
  """
187
  n = len(distribution)
188
+ results = []
189
 
190
+ for i in range(n):
191
+ forced_win_vec = get_expected_value_forced_win(i, distribution, leftover, tier_map_tuple)
192
+ forced_lose_vec = get_expected_value_forced_lose(i, distribution, leftover, tier_map_tuple)
193
 
194
+ # 我们可能只关心玩家 i 本人的比较, 也可以把全部人都算,
195
+ # 这里演示只关心 i
196
+ forced_win_i = forced_win_vec[i]
197
+ forced_lose_i = forced_lose_vec[i]
198
+ diff_i = forced_win_i - forced_lose_i
199
+
200
+ results.append({
201
+ 'player': i,
202
+ 'forcedWinEV': forced_win_i,
203
+ 'forcedLoseEV': forced_lose_i,
204
+ 'difference': diff_i
205
+ })
206
+ return results