diff --git a/games/efg/kuhn-3-card-v1.efg b/games/efg/kuhn-3-card-v1.efg new file mode 100644 index 0000000..324389c --- /dev/null +++ b/games/efg/kuhn-3-card-v1.efg @@ -0,0 +1,58 @@ +EFG 2 R "Three-card poker (K,Q,J)" { "Player One" "Player Two" } +"" + +c "" 1 "" { "P1:K P2:Q" 1/6 "P1:K P2:J" 1/6 "P1:Q P2:K" 1/6 "P1:Q P2:J" 1/6 "P1:J P2:K" 1/6 "P1:J P2:Q" 1/6 } 0 +p "" 1 1 "" { "Check" "Bet" } 0 +p "" 2 1 "" { "Check" "Bet" } 0 +t "" 1 "P1 wins small" { 1, -1 } +p "" 1 2 "" { "Fold" "Call" } 0 +t "" 2 "P2 wins small" { -1, 1 } +t "" 3 "P1 wins big" { 2, -2 } +p "" 2 2 "" { "Fold" "Call" } 0 +t "" 1 "P1 wins small" { 1, -1 } +t "" 3 "P1 wins big" { 2, -2 } +p "" 1 1 "" { "Check" "Bet" } 0 +p "" 2 3 "" { "Check" "Bet" } 0 +t "" 1 "P1 wins small" { 1, -1 } +p "" 1 2 "" { "Fold" "Call" } 0 +t "" 2 "P2 wins small" { -1, 1 } +t "" 3 "P1 wins big" { 2, -2 } +p "" 2 4 "" { "Fold" "Call" } 0 +t "" 1 "P1 wins small" { 1, -1 } +t "" 3 "P1 wins big" { 2, -2 } +p "" 1 3 "" { "Check" "Bet" } 0 +p "" 2 5 "" { "Check" "Bet" } 0 +t "" 2 "P2 wins small" { -1, 1 } +p "" 1 4 "" { "Fold" "Call" } 0 +t "" 2 "P2 wins small" { -1, 1 } +t "" 4 "P2 wins big" { -2, 2 } +p "" 2 6 "" { "Fold" "Call" } 0 +t "" 1 "P1 wins small" { 1, -1 } +t "" 4 "P2 wins big" { -2, 2 } +p "" 1 3 "" { "Check" "Bet" } 0 +p "" 2 3 "" { "Check" "Bet" } 0 +t "" 1 "P1 wins small" { 1, -1 } +p "" 1 4 "" { "Fold" "Call" } 0 +t "" 2 "P2 wins small" { -1, 1 } +t "" 3 "P1 wins big" { 2, -2 } +p "" 2 4 "" { "Fold" "Call" } 0 +t "" 1 "P1 wins small" { 1, -1 } +t "" 3 "P1 wins big" { 2, -2 } +p "" 1 5 "" { "Check" "Bet" } 0 +p "" 2 5 "" { "Check" "Bet" } 0 +t "" 2 "P2 wins small" { -1, 1 } +p "" 1 6 "" { "Fold" "Call" } 0 +t "" 2 "P2 wins small" { -1, 1 } +t "" 4 "P2 wins big" { -2, 2 } +p "" 2 6 "" { "Fold" "Call" } 0 +t "" 1 "P1 wins small" { 1, -1 } +t "" 4 "P2 wins big" { -2, 2 } +p "" 1 5 "" { "Check" "Bet" } 0 +p "" 2 1 "" { "Check" "Bet" } 0 +t "" 2 "P2 wins small" { -1, 1 } +p "" 1 6 "" { "Fold" "Call" } 0 +t "" 2 "P2 wins small" { -1, 1 } +t "" 4 "P2 wins big" { -2, 2 } +p "" 2 2 "" { "Fold" "Call" } 0 +t "" 1 "P1 wins small" { 1, -1 } +t "" 4 "P2 wins big" { -2, 2 } diff --git a/games/efg/kuhn-4-card.efg b/games/efg/kuhn-4-card.efg new file mode 100644 index 0000000..157b428 --- /dev/null +++ b/games/efg/kuhn-4-card.efg @@ -0,0 +1,112 @@ +EFG 2 R "Four-card (A,K,Q,J) betting game" { "Player_One" "Player_Two" } +"" + +c "" 1 "" { "AJ" 1/12 "AK" 1/12 "AQ" 1/12 "JA" 1/12 "JK" 1/12 "JQ" 1/12 "KA" 1/12 "KJ" 1/12 "KQ" 1/12 "QA" 1/12 "QJ" 1/12 "QK" 1/12 } 0 +p "" 1 1 "" { "Check" "Bet" } 0 +p "" 2 1 "" { "Check" "Bet" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +p "" 1 2 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 2 2 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 1 1 "" { "Check" "Bet" } 0 +p "" 2 3 "" { "Check" "Bet" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +p "" 1 2 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 2 4 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 1 1 "" { "Check" "Bet" } 0 +p "" 2 5 "" { "Check" "Bet" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +p "" 1 2 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 2 6 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 1 3 "" { "Check" "Bet" } 0 +p "" 2 7 "" { "Check" "Bet" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +p "" 1 4 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 2 8 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 1 3 "" { "Check" "Bet" } 0 +p "" 2 3 "" { "Check" "Bet" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +p "" 1 4 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 2 4 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 1 3 "" { "Check" "Bet" } 0 +p "" 2 5 "" { "Check" "Bet" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +p "" 1 4 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 2 6 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 1 5 "" { "Check" "Bet" } 0 +p "" 2 7 "" { "Check" "Bet" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +p "" 1 6 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 2 8 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 1 5 "" { "Check" "Bet" } 0 +p "" 2 1 "" { "Check" "Bet" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +p "" 1 6 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 2 2 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 1 5 "" { "Check" "Bet" } 0 +p "" 2 5 "" { "Check" "Bet" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +p "" 1 6 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 2 6 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 1 7 "" { "Check" "Bet" } 0 +p "" 2 7 "" { "Check" "Bet" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +p "" 1 8 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 2 8 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 1 7 "" { "Check" "Bet" } 0 +p "" 2 1 "" { "Check" "Bet" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +p "" 1 8 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 2 2 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 2 "Player_One wins +2" { 2, -2 } +p "" 1 7 "" { "Check" "Bet" } 0 +p "" 2 3 "" { "Check" "Bet" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +p "" 1 8 "" { "Fold" "Call" } 0 +t "" 3 "Player_Two wins +1" { -1, 1 } +t "" 4 "Player_Two wins +2" { -2, 2 } +p "" 2 4 "" { "Fold" "Call" } 0 +t "" 1 "Player_One wins +1" { 1, -1 } +t "" 4 "Player_Two wins +2" { -2, 2 } diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index 3109b98..1dbd328 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -697,7 +697,9 @@ def player(words: List[str]) -> tuple[int, int]: else: advance = 2 # only "player p" parsed if not playerdefined[p]: - defout(playertexname[p], playername[p]) + # Escape underscores in player names to prevent LaTeX errors + safe_playername = playername[p].replace("_", "\\_") + defout(playertexname[p], safe_playername) playerdefined[p] = True return p, advance @@ -1174,6 +1176,7 @@ def generate_legend( player_color = get_player_color(player, color_scheme) player_name = playername[player] if player < len(playername) else str(player) + safe_player_name = player_name.replace("_", "\\_") # Draw colored circle/square if player == 0: @@ -1184,7 +1187,7 @@ def generate_legend( legend_code += f"\\node[inner sep=0pt,minimum size=\\ndiam,draw={player_color},fill={player_color},shape=circle] at (0,{y_offset}) {{}};\n" # Add player label - legend_code += f"\\node[anchor=west] at (0.3,{y_offset}) {{{player_name}}};\n" + legend_code += f"\\node[anchor=west] at (0.3,{y_offset}) {{{safe_player_name}}};\n" y_offset -= y_spacing @@ -1315,6 +1318,8 @@ def level( parent_color = get_player_color(parent_player, color_scheme) edge_color_style = f"color={parent_color}" + + # tikz code - add color to the draw command for edges based on parent s = "\\draw [" + thickn if edge_color_style: @@ -1401,6 +1406,27 @@ def level( s += "," + edge_color_style mov_display = mov + + if color_scheme != "default": + # Color actions like "P1:K" with Player 1's color and remove "P1:" + # Supports both P1:K and P 1 : K formats + def replace_action(match): + try: + p_num = int(match.group(1)) + # Removed playerdefined check to ensure coloring works even if + # level commands precede player commands in the file. + if 1 <= p_num <= 100: + col = get_player_color(p_num, color_scheme) + action = match.group(2) + return f"\\textcolor{{{col}}}{{{action}}}" + except (ValueError, IndexError): + pass + return match.group(0) + + # Match P followed by number, colon, and then the action text + # Stops at space or ~ to preserve multiple actions in one label + mov_display = re.sub(r"[Pp]\s*(\d+)\s*:\s*([^\s~]+)", replace_action, mov_display) + if "$" not in mov_display: mov_display = re.sub( r"(\\frac\s*\{[^}]*\}\s*\{[^}]*\})", r"$\1$", mov_display