diff --git a/.github/workflows/osxbinary.yml b/.github/workflows/osxbinary.yml index ed83ba815..91bc7fd5c 100644 --- a/.github/workflows/osxbinary.yml +++ b/.github/workflows/osxbinary.yml @@ -31,7 +31,7 @@ jobs: - run: make - run: sudo make install - run: make osx-dmg - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: artifact-osx-14 path: "*.dmg" diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 15ba606a0..ed0ee23b9 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -68,7 +68,7 @@ jobs: cp gambit* installer "${WIX}bin/candle" build_support/msw/gambit.wxs "${WIX}bin/light" -ext WixUIExtension gambit.wixobj - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: name: artifact-msw path: "*.msi" diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 867b4f21a..69d62329b 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -27,6 +27,6 @@ jobs: python -m cibuildwheel --output-dir wheelhouse/ env: CIBW_SKIP: "pp*" - - uses: actions/upload-artifact@v6 + - uses: actions/upload-artifact@v7 with: path: ./wheelhouse/*.whl diff --git a/.gitignore b/.gitignore index 643f8c565..65fe1c9d4 100644 --- a/.gitignore +++ b/.gitignore @@ -41,13 +41,10 @@ dist *.dmg Gambit.app/* *.so -doc/tutorials/games/*.nfg -doc/tutorials/games/*.efg -doc/tutorials/*.png *.dmg Gambit.app/* *.ipynb_checkpoints -*.ef +doc/**/*.ef build_support/msw/gambit.wxs build_support/osx/Info.plist src/pygambit/catalog diff --git a/.readthedocs.yml b/.readthedocs.yml index dc6c7c782..b30aba061 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -13,8 +13,9 @@ build: - libgmp-dev - pandoc - texlive-full + - imagemagick jobs: - # Create CSV for catalog table in docs + # Create RST for catalog table in docs post_install: - $READTHEDOCS_VIRTUALENV_PATH/bin/python build_support/catalog/update.py diff --git a/ChangeLog b/ChangeLog index cc54dd62e..e20c9a7fa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,19 +5,25 @@ ### Changed - `Game.comment` has been renamed to `Game.description` +### Added +- Implement linear-time algorithm to find all root nodes of proper subgames, using an adaptation of + Tarjan's (1974) algorithm for finding bridges in an undirected graph. Subgame roots are cached so + subsequent lookup is constant-time (if the game is unchanged). (#584) + ### Fixed - `enumpoly` would take a very long time on some supports where an equilibrium is located on the boundary of the projected game. Search is now restricted to the interior of the space ruling these out; these will always be found by another projection. (#756) - In the graphical interface, the logit correspondence display would fail and terminate the program on very small (<10^{-300}) probabilities. +- The new subgame root computation fixes a bug which failed to detect subgames where the subgame + root node is a member of an absent-minded infoset. (#584) ## [16.5.1] - unreleased ### Fixed - `Game.reveal` raised a null pointer access exception or dumped core in some cases (#749) - ## [16.5.0] - 2026-01-05 ### Fixed diff --git a/build_support/catalog/update.py b/build_support/catalog/update.py index fd012ee65..74140bddd 100644 --- a/build_support/catalog/update.py +++ b/build_support/catalog/update.py @@ -1,7 +1,9 @@ import argparse +import re from pathlib import Path import pandas as pd +from draw_tree import generate_pdf, generate_png, generate_tex import pygambit as gbt @@ -9,39 +11,126 @@ CATALOG_DIR = Path(__file__).parent.parent.parent / "catalog" MAKEFILE_AM = Path(__file__).parent.parent.parent / "Makefile.am" +# Common arguments for visualization generation +draw_tree_args = { + "color_scheme": "gambit", + "sublevel_scaling": 0, + "shared_terminal_depth": True, +} + + +def _write_efg_table(df: pd.DataFrame, f, tikz_re, regenerate_images: bool): + """Write the EFG games list-table to file handle f.""" + f.write(".. list-table::\n") + f.write(" :header-rows: 1\n") + f.write(" :widths: 100\n") + f.write(" :class: tight-table\n") + f.write("\n") + f.write(" * - **Extensive form games**\n") + + efg_df = df[df["Format"] == "efg"] + for _, row in efg_df.iterrows(): + slug = row["Game"] + title = str(row.get("Title", "")).strip() + description = str(row.get("Description", "")).strip() + + tex_path = CATALOG_DIR / "img" / f"{slug}.tex" + if regenerate_images or not tex_path.exists(): + g = gbt.catalog.load(slug) + viz_path = CATALOG_DIR / "img" / f"{slug}" + viz_path.parent.mkdir(parents=True, exist_ok=True) + for func in [generate_tex, generate_png, generate_pdf]: + func(g, save_to=str(viz_path), **draw_tree_args) + + with open(tex_path, encoding="utf-8") as tex_f: + tex_content = tex_f.read() + match = tikz_re.search(tex_content) + tikz = match.group(1).strip() if match else "% Could not extract tikzpicture from tex file" + + # Main dropdown + f.write(f" * - .. dropdown:: {title}\n") + f.write(" \n") + if description: + for line in description.splitlines(): + f.write(f" {line}\n") + f.write(" \n") + f.write(" **Load in PyGambit:**\n") + f.write(" \n") + f.write(" .. code-block:: python\n") + f.write(" \n") + f.write(f' pygambit.catalog.load("{slug}")\n') + f.write(" \n") + + # Download links (inside the dropdown) + download_links = [row["Download"]] + for ext in ["efg", "ef", "tex", "png", "pdf"]: + download_links.append(f":download:`{slug}.{ext} <../catalog/img/{slug}.{ext}>`") + f.write(" **Download game and image files:**\n") + f.write(" \n") + f.write(f" {' '.join(download_links)}\n") + f.write(" \n") + + # TiKZ image (outside dropdown) + f.write(" .. tikz::\n") + f.write(" :align: center\n") + f.write(" \n") + for line in tikz.splitlines(): + f.write(f" {line}\n") + f.write(" \n") + + +def _write_nfg_table(df: pd.DataFrame, f): + """Write the NFG games list-table to file handle f.""" + f.write(".. list-table::\n") + f.write(" :header-rows: 1\n") + f.write(" :widths: 100\n") + f.write(" :class: tight-table\n") + f.write("\n") + f.write(" * - **Strategic form games**\n") + + nfg_df = df[df["Format"] == "nfg"] + for _, row in nfg_df.iterrows(): + slug = row["Game"] + + # Title as plain text header + f.write(" * - \n") + f.write(" \n") + + # Jupyter-execute block (no dropdown) + f.write(" .. jupyter-execute::\n") + f.write(" \n") + f.write(" import pygambit\n") + f.write(f' pygambit.catalog.load("{slug}")\n') + f.write(" \n") + + # Download link (plain, no dropdown) + f.write(f" :download:`{slug}.nfg <../catalog/{slug}.nfg>`\n") + f.write(" \n") + + +def generate_rst_table(df: pd.DataFrame, rst_path: Path, regnerate_images: bool = False): + """Generate RST output with two list-tables: one for EFG and one for NFG games.""" + tikz_re = re.compile(r"\\begin\{document\}(.*?)\\end\{document\}", re.DOTALL) -def generate_rst_table(df: pd.DataFrame, rst_path: Path): - """Generate a list-table RST file with dropdowns for long descriptions.""" with open(rst_path, "w", encoding="utf-8") as f: - f.write(".. list-table::\n") - f.write(" :header-rows: 1\n") - f.write(" :widths: 20 80 20\n") - f.write(" :class: tight-table\n") + # TOC linking to both sections + f.write(".. contents::\n") + f.write(" :local:\n") + f.write(" :depth: 1\n") f.write("\n") - f.write(" * - **Game**\n") - f.write(" - **Description**\n") - f.write(" - **Download**\n") - - for _, row in df.iterrows(): - f.write(f" * - {row['Game']}\n") - - description_cell_lines = [] - title = str(row.get("Title", "")).strip() - description = str(row.get("Description", "")).strip() - if description: - description_cell_lines.append(f".. dropdown:: {title}") - description_cell_lines.append(" ") # Indented blank line - for line in description.splitlines(): - description_cell_lines.append(f" {line}") - else: - description_cell_lines.append(title) - - f.write(f" - {description_cell_lines[0]}\n") - for line in description_cell_lines[1:]: - f.write(f" {line}\n") + # EFG section + f.write("Extensive form games\n") + f.write("--------------------\n") + f.write("\n") + _write_efg_table(df, f, tikz_re, regnerate_images) + f.write("\n") - f.write(f" - {row['Download']}\n") + # NFG section + f.write("Strategic form games\n") + f.write("--------------------\n") + f.write("\n") + _write_nfg_table(df, f) def update_makefile(): @@ -96,16 +185,15 @@ def update_makefile(): if __name__ == "__main__": - parser = argparse.ArgumentParser() parser.add_argument("--build", action="store_true") + parser.add_argument("--regenerate-images", action="store_true") args = parser.parse_args() # Create RST list-table used by doc/catalog.rst df = gbt.catalog.games(include_descriptions=True) - generate_rst_table(df, CATALOG_RST_TABLE) + generate_rst_table(df, CATALOG_RST_TABLE, regnerate_images=args.regenerate_images) print(f"Generated {CATALOG_RST_TABLE} for use in local docs build. DO NOT COMMIT.") - - # Update the Makefile.am with the current list of catalog files if args.build: + # Update the Makefile.am with the current list of catalog files update_makefile() diff --git a/catalog/__init__.py b/catalog/__init__.py index d4ca2995f..05f0c0321 100644 --- a/catalog/__init__.py +++ b/catalog/__init__.py @@ -157,6 +157,7 @@ def append_record( record["Description"] = game.description ext = "efg" if game.is_tree else "nfg" record["Download"] = f":download:`{slug}.{ext} <../catalog/{slug}.{ext}>`" + record["Format"] = ext records.append(record) # Add all the games stored as EFG/NFG files @@ -186,7 +187,7 @@ def append_record( if include_descriptions: return pd.DataFrame.from_records( - records, columns=["Game", "Title", "Description", "Download"] + records, columns=["Game", "Title", "Description", "Download", "Format"] ) return pd.DataFrame.from_records(records, columns=["Game", "Title"]) diff --git a/catalog/img/2smp.ef b/catalog/img/2smp.ef new file mode 100644 index 000000000..1db68a32e --- /dev/null +++ b/catalog/img/2smp.ef @@ -0,0 +1,38 @@ +player 1 name Player~1 +player 2 name Player~2 +level -2.0 node 1 player 1 +level 2.0 node 1 xshift -8.0 from -2.0,1 move H +level 6.0 node 1 player 1 xshift -4.0 from 2.0,1 move H +level 10.0 node 1 xshift -2.0 from 6.0,1 move H +level 14.0 node 1 xshift -1.0 from 10.0,1 move H payoffs 1 -1 +level 14.0 node 2 xshift 1.0 from 10.0,1 move T payoffs -1 1 +level 10.0 node 2 xshift 2.0 from 6.0,1 move T +level 14.0 node 3 xshift -1.0 from 10.0,2 move H payoffs -1 1 +level 14.0 node 4 xshift 1.0 from 10.0,2 move T payoffs 1 -1 +level 6.0 node 2 player 1 xshift 4.0 from 2.0,1 move T +level 10.0 node 3 xshift -2.0 from 6.0,2 move H +level 14.0 node 5 xshift -1.0 from 10.0,3 move H payoffs 1 -1 +level 14.0 node 6 xshift 1.0 from 10.0,3 move T payoffs -1 1 +level 10.0 node 4 xshift 2.0 from 6.0,2 move T +level 14.0 node 7 xshift -1.0 from 10.0,4 move H payoffs -1 1 +level 14.0 node 8 xshift 1.0 from 10.0,4 move T payoffs 1 -1 +level 2.0 node 2 xshift 8.0 from -2.0,1 move T +level 6.0 node 3 player 1 xshift -4.0 from 2.0,2 move H +level 10.0 node 5 xshift -2.0 from 6.0,3 move H +level 14.0 node 9 xshift -1.0 from 10.0,5 move H payoffs 1 -1 +level 14.0 node 10 xshift 1.0 from 10.0,5 move T payoffs -1 1 +level 10.0 node 6 xshift 2.0 from 6.0,3 move T +level 14.0 node 11 xshift -1.0 from 10.0,6 move H payoffs -1 1 +level 14.0 node 12 xshift 1.0 from 10.0,6 move T payoffs 1 -1 +level 6.0 node 4 player 1 xshift 4.0 from 2.0,2 move T +level 10.0 node 7 xshift -2.0 from 6.0,4 move H +level 14.0 node 13 xshift -1.0 from 10.0,7 move H payoffs 1 -1 +level 14.0 node 14 xshift 1.0 from 10.0,7 move T payoffs -1 1 +level 10.0 node 8 xshift 2.0 from 6.0,4 move T +level 14.0 node 15 xshift -1.0 from 10.0,8 move H payoffs -1 1 +level 14.0 node 16 xshift 1.0 from 10.0,8 move T payoffs 1 -1 +iset 2.0,1 2.0,2 player 2 +iset 10.0,1 10.0,2 player 2 +iset 10.0,3 10.0,4 player 2 +iset 10.0,5 10.0,6 player 2 +iset 10.0,7 10.0,8 player 2 diff --git a/catalog/img/2smp.pdf b/catalog/img/2smp.pdf new file mode 100644 index 000000000..1396c40ba Binary files /dev/null and b/catalog/img/2smp.pdf differ diff --git a/catalog/img/2smp.png b/catalog/img/2smp.png new file mode 100644 index 000000000..9173dbb3f Binary files /dev/null and b/catalog/img/2smp.png differ diff --git a/catalog/img/2smp.tex b/catalog/img/2smp.tex new file mode 100644 index 000000000..108e9bedb --- /dev/null +++ b/catalog/img/2smp.tex @@ -0,0 +1,253 @@ +\documentclass[tikz,border=10pt]{standalone} + \usepackage{newpxtext,newpxmath} + \linespread{1.10} + \usetikzlibrary{shapes} + \usetikzlibrary{arrows.meta} + + \begin{document} + + % TikZ code with built-in styling for game trees +% TikZ libraries required for game trees +\usetikzlibrary{shapes} +\usetikzlibrary{arrows.meta} + +% Style settings for game tree formatting +\tikzset{ + every node/.append style={font=\rmfamily}, + every text node part/.append style={align=center}, + node distance=1.5mm, + thick +} + +% Built-in macro definitions for game tree drawing +\newdimen\ndiam +\ndiam1.5mm +\newdimen\sqwidth +\sqwidth1.6mm +\newdimen\spx +\spx.7mm +\newdimen\spy +\spy.5mm +\newdimen\yup +\yup0.5mm +\newdimen\yfracup +\yfracup1mm +\newdimen\paydown +\paydown2.5ex +\newdimen\treethickn +\treethickn1.0pt +\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} + +% Game tree content from /Users/echalstrey/projects/gambit/catalog/img/2smp.ef +\begin{tikzpicture}[scale=0.8 + , StealthFill/.tip={Stealth[line width=.7pt,inset=0pt,length=13pt,angle'=30]}] +% \draw [help lines, color=green] (-5,0) grid (5,-6); +\draw [color=\playertwocolor] (-8,-1.625) arc(90:270:0.375) + -- (8,-2.375) arc(-90:90:0.375) -- cycle; +\draw [color=\playertwocolor] (-14,-9.625) arc(90:270:0.375) + -- (-10,-10.375) arc(-90:90:0.375) -- cycle; +\draw [color=\playertwocolor] (-6,-9.625) arc(90:270:0.375) + -- (-2,-10.375) arc(-90:90:0.375) -- cycle; +\draw [color=\playertwocolor] (2,-9.625) arc(90:270:0.375) + -- (6,-10.375) arc(-90:90:0.375) -- cycle; +\draw [color=\playertwocolor] (10,-9.625) arc(90:270:0.375) + -- (14,-10.375) arc(-90:90:0.375) -- cycle; +%% player 1 name Player~1 +\def\playerone{Player~1} +%% player 2 name Player~2 +\def\playertwo{Player~2} +%% level -2.0 node 1 player 1 +\draw [line width=\treethickn] (0,2) + ; +%% level 2.0 node 1 xshift -8.0 from -2.0,1 move H +\draw [line width=\treethickn,color=\playeronecolor] (-8,-2) + -- (0,2); +\draw (-4,0) node[left,yshift=\yup,color=\playeronecolor] {$H$\strut}; +%% level 6.0 node 1 player 1 xshift -4.0 from 2.0,1 move H +\draw [line width=\treethickn,color=\playertwocolor] (-12,-6) + -- (-8,-2); +\draw (-10,-4) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 10.0 node 1 xshift -2.0 from 6.0,1 move H +\draw [line width=\treethickn,color=\playeronecolor] (-14,-10) + -- (-12,-6); +\draw (-13,-8) node[left,yshift=\yup,color=\playeronecolor] {$H$\strut}; +%% level 14.0 node 1 xshift -1.0 from 10.0,1 move H payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (-15,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (-14,-10); +\draw (-14.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 2 xshift 1.0 from 10.0,1 move T payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (-13,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (-14,-10); +\draw (-13.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 10.0 node 2 xshift 2.0 from 6.0,1 move T +\draw [line width=\treethickn,color=\playeronecolor] (-10,-10) + -- (-12,-6); +\draw (-11,-8) node[right,yshift=\yup,color=\playeronecolor] {$T$\strut}; +%% level 14.0 node 3 xshift -1.0 from 10.0,2 move H payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (-11,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (-10,-10); +\draw (-10.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 4 xshift 1.0 from 10.0,2 move T payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (-9,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (-10,-10); +\draw (-9.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 6.0 node 2 player 1 xshift 4.0 from 2.0,1 move T +\draw [line width=\treethickn,color=\playertwocolor] (-4,-6) + -- (-8,-2); +\draw (-6,-4) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 10.0 node 3 xshift -2.0 from 6.0,2 move H +\draw [line width=\treethickn,color=\playeronecolor] (-6,-10) + -- (-4,-6); +\draw (-5,-8) node[left,yshift=\yup,color=\playeronecolor] {$H$\strut}; +%% level 14.0 node 5 xshift -1.0 from 10.0,3 move H payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (-7,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (-6,-10); +\draw (-6.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 6 xshift 1.0 from 10.0,3 move T payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (-5,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (-6,-10); +\draw (-5.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 10.0 node 4 xshift 2.0 from 6.0,2 move T +\draw [line width=\treethickn,color=\playeronecolor] (-2,-10) + -- (-4,-6); +\draw (-3,-8) node[right,yshift=\yup,color=\playeronecolor] {$T$\strut}; +%% level 14.0 node 7 xshift -1.0 from 10.0,4 move H payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (-3,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (-2,-10); +\draw (-2.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 8 xshift 1.0 from 10.0,4 move T payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (-1,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (-2,-10); +\draw (-1.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 2.0 node 2 xshift 8.0 from -2.0,1 move T +\draw [line width=\treethickn,color=\playeronecolor] (8,-2) + -- (0,2); +\draw (4,0) node[right,yshift=\yup,color=\playeronecolor] {$T$\strut}; +%% level 6.0 node 3 player 1 xshift -4.0 from 2.0,2 move H +\draw [line width=\treethickn,color=\playertwocolor] (4,-6) + -- (8,-2); +\draw (6,-4) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 10.0 node 5 xshift -2.0 from 6.0,3 move H +\draw [line width=\treethickn,color=\playeronecolor] (2,-10) + -- (4,-6); +\draw (3,-8) node[left,yshift=\yup,color=\playeronecolor] {$H$\strut}; +%% level 14.0 node 9 xshift -1.0 from 10.0,5 move H payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (1,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (2,-10); +\draw (1.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 10 xshift 1.0 from 10.0,5 move T payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (3,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (2,-10); +\draw (2.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 10.0 node 6 xshift 2.0 from 6.0,3 move T +\draw [line width=\treethickn,color=\playeronecolor] (6,-10) + -- (4,-6); +\draw (5,-8) node[right,yshift=\yup,color=\playeronecolor] {$T$\strut}; +%% level 14.0 node 11 xshift -1.0 from 10.0,6 move H payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (5,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (6,-10); +\draw (5.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 12 xshift 1.0 from 10.0,6 move T payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (7,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (6,-10); +\draw (6.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 6.0 node 4 player 1 xshift 4.0 from 2.0,2 move T +\draw [line width=\treethickn,color=\playertwocolor] (12,-6) + -- (8,-2); +\draw (10,-4) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 10.0 node 7 xshift -2.0 from 6.0,4 move H +\draw [line width=\treethickn,color=\playeronecolor] (10,-10) + -- (12,-6); +\draw (11,-8) node[left,yshift=\yup,color=\playeronecolor] {$H$\strut}; +%% level 14.0 node 13 xshift -1.0 from 10.0,7 move H payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (9,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (10,-10); +\draw (9.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 14 xshift 1.0 from 10.0,7 move T payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (11,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (10,-10); +\draw (10.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% level 10.0 node 8 xshift 2.0 from 6.0,4 move T +\draw [line width=\treethickn,color=\playeronecolor] (14,-10) + -- (12,-6); +\draw (13,-8) node[right,yshift=\yup,color=\playeronecolor] {$T$\strut}; +%% level 14.0 node 15 xshift -1.0 from 10.0,8 move H payoffs -1 1 +\draw [line width=\treethickn,color=\playertwocolor] (13,-14) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (14,-10); +\draw (13.5,-12) node[left,yshift=\yup,color=\playertwocolor] {$H$\strut}; +%% level 14.0 node 16 xshift 1.0 from 10.0,8 move T payoffs 1 -1 +\draw [line width=\treethickn,color=\playertwocolor] (15,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$-1{\phantom-}$\strut} + -- (14,-10); +\draw (14.5,-12) node[right,yshift=\yup,color=\playertwocolor] {$T$\strut}; +%% iset 2.0,1 2.0,2 player 2 +%% iset 10.0,1 10.0,2 player 2 +%% iset 10.0,3 10.0,4 player 2 +%% iset 10.0,5 10.0,6 player 2 +%% iset 10.0,7 10.0,8 player 2 +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (0,2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-8,-2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (-12,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-14,-10) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-10,-10) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (-4,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-6,-10) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-2,-10) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (8,-2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (4,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (2,-10) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (6,-10) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (12,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (10,-10) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (14,-10) {}; + +% Player color legend +\begin{scope}[scale=1,shift={(-16.5,2.0)}] +\node[inner sep=0pt,minimum size=\ndiam,draw=\playeronecolor,fill=\playeronecolor,shape=circle] at (0,0) {}; +\node[anchor=west] at (0.3,0) {Player~1}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playertwocolor,fill=\playertwocolor,shape=circle] at (0,-0.625) {}; +\node[anchor=west] at (0.3,-0.625) {Player~2}; +\end{scope} + +\end{tikzpicture} + + \end{document} diff --git a/catalog/img/myerson1991/fig4_2.ef b/catalog/img/myerson1991/fig4_2.ef new file mode 100644 index 000000000..d3838f531 --- /dev/null +++ b/catalog/img/myerson1991/fig4_2.ef @@ -0,0 +1,15 @@ +player 1 name Player~1 +player 2 name Player~2 +level -2.0 node 1 player 1 +level 2.0 node 1 xshift -3.0 from -2.0,1 move A1 +level 6.0 node 1 xshift -2.0 from 2.0,1 move W2 +level 10.0 node 1 xshift -1.0 from 6.0,1 move Y1 payoffs 3 0 +level 10.0 node 2 xshift 1.0 from 6.0,1 move Z1 payoffs 0 0 +level 6.0 node 2 xshift 2.0 from 2.0,1 move X2 +level 10.0 node 3 xshift -1.0 from 6.0,2 move Y1 payoffs 2 3 +level 10.0 node 4 xshift 1.0 from 6.0,2 move Z1 payoffs 4 1 +level 2.0 node 2 xshift 3.0 from -2.0,1 move B1 +level 10.0 node 5 xshift -1.0 from 2.0,2 move W2 payoffs 2 3 +level 10.0 node 6 xshift 1.0 from 2.0,2 move X2 payoffs 3 2 +iset 2.0,1 2.0,2 player 2 +iset 6.0,1 6.0,2 player 1 diff --git a/catalog/img/myerson1991/fig4_2.pdf b/catalog/img/myerson1991/fig4_2.pdf new file mode 100644 index 000000000..4f34840af Binary files /dev/null and b/catalog/img/myerson1991/fig4_2.pdf differ diff --git a/catalog/img/myerson1991/fig4_2.png b/catalog/img/myerson1991/fig4_2.png new file mode 100644 index 000000000..90c809fc0 Binary files /dev/null and b/catalog/img/myerson1991/fig4_2.png differ diff --git a/catalog/img/myerson1991/fig4_2.tex b/catalog/img/myerson1991/fig4_2.tex new file mode 100644 index 000000000..da13f9a35 --- /dev/null +++ b/catalog/img/myerson1991/fig4_2.tex @@ -0,0 +1,134 @@ +\documentclass[tikz,border=10pt]{standalone} + \usepackage{newpxtext,newpxmath} + \linespread{1.10} + \usetikzlibrary{shapes} + \usetikzlibrary{arrows.meta} + + \begin{document} + + % TikZ code with built-in styling for game trees +% TikZ libraries required for game trees +\usetikzlibrary{shapes} +\usetikzlibrary{arrows.meta} + +% Style settings for game tree formatting +\tikzset{ + every node/.append style={font=\rmfamily}, + every text node part/.append style={align=center}, + node distance=1.5mm, + thick +} + +% Built-in macro definitions for game tree drawing +\newdimen\ndiam +\ndiam1.5mm +\newdimen\sqwidth +\sqwidth1.6mm +\newdimen\spx +\spx.7mm +\newdimen\spy +\spy.5mm +\newdimen\yup +\yup0.5mm +\newdimen\yfracup +\yfracup1mm +\newdimen\paydown +\paydown2.5ex +\newdimen\treethickn +\treethickn1.0pt +\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} + +% Game tree content from /Users/echalstrey/projects/gambit/catalog/img/myerson1991/fig4_2.ef +\begin{tikzpicture}[scale=0.8 + , StealthFill/.tip={Stealth[line width=.7pt,inset=0pt,length=13pt,angle'=30]}] +% \draw [help lines, color=green] (-5,0) grid (5,-6); +\draw [color=\playertwocolor] (-3,-1.625) arc(90:270:0.375) + -- (3,-2.375) arc(-90:90:0.375) -- cycle; +\draw [color=\playeronecolor] (-5,-5.625) arc(90:270:0.375) + -- (-1,-6.375) arc(-90:90:0.375) -- cycle; +%% player 1 name Player~1 +\def\playerone{Player~1} +%% player 2 name Player~2 +\def\playertwo{Player~2} +%% level -2.0 node 1 player 1 +\draw [line width=\treethickn] (0,2) + ; +%% level 2.0 node 1 xshift -3.0 from -2.0,1 move A1 +\draw [line width=\treethickn,color=\playeronecolor] (-3,-2) + -- (0,2); +\draw (-1.5,0) node[left,yshift=\yup,color=\playeronecolor] {$A1$\strut}; +%% level 6.0 node 1 xshift -2.0 from 2.0,1 move W2 +\draw [line width=\treethickn,color=\playertwocolor] (-5,-6) + -- (-3,-2); +\draw (-4,-4) node[left,yshift=\yup,color=\playertwocolor] {$W2$\strut}; +%% level 10.0 node 1 xshift -1.0 from 6.0,1 move Y1 payoffs 3 0 +\draw [line width=\treethickn,color=\playeronecolor] (-6,-10) + node[below,yshift=0.1\paydown] {$3$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + -- (-5,-6); +\draw (-5.5,-8) node[left,yshift=\yup,color=\playeronecolor] {$Y1$\strut}; +%% level 10.0 node 2 xshift 1.0 from 6.0,1 move Z1 payoffs 0 0 +\draw [line width=\treethickn,color=\playeronecolor] (-4,-10) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + -- (-5,-6); +\draw (-4.5,-8) node[right,yshift=\yup,color=\playeronecolor] {$Z1$\strut}; +%% level 6.0 node 2 xshift 2.0 from 2.0,1 move X2 +\draw [line width=\treethickn,color=\playertwocolor] (-1,-6) + -- (-3,-2); +\draw (-2,-4) node[right,yshift=\yup,color=\playertwocolor] {$X2$\strut}; +%% level 10.0 node 3 xshift -1.0 from 6.0,2 move Y1 payoffs 2 3 +\draw [line width=\treethickn,color=\playeronecolor] (-2,-10) + node[below,yshift=0.1\paydown] {$2$\strut} + node[below,yshift=-0.9\paydown] {$3$\strut} + -- (-1,-6); +\draw (-1.5,-8) node[left,yshift=\yup,color=\playeronecolor] {$Y1$\strut}; +%% level 10.0 node 4 xshift 1.0 from 6.0,2 move Z1 payoffs 4 1 +\draw [line width=\treethickn,color=\playeronecolor] (0,-10) + node[below,yshift=0.1\paydown] {$4$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (-1,-6); +\draw (-0.5,-8) node[right,yshift=\yup,color=\playeronecolor] {$Z1$\strut}; +%% level 2.0 node 2 xshift 3.0 from -2.0,1 move B1 +\draw [line width=\treethickn,color=\playeronecolor] (3,-2) + -- (0,2); +\draw (1.5,0) node[right,yshift=\yup,color=\playeronecolor] {$B1$\strut}; +%% level 10.0 node 5 xshift -1.0 from 2.0,2 move W2 payoffs 2 3 +\draw [line width=\treethickn,color=\playertwocolor] (2,-10) + node[below,yshift=0.1\paydown] {$2$\strut} + node[below,yshift=-0.9\paydown] {$3$\strut} + -- (3,-2); +\draw (2.5,-6) node[left,yshift=\yup,color=\playertwocolor] {$W2$\strut}; +%% level 10.0 node 6 xshift 1.0 from 2.0,2 move X2 payoffs 3 2 +\draw [line width=\treethickn,color=\playertwocolor] (4,-10) + node[below,yshift=0.1\paydown] {$3$\strut} + node[below,yshift=-0.9\paydown] {$2$\strut} + -- (3,-2); +\draw (3.5,-6) node[right,yshift=\yup,color=\playertwocolor] {$X2$\strut}; +%% iset 2.0,1 2.0,2 player 2 +%% iset 6.0,1 6.0,2 player 1 +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (0,2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-3,-2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (-5,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (-1,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (3,-2) {}; + +% Player color legend +\begin{scope}[scale=1,shift={(-7.5,2.0)}] +\node[inner sep=0pt,minimum size=\ndiam,draw=\playeronecolor,fill=\playeronecolor,shape=circle] at (0,0) {}; +\node[anchor=west] at (0.3,0) {Player~1}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playertwocolor,fill=\playertwocolor,shape=circle] at (0,-0.625) {}; +\node[anchor=west] at (0.3,-0.625) {Player~2}; +\end{scope} + +\end{tikzpicture} + + \end{document} diff --git a/catalog/img/one_shot_trust.ef b/catalog/img/one_shot_trust.ef new file mode 100644 index 000000000..6a5842469 --- /dev/null +++ b/catalog/img/one_shot_trust.ef @@ -0,0 +1,7 @@ +player 1 name Buyer +player 2 name Seller +level -2.0 node 1 player 1 +level 2.0 node 1 player 2 xshift -1.5 from -2.0,1 move Trust +level 6.0 node 1 xshift -1.0 from 2.0,1 move Honor payoffs 1 1 +level 6.0 node 2 xshift 1.0 from 2.0,1 move Abuse payoffs -1 2 +level 6.0 node 3 xshift 1.5 from -2.0,1 move Not~trust payoffs 0 0 diff --git a/catalog/img/one_shot_trust.pdf b/catalog/img/one_shot_trust.pdf new file mode 100644 index 000000000..d26c61fa5 Binary files /dev/null and b/catalog/img/one_shot_trust.pdf differ diff --git a/catalog/img/one_shot_trust.png b/catalog/img/one_shot_trust.png new file mode 100644 index 000000000..9682cd982 Binary files /dev/null and b/catalog/img/one_shot_trust.png differ diff --git a/catalog/img/one_shot_trust.tex b/catalog/img/one_shot_trust.tex new file mode 100644 index 000000000..bf6e4a7d1 --- /dev/null +++ b/catalog/img/one_shot_trust.tex @@ -0,0 +1,95 @@ +\documentclass[tikz,border=10pt]{standalone} + \usepackage{newpxtext,newpxmath} + \linespread{1.10} + \usetikzlibrary{shapes} + \usetikzlibrary{arrows.meta} + + \begin{document} + + % TikZ code with built-in styling for game trees +% TikZ libraries required for game trees +\usetikzlibrary{shapes} +\usetikzlibrary{arrows.meta} + +% Style settings for game tree formatting +\tikzset{ + every node/.append style={font=\rmfamily}, + every text node part/.append style={align=center}, + node distance=1.5mm, + thick +} + +% Built-in macro definitions for game tree drawing +\newdimen\ndiam +\ndiam1.5mm +\newdimen\sqwidth +\sqwidth1.6mm +\newdimen\spx +\spx.7mm +\newdimen\spy +\spy.5mm +\newdimen\yup +\yup0.5mm +\newdimen\yfracup +\yfracup1mm +\newdimen\paydown +\paydown2.5ex +\newdimen\treethickn +\treethickn1.0pt +\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} + +% Game tree content from /Users/echalstrey/projects/gambit/catalog/img/one_shot_trust.ef +\begin{tikzpicture}[scale=0.8 + , StealthFill/.tip={Stealth[line width=.7pt,inset=0pt,length=13pt,angle'=30]}] +% \draw [help lines, color=green] (-5,0) grid (5,-6); +%% player 1 name Buyer +\def\playerone{Buyer} +%% player 2 name Seller +\def\playertwo{Seller} +%% level -2.0 node 1 player 1 +\draw [line width=\treethickn] (0,2) + ; +%% level 2.0 node 1 player 2 xshift -1.5 from -2.0,1 move Trust +\draw [line width=\treethickn,color=\playeronecolor] (-1.5,-2) + -- (0,2); +\draw (-0.75,0) node[left,yshift=\yup,color=\playeronecolor] {$Trust$\strut}; +%% level 6.0 node 1 xshift -1.0 from 2.0,1 move Honor payoffs 1 1 +\draw [line width=\treethickn,color=\playertwocolor] (-2.5,-6) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (-1.5,-2); +\draw (-2,-4) node[left,yshift=\yup,color=\playertwocolor] {$Honor$\strut}; +%% level 6.0 node 2 xshift 1.0 from 2.0,1 move Abuse payoffs -1 2 +\draw [line width=\treethickn,color=\playertwocolor] (-0.5,-6) + node[below,yshift=0.1\paydown] {$-1{\phantom-}$\strut} + node[below,yshift=-0.9\paydown] {$2$\strut} + -- (-1.5,-2); +\draw (-1,-4) node[right,yshift=\yup,color=\playertwocolor] {$Abuse$\strut}; +%% level 6.0 node 3 xshift 1.5 from -2.0,1 move Not~trust payoffs 0 0 +\draw [line width=\treethickn,color=\playeronecolor] (1.5,-6) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + -- (0,2); +\draw (0.75,-2) node[right,yshift=\yup,color=\playeronecolor] {$Not~trust$\strut}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (0,2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-1.5,-2) {}; + +% Player color legend +\begin{scope}[scale=1,shift={(-4.0,2.0)}] +\node[inner sep=0pt,minimum size=\ndiam,draw=\playeronecolor,fill=\playeronecolor,shape=circle] at (0,0) {}; +\node[anchor=west] at (0.3,0) {Buyer}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playertwocolor,fill=\playertwocolor,shape=circle] at (0,-0.625) {}; +\node[anchor=west] at (0.3,-0.625) {Seller}; +\end{scope} + +\end{tikzpicture} + + \end{document} diff --git a/catalog/img/oneshot_trust_unique_NE.ef b/catalog/img/oneshot_trust_unique_NE.ef new file mode 100644 index 000000000..ffef1e816 --- /dev/null +++ b/catalog/img/oneshot_trust_unique_NE.ef @@ -0,0 +1,7 @@ +player 1 name Buyer +player 2 name Seller +level -2.0 node 1 player 1 +level 2.0 node 1 player 2 xshift -1.5 from -2.0,1 move Trust +level 6.0 node 1 xshift -1.0 from 2.0,1 move Honor payoffs 1 1 +level 6.0 node 2 xshift 1.0 from 2.0,1 move Abuse payoffs 1/2 2 +level 6.0 node 3 xshift 1.5 from -2.0,1 move Not~trust payoffs 0 0 diff --git a/catalog/img/oneshot_trust_unique_NE.pdf b/catalog/img/oneshot_trust_unique_NE.pdf new file mode 100644 index 000000000..111663dc5 Binary files /dev/null and b/catalog/img/oneshot_trust_unique_NE.pdf differ diff --git a/catalog/img/oneshot_trust_unique_NE.png b/catalog/img/oneshot_trust_unique_NE.png new file mode 100644 index 000000000..1af36b13c Binary files /dev/null and b/catalog/img/oneshot_trust_unique_NE.png differ diff --git a/catalog/img/oneshot_trust_unique_NE.tex b/catalog/img/oneshot_trust_unique_NE.tex new file mode 100644 index 000000000..e97ea2edc --- /dev/null +++ b/catalog/img/oneshot_trust_unique_NE.tex @@ -0,0 +1,95 @@ +\documentclass[tikz,border=10pt]{standalone} + \usepackage{newpxtext,newpxmath} + \linespread{1.10} + \usetikzlibrary{shapes} + \usetikzlibrary{arrows.meta} + + \begin{document} + + % TikZ code with built-in styling for game trees +% TikZ libraries required for game trees +\usetikzlibrary{shapes} +\usetikzlibrary{arrows.meta} + +% Style settings for game tree formatting +\tikzset{ + every node/.append style={font=\rmfamily}, + every text node part/.append style={align=center}, + node distance=1.5mm, + thick +} + +% Built-in macro definitions for game tree drawing +\newdimen\ndiam +\ndiam1.5mm +\newdimen\sqwidth +\sqwidth1.6mm +\newdimen\spx +\spx.7mm +\newdimen\spy +\spy.5mm +\newdimen\yup +\yup0.5mm +\newdimen\yfracup +\yfracup1mm +\newdimen\paydown +\paydown2.5ex +\newdimen\treethickn +\treethickn1.0pt +\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} + +% Game tree content from /Users/echalstrey/projects/gambit/catalog/img/oneshot_trust_unique_NE.ef +\begin{tikzpicture}[scale=0.8 + , StealthFill/.tip={Stealth[line width=.7pt,inset=0pt,length=13pt,angle'=30]}] +% \draw [help lines, color=green] (-5,0) grid (5,-6); +%% player 1 name Buyer +\def\playerone{Buyer} +%% player 2 name Seller +\def\playertwo{Seller} +%% level -2.0 node 1 player 1 +\draw [line width=\treethickn] (0,2) + ; +%% level 2.0 node 1 player 2 xshift -1.5 from -2.0,1 move Trust +\draw [line width=\treethickn,color=\playeronecolor] (-1.5,-2) + -- (0,2); +\draw (-0.75,0) node[left,yshift=\yup,color=\playeronecolor] {$Trust$\strut}; +%% level 6.0 node 1 xshift -1.0 from 2.0,1 move Honor payoffs 1 1 +\draw [line width=\treethickn,color=\playertwocolor] (-2.5,-6) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (-1.5,-2); +\draw (-2,-4) node[left,yshift=\yup,color=\playertwocolor] {$Honor$\strut}; +%% level 6.0 node 2 xshift 1.0 from 2.0,1 move Abuse payoffs 1/2 2 +\draw [line width=\treethickn,color=\playertwocolor] (-0.5,-6) + node[below,yshift=0.1\paydown] {$1/2$\strut} + node[below,yshift=-0.9\paydown] {$2$\strut} + -- (-1.5,-2); +\draw (-1,-4) node[right,yshift=\yup,color=\playertwocolor] {$Abuse$\strut}; +%% level 6.0 node 3 xshift 1.5 from -2.0,1 move Not~trust payoffs 0 0 +\draw [line width=\treethickn,color=\playeronecolor] (1.5,-6) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + -- (0,2); +\draw (0.75,-2) node[right,yshift=\yup,color=\playeronecolor] {$Not~trust$\strut}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (0,2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-1.5,-2) {}; + +% Player color legend +\begin{scope}[scale=1,shift={(-4.0,2.0)}] +\node[inner sep=0pt,minimum size=\ndiam,draw=\playeronecolor,fill=\playeronecolor,shape=circle] at (0,0) {}; +\node[anchor=west] at (0.3,0) {Buyer}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playertwocolor,fill=\playertwocolor,shape=circle] at (0,-0.625) {}; +\node[anchor=west] at (0.3,-0.625) {Seller}; +\end{scope} + +\end{tikzpicture} + + \end{document} diff --git a/catalog/img/selten1975/fig1.ef b/catalog/img/selten1975/fig1.ef new file mode 100644 index 000000000..32f7227d8 --- /dev/null +++ b/catalog/img/selten1975/fig1.ef @@ -0,0 +1,13 @@ +player 1 name Player~1 +player 2 name Player~2 +player 3 name Player~3 +level -2.0 node 1 player 1 +level 2.0 node 1 player 2 xshift -2.75 from -2.0,1 move R +level 10.0 node 1 xshift -1.5 from 2.0,1 move R payoffs 1 1 1 +level 6.0 node 1 xshift 1.5 from 2.0,1 move L +level 10.0 node 2 xshift -1.0 from 6.0,1 move R payoffs 4 4 0 +level 10.0 node 3 xshift 1.0 from 6.0,1 move L payoffs 0 0 1 +level 2.0 node 2 xshift 2.75 from -2.0,1 move L +level 10.0 node 4 xshift -1.0 from 2.0,2 move R payoffs 3 2 2 +level 10.0 node 5 xshift 1.0 from 2.0,2 move L payoffs 0 0 0 +iset 6.0,1 2.0,2 player 3 diff --git a/catalog/img/selten1975/fig1.pdf b/catalog/img/selten1975/fig1.pdf new file mode 100644 index 000000000..c45e6d8d4 Binary files /dev/null and b/catalog/img/selten1975/fig1.pdf differ diff --git a/catalog/img/selten1975/fig1.png b/catalog/img/selten1975/fig1.png new file mode 100644 index 000000000..cd1f5b50e Binary files /dev/null and b/catalog/img/selten1975/fig1.png differ diff --git a/catalog/img/selten1975/fig1.tex b/catalog/img/selten1975/fig1.tex new file mode 100644 index 000000000..2cd67f2bc --- /dev/null +++ b/catalog/img/selten1975/fig1.tex @@ -0,0 +1,129 @@ +\documentclass[tikz,border=10pt]{standalone} + \usepackage{newpxtext,newpxmath} + \linespread{1.10} + \usetikzlibrary{shapes} + \usetikzlibrary{arrows.meta} + + \begin{document} + + % TikZ code with built-in styling for game trees +% TikZ libraries required for game trees +\usetikzlibrary{shapes} +\usetikzlibrary{arrows.meta} + +% Style settings for game tree formatting +\tikzset{ + every node/.append style={font=\rmfamily}, + every text node part/.append style={align=center}, + node distance=1.5mm, + thick +} + +% Built-in macro definitions for game tree drawing +\newdimen\ndiam +\ndiam1.5mm +\newdimen\sqwidth +\sqwidth1.6mm +\newdimen\spx +\spx.7mm +\newdimen\spy +\spy.5mm +\newdimen\yup +\yup0.5mm +\newdimen\yfracup +\yfracup1mm +\newdimen\paydown +\paydown2.5ex +\newdimen\treethickn +\treethickn1.0pt +\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} + +% Game tree content from /Users/echalstrey/projects/gambit/catalog/img/selten1975/fig1.ef +\begin{tikzpicture}[scale=0.8 + , StealthFill/.tip={Stealth[line width=.7pt,inset=0pt,length=13pt,angle'=30]}] +% \draw [help lines, color=green] (-5,0) grid (5,-6); +\draw [color=\playerthreecolor] (-1.515,-5.735) arc(135:315:0.375) + -- (3.015,-2.265) arc(-45:135:0.375) -- cycle; +%% player 1 name Player~1 +\def\playerone{Player~1} +%% player 2 name Player~2 +\def\playertwo{Player~2} +%% player 3 name Player~3 +\def\playerthree{Player~3} +%% level -2.0 node 1 player 1 +\draw [line width=\treethickn] (0,2) + ; +%% level 2.0 node 1 player 2 xshift -2.75 from -2.0,1 move R +\draw [line width=\treethickn,color=\playeronecolor] (-2.75,-2) + -- (0,2); +\draw (-1.375,0) node[left,yshift=\yup,color=\playeronecolor] {$R$\strut}; +%% level 10.0 node 1 xshift -1.5 from 2.0,1 move R payoffs 1 1 1 +\draw [line width=\treethickn,color=\playertwocolor] (-4.25,-10) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + node[below,yshift=-1.9\paydown] {$1$\strut} + -- (-2.75,-2); +\draw (-3.5,-6) node[left,yshift=\yup,color=\playertwocolor] {$R$\strut}; +%% level 6.0 node 1 xshift 1.5 from 2.0,1 move L +\draw [line width=\treethickn,color=\playertwocolor] (-1.25,-6) + -- (-2.75,-2); +\draw (-2,-4) node[right,yshift=\yup,color=\playertwocolor] {$L$\strut}; +%% level 10.0 node 2 xshift -1.0 from 6.0,1 move R payoffs 4 4 0 +\draw [line width=\treethickn,color=\playerthreecolor] (-2.25,-10) + node[below,yshift=0.1\paydown] {$4$\strut} + node[below,yshift=-0.9\paydown] {$4$\strut} + node[below,yshift=-1.9\paydown] {$0$\strut} + -- (-1.25,-6); +\draw (-1.75,-8) node[left,yshift=\yup,color=\playerthreecolor] {$R$\strut}; +%% level 10.0 node 3 xshift 1.0 from 6.0,1 move L payoffs 0 0 1 +\draw [line width=\treethickn,color=\playerthreecolor] (-0.25,-10) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + node[below,yshift=-1.9\paydown] {$1$\strut} + -- (-1.25,-6); +\draw (-0.75,-8) node[right,yshift=\yup,color=\playerthreecolor] {$L$\strut}; +%% level 2.0 node 2 xshift 2.75 from -2.0,1 move L +\draw [line width=\treethickn,color=\playeronecolor] (2.75,-2) + -- (0,2); +\draw (1.375,0) node[right,yshift=\yup,color=\playeronecolor] {$L$\strut}; +%% level 10.0 node 4 xshift -1.0 from 2.0,2 move R payoffs 3 2 2 +\draw [line width=\treethickn,color=\playerthreecolor] (1.75,-10) + node[below,yshift=0.1\paydown] {$3$\strut} + node[below,yshift=-0.9\paydown] {$2$\strut} + node[below,yshift=-1.9\paydown] {$2$\strut} + -- (2.75,-2); +\draw (2.25,-6) node[left,yshift=\yup,color=\playerthreecolor] {$R$\strut}; +%% level 10.0 node 5 xshift 1.0 from 2.0,2 move L payoffs 0 0 0 +\draw [line width=\treethickn,color=\playerthreecolor] (3.75,-10) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + node[below,yshift=-1.9\paydown] {$0$\strut} + -- (2.75,-2); +\draw (3.25,-6) node[right,yshift=\yup,color=\playerthreecolor] {$L$\strut}; +%% iset 6.0,1 2.0,2 player 3 +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (0,2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (-2.75,-2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playerthreecolor, fill=\playerthreecolor, shape=circle] at (-1.25,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playerthreecolor, fill=\playerthreecolor, shape=circle] at (2.75,-2) {}; + +% Player color legend +\begin{scope}[scale=1,shift={(-5.75,2.0)}] +\node[inner sep=0pt,minimum size=\ndiam,draw=\playeronecolor,fill=\playeronecolor,shape=circle] at (0,0) {}; +\node[anchor=west] at (0.3,0) {Player~1}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playertwocolor,fill=\playertwocolor,shape=circle] at (0,-0.625) {}; +\node[anchor=west] at (0.3,-0.625) {Player~2}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playerthreecolor,fill=\playerthreecolor,shape=circle] at (0,-1.25) {}; +\node[anchor=west] at (0.3,-1.25) {Player~3}; +\end{scope} + +\end{tikzpicture} + + \end{document} diff --git a/catalog/img/selten1975/fig2.ef b/catalog/img/selten1975/fig2.ef new file mode 100644 index 000000000..745edde26 --- /dev/null +++ b/catalog/img/selten1975/fig2.ef @@ -0,0 +1,9 @@ +player 1 name Player~1 +player 2 name Player~2 +level -2.0 node 1 player 1 +level 10.0 node 1 xshift -1.75 from -2.0,1 move R payoffs 1 1 +level 2.0 node 1 player 2 xshift 1.75 from -2.0,1 move L +level 10.0 node 2 xshift -1.5 from 2.0,1 move R payoffs 0 2 +level 6.0 node 1 player 1 xshift 1.5 from 2.0,1 move L +level 10.0 node 3 xshift -1.0 from 6.0,1 move r payoffs 0 3 +level 10.0 node 4 xshift 1.0 from 6.0,1 move l payoffs 2 0 diff --git a/catalog/img/selten1975/fig2.pdf b/catalog/img/selten1975/fig2.pdf new file mode 100644 index 000000000..4a6fde1f2 Binary files /dev/null and b/catalog/img/selten1975/fig2.pdf differ diff --git a/catalog/img/selten1975/fig2.png b/catalog/img/selten1975/fig2.png new file mode 100644 index 000000000..93509150a Binary files /dev/null and b/catalog/img/selten1975/fig2.png differ diff --git a/catalog/img/selten1975/fig2.tex b/catalog/img/selten1975/fig2.tex new file mode 100644 index 000000000..fafc48f7f --- /dev/null +++ b/catalog/img/selten1975/fig2.tex @@ -0,0 +1,106 @@ +\documentclass[tikz,border=10pt]{standalone} + \usepackage{newpxtext,newpxmath} + \linespread{1.10} + \usetikzlibrary{shapes} + \usetikzlibrary{arrows.meta} + + \begin{document} + + % TikZ code with built-in styling for game trees +% TikZ libraries required for game trees +\usetikzlibrary{shapes} +\usetikzlibrary{arrows.meta} + +% Style settings for game tree formatting +\tikzset{ + every node/.append style={font=\rmfamily}, + every text node part/.append style={align=center}, + node distance=1.5mm, + thick +} + +% Built-in macro definitions for game tree drawing +\newdimen\ndiam +\ndiam1.5mm +\newdimen\sqwidth +\sqwidth1.6mm +\newdimen\spx +\spx.7mm +\newdimen\spy +\spy.5mm +\newdimen\yup +\yup0.5mm +\newdimen\yfracup +\yfracup1mm +\newdimen\paydown +\paydown2.5ex +\newdimen\treethickn +\treethickn1.0pt +\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} + +% Game tree content from /Users/echalstrey/projects/gambit/catalog/img/selten1975/fig2.ef +\begin{tikzpicture}[scale=0.8 + , StealthFill/.tip={Stealth[line width=.7pt,inset=0pt,length=13pt,angle'=30]}] +% \draw [help lines, color=green] (-5,0) grid (5,-6); +%% player 1 name Player~1 +\def\playerone{Player~1} +%% player 2 name Player~2 +\def\playertwo{Player~2} +%% level -2.0 node 1 player 1 +\draw [line width=\treethickn] (0,2) + ; +%% level 10.0 node 1 xshift -1.75 from -2.0,1 move R payoffs 1 1 +\draw [line width=\treethickn,color=\playeronecolor] (-1.75,-10) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$1$\strut} + -- (0,2); +\draw (-0.875,-4) node[left,yshift=\yup,color=\playeronecolor] {$R$\strut}; +%% level 2.0 node 1 player 2 xshift 1.75 from -2.0,1 move L +\draw [line width=\treethickn,color=\playeronecolor] (1.75,-2) + -- (0,2); +\draw (0.875,0) node[right,yshift=\yup,color=\playeronecolor] {$L$\strut}; +%% level 10.0 node 2 xshift -1.5 from 2.0,1 move R payoffs 0 2 +\draw [line width=\treethickn,color=\playertwocolor] (0.25,-10) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$2$\strut} + -- (1.75,-2); +\draw (1,-6) node[left,yshift=\yup,color=\playertwocolor] {$R$\strut}; +%% level 6.0 node 1 player 1 xshift 1.5 from 2.0,1 move L +\draw [line width=\treethickn,color=\playertwocolor] (3.25,-6) + -- (1.75,-2); +\draw (2.5,-4) node[right,yshift=\yup,color=\playertwocolor] {$L$\strut}; +%% level 10.0 node 3 xshift -1.0 from 6.0,1 move r payoffs 0 3 +\draw [line width=\treethickn,color=\playeronecolor] (2.25,-10) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$3$\strut} + -- (3.25,-6); +\draw (2.75,-8) node[left,yshift=\yup,color=\playeronecolor] {$r$\strut}; +%% level 10.0 node 4 xshift 1.0 from 6.0,1 move l payoffs 2 0 +\draw [line width=\treethickn,color=\playeronecolor] (4.25,-10) + node[below,yshift=0.1\paydown] {$2$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + -- (3.25,-6); +\draw (3.75,-8) node[right,yshift=\yup,color=\playeronecolor] {$l$\strut}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (0,2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (1.75,-2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (3.25,-6) {}; + +% Player color legend +\begin{scope}[scale=1,shift={(-3.25,2.0)}] +\node[inner sep=0pt,minimum size=\ndiam,draw=\playeronecolor,fill=\playeronecolor,shape=circle] at (0,0) {}; +\node[anchor=west] at (0.3,0) {Player~1}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playertwocolor,fill=\playertwocolor,shape=circle] at (0,-0.625) {}; +\node[anchor=west] at (0.3,-0.625) {Player~2}; +\end{scope} + +\end{tikzpicture} + + \end{document} diff --git a/catalog/img/selten1975/fig3.ef b/catalog/img/selten1975/fig3.ef new file mode 100644 index 000000000..777d2eefc --- /dev/null +++ b/catalog/img/selten1975/fig3.ef @@ -0,0 +1,15 @@ +player 1 name Player~1 +player 2 name Player~2 +player 3 name Player~3 +level -2.0 node 1 player 1 +level 2.0 node 1 xshift -3.625 from -2.0,1 move R +level 14.0 node 1 xshift -1.0 from 2.0,1 move R payoffs 3 0 3 +level 14.0 node 2 xshift 1.0 from 2.0,1 move L payoffs 0 0 0 +level 2.0 node 2 player 2 xshift 3.625 from -2.0,1 move L +level 6.0 node 1 player 1 xshift -1.75 from 2.0,2 move R +level 10.0 node 1 xshift -1.5 from 6.0,1 move R +level 14.0 node 3 xshift -1.0 from 10.0,1 move R payoffs 4 4 0 +level 14.0 node 4 xshift 1.0 from 10.0,1 move L payoffs 0 0 5 +level 14.0 node 5 xshift 1.5 from 6.0,1 move L payoffs 2 0 0 +level 14.0 node 6 xshift 1.75 from 2.0,2 move L payoffs 1 3 0 +iset 2.0,1 10.0,1 player 3 diff --git a/catalog/img/selten1975/fig3.pdf b/catalog/img/selten1975/fig3.pdf new file mode 100644 index 000000000..b625a678d Binary files /dev/null and b/catalog/img/selten1975/fig3.pdf differ diff --git a/catalog/img/selten1975/fig3.png b/catalog/img/selten1975/fig3.png new file mode 100644 index 000000000..e41395fd0 Binary files /dev/null and b/catalog/img/selten1975/fig3.png differ diff --git a/catalog/img/selten1975/fig3.tex b/catalog/img/selten1975/fig3.tex new file mode 100644 index 000000000..dd82fff0f --- /dev/null +++ b/catalog/img/selten1975/fig3.tex @@ -0,0 +1,141 @@ +\documentclass[tikz,border=10pt]{standalone} + \usepackage{newpxtext,newpxmath} + \linespread{1.10} + \usetikzlibrary{shapes} + \usetikzlibrary{arrows.meta} + + \begin{document} + + % TikZ code with built-in styling for game trees +% TikZ libraries required for game trees +\usetikzlibrary{shapes} +\usetikzlibrary{arrows.meta} + +% Style settings for game tree formatting +\tikzset{ + every node/.append style={font=\rmfamily}, + every text node part/.append style={align=center}, + node distance=1.5mm, + thick +} + +% Built-in macro definitions for game tree drawing +\newdimen\ndiam +\ndiam1.5mm +\newdimen\sqwidth +\sqwidth1.6mm +\newdimen\spx +\spx.7mm +\newdimen\spy +\spy.5mm +\newdimen\yup +\yup0.5mm +\newdimen\yfracup +\yfracup1mm +\newdimen\paydown +\paydown2.5ex +\newdimen\treethickn +\treethickn1.0pt +\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} + +% Game tree content from /Users/echalstrey/projects/gambit/catalog/img/selten1975/fig3.ef +\begin{tikzpicture}[scale=0.8 + , StealthFill/.tip={Stealth[line width=.7pt,inset=0pt,length=13pt,angle'=30]}] +% \draw [help lines, color=green] (-5,0) grid (5,-6); +\draw [color=\playerthreecolor] (-3.29,-1.832) arc(26.6:206.6:0.375) + -- (0.04,-10.168) arc(-153.4:26.6:0.375) -- cycle; +%% player 1 name Player~1 +\def\playerone{Player~1} +%% player 2 name Player~2 +\def\playertwo{Player~2} +%% player 3 name Player~3 +\def\playerthree{Player~3} +%% level -2.0 node 1 player 1 +\draw [line width=\treethickn] (0,2) + ; +%% level 2.0 node 1 xshift -3.625 from -2.0,1 move R +\draw [line width=\treethickn,color=\playeronecolor] (-3.625,-2) + -- (0,2); +\draw (-1.812,0) node[left,yshift=\yup,color=\playeronecolor] {$R$\strut}; +%% level 14.0 node 1 xshift -1.0 from 2.0,1 move R payoffs 3 0 3 +\draw [line width=\treethickn,color=\playerthreecolor] (-4.625,-14) + node[below,yshift=0.1\paydown] {$3$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + node[below,yshift=-1.9\paydown] {$3$\strut} + -- (-3.625,-2); +\draw (-4.125,-8) node[left,yshift=\yup,color=\playerthreecolor] {$R$\strut}; +%% level 14.0 node 2 xshift 1.0 from 2.0,1 move L payoffs 0 0 0 +\draw [line width=\treethickn,color=\playerthreecolor] (-2.625,-14) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + node[below,yshift=-1.9\paydown] {$0$\strut} + -- (-3.625,-2); +\draw (-3.125,-8) node[right,yshift=\yup,color=\playerthreecolor] {$L$\strut}; +%% level 2.0 node 2 player 2 xshift 3.625 from -2.0,1 move L +\draw [line width=\treethickn,color=\playeronecolor] (3.625,-2) + -- (0,2); +\draw (1.812,0) node[right,yshift=\yup,color=\playeronecolor] {$L$\strut}; +%% level 6.0 node 1 player 1 xshift -1.75 from 2.0,2 move R +\draw [line width=\treethickn,color=\playertwocolor] (1.875,-6) + -- (3.625,-2); +\draw (2.75,-4) node[left,yshift=\yup,color=\playertwocolor] {$R$\strut}; +%% level 10.0 node 1 xshift -1.5 from 6.0,1 move R +\draw [line width=\treethickn,color=\playeronecolor] (0.375,-10) + -- (1.875,-6); +\draw (1.125,-8) node[left,yshift=\yup,color=\playeronecolor] {$R$\strut}; +%% level 14.0 node 3 xshift -1.0 from 10.0,1 move R payoffs 4 4 0 +\draw [line width=\treethickn,color=\playerthreecolor] (-0.625,-14) + node[below,yshift=0.1\paydown] {$4$\strut} + node[below,yshift=-0.9\paydown] {$4$\strut} + node[below,yshift=-1.9\paydown] {$0$\strut} + -- (0.375,-10); +\draw (-0.125,-12) node[left,yshift=\yup,color=\playerthreecolor] {$R$\strut}; +%% level 14.0 node 4 xshift 1.0 from 10.0,1 move L payoffs 0 0 5 +\draw [line width=\treethickn,color=\playerthreecolor] (1.375,-14) + node[below,yshift=0.1\paydown] {$0$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + node[below,yshift=-1.9\paydown] {$5$\strut} + -- (0.375,-10); +\draw (0.875,-12) node[right,yshift=\yup,color=\playerthreecolor] {$L$\strut}; +%% level 14.0 node 5 xshift 1.5 from 6.0,1 move L payoffs 2 0 0 +\draw [line width=\treethickn,color=\playeronecolor] (3.375,-14) + node[below,yshift=0.1\paydown] {$2$\strut} + node[below,yshift=-0.9\paydown] {$0$\strut} + node[below,yshift=-1.9\paydown] {$0$\strut} + -- (1.875,-6); +\draw (2.625,-10) node[right,yshift=\yup,color=\playeronecolor] {$L$\strut}; +%% level 14.0 node 6 xshift 1.75 from 2.0,2 move L payoffs 1 3 0 +\draw [line width=\treethickn,color=\playertwocolor] (5.375,-14) + node[below,yshift=0.1\paydown] {$1$\strut} + node[below,yshift=-0.9\paydown] {$3$\strut} + node[below,yshift=-1.9\paydown] {$0$\strut} + -- (3.625,-2); +\draw (4.5,-8) node[right,yshift=\yup,color=\playertwocolor] {$L$\strut}; +%% iset 2.0,1 10.0,1 player 3 +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (0,2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playerthreecolor, fill=\playerthreecolor, shape=circle] at (-3.625,-2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playertwocolor, fill=\playertwocolor, shape=circle] at (3.625,-2) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playeronecolor, fill=\playeronecolor, shape=circle] at (1.875,-6) {}; +\node[inner sep=0pt,minimum size=\ndiam, draw=\playerthreecolor, fill=\playerthreecolor, shape=circle] at (0.375,-10) {}; + +% Player color legend +\begin{scope}[scale=1,shift={(-6.125,2.0)}] +\node[inner sep=0pt,minimum size=\ndiam,draw=\playeronecolor,fill=\playeronecolor,shape=circle] at (0,0) {}; +\node[anchor=west] at (0.3,0) {Player~1}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playertwocolor,fill=\playertwocolor,shape=circle] at (0,-0.625) {}; +\node[anchor=west] at (0.3,-0.625) {Player~2}; +\node[inner sep=0pt,minimum size=\ndiam,draw=\playerthreecolor,fill=\playerthreecolor,shape=circle] at (0,-1.25) {}; +\node[anchor=west] at (0.3,-1.25) {Player~3}; +\end{scope} + +\end{tikzpicture} + + \end{document} diff --git a/doc/_static/custom.css b/doc/_static/custom.css new file mode 100644 index 000000000..9a9155293 --- /dev/null +++ b/doc/_static/custom.css @@ -0,0 +1,22 @@ +/* Custom CSS for Gambit Documentation */ +.tight-table .figure img { + max-height: 500px; + width: auto; + display: block; + margin-left: auto; + margin-right: auto; +} + +table.tight-table th, +table.tight-table td { + text-align: center; +} + +table.tight-table td .highlight pre, +table.tight-table td .highlight { + text-align: left; +} + +table.tight-table td .sd-card-body { + text-align: left; +} diff --git a/doc/conf.py b/doc/conf.py index 65760193b..668f99ccc 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -29,6 +29,8 @@ "IPython.sphinxext.ipython_directive", "sphinx_design", "nbsphinx", + "sphinxcontrib.tikz", + "jupyter_sphinx", ] # IPython directive configuration @@ -116,22 +118,10 @@ # documentation. html_theme_options = { "external_links": [ - { - "name": "GitHub", - "url": "https://github.com/gambitproject/gambit" - }, - { - "name": "Releases", - "url": "https://github.com/gambitproject/gambit/releases" - }, - { - "name": "Older releases", - "url": "https://sourceforge.net/projects/gambit/files/" - }, - { - "name": "Cite Gambit", - "url": "https://www.gambit-project.org/cite/" - } + {"name": "GitHub", "url": "https://github.com/gambitproject/gambit"}, + {"name": "Releases", "url": "https://github.com/gambitproject/gambit/releases"}, + {"name": "Older releases", "url": "https://sourceforge.net/projects/gambit/files/"}, + {"name": "Cite Gambit", "url": "https://www.gambit-project.org/cite/"}, ], "navbar_end": ["theme-switcher", "navbar-icon-links"], "icon_links": [ @@ -168,6 +158,11 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] +# Custom CSS files +html_css_files = [ + "custom.css", +] + # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' @@ -217,8 +212,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ("index", "Gambit.tex", "Gambit Documentation", - "The Gambit Project", "manual"), + ("index", "Gambit.tex", "Gambit Documentation", "The Gambit Project", "manual"), ] # The name of an image file (relative to this directory) to place at the top of diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index 66a224cb7..c489e4aa9 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -6,6 +6,7 @@ Updating the Games Catalog This page covers the process for contributing to and updating Gambit's :ref:`Games Catalog `. To do so, you will need to have the `gambit` GitHub repo cloned and be able to submit pull request via GitHub; you may wish to first review the :ref:`contributor guidelines `. +You'll also need to have a developer install of `pygambit` available in your Python environment, see :ref:`build-python`. You can add games to the catalog saved in a valid representation :ref:`format `. Currently supported representations are: @@ -26,15 +27,18 @@ Add new game files 3. **Update the catalog:** - Use the ``update.py`` script to update Gambit's documentation & build files. + Reinstall the package to pick up the new game file(s) in the ``pygambit.catalog`` module. + Then use the ``update.py`` script to update Gambit's documentation & build files, as well as generating images for the new game(s). .. code-block:: bash + pip install . python build_support/catalog/update.py --build .. note:: - Run this script in a Python environment where ``pygambit`` itself is also :ref:`installed `. + Regenerate all images in the catalog with the ``--regenerate-images`` flag. + Update the ``draw_tree_args`` in ``build_support/catalog/update.py`` to change the default visualization parameters. .. warning:: diff --git a/pyproject.toml b/pyproject.toml index 71134753d..b9c98e9b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,9 @@ doc = [ "pickleshare", "jupyter", "open_spiel; sys_platform != 'win32'", - "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.4.0" + "draw-tree @ git+https://github.com/gambitproject/draw_tree.git@v0.4.1", + "sphinxcontrib-tikz", + "jupyter_sphinx", ] [project.urls] diff --git a/src/games/game.h b/src/games/game.h index 8d01bbbe7..fe6a4d1b8 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -939,6 +939,9 @@ class GameRep : public std::enable_shared_from_this { } return false; } + /// Returns a list of all subgame roots in the game + virtual std::vector GetSubgames() const { throw UndefinedException(); } + //@} /// @name Writing data files diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 8933e1bb0..fd0ae033d 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -20,12 +20,14 @@ // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // -#include #include #include +#include +#include #include -#include #include +#include +#include #include #include "gambit.h" @@ -373,32 +375,18 @@ bool GameNodeRep::IsSuccessorOf(GameNode p_node) const bool GameNodeRep::IsSubgameRoot() const { - // First take care of a couple easy cases - if (m_children.empty() || m_infoset->m_members.size() > 1) { - return false; - } - if (!m_parent) { - return true; + // TODO: Currently O(S) per call where S = number of subgames. + // Will become O(1) when GameSubgameRep adds a back-pointer (like m_infoset). + if (m_children.empty()) { + return !GetParent(); } - // A node is a subgame root if and only if in every information set, - // either all members succeed the node in the tree, - // or all members do not succeed the node in the tree. - for (auto player : m_game->GetPlayers()) { - for (auto infoset : player->GetInfosets()) { - const bool precedes = infoset->m_members.front()->IsSuccessorOf( - std::const_pointer_cast(shared_from_this())); - if (std::any_of(std::next(infoset->m_members.begin()), infoset->m_members.end(), - [this, precedes](const std::shared_ptr &m) { - return m->IsSuccessorOf(std::const_pointer_cast( - shared_from_this())) != precedes; - })) { - return false; - } - } + auto *tree_game = static_cast(m_game); + if (tree_game->m_subgames.empty()) { + tree_game->BuildSubgameRoots(); } - return true; + return contains(tree_game->m_subgames, const_cast(this)); } bool GameNodeRep::IsStrategyReachable() const @@ -936,6 +924,7 @@ void GameTreeRep::ClearComputedValues() const m_ownPriorActionInfo = nullptr; const_cast(this)->m_unreachableNodes = nullptr; m_absentMindedInfosets.clear(); + m_subgames.clear(); m_computedValues = false; } @@ -1139,6 +1128,122 @@ void GameTreeRep::BuildUnreachableNodes() const } } +void GameTreeRep::BuildSubgameRoots() const +{ + if (!m_subgames.empty()) { + return; + } + + struct Range { + int m_min = std::numeric_limits::max(); + int m_max = 0; + + void Merge(const Range &p_source) + { + m_min = std::min(m_min, p_source.m_min); + m_max = std::max(m_max, p_source.m_max); + } + + bool operator==(const Range &p_other) const + { + return m_min == p_other.m_min && m_max == p_other.m_max; + } + }; + + std::unordered_map disc; + std::unordered_map hull; + + // Phase 1: Compute subtree spans and infoset hulls + struct SpanVisitor { + std::unordered_map &m_disc; + std::unordered_map &m_hull; + int m_counter = 0; + + static DFSCallbackResult OnEnter(GameNode, int) { return DFSCallbackResult::Continue; } + static DFSCallbackResult OnAction(GameNode, GameNode, int) + { + return DFSCallbackResult::Continue; + } + static void OnVisit(GameNode, int) {} + + DFSCallbackResult OnExit(const GameNode &p_node, int) + { + GameNodeRep *node = p_node.get(); + if (p_node->IsTerminal()) { + m_counter++; + m_disc[node] = {m_counter, m_counter}; + } + else { + Range &node_disc = m_disc[node]; + const auto &children = p_node->GetChildren(); + node_disc.m_min = m_disc.at(children.front().get()).m_min; + node_disc.m_max = m_disc.at(children.back().get()).m_max; + m_hull[node->m_infoset].Merge(node_disc); + } + return DFSCallbackResult::Continue; + } + }; + + // Phase 2: Reachability and detection + struct BridgeVisitor { + const std::unordered_map &m_disc; + const std::unordered_map &m_hull; + std::vector &m_subgames; + std::unordered_map m_low; + + static DFSCallbackResult OnEnter(GameNode, int) { return DFSCallbackResult::Continue; } + static DFSCallbackResult OnAction(GameNode, GameNode, int) + { + return DFSCallbackResult::Continue; + } + static void OnVisit(GameNode, int) {} + + DFSCallbackResult OnExit(const GameNode &p_node, int) + { + GameNodeRep *node = p_node.get(); + if (p_node->IsTerminal()) { + m_low[node] = m_disc.at(node); + return DFSCallbackResult::Continue; + } + + Range &low = m_low[node]; + low = m_hull.at(node->m_infoset); + + for (const auto &child : p_node->GetChildren()) { + low.Merge(m_low.at(child.get())); + } + + if (low == m_disc.at(node)) { + m_subgames.push_back(node); + } + + return DFSCallbackResult::Continue; + } + }; + + auto game = std::const_pointer_cast(shared_from_this()); + + SpanVisitor span_visitor{disc, hull}; + WalkDFS(game, m_root, TraversalOrder::Postorder, span_visitor); + + BridgeVisitor bridge_visitor{disc, hull, m_subgames}; + WalkDFS(game, m_root, TraversalOrder::Postorder, bridge_visitor); +} + +std::vector GameTreeRep::GetSubgames() const +{ + if (m_subgames.empty()) { + BuildSubgameRoots(); + } + + std::vector result; + result.reserve(m_subgames.size()); + for (auto *rep : m_subgames) { + result.emplace_back(rep->shared_from_this()); + } + return result; +} + //------------------------------------------------------------------------ // GameTreeRep: Writing data files //------------------------------------------------------------------------ diff --git a/src/games/gametree.h b/src/games/gametree.h index 03247b661..aaf86ff7f 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -47,6 +47,7 @@ class GameTreeRep final : public GameExplicitRep { mutable std::shared_ptr m_ownPriorActionInfo; mutable std::unique_ptr> m_unreachableNodes; mutable std::set m_absentMindedInfosets; + mutable std::vector m_subgames; /// @name Private auxiliary functions //@{ @@ -98,6 +99,7 @@ class GameTreeRep final : public GameExplicitRep { /// Returns the largest payoff to the player in any play of the game Rational GetPlayerMaxPayoff(const GamePlayer &) const override; bool IsAbsentMinded(const GameInfoset &p_infoset) const override; + std::vector GetSubgames() const override; //@} /// @name Players @@ -182,6 +184,7 @@ class GameTreeRep final : public GameExplicitRep { std::vector BuildConsistentPlaysRecursiveImpl(GameNodeRep *node); void BuildOwnPriorActions() const; void BuildUnreachableNodes() const; + void BuildSubgameRoots() const; }; template class TreeMixedStrategyProfileRep : public MixedStrategyProfileRep { diff --git a/tests/test_games/AM-subgames.efg b/tests/test_games/AM-subgames.efg new file mode 100644 index 000000000..7adfe10ce --- /dev/null +++ b/tests/test_games/AM-subgames.efg @@ -0,0 +1,14 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "2" } 0 +p "" 1 1 "" { "1" "2" } 0 +p "" 2 1 "" { "1" "2" } 0 +t "" 1 "Outcome 1" { 1, -1 } +t "" 2 "Outcome 2" { 2, -2 } +p "" 2 3 "" { "1" "2" } 0 +t "" 3 "Outcome 3" { 3, -3 } +t "" 4 "Outcome 4" { 4, -4 } +p "" 2 2 "" { "1" "2" } 0 +t "" 5 "Outcome 5" { 5, -5 } +t "" 6 "Outcome 6" { 6, -6 } diff --git a/tests/test_games/subgame_roots_finder_overplapping_infosets_with_Nature.efg b/tests/test_games/subgame_roots_finder_overplapping_infosets_with_Nature.efg new file mode 100644 index 000000000..bb6c6b0f0 --- /dev/null +++ b/tests/test_games/subgame_roots_finder_overplapping_infosets_with_Nature.efg @@ -0,0 +1,55 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 1 1 "" { "1" "1" } 0 +t "" 1 "Outcome 1" { 1, -1 } +p "" 2 1 "" { "1" "1" "1" "1" "1" } 0 +p "" 1 2 "" { "1" "1" } 0 +t "" 2 "Outcome 2" { 2, -2 } +p "" 1 3 "" { "1" "1" } 0 +t "" 3 "Outcome 3" { 3, -3 } +t "" 4 "Outcome 4" { 4, -4 } +p "" 1 3 "" { "1" "1" } 0 +p "" 1 2 "" { "1" "1" } 0 +t "" 5 "Outcome 5" { 5, -5 } +t "" 6 "Outcome 6" { 6, -6 } +t "" 7 "Outcome 7" { 7, -7 } +p "" 2 2 "" { "1" "1" } 0 +t "" 8 "Outcome 8" { 8, -8 } +p "" 1 4 "" { "1" "1" } 0 +p "" 2 3 "" { "1" "1" } 0 +t "" 9 "Outcome 9" { 9, -9 } +t "" 10 "Outcome 10" { 10, -10 } +p "" 2 4 "" { "1" "1" } 0 +t "" 11 "Outcome 11" { 11, -11 } +p "" 1 5 "" { "1" "1" } 0 +p "" 1 6 "" { "1" "1" } 0 +p "" 2 5 "" { "1" "1" } 0 +t "" 12 "Outcome 12" { 12, -12 } +t "" 13 "Outcome 13" { 13, -13 } +t "" 14 "Outcome 14" { 14, -14 } +p "" 1 6 "" { "1" "1" } 0 +p "" 2 6 "" { "1" "1" } 0 +c "" 1 "" { "1" 1/2 "1" 1/2 } 0 +p "" 2 5 "" { "1" "1" } 0 +p "" 2 3 "" { "1" "1" } 0 +t "" 15 "Outcome 15" { 15, -15 } +t "" 16 "Outcome 16" { 16, -16 } +t "" 17 "Outcome 17" { 17, -17 } +p "" 2 5 "" { "1" "1" } 0 +t "" 18 "Outcome 18" { 18, -18 } +t "" 19 "Outcome 19" { 19, -19 } +t "" 20 "Outcome 20" { 20, -20 } +p "" 2 6 "" { "1" "1" } 0 +p "" 2 7 "" { "1" "1" } 0 +t "" 21 "Outcome 21" { 21, -21 } +t "" 22 "Outcome 22" { 22, -22 } +p "" 2 7 "" { "1" "1" } 0 +t "" 23 "Outcome 23" { 23, -23 } +t "" 24 "Outcome 24" { 24, -24 } +p "" 1 7 "" { "1" "1" } 0 +t "" 25 "Outcome 25" { 25, -25 } +t "" 26 "Outcome 26" { 26, -26 } +p "" 1 7 "" { "1" "1" } 0 +t "" 27 "Outcome 27" { 27, -27 } +t "" 28 "Outcome 28" { 28, -28 } diff --git a/tests/test_games/subgame_roots_finder_small_subgames_and_overplapping_infosets.efg b/tests/test_games/subgame_roots_finder_small_subgames_and_overplapping_infosets.efg new file mode 100644 index 000000000..e54ada511 --- /dev/null +++ b/tests/test_games/subgame_roots_finder_small_subgames_and_overplapping_infosets.efg @@ -0,0 +1,48 @@ +EFG 2 R "Untitled Extensive Game" { "Player 1" "Player 2" } +"" + +p "" 2 1 "" { "1" "2" } 0 +p "" 1 1 "" { "1" "2" } 0 +t "" 1 "Outcome 1" { 1, -1 } +t "" 2 "Outcome 2" { 2, -2 } +p "" 1 2 "" { "1" "2" } 0 +p "" 2 2 "" { "1" "2" } 0 +t "" 3 "Outcome 3" { 3, -3 } +p "" 1 3 "" { "1" "2" } 0 +p "" 2 3 "" { "1" "1" } 0 +t "" 4 "Outcome 4" { 4, -4 } +p "" 2 4 "" { "1" "1" } 0 +t "" 20 "Outcome 20" { 20, -20 } +t "" 21 "Outcome 21" { 21, -21 } +p "" 2 4 "" { "1" "1" } 0 +p "" 2 3 "" { "1" "1" } 0 +t "" 5 "Outcome 5" { 5, -5 } +t "" 22 "Outcome 22" { 22, -22 } +t "" 23 "Outcome 23" { 23, -23 } +p "" 2 2 "" { "1" "2" } 0 +p "" 2 5 "" { "1" "2" } 0 +p "" 1 4 "" { "1" "2" } 0 +p "" 2 6 "" { "1" "2" } 0 +t "" 6 "Outcome 6" { 6, -6 } +t "" 7 "Outcome 7" { 7, -7 } +t "" 8 "Outcome 8" { 8, -8 } +p "" 1 5 "" { "1" "2" } 0 +p "" 2 7 "" { "1" "2" } 0 +t "" 9 "Outcome 9" { 9, -9 } +t "" 10 "Outcome 10" { 10, -10 } +p "" 2 7 "" { "1" "2" } 0 +p "" 1 6 "" { "1" "2" } 0 +p "" 2 8 "" { "1" "2" } 0 +p "" 1 4 "" { "1" "2" } 0 +t "" 11 "Outcome 11" { 11, -11 } +t "" 12 "Outcome 12" { 12, -12 } +p "" 1 4 "" { "1" "2" } 0 +t "" 13 "Outcome 13" { 13, -13 } +t "" 14 "Outcome 14" { 14, -14 } +t "" 15 "Outcome 15" { 15, -15 } +p "" 1 6 "" { "1" "2" } 0 +t "" 16 "Outcome 16" { 16, -16 } +t "" 17 "Outcome 17" { 17, -17 } +p "" 1 7 "" { "1" "2" } 0 +t "" 18 "Outcome 18" { 18, -18 } +t "" 19 "Outcome 19" { 19, -19 } diff --git a/tests/test_node.py b/tests/test_node.py index 012e9ce0a..c3d35d04f 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -1,3 +1,5 @@ +import dataclasses +import functools import itertools import typing @@ -93,32 +95,6 @@ def test_is_successor_of(): game.root.is_successor_of(game.players[0]) -@pytest.mark.parametrize("game, expected_result", [ - # Games without Absent-Mindedness for which the legacy method is known to be correct. - (games.read_from_file("wichardt.efg"), {0}), - (games.read_from_file("e02.efg"), {0, 2, 4}), - (games.read_from_file("subgames.efg"), {0, 1, 4, 7, 11, 13, 34}), - - pytest.param( - games.read_from_file("AM-driver-subgame.efg"), - {0, 3}, # The correct set of subgame roots - marks=pytest.mark.xfail( - reason="Current method does not detect roots of proper subgames " - "that are members of AM-infosets." - ) - ), -]) -def test_legacy_is_subgame_root_set(game: gbt.Game, expected_result: set): - """ - Tests the legacy `node.is_subgame_root` against an expected set of nodes. - Includes both passing cases and games with Absent-Mindedness where it is expected to fail. - """ - list_nodes = list(game.nodes) - expected_roots = {list_nodes[i] for i in expected_result} - legacy_roots = {node for node in game.nodes if node.is_subgame_root} - assert legacy_roots == expected_roots - - def _get_path_of_action_labels(node: gbt.Node) -> list[str]: """ Computes the path of action labels from the root to the given node. @@ -136,6 +112,109 @@ def _get_path_of_action_labels(node: gbt.Node) -> list[str]: return path +@dataclasses.dataclass +class SubgameRootsTestCase: + """TestCase for testing subgame root detection.""" + factory: typing.Callable[[], gbt.Game] + expected_paths: list[list[str]] + + +SUBGAME_ROOTS_CASES = [ + # ------------------------------------------------------------------------ + # Empty Game + # ------------------------------------------------------------------------ + pytest.param( + SubgameRootsTestCase(factory=gbt.Game.new_tree, expected_paths=[[]]), + id="empty_tree" + ), + # ------------------------------------------------------------------------ + # Perfect Information Games + # ------------------------------------------------------------------------ + pytest.param( + SubgameRootsTestCase( + factory=functools.partial(games.read_from_file, "e02.efg"), + expected_paths=[[], ["L"], ["L", "L"]] + ), + id="centipede_3_rounds" + ), + pytest.param( + SubgameRootsTestCase( + factory=lambda: games.Centipede.get_test_data(N=5, m0=2, m1=7)[0], + expected_paths=[[], ["Push"], ["Push", "Push"], ["Push", "Push", "Push"], + ["Push", "Push", "Push", "Push"]] + ), + id="centipede_5_rounds" + ), + # ------------------------------------------------------------------------ + # Imperfect Information (No Absent-Mindedness) + # ------------------------------------------------------------------------ + pytest.param( + SubgameRootsTestCase( + factory=functools.partial(games.read_from_file, "wichardt.efg"), + expected_paths=[[]] + ), + id="wichardt_no_nontrivial_subgames" + ), + pytest.param( + SubgameRootsTestCase( + factory=functools.partial(games.read_from_file, "binary_3_levels_generic_payoffs.efg"), + expected_paths=[[]] + ), + id="binary_3_levels_no_nontrivial_subgames" + ), + pytest.param( + SubgameRootsTestCase( + factory=functools.partial( + games.read_from_file, + "subgame_roots_finder_small_subgames_and_overplapping_infosets.efg"), + expected_paths=[[], ["1"], ["2"], ["1", "2", "2"], ["2", "1", "2"], + ["1", "1", "1", "2", "2"], ["2", "2", "2"]] + ), + id="small_subgames_and_overlapping_infosets_inside_subgames_no_Nature_moves" + ), + pytest.param( + SubgameRootsTestCase( + factory=functools.partial( + games.read_from_file, + "subgame_roots_finder_overplapping_infosets_with_Nature.efg"), + expected_paths=[[], ["1"], ["1", "1"], ["1", "1", "1"]] + ), + id="overlapping_infosets_inside_subgames_and_Nature_move" + ), + # ------------------------------------------------------------------------ + # Absent-Minded Games + # ------------------------------------------------------------------------ + pytest.param( + SubgameRootsTestCase( + factory=functools.partial(games.read_from_file, "AM-subgames.efg"), + expected_paths=[[], ["2"], ["1", "1"], ["2", "1"]] + ), + id="Absent-minded-game-with-paths-intersecting-infoset-two-times" + ), + pytest.param( + SubgameRootsTestCase( + factory=functools.partial(games.read_from_file, "noPR-action-AM-two-hops.efg"), + expected_paths=[[], ["2", "1", "1"]] + ), + id="Absent-minded-game-with-paths-intersecting-infoset-three-times" + ), +] + + +@pytest.mark.parametrize("test_case", SUBGAME_ROOTS_CASES) +def test_subgame_roots(test_case: SubgameRootsTestCase): + """ + Tests that the set of nodes marked as subgame roots matches the expected + set of paths (Action Labels from Root -> Node). + """ + game = test_case.factory() + + actual_roots = [node for node in game.nodes if node.is_subgame_root] + actual_paths = [_get_path_of_action_labels(node) for node in actual_roots] + + assert sorted(actual_paths) == sorted(test_case.expected_paths) + + @pytest.mark.parametrize("game_file, expected_node_data", [ ( "binary_3_levels_generic_payoffs.efg",