From 26d1236543b761d843d6b9ccd01ebfe2275b8fe7 Mon Sep 17 00:00:00 2001 From: TheBestCoder-1 Date: Sun, 8 Mar 2026 20:48:07 -0500 Subject: [PATCH 1/6] feat: implement distinctipy color scheme and gambit limit check (Issue #14) --- pyproject.toml | 2 +- src/draw_tree/core.py | 85 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1bb3e82..5085f50 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ classifiers = [ keywords = ["game theory", "tikz", "visualization", "trees", "economics"] # Required runtime dependencies (previously optional under the 'jupyter' extra) -dependencies = ["jupyter-tikz", "ipykernel"] +dependencies = ["jupyter-tikz", "ipykernel", "distinctipy"] [project.optional-dependencies] dev = ["pytest>=7.0.0", "pytest-cov", "nbformat", "nbclient", "ipykernel", "pygambit"] diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index 779945c..466e320 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -11,6 +11,7 @@ import subprocess import tempfile import re +import distinctipy from typing import TYPE_CHECKING from numpy import save @@ -97,22 +98,58 @@ def get_player_color(player: int, color_scheme: str = "default") -> str: 6: "\\playersixcolor", } - return color_map.get(player, "black") - return "black" + if player not in color_map: + raise ValueError( + f"The 'gambit' color scheme only supports up to 6 players " + f"(got player {player}). Consider using the 'distinctipy' " + f"color scheme for games with more players." + ) + return color_map[player] + + elif color_scheme == "distinctipy": + if player == 0: + return "\\chancecolor" + elif player > 0: + return f"\\player{player}color" + + return "black" # fallback -def color_definitions() -> list[str]: - return [ +def color_definitions(color_scheme: str = "default", num_players: int = 6) -> list[str]: + defs = [ "\\definecolor{chancecolorrgb}{RGB}{117,145,56}", - "\\definecolor{gambitredrgb}{RGB}{234,51,35}", - "\\newcommand\\chancecolor{chancecolorrgb}", - "\\newcommand\\playeronecolor{gambitredrgb}", - "\\newcommand\\playertwocolor{blue}", - "\\newcommand\\playerthreecolor{orange}", - "\\newcommand\\playerfourcolor{purple}", - "\\newcommand\\playerfivecolor{cyan}", - "\\newcommand\\playersixcolor{magenta}", + "\\newcommand\\chancecolor{chancecolorrgb}" ] + if color_scheme == "gambit": + defs.extend([ + "\\definecolor{gambitredrgb}{RGB}{234,51,35}", + "\\newcommand\\playeronecolor{gambitredrgb}", + "\\newcommand\\playertwocolor{blue}", + "\\newcommand\\playerthreecolor{orange}", + "\\newcommand\\playerfourcolor{purple}", + "\\newcommand\\playerfivecolor{cyan}", + "\\newcommand\\playersixcolor{magenta}", + ]) + elif color_scheme == "distinctipy": + chance_rgb = (117/255, 145/255, 56/255) + try: + colors = distinctipy.get_colors( + num_players, + exclude_colors=[(0, 0, 0), (1, 1, 1), chance_rgb], + rng=42 + ) + for i, color in enumerate(colors): + r, g, b = [int(c * 255) for c in color] + p_num = i + 1 + defs.extend([ + f"\\definecolor{{p{p_num}rgb}}{{RGB}}{{{r},{g},{b}}}", + f"\\newcommand\\player{p_num}color{{p{p_num}rgb}}" + ]) + except Exception as e: + print(f"Warning: Failed to generate distinctipy colors: {e}") + + return defs + def outall(stream: Optional[List[str]] = None) -> None: """ @@ -1572,6 +1609,28 @@ def generate_tikz( hide_action_labels=hide_action_labels, shared_terminal_depth=shared_terminal_depth, ) + + num_players = 0 # start from zero, count actual players + if not isinstance(game, str): + try: + num_players = len(game.players) + except AttributeError: + num_players = 6 # fallback only if attribute missing + else: + try: + player_nums = set() + for line in readfile(ef_file): + if line.startswith("player"): + try: + p = int(line.split()[1]) + if p > 0: # exclude chance (player 0) + player_nums.add(p) + except (IndexError, ValueError): + pass + num_players = len(player_nums) if player_nums else 6 + except Exception: + num_players = 6 + # Step 1: Generate the tikzpicture content using ef_to_tex logic tikz_picture_content = ef_to_tex(ef_file, scale_factor, show_grid, color_scheme, action_label_position) @@ -1596,7 +1655,7 @@ def generate_tikz( f"\\treethickn{edge_thickness}pt", ] # Step 2a: Define player color macros - macro_definitions.extend(color_definitions()) + macro_definitions.extend(color_definitions(color_scheme, num_players)) # Step 3: Combine everything into complete TikZ code tikz_code = """% TikZ code with built-in styling for game trees From 30f848f50efd0dabc36430d59cb17e77e610bc5f Mon Sep 17 00:00:00 2001 From: TheBestCoder-1 Date: Sun, 8 Mar 2026 21:18:03 -0500 Subject: [PATCH 2/6] feat: add colorblind-friendly scheme via distinctipy --- src/draw_tree/core.py | 73 +++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index 466e320..f3f0699 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -80,14 +80,19 @@ def get_player_color(player: int, color_scheme: str = "default") -> str: Get the TeX color macro name for a given player number. Args: - player: Player number (1-6 for regular players). - color_scheme: Optional color scheme name. + player: Player number (0 for chance, 1-6 for regular players with + "gambit" scheme, or any positive integer for "distinctipy" and + "colorblind" schemes). + color_scheme: Color scheme name. One of "default", "gambit", + "distinctipy", or "colorblind". Returns: TeX color macro name for the player, or "black" as fallback. + + Raises: + ValueError: If the "gambit" scheme is used with more than 6 players. """ if color_scheme == "gambit": - # Color mapping for up to 6 players color_map = { 0: "\\chancecolor", 1: "\\playeronecolor", @@ -97,29 +102,46 @@ def get_player_color(player: int, color_scheme: str = "default") -> str: 5: "\\playerfivecolor", 6: "\\playersixcolor", } - if player not in color_map: raise ValueError( f"The 'gambit' color scheme only supports up to 6 players " f"(got player {player}). Consider using the 'distinctipy' " - f"color scheme for games with more players." + f"or 'colorblind' color scheme for games with more players." ) return color_map[player] - - elif color_scheme == "distinctipy": + + elif color_scheme in ("distinctipy", "colorblind"): if player == 0: return "\\chancecolor" elif player > 0: return f"\\player{player}color" - - return "black" # fallback + + return "black" def color_definitions(color_scheme: str = "default", num_players: int = 6) -> list[str]: + """ + Generate LaTeX color macro definitions for game tree players. + + Produces ``\\definecolor`` and ``\\newcommand`` lines that are injected + into the TikZ preamble so that player-color macros (e.g. + ``\\playeronecolor``, ``\\player7color``) resolve correctly. + + Args: + color_scheme: One of "default", "gambit", "distinctipy", or + "colorblind". + num_players: Number of players that need colours. Ignored for + "default" and "gambit" (which have fixed palettes). + + Returns: + List of LaTeX definition strings. + """ + # Chance color is shared across all schemes defs = [ "\\definecolor{chancecolorrgb}{RGB}{117,145,56}", - "\\newcommand\\chancecolor{chancecolorrgb}" + "\\newcommand\\chancecolor{chancecolorrgb}", ] + if color_scheme == "gambit": defs.extend([ "\\definecolor{gambitredrgb}{RGB}{234,51,35}", @@ -130,26 +152,31 @@ def color_definitions(color_scheme: str = "default", num_players: int = 6) -> li "\\newcommand\\playerfivecolor{cyan}", "\\newcommand\\playersixcolor{magenta}", ]) - elif color_scheme == "distinctipy": - chance_rgb = (117/255, 145/255, 56/255) + + elif color_scheme in ("distinctipy", "colorblind"): + # Chance color in 0-1 float format for exclusion + chance_rgb = (117 / 255, 145 / 255, 56 / 255) try: + colorblind_type = ( + "Deuteranomaly" if color_scheme == "colorblind" else None + ) colors = distinctipy.get_colors( num_players, exclude_colors=[(0, 0, 0), (1, 1, 1), chance_rgb], - rng=42 + rng=42, + colorblind_type=colorblind_type, ) for i, color in enumerate(colors): r, g, b = [int(c * 255) for c in color] p_num = i + 1 defs.extend([ f"\\definecolor{{p{p_num}rgb}}{{RGB}}{{{r},{g},{b}}}", - f"\\newcommand\\player{p_num}color{{p{p_num}rgb}}" + f"\\newcommand\\player{p_num}color{{p{p_num}rgb}}", ]) except Exception as e: - print(f"Warning: Failed to generate distinctipy colors: {e}") - + print(f"Warning: Failed to generate {color_scheme} colors: {e}") + return defs - def outall(stream: Optional[List[str]] = None) -> None: """ @@ -1609,13 +1636,14 @@ def generate_tikz( hide_action_labels=hide_action_labels, shared_terminal_depth=shared_terminal_depth, ) - - num_players = 0 # start from zero, count actual players + + # Determine the number of players for dynamic color schemes + num_players = 0 if not isinstance(game, str): try: num_players = len(game.players) except AttributeError: - num_players = 6 # fallback only if attribute missing + num_players = 6 else: try: player_nums = set() @@ -1623,7 +1651,7 @@ def generate_tikz( if line.startswith("player"): try: p = int(line.split()[1]) - if p > 0: # exclude chance (player 0) + if p > 0: player_nums.add(p) except (IndexError, ValueError): pass @@ -1631,7 +1659,6 @@ def generate_tikz( except Exception: num_players = 6 - # Step 1: Generate the tikzpicture content using ef_to_tex logic tikz_picture_content = ef_to_tex(ef_file, scale_factor, show_grid, color_scheme, action_label_position) @@ -2256,4 +2283,4 @@ def efg_dl_ef(efg_file: str) -> str: f.write('\n'.join(out_lines) + '\n') return str(out_path) except Exception: - return '\n'.join(out_lines) + return '\n'.join(out_lines) \ No newline at end of file From f8dc4c130c1df4739567997048f644d1bb989de6 Mon Sep 17 00:00:00 2001 From: Xinke Li Date: Mon, 9 Mar 2026 13:44:20 -0500 Subject: [PATCH 3/6] fix: handle player < 0 properly to resolve CI failure --- src/draw_tree/core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index f3f0699..58fbe13 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -102,6 +102,8 @@ def get_player_color(player: int, color_scheme: str = "default") -> str: 5: "\\playerfivecolor", 6: "\\playersixcolor", } + if player < 0: + return "black" # no player assigned yet if player not in color_map: raise ValueError( f"The 'gambit' color scheme only supports up to 6 players " @@ -2283,4 +2285,4 @@ def efg_dl_ef(efg_file: str) -> str: f.write('\n'.join(out_lines) + '\n') return str(out_path) except Exception: - return '\n'.join(out_lines) \ No newline at end of file + return '\n'.join(out_lines) From 0b69d80534e39bf17bc9ccc43b704132b8d52f13 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 26 Mar 2026 12:49:51 +0000 Subject: [PATCH 4/6] add color schemes --- tutorial/draw_ef.ipynb | 1035 +++++++++++++++++++++++----------------- 1 file changed, 610 insertions(+), 425 deletions(-) diff --git a/tutorial/draw_ef.ipynb b/tutorial/draw_ef.ipynb index 7b37bc5..df9e6ac 100644 --- a/tutorial/draw_ef.ipynb +++ b/tutorial/draw_ef.ipynb @@ -19,7 +19,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -135,221 +135,221 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -374,7 +374,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -406,86 +406,86 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -510,23 +510,23 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -581,198 +581,198 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -797,7 +797,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -816,34 +816,34 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -852,157 +852,147 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -1027,142 +1017,142 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -1187,7 +1177,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -1231,93 +1221,93 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -1336,6 +1326,201 @@ { "cell_type": "code", "execution_count": 8, + "id": "4b561b58", + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "draw_tree(\"../games/x1.ef\", color_scheme=\"gambit\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "43054b3e", + "metadata": {}, + "outputs": [ + { + "ename": "KeyboardInterrupt", + "evalue": "", + "output_type": "error", + "traceback": [ + "\u001b[31m---------------------------------------------------------------------------\u001b[39m", + "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)", + "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mdraw_tree\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m../games/x1.ef\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcolor_scheme\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mdistinctipy\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/projects/draw_tree/src/draw_tree/core.py:1845\u001b[39m, in \u001b[36mdraw_tree\u001b[39m\u001b[34m(game, save_to, scale_factor, level_scaling, sublevel_scaling, width_scaling, hide_action_labels, shared_terminal_depth, show_grid, color_scheme, edge_thickness, action_label_position)\u001b[39m\n\u001b[32m 1843\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m jpt_loaded:\n\u001b[32m 1844\u001b[39m ip.run_line_magic(\u001b[33m\"\u001b[39m\u001b[33mload_ext\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mjupyter_tikz\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m1845\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mip\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_cell_magic\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtikz\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtikz_code\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1846\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1847\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m tikz_code\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/IPython/core/interactiveshell.py:2565\u001b[39m, in \u001b[36mInteractiveShell.run_cell_magic\u001b[39m\u001b[34m(self, magic_name, line, cell)\u001b[39m\n\u001b[32m 2563\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m.builtin_trap:\n\u001b[32m 2564\u001b[39m args = (magic_arg_s, cell)\n\u001b[32m-> \u001b[39m\u001b[32m2565\u001b[39m result = \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2567\u001b[39m \u001b[38;5;66;03m# The code below prevents the output from being displayed\u001b[39;00m\n\u001b[32m 2568\u001b[39m \u001b[38;5;66;03m# when using magics with decorator @output_can_be_silenced\u001b[39;00m\n\u001b[32m 2569\u001b[39m \u001b[38;5;66;03m# when the last Python token in the expression is a ';'.\u001b[39;00m\n\u001b[32m 2570\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mgetattr\u001b[39m(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, \u001b[38;5;28;01mFalse\u001b[39;00m):\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/jupyter_tikz/jupyter_tikz.py:746\u001b[39m, in \u001b[36mTikZMagics.tikz\u001b[39m\u001b[34m(self, line, cell, local_ns)\u001b[39m\n\u001b[32m 744\u001b[39m image = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 745\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m.args[\u001b[33m\"\u001b[39m\u001b[33mno_compile\u001b[39m\u001b[33m\"\u001b[39m]:\n\u001b[32m--> \u001b[39m\u001b[32m746\u001b[39m image = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mtex_obj\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_latex\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 747\u001b[39m \u001b[43m \u001b[49m\u001b[43mtex_program\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtex_program\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 748\u001b[39m \u001b[43m \u001b[49m\u001b[43mtex_args\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtex_args\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 749\u001b[39m \u001b[43m \u001b[49m\u001b[43mrasterize\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mrasterize\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 750\u001b[39m \u001b[43m \u001b[49m\u001b[43mfull_err\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mfull_err\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 751\u001b[39m \u001b[43m \u001b[49m\u001b[43mkeep_temp\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mkeep_temp\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 752\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_tikz\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_tikz\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 753\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_tex\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_tex\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 754\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_pdf\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_pdf\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 755\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_image\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_image\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 756\u001b[39m \u001b[43m \u001b[49m\u001b[43mdpi\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mdpi\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 757\u001b[39m \u001b[43m \u001b[49m\u001b[43mgrayscale\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mgray\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 758\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 759\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m image \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 760\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/jupyter_tikz/jupyter_tikz.py:194\u001b[39m, in \u001b[36mTexDocument.run_latex\u001b[39m\u001b[34m(self, tex_program, tex_args, rasterize, full_err, keep_temp, save_image, dpi, grayscale, save_tex, save_tikz, save_pdf)\u001b[39m\n\u001b[32m 191\u001b[39m tex_command += \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtex_args\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 192\u001b[39m tex_command += \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtex_path\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m194\u001b[39m res = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_run_command\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtex_command\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfull_err\u001b[49m\u001b[43m=\u001b[49m\u001b[43mfull_err\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 195\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m res != \u001b[32m0\u001b[39m:\n\u001b[32m 196\u001b[39m \u001b[38;5;28mself\u001b[39m._clearup_latex_garbage(keep_temp)\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/jupyter_tikz/jupyter_tikz.py:111\u001b[39m, in \u001b[36mTexDocument._run_command\u001b[39m\u001b[34m(self, command, full_err, **kwargs)\u001b[39m\n\u001b[32m 109\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_run_command\u001b[39m(\u001b[38;5;28mself\u001b[39m, command: \u001b[38;5;28mstr\u001b[39m, full_err: \u001b[38;5;28mbool\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m, **kwargs) -> \u001b[38;5;28mint\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m111\u001b[39m result = \u001b[43msubprocess\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 112\u001b[39m \u001b[43m \u001b[49m\u001b[43mcommand\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 113\u001b[39m \u001b[43m \u001b[49m\u001b[43mshell\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 114\u001b[39m \u001b[43m \u001b[49m\u001b[43mcapture_output\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 115\u001b[39m \u001b[43m \u001b[49m\u001b[43mtext\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 116\u001b[39m \u001b[43m \u001b[49m\u001b[43mcheck\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 117\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 118\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 119\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m result.returncode != \u001b[32m0\u001b[39m:\n\u001b[32m 120\u001b[39m err_msg = result.stderr \u001b[38;5;28;01mif\u001b[39;00m result.stderr \u001b[38;5;28;01melse\u001b[39;00m result.stdout\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/subprocess.py:556\u001b[39m, in \u001b[36mrun\u001b[39m\u001b[34m(input, capture_output, timeout, check, *popenargs, **kwargs)\u001b[39m\n\u001b[32m 554\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m Popen(*popenargs, **kwargs) \u001b[38;5;28;01mas\u001b[39;00m process:\n\u001b[32m 555\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m556\u001b[39m stdout, stderr = \u001b[43mprocess\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcommunicate\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 557\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m TimeoutExpired \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 558\u001b[39m process.kill()\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/subprocess.py:1222\u001b[39m, in \u001b[36mPopen.communicate\u001b[39m\u001b[34m(self, input, timeout)\u001b[39m\n\u001b[32m 1219\u001b[39m endtime = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 1221\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1222\u001b[39m stdout, stderr = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_communicate\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mendtime\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1223\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyboardInterrupt\u001b[39;00m:\n\u001b[32m 1224\u001b[39m \u001b[38;5;66;03m# https://bugs.python.org/issue25942\u001b[39;00m\n\u001b[32m 1225\u001b[39m \u001b[38;5;66;03m# See the detailed comment in .wait().\u001b[39;00m\n\u001b[32m 1226\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m timeout \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/subprocess.py:2128\u001b[39m, in \u001b[36mPopen._communicate\u001b[39m\u001b[34m(self, input, endtime, orig_timeout)\u001b[39m\n\u001b[32m 2121\u001b[39m \u001b[38;5;28mself\u001b[39m._check_timeout(endtime, orig_timeout,\n\u001b[32m 2122\u001b[39m stdout, stderr,\n\u001b[32m 2123\u001b[39m skip_check_and_raise=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 2124\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m( \u001b[38;5;66;03m# Impossible :)\u001b[39;00m\n\u001b[32m 2125\u001b[39m \u001b[33m'\u001b[39m\u001b[33m_check_timeout(..., skip_check_and_raise=True) \u001b[39m\u001b[33m'\u001b[39m\n\u001b[32m 2126\u001b[39m \u001b[33m'\u001b[39m\u001b[33mfailed to raise TimeoutExpired.\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m2128\u001b[39m ready = \u001b[43mselector\u001b[49m\u001b[43m.\u001b[49m\u001b[43mselect\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2129\u001b[39m \u001b[38;5;28mself\u001b[39m._check_timeout(endtime, orig_timeout, stdout, stderr)\n\u001b[32m 2131\u001b[39m \u001b[38;5;66;03m# XXX Rewrite these to use non-blocking I/O on the file\u001b[39;00m\n\u001b[32m 2132\u001b[39m \u001b[38;5;66;03m# objects; they are no longer using C stdio!\u001b[39;00m\n", + "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/selectors.py:398\u001b[39m, in \u001b[36m_PollLikeSelector.select\u001b[39m\u001b[34m(self, timeout)\u001b[39m\n\u001b[32m 396\u001b[39m ready = []\n\u001b[32m 397\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m398\u001b[39m fd_event_list = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_selector\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpoll\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 399\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mInterruptedError\u001b[39;00m:\n\u001b[32m 400\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m ready\n", + "\u001b[31mKeyboardInterrupt\u001b[39m: " + ] + } + ], + "source": [ + "draw_tree(\"../games/x1.ef\", color_scheme=\"distinctipy\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42b90e13", + "metadata": {}, + "outputs": [], + "source": [ + "draw_tree(\"../games/x1.ef\", color_scheme=\"colorblind\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, "id": "168a43f8-8609-4610-9cdd-474a8086bcc0", "metadata": {}, "outputs": [ @@ -1356,7 +1541,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "acef45b1-1e1d-40b4-94f5-fabad7bb902a", "metadata": {}, "outputs": [ @@ -1377,7 +1562,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "82ea8094-3438-4b2c-9a76-c757e1680cda", "metadata": {}, "outputs": [ @@ -1398,7 +1583,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "fa1ae230-0ee2-4a2b-9fe1-e766898fc0a6", "metadata": {}, "outputs": [ @@ -1419,7 +1604,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "b08d9255", "metadata": {}, "outputs": [ From f0c52b76577228a2adec58f8679f8df59ee9d9ff Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 26 Mar 2026 13:12:17 +0000 Subject: [PATCH 5/6] refactor: Simplify LaTeX color definitions by using direct color names instead of macros, update docstrings --- src/draw_tree/core.py | 53 +- tutorial/draw_ef.ipynb | 1313 ++++++++++++++++++++++++---------------- 2 files changed, 818 insertions(+), 548 deletions(-) diff --git a/src/draw_tree/core.py b/src/draw_tree/core.py index 30ae72a..621a5f6 100644 --- a/src/draw_tree/core.py +++ b/src/draw_tree/core.py @@ -84,7 +84,7 @@ def get_player_color(player: int, color_scheme: str = "default") -> str: """ - Get the TeX color macro name for a given player number. + Get the TeX color name for a given player number. Args: player: Player number (0 for chance, 1-6 for regular players with @@ -94,20 +94,20 @@ def get_player_color(player: int, color_scheme: str = "default") -> str: "distinctipy", or "colorblind". Returns: - TeX color macro name for the player, or "black" as fallback. + TeX color name for the player, or "black" as fallback. Raises: ValueError: If the "gambit" scheme is used with more than 6 players. """ if color_scheme == "gambit": color_map = { - 0: "\\chancecolor", - 1: "\\playeronecolor", - 2: "\\playertwocolor", - 3: "\\playerthreecolor", - 4: "\\playerfourcolor", - 5: "\\playerfivecolor", - 6: "\\playersixcolor", + 0: "chancecolor", + 1: "playeronecolor", + 2: "playertwocolor", + 3: "playerthreecolor", + 4: "playerfourcolor", + 5: "playerfivecolor", + 6: "playersixcolor", } if player < 0: return "black" # no player assigned yet @@ -121,20 +121,20 @@ def get_player_color(player: int, color_scheme: str = "default") -> str: elif color_scheme in ("distinctipy", "colorblind"): if player == 0: - return "\\chancecolor" + return "chancecolor" elif player > 0: - return f"\\player{player}color" + return f"p{player}rgb" return "black" def color_definitions(color_scheme: str = "default", num_players: int = 6) -> list[str]: """ - Generate LaTeX color macro definitions for game tree players. + Generate LaTeX color definitions for game tree players. - Produces ``\\definecolor`` and ``\\newcommand`` lines that are injected - into the TikZ preamble so that player-color macros (e.g. - ``\\playeronecolor``, ``\\player7color``) resolve correctly. + Produces ``\\definecolor`` lines that are injected into the TikZ preamble + so that player-color names (e.g. ``playeronecolor``, ``p7rgb``) + resolve correctly. Args: color_scheme: One of "default", "gambit", "distinctipy", or @@ -147,20 +147,18 @@ def color_definitions(color_scheme: str = "default", num_players: int = 6) -> li """ # Chance color is shared across all schemes defs = [ - "\\definecolor{chancecolorrgb}{RGB}{117,145,56}", - "\\newcommand\\chancecolor{chancecolorrgb}", + "\\definecolor{chancecolor}{RGB}{117,145,56}", ] if color_scheme == "gambit": defs.extend( [ - "\\definecolor{gambitredrgb}{RGB}{234,51,35}", - "\\newcommand\\playeronecolor{gambitredrgb}", - "\\newcommand\\playertwocolor{blue}", - "\\newcommand\\playerthreecolor{orange}", - "\\newcommand\\playerfourcolor{purple}", - "\\newcommand\\playerfivecolor{cyan}", - "\\newcommand\\playersixcolor{magenta}", + "\\definecolor{playeronecolor}{RGB}{234,51,35}", + "\\colorlet{playertwocolor}{blue}", + "\\colorlet{playerthreecolor}{orange}", + "\\colorlet{playerfourcolor}{purple}", + "\\colorlet{playerfivecolor}{cyan}", + "\\colorlet{playersixcolor}{magenta}", ] ) @@ -178,12 +176,7 @@ def color_definitions(color_scheme: str = "default", num_players: int = 6) -> li for i, color in enumerate(colors): r, g, b = [int(c * 255) for c in color] p_num = i + 1 - defs.extend( - [ - f"\\definecolor{{p{p_num}rgb}}{{RGB}}{{{r},{g},{b}}}", - f"\\newcommand\\player{p_num}color{{p{p_num}rgb}}", - ] - ) + defs.append(f"\\definecolor{{p{p_num}rgb}}{{RGB}}{{{r},{g},{b}}}") except Exception as e: print(f"Warning: Failed to generate {color_scheme} colors: {e}") diff --git a/tutorial/draw_ef.ipynb b/tutorial/draw_ef.ipynb index df9e6ac..afaad58 100644 --- a/tutorial/draw_ef.ipynb +++ b/tutorial/draw_ef.ipynb @@ -19,7 +19,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -135,221 +135,221 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -374,118 +374,133 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -510,269 +525,259 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -797,7 +802,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -871,128 +876,128 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -1017,142 +1022,137 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -1177,137 +1177,147 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "" ], "text/plain": [ @@ -1332,7 +1342,7 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", @@ -1376,91 +1386,91 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "" ], @@ -1484,24 +1494,147 @@ "metadata": {}, "outputs": [ { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[9]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[43mdraw_tree\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m../games/x1.ef\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mcolor_scheme\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mdistinctipy\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/projects/draw_tree/src/draw_tree/core.py:1845\u001b[39m, in \u001b[36mdraw_tree\u001b[39m\u001b[34m(game, save_to, scale_factor, level_scaling, sublevel_scaling, width_scaling, hide_action_labels, shared_terminal_depth, show_grid, color_scheme, edge_thickness, action_label_position)\u001b[39m\n\u001b[32m 1843\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m jpt_loaded:\n\u001b[32m 1844\u001b[39m ip.run_line_magic(\u001b[33m\"\u001b[39m\u001b[33mload_ext\u001b[39m\u001b[33m\"\u001b[39m, \u001b[33m\"\u001b[39m\u001b[33mjupyter_tikz\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m1845\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mip\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_cell_magic\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtikz\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtikz_code\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1846\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[32m 1847\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m tikz_code\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/IPython/core/interactiveshell.py:2565\u001b[39m, in \u001b[36mInteractiveShell.run_cell_magic\u001b[39m\u001b[34m(self, magic_name, line, cell)\u001b[39m\n\u001b[32m 2563\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28mself\u001b[39m.builtin_trap:\n\u001b[32m 2564\u001b[39m args = (magic_arg_s, cell)\n\u001b[32m-> \u001b[39m\u001b[32m2565\u001b[39m result = \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43m*\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2567\u001b[39m \u001b[38;5;66;03m# The code below prevents the output from being displayed\u001b[39;00m\n\u001b[32m 2568\u001b[39m \u001b[38;5;66;03m# when using magics with decorator @output_can_be_silenced\u001b[39;00m\n\u001b[32m 2569\u001b[39m \u001b[38;5;66;03m# when the last Python token in the expression is a ';'.\u001b[39;00m\n\u001b[32m 2570\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mgetattr\u001b[39m(fn, magic.MAGIC_OUTPUT_CAN_BE_SILENCED, \u001b[38;5;28;01mFalse\u001b[39;00m):\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/jupyter_tikz/jupyter_tikz.py:746\u001b[39m, in \u001b[36mTikZMagics.tikz\u001b[39m\u001b[34m(self, line, cell, local_ns)\u001b[39m\n\u001b[32m 744\u001b[39m image = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 745\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28mself\u001b[39m.args[\u001b[33m\"\u001b[39m\u001b[33mno_compile\u001b[39m\u001b[33m\"\u001b[39m]:\n\u001b[32m--> \u001b[39m\u001b[32m746\u001b[39m image = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43mtex_obj\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun_latex\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 747\u001b[39m \u001b[43m \u001b[49m\u001b[43mtex_program\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtex_program\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 748\u001b[39m \u001b[43m \u001b[49m\u001b[43mtex_args\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mtex_args\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 749\u001b[39m \u001b[43m \u001b[49m\u001b[43mrasterize\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mrasterize\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 750\u001b[39m \u001b[43m \u001b[49m\u001b[43mfull_err\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mfull_err\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 751\u001b[39m \u001b[43m \u001b[49m\u001b[43mkeep_temp\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mkeep_temp\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 752\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_tikz\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_tikz\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 753\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_tex\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_tex\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 754\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_pdf\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_pdf\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 755\u001b[39m \u001b[43m \u001b[49m\u001b[43msave_image\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43msave_image\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 756\u001b[39m \u001b[43m \u001b[49m\u001b[43mdpi\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mdpi\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 757\u001b[39m \u001b[43m \u001b[49m\u001b[43mgrayscale\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43margs\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mgray\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 758\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 759\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m image \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[32m 760\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/jupyter_tikz/jupyter_tikz.py:194\u001b[39m, in \u001b[36mTexDocument.run_latex\u001b[39m\u001b[34m(self, tex_program, tex_args, rasterize, full_err, keep_temp, save_image, dpi, grayscale, save_tex, save_tikz, save_pdf)\u001b[39m\n\u001b[32m 191\u001b[39m tex_command += \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtex_args\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m 192\u001b[39m tex_command += \u001b[33mf\u001b[39m\u001b[33m\"\u001b[39m\u001b[33m \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mtex_path\u001b[38;5;132;01m}\u001b[39;00m\u001b[33m\"\u001b[39m\n\u001b[32m--> \u001b[39m\u001b[32m194\u001b[39m res = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_run_command\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtex_command\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mfull_err\u001b[49m\u001b[43m=\u001b[49m\u001b[43mfull_err\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 195\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m res != \u001b[32m0\u001b[39m:\n\u001b[32m 196\u001b[39m \u001b[38;5;28mself\u001b[39m._clearup_latex_garbage(keep_temp)\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/site-packages/jupyter_tikz/jupyter_tikz.py:111\u001b[39m, in \u001b[36mTexDocument._run_command\u001b[39m\u001b[34m(self, command, full_err, **kwargs)\u001b[39m\n\u001b[32m 109\u001b[39m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34m_run_command\u001b[39m(\u001b[38;5;28mself\u001b[39m, command: \u001b[38;5;28mstr\u001b[39m, full_err: \u001b[38;5;28mbool\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m, **kwargs) -> \u001b[38;5;28mint\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m111\u001b[39m result = \u001b[43msubprocess\u001b[49m\u001b[43m.\u001b[49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 112\u001b[39m \u001b[43m \u001b[49m\u001b[43mcommand\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 113\u001b[39m \u001b[43m \u001b[49m\u001b[43mshell\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 114\u001b[39m \u001b[43m \u001b[49m\u001b[43mcapture_output\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 115\u001b[39m \u001b[43m \u001b[49m\u001b[43mtext\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 116\u001b[39m \u001b[43m \u001b[49m\u001b[43mcheck\u001b[49m\u001b[43m=\u001b[49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\n\u001b[32m 117\u001b[39m \u001b[43m \u001b[49m\u001b[43m*\u001b[49m\u001b[43m*\u001b[49m\u001b[43mkwargs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 118\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 119\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m result.returncode != \u001b[32m0\u001b[39m:\n\u001b[32m 120\u001b[39m err_msg = result.stderr \u001b[38;5;28;01mif\u001b[39;00m result.stderr \u001b[38;5;28;01melse\u001b[39;00m result.stdout\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/subprocess.py:556\u001b[39m, in \u001b[36mrun\u001b[39m\u001b[34m(input, capture_output, timeout, check, *popenargs, **kwargs)\u001b[39m\n\u001b[32m 554\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m Popen(*popenargs, **kwargs) \u001b[38;5;28;01mas\u001b[39;00m process:\n\u001b[32m 555\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m556\u001b[39m stdout, stderr = \u001b[43mprocess\u001b[49m\u001b[43m.\u001b[49m\u001b[43mcommunicate\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m=\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 557\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m TimeoutExpired \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[32m 558\u001b[39m process.kill()\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/subprocess.py:1222\u001b[39m, in \u001b[36mPopen.communicate\u001b[39m\u001b[34m(self, input, timeout)\u001b[39m\n\u001b[32m 1219\u001b[39m endtime = \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[32m 1221\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m-> \u001b[39m\u001b[32m1222\u001b[39m stdout, stderr = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_communicate\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43minput\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mendtime\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1223\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mKeyboardInterrupt\u001b[39;00m:\n\u001b[32m 1224\u001b[39m \u001b[38;5;66;03m# https://bugs.python.org/issue25942\u001b[39;00m\n\u001b[32m 1225\u001b[39m \u001b[38;5;66;03m# See the detailed comment in .wait().\u001b[39;00m\n\u001b[32m 1226\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m timeout \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/subprocess.py:2128\u001b[39m, in \u001b[36mPopen._communicate\u001b[39m\u001b[34m(self, input, endtime, orig_timeout)\u001b[39m\n\u001b[32m 2121\u001b[39m \u001b[38;5;28mself\u001b[39m._check_timeout(endtime, orig_timeout,\n\u001b[32m 2122\u001b[39m stdout, stderr,\n\u001b[32m 2123\u001b[39m skip_check_and_raise=\u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[32m 2124\u001b[39m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mRuntimeError\u001b[39;00m( \u001b[38;5;66;03m# Impossible :)\u001b[39;00m\n\u001b[32m 2125\u001b[39m \u001b[33m'\u001b[39m\u001b[33m_check_timeout(..., skip_check_and_raise=True) \u001b[39m\u001b[33m'\u001b[39m\n\u001b[32m 2126\u001b[39m \u001b[33m'\u001b[39m\u001b[33mfailed to raise TimeoutExpired.\u001b[39m\u001b[33m'\u001b[39m)\n\u001b[32m-> \u001b[39m\u001b[32m2128\u001b[39m ready = \u001b[43mselector\u001b[49m\u001b[43m.\u001b[49m\u001b[43mselect\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 2129\u001b[39m \u001b[38;5;28mself\u001b[39m._check_timeout(endtime, orig_timeout, stdout, stderr)\n\u001b[32m 2131\u001b[39m \u001b[38;5;66;03m# XXX Rewrite these to use non-blocking I/O on the file\u001b[39;00m\n\u001b[32m 2132\u001b[39m \u001b[38;5;66;03m# objects; they are no longer using C stdio!\u001b[39;00m\n", - "\u001b[36mFile \u001b[39m\u001b[32m~/anaconda3/envs/gambitvenv313/lib/python3.13/selectors.py:398\u001b[39m, in \u001b[36m_PollLikeSelector.select\u001b[39m\u001b[34m(self, timeout)\u001b[39m\n\u001b[32m 396\u001b[39m ready = []\n\u001b[32m 397\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m--> \u001b[39m\u001b[32m398\u001b[39m fd_event_list = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_selector\u001b[49m\u001b[43m.\u001b[49m\u001b[43mpoll\u001b[49m\u001b[43m(\u001b[49m\u001b[43mtimeout\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 399\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mInterruptedError\u001b[39;00m:\n\u001b[32m 400\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m ready\n", - "\u001b[31mKeyboardInterrupt\u001b[39m: " - ] + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ @@ -1510,17 +1643,161 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "42b90e13", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "draw_tree(\"../games/x1.ef\", color_scheme=\"colorblind\")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "168a43f8-8609-4610-9cdd-474a8086bcc0", "metadata": {}, "outputs": [ @@ -1530,7 +1807,7 @@ "'/Users/echalstrey/projects/draw_tree/tutorial/x1.tex'" ] }, - "execution_count": 8, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -1541,7 +1818,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "acef45b1-1e1d-40b4-94f5-fabad7bb902a", "metadata": {}, "outputs": [ @@ -1551,7 +1828,7 @@ "'/Users/echalstrey/projects/draw_tree/tutorial/custom.tex'" ] }, - "execution_count": 9, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -1562,7 +1839,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "82ea8094-3438-4b2c-9a76-c757e1680cda", "metadata": {}, "outputs": [ @@ -1572,7 +1849,7 @@ "'/Users/echalstrey/projects/draw_tree/tutorial/x1.pdf'" ] }, - "execution_count": 10, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -1583,7 +1860,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "fa1ae230-0ee2-4a2b-9fe1-e766898fc0a6", "metadata": {}, "outputs": [ @@ -1593,7 +1870,7 @@ "'/Users/echalstrey/projects/draw_tree/tutorial/x1.png'" ] }, - "execution_count": 11, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -1604,7 +1881,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "b08d9255", "metadata": {}, "outputs": [ @@ -1614,7 +1891,7 @@ "'/Users/echalstrey/projects/draw_tree/tutorial/x1.svg'" ] }, - "execution_count": 2, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } From 90d330fc3ab7df12a1c7f480ccf019e89cac5e00 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 26 Mar 2026 13:37:24 +0000 Subject: [PATCH 6/6] Don't assume chancecolor in result --- tests/test_drawtree.py | 315 +++++++++++++++++++++++++++++------------ 1 file changed, 224 insertions(+), 91 deletions(-) diff --git a/tests/test_drawtree.py b/tests/test_drawtree.py index 7d97b89..5a058b0 100644 --- a/tests/test_drawtree.py +++ b/tests/test_drawtree.py @@ -57,6 +57,7 @@ def test_aeq(self): def test_degrees(self): """Test angle calculation in degrees.""" import math + assert abs(draw_tree.degrees([1, 0]) - 0) < 1e-6 assert abs(draw_tree.degrees([0, 1]) - 90) < 1e-6 assert abs(draw_tree.degrees([-1, 0]) - 180) < 1e-6 @@ -103,7 +104,7 @@ def test_setnodeid(self): def test_cleannodeid(self): """Test node ID standardization.""" # Mock the error function to avoid output during tests - with patch('draw_tree.core.error'): + with patch("draw_tree.core.error"): assert draw_tree.cleannodeid("1,test") == "1,test" assert draw_tree.cleannodeid("0.5,node") == "0.5,node" # Test error cases @@ -117,7 +118,7 @@ class TestOutputRoutines: def test_outall(self): """Test output stream printing.""" test_stream = ["line1", "line2", "line3"] - with patch('builtins.print') as mock_print: + with patch("builtins.print") as mock_print: draw_tree.outall(test_stream) assert mock_print.call_count == 3 @@ -129,7 +130,7 @@ def test_outs(self): def test_comment(self): """Test comment output.""" - with patch('draw_tree.core.outs') as mock_outs: + with patch("draw_tree.core.outs") as mock_outs: draw_tree.comment("test comment") mock_outs.assert_called_with("%% test comment") @@ -139,7 +140,7 @@ class TestFileOperations: def test_readfile(self): """Test file reading with line processing.""" - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f: + with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f: f.write("line 1\n") f.write(" line 2 with spaces \n") f.write("\n") # Empty line @@ -182,7 +183,7 @@ def test_commandline_grid(self): def test_commandline_file(self): """Test file argument processing.""" - original_ef_file = getattr(draw_tree, 'ef_file', None) + original_ef_file = getattr(draw_tree, "ef_file", None) try: draw_tree.commandline(["draw_tree.py", "test_game.ef"]) assert draw_tree.ef_file == "test_game.ef" @@ -194,7 +195,7 @@ def test_commandline_invalid_scale(self): """Test invalid scale argument handling.""" original_scale = draw_tree.scale try: - with patch('draw_tree.core.outs') as mock_outs: + with patch("draw_tree.core.outs") as mock_outs: draw_tree.commandline(["draw_tree.py", "scale=invalid"]) # Should output error message mock_outs.assert_called() @@ -210,7 +211,7 @@ class TestPlayerHandling: def test_player_basic(self): """Test basic player parsing.""" words = ["player", "1"] - with patch('draw_tree.core.defout'): + with patch("draw_tree.core.defout"): p, advance = draw_tree.player(words) assert p == 1 assert advance == 2 @@ -218,7 +219,7 @@ def test_player_basic(self): def test_player_with_name(self): """Test player parsing with name.""" words = ["player", "2", "name", "Alice"] - with patch('draw_tree.core.defout'): + with patch("draw_tree.core.defout"): p, advance = draw_tree.player(words) assert p == 2 assert advance == 4 @@ -227,7 +228,7 @@ def test_player_with_name(self): def test_player_invalid_number(self): """Test player parsing with invalid number.""" words = ["player", "invalid"] - with patch('draw_tree.core.error') as mock_error: + with patch("draw_tree.core.error") as mock_error: p, advance = draw_tree.player(words) assert p == -1 mock_error.assert_called() @@ -261,7 +262,9 @@ class TestDrawTreeFunction: def test_draw_tree_basic(self): """Test basic draw_tree functionality.""" # Create a simple .ef file for testing - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\n") ef_file.write("level 0 node root player 1\n") ef_file.write("level 1 node left from 0,root player 2 payoffs 1 2\n") @@ -269,7 +272,7 @@ def test_draw_tree_basic(self): try: result = draw_tree.generate_tikz(ef_file_path) - + # Verify the result contains expected components assert isinstance(result, str) assert len(result) > 0 @@ -278,12 +281,11 @@ def test_draw_tree_basic(self): assert "\\begin{tikzpicture}" in result assert "\\end{tikzpicture}" in result # Check for built-in macro definitions - assert "\\newcommand\\chancecolor" in result assert "\\newdimen\\ndiam" in result assert "\\ndiam1.5mm" in result assert "\\newdimen\\paydown" in result assert "\\paydown2.5ex" in result - + finally: os.unlink(ef_file_path) @@ -292,7 +294,9 @@ def test_draw_tree_calls_ipython_magic_when_available(self): extension if needed and call the tikz cell magic with the generated code. """ # Create a simple .ef file for testing - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\n") ef_file.write("level 0 node root player 1\n") ef_file_path = ef_file.name @@ -318,9 +322,9 @@ def run_cell_magic(self, magic_name, args, code): try: # Case 1: extension already loaded - em = DummyEM(loaded={'jupyter_tikz'}) + em = DummyEM(loaded={"jupyter_tikz"}) ip = DummyIP(em) - with patch('draw_tree.core.get_ipython', return_value=ip): + with patch("draw_tree.core.get_ipython", return_value=ip): res = draw_tree.draw_tree(ef_file_path) # Should call run_cell_magic and return its value assert res == "MAGIC-RESULT" @@ -328,11 +332,11 @@ def run_cell_magic(self, magic_name, args, code): # Case 2: extension not loaded -> run_line_magic should be called em2 = DummyEM(loaded=set()) ip2 = DummyIP(em2) - with patch('draw_tree.core.get_ipython', return_value=ip2): + with patch("draw_tree.core.get_ipython", return_value=ip2): res2 = draw_tree.draw_tree(ef_file_path) assert res2 == "MAGIC-RESULT" # run_line_magic should have been called to load the extension - assert ('load_ext', 'jupyter_tikz') in ip2._loaded_magics + assert ("load_ext", "jupyter_tikz") in ip2._loaded_magics finally: os.unlink(ef_file_path) @@ -340,7 +344,9 @@ def run_cell_magic(self, magic_name, args, code): def test_draw_tree_with_options(self): """Test draw_tree with different options.""" # Create a simple .ef file for testing - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\n") ef_file.write("level 0 node root player 1\n") ef_file_path = ef_file.name @@ -353,11 +359,11 @@ def test_draw_tree_with_options(self): # Test with grid result_grid = draw_tree.generate_tikz(ef_file_path, show_grid=True) assert "\\draw [help lines, color=green]" in result_grid - + # Test without grid (default) result_no_grid = draw_tree.generate_tikz(ef_file_path, show_grid=False) assert "% \\draw [help lines, color=green]" in result_no_grid - + finally: os.unlink(ef_file_path) @@ -368,7 +374,9 @@ def test_draw_tree_missing_files(self): draw_tree.generate_tikz("nonexistent.ef") # Test with valid .ef file (should work with built-in macros) - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name @@ -376,7 +384,6 @@ def test_draw_tree_missing_files(self): result = draw_tree.generate_tikz(ef_file_path) # Should work with built-in macros assert "\\begin{tikzpicture}" in result - assert "\\newcommand\\chancecolor" in result finally: os.unlink(ef_file_path) @@ -389,14 +396,16 @@ def test_generate_png_missing_file(self): with pytest.raises(FileNotFoundError): draw_tree.generate_png("nonexistent.ef") - @patch('draw_tree.core.subprocess.run') + @patch("draw_tree.core.subprocess.run") def test_generate_png_pdflatex_not_found(self, mock_run): """Test PNG generation when pdflatex is not available.""" # Mock pdflatex not being found mock_run.side_effect = FileNotFoundError("pdflatex not found") - + # Create a temporary .ef file - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name @@ -409,15 +418,17 @@ def test_generate_png_pdflatex_not_found(self, mock_run): def test_generate_png_default_parameters(self): """Test PNG generation with default parameters.""" # Create a temporary .ef file - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: # Mock both pdflatex and convert being unavailable to test error handling - with patch('draw_tree.core.subprocess.run') as mock_run: + with patch("draw_tree.core.subprocess.run") as mock_run: mock_run.side_effect = FileNotFoundError("Command not found") - + with pytest.raises(RuntimeError): draw_tree.generate_png(ef_file_path) finally: @@ -426,15 +437,17 @@ def test_generate_png_default_parameters(self): def test_generate_png_custom_dpi(self): """Test PNG generation with custom DPI setting.""" # Create a temporary .ef file - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: # Test that custom DPI is handled properly - with patch('draw_tree.core.subprocess.run') as mock_run: + with patch("draw_tree.core.subprocess.run") as mock_run: mock_run.side_effect = FileNotFoundError("Command not found") - + with pytest.raises(RuntimeError): draw_tree.generate_png(ef_file_path, dpi=600) finally: @@ -443,14 +456,16 @@ def test_generate_png_custom_dpi(self): def test_generate_png_output_filename(self): """Test PNG generation with custom output filename.""" # Create a temporary .ef file - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: - with patch('draw_tree.core.subprocess.run') as mock_run: + with patch("draw_tree.core.subprocess.run") as mock_run: mock_run.side_effect = FileNotFoundError("Command not found") - + with pytest.raises(RuntimeError): draw_tree.generate_png(ef_file_path, save_to="custom_name.png") finally: @@ -464,10 +479,12 @@ def test_generate_svg_missing_file(self): with pytest.raises(FileNotFoundError): draw_tree.generate_svg("nonexistent.ef") - @patch('draw_tree.core.subprocess.run') + @patch("draw_tree.core.subprocess.run") def test_generate_svg_pdflatex_not_found(self, mock_run): mock_run.side_effect = FileNotFoundError("pdflatex not found") - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name @@ -478,12 +495,14 @@ def test_generate_svg_pdflatex_not_found(self, mock_run): os.unlink(ef_file_path) def test_generate_svg_default_parameters(self): - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: - with patch('draw_tree.core.subprocess.run') as mock_run: + with patch("draw_tree.core.subprocess.run") as mock_run: mock_run.side_effect = FileNotFoundError("Command not found") with pytest.raises(RuntimeError): draw_tree.generate_svg(ef_file_path) @@ -491,12 +510,14 @@ def test_generate_svg_default_parameters(self): os.unlink(ef_file_path) def test_generate_svg_output_filename(self): - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: - with patch('draw_tree.core.subprocess.run') as mock_run: + with patch("draw_tree.core.subprocess.run") as mock_run: mock_run.side_effect = FileNotFoundError("Command not found") with pytest.raises(RuntimeError): draw_tree.generate_svg(ef_file_path, save_to="custom_name.svg") @@ -515,77 +536,85 @@ def test_generate_tex_missing_file(self): def test_generate_tex_default_parameters(self): """Test LaTeX generation with default parameters.""" # Create a temporary .ef file - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: # Generate LaTeX file tex_path = draw_tree.generate_tex(ef_file_path) - + # Verify the file was created and contains expected content assert os.path.exists(tex_path) - - with open(tex_path, 'r') as f: + + with open(tex_path, "r") as f: content = f.read() - + # Check for LaTeX document structure assert "\\documentclass[tikz,border=10pt]{standalone}" in content assert "\\begin{document}" in content assert "\\end{document}" in content assert "\\begin{tikzpicture}" in content assert "\\end{tikzpicture}" in content - + # Clean up generated file os.unlink(tex_path) - + finally: os.unlink(ef_file_path) def test_generate_tex_custom_filename(self): """Test LaTeX generation with custom output filename.""" # Create a temporary .ef file - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: custom_filename = "custom_output.tex" tex_path = draw_tree.generate_tex(ef_file_path, save_to=custom_filename) - + # Verify the custom filename was used assert tex_path.endswith(custom_filename) assert os.path.exists(custom_filename) - + # Clean up os.unlink(custom_filename) - + finally: os.unlink(ef_file_path) def test_generate_tex_with_scale_and_grid(self): """Test LaTeX generation with scale and grid options.""" # Create a temporary .ef file - with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.ef') as ef_file: + with tempfile.NamedTemporaryFile( + mode="w", delete=False, suffix=".ef" + ) as ef_file: ef_file.write("player 1\nlevel 0 node root player 1\n") ef_file_path = ef_file.name try: - tex_path = draw_tree.generate_tex(ef_file_path, scale_factor=2.0, show_grid=True) - + tex_path = draw_tree.generate_tex( + ef_file_path, scale_factor=2.0, show_grid=True + ) + # Verify the file was created assert os.path.exists(tex_path) - - with open(tex_path, 'r') as f: + + with open(tex_path, "r") as f: content = f.read() - + # Check for scale and grid options assert "scale=1.6" in content # 2 * 0.8 assert "\\draw [help lines, color=green]" in content - + # Clean up os.unlink(tex_path) - + finally: os.unlink(ef_file_path) @@ -595,8 +624,16 @@ class TestCommandlineArguments: def test_commandline_png_flag(self): """Test --png flag parsing.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--png']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline(["draw_tree.py", "test.ef", "--png"]) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "png" assert not pdf_requested assert png_requested @@ -607,8 +644,18 @@ def test_commandline_png_flag(self): def test_commandline_png_with_dpi(self): """Test --png flag with --dpi option.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--png', '--dpi=600']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline( + ["draw_tree.py", "test.ef", "--png", "--dpi=600"] + ) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "png" assert not pdf_requested assert png_requested @@ -619,8 +666,18 @@ def test_commandline_png_with_dpi(self): def test_commandline_png_output_file(self): """Test PNG output with custom filename.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--output=custom.png']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline( + ["draw_tree.py", "test.ef", "--output=custom.png"] + ) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "png" assert not pdf_requested assert png_requested @@ -631,8 +688,18 @@ def test_commandline_png_output_file(self): def test_commandline_pdf_output_file(self): """Test PDF output with custom filename.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--output=custom.pdf']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline( + ["draw_tree.py", "test.ef", "--output=custom.pdf"] + ) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "pdf" assert pdf_requested assert not png_requested @@ -643,8 +710,16 @@ def test_commandline_pdf_output_file(self): def test_commandline_tex_flag(self): """Test --tex flag parsing.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--tex']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline(["draw_tree.py", "test.ef", "--tex"]) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "tex" assert not pdf_requested assert not png_requested @@ -655,8 +730,18 @@ def test_commandline_tex_flag(self): def test_commandline_tex_output_file(self): """Test LaTeX output with custom filename.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--output=custom.tex']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline( + ["draw_tree.py", "test.ef", "--output=custom.tex"] + ) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "tex" assert not pdf_requested assert not png_requested @@ -668,25 +753,61 @@ def test_commandline_tex_output_file(self): def test_commandline_invalid_dpi(self): """Test invalid DPI values.""" # Too low DPI should default to 300 - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--png', '--dpi=50']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline(["draw_tree.py", "test.ef", "--png", "--dpi=50"]) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert dpi == 300 # Should default to 300 for out-of-range values # Too high DPI should default to 300 - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--png', '--dpi=5000']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline( + ["draw_tree.py", "test.ef", "--png", "--dpi=5000"] + ) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert dpi == 300 # Should default to 300 for out-of-range values def test_commandline_invalid_dpi_string(self): """Test non-numeric DPI values.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--png', '--dpi=high']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline( + ["draw_tree.py", "test.ef", "--png", "--dpi=high"] + ) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert dpi == 300 # Should default to 300 for invalid values def test_commandline_svg_flag(self): """Test --svg flag parsing.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--svg']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline(["draw_tree.py", "test.ef", "--svg"]) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "svg" assert not pdf_requested assert not png_requested @@ -697,8 +818,18 @@ def test_commandline_svg_flag(self): def test_commandline_svg_output_file(self): """Test SVG output with custom filename.""" - result = draw_tree.commandline(['draw_tree.py', 'test.ef', '--output=custom.svg']) - output_mode, pdf_requested, png_requested, svg_requested, tex_requested, output_file, dpi = result + result = draw_tree.commandline( + ["draw_tree.py", "test.ef", "--output=custom.svg"] + ) + ( + output_mode, + pdf_requested, + png_requested, + svg_requested, + tex_requested, + output_file, + dpi, + ) = result assert output_mode == "svg" assert not pdf_requested assert not png_requested @@ -716,10 +847,10 @@ def test_efg_dl_ef_conversion_examples(): extend with additional examples in the future. """ examples = [ - ('games/efg/one_card_poker.efg', 'games/one_card_poker.ef'), - ('games/efg/2smp.efg', 'games/2smp.ef'), - ('games/efg/2s2x2x2.efg', 'games/2s2x2x2.ef'), - ('games/efg/cent2.efg', 'games/cent2.ef'), + ("games/efg/one_card_poker.efg", "games/one_card_poker.ef"), + ("games/efg/2smp.efg", "games/2smp.ef"), + ("games/efg/2s2x2x2.efg", "games/2s2x2x2.ef"), + ("games/efg/cent2.efg", "games/cent2.ef"), ] for efg_path, expected_ef_path in examples: @@ -728,18 +859,20 @@ def test_efg_dl_ef_conversion_examples(): assert isinstance(out, str), "efg_dl_ef must return a file path string" assert os.path.exists(out), f"efg_dl_ef did not create output file: {out}" - with open(out, 'r', encoding='utf-8') as f: + with open(out, "r", encoding="utf-8") as f: generated = f.read().strip().splitlines() - with open(expected_ef_path, 'r', encoding='utf-8') as f: + with open(expected_ef_path, "r", encoding="utf-8") as f: expected = f.read().strip().splitlines() gen_norm = [line.strip() for line in generated if line.strip()] expected_lines = [ln.strip() for ln in expected if ln.strip()] assert gen_norm == expected_lines, ( - f"Generated .ef does not match expected for {efg_path}.\nGenerated:\n" + "\n".join(gen_norm) - + "\n\nExpected:\n" + "\n".join(expected_lines) + f"Generated .ef does not match expected for {efg_path}.\nGenerated:\n" + + "\n".join(gen_norm) + + "\n\nExpected:\n" + + "\n".join(expected_lines) ) if __name__ == "__main__": - pytest.main([__file__]) \ No newline at end of file + pytest.main([__file__])