diff --git a/.gitignore b/.gitignore index d2f3fe579..9d37e0d3b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ doc/tutorials/*.png Gambit.app/* *.ipynb_checkpoints *.ef +build_support/msw/gambit.wxs +build_support/osx/Info.plist diff --git a/Makefile.am b/Makefile.am index fe3fc232f..b0680a7d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -100,147 +100,152 @@ EXTRA_DIST = \ src/gui/bitmaps/zoomout.xpm \ src/gui/bitmaps/zoom1.xpm \ src/gui/bitmaps/gambitrc.rc \ - contrib/games/2s2x2x2.efg \ - contrib/games/2smp.efg \ - contrib/games/2x2x2.efg \ - contrib/games/4cards.efg \ - contrib/games/artist1.efg \ - contrib/games/artist2.efg \ - contrib/games/badgame1.efg \ - contrib/games/badgame2.efg \ - contrib/games/bayes1a.efg \ - contrib/games/bayes2a.efg \ - contrib/games/bcp2.efg \ - contrib/games/bcp3.efg \ - contrib/games/bcp4.efg \ - contrib/games/bhg1.efg \ - contrib/games/bhg2.efg \ - contrib/games/bhg3.efg \ - contrib/games/bhg4.efg \ - contrib/games/bhg5.efg \ - contrib/games/caro2.efg \ - contrib/games/cent2.efg \ - contrib/games/cent3.efg \ - contrib/games/cent4.efg \ - contrib/games/cent6.efg \ - contrib/games/centcs10.efg \ - contrib/games/centcs6.efg \ - contrib/games/condjury.efg \ - contrib/games/coord2.efg \ - contrib/games/coord2ts.efg \ - contrib/games/coord3.efg \ - contrib/games/coord4.efg \ - contrib/games/cross.efg \ - contrib/games/cs.efg \ - contrib/games/e01.efg \ - contrib/games/e02.efg \ - contrib/games/e03.efg \ - contrib/games/e04.efg \ - contrib/games/e05.efg \ - contrib/games/e06.efg \ - contrib/games/e07.efg \ - contrib/games/e08.efg \ - contrib/games/e09.efg \ - contrib/games/e10a.efg \ - contrib/games/e10.efg \ - contrib/games/e13.efg \ - contrib/games/e16.efg \ - contrib/games/e17.efg \ - contrib/games/e18.efg \ - contrib/games/g1.efg \ - contrib/games/g2.efg \ - contrib/games/g3.efg \ - contrib/games/holdout7.efg \ - contrib/games/holdout.efg \ - contrib/games/hs1.efg \ - contrib/games/jury_mr.efg \ - contrib/games/jury_un.efg \ - contrib/games/km1.efg \ - contrib/games/km2.efg \ - contrib/games/km3.efg \ - contrib/games/km6.efg \ - contrib/games/montyhal.efg \ - contrib/games/my_2-1.efg \ - contrib/games/my_2-4.efg \ - contrib/games/my_2-8.efg \ - contrib/games/my_3-3a.efg \ - contrib/games/my_3-3b.efg \ - contrib/games/my_3-3c.efg \ - contrib/games/my_3-3d.efg \ - contrib/games/my_3-3e.efg \ - contrib/games/my_3-4.efg \ - contrib/games/myerson.efg \ - contrib/games/nim7.efg \ - contrib/games/nim.efg \ - contrib/games/palf2.efg \ - contrib/games/palf3.efg \ - contrib/games/palf.efg \ - contrib/games/poker2.efg \ - contrib/games/poker.efg \ - contrib/games/pvw2.efg \ - contrib/games/pvw.efg \ - contrib/games/sh3.efg \ - contrib/games/sww1.efg \ - contrib/games/sww2.efg \ - contrib/games/sww3.efg \ - contrib/games/tim.efg \ - contrib/games/ttt.efg \ - contrib/games/vd.efg \ - contrib/games/w_ex1.efg \ - contrib/games/w_ex2.efg \ - contrib/games/wilson1.efg \ - contrib/games/work1.efg \ - contrib/games/work2.efg \ - contrib/games/work3.efg \ - contrib/games/2x2a.nfg \ - contrib/games/2x2const.nfg \ - contrib/games/2x2.nfg \ - contrib/games/2x2x2.nfg \ - contrib/games/2x2x2x2.nfg \ - contrib/games/2x2x2x2x2.nfg \ - contrib/games/3x3x3.nfg \ - contrib/games/5x4x3.nfg \ - contrib/games/8x2x2.nfg \ - contrib/games/8x8.nfg \ - contrib/games/cent2.nfg \ - contrib/games/coord2.nfg \ - contrib/games/coord333.nfg \ - contrib/games/coord3.nfg \ - contrib/games/coord4.nfg \ - contrib/games/csg1.nfg \ - contrib/games/csg2.nfg \ - contrib/games/csg3.nfg \ - contrib/games/csg4.nfg \ - contrib/games/deg1.nfg \ - contrib/games/deg2.nfg \ - contrib/games/e01.nfg \ - contrib/games/e02.nfg \ - contrib/games/e04.nfg \ - contrib/games/e07.nfg \ - contrib/games/g1.nfg \ - contrib/games/g2.nfg \ - contrib/games/g3.nfg \ - contrib/games/loopback.nfg \ - contrib/games/mixdom2.nfg \ - contrib/games/mixdom.nfg \ - contrib/games/oneill.nfg \ - contrib/games/pd.nfg \ - contrib/games/perfect1.nfg \ - contrib/games/perfect2.nfg \ - contrib/games/perfect3.nfg \ - contrib/games/poker.nfg \ - contrib/games/sh3.nfg \ - contrib/games/stengel.nfg \ - contrib/games/sww1.nfg \ - contrib/games/todd1.nfg \ - contrib/games/todd2.nfg \ - contrib/games/todd3.nfg \ - contrib/games/vd.nfg \ - contrib/games/wink3.nfg \ - contrib/games/winkels.nfg \ - contrib/games/yamamoto.nfg \ - contrib/games/zero.nfg \ - src/README.rst + src/README.rst \ + src/pygambit/catalog_game_files/2s2x2x2.efg \ + src/pygambit/catalog_game_files/2smp.efg \ + src/pygambit/catalog_game_files/2x2.nfg \ + src/pygambit/catalog_game_files/2x2a.nfg \ + src/pygambit/catalog_game_files/2x2const.nfg \ + src/pygambit/catalog_game_files/2x2x2-nau.nfg \ + src/pygambit/catalog_game_files/2x2x2.efg \ + src/pygambit/catalog_game_files/2x2x2.nfg \ + src/pygambit/catalog_game_files/2x2x2x2.nfg \ + src/pygambit/catalog_game_files/2x2x2x2x2.nfg \ + src/pygambit/catalog_game_files/3x3x3.nfg \ + src/pygambit/catalog_game_files/4cards.efg \ + src/pygambit/catalog_game_files/5x4x3.nfg \ + src/pygambit/catalog_game_files/8x2x2.nfg \ + src/pygambit/catalog_game_files/8x8.nfg \ + src/pygambit/catalog_game_files/artist1.efg \ + src/pygambit/catalog_game_files/artist2.efg \ + src/pygambit/catalog_game_files/badgame1.efg \ + src/pygambit/catalog_game_files/badgame2.efg \ + src/pygambit/catalog_game_files/bagwell.efg \ + src/pygambit/catalog_game_files/bayes1a.efg \ + src/pygambit/catalog_game_files/bayes2a.efg \ + src/pygambit/catalog_game_files/bcp2.efg \ + src/pygambit/catalog_game_files/bcp3.efg \ + src/pygambit/catalog_game_files/bcp4.efg \ + src/pygambit/catalog_game_files/bhg1.efg \ + src/pygambit/catalog_game_files/bhg2.efg \ + src/pygambit/catalog_game_files/bhg3.efg \ + src/pygambit/catalog_game_files/bhg4.efg \ + src/pygambit/catalog_game_files/bhg5.efg \ + src/pygambit/catalog_game_files/caro2.efg \ + src/pygambit/catalog_game_files/cent2.efg \ + src/pygambit/catalog_game_files/cent2.nfg \ + src/pygambit/catalog_game_files/cent3.efg \ + src/pygambit/catalog_game_files/cent4.efg \ + src/pygambit/catalog_game_files/cent6.efg \ + src/pygambit/catalog_game_files/centcs10.efg \ + src/pygambit/catalog_game_files/centcs6.efg \ + src/pygambit/catalog_game_files/condjury.efg \ + src/pygambit/catalog_game_files/coord2.efg \ + src/pygambit/catalog_game_files/coord2.nfg \ + src/pygambit/catalog_game_files/coord2ts.efg \ + src/pygambit/catalog_game_files/coord3.efg \ + src/pygambit/catalog_game_files/coord3.nfg \ + src/pygambit/catalog_game_files/coord333.nfg \ + src/pygambit/catalog_game_files/coord4.efg \ + src/pygambit/catalog_game_files/coord4.nfg \ + src/pygambit/catalog_game_files/cross.efg \ + src/pygambit/catalog_game_files/cs.efg \ + src/pygambit/catalog_game_files/csg1.nfg \ + src/pygambit/catalog_game_files/csg2.nfg \ + src/pygambit/catalog_game_files/csg3.nfg \ + src/pygambit/catalog_game_files/csg4.nfg \ + src/pygambit/catalog_game_files/deg1.nfg \ + src/pygambit/catalog_game_files/deg2.nfg \ + src/pygambit/catalog_game_files/e01.efg \ + src/pygambit/catalog_game_files/e01.nfg \ + src/pygambit/catalog_game_files/e02.efg \ + src/pygambit/catalog_game_files/e02.nfg \ + src/pygambit/catalog_game_files/e03.efg \ + src/pygambit/catalog_game_files/e04.efg \ + src/pygambit/catalog_game_files/e04.nfg \ + src/pygambit/catalog_game_files/e05.efg \ + src/pygambit/catalog_game_files/e06.efg \ + src/pygambit/catalog_game_files/e07.efg \ + src/pygambit/catalog_game_files/e07.nfg \ + src/pygambit/catalog_game_files/e08.efg \ + src/pygambit/catalog_game_files/e09.efg \ + src/pygambit/catalog_game_files/e10.efg \ + src/pygambit/catalog_game_files/e10a.efg \ + src/pygambit/catalog_game_files/e13.efg \ + src/pygambit/catalog_game_files/e16.efg \ + src/pygambit/catalog_game_files/e17.efg \ + src/pygambit/catalog_game_files/e18.efg \ + src/pygambit/catalog_game_files/g1.efg \ + src/pygambit/catalog_game_files/g1.nfg \ + src/pygambit/catalog_game_files/g2.efg \ + src/pygambit/catalog_game_files/g2.nfg \ + src/pygambit/catalog_game_files/g3.efg \ + src/pygambit/catalog_game_files/g3.nfg \ + src/pygambit/catalog_game_files/holdout.efg \ + src/pygambit/catalog_game_files/holdout7.efg \ + src/pygambit/catalog_game_files/hs1.efg \ + src/pygambit/catalog_game_files/jury_mr.efg \ + src/pygambit/catalog_game_files/jury_un.efg \ + src/pygambit/catalog_game_files/km1.efg \ + src/pygambit/catalog_game_files/km2.efg \ + src/pygambit/catalog_game_files/km3.efg \ + src/pygambit/catalog_game_files/km6.efg \ + src/pygambit/catalog_game_files/loopback.nfg \ + src/pygambit/catalog_game_files/mixdom.nfg \ + src/pygambit/catalog_game_files/mixdom2.nfg \ + src/pygambit/catalog_game_files/montyhal.efg \ + src/pygambit/catalog_game_files/my_2-1.efg \ + src/pygambit/catalog_game_files/my_2-4.efg \ + src/pygambit/catalog_game_files/my_2-8.efg \ + src/pygambit/catalog_game_files/my_3-3a.efg \ + src/pygambit/catalog_game_files/my_3-3b.efg \ + src/pygambit/catalog_game_files/my_3-3c.efg \ + src/pygambit/catalog_game_files/my_3-3d.efg \ + src/pygambit/catalog_game_files/my_3-3e.efg \ + src/pygambit/catalog_game_files/my_3-4.efg \ + src/pygambit/catalog_game_files/myerson.efg \ + src/pygambit/catalog_game_files/myerson_fig_4_2.efg \ + src/pygambit/catalog_game_files/nim.efg \ + src/pygambit/catalog_game_files/nim7.efg \ + src/pygambit/catalog_game_files/oneill.nfg \ + src/pygambit/catalog_game_files/palf.efg \ + src/pygambit/catalog_game_files/palf2.efg \ + src/pygambit/catalog_game_files/palf3.efg \ + src/pygambit/catalog_game_files/pbride.efg \ + src/pygambit/catalog_game_files/pd.nfg \ + src/pygambit/catalog_game_files/perfect1.nfg \ + src/pygambit/catalog_game_files/perfect2.nfg \ + src/pygambit/catalog_game_files/perfect3.nfg \ + src/pygambit/catalog_game_files/poker.efg \ + src/pygambit/catalog_game_files/poker.nfg \ + src/pygambit/catalog_game_files/poker2.efg \ + src/pygambit/catalog_game_files/pvw.efg \ + src/pygambit/catalog_game_files/pvw2.efg \ + src/pygambit/catalog_game_files/sh3.efg \ + src/pygambit/catalog_game_files/sh3.nfg \ + src/pygambit/catalog_game_files/spence.efg \ + src/pygambit/catalog_game_files/stengel.nfg \ + src/pygambit/catalog_game_files/sww1.efg \ + src/pygambit/catalog_game_files/sww1.nfg \ + src/pygambit/catalog_game_files/sww2.efg \ + src/pygambit/catalog_game_files/sww3.efg \ + src/pygambit/catalog_game_files/tim.efg \ + src/pygambit/catalog_game_files/todd1.nfg \ + src/pygambit/catalog_game_files/todd2.nfg \ + src/pygambit/catalog_game_files/todd3.nfg \ + src/pygambit/catalog_game_files/ttt.efg \ + src/pygambit/catalog_game_files/vd.efg \ + src/pygambit/catalog_game_files/vd.nfg \ + src/pygambit/catalog_game_files/w_ex1.efg \ + src/pygambit/catalog_game_files/w_ex2.efg \ + src/pygambit/catalog_game_files/wilson1.efg \ + src/pygambit/catalog_game_files/wink3.nfg \ + src/pygambit/catalog_game_files/winkels.nfg \ + src/pygambit/catalog_game_files/work1.efg \ + src/pygambit/catalog_game_files/work2.efg \ + src/pygambit/catalog_game_files/work3.efg \ + src/pygambit/catalog_game_files/yamamoto.nfg \ + src/pygambit/catalog_game_files/zero.nfg core_SOURCES = \ src/core/core.h \ diff --git a/doc/catalog.rst b/doc/catalog.rst new file mode 100644 index 000000000..c9e73ea3b --- /dev/null +++ b/doc/catalog.rst @@ -0,0 +1,12 @@ +Catalog of games +================ + +.. raw:: html + + + +.. note:: + If you are not redirected automatically, see + `Games Catalog `. diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst new file mode 100644 index 000000000..264e3d37b --- /dev/null +++ b/doc/developer.catalog.rst @@ -0,0 +1,184 @@ +.. _update-the-catlog: + +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 can add games to the catalog saved in a valid representation :ref:`format `, e.g. for `.efg` for extensive form games. +Alternatively, you can define games for the catalog in *pygambit* code. +You may wish to first review the :ref:`pygambit ` docs pages. + +Both options for adding to the catalog below include an optional step for adding metadata to the catalog for your game(s). +Adding metadata to games will create a new filter for the :func:`pygambit.catalog.games` that users can search the catalog with. + +.. dropdown:: Add game files to the catalog + :class-container: sd-border-0 + + 1. **Create the game file:** + + Use either :ref:`pygambit `, the Gambit :ref:`CLI ` or :ref:`GUI ` to create and save game in a valid representation :ref:`format `. + + 2. **Add the game file:** + + Create a new branch in the `gambit` repo and commit the new game to `src/pygambit/catalog_game_files` + + 3. **Update the catalog:** + + Use the `catalog_update.py` script to update the catalog and associated documatation & build files. + + .. code-block:: bash + + pip install ruamel.yaml + cd src/pygambit + python catalog_update.py + + .. note:: + Run this script in a Python environment where `pygambit` itself is also :ref:`installed ` + + 4. **[Optional] Edit the catalog entry:** + + Open `src/pygambit/catalog.yml` and find the new entry that was created in the previous step. + The entry will be named after the game file, but in camel case, with "Game" prefixed if the file started with a number. + + If the game file doesn't already include a description ("comments" in older versions of Gambit) you should add a description field: + + .. code-block:: yaml + + MyGame: + file: my_game.nfg + description: "A game that... originally created by Author (2000)." + + 5. **[Optional] Add custom metadata to catalog games:** + + Open `src/pygambit/catalog.py` and add update the `CatalogGame` base class with the new metadata field. + Include a type hint, a default value and a docstring. + + .. code-block:: python + + # Metadata fields + my_metadata: int | None = None + """Explanation of my_metadata field.""" + + Open `src/pygambit/catalog.yml` and add the field under the `metadata` for the game you added, and any others that shouldn't have the default. + + .. code-block:: yaml + + MyGame: + file: my_game.efg + metadata: + my_metadata: 17 + + 6. **Submit a pull request to GitHub with all changes:** + + .. warning:: + If you made changes in step 4 or 5 above, re-run the update script from step 3. + + .. warning:: + Make sure you commit all changed files e.g. run `git add --all` before committing and pushing. + +.. dropdown:: Code games for the catalog + :class-container: sd-border-0 + + 1. **Write the pygambit code:** + + Write code for your game with :ref:`pygambit `. + Your code should create a ``Game`` object with a title and description. + + .. note:: + Test your game by visualising it in `draw_tree` and running Gambit's solvers. + + 2. **Create a new Python class for your game:** + + Create a new branch in the `gambit` repo and open `src/pygambit/catalog_games.py`. + Add a subclass of `CatalogGame` with your code implemented as the `_game` function, + which should be defined as a `staticmethod` returning a ``Game`` object. + You can optionally include paramaters to generate game variants. + + .. code-block:: python + + class MyGame(gbt.catalog.CatalogGame): + + @staticmethod + def _game(some_param: bool = False) -> gbt.Game: + """ + Docstring for _game function. + + Parameters + ---------- + some_param : bool, optional + Description of optional paramater. + Defaults to False. + + Returns + ------- + gbt.Game + The constructed game. + + Examples + -------- + >>> MyGame(some_param=False) # Constructs the standard game + >>> MyGame(some_param=True) # Constructs the alternate game + """ + g = gbt.Game.new_tree( + players=["Buyer", "Seller"], title="My game" + ) + g.description = "Longer description of My game, originally by Author (2000)." + g.append_move(g.root, "Buyer", ["Trust", "Not trust"]) + g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"]) + g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy")) + if some_param: + g.set_outcome( + g.root.children[0].children[1], g.add_outcome(["1/2", 2], label="Untrustworthy") + ) + else: + g.set_outcome( + g.root.children[0].children[1], g.add_outcome([-1, 2], label="Untrustworthy") + ) + g.set_outcome(g.root.children[1], g.add_outcome([0, 0], label="Opt-out")) + return g + + .. note:: + If you add a docstring to the class itself, this will be used in the API reference docs as the game description instead of `g.description` + + 3. **Update the catalog:** + + Use the `catalog_update.py` script to update the catalog and associated documatation & build files. + + .. code-block:: bash + + pip install ruamel.yaml + cd src/pygambit + python catalog_update.py + + .. note:: + Run this script in a Python environment where `pygambit` itself is also :ref:`installed ` + + 4. **[Optional] Add custom metadata to catalog games:** + + Open `src/pygambit/catalog.py` and add update the `CatalogGame` base class with the new metadata field. + Include a type hint, a default value and a docstring. + + .. code-block:: python + + # Metadata fields + my_metadata: int | None = None + """Explanation of my_metadata field.""" + + Open `src/pygambit/catalog_games.py` and add a value for the field as a class attribute for the game(s) you added. + + .. code-block:: python + + class MyGame(gbt.catalog.CatalogGame): + my_metadata = 17 + ... + + 6. **Submit a pull request to GitHub with all changes:** + + .. warning:: + If you made changes in step 4 above, re-run the update script from step 3. + + .. warning:: + Make sure you commit all changed files e.g. run `git add --all` before committing and pushing. diff --git a/doc/developer.contributing.rst b/doc/developer.contributing.rst index f86939850..02ca37747 100644 --- a/doc/developer.contributing.rst +++ b/doc/developer.contributing.rst @@ -1,3 +1,5 @@ +.. _contributing: + Contributing to Gambit ====================== diff --git a/doc/developer.rst b/doc/developer.rst index 0a1512659..b954f0850 100644 --- a/doc/developer.rst +++ b/doc/developer.rst @@ -11,3 +11,4 @@ This section contains information for developers who want to contribute to the G developer.build developer.contributing + developer.catalog diff --git a/doc/index.rst b/doc/index.rst index 79076153a..50aa0bc7a 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -64,7 +64,7 @@ We recommended most new users install the PyGambit Python package and read the a pygambit tools gui - samples + catalog developer formats biblio diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index ae029bae7..fc36aac62 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -95,7 +95,7 @@ Information about the game :toctree: api/ Game.title - Game.comment + Game.description Game.is_const_sum Game.is_tree Game.is_perfect_recall @@ -325,3 +325,161 @@ Computation of quantal response equilibria logit_estimate LogitQREMixedStrategyFitResult LogitQREMixedBehaviorFitResult + + +.. _pygambit-catalog: + +Catalog of games +~~~~~~~~~~~~~~~~ + +.. currentmodule:: pygambit.catalog + +.. autosummary:: + :toctree: api/ + + games + Artist1 + Artist2 + Badgame1 + Badgame2 + Bagwell + Bayes1a + Bayes2a + Bcp2 + Bcp3 + Bcp4 + Bhg1 + Bhg2 + Bhg3 + Bhg4 + Bhg5 + Caro2 + Cent2 + Cent2NFG + Cent3 + Cent4 + Cent6 + Centcs10 + Centcs6 + Condjury + Coord2 + Coord2NFG + Coord2ts + Coord3 + Coord333 + Coord3NFG + Coord4 + Coord4NFG + Cross + Cs + Csg1 + Csg2 + Csg3 + Csg4 + Deg1 + Deg2 + E01 + E01NFG + E02 + E02NFG + E03 + E04 + E04NFG + E05 + E06 + E07 + E07NFG + E08 + E09 + E10 + E10a + E13 + E16 + E17 + E18 + G1 + G1NFG + G2 + G2NFG + G3 + G3NFG + Game2s2x2x2 + Game2smp + Game2x2 + Game2x2a + Game2x2const + Game2x2x2 + Game2x2x2NFG + Game2x2x2_nau + Game2x2x2x2 + Game2x2x2x2x2 + Game3x3x3 + Game4cards + Game5x4x3 + Game8x2x2 + Game8x8 + Holdout + Hs1 + Jury_mr + Jury_un + Km1 + Km2 + Km3 + Km6 + Loopback + Mixdom + Mixdom2 + Montyhal + My_2_1 + My_2_4 + My_2_8 + My_3_3a + My_3_3b + My_3_3c + My_3_3d + My_3_3e + My_3_4 + Myerson + Myerson_fig_4_2 + Nim + Nim7 + OneShotTrust + Oneill + Palf + Palf2 + Palf3 + Pbride + Perfect1 + Perfect2 + Perfect3 + Poker + Poker2 + PokerNFG + PrisonersDilemma + Pvw + Pvw2 + Sh3 + Sh3NFG + Spence + Stengel + Sww1 + Sww1NFG + Sww2 + Sww3 + Tim + Todd1 + Todd2 + Todd3 + Ttt + Vd + VdNFG + W_ex1 + W_ex2 + Wilson1 + Wink3 + Winkels + Work1 + Work2 + Work3 + Yamamoto + Zero diff --git a/doc/tools.enummixed.rst b/doc/tools.enummixed.rst index c3ad6df12..f57ffbbc8 100644 --- a/doc/tools.enummixed.rst +++ b/doc/tools.enummixed.rst @@ -64,7 +64,7 @@ points. feature that has not been widely tested. Computing the equilibria, in mixed strategies, of :download:`e02.nfg -<../contrib/games/e02.nfg>`, the reduced strategic form of the example +<../src/pygambit/catalog_game_files/e02.nfg>`, the reduced strategic form of the example in Figure 2 of Selten (International Journal of Game Theory, 1975) diff --git a/doc/tools.enumpoly.rst b/doc/tools.enumpoly.rst index a04362e98..23875d684 100644 --- a/doc/tools.enumpoly.rst +++ b/doc/tools.enumpoly.rst @@ -92,7 +92,7 @@ support of some set of equilibria. default, no information about supports is printed. Computing equilibria of the strategic game :download:`e01.nfg -<../contrib/games/e01.efg>`, the example in Figure 1 of Selten +<../src/pygambit/catalog_game_files/e01.efg>`, the example in Figure 1 of Selten (International Journal of Game Theory, 1975) sometimes called "Selten's horse" diff --git a/doc/tools.enumpure.rst b/doc/tools.enumpure.rst index c653bc7bd..1a03109fe 100644 --- a/doc/tools.enumpure.rst +++ b/doc/tools.enumpure.rst @@ -53,7 +53,7 @@ pure-strategy Nash equilibria. Computing the pure-strategy equilibria of extensive game :download:`e02.efg -<../contrib/games/e02.efg>`, the example in Figure 2 of Selten +<../src/pygambit/catalog_game_files/e02.efg>`, the example in Figure 2 of Selten (International Journal of Game Theory, 1975) $ gambit-enumpure e02.efg diff --git a/doc/tools.gnm.rst b/doc/tools.gnm.rst index 7bd63c3cc..b70c3beee 100644 --- a/doc/tools.gnm.rst +++ b/doc/tools.gnm.rst @@ -81,7 +81,7 @@ subsets of equilibria being found. Show intermediate output of the algorithm. If this option is not specified, only the equilibria found are reported. -Computing an equilibrium of :download:`e02.nfg <../contrib/games/e02.nfg>`, +Computing an equilibrium of :download:`e02.nfg <../src/pygambit/catalog_game_files/e02.nfg>`, the reduced strategic form of the example in Figure 2 of Selten (International Journal of Game Theory, 1975) diff --git a/doc/tools.ipa.rst b/doc/tools.ipa.rst index adac09b2f..2790a6a3f 100644 --- a/doc/tools.ipa.rst +++ b/doc/tools.ipa.rst @@ -48,7 +48,7 @@ equilibria being found. output of equilibria (excluding the initial NE tag). -Computing an equilibrium of :download:`e02.nfg <../contrib/games/e02.nfg>`, +Computing an equilibrium of :download:`e02.nfg <../src/pygambit/catalog_game_files/e02.nfg>`, the reduced strategic form of the example in Figure 2 of Selten (International Journal of Game Theory, 1975) diff --git a/doc/tools.lcp.rst b/doc/tools.lcp.rst index 19b62d812..55e161f23 100644 --- a/doc/tools.lcp.rst +++ b/doc/tools.lcp.rst @@ -76,7 +76,7 @@ game. Computing an equilibrium of extensive game :download:`e02.efg -<../contrib/games/e02.efg>`, the example in Figure 2 of Selten +<../src/pygambit/catalog_game_files/e02.efg>`, the example in Figure 2 of Selten (International Journal of Game Theory, 1975) $ gambit-lcp e02.efg diff --git a/doc/tools.liap.rst b/doc/tools.liap.rst index fdac11f51..10cab5043 100644 --- a/doc/tools.liap.rst +++ b/doc/tools.liap.rst @@ -96,7 +96,7 @@ not guaranteed to find all, or even any, Nash equilibria. equilibria found. Computing an equilibrium in mixed strategies of :download:`e02.efg -<../contrib/games/e02.efg>`, the example in Figure 2 of Selten +<../src/pygambit/catalog_game_files/e02.efg>`, the example in Figure 2 of Selten (International Journal of Game Theory, 1975) $ gambit-liap e02.nfg diff --git a/doc/tools.logit.rst b/doc/tools.logit.rst index 908cb201c..2f19a71b1 100644 --- a/doc/tools.logit.rst +++ b/doc/tools.logit.rst @@ -102,7 +102,7 @@ the beliefs at such information sets as being uniform across all member nodes. equilibrium at the end of the branch is output. Computing the principal branch, in mixed strategies, of :download:`e02.nfg -<../contrib/games/e02.nfg>`, the reduced strategic form of the example +<../src/pygambit/catalog_game_files/e02.nfg>`, the reduced strategic form of the example in Figure 2 of Selten (International Journal of Game Theory, 1975) diff --git a/doc/tools.lp.rst b/doc/tools.lp.rst index f09e97663..e637cbaa9 100644 --- a/doc/tools.lp.rst +++ b/doc/tools.lp.rst @@ -53,7 +53,7 @@ points of that set. Suppresses printing of the banner at program launch. Computing an equilibrium of the game :download:`2x2const.nfg -<../contrib/games/2x2const.nfg>`, a game with two players with two +<../src/pygambit/catalog_game_files/2x2const.nfg>`, a game with two players with two strategies each, with a unique equilibrium in mixed strategies $ gambit-lp 2x2const.nfg diff --git a/doc/tools.simpdiv.rst b/doc/tools.simpdiv.rst index ccebe633c..8c65c6810 100644 --- a/doc/tools.simpdiv.rst +++ b/doc/tools.simpdiv.rst @@ -87,7 +87,7 @@ options to specify additional starting points for the algorithm. Computing an equilibrium in mixed strategies of :download:`e02.efg -<../contrib/games/e02.efg>`, the example in Figure 2 of Selten +<../src/pygambit/catalog_game_files/e02.efg>`, the example in Figure 2 of Selten (International Journal of Game Theory, 1975) $ gambit-simpdiv e02.nfg diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 06ec6f91e..23e07731c 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "c58d382d", "metadata": {}, "outputs": [], @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "2060c1ed", "metadata": {}, "outputs": [ @@ -60,7 +60,7 @@ "pygambit.gambit.Game" ] }, - "execution_count": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "9d8203e8", "metadata": {}, "outputs": [], @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "61030607", "metadata": {}, "outputs": [], @@ -135,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "caecc334", "metadata": {}, "outputs": [ @@ -149,7 +149,7 @@ "Game(title='Prisoner's Dilemma')" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -189,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "843ba7f3", "metadata": {}, "outputs": [ @@ -203,7 +203,7 @@ "Game(title='Another Prisoner's Dilemma')" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "5ee752c4", "metadata": {}, "outputs": [ @@ -270,7 +270,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "a81c06c7", "metadata": {}, "outputs": [ @@ -280,7 +280,7 @@ "pygambit.nash.NashComputationResult" ] }, - "execution_count": 7, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -300,7 +300,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "bd395180", "metadata": {}, "outputs": [ @@ -310,7 +310,7 @@ "1" ] }, - "execution_count": 8, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -329,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "76570ebc", "metadata": {}, "outputs": [ @@ -342,7 +342,7 @@ "[[Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1)]]" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -354,7 +354,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "6e8cfcde", "metadata": {}, "outputs": [ @@ -364,7 +364,7 @@ "pygambit.gambit.MixedStrategyProfileRational" ] }, - "execution_count": 10, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -385,7 +385,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "980bf6b1", "metadata": {}, "outputs": [ @@ -417,11 +417,89 @@ }, { "cell_type": "markdown", - "id": "24f36b0d", + "id": "15ab8d84", "metadata": {}, "source": [ "The equilibrium shows that both players are playing their dominant strategy, which is to defect. This is because defecting is the best response to the other player's strategy, regardless of what that strategy is.\n", "\n", + "Loading games from the catalog\n", + "------------------------------\n", + "\n", + "Gambit includes a catalog of standard games that can be loaded directly by name.\n", + "You can list all the available games and filtering on the game type and number of players in the catalog like so:" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "701aa52a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['Coord2NFG',\n", + " 'Game2x2',\n", + " 'Game2x2a',\n", + " 'Game2x2const',\n", + " 'Loopback',\n", + " 'PrisonersDilemma',\n", + " 'Sww1NFG',\n", + " 'Zero']" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.catalog.games(\n", + " is_tree=False,\n", + " num_strategies=4,\n", + " num_players=2\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "a919ddf7", + "metadata": {}, + "source": [ + "You can then load a specific game by its name. For example, to load the \"Prisoner's Dilemma\" game from the catalog, you would do the following:" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "6db7a29a", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "

Two person Prisoner's Dilemma game

\n", + "
12
19,90,10
210,01,1
\n" + ], + "text/plain": [ + "Game(title='Two person Prisoner's Dilemma game')" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g = gbt.catalog.PrisonersDilemma()\n", + "g" + ] + }, + { + "cell_type": "markdown", + "id": "24f36b0d", + "metadata": {}, + "source": [ "Saving and reading strategic form games to and from file\n", "--------------------\n", "\n", @@ -433,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -451,23 +529,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "4119a2ac", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "pygambit.gambit.Game" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "# gbt.read_nfg(\"test_games/prisoners_dilemma.nfg\")" + "# gbt.read_nfg(\"prisoners_dilemma.nfg\")" ] } ], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 528970c61..ce84fe0f3 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 22, "id": "5946289b", "metadata": {}, "outputs": [], @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 23, "id": "91ed4dfb", "metadata": {}, "outputs": [], @@ -72,27 +72,27 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 24, "id": "3cd94917", "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", "" ], @@ -100,7 +100,7 @@ "" ] }, - "execution_count": 3, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -121,7 +121,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 25, "id": "5d27a07a", "metadata": {}, "outputs": [], @@ -135,126 +135,126 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 26, "id": "45638fda-7e25-4c8e-b709-24b05780581b", "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", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "" ], @@ -262,7 +262,7 @@ "" ] }, - "execution_count": 5, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -281,7 +281,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 27, "id": "47c4a31b", "metadata": {}, "outputs": [], @@ -295,195 +295,185 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 28, "id": "ce41e9fe-cca4-46fb-8e9d-b2c27342e5ef", "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", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", - "\n", - "\n", - "\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": 7, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -506,7 +496,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 29, "id": "716e9b9a", "metadata": {}, "outputs": [], @@ -522,171 +512,161 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 30, "id": "b3408c55-714e-4a6f-b598-e338839442e4", "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", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\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, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -705,7 +685,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 31, "id": "695b1aad", "metadata": {}, "outputs": [], @@ -721,185 +701,175 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 32, "id": "09bedb3a-aac7-46e6-ae93-c47932c746d4", "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", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\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": 11, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -918,7 +888,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 33, "id": "0704ef86", "metadata": {}, "outputs": [], @@ -934,193 +904,183 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 34, "id": "cba0e562-2989-4dae-a0f0-b121635ba032", "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", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\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": 13, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1156,7 +1116,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 35, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1174,7 +1134,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 36, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1193,7 +1153,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 37, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1205,7 +1165,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 38, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/advanced_tutorials/agent_versus_non_agent_regret.ipynb b/doc/tutorials/advanced_tutorials/agent_versus_non_agent_regret.ipynb index 68b78ee11..f45cfe61c 100644 --- a/doc/tutorials/advanced_tutorials/agent_versus_non_agent_regret.ipynb +++ b/doc/tutorials/advanced_tutorials/agent_versus_non_agent_regret.ipynb @@ -28,249 +28,239 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "5142d6ba-da13-4500-bca6-e68b608bfae9", "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", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\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": 1, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -280,7 +270,7 @@ "\n", "import pygambit as gbt\n", "\n", - "g = gbt.read_efg(\"../../../contrib/games/myerson_fig_4_2.efg\")\n", + "g = gbt.catalog.Myerson_fig_4_2()\n", "draw_tree(g)" ] }, @@ -294,7 +284,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "7882d327-ce04-43d3-bb5a-36cff6da6e96", "metadata": {}, "outputs": [ @@ -324,7 +314,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "6e3e9303-453a-4bac-a449-fa8fda2ba5ec", "metadata": {}, "outputs": [ @@ -354,7 +344,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "804345b9-d32b-4f60-b4a0-f9d69dca10a8", "metadata": {}, "outputs": [ @@ -390,7 +380,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "9d18768b-db9b-41ef-aee7-5fe5f524a59e", "metadata": {}, "outputs": [ @@ -428,7 +418,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "b885271f-7279-4d87-a0b9-bc28449b00ba", "metadata": {}, "outputs": [ @@ -436,7 +426,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[4.2517925671604327e-07, 0.49999911111761514, 0.5000004637031282], [0.3333333517938241, 0.6666666482061759]]\n" + "[[6.949101896011271e-07, 0.49999858461819596, 0.5000007204716144], [0.33333333942537524, 0.6666666605746248]]\n" ] } ], @@ -447,7 +437,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "f8a90a9c-393e-4812-9418-76e705880f6f", "metadata": {}, "outputs": [ @@ -455,8 +445,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "Liap value: 4.43446520109796e-14\n", - "Max regret: 1.694170896904268e-07\n" + "Liap value: 1.0863970174089946e-13\n", + "Max regret: 2.407747583532682e-07\n" ] } ], @@ -467,7 +457,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "id": "567e6a6a-fc8d-4142-806c-6510b2a4c624", "metadata": {}, "outputs": [ @@ -496,7 +486,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "id": "87a62c9e-b109-4f88-ac25-d0e0db3f27ea", "metadata": {}, "outputs": [ @@ -526,7 +516,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "id": "2c8ed3df-958e-4ee9-aed6-a106547fbd37", "metadata": {}, "outputs": [ @@ -573,7 +563,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "id": "f46ce825-d2b7-492f-b0cf-6f213607e121", "metadata": {}, "outputs": [ @@ -604,7 +594,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "id": "dbfa7035", "metadata": {}, "outputs": [ @@ -614,7 +604,7 @@ "True" ] }, - "execution_count": 12, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -633,7 +623,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "id": "85760cec-5760-4f9d-8ca2-99fba79c7c3c", "metadata": {}, "outputs": [ @@ -699,7 +689,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.9" + "version": "3.13.5" } }, "nbformat": 4, diff --git a/doc/tutorials/advanced_tutorials/starting_points.ipynb b/doc/tutorials/advanced_tutorials/starting_points.ipynb index fb976245b..b09a438de 100644 --- a/doc/tutorials/advanced_tutorials/starting_points.ipynb +++ b/doc/tutorials/advanced_tutorials/starting_points.ipynb @@ -25,7 +25,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, + "id": "57df88bc-3ff1-4f0a-848e-02724351976c", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "import pygambit as gbt" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "id": "493cafb8", "metadata": {}, "outputs": [ @@ -39,14 +51,13 @@ "Game(title='2x2x2 Example from McKelvey-McLennan, with 9 Nash equilibria, 2 totally mixed')" ] }, - "execution_count": 1, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "import pygambit as gbt\n", - "g = gbt.read_nfg(\"../../2x2x2.nfg\")\n", + "g = gbt.catalog.Game2x2x2NFG()\n", "g" ] }, @@ -61,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 17, "id": "b32adf22", "metadata": {}, "outputs": [ @@ -74,7 +85,7 @@ "[[0.5, 0.5], [0.5, 0.5], [0.5, 0.5]]" ] }, - "execution_count": 2, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -86,20 +97,20 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 18, "id": "c0b62502", "metadata": {}, "outputs": [ { "data": { "text/latex": [ - "$\\left[[0.3999999026224355, 0.6000000973775644],[0.49999981670851457, 0.5000001832914854],[0.3333329684317666, 0.6666670315682334]\\right]$" + "$\\left[[0.4000002947381336, 0.5999997052618664],[0.4999994065053205, 0.5000005934946796],[0.3333334410505316, 0.6666665589494684]\\right]$" ], "text/plain": [ - "[[0.3999999026224355, 0.6000000973775644], [0.49999981670851457, 0.5000001832914854], [0.3333329684317666, 0.6666670315682334]]" + "[[0.4000002947381336, 0.5999997052618664], [0.4999994065053205, 0.5000005934946796], [0.3333334410505316, 0.6666665589494684]]" ] }, - "execution_count": 3, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -121,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 19, "id": "cf22064e", "metadata": {}, "outputs": [ @@ -134,7 +145,7 @@ "[[0.9, 0.1], [0.9, 0.1], [0.9, 0.1]]" ] }, - "execution_count": 4, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -146,20 +157,20 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 20, "id": "08a22505", "metadata": {}, "outputs": [ { "data": { "text/latex": [ - "$\\left[[1.0, 0.0],[0.9999999944750116, 5.524988446860122e-09],[0.9999999991845827, 8.154173380971617e-10]\\right]$" + "$\\left[[0.9999999968173041, 3.1826958638999237e-09],[0.9999999929853859, 7.014614077736281e-09],[0.999999999193891, 8.061090096721423e-10]\\right]$" ], "text/plain": [ - "[[1.0, 0.0], [0.9999999944750116, 5.524988446860122e-09], [0.9999999991845827, 8.154173380971617e-10]]" + "[[0.9999999968173041, 3.1826958638999237e-09], [0.9999999929853859, 7.014614077736281e-09], [0.999999999193891, 8.061090096721423e-10]]" ] }, - "execution_count": 5, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -178,20 +189,20 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 21, "id": "cfbc2714", "metadata": {}, "outputs": [ { "data": { "text/latex": [ - "$\\left[[0.7187961367413075, 0.2812038632586925],[0.1291105793795489, 0.8708894206204512],[0.12367227612277114, 0.876327723877229]\\right]$" + "$\\left[[0.5816023335384932, 0.41839766646150667],[0.14988214487024656, 0.8501178551297536],[0.1633565719798093, 0.8366434280201908]\\right]$" ], "text/plain": [ - "[[0.7187961367413075, 0.2812038632586925], [0.1291105793795489, 0.8708894206204512], [0.12367227612277114, 0.876327723877229]]" + "[[0.5816023335384932, 0.41839766646150667], [0.14988214487024656, 0.8501178551297536], [0.1633565719798093, 0.8366434280201908]]" ] }, - "execution_count": 6, + "execution_count": 21, "metadata": {}, "output_type": "execute_result" } @@ -203,20 +214,20 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 22, "id": "eb53062a", "metadata": {}, "outputs": [ { "data": { "text/latex": [ - "$\\left[[0.5000003932357804, 0.4999996067642197],[0.3999998501612186, 0.6000001498387814],[0.2500001518113522, 0.7499998481886477]\\right]$" + "$\\left[[0.5000006764533134, 0.49999932354668664],[0.39999957335142133, 0.6000004266485787],[0.24999957325483269, 0.7500004267451672]\\right]$" ], "text/plain": [ - "[[0.5000003932357804, 0.4999996067642197], [0.3999998501612186, 0.6000001498387814], [0.2500001518113522, 0.7499998481886477]]" + "[[0.5000006764533134, 0.49999932354668664], [0.39999957335142133, 0.6000004266485787], [0.24999957325483269, 0.7500004267451672]]" ] }, - "execution_count": 7, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -238,7 +249,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 23, "id": "4293343a", "metadata": {}, "outputs": [ @@ -248,13 +259,12 @@ "True" ] }, - "execution_count": 8, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "import numpy as np\n", "gen = np.random.default_rng(seed=1234567890)\n", "p1 = g.random_strategy_profile(gen=gen)\n", "gen = np.random.default_rng(seed=1234567890)\n", @@ -278,7 +288,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 24, "id": "e9716ae0", "metadata": {}, "outputs": [ @@ -291,7 +301,7 @@ "[[Rational(1, 2), Rational(1, 2)], [Rational(7, 10), Rational(3, 10)], [Rational(0, 1), Rational(1, 1)]]" ] }, - "execution_count": 9, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -304,7 +314,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 25, "id": "c153918a", "metadata": {}, "outputs": [ @@ -317,7 +327,7 @@ "[[Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)]]" ] }, - "execution_count": 10, + "execution_count": 25, "metadata": {}, "output_type": "execute_result" } @@ -328,7 +338,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 26, "id": "70a57b26", "metadata": {}, "outputs": [ @@ -341,7 +351,7 @@ "[[Rational(1, 10), Rational(9, 10)], [Rational(3, 5), Rational(2, 5)], [Rational(3, 5), Rational(2, 5)]]" ] }, - "execution_count": 11, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -353,7 +363,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 27, "id": "11995836", "metadata": {}, "outputs": [ @@ -366,7 +376,7 @@ "[[Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1)], [Rational(1, 1), Rational(0, 1)]]" ] }, - "execution_count": 12, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -377,7 +387,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 28, "id": "2791ffe2", "metadata": {}, "outputs": [ @@ -390,7 +400,7 @@ "[[Rational(7, 10), Rational(3, 10)], [Rational(4, 5), Rational(1, 5)], [Rational(0, 1), Rational(1, 1)]]" ] }, - "execution_count": 13, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -402,7 +412,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 29, "id": "2ab2caa4", "metadata": {}, "outputs": [ @@ -415,7 +425,7 @@ "[[Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)], [Rational(1, 1), Rational(0, 1)]]" ] }, - "execution_count": 14, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -427,7 +437,7 @@ ], "metadata": { "kernelspec": { - "display_name": "gambitvenv313", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/pyproject.toml b/pyproject.toml index 3c372ec34..b8b8d9c9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,5 +85,14 @@ markers = [ "slow: all time-consuming tests", ] +[tool.setuptools] +include-package-data = true + +[tool.setuptools.package-data] +pygambit = [ + "catalog.yml", + "catalog_game_files/*", +] + [tool.setuptools.dynamic] version = {file = "build_support/GAMBIT_VERSION"} diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 1d6f730bf..422a21439 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -26,10 +26,17 @@ nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 + catalog, # noqa: F401 + catalog_games, ) import importlib.metadata +# Import all coded game classes from catalog_games +# into this module's namespace +# so they are registered as CatalogGame subclasses +catalog.load_coded_games() + try: __version__ = importlib.metadata.version("pygambit") except importlib.metadata.PackageNotFoundError: diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py new file mode 100644 index 000000000..6049baa92 --- /dev/null +++ b/src/pygambit/catalog.py @@ -0,0 +1,357 @@ +import inspect +import sys +from importlib.resources import as_file, files + +import yaml + +import pygambit as gbt + +_GAMEFILES_DIR = files(__package__) / "catalog_game_files" + + +class CatalogGame: + """ + Base class for catalog games. + This class serves as a template for specific games in the catalog. + Calling any subclass will return an instance of the corresponding game. + """ + + game: gbt.Game | None = None + """Cached ``Game`` instance. Overwritten on each instantiation.""" + + # Metadata fields + test_suite: bool = False + """Indicates if this game is included in the pygambit test suite.""" + + def __new__(cls, *args, **kwargs) -> gbt.Game: + """Create a game instance by calling the _game() method.""" + cls.game = cls._game(*args, **kwargs) + cls._build_docstring(cls.game) + return cls.game + + @staticmethod + def _game() -> gbt.Game: + """ + Examples + -------- + >>> {classname}() # Return the Game + """ + raise NotImplementedError("Subclasses must implement _game() method") + + # TODO: Add images for catalog games + # @classmethod + # def _img_docstring(cls) -> str: + # """Return the image docstring for the game.""" + # file = f"/_static/catalog_drawn/{cls.__name__}.png" + # docstring = f""" + # .. figure:: {file} + # :align: center + # :width: 400px + + # {cls.__name__} game diagram + # """ + # return docstring + + @classmethod + def _build_docstring(cls, game: gbt.Game) -> None: + """Create the docstring for the catalog entry.""" + # Add the game title + cleaned_docstring = game.title + if game.title[-1] != ".": + cleaned_docstring += "." + + # Add the game description (or docstring) + desc = game.description + if cls.__doc__: + desc = inspect.cleandoc(cls.__doc__) + if desc: + cleaned_docstring += "\n\n**Description:** " + desc + + cleaned_docstring += "\n" + + # TODO: Add images for catalog games + # cleaned_docstring += cls._img_docstring() + + # If the class has a _game function, concatenate its docstring to the class docstring + game_docstring = inspect.getdoc(cls._game) + if game_docstring: + game_docstring = inspect.cleandoc(game_docstring) + # For games from file, sub in the class name + game_docstring = game_docstring.replace("{classname}", cls.__name__) + cleaned_docstring += "\n\n" + game_docstring + cls.__doc__ = cleaned_docstring + + def __init_subclass__(cls, **kwargs): + """Extract metadata when subclass is defined (if not a file-based game).""" + super().__init_subclass__(**kwargs) + + # Skip if this is CatalogGameFromFile or its subclasses + if cls.__name__ == "CatalogGameFromFile" or issubclass(cls, CatalogGameFromFile): + return + + # Load game and extract metadata immediately when class is defined + cls.game = cls._game() + cls._build_docstring(cls.game) + + +class CatalogGameFromFile(CatalogGame): + """ + Base class for catalog games loaded from files. + This class serves as a template for specific games in the catalog. + Calling any subclass will return an instance of the corresponding game. + """ + + game_file: str + """Filename of the game file in catalog_game_files directory.""" + + def __new__(cls) -> gbt.Game: + if getattr(cls, "game", None) is None: + cls.game = cls._load_game() + return cls.game + + @classmethod + def _load_game(cls) -> gbt.Game: + """Load the game from file.""" + if not hasattr(cls, "game_file") or cls.game_file is None: + raise TypeError(f"{cls.__name__} must define 'game_file'") + + game_type = cls.game_file.rsplit(".", 1)[-1] + resource = _GAMEFILES_DIR / cls.game_file + + with as_file(resource) as path: + if game_type == "nfg": + return gbt.read_nfg(str(path)) + elif game_type == "efg": + return gbt.read_efg(str(path)) + else: + raise ValueError( + f"gbt.Game file extension must be 'nfg' or 'efg', got '{game_type}'" + ) + + def __init_subclass__(cls, **kwargs): + """Validate and extract metadata when subclass is defined.""" + super().__init_subclass__(**kwargs) + + # Load game and extract metadata immediately when class is defined + cls.game = cls._load_game() + cls._build_docstring(cls.game) + + +def games( + num_actions: int | None = None, + num_contingencies: int | None = None, + num_infosets: int | None = None, + is_const_sum: bool | None = None, + is_perfect_recall: bool | None = None, + is_tree: bool | None = None, + num_nodes: int | None = None, + num_outcomes: int | None = None, + num_players: int | None = None, + num_strategies: int | None = None, + **metadata_filters, +) -> list[str]: + """ + Return a list of catalog game class names. + + Parameters + ---------- + num_actions : int | None, default None + If specified, only return games with the given number of actions. + num_contingencies : int | None, default None + If specified, only return games with the given number of contingencies. + num_infosets : int | None, default None + If specified, only return games with the given number of information sets. + is_const_sum : bool | None, default None + If specified, only return games that are (or are not) constant-sum. + is_perfect_recall : bool | None, default None + If specified, only return games that have (or do not have) perfect recall. + is_tree : bool | None, default None + If specified, only return games that are (or are not) extensive-form. + num_nodes : int | None, default None + If specified, only return games with the given number of nodes. + num_outcomes : int | None, default None + If specified, only return games with the given number of outcomes. + num_strategies : int | None, default None + If specified, only return games with the given number of strategies. + num_players : int | None, default None + If specified, only return games with the given number of players. + **metadata_filters + Additional keyword arguments to filter by catalog.yml metadata fields. + For example, `x=1` filters for games with `x: 1` in metadata. + + Returns + ------- + list[str] + List of game class names matching the specified filters. + + Examples + -------- + >>> games(x=1) # Games with a custom metadata field 'x' equal to 1 + >>> games(is_tree=True, num_players=2) # 2-player extensive-form games + """ + + # Filter by extensive-form if filtering by tree-specific attributes + if ( + num_actions is not None or + num_infosets is not None or + num_nodes is not None + ): + is_tree = True + + def get_all_subclasses(cls): + """Recursively get all subclasses.""" + all_subclasses = [] + for subclass in cls.__subclasses__(): + + # Don't include CatalogGameFromFile in result + if subclass.__name__ in ["CatalogGameFromFile"]: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + # Don't include classes that lack a cached game + if subclass.game is None: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if is_tree is not None and is_tree != subclass.game.is_tree: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_actions is not None: + if not getattr(subclass.game, "is_tree", False): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + if num_actions != len(getattr(subclass.game, "actions", [])): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_infosets is not None: + if not getattr(subclass.game, "is_tree", False): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + if num_infosets != len(subclass.game.infosets): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_nodes is not None: + if not getattr(subclass.game, "is_tree", False): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + if num_nodes != len(subclass.game.nodes): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if ( + num_contingencies is not None + and num_contingencies != len(subclass.game.contingencies) + ): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if is_const_sum is not None and is_const_sum != subclass.game.is_const_sum: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if ( + is_perfect_recall is not None + and is_perfect_recall != subclass.game.is_perfect_recall + ): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if ( + num_outcomes is not None + and num_outcomes != len(subclass.game.outcomes) + ): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if ( + num_strategies is not None + and num_strategies != len(subclass.game.strategies) + ): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if ( + num_players is not None + and num_players != len(subclass.game.players) + ): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + # Check metadata filters + metadata_match = True + for key, value in metadata_filters.items(): + if not hasattr(subclass, key) or getattr(subclass, key) != value: + metadata_match = False + break + + if metadata_match: + all_subclasses.append(subclass.__name__) + + all_subclasses.extend(get_all_subclasses(subclass)) + return all_subclasses + # Sort alphabetically for consistency + return sorted(get_all_subclasses(CatalogGame)) + + +def _load_catalog_from_yaml() -> dict[str, dict]: + path = files(__package__) / "catalog.yml" + with path.open("r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} + + +def _generate_game_classes_from_catalog(catalog: dict[str, dict]) -> None: + """ + Dynamically generate CatalogGameFromFile subclasses from YAML + and attach them to this module's namespace. + """ + module = sys.modules[__name__] + + for class_name, entry in catalog.items(): + if "file" not in entry: + raise ValueError(f"Missing 'file' for catalog entry '{class_name}'") + + game_file = entry["file"] + metadata = entry.get("metadata", {}) + + # Build class attributes dict + class_attrs = { + "game_file": game_file, + "__module__": __name__, + "__doc__": entry.get("description", None), + } + + # Add metadata fields as class attributes + if metadata and "valid_game" in metadata and metadata["valid_game"] is False: + pass # Marked as invalid game, do not create class + else: + if metadata: + for key, value in metadata.items(): + class_attrs[key] = value + + cls = type( + class_name, + (CatalogGameFromFile,), + class_attrs, + ) + + setattr(module, class_name, cls) + + +def load_coded_games(): + """ + Lazy load coded games. + This function is called in __init__.py to load manually coded games. + """ + for name in dir(gbt.catalog_games): + if not name.startswith("_"): + obj = getattr(gbt.catalog_games, name) + if isinstance(obj, type) and issubclass(obj, CatalogGame): + globals()[name] = obj + + +# Generate classes at import time +_catalog_data = _load_catalog_from_yaml() +_generate_game_classes_from_catalog(_catalog_data) diff --git a/src/pygambit/catalog.yml b/src/pygambit/catalog.yml new file mode 100644 index 000000000..a9b0288f5 --- /dev/null +++ b/src/pygambit/catalog.yml @@ -0,0 +1,438 @@ +Game2s2x2x2: + file: 2s2x2x2.efg + metadata: +Game2smp: + file: 2smp.efg + metadata: +Game2x2: + file: 2x2.nfg + metadata: +Game2x2a: + file: 2x2a.nfg + metadata: +Game2x2const: + file: 2x2const.nfg + metadata: +Game2x2x2_nau: + file: 2x2x2-nau.nfg + metadata: +Game2x2x2: + file: 2x2x2.efg + metadata: +Game2x2x2NFG: + file: 2x2x2.nfg + metadata: +Game2x2x2x2: + file: 2x2x2x2.nfg + metadata: +Game2x2x2x2x2: + file: 2x2x2x2x2.nfg + metadata: +Game3x3x3: + file: 3x3x3.nfg + metadata: +Game4cards: + file: 4cards.efg + metadata: +Game5x4x3: + file: 5x4x3.nfg + metadata: +Game8x2x2: + file: 8x2x2.nfg + metadata: +Game8x8: + file: 8x8.nfg + metadata: +Artist1: + file: artist1.efg + metadata: +Artist2: + file: artist2.efg + metadata: +Badgame1: + file: badgame1.efg + metadata: +Badgame2: + file: badgame2.efg + metadata: +Bagwell: + file: bagwell.efg + metadata: +Bayes1a: + file: bayes1a.efg + metadata: +Bayes2a: + file: bayes2a.efg + metadata: +Bcp2: + file: bcp2.efg + metadata: +Bcp3: + file: bcp3.efg + metadata: +Bcp4: + file: bcp4.efg + metadata: +Bhg1: + file: bhg1.efg + metadata: +Bhg2: + file: bhg2.efg + metadata: +Bhg3: + file: bhg3.efg + metadata: +Bhg4: + file: bhg4.efg + metadata: +Bhg5: + file: bhg5.efg + metadata: +Caro2: + file: caro2.efg + metadata: +Cent2: + file: cent2.efg + metadata: +Cent2NFG: + file: cent2.nfg + metadata: +Cent3: + file: cent3.efg + metadata: +Cent4: + file: cent4.efg + metadata: +Cent6: + file: cent6.efg + metadata: +Centcs10: + file: centcs10.efg + metadata: +Centcs6: + file: centcs6.efg + metadata: +Condjury: + file: condjury.efg + metadata: +Coord2: + file: coord2.efg + metadata: +Coord2NFG: + file: coord2.nfg + metadata: +Coord2ts: + file: coord2ts.efg + metadata: +Coord3: + file: coord3.efg + metadata: +Coord3NFG: + file: coord3.nfg + metadata: +Coord333: + file: coord333.nfg + metadata: +Coord4: + file: coord4.efg + metadata: +Coord4NFG: + file: coord4.nfg + metadata: +Cross: + file: cross.efg + metadata: +Cs: + file: cs.efg + metadata: +Csg1: + file: csg1.nfg + metadata: +Csg2: + file: csg2.nfg + metadata: +Csg3: + file: csg3.nfg + metadata: +Csg4: + file: csg4.nfg + metadata: +Deg1: + file: deg1.nfg + metadata: +Deg2: + file: deg2.nfg + metadata: +E01: + file: e01.efg + metadata: +E01NFG: + file: e01.nfg + metadata: +E02: + file: e02.efg + metadata: +E02NFG: + file: e02.nfg + metadata: +E03: + file: e03.efg + metadata: +E04: + file: e04.efg + metadata: +E04NFG: + file: e04.nfg + metadata: +E05: + file: e05.efg + metadata: +E06: + file: e06.efg + metadata: +E07: + file: e07.efg + metadata: +E07NFG: + file: e07.nfg + metadata: +E08: + file: e08.efg + metadata: +E09: + file: e09.efg + metadata: +E10: + file: e10.efg + metadata: +E10a: + file: e10a.efg + metadata: +E13: + file: e13.efg + metadata: +E16: + file: e16.efg + metadata: +E17: + file: e17.efg + metadata: +E18: + file: e18.efg + metadata: +G1: + file: g1.efg + metadata: +G1NFG: + file: g1.nfg + metadata: +G2: + file: g2.efg + metadata: +G2NFG: + file: g2.nfg + metadata: +G3: + file: g3.efg + metadata: +G3NFG: + file: g3.nfg + metadata: +Holdout: + file: holdout.efg + metadata: +Holdout7: + file: holdout7.efg + metadata: + valid_game: false # This game does not work (ValueError: Parse error in game file: Probabilities must sum to exactly one) +Hs1: + file: hs1.efg + metadata: +Jury_mr: + file: jury_mr.efg + metadata: +Jury_un: + file: jury_un.efg + metadata: +Km1: + file: km1.efg + metadata: +Km2: + file: km2.efg + metadata: +Km3: + file: km3.efg + metadata: +Km6: + file: km6.efg + metadata: +Loopback: + file: loopback.nfg + metadata: +Mixdom: + file: mixdom.nfg + metadata: +Mixdom2: + file: mixdom2.nfg + metadata: +Montyhal: + file: montyhal.efg + metadata: +My_2_1: + file: my_2-1.efg + metadata: +My_2_4: + file: my_2-4.efg + metadata: +My_2_8: + file: my_2-8.efg + metadata: +My_3_3a: + file: my_3-3a.efg + metadata: +My_3_3b: + file: my_3-3b.efg + metadata: +My_3_3c: + file: my_3-3c.efg + metadata: +My_3_3d: + file: my_3-3d.efg + metadata: +My_3_3e: + file: my_3-3e.efg + metadata: +My_3_4: + file: my_3-4.efg + metadata: +Myerson: + file: myerson.efg + metadata: +Myerson_fig_4_2: + file: myerson_fig_4_2.efg + metadata: +Nim: + file: nim.efg + metadata: +Nim7: + file: nim7.efg + metadata: +Oneill: + file: oneill.nfg + metadata: +Palf: + file: palf.efg + metadata: +Palf2: + file: palf2.efg + metadata: +Palf3: + file: palf3.efg + metadata: +Pbride: + file: pbride.efg + metadata: +PrisonersDilemma: + file: pd.nfg + description: "A simple implementation of a two person Prisoner's Dilemma game." + metadata: + test_suite: true +Perfect1: + file: perfect1.nfg + metadata: +Perfect2: + file: perfect2.nfg + metadata: +Perfect3: + file: perfect3.nfg + metadata: +Poker: + file: poker.efg + metadata: +PokerNFG: + file: poker.nfg + metadata: +Poker2: + file: poker2.efg + metadata: +Pvw: + file: pvw.efg + metadata: +Pvw2: + file: pvw2.efg + metadata: +Sh3: + file: sh3.efg + metadata: +Sh3NFG: + file: sh3.nfg + metadata: +Spence: + file: spence.efg + metadata: +Stengel: + file: stengel.nfg + metadata: +Sww1: + file: sww1.efg + metadata: +Sww1NFG: + file: sww1.nfg + metadata: +Sww2: + file: sww2.efg + metadata: +Sww3: + file: sww3.efg + metadata: +Tim: + file: tim.efg + metadata: +Todd1: + file: todd1.nfg + metadata: +Todd2: + file: todd2.nfg + metadata: +Todd3: + file: todd3.nfg + metadata: +Ttt: + file: ttt.efg + metadata: +Vd: + file: vd.efg + metadata: +VdNFG: + file: vd.nfg + metadata: +W_ex1: + file: w_ex1.efg + metadata: +W_ex2: + file: w_ex2.efg + metadata: +Wilson1: + file: wilson1.efg + metadata: +Wink3: + file: wink3.nfg + metadata: +Winkels: + file: winkels.nfg + metadata: +Work1: + file: work1.efg + metadata: +Work2: + file: work2.efg + metadata: +Work3: + file: work3.efg + metadata: +Yamamoto: + file: yamamoto.nfg + metadata: +Zero: + file: zero.nfg + metadata: diff --git a/contrib/games/2s2x2x2.efg b/src/pygambit/catalog_game_files/2s2x2x2.efg similarity index 100% rename from contrib/games/2s2x2x2.efg rename to src/pygambit/catalog_game_files/2s2x2x2.efg diff --git a/contrib/games/2smp.efg b/src/pygambit/catalog_game_files/2smp.efg similarity index 100% rename from contrib/games/2smp.efg rename to src/pygambit/catalog_game_files/2smp.efg diff --git a/contrib/games/2x2.agg b/src/pygambit/catalog_game_files/2x2.agg similarity index 100% rename from contrib/games/2x2.agg rename to src/pygambit/catalog_game_files/2x2.agg diff --git a/contrib/games/2x2.nfg b/src/pygambit/catalog_game_files/2x2.nfg similarity index 100% rename from contrib/games/2x2.nfg rename to src/pygambit/catalog_game_files/2x2.nfg diff --git a/contrib/games/2x2a.nfg b/src/pygambit/catalog_game_files/2x2a.nfg similarity index 100% rename from contrib/games/2x2a.nfg rename to src/pygambit/catalog_game_files/2x2a.nfg diff --git a/contrib/games/2x2const.nfg b/src/pygambit/catalog_game_files/2x2const.nfg similarity index 100% rename from contrib/games/2x2const.nfg rename to src/pygambit/catalog_game_files/2x2const.nfg diff --git a/contrib/games/2x2x2-nau.nfg b/src/pygambit/catalog_game_files/2x2x2-nau.nfg similarity index 100% rename from contrib/games/2x2x2-nau.nfg rename to src/pygambit/catalog_game_files/2x2x2-nau.nfg diff --git a/contrib/games/2x2x2.efg b/src/pygambit/catalog_game_files/2x2x2.efg similarity index 100% rename from contrib/games/2x2x2.efg rename to src/pygambit/catalog_game_files/2x2x2.efg diff --git a/contrib/games/2x2x2.nfg b/src/pygambit/catalog_game_files/2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2.nfg rename to src/pygambit/catalog_game_files/2x2x2.nfg diff --git a/contrib/games/2x2x2x2.nfg b/src/pygambit/catalog_game_files/2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2.nfg rename to src/pygambit/catalog_game_files/2x2x2x2.nfg diff --git a/contrib/games/2x2x2x2x2.nfg b/src/pygambit/catalog_game_files/2x2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2x2.nfg rename to src/pygambit/catalog_game_files/2x2x2x2x2.nfg diff --git a/contrib/games/3x3x3.nfg b/src/pygambit/catalog_game_files/3x3x3.nfg similarity index 100% rename from contrib/games/3x3x3.nfg rename to src/pygambit/catalog_game_files/3x3x3.nfg diff --git a/contrib/games/4cards.efg b/src/pygambit/catalog_game_files/4cards.efg similarity index 100% rename from contrib/games/4cards.efg rename to src/pygambit/catalog_game_files/4cards.efg diff --git a/contrib/games/5x4x3.nfg b/src/pygambit/catalog_game_files/5x4x3.nfg similarity index 100% rename from contrib/games/5x4x3.nfg rename to src/pygambit/catalog_game_files/5x4x3.nfg diff --git a/contrib/games/8x2x2.nfg b/src/pygambit/catalog_game_files/8x2x2.nfg similarity index 100% rename from contrib/games/8x2x2.nfg rename to src/pygambit/catalog_game_files/8x2x2.nfg diff --git a/contrib/games/8x8.nfg b/src/pygambit/catalog_game_files/8x8.nfg similarity index 100% rename from contrib/games/8x8.nfg rename to src/pygambit/catalog_game_files/8x8.nfg diff --git a/contrib/games/BSS_S_085.Weighted.agg b/src/pygambit/catalog_game_files/BSS_S_085.Weighted.agg similarity index 100% rename from contrib/games/BSS_S_085.Weighted.agg rename to src/pygambit/catalog_game_files/BSS_S_085.Weighted.agg diff --git a/contrib/games/Bayesian-Coffee-3-2-2-3.bagg b/src/pygambit/catalog_game_files/Bayesian-Coffee-3-2-2-3.bagg similarity index 100% rename from contrib/games/Bayesian-Coffee-3-2-2-3.bagg rename to src/pygambit/catalog_game_files/Bayesian-Coffee-3-2-2-3.bagg diff --git a/contrib/games/GenRPS5.agg b/src/pygambit/catalog_game_files/GenRPS5.agg similarity index 100% rename from contrib/games/GenRPS5.agg rename to src/pygambit/catalog_game_files/GenRPS5.agg diff --git a/contrib/games/artist1.efg b/src/pygambit/catalog_game_files/artist1.efg similarity index 100% rename from contrib/games/artist1.efg rename to src/pygambit/catalog_game_files/artist1.efg diff --git a/contrib/games/artist2.efg b/src/pygambit/catalog_game_files/artist2.efg similarity index 100% rename from contrib/games/artist2.efg rename to src/pygambit/catalog_game_files/artist2.efg diff --git a/contrib/games/badgame1.efg b/src/pygambit/catalog_game_files/badgame1.efg similarity index 100% rename from contrib/games/badgame1.efg rename to src/pygambit/catalog_game_files/badgame1.efg diff --git a/contrib/games/badgame2.efg b/src/pygambit/catalog_game_files/badgame2.efg similarity index 100% rename from contrib/games/badgame2.efg rename to src/pygambit/catalog_game_files/badgame2.efg diff --git a/contrib/games/bagwell.efg b/src/pygambit/catalog_game_files/bagwell.efg similarity index 100% rename from contrib/games/bagwell.efg rename to src/pygambit/catalog_game_files/bagwell.efg diff --git a/contrib/games/bayes1a.efg b/src/pygambit/catalog_game_files/bayes1a.efg similarity index 100% rename from contrib/games/bayes1a.efg rename to src/pygambit/catalog_game_files/bayes1a.efg diff --git a/contrib/games/bayes2a.efg b/src/pygambit/catalog_game_files/bayes2a.efg similarity index 100% rename from contrib/games/bayes2a.efg rename to src/pygambit/catalog_game_files/bayes2a.efg diff --git a/contrib/games/bcp2.efg b/src/pygambit/catalog_game_files/bcp2.efg similarity index 100% rename from contrib/games/bcp2.efg rename to src/pygambit/catalog_game_files/bcp2.efg diff --git a/contrib/games/bcp3.efg b/src/pygambit/catalog_game_files/bcp3.efg similarity index 100% rename from contrib/games/bcp3.efg rename to src/pygambit/catalog_game_files/bcp3.efg diff --git a/contrib/games/bcp4.efg b/src/pygambit/catalog_game_files/bcp4.efg similarity index 100% rename from contrib/games/bcp4.efg rename to src/pygambit/catalog_game_files/bcp4.efg diff --git a/contrib/games/bhg1.efg b/src/pygambit/catalog_game_files/bhg1.efg similarity index 100% rename from contrib/games/bhg1.efg rename to src/pygambit/catalog_game_files/bhg1.efg diff --git a/contrib/games/bhg2.efg b/src/pygambit/catalog_game_files/bhg2.efg similarity index 100% rename from contrib/games/bhg2.efg rename to src/pygambit/catalog_game_files/bhg2.efg diff --git a/contrib/games/bhg3.efg b/src/pygambit/catalog_game_files/bhg3.efg similarity index 100% rename from contrib/games/bhg3.efg rename to src/pygambit/catalog_game_files/bhg3.efg diff --git a/contrib/games/bhg4.efg b/src/pygambit/catalog_game_files/bhg4.efg similarity index 100% rename from contrib/games/bhg4.efg rename to src/pygambit/catalog_game_files/bhg4.efg diff --git a/contrib/games/bhg5.efg b/src/pygambit/catalog_game_files/bhg5.efg similarity index 100% rename from contrib/games/bhg5.efg rename to src/pygambit/catalog_game_files/bhg5.efg diff --git a/contrib/games/caro2.efg b/src/pygambit/catalog_game_files/caro2.efg similarity index 100% rename from contrib/games/caro2.efg rename to src/pygambit/catalog_game_files/caro2.efg diff --git a/contrib/games/cent2.efg b/src/pygambit/catalog_game_files/cent2.efg similarity index 100% rename from contrib/games/cent2.efg rename to src/pygambit/catalog_game_files/cent2.efg diff --git a/contrib/games/cent2.nfg b/src/pygambit/catalog_game_files/cent2.nfg similarity index 100% rename from contrib/games/cent2.nfg rename to src/pygambit/catalog_game_files/cent2.nfg diff --git a/contrib/games/cent3.efg b/src/pygambit/catalog_game_files/cent3.efg similarity index 100% rename from contrib/games/cent3.efg rename to src/pygambit/catalog_game_files/cent3.efg diff --git a/contrib/games/cent4.efg b/src/pygambit/catalog_game_files/cent4.efg similarity index 100% rename from contrib/games/cent4.efg rename to src/pygambit/catalog_game_files/cent4.efg diff --git a/contrib/games/cent6.efg b/src/pygambit/catalog_game_files/cent6.efg similarity index 100% rename from contrib/games/cent6.efg rename to src/pygambit/catalog_game_files/cent6.efg diff --git a/contrib/games/centcs10.efg b/src/pygambit/catalog_game_files/centcs10.efg similarity index 100% rename from contrib/games/centcs10.efg rename to src/pygambit/catalog_game_files/centcs10.efg diff --git a/contrib/games/centcs6.efg b/src/pygambit/catalog_game_files/centcs6.efg similarity index 100% rename from contrib/games/centcs6.efg rename to src/pygambit/catalog_game_files/centcs6.efg diff --git a/contrib/games/condjury.efg b/src/pygambit/catalog_game_files/condjury.efg similarity index 100% rename from contrib/games/condjury.efg rename to src/pygambit/catalog_game_files/condjury.efg diff --git a/contrib/games/coord2.efg b/src/pygambit/catalog_game_files/coord2.efg similarity index 100% rename from contrib/games/coord2.efg rename to src/pygambit/catalog_game_files/coord2.efg diff --git a/contrib/games/coord2.nfg b/src/pygambit/catalog_game_files/coord2.nfg similarity index 100% rename from contrib/games/coord2.nfg rename to src/pygambit/catalog_game_files/coord2.nfg diff --git a/contrib/games/coord2ts.efg b/src/pygambit/catalog_game_files/coord2ts.efg similarity index 100% rename from contrib/games/coord2ts.efg rename to src/pygambit/catalog_game_files/coord2ts.efg diff --git a/contrib/games/coord3.efg b/src/pygambit/catalog_game_files/coord3.efg similarity index 100% rename from contrib/games/coord3.efg rename to src/pygambit/catalog_game_files/coord3.efg diff --git a/contrib/games/coord3.nfg b/src/pygambit/catalog_game_files/coord3.nfg similarity index 100% rename from contrib/games/coord3.nfg rename to src/pygambit/catalog_game_files/coord3.nfg diff --git a/contrib/games/coord333.nfg b/src/pygambit/catalog_game_files/coord333.nfg similarity index 100% rename from contrib/games/coord333.nfg rename to src/pygambit/catalog_game_files/coord333.nfg diff --git a/contrib/games/coord4.efg b/src/pygambit/catalog_game_files/coord4.efg similarity index 100% rename from contrib/games/coord4.efg rename to src/pygambit/catalog_game_files/coord4.efg diff --git a/contrib/games/coord4.nfg b/src/pygambit/catalog_game_files/coord4.nfg similarity index 100% rename from contrib/games/coord4.nfg rename to src/pygambit/catalog_game_files/coord4.nfg diff --git a/contrib/games/cross.efg b/src/pygambit/catalog_game_files/cross.efg similarity index 100% rename from contrib/games/cross.efg rename to src/pygambit/catalog_game_files/cross.efg diff --git a/contrib/games/cs.efg b/src/pygambit/catalog_game_files/cs.efg similarity index 100% rename from contrib/games/cs.efg rename to src/pygambit/catalog_game_files/cs.efg diff --git a/contrib/games/csg1.nfg b/src/pygambit/catalog_game_files/csg1.nfg similarity index 100% rename from contrib/games/csg1.nfg rename to src/pygambit/catalog_game_files/csg1.nfg diff --git a/contrib/games/csg2.nfg b/src/pygambit/catalog_game_files/csg2.nfg similarity index 100% rename from contrib/games/csg2.nfg rename to src/pygambit/catalog_game_files/csg2.nfg diff --git a/contrib/games/csg3.nfg b/src/pygambit/catalog_game_files/csg3.nfg similarity index 100% rename from contrib/games/csg3.nfg rename to src/pygambit/catalog_game_files/csg3.nfg diff --git a/contrib/games/csg4.nfg b/src/pygambit/catalog_game_files/csg4.nfg similarity index 100% rename from contrib/games/csg4.nfg rename to src/pygambit/catalog_game_files/csg4.nfg diff --git a/contrib/games/deg1.nfg b/src/pygambit/catalog_game_files/deg1.nfg similarity index 100% rename from contrib/games/deg1.nfg rename to src/pygambit/catalog_game_files/deg1.nfg diff --git a/contrib/games/deg2.nfg b/src/pygambit/catalog_game_files/deg2.nfg similarity index 100% rename from contrib/games/deg2.nfg rename to src/pygambit/catalog_game_files/deg2.nfg diff --git a/contrib/games/e01.efg b/src/pygambit/catalog_game_files/e01.efg similarity index 100% rename from contrib/games/e01.efg rename to src/pygambit/catalog_game_files/e01.efg diff --git a/contrib/games/e01.nfg b/src/pygambit/catalog_game_files/e01.nfg similarity index 100% rename from contrib/games/e01.nfg rename to src/pygambit/catalog_game_files/e01.nfg diff --git a/contrib/games/e02.efg b/src/pygambit/catalog_game_files/e02.efg similarity index 100% rename from contrib/games/e02.efg rename to src/pygambit/catalog_game_files/e02.efg diff --git a/contrib/games/e02.nfg b/src/pygambit/catalog_game_files/e02.nfg similarity index 100% rename from contrib/games/e02.nfg rename to src/pygambit/catalog_game_files/e02.nfg diff --git a/contrib/games/e03.efg b/src/pygambit/catalog_game_files/e03.efg similarity index 100% rename from contrib/games/e03.efg rename to src/pygambit/catalog_game_files/e03.efg diff --git a/contrib/games/e04.efg b/src/pygambit/catalog_game_files/e04.efg similarity index 100% rename from contrib/games/e04.efg rename to src/pygambit/catalog_game_files/e04.efg diff --git a/contrib/games/e04.nfg b/src/pygambit/catalog_game_files/e04.nfg similarity index 100% rename from contrib/games/e04.nfg rename to src/pygambit/catalog_game_files/e04.nfg diff --git a/contrib/games/e05.efg b/src/pygambit/catalog_game_files/e05.efg similarity index 100% rename from contrib/games/e05.efg rename to src/pygambit/catalog_game_files/e05.efg diff --git a/contrib/games/e06.efg b/src/pygambit/catalog_game_files/e06.efg similarity index 100% rename from contrib/games/e06.efg rename to src/pygambit/catalog_game_files/e06.efg diff --git a/contrib/games/e07.efg b/src/pygambit/catalog_game_files/e07.efg similarity index 100% rename from contrib/games/e07.efg rename to src/pygambit/catalog_game_files/e07.efg diff --git a/contrib/games/e07.nfg b/src/pygambit/catalog_game_files/e07.nfg similarity index 100% rename from contrib/games/e07.nfg rename to src/pygambit/catalog_game_files/e07.nfg diff --git a/contrib/games/e08.efg b/src/pygambit/catalog_game_files/e08.efg similarity index 100% rename from contrib/games/e08.efg rename to src/pygambit/catalog_game_files/e08.efg diff --git a/contrib/games/e09.efg b/src/pygambit/catalog_game_files/e09.efg similarity index 100% rename from contrib/games/e09.efg rename to src/pygambit/catalog_game_files/e09.efg diff --git a/contrib/games/e10.efg b/src/pygambit/catalog_game_files/e10.efg similarity index 100% rename from contrib/games/e10.efg rename to src/pygambit/catalog_game_files/e10.efg diff --git a/contrib/games/e10a.efg b/src/pygambit/catalog_game_files/e10a.efg similarity index 100% rename from contrib/games/e10a.efg rename to src/pygambit/catalog_game_files/e10a.efg diff --git a/contrib/games/e13.efg b/src/pygambit/catalog_game_files/e13.efg similarity index 100% rename from contrib/games/e13.efg rename to src/pygambit/catalog_game_files/e13.efg diff --git a/contrib/games/e16.efg b/src/pygambit/catalog_game_files/e16.efg similarity index 100% rename from contrib/games/e16.efg rename to src/pygambit/catalog_game_files/e16.efg diff --git a/contrib/games/e17.efg b/src/pygambit/catalog_game_files/e17.efg similarity index 100% rename from contrib/games/e17.efg rename to src/pygambit/catalog_game_files/e17.efg diff --git a/contrib/games/e18.efg b/src/pygambit/catalog_game_files/e18.efg similarity index 100% rename from contrib/games/e18.efg rename to src/pygambit/catalog_game_files/e18.efg diff --git a/contrib/games/g1.efg b/src/pygambit/catalog_game_files/g1.efg similarity index 100% rename from contrib/games/g1.efg rename to src/pygambit/catalog_game_files/g1.efg diff --git a/contrib/games/g1.nfg b/src/pygambit/catalog_game_files/g1.nfg similarity index 100% rename from contrib/games/g1.nfg rename to src/pygambit/catalog_game_files/g1.nfg diff --git a/contrib/games/g2.efg b/src/pygambit/catalog_game_files/g2.efg similarity index 100% rename from contrib/games/g2.efg rename to src/pygambit/catalog_game_files/g2.efg diff --git a/contrib/games/g2.nfg b/src/pygambit/catalog_game_files/g2.nfg similarity index 100% rename from contrib/games/g2.nfg rename to src/pygambit/catalog_game_files/g2.nfg diff --git a/contrib/games/g3.efg b/src/pygambit/catalog_game_files/g3.efg similarity index 100% rename from contrib/games/g3.efg rename to src/pygambit/catalog_game_files/g3.efg diff --git a/contrib/games/g3.nfg b/src/pygambit/catalog_game_files/g3.nfg similarity index 100% rename from contrib/games/g3.nfg rename to src/pygambit/catalog_game_files/g3.nfg diff --git a/contrib/games/holdout.efg b/src/pygambit/catalog_game_files/holdout.efg similarity index 100% rename from contrib/games/holdout.efg rename to src/pygambit/catalog_game_files/holdout.efg diff --git a/contrib/games/holdout7.efg b/src/pygambit/catalog_game_files/holdout7.efg similarity index 100% rename from contrib/games/holdout7.efg rename to src/pygambit/catalog_game_files/holdout7.efg diff --git a/contrib/games/hs1.efg b/src/pygambit/catalog_game_files/hs1.efg similarity index 100% rename from contrib/games/hs1.efg rename to src/pygambit/catalog_game_files/hs1.efg diff --git a/contrib/games/jury_mr.efg b/src/pygambit/catalog_game_files/jury_mr.efg similarity index 100% rename from contrib/games/jury_mr.efg rename to src/pygambit/catalog_game_files/jury_mr.efg diff --git a/contrib/games/jury_un.efg b/src/pygambit/catalog_game_files/jury_un.efg similarity index 100% rename from contrib/games/jury_un.efg rename to src/pygambit/catalog_game_files/jury_un.efg diff --git a/contrib/games/km1.efg b/src/pygambit/catalog_game_files/km1.efg similarity index 100% rename from contrib/games/km1.efg rename to src/pygambit/catalog_game_files/km1.efg diff --git a/contrib/games/km2.efg b/src/pygambit/catalog_game_files/km2.efg similarity index 100% rename from contrib/games/km2.efg rename to src/pygambit/catalog_game_files/km2.efg diff --git a/contrib/games/km3.efg b/src/pygambit/catalog_game_files/km3.efg similarity index 100% rename from contrib/games/km3.efg rename to src/pygambit/catalog_game_files/km3.efg diff --git a/contrib/games/km6.efg b/src/pygambit/catalog_game_files/km6.efg similarity index 100% rename from contrib/games/km6.efg rename to src/pygambit/catalog_game_files/km6.efg diff --git a/contrib/games/loopback.nfg b/src/pygambit/catalog_game_files/loopback.nfg similarity index 100% rename from contrib/games/loopback.nfg rename to src/pygambit/catalog_game_files/loopback.nfg diff --git a/contrib/games/mixdom.nfg b/src/pygambit/catalog_game_files/mixdom.nfg similarity index 100% rename from contrib/games/mixdom.nfg rename to src/pygambit/catalog_game_files/mixdom.nfg diff --git a/contrib/games/mixdom2.nfg b/src/pygambit/catalog_game_files/mixdom2.nfg similarity index 100% rename from contrib/games/mixdom2.nfg rename to src/pygambit/catalog_game_files/mixdom2.nfg diff --git a/contrib/games/montyhal.efg b/src/pygambit/catalog_game_files/montyhal.efg similarity index 100% rename from contrib/games/montyhal.efg rename to src/pygambit/catalog_game_files/montyhal.efg diff --git a/contrib/games/my_2-1.efg b/src/pygambit/catalog_game_files/my_2-1.efg similarity index 100% rename from contrib/games/my_2-1.efg rename to src/pygambit/catalog_game_files/my_2-1.efg diff --git a/contrib/games/my_2-4.efg b/src/pygambit/catalog_game_files/my_2-4.efg similarity index 100% rename from contrib/games/my_2-4.efg rename to src/pygambit/catalog_game_files/my_2-4.efg diff --git a/contrib/games/my_2-8.efg b/src/pygambit/catalog_game_files/my_2-8.efg similarity index 100% rename from contrib/games/my_2-8.efg rename to src/pygambit/catalog_game_files/my_2-8.efg diff --git a/contrib/games/my_3-3a.efg b/src/pygambit/catalog_game_files/my_3-3a.efg similarity index 100% rename from contrib/games/my_3-3a.efg rename to src/pygambit/catalog_game_files/my_3-3a.efg diff --git a/contrib/games/my_3-3b.efg b/src/pygambit/catalog_game_files/my_3-3b.efg similarity index 100% rename from contrib/games/my_3-3b.efg rename to src/pygambit/catalog_game_files/my_3-3b.efg diff --git a/contrib/games/my_3-3c.efg b/src/pygambit/catalog_game_files/my_3-3c.efg similarity index 100% rename from contrib/games/my_3-3c.efg rename to src/pygambit/catalog_game_files/my_3-3c.efg diff --git a/contrib/games/my_3-3d.efg b/src/pygambit/catalog_game_files/my_3-3d.efg similarity index 100% rename from contrib/games/my_3-3d.efg rename to src/pygambit/catalog_game_files/my_3-3d.efg diff --git a/contrib/games/my_3-3e.efg b/src/pygambit/catalog_game_files/my_3-3e.efg similarity index 100% rename from contrib/games/my_3-3e.efg rename to src/pygambit/catalog_game_files/my_3-3e.efg diff --git a/contrib/games/my_3-4.efg b/src/pygambit/catalog_game_files/my_3-4.efg similarity index 100% rename from contrib/games/my_3-4.efg rename to src/pygambit/catalog_game_files/my_3-4.efg diff --git a/contrib/games/myerson.efg b/src/pygambit/catalog_game_files/myerson.efg similarity index 100% rename from contrib/games/myerson.efg rename to src/pygambit/catalog_game_files/myerson.efg diff --git a/contrib/games/myerson_fig_4_2.efg b/src/pygambit/catalog_game_files/myerson_fig_4_2.efg similarity index 100% rename from contrib/games/myerson_fig_4_2.efg rename to src/pygambit/catalog_game_files/myerson_fig_4_2.efg diff --git a/contrib/games/nim.efg b/src/pygambit/catalog_game_files/nim.efg similarity index 100% rename from contrib/games/nim.efg rename to src/pygambit/catalog_game_files/nim.efg diff --git a/contrib/games/nim7.efg b/src/pygambit/catalog_game_files/nim7.efg similarity index 100% rename from contrib/games/nim7.efg rename to src/pygambit/catalog_game_files/nim7.efg diff --git a/contrib/games/oneill.nfg b/src/pygambit/catalog_game_files/oneill.nfg similarity index 100% rename from contrib/games/oneill.nfg rename to src/pygambit/catalog_game_files/oneill.nfg diff --git a/contrib/games/palf.efg b/src/pygambit/catalog_game_files/palf.efg similarity index 100% rename from contrib/games/palf.efg rename to src/pygambit/catalog_game_files/palf.efg diff --git a/contrib/games/palf2.efg b/src/pygambit/catalog_game_files/palf2.efg similarity index 100% rename from contrib/games/palf2.efg rename to src/pygambit/catalog_game_files/palf2.efg diff --git a/contrib/games/palf3.efg b/src/pygambit/catalog_game_files/palf3.efg similarity index 100% rename from contrib/games/palf3.efg rename to src/pygambit/catalog_game_files/palf3.efg diff --git a/contrib/games/pbride.efg b/src/pygambit/catalog_game_files/pbride.efg similarity index 100% rename from contrib/games/pbride.efg rename to src/pygambit/catalog_game_files/pbride.efg diff --git a/contrib/games/pd.nfg b/src/pygambit/catalog_game_files/pd.nfg similarity index 100% rename from contrib/games/pd.nfg rename to src/pygambit/catalog_game_files/pd.nfg diff --git a/contrib/games/perfect1.nfg b/src/pygambit/catalog_game_files/perfect1.nfg similarity index 100% rename from contrib/games/perfect1.nfg rename to src/pygambit/catalog_game_files/perfect1.nfg diff --git a/contrib/games/perfect2.nfg b/src/pygambit/catalog_game_files/perfect2.nfg similarity index 100% rename from contrib/games/perfect2.nfg rename to src/pygambit/catalog_game_files/perfect2.nfg diff --git a/contrib/games/perfect3.nfg b/src/pygambit/catalog_game_files/perfect3.nfg similarity index 100% rename from contrib/games/perfect3.nfg rename to src/pygambit/catalog_game_files/perfect3.nfg diff --git a/contrib/games/poker.efg b/src/pygambit/catalog_game_files/poker.efg similarity index 100% rename from contrib/games/poker.efg rename to src/pygambit/catalog_game_files/poker.efg diff --git a/contrib/games/poker.nfg b/src/pygambit/catalog_game_files/poker.nfg similarity index 100% rename from contrib/games/poker.nfg rename to src/pygambit/catalog_game_files/poker.nfg diff --git a/contrib/games/poker2.efg b/src/pygambit/catalog_game_files/poker2.efg similarity index 100% rename from contrib/games/poker2.efg rename to src/pygambit/catalog_game_files/poker2.efg diff --git a/contrib/games/pvw.efg b/src/pygambit/catalog_game_files/pvw.efg similarity index 100% rename from contrib/games/pvw.efg rename to src/pygambit/catalog_game_files/pvw.efg diff --git a/contrib/games/pvw2.efg b/src/pygambit/catalog_game_files/pvw2.efg similarity index 100% rename from contrib/games/pvw2.efg rename to src/pygambit/catalog_game_files/pvw2.efg diff --git a/contrib/games/sh3.efg b/src/pygambit/catalog_game_files/sh3.efg similarity index 100% rename from contrib/games/sh3.efg rename to src/pygambit/catalog_game_files/sh3.efg diff --git a/contrib/games/sh3.nfg b/src/pygambit/catalog_game_files/sh3.nfg similarity index 100% rename from contrib/games/sh3.nfg rename to src/pygambit/catalog_game_files/sh3.nfg diff --git a/contrib/games/spence.efg b/src/pygambit/catalog_game_files/spence.efg similarity index 100% rename from contrib/games/spence.efg rename to src/pygambit/catalog_game_files/spence.efg diff --git a/contrib/games/stengel.nfg b/src/pygambit/catalog_game_files/stengel.nfg similarity index 100% rename from contrib/games/stengel.nfg rename to src/pygambit/catalog_game_files/stengel.nfg diff --git a/contrib/games/sww1.efg b/src/pygambit/catalog_game_files/sww1.efg similarity index 100% rename from contrib/games/sww1.efg rename to src/pygambit/catalog_game_files/sww1.efg diff --git a/contrib/games/sww1.nfg b/src/pygambit/catalog_game_files/sww1.nfg similarity index 100% rename from contrib/games/sww1.nfg rename to src/pygambit/catalog_game_files/sww1.nfg diff --git a/contrib/games/sww2.efg b/src/pygambit/catalog_game_files/sww2.efg similarity index 100% rename from contrib/games/sww2.efg rename to src/pygambit/catalog_game_files/sww2.efg diff --git a/contrib/games/sww3.efg b/src/pygambit/catalog_game_files/sww3.efg similarity index 100% rename from contrib/games/sww3.efg rename to src/pygambit/catalog_game_files/sww3.efg diff --git a/contrib/games/tim.efg b/src/pygambit/catalog_game_files/tim.efg similarity index 100% rename from contrib/games/tim.efg rename to src/pygambit/catalog_game_files/tim.efg diff --git a/contrib/games/todd1.nfg b/src/pygambit/catalog_game_files/todd1.nfg similarity index 100% rename from contrib/games/todd1.nfg rename to src/pygambit/catalog_game_files/todd1.nfg diff --git a/contrib/games/todd2.nfg b/src/pygambit/catalog_game_files/todd2.nfg similarity index 100% rename from contrib/games/todd2.nfg rename to src/pygambit/catalog_game_files/todd2.nfg diff --git a/contrib/games/todd3.nfg b/src/pygambit/catalog_game_files/todd3.nfg similarity index 100% rename from contrib/games/todd3.nfg rename to src/pygambit/catalog_game_files/todd3.nfg diff --git a/contrib/games/ttt.efg b/src/pygambit/catalog_game_files/ttt.efg similarity index 100% rename from contrib/games/ttt.efg rename to src/pygambit/catalog_game_files/ttt.efg diff --git a/contrib/games/vd.efg b/src/pygambit/catalog_game_files/vd.efg similarity index 100% rename from contrib/games/vd.efg rename to src/pygambit/catalog_game_files/vd.efg diff --git a/contrib/games/vd.nfg b/src/pygambit/catalog_game_files/vd.nfg similarity index 100% rename from contrib/games/vd.nfg rename to src/pygambit/catalog_game_files/vd.nfg diff --git a/contrib/games/w_ex1.efg b/src/pygambit/catalog_game_files/w_ex1.efg similarity index 100% rename from contrib/games/w_ex1.efg rename to src/pygambit/catalog_game_files/w_ex1.efg diff --git a/contrib/games/w_ex2.efg b/src/pygambit/catalog_game_files/w_ex2.efg similarity index 100% rename from contrib/games/w_ex2.efg rename to src/pygambit/catalog_game_files/w_ex2.efg diff --git a/contrib/games/wilson1.efg b/src/pygambit/catalog_game_files/wilson1.efg similarity index 100% rename from contrib/games/wilson1.efg rename to src/pygambit/catalog_game_files/wilson1.efg diff --git a/contrib/games/wink3.nfg b/src/pygambit/catalog_game_files/wink3.nfg similarity index 100% rename from contrib/games/wink3.nfg rename to src/pygambit/catalog_game_files/wink3.nfg diff --git a/contrib/games/winkels.nfg b/src/pygambit/catalog_game_files/winkels.nfg similarity index 100% rename from contrib/games/winkels.nfg rename to src/pygambit/catalog_game_files/winkels.nfg diff --git a/contrib/games/work1.efg b/src/pygambit/catalog_game_files/work1.efg similarity index 100% rename from contrib/games/work1.efg rename to src/pygambit/catalog_game_files/work1.efg diff --git a/contrib/games/work2.efg b/src/pygambit/catalog_game_files/work2.efg similarity index 100% rename from contrib/games/work2.efg rename to src/pygambit/catalog_game_files/work2.efg diff --git a/contrib/games/work3.efg b/src/pygambit/catalog_game_files/work3.efg similarity index 100% rename from contrib/games/work3.efg rename to src/pygambit/catalog_game_files/work3.efg diff --git a/contrib/games/yamamoto.nfg b/src/pygambit/catalog_game_files/yamamoto.nfg similarity index 100% rename from contrib/games/yamamoto.nfg rename to src/pygambit/catalog_game_files/yamamoto.nfg diff --git a/contrib/games/zero.nfg b/src/pygambit/catalog_game_files/zero.nfg similarity index 100% rename from contrib/games/zero.nfg rename to src/pygambit/catalog_game_files/zero.nfg diff --git a/src/pygambit/catalog_games.py b/src/pygambit/catalog_games.py new file mode 100644 index 000000000..67ca736cc --- /dev/null +++ b/src/pygambit/catalog_games.py @@ -0,0 +1,50 @@ +import pygambit as gbt + + +class OneShotTrust(gbt.catalog.CatalogGame): + + test_suite = True + + @staticmethod + def _game(unique_NE_variant: bool = False) -> gbt.Game: + """ + The unique_NE_variant makes Trust a dominant strategy, replacing the + non-singleton equilibrium component from the standard version of the game + where the Buyer plays "Not Trust" and the seller can play any mixture with + < 0.5 probability on Honor with a unique NE where the Buyer plays Trust and + the Seller plays Abuse. + + Parameters + ---------- + unique_NE_variant : bool, optional + Whether to modify the game so that it has a unique Nash equilibrium. + Defaults to False. + + Returns + ------- + gbt.Game + The constructed extensive-form game. + + Examples + -------- + >>> OneShotTrust(unique_NE_variant=False) # Constructs the standard game + >>> OneShotTrust(unique_NE_variant=True) # Constructs the game with unique NE variant + """ + g = gbt.Game.new_tree( + players=["Buyer", "Seller"], + title="One-shot trust game" + ) + g.description = "One-shot trust game with binary actions, originally from Kreps (1990)." + g.append_move(g.root, "Buyer", ["Trust", "Not trust"]) + g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"]) + g.set_outcome(g.root.children[0].children[0], g.add_outcome([1, 1], label="Trustworthy")) + if unique_NE_variant: + g.set_outcome( + g.root.children[0].children[1], g.add_outcome(["1/2", 2], label="Untrustworthy") + ) + else: + g.set_outcome( + g.root.children[0].children[1], g.add_outcome([-1, 2], label="Untrustworthy") + ) + g.set_outcome(g.root.children[1], g.add_outcome([0, 0], label="Opt-out")) + return g diff --git a/src/pygambit/catalog_update.py b/src/pygambit/catalog_update.py new file mode 100644 index 000000000..c731112d5 --- /dev/null +++ b/src/pygambit/catalog_update.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 + +import sys +from pathlib import Path + +from ruamel.yaml import YAML + +import pygambit as gbt + +_CATALOG_YAML = Path(__file__).parent / "catalog.yml" +_GAMEFILES_DIR = Path(__file__).parent / "catalog_game_files" +_API_RST = Path(__file__).parent.parent.parent / "doc/pygambit.api.rst" +_MAKEFILE_AM = Path(__file__).parent.parent.parent / "Makefile.am" + + +def make_class_name(filename: str) -> str: + """ + Convert a filename (without extension) into a class name. + - Replace hyphens with underscores + - Capitalise + - Prepend 'Game' if it starts with a digit + """ + name = filename.replace("-", "_") + + # Capitalise in a simple, predictable way + name = name[0].upper() + name[1:] if name else name + + if name and name[0].isdigit(): + name = f"Game{name}" + + return name + + +def update_api_rst(new_classes: list) -> None: + """Update the Game catalog section in pygambit.api.rst with all class names.""" + _all_catalog_classes = gbt.catalog.games() + for class_name in new_classes: + if class_name not in _all_catalog_classes: + _all_catalog_classes.append(class_name) + _all_catalog_classes.sort() + with open(_API_RST, encoding="utf-8") as f: + content = f.read() + + # Find the Game catalog section + game_catalog_start = content.find("Game catalog\n~~~~~~~~~~~~") + if game_catalog_start == -1: + print("Warning: 'Game catalog' section not found in pygambit.api.rst") + return + + # Find the autosummary block + autosummary_start = content.find(".. autosummary::", game_catalog_start) + toctree_start = content.find(":toctree: api/", autosummary_start) + + # Find the next section (starts with ~~) + next_section = content.find("\n~~", toctree_start) + if next_section == -1: + next_section = len(content) + + # Build the new toctree content + new_toctree = ".. autosummary::\n :toctree: api/\n\n games\n" + for class_name in _all_catalog_classes: + cls = getattr(sys.modules[__name__], class_name, None) + if cls is not None and hasattr(cls, "valid_game") and cls.valid_game is False: + pass # Marked as invalid game, do not add class to toctree + else: + new_toctree += f" {class_name}\n" + + # Replace the old toctree with the new one + old_toctree_start = content.rfind(".. autosummary::", game_catalog_start, toctree_start + 100) + old_toctree_end = next_section + + new_content = ( + content[:old_toctree_start] + + new_toctree + + content[old_toctree_end:] + ) + if new_content != content: + print(f"Updated {_API_RST}") + + with open(_API_RST, "w", encoding="utf-8") as f: + f.write(new_content) + + +def update_makefile(): + """Update the Makefile.am with all game files from the catalog.""" + with open(_CATALOG_YAML, encoding="utf-8") as f: + yaml = YAML() + catalog = yaml.load(f) or {} + + game_files = [] + for entry in catalog.values(): + file_name = entry.get("file") + if file_name: + game_files.append(f"src/pygambit/catalog_game_files/{file_name}") + + game_files.sort() + + with open(_MAKEFILE_AM, encoding="utf-8") as f: + content = f.readlines() + + with open(_MAKEFILE_AM, "w", encoding="utf-8") as f: + in_gamefiles_section = False + for line in content: + # Add to the EXTRA_DIST after the README.rst line + if line.startswith(" src/README.rst \\"): + in_gamefiles_section = True + f.write(" src/README.rst \\\n") + for gf in game_files: + if gf == game_files[-1]: + f.write(f"\t{gf}\n") + else: + f.write(f"\t{gf} \\\n") + f.write("\n") + elif in_gamefiles_section: + if line.strip() == "": + in_gamefiles_section = False + continue # Skip old gamefiles lines + else: + f.write(line) + + with open(_MAKEFILE_AM, encoding="utf-8") as f: + updated_content = f.readlines() + + if content != updated_content: + print(f"Updated {_MAKEFILE_AM}") + + +# TODO: Add images for catalog games +# def generate_draw_tree_images(): +# """Generate tree images for all extensive-form games in the catalog.""" +# from draw_tree import generate_png +# for class_name in gbt.catalog.games(): +# cls = getattr(gbt.catalog, class_name) +# g = cls() +# if g.is_tree: +# image_path = ( +# Path(__file__).parent.parent.parent +# / "doc/_static/catalog_drawn/" +# / f"{class_name}.png" +# ) +# generate_png(g, output_png=image_path) +# print(f"Generated tree image for {class_name} at {image_path}") + + +if __name__ == "__main__": + # Use ruamel.yaml to preserve comments + yaml = YAML() + yaml.preserve_quotes = True + yaml.default_flow_style = False + + efg_files = list(_GAMEFILES_DIR.rglob("*.efg")) + nfg_files = list(_GAMEFILES_DIR.rglob("*.nfg")) + + print(f"Found {len(efg_files)} .efg files in catalog_game_files") + print(f"Found {len(nfg_files)} .nfg files in catalog_game_files") + + all_files = sorted(efg_files + nfg_files) + + # Get the current class names from the catalog + with open(_CATALOG_YAML, encoding="utf-8") as f: + catalog = yaml.load(f) or {} + file_names = [entry["file"] for entry in catalog.values() if "file" in entry] + + # Iterate through catalog_game_files and update the catalog + # with new/missing entries + new_entries_counter = 0 + new_entries = {} + for path in all_files: + stem = path.stem + class_name = make_class_name(stem) + + # Avoid duplicates by appending EFG or NFG + if class_name in new_entries: + class_name += path.suffix.split(".")[-1].upper() + + if path.name not in file_names: + new_entries[class_name] = { + "file": path.name, + "metadata": {}, + } + new_entries_counter += 1 + + # Update the catalog + catalog.update(new_entries) + with _CATALOG_YAML.open("w", encoding="utf-8") as f: + yaml.dump(catalog, f) + + print(f"Added {new_entries_counter} new entries to the catalog: ", list(new_entries.keys())) + if new_entries_counter > 0: + print(f"Updated: {_CATALOG_YAML}") + + # Update the RST documentation with the new full catalog + # This includes games from coded_games.py as well as catalog.yml + update_api_rst(new_classes=list(new_entries.keys())) + + # Update the Makefile.am with all game files + update_makefile() + + # TODO: Add images for catalog games + # Generate EFG game images and add to _static/catalog_drawn/ + # generate_draw_tree_images() + + print("Done.") diff --git a/src/pygambit/game.pxi b/src/pygambit/game.pxi index 2fbde8186..403eb9f98 100644 --- a/src/pygambit/game.pxi +++ b/src/pygambit/game.pxi @@ -661,16 +661,16 @@ class Game: self.game.deref().SetTitle(value.encode("ascii")) @property - def comment(self) -> str: - """Get or set the comment of the game. + def description(self) -> str: + """Get or set the description of the game. - A game's comment is an arbitrary string, and may be more discursive + A game's description/comment is an arbitrary string, and may be more discursive than a title. """ return self.game.deref().GetComment().decode("ascii") - @comment.setter - def comment(self, value: str) -> None: + @description.setter + def description(self, value: str) -> None: self.game.deref().SetComment(value.encode("ascii")) @property diff --git a/tests/test_catalog.py b/tests/test_catalog.py new file mode 100644 index 000000000..62c8d8b64 --- /dev/null +++ b/tests/test_catalog.py @@ -0,0 +1,132 @@ +import pytest + +import pygambit as gbt + + +class ExampleGame(gbt.catalog.CatalogGame): + + @staticmethod + def _game(some_param: bool = False): + """_game docstring""" + if some_param: + g = gbt.Game.new_tree( + players=["A", "B"], title="Test game T" + ) + else: + g = gbt.Game.new_tree( + players=["A", "B"], title="Test game F" + ) + g.description = "Game description" + return g + + +class ExampleGameWithDocstring(ExampleGame): + """ + Class docstring + """ + + +class TestCatalogGame: + """Tests for CatalogGame base class and subclassing.""" + + def test_catalog_game_not_instantiable(self): + """CatalogGame should not be directly instantiable.""" + with pytest.raises(NotImplementedError): + gbt.catalog.CatalogGame() + + def test_docstring_handling_from_game(self): + """ + Custom CatalogGame subclass should extract title and description + from game object and _game docstring. + """ + assert "Game description" in ExampleGame.__doc__ + assert "_game docstring" in ExampleGame.__doc__ + assert "Test game F" in ExampleGame.__doc__ + + def test_description_handling_from_docstring(self): + """Custom CatalogGame subclass should get docstring over game description/title.""" + assert "Class docstring" in ExampleGameWithDocstring.__doc__ + assert "Game description" not in ExampleGameWithDocstring.__doc__ + assert "_game docstring" in ExampleGameWithDocstring.__doc__ + assert "Test game F" in ExampleGameWithDocstring.__doc__ + + def test_catalog_py_game_with_parameters(self): + """ + Custom CatalogGame subclass should return Game + and support parameters. + """ + assert ExampleGame(some_param=False).title == "Test game F" + assert ExampleGame(some_param=True).title == "Test game T" + + def test_catalog_yml_game_instantiation(self): + """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" + assert isinstance(gbt.catalog.PrisonersDilemma(), gbt.Game) + + def test_catalog_yml_game_description_in_docstring(self): + """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" + assert ( + "A simple implementation of a two person Prisoner's Dilemma game." in + gbt.catalog.PrisonersDilemma.__doc__ + ) + + def test_catalog_game_missing_game_method(self): + """CatalogGame subclass missing _game should raise NotImplementedError.""" + with pytest.raises(NotImplementedError): + class InvalidGame(gbt.catalog.CatalogGame): + """A CatalogGame that does not implement _game.""" + + +class TestGamesFunction: + """Tests for the games() query function.""" + + def test_games_returns_list_of_strings(self): + """games() should return a list of class name strings.""" + result = gbt.catalog.games() + assert isinstance(result, list) + assert all(isinstance(name, str) for name in result) + + def test_invalid_games_not_included(self): + """games() should not return classes marked as valid_game=False.""" + result = gbt.catalog.games() + assert "InvalidGame" not in result + + def test_games_filter_by_game_type(self): + """Filtering should split games into NFG/EFG.""" + nfg_games = gbt.catalog.games(is_tree=False) + efg_games = gbt.catalog.games(is_tree=True) + all_games = gbt.catalog.games(is_tree=None) + assert len(all_games) == len(set(nfg_games + efg_games)) + + def test_games_filter_by_num_players(self): + """games(num_players=n) should return only n-player games.""" + three_player_games = gbt.catalog.games(num_players=3) + for game_name in three_player_games: + game_class = getattr(gbt.catalog, game_name) + assert len(game_class.game.players) == 3 + + def test_games_filter_by_num_infosets(self): + """games(num_infosets=n) should return only n-infoset games.""" + two_infoset_games = gbt.catalog.games(num_infosets=2) + for game_name in two_infoset_games: + game_class = getattr(gbt.catalog, game_name) + assert len(game_class.game.infosets) == 2 + + def test_games_filter_by_custom_metadata(self): + """games() should filter by custom metadata fields.""" + custom_filter_games = gbt.catalog.games(test_suite=True) + assert isinstance(custom_filter_games, list) + for game_name in custom_filter_games: + game_class = getattr(gbt.catalog, game_name) + assert hasattr(game_class, "test_suite") + assert game_class.test_suite is True + + def test_games_excludes_base_classes(self): + """games() should not include base classes like CatalogGameFromFile.""" + result = gbt.catalog.games() + assert "CatalogGame" not in result + assert "CatalogGameFromFile" not in result + + def test_games_includes_coded_games(self): + """games() should include manually coded games.""" + result = gbt.catalog.games(test_suite=True) + assert "OneShotTrust" in result diff --git a/tests/test_extensive.py b/tests/test_extensive.py index 41528afbe..348ddbb35 100644 --- a/tests/test_extensive.py +++ b/tests/test_extensive.py @@ -25,12 +25,12 @@ def test_game_title(title: str): @pytest.mark.parametrize( - "comment", ["This is a comment describing the game in more detail"] + "description", ["This is a description describing the game in more detail"] ) -def test_game_comment(comment: str): +def test_game_description(description: str): game = gbt.Game.new_tree() - game.comment = comment - assert game.comment == comment + game.description = description + assert game.description == description @pytest.mark.parametrize("players", [["Alice"], ["Oscar", "Felix"]])