From 90cb55f16f528a023d040bd21cce312b6f726a55 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 13:23:03 +0000 Subject: [PATCH 001/170] Add generated build support files to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) 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 From 6a70eeb743f82500bf3f7a9f6666ac6b90ff39cf Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 13:30:56 +0000 Subject: [PATCH 002/170] fix differing path --- doc/tutorials/01_quickstart.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 06ec6f91e..4c1d85f7b 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -467,13 +467,13 @@ } ], "source": [ - "# gbt.read_nfg(\"test_games/prisoners_dilemma.nfg\")" + "# gbt.read_nfg(\"prisoners_dilemma.nfg\")" ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "gambitvenv313", "language": "python", "name": "python3" }, From 7d03a2c8a1a2a987a5643c76498588c31b866749 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 13:36:36 +0000 Subject: [PATCH 003/170] mock up catalog section of tutorial --- doc/tutorials/01_quickstart.ipynb | 43 ++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 4c1d85f7b..e7c6ee954 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -417,11 +417,52 @@ }, { "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 in the catalog like so:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "701aa52a", + "metadata": {}, + "outputs": [], + "source": [ + "# gambit.catalog.games()" + ] + }, + { + "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": null, + "id": "6db7a29a", + "metadata": {}, + "outputs": [], + "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", From b65bf9c0e63ac517ce6b8082c3bbb215432b972c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 13:47:54 +0000 Subject: [PATCH 004/170] move game files into catalog --- {contrib/games => catalog/games/files}/2s2x2x2.efg | 0 {contrib/games => catalog/games/files}/2smp.efg | 0 {contrib/games => catalog/games/files}/2x2.agg | 0 {contrib/games => catalog/games/files}/2x2.nfg | 0 {contrib/games => catalog/games/files}/2x2a.nfg | 0 {contrib/games => catalog/games/files}/2x2const.nfg | 0 {contrib/games => catalog/games/files}/2x2x2-nau.nfg | 0 {contrib/games => catalog/games/files}/2x2x2.efg | 0 {contrib/games => catalog/games/files}/2x2x2.nfg | 0 {contrib/games => catalog/games/files}/2x2x2x2.nfg | 0 {contrib/games => catalog/games/files}/2x2x2x2x2.nfg | 0 {contrib/games => catalog/games/files}/3x3x3.nfg | 0 {contrib/games => catalog/games/files}/4cards.efg | 0 {contrib/games => catalog/games/files}/5x4x3.nfg | 0 {contrib/games => catalog/games/files}/8x2x2.nfg | 0 {contrib/games => catalog/games/files}/8x8.nfg | 0 {contrib/games => catalog/games/files}/BSS_S_085.Weighted.agg | 0 .../games => catalog/games/files}/Bayesian-Coffee-3-2-2-3.bagg | 0 {contrib/games => catalog/games/files}/GenRPS5.agg | 0 {contrib/games => catalog/games/files}/artist1.efg | 0 {contrib/games => catalog/games/files}/artist2.efg | 0 {contrib/games => catalog/games/files}/badgame1.efg | 0 {contrib/games => catalog/games/files}/badgame2.efg | 0 {contrib/games => catalog/games/files}/bagwell.efg | 0 {contrib/games => catalog/games/files}/bayes1a.efg | 0 {contrib/games => catalog/games/files}/bayes2a.efg | 0 {contrib/games => catalog/games/files}/bcp2.efg | 0 {contrib/games => catalog/games/files}/bcp3.efg | 0 {contrib/games => catalog/games/files}/bcp4.efg | 0 {contrib/games => catalog/games/files}/bhg1.efg | 0 {contrib/games => catalog/games/files}/bhg2.efg | 0 {contrib/games => catalog/games/files}/bhg3.efg | 0 {contrib/games => catalog/games/files}/bhg4.efg | 0 {contrib/games => catalog/games/files}/bhg5.efg | 0 {contrib/games => catalog/games/files}/caro2.efg | 0 {contrib/games => catalog/games/files}/cent2.efg | 0 {contrib/games => catalog/games/files}/cent2.nfg | 0 {contrib/games => catalog/games/files}/cent3.efg | 0 {contrib/games => catalog/games/files}/cent4.efg | 0 {contrib/games => catalog/games/files}/cent6.efg | 0 {contrib/games => catalog/games/files}/centcs10.efg | 0 {contrib/games => catalog/games/files}/centcs6.efg | 0 {contrib/games => catalog/games/files}/condjury.efg | 0 {contrib/games => catalog/games/files}/coord2.efg | 0 {contrib/games => catalog/games/files}/coord2.nfg | 0 {contrib/games => catalog/games/files}/coord2ts.efg | 0 {contrib/games => catalog/games/files}/coord3.efg | 0 {contrib/games => catalog/games/files}/coord3.nfg | 0 {contrib/games => catalog/games/files}/coord333.nfg | 0 {contrib/games => catalog/games/files}/coord4.efg | 0 {contrib/games => catalog/games/files}/coord4.nfg | 0 {contrib/games => catalog/games/files}/cross.efg | 0 {contrib/games => catalog/games/files}/cs.efg | 0 {contrib/games => catalog/games/files}/csg1.nfg | 0 {contrib/games => catalog/games/files}/csg2.nfg | 0 {contrib/games => catalog/games/files}/csg3.nfg | 0 {contrib/games => catalog/games/files}/csg4.nfg | 0 {contrib/games => catalog/games/files}/deg1.nfg | 0 {contrib/games => catalog/games/files}/deg2.nfg | 0 {contrib/games => catalog/games/files}/e01.efg | 0 {contrib/games => catalog/games/files}/e01.nfg | 0 {contrib/games => catalog/games/files}/e02.efg | 0 {contrib/games => catalog/games/files}/e02.nfg | 0 {contrib/games => catalog/games/files}/e03.efg | 0 {contrib/games => catalog/games/files}/e04.efg | 0 {contrib/games => catalog/games/files}/e04.nfg | 0 {contrib/games => catalog/games/files}/e05.efg | 0 {contrib/games => catalog/games/files}/e06.efg | 0 {contrib/games => catalog/games/files}/e07.efg | 0 {contrib/games => catalog/games/files}/e07.nfg | 0 {contrib/games => catalog/games/files}/e08.efg | 0 {contrib/games => catalog/games/files}/e09.efg | 0 {contrib/games => catalog/games/files}/e10.efg | 0 {contrib/games => catalog/games/files}/e10a.efg | 0 {contrib/games => catalog/games/files}/e13.efg | 0 {contrib/games => catalog/games/files}/e16.efg | 0 {contrib/games => catalog/games/files}/e17.efg | 0 {contrib/games => catalog/games/files}/e18.efg | 0 {contrib/games => catalog/games/files}/g1.efg | 0 {contrib/games => catalog/games/files}/g1.nfg | 0 {contrib/games => catalog/games/files}/g2.efg | 0 {contrib/games => catalog/games/files}/g2.nfg | 0 {contrib/games => catalog/games/files}/g3.efg | 0 {contrib/games => catalog/games/files}/g3.nfg | 0 {contrib/games => catalog/games/files}/holdout.efg | 0 {contrib/games => catalog/games/files}/holdout7.efg | 0 {contrib/games => catalog/games/files}/hs1.efg | 0 {contrib/games => catalog/games/files}/jury_mr.efg | 0 {contrib/games => catalog/games/files}/jury_un.efg | 0 {contrib/games => catalog/games/files}/km1.efg | 0 {contrib/games => catalog/games/files}/km2.efg | 0 {contrib/games => catalog/games/files}/km3.efg | 0 {contrib/games => catalog/games/files}/km6.efg | 0 {contrib/games => catalog/games/files}/loopback.nfg | 0 {contrib/games => catalog/games/files}/mixdom.nfg | 0 {contrib/games => catalog/games/files}/mixdom2.nfg | 0 {contrib/games => catalog/games/files}/montyhal.efg | 0 {contrib/games => catalog/games/files}/my_2-1.efg | 0 {contrib/games => catalog/games/files}/my_2-4.efg | 0 {contrib/games => catalog/games/files}/my_2-8.efg | 0 {contrib/games => catalog/games/files}/my_3-3a.efg | 0 {contrib/games => catalog/games/files}/my_3-3b.efg | 0 {contrib/games => catalog/games/files}/my_3-3c.efg | 0 {contrib/games => catalog/games/files}/my_3-3d.efg | 0 {contrib/games => catalog/games/files}/my_3-3e.efg | 0 {contrib/games => catalog/games/files}/my_3-4.efg | 0 {contrib/games => catalog/games/files}/myerson.efg | 0 {contrib/games => catalog/games/files}/myerson_fig_4_2.efg | 0 {contrib/games => catalog/games/files}/nim.efg | 0 {contrib/games => catalog/games/files}/nim7.efg | 0 {contrib/games => catalog/games/files}/oneill.nfg | 0 {contrib/games => catalog/games/files}/palf.efg | 0 {contrib/games => catalog/games/files}/palf2.efg | 0 {contrib/games => catalog/games/files}/palf3.efg | 0 {contrib/games => catalog/games/files}/pbride.efg | 0 {contrib/games => catalog/games/files}/pd.nfg | 0 {contrib/games => catalog/games/files}/perfect1.nfg | 0 {contrib/games => catalog/games/files}/perfect2.nfg | 0 {contrib/games => catalog/games/files}/perfect3.nfg | 0 {contrib/games => catalog/games/files}/poker.efg | 0 {contrib/games => catalog/games/files}/poker.nfg | 0 {contrib/games => catalog/games/files}/poker2.efg | 0 {contrib/games => catalog/games/files}/pvw.efg | 0 {contrib/games => catalog/games/files}/pvw2.efg | 0 {contrib/games => catalog/games/files}/sh3.efg | 0 {contrib/games => catalog/games/files}/sh3.nfg | 0 {contrib/games => catalog/games/files}/spence.efg | 0 {contrib/games => catalog/games/files}/stengel.nfg | 0 {contrib/games => catalog/games/files}/sww1.efg | 0 {contrib/games => catalog/games/files}/sww1.nfg | 0 {contrib/games => catalog/games/files}/sww2.efg | 0 {contrib/games => catalog/games/files}/sww3.efg | 0 {contrib/games => catalog/games/files}/tim.efg | 0 {contrib/games => catalog/games/files}/todd1.nfg | 0 {contrib/games => catalog/games/files}/todd2.nfg | 0 {contrib/games => catalog/games/files}/todd3.nfg | 0 {contrib/games => catalog/games/files}/ttt.efg | 0 {contrib/games => catalog/games/files}/vd.efg | 0 {contrib/games => catalog/games/files}/vd.nfg | 0 {contrib/games => catalog/games/files}/w_ex1.efg | 0 {contrib/games => catalog/games/files}/w_ex2.efg | 0 {contrib/games => catalog/games/files}/wilson1.efg | 0 {contrib/games => catalog/games/files}/wink3.nfg | 0 {contrib/games => catalog/games/files}/winkels.nfg | 0 {contrib/games => catalog/games/files}/work1.efg | 0 {contrib/games => catalog/games/files}/work2.efg | 0 {contrib/games => catalog/games/files}/work3.efg | 0 {contrib/games => catalog/games/files}/yamamoto.nfg | 0 {contrib/games => catalog/games/files}/zero.nfg | 0 149 files changed, 0 insertions(+), 0 deletions(-) rename {contrib/games => catalog/games/files}/2s2x2x2.efg (100%) rename {contrib/games => catalog/games/files}/2smp.efg (100%) rename {contrib/games => catalog/games/files}/2x2.agg (100%) rename {contrib/games => catalog/games/files}/2x2.nfg (100%) rename {contrib/games => catalog/games/files}/2x2a.nfg (100%) rename {contrib/games => catalog/games/files}/2x2const.nfg (100%) rename {contrib/games => catalog/games/files}/2x2x2-nau.nfg (100%) rename {contrib/games => catalog/games/files}/2x2x2.efg (100%) rename {contrib/games => catalog/games/files}/2x2x2.nfg (100%) rename {contrib/games => catalog/games/files}/2x2x2x2.nfg (100%) rename {contrib/games => catalog/games/files}/2x2x2x2x2.nfg (100%) rename {contrib/games => catalog/games/files}/3x3x3.nfg (100%) rename {contrib/games => catalog/games/files}/4cards.efg (100%) rename {contrib/games => catalog/games/files}/5x4x3.nfg (100%) rename {contrib/games => catalog/games/files}/8x2x2.nfg (100%) rename {contrib/games => catalog/games/files}/8x8.nfg (100%) rename {contrib/games => catalog/games/files}/BSS_S_085.Weighted.agg (100%) rename {contrib/games => catalog/games/files}/Bayesian-Coffee-3-2-2-3.bagg (100%) rename {contrib/games => catalog/games/files}/GenRPS5.agg (100%) rename {contrib/games => catalog/games/files}/artist1.efg (100%) rename {contrib/games => catalog/games/files}/artist2.efg (100%) rename {contrib/games => catalog/games/files}/badgame1.efg (100%) rename {contrib/games => catalog/games/files}/badgame2.efg (100%) rename {contrib/games => catalog/games/files}/bagwell.efg (100%) rename {contrib/games => catalog/games/files}/bayes1a.efg (100%) rename {contrib/games => catalog/games/files}/bayes2a.efg (100%) rename {contrib/games => catalog/games/files}/bcp2.efg (100%) rename {contrib/games => catalog/games/files}/bcp3.efg (100%) rename {contrib/games => catalog/games/files}/bcp4.efg (100%) rename {contrib/games => catalog/games/files}/bhg1.efg (100%) rename {contrib/games => catalog/games/files}/bhg2.efg (100%) rename {contrib/games => catalog/games/files}/bhg3.efg (100%) rename {contrib/games => catalog/games/files}/bhg4.efg (100%) rename {contrib/games => catalog/games/files}/bhg5.efg (100%) rename {contrib/games => catalog/games/files}/caro2.efg (100%) rename {contrib/games => catalog/games/files}/cent2.efg (100%) rename {contrib/games => catalog/games/files}/cent2.nfg (100%) rename {contrib/games => catalog/games/files}/cent3.efg (100%) rename {contrib/games => catalog/games/files}/cent4.efg (100%) rename {contrib/games => catalog/games/files}/cent6.efg (100%) rename {contrib/games => catalog/games/files}/centcs10.efg (100%) rename {contrib/games => catalog/games/files}/centcs6.efg (100%) rename {contrib/games => catalog/games/files}/condjury.efg (100%) rename {contrib/games => catalog/games/files}/coord2.efg (100%) rename {contrib/games => catalog/games/files}/coord2.nfg (100%) rename {contrib/games => catalog/games/files}/coord2ts.efg (100%) rename {contrib/games => catalog/games/files}/coord3.efg (100%) rename {contrib/games => catalog/games/files}/coord3.nfg (100%) rename {contrib/games => catalog/games/files}/coord333.nfg (100%) rename {contrib/games => catalog/games/files}/coord4.efg (100%) rename {contrib/games => catalog/games/files}/coord4.nfg (100%) rename {contrib/games => catalog/games/files}/cross.efg (100%) rename {contrib/games => catalog/games/files}/cs.efg (100%) rename {contrib/games => catalog/games/files}/csg1.nfg (100%) rename {contrib/games => catalog/games/files}/csg2.nfg (100%) rename {contrib/games => catalog/games/files}/csg3.nfg (100%) rename {contrib/games => catalog/games/files}/csg4.nfg (100%) rename {contrib/games => catalog/games/files}/deg1.nfg (100%) rename {contrib/games => catalog/games/files}/deg2.nfg (100%) rename {contrib/games => catalog/games/files}/e01.efg (100%) rename {contrib/games => catalog/games/files}/e01.nfg (100%) rename {contrib/games => catalog/games/files}/e02.efg (100%) rename {contrib/games => catalog/games/files}/e02.nfg (100%) rename {contrib/games => catalog/games/files}/e03.efg (100%) rename {contrib/games => catalog/games/files}/e04.efg (100%) rename {contrib/games => catalog/games/files}/e04.nfg (100%) rename {contrib/games => catalog/games/files}/e05.efg (100%) rename {contrib/games => catalog/games/files}/e06.efg (100%) rename {contrib/games => catalog/games/files}/e07.efg (100%) rename {contrib/games => catalog/games/files}/e07.nfg (100%) rename {contrib/games => catalog/games/files}/e08.efg (100%) rename {contrib/games => catalog/games/files}/e09.efg (100%) rename {contrib/games => catalog/games/files}/e10.efg (100%) rename {contrib/games => catalog/games/files}/e10a.efg (100%) rename {contrib/games => catalog/games/files}/e13.efg (100%) rename {contrib/games => catalog/games/files}/e16.efg (100%) rename {contrib/games => catalog/games/files}/e17.efg (100%) rename {contrib/games => catalog/games/files}/e18.efg (100%) rename {contrib/games => catalog/games/files}/g1.efg (100%) rename {contrib/games => catalog/games/files}/g1.nfg (100%) rename {contrib/games => catalog/games/files}/g2.efg (100%) rename {contrib/games => catalog/games/files}/g2.nfg (100%) rename {contrib/games => catalog/games/files}/g3.efg (100%) rename {contrib/games => catalog/games/files}/g3.nfg (100%) rename {contrib/games => catalog/games/files}/holdout.efg (100%) rename {contrib/games => catalog/games/files}/holdout7.efg (100%) rename {contrib/games => catalog/games/files}/hs1.efg (100%) rename {contrib/games => catalog/games/files}/jury_mr.efg (100%) rename {contrib/games => catalog/games/files}/jury_un.efg (100%) rename {contrib/games => catalog/games/files}/km1.efg (100%) rename {contrib/games => catalog/games/files}/km2.efg (100%) rename {contrib/games => catalog/games/files}/km3.efg (100%) rename {contrib/games => catalog/games/files}/km6.efg (100%) rename {contrib/games => catalog/games/files}/loopback.nfg (100%) rename {contrib/games => catalog/games/files}/mixdom.nfg (100%) rename {contrib/games => catalog/games/files}/mixdom2.nfg (100%) rename {contrib/games => catalog/games/files}/montyhal.efg (100%) rename {contrib/games => catalog/games/files}/my_2-1.efg (100%) rename {contrib/games => catalog/games/files}/my_2-4.efg (100%) rename {contrib/games => catalog/games/files}/my_2-8.efg (100%) rename {contrib/games => catalog/games/files}/my_3-3a.efg (100%) rename {contrib/games => catalog/games/files}/my_3-3b.efg (100%) rename {contrib/games => catalog/games/files}/my_3-3c.efg (100%) rename {contrib/games => catalog/games/files}/my_3-3d.efg (100%) rename {contrib/games => catalog/games/files}/my_3-3e.efg (100%) rename {contrib/games => catalog/games/files}/my_3-4.efg (100%) rename {contrib/games => catalog/games/files}/myerson.efg (100%) rename {contrib/games => catalog/games/files}/myerson_fig_4_2.efg (100%) rename {contrib/games => catalog/games/files}/nim.efg (100%) rename {contrib/games => catalog/games/files}/nim7.efg (100%) rename {contrib/games => catalog/games/files}/oneill.nfg (100%) rename {contrib/games => catalog/games/files}/palf.efg (100%) rename {contrib/games => catalog/games/files}/palf2.efg (100%) rename {contrib/games => catalog/games/files}/palf3.efg (100%) rename {contrib/games => catalog/games/files}/pbride.efg (100%) rename {contrib/games => catalog/games/files}/pd.nfg (100%) rename {contrib/games => catalog/games/files}/perfect1.nfg (100%) rename {contrib/games => catalog/games/files}/perfect2.nfg (100%) rename {contrib/games => catalog/games/files}/perfect3.nfg (100%) rename {contrib/games => catalog/games/files}/poker.efg (100%) rename {contrib/games => catalog/games/files}/poker.nfg (100%) rename {contrib/games => catalog/games/files}/poker2.efg (100%) rename {contrib/games => catalog/games/files}/pvw.efg (100%) rename {contrib/games => catalog/games/files}/pvw2.efg (100%) rename {contrib/games => catalog/games/files}/sh3.efg (100%) rename {contrib/games => catalog/games/files}/sh3.nfg (100%) rename {contrib/games => catalog/games/files}/spence.efg (100%) rename {contrib/games => catalog/games/files}/stengel.nfg (100%) rename {contrib/games => catalog/games/files}/sww1.efg (100%) rename {contrib/games => catalog/games/files}/sww1.nfg (100%) rename {contrib/games => catalog/games/files}/sww2.efg (100%) rename {contrib/games => catalog/games/files}/sww3.efg (100%) rename {contrib/games => catalog/games/files}/tim.efg (100%) rename {contrib/games => catalog/games/files}/todd1.nfg (100%) rename {contrib/games => catalog/games/files}/todd2.nfg (100%) rename {contrib/games => catalog/games/files}/todd3.nfg (100%) rename {contrib/games => catalog/games/files}/ttt.efg (100%) rename {contrib/games => catalog/games/files}/vd.efg (100%) rename {contrib/games => catalog/games/files}/vd.nfg (100%) rename {contrib/games => catalog/games/files}/w_ex1.efg (100%) rename {contrib/games => catalog/games/files}/w_ex2.efg (100%) rename {contrib/games => catalog/games/files}/wilson1.efg (100%) rename {contrib/games => catalog/games/files}/wink3.nfg (100%) rename {contrib/games => catalog/games/files}/winkels.nfg (100%) rename {contrib/games => catalog/games/files}/work1.efg (100%) rename {contrib/games => catalog/games/files}/work2.efg (100%) rename {contrib/games => catalog/games/files}/work3.efg (100%) rename {contrib/games => catalog/games/files}/yamamoto.nfg (100%) rename {contrib/games => catalog/games/files}/zero.nfg (100%) diff --git a/contrib/games/2s2x2x2.efg b/catalog/games/files/2s2x2x2.efg similarity index 100% rename from contrib/games/2s2x2x2.efg rename to catalog/games/files/2s2x2x2.efg diff --git a/contrib/games/2smp.efg b/catalog/games/files/2smp.efg similarity index 100% rename from contrib/games/2smp.efg rename to catalog/games/files/2smp.efg diff --git a/contrib/games/2x2.agg b/catalog/games/files/2x2.agg similarity index 100% rename from contrib/games/2x2.agg rename to catalog/games/files/2x2.agg diff --git a/contrib/games/2x2.nfg b/catalog/games/files/2x2.nfg similarity index 100% rename from contrib/games/2x2.nfg rename to catalog/games/files/2x2.nfg diff --git a/contrib/games/2x2a.nfg b/catalog/games/files/2x2a.nfg similarity index 100% rename from contrib/games/2x2a.nfg rename to catalog/games/files/2x2a.nfg diff --git a/contrib/games/2x2const.nfg b/catalog/games/files/2x2const.nfg similarity index 100% rename from contrib/games/2x2const.nfg rename to catalog/games/files/2x2const.nfg diff --git a/contrib/games/2x2x2-nau.nfg b/catalog/games/files/2x2x2-nau.nfg similarity index 100% rename from contrib/games/2x2x2-nau.nfg rename to catalog/games/files/2x2x2-nau.nfg diff --git a/contrib/games/2x2x2.efg b/catalog/games/files/2x2x2.efg similarity index 100% rename from contrib/games/2x2x2.efg rename to catalog/games/files/2x2x2.efg diff --git a/contrib/games/2x2x2.nfg b/catalog/games/files/2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2.nfg rename to catalog/games/files/2x2x2.nfg diff --git a/contrib/games/2x2x2x2.nfg b/catalog/games/files/2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2.nfg rename to catalog/games/files/2x2x2x2.nfg diff --git a/contrib/games/2x2x2x2x2.nfg b/catalog/games/files/2x2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2x2.nfg rename to catalog/games/files/2x2x2x2x2.nfg diff --git a/contrib/games/3x3x3.nfg b/catalog/games/files/3x3x3.nfg similarity index 100% rename from contrib/games/3x3x3.nfg rename to catalog/games/files/3x3x3.nfg diff --git a/contrib/games/4cards.efg b/catalog/games/files/4cards.efg similarity index 100% rename from contrib/games/4cards.efg rename to catalog/games/files/4cards.efg diff --git a/contrib/games/5x4x3.nfg b/catalog/games/files/5x4x3.nfg similarity index 100% rename from contrib/games/5x4x3.nfg rename to catalog/games/files/5x4x3.nfg diff --git a/contrib/games/8x2x2.nfg b/catalog/games/files/8x2x2.nfg similarity index 100% rename from contrib/games/8x2x2.nfg rename to catalog/games/files/8x2x2.nfg diff --git a/contrib/games/8x8.nfg b/catalog/games/files/8x8.nfg similarity index 100% rename from contrib/games/8x8.nfg rename to catalog/games/files/8x8.nfg diff --git a/contrib/games/BSS_S_085.Weighted.agg b/catalog/games/files/BSS_S_085.Weighted.agg similarity index 100% rename from contrib/games/BSS_S_085.Weighted.agg rename to catalog/games/files/BSS_S_085.Weighted.agg diff --git a/contrib/games/Bayesian-Coffee-3-2-2-3.bagg b/catalog/games/files/Bayesian-Coffee-3-2-2-3.bagg similarity index 100% rename from contrib/games/Bayesian-Coffee-3-2-2-3.bagg rename to catalog/games/files/Bayesian-Coffee-3-2-2-3.bagg diff --git a/contrib/games/GenRPS5.agg b/catalog/games/files/GenRPS5.agg similarity index 100% rename from contrib/games/GenRPS5.agg rename to catalog/games/files/GenRPS5.agg diff --git a/contrib/games/artist1.efg b/catalog/games/files/artist1.efg similarity index 100% rename from contrib/games/artist1.efg rename to catalog/games/files/artist1.efg diff --git a/contrib/games/artist2.efg b/catalog/games/files/artist2.efg similarity index 100% rename from contrib/games/artist2.efg rename to catalog/games/files/artist2.efg diff --git a/contrib/games/badgame1.efg b/catalog/games/files/badgame1.efg similarity index 100% rename from contrib/games/badgame1.efg rename to catalog/games/files/badgame1.efg diff --git a/contrib/games/badgame2.efg b/catalog/games/files/badgame2.efg similarity index 100% rename from contrib/games/badgame2.efg rename to catalog/games/files/badgame2.efg diff --git a/contrib/games/bagwell.efg b/catalog/games/files/bagwell.efg similarity index 100% rename from contrib/games/bagwell.efg rename to catalog/games/files/bagwell.efg diff --git a/contrib/games/bayes1a.efg b/catalog/games/files/bayes1a.efg similarity index 100% rename from contrib/games/bayes1a.efg rename to catalog/games/files/bayes1a.efg diff --git a/contrib/games/bayes2a.efg b/catalog/games/files/bayes2a.efg similarity index 100% rename from contrib/games/bayes2a.efg rename to catalog/games/files/bayes2a.efg diff --git a/contrib/games/bcp2.efg b/catalog/games/files/bcp2.efg similarity index 100% rename from contrib/games/bcp2.efg rename to catalog/games/files/bcp2.efg diff --git a/contrib/games/bcp3.efg b/catalog/games/files/bcp3.efg similarity index 100% rename from contrib/games/bcp3.efg rename to catalog/games/files/bcp3.efg diff --git a/contrib/games/bcp4.efg b/catalog/games/files/bcp4.efg similarity index 100% rename from contrib/games/bcp4.efg rename to catalog/games/files/bcp4.efg diff --git a/contrib/games/bhg1.efg b/catalog/games/files/bhg1.efg similarity index 100% rename from contrib/games/bhg1.efg rename to catalog/games/files/bhg1.efg diff --git a/contrib/games/bhg2.efg b/catalog/games/files/bhg2.efg similarity index 100% rename from contrib/games/bhg2.efg rename to catalog/games/files/bhg2.efg diff --git a/contrib/games/bhg3.efg b/catalog/games/files/bhg3.efg similarity index 100% rename from contrib/games/bhg3.efg rename to catalog/games/files/bhg3.efg diff --git a/contrib/games/bhg4.efg b/catalog/games/files/bhg4.efg similarity index 100% rename from contrib/games/bhg4.efg rename to catalog/games/files/bhg4.efg diff --git a/contrib/games/bhg5.efg b/catalog/games/files/bhg5.efg similarity index 100% rename from contrib/games/bhg5.efg rename to catalog/games/files/bhg5.efg diff --git a/contrib/games/caro2.efg b/catalog/games/files/caro2.efg similarity index 100% rename from contrib/games/caro2.efg rename to catalog/games/files/caro2.efg diff --git a/contrib/games/cent2.efg b/catalog/games/files/cent2.efg similarity index 100% rename from contrib/games/cent2.efg rename to catalog/games/files/cent2.efg diff --git a/contrib/games/cent2.nfg b/catalog/games/files/cent2.nfg similarity index 100% rename from contrib/games/cent2.nfg rename to catalog/games/files/cent2.nfg diff --git a/contrib/games/cent3.efg b/catalog/games/files/cent3.efg similarity index 100% rename from contrib/games/cent3.efg rename to catalog/games/files/cent3.efg diff --git a/contrib/games/cent4.efg b/catalog/games/files/cent4.efg similarity index 100% rename from contrib/games/cent4.efg rename to catalog/games/files/cent4.efg diff --git a/contrib/games/cent6.efg b/catalog/games/files/cent6.efg similarity index 100% rename from contrib/games/cent6.efg rename to catalog/games/files/cent6.efg diff --git a/contrib/games/centcs10.efg b/catalog/games/files/centcs10.efg similarity index 100% rename from contrib/games/centcs10.efg rename to catalog/games/files/centcs10.efg diff --git a/contrib/games/centcs6.efg b/catalog/games/files/centcs6.efg similarity index 100% rename from contrib/games/centcs6.efg rename to catalog/games/files/centcs6.efg diff --git a/contrib/games/condjury.efg b/catalog/games/files/condjury.efg similarity index 100% rename from contrib/games/condjury.efg rename to catalog/games/files/condjury.efg diff --git a/contrib/games/coord2.efg b/catalog/games/files/coord2.efg similarity index 100% rename from contrib/games/coord2.efg rename to catalog/games/files/coord2.efg diff --git a/contrib/games/coord2.nfg b/catalog/games/files/coord2.nfg similarity index 100% rename from contrib/games/coord2.nfg rename to catalog/games/files/coord2.nfg diff --git a/contrib/games/coord2ts.efg b/catalog/games/files/coord2ts.efg similarity index 100% rename from contrib/games/coord2ts.efg rename to catalog/games/files/coord2ts.efg diff --git a/contrib/games/coord3.efg b/catalog/games/files/coord3.efg similarity index 100% rename from contrib/games/coord3.efg rename to catalog/games/files/coord3.efg diff --git a/contrib/games/coord3.nfg b/catalog/games/files/coord3.nfg similarity index 100% rename from contrib/games/coord3.nfg rename to catalog/games/files/coord3.nfg diff --git a/contrib/games/coord333.nfg b/catalog/games/files/coord333.nfg similarity index 100% rename from contrib/games/coord333.nfg rename to catalog/games/files/coord333.nfg diff --git a/contrib/games/coord4.efg b/catalog/games/files/coord4.efg similarity index 100% rename from contrib/games/coord4.efg rename to catalog/games/files/coord4.efg diff --git a/contrib/games/coord4.nfg b/catalog/games/files/coord4.nfg similarity index 100% rename from contrib/games/coord4.nfg rename to catalog/games/files/coord4.nfg diff --git a/contrib/games/cross.efg b/catalog/games/files/cross.efg similarity index 100% rename from contrib/games/cross.efg rename to catalog/games/files/cross.efg diff --git a/contrib/games/cs.efg b/catalog/games/files/cs.efg similarity index 100% rename from contrib/games/cs.efg rename to catalog/games/files/cs.efg diff --git a/contrib/games/csg1.nfg b/catalog/games/files/csg1.nfg similarity index 100% rename from contrib/games/csg1.nfg rename to catalog/games/files/csg1.nfg diff --git a/contrib/games/csg2.nfg b/catalog/games/files/csg2.nfg similarity index 100% rename from contrib/games/csg2.nfg rename to catalog/games/files/csg2.nfg diff --git a/contrib/games/csg3.nfg b/catalog/games/files/csg3.nfg similarity index 100% rename from contrib/games/csg3.nfg rename to catalog/games/files/csg3.nfg diff --git a/contrib/games/csg4.nfg b/catalog/games/files/csg4.nfg similarity index 100% rename from contrib/games/csg4.nfg rename to catalog/games/files/csg4.nfg diff --git a/contrib/games/deg1.nfg b/catalog/games/files/deg1.nfg similarity index 100% rename from contrib/games/deg1.nfg rename to catalog/games/files/deg1.nfg diff --git a/contrib/games/deg2.nfg b/catalog/games/files/deg2.nfg similarity index 100% rename from contrib/games/deg2.nfg rename to catalog/games/files/deg2.nfg diff --git a/contrib/games/e01.efg b/catalog/games/files/e01.efg similarity index 100% rename from contrib/games/e01.efg rename to catalog/games/files/e01.efg diff --git a/contrib/games/e01.nfg b/catalog/games/files/e01.nfg similarity index 100% rename from contrib/games/e01.nfg rename to catalog/games/files/e01.nfg diff --git a/contrib/games/e02.efg b/catalog/games/files/e02.efg similarity index 100% rename from contrib/games/e02.efg rename to catalog/games/files/e02.efg diff --git a/contrib/games/e02.nfg b/catalog/games/files/e02.nfg similarity index 100% rename from contrib/games/e02.nfg rename to catalog/games/files/e02.nfg diff --git a/contrib/games/e03.efg b/catalog/games/files/e03.efg similarity index 100% rename from contrib/games/e03.efg rename to catalog/games/files/e03.efg diff --git a/contrib/games/e04.efg b/catalog/games/files/e04.efg similarity index 100% rename from contrib/games/e04.efg rename to catalog/games/files/e04.efg diff --git a/contrib/games/e04.nfg b/catalog/games/files/e04.nfg similarity index 100% rename from contrib/games/e04.nfg rename to catalog/games/files/e04.nfg diff --git a/contrib/games/e05.efg b/catalog/games/files/e05.efg similarity index 100% rename from contrib/games/e05.efg rename to catalog/games/files/e05.efg diff --git a/contrib/games/e06.efg b/catalog/games/files/e06.efg similarity index 100% rename from contrib/games/e06.efg rename to catalog/games/files/e06.efg diff --git a/contrib/games/e07.efg b/catalog/games/files/e07.efg similarity index 100% rename from contrib/games/e07.efg rename to catalog/games/files/e07.efg diff --git a/contrib/games/e07.nfg b/catalog/games/files/e07.nfg similarity index 100% rename from contrib/games/e07.nfg rename to catalog/games/files/e07.nfg diff --git a/contrib/games/e08.efg b/catalog/games/files/e08.efg similarity index 100% rename from contrib/games/e08.efg rename to catalog/games/files/e08.efg diff --git a/contrib/games/e09.efg b/catalog/games/files/e09.efg similarity index 100% rename from contrib/games/e09.efg rename to catalog/games/files/e09.efg diff --git a/contrib/games/e10.efg b/catalog/games/files/e10.efg similarity index 100% rename from contrib/games/e10.efg rename to catalog/games/files/e10.efg diff --git a/contrib/games/e10a.efg b/catalog/games/files/e10a.efg similarity index 100% rename from contrib/games/e10a.efg rename to catalog/games/files/e10a.efg diff --git a/contrib/games/e13.efg b/catalog/games/files/e13.efg similarity index 100% rename from contrib/games/e13.efg rename to catalog/games/files/e13.efg diff --git a/contrib/games/e16.efg b/catalog/games/files/e16.efg similarity index 100% rename from contrib/games/e16.efg rename to catalog/games/files/e16.efg diff --git a/contrib/games/e17.efg b/catalog/games/files/e17.efg similarity index 100% rename from contrib/games/e17.efg rename to catalog/games/files/e17.efg diff --git a/contrib/games/e18.efg b/catalog/games/files/e18.efg similarity index 100% rename from contrib/games/e18.efg rename to catalog/games/files/e18.efg diff --git a/contrib/games/g1.efg b/catalog/games/files/g1.efg similarity index 100% rename from contrib/games/g1.efg rename to catalog/games/files/g1.efg diff --git a/contrib/games/g1.nfg b/catalog/games/files/g1.nfg similarity index 100% rename from contrib/games/g1.nfg rename to catalog/games/files/g1.nfg diff --git a/contrib/games/g2.efg b/catalog/games/files/g2.efg similarity index 100% rename from contrib/games/g2.efg rename to catalog/games/files/g2.efg diff --git a/contrib/games/g2.nfg b/catalog/games/files/g2.nfg similarity index 100% rename from contrib/games/g2.nfg rename to catalog/games/files/g2.nfg diff --git a/contrib/games/g3.efg b/catalog/games/files/g3.efg similarity index 100% rename from contrib/games/g3.efg rename to catalog/games/files/g3.efg diff --git a/contrib/games/g3.nfg b/catalog/games/files/g3.nfg similarity index 100% rename from contrib/games/g3.nfg rename to catalog/games/files/g3.nfg diff --git a/contrib/games/holdout.efg b/catalog/games/files/holdout.efg similarity index 100% rename from contrib/games/holdout.efg rename to catalog/games/files/holdout.efg diff --git a/contrib/games/holdout7.efg b/catalog/games/files/holdout7.efg similarity index 100% rename from contrib/games/holdout7.efg rename to catalog/games/files/holdout7.efg diff --git a/contrib/games/hs1.efg b/catalog/games/files/hs1.efg similarity index 100% rename from contrib/games/hs1.efg rename to catalog/games/files/hs1.efg diff --git a/contrib/games/jury_mr.efg b/catalog/games/files/jury_mr.efg similarity index 100% rename from contrib/games/jury_mr.efg rename to catalog/games/files/jury_mr.efg diff --git a/contrib/games/jury_un.efg b/catalog/games/files/jury_un.efg similarity index 100% rename from contrib/games/jury_un.efg rename to catalog/games/files/jury_un.efg diff --git a/contrib/games/km1.efg b/catalog/games/files/km1.efg similarity index 100% rename from contrib/games/km1.efg rename to catalog/games/files/km1.efg diff --git a/contrib/games/km2.efg b/catalog/games/files/km2.efg similarity index 100% rename from contrib/games/km2.efg rename to catalog/games/files/km2.efg diff --git a/contrib/games/km3.efg b/catalog/games/files/km3.efg similarity index 100% rename from contrib/games/km3.efg rename to catalog/games/files/km3.efg diff --git a/contrib/games/km6.efg b/catalog/games/files/km6.efg similarity index 100% rename from contrib/games/km6.efg rename to catalog/games/files/km6.efg diff --git a/contrib/games/loopback.nfg b/catalog/games/files/loopback.nfg similarity index 100% rename from contrib/games/loopback.nfg rename to catalog/games/files/loopback.nfg diff --git a/contrib/games/mixdom.nfg b/catalog/games/files/mixdom.nfg similarity index 100% rename from contrib/games/mixdom.nfg rename to catalog/games/files/mixdom.nfg diff --git a/contrib/games/mixdom2.nfg b/catalog/games/files/mixdom2.nfg similarity index 100% rename from contrib/games/mixdom2.nfg rename to catalog/games/files/mixdom2.nfg diff --git a/contrib/games/montyhal.efg b/catalog/games/files/montyhal.efg similarity index 100% rename from contrib/games/montyhal.efg rename to catalog/games/files/montyhal.efg diff --git a/contrib/games/my_2-1.efg b/catalog/games/files/my_2-1.efg similarity index 100% rename from contrib/games/my_2-1.efg rename to catalog/games/files/my_2-1.efg diff --git a/contrib/games/my_2-4.efg b/catalog/games/files/my_2-4.efg similarity index 100% rename from contrib/games/my_2-4.efg rename to catalog/games/files/my_2-4.efg diff --git a/contrib/games/my_2-8.efg b/catalog/games/files/my_2-8.efg similarity index 100% rename from contrib/games/my_2-8.efg rename to catalog/games/files/my_2-8.efg diff --git a/contrib/games/my_3-3a.efg b/catalog/games/files/my_3-3a.efg similarity index 100% rename from contrib/games/my_3-3a.efg rename to catalog/games/files/my_3-3a.efg diff --git a/contrib/games/my_3-3b.efg b/catalog/games/files/my_3-3b.efg similarity index 100% rename from contrib/games/my_3-3b.efg rename to catalog/games/files/my_3-3b.efg diff --git a/contrib/games/my_3-3c.efg b/catalog/games/files/my_3-3c.efg similarity index 100% rename from contrib/games/my_3-3c.efg rename to catalog/games/files/my_3-3c.efg diff --git a/contrib/games/my_3-3d.efg b/catalog/games/files/my_3-3d.efg similarity index 100% rename from contrib/games/my_3-3d.efg rename to catalog/games/files/my_3-3d.efg diff --git a/contrib/games/my_3-3e.efg b/catalog/games/files/my_3-3e.efg similarity index 100% rename from contrib/games/my_3-3e.efg rename to catalog/games/files/my_3-3e.efg diff --git a/contrib/games/my_3-4.efg b/catalog/games/files/my_3-4.efg similarity index 100% rename from contrib/games/my_3-4.efg rename to catalog/games/files/my_3-4.efg diff --git a/contrib/games/myerson.efg b/catalog/games/files/myerson.efg similarity index 100% rename from contrib/games/myerson.efg rename to catalog/games/files/myerson.efg diff --git a/contrib/games/myerson_fig_4_2.efg b/catalog/games/files/myerson_fig_4_2.efg similarity index 100% rename from contrib/games/myerson_fig_4_2.efg rename to catalog/games/files/myerson_fig_4_2.efg diff --git a/contrib/games/nim.efg b/catalog/games/files/nim.efg similarity index 100% rename from contrib/games/nim.efg rename to catalog/games/files/nim.efg diff --git a/contrib/games/nim7.efg b/catalog/games/files/nim7.efg similarity index 100% rename from contrib/games/nim7.efg rename to catalog/games/files/nim7.efg diff --git a/contrib/games/oneill.nfg b/catalog/games/files/oneill.nfg similarity index 100% rename from contrib/games/oneill.nfg rename to catalog/games/files/oneill.nfg diff --git a/contrib/games/palf.efg b/catalog/games/files/palf.efg similarity index 100% rename from contrib/games/palf.efg rename to catalog/games/files/palf.efg diff --git a/contrib/games/palf2.efg b/catalog/games/files/palf2.efg similarity index 100% rename from contrib/games/palf2.efg rename to catalog/games/files/palf2.efg diff --git a/contrib/games/palf3.efg b/catalog/games/files/palf3.efg similarity index 100% rename from contrib/games/palf3.efg rename to catalog/games/files/palf3.efg diff --git a/contrib/games/pbride.efg b/catalog/games/files/pbride.efg similarity index 100% rename from contrib/games/pbride.efg rename to catalog/games/files/pbride.efg diff --git a/contrib/games/pd.nfg b/catalog/games/files/pd.nfg similarity index 100% rename from contrib/games/pd.nfg rename to catalog/games/files/pd.nfg diff --git a/contrib/games/perfect1.nfg b/catalog/games/files/perfect1.nfg similarity index 100% rename from contrib/games/perfect1.nfg rename to catalog/games/files/perfect1.nfg diff --git a/contrib/games/perfect2.nfg b/catalog/games/files/perfect2.nfg similarity index 100% rename from contrib/games/perfect2.nfg rename to catalog/games/files/perfect2.nfg diff --git a/contrib/games/perfect3.nfg b/catalog/games/files/perfect3.nfg similarity index 100% rename from contrib/games/perfect3.nfg rename to catalog/games/files/perfect3.nfg diff --git a/contrib/games/poker.efg b/catalog/games/files/poker.efg similarity index 100% rename from contrib/games/poker.efg rename to catalog/games/files/poker.efg diff --git a/contrib/games/poker.nfg b/catalog/games/files/poker.nfg similarity index 100% rename from contrib/games/poker.nfg rename to catalog/games/files/poker.nfg diff --git a/contrib/games/poker2.efg b/catalog/games/files/poker2.efg similarity index 100% rename from contrib/games/poker2.efg rename to catalog/games/files/poker2.efg diff --git a/contrib/games/pvw.efg b/catalog/games/files/pvw.efg similarity index 100% rename from contrib/games/pvw.efg rename to catalog/games/files/pvw.efg diff --git a/contrib/games/pvw2.efg b/catalog/games/files/pvw2.efg similarity index 100% rename from contrib/games/pvw2.efg rename to catalog/games/files/pvw2.efg diff --git a/contrib/games/sh3.efg b/catalog/games/files/sh3.efg similarity index 100% rename from contrib/games/sh3.efg rename to catalog/games/files/sh3.efg diff --git a/contrib/games/sh3.nfg b/catalog/games/files/sh3.nfg similarity index 100% rename from contrib/games/sh3.nfg rename to catalog/games/files/sh3.nfg diff --git a/contrib/games/spence.efg b/catalog/games/files/spence.efg similarity index 100% rename from contrib/games/spence.efg rename to catalog/games/files/spence.efg diff --git a/contrib/games/stengel.nfg b/catalog/games/files/stengel.nfg similarity index 100% rename from contrib/games/stengel.nfg rename to catalog/games/files/stengel.nfg diff --git a/contrib/games/sww1.efg b/catalog/games/files/sww1.efg similarity index 100% rename from contrib/games/sww1.efg rename to catalog/games/files/sww1.efg diff --git a/contrib/games/sww1.nfg b/catalog/games/files/sww1.nfg similarity index 100% rename from contrib/games/sww1.nfg rename to catalog/games/files/sww1.nfg diff --git a/contrib/games/sww2.efg b/catalog/games/files/sww2.efg similarity index 100% rename from contrib/games/sww2.efg rename to catalog/games/files/sww2.efg diff --git a/contrib/games/sww3.efg b/catalog/games/files/sww3.efg similarity index 100% rename from contrib/games/sww3.efg rename to catalog/games/files/sww3.efg diff --git a/contrib/games/tim.efg b/catalog/games/files/tim.efg similarity index 100% rename from contrib/games/tim.efg rename to catalog/games/files/tim.efg diff --git a/contrib/games/todd1.nfg b/catalog/games/files/todd1.nfg similarity index 100% rename from contrib/games/todd1.nfg rename to catalog/games/files/todd1.nfg diff --git a/contrib/games/todd2.nfg b/catalog/games/files/todd2.nfg similarity index 100% rename from contrib/games/todd2.nfg rename to catalog/games/files/todd2.nfg diff --git a/contrib/games/todd3.nfg b/catalog/games/files/todd3.nfg similarity index 100% rename from contrib/games/todd3.nfg rename to catalog/games/files/todd3.nfg diff --git a/contrib/games/ttt.efg b/catalog/games/files/ttt.efg similarity index 100% rename from contrib/games/ttt.efg rename to catalog/games/files/ttt.efg diff --git a/contrib/games/vd.efg b/catalog/games/files/vd.efg similarity index 100% rename from contrib/games/vd.efg rename to catalog/games/files/vd.efg diff --git a/contrib/games/vd.nfg b/catalog/games/files/vd.nfg similarity index 100% rename from contrib/games/vd.nfg rename to catalog/games/files/vd.nfg diff --git a/contrib/games/w_ex1.efg b/catalog/games/files/w_ex1.efg similarity index 100% rename from contrib/games/w_ex1.efg rename to catalog/games/files/w_ex1.efg diff --git a/contrib/games/w_ex2.efg b/catalog/games/files/w_ex2.efg similarity index 100% rename from contrib/games/w_ex2.efg rename to catalog/games/files/w_ex2.efg diff --git a/contrib/games/wilson1.efg b/catalog/games/files/wilson1.efg similarity index 100% rename from contrib/games/wilson1.efg rename to catalog/games/files/wilson1.efg diff --git a/contrib/games/wink3.nfg b/catalog/games/files/wink3.nfg similarity index 100% rename from contrib/games/wink3.nfg rename to catalog/games/files/wink3.nfg diff --git a/contrib/games/winkels.nfg b/catalog/games/files/winkels.nfg similarity index 100% rename from contrib/games/winkels.nfg rename to catalog/games/files/winkels.nfg diff --git a/contrib/games/work1.efg b/catalog/games/files/work1.efg similarity index 100% rename from contrib/games/work1.efg rename to catalog/games/files/work1.efg diff --git a/contrib/games/work2.efg b/catalog/games/files/work2.efg similarity index 100% rename from contrib/games/work2.efg rename to catalog/games/files/work2.efg diff --git a/contrib/games/work3.efg b/catalog/games/files/work3.efg similarity index 100% rename from contrib/games/work3.efg rename to catalog/games/files/work3.efg diff --git a/contrib/games/yamamoto.nfg b/catalog/games/files/yamamoto.nfg similarity index 100% rename from contrib/games/yamamoto.nfg rename to catalog/games/files/yamamoto.nfg diff --git a/contrib/games/zero.nfg b/catalog/games/files/zero.nfg similarity index 100% rename from contrib/games/zero.nfg rename to catalog/games/files/zero.nfg From 4a66d92c1adb38e721e9cae6723ae17133277a38 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 13:51:34 +0000 Subject: [PATCH 005/170] rename gamefiles dir --- catalog/catalog.py | 0 catalog/{games/files => gamefiles}/2s2x2x2.efg | 0 catalog/{games/files => gamefiles}/2smp.efg | 0 catalog/{games/files => gamefiles}/2x2.agg | 0 catalog/{games/files => gamefiles}/2x2.nfg | 0 catalog/{games/files => gamefiles}/2x2a.nfg | 0 catalog/{games/files => gamefiles}/2x2const.nfg | 0 catalog/{games/files => gamefiles}/2x2x2-nau.nfg | 0 catalog/{games/files => gamefiles}/2x2x2.efg | 0 catalog/{games/files => gamefiles}/2x2x2.nfg | 0 catalog/{games/files => gamefiles}/2x2x2x2.nfg | 0 catalog/{games/files => gamefiles}/2x2x2x2x2.nfg | 0 catalog/{games/files => gamefiles}/3x3x3.nfg | 0 catalog/{games/files => gamefiles}/4cards.efg | 0 catalog/{games/files => gamefiles}/5x4x3.nfg | 0 catalog/{games/files => gamefiles}/8x2x2.nfg | 0 catalog/{games/files => gamefiles}/8x8.nfg | 0 catalog/{games/files => gamefiles}/BSS_S_085.Weighted.agg | 0 catalog/{games/files => gamefiles}/Bayesian-Coffee-3-2-2-3.bagg | 0 catalog/{games/files => gamefiles}/GenRPS5.agg | 0 catalog/{games/files => gamefiles}/artist1.efg | 0 catalog/{games/files => gamefiles}/artist2.efg | 0 catalog/{games/files => gamefiles}/badgame1.efg | 0 catalog/{games/files => gamefiles}/badgame2.efg | 0 catalog/{games/files => gamefiles}/bagwell.efg | 0 catalog/{games/files => gamefiles}/bayes1a.efg | 0 catalog/{games/files => gamefiles}/bayes2a.efg | 0 catalog/{games/files => gamefiles}/bcp2.efg | 0 catalog/{games/files => gamefiles}/bcp3.efg | 0 catalog/{games/files => gamefiles}/bcp4.efg | 0 catalog/{games/files => gamefiles}/bhg1.efg | 0 catalog/{games/files => gamefiles}/bhg2.efg | 0 catalog/{games/files => gamefiles}/bhg3.efg | 0 catalog/{games/files => gamefiles}/bhg4.efg | 0 catalog/{games/files => gamefiles}/bhg5.efg | 0 catalog/{games/files => gamefiles}/caro2.efg | 0 catalog/{games/files => gamefiles}/cent2.efg | 0 catalog/{games/files => gamefiles}/cent2.nfg | 0 catalog/{games/files => gamefiles}/cent3.efg | 0 catalog/{games/files => gamefiles}/cent4.efg | 0 catalog/{games/files => gamefiles}/cent6.efg | 0 catalog/{games/files => gamefiles}/centcs10.efg | 0 catalog/{games/files => gamefiles}/centcs6.efg | 0 catalog/{games/files => gamefiles}/condjury.efg | 0 catalog/{games/files => gamefiles}/coord2.efg | 0 catalog/{games/files => gamefiles}/coord2.nfg | 0 catalog/{games/files => gamefiles}/coord2ts.efg | 0 catalog/{games/files => gamefiles}/coord3.efg | 0 catalog/{games/files => gamefiles}/coord3.nfg | 0 catalog/{games/files => gamefiles}/coord333.nfg | 0 catalog/{games/files => gamefiles}/coord4.efg | 0 catalog/{games/files => gamefiles}/coord4.nfg | 0 catalog/{games/files => gamefiles}/cross.efg | 0 catalog/{games/files => gamefiles}/cs.efg | 0 catalog/{games/files => gamefiles}/csg1.nfg | 0 catalog/{games/files => gamefiles}/csg2.nfg | 0 catalog/{games/files => gamefiles}/csg3.nfg | 0 catalog/{games/files => gamefiles}/csg4.nfg | 0 catalog/{games/files => gamefiles}/deg1.nfg | 0 catalog/{games/files => gamefiles}/deg2.nfg | 0 catalog/{games/files => gamefiles}/e01.efg | 0 catalog/{games/files => gamefiles}/e01.nfg | 0 catalog/{games/files => gamefiles}/e02.efg | 0 catalog/{games/files => gamefiles}/e02.nfg | 0 catalog/{games/files => gamefiles}/e03.efg | 0 catalog/{games/files => gamefiles}/e04.efg | 0 catalog/{games/files => gamefiles}/e04.nfg | 0 catalog/{games/files => gamefiles}/e05.efg | 0 catalog/{games/files => gamefiles}/e06.efg | 0 catalog/{games/files => gamefiles}/e07.efg | 0 catalog/{games/files => gamefiles}/e07.nfg | 0 catalog/{games/files => gamefiles}/e08.efg | 0 catalog/{games/files => gamefiles}/e09.efg | 0 catalog/{games/files => gamefiles}/e10.efg | 0 catalog/{games/files => gamefiles}/e10a.efg | 0 catalog/{games/files => gamefiles}/e13.efg | 0 catalog/{games/files => gamefiles}/e16.efg | 0 catalog/{games/files => gamefiles}/e17.efg | 0 catalog/{games/files => gamefiles}/e18.efg | 0 catalog/{games/files => gamefiles}/g1.efg | 0 catalog/{games/files => gamefiles}/g1.nfg | 0 catalog/{games/files => gamefiles}/g2.efg | 0 catalog/{games/files => gamefiles}/g2.nfg | 0 catalog/{games/files => gamefiles}/g3.efg | 0 catalog/{games/files => gamefiles}/g3.nfg | 0 catalog/{games/files => gamefiles}/holdout.efg | 0 catalog/{games/files => gamefiles}/holdout7.efg | 0 catalog/{games/files => gamefiles}/hs1.efg | 0 catalog/{games/files => gamefiles}/jury_mr.efg | 0 catalog/{games/files => gamefiles}/jury_un.efg | 0 catalog/{games/files => gamefiles}/km1.efg | 0 catalog/{games/files => gamefiles}/km2.efg | 0 catalog/{games/files => gamefiles}/km3.efg | 0 catalog/{games/files => gamefiles}/km6.efg | 0 catalog/{games/files => gamefiles}/loopback.nfg | 0 catalog/{games/files => gamefiles}/mixdom.nfg | 0 catalog/{games/files => gamefiles}/mixdom2.nfg | 0 catalog/{games/files => gamefiles}/montyhal.efg | 0 catalog/{games/files => gamefiles}/my_2-1.efg | 0 catalog/{games/files => gamefiles}/my_2-4.efg | 0 catalog/{games/files => gamefiles}/my_2-8.efg | 0 catalog/{games/files => gamefiles}/my_3-3a.efg | 0 catalog/{games/files => gamefiles}/my_3-3b.efg | 0 catalog/{games/files => gamefiles}/my_3-3c.efg | 0 catalog/{games/files => gamefiles}/my_3-3d.efg | 0 catalog/{games/files => gamefiles}/my_3-3e.efg | 0 catalog/{games/files => gamefiles}/my_3-4.efg | 0 catalog/{games/files => gamefiles}/myerson.efg | 0 catalog/{games/files => gamefiles}/myerson_fig_4_2.efg | 0 catalog/{games/files => gamefiles}/nim.efg | 0 catalog/{games/files => gamefiles}/nim7.efg | 0 catalog/{games/files => gamefiles}/oneill.nfg | 0 catalog/{games/files => gamefiles}/palf.efg | 0 catalog/{games/files => gamefiles}/palf2.efg | 0 catalog/{games/files => gamefiles}/palf3.efg | 0 catalog/{games/files => gamefiles}/pbride.efg | 0 catalog/{games/files => gamefiles}/pd.nfg | 0 catalog/{games/files => gamefiles}/perfect1.nfg | 0 catalog/{games/files => gamefiles}/perfect2.nfg | 0 catalog/{games/files => gamefiles}/perfect3.nfg | 0 catalog/{games/files => gamefiles}/poker.efg | 0 catalog/{games/files => gamefiles}/poker.nfg | 0 catalog/{games/files => gamefiles}/poker2.efg | 0 catalog/{games/files => gamefiles}/pvw.efg | 0 catalog/{games/files => gamefiles}/pvw2.efg | 0 catalog/{games/files => gamefiles}/sh3.efg | 0 catalog/{games/files => gamefiles}/sh3.nfg | 0 catalog/{games/files => gamefiles}/spence.efg | 0 catalog/{games/files => gamefiles}/stengel.nfg | 0 catalog/{games/files => gamefiles}/sww1.efg | 0 catalog/{games/files => gamefiles}/sww1.nfg | 0 catalog/{games/files => gamefiles}/sww2.efg | 0 catalog/{games/files => gamefiles}/sww3.efg | 0 catalog/{games/files => gamefiles}/tim.efg | 0 catalog/{games/files => gamefiles}/todd1.nfg | 0 catalog/{games/files => gamefiles}/todd2.nfg | 0 catalog/{games/files => gamefiles}/todd3.nfg | 0 catalog/{games/files => gamefiles}/ttt.efg | 0 catalog/{games/files => gamefiles}/vd.efg | 0 catalog/{games/files => gamefiles}/vd.nfg | 0 catalog/{games/files => gamefiles}/w_ex1.efg | 0 catalog/{games/files => gamefiles}/w_ex2.efg | 0 catalog/{games/files => gamefiles}/wilson1.efg | 0 catalog/{games/files => gamefiles}/wink3.nfg | 0 catalog/{games/files => gamefiles}/winkels.nfg | 0 catalog/{games/files => gamefiles}/work1.efg | 0 catalog/{games/files => gamefiles}/work2.efg | 0 catalog/{games/files => gamefiles}/work3.efg | 0 catalog/{games/files => gamefiles}/yamamoto.nfg | 0 catalog/{games/files => gamefiles}/zero.nfg | 0 150 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 catalog/catalog.py rename catalog/{games/files => gamefiles}/2s2x2x2.efg (100%) rename catalog/{games/files => gamefiles}/2smp.efg (100%) rename catalog/{games/files => gamefiles}/2x2.agg (100%) rename catalog/{games/files => gamefiles}/2x2.nfg (100%) rename catalog/{games/files => gamefiles}/2x2a.nfg (100%) rename catalog/{games/files => gamefiles}/2x2const.nfg (100%) rename catalog/{games/files => gamefiles}/2x2x2-nau.nfg (100%) rename catalog/{games/files => gamefiles}/2x2x2.efg (100%) rename catalog/{games/files => gamefiles}/2x2x2.nfg (100%) rename catalog/{games/files => gamefiles}/2x2x2x2.nfg (100%) rename catalog/{games/files => gamefiles}/2x2x2x2x2.nfg (100%) rename catalog/{games/files => gamefiles}/3x3x3.nfg (100%) rename catalog/{games/files => gamefiles}/4cards.efg (100%) rename catalog/{games/files => gamefiles}/5x4x3.nfg (100%) rename catalog/{games/files => gamefiles}/8x2x2.nfg (100%) rename catalog/{games/files => gamefiles}/8x8.nfg (100%) rename catalog/{games/files => gamefiles}/BSS_S_085.Weighted.agg (100%) rename catalog/{games/files => gamefiles}/Bayesian-Coffee-3-2-2-3.bagg (100%) rename catalog/{games/files => gamefiles}/GenRPS5.agg (100%) rename catalog/{games/files => gamefiles}/artist1.efg (100%) rename catalog/{games/files => gamefiles}/artist2.efg (100%) rename catalog/{games/files => gamefiles}/badgame1.efg (100%) rename catalog/{games/files => gamefiles}/badgame2.efg (100%) rename catalog/{games/files => gamefiles}/bagwell.efg (100%) rename catalog/{games/files => gamefiles}/bayes1a.efg (100%) rename catalog/{games/files => gamefiles}/bayes2a.efg (100%) rename catalog/{games/files => gamefiles}/bcp2.efg (100%) rename catalog/{games/files => gamefiles}/bcp3.efg (100%) rename catalog/{games/files => gamefiles}/bcp4.efg (100%) rename catalog/{games/files => gamefiles}/bhg1.efg (100%) rename catalog/{games/files => gamefiles}/bhg2.efg (100%) rename catalog/{games/files => gamefiles}/bhg3.efg (100%) rename catalog/{games/files => gamefiles}/bhg4.efg (100%) rename catalog/{games/files => gamefiles}/bhg5.efg (100%) rename catalog/{games/files => gamefiles}/caro2.efg (100%) rename catalog/{games/files => gamefiles}/cent2.efg (100%) rename catalog/{games/files => gamefiles}/cent2.nfg (100%) rename catalog/{games/files => gamefiles}/cent3.efg (100%) rename catalog/{games/files => gamefiles}/cent4.efg (100%) rename catalog/{games/files => gamefiles}/cent6.efg (100%) rename catalog/{games/files => gamefiles}/centcs10.efg (100%) rename catalog/{games/files => gamefiles}/centcs6.efg (100%) rename catalog/{games/files => gamefiles}/condjury.efg (100%) rename catalog/{games/files => gamefiles}/coord2.efg (100%) rename catalog/{games/files => gamefiles}/coord2.nfg (100%) rename catalog/{games/files => gamefiles}/coord2ts.efg (100%) rename catalog/{games/files => gamefiles}/coord3.efg (100%) rename catalog/{games/files => gamefiles}/coord3.nfg (100%) rename catalog/{games/files => gamefiles}/coord333.nfg (100%) rename catalog/{games/files => gamefiles}/coord4.efg (100%) rename catalog/{games/files => gamefiles}/coord4.nfg (100%) rename catalog/{games/files => gamefiles}/cross.efg (100%) rename catalog/{games/files => gamefiles}/cs.efg (100%) rename catalog/{games/files => gamefiles}/csg1.nfg (100%) rename catalog/{games/files => gamefiles}/csg2.nfg (100%) rename catalog/{games/files => gamefiles}/csg3.nfg (100%) rename catalog/{games/files => gamefiles}/csg4.nfg (100%) rename catalog/{games/files => gamefiles}/deg1.nfg (100%) rename catalog/{games/files => gamefiles}/deg2.nfg (100%) rename catalog/{games/files => gamefiles}/e01.efg (100%) rename catalog/{games/files => gamefiles}/e01.nfg (100%) rename catalog/{games/files => gamefiles}/e02.efg (100%) rename catalog/{games/files => gamefiles}/e02.nfg (100%) rename catalog/{games/files => gamefiles}/e03.efg (100%) rename catalog/{games/files => gamefiles}/e04.efg (100%) rename catalog/{games/files => gamefiles}/e04.nfg (100%) rename catalog/{games/files => gamefiles}/e05.efg (100%) rename catalog/{games/files => gamefiles}/e06.efg (100%) rename catalog/{games/files => gamefiles}/e07.efg (100%) rename catalog/{games/files => gamefiles}/e07.nfg (100%) rename catalog/{games/files => gamefiles}/e08.efg (100%) rename catalog/{games/files => gamefiles}/e09.efg (100%) rename catalog/{games/files => gamefiles}/e10.efg (100%) rename catalog/{games/files => gamefiles}/e10a.efg (100%) rename catalog/{games/files => gamefiles}/e13.efg (100%) rename catalog/{games/files => gamefiles}/e16.efg (100%) rename catalog/{games/files => gamefiles}/e17.efg (100%) rename catalog/{games/files => gamefiles}/e18.efg (100%) rename catalog/{games/files => gamefiles}/g1.efg (100%) rename catalog/{games/files => gamefiles}/g1.nfg (100%) rename catalog/{games/files => gamefiles}/g2.efg (100%) rename catalog/{games/files => gamefiles}/g2.nfg (100%) rename catalog/{games/files => gamefiles}/g3.efg (100%) rename catalog/{games/files => gamefiles}/g3.nfg (100%) rename catalog/{games/files => gamefiles}/holdout.efg (100%) rename catalog/{games/files => gamefiles}/holdout7.efg (100%) rename catalog/{games/files => gamefiles}/hs1.efg (100%) rename catalog/{games/files => gamefiles}/jury_mr.efg (100%) rename catalog/{games/files => gamefiles}/jury_un.efg (100%) rename catalog/{games/files => gamefiles}/km1.efg (100%) rename catalog/{games/files => gamefiles}/km2.efg (100%) rename catalog/{games/files => gamefiles}/km3.efg (100%) rename catalog/{games/files => gamefiles}/km6.efg (100%) rename catalog/{games/files => gamefiles}/loopback.nfg (100%) rename catalog/{games/files => gamefiles}/mixdom.nfg (100%) rename catalog/{games/files => gamefiles}/mixdom2.nfg (100%) rename catalog/{games/files => gamefiles}/montyhal.efg (100%) rename catalog/{games/files => gamefiles}/my_2-1.efg (100%) rename catalog/{games/files => gamefiles}/my_2-4.efg (100%) rename catalog/{games/files => gamefiles}/my_2-8.efg (100%) rename catalog/{games/files => gamefiles}/my_3-3a.efg (100%) rename catalog/{games/files => gamefiles}/my_3-3b.efg (100%) rename catalog/{games/files => gamefiles}/my_3-3c.efg (100%) rename catalog/{games/files => gamefiles}/my_3-3d.efg (100%) rename catalog/{games/files => gamefiles}/my_3-3e.efg (100%) rename catalog/{games/files => gamefiles}/my_3-4.efg (100%) rename catalog/{games/files => gamefiles}/myerson.efg (100%) rename catalog/{games/files => gamefiles}/myerson_fig_4_2.efg (100%) rename catalog/{games/files => gamefiles}/nim.efg (100%) rename catalog/{games/files => gamefiles}/nim7.efg (100%) rename catalog/{games/files => gamefiles}/oneill.nfg (100%) rename catalog/{games/files => gamefiles}/palf.efg (100%) rename catalog/{games/files => gamefiles}/palf2.efg (100%) rename catalog/{games/files => gamefiles}/palf3.efg (100%) rename catalog/{games/files => gamefiles}/pbride.efg (100%) rename catalog/{games/files => gamefiles}/pd.nfg (100%) rename catalog/{games/files => gamefiles}/perfect1.nfg (100%) rename catalog/{games/files => gamefiles}/perfect2.nfg (100%) rename catalog/{games/files => gamefiles}/perfect3.nfg (100%) rename catalog/{games/files => gamefiles}/poker.efg (100%) rename catalog/{games/files => gamefiles}/poker.nfg (100%) rename catalog/{games/files => gamefiles}/poker2.efg (100%) rename catalog/{games/files => gamefiles}/pvw.efg (100%) rename catalog/{games/files => gamefiles}/pvw2.efg (100%) rename catalog/{games/files => gamefiles}/sh3.efg (100%) rename catalog/{games/files => gamefiles}/sh3.nfg (100%) rename catalog/{games/files => gamefiles}/spence.efg (100%) rename catalog/{games/files => gamefiles}/stengel.nfg (100%) rename catalog/{games/files => gamefiles}/sww1.efg (100%) rename catalog/{games/files => gamefiles}/sww1.nfg (100%) rename catalog/{games/files => gamefiles}/sww2.efg (100%) rename catalog/{games/files => gamefiles}/sww3.efg (100%) rename catalog/{games/files => gamefiles}/tim.efg (100%) rename catalog/{games/files => gamefiles}/todd1.nfg (100%) rename catalog/{games/files => gamefiles}/todd2.nfg (100%) rename catalog/{games/files => gamefiles}/todd3.nfg (100%) rename catalog/{games/files => gamefiles}/ttt.efg (100%) rename catalog/{games/files => gamefiles}/vd.efg (100%) rename catalog/{games/files => gamefiles}/vd.nfg (100%) rename catalog/{games/files => gamefiles}/w_ex1.efg (100%) rename catalog/{games/files => gamefiles}/w_ex2.efg (100%) rename catalog/{games/files => gamefiles}/wilson1.efg (100%) rename catalog/{games/files => gamefiles}/wink3.nfg (100%) rename catalog/{games/files => gamefiles}/winkels.nfg (100%) rename catalog/{games/files => gamefiles}/work1.efg (100%) rename catalog/{games/files => gamefiles}/work2.efg (100%) rename catalog/{games/files => gamefiles}/work3.efg (100%) rename catalog/{games/files => gamefiles}/yamamoto.nfg (100%) rename catalog/{games/files => gamefiles}/zero.nfg (100%) diff --git a/catalog/catalog.py b/catalog/catalog.py new file mode 100644 index 000000000..e69de29bb diff --git a/catalog/games/files/2s2x2x2.efg b/catalog/gamefiles/2s2x2x2.efg similarity index 100% rename from catalog/games/files/2s2x2x2.efg rename to catalog/gamefiles/2s2x2x2.efg diff --git a/catalog/games/files/2smp.efg b/catalog/gamefiles/2smp.efg similarity index 100% rename from catalog/games/files/2smp.efg rename to catalog/gamefiles/2smp.efg diff --git a/catalog/games/files/2x2.agg b/catalog/gamefiles/2x2.agg similarity index 100% rename from catalog/games/files/2x2.agg rename to catalog/gamefiles/2x2.agg diff --git a/catalog/games/files/2x2.nfg b/catalog/gamefiles/2x2.nfg similarity index 100% rename from catalog/games/files/2x2.nfg rename to catalog/gamefiles/2x2.nfg diff --git a/catalog/games/files/2x2a.nfg b/catalog/gamefiles/2x2a.nfg similarity index 100% rename from catalog/games/files/2x2a.nfg rename to catalog/gamefiles/2x2a.nfg diff --git a/catalog/games/files/2x2const.nfg b/catalog/gamefiles/2x2const.nfg similarity index 100% rename from catalog/games/files/2x2const.nfg rename to catalog/gamefiles/2x2const.nfg diff --git a/catalog/games/files/2x2x2-nau.nfg b/catalog/gamefiles/2x2x2-nau.nfg similarity index 100% rename from catalog/games/files/2x2x2-nau.nfg rename to catalog/gamefiles/2x2x2-nau.nfg diff --git a/catalog/games/files/2x2x2.efg b/catalog/gamefiles/2x2x2.efg similarity index 100% rename from catalog/games/files/2x2x2.efg rename to catalog/gamefiles/2x2x2.efg diff --git a/catalog/games/files/2x2x2.nfg b/catalog/gamefiles/2x2x2.nfg similarity index 100% rename from catalog/games/files/2x2x2.nfg rename to catalog/gamefiles/2x2x2.nfg diff --git a/catalog/games/files/2x2x2x2.nfg b/catalog/gamefiles/2x2x2x2.nfg similarity index 100% rename from catalog/games/files/2x2x2x2.nfg rename to catalog/gamefiles/2x2x2x2.nfg diff --git a/catalog/games/files/2x2x2x2x2.nfg b/catalog/gamefiles/2x2x2x2x2.nfg similarity index 100% rename from catalog/games/files/2x2x2x2x2.nfg rename to catalog/gamefiles/2x2x2x2x2.nfg diff --git a/catalog/games/files/3x3x3.nfg b/catalog/gamefiles/3x3x3.nfg similarity index 100% rename from catalog/games/files/3x3x3.nfg rename to catalog/gamefiles/3x3x3.nfg diff --git a/catalog/games/files/4cards.efg b/catalog/gamefiles/4cards.efg similarity index 100% rename from catalog/games/files/4cards.efg rename to catalog/gamefiles/4cards.efg diff --git a/catalog/games/files/5x4x3.nfg b/catalog/gamefiles/5x4x3.nfg similarity index 100% rename from catalog/games/files/5x4x3.nfg rename to catalog/gamefiles/5x4x3.nfg diff --git a/catalog/games/files/8x2x2.nfg b/catalog/gamefiles/8x2x2.nfg similarity index 100% rename from catalog/games/files/8x2x2.nfg rename to catalog/gamefiles/8x2x2.nfg diff --git a/catalog/games/files/8x8.nfg b/catalog/gamefiles/8x8.nfg similarity index 100% rename from catalog/games/files/8x8.nfg rename to catalog/gamefiles/8x8.nfg diff --git a/catalog/games/files/BSS_S_085.Weighted.agg b/catalog/gamefiles/BSS_S_085.Weighted.agg similarity index 100% rename from catalog/games/files/BSS_S_085.Weighted.agg rename to catalog/gamefiles/BSS_S_085.Weighted.agg diff --git a/catalog/games/files/Bayesian-Coffee-3-2-2-3.bagg b/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg similarity index 100% rename from catalog/games/files/Bayesian-Coffee-3-2-2-3.bagg rename to catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg diff --git a/catalog/games/files/GenRPS5.agg b/catalog/gamefiles/GenRPS5.agg similarity index 100% rename from catalog/games/files/GenRPS5.agg rename to catalog/gamefiles/GenRPS5.agg diff --git a/catalog/games/files/artist1.efg b/catalog/gamefiles/artist1.efg similarity index 100% rename from catalog/games/files/artist1.efg rename to catalog/gamefiles/artist1.efg diff --git a/catalog/games/files/artist2.efg b/catalog/gamefiles/artist2.efg similarity index 100% rename from catalog/games/files/artist2.efg rename to catalog/gamefiles/artist2.efg diff --git a/catalog/games/files/badgame1.efg b/catalog/gamefiles/badgame1.efg similarity index 100% rename from catalog/games/files/badgame1.efg rename to catalog/gamefiles/badgame1.efg diff --git a/catalog/games/files/badgame2.efg b/catalog/gamefiles/badgame2.efg similarity index 100% rename from catalog/games/files/badgame2.efg rename to catalog/gamefiles/badgame2.efg diff --git a/catalog/games/files/bagwell.efg b/catalog/gamefiles/bagwell.efg similarity index 100% rename from catalog/games/files/bagwell.efg rename to catalog/gamefiles/bagwell.efg diff --git a/catalog/games/files/bayes1a.efg b/catalog/gamefiles/bayes1a.efg similarity index 100% rename from catalog/games/files/bayes1a.efg rename to catalog/gamefiles/bayes1a.efg diff --git a/catalog/games/files/bayes2a.efg b/catalog/gamefiles/bayes2a.efg similarity index 100% rename from catalog/games/files/bayes2a.efg rename to catalog/gamefiles/bayes2a.efg diff --git a/catalog/games/files/bcp2.efg b/catalog/gamefiles/bcp2.efg similarity index 100% rename from catalog/games/files/bcp2.efg rename to catalog/gamefiles/bcp2.efg diff --git a/catalog/games/files/bcp3.efg b/catalog/gamefiles/bcp3.efg similarity index 100% rename from catalog/games/files/bcp3.efg rename to catalog/gamefiles/bcp3.efg diff --git a/catalog/games/files/bcp4.efg b/catalog/gamefiles/bcp4.efg similarity index 100% rename from catalog/games/files/bcp4.efg rename to catalog/gamefiles/bcp4.efg diff --git a/catalog/games/files/bhg1.efg b/catalog/gamefiles/bhg1.efg similarity index 100% rename from catalog/games/files/bhg1.efg rename to catalog/gamefiles/bhg1.efg diff --git a/catalog/games/files/bhg2.efg b/catalog/gamefiles/bhg2.efg similarity index 100% rename from catalog/games/files/bhg2.efg rename to catalog/gamefiles/bhg2.efg diff --git a/catalog/games/files/bhg3.efg b/catalog/gamefiles/bhg3.efg similarity index 100% rename from catalog/games/files/bhg3.efg rename to catalog/gamefiles/bhg3.efg diff --git a/catalog/games/files/bhg4.efg b/catalog/gamefiles/bhg4.efg similarity index 100% rename from catalog/games/files/bhg4.efg rename to catalog/gamefiles/bhg4.efg diff --git a/catalog/games/files/bhg5.efg b/catalog/gamefiles/bhg5.efg similarity index 100% rename from catalog/games/files/bhg5.efg rename to catalog/gamefiles/bhg5.efg diff --git a/catalog/games/files/caro2.efg b/catalog/gamefiles/caro2.efg similarity index 100% rename from catalog/games/files/caro2.efg rename to catalog/gamefiles/caro2.efg diff --git a/catalog/games/files/cent2.efg b/catalog/gamefiles/cent2.efg similarity index 100% rename from catalog/games/files/cent2.efg rename to catalog/gamefiles/cent2.efg diff --git a/catalog/games/files/cent2.nfg b/catalog/gamefiles/cent2.nfg similarity index 100% rename from catalog/games/files/cent2.nfg rename to catalog/gamefiles/cent2.nfg diff --git a/catalog/games/files/cent3.efg b/catalog/gamefiles/cent3.efg similarity index 100% rename from catalog/games/files/cent3.efg rename to catalog/gamefiles/cent3.efg diff --git a/catalog/games/files/cent4.efg b/catalog/gamefiles/cent4.efg similarity index 100% rename from catalog/games/files/cent4.efg rename to catalog/gamefiles/cent4.efg diff --git a/catalog/games/files/cent6.efg b/catalog/gamefiles/cent6.efg similarity index 100% rename from catalog/games/files/cent6.efg rename to catalog/gamefiles/cent6.efg diff --git a/catalog/games/files/centcs10.efg b/catalog/gamefiles/centcs10.efg similarity index 100% rename from catalog/games/files/centcs10.efg rename to catalog/gamefiles/centcs10.efg diff --git a/catalog/games/files/centcs6.efg b/catalog/gamefiles/centcs6.efg similarity index 100% rename from catalog/games/files/centcs6.efg rename to catalog/gamefiles/centcs6.efg diff --git a/catalog/games/files/condjury.efg b/catalog/gamefiles/condjury.efg similarity index 100% rename from catalog/games/files/condjury.efg rename to catalog/gamefiles/condjury.efg diff --git a/catalog/games/files/coord2.efg b/catalog/gamefiles/coord2.efg similarity index 100% rename from catalog/games/files/coord2.efg rename to catalog/gamefiles/coord2.efg diff --git a/catalog/games/files/coord2.nfg b/catalog/gamefiles/coord2.nfg similarity index 100% rename from catalog/games/files/coord2.nfg rename to catalog/gamefiles/coord2.nfg diff --git a/catalog/games/files/coord2ts.efg b/catalog/gamefiles/coord2ts.efg similarity index 100% rename from catalog/games/files/coord2ts.efg rename to catalog/gamefiles/coord2ts.efg diff --git a/catalog/games/files/coord3.efg b/catalog/gamefiles/coord3.efg similarity index 100% rename from catalog/games/files/coord3.efg rename to catalog/gamefiles/coord3.efg diff --git a/catalog/games/files/coord3.nfg b/catalog/gamefiles/coord3.nfg similarity index 100% rename from catalog/games/files/coord3.nfg rename to catalog/gamefiles/coord3.nfg diff --git a/catalog/games/files/coord333.nfg b/catalog/gamefiles/coord333.nfg similarity index 100% rename from catalog/games/files/coord333.nfg rename to catalog/gamefiles/coord333.nfg diff --git a/catalog/games/files/coord4.efg b/catalog/gamefiles/coord4.efg similarity index 100% rename from catalog/games/files/coord4.efg rename to catalog/gamefiles/coord4.efg diff --git a/catalog/games/files/coord4.nfg b/catalog/gamefiles/coord4.nfg similarity index 100% rename from catalog/games/files/coord4.nfg rename to catalog/gamefiles/coord4.nfg diff --git a/catalog/games/files/cross.efg b/catalog/gamefiles/cross.efg similarity index 100% rename from catalog/games/files/cross.efg rename to catalog/gamefiles/cross.efg diff --git a/catalog/games/files/cs.efg b/catalog/gamefiles/cs.efg similarity index 100% rename from catalog/games/files/cs.efg rename to catalog/gamefiles/cs.efg diff --git a/catalog/games/files/csg1.nfg b/catalog/gamefiles/csg1.nfg similarity index 100% rename from catalog/games/files/csg1.nfg rename to catalog/gamefiles/csg1.nfg diff --git a/catalog/games/files/csg2.nfg b/catalog/gamefiles/csg2.nfg similarity index 100% rename from catalog/games/files/csg2.nfg rename to catalog/gamefiles/csg2.nfg diff --git a/catalog/games/files/csg3.nfg b/catalog/gamefiles/csg3.nfg similarity index 100% rename from catalog/games/files/csg3.nfg rename to catalog/gamefiles/csg3.nfg diff --git a/catalog/games/files/csg4.nfg b/catalog/gamefiles/csg4.nfg similarity index 100% rename from catalog/games/files/csg4.nfg rename to catalog/gamefiles/csg4.nfg diff --git a/catalog/games/files/deg1.nfg b/catalog/gamefiles/deg1.nfg similarity index 100% rename from catalog/games/files/deg1.nfg rename to catalog/gamefiles/deg1.nfg diff --git a/catalog/games/files/deg2.nfg b/catalog/gamefiles/deg2.nfg similarity index 100% rename from catalog/games/files/deg2.nfg rename to catalog/gamefiles/deg2.nfg diff --git a/catalog/games/files/e01.efg b/catalog/gamefiles/e01.efg similarity index 100% rename from catalog/games/files/e01.efg rename to catalog/gamefiles/e01.efg diff --git a/catalog/games/files/e01.nfg b/catalog/gamefiles/e01.nfg similarity index 100% rename from catalog/games/files/e01.nfg rename to catalog/gamefiles/e01.nfg diff --git a/catalog/games/files/e02.efg b/catalog/gamefiles/e02.efg similarity index 100% rename from catalog/games/files/e02.efg rename to catalog/gamefiles/e02.efg diff --git a/catalog/games/files/e02.nfg b/catalog/gamefiles/e02.nfg similarity index 100% rename from catalog/games/files/e02.nfg rename to catalog/gamefiles/e02.nfg diff --git a/catalog/games/files/e03.efg b/catalog/gamefiles/e03.efg similarity index 100% rename from catalog/games/files/e03.efg rename to catalog/gamefiles/e03.efg diff --git a/catalog/games/files/e04.efg b/catalog/gamefiles/e04.efg similarity index 100% rename from catalog/games/files/e04.efg rename to catalog/gamefiles/e04.efg diff --git a/catalog/games/files/e04.nfg b/catalog/gamefiles/e04.nfg similarity index 100% rename from catalog/games/files/e04.nfg rename to catalog/gamefiles/e04.nfg diff --git a/catalog/games/files/e05.efg b/catalog/gamefiles/e05.efg similarity index 100% rename from catalog/games/files/e05.efg rename to catalog/gamefiles/e05.efg diff --git a/catalog/games/files/e06.efg b/catalog/gamefiles/e06.efg similarity index 100% rename from catalog/games/files/e06.efg rename to catalog/gamefiles/e06.efg diff --git a/catalog/games/files/e07.efg b/catalog/gamefiles/e07.efg similarity index 100% rename from catalog/games/files/e07.efg rename to catalog/gamefiles/e07.efg diff --git a/catalog/games/files/e07.nfg b/catalog/gamefiles/e07.nfg similarity index 100% rename from catalog/games/files/e07.nfg rename to catalog/gamefiles/e07.nfg diff --git a/catalog/games/files/e08.efg b/catalog/gamefiles/e08.efg similarity index 100% rename from catalog/games/files/e08.efg rename to catalog/gamefiles/e08.efg diff --git a/catalog/games/files/e09.efg b/catalog/gamefiles/e09.efg similarity index 100% rename from catalog/games/files/e09.efg rename to catalog/gamefiles/e09.efg diff --git a/catalog/games/files/e10.efg b/catalog/gamefiles/e10.efg similarity index 100% rename from catalog/games/files/e10.efg rename to catalog/gamefiles/e10.efg diff --git a/catalog/games/files/e10a.efg b/catalog/gamefiles/e10a.efg similarity index 100% rename from catalog/games/files/e10a.efg rename to catalog/gamefiles/e10a.efg diff --git a/catalog/games/files/e13.efg b/catalog/gamefiles/e13.efg similarity index 100% rename from catalog/games/files/e13.efg rename to catalog/gamefiles/e13.efg diff --git a/catalog/games/files/e16.efg b/catalog/gamefiles/e16.efg similarity index 100% rename from catalog/games/files/e16.efg rename to catalog/gamefiles/e16.efg diff --git a/catalog/games/files/e17.efg b/catalog/gamefiles/e17.efg similarity index 100% rename from catalog/games/files/e17.efg rename to catalog/gamefiles/e17.efg diff --git a/catalog/games/files/e18.efg b/catalog/gamefiles/e18.efg similarity index 100% rename from catalog/games/files/e18.efg rename to catalog/gamefiles/e18.efg diff --git a/catalog/games/files/g1.efg b/catalog/gamefiles/g1.efg similarity index 100% rename from catalog/games/files/g1.efg rename to catalog/gamefiles/g1.efg diff --git a/catalog/games/files/g1.nfg b/catalog/gamefiles/g1.nfg similarity index 100% rename from catalog/games/files/g1.nfg rename to catalog/gamefiles/g1.nfg diff --git a/catalog/games/files/g2.efg b/catalog/gamefiles/g2.efg similarity index 100% rename from catalog/games/files/g2.efg rename to catalog/gamefiles/g2.efg diff --git a/catalog/games/files/g2.nfg b/catalog/gamefiles/g2.nfg similarity index 100% rename from catalog/games/files/g2.nfg rename to catalog/gamefiles/g2.nfg diff --git a/catalog/games/files/g3.efg b/catalog/gamefiles/g3.efg similarity index 100% rename from catalog/games/files/g3.efg rename to catalog/gamefiles/g3.efg diff --git a/catalog/games/files/g3.nfg b/catalog/gamefiles/g3.nfg similarity index 100% rename from catalog/games/files/g3.nfg rename to catalog/gamefiles/g3.nfg diff --git a/catalog/games/files/holdout.efg b/catalog/gamefiles/holdout.efg similarity index 100% rename from catalog/games/files/holdout.efg rename to catalog/gamefiles/holdout.efg diff --git a/catalog/games/files/holdout7.efg b/catalog/gamefiles/holdout7.efg similarity index 100% rename from catalog/games/files/holdout7.efg rename to catalog/gamefiles/holdout7.efg diff --git a/catalog/games/files/hs1.efg b/catalog/gamefiles/hs1.efg similarity index 100% rename from catalog/games/files/hs1.efg rename to catalog/gamefiles/hs1.efg diff --git a/catalog/games/files/jury_mr.efg b/catalog/gamefiles/jury_mr.efg similarity index 100% rename from catalog/games/files/jury_mr.efg rename to catalog/gamefiles/jury_mr.efg diff --git a/catalog/games/files/jury_un.efg b/catalog/gamefiles/jury_un.efg similarity index 100% rename from catalog/games/files/jury_un.efg rename to catalog/gamefiles/jury_un.efg diff --git a/catalog/games/files/km1.efg b/catalog/gamefiles/km1.efg similarity index 100% rename from catalog/games/files/km1.efg rename to catalog/gamefiles/km1.efg diff --git a/catalog/games/files/km2.efg b/catalog/gamefiles/km2.efg similarity index 100% rename from catalog/games/files/km2.efg rename to catalog/gamefiles/km2.efg diff --git a/catalog/games/files/km3.efg b/catalog/gamefiles/km3.efg similarity index 100% rename from catalog/games/files/km3.efg rename to catalog/gamefiles/km3.efg diff --git a/catalog/games/files/km6.efg b/catalog/gamefiles/km6.efg similarity index 100% rename from catalog/games/files/km6.efg rename to catalog/gamefiles/km6.efg diff --git a/catalog/games/files/loopback.nfg b/catalog/gamefiles/loopback.nfg similarity index 100% rename from catalog/games/files/loopback.nfg rename to catalog/gamefiles/loopback.nfg diff --git a/catalog/games/files/mixdom.nfg b/catalog/gamefiles/mixdom.nfg similarity index 100% rename from catalog/games/files/mixdom.nfg rename to catalog/gamefiles/mixdom.nfg diff --git a/catalog/games/files/mixdom2.nfg b/catalog/gamefiles/mixdom2.nfg similarity index 100% rename from catalog/games/files/mixdom2.nfg rename to catalog/gamefiles/mixdom2.nfg diff --git a/catalog/games/files/montyhal.efg b/catalog/gamefiles/montyhal.efg similarity index 100% rename from catalog/games/files/montyhal.efg rename to catalog/gamefiles/montyhal.efg diff --git a/catalog/games/files/my_2-1.efg b/catalog/gamefiles/my_2-1.efg similarity index 100% rename from catalog/games/files/my_2-1.efg rename to catalog/gamefiles/my_2-1.efg diff --git a/catalog/games/files/my_2-4.efg b/catalog/gamefiles/my_2-4.efg similarity index 100% rename from catalog/games/files/my_2-4.efg rename to catalog/gamefiles/my_2-4.efg diff --git a/catalog/games/files/my_2-8.efg b/catalog/gamefiles/my_2-8.efg similarity index 100% rename from catalog/games/files/my_2-8.efg rename to catalog/gamefiles/my_2-8.efg diff --git a/catalog/games/files/my_3-3a.efg b/catalog/gamefiles/my_3-3a.efg similarity index 100% rename from catalog/games/files/my_3-3a.efg rename to catalog/gamefiles/my_3-3a.efg diff --git a/catalog/games/files/my_3-3b.efg b/catalog/gamefiles/my_3-3b.efg similarity index 100% rename from catalog/games/files/my_3-3b.efg rename to catalog/gamefiles/my_3-3b.efg diff --git a/catalog/games/files/my_3-3c.efg b/catalog/gamefiles/my_3-3c.efg similarity index 100% rename from catalog/games/files/my_3-3c.efg rename to catalog/gamefiles/my_3-3c.efg diff --git a/catalog/games/files/my_3-3d.efg b/catalog/gamefiles/my_3-3d.efg similarity index 100% rename from catalog/games/files/my_3-3d.efg rename to catalog/gamefiles/my_3-3d.efg diff --git a/catalog/games/files/my_3-3e.efg b/catalog/gamefiles/my_3-3e.efg similarity index 100% rename from catalog/games/files/my_3-3e.efg rename to catalog/gamefiles/my_3-3e.efg diff --git a/catalog/games/files/my_3-4.efg b/catalog/gamefiles/my_3-4.efg similarity index 100% rename from catalog/games/files/my_3-4.efg rename to catalog/gamefiles/my_3-4.efg diff --git a/catalog/games/files/myerson.efg b/catalog/gamefiles/myerson.efg similarity index 100% rename from catalog/games/files/myerson.efg rename to catalog/gamefiles/myerson.efg diff --git a/catalog/games/files/myerson_fig_4_2.efg b/catalog/gamefiles/myerson_fig_4_2.efg similarity index 100% rename from catalog/games/files/myerson_fig_4_2.efg rename to catalog/gamefiles/myerson_fig_4_2.efg diff --git a/catalog/games/files/nim.efg b/catalog/gamefiles/nim.efg similarity index 100% rename from catalog/games/files/nim.efg rename to catalog/gamefiles/nim.efg diff --git a/catalog/games/files/nim7.efg b/catalog/gamefiles/nim7.efg similarity index 100% rename from catalog/games/files/nim7.efg rename to catalog/gamefiles/nim7.efg diff --git a/catalog/games/files/oneill.nfg b/catalog/gamefiles/oneill.nfg similarity index 100% rename from catalog/games/files/oneill.nfg rename to catalog/gamefiles/oneill.nfg diff --git a/catalog/games/files/palf.efg b/catalog/gamefiles/palf.efg similarity index 100% rename from catalog/games/files/palf.efg rename to catalog/gamefiles/palf.efg diff --git a/catalog/games/files/palf2.efg b/catalog/gamefiles/palf2.efg similarity index 100% rename from catalog/games/files/palf2.efg rename to catalog/gamefiles/palf2.efg diff --git a/catalog/games/files/palf3.efg b/catalog/gamefiles/palf3.efg similarity index 100% rename from catalog/games/files/palf3.efg rename to catalog/gamefiles/palf3.efg diff --git a/catalog/games/files/pbride.efg b/catalog/gamefiles/pbride.efg similarity index 100% rename from catalog/games/files/pbride.efg rename to catalog/gamefiles/pbride.efg diff --git a/catalog/games/files/pd.nfg b/catalog/gamefiles/pd.nfg similarity index 100% rename from catalog/games/files/pd.nfg rename to catalog/gamefiles/pd.nfg diff --git a/catalog/games/files/perfect1.nfg b/catalog/gamefiles/perfect1.nfg similarity index 100% rename from catalog/games/files/perfect1.nfg rename to catalog/gamefiles/perfect1.nfg diff --git a/catalog/games/files/perfect2.nfg b/catalog/gamefiles/perfect2.nfg similarity index 100% rename from catalog/games/files/perfect2.nfg rename to catalog/gamefiles/perfect2.nfg diff --git a/catalog/games/files/perfect3.nfg b/catalog/gamefiles/perfect3.nfg similarity index 100% rename from catalog/games/files/perfect3.nfg rename to catalog/gamefiles/perfect3.nfg diff --git a/catalog/games/files/poker.efg b/catalog/gamefiles/poker.efg similarity index 100% rename from catalog/games/files/poker.efg rename to catalog/gamefiles/poker.efg diff --git a/catalog/games/files/poker.nfg b/catalog/gamefiles/poker.nfg similarity index 100% rename from catalog/games/files/poker.nfg rename to catalog/gamefiles/poker.nfg diff --git a/catalog/games/files/poker2.efg b/catalog/gamefiles/poker2.efg similarity index 100% rename from catalog/games/files/poker2.efg rename to catalog/gamefiles/poker2.efg diff --git a/catalog/games/files/pvw.efg b/catalog/gamefiles/pvw.efg similarity index 100% rename from catalog/games/files/pvw.efg rename to catalog/gamefiles/pvw.efg diff --git a/catalog/games/files/pvw2.efg b/catalog/gamefiles/pvw2.efg similarity index 100% rename from catalog/games/files/pvw2.efg rename to catalog/gamefiles/pvw2.efg diff --git a/catalog/games/files/sh3.efg b/catalog/gamefiles/sh3.efg similarity index 100% rename from catalog/games/files/sh3.efg rename to catalog/gamefiles/sh3.efg diff --git a/catalog/games/files/sh3.nfg b/catalog/gamefiles/sh3.nfg similarity index 100% rename from catalog/games/files/sh3.nfg rename to catalog/gamefiles/sh3.nfg diff --git a/catalog/games/files/spence.efg b/catalog/gamefiles/spence.efg similarity index 100% rename from catalog/games/files/spence.efg rename to catalog/gamefiles/spence.efg diff --git a/catalog/games/files/stengel.nfg b/catalog/gamefiles/stengel.nfg similarity index 100% rename from catalog/games/files/stengel.nfg rename to catalog/gamefiles/stengel.nfg diff --git a/catalog/games/files/sww1.efg b/catalog/gamefiles/sww1.efg similarity index 100% rename from catalog/games/files/sww1.efg rename to catalog/gamefiles/sww1.efg diff --git a/catalog/games/files/sww1.nfg b/catalog/gamefiles/sww1.nfg similarity index 100% rename from catalog/games/files/sww1.nfg rename to catalog/gamefiles/sww1.nfg diff --git a/catalog/games/files/sww2.efg b/catalog/gamefiles/sww2.efg similarity index 100% rename from catalog/games/files/sww2.efg rename to catalog/gamefiles/sww2.efg diff --git a/catalog/games/files/sww3.efg b/catalog/gamefiles/sww3.efg similarity index 100% rename from catalog/games/files/sww3.efg rename to catalog/gamefiles/sww3.efg diff --git a/catalog/games/files/tim.efg b/catalog/gamefiles/tim.efg similarity index 100% rename from catalog/games/files/tim.efg rename to catalog/gamefiles/tim.efg diff --git a/catalog/games/files/todd1.nfg b/catalog/gamefiles/todd1.nfg similarity index 100% rename from catalog/games/files/todd1.nfg rename to catalog/gamefiles/todd1.nfg diff --git a/catalog/games/files/todd2.nfg b/catalog/gamefiles/todd2.nfg similarity index 100% rename from catalog/games/files/todd2.nfg rename to catalog/gamefiles/todd2.nfg diff --git a/catalog/games/files/todd3.nfg b/catalog/gamefiles/todd3.nfg similarity index 100% rename from catalog/games/files/todd3.nfg rename to catalog/gamefiles/todd3.nfg diff --git a/catalog/games/files/ttt.efg b/catalog/gamefiles/ttt.efg similarity index 100% rename from catalog/games/files/ttt.efg rename to catalog/gamefiles/ttt.efg diff --git a/catalog/games/files/vd.efg b/catalog/gamefiles/vd.efg similarity index 100% rename from catalog/games/files/vd.efg rename to catalog/gamefiles/vd.efg diff --git a/catalog/games/files/vd.nfg b/catalog/gamefiles/vd.nfg similarity index 100% rename from catalog/games/files/vd.nfg rename to catalog/gamefiles/vd.nfg diff --git a/catalog/games/files/w_ex1.efg b/catalog/gamefiles/w_ex1.efg similarity index 100% rename from catalog/games/files/w_ex1.efg rename to catalog/gamefiles/w_ex1.efg diff --git a/catalog/games/files/w_ex2.efg b/catalog/gamefiles/w_ex2.efg similarity index 100% rename from catalog/games/files/w_ex2.efg rename to catalog/gamefiles/w_ex2.efg diff --git a/catalog/games/files/wilson1.efg b/catalog/gamefiles/wilson1.efg similarity index 100% rename from catalog/games/files/wilson1.efg rename to catalog/gamefiles/wilson1.efg diff --git a/catalog/games/files/wink3.nfg b/catalog/gamefiles/wink3.nfg similarity index 100% rename from catalog/games/files/wink3.nfg rename to catalog/gamefiles/wink3.nfg diff --git a/catalog/games/files/winkels.nfg b/catalog/gamefiles/winkels.nfg similarity index 100% rename from catalog/games/files/winkels.nfg rename to catalog/gamefiles/winkels.nfg diff --git a/catalog/games/files/work1.efg b/catalog/gamefiles/work1.efg similarity index 100% rename from catalog/games/files/work1.efg rename to catalog/gamefiles/work1.efg diff --git a/catalog/games/files/work2.efg b/catalog/gamefiles/work2.efg similarity index 100% rename from catalog/games/files/work2.efg rename to catalog/gamefiles/work2.efg diff --git a/catalog/games/files/work3.efg b/catalog/gamefiles/work3.efg similarity index 100% rename from catalog/games/files/work3.efg rename to catalog/gamefiles/work3.efg diff --git a/catalog/games/files/yamamoto.nfg b/catalog/gamefiles/yamamoto.nfg similarity index 100% rename from catalog/games/files/yamamoto.nfg rename to catalog/gamefiles/yamamoto.nfg diff --git a/catalog/games/files/zero.nfg b/catalog/gamefiles/zero.nfg similarity index 100% rename from catalog/games/files/zero.nfg rename to catalog/gamefiles/zero.nfg From c4fc3a86596712dfb08871c010f4c83a2c8e0227 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 14:24:37 +0000 Subject: [PATCH 006/170] initial catalog.py --- catalog/__init__.py | 3 +++ catalog/catalog.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 catalog/__init__.py diff --git a/catalog/__init__.py b/catalog/__init__.py new file mode 100644 index 000000000..a57c5a20f --- /dev/null +++ b/catalog/__init__.py @@ -0,0 +1,3 @@ +from .catalog import PrisonersDilemma, TwoStageMatchingPennies + +__all__ = ["PrisonersDilemma", "TwoStageMatchingPennies"] diff --git a/catalog/catalog.py b/catalog/catalog.py index e69de29bb..fef3c339c 100644 --- a/catalog/catalog.py +++ b/catalog/catalog.py @@ -0,0 +1,28 @@ +from pathlib import Path + +from pygambit import read_efg, read_nfg + +_CATALOG_DIR = Path(__file__).parent +_GAMEFILES_DIR = _CATALOG_DIR / "gamefiles" + + +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. + """ + def __new__(cls): + raise NotImplementedError("This method should be implemented by subclasses.") + + +class PrisonersDilemma(CatalogGame): + """Prisoner's Dilemma game.""" + def __new__(cls): + return read_nfg(str(_GAMEFILES_DIR / "pd.nfg")) + + +class TwoStageMatchingPennies(CatalogGame): + """Two-Stage Matching Pennies game.""" + def __new__(cls): + return read_efg(str(_GAMEFILES_DIR / "2smp.efg")) From c7deefc8981ca60e74b1ccd814c7b0cc3aa7f36b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 14:26:44 +0000 Subject: [PATCH 007/170] move catalog into pygambit --- doc/tutorials/01_quickstart.ipynb | 25 +++++++++++++++---- src/pygambit/__init__.py | 1 + {catalog => src/pygambit/catalog}/__init__.py | 0 {catalog => src/pygambit/catalog}/catalog.py | 0 .../pygambit/catalog}/gamefiles/2s2x2x2.efg | 0 .../pygambit/catalog}/gamefiles/2smp.efg | 0 .../pygambit/catalog}/gamefiles/2x2.agg | 0 .../pygambit/catalog}/gamefiles/2x2.nfg | 0 .../pygambit/catalog}/gamefiles/2x2a.nfg | 0 .../pygambit/catalog}/gamefiles/2x2const.nfg | 0 .../pygambit/catalog}/gamefiles/2x2x2-nau.nfg | 0 .../pygambit/catalog}/gamefiles/2x2x2.efg | 0 .../pygambit/catalog}/gamefiles/2x2x2.nfg | 0 .../pygambit/catalog}/gamefiles/2x2x2x2.nfg | 0 .../pygambit/catalog}/gamefiles/2x2x2x2x2.nfg | 0 .../pygambit/catalog}/gamefiles/3x3x3.nfg | 0 .../pygambit/catalog}/gamefiles/4cards.efg | 0 .../pygambit/catalog}/gamefiles/5x4x3.nfg | 0 .../pygambit/catalog}/gamefiles/8x2x2.nfg | 0 .../pygambit/catalog}/gamefiles/8x8.nfg | 0 .../catalog}/gamefiles/BSS_S_085.Weighted.agg | 0 .../gamefiles/Bayesian-Coffee-3-2-2-3.bagg | 0 .../pygambit/catalog}/gamefiles/GenRPS5.agg | 0 .../pygambit/catalog}/gamefiles/artist1.efg | 0 .../pygambit/catalog}/gamefiles/artist2.efg | 0 .../pygambit/catalog}/gamefiles/badgame1.efg | 0 .../pygambit/catalog}/gamefiles/badgame2.efg | 0 .../pygambit/catalog}/gamefiles/bagwell.efg | 0 .../pygambit/catalog}/gamefiles/bayes1a.efg | 0 .../pygambit/catalog}/gamefiles/bayes2a.efg | 0 .../pygambit/catalog}/gamefiles/bcp2.efg | 0 .../pygambit/catalog}/gamefiles/bcp3.efg | 0 .../pygambit/catalog}/gamefiles/bcp4.efg | 0 .../pygambit/catalog}/gamefiles/bhg1.efg | 0 .../pygambit/catalog}/gamefiles/bhg2.efg | 0 .../pygambit/catalog}/gamefiles/bhg3.efg | 0 .../pygambit/catalog}/gamefiles/bhg4.efg | 0 .../pygambit/catalog}/gamefiles/bhg5.efg | 0 .../pygambit/catalog}/gamefiles/caro2.efg | 0 .../pygambit/catalog}/gamefiles/cent2.efg | 0 .../pygambit/catalog}/gamefiles/cent2.nfg | 0 .../pygambit/catalog}/gamefiles/cent3.efg | 0 .../pygambit/catalog}/gamefiles/cent4.efg | 0 .../pygambit/catalog}/gamefiles/cent6.efg | 0 .../pygambit/catalog}/gamefiles/centcs10.efg | 0 .../pygambit/catalog}/gamefiles/centcs6.efg | 0 .../pygambit/catalog}/gamefiles/condjury.efg | 0 .../pygambit/catalog}/gamefiles/coord2.efg | 0 .../pygambit/catalog}/gamefiles/coord2.nfg | 0 .../pygambit/catalog}/gamefiles/coord2ts.efg | 0 .../pygambit/catalog}/gamefiles/coord3.efg | 0 .../pygambit/catalog}/gamefiles/coord3.nfg | 0 .../pygambit/catalog}/gamefiles/coord333.nfg | 0 .../pygambit/catalog}/gamefiles/coord4.efg | 0 .../pygambit/catalog}/gamefiles/coord4.nfg | 0 .../pygambit/catalog}/gamefiles/cross.efg | 0 .../pygambit/catalog}/gamefiles/cs.efg | 0 .../pygambit/catalog}/gamefiles/csg1.nfg | 0 .../pygambit/catalog}/gamefiles/csg2.nfg | 0 .../pygambit/catalog}/gamefiles/csg3.nfg | 0 .../pygambit/catalog}/gamefiles/csg4.nfg | 0 .../pygambit/catalog}/gamefiles/deg1.nfg | 0 .../pygambit/catalog}/gamefiles/deg2.nfg | 0 .../pygambit/catalog}/gamefiles/e01.efg | 0 .../pygambit/catalog}/gamefiles/e01.nfg | 0 .../pygambit/catalog}/gamefiles/e02.efg | 0 .../pygambit/catalog}/gamefiles/e02.nfg | 0 .../pygambit/catalog}/gamefiles/e03.efg | 0 .../pygambit/catalog}/gamefiles/e04.efg | 0 .../pygambit/catalog}/gamefiles/e04.nfg | 0 .../pygambit/catalog}/gamefiles/e05.efg | 0 .../pygambit/catalog}/gamefiles/e06.efg | 0 .../pygambit/catalog}/gamefiles/e07.efg | 0 .../pygambit/catalog}/gamefiles/e07.nfg | 0 .../pygambit/catalog}/gamefiles/e08.efg | 0 .../pygambit/catalog}/gamefiles/e09.efg | 0 .../pygambit/catalog}/gamefiles/e10.efg | 0 .../pygambit/catalog}/gamefiles/e10a.efg | 0 .../pygambit/catalog}/gamefiles/e13.efg | 0 .../pygambit/catalog}/gamefiles/e16.efg | 0 .../pygambit/catalog}/gamefiles/e17.efg | 0 .../pygambit/catalog}/gamefiles/e18.efg | 0 .../pygambit/catalog}/gamefiles/g1.efg | 0 .../pygambit/catalog}/gamefiles/g1.nfg | 0 .../pygambit/catalog}/gamefiles/g2.efg | 0 .../pygambit/catalog}/gamefiles/g2.nfg | 0 .../pygambit/catalog}/gamefiles/g3.efg | 0 .../pygambit/catalog}/gamefiles/g3.nfg | 0 .../pygambit/catalog}/gamefiles/holdout.efg | 0 .../pygambit/catalog}/gamefiles/holdout7.efg | 0 .../pygambit/catalog}/gamefiles/hs1.efg | 0 .../pygambit/catalog}/gamefiles/jury_mr.efg | 0 .../pygambit/catalog}/gamefiles/jury_un.efg | 0 .../pygambit/catalog}/gamefiles/km1.efg | 0 .../pygambit/catalog}/gamefiles/km2.efg | 0 .../pygambit/catalog}/gamefiles/km3.efg | 0 .../pygambit/catalog}/gamefiles/km6.efg | 0 .../pygambit/catalog}/gamefiles/loopback.nfg | 0 .../pygambit/catalog}/gamefiles/mixdom.nfg | 0 .../pygambit/catalog}/gamefiles/mixdom2.nfg | 0 .../pygambit/catalog}/gamefiles/montyhal.efg | 0 .../pygambit/catalog}/gamefiles/my_2-1.efg | 0 .../pygambit/catalog}/gamefiles/my_2-4.efg | 0 .../pygambit/catalog}/gamefiles/my_2-8.efg | 0 .../pygambit/catalog}/gamefiles/my_3-3a.efg | 0 .../pygambit/catalog}/gamefiles/my_3-3b.efg | 0 .../pygambit/catalog}/gamefiles/my_3-3c.efg | 0 .../pygambit/catalog}/gamefiles/my_3-3d.efg | 0 .../pygambit/catalog}/gamefiles/my_3-3e.efg | 0 .../pygambit/catalog}/gamefiles/my_3-4.efg | 0 .../pygambit/catalog}/gamefiles/myerson.efg | 0 .../catalog}/gamefiles/myerson_fig_4_2.efg | 0 .../pygambit/catalog}/gamefiles/nim.efg | 0 .../pygambit/catalog}/gamefiles/nim7.efg | 0 .../pygambit/catalog}/gamefiles/oneill.nfg | 0 .../pygambit/catalog}/gamefiles/palf.efg | 0 .../pygambit/catalog}/gamefiles/palf2.efg | 0 .../pygambit/catalog}/gamefiles/palf3.efg | 0 .../pygambit/catalog}/gamefiles/pbride.efg | 0 .../pygambit/catalog}/gamefiles/pd.nfg | 0 .../pygambit/catalog}/gamefiles/perfect1.nfg | 0 .../pygambit/catalog}/gamefiles/perfect2.nfg | 0 .../pygambit/catalog}/gamefiles/perfect3.nfg | 0 .../pygambit/catalog}/gamefiles/poker.efg | 0 .../pygambit/catalog}/gamefiles/poker.nfg | 0 .../pygambit/catalog}/gamefiles/poker2.efg | 0 .../pygambit/catalog}/gamefiles/pvw.efg | 0 .../pygambit/catalog}/gamefiles/pvw2.efg | 0 .../pygambit/catalog}/gamefiles/sh3.efg | 0 .../pygambit/catalog}/gamefiles/sh3.nfg | 0 .../pygambit/catalog}/gamefiles/spence.efg | 0 .../pygambit/catalog}/gamefiles/stengel.nfg | 0 .../pygambit/catalog}/gamefiles/sww1.efg | 0 .../pygambit/catalog}/gamefiles/sww1.nfg | 0 .../pygambit/catalog}/gamefiles/sww2.efg | 0 .../pygambit/catalog}/gamefiles/sww3.efg | 0 .../pygambit/catalog}/gamefiles/tim.efg | 0 .../pygambit/catalog}/gamefiles/todd1.nfg | 0 .../pygambit/catalog}/gamefiles/todd2.nfg | 0 .../pygambit/catalog}/gamefiles/todd3.nfg | 0 .../pygambit/catalog}/gamefiles/ttt.efg | 0 .../pygambit/catalog}/gamefiles/vd.efg | 0 .../pygambit/catalog}/gamefiles/vd.nfg | 0 .../pygambit/catalog}/gamefiles/w_ex1.efg | 0 .../pygambit/catalog}/gamefiles/w_ex2.efg | 0 .../pygambit/catalog}/gamefiles/wilson1.efg | 0 .../pygambit/catalog}/gamefiles/wink3.nfg | 0 .../pygambit/catalog}/gamefiles/winkels.nfg | 0 .../pygambit/catalog}/gamefiles/work1.efg | 0 .../pygambit/catalog}/gamefiles/work2.efg | 0 .../pygambit/catalog}/gamefiles/work3.efg | 0 .../pygambit/catalog}/gamefiles/yamamoto.nfg | 0 .../pygambit/catalog}/gamefiles/zero.nfg | 0 153 files changed, 21 insertions(+), 5 deletions(-) rename {catalog => src/pygambit/catalog}/__init__.py (100%) rename {catalog => src/pygambit/catalog}/catalog.py (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2s2x2x2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2smp.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2.agg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2a.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2const.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2x2-nau.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2x2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2x2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2x2x2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/2x2x2x2x2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/3x3x3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/4cards.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/5x4x3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/8x2x2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/8x8.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/BSS_S_085.Weighted.agg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/Bayesian-Coffee-3-2-2-3.bagg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/GenRPS5.agg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/artist1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/artist2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/badgame1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/badgame2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bagwell.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bayes1a.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bayes2a.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bcp2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bcp3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bcp4.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bhg1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bhg2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bhg3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bhg4.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/bhg5.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/caro2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/cent2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/cent2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/cent3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/cent4.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/cent6.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/centcs10.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/centcs6.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/condjury.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord2ts.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord333.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord4.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/coord4.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/cross.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/cs.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/csg1.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/csg2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/csg3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/csg4.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/deg1.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/deg2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e01.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e01.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e02.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e02.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e03.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e04.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e04.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e05.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e06.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e07.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e07.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e08.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e09.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e10.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e10a.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e13.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e16.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e17.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/e18.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/g1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/g1.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/g2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/g2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/g3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/g3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/holdout.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/holdout7.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/hs1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/jury_mr.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/jury_un.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/km1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/km2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/km3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/km6.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/loopback.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/mixdom.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/mixdom2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/montyhal.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_2-1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_2-4.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_2-8.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_3-3a.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_3-3b.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_3-3c.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_3-3d.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_3-3e.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/my_3-4.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/myerson.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/myerson_fig_4_2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/nim.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/nim7.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/oneill.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/palf.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/palf2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/palf3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/pbride.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/pd.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/perfect1.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/perfect2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/perfect3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/poker.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/poker.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/poker2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/pvw.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/pvw2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/sh3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/sh3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/spence.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/stengel.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/sww1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/sww1.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/sww2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/sww3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/tim.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/todd1.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/todd2.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/todd3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/ttt.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/vd.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/vd.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/w_ex1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/w_ex2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/wilson1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/wink3.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/winkels.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/work1.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/work2.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/work3.efg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/yamamoto.nfg (100%) rename {catalog => src/pygambit/catalog}/gamefiles/zero.nfg (100%) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index e7c6ee954..c6a9a6dc6 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": [], @@ -449,13 +449,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "6db7a29a", "metadata": {}, - "outputs": [], + "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": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# g = gbt.catalog.PrisonersDilemma()\n", - "# g" + "g = gbt.catalog.PrisonersDilemma()\n", + "g" ] }, { diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 1d6f730bf..5f9fb08cc 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -26,6 +26,7 @@ nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 + catalog, # noqa: F401 ) import importlib.metadata diff --git a/catalog/__init__.py b/src/pygambit/catalog/__init__.py similarity index 100% rename from catalog/__init__.py rename to src/pygambit/catalog/__init__.py diff --git a/catalog/catalog.py b/src/pygambit/catalog/catalog.py similarity index 100% rename from catalog/catalog.py rename to src/pygambit/catalog/catalog.py diff --git a/catalog/gamefiles/2s2x2x2.efg b/src/pygambit/catalog/gamefiles/2s2x2x2.efg similarity index 100% rename from catalog/gamefiles/2s2x2x2.efg rename to src/pygambit/catalog/gamefiles/2s2x2x2.efg diff --git a/catalog/gamefiles/2smp.efg b/src/pygambit/catalog/gamefiles/2smp.efg similarity index 100% rename from catalog/gamefiles/2smp.efg rename to src/pygambit/catalog/gamefiles/2smp.efg diff --git a/catalog/gamefiles/2x2.agg b/src/pygambit/catalog/gamefiles/2x2.agg similarity index 100% rename from catalog/gamefiles/2x2.agg rename to src/pygambit/catalog/gamefiles/2x2.agg diff --git a/catalog/gamefiles/2x2.nfg b/src/pygambit/catalog/gamefiles/2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2.nfg rename to src/pygambit/catalog/gamefiles/2x2.nfg diff --git a/catalog/gamefiles/2x2a.nfg b/src/pygambit/catalog/gamefiles/2x2a.nfg similarity index 100% rename from catalog/gamefiles/2x2a.nfg rename to src/pygambit/catalog/gamefiles/2x2a.nfg diff --git a/catalog/gamefiles/2x2const.nfg b/src/pygambit/catalog/gamefiles/2x2const.nfg similarity index 100% rename from catalog/gamefiles/2x2const.nfg rename to src/pygambit/catalog/gamefiles/2x2const.nfg diff --git a/catalog/gamefiles/2x2x2-nau.nfg b/src/pygambit/catalog/gamefiles/2x2x2-nau.nfg similarity index 100% rename from catalog/gamefiles/2x2x2-nau.nfg rename to src/pygambit/catalog/gamefiles/2x2x2-nau.nfg diff --git a/catalog/gamefiles/2x2x2.efg b/src/pygambit/catalog/gamefiles/2x2x2.efg similarity index 100% rename from catalog/gamefiles/2x2x2.efg rename to src/pygambit/catalog/gamefiles/2x2x2.efg diff --git a/catalog/gamefiles/2x2x2.nfg b/src/pygambit/catalog/gamefiles/2x2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2x2.nfg rename to src/pygambit/catalog/gamefiles/2x2x2.nfg diff --git a/catalog/gamefiles/2x2x2x2.nfg b/src/pygambit/catalog/gamefiles/2x2x2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2x2x2.nfg rename to src/pygambit/catalog/gamefiles/2x2x2x2.nfg diff --git a/catalog/gamefiles/2x2x2x2x2.nfg b/src/pygambit/catalog/gamefiles/2x2x2x2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2x2x2x2.nfg rename to src/pygambit/catalog/gamefiles/2x2x2x2x2.nfg diff --git a/catalog/gamefiles/3x3x3.nfg b/src/pygambit/catalog/gamefiles/3x3x3.nfg similarity index 100% rename from catalog/gamefiles/3x3x3.nfg rename to src/pygambit/catalog/gamefiles/3x3x3.nfg diff --git a/catalog/gamefiles/4cards.efg b/src/pygambit/catalog/gamefiles/4cards.efg similarity index 100% rename from catalog/gamefiles/4cards.efg rename to src/pygambit/catalog/gamefiles/4cards.efg diff --git a/catalog/gamefiles/5x4x3.nfg b/src/pygambit/catalog/gamefiles/5x4x3.nfg similarity index 100% rename from catalog/gamefiles/5x4x3.nfg rename to src/pygambit/catalog/gamefiles/5x4x3.nfg diff --git a/catalog/gamefiles/8x2x2.nfg b/src/pygambit/catalog/gamefiles/8x2x2.nfg similarity index 100% rename from catalog/gamefiles/8x2x2.nfg rename to src/pygambit/catalog/gamefiles/8x2x2.nfg diff --git a/catalog/gamefiles/8x8.nfg b/src/pygambit/catalog/gamefiles/8x8.nfg similarity index 100% rename from catalog/gamefiles/8x8.nfg rename to src/pygambit/catalog/gamefiles/8x8.nfg diff --git a/catalog/gamefiles/BSS_S_085.Weighted.agg b/src/pygambit/catalog/gamefiles/BSS_S_085.Weighted.agg similarity index 100% rename from catalog/gamefiles/BSS_S_085.Weighted.agg rename to src/pygambit/catalog/gamefiles/BSS_S_085.Weighted.agg diff --git a/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg b/src/pygambit/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg similarity index 100% rename from catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg rename to src/pygambit/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg diff --git a/catalog/gamefiles/GenRPS5.agg b/src/pygambit/catalog/gamefiles/GenRPS5.agg similarity index 100% rename from catalog/gamefiles/GenRPS5.agg rename to src/pygambit/catalog/gamefiles/GenRPS5.agg diff --git a/catalog/gamefiles/artist1.efg b/src/pygambit/catalog/gamefiles/artist1.efg similarity index 100% rename from catalog/gamefiles/artist1.efg rename to src/pygambit/catalog/gamefiles/artist1.efg diff --git a/catalog/gamefiles/artist2.efg b/src/pygambit/catalog/gamefiles/artist2.efg similarity index 100% rename from catalog/gamefiles/artist2.efg rename to src/pygambit/catalog/gamefiles/artist2.efg diff --git a/catalog/gamefiles/badgame1.efg b/src/pygambit/catalog/gamefiles/badgame1.efg similarity index 100% rename from catalog/gamefiles/badgame1.efg rename to src/pygambit/catalog/gamefiles/badgame1.efg diff --git a/catalog/gamefiles/badgame2.efg b/src/pygambit/catalog/gamefiles/badgame2.efg similarity index 100% rename from catalog/gamefiles/badgame2.efg rename to src/pygambit/catalog/gamefiles/badgame2.efg diff --git a/catalog/gamefiles/bagwell.efg b/src/pygambit/catalog/gamefiles/bagwell.efg similarity index 100% rename from catalog/gamefiles/bagwell.efg rename to src/pygambit/catalog/gamefiles/bagwell.efg diff --git a/catalog/gamefiles/bayes1a.efg b/src/pygambit/catalog/gamefiles/bayes1a.efg similarity index 100% rename from catalog/gamefiles/bayes1a.efg rename to src/pygambit/catalog/gamefiles/bayes1a.efg diff --git a/catalog/gamefiles/bayes2a.efg b/src/pygambit/catalog/gamefiles/bayes2a.efg similarity index 100% rename from catalog/gamefiles/bayes2a.efg rename to src/pygambit/catalog/gamefiles/bayes2a.efg diff --git a/catalog/gamefiles/bcp2.efg b/src/pygambit/catalog/gamefiles/bcp2.efg similarity index 100% rename from catalog/gamefiles/bcp2.efg rename to src/pygambit/catalog/gamefiles/bcp2.efg diff --git a/catalog/gamefiles/bcp3.efg b/src/pygambit/catalog/gamefiles/bcp3.efg similarity index 100% rename from catalog/gamefiles/bcp3.efg rename to src/pygambit/catalog/gamefiles/bcp3.efg diff --git a/catalog/gamefiles/bcp4.efg b/src/pygambit/catalog/gamefiles/bcp4.efg similarity index 100% rename from catalog/gamefiles/bcp4.efg rename to src/pygambit/catalog/gamefiles/bcp4.efg diff --git a/catalog/gamefiles/bhg1.efg b/src/pygambit/catalog/gamefiles/bhg1.efg similarity index 100% rename from catalog/gamefiles/bhg1.efg rename to src/pygambit/catalog/gamefiles/bhg1.efg diff --git a/catalog/gamefiles/bhg2.efg b/src/pygambit/catalog/gamefiles/bhg2.efg similarity index 100% rename from catalog/gamefiles/bhg2.efg rename to src/pygambit/catalog/gamefiles/bhg2.efg diff --git a/catalog/gamefiles/bhg3.efg b/src/pygambit/catalog/gamefiles/bhg3.efg similarity index 100% rename from catalog/gamefiles/bhg3.efg rename to src/pygambit/catalog/gamefiles/bhg3.efg diff --git a/catalog/gamefiles/bhg4.efg b/src/pygambit/catalog/gamefiles/bhg4.efg similarity index 100% rename from catalog/gamefiles/bhg4.efg rename to src/pygambit/catalog/gamefiles/bhg4.efg diff --git a/catalog/gamefiles/bhg5.efg b/src/pygambit/catalog/gamefiles/bhg5.efg similarity index 100% rename from catalog/gamefiles/bhg5.efg rename to src/pygambit/catalog/gamefiles/bhg5.efg diff --git a/catalog/gamefiles/caro2.efg b/src/pygambit/catalog/gamefiles/caro2.efg similarity index 100% rename from catalog/gamefiles/caro2.efg rename to src/pygambit/catalog/gamefiles/caro2.efg diff --git a/catalog/gamefiles/cent2.efg b/src/pygambit/catalog/gamefiles/cent2.efg similarity index 100% rename from catalog/gamefiles/cent2.efg rename to src/pygambit/catalog/gamefiles/cent2.efg diff --git a/catalog/gamefiles/cent2.nfg b/src/pygambit/catalog/gamefiles/cent2.nfg similarity index 100% rename from catalog/gamefiles/cent2.nfg rename to src/pygambit/catalog/gamefiles/cent2.nfg diff --git a/catalog/gamefiles/cent3.efg b/src/pygambit/catalog/gamefiles/cent3.efg similarity index 100% rename from catalog/gamefiles/cent3.efg rename to src/pygambit/catalog/gamefiles/cent3.efg diff --git a/catalog/gamefiles/cent4.efg b/src/pygambit/catalog/gamefiles/cent4.efg similarity index 100% rename from catalog/gamefiles/cent4.efg rename to src/pygambit/catalog/gamefiles/cent4.efg diff --git a/catalog/gamefiles/cent6.efg b/src/pygambit/catalog/gamefiles/cent6.efg similarity index 100% rename from catalog/gamefiles/cent6.efg rename to src/pygambit/catalog/gamefiles/cent6.efg diff --git a/catalog/gamefiles/centcs10.efg b/src/pygambit/catalog/gamefiles/centcs10.efg similarity index 100% rename from catalog/gamefiles/centcs10.efg rename to src/pygambit/catalog/gamefiles/centcs10.efg diff --git a/catalog/gamefiles/centcs6.efg b/src/pygambit/catalog/gamefiles/centcs6.efg similarity index 100% rename from catalog/gamefiles/centcs6.efg rename to src/pygambit/catalog/gamefiles/centcs6.efg diff --git a/catalog/gamefiles/condjury.efg b/src/pygambit/catalog/gamefiles/condjury.efg similarity index 100% rename from catalog/gamefiles/condjury.efg rename to src/pygambit/catalog/gamefiles/condjury.efg diff --git a/catalog/gamefiles/coord2.efg b/src/pygambit/catalog/gamefiles/coord2.efg similarity index 100% rename from catalog/gamefiles/coord2.efg rename to src/pygambit/catalog/gamefiles/coord2.efg diff --git a/catalog/gamefiles/coord2.nfg b/src/pygambit/catalog/gamefiles/coord2.nfg similarity index 100% rename from catalog/gamefiles/coord2.nfg rename to src/pygambit/catalog/gamefiles/coord2.nfg diff --git a/catalog/gamefiles/coord2ts.efg b/src/pygambit/catalog/gamefiles/coord2ts.efg similarity index 100% rename from catalog/gamefiles/coord2ts.efg rename to src/pygambit/catalog/gamefiles/coord2ts.efg diff --git a/catalog/gamefiles/coord3.efg b/src/pygambit/catalog/gamefiles/coord3.efg similarity index 100% rename from catalog/gamefiles/coord3.efg rename to src/pygambit/catalog/gamefiles/coord3.efg diff --git a/catalog/gamefiles/coord3.nfg b/src/pygambit/catalog/gamefiles/coord3.nfg similarity index 100% rename from catalog/gamefiles/coord3.nfg rename to src/pygambit/catalog/gamefiles/coord3.nfg diff --git a/catalog/gamefiles/coord333.nfg b/src/pygambit/catalog/gamefiles/coord333.nfg similarity index 100% rename from catalog/gamefiles/coord333.nfg rename to src/pygambit/catalog/gamefiles/coord333.nfg diff --git a/catalog/gamefiles/coord4.efg b/src/pygambit/catalog/gamefiles/coord4.efg similarity index 100% rename from catalog/gamefiles/coord4.efg rename to src/pygambit/catalog/gamefiles/coord4.efg diff --git a/catalog/gamefiles/coord4.nfg b/src/pygambit/catalog/gamefiles/coord4.nfg similarity index 100% rename from catalog/gamefiles/coord4.nfg rename to src/pygambit/catalog/gamefiles/coord4.nfg diff --git a/catalog/gamefiles/cross.efg b/src/pygambit/catalog/gamefiles/cross.efg similarity index 100% rename from catalog/gamefiles/cross.efg rename to src/pygambit/catalog/gamefiles/cross.efg diff --git a/catalog/gamefiles/cs.efg b/src/pygambit/catalog/gamefiles/cs.efg similarity index 100% rename from catalog/gamefiles/cs.efg rename to src/pygambit/catalog/gamefiles/cs.efg diff --git a/catalog/gamefiles/csg1.nfg b/src/pygambit/catalog/gamefiles/csg1.nfg similarity index 100% rename from catalog/gamefiles/csg1.nfg rename to src/pygambit/catalog/gamefiles/csg1.nfg diff --git a/catalog/gamefiles/csg2.nfg b/src/pygambit/catalog/gamefiles/csg2.nfg similarity index 100% rename from catalog/gamefiles/csg2.nfg rename to src/pygambit/catalog/gamefiles/csg2.nfg diff --git a/catalog/gamefiles/csg3.nfg b/src/pygambit/catalog/gamefiles/csg3.nfg similarity index 100% rename from catalog/gamefiles/csg3.nfg rename to src/pygambit/catalog/gamefiles/csg3.nfg diff --git a/catalog/gamefiles/csg4.nfg b/src/pygambit/catalog/gamefiles/csg4.nfg similarity index 100% rename from catalog/gamefiles/csg4.nfg rename to src/pygambit/catalog/gamefiles/csg4.nfg diff --git a/catalog/gamefiles/deg1.nfg b/src/pygambit/catalog/gamefiles/deg1.nfg similarity index 100% rename from catalog/gamefiles/deg1.nfg rename to src/pygambit/catalog/gamefiles/deg1.nfg diff --git a/catalog/gamefiles/deg2.nfg b/src/pygambit/catalog/gamefiles/deg2.nfg similarity index 100% rename from catalog/gamefiles/deg2.nfg rename to src/pygambit/catalog/gamefiles/deg2.nfg diff --git a/catalog/gamefiles/e01.efg b/src/pygambit/catalog/gamefiles/e01.efg similarity index 100% rename from catalog/gamefiles/e01.efg rename to src/pygambit/catalog/gamefiles/e01.efg diff --git a/catalog/gamefiles/e01.nfg b/src/pygambit/catalog/gamefiles/e01.nfg similarity index 100% rename from catalog/gamefiles/e01.nfg rename to src/pygambit/catalog/gamefiles/e01.nfg diff --git a/catalog/gamefiles/e02.efg b/src/pygambit/catalog/gamefiles/e02.efg similarity index 100% rename from catalog/gamefiles/e02.efg rename to src/pygambit/catalog/gamefiles/e02.efg diff --git a/catalog/gamefiles/e02.nfg b/src/pygambit/catalog/gamefiles/e02.nfg similarity index 100% rename from catalog/gamefiles/e02.nfg rename to src/pygambit/catalog/gamefiles/e02.nfg diff --git a/catalog/gamefiles/e03.efg b/src/pygambit/catalog/gamefiles/e03.efg similarity index 100% rename from catalog/gamefiles/e03.efg rename to src/pygambit/catalog/gamefiles/e03.efg diff --git a/catalog/gamefiles/e04.efg b/src/pygambit/catalog/gamefiles/e04.efg similarity index 100% rename from catalog/gamefiles/e04.efg rename to src/pygambit/catalog/gamefiles/e04.efg diff --git a/catalog/gamefiles/e04.nfg b/src/pygambit/catalog/gamefiles/e04.nfg similarity index 100% rename from catalog/gamefiles/e04.nfg rename to src/pygambit/catalog/gamefiles/e04.nfg diff --git a/catalog/gamefiles/e05.efg b/src/pygambit/catalog/gamefiles/e05.efg similarity index 100% rename from catalog/gamefiles/e05.efg rename to src/pygambit/catalog/gamefiles/e05.efg diff --git a/catalog/gamefiles/e06.efg b/src/pygambit/catalog/gamefiles/e06.efg similarity index 100% rename from catalog/gamefiles/e06.efg rename to src/pygambit/catalog/gamefiles/e06.efg diff --git a/catalog/gamefiles/e07.efg b/src/pygambit/catalog/gamefiles/e07.efg similarity index 100% rename from catalog/gamefiles/e07.efg rename to src/pygambit/catalog/gamefiles/e07.efg diff --git a/catalog/gamefiles/e07.nfg b/src/pygambit/catalog/gamefiles/e07.nfg similarity index 100% rename from catalog/gamefiles/e07.nfg rename to src/pygambit/catalog/gamefiles/e07.nfg diff --git a/catalog/gamefiles/e08.efg b/src/pygambit/catalog/gamefiles/e08.efg similarity index 100% rename from catalog/gamefiles/e08.efg rename to src/pygambit/catalog/gamefiles/e08.efg diff --git a/catalog/gamefiles/e09.efg b/src/pygambit/catalog/gamefiles/e09.efg similarity index 100% rename from catalog/gamefiles/e09.efg rename to src/pygambit/catalog/gamefiles/e09.efg diff --git a/catalog/gamefiles/e10.efg b/src/pygambit/catalog/gamefiles/e10.efg similarity index 100% rename from catalog/gamefiles/e10.efg rename to src/pygambit/catalog/gamefiles/e10.efg diff --git a/catalog/gamefiles/e10a.efg b/src/pygambit/catalog/gamefiles/e10a.efg similarity index 100% rename from catalog/gamefiles/e10a.efg rename to src/pygambit/catalog/gamefiles/e10a.efg diff --git a/catalog/gamefiles/e13.efg b/src/pygambit/catalog/gamefiles/e13.efg similarity index 100% rename from catalog/gamefiles/e13.efg rename to src/pygambit/catalog/gamefiles/e13.efg diff --git a/catalog/gamefiles/e16.efg b/src/pygambit/catalog/gamefiles/e16.efg similarity index 100% rename from catalog/gamefiles/e16.efg rename to src/pygambit/catalog/gamefiles/e16.efg diff --git a/catalog/gamefiles/e17.efg b/src/pygambit/catalog/gamefiles/e17.efg similarity index 100% rename from catalog/gamefiles/e17.efg rename to src/pygambit/catalog/gamefiles/e17.efg diff --git a/catalog/gamefiles/e18.efg b/src/pygambit/catalog/gamefiles/e18.efg similarity index 100% rename from catalog/gamefiles/e18.efg rename to src/pygambit/catalog/gamefiles/e18.efg diff --git a/catalog/gamefiles/g1.efg b/src/pygambit/catalog/gamefiles/g1.efg similarity index 100% rename from catalog/gamefiles/g1.efg rename to src/pygambit/catalog/gamefiles/g1.efg diff --git a/catalog/gamefiles/g1.nfg b/src/pygambit/catalog/gamefiles/g1.nfg similarity index 100% rename from catalog/gamefiles/g1.nfg rename to src/pygambit/catalog/gamefiles/g1.nfg diff --git a/catalog/gamefiles/g2.efg b/src/pygambit/catalog/gamefiles/g2.efg similarity index 100% rename from catalog/gamefiles/g2.efg rename to src/pygambit/catalog/gamefiles/g2.efg diff --git a/catalog/gamefiles/g2.nfg b/src/pygambit/catalog/gamefiles/g2.nfg similarity index 100% rename from catalog/gamefiles/g2.nfg rename to src/pygambit/catalog/gamefiles/g2.nfg diff --git a/catalog/gamefiles/g3.efg b/src/pygambit/catalog/gamefiles/g3.efg similarity index 100% rename from catalog/gamefiles/g3.efg rename to src/pygambit/catalog/gamefiles/g3.efg diff --git a/catalog/gamefiles/g3.nfg b/src/pygambit/catalog/gamefiles/g3.nfg similarity index 100% rename from catalog/gamefiles/g3.nfg rename to src/pygambit/catalog/gamefiles/g3.nfg diff --git a/catalog/gamefiles/holdout.efg b/src/pygambit/catalog/gamefiles/holdout.efg similarity index 100% rename from catalog/gamefiles/holdout.efg rename to src/pygambit/catalog/gamefiles/holdout.efg diff --git a/catalog/gamefiles/holdout7.efg b/src/pygambit/catalog/gamefiles/holdout7.efg similarity index 100% rename from catalog/gamefiles/holdout7.efg rename to src/pygambit/catalog/gamefiles/holdout7.efg diff --git a/catalog/gamefiles/hs1.efg b/src/pygambit/catalog/gamefiles/hs1.efg similarity index 100% rename from catalog/gamefiles/hs1.efg rename to src/pygambit/catalog/gamefiles/hs1.efg diff --git a/catalog/gamefiles/jury_mr.efg b/src/pygambit/catalog/gamefiles/jury_mr.efg similarity index 100% rename from catalog/gamefiles/jury_mr.efg rename to src/pygambit/catalog/gamefiles/jury_mr.efg diff --git a/catalog/gamefiles/jury_un.efg b/src/pygambit/catalog/gamefiles/jury_un.efg similarity index 100% rename from catalog/gamefiles/jury_un.efg rename to src/pygambit/catalog/gamefiles/jury_un.efg diff --git a/catalog/gamefiles/km1.efg b/src/pygambit/catalog/gamefiles/km1.efg similarity index 100% rename from catalog/gamefiles/km1.efg rename to src/pygambit/catalog/gamefiles/km1.efg diff --git a/catalog/gamefiles/km2.efg b/src/pygambit/catalog/gamefiles/km2.efg similarity index 100% rename from catalog/gamefiles/km2.efg rename to src/pygambit/catalog/gamefiles/km2.efg diff --git a/catalog/gamefiles/km3.efg b/src/pygambit/catalog/gamefiles/km3.efg similarity index 100% rename from catalog/gamefiles/km3.efg rename to src/pygambit/catalog/gamefiles/km3.efg diff --git a/catalog/gamefiles/km6.efg b/src/pygambit/catalog/gamefiles/km6.efg similarity index 100% rename from catalog/gamefiles/km6.efg rename to src/pygambit/catalog/gamefiles/km6.efg diff --git a/catalog/gamefiles/loopback.nfg b/src/pygambit/catalog/gamefiles/loopback.nfg similarity index 100% rename from catalog/gamefiles/loopback.nfg rename to src/pygambit/catalog/gamefiles/loopback.nfg diff --git a/catalog/gamefiles/mixdom.nfg b/src/pygambit/catalog/gamefiles/mixdom.nfg similarity index 100% rename from catalog/gamefiles/mixdom.nfg rename to src/pygambit/catalog/gamefiles/mixdom.nfg diff --git a/catalog/gamefiles/mixdom2.nfg b/src/pygambit/catalog/gamefiles/mixdom2.nfg similarity index 100% rename from catalog/gamefiles/mixdom2.nfg rename to src/pygambit/catalog/gamefiles/mixdom2.nfg diff --git a/catalog/gamefiles/montyhal.efg b/src/pygambit/catalog/gamefiles/montyhal.efg similarity index 100% rename from catalog/gamefiles/montyhal.efg rename to src/pygambit/catalog/gamefiles/montyhal.efg diff --git a/catalog/gamefiles/my_2-1.efg b/src/pygambit/catalog/gamefiles/my_2-1.efg similarity index 100% rename from catalog/gamefiles/my_2-1.efg rename to src/pygambit/catalog/gamefiles/my_2-1.efg diff --git a/catalog/gamefiles/my_2-4.efg b/src/pygambit/catalog/gamefiles/my_2-4.efg similarity index 100% rename from catalog/gamefiles/my_2-4.efg rename to src/pygambit/catalog/gamefiles/my_2-4.efg diff --git a/catalog/gamefiles/my_2-8.efg b/src/pygambit/catalog/gamefiles/my_2-8.efg similarity index 100% rename from catalog/gamefiles/my_2-8.efg rename to src/pygambit/catalog/gamefiles/my_2-8.efg diff --git a/catalog/gamefiles/my_3-3a.efg b/src/pygambit/catalog/gamefiles/my_3-3a.efg similarity index 100% rename from catalog/gamefiles/my_3-3a.efg rename to src/pygambit/catalog/gamefiles/my_3-3a.efg diff --git a/catalog/gamefiles/my_3-3b.efg b/src/pygambit/catalog/gamefiles/my_3-3b.efg similarity index 100% rename from catalog/gamefiles/my_3-3b.efg rename to src/pygambit/catalog/gamefiles/my_3-3b.efg diff --git a/catalog/gamefiles/my_3-3c.efg b/src/pygambit/catalog/gamefiles/my_3-3c.efg similarity index 100% rename from catalog/gamefiles/my_3-3c.efg rename to src/pygambit/catalog/gamefiles/my_3-3c.efg diff --git a/catalog/gamefiles/my_3-3d.efg b/src/pygambit/catalog/gamefiles/my_3-3d.efg similarity index 100% rename from catalog/gamefiles/my_3-3d.efg rename to src/pygambit/catalog/gamefiles/my_3-3d.efg diff --git a/catalog/gamefiles/my_3-3e.efg b/src/pygambit/catalog/gamefiles/my_3-3e.efg similarity index 100% rename from catalog/gamefiles/my_3-3e.efg rename to src/pygambit/catalog/gamefiles/my_3-3e.efg diff --git a/catalog/gamefiles/my_3-4.efg b/src/pygambit/catalog/gamefiles/my_3-4.efg similarity index 100% rename from catalog/gamefiles/my_3-4.efg rename to src/pygambit/catalog/gamefiles/my_3-4.efg diff --git a/catalog/gamefiles/myerson.efg b/src/pygambit/catalog/gamefiles/myerson.efg similarity index 100% rename from catalog/gamefiles/myerson.efg rename to src/pygambit/catalog/gamefiles/myerson.efg diff --git a/catalog/gamefiles/myerson_fig_4_2.efg b/src/pygambit/catalog/gamefiles/myerson_fig_4_2.efg similarity index 100% rename from catalog/gamefiles/myerson_fig_4_2.efg rename to src/pygambit/catalog/gamefiles/myerson_fig_4_2.efg diff --git a/catalog/gamefiles/nim.efg b/src/pygambit/catalog/gamefiles/nim.efg similarity index 100% rename from catalog/gamefiles/nim.efg rename to src/pygambit/catalog/gamefiles/nim.efg diff --git a/catalog/gamefiles/nim7.efg b/src/pygambit/catalog/gamefiles/nim7.efg similarity index 100% rename from catalog/gamefiles/nim7.efg rename to src/pygambit/catalog/gamefiles/nim7.efg diff --git a/catalog/gamefiles/oneill.nfg b/src/pygambit/catalog/gamefiles/oneill.nfg similarity index 100% rename from catalog/gamefiles/oneill.nfg rename to src/pygambit/catalog/gamefiles/oneill.nfg diff --git a/catalog/gamefiles/palf.efg b/src/pygambit/catalog/gamefiles/palf.efg similarity index 100% rename from catalog/gamefiles/palf.efg rename to src/pygambit/catalog/gamefiles/palf.efg diff --git a/catalog/gamefiles/palf2.efg b/src/pygambit/catalog/gamefiles/palf2.efg similarity index 100% rename from catalog/gamefiles/palf2.efg rename to src/pygambit/catalog/gamefiles/palf2.efg diff --git a/catalog/gamefiles/palf3.efg b/src/pygambit/catalog/gamefiles/palf3.efg similarity index 100% rename from catalog/gamefiles/palf3.efg rename to src/pygambit/catalog/gamefiles/palf3.efg diff --git a/catalog/gamefiles/pbride.efg b/src/pygambit/catalog/gamefiles/pbride.efg similarity index 100% rename from catalog/gamefiles/pbride.efg rename to src/pygambit/catalog/gamefiles/pbride.efg diff --git a/catalog/gamefiles/pd.nfg b/src/pygambit/catalog/gamefiles/pd.nfg similarity index 100% rename from catalog/gamefiles/pd.nfg rename to src/pygambit/catalog/gamefiles/pd.nfg diff --git a/catalog/gamefiles/perfect1.nfg b/src/pygambit/catalog/gamefiles/perfect1.nfg similarity index 100% rename from catalog/gamefiles/perfect1.nfg rename to src/pygambit/catalog/gamefiles/perfect1.nfg diff --git a/catalog/gamefiles/perfect2.nfg b/src/pygambit/catalog/gamefiles/perfect2.nfg similarity index 100% rename from catalog/gamefiles/perfect2.nfg rename to src/pygambit/catalog/gamefiles/perfect2.nfg diff --git a/catalog/gamefiles/perfect3.nfg b/src/pygambit/catalog/gamefiles/perfect3.nfg similarity index 100% rename from catalog/gamefiles/perfect3.nfg rename to src/pygambit/catalog/gamefiles/perfect3.nfg diff --git a/catalog/gamefiles/poker.efg b/src/pygambit/catalog/gamefiles/poker.efg similarity index 100% rename from catalog/gamefiles/poker.efg rename to src/pygambit/catalog/gamefiles/poker.efg diff --git a/catalog/gamefiles/poker.nfg b/src/pygambit/catalog/gamefiles/poker.nfg similarity index 100% rename from catalog/gamefiles/poker.nfg rename to src/pygambit/catalog/gamefiles/poker.nfg diff --git a/catalog/gamefiles/poker2.efg b/src/pygambit/catalog/gamefiles/poker2.efg similarity index 100% rename from catalog/gamefiles/poker2.efg rename to src/pygambit/catalog/gamefiles/poker2.efg diff --git a/catalog/gamefiles/pvw.efg b/src/pygambit/catalog/gamefiles/pvw.efg similarity index 100% rename from catalog/gamefiles/pvw.efg rename to src/pygambit/catalog/gamefiles/pvw.efg diff --git a/catalog/gamefiles/pvw2.efg b/src/pygambit/catalog/gamefiles/pvw2.efg similarity index 100% rename from catalog/gamefiles/pvw2.efg rename to src/pygambit/catalog/gamefiles/pvw2.efg diff --git a/catalog/gamefiles/sh3.efg b/src/pygambit/catalog/gamefiles/sh3.efg similarity index 100% rename from catalog/gamefiles/sh3.efg rename to src/pygambit/catalog/gamefiles/sh3.efg diff --git a/catalog/gamefiles/sh3.nfg b/src/pygambit/catalog/gamefiles/sh3.nfg similarity index 100% rename from catalog/gamefiles/sh3.nfg rename to src/pygambit/catalog/gamefiles/sh3.nfg diff --git a/catalog/gamefiles/spence.efg b/src/pygambit/catalog/gamefiles/spence.efg similarity index 100% rename from catalog/gamefiles/spence.efg rename to src/pygambit/catalog/gamefiles/spence.efg diff --git a/catalog/gamefiles/stengel.nfg b/src/pygambit/catalog/gamefiles/stengel.nfg similarity index 100% rename from catalog/gamefiles/stengel.nfg rename to src/pygambit/catalog/gamefiles/stengel.nfg diff --git a/catalog/gamefiles/sww1.efg b/src/pygambit/catalog/gamefiles/sww1.efg similarity index 100% rename from catalog/gamefiles/sww1.efg rename to src/pygambit/catalog/gamefiles/sww1.efg diff --git a/catalog/gamefiles/sww1.nfg b/src/pygambit/catalog/gamefiles/sww1.nfg similarity index 100% rename from catalog/gamefiles/sww1.nfg rename to src/pygambit/catalog/gamefiles/sww1.nfg diff --git a/catalog/gamefiles/sww2.efg b/src/pygambit/catalog/gamefiles/sww2.efg similarity index 100% rename from catalog/gamefiles/sww2.efg rename to src/pygambit/catalog/gamefiles/sww2.efg diff --git a/catalog/gamefiles/sww3.efg b/src/pygambit/catalog/gamefiles/sww3.efg similarity index 100% rename from catalog/gamefiles/sww3.efg rename to src/pygambit/catalog/gamefiles/sww3.efg diff --git a/catalog/gamefiles/tim.efg b/src/pygambit/catalog/gamefiles/tim.efg similarity index 100% rename from catalog/gamefiles/tim.efg rename to src/pygambit/catalog/gamefiles/tim.efg diff --git a/catalog/gamefiles/todd1.nfg b/src/pygambit/catalog/gamefiles/todd1.nfg similarity index 100% rename from catalog/gamefiles/todd1.nfg rename to src/pygambit/catalog/gamefiles/todd1.nfg diff --git a/catalog/gamefiles/todd2.nfg b/src/pygambit/catalog/gamefiles/todd2.nfg similarity index 100% rename from catalog/gamefiles/todd2.nfg rename to src/pygambit/catalog/gamefiles/todd2.nfg diff --git a/catalog/gamefiles/todd3.nfg b/src/pygambit/catalog/gamefiles/todd3.nfg similarity index 100% rename from catalog/gamefiles/todd3.nfg rename to src/pygambit/catalog/gamefiles/todd3.nfg diff --git a/catalog/gamefiles/ttt.efg b/src/pygambit/catalog/gamefiles/ttt.efg similarity index 100% rename from catalog/gamefiles/ttt.efg rename to src/pygambit/catalog/gamefiles/ttt.efg diff --git a/catalog/gamefiles/vd.efg b/src/pygambit/catalog/gamefiles/vd.efg similarity index 100% rename from catalog/gamefiles/vd.efg rename to src/pygambit/catalog/gamefiles/vd.efg diff --git a/catalog/gamefiles/vd.nfg b/src/pygambit/catalog/gamefiles/vd.nfg similarity index 100% rename from catalog/gamefiles/vd.nfg rename to src/pygambit/catalog/gamefiles/vd.nfg diff --git a/catalog/gamefiles/w_ex1.efg b/src/pygambit/catalog/gamefiles/w_ex1.efg similarity index 100% rename from catalog/gamefiles/w_ex1.efg rename to src/pygambit/catalog/gamefiles/w_ex1.efg diff --git a/catalog/gamefiles/w_ex2.efg b/src/pygambit/catalog/gamefiles/w_ex2.efg similarity index 100% rename from catalog/gamefiles/w_ex2.efg rename to src/pygambit/catalog/gamefiles/w_ex2.efg diff --git a/catalog/gamefiles/wilson1.efg b/src/pygambit/catalog/gamefiles/wilson1.efg similarity index 100% rename from catalog/gamefiles/wilson1.efg rename to src/pygambit/catalog/gamefiles/wilson1.efg diff --git a/catalog/gamefiles/wink3.nfg b/src/pygambit/catalog/gamefiles/wink3.nfg similarity index 100% rename from catalog/gamefiles/wink3.nfg rename to src/pygambit/catalog/gamefiles/wink3.nfg diff --git a/catalog/gamefiles/winkels.nfg b/src/pygambit/catalog/gamefiles/winkels.nfg similarity index 100% rename from catalog/gamefiles/winkels.nfg rename to src/pygambit/catalog/gamefiles/winkels.nfg diff --git a/catalog/gamefiles/work1.efg b/src/pygambit/catalog/gamefiles/work1.efg similarity index 100% rename from catalog/gamefiles/work1.efg rename to src/pygambit/catalog/gamefiles/work1.efg diff --git a/catalog/gamefiles/work2.efg b/src/pygambit/catalog/gamefiles/work2.efg similarity index 100% rename from catalog/gamefiles/work2.efg rename to src/pygambit/catalog/gamefiles/work2.efg diff --git a/catalog/gamefiles/work3.efg b/src/pygambit/catalog/gamefiles/work3.efg similarity index 100% rename from catalog/gamefiles/work3.efg rename to src/pygambit/catalog/gamefiles/work3.efg diff --git a/catalog/gamefiles/yamamoto.nfg b/src/pygambit/catalog/gamefiles/yamamoto.nfg similarity index 100% rename from catalog/gamefiles/yamamoto.nfg rename to src/pygambit/catalog/gamefiles/yamamoto.nfg diff --git a/catalog/gamefiles/zero.nfg b/src/pygambit/catalog/gamefiles/zero.nfg similarity index 100% rename from catalog/gamefiles/zero.nfg rename to src/pygambit/catalog/gamefiles/zero.nfg From 07b1beea68c8d1f2e12502503728d8fa637cb5cb Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 14:55:10 +0000 Subject: [PATCH 008/170] enforce gamefile and gametype on subclasses --- src/pygambit/catalog/catalog.py | 41 +++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index fef3c339c..58198d1d5 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,6 +1,6 @@ from pathlib import Path -from pygambit import read_efg, read_nfg +from pygambit import Game, read_efg, read_nfg _CATALOG_DIR = Path(__file__).parent _GAMEFILES_DIR = _CATALOG_DIR / "gamefiles" @@ -12,17 +12,44 @@ class CatalogGame: This class serves as a template for specific games in the catalog. Calling any subclass will return an instance of the corresponding game. """ - def __new__(cls): - raise NotImplementedError("This method should be implemented by subclasses.") + # Subclasses must define these + game_file: str | None = None + game_type: str | None = None # 'nfg' or 'efg' + + def __new__(cls) -> Game: + if cls.game_file is None: + raise NotImplementedError(f"{cls.__name__} must define 'game_file'") + if cls.game_type is None: + raise NotImplementedError(f"{cls.__name__} must define 'game_type'") + + # Load the appropriate game type + file_path = _GAMEFILES_DIR / cls.game_file + if cls.game_type == "nfg": + game = read_nfg(str(file_path)) + elif cls.game_type == "efg": + game = read_efg(str(file_path)) + else: + raise ValueError(f"game_type must be 'nfg' or 'efg', got '{cls.game_type}'") + + return game + + def __init_subclass__(cls, **kwargs): + """Validate that subclasses define required attributes.""" + super().__init_subclass__(**kwargs) + # This runs when a subclass is defined + if not hasattr(cls, "game_file") or cls.game_file is None: + raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") + if not hasattr(cls, "game_type") or cls.game_type is None: + raise TypeError(f"{cls.__name__} must define 'game_type' class attribute") class PrisonersDilemma(CatalogGame): """Prisoner's Dilemma game.""" - def __new__(cls): - return read_nfg(str(_GAMEFILES_DIR / "pd.nfg")) + game_file = "pd.nfg" + game_type = "nfg" class TwoStageMatchingPennies(CatalogGame): """Two-Stage Matching Pennies game.""" - def __new__(cls): - return read_efg(str(_GAMEFILES_DIR / "2smp.efg")) + game_file = "2smp.efg" + game_type = "efg" From d11ada75d5d050dae464cf1a0189c2b3e0a4090d Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 14:58:55 +0000 Subject: [PATCH 009/170] don't enforce game_type as this can be inferred from the file extension --- src/pygambit/catalog/catalog.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 58198d1d5..e27b6e2e5 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -14,13 +14,11 @@ class CatalogGame: """ # Subclasses must define these game_file: str | None = None - game_type: str | None = None # 'nfg' or 'efg' def __new__(cls) -> Game: if cls.game_file is None: raise NotImplementedError(f"{cls.__name__} must define 'game_file'") - if cls.game_type is None: - raise NotImplementedError(f"{cls.__name__} must define 'game_type'") + cls.game_type = cls.game_file.split(".")[-1] # infer game type from file extension # Load the appropriate game type file_path = _GAMEFILES_DIR / cls.game_file @@ -29,7 +27,7 @@ def __new__(cls) -> Game: elif cls.game_type == "efg": game = read_efg(str(file_path)) else: - raise ValueError(f"game_type must be 'nfg' or 'efg', got '{cls.game_type}'") + raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{cls.game_type}'") return game @@ -39,17 +37,13 @@ def __init_subclass__(cls, **kwargs): # This runs when a subclass is defined if not hasattr(cls, "game_file") or cls.game_file is None: raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") - if not hasattr(cls, "game_type") or cls.game_type is None: - raise TypeError(f"{cls.__name__} must define 'game_type' class attribute") class PrisonersDilemma(CatalogGame): """Prisoner's Dilemma game.""" game_file = "pd.nfg" - game_type = "nfg" class TwoStageMatchingPennies(CatalogGame): """Two-Stage Matching Pennies game.""" game_file = "2smp.efg" - game_type = "efg" From 828eabda64385b4790f616e0298b09cd3921ed60 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 15:13:04 +0000 Subject: [PATCH 010/170] add a test game to the catalog defined directly in code --- src/pygambit/catalog/__init__.py | 4 ++-- src/pygambit/catalog/catalog.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index a57c5a20f..ce49d8546 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,3 +1,3 @@ -from .catalog import PrisonersDilemma, TwoStageMatchingPennies +from .catalog import PrisonersDilemma, PrisonersDilemmaTestgame, TwoStageMatchingPennies -__all__ = ["PrisonersDilemma", "TwoStageMatchingPennies"] +__all__ = ["PrisonersDilemma", "TwoStageMatchingPennies", "PrisonersDilemmaTestgame"] diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index e27b6e2e5..96206089e 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,5 +1,7 @@ from pathlib import Path +import numpy as np + from pygambit import Game, read_efg, read_nfg _CATALOG_DIR = Path(__file__).parent @@ -12,6 +14,17 @@ class CatalogGame: This class serves as a template for specific games in the catalog. Calling any subclass will return an instance of the corresponding game. """ + + def __new__(cls) -> Game: + raise NotImplementedError("Subclasses must implement __new__ method") + + +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. + """ # Subclasses must define these game_file: str | None = None @@ -39,11 +52,25 @@ def __init_subclass__(cls, **kwargs): raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") -class PrisonersDilemma(CatalogGame): +class PrisonersDilemma(CatalogGameFromFile): """Prisoner's Dilemma game.""" game_file = "pd.nfg" -class TwoStageMatchingPennies(CatalogGame): +class TwoStageMatchingPennies(CatalogGameFromFile): """Two-Stage Matching Pennies game.""" game_file = "2smp.efg" + + +class PrisonersDilemmaTestgame(CatalogGame): + """A simple test game based on the Prisoner's Dilemma.""" + def __new__(cls) -> Game: + player1_payoffs = np.array([[-1, -3], [0, -2]]) + player2_payoffs = np.transpose(player1_payoffs) + + g1 = Game.from_arrays( + player1_payoffs, + player2_payoffs, + title="Test Prisoner's Dilemma" + ) + return g1 From faeef279358ed6e2a4ba97a54610954b95884256 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 15:31:56 +0000 Subject: [PATCH 011/170] enhance CatalogGame and CatalogGameFromFile to extract metadata without needing to instantiate the full game object --- src/pygambit/catalog/catalog.py | 39 ++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 96206089e..fd0322ee1 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -18,6 +18,12 @@ class CatalogGame: def __new__(cls) -> Game: raise NotImplementedError("Subclasses must implement __new__ method") + @classmethod + def _extract_metadata_from_game(cls, game: Game) -> None: + """Extract metadata from the game and set as class attributes.""" + cls.title = game.title + cls.num_players = len(game.players) + class CatalogGameFromFile(CatalogGame): """ @@ -27,30 +33,42 @@ class CatalogGameFromFile(CatalogGame): """ # Subclasses must define these game_file: str | None = None + _cached_game: Game | None = None def __new__(cls) -> Game: + # Return cached game if available, otherwise load it + if cls._cached_game is None: + cls._cached_game = cls._load_game() + # Return a fresh instance (not the cached one) + return cls._load_game() + + @classmethod + def _load_game(cls) -> Game: + """Load the game from file.""" if cls.game_file is None: raise NotImplementedError(f"{cls.__name__} must define 'game_file'") - cls.game_type = cls.game_file.split(".")[-1] # infer game type from file extension - # Load the appropriate game type + cls.game_type = cls.game_file.split(".")[-1] file_path = _GAMEFILES_DIR / cls.game_file + if cls.game_type == "nfg": - game = read_nfg(str(file_path)) + return read_nfg(str(file_path)) elif cls.game_type == "efg": - game = read_efg(str(file_path)) + return read_efg(str(file_path)) else: raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{cls.game_type}'") - return game - def __init_subclass__(cls, **kwargs): - """Validate that subclasses define required attributes.""" + """Validate and extract metadata when subclass is defined.""" super().__init_subclass__(**kwargs) - # This runs when a subclass is defined + if not hasattr(cls, "game_file") or cls.game_file is None: raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") + # Load game and extract metadata immediately when class is defined + cls._cached_game = cls._load_game() + cls._extract_metadata_from_game(cls._cached_game) + class PrisonersDilemma(CatalogGameFromFile): """Prisoner's Dilemma game.""" @@ -64,6 +82,7 @@ class TwoStageMatchingPennies(CatalogGameFromFile): class PrisonersDilemmaTestgame(CatalogGame): """A simple test game based on the Prisoner's Dilemma.""" + def __new__(cls) -> Game: player1_payoffs = np.array([[-1, -3], [0, -2]]) player2_payoffs = np.transpose(player1_payoffs) @@ -73,4 +92,8 @@ def __new__(cls) -> Game: player2_payoffs, title="Test Prisoner's Dilemma" ) + + # Extract metadata from game and set as class attributes + cls._extract_metadata_from_game(g1) + return g1 From 8c49b31c84a25b46397a9b5225e36d10bb729997 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 15:36:10 +0000 Subject: [PATCH 012/170] refactor CatalogGame to extract metadata during subclass initialization --- src/pygambit/catalog/catalog.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index fd0322ee1..7acfe647a 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -24,6 +24,22 @@ def _extract_metadata_from_game(cls, game: Game) -> None: cls.title = game.title cls.num_players = len(game.players) + 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 + + # For non-file-based games, create a temporary instance to extract metadata + try: + temp_game = cls.__new__(cls) + cls._extract_metadata_from_game(temp_game) + except NotImplementedError: + # Base class, skip + pass + class CatalogGameFromFile(CatalogGame): """ @@ -93,7 +109,4 @@ def __new__(cls) -> Game: title="Test Prisoner's Dilemma" ) - # Extract metadata from game and set as class attributes - cls._extract_metadata_from_game(g1) - return g1 From 8cc6b5b4a2b4ef8f46009bb8506d6f1eb587fc42 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 16:19:03 +0000 Subject: [PATCH 013/170] include metadata attributes for games --- src/pygambit/catalog/catalog.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 7acfe647a..6e9913e9e 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -15,6 +15,12 @@ class CatalogGame: Calling any subclass will return an instance of the corresponding game. """ + title: str | None = None + num_players: int | None = None + game_type: str | None = None + description: str | None = None + citation: str | None = None + def __new__(cls) -> Game: raise NotImplementedError("Subclasses must implement __new__ method") @@ -47,7 +53,7 @@ class CatalogGameFromFile(CatalogGame): This class serves as a template for specific games in the catalog. Calling any subclass will return an instance of the corresponding game. """ - # Subclasses must define these + game_file: str | None = None _cached_game: Game | None = None @@ -87,17 +93,22 @@ def __init_subclass__(cls, **kwargs): class PrisonersDilemma(CatalogGameFromFile): - """Prisoner's Dilemma game.""" game_file = "pd.nfg" + description = "Prisoner's Dilemma game." + citation = "Example citation for Prisoner's Dilemma." class TwoStageMatchingPennies(CatalogGameFromFile): - """Two-Stage Matching Pennies game.""" game_file = "2smp.efg" + description = "Two-Stage Matching Pennies game." + citation = "Example citation for Two-Stage Matching Pennies." class PrisonersDilemmaTestgame(CatalogGame): - """A simple test game based on the Prisoner's Dilemma.""" + title = "Test Prisoner's Dilemma" + game_type = "nfg" + description = "A simple test game based on the Prisoner's Dilemma." + citation = "Example citation for Test Prisoner's Dilemma." def __new__(cls) -> Game: player1_payoffs = np.array([[-1, -3], [0, -2]]) @@ -106,7 +117,7 @@ def __new__(cls) -> Game: g1 = Game.from_arrays( player1_payoffs, player2_payoffs, - title="Test Prisoner's Dilemma" + title=cls.title, ) return g1 From 70f176ff4fbf1b07340c66ec32e446926f751245 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 16:22:50 +0000 Subject: [PATCH 014/170] don't allow None for CatalogGame attributes --- src/pygambit/catalog/catalog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 6e9913e9e..3d6b6c72a 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -15,11 +15,11 @@ class CatalogGame: Calling any subclass will return an instance of the corresponding game. """ - title: str | None = None - num_players: int | None = None - game_type: str | None = None - description: str | None = None - citation: str | None = None + title: str + num_players: int + game_type: str + description: str + citation: str def __new__(cls) -> Game: raise NotImplementedError("Subclasses must implement __new__ method") @@ -54,7 +54,7 @@ class CatalogGameFromFile(CatalogGame): Calling any subclass will return an instance of the corresponding game. """ - game_file: str | None = None + game_file: str _cached_game: Game | None = None def __new__(cls) -> Game: From f5b7fcb651f39f7df7597decf2c012b7949ca050 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 16:28:27 +0000 Subject: [PATCH 015/170] organize catalog game classes with section headers for better readability --- src/pygambit/catalog/catalog.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 3d6b6c72a..7b53eb1a5 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -92,6 +92,11 @@ def __init_subclass__(cls, **kwargs): cls._extract_metadata_from_game(cls._cached_game) +############################ +# Catalog games from files # +############################ + + class PrisonersDilemma(CatalogGameFromFile): game_file = "pd.nfg" description = "Prisoner's Dilemma game." @@ -104,6 +109,11 @@ class TwoStageMatchingPennies(CatalogGameFromFile): citation = "Example citation for Two-Stage Matching Pennies." +########################################## +# Catalog games defined programmatically # +########################################## + + class PrisonersDilemmaTestgame(CatalogGame): title = "Test Prisoner's Dilemma" game_type = "nfg" From 37b1e2ba39ea03bb799e7fba736743b36345cc39 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 16:38:45 +0000 Subject: [PATCH 016/170] implement gbt.catalog.games() --- doc/tutorials/01_quickstart.ipynb | 21 ++++++++++++++++----- src/pygambit/catalog/__init__.py | 14 ++++++++++++-- src/pygambit/catalog/catalog.py | 14 ++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index c6a9a6dc6..499f221b1 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -431,12 +431,23 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "701aa52a", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "['PrisonersDilemma', 'TwoStageMatchingPennies', 'PrisonersDilemmaTestgame']" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# gambit.catalog.games()" + "gbt.catalog.games()" ] }, { @@ -449,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -463,7 +474,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index ce49d8546..dd758f759 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,3 +1,13 @@ -from .catalog import PrisonersDilemma, PrisonersDilemmaTestgame, TwoStageMatchingPennies +from .catalog import ( + PrisonersDilemma, + PrisonersDilemmaTestgame, + TwoStageMatchingPennies, + games, +) -__all__ = ["PrisonersDilemma", "TwoStageMatchingPennies", "PrisonersDilemmaTestgame"] +__all__ = [ + "games", + "PrisonersDilemma", + "TwoStageMatchingPennies", + "PrisonersDilemmaTestgame" +] diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 7b53eb1a5..3f2201807 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -92,6 +92,20 @@ def __init_subclass__(cls, **kwargs): cls._extract_metadata_from_game(cls._cached_game) +def games() -> list[str]: + """Return a list of all catalog game names.""" + def get_all_subclasses(cls): + """Recursively get all subclasses.""" + all_subclasses = [] + for subclass in cls.__subclasses__(): + if subclass.__name__ not in ["CatalogGameFromFile"]: + all_subclasses.append(subclass.__name__) + all_subclasses.extend(get_all_subclasses(subclass)) + return all_subclasses + + return get_all_subclasses(CatalogGame) + + ############################ # Catalog games from files # ############################ From a9dc597dfecd9f2d7058ac96387eb463a2908cad Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 16:44:22 +0000 Subject: [PATCH 017/170] dynamically import all catalog games and update __all__ accordingly --- src/pygambit/catalog/__init__.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index dd758f759..619e73f41 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,13 +1,16 @@ -from .catalog import ( - PrisonersDilemma, - PrisonersDilemmaTestgame, - TwoStageMatchingPennies, - games, -) - -__all__ = [ - "games", - "PrisonersDilemma", - "TwoStageMatchingPennies", - "PrisonersDilemmaTestgame" -] +from .catalog import games + +# Dynamically import all catalog games +_all_games = games() +_game_classes = {} + +for game_name in _all_games: + # Import each game class from catalog module + from . import catalog + _game_classes[game_name] = getattr(catalog, game_name) + +# Add to module namespace +globals().update(_game_classes) + +# Build __all__ dynamically +__all__ = ["games"] + list(_all_games) # type: ignore[assignment] From a314e30d10743f4f9335691a174dcf09b19456e7 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 7 Jan 2026 17:03:56 +0000 Subject: [PATCH 018/170] add filter for game type in catalog.games() --- doc/tutorials/01_quickstart.ipynb | 8 ++++---- src/pygambit/catalog/catalog.py | 26 ++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 499f221b1..8fdf00b0d 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -431,23 +431,23 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "701aa52a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "['PrisonersDilemma', 'TwoStageMatchingPennies', 'PrisonersDilemmaTestgame']" + "['PrisonersDilemma', 'PrisonersDilemmaTestgame']" ] }, - "execution_count": 2, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "gbt.catalog.games()" + "gbt.catalog.games(game_type=\"nfg\")" ] }, { diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 3f2201807..37ba8049e 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,4 +1,5 @@ from pathlib import Path +from typing import Literal import numpy as np @@ -17,7 +18,7 @@ class CatalogGame: title: str num_players: int - game_type: str + game_type: Literal["nfg", "efg"] description: str citation: str @@ -92,13 +93,30 @@ def __init_subclass__(cls, **kwargs): cls._extract_metadata_from_game(cls._cached_game) -def games() -> list[str]: - """Return a list of all catalog game names.""" +def games(game_type: Literal["all", "nfg", "efg"] = "all") -> list[str]: + """ + Return a list of catalog game names. + + Parameters + ---------- + game_type : {"all", "nfg", "efg"}, default "all" + Filter games by type: + - "all": return all games + - "nfg": return only normal-form (strategic) games + - "efg": return only extensive-form games + + Returns + ------- + list[str] + List of game class names matching the specified type. + """ def get_all_subclasses(cls): """Recursively get all subclasses.""" all_subclasses = [] for subclass in cls.__subclasses__(): - if subclass.__name__ not in ["CatalogGameFromFile"]: + if subclass.__name__ not in ["CatalogGameFromFile"] and ( + game_type == "all" or subclass.game_type == game_type + ): all_subclasses.append(subclass.__name__) all_subclasses.extend(get_all_subclasses(subclass)) return all_subclasses From e17687744ea8126e1a6d2fd5e2a5851a8d06f466 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 09:58:07 +0000 Subject: [PATCH 019/170] add filter for number of players --- doc/tutorials/01_quickstart.ipynb | 63 +++++++++++++------------------ src/pygambit/catalog/catalog.py | 9 ++++- 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 8fdf00b0d..3407adf0c 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -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": [ @@ -431,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "id": "701aa52a", "metadata": {}, "outputs": [ @@ -441,13 +441,13 @@ "['PrisonersDilemma', 'PrisonersDilemmaTestgame']" ] }, - "execution_count": 3, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "gbt.catalog.games(game_type=\"nfg\")" + "gbt.catalog.games(game_type=\"nfg\", num_players=2)" ] }, { @@ -460,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 14, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -474,7 +474,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 3, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -500,7 +500,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -518,21 +518,10 @@ }, { "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(\"prisoners_dilemma.nfg\")" ] diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 37ba8049e..016775bb0 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -93,7 +93,10 @@ def __init_subclass__(cls, **kwargs): cls._extract_metadata_from_game(cls._cached_game) -def games(game_type: Literal["all", "nfg", "efg"] = "all") -> list[str]: +def games( + game_type: Literal["all", "nfg", "efg"] = "all", + num_players: int | None = None, +) -> list[str]: """ Return a list of catalog game names. @@ -104,6 +107,8 @@ def games(game_type: Literal["all", "nfg", "efg"] = "all") -> list[str]: - "all": return all games - "nfg": return only normal-form (strategic) games - "efg": return only extensive-form games + num_players : int | None, default None + If specified, only return games with the given number of players. Returns ------- @@ -116,6 +121,8 @@ def get_all_subclasses(cls): for subclass in cls.__subclasses__(): if subclass.__name__ not in ["CatalogGameFromFile"] and ( game_type == "all" or subclass.game_type == game_type + ) and ( + num_players is None or subclass.num_players == num_players ): all_subclasses.append(subclass.__name__) all_subclasses.extend(get_all_subclasses(subclass)) From 292a55d0feb7e867c9aa62e5ee199b00a76fae07 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 10:15:24 +0000 Subject: [PATCH 020/170] move catalog gamefiles into top dir for now --- {src/pygambit/catalog => catalog}/gamefiles/2s2x2x2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/2smp.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2.agg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2a.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2const.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2x2-nau.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2x2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2x2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2x2x2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/2x2x2x2x2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/3x3x3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/4cards.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/5x4x3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/8x2x2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/8x8.nfg | 0 .../catalog => catalog}/gamefiles/BSS_S_085.Weighted.agg | 0 .../catalog => catalog}/gamefiles/Bayesian-Coffee-3-2-2-3.bagg | 0 {src/pygambit/catalog => catalog}/gamefiles/GenRPS5.agg | 0 {src/pygambit/catalog => catalog}/gamefiles/artist1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/artist2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/badgame1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/badgame2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bagwell.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bayes1a.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bayes2a.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bcp2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bcp3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bcp4.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bhg1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bhg2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bhg3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bhg4.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/bhg5.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/caro2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/cent2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/cent2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/cent3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/cent4.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/cent6.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/centcs10.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/centcs6.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/condjury.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord2ts.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord333.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord4.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/coord4.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/cross.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/cs.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/csg1.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/csg2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/csg3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/csg4.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/deg1.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/deg2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/e01.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e01.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/e02.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e02.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/e03.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e04.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e04.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/e05.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e06.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e07.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e07.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/e08.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e09.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e10.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e10a.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e13.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e16.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e17.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/e18.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/g1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/g1.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/g2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/g2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/g3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/g3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/holdout.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/holdout7.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/hs1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/jury_mr.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/jury_un.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/km1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/km2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/km3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/km6.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/loopback.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/mixdom.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/mixdom2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/montyhal.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_2-1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_2-4.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_2-8.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_3-3a.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_3-3b.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_3-3c.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_3-3d.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_3-3e.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/my_3-4.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/myerson.efg | 0 .../pygambit/catalog => catalog}/gamefiles/myerson_fig_4_2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/nim.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/nim7.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/oneill.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/palf.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/palf2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/palf3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/pbride.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/pd.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/perfect1.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/perfect2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/perfect3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/poker.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/poker.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/poker2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/pvw.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/pvw2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/sh3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/sh3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/spence.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/stengel.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/sww1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/sww1.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/sww2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/sww3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/tim.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/todd1.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/todd2.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/todd3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/ttt.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/vd.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/vd.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/w_ex1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/w_ex2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/wilson1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/wink3.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/winkels.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/work1.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/work2.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/work3.efg | 0 {src/pygambit/catalog => catalog}/gamefiles/yamamoto.nfg | 0 {src/pygambit/catalog => catalog}/gamefiles/zero.nfg | 0 src/pygambit/catalog/catalog.py | 3 +-- 150 files changed, 1 insertion(+), 2 deletions(-) rename {src/pygambit/catalog => catalog}/gamefiles/2s2x2x2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2smp.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2.agg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2a.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2const.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2x2-nau.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2x2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2x2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2x2x2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/2x2x2x2x2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/3x3x3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/4cards.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/5x4x3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/8x2x2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/8x8.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/BSS_S_085.Weighted.agg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/Bayesian-Coffee-3-2-2-3.bagg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/GenRPS5.agg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/artist1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/artist2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/badgame1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/badgame2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bagwell.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bayes1a.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bayes2a.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bcp2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bcp3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bcp4.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bhg1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bhg2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bhg3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bhg4.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/bhg5.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/caro2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/cent2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/cent2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/cent3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/cent4.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/cent6.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/centcs10.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/centcs6.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/condjury.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord2ts.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord333.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord4.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/coord4.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/cross.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/cs.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/csg1.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/csg2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/csg3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/csg4.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/deg1.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/deg2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e01.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e01.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e02.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e02.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e03.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e04.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e04.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e05.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e06.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e07.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e07.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e08.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e09.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e10.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e10a.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e13.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e16.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e17.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/e18.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/g1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/g1.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/g2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/g2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/g3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/g3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/holdout.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/holdout7.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/hs1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/jury_mr.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/jury_un.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/km1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/km2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/km3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/km6.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/loopback.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/mixdom.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/mixdom2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/montyhal.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_2-1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_2-4.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_2-8.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_3-3a.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_3-3b.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_3-3c.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_3-3d.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_3-3e.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/my_3-4.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/myerson.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/myerson_fig_4_2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/nim.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/nim7.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/oneill.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/palf.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/palf2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/palf3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/pbride.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/pd.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/perfect1.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/perfect2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/perfect3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/poker.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/poker.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/poker2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/pvw.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/pvw2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/sh3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/sh3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/spence.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/stengel.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/sww1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/sww1.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/sww2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/sww3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/tim.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/todd1.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/todd2.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/todd3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/ttt.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/vd.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/vd.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/w_ex1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/w_ex2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/wilson1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/wink3.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/winkels.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/work1.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/work2.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/work3.efg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/yamamoto.nfg (100%) rename {src/pygambit/catalog => catalog}/gamefiles/zero.nfg (100%) diff --git a/src/pygambit/catalog/gamefiles/2s2x2x2.efg b/catalog/gamefiles/2s2x2x2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/2s2x2x2.efg rename to catalog/gamefiles/2s2x2x2.efg diff --git a/src/pygambit/catalog/gamefiles/2smp.efg b/catalog/gamefiles/2smp.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/2smp.efg rename to catalog/gamefiles/2smp.efg diff --git a/src/pygambit/catalog/gamefiles/2x2.agg b/catalog/gamefiles/2x2.agg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2.agg rename to catalog/gamefiles/2x2.agg diff --git a/src/pygambit/catalog/gamefiles/2x2.nfg b/catalog/gamefiles/2x2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2.nfg rename to catalog/gamefiles/2x2.nfg diff --git a/src/pygambit/catalog/gamefiles/2x2a.nfg b/catalog/gamefiles/2x2a.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2a.nfg rename to catalog/gamefiles/2x2a.nfg diff --git a/src/pygambit/catalog/gamefiles/2x2const.nfg b/catalog/gamefiles/2x2const.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2const.nfg rename to catalog/gamefiles/2x2const.nfg diff --git a/src/pygambit/catalog/gamefiles/2x2x2-nau.nfg b/catalog/gamefiles/2x2x2-nau.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2x2-nau.nfg rename to catalog/gamefiles/2x2x2-nau.nfg diff --git a/src/pygambit/catalog/gamefiles/2x2x2.efg b/catalog/gamefiles/2x2x2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2x2.efg rename to catalog/gamefiles/2x2x2.efg diff --git a/src/pygambit/catalog/gamefiles/2x2x2.nfg b/catalog/gamefiles/2x2x2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2x2.nfg rename to catalog/gamefiles/2x2x2.nfg diff --git a/src/pygambit/catalog/gamefiles/2x2x2x2.nfg b/catalog/gamefiles/2x2x2x2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2x2x2.nfg rename to catalog/gamefiles/2x2x2x2.nfg diff --git a/src/pygambit/catalog/gamefiles/2x2x2x2x2.nfg b/catalog/gamefiles/2x2x2x2x2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/2x2x2x2x2.nfg rename to catalog/gamefiles/2x2x2x2x2.nfg diff --git a/src/pygambit/catalog/gamefiles/3x3x3.nfg b/catalog/gamefiles/3x3x3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/3x3x3.nfg rename to catalog/gamefiles/3x3x3.nfg diff --git a/src/pygambit/catalog/gamefiles/4cards.efg b/catalog/gamefiles/4cards.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/4cards.efg rename to catalog/gamefiles/4cards.efg diff --git a/src/pygambit/catalog/gamefiles/5x4x3.nfg b/catalog/gamefiles/5x4x3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/5x4x3.nfg rename to catalog/gamefiles/5x4x3.nfg diff --git a/src/pygambit/catalog/gamefiles/8x2x2.nfg b/catalog/gamefiles/8x2x2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/8x2x2.nfg rename to catalog/gamefiles/8x2x2.nfg diff --git a/src/pygambit/catalog/gamefiles/8x8.nfg b/catalog/gamefiles/8x8.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/8x8.nfg rename to catalog/gamefiles/8x8.nfg diff --git a/src/pygambit/catalog/gamefiles/BSS_S_085.Weighted.agg b/catalog/gamefiles/BSS_S_085.Weighted.agg similarity index 100% rename from src/pygambit/catalog/gamefiles/BSS_S_085.Weighted.agg rename to catalog/gamefiles/BSS_S_085.Weighted.agg diff --git a/src/pygambit/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg b/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg similarity index 100% rename from src/pygambit/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg rename to catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg diff --git a/src/pygambit/catalog/gamefiles/GenRPS5.agg b/catalog/gamefiles/GenRPS5.agg similarity index 100% rename from src/pygambit/catalog/gamefiles/GenRPS5.agg rename to catalog/gamefiles/GenRPS5.agg diff --git a/src/pygambit/catalog/gamefiles/artist1.efg b/catalog/gamefiles/artist1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/artist1.efg rename to catalog/gamefiles/artist1.efg diff --git a/src/pygambit/catalog/gamefiles/artist2.efg b/catalog/gamefiles/artist2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/artist2.efg rename to catalog/gamefiles/artist2.efg diff --git a/src/pygambit/catalog/gamefiles/badgame1.efg b/catalog/gamefiles/badgame1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/badgame1.efg rename to catalog/gamefiles/badgame1.efg diff --git a/src/pygambit/catalog/gamefiles/badgame2.efg b/catalog/gamefiles/badgame2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/badgame2.efg rename to catalog/gamefiles/badgame2.efg diff --git a/src/pygambit/catalog/gamefiles/bagwell.efg b/catalog/gamefiles/bagwell.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bagwell.efg rename to catalog/gamefiles/bagwell.efg diff --git a/src/pygambit/catalog/gamefiles/bayes1a.efg b/catalog/gamefiles/bayes1a.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bayes1a.efg rename to catalog/gamefiles/bayes1a.efg diff --git a/src/pygambit/catalog/gamefiles/bayes2a.efg b/catalog/gamefiles/bayes2a.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bayes2a.efg rename to catalog/gamefiles/bayes2a.efg diff --git a/src/pygambit/catalog/gamefiles/bcp2.efg b/catalog/gamefiles/bcp2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bcp2.efg rename to catalog/gamefiles/bcp2.efg diff --git a/src/pygambit/catalog/gamefiles/bcp3.efg b/catalog/gamefiles/bcp3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bcp3.efg rename to catalog/gamefiles/bcp3.efg diff --git a/src/pygambit/catalog/gamefiles/bcp4.efg b/catalog/gamefiles/bcp4.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bcp4.efg rename to catalog/gamefiles/bcp4.efg diff --git a/src/pygambit/catalog/gamefiles/bhg1.efg b/catalog/gamefiles/bhg1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bhg1.efg rename to catalog/gamefiles/bhg1.efg diff --git a/src/pygambit/catalog/gamefiles/bhg2.efg b/catalog/gamefiles/bhg2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bhg2.efg rename to catalog/gamefiles/bhg2.efg diff --git a/src/pygambit/catalog/gamefiles/bhg3.efg b/catalog/gamefiles/bhg3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bhg3.efg rename to catalog/gamefiles/bhg3.efg diff --git a/src/pygambit/catalog/gamefiles/bhg4.efg b/catalog/gamefiles/bhg4.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bhg4.efg rename to catalog/gamefiles/bhg4.efg diff --git a/src/pygambit/catalog/gamefiles/bhg5.efg b/catalog/gamefiles/bhg5.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/bhg5.efg rename to catalog/gamefiles/bhg5.efg diff --git a/src/pygambit/catalog/gamefiles/caro2.efg b/catalog/gamefiles/caro2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/caro2.efg rename to catalog/gamefiles/caro2.efg diff --git a/src/pygambit/catalog/gamefiles/cent2.efg b/catalog/gamefiles/cent2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/cent2.efg rename to catalog/gamefiles/cent2.efg diff --git a/src/pygambit/catalog/gamefiles/cent2.nfg b/catalog/gamefiles/cent2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/cent2.nfg rename to catalog/gamefiles/cent2.nfg diff --git a/src/pygambit/catalog/gamefiles/cent3.efg b/catalog/gamefiles/cent3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/cent3.efg rename to catalog/gamefiles/cent3.efg diff --git a/src/pygambit/catalog/gamefiles/cent4.efg b/catalog/gamefiles/cent4.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/cent4.efg rename to catalog/gamefiles/cent4.efg diff --git a/src/pygambit/catalog/gamefiles/cent6.efg b/catalog/gamefiles/cent6.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/cent6.efg rename to catalog/gamefiles/cent6.efg diff --git a/src/pygambit/catalog/gamefiles/centcs10.efg b/catalog/gamefiles/centcs10.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/centcs10.efg rename to catalog/gamefiles/centcs10.efg diff --git a/src/pygambit/catalog/gamefiles/centcs6.efg b/catalog/gamefiles/centcs6.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/centcs6.efg rename to catalog/gamefiles/centcs6.efg diff --git a/src/pygambit/catalog/gamefiles/condjury.efg b/catalog/gamefiles/condjury.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/condjury.efg rename to catalog/gamefiles/condjury.efg diff --git a/src/pygambit/catalog/gamefiles/coord2.efg b/catalog/gamefiles/coord2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord2.efg rename to catalog/gamefiles/coord2.efg diff --git a/src/pygambit/catalog/gamefiles/coord2.nfg b/catalog/gamefiles/coord2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord2.nfg rename to catalog/gamefiles/coord2.nfg diff --git a/src/pygambit/catalog/gamefiles/coord2ts.efg b/catalog/gamefiles/coord2ts.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord2ts.efg rename to catalog/gamefiles/coord2ts.efg diff --git a/src/pygambit/catalog/gamefiles/coord3.efg b/catalog/gamefiles/coord3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord3.efg rename to catalog/gamefiles/coord3.efg diff --git a/src/pygambit/catalog/gamefiles/coord3.nfg b/catalog/gamefiles/coord3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord3.nfg rename to catalog/gamefiles/coord3.nfg diff --git a/src/pygambit/catalog/gamefiles/coord333.nfg b/catalog/gamefiles/coord333.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord333.nfg rename to catalog/gamefiles/coord333.nfg diff --git a/src/pygambit/catalog/gamefiles/coord4.efg b/catalog/gamefiles/coord4.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord4.efg rename to catalog/gamefiles/coord4.efg diff --git a/src/pygambit/catalog/gamefiles/coord4.nfg b/catalog/gamefiles/coord4.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/coord4.nfg rename to catalog/gamefiles/coord4.nfg diff --git a/src/pygambit/catalog/gamefiles/cross.efg b/catalog/gamefiles/cross.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/cross.efg rename to catalog/gamefiles/cross.efg diff --git a/src/pygambit/catalog/gamefiles/cs.efg b/catalog/gamefiles/cs.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/cs.efg rename to catalog/gamefiles/cs.efg diff --git a/src/pygambit/catalog/gamefiles/csg1.nfg b/catalog/gamefiles/csg1.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/csg1.nfg rename to catalog/gamefiles/csg1.nfg diff --git a/src/pygambit/catalog/gamefiles/csg2.nfg b/catalog/gamefiles/csg2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/csg2.nfg rename to catalog/gamefiles/csg2.nfg diff --git a/src/pygambit/catalog/gamefiles/csg3.nfg b/catalog/gamefiles/csg3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/csg3.nfg rename to catalog/gamefiles/csg3.nfg diff --git a/src/pygambit/catalog/gamefiles/csg4.nfg b/catalog/gamefiles/csg4.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/csg4.nfg rename to catalog/gamefiles/csg4.nfg diff --git a/src/pygambit/catalog/gamefiles/deg1.nfg b/catalog/gamefiles/deg1.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/deg1.nfg rename to catalog/gamefiles/deg1.nfg diff --git a/src/pygambit/catalog/gamefiles/deg2.nfg b/catalog/gamefiles/deg2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/deg2.nfg rename to catalog/gamefiles/deg2.nfg diff --git a/src/pygambit/catalog/gamefiles/e01.efg b/catalog/gamefiles/e01.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e01.efg rename to catalog/gamefiles/e01.efg diff --git a/src/pygambit/catalog/gamefiles/e01.nfg b/catalog/gamefiles/e01.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/e01.nfg rename to catalog/gamefiles/e01.nfg diff --git a/src/pygambit/catalog/gamefiles/e02.efg b/catalog/gamefiles/e02.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e02.efg rename to catalog/gamefiles/e02.efg diff --git a/src/pygambit/catalog/gamefiles/e02.nfg b/catalog/gamefiles/e02.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/e02.nfg rename to catalog/gamefiles/e02.nfg diff --git a/src/pygambit/catalog/gamefiles/e03.efg b/catalog/gamefiles/e03.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e03.efg rename to catalog/gamefiles/e03.efg diff --git a/src/pygambit/catalog/gamefiles/e04.efg b/catalog/gamefiles/e04.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e04.efg rename to catalog/gamefiles/e04.efg diff --git a/src/pygambit/catalog/gamefiles/e04.nfg b/catalog/gamefiles/e04.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/e04.nfg rename to catalog/gamefiles/e04.nfg diff --git a/src/pygambit/catalog/gamefiles/e05.efg b/catalog/gamefiles/e05.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e05.efg rename to catalog/gamefiles/e05.efg diff --git a/src/pygambit/catalog/gamefiles/e06.efg b/catalog/gamefiles/e06.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e06.efg rename to catalog/gamefiles/e06.efg diff --git a/src/pygambit/catalog/gamefiles/e07.efg b/catalog/gamefiles/e07.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e07.efg rename to catalog/gamefiles/e07.efg diff --git a/src/pygambit/catalog/gamefiles/e07.nfg b/catalog/gamefiles/e07.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/e07.nfg rename to catalog/gamefiles/e07.nfg diff --git a/src/pygambit/catalog/gamefiles/e08.efg b/catalog/gamefiles/e08.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e08.efg rename to catalog/gamefiles/e08.efg diff --git a/src/pygambit/catalog/gamefiles/e09.efg b/catalog/gamefiles/e09.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e09.efg rename to catalog/gamefiles/e09.efg diff --git a/src/pygambit/catalog/gamefiles/e10.efg b/catalog/gamefiles/e10.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e10.efg rename to catalog/gamefiles/e10.efg diff --git a/src/pygambit/catalog/gamefiles/e10a.efg b/catalog/gamefiles/e10a.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e10a.efg rename to catalog/gamefiles/e10a.efg diff --git a/src/pygambit/catalog/gamefiles/e13.efg b/catalog/gamefiles/e13.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e13.efg rename to catalog/gamefiles/e13.efg diff --git a/src/pygambit/catalog/gamefiles/e16.efg b/catalog/gamefiles/e16.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e16.efg rename to catalog/gamefiles/e16.efg diff --git a/src/pygambit/catalog/gamefiles/e17.efg b/catalog/gamefiles/e17.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e17.efg rename to catalog/gamefiles/e17.efg diff --git a/src/pygambit/catalog/gamefiles/e18.efg b/catalog/gamefiles/e18.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/e18.efg rename to catalog/gamefiles/e18.efg diff --git a/src/pygambit/catalog/gamefiles/g1.efg b/catalog/gamefiles/g1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/g1.efg rename to catalog/gamefiles/g1.efg diff --git a/src/pygambit/catalog/gamefiles/g1.nfg b/catalog/gamefiles/g1.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/g1.nfg rename to catalog/gamefiles/g1.nfg diff --git a/src/pygambit/catalog/gamefiles/g2.efg b/catalog/gamefiles/g2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/g2.efg rename to catalog/gamefiles/g2.efg diff --git a/src/pygambit/catalog/gamefiles/g2.nfg b/catalog/gamefiles/g2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/g2.nfg rename to catalog/gamefiles/g2.nfg diff --git a/src/pygambit/catalog/gamefiles/g3.efg b/catalog/gamefiles/g3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/g3.efg rename to catalog/gamefiles/g3.efg diff --git a/src/pygambit/catalog/gamefiles/g3.nfg b/catalog/gamefiles/g3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/g3.nfg rename to catalog/gamefiles/g3.nfg diff --git a/src/pygambit/catalog/gamefiles/holdout.efg b/catalog/gamefiles/holdout.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/holdout.efg rename to catalog/gamefiles/holdout.efg diff --git a/src/pygambit/catalog/gamefiles/holdout7.efg b/catalog/gamefiles/holdout7.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/holdout7.efg rename to catalog/gamefiles/holdout7.efg diff --git a/src/pygambit/catalog/gamefiles/hs1.efg b/catalog/gamefiles/hs1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/hs1.efg rename to catalog/gamefiles/hs1.efg diff --git a/src/pygambit/catalog/gamefiles/jury_mr.efg b/catalog/gamefiles/jury_mr.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/jury_mr.efg rename to catalog/gamefiles/jury_mr.efg diff --git a/src/pygambit/catalog/gamefiles/jury_un.efg b/catalog/gamefiles/jury_un.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/jury_un.efg rename to catalog/gamefiles/jury_un.efg diff --git a/src/pygambit/catalog/gamefiles/km1.efg b/catalog/gamefiles/km1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/km1.efg rename to catalog/gamefiles/km1.efg diff --git a/src/pygambit/catalog/gamefiles/km2.efg b/catalog/gamefiles/km2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/km2.efg rename to catalog/gamefiles/km2.efg diff --git a/src/pygambit/catalog/gamefiles/km3.efg b/catalog/gamefiles/km3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/km3.efg rename to catalog/gamefiles/km3.efg diff --git a/src/pygambit/catalog/gamefiles/km6.efg b/catalog/gamefiles/km6.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/km6.efg rename to catalog/gamefiles/km6.efg diff --git a/src/pygambit/catalog/gamefiles/loopback.nfg b/catalog/gamefiles/loopback.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/loopback.nfg rename to catalog/gamefiles/loopback.nfg diff --git a/src/pygambit/catalog/gamefiles/mixdom.nfg b/catalog/gamefiles/mixdom.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/mixdom.nfg rename to catalog/gamefiles/mixdom.nfg diff --git a/src/pygambit/catalog/gamefiles/mixdom2.nfg b/catalog/gamefiles/mixdom2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/mixdom2.nfg rename to catalog/gamefiles/mixdom2.nfg diff --git a/src/pygambit/catalog/gamefiles/montyhal.efg b/catalog/gamefiles/montyhal.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/montyhal.efg rename to catalog/gamefiles/montyhal.efg diff --git a/src/pygambit/catalog/gamefiles/my_2-1.efg b/catalog/gamefiles/my_2-1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_2-1.efg rename to catalog/gamefiles/my_2-1.efg diff --git a/src/pygambit/catalog/gamefiles/my_2-4.efg b/catalog/gamefiles/my_2-4.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_2-4.efg rename to catalog/gamefiles/my_2-4.efg diff --git a/src/pygambit/catalog/gamefiles/my_2-8.efg b/catalog/gamefiles/my_2-8.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_2-8.efg rename to catalog/gamefiles/my_2-8.efg diff --git a/src/pygambit/catalog/gamefiles/my_3-3a.efg b/catalog/gamefiles/my_3-3a.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_3-3a.efg rename to catalog/gamefiles/my_3-3a.efg diff --git a/src/pygambit/catalog/gamefiles/my_3-3b.efg b/catalog/gamefiles/my_3-3b.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_3-3b.efg rename to catalog/gamefiles/my_3-3b.efg diff --git a/src/pygambit/catalog/gamefiles/my_3-3c.efg b/catalog/gamefiles/my_3-3c.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_3-3c.efg rename to catalog/gamefiles/my_3-3c.efg diff --git a/src/pygambit/catalog/gamefiles/my_3-3d.efg b/catalog/gamefiles/my_3-3d.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_3-3d.efg rename to catalog/gamefiles/my_3-3d.efg diff --git a/src/pygambit/catalog/gamefiles/my_3-3e.efg b/catalog/gamefiles/my_3-3e.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_3-3e.efg rename to catalog/gamefiles/my_3-3e.efg diff --git a/src/pygambit/catalog/gamefiles/my_3-4.efg b/catalog/gamefiles/my_3-4.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/my_3-4.efg rename to catalog/gamefiles/my_3-4.efg diff --git a/src/pygambit/catalog/gamefiles/myerson.efg b/catalog/gamefiles/myerson.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/myerson.efg rename to catalog/gamefiles/myerson.efg diff --git a/src/pygambit/catalog/gamefiles/myerson_fig_4_2.efg b/catalog/gamefiles/myerson_fig_4_2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/myerson_fig_4_2.efg rename to catalog/gamefiles/myerson_fig_4_2.efg diff --git a/src/pygambit/catalog/gamefiles/nim.efg b/catalog/gamefiles/nim.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/nim.efg rename to catalog/gamefiles/nim.efg diff --git a/src/pygambit/catalog/gamefiles/nim7.efg b/catalog/gamefiles/nim7.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/nim7.efg rename to catalog/gamefiles/nim7.efg diff --git a/src/pygambit/catalog/gamefiles/oneill.nfg b/catalog/gamefiles/oneill.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/oneill.nfg rename to catalog/gamefiles/oneill.nfg diff --git a/src/pygambit/catalog/gamefiles/palf.efg b/catalog/gamefiles/palf.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/palf.efg rename to catalog/gamefiles/palf.efg diff --git a/src/pygambit/catalog/gamefiles/palf2.efg b/catalog/gamefiles/palf2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/palf2.efg rename to catalog/gamefiles/palf2.efg diff --git a/src/pygambit/catalog/gamefiles/palf3.efg b/catalog/gamefiles/palf3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/palf3.efg rename to catalog/gamefiles/palf3.efg diff --git a/src/pygambit/catalog/gamefiles/pbride.efg b/catalog/gamefiles/pbride.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/pbride.efg rename to catalog/gamefiles/pbride.efg diff --git a/src/pygambit/catalog/gamefiles/pd.nfg b/catalog/gamefiles/pd.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/pd.nfg rename to catalog/gamefiles/pd.nfg diff --git a/src/pygambit/catalog/gamefiles/perfect1.nfg b/catalog/gamefiles/perfect1.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/perfect1.nfg rename to catalog/gamefiles/perfect1.nfg diff --git a/src/pygambit/catalog/gamefiles/perfect2.nfg b/catalog/gamefiles/perfect2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/perfect2.nfg rename to catalog/gamefiles/perfect2.nfg diff --git a/src/pygambit/catalog/gamefiles/perfect3.nfg b/catalog/gamefiles/perfect3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/perfect3.nfg rename to catalog/gamefiles/perfect3.nfg diff --git a/src/pygambit/catalog/gamefiles/poker.efg b/catalog/gamefiles/poker.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/poker.efg rename to catalog/gamefiles/poker.efg diff --git a/src/pygambit/catalog/gamefiles/poker.nfg b/catalog/gamefiles/poker.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/poker.nfg rename to catalog/gamefiles/poker.nfg diff --git a/src/pygambit/catalog/gamefiles/poker2.efg b/catalog/gamefiles/poker2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/poker2.efg rename to catalog/gamefiles/poker2.efg diff --git a/src/pygambit/catalog/gamefiles/pvw.efg b/catalog/gamefiles/pvw.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/pvw.efg rename to catalog/gamefiles/pvw.efg diff --git a/src/pygambit/catalog/gamefiles/pvw2.efg b/catalog/gamefiles/pvw2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/pvw2.efg rename to catalog/gamefiles/pvw2.efg diff --git a/src/pygambit/catalog/gamefiles/sh3.efg b/catalog/gamefiles/sh3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/sh3.efg rename to catalog/gamefiles/sh3.efg diff --git a/src/pygambit/catalog/gamefiles/sh3.nfg b/catalog/gamefiles/sh3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/sh3.nfg rename to catalog/gamefiles/sh3.nfg diff --git a/src/pygambit/catalog/gamefiles/spence.efg b/catalog/gamefiles/spence.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/spence.efg rename to catalog/gamefiles/spence.efg diff --git a/src/pygambit/catalog/gamefiles/stengel.nfg b/catalog/gamefiles/stengel.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/stengel.nfg rename to catalog/gamefiles/stengel.nfg diff --git a/src/pygambit/catalog/gamefiles/sww1.efg b/catalog/gamefiles/sww1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/sww1.efg rename to catalog/gamefiles/sww1.efg diff --git a/src/pygambit/catalog/gamefiles/sww1.nfg b/catalog/gamefiles/sww1.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/sww1.nfg rename to catalog/gamefiles/sww1.nfg diff --git a/src/pygambit/catalog/gamefiles/sww2.efg b/catalog/gamefiles/sww2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/sww2.efg rename to catalog/gamefiles/sww2.efg diff --git a/src/pygambit/catalog/gamefiles/sww3.efg b/catalog/gamefiles/sww3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/sww3.efg rename to catalog/gamefiles/sww3.efg diff --git a/src/pygambit/catalog/gamefiles/tim.efg b/catalog/gamefiles/tim.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/tim.efg rename to catalog/gamefiles/tim.efg diff --git a/src/pygambit/catalog/gamefiles/todd1.nfg b/catalog/gamefiles/todd1.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/todd1.nfg rename to catalog/gamefiles/todd1.nfg diff --git a/src/pygambit/catalog/gamefiles/todd2.nfg b/catalog/gamefiles/todd2.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/todd2.nfg rename to catalog/gamefiles/todd2.nfg diff --git a/src/pygambit/catalog/gamefiles/todd3.nfg b/catalog/gamefiles/todd3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/todd3.nfg rename to catalog/gamefiles/todd3.nfg diff --git a/src/pygambit/catalog/gamefiles/ttt.efg b/catalog/gamefiles/ttt.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/ttt.efg rename to catalog/gamefiles/ttt.efg diff --git a/src/pygambit/catalog/gamefiles/vd.efg b/catalog/gamefiles/vd.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/vd.efg rename to catalog/gamefiles/vd.efg diff --git a/src/pygambit/catalog/gamefiles/vd.nfg b/catalog/gamefiles/vd.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/vd.nfg rename to catalog/gamefiles/vd.nfg diff --git a/src/pygambit/catalog/gamefiles/w_ex1.efg b/catalog/gamefiles/w_ex1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/w_ex1.efg rename to catalog/gamefiles/w_ex1.efg diff --git a/src/pygambit/catalog/gamefiles/w_ex2.efg b/catalog/gamefiles/w_ex2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/w_ex2.efg rename to catalog/gamefiles/w_ex2.efg diff --git a/src/pygambit/catalog/gamefiles/wilson1.efg b/catalog/gamefiles/wilson1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/wilson1.efg rename to catalog/gamefiles/wilson1.efg diff --git a/src/pygambit/catalog/gamefiles/wink3.nfg b/catalog/gamefiles/wink3.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/wink3.nfg rename to catalog/gamefiles/wink3.nfg diff --git a/src/pygambit/catalog/gamefiles/winkels.nfg b/catalog/gamefiles/winkels.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/winkels.nfg rename to catalog/gamefiles/winkels.nfg diff --git a/src/pygambit/catalog/gamefiles/work1.efg b/catalog/gamefiles/work1.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/work1.efg rename to catalog/gamefiles/work1.efg diff --git a/src/pygambit/catalog/gamefiles/work2.efg b/catalog/gamefiles/work2.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/work2.efg rename to catalog/gamefiles/work2.efg diff --git a/src/pygambit/catalog/gamefiles/work3.efg b/catalog/gamefiles/work3.efg similarity index 100% rename from src/pygambit/catalog/gamefiles/work3.efg rename to catalog/gamefiles/work3.efg diff --git a/src/pygambit/catalog/gamefiles/yamamoto.nfg b/catalog/gamefiles/yamamoto.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/yamamoto.nfg rename to catalog/gamefiles/yamamoto.nfg diff --git a/src/pygambit/catalog/gamefiles/zero.nfg b/catalog/gamefiles/zero.nfg similarity index 100% rename from src/pygambit/catalog/gamefiles/zero.nfg rename to catalog/gamefiles/zero.nfg diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 016775bb0..36386e98f 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -5,8 +5,7 @@ from pygambit import Game, read_efg, read_nfg -_CATALOG_DIR = Path(__file__).parent -_GAMEFILES_DIR = _CATALOG_DIR / "gamefiles" +_GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "catalog/gamefiles" class CatalogGame: From 6a2677a215b139b7f3022a20eb435426ec5ea111 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 10:18:40 +0000 Subject: [PATCH 021/170] move games back to contrib/games --- {catalog/gamefiles => contrib/games}/2s2x2x2.efg | 0 {catalog/gamefiles => contrib/games}/2smp.efg | 0 {catalog/gamefiles => contrib/games}/2x2.agg | 0 {catalog/gamefiles => contrib/games}/2x2.nfg | 0 {catalog/gamefiles => contrib/games}/2x2a.nfg | 0 {catalog/gamefiles => contrib/games}/2x2const.nfg | 0 {catalog/gamefiles => contrib/games}/2x2x2-nau.nfg | 0 {catalog/gamefiles => contrib/games}/2x2x2.efg | 0 {catalog/gamefiles => contrib/games}/2x2x2.nfg | 0 {catalog/gamefiles => contrib/games}/2x2x2x2.nfg | 0 {catalog/gamefiles => contrib/games}/2x2x2x2x2.nfg | 0 {catalog/gamefiles => contrib/games}/3x3x3.nfg | 0 {catalog/gamefiles => contrib/games}/4cards.efg | 0 {catalog/gamefiles => contrib/games}/5x4x3.nfg | 0 {catalog/gamefiles => contrib/games}/8x2x2.nfg | 0 {catalog/gamefiles => contrib/games}/8x8.nfg | 0 .../gamefiles => contrib/games}/BSS_S_085.Weighted.agg | 0 .../games}/Bayesian-Coffee-3-2-2-3.bagg | 0 {catalog/gamefiles => contrib/games}/GenRPS5.agg | 0 {catalog/gamefiles => contrib/games}/artist1.efg | 0 {catalog/gamefiles => contrib/games}/artist2.efg | 0 {catalog/gamefiles => contrib/games}/badgame1.efg | 0 {catalog/gamefiles => contrib/games}/badgame2.efg | 0 {catalog/gamefiles => contrib/games}/bagwell.efg | 0 {catalog/gamefiles => contrib/games}/bayes1a.efg | 0 {catalog/gamefiles => contrib/games}/bayes2a.efg | 0 {catalog/gamefiles => contrib/games}/bcp2.efg | 0 {catalog/gamefiles => contrib/games}/bcp3.efg | 0 {catalog/gamefiles => contrib/games}/bcp4.efg | 0 {catalog/gamefiles => contrib/games}/bhg1.efg | 0 {catalog/gamefiles => contrib/games}/bhg2.efg | 0 {catalog/gamefiles => contrib/games}/bhg3.efg | 0 {catalog/gamefiles => contrib/games}/bhg4.efg | 0 {catalog/gamefiles => contrib/games}/bhg5.efg | 0 {catalog/gamefiles => contrib/games}/caro2.efg | 0 {catalog/gamefiles => contrib/games}/cent2.efg | 0 {catalog/gamefiles => contrib/games}/cent2.nfg | 0 {catalog/gamefiles => contrib/games}/cent3.efg | 0 {catalog/gamefiles => contrib/games}/cent4.efg | 0 {catalog/gamefiles => contrib/games}/cent6.efg | 0 {catalog/gamefiles => contrib/games}/centcs10.efg | 0 {catalog/gamefiles => contrib/games}/centcs6.efg | 0 {catalog/gamefiles => contrib/games}/condjury.efg | 0 {catalog/gamefiles => contrib/games}/coord2.efg | 0 {catalog/gamefiles => contrib/games}/coord2.nfg | 0 {catalog/gamefiles => contrib/games}/coord2ts.efg | 0 {catalog/gamefiles => contrib/games}/coord3.efg | 0 {catalog/gamefiles => contrib/games}/coord3.nfg | 0 {catalog/gamefiles => contrib/games}/coord333.nfg | 0 {catalog/gamefiles => contrib/games}/coord4.efg | 0 {catalog/gamefiles => contrib/games}/coord4.nfg | 0 {catalog/gamefiles => contrib/games}/cross.efg | 0 {catalog/gamefiles => contrib/games}/cs.efg | 0 {catalog/gamefiles => contrib/games}/csg1.nfg | 0 {catalog/gamefiles => contrib/games}/csg2.nfg | 0 {catalog/gamefiles => contrib/games}/csg3.nfg | 0 {catalog/gamefiles => contrib/games}/csg4.nfg | 0 {catalog/gamefiles => contrib/games}/deg1.nfg | 0 {catalog/gamefiles => contrib/games}/deg2.nfg | 0 {catalog/gamefiles => contrib/games}/e01.efg | 0 {catalog/gamefiles => contrib/games}/e01.nfg | 0 {catalog/gamefiles => contrib/games}/e02.efg | 0 {catalog/gamefiles => contrib/games}/e02.nfg | 0 {catalog/gamefiles => contrib/games}/e03.efg | 0 {catalog/gamefiles => contrib/games}/e04.efg | 0 {catalog/gamefiles => contrib/games}/e04.nfg | 0 {catalog/gamefiles => contrib/games}/e05.efg | 0 {catalog/gamefiles => contrib/games}/e06.efg | 0 {catalog/gamefiles => contrib/games}/e07.efg | 0 {catalog/gamefiles => contrib/games}/e07.nfg | 0 {catalog/gamefiles => contrib/games}/e08.efg | 0 {catalog/gamefiles => contrib/games}/e09.efg | 0 {catalog/gamefiles => contrib/games}/e10.efg | 0 {catalog/gamefiles => contrib/games}/e10a.efg | 0 {catalog/gamefiles => contrib/games}/e13.efg | 0 {catalog/gamefiles => contrib/games}/e16.efg | 0 {catalog/gamefiles => contrib/games}/e17.efg | 0 {catalog/gamefiles => contrib/games}/e18.efg | 0 {catalog/gamefiles => contrib/games}/g1.efg | 0 {catalog/gamefiles => contrib/games}/g1.nfg | 0 {catalog/gamefiles => contrib/games}/g2.efg | 0 {catalog/gamefiles => contrib/games}/g2.nfg | 0 {catalog/gamefiles => contrib/games}/g3.efg | 0 {catalog/gamefiles => contrib/games}/g3.nfg | 0 {catalog/gamefiles => contrib/games}/holdout.efg | 0 {catalog/gamefiles => contrib/games}/holdout7.efg | 0 {catalog/gamefiles => contrib/games}/hs1.efg | 0 {catalog/gamefiles => contrib/games}/jury_mr.efg | 0 {catalog/gamefiles => contrib/games}/jury_un.efg | 0 {catalog/gamefiles => contrib/games}/km1.efg | 0 {catalog/gamefiles => contrib/games}/km2.efg | 0 {catalog/gamefiles => contrib/games}/km3.efg | 0 {catalog/gamefiles => contrib/games}/km6.efg | 0 {catalog/gamefiles => contrib/games}/loopback.nfg | 0 {catalog/gamefiles => contrib/games}/mixdom.nfg | 0 {catalog/gamefiles => contrib/games}/mixdom2.nfg | 0 {catalog/gamefiles => contrib/games}/montyhal.efg | 0 {catalog/gamefiles => contrib/games}/my_2-1.efg | 0 {catalog/gamefiles => contrib/games}/my_2-4.efg | 0 {catalog/gamefiles => contrib/games}/my_2-8.efg | 0 {catalog/gamefiles => contrib/games}/my_3-3a.efg | 0 {catalog/gamefiles => contrib/games}/my_3-3b.efg | 0 {catalog/gamefiles => contrib/games}/my_3-3c.efg | 0 {catalog/gamefiles => contrib/games}/my_3-3d.efg | 0 {catalog/gamefiles => contrib/games}/my_3-3e.efg | 0 {catalog/gamefiles => contrib/games}/my_3-4.efg | 0 {catalog/gamefiles => contrib/games}/myerson.efg | 0 .../gamefiles => contrib/games}/myerson_fig_4_2.efg | 0 {catalog/gamefiles => contrib/games}/nim.efg | 0 {catalog/gamefiles => contrib/games}/nim7.efg | 0 {catalog/gamefiles => contrib/games}/oneill.nfg | 0 {catalog/gamefiles => contrib/games}/palf.efg | 0 {catalog/gamefiles => contrib/games}/palf2.efg | 0 {catalog/gamefiles => contrib/games}/palf3.efg | 0 {catalog/gamefiles => contrib/games}/pbride.efg | 0 {catalog/gamefiles => contrib/games}/pd.nfg | 0 {catalog/gamefiles => contrib/games}/perfect1.nfg | 0 {catalog/gamefiles => contrib/games}/perfect2.nfg | 0 {catalog/gamefiles => contrib/games}/perfect3.nfg | 0 {catalog/gamefiles => contrib/games}/poker.efg | 0 {catalog/gamefiles => contrib/games}/poker.nfg | 0 {catalog/gamefiles => contrib/games}/poker2.efg | 0 {catalog/gamefiles => contrib/games}/pvw.efg | 0 {catalog/gamefiles => contrib/games}/pvw2.efg | 0 {catalog/gamefiles => contrib/games}/sh3.efg | 0 {catalog/gamefiles => contrib/games}/sh3.nfg | 0 {catalog/gamefiles => contrib/games}/spence.efg | 0 {catalog/gamefiles => contrib/games}/stengel.nfg | 0 {catalog/gamefiles => contrib/games}/sww1.efg | 0 {catalog/gamefiles => contrib/games}/sww1.nfg | 0 {catalog/gamefiles => contrib/games}/sww2.efg | 0 {catalog/gamefiles => contrib/games}/sww3.efg | 0 {catalog/gamefiles => contrib/games}/tim.efg | 0 {catalog/gamefiles => contrib/games}/todd1.nfg | 0 {catalog/gamefiles => contrib/games}/todd2.nfg | 0 {catalog/gamefiles => contrib/games}/todd3.nfg | 0 {catalog/gamefiles => contrib/games}/ttt.efg | 0 {catalog/gamefiles => contrib/games}/vd.efg | 0 {catalog/gamefiles => contrib/games}/vd.nfg | 0 {catalog/gamefiles => contrib/games}/w_ex1.efg | 0 {catalog/gamefiles => contrib/games}/w_ex2.efg | 0 {catalog/gamefiles => contrib/games}/wilson1.efg | 0 {catalog/gamefiles => contrib/games}/wink3.nfg | 0 {catalog/gamefiles => contrib/games}/winkels.nfg | 0 {catalog/gamefiles => contrib/games}/work1.efg | 0 {catalog/gamefiles => contrib/games}/work2.efg | 0 {catalog/gamefiles => contrib/games}/work3.efg | 0 {catalog/gamefiles => contrib/games}/yamamoto.nfg | 0 {catalog/gamefiles => contrib/games}/zero.nfg | 0 doc/tutorials/01_quickstart.ipynb | 10 +++++----- src/pygambit/catalog/catalog.py | 2 +- 151 files changed, 6 insertions(+), 6 deletions(-) rename {catalog/gamefiles => contrib/games}/2s2x2x2.efg (100%) rename {catalog/gamefiles => contrib/games}/2smp.efg (100%) rename {catalog/gamefiles => contrib/games}/2x2.agg (100%) rename {catalog/gamefiles => contrib/games}/2x2.nfg (100%) rename {catalog/gamefiles => contrib/games}/2x2a.nfg (100%) rename {catalog/gamefiles => contrib/games}/2x2const.nfg (100%) rename {catalog/gamefiles => contrib/games}/2x2x2-nau.nfg (100%) rename {catalog/gamefiles => contrib/games}/2x2x2.efg (100%) rename {catalog/gamefiles => contrib/games}/2x2x2.nfg (100%) rename {catalog/gamefiles => contrib/games}/2x2x2x2.nfg (100%) rename {catalog/gamefiles => contrib/games}/2x2x2x2x2.nfg (100%) rename {catalog/gamefiles => contrib/games}/3x3x3.nfg (100%) rename {catalog/gamefiles => contrib/games}/4cards.efg (100%) rename {catalog/gamefiles => contrib/games}/5x4x3.nfg (100%) rename {catalog/gamefiles => contrib/games}/8x2x2.nfg (100%) rename {catalog/gamefiles => contrib/games}/8x8.nfg (100%) rename {catalog/gamefiles => contrib/games}/BSS_S_085.Weighted.agg (100%) rename {catalog/gamefiles => contrib/games}/Bayesian-Coffee-3-2-2-3.bagg (100%) rename {catalog/gamefiles => contrib/games}/GenRPS5.agg (100%) rename {catalog/gamefiles => contrib/games}/artist1.efg (100%) rename {catalog/gamefiles => contrib/games}/artist2.efg (100%) rename {catalog/gamefiles => contrib/games}/badgame1.efg (100%) rename {catalog/gamefiles => contrib/games}/badgame2.efg (100%) rename {catalog/gamefiles => contrib/games}/bagwell.efg (100%) rename {catalog/gamefiles => contrib/games}/bayes1a.efg (100%) rename {catalog/gamefiles => contrib/games}/bayes2a.efg (100%) rename {catalog/gamefiles => contrib/games}/bcp2.efg (100%) rename {catalog/gamefiles => contrib/games}/bcp3.efg (100%) rename {catalog/gamefiles => contrib/games}/bcp4.efg (100%) rename {catalog/gamefiles => contrib/games}/bhg1.efg (100%) rename {catalog/gamefiles => contrib/games}/bhg2.efg (100%) rename {catalog/gamefiles => contrib/games}/bhg3.efg (100%) rename {catalog/gamefiles => contrib/games}/bhg4.efg (100%) rename {catalog/gamefiles => contrib/games}/bhg5.efg (100%) rename {catalog/gamefiles => contrib/games}/caro2.efg (100%) rename {catalog/gamefiles => contrib/games}/cent2.efg (100%) rename {catalog/gamefiles => contrib/games}/cent2.nfg (100%) rename {catalog/gamefiles => contrib/games}/cent3.efg (100%) rename {catalog/gamefiles => contrib/games}/cent4.efg (100%) rename {catalog/gamefiles => contrib/games}/cent6.efg (100%) rename {catalog/gamefiles => contrib/games}/centcs10.efg (100%) rename {catalog/gamefiles => contrib/games}/centcs6.efg (100%) rename {catalog/gamefiles => contrib/games}/condjury.efg (100%) rename {catalog/gamefiles => contrib/games}/coord2.efg (100%) rename {catalog/gamefiles => contrib/games}/coord2.nfg (100%) rename {catalog/gamefiles => contrib/games}/coord2ts.efg (100%) rename {catalog/gamefiles => contrib/games}/coord3.efg (100%) rename {catalog/gamefiles => contrib/games}/coord3.nfg (100%) rename {catalog/gamefiles => contrib/games}/coord333.nfg (100%) rename {catalog/gamefiles => contrib/games}/coord4.efg (100%) rename {catalog/gamefiles => contrib/games}/coord4.nfg (100%) rename {catalog/gamefiles => contrib/games}/cross.efg (100%) rename {catalog/gamefiles => contrib/games}/cs.efg (100%) rename {catalog/gamefiles => contrib/games}/csg1.nfg (100%) rename {catalog/gamefiles => contrib/games}/csg2.nfg (100%) rename {catalog/gamefiles => contrib/games}/csg3.nfg (100%) rename {catalog/gamefiles => contrib/games}/csg4.nfg (100%) rename {catalog/gamefiles => contrib/games}/deg1.nfg (100%) rename {catalog/gamefiles => contrib/games}/deg2.nfg (100%) rename {catalog/gamefiles => contrib/games}/e01.efg (100%) rename {catalog/gamefiles => contrib/games}/e01.nfg (100%) rename {catalog/gamefiles => contrib/games}/e02.efg (100%) rename {catalog/gamefiles => contrib/games}/e02.nfg (100%) rename {catalog/gamefiles => contrib/games}/e03.efg (100%) rename {catalog/gamefiles => contrib/games}/e04.efg (100%) rename {catalog/gamefiles => contrib/games}/e04.nfg (100%) rename {catalog/gamefiles => contrib/games}/e05.efg (100%) rename {catalog/gamefiles => contrib/games}/e06.efg (100%) rename {catalog/gamefiles => contrib/games}/e07.efg (100%) rename {catalog/gamefiles => contrib/games}/e07.nfg (100%) rename {catalog/gamefiles => contrib/games}/e08.efg (100%) rename {catalog/gamefiles => contrib/games}/e09.efg (100%) rename {catalog/gamefiles => contrib/games}/e10.efg (100%) rename {catalog/gamefiles => contrib/games}/e10a.efg (100%) rename {catalog/gamefiles => contrib/games}/e13.efg (100%) rename {catalog/gamefiles => contrib/games}/e16.efg (100%) rename {catalog/gamefiles => contrib/games}/e17.efg (100%) rename {catalog/gamefiles => contrib/games}/e18.efg (100%) rename {catalog/gamefiles => contrib/games}/g1.efg (100%) rename {catalog/gamefiles => contrib/games}/g1.nfg (100%) rename {catalog/gamefiles => contrib/games}/g2.efg (100%) rename {catalog/gamefiles => contrib/games}/g2.nfg (100%) rename {catalog/gamefiles => contrib/games}/g3.efg (100%) rename {catalog/gamefiles => contrib/games}/g3.nfg (100%) rename {catalog/gamefiles => contrib/games}/holdout.efg (100%) rename {catalog/gamefiles => contrib/games}/holdout7.efg (100%) rename {catalog/gamefiles => contrib/games}/hs1.efg (100%) rename {catalog/gamefiles => contrib/games}/jury_mr.efg (100%) rename {catalog/gamefiles => contrib/games}/jury_un.efg (100%) rename {catalog/gamefiles => contrib/games}/km1.efg (100%) rename {catalog/gamefiles => contrib/games}/km2.efg (100%) rename {catalog/gamefiles => contrib/games}/km3.efg (100%) rename {catalog/gamefiles => contrib/games}/km6.efg (100%) rename {catalog/gamefiles => contrib/games}/loopback.nfg (100%) rename {catalog/gamefiles => contrib/games}/mixdom.nfg (100%) rename {catalog/gamefiles => contrib/games}/mixdom2.nfg (100%) rename {catalog/gamefiles => contrib/games}/montyhal.efg (100%) rename {catalog/gamefiles => contrib/games}/my_2-1.efg (100%) rename {catalog/gamefiles => contrib/games}/my_2-4.efg (100%) rename {catalog/gamefiles => contrib/games}/my_2-8.efg (100%) rename {catalog/gamefiles => contrib/games}/my_3-3a.efg (100%) rename {catalog/gamefiles => contrib/games}/my_3-3b.efg (100%) rename {catalog/gamefiles => contrib/games}/my_3-3c.efg (100%) rename {catalog/gamefiles => contrib/games}/my_3-3d.efg (100%) rename {catalog/gamefiles => contrib/games}/my_3-3e.efg (100%) rename {catalog/gamefiles => contrib/games}/my_3-4.efg (100%) rename {catalog/gamefiles => contrib/games}/myerson.efg (100%) rename {catalog/gamefiles => contrib/games}/myerson_fig_4_2.efg (100%) rename {catalog/gamefiles => contrib/games}/nim.efg (100%) rename {catalog/gamefiles => contrib/games}/nim7.efg (100%) rename {catalog/gamefiles => contrib/games}/oneill.nfg (100%) rename {catalog/gamefiles => contrib/games}/palf.efg (100%) rename {catalog/gamefiles => contrib/games}/palf2.efg (100%) rename {catalog/gamefiles => contrib/games}/palf3.efg (100%) rename {catalog/gamefiles => contrib/games}/pbride.efg (100%) rename {catalog/gamefiles => contrib/games}/pd.nfg (100%) rename {catalog/gamefiles => contrib/games}/perfect1.nfg (100%) rename {catalog/gamefiles => contrib/games}/perfect2.nfg (100%) rename {catalog/gamefiles => contrib/games}/perfect3.nfg (100%) rename {catalog/gamefiles => contrib/games}/poker.efg (100%) rename {catalog/gamefiles => contrib/games}/poker.nfg (100%) rename {catalog/gamefiles => contrib/games}/poker2.efg (100%) rename {catalog/gamefiles => contrib/games}/pvw.efg (100%) rename {catalog/gamefiles => contrib/games}/pvw2.efg (100%) rename {catalog/gamefiles => contrib/games}/sh3.efg (100%) rename {catalog/gamefiles => contrib/games}/sh3.nfg (100%) rename {catalog/gamefiles => contrib/games}/spence.efg (100%) rename {catalog/gamefiles => contrib/games}/stengel.nfg (100%) rename {catalog/gamefiles => contrib/games}/sww1.efg (100%) rename {catalog/gamefiles => contrib/games}/sww1.nfg (100%) rename {catalog/gamefiles => contrib/games}/sww2.efg (100%) rename {catalog/gamefiles => contrib/games}/sww3.efg (100%) rename {catalog/gamefiles => contrib/games}/tim.efg (100%) rename {catalog/gamefiles => contrib/games}/todd1.nfg (100%) rename {catalog/gamefiles => contrib/games}/todd2.nfg (100%) rename {catalog/gamefiles => contrib/games}/todd3.nfg (100%) rename {catalog/gamefiles => contrib/games}/ttt.efg (100%) rename {catalog/gamefiles => contrib/games}/vd.efg (100%) rename {catalog/gamefiles => contrib/games}/vd.nfg (100%) rename {catalog/gamefiles => contrib/games}/w_ex1.efg (100%) rename {catalog/gamefiles => contrib/games}/w_ex2.efg (100%) rename {catalog/gamefiles => contrib/games}/wilson1.efg (100%) rename {catalog/gamefiles => contrib/games}/wink3.nfg (100%) rename {catalog/gamefiles => contrib/games}/winkels.nfg (100%) rename {catalog/gamefiles => contrib/games}/work1.efg (100%) rename {catalog/gamefiles => contrib/games}/work2.efg (100%) rename {catalog/gamefiles => contrib/games}/work3.efg (100%) rename {catalog/gamefiles => contrib/games}/yamamoto.nfg (100%) rename {catalog/gamefiles => contrib/games}/zero.nfg (100%) diff --git a/catalog/gamefiles/2s2x2x2.efg b/contrib/games/2s2x2x2.efg similarity index 100% rename from catalog/gamefiles/2s2x2x2.efg rename to contrib/games/2s2x2x2.efg diff --git a/catalog/gamefiles/2smp.efg b/contrib/games/2smp.efg similarity index 100% rename from catalog/gamefiles/2smp.efg rename to contrib/games/2smp.efg diff --git a/catalog/gamefiles/2x2.agg b/contrib/games/2x2.agg similarity index 100% rename from catalog/gamefiles/2x2.agg rename to contrib/games/2x2.agg diff --git a/catalog/gamefiles/2x2.nfg b/contrib/games/2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2.nfg rename to contrib/games/2x2.nfg diff --git a/catalog/gamefiles/2x2a.nfg b/contrib/games/2x2a.nfg similarity index 100% rename from catalog/gamefiles/2x2a.nfg rename to contrib/games/2x2a.nfg diff --git a/catalog/gamefiles/2x2const.nfg b/contrib/games/2x2const.nfg similarity index 100% rename from catalog/gamefiles/2x2const.nfg rename to contrib/games/2x2const.nfg diff --git a/catalog/gamefiles/2x2x2-nau.nfg b/contrib/games/2x2x2-nau.nfg similarity index 100% rename from catalog/gamefiles/2x2x2-nau.nfg rename to contrib/games/2x2x2-nau.nfg diff --git a/catalog/gamefiles/2x2x2.efg b/contrib/games/2x2x2.efg similarity index 100% rename from catalog/gamefiles/2x2x2.efg rename to contrib/games/2x2x2.efg diff --git a/catalog/gamefiles/2x2x2.nfg b/contrib/games/2x2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2x2.nfg rename to contrib/games/2x2x2.nfg diff --git a/catalog/gamefiles/2x2x2x2.nfg b/contrib/games/2x2x2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2x2x2.nfg rename to contrib/games/2x2x2x2.nfg diff --git a/catalog/gamefiles/2x2x2x2x2.nfg b/contrib/games/2x2x2x2x2.nfg similarity index 100% rename from catalog/gamefiles/2x2x2x2x2.nfg rename to contrib/games/2x2x2x2x2.nfg diff --git a/catalog/gamefiles/3x3x3.nfg b/contrib/games/3x3x3.nfg similarity index 100% rename from catalog/gamefiles/3x3x3.nfg rename to contrib/games/3x3x3.nfg diff --git a/catalog/gamefiles/4cards.efg b/contrib/games/4cards.efg similarity index 100% rename from catalog/gamefiles/4cards.efg rename to contrib/games/4cards.efg diff --git a/catalog/gamefiles/5x4x3.nfg b/contrib/games/5x4x3.nfg similarity index 100% rename from catalog/gamefiles/5x4x3.nfg rename to contrib/games/5x4x3.nfg diff --git a/catalog/gamefiles/8x2x2.nfg b/contrib/games/8x2x2.nfg similarity index 100% rename from catalog/gamefiles/8x2x2.nfg rename to contrib/games/8x2x2.nfg diff --git a/catalog/gamefiles/8x8.nfg b/contrib/games/8x8.nfg similarity index 100% rename from catalog/gamefiles/8x8.nfg rename to contrib/games/8x8.nfg diff --git a/catalog/gamefiles/BSS_S_085.Weighted.agg b/contrib/games/BSS_S_085.Weighted.agg similarity index 100% rename from catalog/gamefiles/BSS_S_085.Weighted.agg rename to contrib/games/BSS_S_085.Weighted.agg diff --git a/catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg b/contrib/games/Bayesian-Coffee-3-2-2-3.bagg similarity index 100% rename from catalog/gamefiles/Bayesian-Coffee-3-2-2-3.bagg rename to contrib/games/Bayesian-Coffee-3-2-2-3.bagg diff --git a/catalog/gamefiles/GenRPS5.agg b/contrib/games/GenRPS5.agg similarity index 100% rename from catalog/gamefiles/GenRPS5.agg rename to contrib/games/GenRPS5.agg diff --git a/catalog/gamefiles/artist1.efg b/contrib/games/artist1.efg similarity index 100% rename from catalog/gamefiles/artist1.efg rename to contrib/games/artist1.efg diff --git a/catalog/gamefiles/artist2.efg b/contrib/games/artist2.efg similarity index 100% rename from catalog/gamefiles/artist2.efg rename to contrib/games/artist2.efg diff --git a/catalog/gamefiles/badgame1.efg b/contrib/games/badgame1.efg similarity index 100% rename from catalog/gamefiles/badgame1.efg rename to contrib/games/badgame1.efg diff --git a/catalog/gamefiles/badgame2.efg b/contrib/games/badgame2.efg similarity index 100% rename from catalog/gamefiles/badgame2.efg rename to contrib/games/badgame2.efg diff --git a/catalog/gamefiles/bagwell.efg b/contrib/games/bagwell.efg similarity index 100% rename from catalog/gamefiles/bagwell.efg rename to contrib/games/bagwell.efg diff --git a/catalog/gamefiles/bayes1a.efg b/contrib/games/bayes1a.efg similarity index 100% rename from catalog/gamefiles/bayes1a.efg rename to contrib/games/bayes1a.efg diff --git a/catalog/gamefiles/bayes2a.efg b/contrib/games/bayes2a.efg similarity index 100% rename from catalog/gamefiles/bayes2a.efg rename to contrib/games/bayes2a.efg diff --git a/catalog/gamefiles/bcp2.efg b/contrib/games/bcp2.efg similarity index 100% rename from catalog/gamefiles/bcp2.efg rename to contrib/games/bcp2.efg diff --git a/catalog/gamefiles/bcp3.efg b/contrib/games/bcp3.efg similarity index 100% rename from catalog/gamefiles/bcp3.efg rename to contrib/games/bcp3.efg diff --git a/catalog/gamefiles/bcp4.efg b/contrib/games/bcp4.efg similarity index 100% rename from catalog/gamefiles/bcp4.efg rename to contrib/games/bcp4.efg diff --git a/catalog/gamefiles/bhg1.efg b/contrib/games/bhg1.efg similarity index 100% rename from catalog/gamefiles/bhg1.efg rename to contrib/games/bhg1.efg diff --git a/catalog/gamefiles/bhg2.efg b/contrib/games/bhg2.efg similarity index 100% rename from catalog/gamefiles/bhg2.efg rename to contrib/games/bhg2.efg diff --git a/catalog/gamefiles/bhg3.efg b/contrib/games/bhg3.efg similarity index 100% rename from catalog/gamefiles/bhg3.efg rename to contrib/games/bhg3.efg diff --git a/catalog/gamefiles/bhg4.efg b/contrib/games/bhg4.efg similarity index 100% rename from catalog/gamefiles/bhg4.efg rename to contrib/games/bhg4.efg diff --git a/catalog/gamefiles/bhg5.efg b/contrib/games/bhg5.efg similarity index 100% rename from catalog/gamefiles/bhg5.efg rename to contrib/games/bhg5.efg diff --git a/catalog/gamefiles/caro2.efg b/contrib/games/caro2.efg similarity index 100% rename from catalog/gamefiles/caro2.efg rename to contrib/games/caro2.efg diff --git a/catalog/gamefiles/cent2.efg b/contrib/games/cent2.efg similarity index 100% rename from catalog/gamefiles/cent2.efg rename to contrib/games/cent2.efg diff --git a/catalog/gamefiles/cent2.nfg b/contrib/games/cent2.nfg similarity index 100% rename from catalog/gamefiles/cent2.nfg rename to contrib/games/cent2.nfg diff --git a/catalog/gamefiles/cent3.efg b/contrib/games/cent3.efg similarity index 100% rename from catalog/gamefiles/cent3.efg rename to contrib/games/cent3.efg diff --git a/catalog/gamefiles/cent4.efg b/contrib/games/cent4.efg similarity index 100% rename from catalog/gamefiles/cent4.efg rename to contrib/games/cent4.efg diff --git a/catalog/gamefiles/cent6.efg b/contrib/games/cent6.efg similarity index 100% rename from catalog/gamefiles/cent6.efg rename to contrib/games/cent6.efg diff --git a/catalog/gamefiles/centcs10.efg b/contrib/games/centcs10.efg similarity index 100% rename from catalog/gamefiles/centcs10.efg rename to contrib/games/centcs10.efg diff --git a/catalog/gamefiles/centcs6.efg b/contrib/games/centcs6.efg similarity index 100% rename from catalog/gamefiles/centcs6.efg rename to contrib/games/centcs6.efg diff --git a/catalog/gamefiles/condjury.efg b/contrib/games/condjury.efg similarity index 100% rename from catalog/gamefiles/condjury.efg rename to contrib/games/condjury.efg diff --git a/catalog/gamefiles/coord2.efg b/contrib/games/coord2.efg similarity index 100% rename from catalog/gamefiles/coord2.efg rename to contrib/games/coord2.efg diff --git a/catalog/gamefiles/coord2.nfg b/contrib/games/coord2.nfg similarity index 100% rename from catalog/gamefiles/coord2.nfg rename to contrib/games/coord2.nfg diff --git a/catalog/gamefiles/coord2ts.efg b/contrib/games/coord2ts.efg similarity index 100% rename from catalog/gamefiles/coord2ts.efg rename to contrib/games/coord2ts.efg diff --git a/catalog/gamefiles/coord3.efg b/contrib/games/coord3.efg similarity index 100% rename from catalog/gamefiles/coord3.efg rename to contrib/games/coord3.efg diff --git a/catalog/gamefiles/coord3.nfg b/contrib/games/coord3.nfg similarity index 100% rename from catalog/gamefiles/coord3.nfg rename to contrib/games/coord3.nfg diff --git a/catalog/gamefiles/coord333.nfg b/contrib/games/coord333.nfg similarity index 100% rename from catalog/gamefiles/coord333.nfg rename to contrib/games/coord333.nfg diff --git a/catalog/gamefiles/coord4.efg b/contrib/games/coord4.efg similarity index 100% rename from catalog/gamefiles/coord4.efg rename to contrib/games/coord4.efg diff --git a/catalog/gamefiles/coord4.nfg b/contrib/games/coord4.nfg similarity index 100% rename from catalog/gamefiles/coord4.nfg rename to contrib/games/coord4.nfg diff --git a/catalog/gamefiles/cross.efg b/contrib/games/cross.efg similarity index 100% rename from catalog/gamefiles/cross.efg rename to contrib/games/cross.efg diff --git a/catalog/gamefiles/cs.efg b/contrib/games/cs.efg similarity index 100% rename from catalog/gamefiles/cs.efg rename to contrib/games/cs.efg diff --git a/catalog/gamefiles/csg1.nfg b/contrib/games/csg1.nfg similarity index 100% rename from catalog/gamefiles/csg1.nfg rename to contrib/games/csg1.nfg diff --git a/catalog/gamefiles/csg2.nfg b/contrib/games/csg2.nfg similarity index 100% rename from catalog/gamefiles/csg2.nfg rename to contrib/games/csg2.nfg diff --git a/catalog/gamefiles/csg3.nfg b/contrib/games/csg3.nfg similarity index 100% rename from catalog/gamefiles/csg3.nfg rename to contrib/games/csg3.nfg diff --git a/catalog/gamefiles/csg4.nfg b/contrib/games/csg4.nfg similarity index 100% rename from catalog/gamefiles/csg4.nfg rename to contrib/games/csg4.nfg diff --git a/catalog/gamefiles/deg1.nfg b/contrib/games/deg1.nfg similarity index 100% rename from catalog/gamefiles/deg1.nfg rename to contrib/games/deg1.nfg diff --git a/catalog/gamefiles/deg2.nfg b/contrib/games/deg2.nfg similarity index 100% rename from catalog/gamefiles/deg2.nfg rename to contrib/games/deg2.nfg diff --git a/catalog/gamefiles/e01.efg b/contrib/games/e01.efg similarity index 100% rename from catalog/gamefiles/e01.efg rename to contrib/games/e01.efg diff --git a/catalog/gamefiles/e01.nfg b/contrib/games/e01.nfg similarity index 100% rename from catalog/gamefiles/e01.nfg rename to contrib/games/e01.nfg diff --git a/catalog/gamefiles/e02.efg b/contrib/games/e02.efg similarity index 100% rename from catalog/gamefiles/e02.efg rename to contrib/games/e02.efg diff --git a/catalog/gamefiles/e02.nfg b/contrib/games/e02.nfg similarity index 100% rename from catalog/gamefiles/e02.nfg rename to contrib/games/e02.nfg diff --git a/catalog/gamefiles/e03.efg b/contrib/games/e03.efg similarity index 100% rename from catalog/gamefiles/e03.efg rename to contrib/games/e03.efg diff --git a/catalog/gamefiles/e04.efg b/contrib/games/e04.efg similarity index 100% rename from catalog/gamefiles/e04.efg rename to contrib/games/e04.efg diff --git a/catalog/gamefiles/e04.nfg b/contrib/games/e04.nfg similarity index 100% rename from catalog/gamefiles/e04.nfg rename to contrib/games/e04.nfg diff --git a/catalog/gamefiles/e05.efg b/contrib/games/e05.efg similarity index 100% rename from catalog/gamefiles/e05.efg rename to contrib/games/e05.efg diff --git a/catalog/gamefiles/e06.efg b/contrib/games/e06.efg similarity index 100% rename from catalog/gamefiles/e06.efg rename to contrib/games/e06.efg diff --git a/catalog/gamefiles/e07.efg b/contrib/games/e07.efg similarity index 100% rename from catalog/gamefiles/e07.efg rename to contrib/games/e07.efg diff --git a/catalog/gamefiles/e07.nfg b/contrib/games/e07.nfg similarity index 100% rename from catalog/gamefiles/e07.nfg rename to contrib/games/e07.nfg diff --git a/catalog/gamefiles/e08.efg b/contrib/games/e08.efg similarity index 100% rename from catalog/gamefiles/e08.efg rename to contrib/games/e08.efg diff --git a/catalog/gamefiles/e09.efg b/contrib/games/e09.efg similarity index 100% rename from catalog/gamefiles/e09.efg rename to contrib/games/e09.efg diff --git a/catalog/gamefiles/e10.efg b/contrib/games/e10.efg similarity index 100% rename from catalog/gamefiles/e10.efg rename to contrib/games/e10.efg diff --git a/catalog/gamefiles/e10a.efg b/contrib/games/e10a.efg similarity index 100% rename from catalog/gamefiles/e10a.efg rename to contrib/games/e10a.efg diff --git a/catalog/gamefiles/e13.efg b/contrib/games/e13.efg similarity index 100% rename from catalog/gamefiles/e13.efg rename to contrib/games/e13.efg diff --git a/catalog/gamefiles/e16.efg b/contrib/games/e16.efg similarity index 100% rename from catalog/gamefiles/e16.efg rename to contrib/games/e16.efg diff --git a/catalog/gamefiles/e17.efg b/contrib/games/e17.efg similarity index 100% rename from catalog/gamefiles/e17.efg rename to contrib/games/e17.efg diff --git a/catalog/gamefiles/e18.efg b/contrib/games/e18.efg similarity index 100% rename from catalog/gamefiles/e18.efg rename to contrib/games/e18.efg diff --git a/catalog/gamefiles/g1.efg b/contrib/games/g1.efg similarity index 100% rename from catalog/gamefiles/g1.efg rename to contrib/games/g1.efg diff --git a/catalog/gamefiles/g1.nfg b/contrib/games/g1.nfg similarity index 100% rename from catalog/gamefiles/g1.nfg rename to contrib/games/g1.nfg diff --git a/catalog/gamefiles/g2.efg b/contrib/games/g2.efg similarity index 100% rename from catalog/gamefiles/g2.efg rename to contrib/games/g2.efg diff --git a/catalog/gamefiles/g2.nfg b/contrib/games/g2.nfg similarity index 100% rename from catalog/gamefiles/g2.nfg rename to contrib/games/g2.nfg diff --git a/catalog/gamefiles/g3.efg b/contrib/games/g3.efg similarity index 100% rename from catalog/gamefiles/g3.efg rename to contrib/games/g3.efg diff --git a/catalog/gamefiles/g3.nfg b/contrib/games/g3.nfg similarity index 100% rename from catalog/gamefiles/g3.nfg rename to contrib/games/g3.nfg diff --git a/catalog/gamefiles/holdout.efg b/contrib/games/holdout.efg similarity index 100% rename from catalog/gamefiles/holdout.efg rename to contrib/games/holdout.efg diff --git a/catalog/gamefiles/holdout7.efg b/contrib/games/holdout7.efg similarity index 100% rename from catalog/gamefiles/holdout7.efg rename to contrib/games/holdout7.efg diff --git a/catalog/gamefiles/hs1.efg b/contrib/games/hs1.efg similarity index 100% rename from catalog/gamefiles/hs1.efg rename to contrib/games/hs1.efg diff --git a/catalog/gamefiles/jury_mr.efg b/contrib/games/jury_mr.efg similarity index 100% rename from catalog/gamefiles/jury_mr.efg rename to contrib/games/jury_mr.efg diff --git a/catalog/gamefiles/jury_un.efg b/contrib/games/jury_un.efg similarity index 100% rename from catalog/gamefiles/jury_un.efg rename to contrib/games/jury_un.efg diff --git a/catalog/gamefiles/km1.efg b/contrib/games/km1.efg similarity index 100% rename from catalog/gamefiles/km1.efg rename to contrib/games/km1.efg diff --git a/catalog/gamefiles/km2.efg b/contrib/games/km2.efg similarity index 100% rename from catalog/gamefiles/km2.efg rename to contrib/games/km2.efg diff --git a/catalog/gamefiles/km3.efg b/contrib/games/km3.efg similarity index 100% rename from catalog/gamefiles/km3.efg rename to contrib/games/km3.efg diff --git a/catalog/gamefiles/km6.efg b/contrib/games/km6.efg similarity index 100% rename from catalog/gamefiles/km6.efg rename to contrib/games/km6.efg diff --git a/catalog/gamefiles/loopback.nfg b/contrib/games/loopback.nfg similarity index 100% rename from catalog/gamefiles/loopback.nfg rename to contrib/games/loopback.nfg diff --git a/catalog/gamefiles/mixdom.nfg b/contrib/games/mixdom.nfg similarity index 100% rename from catalog/gamefiles/mixdom.nfg rename to contrib/games/mixdom.nfg diff --git a/catalog/gamefiles/mixdom2.nfg b/contrib/games/mixdom2.nfg similarity index 100% rename from catalog/gamefiles/mixdom2.nfg rename to contrib/games/mixdom2.nfg diff --git a/catalog/gamefiles/montyhal.efg b/contrib/games/montyhal.efg similarity index 100% rename from catalog/gamefiles/montyhal.efg rename to contrib/games/montyhal.efg diff --git a/catalog/gamefiles/my_2-1.efg b/contrib/games/my_2-1.efg similarity index 100% rename from catalog/gamefiles/my_2-1.efg rename to contrib/games/my_2-1.efg diff --git a/catalog/gamefiles/my_2-4.efg b/contrib/games/my_2-4.efg similarity index 100% rename from catalog/gamefiles/my_2-4.efg rename to contrib/games/my_2-4.efg diff --git a/catalog/gamefiles/my_2-8.efg b/contrib/games/my_2-8.efg similarity index 100% rename from catalog/gamefiles/my_2-8.efg rename to contrib/games/my_2-8.efg diff --git a/catalog/gamefiles/my_3-3a.efg b/contrib/games/my_3-3a.efg similarity index 100% rename from catalog/gamefiles/my_3-3a.efg rename to contrib/games/my_3-3a.efg diff --git a/catalog/gamefiles/my_3-3b.efg b/contrib/games/my_3-3b.efg similarity index 100% rename from catalog/gamefiles/my_3-3b.efg rename to contrib/games/my_3-3b.efg diff --git a/catalog/gamefiles/my_3-3c.efg b/contrib/games/my_3-3c.efg similarity index 100% rename from catalog/gamefiles/my_3-3c.efg rename to contrib/games/my_3-3c.efg diff --git a/catalog/gamefiles/my_3-3d.efg b/contrib/games/my_3-3d.efg similarity index 100% rename from catalog/gamefiles/my_3-3d.efg rename to contrib/games/my_3-3d.efg diff --git a/catalog/gamefiles/my_3-3e.efg b/contrib/games/my_3-3e.efg similarity index 100% rename from catalog/gamefiles/my_3-3e.efg rename to contrib/games/my_3-3e.efg diff --git a/catalog/gamefiles/my_3-4.efg b/contrib/games/my_3-4.efg similarity index 100% rename from catalog/gamefiles/my_3-4.efg rename to contrib/games/my_3-4.efg diff --git a/catalog/gamefiles/myerson.efg b/contrib/games/myerson.efg similarity index 100% rename from catalog/gamefiles/myerson.efg rename to contrib/games/myerson.efg diff --git a/catalog/gamefiles/myerson_fig_4_2.efg b/contrib/games/myerson_fig_4_2.efg similarity index 100% rename from catalog/gamefiles/myerson_fig_4_2.efg rename to contrib/games/myerson_fig_4_2.efg diff --git a/catalog/gamefiles/nim.efg b/contrib/games/nim.efg similarity index 100% rename from catalog/gamefiles/nim.efg rename to contrib/games/nim.efg diff --git a/catalog/gamefiles/nim7.efg b/contrib/games/nim7.efg similarity index 100% rename from catalog/gamefiles/nim7.efg rename to contrib/games/nim7.efg diff --git a/catalog/gamefiles/oneill.nfg b/contrib/games/oneill.nfg similarity index 100% rename from catalog/gamefiles/oneill.nfg rename to contrib/games/oneill.nfg diff --git a/catalog/gamefiles/palf.efg b/contrib/games/palf.efg similarity index 100% rename from catalog/gamefiles/palf.efg rename to contrib/games/palf.efg diff --git a/catalog/gamefiles/palf2.efg b/contrib/games/palf2.efg similarity index 100% rename from catalog/gamefiles/palf2.efg rename to contrib/games/palf2.efg diff --git a/catalog/gamefiles/palf3.efg b/contrib/games/palf3.efg similarity index 100% rename from catalog/gamefiles/palf3.efg rename to contrib/games/palf3.efg diff --git a/catalog/gamefiles/pbride.efg b/contrib/games/pbride.efg similarity index 100% rename from catalog/gamefiles/pbride.efg rename to contrib/games/pbride.efg diff --git a/catalog/gamefiles/pd.nfg b/contrib/games/pd.nfg similarity index 100% rename from catalog/gamefiles/pd.nfg rename to contrib/games/pd.nfg diff --git a/catalog/gamefiles/perfect1.nfg b/contrib/games/perfect1.nfg similarity index 100% rename from catalog/gamefiles/perfect1.nfg rename to contrib/games/perfect1.nfg diff --git a/catalog/gamefiles/perfect2.nfg b/contrib/games/perfect2.nfg similarity index 100% rename from catalog/gamefiles/perfect2.nfg rename to contrib/games/perfect2.nfg diff --git a/catalog/gamefiles/perfect3.nfg b/contrib/games/perfect3.nfg similarity index 100% rename from catalog/gamefiles/perfect3.nfg rename to contrib/games/perfect3.nfg diff --git a/catalog/gamefiles/poker.efg b/contrib/games/poker.efg similarity index 100% rename from catalog/gamefiles/poker.efg rename to contrib/games/poker.efg diff --git a/catalog/gamefiles/poker.nfg b/contrib/games/poker.nfg similarity index 100% rename from catalog/gamefiles/poker.nfg rename to contrib/games/poker.nfg diff --git a/catalog/gamefiles/poker2.efg b/contrib/games/poker2.efg similarity index 100% rename from catalog/gamefiles/poker2.efg rename to contrib/games/poker2.efg diff --git a/catalog/gamefiles/pvw.efg b/contrib/games/pvw.efg similarity index 100% rename from catalog/gamefiles/pvw.efg rename to contrib/games/pvw.efg diff --git a/catalog/gamefiles/pvw2.efg b/contrib/games/pvw2.efg similarity index 100% rename from catalog/gamefiles/pvw2.efg rename to contrib/games/pvw2.efg diff --git a/catalog/gamefiles/sh3.efg b/contrib/games/sh3.efg similarity index 100% rename from catalog/gamefiles/sh3.efg rename to contrib/games/sh3.efg diff --git a/catalog/gamefiles/sh3.nfg b/contrib/games/sh3.nfg similarity index 100% rename from catalog/gamefiles/sh3.nfg rename to contrib/games/sh3.nfg diff --git a/catalog/gamefiles/spence.efg b/contrib/games/spence.efg similarity index 100% rename from catalog/gamefiles/spence.efg rename to contrib/games/spence.efg diff --git a/catalog/gamefiles/stengel.nfg b/contrib/games/stengel.nfg similarity index 100% rename from catalog/gamefiles/stengel.nfg rename to contrib/games/stengel.nfg diff --git a/catalog/gamefiles/sww1.efg b/contrib/games/sww1.efg similarity index 100% rename from catalog/gamefiles/sww1.efg rename to contrib/games/sww1.efg diff --git a/catalog/gamefiles/sww1.nfg b/contrib/games/sww1.nfg similarity index 100% rename from catalog/gamefiles/sww1.nfg rename to contrib/games/sww1.nfg diff --git a/catalog/gamefiles/sww2.efg b/contrib/games/sww2.efg similarity index 100% rename from catalog/gamefiles/sww2.efg rename to contrib/games/sww2.efg diff --git a/catalog/gamefiles/sww3.efg b/contrib/games/sww3.efg similarity index 100% rename from catalog/gamefiles/sww3.efg rename to contrib/games/sww3.efg diff --git a/catalog/gamefiles/tim.efg b/contrib/games/tim.efg similarity index 100% rename from catalog/gamefiles/tim.efg rename to contrib/games/tim.efg diff --git a/catalog/gamefiles/todd1.nfg b/contrib/games/todd1.nfg similarity index 100% rename from catalog/gamefiles/todd1.nfg rename to contrib/games/todd1.nfg diff --git a/catalog/gamefiles/todd2.nfg b/contrib/games/todd2.nfg similarity index 100% rename from catalog/gamefiles/todd2.nfg rename to contrib/games/todd2.nfg diff --git a/catalog/gamefiles/todd3.nfg b/contrib/games/todd3.nfg similarity index 100% rename from catalog/gamefiles/todd3.nfg rename to contrib/games/todd3.nfg diff --git a/catalog/gamefiles/ttt.efg b/contrib/games/ttt.efg similarity index 100% rename from catalog/gamefiles/ttt.efg rename to contrib/games/ttt.efg diff --git a/catalog/gamefiles/vd.efg b/contrib/games/vd.efg similarity index 100% rename from catalog/gamefiles/vd.efg rename to contrib/games/vd.efg diff --git a/catalog/gamefiles/vd.nfg b/contrib/games/vd.nfg similarity index 100% rename from catalog/gamefiles/vd.nfg rename to contrib/games/vd.nfg diff --git a/catalog/gamefiles/w_ex1.efg b/contrib/games/w_ex1.efg similarity index 100% rename from catalog/gamefiles/w_ex1.efg rename to contrib/games/w_ex1.efg diff --git a/catalog/gamefiles/w_ex2.efg b/contrib/games/w_ex2.efg similarity index 100% rename from catalog/gamefiles/w_ex2.efg rename to contrib/games/w_ex2.efg diff --git a/catalog/gamefiles/wilson1.efg b/contrib/games/wilson1.efg similarity index 100% rename from catalog/gamefiles/wilson1.efg rename to contrib/games/wilson1.efg diff --git a/catalog/gamefiles/wink3.nfg b/contrib/games/wink3.nfg similarity index 100% rename from catalog/gamefiles/wink3.nfg rename to contrib/games/wink3.nfg diff --git a/catalog/gamefiles/winkels.nfg b/contrib/games/winkels.nfg similarity index 100% rename from catalog/gamefiles/winkels.nfg rename to contrib/games/winkels.nfg diff --git a/catalog/gamefiles/work1.efg b/contrib/games/work1.efg similarity index 100% rename from catalog/gamefiles/work1.efg rename to contrib/games/work1.efg diff --git a/catalog/gamefiles/work2.efg b/contrib/games/work2.efg similarity index 100% rename from catalog/gamefiles/work2.efg rename to contrib/games/work2.efg diff --git a/catalog/gamefiles/work3.efg b/contrib/games/work3.efg similarity index 100% rename from catalog/gamefiles/work3.efg rename to contrib/games/work3.efg diff --git a/catalog/gamefiles/yamamoto.nfg b/contrib/games/yamamoto.nfg similarity index 100% rename from catalog/gamefiles/yamamoto.nfg rename to contrib/games/yamamoto.nfg diff --git a/catalog/gamefiles/zero.nfg b/contrib/games/zero.nfg similarity index 100% rename from catalog/gamefiles/zero.nfg rename to contrib/games/zero.nfg diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 3407adf0c..c12a5fac6 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -431,17 +431,17 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "701aa52a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "['PrisonersDilemma', 'PrisonersDilemmaTestgame']" + "['PrisonersDilemma', 'TwoStageMatchingPennies', 'PrisonersDilemmaTestgame']" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -460,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 3, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -474,7 +474,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 14, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 36386e98f..38fdd015a 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -5,7 +5,7 @@ from pygambit import Game, read_efg, read_nfg -_GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "catalog/gamefiles" +_GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" class CatalogGame: From a0533e53453887c7bd305a0af8279466c12d6337 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 11:02:51 +0000 Subject: [PATCH 022/170] create example fom test suite --- src/pygambit/catalog/catalog.py | 44 +++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 38fdd015a..3e90f0a22 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,8 +1,7 @@ from pathlib import Path from typing import Literal -import numpy as np - +# import numpy as np from pygambit import Game, read_efg, read_nfg _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" @@ -152,20 +151,33 @@ class TwoStageMatchingPennies(CatalogGameFromFile): ########################################## -class PrisonersDilemmaTestgame(CatalogGame): - title = "Test Prisoner's Dilemma" - game_type = "nfg" - description = "A simple test game based on the Prisoner's Dilemma." - citation = "Example citation for Test Prisoner's Dilemma." +class OneShotTrust(CatalogGame): - def __new__(cls) -> Game: - player1_payoffs = np.array([[-1, -3], [0, -2]]) - player2_payoffs = np.transpose(player1_payoffs) + title = "One-shot trust game, after Kreps (1990)" + game_type = "efg" + description = """ + 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. + """ + citation = "Kreps (1990)" - g1 = Game.from_arrays( - player1_payoffs, - player2_payoffs, - title=cls.title, + def __new__(cls, unique_NE_variant: bool = False) -> Game: + g = Game.new_tree( + players=["Buyer", "Seller"], title=cls.title ) - - return g1 + 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 From 5a9dce2b5cd8621900e3ea3129c05d0452003b9f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 11:13:17 +0000 Subject: [PATCH 023/170] make it so each CatalogGame subclass implements _game() instead of __new__() --- src/pygambit/catalog/catalog.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 3e90f0a22..80a713df0 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -20,8 +20,16 @@ class CatalogGame: description: str citation: str - def __new__(cls) -> Game: - raise NotImplementedError("Subclasses must implement __new__ method") + def __new__(cls, *args, **kwargs) -> Game: + """Create a game instance by calling the _game() method.""" + if hasattr(cls, "_game") and cls._game is not CatalogGame._game: + return cls._game(*args, **kwargs) + raise NotImplementedError("Subclasses must implement _game() method") + + @staticmethod + def _game() -> Game: + """Override this method in subclasses to define the game.""" + raise NotImplementedError("Subclasses must implement _game() method") @classmethod def _extract_metadata_from_game(cls, game: Game) -> None: @@ -39,7 +47,7 @@ def __init_subclass__(cls, **kwargs): # For non-file-based games, create a temporary instance to extract metadata try: - temp_game = cls.__new__(cls) + temp_game = cls._game() cls._extract_metadata_from_game(temp_game) except NotImplementedError: # Base class, skip @@ -164,9 +172,10 @@ class OneShotTrust(CatalogGame): """ citation = "Kreps (1990)" - def __new__(cls, unique_NE_variant: bool = False) -> Game: + @staticmethod + def _game(unique_NE_variant: bool = False): g = Game.new_tree( - players=["Buyer", "Seller"], title=cls.title + players=["Buyer", "Seller"], title="One-shot trust game, after Kreps (1990)" ) g.append_move(g.root, "Buyer", ["Trust", "Not trust"]) g.append_move(g.root.children[0], "Seller", ["Honor", "Abuse"]) From 621242fed38ee4b3585659f43ce8107ddfe073d8 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 11:16:35 +0000 Subject: [PATCH 024/170] avoid redefining title --- src/pygambit/catalog/catalog.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 80a713df0..c975408c0 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -160,8 +160,6 @@ class TwoStageMatchingPennies(CatalogGameFromFile): class OneShotTrust(CatalogGame): - - title = "One-shot trust game, after Kreps (1990)" game_type = "efg" description = """ The unique_NE_variant makes Trust a dominant strategy, replacing the From dbe751c1be709d2c97993d711107b8615553a495 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 11:21:33 +0000 Subject: [PATCH 025/170] rename CatalogGameFromFile to CatalogGameFromContrib and update references --- src/pygambit/catalog/catalog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index c975408c0..762393932 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -41,8 +41,8 @@ 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): + # Skip if this is CatalogGameFromContrib or its subclasses + if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): return # For non-file-based games, create a temporary instance to extract metadata @@ -54,7 +54,7 @@ def __init_subclass__(cls, **kwargs): pass -class CatalogGameFromFile(CatalogGame): +class CatalogGameFromContrib(CatalogGame): """ Base class for catalog games loaded from files. This class serves as a template for specific games in the catalog. @@ -125,7 +125,7 @@ def get_all_subclasses(cls): """Recursively get all subclasses.""" all_subclasses = [] for subclass in cls.__subclasses__(): - if subclass.__name__ not in ["CatalogGameFromFile"] and ( + if subclass.__name__ not in ["CatalogGameFromContrib"] and ( game_type == "all" or subclass.game_type == game_type ) and ( num_players is None or subclass.num_players == num_players @@ -142,13 +142,13 @@ def get_all_subclasses(cls): ############################ -class PrisonersDilemma(CatalogGameFromFile): +class PrisonersDilemma(CatalogGameFromContrib): game_file = "pd.nfg" description = "Prisoner's Dilemma game." citation = "Example citation for Prisoner's Dilemma." -class TwoStageMatchingPennies(CatalogGameFromFile): +class TwoStageMatchingPennies(CatalogGameFromContrib): game_file = "2smp.efg" description = "Two-Stage Matching Pennies game." citation = "Example citation for Two-Stage Matching Pennies." From 7ddd64b9d15c9ac1af859775a98e60b08ca044b8 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 11:28:40 +0000 Subject: [PATCH 026/170] loading from the catalog examples in tutorials --- doc/tutorials/01_quickstart.ipynb | 54 +- doc/tutorials/02_extensive_form.ipynb | 942 ++++++++++++++++---------- 2 files changed, 593 insertions(+), 403 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index c12a5fac6..53836406f 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 19, "id": "c58d382d", "metadata": {}, "outputs": [], @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 20, "id": "2060c1ed", "metadata": {}, "outputs": [ @@ -60,7 +60,7 @@ "pygambit.gambit.Game" ] }, - "execution_count": 2, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 21, "id": "9d8203e8", "metadata": {}, "outputs": [], @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 22, "id": "61030607", "metadata": {}, "outputs": [], @@ -135,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 23, "id": "caecc334", "metadata": {}, "outputs": [ @@ -149,7 +149,7 @@ "Game(title='Prisoner's Dilemma')" ] }, - "execution_count": 5, + "execution_count": 23, "metadata": {}, "output_type": "execute_result" } @@ -189,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 24, "id": "843ba7f3", "metadata": {}, "outputs": [ @@ -203,7 +203,7 @@ "Game(title='Another Prisoner's Dilemma')" ] }, - "execution_count": 6, + "execution_count": 24, "metadata": {}, "output_type": "execute_result" } @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 25, "id": "5ee752c4", "metadata": {}, "outputs": [ @@ -270,7 +270,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 26, "id": "a81c06c7", "metadata": {}, "outputs": [ @@ -280,7 +280,7 @@ "pygambit.nash.NashComputationResult" ] }, - "execution_count": 8, + "execution_count": 26, "metadata": {}, "output_type": "execute_result" } @@ -300,7 +300,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 27, "id": "bd395180", "metadata": {}, "outputs": [ @@ -310,7 +310,7 @@ "1" ] }, - "execution_count": 9, + "execution_count": 27, "metadata": {}, "output_type": "execute_result" } @@ -329,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 28, "id": "76570ebc", "metadata": {}, "outputs": [ @@ -342,7 +342,7 @@ "[[Rational(0, 1), Rational(1, 1)], [Rational(0, 1), Rational(1, 1)]]" ] }, - "execution_count": 10, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -354,7 +354,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 29, "id": "6e8cfcde", "metadata": {}, "outputs": [ @@ -364,7 +364,7 @@ "pygambit.gambit.MixedStrategyProfileRational" ] }, - "execution_count": 11, + "execution_count": 29, "metadata": {}, "output_type": "execute_result" } @@ -385,7 +385,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 30, "id": "980bf6b1", "metadata": {}, "outputs": [ @@ -426,22 +426,22 @@ "------------------------------\n", "\n", "Gambit includes a catalog of standard games that can be loaded directly by name.\n", - "You can list all the available games in the catalog like so:" + "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": null, + "execution_count": 31, "id": "701aa52a", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "['PrisonersDilemma', 'TwoStageMatchingPennies', 'PrisonersDilemmaTestgame']" + "['PrisonersDilemma']" ] }, - "execution_count": 4, + "execution_count": 31, "metadata": {}, "output_type": "execute_result" } @@ -460,7 +460,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 32, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -474,7 +474,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 3, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -500,7 +500,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 33, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -518,7 +518,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 34, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 528970c61..d8b7e108e 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -79,20 +79,20 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "" ], @@ -142,119 +142,119 @@ { "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", "" ], @@ -302,181 +302,171 @@ { "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": [ @@ -529,157 +519,147 @@ { "data": { "image/svg+xml": [ - "\n", + "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", "\n", "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\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": [ @@ -728,171 +708,161 @@ { "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": [ @@ -941,179 +911,169 @@ { "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": [ @@ -1136,7 +1096,237 @@ "source": [ "Nodes without an outcome attached are assumed to have payoffs of zero for all players.\n", "\n", - "Therefore, adding the outcome to this latter terminal node is not strictly necessary in Gambit, but it is useful to be explicit for readability." + "Therefore, adding the outcome to this latter terminal node is not strictly necessary in Gambit, but it is useful to be explicit for readability.\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": 14, + "id": "3207441f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['TwoStageMatchingPennies', 'OneShotTrust']" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.catalog.games(game_type=\"efg\", num_players=2)" + ] + }, + { + "cell_type": "markdown", + "id": "af175f99", + "metadata": {}, + "source": [ + "Some games in the catalog have configurable parameters.\n", + "For example, the `OneShotTrust` game has the `unique_NE_variant` parameter, which makes Trust a dominant strategy, replacing the\n", + " non-singleton equilibrium component from the standard version of the game\n", + " where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", + " < 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", + " the Seller plays Abuse." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "4556c0bf", + "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", + "" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "draw_tree(gbt.catalog.OneShotTrust(unique_NE_variant=True))" ] }, { @@ -1156,7 +1346,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 16, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1174,7 +1364,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 17, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1193,7 +1383,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 18, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1205,7 +1395,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 19, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], From d84c3044e736c4a08e2dd77e98b540af068410f5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 15:52:50 +0000 Subject: [PATCH 027/170] fix: correct import path for Game, read_efg, and read_nfg to avoid circular import --- src/pygambit/catalog/catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 762393932..d61776eb1 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -2,7 +2,7 @@ from typing import Literal # import numpy as np -from pygambit import Game, read_efg, read_nfg +from pygambit.gambit import Game, read_efg, read_nfg _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" From 4d46dd596fece304c2d7d28951cbb86994f715e0 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 16:08:39 +0000 Subject: [PATCH 028/170] refactor: implement lazy loading for game classes in catalog to avoid circular imports issue --- src/pygambit/catalog/__init__.py | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 619e73f41..1e54dc415 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,16 +1,25 @@ -from .catalog import games +def __getattr__(name: str): + """Lazily load game classes and the games function.""" + if name == "games": + from .catalog import games + return games -# Dynamically import all catalog games -_all_games = games() -_game_classes = {} + # Try to import the game class + try: + from . import catalog + if hasattr(catalog, name): + return getattr(catalog, name) + except ImportError: + pass -for game_name in _all_games: - # Import each game class from catalog module - from . import catalog - _game_classes[game_name] = getattr(catalog, game_name) + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") -# Add to module namespace -globals().update(_game_classes) -# Build __all__ dynamically -__all__ = ["games"] + list(_all_games) # type: ignore[assignment] +def __dir__(): + """Support dir() by lazily loading all game names.""" + from .catalog import games + _all_games = games() + return ["games"] + _all_games + + +__all__ = ["games"] # type: ignore[assignment] From c5b8dc56a918bf21a568ee209560ba0cfe90ef15 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 16:44:57 +0000 Subject: [PATCH 029/170] Revert "refactor: implement lazy loading for game classes in catalog to avoid circular imports issue" This reverts commit 4d46dd596fece304c2d7d28951cbb86994f715e0. --- src/pygambit/catalog/__init__.py | 33 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 1e54dc415..619e73f41 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,25 +1,16 @@ -def __getattr__(name: str): - """Lazily load game classes and the games function.""" - if name == "games": - from .catalog import games - return games +from .catalog import games - # Try to import the game class - try: - from . import catalog - if hasattr(catalog, name): - return getattr(catalog, name) - except ImportError: - pass +# Dynamically import all catalog games +_all_games = games() +_game_classes = {} - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") +for game_name in _all_games: + # Import each game class from catalog module + from . import catalog + _game_classes[game_name] = getattr(catalog, game_name) +# Add to module namespace +globals().update(_game_classes) -def __dir__(): - """Support dir() by lazily loading all game names.""" - from .catalog import games - _all_games = games() - return ["games"] + _all_games - - -__all__ = ["games"] # type: ignore[assignment] +# Build __all__ dynamically +__all__ = ["games"] + list(_all_games) # type: ignore[assignment] From 8fbe66f74310086b8150908511941b22ee5268a2 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 8 Jan 2026 16:49:10 +0000 Subject: [PATCH 030/170] Don't import catalog when importing pygambit to avoid circular imports --- doc/tutorials/01_quickstart.ipynb | 58 ++++++++++++++------------- doc/tutorials/02_extensive_form.ipynb | 6 ++- src/pygambit/__init__.py | 1 - 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 53836406f..c2b4d900d 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 1, "id": "c58d382d", "metadata": {}, "outputs": [], @@ -50,7 +50,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 2, "id": "2060c1ed", "metadata": {}, "outputs": [ @@ -60,7 +60,7 @@ "pygambit.gambit.Game" ] }, - "execution_count": 20, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 3, "id": "9d8203e8", "metadata": {}, "outputs": [], @@ -111,7 +111,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 4, "id": "61030607", "metadata": {}, "outputs": [], @@ -135,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 5, "id": "caecc334", "metadata": {}, "outputs": [ @@ -149,7 +149,7 @@ "Game(title='Prisoner's Dilemma')" ] }, - "execution_count": 23, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -189,7 +189,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 6, "id": "843ba7f3", "metadata": {}, "outputs": [ @@ -203,7 +203,7 @@ "Game(title='Another Prisoner's Dilemma')" ] }, - "execution_count": 24, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -233,7 +233,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 7, "id": "5ee752c4", "metadata": {}, "outputs": [ @@ -270,7 +270,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 8, "id": "a81c06c7", "metadata": {}, "outputs": [ @@ -280,7 +280,7 @@ "pygambit.nash.NashComputationResult" ] }, - "execution_count": 26, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -300,7 +300,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 9, "id": "bd395180", "metadata": {}, "outputs": [ @@ -310,7 +310,7 @@ "1" ] }, - "execution_count": 27, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -329,7 +329,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "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": 28, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -354,7 +354,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 11, "id": "6e8cfcde", "metadata": {}, "outputs": [ @@ -364,7 +364,7 @@ "pygambit.gambit.MixedStrategyProfileRational" ] }, - "execution_count": 29, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } @@ -385,7 +385,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 12, "id": "980bf6b1", "metadata": {}, "outputs": [ @@ -431,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 14, "id": "701aa52a", "metadata": {}, "outputs": [ @@ -441,13 +441,15 @@ "['PrisonersDilemma']" ] }, - "execution_count": 31, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "gbt.catalog.games(game_type=\"nfg\", num_players=2)" + "from pygambit import catalog\n", + "\n", + "catalog.games(game_type=\"nfg\", num_players=2)" ] }, { @@ -460,7 +462,7 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": 15, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -474,13 +476,13 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 32, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "g = gbt.catalog.PrisonersDilemma()\n", + "g = catalog.PrisonersDilemma()\n", "g" ] }, @@ -500,7 +502,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -518,7 +520,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "id": "4119a2ac", "metadata": {}, "outputs": [], @@ -529,7 +531,7 @@ ], "metadata": { "kernelspec": { - "display_name": "gambitvenv313", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index d8b7e108e..ff7ba5659 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1123,7 +1123,9 @@ } ], "source": [ - "gbt.catalog.games(game_type=\"efg\", num_players=2)" + "from pygambit import catalog\n", + "\n", + "catalog.games(game_type=\"efg\", num_players=2)" ] }, { @@ -1326,7 +1328,7 @@ } ], "source": [ - "draw_tree(gbt.catalog.OneShotTrust(unique_NE_variant=True))" + "draw_tree(catalog.OneShotTrust(unique_NE_variant=True))" ] }, { diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 5f9fb08cc..1d6f730bf 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -26,7 +26,6 @@ nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 - catalog, # noqa: F401 ) import importlib.metadata From a871e3c32343c78ba1a641cf8aa4d103b4138763 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 13 Jan 2026 10:23:11 +0000 Subject: [PATCH 031/170] print description directly from catalog game --- doc/tutorials/02_extensive_form.ipynb | 30 ++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index ff7ba5659..a2c1f0eca 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1134,11 +1134,31 @@ "metadata": {}, "source": [ "Some games in the catalog have configurable parameters.\n", - "For example, the `OneShotTrust` game has the `unique_NE_variant` parameter, which makes Trust a dominant strategy, replacing the\n", - " non-singleton equilibrium component from the standard version of the game\n", - " where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", - " < 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", - " the Seller plays Abuse." + "For example, the `OneShotTrust` game has the `unique_NE_variant` parameter, which is explained in the game's description:" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "2933061a-a2eb-4ca7-8ce3-f350bc0678ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " The unique_NE_variant makes Trust a dominant strategy, replacing the\n", + " non-singleton equilibrium component from the standard version of the game\n", + " where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", + " < 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", + " the Seller plays Abuse.\n", + " \n" + ] + } + ], + "source": [ + "print(catalog.OneShotTrust.description)" ] }, { From 3e0e9408c385bc302bcc6b470dbbef1cabef2967 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 13 Jan 2026 11:29:01 +0000 Subject: [PATCH 032/170] use docstrings as description fields --- src/pygambit/catalog/catalog.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index d61776eb1..cdf379e73 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,3 +1,4 @@ +import inspect from pathlib import Path from typing import Literal @@ -37,6 +38,14 @@ def _extract_metadata_from_game(cls, game: Game) -> None: cls.title = game.title cls.num_players = len(game.players) + @classmethod + def _extract_description_from_docstring(cls) -> None: + """Populate description from the class docstring.""" + if cls.__doc__: + cls.description = inspect.cleandoc(cls.__doc__) + else: + cls.description = "" + def __init_subclass__(cls, **kwargs): """Extract metadata when subclass is defined (if not a file-based game).""" super().__init_subclass__(**kwargs) @@ -45,6 +54,9 @@ def __init_subclass__(cls, **kwargs): if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): return + # Pull description from docstring + cls._extract_description_from_docstring() + # For non-file-based games, create a temporary instance to extract metadata try: temp_game = cls._game() @@ -94,6 +106,9 @@ def __init_subclass__(cls, **kwargs): if not hasattr(cls, "game_file") or cls.game_file is None: raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") + # Pull description from docstring + cls._extract_description_from_docstring() + # Load game and extract metadata immediately when class is defined cls._cached_game = cls._load_game() cls._extract_metadata_from_game(cls._cached_game) @@ -143,14 +158,18 @@ def get_all_subclasses(cls): class PrisonersDilemma(CatalogGameFromContrib): + """ + Prisoner's Dilemma game. + """ game_file = "pd.nfg" - description = "Prisoner's Dilemma game." citation = "Example citation for Prisoner's Dilemma." class TwoStageMatchingPennies(CatalogGameFromContrib): + """ + Two-Stage Matching Pennies game. + """ game_file = "2smp.efg" - description = "Two-Stage Matching Pennies game." citation = "Example citation for Two-Stage Matching Pennies." @@ -160,14 +179,14 @@ class TwoStageMatchingPennies(CatalogGameFromContrib): class OneShotTrust(CatalogGame): - game_type = "efg" - description = """ + """ 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. """ + game_type = "efg" citation = "Kreps (1990)" @staticmethod From 450d06c1ad374331ead9dc4e2d2f6745a79588b0 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 13 Jan 2026 13:13:56 +0000 Subject: [PATCH 033/170] don't instantiate game to get metadata --- src/pygambit/catalog/catalog.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index cdf379e73..202e3a66d 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -32,12 +32,6 @@ def _game() -> Game: """Override this method in subclasses to define the game.""" raise NotImplementedError("Subclasses must implement _game() method") - @classmethod - def _extract_metadata_from_game(cls, game: Game) -> None: - """Extract metadata from the game and set as class attributes.""" - cls.title = game.title - cls.num_players = len(game.players) - @classmethod def _extract_description_from_docstring(cls) -> None: """Populate description from the class docstring.""" @@ -57,14 +51,6 @@ def __init_subclass__(cls, **kwargs): # Pull description from docstring cls._extract_description_from_docstring() - # For non-file-based games, create a temporary instance to extract metadata - try: - temp_game = cls._game() - cls._extract_metadata_from_game(temp_game) - except NotImplementedError: - # Base class, skip - pass - class CatalogGameFromContrib(CatalogGame): """ @@ -109,10 +95,6 @@ def __init_subclass__(cls, **kwargs): # Pull description from docstring cls._extract_description_from_docstring() - # Load game and extract metadata immediately when class is defined - cls._cached_game = cls._load_game() - cls._extract_metadata_from_game(cls._cached_game) - def games( game_type: Literal["all", "nfg", "efg"] = "all", @@ -162,6 +144,9 @@ class PrisonersDilemma(CatalogGameFromContrib): Prisoner's Dilemma game. """ game_file = "pd.nfg" + game_type = "nfg" + title = "Prisoner's Dilemma" + num_players = 2 citation = "Example citation for Prisoner's Dilemma." @@ -170,6 +155,9 @@ class TwoStageMatchingPennies(CatalogGameFromContrib): Two-Stage Matching Pennies game. """ game_file = "2smp.efg" + game_type = "efg" + title = "Two-Stage Matching Pennies game." + num_players = 2 citation = "Example citation for Two-Stage Matching Pennies." @@ -187,6 +175,8 @@ class OneShotTrust(CatalogGame): the Seller plays Abuse. """ game_type = "efg" + title = "One shot trust game." + num_players = 2 citation = "Kreps (1990)" @staticmethod From 9d1cd75d56574acfe7b7eff7f31353152dcd549a Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 13 Jan 2026 13:21:55 +0000 Subject: [PATCH 034/170] simplify docstring retrieval --- src/pygambit/catalog/catalog.py | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 202e3a66d..57c5f78ed 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -32,25 +32,13 @@ def _game() -> Game: """Override this method in subclasses to define the game.""" raise NotImplementedError("Subclasses must implement _game() method") - @classmethod - def _extract_description_from_docstring(cls) -> None: - """Populate description from the class docstring.""" + def __init_subclass__(cls): + """Extract description from docstring when subclass defined.""" if cls.__doc__: cls.description = inspect.cleandoc(cls.__doc__) else: cls.description = "" - 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 CatalogGameFromContrib or its subclasses - if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): - return - - # Pull description from docstring - cls._extract_description_from_docstring() - class CatalogGameFromContrib(CatalogGame): """ @@ -85,16 +73,6 @@ def _load_game(cls) -> Game: else: raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{cls.game_type}'") - def __init_subclass__(cls, **kwargs): - """Validate and extract metadata when subclass is defined.""" - super().__init_subclass__(**kwargs) - - if not hasattr(cls, "game_file") or cls.game_file is None: - raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") - - # Pull description from docstring - cls._extract_description_from_docstring() - def games( game_type: Literal["all", "nfg", "efg"] = "all", From 8a4c26bbf4cce2b32ffceb8719d2e68f3eb7a186 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 13 Jan 2026 13:34:16 +0000 Subject: [PATCH 035/170] simplify CatalogGameFromContrib --- src/pygambit/catalog/catalog.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 57c5f78ed..e6942587a 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -47,23 +47,13 @@ class CatalogGameFromContrib(CatalogGame): Calling any subclass will return an instance of the corresponding game. """ - game_file: str - _cached_game: Game | None = None + game_file: str | None = None def __new__(cls) -> Game: - # Return cached game if available, otherwise load it - if cls._cached_game is None: - cls._cached_game = cls._load_game() - # Return a fresh instance (not the cached one) - return cls._load_game() - - @classmethod - def _load_game(cls) -> Game: """Load the game from file.""" if cls.game_file is None: raise NotImplementedError(f"{cls.__name__} must define 'game_file'") - cls.game_type = cls.game_file.split(".")[-1] file_path = _GAMEFILES_DIR / cls.game_file if cls.game_type == "nfg": From a8148c8be947fff5101ee9121945114fd29adc00 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 13 Jan 2026 13:37:53 +0000 Subject: [PATCH 036/170] revoke need to import catalog separately --- doc/tutorials/01_quickstart.ipynb | 33 ++++++++++++++++++++------ doc/tutorials/02_extensive_form.ipynb | 34 ++++++++++++--------------- src/pygambit/__init__.py | 1 + 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index c2b4d900d..f3b6774e8 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -431,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "701aa52a", "metadata": {}, "outputs": [ @@ -441,15 +441,34 @@ "['PrisonersDilemma']" ] }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.catalog.games(game_type=\"nfg\", num_players=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d89eff93-d9f3-4de0-ab61-6a2b8d2397b3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Prisoner's Dilemma game.\"" + ] + }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from pygambit import catalog\n", - "\n", - "catalog.games(game_type=\"nfg\", num_players=2)" + "gbt.catalog.PrisonersDilemma.description" ] }, { @@ -482,7 +501,7 @@ } ], "source": [ - "g = catalog.PrisonersDilemma()\n", + "g = gbt.catalog.PrisonersDilemma()\n", "g" ] }, @@ -502,7 +521,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -520,7 +539,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index a2c1f0eca..e5963b7ee 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1123,9 +1123,7 @@ } ], "source": [ - "from pygambit import catalog\n", - "\n", - "catalog.games(game_type=\"efg\", num_players=2)" + "gbt.catalog.games(game_type=\"efg\", num_players=2)" ] }, { @@ -1139,7 +1137,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 15, "id": "2933061a-a2eb-4ca7-8ce3-f350bc0678ce", "metadata": {}, "outputs": [ @@ -1147,23 +1145,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "\n", - " The unique_NE_variant makes Trust a dominant strategy, replacing the\n", - " non-singleton equilibrium component from the standard version of the game\n", - " where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", - " < 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", - " the Seller plays Abuse.\n", - " \n" + "The unique_NE_variant makes Trust a dominant strategy, replacing the\n", + "non-singleton equilibrium component from the standard version of the game\n", + "where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", + "< 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", + "the Seller plays Abuse.\n" ] } ], "source": [ - "print(catalog.OneShotTrust.description)" + "print(gbt.catalog.OneShotTrust.description)" ] }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "4556c0bf", "metadata": {}, "outputs": [ @@ -1342,13 +1338,13 @@ "" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "draw_tree(catalog.OneShotTrust(unique_NE_variant=True))" + "draw_tree(gbt.catalog.OneShotTrust(unique_NE_variant=True))" ] }, { @@ -1368,7 +1364,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1386,7 +1382,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1405,7 +1401,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1417,7 +1413,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 1d6f730bf..5f9fb08cc 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -26,6 +26,7 @@ nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 + catalog, # noqa: F401 ) import importlib.metadata From 32ff30756de9f5ae77de0c6adf7c153c38655c60 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 13 Jan 2026 13:54:28 +0000 Subject: [PATCH 037/170] test whether importing one at a time in __init__.py fixes circular import --- src/pygambit/catalog/__init__.py | 38 ++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 619e73f41..1d8710858 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,16 +1,30 @@ -from .catalog import games +# from .catalog import games -# Dynamically import all catalog games -_all_games = games() -_game_classes = {} +# # Dynamically import all catalog games +# _all_games = games() +# _game_classes = {} -for game_name in _all_games: - # Import each game class from catalog module - from . import catalog - _game_classes[game_name] = getattr(catalog, game_name) +# for game_name in _all_games: +# # Import each game class from catalog module +# from . import catalog +# _game_classes[game_name] = getattr(catalog, game_name) -# Add to module namespace -globals().update(_game_classes) +# # Add to module namespace +# globals().update(_game_classes) -# Build __all__ dynamically -__all__ = ["games"] + list(_all_games) # type: ignore[assignment] +# # Build __all__ dynamically +# __all__ = ["games"] + list(_all_games) # type: ignore[assignment] + +from .catalog import ( + OneShotTrust, + PrisonersDilemma, + TwoStageMatchingPennies, + games, +) + +__all__ = [ + "games", + "PrisonersDilemma", + "TwoStageMatchingPennies", + "OneShotTrust", +] From eedce0e89bdf36122f1eb8c5d8d71f48fa7b1126 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 14:27:07 +0000 Subject: [PATCH 038/170] add a couple more example games and import all in __init__.py --- src/pygambit/catalog/__init__.py | 48 ++++++++++++++++---------------- src/pygambit/catalog/catalog.py | 23 +++++++++++++++ 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 1d8710858..52431164c 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,30 +1,30 @@ -# from .catalog import games +from .catalog import games -# # Dynamically import all catalog games -# _all_games = games() -# _game_classes = {} +# Dynamically import all catalog games +_all_games = games() +_game_classes = {} -# for game_name in _all_games: -# # Import each game class from catalog module -# from . import catalog -# _game_classes[game_name] = getattr(catalog, game_name) +for game_name in _all_games: + # Import each game class from catalog module + from . import catalog + _game_classes[game_name] = getattr(catalog, game_name) -# # Add to module namespace -# globals().update(_game_classes) +# Add to module namespace +globals().update(_game_classes) -# # Build __all__ dynamically -# __all__ = ["games"] + list(_all_games) # type: ignore[assignment] +# Build __all__ dynamically +__all__ = ["games"] + list(_all_games) # type: ignore[assignment] -from .catalog import ( - OneShotTrust, - PrisonersDilemma, - TwoStageMatchingPennies, - games, -) +# from .catalog import ( +# OneShotTrust, +# PrisonersDilemma, +# TwoStageMatchingPennies, +# games, +# ) -__all__ = [ - "games", - "PrisonersDilemma", - "TwoStageMatchingPennies", - "OneShotTrust", -] +# __all__ = [ +# "games", +# "PrisonersDilemma", +# "TwoStageMatchingPennies", +# "OneShotTrust", +# ] diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index e6942587a..40828ff15 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -129,6 +129,29 @@ class TwoStageMatchingPennies(CatalogGameFromContrib): citation = "Example citation for Two-Stage Matching Pennies." +class Game2s2x2x2(CatalogGameFromContrib): + """ + Two stage McKelvey McLennan game with 9 equilibria each stage. + """ + + game_file = "2s2x2x2.efg" + game_type = "efg" + title = "Two stage McKelvey McLennan game with 9 equilibria each stage" + num_players = 3 + citation = "Test." + + +class Artist1(CatalogGameFromContrib): + """ + Artist problem, one stage. + """ + + game_file = "artist1.efg" + game_type = "efg" + title = "Artist problem, one stage" + num_players = 2 + citation = "Test." + ########################################## # Catalog games defined programmatically # ########################################## From e7559118f92cb0a5893b5ef6bfcb77fc00a90f2f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 16:22:15 +0000 Subject: [PATCH 039/170] add importability to init --- src/pygambit/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 1d6f730bf..5f9fb08cc 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -26,6 +26,7 @@ nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 + catalog, # noqa: F401 ) import importlib.metadata From 2acb948621002cf78cfacb17a5482c927e8a06dc Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 16:28:58 +0000 Subject: [PATCH 040/170] restore getting attributes from games --- doc/tutorials/01_quickstart.ipynb | 33 +++++++++++++++---- doc/tutorials/02_extensive_form.ipynb | 46 ++++++++++++++++++--------- src/pygambit/catalog/catalog.py | 35 +++++++++++++++----- 3 files changed, 84 insertions(+), 30 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index c2b4d900d..f3b6774e8 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -431,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "701aa52a", "metadata": {}, "outputs": [ @@ -441,15 +441,34 @@ "['PrisonersDilemma']" ] }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.catalog.games(game_type=\"nfg\", num_players=2)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d89eff93-d9f3-4de0-ab61-6a2b8d2397b3", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Prisoner's Dilemma game.\"" + ] + }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from pygambit import catalog\n", - "\n", - "catalog.games(game_type=\"nfg\", num_players=2)" + "gbt.catalog.PrisonersDilemma.description" ] }, { @@ -482,7 +501,7 @@ } ], "source": [ - "g = catalog.PrisonersDilemma()\n", + "g = gbt.catalog.PrisonersDilemma()\n", "g" ] }, @@ -502,7 +521,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -520,7 +539,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index ff7ba5659..4d038b1e9 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1114,7 +1114,7 @@ { "data": { "text/plain": [ - "['TwoStageMatchingPennies', 'OneShotTrust']" + "['TwoStageMatchingPennies', 'Artist1', 'OneShotTrust']" ] }, "execution_count": 14, @@ -1123,9 +1123,7 @@ } ], "source": [ - "from pygambit import catalog\n", - "\n", - "catalog.games(game_type=\"efg\", num_players=2)" + "gbt.catalog.games(game_type=\"efg\", num_players=2)" ] }, { @@ -1134,16 +1132,34 @@ "metadata": {}, "source": [ "Some games in the catalog have configurable parameters.\n", - "For example, the `OneShotTrust` game has the `unique_NE_variant` parameter, which makes Trust a dominant strategy, replacing the\n", - " non-singleton equilibrium component from the standard version of the game\n", - " where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", - " < 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", - " the Seller plays Abuse." + "For example, the `OneShotTrust` game has the `unique_NE_variant` parameter, which is explained in the game's description:" ] }, { "cell_type": "code", "execution_count": 15, + "id": "2933061a-a2eb-4ca7-8ce3-f350bc0678ce", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The unique_NE_variant makes Trust a dominant strategy, replacing the\n", + "non-singleton equilibrium component from the standard version of the game\n", + "where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", + "< 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", + "the Seller plays Abuse.\n" + ] + } + ], + "source": [ + "print(gbt.catalog.OneShotTrust.description)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "id": "4556c0bf", "metadata": {}, "outputs": [ @@ -1322,13 +1338,13 @@ "" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "draw_tree(catalog.OneShotTrust(unique_NE_variant=True))" + "draw_tree(gbt.catalog.OneShotTrust(unique_NE_variant=True))" ] }, { @@ -1348,7 +1364,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1366,7 +1382,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1385,7 +1401,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1397,7 +1413,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index d61776eb1..8d17dda8a 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,3 +1,4 @@ +import inspect from pathlib import Path from typing import Literal @@ -18,7 +19,6 @@ class CatalogGame: num_players: int game_type: Literal["nfg", "efg"] description: str - citation: str def __new__(cls, *args, **kwargs) -> Game: """Create a game instance by calling the _game() method.""" @@ -36,6 +36,10 @@ def _extract_metadata_from_game(cls, game: Game) -> None: """Extract metadata from the game and set as class attributes.""" cls.title = game.title cls.num_players = len(game.players) + if cls.__doc__: + cls.description = inspect.cleandoc(cls.__doc__) + else: + cls.description = game.comment def __init_subclass__(cls, **kwargs): """Extract metadata when subclass is defined (if not a file-based game).""" @@ -143,15 +147,31 @@ def get_all_subclasses(cls): class PrisonersDilemma(CatalogGameFromContrib): + """ + Prisoner's Dilemma game. + """ game_file = "pd.nfg" - description = "Prisoner's Dilemma game." - citation = "Example citation for Prisoner's Dilemma." class TwoStageMatchingPennies(CatalogGameFromContrib): + """ + Two-Stage Matching Pennies game. + """ game_file = "2smp.efg" - description = "Two-Stage Matching Pennies game." - citation = "Example citation for Two-Stage Matching Pennies." + + +class Game2s2x2x2(CatalogGameFromContrib): + """ + Two stage McKelvey McLennan game with 9 equilibria each stage. + """ + game_file = "2s2x2x2.efg" + + +class Artist1(CatalogGameFromContrib): + """ + Artist problem, one stage. + """ + game_file = "artist1.efg" ########################################## @@ -160,15 +180,14 @@ class TwoStageMatchingPennies(CatalogGameFromContrib): class OneShotTrust(CatalogGame): - game_type = "efg" - description = """ + """ 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. """ - citation = "Kreps (1990)" + game_type = "efg" @staticmethod def _game(unique_NE_variant: bool = False): From d88d469e70ee8933b939ec958740cf5602a1a9c5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 16:57:10 +0000 Subject: [PATCH 041/170] finish merge --- src/pygambit/catalog/catalog.py | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 3e4603edc..50ec112ca 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -65,13 +65,23 @@ class CatalogGameFromContrib(CatalogGame): Calling any subclass will return an instance of the corresponding game. """ - game_file: str | None = None + game_file: str + _cached_game: Game | None = None def __new__(cls) -> Game: + # Return cached game if available, otherwise load it + if cls._cached_game is None: + cls._cached_game = cls._load_game() + # Return a fresh instance (not the cached one) + return cls._load_game() + + @classmethod + def _load_game(cls) -> Game: """Load the game from file.""" if cls.game_file is None: raise NotImplementedError(f"{cls.__name__} must define 'game_file'") + cls.game_type = cls.game_file.split(".")[-1] file_path = _GAMEFILES_DIR / cls.game_file if cls.game_type == "nfg": @@ -81,6 +91,17 @@ def __new__(cls) -> Game: else: raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{cls.game_type}'") + def __init_subclass__(cls, **kwargs): + """Validate and extract metadata when subclass is defined.""" + super().__init_subclass__(**kwargs) + + if not hasattr(cls, "game_file") or cls.game_file is None: + raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") + + # Load game and extract metadata immediately when class is defined + cls._cached_game = cls._load_game() + cls._extract_metadata_from_game(cls._cached_game) + def games( game_type: Literal["all", "nfg", "efg"] = "all", @@ -104,14 +125,15 @@ def games( list[str] List of game class names matching the specified type. """ + def get_all_subclasses(cls): """Recursively get all subclasses.""" all_subclasses = [] for subclass in cls.__subclasses__(): - if subclass.__name__ not in ["CatalogGameFromContrib"] and ( - game_type == "all" or subclass.game_type == game_type - ) and ( - num_players is None or subclass.num_players == num_players + if ( + subclass.__name__ not in ["CatalogGameFromContrib"] + and (game_type == "all" or subclass.game_type == game_type) + and (num_players is None or subclass.num_players == num_players) ): all_subclasses.append(subclass.__name__) all_subclasses.extend(get_all_subclasses(subclass)) From 7ca3583cf22d0bd61cc40d3eedda7c524856df9c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 16:59:04 +0000 Subject: [PATCH 042/170] initial script for generating catalog from contrib --- src/pygambit/catalog/get_contrib_games.py | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/pygambit/catalog/get_contrib_games.py diff --git a/src/pygambit/catalog/get_contrib_games.py b/src/pygambit/catalog/get_contrib_games.py new file mode 100644 index 000000000..49b706966 --- /dev/null +++ b/src/pygambit/catalog/get_contrib_games.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +from pathlib import Path + +OUTPUT_FILE = "generated_catalog_games.py" +_GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" + + +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 + + +if __name__ == "__main__": + efg_files = list(_GAMEFILES_DIR.rglob("*.efg")) + nfg_files = list(_GAMEFILES_DIR.rglob("*.nfg")) + + print(f"Found {len(efg_files)} .efg files") + print(f"Found {len(nfg_files)} .nfg files") + + all_files = sorted(efg_files + nfg_files) + + lines = [] + lines.append("# Auto-generated file. Do not edit manually.\n") + lines.append("from .catalog_game import CatalogGameFromContrib\n\n") + + for path in all_files: + stem = path.stem + class_name = make_class_name(stem) + + lines.append(f"class {class_name}(CatalogGameFromContrib):") + lines.append(f' game_file = "{path.name}"') + lines.append("\n") + + output_path = Path(__file__) / OUTPUT_FILE + output_path.write_text("\n".join(lines), encoding="utf-8") + + print(f"Generated {len(all_files)} classes") + print(f"Output written to: {output_path}") + print("Done.") From f0bbd32568d961caa7366a470f208fd811192264 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 17:00:14 +0000 Subject: [PATCH 043/170] fix output path --- src/pygambit/catalog/get_contrib_games.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/catalog/get_contrib_games.py b/src/pygambit/catalog/get_contrib_games.py index 49b706966..3abe45ebc 100644 --- a/src/pygambit/catalog/get_contrib_games.py +++ b/src/pygambit/catalog/get_contrib_games.py @@ -45,7 +45,7 @@ def make_class_name(filename: str) -> str: lines.append(f' game_file = "{path.name}"') lines.append("\n") - output_path = Path(__file__) / OUTPUT_FILE + output_path = Path(__file__).parent / OUTPUT_FILE output_path.write_text("\n".join(lines), encoding="utf-8") print(f"Generated {len(all_files)} classes") From 4dc7227a38085103c5747719dc28e481cd45d72c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 17:01:12 +0000 Subject: [PATCH 044/170] remove disclaimer line --- src/pygambit/catalog/get_contrib_games.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pygambit/catalog/get_contrib_games.py b/src/pygambit/catalog/get_contrib_games.py index 3abe45ebc..8b9be4504 100644 --- a/src/pygambit/catalog/get_contrib_games.py +++ b/src/pygambit/catalog/get_contrib_games.py @@ -34,7 +34,6 @@ def make_class_name(filename: str) -> str: all_files = sorted(efg_files + nfg_files) lines = [] - lines.append("# Auto-generated file. Do not edit manually.\n") lines.append("from .catalog_game import CatalogGameFromContrib\n\n") for path in all_files: From 3a8d2657262aa54e9af3a09898b9d12551be20e9 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 17:10:26 +0000 Subject: [PATCH 045/170] avoid duplicates where EFG and NFG games have the same name --- src/pygambit/catalog/get_contrib_games.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/pygambit/catalog/get_contrib_games.py b/src/pygambit/catalog/get_contrib_games.py index 8b9be4504..03408ed6d 100644 --- a/src/pygambit/catalog/get_contrib_games.py +++ b/src/pygambit/catalog/get_contrib_games.py @@ -36,10 +36,16 @@ def make_class_name(filename: str) -> str: lines = [] lines.append("from .catalog_game import CatalogGameFromContrib\n\n") + class_names = [] for path in all_files: stem = path.stem class_name = make_class_name(stem) + # Avoid duplicates (some EFG and NFG have same name) + if class_name in class_names: + class_name += path.suffix.split(".")[-1].upper() + + class_names.append(class_name) lines.append(f"class {class_name}(CatalogGameFromContrib):") lines.append(f' game_file = "{path.name}"') lines.append("\n") From e481de1ecb83fa0723541226760ca4145f0afc67 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 17:16:05 +0000 Subject: [PATCH 046/170] Add all EFG and NFG games from contrib --- doc/tutorials/01_quickstart.ipynb | 58 +- doc/tutorials/02_extensive_form.ipynb | 80 ++- src/pygambit/catalog/catalog.py | 587 +++++++++++++++++- .../catalog/generated_catalog_games.py | 581 +++++++++++++++++ 4 files changed, 1266 insertions(+), 40 deletions(-) create mode 100644 src/pygambit/catalog/generated_catalog_games.py diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index f3b6774e8..371721aa9 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -438,7 +438,42 @@ { "data": { "text/plain": [ - "['PrisonersDilemma']" + "['Game2x2',\n", + " 'Game2x2a',\n", + " 'Game2x2const',\n", + " 'Game8x8',\n", + " 'Cent2NFG',\n", + " 'Coord2NFG',\n", + " 'Coord3NFG',\n", + " 'Coord4NFG',\n", + " 'Csg1',\n", + " 'Csg2',\n", + " 'Csg3',\n", + " 'Csg4',\n", + " 'Deg1',\n", + " 'Deg2',\n", + " 'E02NFG',\n", + " 'E04NFG',\n", + " 'E07NFG',\n", + " 'Loopback',\n", + " 'Mixdom',\n", + " 'Mixdom2',\n", + " 'Oneill',\n", + " 'PrisonersDilemma',\n", + " 'Perfect1',\n", + " 'Perfect2',\n", + " 'PokerNFG',\n", + " 'Sh3NFG',\n", + " 'Stengel',\n", + " 'Sww1NFG',\n", + " 'Todd1',\n", + " 'Todd2',\n", + " 'Todd3',\n", + " 'VdNFG',\n", + " 'Wink3',\n", + " 'Winkels',\n", + " 'Yamamoto',\n", + " 'Zero']" ] }, "execution_count": 13, @@ -450,27 +485,6 @@ "gbt.catalog.games(game_type=\"nfg\", num_players=2)" ] }, - { - "cell_type": "code", - "execution_count": 14, - "id": "d89eff93-d9f3-4de0-ab61-6a2b8d2397b3", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "\"Prisoner's Dilemma game.\"" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gbt.catalog.PrisonersDilemma.description" - ] - }, { "cell_type": "markdown", "id": "a919ddf7", diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 4d038b1e9..10e59fc5f 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1114,7 +1114,85 @@ { "data": { "text/plain": [ - "['TwoStageMatchingPennies', 'Artist1', 'OneShotTrust']" + "['Game2smp',\n", + " 'Game4cards',\n", + " 'Artist1',\n", + " 'Artist2',\n", + " 'Badgame1',\n", + " 'Badgame2',\n", + " 'Bagwell',\n", + " 'Bayes1a',\n", + " 'Bayes2a',\n", + " 'Bcp2',\n", + " 'Bcp3',\n", + " 'Bcp4',\n", + " 'Bhg1',\n", + " 'Bhg2',\n", + " 'Bhg3',\n", + " 'Bhg4',\n", + " 'Bhg5',\n", + " 'Caro2',\n", + " 'Cent2',\n", + " 'Cent3',\n", + " 'Cent4',\n", + " 'Cent6',\n", + " 'Centcs10',\n", + " 'Centcs6',\n", + " 'Coord2',\n", + " 'Coord2ts',\n", + " 'Coord3',\n", + " 'Coord4',\n", + " 'Cross',\n", + " 'E02',\n", + " 'E04',\n", + " 'E07',\n", + " 'E10',\n", + " 'E10a',\n", + " 'E13',\n", + " 'E16',\n", + " 'E17',\n", + " 'E18',\n", + " 'Holdout',\n", + " 'Hs1',\n", + " 'Km1',\n", + " 'Km2',\n", + " 'Km3',\n", + " 'Km6',\n", + " 'Montyhal',\n", + " 'My_2_1',\n", + " 'My_2_4',\n", + " 'My_3_3a',\n", + " 'My_3_3b',\n", + " 'My_3_3c',\n", + " 'My_3_3d',\n", + " 'My_3_3e',\n", + " 'Myerson',\n", + " 'Myerson_fig_4_2',\n", + " 'Nim',\n", + " 'Nim7',\n", + " 'Palf',\n", + " 'Palf2',\n", + " 'Palf3',\n", + " 'Pbride',\n", + " 'Poker',\n", + " 'Poker2',\n", + " 'Pvw',\n", + " 'Pvw2',\n", + " 'Sh3',\n", + " 'Spence',\n", + " 'Sww1',\n", + " 'Sww2',\n", + " 'Sww3',\n", + " 'Tim',\n", + " 'Ttt',\n", + " 'Vd',\n", + " 'W_ex1',\n", + " 'W_ex2',\n", + " 'Wilson1',\n", + " 'Work1',\n", + " 'Work2',\n", + " 'Work3',\n", + " 'OneShotTrust']" ] }, "execution_count": 14, diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 50ec112ca..18ee23895 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -147,34 +147,587 @@ def get_all_subclasses(cls): ############################ -class PrisonersDilemma(CatalogGameFromContrib): - """ - Prisoner's Dilemma game. - """ - game_file = "pd.nfg" +class Game2s2x2x2(CatalogGameFromContrib): + game_file = "2s2x2x2.efg" -class TwoStageMatchingPennies(CatalogGameFromContrib): - """ - Two-Stage Matching Pennies game. - """ +class Game2smp(CatalogGameFromContrib): game_file = "2smp.efg" -class Game2s2x2x2(CatalogGameFromContrib): - """ - Two stage McKelvey McLennan game with 9 equilibria each stage. - """ - game_file = "2s2x2x2.efg" +class Game2x2(CatalogGameFromContrib): + game_file = "2x2.nfg" + + +class Game2x2a(CatalogGameFromContrib): + game_file = "2x2a.nfg" + + +class Game2x2const(CatalogGameFromContrib): + game_file = "2x2const.nfg" + + +class Game2x2x2_nau(CatalogGameFromContrib): + game_file = "2x2x2-nau.nfg" + + +class Game2x2x2(CatalogGameFromContrib): + game_file = "2x2x2.efg" + + +class Game2x2x2NFG(CatalogGameFromContrib): + game_file = "2x2x2.nfg" + + +class Game2x2x2x2(CatalogGameFromContrib): + game_file = "2x2x2x2.nfg" + + +class Game2x2x2x2x2(CatalogGameFromContrib): + game_file = "2x2x2x2x2.nfg" + + +class Game3x3x3(CatalogGameFromContrib): + game_file = "3x3x3.nfg" + + +class Game4cards(CatalogGameFromContrib): + game_file = "4cards.efg" + + +class Game5x4x3(CatalogGameFromContrib): + game_file = "5x4x3.nfg" + + +class Game8x2x2(CatalogGameFromContrib): + game_file = "8x2x2.nfg" + + +class Game8x8(CatalogGameFromContrib): + game_file = "8x8.nfg" class Artist1(CatalogGameFromContrib): - """ - Artist problem, one stage. - """ game_file = "artist1.efg" +class Artist2(CatalogGameFromContrib): + game_file = "artist2.efg" + + +class Badgame1(CatalogGameFromContrib): + game_file = "badgame1.efg" + + +class Badgame2(CatalogGameFromContrib): + game_file = "badgame2.efg" + + +class Bagwell(CatalogGameFromContrib): + game_file = "bagwell.efg" + + +class Bayes1a(CatalogGameFromContrib): + game_file = "bayes1a.efg" + + +class Bayes2a(CatalogGameFromContrib): + game_file = "bayes2a.efg" + + +class Bcp2(CatalogGameFromContrib): + game_file = "bcp2.efg" + + +class Bcp3(CatalogGameFromContrib): + game_file = "bcp3.efg" + + +class Bcp4(CatalogGameFromContrib): + game_file = "bcp4.efg" + + +class Bhg1(CatalogGameFromContrib): + game_file = "bhg1.efg" + + +class Bhg2(CatalogGameFromContrib): + game_file = "bhg2.efg" + + +class Bhg3(CatalogGameFromContrib): + game_file = "bhg3.efg" + + +class Bhg4(CatalogGameFromContrib): + game_file = "bhg4.efg" + + +class Bhg5(CatalogGameFromContrib): + game_file = "bhg5.efg" + + +class Caro2(CatalogGameFromContrib): + game_file = "caro2.efg" + + +class Cent2(CatalogGameFromContrib): + game_file = "cent2.efg" + + +class Cent2NFG(CatalogGameFromContrib): + game_file = "cent2.nfg" + + +class Cent3(CatalogGameFromContrib): + game_file = "cent3.efg" + + +class Cent4(CatalogGameFromContrib): + game_file = "cent4.efg" + + +class Cent6(CatalogGameFromContrib): + game_file = "cent6.efg" + + +class Centcs10(CatalogGameFromContrib): + game_file = "centcs10.efg" + + +class Centcs6(CatalogGameFromContrib): + game_file = "centcs6.efg" + + +class Condjury(CatalogGameFromContrib): + game_file = "condjury.efg" + + +class Coord2(CatalogGameFromContrib): + game_file = "coord2.efg" + + +class Coord2NFG(CatalogGameFromContrib): + game_file = "coord2.nfg" + + +class Coord2ts(CatalogGameFromContrib): + game_file = "coord2ts.efg" + + +class Coord3(CatalogGameFromContrib): + game_file = "coord3.efg" + + +class Coord3NFG(CatalogGameFromContrib): + game_file = "coord3.nfg" + + +class Coord333(CatalogGameFromContrib): + game_file = "coord333.nfg" + + +class Coord4(CatalogGameFromContrib): + game_file = "coord4.efg" + + +class Coord4NFG(CatalogGameFromContrib): + game_file = "coord4.nfg" + + +class Cross(CatalogGameFromContrib): + game_file = "cross.efg" + + +class Cs(CatalogGameFromContrib): + game_file = "cs.efg" + + +class Csg1(CatalogGameFromContrib): + game_file = "csg1.nfg" + + +class Csg2(CatalogGameFromContrib): + game_file = "csg2.nfg" + + +class Csg3(CatalogGameFromContrib): + game_file = "csg3.nfg" + + +class Csg4(CatalogGameFromContrib): + game_file = "csg4.nfg" + + +class Deg1(CatalogGameFromContrib): + game_file = "deg1.nfg" + + +class Deg2(CatalogGameFromContrib): + game_file = "deg2.nfg" + + +class E01(CatalogGameFromContrib): + game_file = "e01.efg" + + +class E01NFG(CatalogGameFromContrib): + game_file = "e01.nfg" + + +class E02(CatalogGameFromContrib): + game_file = "e02.efg" + + +class E02NFG(CatalogGameFromContrib): + game_file = "e02.nfg" + + +class E03(CatalogGameFromContrib): + game_file = "e03.efg" + + +class E04(CatalogGameFromContrib): + game_file = "e04.efg" + + +class E04NFG(CatalogGameFromContrib): + game_file = "e04.nfg" + + +class E05(CatalogGameFromContrib): + game_file = "e05.efg" + + +class E06(CatalogGameFromContrib): + game_file = "e06.efg" + + +class E07(CatalogGameFromContrib): + game_file = "e07.efg" + + +class E07NFG(CatalogGameFromContrib): + game_file = "e07.nfg" + + +class E08(CatalogGameFromContrib): + game_file = "e08.efg" + + +class E09(CatalogGameFromContrib): + game_file = "e09.efg" + + +class E10(CatalogGameFromContrib): + game_file = "e10.efg" + + +class E10a(CatalogGameFromContrib): + game_file = "e10a.efg" + + +class E13(CatalogGameFromContrib): + game_file = "e13.efg" + + +class E16(CatalogGameFromContrib): + game_file = "e16.efg" + + +class E17(CatalogGameFromContrib): + game_file = "e17.efg" + + +class E18(CatalogGameFromContrib): + game_file = "e18.efg" + + +class G1(CatalogGameFromContrib): + game_file = "g1.efg" + + +class G1NFG(CatalogGameFromContrib): + game_file = "g1.nfg" + + +class G2(CatalogGameFromContrib): + game_file = "g2.efg" + + +class G2NFG(CatalogGameFromContrib): + game_file = "g2.nfg" + + +class G3(CatalogGameFromContrib): + game_file = "g3.efg" + + +class G3NFG(CatalogGameFromContrib): + game_file = "g3.nfg" + + +class Holdout(CatalogGameFromContrib): + game_file = "holdout.efg" + + +# Note: this EFG did not load correctly +# class Holdout7(CatalogGameFromContrib): +# game_file = "holdout7.efg" + + +class Hs1(CatalogGameFromContrib): + game_file = "hs1.efg" + + +class Jury_mr(CatalogGameFromContrib): + game_file = "jury_mr.efg" + + +class Jury_un(CatalogGameFromContrib): + game_file = "jury_un.efg" + + +class Km1(CatalogGameFromContrib): + game_file = "km1.efg" + + +class Km2(CatalogGameFromContrib): + game_file = "km2.efg" + + +class Km3(CatalogGameFromContrib): + game_file = "km3.efg" + + +class Km6(CatalogGameFromContrib): + game_file = "km6.efg" + + +class Loopback(CatalogGameFromContrib): + game_file = "loopback.nfg" + + +class Mixdom(CatalogGameFromContrib): + game_file = "mixdom.nfg" + + +class Mixdom2(CatalogGameFromContrib): + game_file = "mixdom2.nfg" + + +class Montyhal(CatalogGameFromContrib): + game_file = "montyhal.efg" + + +class My_2_1(CatalogGameFromContrib): + game_file = "my_2-1.efg" + + +class My_2_4(CatalogGameFromContrib): + game_file = "my_2-4.efg" + + +class My_2_8(CatalogGameFromContrib): + game_file = "my_2-8.efg" + + +class My_3_3a(CatalogGameFromContrib): + game_file = "my_3-3a.efg" + + +class My_3_3b(CatalogGameFromContrib): + game_file = "my_3-3b.efg" + + +class My_3_3c(CatalogGameFromContrib): + game_file = "my_3-3c.efg" + + +class My_3_3d(CatalogGameFromContrib): + game_file = "my_3-3d.efg" + + +class My_3_3e(CatalogGameFromContrib): + game_file = "my_3-3e.efg" + + +class My_3_4(CatalogGameFromContrib): + game_file = "my_3-4.efg" + + +class Myerson(CatalogGameFromContrib): + game_file = "myerson.efg" + + +class Myerson_fig_4_2(CatalogGameFromContrib): + game_file = "myerson_fig_4_2.efg" + + +class Nim(CatalogGameFromContrib): + game_file = "nim.efg" + + +class Nim7(CatalogGameFromContrib): + game_file = "nim7.efg" + + +class Oneill(CatalogGameFromContrib): + game_file = "oneill.nfg" + + +class Palf(CatalogGameFromContrib): + game_file = "palf.efg" + + +class Palf2(CatalogGameFromContrib): + game_file = "palf2.efg" + + +class Palf3(CatalogGameFromContrib): + game_file = "palf3.efg" + + +class Pbride(CatalogGameFromContrib): + game_file = "pbride.efg" + + +class PrisonersDilemma(CatalogGameFromContrib): + game_file = "pd.nfg" + + +class Perfect1(CatalogGameFromContrib): + game_file = "perfect1.nfg" + + +class Perfect2(CatalogGameFromContrib): + game_file = "perfect2.nfg" + + +class Perfect3(CatalogGameFromContrib): + game_file = "perfect3.nfg" + + +class Poker(CatalogGameFromContrib): + game_file = "poker.efg" + + +class PokerNFG(CatalogGameFromContrib): + game_file = "poker.nfg" + + +class Poker2(CatalogGameFromContrib): + game_file = "poker2.efg" + + +class Pvw(CatalogGameFromContrib): + game_file = "pvw.efg" + + +class Pvw2(CatalogGameFromContrib): + game_file = "pvw2.efg" + + +class Sh3(CatalogGameFromContrib): + game_file = "sh3.efg" + + +class Sh3NFG(CatalogGameFromContrib): + game_file = "sh3.nfg" + + +class Spence(CatalogGameFromContrib): + game_file = "spence.efg" + + +class Stengel(CatalogGameFromContrib): + game_file = "stengel.nfg" + + +class Sww1(CatalogGameFromContrib): + game_file = "sww1.efg" + + +class Sww1NFG(CatalogGameFromContrib): + game_file = "sww1.nfg" + + +class Sww2(CatalogGameFromContrib): + game_file = "sww2.efg" + + +class Sww3(CatalogGameFromContrib): + game_file = "sww3.efg" + + +class Tim(CatalogGameFromContrib): + game_file = "tim.efg" + + +class Todd1(CatalogGameFromContrib): + game_file = "todd1.nfg" + + +class Todd2(CatalogGameFromContrib): + game_file = "todd2.nfg" + + +class Todd3(CatalogGameFromContrib): + game_file = "todd3.nfg" + + +class Ttt(CatalogGameFromContrib): + game_file = "ttt.efg" + + +class Vd(CatalogGameFromContrib): + game_file = "vd.efg" + + +class VdNFG(CatalogGameFromContrib): + game_file = "vd.nfg" + + +class W_ex1(CatalogGameFromContrib): + game_file = "w_ex1.efg" + + +class W_ex2(CatalogGameFromContrib): + game_file = "w_ex2.efg" + + +class Wilson1(CatalogGameFromContrib): + game_file = "wilson1.efg" + + +class Wink3(CatalogGameFromContrib): + game_file = "wink3.nfg" + + +class Winkels(CatalogGameFromContrib): + game_file = "winkels.nfg" + + +class Work1(CatalogGameFromContrib): + game_file = "work1.efg" + + +class Work2(CatalogGameFromContrib): + game_file = "work2.efg" + + +class Work3(CatalogGameFromContrib): + game_file = "work3.efg" + + +class Yamamoto(CatalogGameFromContrib): + game_file = "yamamoto.nfg" + + +class Zero(CatalogGameFromContrib): + game_file = "zero.nfg" + + ########################################## # Catalog games defined programmatically # ########################################## diff --git a/src/pygambit/catalog/generated_catalog_games.py b/src/pygambit/catalog/generated_catalog_games.py new file mode 100644 index 000000000..19a1d5572 --- /dev/null +++ b/src/pygambit/catalog/generated_catalog_games.py @@ -0,0 +1,581 @@ +from .catalog_game import CatalogGameFromContrib + + +class Game2s2x2x2(CatalogGameFromContrib): + game_file = "2s2x2x2.efg" + + +class Game2smp(CatalogGameFromContrib): + game_file = "2smp.efg" + + +class Game2x2(CatalogGameFromContrib): + game_file = "2x2.nfg" + + +class Game2x2a(CatalogGameFromContrib): + game_file = "2x2a.nfg" + + +class Game2x2const(CatalogGameFromContrib): + game_file = "2x2const.nfg" + + +class Game2x2x2_nau(CatalogGameFromContrib): + game_file = "2x2x2-nau.nfg" + + +class Game2x2x2(CatalogGameFromContrib): + game_file = "2x2x2.efg" + + +class Game2x2x2NFG(CatalogGameFromContrib): + game_file = "2x2x2.nfg" + + +class Game2x2x2x2(CatalogGameFromContrib): + game_file = "2x2x2x2.nfg" + + +class Game2x2x2x2x2(CatalogGameFromContrib): + game_file = "2x2x2x2x2.nfg" + + +class Game3x3x3(CatalogGameFromContrib): + game_file = "3x3x3.nfg" + + +class Game4cards(CatalogGameFromContrib): + game_file = "4cards.efg" + + +class Game5x4x3(CatalogGameFromContrib): + game_file = "5x4x3.nfg" + + +class Game8x2x2(CatalogGameFromContrib): + game_file = "8x2x2.nfg" + + +class Game8x8(CatalogGameFromContrib): + game_file = "8x8.nfg" + + +class Artist1(CatalogGameFromContrib): + game_file = "artist1.efg" + + +class Artist2(CatalogGameFromContrib): + game_file = "artist2.efg" + + +class Badgame1(CatalogGameFromContrib): + game_file = "badgame1.efg" + + +class Badgame2(CatalogGameFromContrib): + game_file = "badgame2.efg" + + +class Bagwell(CatalogGameFromContrib): + game_file = "bagwell.efg" + + +class Bayes1a(CatalogGameFromContrib): + game_file = "bayes1a.efg" + + +class Bayes2a(CatalogGameFromContrib): + game_file = "bayes2a.efg" + + +class Bcp2(CatalogGameFromContrib): + game_file = "bcp2.efg" + + +class Bcp3(CatalogGameFromContrib): + game_file = "bcp3.efg" + + +class Bcp4(CatalogGameFromContrib): + game_file = "bcp4.efg" + + +class Bhg1(CatalogGameFromContrib): + game_file = "bhg1.efg" + + +class Bhg2(CatalogGameFromContrib): + game_file = "bhg2.efg" + + +class Bhg3(CatalogGameFromContrib): + game_file = "bhg3.efg" + + +class Bhg4(CatalogGameFromContrib): + game_file = "bhg4.efg" + + +class Bhg5(CatalogGameFromContrib): + game_file = "bhg5.efg" + + +class Caro2(CatalogGameFromContrib): + game_file = "caro2.efg" + + +class Cent2(CatalogGameFromContrib): + game_file = "cent2.efg" + + +class Cent2NFG(CatalogGameFromContrib): + game_file = "cent2.nfg" + + +class Cent3(CatalogGameFromContrib): + game_file = "cent3.efg" + + +class Cent4(CatalogGameFromContrib): + game_file = "cent4.efg" + + +class Cent6(CatalogGameFromContrib): + game_file = "cent6.efg" + + +class Centcs10(CatalogGameFromContrib): + game_file = "centcs10.efg" + + +class Centcs6(CatalogGameFromContrib): + game_file = "centcs6.efg" + + +class Condjury(CatalogGameFromContrib): + game_file = "condjury.efg" + + +class Coord2(CatalogGameFromContrib): + game_file = "coord2.efg" + + +class Coord2NFG(CatalogGameFromContrib): + game_file = "coord2.nfg" + + +class Coord2ts(CatalogGameFromContrib): + game_file = "coord2ts.efg" + + +class Coord3(CatalogGameFromContrib): + game_file = "coord3.efg" + + +class Coord3NFG(CatalogGameFromContrib): + game_file = "coord3.nfg" + + +class Coord333(CatalogGameFromContrib): + game_file = "coord333.nfg" + + +class Coord4(CatalogGameFromContrib): + game_file = "coord4.efg" + + +class Coord4NFG(CatalogGameFromContrib): + game_file = "coord4.nfg" + + +class Cross(CatalogGameFromContrib): + game_file = "cross.efg" + + +class Cs(CatalogGameFromContrib): + game_file = "cs.efg" + + +class Csg1(CatalogGameFromContrib): + game_file = "csg1.nfg" + + +class Csg2(CatalogGameFromContrib): + game_file = "csg2.nfg" + + +class Csg3(CatalogGameFromContrib): + game_file = "csg3.nfg" + + +class Csg4(CatalogGameFromContrib): + game_file = "csg4.nfg" + + +class Deg1(CatalogGameFromContrib): + game_file = "deg1.nfg" + + +class Deg2(CatalogGameFromContrib): + game_file = "deg2.nfg" + + +class E01(CatalogGameFromContrib): + game_file = "e01.efg" + + +class E01NFG(CatalogGameFromContrib): + game_file = "e01.nfg" + + +class E02(CatalogGameFromContrib): + game_file = "e02.efg" + + +class E02NFG(CatalogGameFromContrib): + game_file = "e02.nfg" + + +class E03(CatalogGameFromContrib): + game_file = "e03.efg" + + +class E04(CatalogGameFromContrib): + game_file = "e04.efg" + + +class E04NFG(CatalogGameFromContrib): + game_file = "e04.nfg" + + +class E05(CatalogGameFromContrib): + game_file = "e05.efg" + + +class E06(CatalogGameFromContrib): + game_file = "e06.efg" + + +class E07(CatalogGameFromContrib): + game_file = "e07.efg" + + +class E07NFG(CatalogGameFromContrib): + game_file = "e07.nfg" + + +class E08(CatalogGameFromContrib): + game_file = "e08.efg" + + +class E09(CatalogGameFromContrib): + game_file = "e09.efg" + + +class E10(CatalogGameFromContrib): + game_file = "e10.efg" + + +class E10a(CatalogGameFromContrib): + game_file = "e10a.efg" + + +class E13(CatalogGameFromContrib): + game_file = "e13.efg" + + +class E16(CatalogGameFromContrib): + game_file = "e16.efg" + + +class E17(CatalogGameFromContrib): + game_file = "e17.efg" + + +class E18(CatalogGameFromContrib): + game_file = "e18.efg" + + +class G1(CatalogGameFromContrib): + game_file = "g1.efg" + + +class G1NFG(CatalogGameFromContrib): + game_file = "g1.nfg" + + +class G2(CatalogGameFromContrib): + game_file = "g2.efg" + + +class G2NFG(CatalogGameFromContrib): + game_file = "g2.nfg" + + +class G3(CatalogGameFromContrib): + game_file = "g3.efg" + + +class G3NFG(CatalogGameFromContrib): + game_file = "g3.nfg" + + +class Holdout(CatalogGameFromContrib): + game_file = "holdout.efg" + + +class Holdout7(CatalogGameFromContrib): + game_file = "holdout7.efg" + + +class Hs1(CatalogGameFromContrib): + game_file = "hs1.efg" + + +class Jury_mr(CatalogGameFromContrib): + game_file = "jury_mr.efg" + + +class Jury_un(CatalogGameFromContrib): + game_file = "jury_un.efg" + + +class Km1(CatalogGameFromContrib): + game_file = "km1.efg" + + +class Km2(CatalogGameFromContrib): + game_file = "km2.efg" + + +class Km3(CatalogGameFromContrib): + game_file = "km3.efg" + + +class Km6(CatalogGameFromContrib): + game_file = "km6.efg" + + +class Loopback(CatalogGameFromContrib): + game_file = "loopback.nfg" + + +class Mixdom(CatalogGameFromContrib): + game_file = "mixdom.nfg" + + +class Mixdom2(CatalogGameFromContrib): + game_file = "mixdom2.nfg" + + +class Montyhal(CatalogGameFromContrib): + game_file = "montyhal.efg" + + +class My_2_1(CatalogGameFromContrib): + game_file = "my_2-1.efg" + + +class My_2_4(CatalogGameFromContrib): + game_file = "my_2-4.efg" + + +class My_2_8(CatalogGameFromContrib): + game_file = "my_2-8.efg" + + +class My_3_3a(CatalogGameFromContrib): + game_file = "my_3-3a.efg" + + +class My_3_3b(CatalogGameFromContrib): + game_file = "my_3-3b.efg" + + +class My_3_3c(CatalogGameFromContrib): + game_file = "my_3-3c.efg" + + +class My_3_3d(CatalogGameFromContrib): + game_file = "my_3-3d.efg" + + +class My_3_3e(CatalogGameFromContrib): + game_file = "my_3-3e.efg" + + +class My_3_4(CatalogGameFromContrib): + game_file = "my_3-4.efg" + + +class Myerson(CatalogGameFromContrib): + game_file = "myerson.efg" + + +class Myerson_fig_4_2(CatalogGameFromContrib): + game_file = "myerson_fig_4_2.efg" + + +class Nim(CatalogGameFromContrib): + game_file = "nim.efg" + + +class Nim7(CatalogGameFromContrib): + game_file = "nim7.efg" + + +class Oneill(CatalogGameFromContrib): + game_file = "oneill.nfg" + + +class Palf(CatalogGameFromContrib): + game_file = "palf.efg" + + +class Palf2(CatalogGameFromContrib): + game_file = "palf2.efg" + + +class Palf3(CatalogGameFromContrib): + game_file = "palf3.efg" + + +class Pbride(CatalogGameFromContrib): + game_file = "pbride.efg" + + +class Pd(CatalogGameFromContrib): + game_file = "pd.nfg" + + +class Perfect1(CatalogGameFromContrib): + game_file = "perfect1.nfg" + + +class Perfect2(CatalogGameFromContrib): + game_file = "perfect2.nfg" + + +class Perfect3(CatalogGameFromContrib): + game_file = "perfect3.nfg" + + +class Poker(CatalogGameFromContrib): + game_file = "poker.efg" + + +class PokerNFG(CatalogGameFromContrib): + game_file = "poker.nfg" + + +class Poker2(CatalogGameFromContrib): + game_file = "poker2.efg" + + +class Pvw(CatalogGameFromContrib): + game_file = "pvw.efg" + + +class Pvw2(CatalogGameFromContrib): + game_file = "pvw2.efg" + + +class Sh3(CatalogGameFromContrib): + game_file = "sh3.efg" + + +class Sh3NFG(CatalogGameFromContrib): + game_file = "sh3.nfg" + + +class Spence(CatalogGameFromContrib): + game_file = "spence.efg" + + +class Stengel(CatalogGameFromContrib): + game_file = "stengel.nfg" + + +class Sww1(CatalogGameFromContrib): + game_file = "sww1.efg" + + +class Sww1NFG(CatalogGameFromContrib): + game_file = "sww1.nfg" + + +class Sww2(CatalogGameFromContrib): + game_file = "sww2.efg" + + +class Sww3(CatalogGameFromContrib): + game_file = "sww3.efg" + + +class Tim(CatalogGameFromContrib): + game_file = "tim.efg" + + +class Todd1(CatalogGameFromContrib): + game_file = "todd1.nfg" + + +class Todd2(CatalogGameFromContrib): + game_file = "todd2.nfg" + + +class Todd3(CatalogGameFromContrib): + game_file = "todd3.nfg" + + +class Ttt(CatalogGameFromContrib): + game_file = "ttt.efg" + + +class Vd(CatalogGameFromContrib): + game_file = "vd.efg" + + +class VdNFG(CatalogGameFromContrib): + game_file = "vd.nfg" + + +class W_ex1(CatalogGameFromContrib): + game_file = "w_ex1.efg" + + +class W_ex2(CatalogGameFromContrib): + game_file = "w_ex2.efg" + + +class Wilson1(CatalogGameFromContrib): + game_file = "wilson1.efg" + + +class Wink3(CatalogGameFromContrib): + game_file = "wink3.nfg" + + +class Winkels(CatalogGameFromContrib): + game_file = "winkels.nfg" + + +class Work1(CatalogGameFromContrib): + game_file = "work1.efg" + + +class Work2(CatalogGameFromContrib): + game_file = "work2.efg" + + +class Work3(CatalogGameFromContrib): + game_file = "work3.efg" + + +class Yamamoto(CatalogGameFromContrib): + game_file = "yamamoto.nfg" + + +class Zero(CatalogGameFromContrib): + game_file = "zero.nfg" From 391525a0b8bd1960e1e592797a8da42bfd524492 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 17:22:27 +0000 Subject: [PATCH 047/170] remove generated file from git --- .../catalog/generated_catalog_games.py | 581 ------------------ 1 file changed, 581 deletions(-) delete mode 100644 src/pygambit/catalog/generated_catalog_games.py diff --git a/src/pygambit/catalog/generated_catalog_games.py b/src/pygambit/catalog/generated_catalog_games.py deleted file mode 100644 index 19a1d5572..000000000 --- a/src/pygambit/catalog/generated_catalog_games.py +++ /dev/null @@ -1,581 +0,0 @@ -from .catalog_game import CatalogGameFromContrib - - -class Game2s2x2x2(CatalogGameFromContrib): - game_file = "2s2x2x2.efg" - - -class Game2smp(CatalogGameFromContrib): - game_file = "2smp.efg" - - -class Game2x2(CatalogGameFromContrib): - game_file = "2x2.nfg" - - -class Game2x2a(CatalogGameFromContrib): - game_file = "2x2a.nfg" - - -class Game2x2const(CatalogGameFromContrib): - game_file = "2x2const.nfg" - - -class Game2x2x2_nau(CatalogGameFromContrib): - game_file = "2x2x2-nau.nfg" - - -class Game2x2x2(CatalogGameFromContrib): - game_file = "2x2x2.efg" - - -class Game2x2x2NFG(CatalogGameFromContrib): - game_file = "2x2x2.nfg" - - -class Game2x2x2x2(CatalogGameFromContrib): - game_file = "2x2x2x2.nfg" - - -class Game2x2x2x2x2(CatalogGameFromContrib): - game_file = "2x2x2x2x2.nfg" - - -class Game3x3x3(CatalogGameFromContrib): - game_file = "3x3x3.nfg" - - -class Game4cards(CatalogGameFromContrib): - game_file = "4cards.efg" - - -class Game5x4x3(CatalogGameFromContrib): - game_file = "5x4x3.nfg" - - -class Game8x2x2(CatalogGameFromContrib): - game_file = "8x2x2.nfg" - - -class Game8x8(CatalogGameFromContrib): - game_file = "8x8.nfg" - - -class Artist1(CatalogGameFromContrib): - game_file = "artist1.efg" - - -class Artist2(CatalogGameFromContrib): - game_file = "artist2.efg" - - -class Badgame1(CatalogGameFromContrib): - game_file = "badgame1.efg" - - -class Badgame2(CatalogGameFromContrib): - game_file = "badgame2.efg" - - -class Bagwell(CatalogGameFromContrib): - game_file = "bagwell.efg" - - -class Bayes1a(CatalogGameFromContrib): - game_file = "bayes1a.efg" - - -class Bayes2a(CatalogGameFromContrib): - game_file = "bayes2a.efg" - - -class Bcp2(CatalogGameFromContrib): - game_file = "bcp2.efg" - - -class Bcp3(CatalogGameFromContrib): - game_file = "bcp3.efg" - - -class Bcp4(CatalogGameFromContrib): - game_file = "bcp4.efg" - - -class Bhg1(CatalogGameFromContrib): - game_file = "bhg1.efg" - - -class Bhg2(CatalogGameFromContrib): - game_file = "bhg2.efg" - - -class Bhg3(CatalogGameFromContrib): - game_file = "bhg3.efg" - - -class Bhg4(CatalogGameFromContrib): - game_file = "bhg4.efg" - - -class Bhg5(CatalogGameFromContrib): - game_file = "bhg5.efg" - - -class Caro2(CatalogGameFromContrib): - game_file = "caro2.efg" - - -class Cent2(CatalogGameFromContrib): - game_file = "cent2.efg" - - -class Cent2NFG(CatalogGameFromContrib): - game_file = "cent2.nfg" - - -class Cent3(CatalogGameFromContrib): - game_file = "cent3.efg" - - -class Cent4(CatalogGameFromContrib): - game_file = "cent4.efg" - - -class Cent6(CatalogGameFromContrib): - game_file = "cent6.efg" - - -class Centcs10(CatalogGameFromContrib): - game_file = "centcs10.efg" - - -class Centcs6(CatalogGameFromContrib): - game_file = "centcs6.efg" - - -class Condjury(CatalogGameFromContrib): - game_file = "condjury.efg" - - -class Coord2(CatalogGameFromContrib): - game_file = "coord2.efg" - - -class Coord2NFG(CatalogGameFromContrib): - game_file = "coord2.nfg" - - -class Coord2ts(CatalogGameFromContrib): - game_file = "coord2ts.efg" - - -class Coord3(CatalogGameFromContrib): - game_file = "coord3.efg" - - -class Coord3NFG(CatalogGameFromContrib): - game_file = "coord3.nfg" - - -class Coord333(CatalogGameFromContrib): - game_file = "coord333.nfg" - - -class Coord4(CatalogGameFromContrib): - game_file = "coord4.efg" - - -class Coord4NFG(CatalogGameFromContrib): - game_file = "coord4.nfg" - - -class Cross(CatalogGameFromContrib): - game_file = "cross.efg" - - -class Cs(CatalogGameFromContrib): - game_file = "cs.efg" - - -class Csg1(CatalogGameFromContrib): - game_file = "csg1.nfg" - - -class Csg2(CatalogGameFromContrib): - game_file = "csg2.nfg" - - -class Csg3(CatalogGameFromContrib): - game_file = "csg3.nfg" - - -class Csg4(CatalogGameFromContrib): - game_file = "csg4.nfg" - - -class Deg1(CatalogGameFromContrib): - game_file = "deg1.nfg" - - -class Deg2(CatalogGameFromContrib): - game_file = "deg2.nfg" - - -class E01(CatalogGameFromContrib): - game_file = "e01.efg" - - -class E01NFG(CatalogGameFromContrib): - game_file = "e01.nfg" - - -class E02(CatalogGameFromContrib): - game_file = "e02.efg" - - -class E02NFG(CatalogGameFromContrib): - game_file = "e02.nfg" - - -class E03(CatalogGameFromContrib): - game_file = "e03.efg" - - -class E04(CatalogGameFromContrib): - game_file = "e04.efg" - - -class E04NFG(CatalogGameFromContrib): - game_file = "e04.nfg" - - -class E05(CatalogGameFromContrib): - game_file = "e05.efg" - - -class E06(CatalogGameFromContrib): - game_file = "e06.efg" - - -class E07(CatalogGameFromContrib): - game_file = "e07.efg" - - -class E07NFG(CatalogGameFromContrib): - game_file = "e07.nfg" - - -class E08(CatalogGameFromContrib): - game_file = "e08.efg" - - -class E09(CatalogGameFromContrib): - game_file = "e09.efg" - - -class E10(CatalogGameFromContrib): - game_file = "e10.efg" - - -class E10a(CatalogGameFromContrib): - game_file = "e10a.efg" - - -class E13(CatalogGameFromContrib): - game_file = "e13.efg" - - -class E16(CatalogGameFromContrib): - game_file = "e16.efg" - - -class E17(CatalogGameFromContrib): - game_file = "e17.efg" - - -class E18(CatalogGameFromContrib): - game_file = "e18.efg" - - -class G1(CatalogGameFromContrib): - game_file = "g1.efg" - - -class G1NFG(CatalogGameFromContrib): - game_file = "g1.nfg" - - -class G2(CatalogGameFromContrib): - game_file = "g2.efg" - - -class G2NFG(CatalogGameFromContrib): - game_file = "g2.nfg" - - -class G3(CatalogGameFromContrib): - game_file = "g3.efg" - - -class G3NFG(CatalogGameFromContrib): - game_file = "g3.nfg" - - -class Holdout(CatalogGameFromContrib): - game_file = "holdout.efg" - - -class Holdout7(CatalogGameFromContrib): - game_file = "holdout7.efg" - - -class Hs1(CatalogGameFromContrib): - game_file = "hs1.efg" - - -class Jury_mr(CatalogGameFromContrib): - game_file = "jury_mr.efg" - - -class Jury_un(CatalogGameFromContrib): - game_file = "jury_un.efg" - - -class Km1(CatalogGameFromContrib): - game_file = "km1.efg" - - -class Km2(CatalogGameFromContrib): - game_file = "km2.efg" - - -class Km3(CatalogGameFromContrib): - game_file = "km3.efg" - - -class Km6(CatalogGameFromContrib): - game_file = "km6.efg" - - -class Loopback(CatalogGameFromContrib): - game_file = "loopback.nfg" - - -class Mixdom(CatalogGameFromContrib): - game_file = "mixdom.nfg" - - -class Mixdom2(CatalogGameFromContrib): - game_file = "mixdom2.nfg" - - -class Montyhal(CatalogGameFromContrib): - game_file = "montyhal.efg" - - -class My_2_1(CatalogGameFromContrib): - game_file = "my_2-1.efg" - - -class My_2_4(CatalogGameFromContrib): - game_file = "my_2-4.efg" - - -class My_2_8(CatalogGameFromContrib): - game_file = "my_2-8.efg" - - -class My_3_3a(CatalogGameFromContrib): - game_file = "my_3-3a.efg" - - -class My_3_3b(CatalogGameFromContrib): - game_file = "my_3-3b.efg" - - -class My_3_3c(CatalogGameFromContrib): - game_file = "my_3-3c.efg" - - -class My_3_3d(CatalogGameFromContrib): - game_file = "my_3-3d.efg" - - -class My_3_3e(CatalogGameFromContrib): - game_file = "my_3-3e.efg" - - -class My_3_4(CatalogGameFromContrib): - game_file = "my_3-4.efg" - - -class Myerson(CatalogGameFromContrib): - game_file = "myerson.efg" - - -class Myerson_fig_4_2(CatalogGameFromContrib): - game_file = "myerson_fig_4_2.efg" - - -class Nim(CatalogGameFromContrib): - game_file = "nim.efg" - - -class Nim7(CatalogGameFromContrib): - game_file = "nim7.efg" - - -class Oneill(CatalogGameFromContrib): - game_file = "oneill.nfg" - - -class Palf(CatalogGameFromContrib): - game_file = "palf.efg" - - -class Palf2(CatalogGameFromContrib): - game_file = "palf2.efg" - - -class Palf3(CatalogGameFromContrib): - game_file = "palf3.efg" - - -class Pbride(CatalogGameFromContrib): - game_file = "pbride.efg" - - -class Pd(CatalogGameFromContrib): - game_file = "pd.nfg" - - -class Perfect1(CatalogGameFromContrib): - game_file = "perfect1.nfg" - - -class Perfect2(CatalogGameFromContrib): - game_file = "perfect2.nfg" - - -class Perfect3(CatalogGameFromContrib): - game_file = "perfect3.nfg" - - -class Poker(CatalogGameFromContrib): - game_file = "poker.efg" - - -class PokerNFG(CatalogGameFromContrib): - game_file = "poker.nfg" - - -class Poker2(CatalogGameFromContrib): - game_file = "poker2.efg" - - -class Pvw(CatalogGameFromContrib): - game_file = "pvw.efg" - - -class Pvw2(CatalogGameFromContrib): - game_file = "pvw2.efg" - - -class Sh3(CatalogGameFromContrib): - game_file = "sh3.efg" - - -class Sh3NFG(CatalogGameFromContrib): - game_file = "sh3.nfg" - - -class Spence(CatalogGameFromContrib): - game_file = "spence.efg" - - -class Stengel(CatalogGameFromContrib): - game_file = "stengel.nfg" - - -class Sww1(CatalogGameFromContrib): - game_file = "sww1.efg" - - -class Sww1NFG(CatalogGameFromContrib): - game_file = "sww1.nfg" - - -class Sww2(CatalogGameFromContrib): - game_file = "sww2.efg" - - -class Sww3(CatalogGameFromContrib): - game_file = "sww3.efg" - - -class Tim(CatalogGameFromContrib): - game_file = "tim.efg" - - -class Todd1(CatalogGameFromContrib): - game_file = "todd1.nfg" - - -class Todd2(CatalogGameFromContrib): - game_file = "todd2.nfg" - - -class Todd3(CatalogGameFromContrib): - game_file = "todd3.nfg" - - -class Ttt(CatalogGameFromContrib): - game_file = "ttt.efg" - - -class Vd(CatalogGameFromContrib): - game_file = "vd.efg" - - -class VdNFG(CatalogGameFromContrib): - game_file = "vd.nfg" - - -class W_ex1(CatalogGameFromContrib): - game_file = "w_ex1.efg" - - -class W_ex2(CatalogGameFromContrib): - game_file = "w_ex2.efg" - - -class Wilson1(CatalogGameFromContrib): - game_file = "wilson1.efg" - - -class Wink3(CatalogGameFromContrib): - game_file = "wink3.nfg" - - -class Winkels(CatalogGameFromContrib): - game_file = "winkels.nfg" - - -class Work1(CatalogGameFromContrib): - game_file = "work1.efg" - - -class Work2(CatalogGameFromContrib): - game_file = "work2.efg" - - -class Work3(CatalogGameFromContrib): - game_file = "work3.efg" - - -class Yamamoto(CatalogGameFromContrib): - game_file = "yamamoto.nfg" - - -class Zero(CatalogGameFromContrib): - game_file = "zero.nfg" From 5e99f3b66bec812ad5f68bb732912bdfcd668b98 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 14 Jan 2026 17:24:18 +0000 Subject: [PATCH 048/170] add generated_catalog_games.py to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 9d37e0d3b..9b2c46516 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ Gambit.app/* *.ef build_support/msw/gambit.wxs build_support/osx/Info.plist +src/pygambit/catalog/generated_catalog_games.py From b832ec416a9ed364c825ea5ebad17399c74feaa9 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 10:40:40 +0000 Subject: [PATCH 049/170] create catalog.yml instead of directly creating classes --- src/pygambit/catalog/catalog.yml | 579 ++++++++++++++++++++++ src/pygambit/catalog/get_contrib_games.py | 12 +- 2 files changed, 584 insertions(+), 7 deletions(-) create mode 100644 src/pygambit/catalog/catalog.yml diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog/catalog.yml new file mode 100644 index 000000000..0cdac1729 --- /dev/null +++ b/src/pygambit/catalog/catalog.yml @@ -0,0 +1,579 @@ +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: + +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: + +Pd: + file: "pd.nfg" + metadata: + +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/src/pygambit/catalog/get_contrib_games.py b/src/pygambit/catalog/get_contrib_games.py index 03408ed6d..c0321bdcb 100644 --- a/src/pygambit/catalog/get_contrib_games.py +++ b/src/pygambit/catalog/get_contrib_games.py @@ -2,7 +2,7 @@ from pathlib import Path -OUTPUT_FILE = "generated_catalog_games.py" +OUTPUT_FILE = "catalog.yml" _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" @@ -34,8 +34,6 @@ def make_class_name(filename: str) -> str: all_files = sorted(efg_files + nfg_files) lines = [] - lines.append("from .catalog_game import CatalogGameFromContrib\n\n") - class_names = [] for path in all_files: stem = path.stem @@ -46,13 +44,13 @@ def make_class_name(filename: str) -> str: class_name += path.suffix.split(".")[-1].upper() class_names.append(class_name) - lines.append(f"class {class_name}(CatalogGameFromContrib):") - lines.append(f' game_file = "{path.name}"') - lines.append("\n") + lines.append(f"{class_name}:") + lines.append(f' file: "{path.name}"') + lines.append(" metadata:\n") output_path = Path(__file__).parent / OUTPUT_FILE output_path.write_text("\n".join(lines), encoding="utf-8") - print(f"Generated {len(all_files)} classes") + print(f"Generated {len(all_files)} new entries to the catalog") print(f"Output written to: {output_path}") print("Done.") From a058c3f4e6f4c17c8ff2f9c9e73dd04c329c9d81 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 11:08:05 +0000 Subject: [PATCH 050/170] Dynamically generate CatalogGameFromContrib subclasses from YAML --- doc/tutorials/01_quickstart.ipynb | 8 +- src/pygambit/catalog/__init__.py | 28 +- src/pygambit/catalog/catalog.py | 602 ++---------------------------- src/pygambit/catalog/catalog.yml | 8 +- 4 files changed, 48 insertions(+), 598 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 371721aa9..c6780471e 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -495,7 +495,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -509,7 +509,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 15, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -535,7 +535,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -553,7 +553,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 52431164c..4c3385ef8 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,30 +1,20 @@ +from . import catalog from .catalog import games -# Dynamically import all catalog games +# Ensure catalog module is fully imported including all YAML-generated classes exist _all_games = games() _game_classes = {} for game_name in _all_games: - # Import each game class from catalog module - from . import catalog - _game_classes[game_name] = getattr(catalog, game_name) + try: + _game_classes[game_name] = getattr(catalog, game_name) + except AttributeError as e: + raise ImportError( + f"Catalog game '{game_name}' listed but not found in catalog module" + ) from e # Add to module namespace globals().update(_game_classes) # Build __all__ dynamically -__all__ = ["games"] + list(_all_games) # type: ignore[assignment] - -# from .catalog import ( -# OneShotTrust, -# PrisonersDilemma, -# TwoStageMatchingPennies, -# games, -# ) - -# __all__ = [ -# "games", -# "PrisonersDilemma", -# "TwoStageMatchingPennies", -# "OneShotTrust", -# ] +__all__ = ["games", *list(_all_games)] diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 18ee23895..93b4b1ad4 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,7 +1,10 @@ import inspect +import sys from pathlib import Path from typing import Literal +import yaml + # import numpy as np from pygambit.gambit import Game, read_efg, read_nfg @@ -146,587 +149,44 @@ def get_all_subclasses(cls): # Catalog games from files # ############################ - -class Game2s2x2x2(CatalogGameFromContrib): - game_file = "2s2x2x2.efg" - - -class Game2smp(CatalogGameFromContrib): - game_file = "2smp.efg" - - -class Game2x2(CatalogGameFromContrib): - game_file = "2x2.nfg" - - -class Game2x2a(CatalogGameFromContrib): - game_file = "2x2a.nfg" - - -class Game2x2const(CatalogGameFromContrib): - game_file = "2x2const.nfg" - - -class Game2x2x2_nau(CatalogGameFromContrib): - game_file = "2x2x2-nau.nfg" - - -class Game2x2x2(CatalogGameFromContrib): - game_file = "2x2x2.efg" - - -class Game2x2x2NFG(CatalogGameFromContrib): - game_file = "2x2x2.nfg" - - -class Game2x2x2x2(CatalogGameFromContrib): - game_file = "2x2x2x2.nfg" - - -class Game2x2x2x2x2(CatalogGameFromContrib): - game_file = "2x2x2x2x2.nfg" - - -class Game3x3x3(CatalogGameFromContrib): - game_file = "3x3x3.nfg" - - -class Game4cards(CatalogGameFromContrib): - game_file = "4cards.efg" - - -class Game5x4x3(CatalogGameFromContrib): - game_file = "5x4x3.nfg" - - -class Game8x2x2(CatalogGameFromContrib): - game_file = "8x2x2.nfg" - - -class Game8x8(CatalogGameFromContrib): - game_file = "8x8.nfg" - - -class Artist1(CatalogGameFromContrib): - game_file = "artist1.efg" - - -class Artist2(CatalogGameFromContrib): - game_file = "artist2.efg" - - -class Badgame1(CatalogGameFromContrib): - game_file = "badgame1.efg" - - -class Badgame2(CatalogGameFromContrib): - game_file = "badgame2.efg" - - -class Bagwell(CatalogGameFromContrib): - game_file = "bagwell.efg" - - -class Bayes1a(CatalogGameFromContrib): - game_file = "bayes1a.efg" - - -class Bayes2a(CatalogGameFromContrib): - game_file = "bayes2a.efg" - - -class Bcp2(CatalogGameFromContrib): - game_file = "bcp2.efg" - - -class Bcp3(CatalogGameFromContrib): - game_file = "bcp3.efg" - - -class Bcp4(CatalogGameFromContrib): - game_file = "bcp4.efg" - - -class Bhg1(CatalogGameFromContrib): - game_file = "bhg1.efg" - - -class Bhg2(CatalogGameFromContrib): - game_file = "bhg2.efg" - - -class Bhg3(CatalogGameFromContrib): - game_file = "bhg3.efg" - - -class Bhg4(CatalogGameFromContrib): - game_file = "bhg4.efg" - - -class Bhg5(CatalogGameFromContrib): - game_file = "bhg5.efg" - - -class Caro2(CatalogGameFromContrib): - game_file = "caro2.efg" - - -class Cent2(CatalogGameFromContrib): - game_file = "cent2.efg" - - -class Cent2NFG(CatalogGameFromContrib): - game_file = "cent2.nfg" - - -class Cent3(CatalogGameFromContrib): - game_file = "cent3.efg" - - -class Cent4(CatalogGameFromContrib): - game_file = "cent4.efg" - - -class Cent6(CatalogGameFromContrib): - game_file = "cent6.efg" - - -class Centcs10(CatalogGameFromContrib): - game_file = "centcs10.efg" - - -class Centcs6(CatalogGameFromContrib): - game_file = "centcs6.efg" - - -class Condjury(CatalogGameFromContrib): - game_file = "condjury.efg" - - -class Coord2(CatalogGameFromContrib): - game_file = "coord2.efg" - - -class Coord2NFG(CatalogGameFromContrib): - game_file = "coord2.nfg" - - -class Coord2ts(CatalogGameFromContrib): - game_file = "coord2ts.efg" - - -class Coord3(CatalogGameFromContrib): - game_file = "coord3.efg" - - -class Coord3NFG(CatalogGameFromContrib): - game_file = "coord3.nfg" - - -class Coord333(CatalogGameFromContrib): - game_file = "coord333.nfg" - - -class Coord4(CatalogGameFromContrib): - game_file = "coord4.efg" - - -class Coord4NFG(CatalogGameFromContrib): - game_file = "coord4.nfg" - - -class Cross(CatalogGameFromContrib): - game_file = "cross.efg" - - -class Cs(CatalogGameFromContrib): - game_file = "cs.efg" - - -class Csg1(CatalogGameFromContrib): - game_file = "csg1.nfg" - - -class Csg2(CatalogGameFromContrib): - game_file = "csg2.nfg" - - -class Csg3(CatalogGameFromContrib): - game_file = "csg3.nfg" - - -class Csg4(CatalogGameFromContrib): - game_file = "csg4.nfg" - - -class Deg1(CatalogGameFromContrib): - game_file = "deg1.nfg" - - -class Deg2(CatalogGameFromContrib): - game_file = "deg2.nfg" - - -class E01(CatalogGameFromContrib): - game_file = "e01.efg" - - -class E01NFG(CatalogGameFromContrib): - game_file = "e01.nfg" - - -class E02(CatalogGameFromContrib): - game_file = "e02.efg" - - -class E02NFG(CatalogGameFromContrib): - game_file = "e02.nfg" - - -class E03(CatalogGameFromContrib): - game_file = "e03.efg" - - -class E04(CatalogGameFromContrib): - game_file = "e04.efg" - - -class E04NFG(CatalogGameFromContrib): - game_file = "e04.nfg" - - -class E05(CatalogGameFromContrib): - game_file = "e05.efg" - - -class E06(CatalogGameFromContrib): - game_file = "e06.efg" - - -class E07(CatalogGameFromContrib): - game_file = "e07.efg" - - -class E07NFG(CatalogGameFromContrib): - game_file = "e07.nfg" - - -class E08(CatalogGameFromContrib): - game_file = "e08.efg" - - -class E09(CatalogGameFromContrib): - game_file = "e09.efg" - - -class E10(CatalogGameFromContrib): - game_file = "e10.efg" - - -class E10a(CatalogGameFromContrib): - game_file = "e10a.efg" - - -class E13(CatalogGameFromContrib): - game_file = "e13.efg" +_CATALOG_YAML = Path(__file__).parent / "catalog.yml" -class E16(CatalogGameFromContrib): - game_file = "e16.efg" +def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: + if not path.exists(): + raise FileNotFoundError(f"Catalog YAML not found: {path}") + with path.open("r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} -class E17(CatalogGameFromContrib): - game_file = "e17.efg" - - -class E18(CatalogGameFromContrib): - game_file = "e18.efg" - - -class G1(CatalogGameFromContrib): - game_file = "g1.efg" - - -class G1NFG(CatalogGameFromContrib): - game_file = "g1.nfg" - - -class G2(CatalogGameFromContrib): - game_file = "g2.efg" - - -class G2NFG(CatalogGameFromContrib): - game_file = "g2.nfg" - - -class G3(CatalogGameFromContrib): - game_file = "g3.efg" - - -class G3NFG(CatalogGameFromContrib): - game_file = "g3.nfg" - - -class Holdout(CatalogGameFromContrib): - game_file = "holdout.efg" - - -# Note: this EFG did not load correctly -# class Holdout7(CatalogGameFromContrib): -# game_file = "holdout7.efg" - - -class Hs1(CatalogGameFromContrib): - game_file = "hs1.efg" - - -class Jury_mr(CatalogGameFromContrib): - game_file = "jury_mr.efg" - - -class Jury_un(CatalogGameFromContrib): - game_file = "jury_un.efg" - - -class Km1(CatalogGameFromContrib): - game_file = "km1.efg" - - -class Km2(CatalogGameFromContrib): - game_file = "km2.efg" - - -class Km3(CatalogGameFromContrib): - game_file = "km3.efg" - - -class Km6(CatalogGameFromContrib): - game_file = "km6.efg" - - -class Loopback(CatalogGameFromContrib): - game_file = "loopback.nfg" - - -class Mixdom(CatalogGameFromContrib): - game_file = "mixdom.nfg" - - -class Mixdom2(CatalogGameFromContrib): - game_file = "mixdom2.nfg" - - -class Montyhal(CatalogGameFromContrib): - game_file = "montyhal.efg" - - -class My_2_1(CatalogGameFromContrib): - game_file = "my_2-1.efg" - - -class My_2_4(CatalogGameFromContrib): - game_file = "my_2-4.efg" - - -class My_2_8(CatalogGameFromContrib): - game_file = "my_2-8.efg" - - -class My_3_3a(CatalogGameFromContrib): - game_file = "my_3-3a.efg" - - -class My_3_3b(CatalogGameFromContrib): - game_file = "my_3-3b.efg" - - -class My_3_3c(CatalogGameFromContrib): - game_file = "my_3-3c.efg" - - -class My_3_3d(CatalogGameFromContrib): - game_file = "my_3-3d.efg" - - -class My_3_3e(CatalogGameFromContrib): - game_file = "my_3-3e.efg" - - -class My_3_4(CatalogGameFromContrib): - game_file = "my_3-4.efg" - - -class Myerson(CatalogGameFromContrib): - game_file = "myerson.efg" - - -class Myerson_fig_4_2(CatalogGameFromContrib): - game_file = "myerson_fig_4_2.efg" - - -class Nim(CatalogGameFromContrib): - game_file = "nim.efg" - - -class Nim7(CatalogGameFromContrib): - game_file = "nim7.efg" - - -class Oneill(CatalogGameFromContrib): - game_file = "oneill.nfg" - - -class Palf(CatalogGameFromContrib): - game_file = "palf.efg" - - -class Palf2(CatalogGameFromContrib): - game_file = "palf2.efg" - - -class Palf3(CatalogGameFromContrib): - game_file = "palf3.efg" - - -class Pbride(CatalogGameFromContrib): - game_file = "pbride.efg" - - -class PrisonersDilemma(CatalogGameFromContrib): - game_file = "pd.nfg" - - -class Perfect1(CatalogGameFromContrib): - game_file = "perfect1.nfg" - - -class Perfect2(CatalogGameFromContrib): - game_file = "perfect2.nfg" - - -class Perfect3(CatalogGameFromContrib): - game_file = "perfect3.nfg" - - -class Poker(CatalogGameFromContrib): - game_file = "poker.efg" - - -class PokerNFG(CatalogGameFromContrib): - game_file = "poker.nfg" - - -class Poker2(CatalogGameFromContrib): - game_file = "poker2.efg" - - -class Pvw(CatalogGameFromContrib): - game_file = "pvw.efg" - - -class Pvw2(CatalogGameFromContrib): - game_file = "pvw2.efg" - - -class Sh3(CatalogGameFromContrib): - game_file = "sh3.efg" - - -class Sh3NFG(CatalogGameFromContrib): - game_file = "sh3.nfg" - - -class Spence(CatalogGameFromContrib): - game_file = "spence.efg" - - -class Stengel(CatalogGameFromContrib): - game_file = "stengel.nfg" - - -class Sww1(CatalogGameFromContrib): - game_file = "sww1.efg" - - -class Sww1NFG(CatalogGameFromContrib): - game_file = "sww1.nfg" - - -class Sww2(CatalogGameFromContrib): - game_file = "sww2.efg" - - -class Sww3(CatalogGameFromContrib): - game_file = "sww3.efg" - - -class Tim(CatalogGameFromContrib): - game_file = "tim.efg" - - -class Todd1(CatalogGameFromContrib): - game_file = "todd1.nfg" - - -class Todd2(CatalogGameFromContrib): - game_file = "todd2.nfg" - - -class Todd3(CatalogGameFromContrib): - game_file = "todd3.nfg" - - -class Ttt(CatalogGameFromContrib): - game_file = "ttt.efg" - - -class Vd(CatalogGameFromContrib): - game_file = "vd.efg" - - -class VdNFG(CatalogGameFromContrib): - game_file = "vd.nfg" - - -class W_ex1(CatalogGameFromContrib): - game_file = "w_ex1.efg" - - -class W_ex2(CatalogGameFromContrib): - game_file = "w_ex2.efg" - - -class Wilson1(CatalogGameFromContrib): - game_file = "wilson1.efg" - - -class Wink3(CatalogGameFromContrib): - game_file = "wink3.nfg" - - -class Winkels(CatalogGameFromContrib): - game_file = "winkels.nfg" - - -class Work1(CatalogGameFromContrib): - game_file = "work1.efg" - - -class Work2(CatalogGameFromContrib): - game_file = "work2.efg" - +def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: + """ + Dynamically generate CatalogGameFromContrib subclasses from YAML + and attach them to this module's namespace. + """ + module = sys.modules[__name__] -class Work3(CatalogGameFromContrib): - game_file = "work3.efg" + 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"] -class Yamamoto(CatalogGameFromContrib): - game_file = "yamamoto.nfg" + cls = type( + class_name, + (CatalogGameFromContrib,), + { + "game_file": game_file, + "__module__": __name__, + }, + ) + setattr(module, class_name, cls) -class Zero(CatalogGameFromContrib): - game_file = "zero.nfg" +# Generate classes at import time +_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) +_generate_contrib_game_classes(_catalog_data) ########################################## # Catalog games defined programmatically # diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog/catalog.yml index 0cdac1729..7f806c4be 100644 --- a/src/pygambit/catalog/catalog.yml +++ b/src/pygambit/catalog/catalog.yml @@ -322,9 +322,9 @@ Holdout: file: "holdout.efg" metadata: -Holdout7: - file: "holdout7.efg" - metadata: +# Holdout7: # This game does not work (ValueError: Parse error in game file: Probabilities must sum to exactly one) +# file: "holdout7.efg" +# metadata: Hs1: file: "hs1.efg" @@ -442,7 +442,7 @@ Pbride: file: "pbride.efg" metadata: -Pd: +PrisonersDilemma: file: "pd.nfg" metadata: From ebf69818f7fbd74e2a5b883bf2bcda2dffc5f2e7 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 11:09:42 +0000 Subject: [PATCH 051/170] remove src/pygambit/catalog/generated_catalog_games.py from gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9b2c46516..9d37e0d3b 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,3 @@ Gambit.app/* *.ef build_support/msw/gambit.wxs build_support/osx/Info.plist -src/pygambit/catalog/generated_catalog_games.py From 3a3f7625097d5704709b88792f28e90e742c4906 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 11:29:00 +0000 Subject: [PATCH 052/170] rename catalog gen script to update.py --- src/pygambit/catalog/{get_contrib_games.py => update.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pygambit/catalog/{get_contrib_games.py => update.py} (100%) diff --git a/src/pygambit/catalog/get_contrib_games.py b/src/pygambit/catalog/update.py similarity index 100% rename from src/pygambit/catalog/get_contrib_games.py rename to src/pygambit/catalog/update.py From 2dac733305d706a64a514a7c4c7d9afe9caf7dd1 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 12:00:51 +0000 Subject: [PATCH 053/170] update the catalog instead of overwiting it --- src/pygambit/catalog/update.py | 44 +++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/src/pygambit/catalog/update.py b/src/pygambit/catalog/update.py index c0321bdcb..d2514ee58 100644 --- a/src/pygambit/catalog/update.py +++ b/src/pygambit/catalog/update.py @@ -2,7 +2,9 @@ from pathlib import Path -OUTPUT_FILE = "catalog.yml" +import yaml + +_CATALOG_YAML = Path(__file__).parent / "catalog.yml" _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" @@ -28,29 +30,43 @@ def make_class_name(filename: str) -> str: efg_files = list(_GAMEFILES_DIR.rglob("*.efg")) nfg_files = list(_GAMEFILES_DIR.rglob("*.nfg")) - print(f"Found {len(efg_files)} .efg files") - print(f"Found {len(nfg_files)} .nfg files") + print(f"Found {len(efg_files)} .efg files in contrib/games") + print(f"Found {len(nfg_files)} .nfg files in contrib/games") 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.safe_load(f) or {} + file_names = [entry["file"] for entry in catalog.values() if "file" in entry] + + # Iterate through contrib/games and update the catalog + # with new/missing entries lines = [] class_names = [] + new_entries_counter = 0 for path in all_files: stem = path.stem - class_name = make_class_name(stem) - # Avoid duplicates (some EFG and NFG have same name) + class_name = make_class_name(stem) + # Avoid duplicates by appending EFG or NFG if class_name in class_names: class_name += path.suffix.split(".")[-1].upper() - class_names.append(class_name) - lines.append(f"{class_name}:") - lines.append(f' file: "{path.name}"') - lines.append(" metadata:\n") - - output_path = Path(__file__).parent / OUTPUT_FILE - output_path.write_text("\n".join(lines), encoding="utf-8") - print(f"Generated {len(all_files)} new entries to the catalog") - print(f"Output written to: {output_path}") + # Add any new entries to the catalog + if path.name not in file_names: + lines.append(f"{class_name}:") + lines.append(f' file: "{path.name}"') + lines.append(" metadata:\n") + new_entries_counter += 1 + + # Update the yml + new_entries = yaml.safe_load("\n".join(lines)) or {} + catalog.update(new_entries) + with _CATALOG_YAML.open("w", encoding="utf-8") as f: + yaml.safe_dump(catalog, f, sort_keys=False) + + print(f"Added {new_entries_counter} new entries to the catalog") + print(f"Output written to: {_CATALOG_YAML}") print("Done.") From 2a2cafdc67bbe5d1e42e5fc167fd46814d6f04da Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 13:30:39 +0000 Subject: [PATCH 054/170] write to catalog via dictionary --- src/pygambit/catalog/catalog.yml | 725 +++++++++++++------------------ src/pygambit/catalog/update.py | 17 +- 2 files changed, 300 insertions(+), 442 deletions(-) diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog/catalog.yml index 7f806c4be..59d04054d 100644 --- a/src/pygambit/catalog/catalog.yml +++ b/src/pygambit/catalog/catalog.yml @@ -1,579 +1,436 @@ Game2s2x2x2: - file: "2s2x2x2.efg" - metadata: - + file: 2s2x2x2.efg + metadata: null Game2smp: - file: "2smp.efg" - metadata: - + file: 2smp.efg + metadata: null Game2x2: - file: "2x2.nfg" - metadata: - + file: 2x2.nfg + metadata: null Game2x2a: - file: "2x2a.nfg" - metadata: - + file: 2x2a.nfg + metadata: null Game2x2const: - file: "2x2const.nfg" - metadata: - + file: 2x2const.nfg + metadata: null Game2x2x2_nau: - file: "2x2x2-nau.nfg" - metadata: - + file: 2x2x2-nau.nfg + metadata: null Game2x2x2: - file: "2x2x2.efg" - metadata: - + file: 2x2x2.efg + metadata: null Game2x2x2NFG: - file: "2x2x2.nfg" - metadata: - + file: 2x2x2.nfg + metadata: null Game2x2x2x2: - file: "2x2x2x2.nfg" - metadata: - + file: 2x2x2x2.nfg + metadata: null Game2x2x2x2x2: - file: "2x2x2x2x2.nfg" - metadata: - + file: 2x2x2x2x2.nfg + metadata: null Game3x3x3: - file: "3x3x3.nfg" - metadata: - + file: 3x3x3.nfg + metadata: null Game4cards: - file: "4cards.efg" - metadata: - + file: 4cards.efg + metadata: null Game5x4x3: - file: "5x4x3.nfg" - metadata: - + file: 5x4x3.nfg + metadata: null Game8x2x2: - file: "8x2x2.nfg" - metadata: - + file: 8x2x2.nfg + metadata: null Game8x8: - file: "8x8.nfg" - metadata: - + file: 8x8.nfg + metadata: null Artist1: - file: "artist1.efg" - metadata: - + file: artist1.efg + metadata: null Artist2: - file: "artist2.efg" - metadata: - + file: artist2.efg + metadata: null Badgame1: - file: "badgame1.efg" - metadata: - + file: badgame1.efg + metadata: null Badgame2: - file: "badgame2.efg" - metadata: - + file: badgame2.efg + metadata: null Bagwell: - file: "bagwell.efg" - metadata: - + file: bagwell.efg + metadata: null Bayes1a: - file: "bayes1a.efg" - metadata: - + file: bayes1a.efg + metadata: null Bayes2a: - file: "bayes2a.efg" - metadata: - + file: bayes2a.efg + metadata: null Bcp2: - file: "bcp2.efg" - metadata: - + file: bcp2.efg + metadata: null Bcp3: - file: "bcp3.efg" - metadata: - + file: bcp3.efg + metadata: null Bcp4: - file: "bcp4.efg" - metadata: - + file: bcp4.efg + metadata: null Bhg1: - file: "bhg1.efg" - metadata: - + file: bhg1.efg + metadata: null Bhg2: - file: "bhg2.efg" - metadata: - + file: bhg2.efg + metadata: null Bhg3: - file: "bhg3.efg" - metadata: - + file: bhg3.efg + metadata: null Bhg4: - file: "bhg4.efg" - metadata: - + file: bhg4.efg + metadata: null Bhg5: - file: "bhg5.efg" - metadata: - + file: bhg5.efg + metadata: null Caro2: - file: "caro2.efg" - metadata: - + file: caro2.efg + metadata: null Cent2: - file: "cent2.efg" - metadata: - + file: cent2.efg + metadata: null Cent2NFG: - file: "cent2.nfg" - metadata: - + file: cent2.nfg + metadata: null Cent3: - file: "cent3.efg" - metadata: - + file: cent3.efg + metadata: null Cent4: - file: "cent4.efg" - metadata: - + file: cent4.efg + metadata: null Cent6: - file: "cent6.efg" - metadata: - + file: cent6.efg + metadata: null Centcs10: - file: "centcs10.efg" - metadata: - + file: centcs10.efg + metadata: null Centcs6: - file: "centcs6.efg" - metadata: - + file: centcs6.efg + metadata: null Condjury: - file: "condjury.efg" - metadata: - + file: condjury.efg + metadata: null Coord2: - file: "coord2.efg" - metadata: - + file: coord2.efg + metadata: null Coord2NFG: - file: "coord2.nfg" - metadata: - + file: coord2.nfg + metadata: null Coord2ts: - file: "coord2ts.efg" - metadata: - + file: coord2ts.efg + metadata: null Coord3: - file: "coord3.efg" - metadata: - + file: coord3.efg + metadata: null Coord3NFG: - file: "coord3.nfg" - metadata: - + file: coord3.nfg + metadata: null Coord333: - file: "coord333.nfg" - metadata: - + file: coord333.nfg + metadata: null Coord4: - file: "coord4.efg" - metadata: - + file: coord4.efg + metadata: null Coord4NFG: - file: "coord4.nfg" - metadata: - + file: coord4.nfg + metadata: null Cross: - file: "cross.efg" - metadata: - + file: cross.efg + metadata: null Cs: - file: "cs.efg" - metadata: - + file: cs.efg + metadata: null Csg1: - file: "csg1.nfg" - metadata: - + file: csg1.nfg + metadata: null Csg2: - file: "csg2.nfg" - metadata: - + file: csg2.nfg + metadata: null Csg3: - file: "csg3.nfg" - metadata: - + file: csg3.nfg + metadata: null Csg4: - file: "csg4.nfg" - metadata: - + file: csg4.nfg + metadata: null Deg1: - file: "deg1.nfg" - metadata: - + file: deg1.nfg + metadata: null Deg2: - file: "deg2.nfg" - metadata: - + file: deg2.nfg + metadata: null E01: - file: "e01.efg" - metadata: - + file: e01.efg + metadata: null E01NFG: - file: "e01.nfg" - metadata: - + file: e01.nfg + metadata: null E02: - file: "e02.efg" - metadata: - + file: e02.efg + metadata: null E02NFG: - file: "e02.nfg" - metadata: - + file: e02.nfg + metadata: null E03: - file: "e03.efg" - metadata: - + file: e03.efg + metadata: null E04: - file: "e04.efg" - metadata: - + file: e04.efg + metadata: null E04NFG: - file: "e04.nfg" - metadata: - + file: e04.nfg + metadata: null E05: - file: "e05.efg" - metadata: - + file: e05.efg + metadata: null E06: - file: "e06.efg" - metadata: - + file: e06.efg + metadata: null E07: - file: "e07.efg" - metadata: - + file: e07.efg + metadata: null E07NFG: - file: "e07.nfg" - metadata: - + file: e07.nfg + metadata: null E08: - file: "e08.efg" - metadata: - + file: e08.efg + metadata: null E09: - file: "e09.efg" - metadata: - + file: e09.efg + metadata: null E10: - file: "e10.efg" - metadata: - + file: e10.efg + metadata: null E10a: - file: "e10a.efg" - metadata: - + file: e10a.efg + metadata: null E13: - file: "e13.efg" - metadata: - + file: e13.efg + metadata: null E16: - file: "e16.efg" - metadata: - + file: e16.efg + metadata: null E17: - file: "e17.efg" - metadata: - + file: e17.efg + metadata: null E18: - file: "e18.efg" - metadata: - + file: e18.efg + metadata: null G1: - file: "g1.efg" - metadata: - + file: g1.efg + metadata: null G1NFG: - file: "g1.nfg" - metadata: - + file: g1.nfg + metadata: null G2: - file: "g2.efg" - metadata: - + file: g2.efg + metadata: null G2NFG: - file: "g2.nfg" - metadata: - + file: g2.nfg + metadata: null G3: - file: "g3.efg" - metadata: - + file: g3.efg + metadata: null G3NFG: - file: "g3.nfg" - metadata: - + file: g3.nfg + metadata: null Holdout: - file: "holdout.efg" + file: holdout.efg + metadata: null +Holdout7: + file: holdout7.efg metadata: - -# Holdout7: # This game does not work (ValueError: Parse error in game file: Probabilities must sum to exactly one) -# 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: - + file: hs1.efg + metadata: null Jury_mr: - file: "jury_mr.efg" - metadata: - + file: jury_mr.efg + metadata: null Jury_un: - file: "jury_un.efg" - metadata: - + file: jury_un.efg + metadata: null Km1: - file: "km1.efg" - metadata: - + file: km1.efg + metadata: null Km2: - file: "km2.efg" - metadata: - + file: km2.efg + metadata: null Km3: - file: "km3.efg" - metadata: - + file: km3.efg + metadata: null Km6: - file: "km6.efg" - metadata: - + file: km6.efg + metadata: null Loopback: - file: "loopback.nfg" - metadata: - + file: loopback.nfg + metadata: null Mixdom: - file: "mixdom.nfg" - metadata: - + file: mixdom.nfg + metadata: null Mixdom2: - file: "mixdom2.nfg" - metadata: - + file: mixdom2.nfg + metadata: null Montyhal: - file: "montyhal.efg" - metadata: - + file: montyhal.efg + metadata: null My_2_1: - file: "my_2-1.efg" - metadata: - + file: my_2-1.efg + metadata: null My_2_4: - file: "my_2-4.efg" - metadata: - + file: my_2-4.efg + metadata: null My_2_8: - file: "my_2-8.efg" - metadata: - + file: my_2-8.efg + metadata: null My_3_3a: - file: "my_3-3a.efg" - metadata: - + file: my_3-3a.efg + metadata: null My_3_3b: - file: "my_3-3b.efg" - metadata: - + file: my_3-3b.efg + metadata: null My_3_3c: - file: "my_3-3c.efg" - metadata: - + file: my_3-3c.efg + metadata: null My_3_3d: - file: "my_3-3d.efg" - metadata: - + file: my_3-3d.efg + metadata: null My_3_3e: - file: "my_3-3e.efg" - metadata: - + file: my_3-3e.efg + metadata: null My_3_4: - file: "my_3-4.efg" - metadata: - + file: my_3-4.efg + metadata: null Myerson: - file: "myerson.efg" - metadata: - + file: myerson.efg + metadata: null Myerson_fig_4_2: - file: "myerson_fig_4_2.efg" - metadata: - + file: myerson_fig_4_2.efg + metadata: null Nim: - file: "nim.efg" - metadata: - + file: nim.efg + metadata: null Nim7: - file: "nim7.efg" - metadata: - + file: nim7.efg + metadata: null Oneill: - file: "oneill.nfg" - metadata: - + file: oneill.nfg + metadata: null Palf: - file: "palf.efg" - metadata: - + file: palf.efg + metadata: null Palf2: - file: "palf2.efg" - metadata: - + file: palf2.efg + metadata: null Palf3: - file: "palf3.efg" - metadata: - + file: palf3.efg + metadata: null Pbride: - file: "pbride.efg" - metadata: - + file: pbride.efg + metadata: null PrisonersDilemma: - file: "pd.nfg" - metadata: - + file: pd.nfg + metadata: null Perfect1: - file: "perfect1.nfg" - metadata: - + file: perfect1.nfg + metadata: null Perfect2: - file: "perfect2.nfg" - metadata: - + file: perfect2.nfg + metadata: null Perfect3: - file: "perfect3.nfg" - metadata: - + file: perfect3.nfg + metadata: null Poker: - file: "poker.efg" - metadata: - + file: poker.efg + metadata: null PokerNFG: - file: "poker.nfg" - metadata: - + file: poker.nfg + metadata: null Poker2: - file: "poker2.efg" - metadata: - + file: poker2.efg + metadata: null Pvw: - file: "pvw.efg" - metadata: - + file: pvw.efg + metadata: null Pvw2: - file: "pvw2.efg" - metadata: - + file: pvw2.efg + metadata: null Sh3: - file: "sh3.efg" - metadata: - + file: sh3.efg + metadata: null Sh3NFG: - file: "sh3.nfg" - metadata: - + file: sh3.nfg + metadata: null Spence: - file: "spence.efg" - metadata: - + file: spence.efg + metadata: null Stengel: - file: "stengel.nfg" - metadata: - + file: stengel.nfg + metadata: null Sww1: - file: "sww1.efg" - metadata: - + file: sww1.efg + metadata: null Sww1NFG: - file: "sww1.nfg" - metadata: - + file: sww1.nfg + metadata: null Sww2: - file: "sww2.efg" - metadata: - + file: sww2.efg + metadata: null Sww3: - file: "sww3.efg" - metadata: - + file: sww3.efg + metadata: null Tim: - file: "tim.efg" - metadata: - + file: tim.efg + metadata: null Todd1: - file: "todd1.nfg" - metadata: - + file: todd1.nfg + metadata: null Todd2: - file: "todd2.nfg" - metadata: - + file: todd2.nfg + metadata: null Todd3: - file: "todd3.nfg" - metadata: - + file: todd3.nfg + metadata: null Ttt: - file: "ttt.efg" - metadata: - + file: ttt.efg + metadata: null Vd: - file: "vd.efg" - metadata: - + file: vd.efg + metadata: null VdNFG: - file: "vd.nfg" - metadata: - + file: vd.nfg + metadata: null W_ex1: - file: "w_ex1.efg" - metadata: - + file: w_ex1.efg + metadata: null W_ex2: - file: "w_ex2.efg" - metadata: - + file: w_ex2.efg + metadata: null Wilson1: - file: "wilson1.efg" - metadata: - + file: wilson1.efg + metadata: null Wink3: - file: "wink3.nfg" - metadata: - + file: wink3.nfg + metadata: null Winkels: - file: "winkels.nfg" - metadata: - + file: winkels.nfg + metadata: null Work1: - file: "work1.efg" - metadata: - + file: work1.efg + metadata: null Work2: - file: "work2.efg" - metadata: - + file: work2.efg + metadata: null Work3: - file: "work3.efg" - metadata: - + file: work3.efg + metadata: null Yamamoto: - file: "yamamoto.nfg" - metadata: - + file: yamamoto.nfg + metadata: null Zero: - file: "zero.nfg" - metadata: + file: zero.nfg + metadata: null diff --git a/src/pygambit/catalog/update.py b/src/pygambit/catalog/update.py index d2514ee58..7ecbc70e3 100644 --- a/src/pygambit/catalog/update.py +++ b/src/pygambit/catalog/update.py @@ -45,27 +45,28 @@ def make_class_name(filename: str) -> str: lines = [] class_names = [] 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 class_names: + if class_name in new_entries: class_name += path.suffix.split(".")[-1].upper() - class_names.append(class_name) - # Add any new entries to the catalog if path.name not in file_names: - lines.append(f"{class_name}:") - lines.append(f' file: "{path.name}"') - lines.append(" metadata:\n") + new_entries[class_name] = { + "file": path.name, + "metadata": {}, + } new_entries_counter += 1 # Update the yml new_entries = yaml.safe_load("\n".join(lines)) or {} catalog.update(new_entries) with _CATALOG_YAML.open("w", encoding="utf-8") as f: - yaml.safe_dump(catalog, f, sort_keys=False) + dumped = yaml.safe_dump(catalog, sort_keys=False, default_flow_style=False) + f.write(dumped) print(f"Added {new_entries_counter} new entries to the catalog") print(f"Output written to: {_CATALOG_YAML}") From f31203bc456fc30911a04e665ab0f3fdd187fec9 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 16:07:23 +0000 Subject: [PATCH 055/170] refactor: update YAML handling with ruamel.yaml --- src/pygambit/catalog/catalog.yml | 288 +++++++++++++++---------------- src/pygambit/catalog/update.py | 17 +- 2 files changed, 153 insertions(+), 152 deletions(-) diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog/catalog.yml index 59d04054d..b84ae0573 100644 --- a/src/pygambit/catalog/catalog.yml +++ b/src/pygambit/catalog/catalog.yml @@ -1,436 +1,436 @@ Game2s2x2x2: file: 2s2x2x2.efg - metadata: null + metadata: Game2smp: file: 2smp.efg - metadata: null + metadata: Game2x2: file: 2x2.nfg - metadata: null + metadata: Game2x2a: file: 2x2a.nfg - metadata: null + metadata: Game2x2const: file: 2x2const.nfg - metadata: null + metadata: Game2x2x2_nau: file: 2x2x2-nau.nfg - metadata: null + metadata: Game2x2x2: file: 2x2x2.efg - metadata: null + metadata: Game2x2x2NFG: file: 2x2x2.nfg - metadata: null + metadata: Game2x2x2x2: file: 2x2x2x2.nfg - metadata: null + metadata: Game2x2x2x2x2: file: 2x2x2x2x2.nfg - metadata: null + metadata: Game3x3x3: file: 3x3x3.nfg - metadata: null + metadata: Game4cards: file: 4cards.efg - metadata: null + metadata: Game5x4x3: file: 5x4x3.nfg - metadata: null + metadata: Game8x2x2: file: 8x2x2.nfg - metadata: null + metadata: Game8x8: file: 8x8.nfg - metadata: null + metadata: Artist1: file: artist1.efg - metadata: null + metadata: Artist2: file: artist2.efg - metadata: null + metadata: Badgame1: file: badgame1.efg - metadata: null + metadata: Badgame2: file: badgame2.efg - metadata: null + metadata: Bagwell: file: bagwell.efg - metadata: null + metadata: Bayes1a: file: bayes1a.efg - metadata: null + metadata: Bayes2a: file: bayes2a.efg - metadata: null + metadata: Bcp2: file: bcp2.efg - metadata: null + metadata: Bcp3: file: bcp3.efg - metadata: null + metadata: Bcp4: file: bcp4.efg - metadata: null + metadata: Bhg1: file: bhg1.efg - metadata: null + metadata: Bhg2: file: bhg2.efg - metadata: null + metadata: Bhg3: file: bhg3.efg - metadata: null + metadata: Bhg4: file: bhg4.efg - metadata: null + metadata: Bhg5: file: bhg5.efg - metadata: null + metadata: Caro2: file: caro2.efg - metadata: null + metadata: Cent2: file: cent2.efg - metadata: null + metadata: Cent2NFG: file: cent2.nfg - metadata: null + metadata: Cent3: file: cent3.efg - metadata: null + metadata: Cent4: file: cent4.efg - metadata: null + metadata: Cent6: file: cent6.efg - metadata: null + metadata: Centcs10: file: centcs10.efg - metadata: null + metadata: Centcs6: file: centcs6.efg - metadata: null + metadata: Condjury: file: condjury.efg - metadata: null + metadata: Coord2: file: coord2.efg - metadata: null + metadata: Coord2NFG: file: coord2.nfg - metadata: null + metadata: Coord2ts: file: coord2ts.efg - metadata: null + metadata: Coord3: file: coord3.efg - metadata: null + metadata: Coord3NFG: file: coord3.nfg - metadata: null + metadata: Coord333: file: coord333.nfg - metadata: null + metadata: Coord4: file: coord4.efg - metadata: null + metadata: Coord4NFG: file: coord4.nfg - metadata: null + metadata: Cross: file: cross.efg - metadata: null + metadata: Cs: file: cs.efg - metadata: null + metadata: Csg1: file: csg1.nfg - metadata: null + metadata: Csg2: file: csg2.nfg - metadata: null + metadata: Csg3: file: csg3.nfg - metadata: null + metadata: Csg4: file: csg4.nfg - metadata: null + metadata: Deg1: file: deg1.nfg - metadata: null + metadata: Deg2: file: deg2.nfg - metadata: null + metadata: E01: file: e01.efg - metadata: null + metadata: E01NFG: file: e01.nfg - metadata: null + metadata: E02: file: e02.efg - metadata: null + metadata: E02NFG: file: e02.nfg - metadata: null + metadata: E03: file: e03.efg - metadata: null + metadata: E04: file: e04.efg - metadata: null + metadata: E04NFG: file: e04.nfg - metadata: null + metadata: E05: file: e05.efg - metadata: null + metadata: E06: file: e06.efg - metadata: null + metadata: E07: file: e07.efg - metadata: null + metadata: E07NFG: file: e07.nfg - metadata: null + metadata: E08: file: e08.efg - metadata: null + metadata: E09: file: e09.efg - metadata: null + metadata: E10: file: e10.efg - metadata: null + metadata: E10a: file: e10a.efg - metadata: null + metadata: E13: file: e13.efg - metadata: null + metadata: E16: file: e16.efg - metadata: null + metadata: E17: file: e17.efg - metadata: null + metadata: E18: file: e18.efg - metadata: null + metadata: G1: file: g1.efg - metadata: null + metadata: G1NFG: file: g1.nfg - metadata: null + metadata: G2: file: g2.efg - metadata: null + metadata: G2NFG: file: g2.nfg - metadata: null + metadata: G3: file: g3.efg - metadata: null + metadata: G3NFG: file: g3.nfg - metadata: null + metadata: Holdout: file: holdout.efg - metadata: null + 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: null + metadata: Jury_mr: file: jury_mr.efg - metadata: null + metadata: Jury_un: file: jury_un.efg - metadata: null + metadata: Km1: file: km1.efg - metadata: null + metadata: Km2: file: km2.efg - metadata: null + metadata: Km3: file: km3.efg - metadata: null + metadata: Km6: file: km6.efg - metadata: null + metadata: Loopback: file: loopback.nfg - metadata: null + metadata: Mixdom: file: mixdom.nfg - metadata: null + metadata: Mixdom2: file: mixdom2.nfg - metadata: null + metadata: Montyhal: file: montyhal.efg - metadata: null + metadata: My_2_1: file: my_2-1.efg - metadata: null + metadata: My_2_4: file: my_2-4.efg - metadata: null + metadata: My_2_8: file: my_2-8.efg - metadata: null + metadata: My_3_3a: file: my_3-3a.efg - metadata: null + metadata: My_3_3b: file: my_3-3b.efg - metadata: null + metadata: My_3_3c: file: my_3-3c.efg - metadata: null + metadata: My_3_3d: file: my_3-3d.efg - metadata: null + metadata: My_3_3e: file: my_3-3e.efg - metadata: null + metadata: My_3_4: file: my_3-4.efg - metadata: null + metadata: Myerson: file: myerson.efg - metadata: null + metadata: Myerson_fig_4_2: file: myerson_fig_4_2.efg - metadata: null + metadata: Nim: file: nim.efg - metadata: null + metadata: Nim7: file: nim7.efg - metadata: null + metadata: Oneill: file: oneill.nfg - metadata: null + metadata: Palf: file: palf.efg - metadata: null + metadata: Palf2: file: palf2.efg - metadata: null + metadata: Palf3: file: palf3.efg - metadata: null + metadata: Pbride: file: pbride.efg - metadata: null + metadata: PrisonersDilemma: file: pd.nfg - metadata: null + metadata: Perfect1: file: perfect1.nfg - metadata: null + metadata: Perfect2: file: perfect2.nfg - metadata: null + metadata: Perfect3: file: perfect3.nfg - metadata: null + metadata: Poker: file: poker.efg - metadata: null + metadata: PokerNFG: file: poker.nfg - metadata: null + metadata: Poker2: file: poker2.efg - metadata: null + metadata: Pvw: file: pvw.efg - metadata: null + metadata: Pvw2: file: pvw2.efg - metadata: null + metadata: Sh3: file: sh3.efg - metadata: null + metadata: Sh3NFG: file: sh3.nfg - metadata: null + metadata: Spence: file: spence.efg - metadata: null + metadata: Stengel: file: stengel.nfg - metadata: null + metadata: Sww1: file: sww1.efg - metadata: null + metadata: Sww1NFG: file: sww1.nfg - metadata: null + metadata: Sww2: file: sww2.efg - metadata: null + metadata: Sww3: file: sww3.efg - metadata: null + metadata: Tim: file: tim.efg - metadata: null + metadata: Todd1: file: todd1.nfg - metadata: null + metadata: Todd2: file: todd2.nfg - metadata: null + metadata: Todd3: file: todd3.nfg - metadata: null + metadata: Ttt: file: ttt.efg - metadata: null + metadata: Vd: file: vd.efg - metadata: null + metadata: VdNFG: file: vd.nfg - metadata: null + metadata: W_ex1: file: w_ex1.efg - metadata: null + metadata: W_ex2: file: w_ex2.efg - metadata: null + metadata: Wilson1: file: wilson1.efg - metadata: null + metadata: Wink3: file: wink3.nfg - metadata: null + metadata: Winkels: file: winkels.nfg - metadata: null + metadata: Work1: file: work1.efg - metadata: null + metadata: Work2: file: work2.efg - metadata: null + metadata: Work3: file: work3.efg - metadata: null + metadata: Yamamoto: file: yamamoto.nfg - metadata: null + metadata: Zero: file: zero.nfg - metadata: null + metadata: diff --git a/src/pygambit/catalog/update.py b/src/pygambit/catalog/update.py index 7ecbc70e3..20aeffc2a 100644 --- a/src/pygambit/catalog/update.py +++ b/src/pygambit/catalog/update.py @@ -2,7 +2,7 @@ from pathlib import Path -import yaml +from ruamel.yaml import YAML _CATALOG_YAML = Path(__file__).parent / "catalog.yml" _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" @@ -27,6 +27,11 @@ def make_class_name(filename: str) -> str: 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")) @@ -37,13 +42,11 @@ def make_class_name(filename: str) -> str: # Get the current class names from the catalog with open(_CATALOG_YAML, encoding="utf-8") as f: - catalog = yaml.safe_load(f) or {} + catalog = yaml.load(f) or {} file_names = [entry["file"] for entry in catalog.values() if "file" in entry] # Iterate through contrib/games and update the catalog # with new/missing entries - lines = [] - class_names = [] new_entries_counter = 0 new_entries = {} for path in all_files: @@ -61,12 +64,10 @@ def make_class_name(filename: str) -> str: } new_entries_counter += 1 - # Update the yml - new_entries = yaml.safe_load("\n".join(lines)) or {} + # Update the catalog catalog.update(new_entries) with _CATALOG_YAML.open("w", encoding="utf-8") as f: - dumped = yaml.safe_dump(catalog, sort_keys=False, default_flow_style=False) - f.write(dumped) + yaml.dump(catalog, f) print(f"Added {new_entries_counter} new entries to the catalog") print(f"Output written to: {_CATALOG_YAML}") From 2d3744fde3c35c48c47a1da7dbd5faeab1f6a2aa Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 16:26:03 +0000 Subject: [PATCH 056/170] ignore games that are marked as invalid in the catalog --- doc/tutorials/01_quickstart.ipynb | 29 +++++++++++++++++++---- doc/tutorials/02_extensive_form.ipynb | 34 +++++++++++++++++++++++---- src/pygambit/catalog/catalog.py | 30 ++++++++++++++++------- 3 files changed, 75 insertions(+), 18 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index c6780471e..ee5652966 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -485,6 +485,27 @@ "gbt.catalog.games(game_type=\"nfg\", num_players=2)" ] }, + { + "cell_type": "code", + "execution_count": 14, + "id": "ff307b74-16bc-4889-b57c-fef3124ce5ca", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "''" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.catalog.PrisonersDilemma.description" + ] + }, { "cell_type": "markdown", "id": "a919ddf7", @@ -495,7 +516,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -509,7 +530,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -535,7 +556,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -553,7 +574,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 10e59fc5f..4fed4cfbb 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1238,6 +1238,30 @@ { "cell_type": "code", "execution_count": 16, + "id": "bd8b291b-6cc6-4f9a-b5c3-0d1f4c05252b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "Game(title='One-shot trust game, after Kreps (1990)')" + ], + "text/plain": [ + "Game(title='One-shot trust game, after Kreps (1990)')" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.catalog.OneShotTrust(unique_NE_variant=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, "id": "4556c0bf", "metadata": {}, "outputs": [ @@ -1416,7 +1440,7 @@ "" ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -1442,7 +1466,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1460,7 +1484,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1479,7 +1503,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1491,7 +1515,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 93b4b1ad4..d9ec6ea6a 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -171,17 +171,29 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: raise ValueError(f"Missing 'file' for catalog entry '{class_name}'") game_file = entry["file"] + metadata = entry.get("metadata", {}) - cls = type( - class_name, - (CatalogGameFromContrib,), - { - "game_file": game_file, - "__module__": __name__, - }, - ) + # Build class attributes dict + class_attrs = { + "game_file": game_file, + "__module__": __name__, + } + + # 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, + (CatalogGameFromContrib,), + class_attrs, + ) - setattr(module, class_name, cls) + setattr(module, class_name, cls) # Generate classes at import time From 50062a98dca623ba772f6efc1be65732ea84bce5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 15 Jan 2026 16:44:35 +0000 Subject: [PATCH 057/170] Add ability to filter catalog games by custom metadata fields --- doc/tutorials/01_quickstart.ipynb | 41 ++++++++++++++++++++++---- src/pygambit/catalog/catalog.py | 48 ++++++++++++++++++++++++++----- src/pygambit/catalog/catalog.yml | 1 + 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index ee5652966..354218160 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -485,9 +485,38 @@ "gbt.catalog.games(game_type=\"nfg\", num_players=2)" ] }, + { + "cell_type": "markdown", + "id": "fd3f5829-c268-451a-8bc7-28e766e7d655", + "metadata": {}, + "source": [ + "The catalog can also be searched with custom metadata filters:" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "2e680610-e2c9-45bc-84bd-67006aca5174", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "['PrisonersDilemma']" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gbt.catalog.games(tutorial=1)" + ] + }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "id": "ff307b74-16bc-4889-b57c-fef3124ce5ca", "metadata": {}, "outputs": [ @@ -497,7 +526,7 @@ "''" ] }, - "execution_count": 14, + "execution_count": 15, "metadata": {}, "output_type": "execute_result" } @@ -516,7 +545,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -530,7 +559,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 15, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -556,7 +585,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -574,7 +603,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index d9ec6ea6a..156410aff 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -109,9 +109,10 @@ def __init_subclass__(cls, **kwargs): def games( game_type: Literal["all", "nfg", "efg"] = "all", num_players: int | None = None, + **metadata_filters, ) -> list[str]: """ - Return a list of catalog game names. + Return a list of catalog game class names. Parameters ---------- @@ -122,23 +123,56 @@ def games( - "efg": return only extensive-form games 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 metadata fields. + For example, `tutorial=1` filters for games with `tutorial: 1` in metadata. Returns ------- list[str] - List of game class names matching the specified type. + List of game class names matching the specified filters. + + Examples + -------- + >>> games(tutorial=1) # Games with a custom metadata field 'tutorial' equal to 1 + >>> games(game_type="efg", num_players=2) # 2-player extensive-form games """ def get_all_subclasses(cls): """Recursively get all subclasses.""" all_subclasses = [] for subclass in cls.__subclasses__(): - if ( - subclass.__name__ not in ["CatalogGameFromContrib"] - and (game_type == "all" or subclass.game_type == game_type) - and (num_players is None or subclass.num_players == num_players) - ): + # Check standard filters + if subclass.__name__ in ["CatalogGameFromContrib"]: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if game_type != "all" and not hasattr(subclass, "game_type"): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if game_type != "all" and subclass.game_type != game_type: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_players is not None and not hasattr(subclass, "num_players"): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_players is not None and subclass.num_players != num_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 diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog/catalog.yml index b84ae0573..cf9d79d65 100644 --- a/src/pygambit/catalog/catalog.yml +++ b/src/pygambit/catalog/catalog.yml @@ -335,6 +335,7 @@ Pbride: PrisonersDilemma: file: pd.nfg metadata: + tutorial: 1 Perfect1: file: perfect1.nfg metadata: From 861da2c7552c0833671fb23594066d5b283ad0e8 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 14:14:11 +0000 Subject: [PATCH 058/170] initial catalog test ideas --- tests/test_catalog.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/test_catalog.py diff --git a/tests/test_catalog.py b/tests/test_catalog.py new file mode 100644 index 000000000..63ead571b --- /dev/null +++ b/tests/test_catalog.py @@ -0,0 +1,19 @@ +# import pytest + +from pygambit.catalog import OneShotTrust + + +class TestCatalogGameBase: + """Tests for CatalogGame base class and subclassing.""" + + # This test will only work if CatalogGame added to __init__.py + # def test_catalog_game_not_instantiable(self): + # """CatalogGame should not be directly instantiable.""" + # with pytest.raises(NotImplementedError): + # gbt.catalog.CatalogGame() + + def test_custom_game_subclass_extracts_metadata(self): + """Custom CatalogGame subclasses should extract metadata from _game().""" + assert OneShotTrust.num_players == 2 + assert OneShotTrust.game_type == "efg" + assert OneShotTrust.title == "One-shot trust game, after Kreps (1990)" From 8eefc523c87acee5eda9fafe1ad64e766ee3a734 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 14:18:55 +0000 Subject: [PATCH 059/170] add test_custom_game_instantiation --- tests/test_catalog.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 63ead571b..273c1f062 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,6 +1,7 @@ # import pytest -from pygambit.catalog import OneShotTrust +from pygambit import Game +from pygambit.catalog import OneShotTrust, PrisonersDilemma class TestCatalogGameBase: @@ -17,3 +18,8 @@ def test_custom_game_subclass_extracts_metadata(self): assert OneShotTrust.num_players == 2 assert OneShotTrust.game_type == "efg" assert OneShotTrust.title == "One-shot trust game, after Kreps (1990)" + + def test_custom_game_instantiation(self): + """Custom CatalogGame subclasses should return Game instances.""" + assert isinstance(OneShotTrust(), Game) # from catalog.py + assert isinstance(PrisonersDilemma(), Game) # from catalog.yml From 31e6e932950c2b12ef8cd1c4dc5c40f02a395186 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 14:23:43 +0000 Subject: [PATCH 060/170] add test_catalog_py_game_with_parameters --- tests/test_catalog.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 273c1f062..2fcd6ba6a 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -19,7 +19,14 @@ def test_custom_game_subclass_extracts_metadata(self): assert OneShotTrust.game_type == "efg" assert OneShotTrust.title == "One-shot trust game, after Kreps (1990)" - def test_custom_game_instantiation(self): - """Custom CatalogGame subclasses should return Game instances.""" - assert isinstance(OneShotTrust(), Game) # from catalog.py - assert isinstance(PrisonersDilemma(), Game) # from catalog.yml + def test_catalog_yml_game_instantiation(self): + """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" + assert isinstance(PrisonersDilemma(), Game) + + def test_catalog_py_game_with_parameters(self): + """ + Custom CatalogGame subclass from catalog.py should return Game + and support parameters. + """ + assert isinstance(OneShotTrust(unique_NE_variant=False), Game) + assert isinstance(OneShotTrust(unique_NE_variant=True), Game) From 37e37af6c68357d9f13be95afb5959676b8789b2 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 14:29:27 +0000 Subject: [PATCH 061/170] fix game caching in catalog --- src/pygambit/catalog/catalog.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 156410aff..701172955 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -22,11 +22,14 @@ class CatalogGame: num_players: int game_type: Literal["nfg", "efg"] description: str + _cached_game: Game | None = None def __new__(cls, *args, **kwargs) -> Game: """Create a game instance by calling the _game() method.""" if hasattr(cls, "_game") and cls._game is not CatalogGame._game: - return cls._game(*args, **kwargs) + if cls._cached_game is None: + cls._cached_game = cls._game(*args, **kwargs) + return cls._cached_game raise NotImplementedError("Subclasses must implement _game() method") @staticmethod @@ -69,14 +72,11 @@ class CatalogGameFromContrib(CatalogGame): """ game_file: str - _cached_game: Game | None = None def __new__(cls) -> Game: - # Return cached game if available, otherwise load it if cls._cached_game is None: cls._cached_game = cls._load_game() - # Return a fresh instance (not the cached one) - return cls._load_game() + return cls._cached_game @classmethod def _load_game(cls) -> Game: @@ -248,6 +248,7 @@ class OneShotTrust(CatalogGame): the Seller plays Abuse. """ game_type = "efg" + test_suite = True @staticmethod def _game(unique_NE_variant: bool = False): From f80b33fec12326b7dd6ae56e735896dc94f4bcea Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 14:51:28 +0000 Subject: [PATCH 062/170] simplify game caching --- src/pygambit/catalog/catalog.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 701172955..c985d8060 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -55,13 +55,9 @@ def __init_subclass__(cls, **kwargs): if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): return - # For non-file-based games, create a temporary instance to extract metadata - try: - temp_game = cls._game() - cls._extract_metadata_from_game(temp_game) - except NotImplementedError: - # Base class, skip - pass + # Load game and extract metadata immediately when class is defined + cls._cached_game = cls._game() + cls._extract_metadata_from_game(cls._cached_game) class CatalogGameFromContrib(CatalogGame): @@ -248,7 +244,7 @@ class OneShotTrust(CatalogGame): the Seller plays Abuse. """ game_type = "efg" - test_suite = True + # test_suite = True @staticmethod def _game(unique_NE_variant: bool = False): From 608bd7f5545c661f7692df81ebc24dd291ea7e8f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 14:55:17 +0000 Subject: [PATCH 063/170] simplify error handling --- src/pygambit/catalog/catalog.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index c985d8060..5f0a6668d 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -26,11 +26,9 @@ class CatalogGame: def __new__(cls, *args, **kwargs) -> Game: """Create a game instance by calling the _game() method.""" - if hasattr(cls, "_game") and cls._game is not CatalogGame._game: - if cls._cached_game is None: - cls._cached_game = cls._game(*args, **kwargs) - return cls._cached_game - raise NotImplementedError("Subclasses must implement _game() method") + if cls._cached_game is None: + cls._cached_game = cls._game(*args, **kwargs) + return cls._cached_game @staticmethod def _game() -> Game: From e1d762759a02424c12984c81f192b750a31e051a Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:06:47 +0000 Subject: [PATCH 064/170] tidy _load_game func --- src/pygambit/catalog/catalog.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 5f0a6668d..4379807bc 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -75,26 +75,25 @@ def __new__(cls) -> Game: @classmethod def _load_game(cls) -> Game: """Load the game from file.""" - if cls.game_file is None: - raise NotImplementedError(f"{cls.__name__} must define 'game_file'") + if not hasattr(cls, "game_file") or cls.game_file is None: + raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") - cls.game_type = cls.game_file.split(".")[-1] + game_type = cls.game_file.split(".")[-1] file_path = _GAMEFILES_DIR / cls.game_file - if cls.game_type == "nfg": + if game_type == "nfg": + cls.game_type = "nfg" return read_nfg(str(file_path)) - elif cls.game_type == "efg": + elif game_type == "efg": + cls.game_type = "efg" return read_efg(str(file_path)) else: - raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{cls.game_type}'") + raise ValueError(f"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) - if not hasattr(cls, "game_file") or cls.game_file is None: - raise TypeError(f"{cls.__name__} must define 'game_file' class attribute") - # Load game and extract metadata immediately when class is defined cls._cached_game = cls._load_game() cls._extract_metadata_from_game(cls._cached_game) From 8d57dd2dabe11a0a5c3d7e56df1564ca00bc41f8 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:12:00 +0000 Subject: [PATCH 065/170] assert len(OneShotTrust.description) > 0 --- tests/test_catalog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 2fcd6ba6a..36a442fc3 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -18,6 +18,7 @@ def test_custom_game_subclass_extracts_metadata(self): assert OneShotTrust.num_players == 2 assert OneShotTrust.game_type == "efg" assert OneShotTrust.title == "One-shot trust game, after Kreps (1990)" + assert len(OneShotTrust.description) > 0 def test_catalog_yml_game_instantiation(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" From 05226ef9ab32f6538ef6249c79f727e21d25ea12 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:28:42 +0000 Subject: [PATCH 066/170] replace OneShotTrust with TestGame in test_catalog.py --- src/pygambit/catalog/__init__.py | 10 ++++--- tests/test_catalog.py | 46 +++++++++++++++++++++----------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 4c3385ef8..a26c0f3af 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,11 +1,13 @@ from . import catalog from .catalog import games -# Ensure catalog module is fully imported including all YAML-generated classes exist -_all_games = games() +# Ensure catalog module is fully imported including all YAML-generated classes +_all_catalog_classes = games() +_all_catalog_classes.append("CatalogGame") # Ensure base class is included + _game_classes = {} -for game_name in _all_games: +for game_name in _all_catalog_classes: try: _game_classes[game_name] = getattr(catalog, game_name) except AttributeError as e: @@ -17,4 +19,4 @@ globals().update(_game_classes) # Build __all__ dynamically -__all__ = ["games", *list(_all_games)] +__all__ = ["games", *list(_all_catalog_classes)] diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 36a442fc3..0b287ea74 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,7 +1,23 @@ -# import pytest - from pygambit import Game -from pygambit.catalog import OneShotTrust, PrisonersDilemma +from pygambit.catalog import CatalogGame, PrisonersDilemma + + +class TestGame(CatalogGame): + """ + Test game description. + """ + + game_type = "efg" + + @staticmethod + def _game(some_param: bool = False): + if some_param: + return Game.new_tree( + players=["A", "B"], title="Test game T" + ) + return Game.new_tree( + players=["A", "B"], title="Test game F" + ) class TestCatalogGameBase: @@ -15,19 +31,19 @@ class TestCatalogGameBase: def test_custom_game_subclass_extracts_metadata(self): """Custom CatalogGame subclasses should extract metadata from _game().""" - assert OneShotTrust.num_players == 2 - assert OneShotTrust.game_type == "efg" - assert OneShotTrust.title == "One-shot trust game, after Kreps (1990)" - assert len(OneShotTrust.description) > 0 - - def test_catalog_yml_game_instantiation(self): - """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" - assert isinstance(PrisonersDilemma(), Game) + assert TestGame.num_players == 2 + assert TestGame.game_type == "efg" + assert TestGame.title == "Test game F" + assert len(TestGame.description) > 0 def test_catalog_py_game_with_parameters(self): """ - Custom CatalogGame subclass from catalog.py should return Game - and support parameters. + Custom CatalogGame subclass should return Game + and support parameters. """ - assert isinstance(OneShotTrust(unique_NE_variant=False), Game) - assert isinstance(OneShotTrust(unique_NE_variant=True), Game) + assert isinstance(TestGame(some_param=False), Game) + assert isinstance(TestGame(some_param=True), Game) + + def test_catalog_yml_game_instantiation(self): + """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" + assert isinstance(PrisonersDilemma(), Game) From fb66b81b9c0adf7242a7b4b9982e85a3903d4aae Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:39:30 +0000 Subject: [PATCH 067/170] add test_catalog_game_not_instantiable --- tests/test_catalog.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 0b287ea74..7c76c4937 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,3 +1,5 @@ +import pytest + from pygambit import Game from pygambit.catalog import CatalogGame, PrisonersDilemma @@ -20,14 +22,13 @@ def _game(some_param: bool = False): ) -class TestCatalogGameBase: +class TestCatalogGame: """Tests for CatalogGame base class and subclassing.""" - # This test will only work if CatalogGame added to __init__.py - # def test_catalog_game_not_instantiable(self): - # """CatalogGame should not be directly instantiable.""" - # with pytest.raises(NotImplementedError): - # gbt.catalog.CatalogGame() + def test_catalog_game_not_instantiable(self): + """CatalogGame should not be directly instantiable.""" + with pytest.raises(NotImplementedError): + CatalogGame() def test_custom_game_subclass_extracts_metadata(self): """Custom CatalogGame subclasses should extract metadata from _game().""" From e6ae99be5553654c170de0437080f0541d632d0f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:50:19 +0000 Subject: [PATCH 068/170] test games function filtering on efg/nfg --- tests/test_catalog.py | 65 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 7c76c4937..7936ffa7c 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,6 +1,6 @@ import pytest -from pygambit import Game +from pygambit import Game, catalog from pygambit.catalog import CatalogGame, PrisonersDilemma @@ -48,3 +48,66 @@ def test_catalog_py_game_with_parameters(self): def test_catalog_yml_game_instantiation(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" assert isinstance(PrisonersDilemma(), 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 = catalog.games() + assert isinstance(result, list) + assert all(isinstance(name, str) for name in result) + + def test_games_filter_by_game_type(self): + """Filtering should split games into NFG/EFG.""" + nfg_games = catalog.games(game_type="nfg") + efg_games = catalog.games(game_type="efg") + all_games = catalog.games(game_type="all") + 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.""" +# two_player_games = gbt.catalog.games(num_players=2) +# for game_name in two_player_games: +# game_class = getattr(gbt.catalog, game_name) +# assert game_class.num_players == 2 + +# def test_games_combined_filters(self): +# """games() should support combining multiple filters.""" +# result = gbt.catalog.games(game_type="nfg", num_players=2) +# assert isinstance(result, list) +# for game_name in result: +# game_class = getattr(gbt.catalog, game_name) +# assert game_class.game_type == "nfg" +# assert game_class.num_players == 2 + +# def test_games_filter_by_custom_metadata(self): +# """games() should filter by custom metadata fields.""" +# # Assuming PrisonersDilemma has tutorial: 1 in metadata +# tutorial_games = gbt.catalog.games(tutorial=1) +# assert isinstance(tutorial_games, list) +# if tutorial_games: +# for game_name in tutorial_games: +# game_class = getattr(gbt.catalog, game_name) +# assert hasattr(game_class, "tutorial") +# assert game_class.tutorial == 1 + +# def test_games_all_filter(self): +# """games(game_type='all') should return all games.""" +# all_games = gbt.catalog.games(game_type="all") +# nfg_games = gbt.catalog.games(game_type="nfg") +# efg_games = gbt.catalog.games(game_type="efg") +# # All games should be union of NFG and EFG +# assert len(all_games) == len(set(nfg_games + efg_games)) + +# def test_games_nonexistent_filter(self): +# """games() with non-matching filters should return empty list.""" +# result = gbt.catalog.games(num_players=999) +# assert result == [] + +# def test_games_excludes_base_classes(self): +# """games() should not include base classes like CatalogGameFromContrib.""" +# result = gbt.catalog.games() +# assert "CatalogGame" not in result +# assert "CatalogGameFromContrib" not in result From b8219fda73818856c8960af10119f8cb7be34b3b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:51:48 +0000 Subject: [PATCH 069/170] add test_games_filter_by_num_players --- tests/test_catalog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 7936ffa7c..c196717d7 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -66,12 +66,12 @@ def test_games_filter_by_game_type(self): all_games = catalog.games(game_type="all") 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.""" -# two_player_games = gbt.catalog.games(num_players=2) -# for game_name in two_player_games: -# game_class = getattr(gbt.catalog, game_name) -# assert game_class.num_players == 2 + def test_games_filter_by_num_players(self): + """games(num_players=n) should return only n-player games.""" + two_player_games = catalog.games(num_players=3) + for game_name in two_player_games: + game_class = getattr(catalog, game_name) + assert game_class.num_players == 3 # def test_games_combined_filters(self): # """games() should support combining multiple filters.""" From 173098ffa9317ddc8bf3fbf1cb39e90c11753a3f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:57:46 +0000 Subject: [PATCH 070/170] add a test for custom metadata field in catalog games func --- src/pygambit/catalog/catalog.yml | 2 +- tests/test_catalog.py | 27 ++++++++------------------- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog/catalog.yml index cf9d79d65..277cfae7e 100644 --- a/src/pygambit/catalog/catalog.yml +++ b/src/pygambit/catalog/catalog.yml @@ -335,7 +335,7 @@ Pbride: PrisonersDilemma: file: pd.nfg metadata: - tutorial: 1 + test_suite: true Perfect1: file: perfect1.nfg metadata: diff --git a/tests/test_catalog.py b/tests/test_catalog.py index c196717d7..d0af2a431 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -73,25 +73,14 @@ def test_games_filter_by_num_players(self): game_class = getattr(catalog, game_name) assert game_class.num_players == 3 -# def test_games_combined_filters(self): -# """games() should support combining multiple filters.""" -# result = gbt.catalog.games(game_type="nfg", num_players=2) -# assert isinstance(result, list) -# for game_name in result: -# game_class = getattr(gbt.catalog, game_name) -# assert game_class.game_type == "nfg" -# assert game_class.num_players == 2 - -# def test_games_filter_by_custom_metadata(self): -# """games() should filter by custom metadata fields.""" -# # Assuming PrisonersDilemma has tutorial: 1 in metadata -# tutorial_games = gbt.catalog.games(tutorial=1) -# assert isinstance(tutorial_games, list) -# if tutorial_games: -# for game_name in tutorial_games: -# game_class = getattr(gbt.catalog, game_name) -# assert hasattr(game_class, "tutorial") -# assert game_class.tutorial == 1 + def test_games_filter_by_custom_metadata(self): + """games() should filter by custom metadata fields.""" + custom_filter_games = catalog.games(test_suite=True) + assert isinstance(custom_filter_games, list) + for game_name in custom_filter_games: + game_class = getattr(catalog, game_name) + assert hasattr(game_class, "test_suite") + assert game_class.test_suite is True # def test_games_all_filter(self): # """games(game_type='all') should return all games.""" From 3f2303ac592ffe26245a346fceeeadc8a75c884e Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 15:59:50 +0000 Subject: [PATCH 071/170] add test_games_excludes_base_classes --- tests/test_catalog.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index d0af2a431..bce77d33c 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -82,21 +82,8 @@ def test_games_filter_by_custom_metadata(self): assert hasattr(game_class, "test_suite") assert game_class.test_suite is True -# def test_games_all_filter(self): -# """games(game_type='all') should return all games.""" -# all_games = gbt.catalog.games(game_type="all") -# nfg_games = gbt.catalog.games(game_type="nfg") -# efg_games = gbt.catalog.games(game_type="efg") -# # All games should be union of NFG and EFG -# assert len(all_games) == len(set(nfg_games + efg_games)) - -# def test_games_nonexistent_filter(self): -# """games() with non-matching filters should return empty list.""" -# result = gbt.catalog.games(num_players=999) -# assert result == [] - -# def test_games_excludes_base_classes(self): -# """games() should not include base classes like CatalogGameFromContrib.""" -# result = gbt.catalog.games() -# assert "CatalogGame" not in result -# assert "CatalogGameFromContrib" not in result + def test_games_excludes_base_classes(self): + """games() should not include base classes like CatalogGameFromContrib.""" + result = catalog.games() + assert "CatalogGame" not in result + assert "CatalogGameFromContrib" not in result From 8fb41013c7934e6796e62f2adc9a0181f0fe64c4 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 16:01:25 +0000 Subject: [PATCH 072/170] test exact description content --- tests/test_catalog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index bce77d33c..7cffc16b8 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -35,7 +35,7 @@ def test_custom_game_subclass_extracts_metadata(self): assert TestGame.num_players == 2 assert TestGame.game_type == "efg" assert TestGame.title == "Test game F" - assert len(TestGame.description) > 0 + assert TestGame.description == "Test game description." def test_catalog_py_game_with_parameters(self): """ From cc97d910af19f7fe31153b61d79b9d406f9f6052 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 16:05:05 +0000 Subject: [PATCH 073/170] rename TestGame to ExampleGame --- tests/test_catalog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 7cffc16b8..40cd53fa7 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -4,7 +4,7 @@ from pygambit.catalog import CatalogGame, PrisonersDilemma -class TestGame(CatalogGame): +class ExampleGame(CatalogGame): """ Test game description. """ @@ -32,18 +32,18 @@ def test_catalog_game_not_instantiable(self): def test_custom_game_subclass_extracts_metadata(self): """Custom CatalogGame subclasses should extract metadata from _game().""" - assert TestGame.num_players == 2 - assert TestGame.game_type == "efg" - assert TestGame.title == "Test game F" - assert TestGame.description == "Test game description." + assert ExampleGame.num_players == 2 + assert ExampleGame.game_type == "efg" + assert ExampleGame.title == "Test game F" + assert ExampleGame.description == "Test game description." def test_catalog_py_game_with_parameters(self): """ Custom CatalogGame subclass should return Game and support parameters. """ - assert isinstance(TestGame(some_param=False), Game) - assert isinstance(TestGame(some_param=True), Game) + assert isinstance(ExampleGame(some_param=False), Game) + assert isinstance(ExampleGame(some_param=True), Game) def test_catalog_yml_game_instantiation(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" From bf9f0138f2ec954f5253e90780fad76824fe0c15 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 16:26:23 +0000 Subject: [PATCH 074/170] add initial API reference for catalog module --- doc/pygambit.api.rst | 11 +++++++++++ src/pygambit/catalog/catalog.py | 6 +++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index ae029bae7..b3383d18d 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -325,3 +325,14 @@ Computation of quantal response equilibria logit_estimate LogitQREMixedStrategyFitResult LogitQREMixedBehaviorFitResult + + +Game catalog +~~~~~~~~~~~~ + +.. currentmodule:: pygambit.catalog + +.. autosummary:: + :toctree: api/ + + games diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 4379807bc..fff2c1ae3 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -117,8 +117,8 @@ def games( 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 metadata fields. - For example, `tutorial=1` filters for games with `tutorial: 1` in metadata. + Additional keyword arguments to filter by catalog.yml metadata fields. + For example, `x=1` filters for games with `x: 1` in metadata. Returns ------- @@ -127,7 +127,7 @@ def games( Examples -------- - >>> games(tutorial=1) # Games with a custom metadata field 'tutorial' equal to 1 + >>> games(x=1) # Games with a custom metadata field 'x' equal to 1 >>> games(game_type="efg", num_players=2) # 2-player extensive-form games """ From 052014db6bf16a4fe22f0847655b2a1d18cc4a3e Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 16:38:55 +0000 Subject: [PATCH 075/170] use update script to update api reference doc --- doc/pygambit.api.rst | 145 +++++++++++++++++++++++++++++++++ src/pygambit/catalog/update.py | 46 +++++++++++ 2 files changed, 191 insertions(+) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index b3383d18d..173e492cc 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -336,3 +336,148 @@ Game catalog :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 + Holdout7 + 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 + 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/src/pygambit/catalog/update.py b/src/pygambit/catalog/update.py index 20aeffc2a..10f5f9c68 100644 --- a/src/pygambit/catalog/update.py +++ b/src/pygambit/catalog/update.py @@ -6,6 +6,7 @@ _CATALOG_YAML = Path(__file__).parent / "catalog.yml" _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" +_API_RST = Path(__file__).parent.parent.parent.parent / "doc/pygambit.api.rst" def make_class_name(filename: str) -> str: @@ -26,6 +27,48 @@ def make_class_name(filename: str) -> str: return name +def update_api_rst(catalog: dict[str, dict]) -> None: + """Update the Game catalog section in pygambit.api.rst with all class names.""" + 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 + class_names = sorted(catalog.keys()) + new_toctree = ".. autosummary::\n :toctree: api/\n\n games\n" + for class_name in class_names: + 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:] + ) + + with open(_API_RST, "w", encoding="utf-8") as f: + f.write(new_content) + + print(f"Updated {_API_RST} with {len(class_names)} catalog games") + + if __name__ == "__main__": # Use ruamel.yaml to preserve comments yaml = YAML() @@ -69,6 +112,9 @@ def make_class_name(filename: str) -> str: with _CATALOG_YAML.open("w", encoding="utf-8") as f: yaml.dump(catalog, f) + # Update the RST documentation + update_api_rst(catalog) + print(f"Added {new_entries_counter} new entries to the catalog") print(f"Output written to: {_CATALOG_YAML}") print("Done.") From 510b135def8c23f11248180e9bc3044de3c3bafd Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 16 Jan 2026 16:58:48 +0000 Subject: [PATCH 076/170] ignore invalid games when updating API docs --- doc/pygambit.api.rst | 37 +++++++++++++++++----------------- src/pygambit/catalog/update.py | 11 ++++++---- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 173e492cc..8a83e8b8d 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -336,6 +336,21 @@ Game catalog :toctree: api/ games + Game2s2x2x2 + Game2smp + Game2x2 + Game2x2a + Game2x2const + Game2x2x2_nau + Game2x2x2 + Game2x2x2NFG + Game2x2x2x2 + Game2x2x2x2x2 + Game3x3x3 + Game4cards + Game5x4x3 + Game8x2x2 + Game8x8 Artist1 Artist2 Badgame1 @@ -364,8 +379,8 @@ Game catalog Coord2NFG Coord2ts Coord3 - Coord333 Coord3NFG + Coord333 Coord4 Coord4NFG Cross @@ -401,23 +416,7 @@ Game catalog G2NFG G3 G3NFG - Game2s2x2x2 - Game2smp - Game2x2 - Game2x2a - Game2x2const - Game2x2x2 - Game2x2x2NFG - Game2x2x2_nau - Game2x2x2x2 - Game2x2x2x2x2 - Game3x3x3 - Game4cards - Game5x4x3 - Game8x2x2 - Game8x8 Holdout - Holdout7 Hs1 Jury_mr Jury_un @@ -447,13 +446,13 @@ Game catalog Palf2 Palf3 Pbride + PrisonersDilemma Perfect1 Perfect2 Perfect3 Poker - Poker2 PokerNFG - PrisonersDilemma + Poker2 Pvw Pvw2 Sh3 diff --git a/src/pygambit/catalog/update.py b/src/pygambit/catalog/update.py index 10f5f9c68..cf05c4eec 100644 --- a/src/pygambit/catalog/update.py +++ b/src/pygambit/catalog/update.py @@ -48,10 +48,13 @@ def update_api_rst(catalog: dict[str, dict]) -> None: next_section = len(content) # Build the new toctree content - class_names = sorted(catalog.keys()) new_toctree = ".. autosummary::\n :toctree: api/\n\n games\n" - for class_name in class_names: - new_toctree += f" {class_name}\n" + for class_name, entry in catalog.items(): + metadata = entry.get("metadata", {}) + if metadata and "valid_game" in metadata and metadata["valid_game"] is False: + pass # Marked as invalid game, do not add class + 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) @@ -66,7 +69,7 @@ def update_api_rst(catalog: dict[str, dict]) -> None: with open(_API_RST, "w", encoding="utf-8") as f: f.write(new_content) - print(f"Updated {_API_RST} with {len(class_names)} catalog games") + print(f"Updated {_API_RST} with new catalog game names") if __name__ == "__main__": From 88e1346fd1b8fd45b50fd4b1acffbce2acc18775 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 10:26:41 +0000 Subject: [PATCH 077/170] use game description field if no docstring is provided --- src/pygambit/catalog/catalog.py | 2 +- src/pygambit/game.pxi | 10 +++++----- tests/test_catalog.py | 24 +++++++++++++++++------- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index fff2c1ae3..531022fca 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -43,7 +43,7 @@ def _extract_metadata_from_game(cls, game: Game) -> None: if cls.__doc__: cls.description = inspect.cleandoc(cls.__doc__) else: - cls.description = game.comment + cls.description = game.description def __init_subclass__(cls, **kwargs): """Extract metadata when subclass is defined (if not a file-based game).""" 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 index 40cd53fa7..c73be19d0 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -5,21 +5,27 @@ class ExampleGame(CatalogGame): - """ - Test game description. - """ game_type = "efg" @staticmethod def _game(some_param: bool = False): if some_param: - return Game.new_tree( + g = Game.new_tree( players=["A", "B"], title="Test game T" ) - return Game.new_tree( - players=["A", "B"], title="Test game F" - ) + else: + g = Game.new_tree( + players=["A", "B"], title="Test game F" + ) + g.description = "Test game description." + return g + + +class ExampleGameWithDocstring(ExampleGame): + """ + Alternative test game description. + """ class TestCatalogGame: @@ -37,6 +43,10 @@ def test_custom_game_subclass_extracts_metadata(self): assert ExampleGame.title == "Test game F" assert ExampleGame.description == "Test game description." + def test_can_get_game_description_from_docstring(self): + """CatalogGame should get description from docstring over game description.""" + assert ExampleGameWithDocstring.description == "Alternative test game description." + def test_catalog_py_game_with_parameters(self): """ Custom CatalogGame subclass should return Game From 02740ee994631feb73f4de9497c653b28e25c203 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 10:36:43 +0000 Subject: [PATCH 078/170] Get game description docstring from catalog.yml --- src/pygambit/catalog/catalog.py | 1 + src/pygambit/catalog/catalog.yml | 1 + tests/test_catalog.py | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 531022fca..7266d9475 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -204,6 +204,7 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: class_attrs = { "game_file": game_file, "__module__": __name__, + "__doc__": entry.get("description", None), } # Add metadata fields as class attributes diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog/catalog.yml index 277cfae7e..a9b0288f5 100644 --- a/src/pygambit/catalog/catalog.yml +++ b/src/pygambit/catalog/catalog.yml @@ -334,6 +334,7 @@ Pbride: metadata: PrisonersDilemma: file: pd.nfg + description: "A simple implementation of a two person Prisoner's Dilemma game." metadata: test_suite: true Perfect1: diff --git a/tests/test_catalog.py b/tests/test_catalog.py index c73be19d0..4ea1d6eaa 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -59,6 +59,13 @@ def test_catalog_yml_game_instantiation(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" assert isinstance(PrisonersDilemma(), Game) + def test_catalog_yml_game_description(self): + """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" + assert ( + PrisonersDilemma.description + == "A simple implementation of a two person Prisoner's Dilemma game." + ) + class TestGamesFunction: """Tests for the games() query function.""" From f0cadea4bed68c658ab93609fa4268f6a416d9e6 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 10:44:27 +0000 Subject: [PATCH 079/170] handle change from comment to description in test and documentation --- doc/pygambit.api.rst | 2 +- tests/test_extensive.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 8a83e8b8d..9f02b0605 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 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"]]) From 31f29a6db27e95031e25f0446948320ae379c5d2 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 10:51:50 +0000 Subject: [PATCH 080/170] save nb output --- doc/tutorials/01_quickstart.ipynb | 31 +--------------- doc/tutorials/02_extensive_form.ipynb | 51 +++++++-------------------- 2 files changed, 13 insertions(+), 69 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 354218160..c0b941427 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -485,35 +485,6 @@ "gbt.catalog.games(game_type=\"nfg\", num_players=2)" ] }, - { - "cell_type": "markdown", - "id": "fd3f5829-c268-451a-8bc7-28e766e7d655", - "metadata": {}, - "source": [ - "The catalog can also be searched with custom metadata filters:" - ] - }, - { - "cell_type": "code", - "execution_count": 20, - "id": "2e680610-e2c9-45bc-84bd-67006aca5174", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['PrisonersDilemma']" - ] - }, - "execution_count": 20, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gbt.catalog.games(tutorial=1)" - ] - }, { "cell_type": "code", "execution_count": 15, @@ -523,7 +494,7 @@ { "data": { "text/plain": [ - "''" + "\"A simple implementation of a two person Prisoner's Dilemma game.\"" ] }, "execution_count": 15, diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 4fed4cfbb..7177eff14 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1235,30 +1235,6 @@ "print(gbt.catalog.OneShotTrust.description)" ] }, - { - "cell_type": "code", - "execution_count": 16, - "id": "bd8b291b-6cc6-4f9a-b5c3-0d1f4c05252b", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "Game(title='One-shot trust game, after Kreps (1990)')" - ], - "text/plain": [ - "Game(title='One-shot trust game, after Kreps (1990)')" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gbt.catalog.OneShotTrust(unique_NE_variant=True)" - ] - }, { "cell_type": "code", "execution_count": 17, @@ -1326,20 +1302,20 @@ "\n", "\n", "\n", - "\n", - "\n", - "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -1391,23 +1367,20 @@ "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -1417,7 +1390,7 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", From 355ac3e1a9e96eb2ca371cfb6018ec41f5942527 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 11:21:36 +0000 Subject: [PATCH 081/170] move coded games to their own module --- doc/tutorials/02_extensive_form.ipynb | 12 ++++---- src/pygambit/catalog/__init__.py | 1 + src/pygambit/catalog/catalog.py | 41 ++------------------------- src/pygambit/catalog/coded_games.py | 35 +++++++++++++++++++++++ 4 files changed, 44 insertions(+), 45 deletions(-) create mode 100644 src/pygambit/catalog/coded_games.py diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 7177eff14..be9ff1eff 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1237,7 +1237,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "4556c0bf", "metadata": {}, "outputs": [ @@ -1413,7 +1413,7 @@ "" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -1439,7 +1439,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1457,7 +1457,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1476,7 +1476,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1488,7 +1488,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index a26c0f3af..0affa9c21 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,5 +1,6 @@ from . import catalog from .catalog import games +from .coded_games import * # noqa: F403, F401 # Ensure catalog module is fully imported including all YAML-generated classes _all_catalog_classes = games() diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 7266d9475..1aa11d8a3 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -5,7 +5,6 @@ import yaml -# import numpy as np from pygambit.gambit import Game, read_efg, read_nfg _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" @@ -172,10 +171,6 @@ def get_all_subclasses(cls): return get_all_subclasses(CatalogGame) -############################ -# Catalog games from files # -############################ - _CATALOG_YAML = Path(__file__).parent / "catalog.yml" @@ -228,37 +223,5 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: _catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) _generate_contrib_game_classes(_catalog_data) -########################################## -# Catalog games defined programmatically # -########################################## - - -class OneShotTrust(CatalogGame): - """ - 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. - """ - game_type = "efg" - # test_suite = True - - @staticmethod - def _game(unique_NE_variant: bool = False): - g = Game.new_tree( - players=["Buyer", "Seller"], title="One-shot trust game, after 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 +# Import all coded games to ensure they are registered in the catalog +from .coded_games import * # noqa: E402, F403, F401 diff --git a/src/pygambit/catalog/coded_games.py b/src/pygambit/catalog/coded_games.py new file mode 100644 index 000000000..fd0f5e027 --- /dev/null +++ b/src/pygambit/catalog/coded_games.py @@ -0,0 +1,35 @@ +from pygambit.gambit import Game + +from .catalog import CatalogGame + + +class OneShotTrust(CatalogGame): + """ + 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. + """ + + game_type = "efg" + # test_suite = True + + @staticmethod + def _game(unique_NE_variant: bool = False): + g = Game.new_tree( + players=["Buyer", "Seller"], title="One-shot trust game, after 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 From 9be4983c45c7d110ecfcd372b46f90ec22862fb3 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 11:35:57 +0000 Subject: [PATCH 082/170] sort games alphabetically --- src/pygambit/catalog/catalog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 1aa11d8a3..01444d6b8 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -167,8 +167,8 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) return all_subclasses - - return get_all_subclasses(CatalogGame) + # Sort alphabetically for consistency + return sorted(get_all_subclasses(CatalogGame)) _CATALOG_YAML = Path(__file__).parent / "catalog.yml" From d9ac95227e8f6077fe195f9be31525d7299c7f10 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 11:40:16 +0000 Subject: [PATCH 083/170] add all games including those in coded_games.py to rst toctree in alphabetical order --- doc/pygambit.api.rst | 37 +++++++++++++++++----------------- src/pygambit/catalog/update.py | 19 ++++++++++------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 9f02b0605..d75b0bd91 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -336,21 +336,6 @@ Game catalog :toctree: api/ games - Game2s2x2x2 - Game2smp - Game2x2 - Game2x2a - Game2x2const - Game2x2x2_nau - Game2x2x2 - Game2x2x2NFG - Game2x2x2x2 - Game2x2x2x2x2 - Game3x3x3 - Game4cards - Game5x4x3 - Game8x2x2 - Game8x8 Artist1 Artist2 Badgame1 @@ -379,8 +364,8 @@ Game catalog Coord2NFG Coord2ts Coord3 - Coord3NFG Coord333 + Coord3NFG Coord4 Coord4NFG Cross @@ -416,6 +401,21 @@ Game catalog G2NFG G3 G3NFG + Game2s2x2x2 + Game2smp + Game2x2 + Game2x2a + Game2x2const + Game2x2x2 + Game2x2x2NFG + Game2x2x2_nau + Game2x2x2x2 + Game2x2x2x2x2 + Game3x3x3 + Game4cards + Game5x4x3 + Game8x2x2 + Game8x8 Holdout Hs1 Jury_mr @@ -441,18 +441,19 @@ Game catalog Myerson_fig_4_2 Nim Nim7 + OneShotTrust Oneill Palf Palf2 Palf3 Pbride - PrisonersDilemma Perfect1 Perfect2 Perfect3 Poker - PokerNFG Poker2 + PokerNFG + PrisonersDilemma Pvw Pvw2 Sh3 diff --git a/src/pygambit/catalog/update.py b/src/pygambit/catalog/update.py index cf05c4eec..9fd90f3ab 100644 --- a/src/pygambit/catalog/update.py +++ b/src/pygambit/catalog/update.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 +import sys from pathlib import Path from ruamel.yaml import YAML +from pygambit.catalog import games + _CATALOG_YAML = Path(__file__).parent / "catalog.yml" _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" _API_RST = Path(__file__).parent.parent.parent.parent / "doc/pygambit.api.rst" @@ -27,8 +30,9 @@ def make_class_name(filename: str) -> str: return name -def update_api_rst(catalog: dict[str, dict]) -> None: +def update_api_rst() -> None: """Update the Game catalog section in pygambit.api.rst with all class names.""" + _all_catalog_classes = games() with open(_API_RST, encoding="utf-8") as f: content = f.read() @@ -49,10 +53,10 @@ def update_api_rst(catalog: dict[str, dict]) -> None: # Build the new toctree content new_toctree = ".. autosummary::\n :toctree: api/\n\n games\n" - for class_name, entry in catalog.items(): - metadata = entry.get("metadata", {}) - if metadata and "valid_game" in metadata and metadata["valid_game"] is False: - pass # Marked as invalid game, do not add class + 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" @@ -115,8 +119,9 @@ def update_api_rst(catalog: dict[str, dict]) -> None: with _CATALOG_YAML.open("w", encoding="utf-8") as f: yaml.dump(catalog, f) - # Update the RST documentation - update_api_rst(catalog) + # Update the RST documentation with the new full catalog + # This includes games from coded_games.py as well as catalog.yml + update_api_rst() print(f"Added {new_entries_counter} new entries to the catalog") print(f"Output written to: {_CATALOG_YAML}") From 172a08c6bf92e98963402223cd4f8895af3ca6d4 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 12:00:10 +0000 Subject: [PATCH 084/170] no need for game to be a hidden attribute of CatalogGame classes --- src/pygambit/catalog/catalog.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 01444d6b8..ac87d73a1 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -21,13 +21,13 @@ class CatalogGame: num_players: int game_type: Literal["nfg", "efg"] description: str - _cached_game: Game | None = None + game: Game | None = None def __new__(cls, *args, **kwargs) -> Game: """Create a game instance by calling the _game() method.""" - if cls._cached_game is None: - cls._cached_game = cls._game(*args, **kwargs) - return cls._cached_game + if cls.game is None: + cls.game = cls._game(*args, **kwargs) + return cls.game @staticmethod def _game() -> Game: @@ -53,8 +53,8 @@ def __init_subclass__(cls, **kwargs): return # Load game and extract metadata immediately when class is defined - cls._cached_game = cls._game() - cls._extract_metadata_from_game(cls._cached_game) + cls.game = cls._game() + cls._extract_metadata_from_game(cls.game) class CatalogGameFromContrib(CatalogGame): @@ -67,9 +67,9 @@ class CatalogGameFromContrib(CatalogGame): game_file: str def __new__(cls) -> Game: - if cls._cached_game is None: - cls._cached_game = cls._load_game() - return cls._cached_game + if cls.game is None: + cls.game = cls._load_game() + return cls.game @classmethod def _load_game(cls) -> Game: @@ -94,8 +94,8 @@ def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) # Load game and extract metadata immediately when class is defined - cls._cached_game = cls._load_game() - cls._extract_metadata_from_game(cls._cached_game) + cls.game = cls._load_game() + cls._extract_metadata_from_game(cls.game) def games( From 71d6df88d0083dea8fa49f75ad50724413d895a3 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 14:37:21 +0000 Subject: [PATCH 085/170] instantiate catalog games within games() function to access metadata --- src/pygambit/catalog/catalog.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index ac87d73a1..de783051d 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -134,24 +134,24 @@ def get_all_subclasses(cls): """Recursively get all subclasses.""" all_subclasses = [] for subclass in cls.__subclasses__(): - # Check standard filters - if subclass.__name__ in ["CatalogGameFromContrib"]: - all_subclasses.extend(get_all_subclasses(subclass)) - continue - if game_type != "all" and not hasattr(subclass, "game_type"): + # Don't include CatalogGameFromContrib in result + if subclass.__name__ in ["CatalogGameFromContrib"]: all_subclasses.extend(get_all_subclasses(subclass)) continue - if game_type != "all" and subclass.game_type != game_type: - all_subclasses.extend(get_all_subclasses(subclass)) - continue + # Instantiate game to access metadata + g = subclass() - if num_players is not None and not hasattr(subclass, "num_players"): + # Check standard filters + g_game_type = "nfg" + if g.is_tree: + g_game_type = "efg" + if game_type != "all" and g_game_type != game_type: all_subclasses.extend(get_all_subclasses(subclass)) continue - if num_players is not None and subclass.num_players != num_players: + if num_players is not None and len(g.players) != num_players: all_subclasses.extend(get_all_subclasses(subclass)) continue From 9036f377786fab59d332966597a7b98c7faf66e5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 15:02:46 +0000 Subject: [PATCH 086/170] remove title and num_players attributes from CatalogGame --- src/pygambit/catalog/catalog.py | 4 ---- tests/test_catalog.py | 8 +++----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index de783051d..e68256568 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -17,8 +17,6 @@ class CatalogGame: Calling any subclass will return an instance of the corresponding game. """ - title: str - num_players: int game_type: Literal["nfg", "efg"] description: str game: Game | None = None @@ -37,8 +35,6 @@ def _game() -> Game: @classmethod def _extract_metadata_from_game(cls, game: Game) -> None: """Extract metadata from the game and set as class attributes.""" - cls.title = game.title - cls.num_players = len(game.players) if cls.__doc__: cls.description = inspect.cleandoc(cls.__doc__) else: diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 4ea1d6eaa..ebd53cd9d 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -38,9 +38,7 @@ def test_catalog_game_not_instantiable(self): def test_custom_game_subclass_extracts_metadata(self): """Custom CatalogGame subclasses should extract metadata from _game().""" - assert ExampleGame.num_players == 2 assert ExampleGame.game_type == "efg" - assert ExampleGame.title == "Test game F" assert ExampleGame.description == "Test game description." def test_can_get_game_description_from_docstring(self): @@ -85,10 +83,10 @@ def test_games_filter_by_game_type(self): def test_games_filter_by_num_players(self): """games(num_players=n) should return only n-player games.""" - two_player_games = catalog.games(num_players=3) - for game_name in two_player_games: + three_player_games = catalog.games(num_players=3) + for game_name in three_player_games: game_class = getattr(catalog, game_name) - assert game_class.num_players == 3 + assert len(game_class().players) == 3 def test_games_filter_by_custom_metadata(self): """games() should filter by custom metadata fields.""" From 8d291390282b2cee15b6faf9479eb683c23c6f49 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 15:06:34 +0000 Subject: [PATCH 087/170] remove game_type attribute --- src/pygambit/catalog/catalog.py | 3 --- src/pygambit/catalog/coded_games.py | 17 +++++++---------- tests/test_catalog.py | 3 --- 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index e68256568..c098a23e5 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -17,7 +17,6 @@ class CatalogGame: Calling any subclass will return an instance of the corresponding game. """ - game_type: Literal["nfg", "efg"] description: str game: Game | None = None @@ -77,10 +76,8 @@ def _load_game(cls) -> Game: file_path = _GAMEFILES_DIR / cls.game_file if game_type == "nfg": - cls.game_type = "nfg" return read_nfg(str(file_path)) elif game_type == "efg": - cls.game_type = "efg" return read_efg(str(file_path)) else: raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{game_type}'") diff --git a/src/pygambit/catalog/coded_games.py b/src/pygambit/catalog/coded_games.py index fd0f5e027..59a5cd248 100644 --- a/src/pygambit/catalog/coded_games.py +++ b/src/pygambit/catalog/coded_games.py @@ -4,19 +4,16 @@ class OneShotTrust(CatalogGame): - """ - 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. - """ - - game_type = "efg" - # test_suite = True @staticmethod def _game(unique_NE_variant: bool = False): + """ + 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. + """ g = Game.new_tree( players=["Buyer", "Seller"], title="One-shot trust game, after Kreps (1990)" ) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index ebd53cd9d..0875151d0 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -6,8 +6,6 @@ class ExampleGame(CatalogGame): - game_type = "efg" - @staticmethod def _game(some_param: bool = False): if some_param: @@ -38,7 +36,6 @@ def test_catalog_game_not_instantiable(self): def test_custom_game_subclass_extracts_metadata(self): """Custom CatalogGame subclasses should extract metadata from _game().""" - assert ExampleGame.game_type == "efg" assert ExampleGame.description == "Test game description." def test_can_get_game_description_from_docstring(self): From ef9125dd4cfde77c47b67c8eb78d751e96156fbb Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 15:21:21 +0000 Subject: [PATCH 088/170] get description from catalog --- doc/tutorials/01_quickstart.ipynb | 41 +++++++-------------------- doc/tutorials/02_extensive_form.ipynb | 15 +++++----- src/pygambit/catalog/catalog.py | 16 +++++------ src/pygambit/catalog/coded_games.py | 14 ++++----- tests/test_catalog.py | 6 ++-- 5 files changed, 36 insertions(+), 56 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index c0b941427..3062e500a 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -438,11 +438,7 @@ { "data": { "text/plain": [ - "['Game2x2',\n", - " 'Game2x2a',\n", - " 'Game2x2const',\n", - " 'Game8x8',\n", - " 'Cent2NFG',\n", + "['Cent2NFG',\n", " 'Coord2NFG',\n", " 'Coord3NFG',\n", " 'Coord4NFG',\n", @@ -455,14 +451,18 @@ " 'E02NFG',\n", " 'E04NFG',\n", " 'E07NFG',\n", + " 'Game2x2',\n", + " 'Game2x2a',\n", + " 'Game2x2const',\n", + " 'Game8x8',\n", " 'Loopback',\n", " 'Mixdom',\n", " 'Mixdom2',\n", " 'Oneill',\n", - " 'PrisonersDilemma',\n", " 'Perfect1',\n", " 'Perfect2',\n", " 'PokerNFG',\n", + " 'PrisonersDilemma',\n", " 'Sh3NFG',\n", " 'Stengel',\n", " 'Sww1NFG',\n", @@ -485,27 +485,6 @@ "gbt.catalog.games(game_type=\"nfg\", num_players=2)" ] }, - { - "cell_type": "code", - "execution_count": 15, - "id": "ff307b74-16bc-4889-b57c-fef3124ce5ca", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "\"A simple implementation of a two person Prisoner's Dilemma game.\"" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gbt.catalog.PrisonersDilemma.description" - ] - }, { "cell_type": "markdown", "id": "a919ddf7", @@ -516,7 +495,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 14, "id": "6db7a29a", "metadata": {}, "outputs": [ @@ -530,7 +509,7 @@ "Game(title='Two person Prisoner's Dilemma game')" ] }, - "execution_count": 16, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -556,7 +535,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 15, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -574,7 +553,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 16, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index be9ff1eff..c19ee9891 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1114,9 +1114,7 @@ { "data": { "text/plain": [ - "['Game2smp',\n", - " 'Game4cards',\n", - " 'Artist1',\n", + "['Artist1',\n", " 'Artist2',\n", " 'Badgame1',\n", " 'Badgame2',\n", @@ -1152,6 +1150,8 @@ " 'E16',\n", " 'E17',\n", " 'E18',\n", + " 'Game2smp',\n", + " 'Game4cards',\n", " 'Holdout',\n", " 'Hs1',\n", " 'Km1',\n", @@ -1170,6 +1170,7 @@ " 'Myerson_fig_4_2',\n", " 'Nim',\n", " 'Nim7',\n", + " 'OneShotTrust',\n", " 'Palf',\n", " 'Palf2',\n", " 'Palf3',\n", @@ -1191,8 +1192,7 @@ " 'Wilson1',\n", " 'Work1',\n", " 'Work2',\n", - " 'Work3',\n", - " 'OneShotTrust']" + " 'Work3']" ] }, "execution_count": 14, @@ -1232,7 +1232,8 @@ } ], "source": [ - "print(gbt.catalog.OneShotTrust.description)" + "g_variant = gbt.catalog.OneShotTrust(unique_NE_variant=True)\n", + "print(g_variant.description)" ] }, { @@ -1419,7 +1420,7 @@ } ], "source": [ - "draw_tree(gbt.catalog.OneShotTrust(unique_NE_variant=True))" + "draw_tree(g_variant)" ] }, { diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index c098a23e5..91baef2bc 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -17,7 +17,6 @@ class CatalogGame: Calling any subclass will return an instance of the corresponding game. """ - description: str game: Game | None = None def __new__(cls, *args, **kwargs) -> Game: @@ -32,12 +31,13 @@ def _game() -> Game: raise NotImplementedError("Subclasses must implement _game() method") @classmethod - def _extract_metadata_from_game(cls, game: Game) -> None: - """Extract metadata from the game and set as class attributes.""" + def _extract_description(cls, game: Game) -> None: + """Extract game description from docstring and apply to game.""" + cleaned_docstring = "" if cls.__doc__: - cls.description = inspect.cleandoc(cls.__doc__) - else: - cls.description = game.description + cleaned_docstring = inspect.cleandoc(cls.__doc__) + if len(cleaned_docstring) > 0: + game.description = cleaned_docstring def __init_subclass__(cls, **kwargs): """Extract metadata when subclass is defined (if not a file-based game).""" @@ -49,7 +49,7 @@ def __init_subclass__(cls, **kwargs): # Load game and extract metadata immediately when class is defined cls.game = cls._game() - cls._extract_metadata_from_game(cls.game) + cls._extract_description(cls.game) class CatalogGameFromContrib(CatalogGame): @@ -88,7 +88,7 @@ def __init_subclass__(cls, **kwargs): # Load game and extract metadata immediately when class is defined cls.game = cls._load_game() - cls._extract_metadata_from_game(cls.game) + cls._extract_description(cls.game) def games( diff --git a/src/pygambit/catalog/coded_games.py b/src/pygambit/catalog/coded_games.py index 59a5cd248..7eece37de 100644 --- a/src/pygambit/catalog/coded_games.py +++ b/src/pygambit/catalog/coded_games.py @@ -4,16 +4,16 @@ class OneShotTrust(CatalogGame): + """ + 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. + """ @staticmethod def _game(unique_NE_variant: bool = False): - """ - 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. - """ g = Game.new_tree( players=["Buyer", "Seller"], title="One-shot trust game, after Kreps (1990)" ) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 0875151d0..a3b93f1d4 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -36,11 +36,11 @@ def test_catalog_game_not_instantiable(self): def test_custom_game_subclass_extracts_metadata(self): """Custom CatalogGame subclasses should extract metadata from _game().""" - assert ExampleGame.description == "Test game description." + assert ExampleGame().description == "Test game description." def test_can_get_game_description_from_docstring(self): """CatalogGame should get description from docstring over game description.""" - assert ExampleGameWithDocstring.description == "Alternative test game description." + assert ExampleGameWithDocstring().description == "Alternative test game description." def test_catalog_py_game_with_parameters(self): """ @@ -57,7 +57,7 @@ def test_catalog_yml_game_instantiation(self): def test_catalog_yml_game_description(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" assert ( - PrisonersDilemma.description + PrisonersDilemma().description == "A simple implementation of a two person Prisoner's Dilemma game." ) From d766679634b267835086ded3d6659628167bfe68 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 15:27:09 +0000 Subject: [PATCH 089/170] Add docstring for game_file attribute in CatalogGameFromContrib --- src/pygambit/catalog/catalog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 91baef2bc..25ef3b9b8 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -60,6 +60,7 @@ class CatalogGameFromContrib(CatalogGame): """ game_file: str + """Filename of the game file in contrib/games directory.""" def __new__(cls) -> Game: if cls.game is None: From bb6a763398ccb3df82e784d305d623e19ea4701b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 15:36:02 +0000 Subject: [PATCH 090/170] update test to highlight broken title catching --- tests/test_catalog.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index a3b93f1d4..b657522bf 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -47,8 +47,8 @@ def test_catalog_py_game_with_parameters(self): Custom CatalogGame subclass should return Game and support parameters. """ - assert isinstance(ExampleGame(some_param=False), Game) - assert isinstance(ExampleGame(some_param=True), Game) + 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.""" From 46004387e4ba172536c3c4680c99d3b5086ad95c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 15:42:26 +0000 Subject: [PATCH 091/170] ensure game is recreated on __new__ call with different parameters --- src/pygambit/catalog/catalog.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 25ef3b9b8..abfb0aa56 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -21,9 +21,9 @@ class CatalogGame: def __new__(cls, *args, **kwargs) -> Game: """Create a game instance by calling the _game() method.""" - if cls.game is None: - cls.game = cls._game(*args, **kwargs) - return cls.game + g = cls._game(*args, **kwargs) + cls._extract_description(g) + return g @staticmethod def _game() -> Game: From a792b3171f0729752867644c916b68af355f44b6 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 15:48:34 +0000 Subject: [PATCH 092/170] overwrite cached Game instance on each instantiation --- src/pygambit/catalog/catalog.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index abfb0aa56..e16a2b71e 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -18,12 +18,13 @@ class CatalogGame: """ game: Game | None = None + """Cached ``Game`` instance. Overwritten on each instantiation.""" def __new__(cls, *args, **kwargs) -> Game: """Create a game instance by calling the _game() method.""" - g = cls._game(*args, **kwargs) - cls._extract_description(g) - return g + cls.game = cls._game(*args, **kwargs) + cls._extract_description(cls.game) + return cls.game @staticmethod def _game() -> Game: From ba9a6762f310f161bc2d867e8d5954ddcef7f538 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 16:12:06 +0000 Subject: [PATCH 093/170] add parameters to games() function to filter by game properties --- src/pygambit/catalog/catalog.py | 42 +++++++++++++++++++++++---------- tests/test_catalog.py | 6 ++--- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index e16a2b71e..f6b69e9b0 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,7 +1,6 @@ import inspect import sys from pathlib import Path -from typing import Literal import yaml @@ -94,8 +93,16 @@ def __init_subclass__(cls, **kwargs): def games( - game_type: Literal["all", "nfg", "efg"] = "all", + 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]: """ @@ -103,11 +110,24 @@ def games( Parameters ---------- - game_type : {"all", "nfg", "efg"}, default "all" - Filter games by type: - - "all": return all games - - "nfg": return only normal-form (strategic) games - - "efg": return only extensive-form games + 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 @@ -122,7 +142,7 @@ def games( Examples -------- >>> games(x=1) # Games with a custom metadata field 'x' equal to 1 - >>> games(game_type="efg", num_players=2) # 2-player extensive-form games + >>> games(is_tree=True, num_players=2) # 2-player extensive-form games """ def get_all_subclasses(cls): @@ -138,11 +158,7 @@ def get_all_subclasses(cls): # Instantiate game to access metadata g = subclass() - # Check standard filters - g_game_type = "nfg" - if g.is_tree: - g_game_type = "efg" - if game_type != "all" and g_game_type != game_type: + if is_tree is not None and g.is_tree != is_tree: all_subclasses.extend(get_all_subclasses(subclass)) continue diff --git a/tests/test_catalog.py b/tests/test_catalog.py index b657522bf..df4cc171b 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -73,9 +73,9 @@ def test_games_returns_list_of_strings(self): def test_games_filter_by_game_type(self): """Filtering should split games into NFG/EFG.""" - nfg_games = catalog.games(game_type="nfg") - efg_games = catalog.games(game_type="efg") - all_games = catalog.games(game_type="all") + nfg_games = catalog.games(is_tree=False) + efg_games = catalog.games(is_tree=True) + all_games = catalog.games(is_tree=None) assert len(all_games) == len(set(nfg_games + efg_games)) def test_games_filter_by_num_players(self): From fa21564ce7158bfcbc39d035088577448a56c460 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 16:14:20 +0000 Subject: [PATCH 094/170] refactor games() function to access game metadata directly from subclass cached game instance --- src/pygambit/catalog/catalog.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index f6b69e9b0..5d5a0398e 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -155,14 +155,11 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) continue - # Instantiate game to access metadata - g = subclass() - - if is_tree is not None and g.is_tree != is_tree: + if is_tree is not None and subclass.game.is_tree != is_tree: all_subclasses.extend(get_all_subclasses(subclass)) continue - if num_players is not None and len(g.players) != num_players: + if num_players is not None and len(subclass.game.players) != num_players: all_subclasses.extend(get_all_subclasses(subclass)) continue From 3fb7763994cf8d10b3ab3d616fa4f8f9a42bf5d7 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 16:17:45 +0000 Subject: [PATCH 095/170] add filtering logic for all game metadata in catalog.games() --- src/pygambit/catalog/catalog.py | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 5d5a0398e..c85826605 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -155,10 +155,48 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) continue + if num_actions is not None and subclass.game.num_actions != num_actions: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if ( + num_contingencies is not None + and subclass.game.num_contingencies != num_contingencies + ): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_infosets is not None and subclass.game.num_infosets != num_infosets: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if is_const_sum is not None and subclass.game.is_const_sum != is_const_sum: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if ( + is_perfect_recall is not None + and subclass.game.is_perfect_recall != is_perfect_recall + ): + all_subclasses.extend(get_all_subclasses(subclass)) + continue + if is_tree is not None and subclass.game.is_tree != is_tree: all_subclasses.extend(get_all_subclasses(subclass)) continue + if num_nodes is not None and subclass.game.num_nodes != num_nodes: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_outcomes is not None and subclass.game.num_outcomes != num_outcomes: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + + if num_strategies is not None and subclass.game.num_strategies != num_strategies: + all_subclasses.extend(get_all_subclasses(subclass)) + continue + if num_players is not None and len(subclass.game.players) != num_players: all_subclasses.extend(get_all_subclasses(subclass)) continue From 164eaf91679f770d9babce058dfe605deae8b2a4 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 16:44:42 +0000 Subject: [PATCH 096/170] fix filtering rules --- src/pygambit/catalog/catalog.py | 43 +++++++++++++++++++++++++-------- tests/test_catalog.py | 2 +- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index c85826605..76ed4b828 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -145,6 +145,14 @@ def games( >>> 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 = [] @@ -155,49 +163,64 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) continue - if num_actions is not None and subclass.game.num_actions != num_actions: + if num_actions is not None and num_actions != len(subclass.game.actions): all_subclasses.extend(get_all_subclasses(subclass)) continue if ( num_contingencies is not None - and subclass.game.num_contingencies != num_contingencies + and num_contingencies != len(subclass.game.contingencies) ): all_subclasses.extend(get_all_subclasses(subclass)) continue - if num_infosets is not None and subclass.game.num_infosets != num_infosets: + if ( + num_infosets is not None + and num_infosets != len(subclass.game.infosets) + ): all_subclasses.extend(get_all_subclasses(subclass)) continue - if is_const_sum is not None and subclass.game.is_const_sum != is_const_sum: + 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 subclass.game.is_perfect_recall != is_perfect_recall + and is_perfect_recall != subclass.game.is_perfect_recall ): all_subclasses.extend(get_all_subclasses(subclass)) continue - if is_tree is not None and subclass.game.is_tree != is_tree: + if is_tree is not None and is_tree != subclass.game.is_tree: all_subclasses.extend(get_all_subclasses(subclass)) continue - if num_nodes is not None and subclass.game.num_nodes != num_nodes: + if ( + num_nodes is not None and + num_nodes != len(subclass.game.nodes) + ): all_subclasses.extend(get_all_subclasses(subclass)) continue - if num_outcomes is not None and subclass.game.num_outcomes != num_outcomes: + 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 subclass.game.num_strategies != num_strategies: + 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 len(subclass.game.players) != num_players: + if ( + num_players is not None + and num_players != len(subclass.game.players) + ): all_subclasses.extend(get_all_subclasses(subclass)) continue diff --git a/tests/test_catalog.py b/tests/test_catalog.py index df4cc171b..095e6c136 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -83,7 +83,7 @@ def test_games_filter_by_num_players(self): three_player_games = catalog.games(num_players=3) for game_name in three_player_games: game_class = getattr(catalog, game_name) - assert len(game_class().players) == 3 + assert len(game_class.game.players) == 3 def test_games_filter_by_custom_metadata(self): """games() should filter by custom metadata fields.""" From 5879d139094dbbe9cae302cab0f97d2807ce248b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 19 Jan 2026 17:03:52 +0000 Subject: [PATCH 097/170] handle tree specific attributes properly --- doc/tutorials/01_quickstart.ipynb | 61 ++++++------ doc/tutorials/02_extensive_form.ipynb | 135 ++++++++++---------------- src/pygambit/catalog/catalog.py | 44 +++++---- tests/test_catalog.py | 7 ++ 4 files changed, 110 insertions(+), 137 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 3062e500a..7e828d229 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -438,41 +438,13 @@ { "data": { "text/plain": [ - "['Cent2NFG',\n", - " 'Coord2NFG',\n", - " 'Coord3NFG',\n", - " 'Coord4NFG',\n", - " 'Csg1',\n", - " 'Csg2',\n", - " 'Csg3',\n", - " 'Csg4',\n", - " 'Deg1',\n", - " 'Deg2',\n", - " 'E02NFG',\n", - " 'E04NFG',\n", - " 'E07NFG',\n", + "['Coord2NFG',\n", " 'Game2x2',\n", " 'Game2x2a',\n", " 'Game2x2const',\n", - " 'Game8x8',\n", " 'Loopback',\n", - " 'Mixdom',\n", - " 'Mixdom2',\n", - " 'Oneill',\n", - " 'Perfect1',\n", - " 'Perfect2',\n", - " 'PokerNFG',\n", " 'PrisonersDilemma',\n", - " 'Sh3NFG',\n", - " 'Stengel',\n", " 'Sww1NFG',\n", - " 'Todd1',\n", - " 'Todd2',\n", - " 'Todd3',\n", - " 'VdNFG',\n", - " 'Wink3',\n", - " 'Winkels',\n", - " 'Yamamoto',\n", " 'Zero']" ] }, @@ -482,7 +454,11 @@ } ], "source": [ - "gbt.catalog.games(game_type=\"nfg\", num_players=2)" + "gbt.catalog.games(\n", + " is_tree=False,\n", + " num_strategies=4,\n", + " num_players=2\n", + ")" ] }, { @@ -519,6 +495,27 @@ "g" ] }, + { + "cell_type": "code", + "execution_count": 15, + "id": "df1bd637-a28e-4798-b379-46a064457a65", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "len(g.contingencies)" + ] + }, { "cell_type": "markdown", "id": "24f36b0d", @@ -535,7 +532,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -553,7 +550,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index c19ee9891..d463b10fa 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1107,101 +1107,40 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 22, "id": "3207441f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "['Artist1',\n", - " 'Artist2',\n", - " 'Badgame1',\n", - " 'Badgame2',\n", - " 'Bagwell',\n", - " 'Bayes1a',\n", - " 'Bayes2a',\n", - " 'Bcp2',\n", - " 'Bcp3',\n", - " 'Bcp4',\n", - " 'Bhg1',\n", - " 'Bhg2',\n", - " 'Bhg3',\n", - " 'Bhg4',\n", - " 'Bhg5',\n", - " 'Caro2',\n", - " 'Cent2',\n", - " 'Cent3',\n", - " 'Cent4',\n", - " 'Cent6',\n", - " 'Centcs10',\n", - " 'Centcs6',\n", - " 'Coord2',\n", - " 'Coord2ts',\n", + "['Coord2',\n", " 'Coord3',\n", " 'Coord4',\n", - " 'Cross',\n", - " 'E02',\n", " 'E04',\n", - " 'E07',\n", - " 'E10',\n", - " 'E10a',\n", - " 'E13',\n", - " 'E16',\n", - " 'E17',\n", - " 'E18',\n", - " 'Game2smp',\n", - " 'Game4cards',\n", - " 'Holdout',\n", " 'Hs1',\n", " 'Km1',\n", " 'Km2',\n", - " 'Km3',\n", - " 'Km6',\n", - " 'Montyhal',\n", - " 'My_2_1',\n", - " 'My_2_4',\n", " 'My_3_3a',\n", " 'My_3_3b',\n", " 'My_3_3c',\n", " 'My_3_3d',\n", " 'My_3_3e',\n", - " 'Myerson',\n", - " 'Myerson_fig_4_2',\n", - " 'Nim',\n", - " 'Nim7',\n", " 'OneShotTrust',\n", - " 'Palf',\n", - " 'Palf2',\n", - " 'Palf3',\n", - " 'Pbride',\n", - " 'Poker',\n", - " 'Poker2',\n", - " 'Pvw',\n", - " 'Pvw2',\n", " 'Sh3',\n", - " 'Spence',\n", - " 'Sww1',\n", - " 'Sww2',\n", - " 'Sww3',\n", - " 'Tim',\n", - " 'Ttt',\n", - " 'Vd',\n", - " 'W_ex1',\n", - " 'W_ex2',\n", - " 'Wilson1',\n", - " 'Work1',\n", - " 'Work2',\n", - " 'Work3']" + " 'Sww1']" ] }, - "execution_count": 14, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "gbt.catalog.games(game_type=\"efg\", num_players=2)" + "gbt.catalog.games(\n", + " num_players=2,\n", + " num_infosets=2\n", + ")" ] }, { @@ -1239,6 +1178,27 @@ { "cell_type": "code", "execution_count": 16, + "id": "6fb4714b-19eb-4e33-93c4-b7c199fddcc0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "g_variant.is_const_sum" + ] + }, + { + "cell_type": "code", + "execution_count": 17, "id": "4556c0bf", "metadata": {}, "outputs": [ @@ -1303,19 +1263,19 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", "\n", @@ -1368,20 +1328,23 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -1391,7 +1354,7 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -1414,7 +1377,7 @@ "" ] }, - "execution_count": 16, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -1440,7 +1403,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1458,7 +1421,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1477,7 +1440,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1489,7 +1452,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 21, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 76ed4b828..dd35fe0cf 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -163,10 +163,34 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) continue - if num_actions is not None and num_actions != len(subclass.game.actions): + 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) @@ -174,13 +198,6 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) continue - if ( - num_infosets is not None - and num_infosets != len(subclass.game.infosets) - ): - 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 @@ -192,17 +209,6 @@ def get_all_subclasses(cls): 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_nodes is not None and - num_nodes != len(subclass.game.nodes) - ): - all_subclasses.extend(get_all_subclasses(subclass)) - continue - if ( num_outcomes is not None and num_outcomes != len(subclass.game.outcomes) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 095e6c136..35898df8b 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -85,6 +85,13 @@ def test_games_filter_by_num_players(self): game_class = getattr(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 = catalog.games(num_infosets=2) + for game_name in two_infoset_games: + game_class = getattr(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 = catalog.games(test_suite=True) From 9b32654ebe8c5d4c84c95c58bed6e97596ca0b95 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 20 Jan 2026 16:45:42 +0000 Subject: [PATCH 098/170] add test which checks for coded games in catalog.games() --- src/pygambit/catalog/coded_games.py | 2 ++ tests/test_catalog.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/pygambit/catalog/coded_games.py b/src/pygambit/catalog/coded_games.py index 7eece37de..79af52659 100644 --- a/src/pygambit/catalog/coded_games.py +++ b/src/pygambit/catalog/coded_games.py @@ -11,6 +11,8 @@ class OneShotTrust(CatalogGame): < 0.5 probability on Honor with a unique NE where the Buyer plays Trust and the Seller plays Abuse. """ + test_suite = True + """This game is included in the pygambit test suite.""" @staticmethod def _game(unique_NE_variant: bool = False): diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 35898df8b..1b5c3a5c5 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -106,3 +106,8 @@ def test_games_excludes_base_classes(self): result = catalog.games() assert "CatalogGame" not in result assert "CatalogGameFromContrib" not in result + + def test_games_includes_coded_games(self): + """games() should include manually coded games.""" + result = catalog.games(test_suite=True) + assert "OneShotTrust" in result From c5b3209bf816ef5c35de990750d80fa2dcb07497 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 20 Jan 2026 16:48:07 +0000 Subject: [PATCH 099/170] attempt to avoid circular import issues via lazy loading of manually coded games --- src/pygambit/__init__.py | 12 ++++++++---- src/pygambit/catalog/catalog.py | 26 +++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 5f9fb08cc..eaf103425 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -19,17 +19,21 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # - -from .gambit import * # noqa: F401,F403,I001 +import importlib.metadata from . import ( # noqa: F401 + catalog, # noqa: F401 nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 - catalog, # noqa: F401 ) -import importlib.metadata +# Import manually coded games to ensure they are registered in the catalog +# after catalog module is fully initialized (avoid circular import issues) +from .catalog.catalog import _load_coded_games +from .gambit import * # noqa: F401,F403,I001 + +_load_coded_games() try: __version__ = importlib.metadata.version("pygambit") diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index dd35fe0cf..e4b3b7acd 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -144,6 +144,8 @@ def games( >>> 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 """ + # Import manually coded games to ensure they are registered in the catalog + _load_coded_games() # Filter by extensive-form if filtering by tree-specific attributes if ( @@ -294,9 +296,27 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: setattr(module, class_name, cls) +_coded_games_loaded = False + + +def _load_coded_games(): + """Lazy load coded games.""" + global _coded_games_loaded + if _coded_games_loaded: + return + + from . import coded_games # noqa: F401 + # Import all coded game classes into this module's namespace + # so they are registered as CatalogGame subclasses + for name in dir(coded_games): + if not name.startswith("_"): + obj = getattr(coded_games, name) + if isinstance(obj, type) and issubclass(obj, CatalogGame): + globals()[name] = obj + + _coded_games_loaded = True + + # Generate classes at import time _catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) _generate_contrib_game_classes(_catalog_data) - -# Import all coded games to ensure they are registered in the catalog -from .coded_games import * # noqa: E402, F403, F401 From 5f8b79dbfa39f7bf5b60ec1d3b9bcc30a46f232a Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 20 Jan 2026 16:55:12 +0000 Subject: [PATCH 100/170] remove pointless import --- src/pygambit/catalog/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 0affa9c21..a26c0f3af 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,6 +1,5 @@ from . import catalog from .catalog import games -from .coded_games import * # noqa: F403, F401 # Ensure catalog module is fully imported including all YAML-generated classes _all_catalog_classes = games() From 6144b535dfd2574a502c6531dfeda4435a8ae4fe Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 12:41:00 +0000 Subject: [PATCH 101/170] simplify catalog __init__.py imports --- src/pygambit/catalog/__init__.py | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index a26c0f3af..8dc531b45 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,22 +1,9 @@ -from . import catalog -from .catalog import games +from .catalog import CatalogGame, games -# Ensure catalog module is fully imported including all YAML-generated classes -_all_catalog_classes = games() -_all_catalog_classes.append("CatalogGame") # Ensure base class is included +__all__ = ["games", "CatalogGame"] -_game_classes = {} -for game_name in _all_catalog_classes: - try: - _game_classes[game_name] = getattr(catalog, game_name) - except AttributeError as e: - raise ImportError( - f"Catalog game '{game_name}' listed but not found in catalog module" - ) from e - -# Add to module namespace -globals().update(_game_classes) - -# Build __all__ dynamically -__all__ = ["games", *list(_all_catalog_classes)] +def __getattr__(name: str): + """Lazy load catalog games on access.""" + from . import catalog as _catalog_module + return getattr(_catalog_module, name) From 4e11ee092a5dcf8589315479f0ceddca2eacb612 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 12:51:24 +0000 Subject: [PATCH 102/170] reorder imports in src/pygambit/__init__.py --- src/pygambit/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index eaf103425..e3303eb88 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -19,7 +19,8 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # -import importlib.metadata +import importlib.metadata # noqa: I001 +from .gambit import * # noqa: F401,F403,I001 from . import ( # noqa: F401 catalog, # noqa: F401 @@ -31,7 +32,6 @@ # Import manually coded games to ensure they are registered in the catalog # after catalog module is fully initialized (avoid circular import issues) from .catalog.catalog import _load_coded_games -from .gambit import * # noqa: F401,F403,I001 _load_coded_games() From e99610d4bffb4c912991d7afd3d8b233a5199fc9 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 13:04:40 +0000 Subject: [PATCH 103/170] test commenting out catalog import --- src/pygambit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index e3303eb88..c2db304e8 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -23,7 +23,7 @@ from .gambit import * # noqa: F401,F403,I001 from . import ( # noqa: F401 - catalog, # noqa: F401 + # catalog, # noqa: F401 nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 From 858fff7a1e21a128c94226448cb7aa543dac3494 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 13:15:38 +0000 Subject: [PATCH 104/170] try removing _load_coded_games from __init__.py --- src/pygambit/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index c2db304e8..8a0950e98 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -23,7 +23,7 @@ from .gambit import * # noqa: F401,F403,I001 from . import ( # noqa: F401 - # catalog, # noqa: F401 + catalog, # noqa: F401 nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 @@ -31,9 +31,9 @@ # Import manually coded games to ensure they are registered in the catalog # after catalog module is fully initialized (avoid circular import issues) -from .catalog.catalog import _load_coded_games +# from .catalog.catalog import _load_coded_games -_load_coded_games() +# _load_coded_games() try: __version__ = importlib.metadata.version("pygambit") From 178f89289af0cdcf272a6345267f30d23afe10cf Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 13:22:04 +0000 Subject: [PATCH 105/170] remove import --- src/pygambit/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 8a0950e98..15de41ad3 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -29,12 +29,6 @@ supports, # noqa: F401 ) -# Import manually coded games to ensure they are registered in the catalog -# after catalog module is fully initialized (avoid circular import issues) -# from .catalog.catalog import _load_coded_games - -# _load_coded_games() - try: __version__ = importlib.metadata.version("pygambit") except importlib.metadata.PackageNotFoundError: From 4e9f89abd44d9c505d78998b20499615867e9a1e Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 13:25:48 +0000 Subject: [PATCH 106/170] use relative imports to avoid circular import issues --- src/pygambit/catalog/catalog.py | 2 +- src/pygambit/catalog/coded_games.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index e4b3b7acd..fa0c6c40d 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -4,7 +4,7 @@ import yaml -from pygambit.gambit import Game, read_efg, read_nfg +from ..gambit import Game, read_efg, read_nfg _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" diff --git a/src/pygambit/catalog/coded_games.py b/src/pygambit/catalog/coded_games.py index 79af52659..0f4f31c43 100644 --- a/src/pygambit/catalog/coded_games.py +++ b/src/pygambit/catalog/coded_games.py @@ -1,5 +1,4 @@ -from pygambit.gambit import Game - +from ..gambit import Game from .catalog import CatalogGame From f0ff7f4f0848c0ee6bf9469091592c80d07e05f3 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 13:55:10 +0000 Subject: [PATCH 107/170] disable test temporarily --- tests/test_catalog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 1b5c3a5c5..ebb707289 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -54,12 +54,12 @@ def test_catalog_yml_game_instantiation(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" assert isinstance(PrisonersDilemma(), Game) - def test_catalog_yml_game_description(self): - """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" - assert ( - PrisonersDilemma().description - == "A simple implementation of a two person Prisoner's Dilemma game." - ) +# def test_catalog_yml_game_description(self): +# """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" +# assert ( +# PrisonersDilemma().description +# == "A simple implementation of a two person Prisoner's Dilemma game." +# ) class TestGamesFunction: From 2581c7064184355993b8d585dbf4fb86614280da Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 13:57:15 +0000 Subject: [PATCH 108/170] refactor catalog loading again --- src/pygambit/catalog/__init__.py | 11 ++++++- src/pygambit/catalog/catalog.py | 53 ++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 8dc531b45..5f3142cb0 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -6,4 +6,13 @@ def __getattr__(name: str): """Lazy load catalog games on access.""" from . import catalog as _catalog_module - return getattr(_catalog_module, name) + + # Don't try to load if already initializing to prevent recursion + if not _catalog_module._catalog_initializing: + # Ensure all catalog games are loaded + _catalog_module._ensure_catalog_loaded() + + if hasattr(_catalog_module, name): + return getattr(_catalog_module, name) + + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index fa0c6c40d..b07a3bc8d 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -47,10 +47,6 @@ def __init_subclass__(cls, **kwargs): if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): return - # Load game and extract metadata immediately when class is defined - cls.game = cls._game() - cls._extract_description(cls.game) - class CatalogGameFromContrib(CatalogGame): """ @@ -84,13 +80,9 @@ def _load_game(cls) -> Game: raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{game_type}'") def __init_subclass__(cls, **kwargs): - """Validate and extract metadata when subclass is defined.""" + """Validate 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._extract_description(cls.game) - def games( num_actions: int | None = None, @@ -144,8 +136,8 @@ def games( >>> 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 """ - # Import manually coded games to ensure they are registered in the catalog - _load_coded_games() + # Ensure all games are loaded + _ensure_catalog_loaded() # Filter by extensive-form if filtering by tree-specific attributes if ( @@ -165,6 +157,19 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) continue + # Ensure game is instantiated before filtering + if subclass.game is None: + try: + if issubclass(subclass, CatalogGameFromContrib): + subclass.game = subclass._load_game() + else: + subclass.game = subclass._game() + subclass._extract_description(subclass.game) + except Exception: + # Skip games that fail to load + 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 @@ -250,6 +255,27 @@ def get_all_subclasses(cls): _CATALOG_YAML = Path(__file__).parent / "catalog.yml" +_catalog_initialized = False +_catalog_initializing = False + + +def _ensure_catalog_loaded(): + """Ensure both YAML and coded games are loaded.""" + global _catalog_initialized, _catalog_initializing + if _catalog_initialized: + return + if _catalog_initializing: + return # Already loading, prevent re-entrance + + _catalog_initializing = True + try: + _catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) + _generate_contrib_game_classes(_catalog_data) + _load_coded_games() + _catalog_initialized = True + finally: + _catalog_initializing = False + def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: if not path.exists(): @@ -315,8 +341,3 @@ def _load_coded_games(): globals()[name] = obj _coded_games_loaded = True - - -# Generate classes at import time -_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) -_generate_contrib_game_classes(_catalog_data) From 51f5a37196474a81f60d85f85be384fecf1e3e7e Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:03:04 +0000 Subject: [PATCH 109/170] Revert "refactor catalog loading again" This reverts commit 2581c7064184355993b8d585dbf4fb86614280da. --- src/pygambit/catalog/__init__.py | 11 +------ src/pygambit/catalog/catalog.py | 53 ++++++++++---------------------- 2 files changed, 17 insertions(+), 47 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 5f3142cb0..8dc531b45 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -6,13 +6,4 @@ def __getattr__(name: str): """Lazy load catalog games on access.""" from . import catalog as _catalog_module - - # Don't try to load if already initializing to prevent recursion - if not _catalog_module._catalog_initializing: - # Ensure all catalog games are loaded - _catalog_module._ensure_catalog_loaded() - - if hasattr(_catalog_module, name): - return getattr(_catalog_module, name) - - raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + return getattr(_catalog_module, name) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index b07a3bc8d..fa0c6c40d 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -47,6 +47,10 @@ def __init_subclass__(cls, **kwargs): if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): return + # Load game and extract metadata immediately when class is defined + cls.game = cls._game() + cls._extract_description(cls.game) + class CatalogGameFromContrib(CatalogGame): """ @@ -80,9 +84,13 @@ def _load_game(cls) -> Game: raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{game_type}'") def __init_subclass__(cls, **kwargs): - """Validate metadata when subclass is defined.""" + """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._extract_description(cls.game) + def games( num_actions: int | None = None, @@ -136,8 +144,8 @@ def games( >>> 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 """ - # Ensure all games are loaded - _ensure_catalog_loaded() + # Import manually coded games to ensure they are registered in the catalog + _load_coded_games() # Filter by extensive-form if filtering by tree-specific attributes if ( @@ -157,19 +165,6 @@ def get_all_subclasses(cls): all_subclasses.extend(get_all_subclasses(subclass)) continue - # Ensure game is instantiated before filtering - if subclass.game is None: - try: - if issubclass(subclass, CatalogGameFromContrib): - subclass.game = subclass._load_game() - else: - subclass.game = subclass._game() - subclass._extract_description(subclass.game) - except Exception: - # Skip games that fail to load - 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 @@ -255,27 +250,6 @@ def get_all_subclasses(cls): _CATALOG_YAML = Path(__file__).parent / "catalog.yml" -_catalog_initialized = False -_catalog_initializing = False - - -def _ensure_catalog_loaded(): - """Ensure both YAML and coded games are loaded.""" - global _catalog_initialized, _catalog_initializing - if _catalog_initialized: - return - if _catalog_initializing: - return # Already loading, prevent re-entrance - - _catalog_initializing = True - try: - _catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) - _generate_contrib_game_classes(_catalog_data) - _load_coded_games() - _catalog_initialized = True - finally: - _catalog_initializing = False - def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: if not path.exists(): @@ -341,3 +315,8 @@ def _load_coded_games(): globals()[name] = obj _coded_games_loaded = True + + +# Generate classes at import time +_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) +_generate_contrib_game_classes(_catalog_data) From 3446cb933d78dd8c4e3b856232b2416461e41a35 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:03:23 +0000 Subject: [PATCH 110/170] Revert "disable test temporarily" This reverts commit f0ff7f4f0848c0ee6bf9469091592c80d07e05f3. --- tests/test_catalog.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index ebb707289..1b5c3a5c5 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -54,12 +54,12 @@ def test_catalog_yml_game_instantiation(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" assert isinstance(PrisonersDilemma(), Game) -# def test_catalog_yml_game_description(self): -# """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" -# assert ( -# PrisonersDilemma().description -# == "A simple implementation of a two person Prisoner's Dilemma game." -# ) + def test_catalog_yml_game_description(self): + """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" + assert ( + PrisonersDilemma().description + == "A simple implementation of a two person Prisoner's Dilemma game." + ) class TestGamesFunction: From 3c1c4151ecc535059eb8410f3bc00042c172fc1f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:21:15 +0000 Subject: [PATCH 111/170] refactor module loading in catalog test --- tests/test_catalog.py | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 1b5c3a5c5..fbae02f71 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,19 +1,18 @@ import pytest -from pygambit import Game, catalog -from pygambit.catalog import CatalogGame, PrisonersDilemma +import pygambit as gbt -class ExampleGame(CatalogGame): +class ExampleGame(gbt.catalog.CatalogGame): @staticmethod def _game(some_param: bool = False): if some_param: - g = Game.new_tree( + g = gbt.Game.new_tree( players=["A", "B"], title="Test game T" ) else: - g = Game.new_tree( + g = gbt.Game.new_tree( players=["A", "B"], title="Test game F" ) g.description = "Test game description." @@ -32,7 +31,7 @@ class TestCatalogGame: def test_catalog_game_not_instantiable(self): """CatalogGame should not be directly instantiable.""" with pytest.raises(NotImplementedError): - CatalogGame() + gbt.catalog.CatalogGame() def test_custom_game_subclass_extracts_metadata(self): """Custom CatalogGame subclasses should extract metadata from _game().""" @@ -52,12 +51,12 @@ def test_catalog_py_game_with_parameters(self): def test_catalog_yml_game_instantiation(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" - assert isinstance(PrisonersDilemma(), Game) + assert isinstance(gbt.catalog.PrisonersDilemma(), gbt.Game) def test_catalog_yml_game_description(self): """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" assert ( - PrisonersDilemma().description + gbt.catalog.PrisonersDilemma().description == "A simple implementation of a two person Prisoner's Dilemma game." ) @@ -67,47 +66,47 @@ class TestGamesFunction: def test_games_returns_list_of_strings(self): """games() should return a list of class name strings.""" - result = catalog.games() + result = gbt.catalog.games() assert isinstance(result, list) assert all(isinstance(name, str) for name in result) def test_games_filter_by_game_type(self): """Filtering should split games into NFG/EFG.""" - nfg_games = catalog.games(is_tree=False) - efg_games = catalog.games(is_tree=True) - all_games = catalog.games(is_tree=None) + 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 = catalog.games(num_players=3) + three_player_games = gbt.catalog.games(num_players=3) for game_name in three_player_games: - game_class = getattr(catalog, game_name) + 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 = catalog.games(num_infosets=2) + two_infoset_games = gbt.catalog.games(num_infosets=2) for game_name in two_infoset_games: - game_class = getattr(catalog, game_name) + 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 = catalog.games(test_suite=True) + 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(catalog, game_name) + 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 CatalogGameFromContrib.""" - result = catalog.games() + result = gbt.catalog.games() assert "CatalogGame" not in result assert "CatalogGameFromContrib" not in result def test_games_includes_coded_games(self): """games() should include manually coded games.""" - result = catalog.games(test_suite=True) + result = gbt.catalog.games(test_suite=True) assert "OneShotTrust" in result From e7889ae0c4fb117a280b99b6291a55808afef6c7 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:22:22 +0000 Subject: [PATCH 112/170] resave nb outputs --- doc/tutorials/01_quickstart.ipynb | 25 ++----------------- doc/tutorials/02_extensive_form.ipynb | 35 ++++++--------------------- 2 files changed, 9 insertions(+), 51 deletions(-) diff --git a/doc/tutorials/01_quickstart.ipynb b/doc/tutorials/01_quickstart.ipynb index 7e828d229..23e07731c 100644 --- a/doc/tutorials/01_quickstart.ipynb +++ b/doc/tutorials/01_quickstart.ipynb @@ -495,27 +495,6 @@ "g" ] }, - { - "cell_type": "code", - "execution_count": 15, - "id": "df1bd637-a28e-4798-b379-46a064457a65", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "len(g.contingencies)" - ] - }, { "cell_type": "markdown", "id": "24f36b0d", @@ -532,7 +511,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "f58eaa77", "metadata": {}, "outputs": [], @@ -550,7 +529,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "4119a2ac", "metadata": {}, "outputs": [], diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index d463b10fa..7aa32a64e 100644 --- a/doc/tutorials/02_extensive_form.ipynb +++ b/doc/tutorials/02_extensive_form.ipynb @@ -1107,7 +1107,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 14, "id": "3207441f", "metadata": {}, "outputs": [ @@ -1131,7 +1131,7 @@ " 'Sww1']" ] }, - "execution_count": 22, + "execution_count": 14, "metadata": {}, "output_type": "execute_result" } @@ -1178,27 +1178,6 @@ { "cell_type": "code", "execution_count": 16, - "id": "6fb4714b-19eb-4e33-93c4-b7c199fddcc0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "False" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "g_variant.is_const_sum" - ] - }, - { - "cell_type": "code", - "execution_count": 17, "id": "4556c0bf", "metadata": {}, "outputs": [ @@ -1377,7 +1356,7 @@ "" ] }, - "execution_count": 17, + "execution_count": 16, "metadata": {}, "output_type": "execute_result" } @@ -1403,7 +1382,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1421,7 +1400,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1440,7 +1419,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1452,7 +1431,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], From d0d5894adcbaaffaf9d93a796d1ee0f7747d8a72 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:25:48 +0000 Subject: [PATCH 113/170] temporarily comment out catalog tests and catalog/__init__.py code that loads coded games to avoid circular import issues during testing --- src/pygambit/catalog/__init__.py | 12 +- tests/test_catalog.py | 224 +++++++++++++++---------------- tests/test_tutorials.py | 152 ++++++++++----------- 3 files changed, 194 insertions(+), 194 deletions(-) diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog/__init__.py index 8dc531b45..583f12e1b 100644 --- a/src/pygambit/catalog/__init__.py +++ b/src/pygambit/catalog/__init__.py @@ -1,9 +1,9 @@ -from .catalog import CatalogGame, games +# from .catalog import CatalogGame, games -__all__ = ["games", "CatalogGame"] +# __all__ = ["games", "CatalogGame"] -def __getattr__(name: str): - """Lazy load catalog games on access.""" - from . import catalog as _catalog_module - return getattr(_catalog_module, name) +# def __getattr__(name: str): +# """Lazy load catalog games on access.""" +# from . import catalog as _catalog_module +# return getattr(_catalog_module, name) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index fbae02f71..054313725 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,112 +1,112 @@ -import pytest - -import pygambit as gbt - - -class ExampleGame(gbt.catalog.CatalogGame): - - @staticmethod - def _game(some_param: bool = False): - 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 = "Test game description." - return g - - -class ExampleGameWithDocstring(ExampleGame): - """ - Alternative test game description. - """ - - -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_custom_game_subclass_extracts_metadata(self): - """Custom CatalogGame subclasses should extract metadata from _game().""" - assert ExampleGame().description == "Test game description." - - def test_can_get_game_description_from_docstring(self): - """CatalogGame should get description from docstring over game description.""" - assert ExampleGameWithDocstring().description == "Alternative test game description." - - 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(self): - """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" - assert ( - gbt.catalog.PrisonersDilemma().description - == "A simple implementation of a two person Prisoner's Dilemma 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_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 CatalogGameFromContrib.""" - result = gbt.catalog.games() - assert "CatalogGame" not in result - assert "CatalogGameFromContrib" 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 +# import pytest + +# import pygambit as gbt + + +# class ExampleGame(gbt.catalog.CatalogGame): + +# @staticmethod +# def _game(some_param: bool = False): +# 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 = "Test game description." +# return g + + +# class ExampleGameWithDocstring(ExampleGame): +# """ +# Alternative test game description. +# """ + + +# 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_custom_game_subclass_extracts_metadata(self): +# """Custom CatalogGame subclasses should extract metadata from _game().""" +# assert ExampleGame().description == "Test game description." + +# def test_can_get_game_description_from_docstring(self): +# """CatalogGame should get description from docstring over game description.""" +# assert ExampleGameWithDocstring().description == "Alternative test game description." + +# 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(self): +# """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" +# assert ( +# gbt.catalog.PrisonersDilemma().description +# == "A simple implementation of a two person Prisoner's Dilemma 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_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 CatalogGameFromContrib.""" +# result = gbt.catalog.games() +# assert "CatalogGame" not in result +# assert "CatalogGameFromContrib" 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_tutorials.py b/tests/test_tutorials.py index e15171ebe..9f3ea91dd 100644 --- a/tests/test_tutorials.py +++ b/tests/test_tutorials.py @@ -1,76 +1,76 @@ -import contextlib -import os -import sys -from pathlib import Path - -import nbformat -import pytest - -# Ensure Jupyter uses the new platformdirs paths to avoid DeprecationWarning -# This will become the default in `jupyter_core` v6 -os.environ.setdefault("JUPYTER_PLATFORM_DIRS", "1") - -from nbclient import NotebookClient # noqa: E402 -from nbclient.exceptions import CellExecutionError # noqa: E402 - - -def _find_tutorial_notebooks(): - """Return a sorted list of notebook Paths under doc/tutorials. - - Skips the entire module if the tutorials directory does not exist. - """ - root = Path(__file__).resolve().parents[1] / "doc" / "tutorials" - if not root.exists(): - pytest.skip(f"Tutorials folder not found: {root}") - - # Collect all notebooks under doc/tutorials (including any subfolders). - # Exclude Jupyter checkpoint files - notebooks = sorted( - p for p in root.rglob("*.ipynb") - if ".ipynb_checkpoints" not in p.parts - ) - - if not notebooks: - pytest.skip(f"No tutorial notebooks found in: {root}") - return notebooks - - -# Discover notebooks at import time so pytest can parametrize them. -_NOTEBOOKS = _find_tutorial_notebooks() - - -@pytest.mark.tutorials -@pytest.mark.parametrize("nb_path", _NOTEBOOKS, ids=[p.name for p in _NOTEBOOKS]) -def test_execute_notebook(nb_path): - """Execute a single Jupyter notebook and fail if any cell errors occur. - - This uses nbclient.NotebookClient to run the notebook in its parent directory - so relative paths within the notebook resolve correctly. - """ - - # Skip OpenSpiel notebook on Windows - # (OpenSpiel is not available on Windows without manual install) - if sys.platform == "win32" and "openspiel" in nb_path.name.lower(): - pytest.skip("OpenSpiel notebook requires OpenSpiel, which is not available on Windows") - - nb = nbformat.read(str(nb_path), as_version=4) - - # Prefer the notebook's kernelspec if provided, otherwise let nbclient pick the default. - kernel_name = nb.metadata.get("kernelspec", {}).get("name") - - client = NotebookClient( - nb, - timeout=600, - kernel_name=kernel_name, - resources={"metadata": {"path": str(nb_path.parent)}}, - ) - - try: - client.execute() - except CellExecutionError as exc: - # Re-raise with more context so pytest shows which notebook failed. - raise AssertionError(f"Error while executing notebook {nb_path}: {exc}") from exc - finally: - # Ensure kernel is shut down. - with contextlib.suppress(Exception): - client.shutdown_kernel() +# import contextlib +# import os +# import sys +# from pathlib import Path + +# import nbformat +# import pytest + +# # Ensure Jupyter uses the new platformdirs paths to avoid DeprecationWarning +# # This will become the default in `jupyter_core` v6 +# os.environ.setdefault("JUPYTER_PLATFORM_DIRS", "1") + +# from nbclient import NotebookClient # noqa: E402 +# from nbclient.exceptions import CellExecutionError # noqa: E402 + + +# def _find_tutorial_notebooks(): +# """Return a sorted list of notebook Paths under doc/tutorials. + +# Skips the entire module if the tutorials directory does not exist. +# """ +# root = Path(__file__).resolve().parents[1] / "doc" / "tutorials" +# if not root.exists(): +# pytest.skip(f"Tutorials folder not found: {root}") + +# # Collect all notebooks under doc/tutorials (including any subfolders). +# # Exclude Jupyter checkpoint files +# notebooks = sorted( +# p for p in root.rglob("*.ipynb") +# if ".ipynb_checkpoints" not in p.parts +# ) + +# if not notebooks: +# pytest.skip(f"No tutorial notebooks found in: {root}") +# return notebooks + + +# # Discover notebooks at import time so pytest can parametrize them. +# _NOTEBOOKS = _find_tutorial_notebooks() + + +# @pytest.mark.tutorials +# @pytest.mark.parametrize("nb_path", _NOTEBOOKS, ids=[p.name for p in _NOTEBOOKS]) +# def test_execute_notebook(nb_path): +# """Execute a single Jupyter notebook and fail if any cell errors occur. + +# This uses nbclient.NotebookClient to run the notebook in its parent directory +# so relative paths within the notebook resolve correctly. +# """ + +# # Skip OpenSpiel notebook on Windows +# # (OpenSpiel is not available on Windows without manual install) +# if sys.platform == "win32" and "openspiel" in nb_path.name.lower(): +# pytest.skip("OpenSpiel notebook requires OpenSpiel, which is not available on Windows") + +# nb = nbformat.read(str(nb_path), as_version=4) + +# # Prefer the notebook's kernelspec if provided, otherwise let nbclient pick the default. +# kernel_name = nb.metadata.get("kernelspec", {}).get("name") + +# client = NotebookClient( +# nb, +# timeout=600, +# kernel_name=kernel_name, +# resources={"metadata": {"path": str(nb_path.parent)}}, +# ) + +# try: +# client.execute() +# except CellExecutionError as exc: +# # Re-raise with more context so pytest shows which notebook failed. +# raise AssertionError(f"Error while executing notebook {nb_path}: {exc}") from exc +# finally: +# # Ensure kernel is shut down. +# with contextlib.suppress(Exception): +# client.shutdown_kernel() From fc18ad37558b0351503ae98b38b03b762fbd4db5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:31:48 +0000 Subject: [PATCH 114/170] comment out coded games to help track circular import --- src/pygambit/catalog/catalog.py | 32 +++++++-------- src/pygambit/catalog/coded_games.py | 60 ++++++++++++++--------------- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index fa0c6c40d..ba9a87fab 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -145,7 +145,7 @@ def games( >>> games(is_tree=True, num_players=2) # 2-player extensive-form games """ # Import manually coded games to ensure they are registered in the catalog - _load_coded_games() + # _load_coded_games() # Filter by extensive-form if filtering by tree-specific attributes if ( @@ -296,25 +296,25 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: setattr(module, class_name, cls) -_coded_games_loaded = False +# _coded_games_loaded = False -def _load_coded_games(): - """Lazy load coded games.""" - global _coded_games_loaded - if _coded_games_loaded: - return +# def _load_coded_games(): +# """Lazy load coded games.""" +# global _coded_games_loaded +# if _coded_games_loaded: +# return - from . import coded_games # noqa: F401 - # Import all coded game classes into this module's namespace - # so they are registered as CatalogGame subclasses - for name in dir(coded_games): - if not name.startswith("_"): - obj = getattr(coded_games, name) - if isinstance(obj, type) and issubclass(obj, CatalogGame): - globals()[name] = obj +# from . import coded_games # noqa: F401 +# # Import all coded game classes into this module's namespace +# # so they are registered as CatalogGame subclasses +# for name in dir(coded_games): +# if not name.startswith("_"): +# obj = getattr(coded_games, name) +# if isinstance(obj, type) and issubclass(obj, CatalogGame): +# globals()[name] = obj - _coded_games_loaded = True +# _coded_games_loaded = True # Generate classes at import time diff --git a/src/pygambit/catalog/coded_games.py b/src/pygambit/catalog/coded_games.py index 0f4f31c43..0e1b62b48 100644 --- a/src/pygambit/catalog/coded_games.py +++ b/src/pygambit/catalog/coded_games.py @@ -1,33 +1,33 @@ -from ..gambit import Game -from .catalog import CatalogGame +# from ..gambit import Game +# from .catalog import CatalogGame -class OneShotTrust(CatalogGame): - """ - 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. - """ - test_suite = True - """This game is included in the pygambit test suite.""" +# class OneShotTrust(CatalogGame): +# """ +# 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. +# """ +# test_suite = True +# """This game is included in the pygambit test suite.""" - @staticmethod - def _game(unique_NE_variant: bool = False): - g = Game.new_tree( - players=["Buyer", "Seller"], title="One-shot trust game, after 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 +# @staticmethod +# def _game(unique_NE_variant: bool = False): +# g = Game.new_tree( +# players=["Buyer", "Seller"], title="One-shot trust game, after 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 From 8c679ef4b32f69bb20f26fdaccbf1bfd4c0ffb3e Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:42:14 +0000 Subject: [PATCH 115/170] restore import order --- src/pygambit/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 15de41ad3..7370b0525 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -19,7 +19,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # -import importlib.metadata # noqa: I001 + from .gambit import * # noqa: F401,F403,I001 from . import ( # noqa: F401 @@ -29,6 +29,8 @@ supports, # noqa: F401 ) +import importlib.metadata # noqa: I001 + try: __version__ = importlib.metadata.version("pygambit") except importlib.metadata.PackageNotFoundError: From 71b92b8a9e3be6da447f87e6d50f1ea00c535877 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:42:50 +0000 Subject: [PATCH 116/170] comment out update.py --- src/pygambit/catalog/update.py | 240 ++++++++++++++++----------------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/src/pygambit/catalog/update.py b/src/pygambit/catalog/update.py index 9fd90f3ab..cbae61d61 100644 --- a/src/pygambit/catalog/update.py +++ b/src/pygambit/catalog/update.py @@ -1,128 +1,128 @@ -#!/usr/bin/env python3 +# #!/usr/bin/env python3 -import sys -from pathlib import Path +# import sys +# from pathlib import Path -from ruamel.yaml import YAML +# from ruamel.yaml import YAML -from pygambit.catalog import games +# from pygambit.catalog import games -_CATALOG_YAML = Path(__file__).parent / "catalog.yml" -_GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" -_API_RST = Path(__file__).parent.parent.parent.parent / "doc/pygambit.api.rst" +# _CATALOG_YAML = Path(__file__).parent / "catalog.yml" +# _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" +# _API_RST = Path(__file__).parent.parent.parent.parent / "doc/pygambit.api.rst" -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("-", "_") +# 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 +# # 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() -> None: - """Update the Game catalog section in pygambit.api.rst with all class names.""" - _all_catalog_classes = games() - 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:] - ) - - with open(_API_RST, "w", encoding="utf-8") as f: - f.write(new_content) - - print(f"Updated {_API_RST} with new catalog game names") - - -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 contrib/games") - print(f"Found {len(nfg_files)} .nfg files in contrib/games") - - 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 contrib/games 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) - - # Update the RST documentation with the new full catalog - # This includes games from coded_games.py as well as catalog.yml - update_api_rst() - - print(f"Added {new_entries_counter} new entries to the catalog") - print(f"Output written to: {_CATALOG_YAML}") - print("Done.") +# if name and name[0].isdigit(): +# name = f"Game{name}" + +# return name + + +# def update_api_rst() -> None: +# """Update the Game catalog section in pygambit.api.rst with all class names.""" +# _all_catalog_classes = games() +# 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:] +# ) + +# with open(_API_RST, "w", encoding="utf-8") as f: +# f.write(new_content) + +# print(f"Updated {_API_RST} with new catalog game names") + + +# 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 contrib/games") +# print(f"Found {len(nfg_files)} .nfg files in contrib/games") + +# 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 contrib/games 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) + +# # Update the RST documentation with the new full catalog +# # This includes games from coded_games.py as well as catalog.yml +# update_api_rst() + +# print(f"Added {new_entries_counter} new entries to the catalog") +# print(f"Output written to: {_CATALOG_YAML}") +# print("Done.") From 3e96f500a468d33360b341688256c887c04596cb Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:44:29 +0000 Subject: [PATCH 117/170] remove noqa --- src/pygambit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 7370b0525..9a054cdaa 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -29,7 +29,7 @@ supports, # noqa: F401 ) -import importlib.metadata # noqa: I001 +import importlib.metadata try: __version__ = importlib.metadata.version("pygambit") From e8b6e8218b9e80ba870d15124020880fc3889c67 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 14:53:21 +0000 Subject: [PATCH 118/170] comment out generating classes at import time --- src/pygambit/catalog/catalog.py | 82 ++++++++++++++++----------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index ba9a87fab..c24f9d756 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,9 +1,9 @@ import inspect -import sys -from pathlib import Path -import yaml +# import sys +from pathlib import Path +# import yaml from ..gambit import Game, read_efg, read_nfg _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" @@ -248,52 +248,52 @@ def get_all_subclasses(cls): return sorted(get_all_subclasses(CatalogGame)) -_CATALOG_YAML = Path(__file__).parent / "catalog.yml" +# _CATALOG_YAML = Path(__file__).parent / "catalog.yml" -def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: - if not path.exists(): - raise FileNotFoundError(f"Catalog YAML not found: {path}") - with path.open("r", encoding="utf-8") as f: - return yaml.safe_load(f) or {} +# def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: +# if not path.exists(): +# raise FileNotFoundError(f"Catalog YAML not found: {path}") +# with path.open("r", encoding="utf-8") as f: +# return yaml.safe_load(f) or {} -def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: - """ - Dynamically generate CatalogGameFromContrib subclasses from YAML - and attach them to this module's namespace. - """ - module = sys.modules[__name__] +# def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: +# """ +# Dynamically generate CatalogGameFromContrib 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}'") +# 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", {}) +# 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), - } +# # 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 +# # 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, - (CatalogGameFromContrib,), - class_attrs, - ) +# cls = type( +# class_name, +# (CatalogGameFromContrib,), +# class_attrs, +# ) - setattr(module, class_name, cls) +# setattr(module, class_name, cls) # _coded_games_loaded = False @@ -318,5 +318,5 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: # Generate classes at import time -_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) -_generate_contrib_game_classes(_catalog_data) +# _catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) +# _generate_contrib_game_classes(_catalog_data) From 5891b31762700184a40350efe98287ff9c9ca6ef Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 15:05:45 +0000 Subject: [PATCH 119/170] comment out games function --- src/pygambit/catalog/catalog.py | 308 ++++++++++++++++---------------- 1 file changed, 154 insertions(+), 154 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index c24f9d756..3d0ecde6c 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -92,160 +92,160 @@ def __init_subclass__(cls, **kwargs): cls._extract_description(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 - """ - # Import manually coded games to ensure they are registered in the catalog - # _load_coded_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 CatalogGameFromContrib in result - if subclass.__name__ in ["CatalogGameFromContrib"]: - 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 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 +# """ +# # Import manually coded games to ensure they are registered in the catalog +# # _load_coded_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 CatalogGameFromContrib in result +# if subclass.__name__ in ["CatalogGameFromContrib"]: +# 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)) # _CATALOG_YAML = Path(__file__).parent / "catalog.yml" From 53a35e93ce11f9db74159b4eb1e8310cf2b881c9 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 15:15:50 +0000 Subject: [PATCH 120/170] change how gambit funcs are imported in catalog.py --- src/pygambit/catalog/catalog.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 3d0ecde6c..3a5203a99 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -4,7 +4,8 @@ from pathlib import Path # import yaml -from ..gambit import Game, read_efg, read_nfg +# from ..gambit import Game, read_efg, read_nfg +import pygambit as gbt _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" @@ -16,22 +17,22 @@ class CatalogGame: Calling any subclass will return an instance of the corresponding game. """ - game: Game | None = None + game: gbt.Game | None = None """Cached ``Game`` instance. Overwritten on each instantiation.""" - def __new__(cls, *args, **kwargs) -> Game: + def __new__(cls, *args, **kwargs) -> gbt.Game: """Create a game instance by calling the _game() method.""" cls.game = cls._game(*args, **kwargs) cls._extract_description(cls.game) return cls.game @staticmethod - def _game() -> Game: + def _game() -> gbt.Game: """Override this method in subclasses to define the game.""" raise NotImplementedError("Subclasses must implement _game() method") @classmethod - def _extract_description(cls, game: Game) -> None: + def _extract_description(cls, game: gbt.Game) -> None: """Extract game description from docstring and apply to game.""" cleaned_docstring = "" if cls.__doc__: @@ -62,13 +63,13 @@ class CatalogGameFromContrib(CatalogGame): game_file: str """Filename of the game file in contrib/games directory.""" - def __new__(cls) -> Game: + def __new__(cls) -> gbt.Game: if cls.game is None: cls.game = cls._load_game() return cls.game @classmethod - def _load_game(cls) -> Game: + 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' class attribute") @@ -77,11 +78,11 @@ def _load_game(cls) -> Game: file_path = _GAMEFILES_DIR / cls.game_file if game_type == "nfg": - return read_nfg(str(file_path)) + return gbt.read_nfg(str(file_path)) elif game_type == "efg": - return read_efg(str(file_path)) + return gbt.read_efg(str(file_path)) else: - raise ValueError(f"Game file extension must be 'nfg' or 'efg', got '{game_type}'") + 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.""" From 09b4ad7530605be3741a1877c0537e2f08a782ce Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 15:28:26 +0000 Subject: [PATCH 121/170] comment out everything in catalog.py --- src/pygambit/catalog/catalog.py | 166 ++++++++++++++++---------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog/catalog.py index 3a5203a99..14ae74379 100644 --- a/src/pygambit/catalog/catalog.py +++ b/src/pygambit/catalog/catalog.py @@ -1,96 +1,96 @@ -import inspect +# import inspect # import sys from pathlib import Path # import yaml # from ..gambit import Game, read_efg, read_nfg -import pygambit as gbt +# import pygambit as gbt _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" -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.""" - - def __new__(cls, *args, **kwargs) -> gbt.Game: - """Create a game instance by calling the _game() method.""" - cls.game = cls._game(*args, **kwargs) - cls._extract_description(cls.game) - return cls.game - - @staticmethod - def _game() -> gbt.Game: - """Override this method in subclasses to define the game.""" - raise NotImplementedError("Subclasses must implement _game() method") - - @classmethod - def _extract_description(cls, game: gbt.Game) -> None: - """Extract game description from docstring and apply to game.""" - cleaned_docstring = "" - if cls.__doc__: - cleaned_docstring = inspect.cleandoc(cls.__doc__) - if len(cleaned_docstring) > 0: - game.description = 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 CatalogGameFromContrib or its subclasses - if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): - return - - # Load game and extract metadata immediately when class is defined - cls.game = cls._game() - cls._extract_description(cls.game) - - -class CatalogGameFromContrib(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 contrib/games directory.""" - - def __new__(cls) -> gbt.Game: - if cls.game 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' class attribute") - - game_type = cls.game_file.split(".")[-1] - file_path = _GAMEFILES_DIR / cls.game_file - - if game_type == "nfg": - return gbt.read_nfg(str(file_path)) - elif game_type == "efg": - return gbt.read_efg(str(file_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._extract_description(cls.game) +# 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.""" + +# def __new__(cls, *args, **kwargs) -> gbt.Game: +# """Create a game instance by calling the _game() method.""" +# cls.game = cls._game(*args, **kwargs) +# cls._extract_description(cls.game) +# return cls.game + +# @staticmethod +# def _game() -> gbt.Game: +# """Override this method in subclasses to define the game.""" +# raise NotImplementedError("Subclasses must implement _game() method") + +# @classmethod +# def _extract_description(cls, game: gbt.Game) -> None: +# """Extract game description from docstring and apply to game.""" +# cleaned_docstring = "" +# if cls.__doc__: +# cleaned_docstring = inspect.cleandoc(cls.__doc__) +# if len(cleaned_docstring) > 0: +# game.description = 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 CatalogGameFromContrib or its subclasses +# if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): +# return + +# # Load game and extract metadata immediately when class is defined +# cls.game = cls._game() +# cls._extract_description(cls.game) + + +# class CatalogGameFromContrib(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 contrib/games directory.""" + +# def __new__(cls) -> gbt.Game: +# if cls.game 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' class attribute") + +# game_type = cls.game_file.split(".")[-1] +# file_path = _GAMEFILES_DIR / cls.game_file + +# if game_type == "nfg": +# return gbt.read_nfg(str(file_path)) +# elif game_type == "efg": +# return gbt.read_efg(str(file_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._extract_description(cls.game) # def games( From 658a8af597681bdebca6216758698ad86d1a7aa0 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 15:34:24 +0000 Subject: [PATCH 122/170] reorder catalog to be final import --- src/pygambit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 9a054cdaa..5f9fb08cc 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -23,10 +23,10 @@ from .gambit import * # noqa: F401,F403,I001 from . import ( # noqa: F401 - catalog, # noqa: F401 nash, # noqa: F401 qre, # noqa: F401 supports, # noqa: F401 + catalog, # noqa: F401 ) import importlib.metadata From f14b17e4d181be59dbbcaa01efd447fe0c056e6b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 15:45:05 +0000 Subject: [PATCH 123/170] can catalog be imported from module instead of dir --- src/pygambit/{catalog => }/catalog.py | 0 src/pygambit/{catalog => catalog_dir}/__init__.py | 0 src/pygambit/{catalog => catalog_dir}/catalog.yml | 0 src/pygambit/{catalog => catalog_dir}/coded_games.py | 0 src/pygambit/{catalog => catalog_dir}/update.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename src/pygambit/{catalog => }/catalog.py (100%) rename src/pygambit/{catalog => catalog_dir}/__init__.py (100%) rename src/pygambit/{catalog => catalog_dir}/catalog.yml (100%) rename src/pygambit/{catalog => catalog_dir}/coded_games.py (100%) rename src/pygambit/{catalog => catalog_dir}/update.py (100%) diff --git a/src/pygambit/catalog/catalog.py b/src/pygambit/catalog.py similarity index 100% rename from src/pygambit/catalog/catalog.py rename to src/pygambit/catalog.py diff --git a/src/pygambit/catalog/__init__.py b/src/pygambit/catalog_dir/__init__.py similarity index 100% rename from src/pygambit/catalog/__init__.py rename to src/pygambit/catalog_dir/__init__.py diff --git a/src/pygambit/catalog/catalog.yml b/src/pygambit/catalog_dir/catalog.yml similarity index 100% rename from src/pygambit/catalog/catalog.yml rename to src/pygambit/catalog_dir/catalog.yml diff --git a/src/pygambit/catalog/coded_games.py b/src/pygambit/catalog_dir/coded_games.py similarity index 100% rename from src/pygambit/catalog/coded_games.py rename to src/pygambit/catalog_dir/coded_games.py diff --git a/src/pygambit/catalog/update.py b/src/pygambit/catalog_dir/update.py similarity index 100% rename from src/pygambit/catalog/update.py rename to src/pygambit/catalog_dir/update.py From 2382a1f4a3349de443ffbb44746cd7905edeb5e2 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 16:10:33 +0000 Subject: [PATCH 124/170] move all catalog code outside of catalog dir and into catalog.py --- src/pygambit/catalog.py | 628 +++++++++--------- src/pygambit/{catalog_dir => }/catalog.yml | 0 src/pygambit/catalog_dir/update.py | 128 ---- src/pygambit/{catalog_dir => }/coded_games.py | 7 +- src/pygambit/update.py | 128 ++++ tests/test_catalog.py | 224 +++---- tests/test_tutorials.py | 152 ++--- 7 files changed, 650 insertions(+), 617 deletions(-) rename src/pygambit/{catalog_dir => }/catalog.yml (100%) delete mode 100644 src/pygambit/catalog_dir/update.py rename src/pygambit/{catalog_dir => }/coded_games.py (91%) create mode 100644 src/pygambit/update.py diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 14ae74379..4d541bf7c 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -1,300 +1,299 @@ -# import inspect - -# import sys +import inspect +import sys from pathlib import Path -# import yaml -# from ..gambit import Game, read_efg, read_nfg -# import pygambit as gbt - -_GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" - - -# 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.""" - -# def __new__(cls, *args, **kwargs) -> gbt.Game: -# """Create a game instance by calling the _game() method.""" -# cls.game = cls._game(*args, **kwargs) -# cls._extract_description(cls.game) -# return cls.game - -# @staticmethod -# def _game() -> gbt.Game: -# """Override this method in subclasses to define the game.""" -# raise NotImplementedError("Subclasses must implement _game() method") - -# @classmethod -# def _extract_description(cls, game: gbt.Game) -> None: -# """Extract game description from docstring and apply to game.""" -# cleaned_docstring = "" -# if cls.__doc__: -# cleaned_docstring = inspect.cleandoc(cls.__doc__) -# if len(cleaned_docstring) > 0: -# game.description = 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 CatalogGameFromContrib or its subclasses -# if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): -# return - -# # Load game and extract metadata immediately when class is defined -# cls.game = cls._game() -# cls._extract_description(cls.game) - - -# class CatalogGameFromContrib(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 contrib/games directory.""" - -# def __new__(cls) -> gbt.Game: -# if cls.game 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' class attribute") - -# game_type = cls.game_file.split(".")[-1] -# file_path = _GAMEFILES_DIR / cls.game_file - -# if game_type == "nfg": -# return gbt.read_nfg(str(file_path)) -# elif game_type == "efg": -# return gbt.read_efg(str(file_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._extract_description(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 -# """ -# # Import manually coded games to ensure they are registered in the catalog -# # _load_coded_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 CatalogGameFromContrib in result -# if subclass.__name__ in ["CatalogGameFromContrib"]: -# 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)) - - -# _CATALOG_YAML = Path(__file__).parent / "catalog.yml" - - -# def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: -# if not path.exists(): -# raise FileNotFoundError(f"Catalog YAML not found: {path}") -# with path.open("r", encoding="utf-8") as f: -# return yaml.safe_load(f) or {} - - -# def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: -# """ -# Dynamically generate CatalogGameFromContrib 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, -# (CatalogGameFromContrib,), -# class_attrs, -# ) - -# setattr(module, class_name, cls) +import yaml + +import pygambit as gbt + +_GAMEFILES_DIR = Path(__file__).parent.parent.parent / "contrib/games" + + +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.""" + + def __new__(cls, *args, **kwargs) -> gbt.Game: + """Create a game instance by calling the _game() method.""" + cls.game = cls._game(*args, **kwargs) + cls._extract_description(cls.game) + return cls.game + + @staticmethod + def _game() -> gbt.Game: + """Override this method in subclasses to define the game.""" + raise NotImplementedError("Subclasses must implement _game() method") + + @classmethod + def _extract_description(cls, game: gbt.Game) -> None: + """Extract game description from docstring and apply to game.""" + cleaned_docstring = "" + if cls.__doc__: + cleaned_docstring = inspect.cleandoc(cls.__doc__) + if len(cleaned_docstring) > 0: + game.description = 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 CatalogGameFromContrib or its subclasses + if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): + return + + # Load game and extract metadata immediately when class is defined + cls.game = cls._game() + cls._extract_description(cls.game) + + +class CatalogGameFromContrib(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 contrib/games directory.""" + + def __new__(cls) -> gbt.Game: + if cls.game 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' class attribute") + + game_type = cls.game_file.split(".")[-1] + file_path = _GAMEFILES_DIR / cls.game_file + + if game_type == "nfg": + return gbt.read_nfg(str(file_path)) + elif game_type == "efg": + return gbt.read_efg(str(file_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._extract_description(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 + """ + # Import manually coded games to ensure they are registered in the catalog + # _load_coded_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 CatalogGameFromContrib in result + if subclass.__name__ in ["CatalogGameFromContrib"]: + 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)) + + +_CATALOG_YAML = Path(__file__).parent / "catalog.yml" + + +def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: + if not path.exists(): + raise FileNotFoundError(f"Catalog YAML not found: {path}") + with path.open("r", encoding="utf-8") as f: + return yaml.safe_load(f) or {} + + +def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: + """ + Dynamically generate CatalogGameFromContrib 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, + (CatalogGameFromContrib,), + class_attrs, + ) + + setattr(module, class_name, cls) # _coded_games_loaded = False @@ -319,5 +318,40 @@ # Generate classes at import time -# _catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) -# _generate_contrib_game_classes(_catalog_data) +_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) +_generate_contrib_game_classes(_catalog_data) +# _load_coded_games() + +# Temporarily include coded games here + + +class OneShotTrust(CatalogGame): + """ + 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. + """ + + test_suite = True + """This game is included in the pygambit test suite.""" + + @staticmethod + def _game(unique_NE_variant: bool = False): + g = gbt.Game.new_tree( + players=["Buyer", "Seller"], title="One-shot trust game, after 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_dir/catalog.yml b/src/pygambit/catalog.yml similarity index 100% rename from src/pygambit/catalog_dir/catalog.yml rename to src/pygambit/catalog.yml diff --git a/src/pygambit/catalog_dir/update.py b/src/pygambit/catalog_dir/update.py deleted file mode 100644 index cbae61d61..000000000 --- a/src/pygambit/catalog_dir/update.py +++ /dev/null @@ -1,128 +0,0 @@ -# #!/usr/bin/env python3 - -# import sys -# from pathlib import Path - -# from ruamel.yaml import YAML - -# from pygambit.catalog import games - -# _CATALOG_YAML = Path(__file__).parent / "catalog.yml" -# _GAMEFILES_DIR = Path(__file__).parent.parent.parent.parent / "contrib/games" -# _API_RST = Path(__file__).parent.parent.parent.parent / "doc/pygambit.api.rst" - - -# 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() -> None: -# """Update the Game catalog section in pygambit.api.rst with all class names.""" -# _all_catalog_classes = games() -# 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:] -# ) - -# with open(_API_RST, "w", encoding="utf-8") as f: -# f.write(new_content) - -# print(f"Updated {_API_RST} with new catalog game names") - - -# 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 contrib/games") -# print(f"Found {len(nfg_files)} .nfg files in contrib/games") - -# 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 contrib/games 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) - -# # Update the RST documentation with the new full catalog -# # This includes games from coded_games.py as well as catalog.yml -# update_api_rst() - -# print(f"Added {new_entries_counter} new entries to the catalog") -# print(f"Output written to: {_CATALOG_YAML}") -# print("Done.") diff --git a/src/pygambit/catalog_dir/coded_games.py b/src/pygambit/coded_games.py similarity index 91% rename from src/pygambit/catalog_dir/coded_games.py rename to src/pygambit/coded_games.py index 0e1b62b48..82771ba5f 100644 --- a/src/pygambit/catalog_dir/coded_games.py +++ b/src/pygambit/coded_games.py @@ -1,8 +1,7 @@ -# from ..gambit import Game -# from .catalog import CatalogGame +# import pygambit as gbt -# class OneShotTrust(CatalogGame): +# class OneShotTrust(gbt.catalog.CatalogGame): # """ # The unique_NE_variant makes Trust a dominant strategy, replacing the # non-singleton equilibrium component from the standard version of the game @@ -15,7 +14,7 @@ # @staticmethod # def _game(unique_NE_variant: bool = False): -# g = Game.new_tree( +# g = gbt.Game.new_tree( # players=["Buyer", "Seller"], title="One-shot trust game, after Kreps (1990)" # ) # g.append_move(g.root, "Buyer", ["Trust", "Not trust"]) diff --git a/src/pygambit/update.py b/src/pygambit/update.py new file mode 100644 index 000000000..bb1f646a7 --- /dev/null +++ b/src/pygambit/update.py @@ -0,0 +1,128 @@ +#!/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.parent.parent / "contrib/games" +_API_RST = Path(__file__).parent.parent.parent / "doc/pygambit.api.rst" + + +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() -> None: + """Update the Game catalog section in pygambit.api.rst with all class names.""" + _all_catalog_classes = gbt.catalog.games() + 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:] + ) + + with open(_API_RST, "w", encoding="utf-8") as f: + f.write(new_content) + + print(f"Updated {_API_RST} with new catalog game names") + + +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 contrib/games") + print(f"Found {len(nfg_files)} .nfg files in contrib/games") + + 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 contrib/games 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) + + # Update the RST documentation with the new full catalog + # This includes games from coded_games.py as well as catalog.yml + update_api_rst() + + print(f"Added {new_entries_counter} new entries to the catalog") + print(f"Output written to: {_CATALOG_YAML}") + print("Done.") diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 054313725..fbae02f71 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -1,112 +1,112 @@ -# import pytest - -# import pygambit as gbt - - -# class ExampleGame(gbt.catalog.CatalogGame): - -# @staticmethod -# def _game(some_param: bool = False): -# 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 = "Test game description." -# return g - - -# class ExampleGameWithDocstring(ExampleGame): -# """ -# Alternative test game description. -# """ - - -# 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_custom_game_subclass_extracts_metadata(self): -# """Custom CatalogGame subclasses should extract metadata from _game().""" -# assert ExampleGame().description == "Test game description." - -# def test_can_get_game_description_from_docstring(self): -# """CatalogGame should get description from docstring over game description.""" -# assert ExampleGameWithDocstring().description == "Alternative test game description." - -# 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(self): -# """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" -# assert ( -# gbt.catalog.PrisonersDilemma().description -# == "A simple implementation of a two person Prisoner's Dilemma 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_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 CatalogGameFromContrib.""" -# result = gbt.catalog.games() -# assert "CatalogGame" not in result -# assert "CatalogGameFromContrib" 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 +import pytest + +import pygambit as gbt + + +class ExampleGame(gbt.catalog.CatalogGame): + + @staticmethod + def _game(some_param: bool = False): + 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 = "Test game description." + return g + + +class ExampleGameWithDocstring(ExampleGame): + """ + Alternative test game description. + """ + + +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_custom_game_subclass_extracts_metadata(self): + """Custom CatalogGame subclasses should extract metadata from _game().""" + assert ExampleGame().description == "Test game description." + + def test_can_get_game_description_from_docstring(self): + """CatalogGame should get description from docstring over game description.""" + assert ExampleGameWithDocstring().description == "Alternative test game description." + + 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(self): + """Custom CatalogGame subclasses reading from catalog.yml should return Game instances.""" + assert ( + gbt.catalog.PrisonersDilemma().description + == "A simple implementation of a two person Prisoner's Dilemma 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_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 CatalogGameFromContrib.""" + result = gbt.catalog.games() + assert "CatalogGame" not in result + assert "CatalogGameFromContrib" 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_tutorials.py b/tests/test_tutorials.py index 9f3ea91dd..e15171ebe 100644 --- a/tests/test_tutorials.py +++ b/tests/test_tutorials.py @@ -1,76 +1,76 @@ -# import contextlib -# import os -# import sys -# from pathlib import Path - -# import nbformat -# import pytest - -# # Ensure Jupyter uses the new platformdirs paths to avoid DeprecationWarning -# # This will become the default in `jupyter_core` v6 -# os.environ.setdefault("JUPYTER_PLATFORM_DIRS", "1") - -# from nbclient import NotebookClient # noqa: E402 -# from nbclient.exceptions import CellExecutionError # noqa: E402 - - -# def _find_tutorial_notebooks(): -# """Return a sorted list of notebook Paths under doc/tutorials. - -# Skips the entire module if the tutorials directory does not exist. -# """ -# root = Path(__file__).resolve().parents[1] / "doc" / "tutorials" -# if not root.exists(): -# pytest.skip(f"Tutorials folder not found: {root}") - -# # Collect all notebooks under doc/tutorials (including any subfolders). -# # Exclude Jupyter checkpoint files -# notebooks = sorted( -# p for p in root.rglob("*.ipynb") -# if ".ipynb_checkpoints" not in p.parts -# ) - -# if not notebooks: -# pytest.skip(f"No tutorial notebooks found in: {root}") -# return notebooks - - -# # Discover notebooks at import time so pytest can parametrize them. -# _NOTEBOOKS = _find_tutorial_notebooks() - - -# @pytest.mark.tutorials -# @pytest.mark.parametrize("nb_path", _NOTEBOOKS, ids=[p.name for p in _NOTEBOOKS]) -# def test_execute_notebook(nb_path): -# """Execute a single Jupyter notebook and fail if any cell errors occur. - -# This uses nbclient.NotebookClient to run the notebook in its parent directory -# so relative paths within the notebook resolve correctly. -# """ - -# # Skip OpenSpiel notebook on Windows -# # (OpenSpiel is not available on Windows without manual install) -# if sys.platform == "win32" and "openspiel" in nb_path.name.lower(): -# pytest.skip("OpenSpiel notebook requires OpenSpiel, which is not available on Windows") - -# nb = nbformat.read(str(nb_path), as_version=4) - -# # Prefer the notebook's kernelspec if provided, otherwise let nbclient pick the default. -# kernel_name = nb.metadata.get("kernelspec", {}).get("name") - -# client = NotebookClient( -# nb, -# timeout=600, -# kernel_name=kernel_name, -# resources={"metadata": {"path": str(nb_path.parent)}}, -# ) - -# try: -# client.execute() -# except CellExecutionError as exc: -# # Re-raise with more context so pytest shows which notebook failed. -# raise AssertionError(f"Error while executing notebook {nb_path}: {exc}") from exc -# finally: -# # Ensure kernel is shut down. -# with contextlib.suppress(Exception): -# client.shutdown_kernel() +import contextlib +import os +import sys +from pathlib import Path + +import nbformat +import pytest + +# Ensure Jupyter uses the new platformdirs paths to avoid DeprecationWarning +# This will become the default in `jupyter_core` v6 +os.environ.setdefault("JUPYTER_PLATFORM_DIRS", "1") + +from nbclient import NotebookClient # noqa: E402 +from nbclient.exceptions import CellExecutionError # noqa: E402 + + +def _find_tutorial_notebooks(): + """Return a sorted list of notebook Paths under doc/tutorials. + + Skips the entire module if the tutorials directory does not exist. + """ + root = Path(__file__).resolve().parents[1] / "doc" / "tutorials" + if not root.exists(): + pytest.skip(f"Tutorials folder not found: {root}") + + # Collect all notebooks under doc/tutorials (including any subfolders). + # Exclude Jupyter checkpoint files + notebooks = sorted( + p for p in root.rglob("*.ipynb") + if ".ipynb_checkpoints" not in p.parts + ) + + if not notebooks: + pytest.skip(f"No tutorial notebooks found in: {root}") + return notebooks + + +# Discover notebooks at import time so pytest can parametrize them. +_NOTEBOOKS = _find_tutorial_notebooks() + + +@pytest.mark.tutorials +@pytest.mark.parametrize("nb_path", _NOTEBOOKS, ids=[p.name for p in _NOTEBOOKS]) +def test_execute_notebook(nb_path): + """Execute a single Jupyter notebook and fail if any cell errors occur. + + This uses nbclient.NotebookClient to run the notebook in its parent directory + so relative paths within the notebook resolve correctly. + """ + + # Skip OpenSpiel notebook on Windows + # (OpenSpiel is not available on Windows without manual install) + if sys.platform == "win32" and "openspiel" in nb_path.name.lower(): + pytest.skip("OpenSpiel notebook requires OpenSpiel, which is not available on Windows") + + nb = nbformat.read(str(nb_path), as_version=4) + + # Prefer the notebook's kernelspec if provided, otherwise let nbclient pick the default. + kernel_name = nb.metadata.get("kernelspec", {}).get("name") + + client = NotebookClient( + nb, + timeout=600, + kernel_name=kernel_name, + resources={"metadata": {"path": str(nb_path.parent)}}, + ) + + try: + client.execute() + except CellExecutionError as exc: + # Re-raise with more context so pytest shows which notebook failed. + raise AssertionError(f"Error while executing notebook {nb_path}: {exc}") from exc + finally: + # Ensure kernel is shut down. + with contextlib.suppress(Exception): + client.shutdown_kernel() From 91d39d9997098a8e12cee752e6e65de4de1da2ef Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 16:23:22 +0000 Subject: [PATCH 125/170] refactor yaml loading to use importlib.resources --- src/pygambit/catalog.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 4d541bf7c..439ee3488 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -1,5 +1,6 @@ import inspect import sys +from importlib.resources import files from pathlib import Path import yaml @@ -248,12 +249,8 @@ def get_all_subclasses(cls): return sorted(get_all_subclasses(CatalogGame)) -_CATALOG_YAML = Path(__file__).parent / "catalog.yml" - - -def _load_catalog_from_yaml(path: Path) -> dict[str, dict]: - if not path.exists(): - raise FileNotFoundError(f"Catalog YAML not found: {path}") +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 {} @@ -318,7 +315,7 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: # Generate classes at import time -_catalog_data = _load_catalog_from_yaml(_CATALOG_YAML) +_catalog_data = _load_catalog_from_yaml() _generate_contrib_game_classes(_catalog_data) # _load_coded_games() From 33c8548ba96812bff8532843ec33c0ceccf077ea Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 16:38:17 +0000 Subject: [PATCH 126/170] add catalog.yml to package data --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3c372ec34..13ee42f66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,5 +85,11 @@ markers = [ "slow: all time-consuming tests", ] +[tool.setuptools] +include-package-data = true + +[tool.setuptools.package-data] +pygambit = ["catalog.yml"] + [tool.setuptools.dynamic] version = {file = "build_support/GAMBIT_VERSION"} From 6f05b8ec68bd4965ff1f1e2c567568258cb7683b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 16:53:57 +0000 Subject: [PATCH 127/170] Include contributed games in package data --- pyproject.toml | 5 ++++- src/pygambit/catalog.py | 32 +++++++++++++++++--------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 13ee42f66..30dcc9074 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,10 @@ markers = [ include-package-data = true [tool.setuptools.package-data] -pygambit = ["catalog.yml"] +pygambit = [ + "catalog.yml", + "contrib/games/*", +] [tool.setuptools.dynamic] version = {file = "build_support/GAMBIT_VERSION"} diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 439ee3488..1c79d474c 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -1,13 +1,12 @@ import inspect import sys -from importlib.resources import files -from pathlib import Path +from importlib.resources import as_file, files import yaml import pygambit as gbt -_GAMEFILES_DIR = Path(__file__).parent.parent.parent / "contrib/games" +_GAMEFILES_DIR = files(__package__) / "contrib" / "games" class CatalogGame: @@ -64,7 +63,7 @@ class CatalogGameFromContrib(CatalogGame): """Filename of the game file in contrib/games directory.""" def __new__(cls) -> gbt.Game: - if cls.game is None: + if getattr(cls, "game", None) is None: cls.game = cls._load_game() return cls.game @@ -72,17 +71,20 @@ def __new__(cls) -> gbt.Game: 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' class attribute") - - game_type = cls.game_file.split(".")[-1] - file_path = _GAMEFILES_DIR / cls.game_file - - if game_type == "nfg": - return gbt.read_nfg(str(file_path)) - elif game_type == "efg": - return gbt.read_efg(str(file_path)) - else: - raise ValueError(f"gbt.Game file extension must be 'nfg' or 'efg', got '{game_type}'") + 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.""" From 55d6395e61f7a5d0296b78dcc4f76a2cefbb9c04 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 16:59:09 +0000 Subject: [PATCH 128/170] move contrib/games into pygambit --- {contrib => src/pygambit/contrib}/games/2s2x2x2.efg | 0 {contrib => src/pygambit/contrib}/games/2smp.efg | 0 {contrib => src/pygambit/contrib}/games/2x2.agg | 0 {contrib => src/pygambit/contrib}/games/2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2a.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2const.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2-nau.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2.efg | 0 {contrib => src/pygambit/contrib}/games/2x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/3x3x3.nfg | 0 {contrib => src/pygambit/contrib}/games/4cards.efg | 0 {contrib => src/pygambit/contrib}/games/5x4x3.nfg | 0 {contrib => src/pygambit/contrib}/games/8x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/8x8.nfg | 0 {contrib => src/pygambit/contrib}/games/BSS_S_085.Weighted.agg | 0 .../pygambit/contrib}/games/Bayesian-Coffee-3-2-2-3.bagg | 0 {contrib => src/pygambit/contrib}/games/GenRPS5.agg | 0 {contrib => src/pygambit/contrib}/games/artist1.efg | 0 {contrib => src/pygambit/contrib}/games/artist2.efg | 0 {contrib => src/pygambit/contrib}/games/badgame1.efg | 0 {contrib => src/pygambit/contrib}/games/badgame2.efg | 0 {contrib => src/pygambit/contrib}/games/bagwell.efg | 0 {contrib => src/pygambit/contrib}/games/bayes1a.efg | 0 {contrib => src/pygambit/contrib}/games/bayes2a.efg | 0 {contrib => src/pygambit/contrib}/games/bcp2.efg | 0 {contrib => src/pygambit/contrib}/games/bcp3.efg | 0 {contrib => src/pygambit/contrib}/games/bcp4.efg | 0 {contrib => src/pygambit/contrib}/games/bhg1.efg | 0 {contrib => src/pygambit/contrib}/games/bhg2.efg | 0 {contrib => src/pygambit/contrib}/games/bhg3.efg | 0 {contrib => src/pygambit/contrib}/games/bhg4.efg | 0 {contrib => src/pygambit/contrib}/games/bhg5.efg | 0 {contrib => src/pygambit/contrib}/games/caro2.efg | 0 {contrib => src/pygambit/contrib}/games/cent2.efg | 0 {contrib => src/pygambit/contrib}/games/cent2.nfg | 0 {contrib => src/pygambit/contrib}/games/cent3.efg | 0 {contrib => src/pygambit/contrib}/games/cent4.efg | 0 {contrib => src/pygambit/contrib}/games/cent6.efg | 0 {contrib => src/pygambit/contrib}/games/centcs10.efg | 0 {contrib => src/pygambit/contrib}/games/centcs6.efg | 0 {contrib => src/pygambit/contrib}/games/condjury.efg | 0 {contrib => src/pygambit/contrib}/games/coord2.efg | 0 {contrib => src/pygambit/contrib}/games/coord2.nfg | 0 {contrib => src/pygambit/contrib}/games/coord2ts.efg | 0 {contrib => src/pygambit/contrib}/games/coord3.efg | 0 {contrib => src/pygambit/contrib}/games/coord3.nfg | 0 {contrib => src/pygambit/contrib}/games/coord333.nfg | 0 {contrib => src/pygambit/contrib}/games/coord4.efg | 0 {contrib => src/pygambit/contrib}/games/coord4.nfg | 0 {contrib => src/pygambit/contrib}/games/cross.efg | 0 {contrib => src/pygambit/contrib}/games/cs.efg | 0 {contrib => src/pygambit/contrib}/games/csg1.nfg | 0 {contrib => src/pygambit/contrib}/games/csg2.nfg | 0 {contrib => src/pygambit/contrib}/games/csg3.nfg | 0 {contrib => src/pygambit/contrib}/games/csg4.nfg | 0 {contrib => src/pygambit/contrib}/games/deg1.nfg | 0 {contrib => src/pygambit/contrib}/games/deg2.nfg | 0 {contrib => src/pygambit/contrib}/games/e01.efg | 0 {contrib => src/pygambit/contrib}/games/e01.nfg | 0 {contrib => src/pygambit/contrib}/games/e02.efg | 0 {contrib => src/pygambit/contrib}/games/e02.nfg | 0 {contrib => src/pygambit/contrib}/games/e03.efg | 0 {contrib => src/pygambit/contrib}/games/e04.efg | 0 {contrib => src/pygambit/contrib}/games/e04.nfg | 0 {contrib => src/pygambit/contrib}/games/e05.efg | 0 {contrib => src/pygambit/contrib}/games/e06.efg | 0 {contrib => src/pygambit/contrib}/games/e07.efg | 0 {contrib => src/pygambit/contrib}/games/e07.nfg | 0 {contrib => src/pygambit/contrib}/games/e08.efg | 0 {contrib => src/pygambit/contrib}/games/e09.efg | 0 {contrib => src/pygambit/contrib}/games/e10.efg | 0 {contrib => src/pygambit/contrib}/games/e10a.efg | 0 {contrib => src/pygambit/contrib}/games/e13.efg | 0 {contrib => src/pygambit/contrib}/games/e16.efg | 0 {contrib => src/pygambit/contrib}/games/e17.efg | 0 {contrib => src/pygambit/contrib}/games/e18.efg | 0 {contrib => src/pygambit/contrib}/games/g1.efg | 0 {contrib => src/pygambit/contrib}/games/g1.nfg | 0 {contrib => src/pygambit/contrib}/games/g2.efg | 0 {contrib => src/pygambit/contrib}/games/g2.nfg | 0 {contrib => src/pygambit/contrib}/games/g3.efg | 0 {contrib => src/pygambit/contrib}/games/g3.nfg | 0 {contrib => src/pygambit/contrib}/games/holdout.efg | 0 {contrib => src/pygambit/contrib}/games/holdout7.efg | 0 {contrib => src/pygambit/contrib}/games/hs1.efg | 0 {contrib => src/pygambit/contrib}/games/jury_mr.efg | 0 {contrib => src/pygambit/contrib}/games/jury_un.efg | 0 {contrib => src/pygambit/contrib}/games/km1.efg | 0 {contrib => src/pygambit/contrib}/games/km2.efg | 0 {contrib => src/pygambit/contrib}/games/km3.efg | 0 {contrib => src/pygambit/contrib}/games/km6.efg | 0 {contrib => src/pygambit/contrib}/games/loopback.nfg | 0 {contrib => src/pygambit/contrib}/games/mixdom.nfg | 0 {contrib => src/pygambit/contrib}/games/mixdom2.nfg | 0 {contrib => src/pygambit/contrib}/games/montyhal.efg | 0 {contrib => src/pygambit/contrib}/games/my_2-1.efg | 0 {contrib => src/pygambit/contrib}/games/my_2-4.efg | 0 {contrib => src/pygambit/contrib}/games/my_2-8.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3a.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3b.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3c.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3d.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3e.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-4.efg | 0 {contrib => src/pygambit/contrib}/games/myerson.efg | 0 {contrib => src/pygambit/contrib}/games/myerson_fig_4_2.efg | 0 {contrib => src/pygambit/contrib}/games/nim.efg | 0 {contrib => src/pygambit/contrib}/games/nim7.efg | 0 {contrib => src/pygambit/contrib}/games/oneill.nfg | 0 {contrib => src/pygambit/contrib}/games/palf.efg | 0 {contrib => src/pygambit/contrib}/games/palf2.efg | 0 {contrib => src/pygambit/contrib}/games/palf3.efg | 0 {contrib => src/pygambit/contrib}/games/pbride.efg | 0 {contrib => src/pygambit/contrib}/games/pd.nfg | 0 {contrib => src/pygambit/contrib}/games/perfect1.nfg | 0 {contrib => src/pygambit/contrib}/games/perfect2.nfg | 0 {contrib => src/pygambit/contrib}/games/perfect3.nfg | 0 {contrib => src/pygambit/contrib}/games/poker.efg | 0 {contrib => src/pygambit/contrib}/games/poker.nfg | 0 {contrib => src/pygambit/contrib}/games/poker2.efg | 0 {contrib => src/pygambit/contrib}/games/pvw.efg | 0 {contrib => src/pygambit/contrib}/games/pvw2.efg | 0 {contrib => src/pygambit/contrib}/games/sh3.efg | 0 {contrib => src/pygambit/contrib}/games/sh3.nfg | 0 {contrib => src/pygambit/contrib}/games/spence.efg | 0 {contrib => src/pygambit/contrib}/games/stengel.nfg | 0 {contrib => src/pygambit/contrib}/games/sww1.efg | 0 {contrib => src/pygambit/contrib}/games/sww1.nfg | 0 {contrib => src/pygambit/contrib}/games/sww2.efg | 0 {contrib => src/pygambit/contrib}/games/sww3.efg | 0 {contrib => src/pygambit/contrib}/games/tim.efg | 0 {contrib => src/pygambit/contrib}/games/todd1.nfg | 0 {contrib => src/pygambit/contrib}/games/todd2.nfg | 0 {contrib => src/pygambit/contrib}/games/todd3.nfg | 0 {contrib => src/pygambit/contrib}/games/ttt.efg | 0 {contrib => src/pygambit/contrib}/games/vd.efg | 0 {contrib => src/pygambit/contrib}/games/vd.nfg | 0 {contrib => src/pygambit/contrib}/games/w_ex1.efg | 0 {contrib => src/pygambit/contrib}/games/w_ex2.efg | 0 {contrib => src/pygambit/contrib}/games/wilson1.efg | 0 {contrib => src/pygambit/contrib}/games/wink3.nfg | 0 {contrib => src/pygambit/contrib}/games/winkels.nfg | 0 {contrib => src/pygambit/contrib}/games/work1.efg | 0 {contrib => src/pygambit/contrib}/games/work2.efg | 0 {contrib => src/pygambit/contrib}/games/work3.efg | 0 {contrib => src/pygambit/contrib}/games/yamamoto.nfg | 0 {contrib => src/pygambit/contrib}/games/zero.nfg | 0 149 files changed, 0 insertions(+), 0 deletions(-) rename {contrib => src/pygambit/contrib}/games/2s2x2x2.efg (100%) rename {contrib => src/pygambit/contrib}/games/2smp.efg (100%) rename {contrib => src/pygambit/contrib}/games/2x2.agg (100%) rename {contrib => src/pygambit/contrib}/games/2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2a.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2const.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2-nau.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2.efg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/3x3x3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/4cards.efg (100%) rename {contrib => src/pygambit/contrib}/games/5x4x3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/8x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/8x8.nfg (100%) rename {contrib => src/pygambit/contrib}/games/BSS_S_085.Weighted.agg (100%) rename {contrib => src/pygambit/contrib}/games/Bayesian-Coffee-3-2-2-3.bagg (100%) rename {contrib => src/pygambit/contrib}/games/GenRPS5.agg (100%) rename {contrib => src/pygambit/contrib}/games/artist1.efg (100%) rename {contrib => src/pygambit/contrib}/games/artist2.efg (100%) rename {contrib => src/pygambit/contrib}/games/badgame1.efg (100%) rename {contrib => src/pygambit/contrib}/games/badgame2.efg (100%) rename {contrib => src/pygambit/contrib}/games/bagwell.efg (100%) rename {contrib => src/pygambit/contrib}/games/bayes1a.efg (100%) rename {contrib => src/pygambit/contrib}/games/bayes2a.efg (100%) rename {contrib => src/pygambit/contrib}/games/bcp2.efg (100%) rename {contrib => src/pygambit/contrib}/games/bcp3.efg (100%) rename {contrib => src/pygambit/contrib}/games/bcp4.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg1.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg2.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg3.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg4.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg5.efg (100%) rename {contrib => src/pygambit/contrib}/games/caro2.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent2.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/cent3.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent4.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent6.efg (100%) rename {contrib => src/pygambit/contrib}/games/centcs10.efg (100%) rename {contrib => src/pygambit/contrib}/games/centcs6.efg (100%) rename {contrib => src/pygambit/contrib}/games/condjury.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord2.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/coord2ts.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord3.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/coord333.nfg (100%) rename {contrib => src/pygambit/contrib}/games/coord4.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord4.nfg (100%) rename {contrib => src/pygambit/contrib}/games/cross.efg (100%) rename {contrib => src/pygambit/contrib}/games/cs.efg (100%) rename {contrib => src/pygambit/contrib}/games/csg1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/csg2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/csg3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/csg4.nfg (100%) rename {contrib => src/pygambit/contrib}/games/deg1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/deg2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e01.efg (100%) rename {contrib => src/pygambit/contrib}/games/e01.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e02.efg (100%) rename {contrib => src/pygambit/contrib}/games/e02.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e03.efg (100%) rename {contrib => src/pygambit/contrib}/games/e04.efg (100%) rename {contrib => src/pygambit/contrib}/games/e04.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e05.efg (100%) rename {contrib => src/pygambit/contrib}/games/e06.efg (100%) rename {contrib => src/pygambit/contrib}/games/e07.efg (100%) rename {contrib => src/pygambit/contrib}/games/e07.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e08.efg (100%) rename {contrib => src/pygambit/contrib}/games/e09.efg (100%) rename {contrib => src/pygambit/contrib}/games/e10.efg (100%) rename {contrib => src/pygambit/contrib}/games/e10a.efg (100%) rename {contrib => src/pygambit/contrib}/games/e13.efg (100%) rename {contrib => src/pygambit/contrib}/games/e16.efg (100%) rename {contrib => src/pygambit/contrib}/games/e17.efg (100%) rename {contrib => src/pygambit/contrib}/games/e18.efg (100%) rename {contrib => src/pygambit/contrib}/games/g1.efg (100%) rename {contrib => src/pygambit/contrib}/games/g1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/g2.efg (100%) rename {contrib => src/pygambit/contrib}/games/g2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/g3.efg (100%) rename {contrib => src/pygambit/contrib}/games/g3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/holdout.efg (100%) rename {contrib => src/pygambit/contrib}/games/holdout7.efg (100%) rename {contrib => src/pygambit/contrib}/games/hs1.efg (100%) rename {contrib => src/pygambit/contrib}/games/jury_mr.efg (100%) rename {contrib => src/pygambit/contrib}/games/jury_un.efg (100%) rename {contrib => src/pygambit/contrib}/games/km1.efg (100%) rename {contrib => src/pygambit/contrib}/games/km2.efg (100%) rename {contrib => src/pygambit/contrib}/games/km3.efg (100%) rename {contrib => src/pygambit/contrib}/games/km6.efg (100%) rename {contrib => src/pygambit/contrib}/games/loopback.nfg (100%) rename {contrib => src/pygambit/contrib}/games/mixdom.nfg (100%) rename {contrib => src/pygambit/contrib}/games/mixdom2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/montyhal.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_2-1.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_2-4.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_2-8.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3a.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3b.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3c.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3d.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3e.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-4.efg (100%) rename {contrib => src/pygambit/contrib}/games/myerson.efg (100%) rename {contrib => src/pygambit/contrib}/games/myerson_fig_4_2.efg (100%) rename {contrib => src/pygambit/contrib}/games/nim.efg (100%) rename {contrib => src/pygambit/contrib}/games/nim7.efg (100%) rename {contrib => src/pygambit/contrib}/games/oneill.nfg (100%) rename {contrib => src/pygambit/contrib}/games/palf.efg (100%) rename {contrib => src/pygambit/contrib}/games/palf2.efg (100%) rename {contrib => src/pygambit/contrib}/games/palf3.efg (100%) rename {contrib => src/pygambit/contrib}/games/pbride.efg (100%) rename {contrib => src/pygambit/contrib}/games/pd.nfg (100%) rename {contrib => src/pygambit/contrib}/games/perfect1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/perfect2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/perfect3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/poker.efg (100%) rename {contrib => src/pygambit/contrib}/games/poker.nfg (100%) rename {contrib => src/pygambit/contrib}/games/poker2.efg (100%) rename {contrib => src/pygambit/contrib}/games/pvw.efg (100%) rename {contrib => src/pygambit/contrib}/games/pvw2.efg (100%) rename {contrib => src/pygambit/contrib}/games/sh3.efg (100%) rename {contrib => src/pygambit/contrib}/games/sh3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/spence.efg (100%) rename {contrib => src/pygambit/contrib}/games/stengel.nfg (100%) rename {contrib => src/pygambit/contrib}/games/sww1.efg (100%) rename {contrib => src/pygambit/contrib}/games/sww1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/sww2.efg (100%) rename {contrib => src/pygambit/contrib}/games/sww3.efg (100%) rename {contrib => src/pygambit/contrib}/games/tim.efg (100%) rename {contrib => src/pygambit/contrib}/games/todd1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/todd2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/todd3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/ttt.efg (100%) rename {contrib => src/pygambit/contrib}/games/vd.efg (100%) rename {contrib => src/pygambit/contrib}/games/vd.nfg (100%) rename {contrib => src/pygambit/contrib}/games/w_ex1.efg (100%) rename {contrib => src/pygambit/contrib}/games/w_ex2.efg (100%) rename {contrib => src/pygambit/contrib}/games/wilson1.efg (100%) rename {contrib => src/pygambit/contrib}/games/wink3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/winkels.nfg (100%) rename {contrib => src/pygambit/contrib}/games/work1.efg (100%) rename {contrib => src/pygambit/contrib}/games/work2.efg (100%) rename {contrib => src/pygambit/contrib}/games/work3.efg (100%) rename {contrib => src/pygambit/contrib}/games/yamamoto.nfg (100%) rename {contrib => src/pygambit/contrib}/games/zero.nfg (100%) diff --git a/contrib/games/2s2x2x2.efg b/src/pygambit/contrib/games/2s2x2x2.efg similarity index 100% rename from contrib/games/2s2x2x2.efg rename to src/pygambit/contrib/games/2s2x2x2.efg diff --git a/contrib/games/2smp.efg b/src/pygambit/contrib/games/2smp.efg similarity index 100% rename from contrib/games/2smp.efg rename to src/pygambit/contrib/games/2smp.efg diff --git a/contrib/games/2x2.agg b/src/pygambit/contrib/games/2x2.agg similarity index 100% rename from contrib/games/2x2.agg rename to src/pygambit/contrib/games/2x2.agg diff --git a/contrib/games/2x2.nfg b/src/pygambit/contrib/games/2x2.nfg similarity index 100% rename from contrib/games/2x2.nfg rename to src/pygambit/contrib/games/2x2.nfg diff --git a/contrib/games/2x2a.nfg b/src/pygambit/contrib/games/2x2a.nfg similarity index 100% rename from contrib/games/2x2a.nfg rename to src/pygambit/contrib/games/2x2a.nfg diff --git a/contrib/games/2x2const.nfg b/src/pygambit/contrib/games/2x2const.nfg similarity index 100% rename from contrib/games/2x2const.nfg rename to src/pygambit/contrib/games/2x2const.nfg diff --git a/contrib/games/2x2x2-nau.nfg b/src/pygambit/contrib/games/2x2x2-nau.nfg similarity index 100% rename from contrib/games/2x2x2-nau.nfg rename to src/pygambit/contrib/games/2x2x2-nau.nfg diff --git a/contrib/games/2x2x2.efg b/src/pygambit/contrib/games/2x2x2.efg similarity index 100% rename from contrib/games/2x2x2.efg rename to src/pygambit/contrib/games/2x2x2.efg diff --git a/contrib/games/2x2x2.nfg b/src/pygambit/contrib/games/2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2.nfg rename to src/pygambit/contrib/games/2x2x2.nfg diff --git a/contrib/games/2x2x2x2.nfg b/src/pygambit/contrib/games/2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2.nfg rename to src/pygambit/contrib/games/2x2x2x2.nfg diff --git a/contrib/games/2x2x2x2x2.nfg b/src/pygambit/contrib/games/2x2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2x2.nfg rename to src/pygambit/contrib/games/2x2x2x2x2.nfg diff --git a/contrib/games/3x3x3.nfg b/src/pygambit/contrib/games/3x3x3.nfg similarity index 100% rename from contrib/games/3x3x3.nfg rename to src/pygambit/contrib/games/3x3x3.nfg diff --git a/contrib/games/4cards.efg b/src/pygambit/contrib/games/4cards.efg similarity index 100% rename from contrib/games/4cards.efg rename to src/pygambit/contrib/games/4cards.efg diff --git a/contrib/games/5x4x3.nfg b/src/pygambit/contrib/games/5x4x3.nfg similarity index 100% rename from contrib/games/5x4x3.nfg rename to src/pygambit/contrib/games/5x4x3.nfg diff --git a/contrib/games/8x2x2.nfg b/src/pygambit/contrib/games/8x2x2.nfg similarity index 100% rename from contrib/games/8x2x2.nfg rename to src/pygambit/contrib/games/8x2x2.nfg diff --git a/contrib/games/8x8.nfg b/src/pygambit/contrib/games/8x8.nfg similarity index 100% rename from contrib/games/8x8.nfg rename to src/pygambit/contrib/games/8x8.nfg diff --git a/contrib/games/BSS_S_085.Weighted.agg b/src/pygambit/contrib/games/BSS_S_085.Weighted.agg similarity index 100% rename from contrib/games/BSS_S_085.Weighted.agg rename to src/pygambit/contrib/games/BSS_S_085.Weighted.agg diff --git a/contrib/games/Bayesian-Coffee-3-2-2-3.bagg b/src/pygambit/contrib/games/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/contrib/games/Bayesian-Coffee-3-2-2-3.bagg diff --git a/contrib/games/GenRPS5.agg b/src/pygambit/contrib/games/GenRPS5.agg similarity index 100% rename from contrib/games/GenRPS5.agg rename to src/pygambit/contrib/games/GenRPS5.agg diff --git a/contrib/games/artist1.efg b/src/pygambit/contrib/games/artist1.efg similarity index 100% rename from contrib/games/artist1.efg rename to src/pygambit/contrib/games/artist1.efg diff --git a/contrib/games/artist2.efg b/src/pygambit/contrib/games/artist2.efg similarity index 100% rename from contrib/games/artist2.efg rename to src/pygambit/contrib/games/artist2.efg diff --git a/contrib/games/badgame1.efg b/src/pygambit/contrib/games/badgame1.efg similarity index 100% rename from contrib/games/badgame1.efg rename to src/pygambit/contrib/games/badgame1.efg diff --git a/contrib/games/badgame2.efg b/src/pygambit/contrib/games/badgame2.efg similarity index 100% rename from contrib/games/badgame2.efg rename to src/pygambit/contrib/games/badgame2.efg diff --git a/contrib/games/bagwell.efg b/src/pygambit/contrib/games/bagwell.efg similarity index 100% rename from contrib/games/bagwell.efg rename to src/pygambit/contrib/games/bagwell.efg diff --git a/contrib/games/bayes1a.efg b/src/pygambit/contrib/games/bayes1a.efg similarity index 100% rename from contrib/games/bayes1a.efg rename to src/pygambit/contrib/games/bayes1a.efg diff --git a/contrib/games/bayes2a.efg b/src/pygambit/contrib/games/bayes2a.efg similarity index 100% rename from contrib/games/bayes2a.efg rename to src/pygambit/contrib/games/bayes2a.efg diff --git a/contrib/games/bcp2.efg b/src/pygambit/contrib/games/bcp2.efg similarity index 100% rename from contrib/games/bcp2.efg rename to src/pygambit/contrib/games/bcp2.efg diff --git a/contrib/games/bcp3.efg b/src/pygambit/contrib/games/bcp3.efg similarity index 100% rename from contrib/games/bcp3.efg rename to src/pygambit/contrib/games/bcp3.efg diff --git a/contrib/games/bcp4.efg b/src/pygambit/contrib/games/bcp4.efg similarity index 100% rename from contrib/games/bcp4.efg rename to src/pygambit/contrib/games/bcp4.efg diff --git a/contrib/games/bhg1.efg b/src/pygambit/contrib/games/bhg1.efg similarity index 100% rename from contrib/games/bhg1.efg rename to src/pygambit/contrib/games/bhg1.efg diff --git a/contrib/games/bhg2.efg b/src/pygambit/contrib/games/bhg2.efg similarity index 100% rename from contrib/games/bhg2.efg rename to src/pygambit/contrib/games/bhg2.efg diff --git a/contrib/games/bhg3.efg b/src/pygambit/contrib/games/bhg3.efg similarity index 100% rename from contrib/games/bhg3.efg rename to src/pygambit/contrib/games/bhg3.efg diff --git a/contrib/games/bhg4.efg b/src/pygambit/contrib/games/bhg4.efg similarity index 100% rename from contrib/games/bhg4.efg rename to src/pygambit/contrib/games/bhg4.efg diff --git a/contrib/games/bhg5.efg b/src/pygambit/contrib/games/bhg5.efg similarity index 100% rename from contrib/games/bhg5.efg rename to src/pygambit/contrib/games/bhg5.efg diff --git a/contrib/games/caro2.efg b/src/pygambit/contrib/games/caro2.efg similarity index 100% rename from contrib/games/caro2.efg rename to src/pygambit/contrib/games/caro2.efg diff --git a/contrib/games/cent2.efg b/src/pygambit/contrib/games/cent2.efg similarity index 100% rename from contrib/games/cent2.efg rename to src/pygambit/contrib/games/cent2.efg diff --git a/contrib/games/cent2.nfg b/src/pygambit/contrib/games/cent2.nfg similarity index 100% rename from contrib/games/cent2.nfg rename to src/pygambit/contrib/games/cent2.nfg diff --git a/contrib/games/cent3.efg b/src/pygambit/contrib/games/cent3.efg similarity index 100% rename from contrib/games/cent3.efg rename to src/pygambit/contrib/games/cent3.efg diff --git a/contrib/games/cent4.efg b/src/pygambit/contrib/games/cent4.efg similarity index 100% rename from contrib/games/cent4.efg rename to src/pygambit/contrib/games/cent4.efg diff --git a/contrib/games/cent6.efg b/src/pygambit/contrib/games/cent6.efg similarity index 100% rename from contrib/games/cent6.efg rename to src/pygambit/contrib/games/cent6.efg diff --git a/contrib/games/centcs10.efg b/src/pygambit/contrib/games/centcs10.efg similarity index 100% rename from contrib/games/centcs10.efg rename to src/pygambit/contrib/games/centcs10.efg diff --git a/contrib/games/centcs6.efg b/src/pygambit/contrib/games/centcs6.efg similarity index 100% rename from contrib/games/centcs6.efg rename to src/pygambit/contrib/games/centcs6.efg diff --git a/contrib/games/condjury.efg b/src/pygambit/contrib/games/condjury.efg similarity index 100% rename from contrib/games/condjury.efg rename to src/pygambit/contrib/games/condjury.efg diff --git a/contrib/games/coord2.efg b/src/pygambit/contrib/games/coord2.efg similarity index 100% rename from contrib/games/coord2.efg rename to src/pygambit/contrib/games/coord2.efg diff --git a/contrib/games/coord2.nfg b/src/pygambit/contrib/games/coord2.nfg similarity index 100% rename from contrib/games/coord2.nfg rename to src/pygambit/contrib/games/coord2.nfg diff --git a/contrib/games/coord2ts.efg b/src/pygambit/contrib/games/coord2ts.efg similarity index 100% rename from contrib/games/coord2ts.efg rename to src/pygambit/contrib/games/coord2ts.efg diff --git a/contrib/games/coord3.efg b/src/pygambit/contrib/games/coord3.efg similarity index 100% rename from contrib/games/coord3.efg rename to src/pygambit/contrib/games/coord3.efg diff --git a/contrib/games/coord3.nfg b/src/pygambit/contrib/games/coord3.nfg similarity index 100% rename from contrib/games/coord3.nfg rename to src/pygambit/contrib/games/coord3.nfg diff --git a/contrib/games/coord333.nfg b/src/pygambit/contrib/games/coord333.nfg similarity index 100% rename from contrib/games/coord333.nfg rename to src/pygambit/contrib/games/coord333.nfg diff --git a/contrib/games/coord4.efg b/src/pygambit/contrib/games/coord4.efg similarity index 100% rename from contrib/games/coord4.efg rename to src/pygambit/contrib/games/coord4.efg diff --git a/contrib/games/coord4.nfg b/src/pygambit/contrib/games/coord4.nfg similarity index 100% rename from contrib/games/coord4.nfg rename to src/pygambit/contrib/games/coord4.nfg diff --git a/contrib/games/cross.efg b/src/pygambit/contrib/games/cross.efg similarity index 100% rename from contrib/games/cross.efg rename to src/pygambit/contrib/games/cross.efg diff --git a/contrib/games/cs.efg b/src/pygambit/contrib/games/cs.efg similarity index 100% rename from contrib/games/cs.efg rename to src/pygambit/contrib/games/cs.efg diff --git a/contrib/games/csg1.nfg b/src/pygambit/contrib/games/csg1.nfg similarity index 100% rename from contrib/games/csg1.nfg rename to src/pygambit/contrib/games/csg1.nfg diff --git a/contrib/games/csg2.nfg b/src/pygambit/contrib/games/csg2.nfg similarity index 100% rename from contrib/games/csg2.nfg rename to src/pygambit/contrib/games/csg2.nfg diff --git a/contrib/games/csg3.nfg b/src/pygambit/contrib/games/csg3.nfg similarity index 100% rename from contrib/games/csg3.nfg rename to src/pygambit/contrib/games/csg3.nfg diff --git a/contrib/games/csg4.nfg b/src/pygambit/contrib/games/csg4.nfg similarity index 100% rename from contrib/games/csg4.nfg rename to src/pygambit/contrib/games/csg4.nfg diff --git a/contrib/games/deg1.nfg b/src/pygambit/contrib/games/deg1.nfg similarity index 100% rename from contrib/games/deg1.nfg rename to src/pygambit/contrib/games/deg1.nfg diff --git a/contrib/games/deg2.nfg b/src/pygambit/contrib/games/deg2.nfg similarity index 100% rename from contrib/games/deg2.nfg rename to src/pygambit/contrib/games/deg2.nfg diff --git a/contrib/games/e01.efg b/src/pygambit/contrib/games/e01.efg similarity index 100% rename from contrib/games/e01.efg rename to src/pygambit/contrib/games/e01.efg diff --git a/contrib/games/e01.nfg b/src/pygambit/contrib/games/e01.nfg similarity index 100% rename from contrib/games/e01.nfg rename to src/pygambit/contrib/games/e01.nfg diff --git a/contrib/games/e02.efg b/src/pygambit/contrib/games/e02.efg similarity index 100% rename from contrib/games/e02.efg rename to src/pygambit/contrib/games/e02.efg diff --git a/contrib/games/e02.nfg b/src/pygambit/contrib/games/e02.nfg similarity index 100% rename from contrib/games/e02.nfg rename to src/pygambit/contrib/games/e02.nfg diff --git a/contrib/games/e03.efg b/src/pygambit/contrib/games/e03.efg similarity index 100% rename from contrib/games/e03.efg rename to src/pygambit/contrib/games/e03.efg diff --git a/contrib/games/e04.efg b/src/pygambit/contrib/games/e04.efg similarity index 100% rename from contrib/games/e04.efg rename to src/pygambit/contrib/games/e04.efg diff --git a/contrib/games/e04.nfg b/src/pygambit/contrib/games/e04.nfg similarity index 100% rename from contrib/games/e04.nfg rename to src/pygambit/contrib/games/e04.nfg diff --git a/contrib/games/e05.efg b/src/pygambit/contrib/games/e05.efg similarity index 100% rename from contrib/games/e05.efg rename to src/pygambit/contrib/games/e05.efg diff --git a/contrib/games/e06.efg b/src/pygambit/contrib/games/e06.efg similarity index 100% rename from contrib/games/e06.efg rename to src/pygambit/contrib/games/e06.efg diff --git a/contrib/games/e07.efg b/src/pygambit/contrib/games/e07.efg similarity index 100% rename from contrib/games/e07.efg rename to src/pygambit/contrib/games/e07.efg diff --git a/contrib/games/e07.nfg b/src/pygambit/contrib/games/e07.nfg similarity index 100% rename from contrib/games/e07.nfg rename to src/pygambit/contrib/games/e07.nfg diff --git a/contrib/games/e08.efg b/src/pygambit/contrib/games/e08.efg similarity index 100% rename from contrib/games/e08.efg rename to src/pygambit/contrib/games/e08.efg diff --git a/contrib/games/e09.efg b/src/pygambit/contrib/games/e09.efg similarity index 100% rename from contrib/games/e09.efg rename to src/pygambit/contrib/games/e09.efg diff --git a/contrib/games/e10.efg b/src/pygambit/contrib/games/e10.efg similarity index 100% rename from contrib/games/e10.efg rename to src/pygambit/contrib/games/e10.efg diff --git a/contrib/games/e10a.efg b/src/pygambit/contrib/games/e10a.efg similarity index 100% rename from contrib/games/e10a.efg rename to src/pygambit/contrib/games/e10a.efg diff --git a/contrib/games/e13.efg b/src/pygambit/contrib/games/e13.efg similarity index 100% rename from contrib/games/e13.efg rename to src/pygambit/contrib/games/e13.efg diff --git a/contrib/games/e16.efg b/src/pygambit/contrib/games/e16.efg similarity index 100% rename from contrib/games/e16.efg rename to src/pygambit/contrib/games/e16.efg diff --git a/contrib/games/e17.efg b/src/pygambit/contrib/games/e17.efg similarity index 100% rename from contrib/games/e17.efg rename to src/pygambit/contrib/games/e17.efg diff --git a/contrib/games/e18.efg b/src/pygambit/contrib/games/e18.efg similarity index 100% rename from contrib/games/e18.efg rename to src/pygambit/contrib/games/e18.efg diff --git a/contrib/games/g1.efg b/src/pygambit/contrib/games/g1.efg similarity index 100% rename from contrib/games/g1.efg rename to src/pygambit/contrib/games/g1.efg diff --git a/contrib/games/g1.nfg b/src/pygambit/contrib/games/g1.nfg similarity index 100% rename from contrib/games/g1.nfg rename to src/pygambit/contrib/games/g1.nfg diff --git a/contrib/games/g2.efg b/src/pygambit/contrib/games/g2.efg similarity index 100% rename from contrib/games/g2.efg rename to src/pygambit/contrib/games/g2.efg diff --git a/contrib/games/g2.nfg b/src/pygambit/contrib/games/g2.nfg similarity index 100% rename from contrib/games/g2.nfg rename to src/pygambit/contrib/games/g2.nfg diff --git a/contrib/games/g3.efg b/src/pygambit/contrib/games/g3.efg similarity index 100% rename from contrib/games/g3.efg rename to src/pygambit/contrib/games/g3.efg diff --git a/contrib/games/g3.nfg b/src/pygambit/contrib/games/g3.nfg similarity index 100% rename from contrib/games/g3.nfg rename to src/pygambit/contrib/games/g3.nfg diff --git a/contrib/games/holdout.efg b/src/pygambit/contrib/games/holdout.efg similarity index 100% rename from contrib/games/holdout.efg rename to src/pygambit/contrib/games/holdout.efg diff --git a/contrib/games/holdout7.efg b/src/pygambit/contrib/games/holdout7.efg similarity index 100% rename from contrib/games/holdout7.efg rename to src/pygambit/contrib/games/holdout7.efg diff --git a/contrib/games/hs1.efg b/src/pygambit/contrib/games/hs1.efg similarity index 100% rename from contrib/games/hs1.efg rename to src/pygambit/contrib/games/hs1.efg diff --git a/contrib/games/jury_mr.efg b/src/pygambit/contrib/games/jury_mr.efg similarity index 100% rename from contrib/games/jury_mr.efg rename to src/pygambit/contrib/games/jury_mr.efg diff --git a/contrib/games/jury_un.efg b/src/pygambit/contrib/games/jury_un.efg similarity index 100% rename from contrib/games/jury_un.efg rename to src/pygambit/contrib/games/jury_un.efg diff --git a/contrib/games/km1.efg b/src/pygambit/contrib/games/km1.efg similarity index 100% rename from contrib/games/km1.efg rename to src/pygambit/contrib/games/km1.efg diff --git a/contrib/games/km2.efg b/src/pygambit/contrib/games/km2.efg similarity index 100% rename from contrib/games/km2.efg rename to src/pygambit/contrib/games/km2.efg diff --git a/contrib/games/km3.efg b/src/pygambit/contrib/games/km3.efg similarity index 100% rename from contrib/games/km3.efg rename to src/pygambit/contrib/games/km3.efg diff --git a/contrib/games/km6.efg b/src/pygambit/contrib/games/km6.efg similarity index 100% rename from contrib/games/km6.efg rename to src/pygambit/contrib/games/km6.efg diff --git a/contrib/games/loopback.nfg b/src/pygambit/contrib/games/loopback.nfg similarity index 100% rename from contrib/games/loopback.nfg rename to src/pygambit/contrib/games/loopback.nfg diff --git a/contrib/games/mixdom.nfg b/src/pygambit/contrib/games/mixdom.nfg similarity index 100% rename from contrib/games/mixdom.nfg rename to src/pygambit/contrib/games/mixdom.nfg diff --git a/contrib/games/mixdom2.nfg b/src/pygambit/contrib/games/mixdom2.nfg similarity index 100% rename from contrib/games/mixdom2.nfg rename to src/pygambit/contrib/games/mixdom2.nfg diff --git a/contrib/games/montyhal.efg b/src/pygambit/contrib/games/montyhal.efg similarity index 100% rename from contrib/games/montyhal.efg rename to src/pygambit/contrib/games/montyhal.efg diff --git a/contrib/games/my_2-1.efg b/src/pygambit/contrib/games/my_2-1.efg similarity index 100% rename from contrib/games/my_2-1.efg rename to src/pygambit/contrib/games/my_2-1.efg diff --git a/contrib/games/my_2-4.efg b/src/pygambit/contrib/games/my_2-4.efg similarity index 100% rename from contrib/games/my_2-4.efg rename to src/pygambit/contrib/games/my_2-4.efg diff --git a/contrib/games/my_2-8.efg b/src/pygambit/contrib/games/my_2-8.efg similarity index 100% rename from contrib/games/my_2-8.efg rename to src/pygambit/contrib/games/my_2-8.efg diff --git a/contrib/games/my_3-3a.efg b/src/pygambit/contrib/games/my_3-3a.efg similarity index 100% rename from contrib/games/my_3-3a.efg rename to src/pygambit/contrib/games/my_3-3a.efg diff --git a/contrib/games/my_3-3b.efg b/src/pygambit/contrib/games/my_3-3b.efg similarity index 100% rename from contrib/games/my_3-3b.efg rename to src/pygambit/contrib/games/my_3-3b.efg diff --git a/contrib/games/my_3-3c.efg b/src/pygambit/contrib/games/my_3-3c.efg similarity index 100% rename from contrib/games/my_3-3c.efg rename to src/pygambit/contrib/games/my_3-3c.efg diff --git a/contrib/games/my_3-3d.efg b/src/pygambit/contrib/games/my_3-3d.efg similarity index 100% rename from contrib/games/my_3-3d.efg rename to src/pygambit/contrib/games/my_3-3d.efg diff --git a/contrib/games/my_3-3e.efg b/src/pygambit/contrib/games/my_3-3e.efg similarity index 100% rename from contrib/games/my_3-3e.efg rename to src/pygambit/contrib/games/my_3-3e.efg diff --git a/contrib/games/my_3-4.efg b/src/pygambit/contrib/games/my_3-4.efg similarity index 100% rename from contrib/games/my_3-4.efg rename to src/pygambit/contrib/games/my_3-4.efg diff --git a/contrib/games/myerson.efg b/src/pygambit/contrib/games/myerson.efg similarity index 100% rename from contrib/games/myerson.efg rename to src/pygambit/contrib/games/myerson.efg diff --git a/contrib/games/myerson_fig_4_2.efg b/src/pygambit/contrib/games/myerson_fig_4_2.efg similarity index 100% rename from contrib/games/myerson_fig_4_2.efg rename to src/pygambit/contrib/games/myerson_fig_4_2.efg diff --git a/contrib/games/nim.efg b/src/pygambit/contrib/games/nim.efg similarity index 100% rename from contrib/games/nim.efg rename to src/pygambit/contrib/games/nim.efg diff --git a/contrib/games/nim7.efg b/src/pygambit/contrib/games/nim7.efg similarity index 100% rename from contrib/games/nim7.efg rename to src/pygambit/contrib/games/nim7.efg diff --git a/contrib/games/oneill.nfg b/src/pygambit/contrib/games/oneill.nfg similarity index 100% rename from contrib/games/oneill.nfg rename to src/pygambit/contrib/games/oneill.nfg diff --git a/contrib/games/palf.efg b/src/pygambit/contrib/games/palf.efg similarity index 100% rename from contrib/games/palf.efg rename to src/pygambit/contrib/games/palf.efg diff --git a/contrib/games/palf2.efg b/src/pygambit/contrib/games/palf2.efg similarity index 100% rename from contrib/games/palf2.efg rename to src/pygambit/contrib/games/palf2.efg diff --git a/contrib/games/palf3.efg b/src/pygambit/contrib/games/palf3.efg similarity index 100% rename from contrib/games/palf3.efg rename to src/pygambit/contrib/games/palf3.efg diff --git a/contrib/games/pbride.efg b/src/pygambit/contrib/games/pbride.efg similarity index 100% rename from contrib/games/pbride.efg rename to src/pygambit/contrib/games/pbride.efg diff --git a/contrib/games/pd.nfg b/src/pygambit/contrib/games/pd.nfg similarity index 100% rename from contrib/games/pd.nfg rename to src/pygambit/contrib/games/pd.nfg diff --git a/contrib/games/perfect1.nfg b/src/pygambit/contrib/games/perfect1.nfg similarity index 100% rename from contrib/games/perfect1.nfg rename to src/pygambit/contrib/games/perfect1.nfg diff --git a/contrib/games/perfect2.nfg b/src/pygambit/contrib/games/perfect2.nfg similarity index 100% rename from contrib/games/perfect2.nfg rename to src/pygambit/contrib/games/perfect2.nfg diff --git a/contrib/games/perfect3.nfg b/src/pygambit/contrib/games/perfect3.nfg similarity index 100% rename from contrib/games/perfect3.nfg rename to src/pygambit/contrib/games/perfect3.nfg diff --git a/contrib/games/poker.efg b/src/pygambit/contrib/games/poker.efg similarity index 100% rename from contrib/games/poker.efg rename to src/pygambit/contrib/games/poker.efg diff --git a/contrib/games/poker.nfg b/src/pygambit/contrib/games/poker.nfg similarity index 100% rename from contrib/games/poker.nfg rename to src/pygambit/contrib/games/poker.nfg diff --git a/contrib/games/poker2.efg b/src/pygambit/contrib/games/poker2.efg similarity index 100% rename from contrib/games/poker2.efg rename to src/pygambit/contrib/games/poker2.efg diff --git a/contrib/games/pvw.efg b/src/pygambit/contrib/games/pvw.efg similarity index 100% rename from contrib/games/pvw.efg rename to src/pygambit/contrib/games/pvw.efg diff --git a/contrib/games/pvw2.efg b/src/pygambit/contrib/games/pvw2.efg similarity index 100% rename from contrib/games/pvw2.efg rename to src/pygambit/contrib/games/pvw2.efg diff --git a/contrib/games/sh3.efg b/src/pygambit/contrib/games/sh3.efg similarity index 100% rename from contrib/games/sh3.efg rename to src/pygambit/contrib/games/sh3.efg diff --git a/contrib/games/sh3.nfg b/src/pygambit/contrib/games/sh3.nfg similarity index 100% rename from contrib/games/sh3.nfg rename to src/pygambit/contrib/games/sh3.nfg diff --git a/contrib/games/spence.efg b/src/pygambit/contrib/games/spence.efg similarity index 100% rename from contrib/games/spence.efg rename to src/pygambit/contrib/games/spence.efg diff --git a/contrib/games/stengel.nfg b/src/pygambit/contrib/games/stengel.nfg similarity index 100% rename from contrib/games/stengel.nfg rename to src/pygambit/contrib/games/stengel.nfg diff --git a/contrib/games/sww1.efg b/src/pygambit/contrib/games/sww1.efg similarity index 100% rename from contrib/games/sww1.efg rename to src/pygambit/contrib/games/sww1.efg diff --git a/contrib/games/sww1.nfg b/src/pygambit/contrib/games/sww1.nfg similarity index 100% rename from contrib/games/sww1.nfg rename to src/pygambit/contrib/games/sww1.nfg diff --git a/contrib/games/sww2.efg b/src/pygambit/contrib/games/sww2.efg similarity index 100% rename from contrib/games/sww2.efg rename to src/pygambit/contrib/games/sww2.efg diff --git a/contrib/games/sww3.efg b/src/pygambit/contrib/games/sww3.efg similarity index 100% rename from contrib/games/sww3.efg rename to src/pygambit/contrib/games/sww3.efg diff --git a/contrib/games/tim.efg b/src/pygambit/contrib/games/tim.efg similarity index 100% rename from contrib/games/tim.efg rename to src/pygambit/contrib/games/tim.efg diff --git a/contrib/games/todd1.nfg b/src/pygambit/contrib/games/todd1.nfg similarity index 100% rename from contrib/games/todd1.nfg rename to src/pygambit/contrib/games/todd1.nfg diff --git a/contrib/games/todd2.nfg b/src/pygambit/contrib/games/todd2.nfg similarity index 100% rename from contrib/games/todd2.nfg rename to src/pygambit/contrib/games/todd2.nfg diff --git a/contrib/games/todd3.nfg b/src/pygambit/contrib/games/todd3.nfg similarity index 100% rename from contrib/games/todd3.nfg rename to src/pygambit/contrib/games/todd3.nfg diff --git a/contrib/games/ttt.efg b/src/pygambit/contrib/games/ttt.efg similarity index 100% rename from contrib/games/ttt.efg rename to src/pygambit/contrib/games/ttt.efg diff --git a/contrib/games/vd.efg b/src/pygambit/contrib/games/vd.efg similarity index 100% rename from contrib/games/vd.efg rename to src/pygambit/contrib/games/vd.efg diff --git a/contrib/games/vd.nfg b/src/pygambit/contrib/games/vd.nfg similarity index 100% rename from contrib/games/vd.nfg rename to src/pygambit/contrib/games/vd.nfg diff --git a/contrib/games/w_ex1.efg b/src/pygambit/contrib/games/w_ex1.efg similarity index 100% rename from contrib/games/w_ex1.efg rename to src/pygambit/contrib/games/w_ex1.efg diff --git a/contrib/games/w_ex2.efg b/src/pygambit/contrib/games/w_ex2.efg similarity index 100% rename from contrib/games/w_ex2.efg rename to src/pygambit/contrib/games/w_ex2.efg diff --git a/contrib/games/wilson1.efg b/src/pygambit/contrib/games/wilson1.efg similarity index 100% rename from contrib/games/wilson1.efg rename to src/pygambit/contrib/games/wilson1.efg diff --git a/contrib/games/wink3.nfg b/src/pygambit/contrib/games/wink3.nfg similarity index 100% rename from contrib/games/wink3.nfg rename to src/pygambit/contrib/games/wink3.nfg diff --git a/contrib/games/winkels.nfg b/src/pygambit/contrib/games/winkels.nfg similarity index 100% rename from contrib/games/winkels.nfg rename to src/pygambit/contrib/games/winkels.nfg diff --git a/contrib/games/work1.efg b/src/pygambit/contrib/games/work1.efg similarity index 100% rename from contrib/games/work1.efg rename to src/pygambit/contrib/games/work1.efg diff --git a/contrib/games/work2.efg b/src/pygambit/contrib/games/work2.efg similarity index 100% rename from contrib/games/work2.efg rename to src/pygambit/contrib/games/work2.efg diff --git a/contrib/games/work3.efg b/src/pygambit/contrib/games/work3.efg similarity index 100% rename from contrib/games/work3.efg rename to src/pygambit/contrib/games/work3.efg diff --git a/contrib/games/yamamoto.nfg b/src/pygambit/contrib/games/yamamoto.nfg similarity index 100% rename from contrib/games/yamamoto.nfg rename to src/pygambit/contrib/games/yamamoto.nfg diff --git a/contrib/games/zero.nfg b/src/pygambit/contrib/games/zero.nfg similarity index 100% rename from contrib/games/zero.nfg rename to src/pygambit/contrib/games/zero.nfg From b6bbeb22b80922e5902856dd0fd9c4c482b95983 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 17:00:38 +0000 Subject: [PATCH 129/170] update notebook to load from catalog --- .../agent_versus_non_agent_regret.ipynb | 230 +++++++++--------- 1 file changed, 110 insertions(+), 120 deletions(-) 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, From 0d9c129028d76fc274ca10703450d4e46d1c534c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 17:03:48 +0000 Subject: [PATCH 130/170] Revert "move contrib/games into pygambit" This reverts commit 55d6395e61f7a5d0296b78dcc4f76a2cefbb9c04. --- {src/pygambit/contrib => contrib}/games/2s2x2x2.efg | 0 {src/pygambit/contrib => contrib}/games/2smp.efg | 0 {src/pygambit/contrib => contrib}/games/2x2.agg | 0 {src/pygambit/contrib => contrib}/games/2x2.nfg | 0 {src/pygambit/contrib => contrib}/games/2x2a.nfg | 0 {src/pygambit/contrib => contrib}/games/2x2const.nfg | 0 {src/pygambit/contrib => contrib}/games/2x2x2-nau.nfg | 0 {src/pygambit/contrib => contrib}/games/2x2x2.efg | 0 {src/pygambit/contrib => contrib}/games/2x2x2.nfg | 0 {src/pygambit/contrib => contrib}/games/2x2x2x2.nfg | 0 {src/pygambit/contrib => contrib}/games/2x2x2x2x2.nfg | 0 {src/pygambit/contrib => contrib}/games/3x3x3.nfg | 0 {src/pygambit/contrib => contrib}/games/4cards.efg | 0 {src/pygambit/contrib => contrib}/games/5x4x3.nfg | 0 {src/pygambit/contrib => contrib}/games/8x2x2.nfg | 0 {src/pygambit/contrib => contrib}/games/8x8.nfg | 0 {src/pygambit/contrib => contrib}/games/BSS_S_085.Weighted.agg | 0 .../contrib => contrib}/games/Bayesian-Coffee-3-2-2-3.bagg | 0 {src/pygambit/contrib => contrib}/games/GenRPS5.agg | 0 {src/pygambit/contrib => contrib}/games/artist1.efg | 0 {src/pygambit/contrib => contrib}/games/artist2.efg | 0 {src/pygambit/contrib => contrib}/games/badgame1.efg | 0 {src/pygambit/contrib => contrib}/games/badgame2.efg | 0 {src/pygambit/contrib => contrib}/games/bagwell.efg | 0 {src/pygambit/contrib => contrib}/games/bayes1a.efg | 0 {src/pygambit/contrib => contrib}/games/bayes2a.efg | 0 {src/pygambit/contrib => contrib}/games/bcp2.efg | 0 {src/pygambit/contrib => contrib}/games/bcp3.efg | 0 {src/pygambit/contrib => contrib}/games/bcp4.efg | 0 {src/pygambit/contrib => contrib}/games/bhg1.efg | 0 {src/pygambit/contrib => contrib}/games/bhg2.efg | 0 {src/pygambit/contrib => contrib}/games/bhg3.efg | 0 {src/pygambit/contrib => contrib}/games/bhg4.efg | 0 {src/pygambit/contrib => contrib}/games/bhg5.efg | 0 {src/pygambit/contrib => contrib}/games/caro2.efg | 0 {src/pygambit/contrib => contrib}/games/cent2.efg | 0 {src/pygambit/contrib => contrib}/games/cent2.nfg | 0 {src/pygambit/contrib => contrib}/games/cent3.efg | 0 {src/pygambit/contrib => contrib}/games/cent4.efg | 0 {src/pygambit/contrib => contrib}/games/cent6.efg | 0 {src/pygambit/contrib => contrib}/games/centcs10.efg | 0 {src/pygambit/contrib => contrib}/games/centcs6.efg | 0 {src/pygambit/contrib => contrib}/games/condjury.efg | 0 {src/pygambit/contrib => contrib}/games/coord2.efg | 0 {src/pygambit/contrib => contrib}/games/coord2.nfg | 0 {src/pygambit/contrib => contrib}/games/coord2ts.efg | 0 {src/pygambit/contrib => contrib}/games/coord3.efg | 0 {src/pygambit/contrib => contrib}/games/coord3.nfg | 0 {src/pygambit/contrib => contrib}/games/coord333.nfg | 0 {src/pygambit/contrib => contrib}/games/coord4.efg | 0 {src/pygambit/contrib => contrib}/games/coord4.nfg | 0 {src/pygambit/contrib => contrib}/games/cross.efg | 0 {src/pygambit/contrib => contrib}/games/cs.efg | 0 {src/pygambit/contrib => contrib}/games/csg1.nfg | 0 {src/pygambit/contrib => contrib}/games/csg2.nfg | 0 {src/pygambit/contrib => contrib}/games/csg3.nfg | 0 {src/pygambit/contrib => contrib}/games/csg4.nfg | 0 {src/pygambit/contrib => contrib}/games/deg1.nfg | 0 {src/pygambit/contrib => contrib}/games/deg2.nfg | 0 {src/pygambit/contrib => contrib}/games/e01.efg | 0 {src/pygambit/contrib => contrib}/games/e01.nfg | 0 {src/pygambit/contrib => contrib}/games/e02.efg | 0 {src/pygambit/contrib => contrib}/games/e02.nfg | 0 {src/pygambit/contrib => contrib}/games/e03.efg | 0 {src/pygambit/contrib => contrib}/games/e04.efg | 0 {src/pygambit/contrib => contrib}/games/e04.nfg | 0 {src/pygambit/contrib => contrib}/games/e05.efg | 0 {src/pygambit/contrib => contrib}/games/e06.efg | 0 {src/pygambit/contrib => contrib}/games/e07.efg | 0 {src/pygambit/contrib => contrib}/games/e07.nfg | 0 {src/pygambit/contrib => contrib}/games/e08.efg | 0 {src/pygambit/contrib => contrib}/games/e09.efg | 0 {src/pygambit/contrib => contrib}/games/e10.efg | 0 {src/pygambit/contrib => contrib}/games/e10a.efg | 0 {src/pygambit/contrib => contrib}/games/e13.efg | 0 {src/pygambit/contrib => contrib}/games/e16.efg | 0 {src/pygambit/contrib => contrib}/games/e17.efg | 0 {src/pygambit/contrib => contrib}/games/e18.efg | 0 {src/pygambit/contrib => contrib}/games/g1.efg | 0 {src/pygambit/contrib => contrib}/games/g1.nfg | 0 {src/pygambit/contrib => contrib}/games/g2.efg | 0 {src/pygambit/contrib => contrib}/games/g2.nfg | 0 {src/pygambit/contrib => contrib}/games/g3.efg | 0 {src/pygambit/contrib => contrib}/games/g3.nfg | 0 {src/pygambit/contrib => contrib}/games/holdout.efg | 0 {src/pygambit/contrib => contrib}/games/holdout7.efg | 0 {src/pygambit/contrib => contrib}/games/hs1.efg | 0 {src/pygambit/contrib => contrib}/games/jury_mr.efg | 0 {src/pygambit/contrib => contrib}/games/jury_un.efg | 0 {src/pygambit/contrib => contrib}/games/km1.efg | 0 {src/pygambit/contrib => contrib}/games/km2.efg | 0 {src/pygambit/contrib => contrib}/games/km3.efg | 0 {src/pygambit/contrib => contrib}/games/km6.efg | 0 {src/pygambit/contrib => contrib}/games/loopback.nfg | 0 {src/pygambit/contrib => contrib}/games/mixdom.nfg | 0 {src/pygambit/contrib => contrib}/games/mixdom2.nfg | 0 {src/pygambit/contrib => contrib}/games/montyhal.efg | 0 {src/pygambit/contrib => contrib}/games/my_2-1.efg | 0 {src/pygambit/contrib => contrib}/games/my_2-4.efg | 0 {src/pygambit/contrib => contrib}/games/my_2-8.efg | 0 {src/pygambit/contrib => contrib}/games/my_3-3a.efg | 0 {src/pygambit/contrib => contrib}/games/my_3-3b.efg | 0 {src/pygambit/contrib => contrib}/games/my_3-3c.efg | 0 {src/pygambit/contrib => contrib}/games/my_3-3d.efg | 0 {src/pygambit/contrib => contrib}/games/my_3-3e.efg | 0 {src/pygambit/contrib => contrib}/games/my_3-4.efg | 0 {src/pygambit/contrib => contrib}/games/myerson.efg | 0 {src/pygambit/contrib => contrib}/games/myerson_fig_4_2.efg | 0 {src/pygambit/contrib => contrib}/games/nim.efg | 0 {src/pygambit/contrib => contrib}/games/nim7.efg | 0 {src/pygambit/contrib => contrib}/games/oneill.nfg | 0 {src/pygambit/contrib => contrib}/games/palf.efg | 0 {src/pygambit/contrib => contrib}/games/palf2.efg | 0 {src/pygambit/contrib => contrib}/games/palf3.efg | 0 {src/pygambit/contrib => contrib}/games/pbride.efg | 0 {src/pygambit/contrib => contrib}/games/pd.nfg | 0 {src/pygambit/contrib => contrib}/games/perfect1.nfg | 0 {src/pygambit/contrib => contrib}/games/perfect2.nfg | 0 {src/pygambit/contrib => contrib}/games/perfect3.nfg | 0 {src/pygambit/contrib => contrib}/games/poker.efg | 0 {src/pygambit/contrib => contrib}/games/poker.nfg | 0 {src/pygambit/contrib => contrib}/games/poker2.efg | 0 {src/pygambit/contrib => contrib}/games/pvw.efg | 0 {src/pygambit/contrib => contrib}/games/pvw2.efg | 0 {src/pygambit/contrib => contrib}/games/sh3.efg | 0 {src/pygambit/contrib => contrib}/games/sh3.nfg | 0 {src/pygambit/contrib => contrib}/games/spence.efg | 0 {src/pygambit/contrib => contrib}/games/stengel.nfg | 0 {src/pygambit/contrib => contrib}/games/sww1.efg | 0 {src/pygambit/contrib => contrib}/games/sww1.nfg | 0 {src/pygambit/contrib => contrib}/games/sww2.efg | 0 {src/pygambit/contrib => contrib}/games/sww3.efg | 0 {src/pygambit/contrib => contrib}/games/tim.efg | 0 {src/pygambit/contrib => contrib}/games/todd1.nfg | 0 {src/pygambit/contrib => contrib}/games/todd2.nfg | 0 {src/pygambit/contrib => contrib}/games/todd3.nfg | 0 {src/pygambit/contrib => contrib}/games/ttt.efg | 0 {src/pygambit/contrib => contrib}/games/vd.efg | 0 {src/pygambit/contrib => contrib}/games/vd.nfg | 0 {src/pygambit/contrib => contrib}/games/w_ex1.efg | 0 {src/pygambit/contrib => contrib}/games/w_ex2.efg | 0 {src/pygambit/contrib => contrib}/games/wilson1.efg | 0 {src/pygambit/contrib => contrib}/games/wink3.nfg | 0 {src/pygambit/contrib => contrib}/games/winkels.nfg | 0 {src/pygambit/contrib => contrib}/games/work1.efg | 0 {src/pygambit/contrib => contrib}/games/work2.efg | 0 {src/pygambit/contrib => contrib}/games/work3.efg | 0 {src/pygambit/contrib => contrib}/games/yamamoto.nfg | 0 {src/pygambit/contrib => contrib}/games/zero.nfg | 0 149 files changed, 0 insertions(+), 0 deletions(-) rename {src/pygambit/contrib => contrib}/games/2s2x2x2.efg (100%) rename {src/pygambit/contrib => contrib}/games/2smp.efg (100%) rename {src/pygambit/contrib => contrib}/games/2x2.agg (100%) rename {src/pygambit/contrib => contrib}/games/2x2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/2x2a.nfg (100%) rename {src/pygambit/contrib => contrib}/games/2x2const.nfg (100%) rename {src/pygambit/contrib => contrib}/games/2x2x2-nau.nfg (100%) rename {src/pygambit/contrib => contrib}/games/2x2x2.efg (100%) rename {src/pygambit/contrib => contrib}/games/2x2x2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/2x2x2x2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/2x2x2x2x2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/3x3x3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/4cards.efg (100%) rename {src/pygambit/contrib => contrib}/games/5x4x3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/8x2x2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/8x8.nfg (100%) rename {src/pygambit/contrib => contrib}/games/BSS_S_085.Weighted.agg (100%) rename {src/pygambit/contrib => contrib}/games/Bayesian-Coffee-3-2-2-3.bagg (100%) rename {src/pygambit/contrib => contrib}/games/GenRPS5.agg (100%) rename {src/pygambit/contrib => contrib}/games/artist1.efg (100%) rename {src/pygambit/contrib => contrib}/games/artist2.efg (100%) rename {src/pygambit/contrib => contrib}/games/badgame1.efg (100%) rename {src/pygambit/contrib => contrib}/games/badgame2.efg (100%) rename {src/pygambit/contrib => contrib}/games/bagwell.efg (100%) rename {src/pygambit/contrib => contrib}/games/bayes1a.efg (100%) rename {src/pygambit/contrib => contrib}/games/bayes2a.efg (100%) rename {src/pygambit/contrib => contrib}/games/bcp2.efg (100%) rename {src/pygambit/contrib => contrib}/games/bcp3.efg (100%) rename {src/pygambit/contrib => contrib}/games/bcp4.efg (100%) rename {src/pygambit/contrib => contrib}/games/bhg1.efg (100%) rename {src/pygambit/contrib => contrib}/games/bhg2.efg (100%) rename {src/pygambit/contrib => contrib}/games/bhg3.efg (100%) rename {src/pygambit/contrib => contrib}/games/bhg4.efg (100%) rename {src/pygambit/contrib => contrib}/games/bhg5.efg (100%) rename {src/pygambit/contrib => contrib}/games/caro2.efg (100%) rename {src/pygambit/contrib => contrib}/games/cent2.efg (100%) rename {src/pygambit/contrib => contrib}/games/cent2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/cent3.efg (100%) rename {src/pygambit/contrib => contrib}/games/cent4.efg (100%) rename {src/pygambit/contrib => contrib}/games/cent6.efg (100%) rename {src/pygambit/contrib => contrib}/games/centcs10.efg (100%) rename {src/pygambit/contrib => contrib}/games/centcs6.efg (100%) rename {src/pygambit/contrib => contrib}/games/condjury.efg (100%) rename {src/pygambit/contrib => contrib}/games/coord2.efg (100%) rename {src/pygambit/contrib => contrib}/games/coord2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/coord2ts.efg (100%) rename {src/pygambit/contrib => contrib}/games/coord3.efg (100%) rename {src/pygambit/contrib => contrib}/games/coord3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/coord333.nfg (100%) rename {src/pygambit/contrib => contrib}/games/coord4.efg (100%) rename {src/pygambit/contrib => contrib}/games/coord4.nfg (100%) rename {src/pygambit/contrib => contrib}/games/cross.efg (100%) rename {src/pygambit/contrib => contrib}/games/cs.efg (100%) rename {src/pygambit/contrib => contrib}/games/csg1.nfg (100%) rename {src/pygambit/contrib => contrib}/games/csg2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/csg3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/csg4.nfg (100%) rename {src/pygambit/contrib => contrib}/games/deg1.nfg (100%) rename {src/pygambit/contrib => contrib}/games/deg2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/e01.efg (100%) rename {src/pygambit/contrib => contrib}/games/e01.nfg (100%) rename {src/pygambit/contrib => contrib}/games/e02.efg (100%) rename {src/pygambit/contrib => contrib}/games/e02.nfg (100%) rename {src/pygambit/contrib => contrib}/games/e03.efg (100%) rename {src/pygambit/contrib => contrib}/games/e04.efg (100%) rename {src/pygambit/contrib => contrib}/games/e04.nfg (100%) rename {src/pygambit/contrib => contrib}/games/e05.efg (100%) rename {src/pygambit/contrib => contrib}/games/e06.efg (100%) rename {src/pygambit/contrib => contrib}/games/e07.efg (100%) rename {src/pygambit/contrib => contrib}/games/e07.nfg (100%) rename {src/pygambit/contrib => contrib}/games/e08.efg (100%) rename {src/pygambit/contrib => contrib}/games/e09.efg (100%) rename {src/pygambit/contrib => contrib}/games/e10.efg (100%) rename {src/pygambit/contrib => contrib}/games/e10a.efg (100%) rename {src/pygambit/contrib => contrib}/games/e13.efg (100%) rename {src/pygambit/contrib => contrib}/games/e16.efg (100%) rename {src/pygambit/contrib => contrib}/games/e17.efg (100%) rename {src/pygambit/contrib => contrib}/games/e18.efg (100%) rename {src/pygambit/contrib => contrib}/games/g1.efg (100%) rename {src/pygambit/contrib => contrib}/games/g1.nfg (100%) rename {src/pygambit/contrib => contrib}/games/g2.efg (100%) rename {src/pygambit/contrib => contrib}/games/g2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/g3.efg (100%) rename {src/pygambit/contrib => contrib}/games/g3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/holdout.efg (100%) rename {src/pygambit/contrib => contrib}/games/holdout7.efg (100%) rename {src/pygambit/contrib => contrib}/games/hs1.efg (100%) rename {src/pygambit/contrib => contrib}/games/jury_mr.efg (100%) rename {src/pygambit/contrib => contrib}/games/jury_un.efg (100%) rename {src/pygambit/contrib => contrib}/games/km1.efg (100%) rename {src/pygambit/contrib => contrib}/games/km2.efg (100%) rename {src/pygambit/contrib => contrib}/games/km3.efg (100%) rename {src/pygambit/contrib => contrib}/games/km6.efg (100%) rename {src/pygambit/contrib => contrib}/games/loopback.nfg (100%) rename {src/pygambit/contrib => contrib}/games/mixdom.nfg (100%) rename {src/pygambit/contrib => contrib}/games/mixdom2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/montyhal.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_2-1.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_2-4.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_2-8.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_3-3a.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_3-3b.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_3-3c.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_3-3d.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_3-3e.efg (100%) rename {src/pygambit/contrib => contrib}/games/my_3-4.efg (100%) rename {src/pygambit/contrib => contrib}/games/myerson.efg (100%) rename {src/pygambit/contrib => contrib}/games/myerson_fig_4_2.efg (100%) rename {src/pygambit/contrib => contrib}/games/nim.efg (100%) rename {src/pygambit/contrib => contrib}/games/nim7.efg (100%) rename {src/pygambit/contrib => contrib}/games/oneill.nfg (100%) rename {src/pygambit/contrib => contrib}/games/palf.efg (100%) rename {src/pygambit/contrib => contrib}/games/palf2.efg (100%) rename {src/pygambit/contrib => contrib}/games/palf3.efg (100%) rename {src/pygambit/contrib => contrib}/games/pbride.efg (100%) rename {src/pygambit/contrib => contrib}/games/pd.nfg (100%) rename {src/pygambit/contrib => contrib}/games/perfect1.nfg (100%) rename {src/pygambit/contrib => contrib}/games/perfect2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/perfect3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/poker.efg (100%) rename {src/pygambit/contrib => contrib}/games/poker.nfg (100%) rename {src/pygambit/contrib => contrib}/games/poker2.efg (100%) rename {src/pygambit/contrib => contrib}/games/pvw.efg (100%) rename {src/pygambit/contrib => contrib}/games/pvw2.efg (100%) rename {src/pygambit/contrib => contrib}/games/sh3.efg (100%) rename {src/pygambit/contrib => contrib}/games/sh3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/spence.efg (100%) rename {src/pygambit/contrib => contrib}/games/stengel.nfg (100%) rename {src/pygambit/contrib => contrib}/games/sww1.efg (100%) rename {src/pygambit/contrib => contrib}/games/sww1.nfg (100%) rename {src/pygambit/contrib => contrib}/games/sww2.efg (100%) rename {src/pygambit/contrib => contrib}/games/sww3.efg (100%) rename {src/pygambit/contrib => contrib}/games/tim.efg (100%) rename {src/pygambit/contrib => contrib}/games/todd1.nfg (100%) rename {src/pygambit/contrib => contrib}/games/todd2.nfg (100%) rename {src/pygambit/contrib => contrib}/games/todd3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/ttt.efg (100%) rename {src/pygambit/contrib => contrib}/games/vd.efg (100%) rename {src/pygambit/contrib => contrib}/games/vd.nfg (100%) rename {src/pygambit/contrib => contrib}/games/w_ex1.efg (100%) rename {src/pygambit/contrib => contrib}/games/w_ex2.efg (100%) rename {src/pygambit/contrib => contrib}/games/wilson1.efg (100%) rename {src/pygambit/contrib => contrib}/games/wink3.nfg (100%) rename {src/pygambit/contrib => contrib}/games/winkels.nfg (100%) rename {src/pygambit/contrib => contrib}/games/work1.efg (100%) rename {src/pygambit/contrib => contrib}/games/work2.efg (100%) rename {src/pygambit/contrib => contrib}/games/work3.efg (100%) rename {src/pygambit/contrib => contrib}/games/yamamoto.nfg (100%) rename {src/pygambit/contrib => contrib}/games/zero.nfg (100%) diff --git a/src/pygambit/contrib/games/2s2x2x2.efg b/contrib/games/2s2x2x2.efg similarity index 100% rename from src/pygambit/contrib/games/2s2x2x2.efg rename to contrib/games/2s2x2x2.efg diff --git a/src/pygambit/contrib/games/2smp.efg b/contrib/games/2smp.efg similarity index 100% rename from src/pygambit/contrib/games/2smp.efg rename to contrib/games/2smp.efg diff --git a/src/pygambit/contrib/games/2x2.agg b/contrib/games/2x2.agg similarity index 100% rename from src/pygambit/contrib/games/2x2.agg rename to contrib/games/2x2.agg diff --git a/src/pygambit/contrib/games/2x2.nfg b/contrib/games/2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2.nfg rename to contrib/games/2x2.nfg diff --git a/src/pygambit/contrib/games/2x2a.nfg b/contrib/games/2x2a.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2a.nfg rename to contrib/games/2x2a.nfg diff --git a/src/pygambit/contrib/games/2x2const.nfg b/contrib/games/2x2const.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2const.nfg rename to contrib/games/2x2const.nfg diff --git a/src/pygambit/contrib/games/2x2x2-nau.nfg b/contrib/games/2x2x2-nau.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2-nau.nfg rename to contrib/games/2x2x2-nau.nfg diff --git a/src/pygambit/contrib/games/2x2x2.efg b/contrib/games/2x2x2.efg similarity index 100% rename from src/pygambit/contrib/games/2x2x2.efg rename to contrib/games/2x2x2.efg diff --git a/src/pygambit/contrib/games/2x2x2.nfg b/contrib/games/2x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2.nfg rename to contrib/games/2x2x2.nfg diff --git a/src/pygambit/contrib/games/2x2x2x2.nfg b/contrib/games/2x2x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2x2.nfg rename to contrib/games/2x2x2x2.nfg diff --git a/src/pygambit/contrib/games/2x2x2x2x2.nfg b/contrib/games/2x2x2x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2x2x2.nfg rename to contrib/games/2x2x2x2x2.nfg diff --git a/src/pygambit/contrib/games/3x3x3.nfg b/contrib/games/3x3x3.nfg similarity index 100% rename from src/pygambit/contrib/games/3x3x3.nfg rename to contrib/games/3x3x3.nfg diff --git a/src/pygambit/contrib/games/4cards.efg b/contrib/games/4cards.efg similarity index 100% rename from src/pygambit/contrib/games/4cards.efg rename to contrib/games/4cards.efg diff --git a/src/pygambit/contrib/games/5x4x3.nfg b/contrib/games/5x4x3.nfg similarity index 100% rename from src/pygambit/contrib/games/5x4x3.nfg rename to contrib/games/5x4x3.nfg diff --git a/src/pygambit/contrib/games/8x2x2.nfg b/contrib/games/8x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/8x2x2.nfg rename to contrib/games/8x2x2.nfg diff --git a/src/pygambit/contrib/games/8x8.nfg b/contrib/games/8x8.nfg similarity index 100% rename from src/pygambit/contrib/games/8x8.nfg rename to contrib/games/8x8.nfg diff --git a/src/pygambit/contrib/games/BSS_S_085.Weighted.agg b/contrib/games/BSS_S_085.Weighted.agg similarity index 100% rename from src/pygambit/contrib/games/BSS_S_085.Weighted.agg rename to contrib/games/BSS_S_085.Weighted.agg diff --git a/src/pygambit/contrib/games/Bayesian-Coffee-3-2-2-3.bagg b/contrib/games/Bayesian-Coffee-3-2-2-3.bagg similarity index 100% rename from src/pygambit/contrib/games/Bayesian-Coffee-3-2-2-3.bagg rename to contrib/games/Bayesian-Coffee-3-2-2-3.bagg diff --git a/src/pygambit/contrib/games/GenRPS5.agg b/contrib/games/GenRPS5.agg similarity index 100% rename from src/pygambit/contrib/games/GenRPS5.agg rename to contrib/games/GenRPS5.agg diff --git a/src/pygambit/contrib/games/artist1.efg b/contrib/games/artist1.efg similarity index 100% rename from src/pygambit/contrib/games/artist1.efg rename to contrib/games/artist1.efg diff --git a/src/pygambit/contrib/games/artist2.efg b/contrib/games/artist2.efg similarity index 100% rename from src/pygambit/contrib/games/artist2.efg rename to contrib/games/artist2.efg diff --git a/src/pygambit/contrib/games/badgame1.efg b/contrib/games/badgame1.efg similarity index 100% rename from src/pygambit/contrib/games/badgame1.efg rename to contrib/games/badgame1.efg diff --git a/src/pygambit/contrib/games/badgame2.efg b/contrib/games/badgame2.efg similarity index 100% rename from src/pygambit/contrib/games/badgame2.efg rename to contrib/games/badgame2.efg diff --git a/src/pygambit/contrib/games/bagwell.efg b/contrib/games/bagwell.efg similarity index 100% rename from src/pygambit/contrib/games/bagwell.efg rename to contrib/games/bagwell.efg diff --git a/src/pygambit/contrib/games/bayes1a.efg b/contrib/games/bayes1a.efg similarity index 100% rename from src/pygambit/contrib/games/bayes1a.efg rename to contrib/games/bayes1a.efg diff --git a/src/pygambit/contrib/games/bayes2a.efg b/contrib/games/bayes2a.efg similarity index 100% rename from src/pygambit/contrib/games/bayes2a.efg rename to contrib/games/bayes2a.efg diff --git a/src/pygambit/contrib/games/bcp2.efg b/contrib/games/bcp2.efg similarity index 100% rename from src/pygambit/contrib/games/bcp2.efg rename to contrib/games/bcp2.efg diff --git a/src/pygambit/contrib/games/bcp3.efg b/contrib/games/bcp3.efg similarity index 100% rename from src/pygambit/contrib/games/bcp3.efg rename to contrib/games/bcp3.efg diff --git a/src/pygambit/contrib/games/bcp4.efg b/contrib/games/bcp4.efg similarity index 100% rename from src/pygambit/contrib/games/bcp4.efg rename to contrib/games/bcp4.efg diff --git a/src/pygambit/contrib/games/bhg1.efg b/contrib/games/bhg1.efg similarity index 100% rename from src/pygambit/contrib/games/bhg1.efg rename to contrib/games/bhg1.efg diff --git a/src/pygambit/contrib/games/bhg2.efg b/contrib/games/bhg2.efg similarity index 100% rename from src/pygambit/contrib/games/bhg2.efg rename to contrib/games/bhg2.efg diff --git a/src/pygambit/contrib/games/bhg3.efg b/contrib/games/bhg3.efg similarity index 100% rename from src/pygambit/contrib/games/bhg3.efg rename to contrib/games/bhg3.efg diff --git a/src/pygambit/contrib/games/bhg4.efg b/contrib/games/bhg4.efg similarity index 100% rename from src/pygambit/contrib/games/bhg4.efg rename to contrib/games/bhg4.efg diff --git a/src/pygambit/contrib/games/bhg5.efg b/contrib/games/bhg5.efg similarity index 100% rename from src/pygambit/contrib/games/bhg5.efg rename to contrib/games/bhg5.efg diff --git a/src/pygambit/contrib/games/caro2.efg b/contrib/games/caro2.efg similarity index 100% rename from src/pygambit/contrib/games/caro2.efg rename to contrib/games/caro2.efg diff --git a/src/pygambit/contrib/games/cent2.efg b/contrib/games/cent2.efg similarity index 100% rename from src/pygambit/contrib/games/cent2.efg rename to contrib/games/cent2.efg diff --git a/src/pygambit/contrib/games/cent2.nfg b/contrib/games/cent2.nfg similarity index 100% rename from src/pygambit/contrib/games/cent2.nfg rename to contrib/games/cent2.nfg diff --git a/src/pygambit/contrib/games/cent3.efg b/contrib/games/cent3.efg similarity index 100% rename from src/pygambit/contrib/games/cent3.efg rename to contrib/games/cent3.efg diff --git a/src/pygambit/contrib/games/cent4.efg b/contrib/games/cent4.efg similarity index 100% rename from src/pygambit/contrib/games/cent4.efg rename to contrib/games/cent4.efg diff --git a/src/pygambit/contrib/games/cent6.efg b/contrib/games/cent6.efg similarity index 100% rename from src/pygambit/contrib/games/cent6.efg rename to contrib/games/cent6.efg diff --git a/src/pygambit/contrib/games/centcs10.efg b/contrib/games/centcs10.efg similarity index 100% rename from src/pygambit/contrib/games/centcs10.efg rename to contrib/games/centcs10.efg diff --git a/src/pygambit/contrib/games/centcs6.efg b/contrib/games/centcs6.efg similarity index 100% rename from src/pygambit/contrib/games/centcs6.efg rename to contrib/games/centcs6.efg diff --git a/src/pygambit/contrib/games/condjury.efg b/contrib/games/condjury.efg similarity index 100% rename from src/pygambit/contrib/games/condjury.efg rename to contrib/games/condjury.efg diff --git a/src/pygambit/contrib/games/coord2.efg b/contrib/games/coord2.efg similarity index 100% rename from src/pygambit/contrib/games/coord2.efg rename to contrib/games/coord2.efg diff --git a/src/pygambit/contrib/games/coord2.nfg b/contrib/games/coord2.nfg similarity index 100% rename from src/pygambit/contrib/games/coord2.nfg rename to contrib/games/coord2.nfg diff --git a/src/pygambit/contrib/games/coord2ts.efg b/contrib/games/coord2ts.efg similarity index 100% rename from src/pygambit/contrib/games/coord2ts.efg rename to contrib/games/coord2ts.efg diff --git a/src/pygambit/contrib/games/coord3.efg b/contrib/games/coord3.efg similarity index 100% rename from src/pygambit/contrib/games/coord3.efg rename to contrib/games/coord3.efg diff --git a/src/pygambit/contrib/games/coord3.nfg b/contrib/games/coord3.nfg similarity index 100% rename from src/pygambit/contrib/games/coord3.nfg rename to contrib/games/coord3.nfg diff --git a/src/pygambit/contrib/games/coord333.nfg b/contrib/games/coord333.nfg similarity index 100% rename from src/pygambit/contrib/games/coord333.nfg rename to contrib/games/coord333.nfg diff --git a/src/pygambit/contrib/games/coord4.efg b/contrib/games/coord4.efg similarity index 100% rename from src/pygambit/contrib/games/coord4.efg rename to contrib/games/coord4.efg diff --git a/src/pygambit/contrib/games/coord4.nfg b/contrib/games/coord4.nfg similarity index 100% rename from src/pygambit/contrib/games/coord4.nfg rename to contrib/games/coord4.nfg diff --git a/src/pygambit/contrib/games/cross.efg b/contrib/games/cross.efg similarity index 100% rename from src/pygambit/contrib/games/cross.efg rename to contrib/games/cross.efg diff --git a/src/pygambit/contrib/games/cs.efg b/contrib/games/cs.efg similarity index 100% rename from src/pygambit/contrib/games/cs.efg rename to contrib/games/cs.efg diff --git a/src/pygambit/contrib/games/csg1.nfg b/contrib/games/csg1.nfg similarity index 100% rename from src/pygambit/contrib/games/csg1.nfg rename to contrib/games/csg1.nfg diff --git a/src/pygambit/contrib/games/csg2.nfg b/contrib/games/csg2.nfg similarity index 100% rename from src/pygambit/contrib/games/csg2.nfg rename to contrib/games/csg2.nfg diff --git a/src/pygambit/contrib/games/csg3.nfg b/contrib/games/csg3.nfg similarity index 100% rename from src/pygambit/contrib/games/csg3.nfg rename to contrib/games/csg3.nfg diff --git a/src/pygambit/contrib/games/csg4.nfg b/contrib/games/csg4.nfg similarity index 100% rename from src/pygambit/contrib/games/csg4.nfg rename to contrib/games/csg4.nfg diff --git a/src/pygambit/contrib/games/deg1.nfg b/contrib/games/deg1.nfg similarity index 100% rename from src/pygambit/contrib/games/deg1.nfg rename to contrib/games/deg1.nfg diff --git a/src/pygambit/contrib/games/deg2.nfg b/contrib/games/deg2.nfg similarity index 100% rename from src/pygambit/contrib/games/deg2.nfg rename to contrib/games/deg2.nfg diff --git a/src/pygambit/contrib/games/e01.efg b/contrib/games/e01.efg similarity index 100% rename from src/pygambit/contrib/games/e01.efg rename to contrib/games/e01.efg diff --git a/src/pygambit/contrib/games/e01.nfg b/contrib/games/e01.nfg similarity index 100% rename from src/pygambit/contrib/games/e01.nfg rename to contrib/games/e01.nfg diff --git a/src/pygambit/contrib/games/e02.efg b/contrib/games/e02.efg similarity index 100% rename from src/pygambit/contrib/games/e02.efg rename to contrib/games/e02.efg diff --git a/src/pygambit/contrib/games/e02.nfg b/contrib/games/e02.nfg similarity index 100% rename from src/pygambit/contrib/games/e02.nfg rename to contrib/games/e02.nfg diff --git a/src/pygambit/contrib/games/e03.efg b/contrib/games/e03.efg similarity index 100% rename from src/pygambit/contrib/games/e03.efg rename to contrib/games/e03.efg diff --git a/src/pygambit/contrib/games/e04.efg b/contrib/games/e04.efg similarity index 100% rename from src/pygambit/contrib/games/e04.efg rename to contrib/games/e04.efg diff --git a/src/pygambit/contrib/games/e04.nfg b/contrib/games/e04.nfg similarity index 100% rename from src/pygambit/contrib/games/e04.nfg rename to contrib/games/e04.nfg diff --git a/src/pygambit/contrib/games/e05.efg b/contrib/games/e05.efg similarity index 100% rename from src/pygambit/contrib/games/e05.efg rename to contrib/games/e05.efg diff --git a/src/pygambit/contrib/games/e06.efg b/contrib/games/e06.efg similarity index 100% rename from src/pygambit/contrib/games/e06.efg rename to contrib/games/e06.efg diff --git a/src/pygambit/contrib/games/e07.efg b/contrib/games/e07.efg similarity index 100% rename from src/pygambit/contrib/games/e07.efg rename to contrib/games/e07.efg diff --git a/src/pygambit/contrib/games/e07.nfg b/contrib/games/e07.nfg similarity index 100% rename from src/pygambit/contrib/games/e07.nfg rename to contrib/games/e07.nfg diff --git a/src/pygambit/contrib/games/e08.efg b/contrib/games/e08.efg similarity index 100% rename from src/pygambit/contrib/games/e08.efg rename to contrib/games/e08.efg diff --git a/src/pygambit/contrib/games/e09.efg b/contrib/games/e09.efg similarity index 100% rename from src/pygambit/contrib/games/e09.efg rename to contrib/games/e09.efg diff --git a/src/pygambit/contrib/games/e10.efg b/contrib/games/e10.efg similarity index 100% rename from src/pygambit/contrib/games/e10.efg rename to contrib/games/e10.efg diff --git a/src/pygambit/contrib/games/e10a.efg b/contrib/games/e10a.efg similarity index 100% rename from src/pygambit/contrib/games/e10a.efg rename to contrib/games/e10a.efg diff --git a/src/pygambit/contrib/games/e13.efg b/contrib/games/e13.efg similarity index 100% rename from src/pygambit/contrib/games/e13.efg rename to contrib/games/e13.efg diff --git a/src/pygambit/contrib/games/e16.efg b/contrib/games/e16.efg similarity index 100% rename from src/pygambit/contrib/games/e16.efg rename to contrib/games/e16.efg diff --git a/src/pygambit/contrib/games/e17.efg b/contrib/games/e17.efg similarity index 100% rename from src/pygambit/contrib/games/e17.efg rename to contrib/games/e17.efg diff --git a/src/pygambit/contrib/games/e18.efg b/contrib/games/e18.efg similarity index 100% rename from src/pygambit/contrib/games/e18.efg rename to contrib/games/e18.efg diff --git a/src/pygambit/contrib/games/g1.efg b/contrib/games/g1.efg similarity index 100% rename from src/pygambit/contrib/games/g1.efg rename to contrib/games/g1.efg diff --git a/src/pygambit/contrib/games/g1.nfg b/contrib/games/g1.nfg similarity index 100% rename from src/pygambit/contrib/games/g1.nfg rename to contrib/games/g1.nfg diff --git a/src/pygambit/contrib/games/g2.efg b/contrib/games/g2.efg similarity index 100% rename from src/pygambit/contrib/games/g2.efg rename to contrib/games/g2.efg diff --git a/src/pygambit/contrib/games/g2.nfg b/contrib/games/g2.nfg similarity index 100% rename from src/pygambit/contrib/games/g2.nfg rename to contrib/games/g2.nfg diff --git a/src/pygambit/contrib/games/g3.efg b/contrib/games/g3.efg similarity index 100% rename from src/pygambit/contrib/games/g3.efg rename to contrib/games/g3.efg diff --git a/src/pygambit/contrib/games/g3.nfg b/contrib/games/g3.nfg similarity index 100% rename from src/pygambit/contrib/games/g3.nfg rename to contrib/games/g3.nfg diff --git a/src/pygambit/contrib/games/holdout.efg b/contrib/games/holdout.efg similarity index 100% rename from src/pygambit/contrib/games/holdout.efg rename to contrib/games/holdout.efg diff --git a/src/pygambit/contrib/games/holdout7.efg b/contrib/games/holdout7.efg similarity index 100% rename from src/pygambit/contrib/games/holdout7.efg rename to contrib/games/holdout7.efg diff --git a/src/pygambit/contrib/games/hs1.efg b/contrib/games/hs1.efg similarity index 100% rename from src/pygambit/contrib/games/hs1.efg rename to contrib/games/hs1.efg diff --git a/src/pygambit/contrib/games/jury_mr.efg b/contrib/games/jury_mr.efg similarity index 100% rename from src/pygambit/contrib/games/jury_mr.efg rename to contrib/games/jury_mr.efg diff --git a/src/pygambit/contrib/games/jury_un.efg b/contrib/games/jury_un.efg similarity index 100% rename from src/pygambit/contrib/games/jury_un.efg rename to contrib/games/jury_un.efg diff --git a/src/pygambit/contrib/games/km1.efg b/contrib/games/km1.efg similarity index 100% rename from src/pygambit/contrib/games/km1.efg rename to contrib/games/km1.efg diff --git a/src/pygambit/contrib/games/km2.efg b/contrib/games/km2.efg similarity index 100% rename from src/pygambit/contrib/games/km2.efg rename to contrib/games/km2.efg diff --git a/src/pygambit/contrib/games/km3.efg b/contrib/games/km3.efg similarity index 100% rename from src/pygambit/contrib/games/km3.efg rename to contrib/games/km3.efg diff --git a/src/pygambit/contrib/games/km6.efg b/contrib/games/km6.efg similarity index 100% rename from src/pygambit/contrib/games/km6.efg rename to contrib/games/km6.efg diff --git a/src/pygambit/contrib/games/loopback.nfg b/contrib/games/loopback.nfg similarity index 100% rename from src/pygambit/contrib/games/loopback.nfg rename to contrib/games/loopback.nfg diff --git a/src/pygambit/contrib/games/mixdom.nfg b/contrib/games/mixdom.nfg similarity index 100% rename from src/pygambit/contrib/games/mixdom.nfg rename to contrib/games/mixdom.nfg diff --git a/src/pygambit/contrib/games/mixdom2.nfg b/contrib/games/mixdom2.nfg similarity index 100% rename from src/pygambit/contrib/games/mixdom2.nfg rename to contrib/games/mixdom2.nfg diff --git a/src/pygambit/contrib/games/montyhal.efg b/contrib/games/montyhal.efg similarity index 100% rename from src/pygambit/contrib/games/montyhal.efg rename to contrib/games/montyhal.efg diff --git a/src/pygambit/contrib/games/my_2-1.efg b/contrib/games/my_2-1.efg similarity index 100% rename from src/pygambit/contrib/games/my_2-1.efg rename to contrib/games/my_2-1.efg diff --git a/src/pygambit/contrib/games/my_2-4.efg b/contrib/games/my_2-4.efg similarity index 100% rename from src/pygambit/contrib/games/my_2-4.efg rename to contrib/games/my_2-4.efg diff --git a/src/pygambit/contrib/games/my_2-8.efg b/contrib/games/my_2-8.efg similarity index 100% rename from src/pygambit/contrib/games/my_2-8.efg rename to contrib/games/my_2-8.efg diff --git a/src/pygambit/contrib/games/my_3-3a.efg b/contrib/games/my_3-3a.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3a.efg rename to contrib/games/my_3-3a.efg diff --git a/src/pygambit/contrib/games/my_3-3b.efg b/contrib/games/my_3-3b.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3b.efg rename to contrib/games/my_3-3b.efg diff --git a/src/pygambit/contrib/games/my_3-3c.efg b/contrib/games/my_3-3c.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3c.efg rename to contrib/games/my_3-3c.efg diff --git a/src/pygambit/contrib/games/my_3-3d.efg b/contrib/games/my_3-3d.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3d.efg rename to contrib/games/my_3-3d.efg diff --git a/src/pygambit/contrib/games/my_3-3e.efg b/contrib/games/my_3-3e.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3e.efg rename to contrib/games/my_3-3e.efg diff --git a/src/pygambit/contrib/games/my_3-4.efg b/contrib/games/my_3-4.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-4.efg rename to contrib/games/my_3-4.efg diff --git a/src/pygambit/contrib/games/myerson.efg b/contrib/games/myerson.efg similarity index 100% rename from src/pygambit/contrib/games/myerson.efg rename to contrib/games/myerson.efg diff --git a/src/pygambit/contrib/games/myerson_fig_4_2.efg b/contrib/games/myerson_fig_4_2.efg similarity index 100% rename from src/pygambit/contrib/games/myerson_fig_4_2.efg rename to contrib/games/myerson_fig_4_2.efg diff --git a/src/pygambit/contrib/games/nim.efg b/contrib/games/nim.efg similarity index 100% rename from src/pygambit/contrib/games/nim.efg rename to contrib/games/nim.efg diff --git a/src/pygambit/contrib/games/nim7.efg b/contrib/games/nim7.efg similarity index 100% rename from src/pygambit/contrib/games/nim7.efg rename to contrib/games/nim7.efg diff --git a/src/pygambit/contrib/games/oneill.nfg b/contrib/games/oneill.nfg similarity index 100% rename from src/pygambit/contrib/games/oneill.nfg rename to contrib/games/oneill.nfg diff --git a/src/pygambit/contrib/games/palf.efg b/contrib/games/palf.efg similarity index 100% rename from src/pygambit/contrib/games/palf.efg rename to contrib/games/palf.efg diff --git a/src/pygambit/contrib/games/palf2.efg b/contrib/games/palf2.efg similarity index 100% rename from src/pygambit/contrib/games/palf2.efg rename to contrib/games/palf2.efg diff --git a/src/pygambit/contrib/games/palf3.efg b/contrib/games/palf3.efg similarity index 100% rename from src/pygambit/contrib/games/palf3.efg rename to contrib/games/palf3.efg diff --git a/src/pygambit/contrib/games/pbride.efg b/contrib/games/pbride.efg similarity index 100% rename from src/pygambit/contrib/games/pbride.efg rename to contrib/games/pbride.efg diff --git a/src/pygambit/contrib/games/pd.nfg b/contrib/games/pd.nfg similarity index 100% rename from src/pygambit/contrib/games/pd.nfg rename to contrib/games/pd.nfg diff --git a/src/pygambit/contrib/games/perfect1.nfg b/contrib/games/perfect1.nfg similarity index 100% rename from src/pygambit/contrib/games/perfect1.nfg rename to contrib/games/perfect1.nfg diff --git a/src/pygambit/contrib/games/perfect2.nfg b/contrib/games/perfect2.nfg similarity index 100% rename from src/pygambit/contrib/games/perfect2.nfg rename to contrib/games/perfect2.nfg diff --git a/src/pygambit/contrib/games/perfect3.nfg b/contrib/games/perfect3.nfg similarity index 100% rename from src/pygambit/contrib/games/perfect3.nfg rename to contrib/games/perfect3.nfg diff --git a/src/pygambit/contrib/games/poker.efg b/contrib/games/poker.efg similarity index 100% rename from src/pygambit/contrib/games/poker.efg rename to contrib/games/poker.efg diff --git a/src/pygambit/contrib/games/poker.nfg b/contrib/games/poker.nfg similarity index 100% rename from src/pygambit/contrib/games/poker.nfg rename to contrib/games/poker.nfg diff --git a/src/pygambit/contrib/games/poker2.efg b/contrib/games/poker2.efg similarity index 100% rename from src/pygambit/contrib/games/poker2.efg rename to contrib/games/poker2.efg diff --git a/src/pygambit/contrib/games/pvw.efg b/contrib/games/pvw.efg similarity index 100% rename from src/pygambit/contrib/games/pvw.efg rename to contrib/games/pvw.efg diff --git a/src/pygambit/contrib/games/pvw2.efg b/contrib/games/pvw2.efg similarity index 100% rename from src/pygambit/contrib/games/pvw2.efg rename to contrib/games/pvw2.efg diff --git a/src/pygambit/contrib/games/sh3.efg b/contrib/games/sh3.efg similarity index 100% rename from src/pygambit/contrib/games/sh3.efg rename to contrib/games/sh3.efg diff --git a/src/pygambit/contrib/games/sh3.nfg b/contrib/games/sh3.nfg similarity index 100% rename from src/pygambit/contrib/games/sh3.nfg rename to contrib/games/sh3.nfg diff --git a/src/pygambit/contrib/games/spence.efg b/contrib/games/spence.efg similarity index 100% rename from src/pygambit/contrib/games/spence.efg rename to contrib/games/spence.efg diff --git a/src/pygambit/contrib/games/stengel.nfg b/contrib/games/stengel.nfg similarity index 100% rename from src/pygambit/contrib/games/stengel.nfg rename to contrib/games/stengel.nfg diff --git a/src/pygambit/contrib/games/sww1.efg b/contrib/games/sww1.efg similarity index 100% rename from src/pygambit/contrib/games/sww1.efg rename to contrib/games/sww1.efg diff --git a/src/pygambit/contrib/games/sww1.nfg b/contrib/games/sww1.nfg similarity index 100% rename from src/pygambit/contrib/games/sww1.nfg rename to contrib/games/sww1.nfg diff --git a/src/pygambit/contrib/games/sww2.efg b/contrib/games/sww2.efg similarity index 100% rename from src/pygambit/contrib/games/sww2.efg rename to contrib/games/sww2.efg diff --git a/src/pygambit/contrib/games/sww3.efg b/contrib/games/sww3.efg similarity index 100% rename from src/pygambit/contrib/games/sww3.efg rename to contrib/games/sww3.efg diff --git a/src/pygambit/contrib/games/tim.efg b/contrib/games/tim.efg similarity index 100% rename from src/pygambit/contrib/games/tim.efg rename to contrib/games/tim.efg diff --git a/src/pygambit/contrib/games/todd1.nfg b/contrib/games/todd1.nfg similarity index 100% rename from src/pygambit/contrib/games/todd1.nfg rename to contrib/games/todd1.nfg diff --git a/src/pygambit/contrib/games/todd2.nfg b/contrib/games/todd2.nfg similarity index 100% rename from src/pygambit/contrib/games/todd2.nfg rename to contrib/games/todd2.nfg diff --git a/src/pygambit/contrib/games/todd3.nfg b/contrib/games/todd3.nfg similarity index 100% rename from src/pygambit/contrib/games/todd3.nfg rename to contrib/games/todd3.nfg diff --git a/src/pygambit/contrib/games/ttt.efg b/contrib/games/ttt.efg similarity index 100% rename from src/pygambit/contrib/games/ttt.efg rename to contrib/games/ttt.efg diff --git a/src/pygambit/contrib/games/vd.efg b/contrib/games/vd.efg similarity index 100% rename from src/pygambit/contrib/games/vd.efg rename to contrib/games/vd.efg diff --git a/src/pygambit/contrib/games/vd.nfg b/contrib/games/vd.nfg similarity index 100% rename from src/pygambit/contrib/games/vd.nfg rename to contrib/games/vd.nfg diff --git a/src/pygambit/contrib/games/w_ex1.efg b/contrib/games/w_ex1.efg similarity index 100% rename from src/pygambit/contrib/games/w_ex1.efg rename to contrib/games/w_ex1.efg diff --git a/src/pygambit/contrib/games/w_ex2.efg b/contrib/games/w_ex2.efg similarity index 100% rename from src/pygambit/contrib/games/w_ex2.efg rename to contrib/games/w_ex2.efg diff --git a/src/pygambit/contrib/games/wilson1.efg b/contrib/games/wilson1.efg similarity index 100% rename from src/pygambit/contrib/games/wilson1.efg rename to contrib/games/wilson1.efg diff --git a/src/pygambit/contrib/games/wink3.nfg b/contrib/games/wink3.nfg similarity index 100% rename from src/pygambit/contrib/games/wink3.nfg rename to contrib/games/wink3.nfg diff --git a/src/pygambit/contrib/games/winkels.nfg b/contrib/games/winkels.nfg similarity index 100% rename from src/pygambit/contrib/games/winkels.nfg rename to contrib/games/winkels.nfg diff --git a/src/pygambit/contrib/games/work1.efg b/contrib/games/work1.efg similarity index 100% rename from src/pygambit/contrib/games/work1.efg rename to contrib/games/work1.efg diff --git a/src/pygambit/contrib/games/work2.efg b/contrib/games/work2.efg similarity index 100% rename from src/pygambit/contrib/games/work2.efg rename to contrib/games/work2.efg diff --git a/src/pygambit/contrib/games/work3.efg b/contrib/games/work3.efg similarity index 100% rename from src/pygambit/contrib/games/work3.efg rename to contrib/games/work3.efg diff --git a/src/pygambit/contrib/games/yamamoto.nfg b/contrib/games/yamamoto.nfg similarity index 100% rename from src/pygambit/contrib/games/yamamoto.nfg rename to contrib/games/yamamoto.nfg diff --git a/src/pygambit/contrib/games/zero.nfg b/contrib/games/zero.nfg similarity index 100% rename from src/pygambit/contrib/games/zero.nfg rename to contrib/games/zero.nfg From bfa7067f51f366088cf06636fc47e6957e3c04a5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 17:10:02 +0000 Subject: [PATCH 131/170] Revert "Revert "move contrib/games into pygambit"" This reverts commit 0d9c129028d76fc274ca10703450d4e46d1c534c. --- {contrib => src/pygambit/contrib}/games/2s2x2x2.efg | 0 {contrib => src/pygambit/contrib}/games/2smp.efg | 0 {contrib => src/pygambit/contrib}/games/2x2.agg | 0 {contrib => src/pygambit/contrib}/games/2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2a.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2const.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2-nau.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2.efg | 0 {contrib => src/pygambit/contrib}/games/2x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/2x2x2x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/3x3x3.nfg | 0 {contrib => src/pygambit/contrib}/games/4cards.efg | 0 {contrib => src/pygambit/contrib}/games/5x4x3.nfg | 0 {contrib => src/pygambit/contrib}/games/8x2x2.nfg | 0 {contrib => src/pygambit/contrib}/games/8x8.nfg | 0 {contrib => src/pygambit/contrib}/games/BSS_S_085.Weighted.agg | 0 .../pygambit/contrib}/games/Bayesian-Coffee-3-2-2-3.bagg | 0 {contrib => src/pygambit/contrib}/games/GenRPS5.agg | 0 {contrib => src/pygambit/contrib}/games/artist1.efg | 0 {contrib => src/pygambit/contrib}/games/artist2.efg | 0 {contrib => src/pygambit/contrib}/games/badgame1.efg | 0 {contrib => src/pygambit/contrib}/games/badgame2.efg | 0 {contrib => src/pygambit/contrib}/games/bagwell.efg | 0 {contrib => src/pygambit/contrib}/games/bayes1a.efg | 0 {contrib => src/pygambit/contrib}/games/bayes2a.efg | 0 {contrib => src/pygambit/contrib}/games/bcp2.efg | 0 {contrib => src/pygambit/contrib}/games/bcp3.efg | 0 {contrib => src/pygambit/contrib}/games/bcp4.efg | 0 {contrib => src/pygambit/contrib}/games/bhg1.efg | 0 {contrib => src/pygambit/contrib}/games/bhg2.efg | 0 {contrib => src/pygambit/contrib}/games/bhg3.efg | 0 {contrib => src/pygambit/contrib}/games/bhg4.efg | 0 {contrib => src/pygambit/contrib}/games/bhg5.efg | 0 {contrib => src/pygambit/contrib}/games/caro2.efg | 0 {contrib => src/pygambit/contrib}/games/cent2.efg | 0 {contrib => src/pygambit/contrib}/games/cent2.nfg | 0 {contrib => src/pygambit/contrib}/games/cent3.efg | 0 {contrib => src/pygambit/contrib}/games/cent4.efg | 0 {contrib => src/pygambit/contrib}/games/cent6.efg | 0 {contrib => src/pygambit/contrib}/games/centcs10.efg | 0 {contrib => src/pygambit/contrib}/games/centcs6.efg | 0 {contrib => src/pygambit/contrib}/games/condjury.efg | 0 {contrib => src/pygambit/contrib}/games/coord2.efg | 0 {contrib => src/pygambit/contrib}/games/coord2.nfg | 0 {contrib => src/pygambit/contrib}/games/coord2ts.efg | 0 {contrib => src/pygambit/contrib}/games/coord3.efg | 0 {contrib => src/pygambit/contrib}/games/coord3.nfg | 0 {contrib => src/pygambit/contrib}/games/coord333.nfg | 0 {contrib => src/pygambit/contrib}/games/coord4.efg | 0 {contrib => src/pygambit/contrib}/games/coord4.nfg | 0 {contrib => src/pygambit/contrib}/games/cross.efg | 0 {contrib => src/pygambit/contrib}/games/cs.efg | 0 {contrib => src/pygambit/contrib}/games/csg1.nfg | 0 {contrib => src/pygambit/contrib}/games/csg2.nfg | 0 {contrib => src/pygambit/contrib}/games/csg3.nfg | 0 {contrib => src/pygambit/contrib}/games/csg4.nfg | 0 {contrib => src/pygambit/contrib}/games/deg1.nfg | 0 {contrib => src/pygambit/contrib}/games/deg2.nfg | 0 {contrib => src/pygambit/contrib}/games/e01.efg | 0 {contrib => src/pygambit/contrib}/games/e01.nfg | 0 {contrib => src/pygambit/contrib}/games/e02.efg | 0 {contrib => src/pygambit/contrib}/games/e02.nfg | 0 {contrib => src/pygambit/contrib}/games/e03.efg | 0 {contrib => src/pygambit/contrib}/games/e04.efg | 0 {contrib => src/pygambit/contrib}/games/e04.nfg | 0 {contrib => src/pygambit/contrib}/games/e05.efg | 0 {contrib => src/pygambit/contrib}/games/e06.efg | 0 {contrib => src/pygambit/contrib}/games/e07.efg | 0 {contrib => src/pygambit/contrib}/games/e07.nfg | 0 {contrib => src/pygambit/contrib}/games/e08.efg | 0 {contrib => src/pygambit/contrib}/games/e09.efg | 0 {contrib => src/pygambit/contrib}/games/e10.efg | 0 {contrib => src/pygambit/contrib}/games/e10a.efg | 0 {contrib => src/pygambit/contrib}/games/e13.efg | 0 {contrib => src/pygambit/contrib}/games/e16.efg | 0 {contrib => src/pygambit/contrib}/games/e17.efg | 0 {contrib => src/pygambit/contrib}/games/e18.efg | 0 {contrib => src/pygambit/contrib}/games/g1.efg | 0 {contrib => src/pygambit/contrib}/games/g1.nfg | 0 {contrib => src/pygambit/contrib}/games/g2.efg | 0 {contrib => src/pygambit/contrib}/games/g2.nfg | 0 {contrib => src/pygambit/contrib}/games/g3.efg | 0 {contrib => src/pygambit/contrib}/games/g3.nfg | 0 {contrib => src/pygambit/contrib}/games/holdout.efg | 0 {contrib => src/pygambit/contrib}/games/holdout7.efg | 0 {contrib => src/pygambit/contrib}/games/hs1.efg | 0 {contrib => src/pygambit/contrib}/games/jury_mr.efg | 0 {contrib => src/pygambit/contrib}/games/jury_un.efg | 0 {contrib => src/pygambit/contrib}/games/km1.efg | 0 {contrib => src/pygambit/contrib}/games/km2.efg | 0 {contrib => src/pygambit/contrib}/games/km3.efg | 0 {contrib => src/pygambit/contrib}/games/km6.efg | 0 {contrib => src/pygambit/contrib}/games/loopback.nfg | 0 {contrib => src/pygambit/contrib}/games/mixdom.nfg | 0 {contrib => src/pygambit/contrib}/games/mixdom2.nfg | 0 {contrib => src/pygambit/contrib}/games/montyhal.efg | 0 {contrib => src/pygambit/contrib}/games/my_2-1.efg | 0 {contrib => src/pygambit/contrib}/games/my_2-4.efg | 0 {contrib => src/pygambit/contrib}/games/my_2-8.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3a.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3b.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3c.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3d.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-3e.efg | 0 {contrib => src/pygambit/contrib}/games/my_3-4.efg | 0 {contrib => src/pygambit/contrib}/games/myerson.efg | 0 {contrib => src/pygambit/contrib}/games/myerson_fig_4_2.efg | 0 {contrib => src/pygambit/contrib}/games/nim.efg | 0 {contrib => src/pygambit/contrib}/games/nim7.efg | 0 {contrib => src/pygambit/contrib}/games/oneill.nfg | 0 {contrib => src/pygambit/contrib}/games/palf.efg | 0 {contrib => src/pygambit/contrib}/games/palf2.efg | 0 {contrib => src/pygambit/contrib}/games/palf3.efg | 0 {contrib => src/pygambit/contrib}/games/pbride.efg | 0 {contrib => src/pygambit/contrib}/games/pd.nfg | 0 {contrib => src/pygambit/contrib}/games/perfect1.nfg | 0 {contrib => src/pygambit/contrib}/games/perfect2.nfg | 0 {contrib => src/pygambit/contrib}/games/perfect3.nfg | 0 {contrib => src/pygambit/contrib}/games/poker.efg | 0 {contrib => src/pygambit/contrib}/games/poker.nfg | 0 {contrib => src/pygambit/contrib}/games/poker2.efg | 0 {contrib => src/pygambit/contrib}/games/pvw.efg | 0 {contrib => src/pygambit/contrib}/games/pvw2.efg | 0 {contrib => src/pygambit/contrib}/games/sh3.efg | 0 {contrib => src/pygambit/contrib}/games/sh3.nfg | 0 {contrib => src/pygambit/contrib}/games/spence.efg | 0 {contrib => src/pygambit/contrib}/games/stengel.nfg | 0 {contrib => src/pygambit/contrib}/games/sww1.efg | 0 {contrib => src/pygambit/contrib}/games/sww1.nfg | 0 {contrib => src/pygambit/contrib}/games/sww2.efg | 0 {contrib => src/pygambit/contrib}/games/sww3.efg | 0 {contrib => src/pygambit/contrib}/games/tim.efg | 0 {contrib => src/pygambit/contrib}/games/todd1.nfg | 0 {contrib => src/pygambit/contrib}/games/todd2.nfg | 0 {contrib => src/pygambit/contrib}/games/todd3.nfg | 0 {contrib => src/pygambit/contrib}/games/ttt.efg | 0 {contrib => src/pygambit/contrib}/games/vd.efg | 0 {contrib => src/pygambit/contrib}/games/vd.nfg | 0 {contrib => src/pygambit/contrib}/games/w_ex1.efg | 0 {contrib => src/pygambit/contrib}/games/w_ex2.efg | 0 {contrib => src/pygambit/contrib}/games/wilson1.efg | 0 {contrib => src/pygambit/contrib}/games/wink3.nfg | 0 {contrib => src/pygambit/contrib}/games/winkels.nfg | 0 {contrib => src/pygambit/contrib}/games/work1.efg | 0 {contrib => src/pygambit/contrib}/games/work2.efg | 0 {contrib => src/pygambit/contrib}/games/work3.efg | 0 {contrib => src/pygambit/contrib}/games/yamamoto.nfg | 0 {contrib => src/pygambit/contrib}/games/zero.nfg | 0 149 files changed, 0 insertions(+), 0 deletions(-) rename {contrib => src/pygambit/contrib}/games/2s2x2x2.efg (100%) rename {contrib => src/pygambit/contrib}/games/2smp.efg (100%) rename {contrib => src/pygambit/contrib}/games/2x2.agg (100%) rename {contrib => src/pygambit/contrib}/games/2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2a.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2const.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2-nau.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2.efg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/2x2x2x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/3x3x3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/4cards.efg (100%) rename {contrib => src/pygambit/contrib}/games/5x4x3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/8x2x2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/8x8.nfg (100%) rename {contrib => src/pygambit/contrib}/games/BSS_S_085.Weighted.agg (100%) rename {contrib => src/pygambit/contrib}/games/Bayesian-Coffee-3-2-2-3.bagg (100%) rename {contrib => src/pygambit/contrib}/games/GenRPS5.agg (100%) rename {contrib => src/pygambit/contrib}/games/artist1.efg (100%) rename {contrib => src/pygambit/contrib}/games/artist2.efg (100%) rename {contrib => src/pygambit/contrib}/games/badgame1.efg (100%) rename {contrib => src/pygambit/contrib}/games/badgame2.efg (100%) rename {contrib => src/pygambit/contrib}/games/bagwell.efg (100%) rename {contrib => src/pygambit/contrib}/games/bayes1a.efg (100%) rename {contrib => src/pygambit/contrib}/games/bayes2a.efg (100%) rename {contrib => src/pygambit/contrib}/games/bcp2.efg (100%) rename {contrib => src/pygambit/contrib}/games/bcp3.efg (100%) rename {contrib => src/pygambit/contrib}/games/bcp4.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg1.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg2.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg3.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg4.efg (100%) rename {contrib => src/pygambit/contrib}/games/bhg5.efg (100%) rename {contrib => src/pygambit/contrib}/games/caro2.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent2.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/cent3.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent4.efg (100%) rename {contrib => src/pygambit/contrib}/games/cent6.efg (100%) rename {contrib => src/pygambit/contrib}/games/centcs10.efg (100%) rename {contrib => src/pygambit/contrib}/games/centcs6.efg (100%) rename {contrib => src/pygambit/contrib}/games/condjury.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord2.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/coord2ts.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord3.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/coord333.nfg (100%) rename {contrib => src/pygambit/contrib}/games/coord4.efg (100%) rename {contrib => src/pygambit/contrib}/games/coord4.nfg (100%) rename {contrib => src/pygambit/contrib}/games/cross.efg (100%) rename {contrib => src/pygambit/contrib}/games/cs.efg (100%) rename {contrib => src/pygambit/contrib}/games/csg1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/csg2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/csg3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/csg4.nfg (100%) rename {contrib => src/pygambit/contrib}/games/deg1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/deg2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e01.efg (100%) rename {contrib => src/pygambit/contrib}/games/e01.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e02.efg (100%) rename {contrib => src/pygambit/contrib}/games/e02.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e03.efg (100%) rename {contrib => src/pygambit/contrib}/games/e04.efg (100%) rename {contrib => src/pygambit/contrib}/games/e04.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e05.efg (100%) rename {contrib => src/pygambit/contrib}/games/e06.efg (100%) rename {contrib => src/pygambit/contrib}/games/e07.efg (100%) rename {contrib => src/pygambit/contrib}/games/e07.nfg (100%) rename {contrib => src/pygambit/contrib}/games/e08.efg (100%) rename {contrib => src/pygambit/contrib}/games/e09.efg (100%) rename {contrib => src/pygambit/contrib}/games/e10.efg (100%) rename {contrib => src/pygambit/contrib}/games/e10a.efg (100%) rename {contrib => src/pygambit/contrib}/games/e13.efg (100%) rename {contrib => src/pygambit/contrib}/games/e16.efg (100%) rename {contrib => src/pygambit/contrib}/games/e17.efg (100%) rename {contrib => src/pygambit/contrib}/games/e18.efg (100%) rename {contrib => src/pygambit/contrib}/games/g1.efg (100%) rename {contrib => src/pygambit/contrib}/games/g1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/g2.efg (100%) rename {contrib => src/pygambit/contrib}/games/g2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/g3.efg (100%) rename {contrib => src/pygambit/contrib}/games/g3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/holdout.efg (100%) rename {contrib => src/pygambit/contrib}/games/holdout7.efg (100%) rename {contrib => src/pygambit/contrib}/games/hs1.efg (100%) rename {contrib => src/pygambit/contrib}/games/jury_mr.efg (100%) rename {contrib => src/pygambit/contrib}/games/jury_un.efg (100%) rename {contrib => src/pygambit/contrib}/games/km1.efg (100%) rename {contrib => src/pygambit/contrib}/games/km2.efg (100%) rename {contrib => src/pygambit/contrib}/games/km3.efg (100%) rename {contrib => src/pygambit/contrib}/games/km6.efg (100%) rename {contrib => src/pygambit/contrib}/games/loopback.nfg (100%) rename {contrib => src/pygambit/contrib}/games/mixdom.nfg (100%) rename {contrib => src/pygambit/contrib}/games/mixdom2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/montyhal.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_2-1.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_2-4.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_2-8.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3a.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3b.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3c.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3d.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-3e.efg (100%) rename {contrib => src/pygambit/contrib}/games/my_3-4.efg (100%) rename {contrib => src/pygambit/contrib}/games/myerson.efg (100%) rename {contrib => src/pygambit/contrib}/games/myerson_fig_4_2.efg (100%) rename {contrib => src/pygambit/contrib}/games/nim.efg (100%) rename {contrib => src/pygambit/contrib}/games/nim7.efg (100%) rename {contrib => src/pygambit/contrib}/games/oneill.nfg (100%) rename {contrib => src/pygambit/contrib}/games/palf.efg (100%) rename {contrib => src/pygambit/contrib}/games/palf2.efg (100%) rename {contrib => src/pygambit/contrib}/games/palf3.efg (100%) rename {contrib => src/pygambit/contrib}/games/pbride.efg (100%) rename {contrib => src/pygambit/contrib}/games/pd.nfg (100%) rename {contrib => src/pygambit/contrib}/games/perfect1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/perfect2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/perfect3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/poker.efg (100%) rename {contrib => src/pygambit/contrib}/games/poker.nfg (100%) rename {contrib => src/pygambit/contrib}/games/poker2.efg (100%) rename {contrib => src/pygambit/contrib}/games/pvw.efg (100%) rename {contrib => src/pygambit/contrib}/games/pvw2.efg (100%) rename {contrib => src/pygambit/contrib}/games/sh3.efg (100%) rename {contrib => src/pygambit/contrib}/games/sh3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/spence.efg (100%) rename {contrib => src/pygambit/contrib}/games/stengel.nfg (100%) rename {contrib => src/pygambit/contrib}/games/sww1.efg (100%) rename {contrib => src/pygambit/contrib}/games/sww1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/sww2.efg (100%) rename {contrib => src/pygambit/contrib}/games/sww3.efg (100%) rename {contrib => src/pygambit/contrib}/games/tim.efg (100%) rename {contrib => src/pygambit/contrib}/games/todd1.nfg (100%) rename {contrib => src/pygambit/contrib}/games/todd2.nfg (100%) rename {contrib => src/pygambit/contrib}/games/todd3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/ttt.efg (100%) rename {contrib => src/pygambit/contrib}/games/vd.efg (100%) rename {contrib => src/pygambit/contrib}/games/vd.nfg (100%) rename {contrib => src/pygambit/contrib}/games/w_ex1.efg (100%) rename {contrib => src/pygambit/contrib}/games/w_ex2.efg (100%) rename {contrib => src/pygambit/contrib}/games/wilson1.efg (100%) rename {contrib => src/pygambit/contrib}/games/wink3.nfg (100%) rename {contrib => src/pygambit/contrib}/games/winkels.nfg (100%) rename {contrib => src/pygambit/contrib}/games/work1.efg (100%) rename {contrib => src/pygambit/contrib}/games/work2.efg (100%) rename {contrib => src/pygambit/contrib}/games/work3.efg (100%) rename {contrib => src/pygambit/contrib}/games/yamamoto.nfg (100%) rename {contrib => src/pygambit/contrib}/games/zero.nfg (100%) diff --git a/contrib/games/2s2x2x2.efg b/src/pygambit/contrib/games/2s2x2x2.efg similarity index 100% rename from contrib/games/2s2x2x2.efg rename to src/pygambit/contrib/games/2s2x2x2.efg diff --git a/contrib/games/2smp.efg b/src/pygambit/contrib/games/2smp.efg similarity index 100% rename from contrib/games/2smp.efg rename to src/pygambit/contrib/games/2smp.efg diff --git a/contrib/games/2x2.agg b/src/pygambit/contrib/games/2x2.agg similarity index 100% rename from contrib/games/2x2.agg rename to src/pygambit/contrib/games/2x2.agg diff --git a/contrib/games/2x2.nfg b/src/pygambit/contrib/games/2x2.nfg similarity index 100% rename from contrib/games/2x2.nfg rename to src/pygambit/contrib/games/2x2.nfg diff --git a/contrib/games/2x2a.nfg b/src/pygambit/contrib/games/2x2a.nfg similarity index 100% rename from contrib/games/2x2a.nfg rename to src/pygambit/contrib/games/2x2a.nfg diff --git a/contrib/games/2x2const.nfg b/src/pygambit/contrib/games/2x2const.nfg similarity index 100% rename from contrib/games/2x2const.nfg rename to src/pygambit/contrib/games/2x2const.nfg diff --git a/contrib/games/2x2x2-nau.nfg b/src/pygambit/contrib/games/2x2x2-nau.nfg similarity index 100% rename from contrib/games/2x2x2-nau.nfg rename to src/pygambit/contrib/games/2x2x2-nau.nfg diff --git a/contrib/games/2x2x2.efg b/src/pygambit/contrib/games/2x2x2.efg similarity index 100% rename from contrib/games/2x2x2.efg rename to src/pygambit/contrib/games/2x2x2.efg diff --git a/contrib/games/2x2x2.nfg b/src/pygambit/contrib/games/2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2.nfg rename to src/pygambit/contrib/games/2x2x2.nfg diff --git a/contrib/games/2x2x2x2.nfg b/src/pygambit/contrib/games/2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2.nfg rename to src/pygambit/contrib/games/2x2x2x2.nfg diff --git a/contrib/games/2x2x2x2x2.nfg b/src/pygambit/contrib/games/2x2x2x2x2.nfg similarity index 100% rename from contrib/games/2x2x2x2x2.nfg rename to src/pygambit/contrib/games/2x2x2x2x2.nfg diff --git a/contrib/games/3x3x3.nfg b/src/pygambit/contrib/games/3x3x3.nfg similarity index 100% rename from contrib/games/3x3x3.nfg rename to src/pygambit/contrib/games/3x3x3.nfg diff --git a/contrib/games/4cards.efg b/src/pygambit/contrib/games/4cards.efg similarity index 100% rename from contrib/games/4cards.efg rename to src/pygambit/contrib/games/4cards.efg diff --git a/contrib/games/5x4x3.nfg b/src/pygambit/contrib/games/5x4x3.nfg similarity index 100% rename from contrib/games/5x4x3.nfg rename to src/pygambit/contrib/games/5x4x3.nfg diff --git a/contrib/games/8x2x2.nfg b/src/pygambit/contrib/games/8x2x2.nfg similarity index 100% rename from contrib/games/8x2x2.nfg rename to src/pygambit/contrib/games/8x2x2.nfg diff --git a/contrib/games/8x8.nfg b/src/pygambit/contrib/games/8x8.nfg similarity index 100% rename from contrib/games/8x8.nfg rename to src/pygambit/contrib/games/8x8.nfg diff --git a/contrib/games/BSS_S_085.Weighted.agg b/src/pygambit/contrib/games/BSS_S_085.Weighted.agg similarity index 100% rename from contrib/games/BSS_S_085.Weighted.agg rename to src/pygambit/contrib/games/BSS_S_085.Weighted.agg diff --git a/contrib/games/Bayesian-Coffee-3-2-2-3.bagg b/src/pygambit/contrib/games/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/contrib/games/Bayesian-Coffee-3-2-2-3.bagg diff --git a/contrib/games/GenRPS5.agg b/src/pygambit/contrib/games/GenRPS5.agg similarity index 100% rename from contrib/games/GenRPS5.agg rename to src/pygambit/contrib/games/GenRPS5.agg diff --git a/contrib/games/artist1.efg b/src/pygambit/contrib/games/artist1.efg similarity index 100% rename from contrib/games/artist1.efg rename to src/pygambit/contrib/games/artist1.efg diff --git a/contrib/games/artist2.efg b/src/pygambit/contrib/games/artist2.efg similarity index 100% rename from contrib/games/artist2.efg rename to src/pygambit/contrib/games/artist2.efg diff --git a/contrib/games/badgame1.efg b/src/pygambit/contrib/games/badgame1.efg similarity index 100% rename from contrib/games/badgame1.efg rename to src/pygambit/contrib/games/badgame1.efg diff --git a/contrib/games/badgame2.efg b/src/pygambit/contrib/games/badgame2.efg similarity index 100% rename from contrib/games/badgame2.efg rename to src/pygambit/contrib/games/badgame2.efg diff --git a/contrib/games/bagwell.efg b/src/pygambit/contrib/games/bagwell.efg similarity index 100% rename from contrib/games/bagwell.efg rename to src/pygambit/contrib/games/bagwell.efg diff --git a/contrib/games/bayes1a.efg b/src/pygambit/contrib/games/bayes1a.efg similarity index 100% rename from contrib/games/bayes1a.efg rename to src/pygambit/contrib/games/bayes1a.efg diff --git a/contrib/games/bayes2a.efg b/src/pygambit/contrib/games/bayes2a.efg similarity index 100% rename from contrib/games/bayes2a.efg rename to src/pygambit/contrib/games/bayes2a.efg diff --git a/contrib/games/bcp2.efg b/src/pygambit/contrib/games/bcp2.efg similarity index 100% rename from contrib/games/bcp2.efg rename to src/pygambit/contrib/games/bcp2.efg diff --git a/contrib/games/bcp3.efg b/src/pygambit/contrib/games/bcp3.efg similarity index 100% rename from contrib/games/bcp3.efg rename to src/pygambit/contrib/games/bcp3.efg diff --git a/contrib/games/bcp4.efg b/src/pygambit/contrib/games/bcp4.efg similarity index 100% rename from contrib/games/bcp4.efg rename to src/pygambit/contrib/games/bcp4.efg diff --git a/contrib/games/bhg1.efg b/src/pygambit/contrib/games/bhg1.efg similarity index 100% rename from contrib/games/bhg1.efg rename to src/pygambit/contrib/games/bhg1.efg diff --git a/contrib/games/bhg2.efg b/src/pygambit/contrib/games/bhg2.efg similarity index 100% rename from contrib/games/bhg2.efg rename to src/pygambit/contrib/games/bhg2.efg diff --git a/contrib/games/bhg3.efg b/src/pygambit/contrib/games/bhg3.efg similarity index 100% rename from contrib/games/bhg3.efg rename to src/pygambit/contrib/games/bhg3.efg diff --git a/contrib/games/bhg4.efg b/src/pygambit/contrib/games/bhg4.efg similarity index 100% rename from contrib/games/bhg4.efg rename to src/pygambit/contrib/games/bhg4.efg diff --git a/contrib/games/bhg5.efg b/src/pygambit/contrib/games/bhg5.efg similarity index 100% rename from contrib/games/bhg5.efg rename to src/pygambit/contrib/games/bhg5.efg diff --git a/contrib/games/caro2.efg b/src/pygambit/contrib/games/caro2.efg similarity index 100% rename from contrib/games/caro2.efg rename to src/pygambit/contrib/games/caro2.efg diff --git a/contrib/games/cent2.efg b/src/pygambit/contrib/games/cent2.efg similarity index 100% rename from contrib/games/cent2.efg rename to src/pygambit/contrib/games/cent2.efg diff --git a/contrib/games/cent2.nfg b/src/pygambit/contrib/games/cent2.nfg similarity index 100% rename from contrib/games/cent2.nfg rename to src/pygambit/contrib/games/cent2.nfg diff --git a/contrib/games/cent3.efg b/src/pygambit/contrib/games/cent3.efg similarity index 100% rename from contrib/games/cent3.efg rename to src/pygambit/contrib/games/cent3.efg diff --git a/contrib/games/cent4.efg b/src/pygambit/contrib/games/cent4.efg similarity index 100% rename from contrib/games/cent4.efg rename to src/pygambit/contrib/games/cent4.efg diff --git a/contrib/games/cent6.efg b/src/pygambit/contrib/games/cent6.efg similarity index 100% rename from contrib/games/cent6.efg rename to src/pygambit/contrib/games/cent6.efg diff --git a/contrib/games/centcs10.efg b/src/pygambit/contrib/games/centcs10.efg similarity index 100% rename from contrib/games/centcs10.efg rename to src/pygambit/contrib/games/centcs10.efg diff --git a/contrib/games/centcs6.efg b/src/pygambit/contrib/games/centcs6.efg similarity index 100% rename from contrib/games/centcs6.efg rename to src/pygambit/contrib/games/centcs6.efg diff --git a/contrib/games/condjury.efg b/src/pygambit/contrib/games/condjury.efg similarity index 100% rename from contrib/games/condjury.efg rename to src/pygambit/contrib/games/condjury.efg diff --git a/contrib/games/coord2.efg b/src/pygambit/contrib/games/coord2.efg similarity index 100% rename from contrib/games/coord2.efg rename to src/pygambit/contrib/games/coord2.efg diff --git a/contrib/games/coord2.nfg b/src/pygambit/contrib/games/coord2.nfg similarity index 100% rename from contrib/games/coord2.nfg rename to src/pygambit/contrib/games/coord2.nfg diff --git a/contrib/games/coord2ts.efg b/src/pygambit/contrib/games/coord2ts.efg similarity index 100% rename from contrib/games/coord2ts.efg rename to src/pygambit/contrib/games/coord2ts.efg diff --git a/contrib/games/coord3.efg b/src/pygambit/contrib/games/coord3.efg similarity index 100% rename from contrib/games/coord3.efg rename to src/pygambit/contrib/games/coord3.efg diff --git a/contrib/games/coord3.nfg b/src/pygambit/contrib/games/coord3.nfg similarity index 100% rename from contrib/games/coord3.nfg rename to src/pygambit/contrib/games/coord3.nfg diff --git a/contrib/games/coord333.nfg b/src/pygambit/contrib/games/coord333.nfg similarity index 100% rename from contrib/games/coord333.nfg rename to src/pygambit/contrib/games/coord333.nfg diff --git a/contrib/games/coord4.efg b/src/pygambit/contrib/games/coord4.efg similarity index 100% rename from contrib/games/coord4.efg rename to src/pygambit/contrib/games/coord4.efg diff --git a/contrib/games/coord4.nfg b/src/pygambit/contrib/games/coord4.nfg similarity index 100% rename from contrib/games/coord4.nfg rename to src/pygambit/contrib/games/coord4.nfg diff --git a/contrib/games/cross.efg b/src/pygambit/contrib/games/cross.efg similarity index 100% rename from contrib/games/cross.efg rename to src/pygambit/contrib/games/cross.efg diff --git a/contrib/games/cs.efg b/src/pygambit/contrib/games/cs.efg similarity index 100% rename from contrib/games/cs.efg rename to src/pygambit/contrib/games/cs.efg diff --git a/contrib/games/csg1.nfg b/src/pygambit/contrib/games/csg1.nfg similarity index 100% rename from contrib/games/csg1.nfg rename to src/pygambit/contrib/games/csg1.nfg diff --git a/contrib/games/csg2.nfg b/src/pygambit/contrib/games/csg2.nfg similarity index 100% rename from contrib/games/csg2.nfg rename to src/pygambit/contrib/games/csg2.nfg diff --git a/contrib/games/csg3.nfg b/src/pygambit/contrib/games/csg3.nfg similarity index 100% rename from contrib/games/csg3.nfg rename to src/pygambit/contrib/games/csg3.nfg diff --git a/contrib/games/csg4.nfg b/src/pygambit/contrib/games/csg4.nfg similarity index 100% rename from contrib/games/csg4.nfg rename to src/pygambit/contrib/games/csg4.nfg diff --git a/contrib/games/deg1.nfg b/src/pygambit/contrib/games/deg1.nfg similarity index 100% rename from contrib/games/deg1.nfg rename to src/pygambit/contrib/games/deg1.nfg diff --git a/contrib/games/deg2.nfg b/src/pygambit/contrib/games/deg2.nfg similarity index 100% rename from contrib/games/deg2.nfg rename to src/pygambit/contrib/games/deg2.nfg diff --git a/contrib/games/e01.efg b/src/pygambit/contrib/games/e01.efg similarity index 100% rename from contrib/games/e01.efg rename to src/pygambit/contrib/games/e01.efg diff --git a/contrib/games/e01.nfg b/src/pygambit/contrib/games/e01.nfg similarity index 100% rename from contrib/games/e01.nfg rename to src/pygambit/contrib/games/e01.nfg diff --git a/contrib/games/e02.efg b/src/pygambit/contrib/games/e02.efg similarity index 100% rename from contrib/games/e02.efg rename to src/pygambit/contrib/games/e02.efg diff --git a/contrib/games/e02.nfg b/src/pygambit/contrib/games/e02.nfg similarity index 100% rename from contrib/games/e02.nfg rename to src/pygambit/contrib/games/e02.nfg diff --git a/contrib/games/e03.efg b/src/pygambit/contrib/games/e03.efg similarity index 100% rename from contrib/games/e03.efg rename to src/pygambit/contrib/games/e03.efg diff --git a/contrib/games/e04.efg b/src/pygambit/contrib/games/e04.efg similarity index 100% rename from contrib/games/e04.efg rename to src/pygambit/contrib/games/e04.efg diff --git a/contrib/games/e04.nfg b/src/pygambit/contrib/games/e04.nfg similarity index 100% rename from contrib/games/e04.nfg rename to src/pygambit/contrib/games/e04.nfg diff --git a/contrib/games/e05.efg b/src/pygambit/contrib/games/e05.efg similarity index 100% rename from contrib/games/e05.efg rename to src/pygambit/contrib/games/e05.efg diff --git a/contrib/games/e06.efg b/src/pygambit/contrib/games/e06.efg similarity index 100% rename from contrib/games/e06.efg rename to src/pygambit/contrib/games/e06.efg diff --git a/contrib/games/e07.efg b/src/pygambit/contrib/games/e07.efg similarity index 100% rename from contrib/games/e07.efg rename to src/pygambit/contrib/games/e07.efg diff --git a/contrib/games/e07.nfg b/src/pygambit/contrib/games/e07.nfg similarity index 100% rename from contrib/games/e07.nfg rename to src/pygambit/contrib/games/e07.nfg diff --git a/contrib/games/e08.efg b/src/pygambit/contrib/games/e08.efg similarity index 100% rename from contrib/games/e08.efg rename to src/pygambit/contrib/games/e08.efg diff --git a/contrib/games/e09.efg b/src/pygambit/contrib/games/e09.efg similarity index 100% rename from contrib/games/e09.efg rename to src/pygambit/contrib/games/e09.efg diff --git a/contrib/games/e10.efg b/src/pygambit/contrib/games/e10.efg similarity index 100% rename from contrib/games/e10.efg rename to src/pygambit/contrib/games/e10.efg diff --git a/contrib/games/e10a.efg b/src/pygambit/contrib/games/e10a.efg similarity index 100% rename from contrib/games/e10a.efg rename to src/pygambit/contrib/games/e10a.efg diff --git a/contrib/games/e13.efg b/src/pygambit/contrib/games/e13.efg similarity index 100% rename from contrib/games/e13.efg rename to src/pygambit/contrib/games/e13.efg diff --git a/contrib/games/e16.efg b/src/pygambit/contrib/games/e16.efg similarity index 100% rename from contrib/games/e16.efg rename to src/pygambit/contrib/games/e16.efg diff --git a/contrib/games/e17.efg b/src/pygambit/contrib/games/e17.efg similarity index 100% rename from contrib/games/e17.efg rename to src/pygambit/contrib/games/e17.efg diff --git a/contrib/games/e18.efg b/src/pygambit/contrib/games/e18.efg similarity index 100% rename from contrib/games/e18.efg rename to src/pygambit/contrib/games/e18.efg diff --git a/contrib/games/g1.efg b/src/pygambit/contrib/games/g1.efg similarity index 100% rename from contrib/games/g1.efg rename to src/pygambit/contrib/games/g1.efg diff --git a/contrib/games/g1.nfg b/src/pygambit/contrib/games/g1.nfg similarity index 100% rename from contrib/games/g1.nfg rename to src/pygambit/contrib/games/g1.nfg diff --git a/contrib/games/g2.efg b/src/pygambit/contrib/games/g2.efg similarity index 100% rename from contrib/games/g2.efg rename to src/pygambit/contrib/games/g2.efg diff --git a/contrib/games/g2.nfg b/src/pygambit/contrib/games/g2.nfg similarity index 100% rename from contrib/games/g2.nfg rename to src/pygambit/contrib/games/g2.nfg diff --git a/contrib/games/g3.efg b/src/pygambit/contrib/games/g3.efg similarity index 100% rename from contrib/games/g3.efg rename to src/pygambit/contrib/games/g3.efg diff --git a/contrib/games/g3.nfg b/src/pygambit/contrib/games/g3.nfg similarity index 100% rename from contrib/games/g3.nfg rename to src/pygambit/contrib/games/g3.nfg diff --git a/contrib/games/holdout.efg b/src/pygambit/contrib/games/holdout.efg similarity index 100% rename from contrib/games/holdout.efg rename to src/pygambit/contrib/games/holdout.efg diff --git a/contrib/games/holdout7.efg b/src/pygambit/contrib/games/holdout7.efg similarity index 100% rename from contrib/games/holdout7.efg rename to src/pygambit/contrib/games/holdout7.efg diff --git a/contrib/games/hs1.efg b/src/pygambit/contrib/games/hs1.efg similarity index 100% rename from contrib/games/hs1.efg rename to src/pygambit/contrib/games/hs1.efg diff --git a/contrib/games/jury_mr.efg b/src/pygambit/contrib/games/jury_mr.efg similarity index 100% rename from contrib/games/jury_mr.efg rename to src/pygambit/contrib/games/jury_mr.efg diff --git a/contrib/games/jury_un.efg b/src/pygambit/contrib/games/jury_un.efg similarity index 100% rename from contrib/games/jury_un.efg rename to src/pygambit/contrib/games/jury_un.efg diff --git a/contrib/games/km1.efg b/src/pygambit/contrib/games/km1.efg similarity index 100% rename from contrib/games/km1.efg rename to src/pygambit/contrib/games/km1.efg diff --git a/contrib/games/km2.efg b/src/pygambit/contrib/games/km2.efg similarity index 100% rename from contrib/games/km2.efg rename to src/pygambit/contrib/games/km2.efg diff --git a/contrib/games/km3.efg b/src/pygambit/contrib/games/km3.efg similarity index 100% rename from contrib/games/km3.efg rename to src/pygambit/contrib/games/km3.efg diff --git a/contrib/games/km6.efg b/src/pygambit/contrib/games/km6.efg similarity index 100% rename from contrib/games/km6.efg rename to src/pygambit/contrib/games/km6.efg diff --git a/contrib/games/loopback.nfg b/src/pygambit/contrib/games/loopback.nfg similarity index 100% rename from contrib/games/loopback.nfg rename to src/pygambit/contrib/games/loopback.nfg diff --git a/contrib/games/mixdom.nfg b/src/pygambit/contrib/games/mixdom.nfg similarity index 100% rename from contrib/games/mixdom.nfg rename to src/pygambit/contrib/games/mixdom.nfg diff --git a/contrib/games/mixdom2.nfg b/src/pygambit/contrib/games/mixdom2.nfg similarity index 100% rename from contrib/games/mixdom2.nfg rename to src/pygambit/contrib/games/mixdom2.nfg diff --git a/contrib/games/montyhal.efg b/src/pygambit/contrib/games/montyhal.efg similarity index 100% rename from contrib/games/montyhal.efg rename to src/pygambit/contrib/games/montyhal.efg diff --git a/contrib/games/my_2-1.efg b/src/pygambit/contrib/games/my_2-1.efg similarity index 100% rename from contrib/games/my_2-1.efg rename to src/pygambit/contrib/games/my_2-1.efg diff --git a/contrib/games/my_2-4.efg b/src/pygambit/contrib/games/my_2-4.efg similarity index 100% rename from contrib/games/my_2-4.efg rename to src/pygambit/contrib/games/my_2-4.efg diff --git a/contrib/games/my_2-8.efg b/src/pygambit/contrib/games/my_2-8.efg similarity index 100% rename from contrib/games/my_2-8.efg rename to src/pygambit/contrib/games/my_2-8.efg diff --git a/contrib/games/my_3-3a.efg b/src/pygambit/contrib/games/my_3-3a.efg similarity index 100% rename from contrib/games/my_3-3a.efg rename to src/pygambit/contrib/games/my_3-3a.efg diff --git a/contrib/games/my_3-3b.efg b/src/pygambit/contrib/games/my_3-3b.efg similarity index 100% rename from contrib/games/my_3-3b.efg rename to src/pygambit/contrib/games/my_3-3b.efg diff --git a/contrib/games/my_3-3c.efg b/src/pygambit/contrib/games/my_3-3c.efg similarity index 100% rename from contrib/games/my_3-3c.efg rename to src/pygambit/contrib/games/my_3-3c.efg diff --git a/contrib/games/my_3-3d.efg b/src/pygambit/contrib/games/my_3-3d.efg similarity index 100% rename from contrib/games/my_3-3d.efg rename to src/pygambit/contrib/games/my_3-3d.efg diff --git a/contrib/games/my_3-3e.efg b/src/pygambit/contrib/games/my_3-3e.efg similarity index 100% rename from contrib/games/my_3-3e.efg rename to src/pygambit/contrib/games/my_3-3e.efg diff --git a/contrib/games/my_3-4.efg b/src/pygambit/contrib/games/my_3-4.efg similarity index 100% rename from contrib/games/my_3-4.efg rename to src/pygambit/contrib/games/my_3-4.efg diff --git a/contrib/games/myerson.efg b/src/pygambit/contrib/games/myerson.efg similarity index 100% rename from contrib/games/myerson.efg rename to src/pygambit/contrib/games/myerson.efg diff --git a/contrib/games/myerson_fig_4_2.efg b/src/pygambit/contrib/games/myerson_fig_4_2.efg similarity index 100% rename from contrib/games/myerson_fig_4_2.efg rename to src/pygambit/contrib/games/myerson_fig_4_2.efg diff --git a/contrib/games/nim.efg b/src/pygambit/contrib/games/nim.efg similarity index 100% rename from contrib/games/nim.efg rename to src/pygambit/contrib/games/nim.efg diff --git a/contrib/games/nim7.efg b/src/pygambit/contrib/games/nim7.efg similarity index 100% rename from contrib/games/nim7.efg rename to src/pygambit/contrib/games/nim7.efg diff --git a/contrib/games/oneill.nfg b/src/pygambit/contrib/games/oneill.nfg similarity index 100% rename from contrib/games/oneill.nfg rename to src/pygambit/contrib/games/oneill.nfg diff --git a/contrib/games/palf.efg b/src/pygambit/contrib/games/palf.efg similarity index 100% rename from contrib/games/palf.efg rename to src/pygambit/contrib/games/palf.efg diff --git a/contrib/games/palf2.efg b/src/pygambit/contrib/games/palf2.efg similarity index 100% rename from contrib/games/palf2.efg rename to src/pygambit/contrib/games/palf2.efg diff --git a/contrib/games/palf3.efg b/src/pygambit/contrib/games/palf3.efg similarity index 100% rename from contrib/games/palf3.efg rename to src/pygambit/contrib/games/palf3.efg diff --git a/contrib/games/pbride.efg b/src/pygambit/contrib/games/pbride.efg similarity index 100% rename from contrib/games/pbride.efg rename to src/pygambit/contrib/games/pbride.efg diff --git a/contrib/games/pd.nfg b/src/pygambit/contrib/games/pd.nfg similarity index 100% rename from contrib/games/pd.nfg rename to src/pygambit/contrib/games/pd.nfg diff --git a/contrib/games/perfect1.nfg b/src/pygambit/contrib/games/perfect1.nfg similarity index 100% rename from contrib/games/perfect1.nfg rename to src/pygambit/contrib/games/perfect1.nfg diff --git a/contrib/games/perfect2.nfg b/src/pygambit/contrib/games/perfect2.nfg similarity index 100% rename from contrib/games/perfect2.nfg rename to src/pygambit/contrib/games/perfect2.nfg diff --git a/contrib/games/perfect3.nfg b/src/pygambit/contrib/games/perfect3.nfg similarity index 100% rename from contrib/games/perfect3.nfg rename to src/pygambit/contrib/games/perfect3.nfg diff --git a/contrib/games/poker.efg b/src/pygambit/contrib/games/poker.efg similarity index 100% rename from contrib/games/poker.efg rename to src/pygambit/contrib/games/poker.efg diff --git a/contrib/games/poker.nfg b/src/pygambit/contrib/games/poker.nfg similarity index 100% rename from contrib/games/poker.nfg rename to src/pygambit/contrib/games/poker.nfg diff --git a/contrib/games/poker2.efg b/src/pygambit/contrib/games/poker2.efg similarity index 100% rename from contrib/games/poker2.efg rename to src/pygambit/contrib/games/poker2.efg diff --git a/contrib/games/pvw.efg b/src/pygambit/contrib/games/pvw.efg similarity index 100% rename from contrib/games/pvw.efg rename to src/pygambit/contrib/games/pvw.efg diff --git a/contrib/games/pvw2.efg b/src/pygambit/contrib/games/pvw2.efg similarity index 100% rename from contrib/games/pvw2.efg rename to src/pygambit/contrib/games/pvw2.efg diff --git a/contrib/games/sh3.efg b/src/pygambit/contrib/games/sh3.efg similarity index 100% rename from contrib/games/sh3.efg rename to src/pygambit/contrib/games/sh3.efg diff --git a/contrib/games/sh3.nfg b/src/pygambit/contrib/games/sh3.nfg similarity index 100% rename from contrib/games/sh3.nfg rename to src/pygambit/contrib/games/sh3.nfg diff --git a/contrib/games/spence.efg b/src/pygambit/contrib/games/spence.efg similarity index 100% rename from contrib/games/spence.efg rename to src/pygambit/contrib/games/spence.efg diff --git a/contrib/games/stengel.nfg b/src/pygambit/contrib/games/stengel.nfg similarity index 100% rename from contrib/games/stengel.nfg rename to src/pygambit/contrib/games/stengel.nfg diff --git a/contrib/games/sww1.efg b/src/pygambit/contrib/games/sww1.efg similarity index 100% rename from contrib/games/sww1.efg rename to src/pygambit/contrib/games/sww1.efg diff --git a/contrib/games/sww1.nfg b/src/pygambit/contrib/games/sww1.nfg similarity index 100% rename from contrib/games/sww1.nfg rename to src/pygambit/contrib/games/sww1.nfg diff --git a/contrib/games/sww2.efg b/src/pygambit/contrib/games/sww2.efg similarity index 100% rename from contrib/games/sww2.efg rename to src/pygambit/contrib/games/sww2.efg diff --git a/contrib/games/sww3.efg b/src/pygambit/contrib/games/sww3.efg similarity index 100% rename from contrib/games/sww3.efg rename to src/pygambit/contrib/games/sww3.efg diff --git a/contrib/games/tim.efg b/src/pygambit/contrib/games/tim.efg similarity index 100% rename from contrib/games/tim.efg rename to src/pygambit/contrib/games/tim.efg diff --git a/contrib/games/todd1.nfg b/src/pygambit/contrib/games/todd1.nfg similarity index 100% rename from contrib/games/todd1.nfg rename to src/pygambit/contrib/games/todd1.nfg diff --git a/contrib/games/todd2.nfg b/src/pygambit/contrib/games/todd2.nfg similarity index 100% rename from contrib/games/todd2.nfg rename to src/pygambit/contrib/games/todd2.nfg diff --git a/contrib/games/todd3.nfg b/src/pygambit/contrib/games/todd3.nfg similarity index 100% rename from contrib/games/todd3.nfg rename to src/pygambit/contrib/games/todd3.nfg diff --git a/contrib/games/ttt.efg b/src/pygambit/contrib/games/ttt.efg similarity index 100% rename from contrib/games/ttt.efg rename to src/pygambit/contrib/games/ttt.efg diff --git a/contrib/games/vd.efg b/src/pygambit/contrib/games/vd.efg similarity index 100% rename from contrib/games/vd.efg rename to src/pygambit/contrib/games/vd.efg diff --git a/contrib/games/vd.nfg b/src/pygambit/contrib/games/vd.nfg similarity index 100% rename from contrib/games/vd.nfg rename to src/pygambit/contrib/games/vd.nfg diff --git a/contrib/games/w_ex1.efg b/src/pygambit/contrib/games/w_ex1.efg similarity index 100% rename from contrib/games/w_ex1.efg rename to src/pygambit/contrib/games/w_ex1.efg diff --git a/contrib/games/w_ex2.efg b/src/pygambit/contrib/games/w_ex2.efg similarity index 100% rename from contrib/games/w_ex2.efg rename to src/pygambit/contrib/games/w_ex2.efg diff --git a/contrib/games/wilson1.efg b/src/pygambit/contrib/games/wilson1.efg similarity index 100% rename from contrib/games/wilson1.efg rename to src/pygambit/contrib/games/wilson1.efg diff --git a/contrib/games/wink3.nfg b/src/pygambit/contrib/games/wink3.nfg similarity index 100% rename from contrib/games/wink3.nfg rename to src/pygambit/contrib/games/wink3.nfg diff --git a/contrib/games/winkels.nfg b/src/pygambit/contrib/games/winkels.nfg similarity index 100% rename from contrib/games/winkels.nfg rename to src/pygambit/contrib/games/winkels.nfg diff --git a/contrib/games/work1.efg b/src/pygambit/contrib/games/work1.efg similarity index 100% rename from contrib/games/work1.efg rename to src/pygambit/contrib/games/work1.efg diff --git a/contrib/games/work2.efg b/src/pygambit/contrib/games/work2.efg similarity index 100% rename from contrib/games/work2.efg rename to src/pygambit/contrib/games/work2.efg diff --git a/contrib/games/work3.efg b/src/pygambit/contrib/games/work3.efg similarity index 100% rename from contrib/games/work3.efg rename to src/pygambit/contrib/games/work3.efg diff --git a/contrib/games/yamamoto.nfg b/src/pygambit/contrib/games/yamamoto.nfg similarity index 100% rename from contrib/games/yamamoto.nfg rename to src/pygambit/contrib/games/yamamoto.nfg diff --git a/contrib/games/zero.nfg b/src/pygambit/contrib/games/zero.nfg similarity index 100% rename from contrib/games/zero.nfg rename to src/pygambit/contrib/games/zero.nfg From 8283da3eaa9baa81aa797aebe682038060fd1a2f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 17:12:35 +0000 Subject: [PATCH 132/170] update gamefile paths in Makefile.am --- Makefile.am | 280 ++++++++++++++++++++++++++-------------------------- 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/Makefile.am b/Makefile.am index fe3fc232f..410297f2e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -100,146 +100,146 @@ 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/pygambit/contrib/games/2s2x2x2.efg \ + src/pygambit/contrib/games/2smp.efg \ + src/pygambit/contrib/games/2x2x2.efg \ + src/pygambit/contrib/games/4cards.efg \ + src/pygambit/contrib/games/artist1.efg \ + src/pygambit/contrib/games/artist2.efg \ + src/pygambit/contrib/games/badgame1.efg \ + src/pygambit/contrib/games/badgame2.efg \ + src/pygambit/contrib/games/bayes1a.efg \ + src/pygambit/contrib/games/bayes2a.efg \ + src/pygambit/contrib/games/bcp2.efg \ + src/pygambit/contrib/games/bcp3.efg \ + src/pygambit/contrib/games/bcp4.efg \ + src/pygambit/contrib/games/bhg1.efg \ + src/pygambit/contrib/games/bhg2.efg \ + src/pygambit/contrib/games/bhg3.efg \ + src/pygambit/contrib/games/bhg4.efg \ + src/pygambit/contrib/games/bhg5.efg \ + src/pygambit/contrib/games/caro2.efg \ + src/pygambit/contrib/games/cent2.efg \ + src/pygambit/contrib/games/cent3.efg \ + src/pygambit/contrib/games/cent4.efg \ + src/pygambit/contrib/games/cent6.efg \ + src/pygambit/contrib/games/centcs10.efg \ + src/pygambit/contrib/games/centcs6.efg \ + src/pygambit/contrib/games/condjury.efg \ + src/pygambit/contrib/games/coord2.efg \ + src/pygambit/contrib/games/coord2ts.efg \ + src/pygambit/contrib/games/coord3.efg \ + src/pygambit/contrib/games/coord4.efg \ + src/pygambit/contrib/games/cross.efg \ + src/pygambit/contrib/games/cs.efg \ + src/pygambit/contrib/games/e01.efg \ + src/pygambit/contrib/games/e02.efg \ + src/pygambit/contrib/games/e03.efg \ + src/pygambit/contrib/games/e04.efg \ + src/pygambit/contrib/games/e05.efg \ + src/pygambit/contrib/games/e06.efg \ + src/pygambit/contrib/games/e07.efg \ + src/pygambit/contrib/games/e08.efg \ + src/pygambit/contrib/games/e09.efg \ + src/pygambit/contrib/games/e10a.efg \ + src/pygambit/contrib/games/e10.efg \ + src/pygambit/contrib/games/e13.efg \ + src/pygambit/contrib/games/e16.efg \ + src/pygambit/contrib/games/e17.efg \ + src/pygambit/contrib/games/e18.efg \ + src/pygambit/contrib/games/g1.efg \ + src/pygambit/contrib/games/g2.efg \ + src/pygambit/contrib/games/g3.efg \ + src/pygambit/contrib/games/holdout7.efg \ + src/pygambit/contrib/games/holdout.efg \ + src/pygambit/contrib/games/hs1.efg \ + src/pygambit/contrib/games/jury_mr.efg \ + src/pygambit/contrib/games/jury_un.efg \ + src/pygambit/contrib/games/km1.efg \ + src/pygambit/contrib/games/km2.efg \ + src/pygambit/contrib/games/km3.efg \ + src/pygambit/contrib/games/km6.efg \ + src/pygambit/contrib/games/montyhal.efg \ + src/pygambit/contrib/games/my_2-1.efg \ + src/pygambit/contrib/games/my_2-4.efg \ + src/pygambit/contrib/games/my_2-8.efg \ + src/pygambit/contrib/games/my_3-3a.efg \ + src/pygambit/contrib/games/my_3-3b.efg \ + src/pygambit/contrib/games/my_3-3c.efg \ + src/pygambit/contrib/games/my_3-3d.efg \ + src/pygambit/contrib/games/my_3-3e.efg \ + src/pygambit/contrib/games/my_3-4.efg \ + src/pygambit/contrib/games/myerson.efg \ + src/pygambit/contrib/games/nim7.efg \ + src/pygambit/contrib/games/nim.efg \ + src/pygambit/contrib/games/palf2.efg \ + src/pygambit/contrib/games/palf3.efg \ + src/pygambit/contrib/games/palf.efg \ + src/pygambit/contrib/games/poker2.efg \ + src/pygambit/contrib/games/poker.efg \ + src/pygambit/contrib/games/pvw2.efg \ + src/pygambit/contrib/games/pvw.efg \ + src/pygambit/contrib/games/sh3.efg \ + src/pygambit/contrib/games/sww1.efg \ + src/pygambit/contrib/games/sww2.efg \ + src/pygambit/contrib/games/sww3.efg \ + src/pygambit/contrib/games/tim.efg \ + src/pygambit/contrib/games/ttt.efg \ + src/pygambit/contrib/games/vd.efg \ + src/pygambit/contrib/games/w_ex1.efg \ + src/pygambit/contrib/games/w_ex2.efg \ + src/pygambit/contrib/games/wilson1.efg \ + src/pygambit/contrib/games/work1.efg \ + src/pygambit/contrib/games/work2.efg \ + src/pygambit/contrib/games/work3.efg \ + src/pygambit/contrib/games/2x2a.nfg \ + src/pygambit/contrib/games/2x2const.nfg \ + src/pygambit/contrib/games/2x2.nfg \ + src/pygambit/contrib/games/2x2x2.nfg \ + src/pygambit/contrib/games/2x2x2x2.nfg \ + src/pygambit/contrib/games/2x2x2x2x2.nfg \ + src/pygambit/contrib/games/3x3x3.nfg \ + src/pygambit/contrib/games/5x4x3.nfg \ + src/pygambit/contrib/games/8x2x2.nfg \ + src/pygambit/contrib/games/8x8.nfg \ + src/pygambit/contrib/games/cent2.nfg \ + src/pygambit/contrib/games/coord2.nfg \ + src/pygambit/contrib/games/coord333.nfg \ + src/pygambit/contrib/games/coord3.nfg \ + src/pygambit/contrib/games/coord4.nfg \ + src/pygambit/contrib/games/csg1.nfg \ + src/pygambit/contrib/games/csg2.nfg \ + src/pygambit/contrib/games/csg3.nfg \ + src/pygambit/contrib/games/csg4.nfg \ + src/pygambit/contrib/games/deg1.nfg \ + src/pygambit/contrib/games/deg2.nfg \ + src/pygambit/contrib/games/e01.nfg \ + src/pygambit/contrib/games/e02.nfg \ + src/pygambit/contrib/games/e04.nfg \ + src/pygambit/contrib/games/e07.nfg \ + src/pygambit/contrib/games/g1.nfg \ + src/pygambit/contrib/games/g2.nfg \ + src/pygambit/contrib/games/g3.nfg \ + src/pygambit/contrib/games/loopback.nfg \ + src/pygambit/contrib/games/mixdom2.nfg \ + src/pygambit/contrib/games/mixdom.nfg \ + src/pygambit/contrib/games/oneill.nfg \ + src/pygambit/contrib/games/pd.nfg \ + src/pygambit/contrib/games/perfect1.nfg \ + src/pygambit/contrib/games/perfect2.nfg \ + src/pygambit/contrib/games/perfect3.nfg \ + src/pygambit/contrib/games/poker.nfg \ + src/pygambit/contrib/games/sh3.nfg \ + src/pygambit/contrib/games/stengel.nfg \ + src/pygambit/contrib/games/sww1.nfg \ + src/pygambit/contrib/games/todd1.nfg \ + src/pygambit/contrib/games/todd2.nfg \ + src/pygambit/contrib/games/todd3.nfg \ + src/pygambit/contrib/games/vd.nfg \ + src/pygambit/contrib/games/wink3.nfg \ + src/pygambit/contrib/games/winkels.nfg \ + src/pygambit/contrib/games/yamamoto.nfg \ + src/pygambit/contrib/games/zero.nfg \ src/README.rst core_SOURCES = \ From d16557ce89acec47ff4b20fd637eec23b2674e04 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Wed, 21 Jan 2026 17:21:10 +0000 Subject: [PATCH 133/170] remove pointless init file --- src/pygambit/catalog_dir/__init__.py | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 src/pygambit/catalog_dir/__init__.py diff --git a/src/pygambit/catalog_dir/__init__.py b/src/pygambit/catalog_dir/__init__.py deleted file mode 100644 index 583f12e1b..000000000 --- a/src/pygambit/catalog_dir/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# from .catalog import CatalogGame, games - -# __all__ = ["games", "CatalogGame"] - - -# def __getattr__(name: str): -# """Lazy load catalog games on access.""" -# from . import catalog as _catalog_module -# return getattr(_catalog_module, name) From 7d80f3548298bd3f3a40268acad342c89e446219 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 10:19:07 +0000 Subject: [PATCH 134/170] move manually coded games to catalog_games module --- src/pygambit/__init__.py | 6 ++++ src/pygambit/catalog.py | 64 ++++++----------------------------- src/pygambit/catalog_games.py | 32 ++++++++++++++++++ src/pygambit/coded_games.py | 32 ------------------ 4 files changed, 48 insertions(+), 86 deletions(-) create mode 100644 src/pygambit/catalog_games.py delete mode 100644 src/pygambit/coded_games.py diff --git a/src/pygambit/__init__.py b/src/pygambit/__init__.py index 5f9fb08cc..422a21439 100644 --- a/src/pygambit/__init__.py +++ b/src/pygambit/__init__.py @@ -27,10 +27,16 @@ 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 index 1c79d474c..e945215ec 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -295,62 +295,18 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: setattr(module, class_name, cls) -# _coded_games_loaded = False - - -# def _load_coded_games(): -# """Lazy load coded games.""" -# global _coded_games_loaded -# if _coded_games_loaded: -# return - -# from . import coded_games # noqa: F401 -# # Import all coded game classes into this module's namespace -# # so they are registered as CatalogGame subclasses -# for name in dir(coded_games): -# if not name.startswith("_"): -# obj = getattr(coded_games, name) -# if isinstance(obj, type) and issubclass(obj, CatalogGame): -# globals()[name] = obj - -# _coded_games_loaded = True +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_contrib_game_classes(_catalog_data) -# _load_coded_games() - -# Temporarily include coded games here - - -class OneShotTrust(CatalogGame): - """ - 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. - """ - - test_suite = True - """This game is included in the pygambit test suite.""" - - @staticmethod - def _game(unique_NE_variant: bool = False): - g = gbt.Game.new_tree( - players=["Buyer", "Seller"], title="One-shot trust game, after 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_games.py b/src/pygambit/catalog_games.py new file mode 100644 index 000000000..eb1b2ca40 --- /dev/null +++ b/src/pygambit/catalog_games.py @@ -0,0 +1,32 @@ +import pygambit as gbt + + +class OneShotTrust(gbt.catalog.CatalogGame): + """ + 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. + """ + test_suite = True + """This game is included in the pygambit test suite.""" + + @staticmethod + def _game(unique_NE_variant: bool = False): + g = gbt.Game.new_tree( + players=["Buyer", "Seller"], title="One-shot trust game, after 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/coded_games.py b/src/pygambit/coded_games.py deleted file mode 100644 index 82771ba5f..000000000 --- a/src/pygambit/coded_games.py +++ /dev/null @@ -1,32 +0,0 @@ -# import pygambit as gbt - - -# class OneShotTrust(gbt.catalog.CatalogGame): -# """ -# 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. -# """ -# test_suite = True -# """This game is included in the pygambit test suite.""" - -# @staticmethod -# def _game(unique_NE_variant: bool = False): -# g = gbt.Game.new_tree( -# players=["Buyer", "Seller"], title="One-shot trust game, after 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 From 2560e23dc4a6c91175979b02bac0fd8e58261b11 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 10:37:57 +0000 Subject: [PATCH 135/170] fix path to contrib/games --- src/pygambit/update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/update.py b/src/pygambit/update.py index bb1f646a7..837558131 100644 --- a/src/pygambit/update.py +++ b/src/pygambit/update.py @@ -8,7 +8,7 @@ import pygambit as gbt _CATALOG_YAML = Path(__file__).parent / "catalog.yml" -_GAMEFILES_DIR = Path(__file__).parent.parent.parent / "contrib/games" +_GAMEFILES_DIR = Path(__file__).parent / "contrib/games" _API_RST = Path(__file__).parent.parent.parent / "doc/pygambit.api.rst" From f0d2246cdd4821b980054c211f6e7b4809c78562 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 10:43:02 +0000 Subject: [PATCH 136/170] Ensure update script updates RST with new games from catalog.yml --- src/pygambit/update.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/pygambit/update.py b/src/pygambit/update.py index 837558131..c0e38fb19 100644 --- a/src/pygambit/update.py +++ b/src/pygambit/update.py @@ -30,9 +30,13 @@ def make_class_name(filename: str) -> str: return name -def update_api_rst() -> None: +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() @@ -121,7 +125,7 @@ def update_api_rst() -> None: # Update the RST documentation with the new full catalog # This includes games from coded_games.py as well as catalog.yml - update_api_rst() + update_api_rst(new_classes=list(new_entries.keys())) print(f"Added {new_entries_counter} new entries to the catalog") print(f"Output written to: {_CATALOG_YAML}") From e0da34d9da18089ce9d89c9c9481b9b0f4058f93 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 10:45:19 +0000 Subject: [PATCH 137/170] rename update.py to catalog_update.py --- src/pygambit/{update.py => catalog_update.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/pygambit/{update.py => catalog_update.py} (100%) diff --git a/src/pygambit/update.py b/src/pygambit/catalog_update.py similarity index 100% rename from src/pygambit/update.py rename to src/pygambit/catalog_update.py From bf75bc7ecbb2af33bfdb52404ca2c01806a76867 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 10:49:06 +0000 Subject: [PATCH 138/170] update print statements in catalog_update.py --- src/pygambit/catalog_update.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pygambit/catalog_update.py b/src/pygambit/catalog_update.py index c0e38fb19..4b19e9f2a 100644 --- a/src/pygambit/catalog_update.py +++ b/src/pygambit/catalog_update.py @@ -77,8 +77,6 @@ def update_api_rst(new_classes: list) -> None: with open(_API_RST, "w", encoding="utf-8") as f: f.write(new_content) - print(f"Updated {_API_RST} with new catalog game names") - if __name__ == "__main__": # Use ruamel.yaml to preserve comments @@ -128,5 +126,7 @@ def update_api_rst(new_classes: list) -> None: update_api_rst(new_classes=list(new_entries.keys())) print(f"Added {new_entries_counter} new entries to the catalog") - print(f"Output written to: {_CATALOG_YAML}") + if new_entries_counter > 0: + print(f"Updated: {_CATALOG_YAML}") + print(f"Updated {_API_RST}") print("Done.") From cf6758548e77b8677a139df1068bc410f6745c0f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 10:54:24 +0000 Subject: [PATCH 139/170] print which catalog games were added --- src/pygambit/catalog_update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/catalog_update.py b/src/pygambit/catalog_update.py index 4b19e9f2a..f0670bdf1 100644 --- a/src/pygambit/catalog_update.py +++ b/src/pygambit/catalog_update.py @@ -125,7 +125,7 @@ def update_api_rst(new_classes: list) -> None: # This includes games from coded_games.py as well as catalog.yml update_api_rst(new_classes=list(new_entries.keys())) - print(f"Added {new_entries_counter} new entries to the catalog") + 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}") print(f"Updated {_API_RST}") From c5f2a50edff7f61096c3490c98cd5932dbd528d1 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 11:47:42 +0000 Subject: [PATCH 140/170] use catalog_update.py to also update Makefile.am with game files --- Makefile.am | 147 +++++++++++++-------------------- src/pygambit/catalog_update.py | 43 ++++++++++ 2 files changed, 102 insertions(+), 88 deletions(-) diff --git a/Makefile.am b/Makefile.am index 410297f2e..fb1e28622 100644 --- a/Makefile.am +++ b/Makefile.am @@ -100,14 +100,27 @@ EXTRA_DIST = \ src/gui/bitmaps/zoomout.xpm \ src/gui/bitmaps/zoom1.xpm \ src/gui/bitmaps/gambitrc.rc \ + src/README.rst \ src/pygambit/contrib/games/2s2x2x2.efg \ src/pygambit/contrib/games/2smp.efg \ + src/pygambit/contrib/games/2x2.nfg \ + src/pygambit/contrib/games/2x2a.nfg \ + src/pygambit/contrib/games/2x2const.nfg \ + src/pygambit/contrib/games/2x2x2-nau.nfg \ src/pygambit/contrib/games/2x2x2.efg \ + src/pygambit/contrib/games/2x2x2.nfg \ + src/pygambit/contrib/games/2x2x2x2.nfg \ + src/pygambit/contrib/games/2x2x2x2x2.nfg \ + src/pygambit/contrib/games/3x3x3.nfg \ src/pygambit/contrib/games/4cards.efg \ + src/pygambit/contrib/games/5x4x3.nfg \ + src/pygambit/contrib/games/8x2x2.nfg \ + src/pygambit/contrib/games/8x8.nfg \ src/pygambit/contrib/games/artist1.efg \ src/pygambit/contrib/games/artist2.efg \ src/pygambit/contrib/games/badgame1.efg \ src/pygambit/contrib/games/badgame2.efg \ + src/pygambit/contrib/games/bagwell.efg \ src/pygambit/contrib/games/bayes1a.efg \ src/pygambit/contrib/games/bayes2a.efg \ src/pygambit/contrib/games/bcp2.efg \ @@ -120,6 +133,7 @@ EXTRA_DIST = \ src/pygambit/contrib/games/bhg5.efg \ src/pygambit/contrib/games/caro2.efg \ src/pygambit/contrib/games/cent2.efg \ + src/pygambit/contrib/games/cent2.nfg \ src/pygambit/contrib/games/cent3.efg \ src/pygambit/contrib/games/cent4.efg \ src/pygambit/contrib/games/cent6.efg \ @@ -127,31 +141,48 @@ EXTRA_DIST = \ src/pygambit/contrib/games/centcs6.efg \ src/pygambit/contrib/games/condjury.efg \ src/pygambit/contrib/games/coord2.efg \ + src/pygambit/contrib/games/coord2.nfg \ src/pygambit/contrib/games/coord2ts.efg \ src/pygambit/contrib/games/coord3.efg \ + src/pygambit/contrib/games/coord3.nfg \ + src/pygambit/contrib/games/coord333.nfg \ src/pygambit/contrib/games/coord4.efg \ + src/pygambit/contrib/games/coord4.nfg \ src/pygambit/contrib/games/cross.efg \ src/pygambit/contrib/games/cs.efg \ + src/pygambit/contrib/games/csg1.nfg \ + src/pygambit/contrib/games/csg2.nfg \ + src/pygambit/contrib/games/csg3.nfg \ + src/pygambit/contrib/games/csg4.nfg \ + src/pygambit/contrib/games/deg1.nfg \ + src/pygambit/contrib/games/deg2.nfg \ src/pygambit/contrib/games/e01.efg \ + src/pygambit/contrib/games/e01.nfg \ src/pygambit/contrib/games/e02.efg \ + src/pygambit/contrib/games/e02.nfg \ src/pygambit/contrib/games/e03.efg \ src/pygambit/contrib/games/e04.efg \ + src/pygambit/contrib/games/e04.nfg \ src/pygambit/contrib/games/e05.efg \ src/pygambit/contrib/games/e06.efg \ src/pygambit/contrib/games/e07.efg \ + src/pygambit/contrib/games/e07.nfg \ src/pygambit/contrib/games/e08.efg \ src/pygambit/contrib/games/e09.efg \ - src/pygambit/contrib/games/e10a.efg \ src/pygambit/contrib/games/e10.efg \ + src/pygambit/contrib/games/e10a.efg \ src/pygambit/contrib/games/e13.efg \ src/pygambit/contrib/games/e16.efg \ src/pygambit/contrib/games/e17.efg \ src/pygambit/contrib/games/e18.efg \ src/pygambit/contrib/games/g1.efg \ + src/pygambit/contrib/games/g1.nfg \ src/pygambit/contrib/games/g2.efg \ + src/pygambit/contrib/games/g2.nfg \ src/pygambit/contrib/games/g3.efg \ - src/pygambit/contrib/games/holdout7.efg \ + src/pygambit/contrib/games/g3.nfg \ src/pygambit/contrib/games/holdout.efg \ + src/pygambit/contrib/games/holdout7.efg \ src/pygambit/contrib/games/hs1.efg \ src/pygambit/contrib/games/jury_mr.efg \ src/pygambit/contrib/games/jury_un.efg \ @@ -159,6 +190,9 @@ EXTRA_DIST = \ src/pygambit/contrib/games/km2.efg \ src/pygambit/contrib/games/km3.efg \ src/pygambit/contrib/games/km6.efg \ + src/pygambit/contrib/games/loopback.nfg \ + src/pygambit/contrib/games/mixdom.nfg \ + src/pygambit/contrib/games/mixdom2.nfg \ src/pygambit/contrib/games/montyhal.efg \ src/pygambit/contrib/games/my_2-1.efg \ src/pygambit/contrib/games/my_2-4.efg \ @@ -170,111 +204,48 @@ EXTRA_DIST = \ src/pygambit/contrib/games/my_3-3e.efg \ src/pygambit/contrib/games/my_3-4.efg \ src/pygambit/contrib/games/myerson.efg \ - src/pygambit/contrib/games/nim7.efg \ + src/pygambit/contrib/games/myerson_fig_4_2.efg \ src/pygambit/contrib/games/nim.efg \ + src/pygambit/contrib/games/nim7.efg \ + src/pygambit/contrib/games/oneill.nfg \ + src/pygambit/contrib/games/palf.efg \ src/pygambit/contrib/games/palf2.efg \ src/pygambit/contrib/games/palf3.efg \ - src/pygambit/contrib/games/palf.efg \ - src/pygambit/contrib/games/poker2.efg \ + src/pygambit/contrib/games/pbride.efg \ + src/pygambit/contrib/games/pd.nfg \ + src/pygambit/contrib/games/perfect1.nfg \ + src/pygambit/contrib/games/perfect2.nfg \ + src/pygambit/contrib/games/perfect3.nfg \ src/pygambit/contrib/games/poker.efg \ - src/pygambit/contrib/games/pvw2.efg \ + src/pygambit/contrib/games/poker.nfg \ + src/pygambit/contrib/games/poker2.efg \ src/pygambit/contrib/games/pvw.efg \ + src/pygambit/contrib/games/pvw2.efg \ src/pygambit/contrib/games/sh3.efg \ + src/pygambit/contrib/games/sh3.nfg \ + src/pygambit/contrib/games/spence.efg \ + src/pygambit/contrib/games/stengel.nfg \ src/pygambit/contrib/games/sww1.efg \ + src/pygambit/contrib/games/sww1.nfg \ src/pygambit/contrib/games/sww2.efg \ src/pygambit/contrib/games/sww3.efg \ src/pygambit/contrib/games/tim.efg \ + src/pygambit/contrib/games/todd1.nfg \ + src/pygambit/contrib/games/todd2.nfg \ + src/pygambit/contrib/games/todd3.nfg \ src/pygambit/contrib/games/ttt.efg \ src/pygambit/contrib/games/vd.efg \ + src/pygambit/contrib/games/vd.nfg \ src/pygambit/contrib/games/w_ex1.efg \ src/pygambit/contrib/games/w_ex2.efg \ src/pygambit/contrib/games/wilson1.efg \ + src/pygambit/contrib/games/wink3.nfg \ + src/pygambit/contrib/games/winkels.nfg \ src/pygambit/contrib/games/work1.efg \ src/pygambit/contrib/games/work2.efg \ src/pygambit/contrib/games/work3.efg \ - src/pygambit/contrib/games/2x2a.nfg \ - src/pygambit/contrib/games/2x2const.nfg \ - src/pygambit/contrib/games/2x2.nfg \ - src/pygambit/contrib/games/2x2x2.nfg \ - src/pygambit/contrib/games/2x2x2x2.nfg \ - src/pygambit/contrib/games/2x2x2x2x2.nfg \ - src/pygambit/contrib/games/3x3x3.nfg \ - src/pygambit/contrib/games/5x4x3.nfg \ - src/pygambit/contrib/games/8x2x2.nfg \ - src/pygambit/contrib/games/8x8.nfg \ - src/pygambit/contrib/games/cent2.nfg \ - src/pygambit/contrib/games/coord2.nfg \ - src/pygambit/contrib/games/coord333.nfg \ - src/pygambit/contrib/games/coord3.nfg \ - src/pygambit/contrib/games/coord4.nfg \ - src/pygambit/contrib/games/csg1.nfg \ - src/pygambit/contrib/games/csg2.nfg \ - src/pygambit/contrib/games/csg3.nfg \ - src/pygambit/contrib/games/csg4.nfg \ - src/pygambit/contrib/games/deg1.nfg \ - src/pygambit/contrib/games/deg2.nfg \ - src/pygambit/contrib/games/e01.nfg \ - src/pygambit/contrib/games/e02.nfg \ - src/pygambit/contrib/games/e04.nfg \ - src/pygambit/contrib/games/e07.nfg \ - src/pygambit/contrib/games/g1.nfg \ - src/pygambit/contrib/games/g2.nfg \ - src/pygambit/contrib/games/g3.nfg \ - src/pygambit/contrib/games/loopback.nfg \ - src/pygambit/contrib/games/mixdom2.nfg \ - src/pygambit/contrib/games/mixdom.nfg \ - src/pygambit/contrib/games/oneill.nfg \ - src/pygambit/contrib/games/pd.nfg \ - src/pygambit/contrib/games/perfect1.nfg \ - src/pygambit/contrib/games/perfect2.nfg \ - src/pygambit/contrib/games/perfect3.nfg \ - src/pygambit/contrib/games/poker.nfg \ - src/pygambit/contrib/games/sh3.nfg \ - src/pygambit/contrib/games/stengel.nfg \ - src/pygambit/contrib/games/sww1.nfg \ - src/pygambit/contrib/games/todd1.nfg \ - src/pygambit/contrib/games/todd2.nfg \ - src/pygambit/contrib/games/todd3.nfg \ - src/pygambit/contrib/games/vd.nfg \ - src/pygambit/contrib/games/wink3.nfg \ - src/pygambit/contrib/games/winkels.nfg \ src/pygambit/contrib/games/yamamoto.nfg \ - src/pygambit/contrib/games/zero.nfg \ - src/README.rst - -core_SOURCES = \ - src/core/core.h \ - src/core/util.h \ - src/core/array.h \ - src/core/vector.h \ - src/core/recarray.h \ - src/core/matrix.h \ - src/core/integer.cc \ - src/core/integer.h \ - src/core/rational.cc \ - src/core/rational.h \ - src/core/matrix.cc \ - src/core/function.cc \ - src/core/function.h \ - src/core/tinyxml.cc \ - src/core/tinyxmlerror.cc \ - src/core/tinyxmlparser.cc \ - src/core/tinyxml.h - -agg_SOURCES = \ - src/games/gameagg.cc \ - src/games/gameagg.h \ - src/games/gamebagg.cc \ - src/games/gamebagg.h \ - src/games/agg/gray.h \ - src/games/agg/agg.cc \ - src/games/agg/agg.h \ - src/games/agg/bagg.cc \ - src/games/agg/bagg.h \ - src/games/agg/proj_func.h \ - src/games/agg/trie_map.h \ - src/games/agg/trie_map.imp \ - src/games/agg/trie_map.cc + src/pygambit/contrib/games/zero.nfg game_SOURCES = \ src/gambit.h \ diff --git a/src/pygambit/catalog_update.py b/src/pygambit/catalog_update.py index f0670bdf1..c22a4f5fc 100644 --- a/src/pygambit/catalog_update.py +++ b/src/pygambit/catalog_update.py @@ -10,6 +10,7 @@ _CATALOG_YAML = Path(__file__).parent / "catalog.yml" _GAMEFILES_DIR = Path(__file__).parent / "contrib/games" _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: @@ -78,6 +79,44 @@ def update_api_rst(new_classes: list) -> None: 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/contrib/games/{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) + + if __name__ == "__main__": # Use ruamel.yaml to preserve comments yaml = YAML() @@ -125,8 +164,12 @@ def update_api_rst(new_classes: list) -> None: # 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() + 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}") print(f"Updated {_API_RST}") + print(f"Updated {_MAKEFILE_AM}") print("Done.") From d611112d5ced74f834a65b093532be27b8e2123f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 11:52:03 +0000 Subject: [PATCH 141/170] restore parts of Makefile.am removed in error --- Makefile.am | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Makefile.am b/Makefile.am index fb1e28622..30d35a65a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -247,6 +247,40 @@ EXTRA_DIST = \ src/pygambit/contrib/games/yamamoto.nfg \ src/pygambit/contrib/games/zero.nfg +core_SOURCES = \ + src/core/core.h \ + src/core/util.h \ + src/core/array.h \ + src/core/vector.h \ + src/core/recarray.h \ + src/core/matrix.h \ + src/core/integer.cc \ + src/core/integer.h \ + src/core/rational.cc \ + src/core/rational.h \ + src/core/matrix.cc \ + src/core/function.cc \ + src/core/function.h \ + src/core/tinyxml.cc \ + src/core/tinyxmlerror.cc \ + src/core/tinyxmlparser.cc \ + src/core/tinyxml.h + +agg_SOURCES = \ + src/games/gameagg.cc \ + src/games/gameagg.h \ + src/games/gamebagg.cc \ + src/games/gamebagg.h \ + src/games/agg/gray.h \ + src/games/agg/agg.cc \ + src/games/agg/agg.h \ + src/games/agg/bagg.cc \ + src/games/agg/bagg.h \ + src/games/agg/proj_func.h \ + src/games/agg/trie_map.h \ + src/games/agg/trie_map.imp \ + src/games/agg/trie_map.cc + game_SOURCES = \ src/gambit.h \ src/games/gameseq.cc \ From 14be5df046b3f64f77ebb15afb959d449ec68a59 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 13:42:11 +0000 Subject: [PATCH 142/170] clearer print statements in catalog_update.py --- src/pygambit/catalog_update.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog_update.py b/src/pygambit/catalog_update.py index c22a4f5fc..6756a1996 100644 --- a/src/pygambit/catalog_update.py +++ b/src/pygambit/catalog_update.py @@ -74,6 +74,8 @@ def update_api_rst(new_classes: list) -> None: + 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) @@ -116,6 +118,12 @@ def update_makefile(): 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}") + if __name__ == "__main__": # Use ruamel.yaml to preserve comments @@ -160,6 +168,10 @@ def update_makefile(): 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())) @@ -167,9 +179,4 @@ def update_makefile(): # Update the Makefile.am with all game files update_makefile() - 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}") - print(f"Updated {_API_RST}") - print(f"Updated {_MAKEFILE_AM}") print("Done.") From 7606fef48311c3fbef7a73951850cfd4148c395d Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 15:42:00 +0000 Subject: [PATCH 143/170] add metadata section to CatalogGame atrributes --- src/pygambit/catalog.py | 4 ++++ src/pygambit/catalog_games.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index e945215ec..1f1809eac 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -19,6 +19,10 @@ class CatalogGame: 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) diff --git a/src/pygambit/catalog_games.py b/src/pygambit/catalog_games.py index eb1b2ca40..272783d0a 100644 --- a/src/pygambit/catalog_games.py +++ b/src/pygambit/catalog_games.py @@ -10,7 +10,6 @@ class OneShotTrust(gbt.catalog.CatalogGame): the Seller plays Abuse. """ test_suite = True - """This game is included in the pygambit test suite.""" @staticmethod def _game(unique_NE_variant: bool = False): From a03afaad565eb3d806d9e116c0bd345bdcd9ae71 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 15:56:20 +0000 Subject: [PATCH 144/170] rename contrib/games to catalog_game_files --- Makefile.am | 290 +++++++++--------- pyproject.toml | 2 +- src/pygambit/catalog.py | 22 +- .../games => catalog_game_files}/2s2x2x2.efg | 0 .../games => catalog_game_files}/2smp.efg | 0 .../games => catalog_game_files}/2x2.agg | 0 .../games => catalog_game_files}/2x2.nfg | 0 .../games => catalog_game_files}/2x2a.nfg | 0 .../games => catalog_game_files}/2x2const.nfg | 0 .../2x2x2-nau.nfg | 0 .../games => catalog_game_files}/2x2x2.efg | 0 .../games => catalog_game_files}/2x2x2.nfg | 0 .../games => catalog_game_files}/2x2x2x2.nfg | 0 .../2x2x2x2x2.nfg | 0 .../games => catalog_game_files}/3x3x3.nfg | 0 .../games => catalog_game_files}/4cards.efg | 0 .../games => catalog_game_files}/5x4x3.nfg | 0 .../games => catalog_game_files}/8x2x2.nfg | 0 .../games => catalog_game_files}/8x8.nfg | 0 .../BSS_S_085.Weighted.agg | 0 .../Bayesian-Coffee-3-2-2-3.bagg | 0 .../games => catalog_game_files}/GenRPS5.agg | 0 .../games => catalog_game_files}/artist1.efg | 0 .../games => catalog_game_files}/artist2.efg | 0 .../games => catalog_game_files}/badgame1.efg | 0 .../games => catalog_game_files}/badgame2.efg | 0 .../games => catalog_game_files}/bagwell.efg | 0 .../games => catalog_game_files}/bayes1a.efg | 0 .../games => catalog_game_files}/bayes2a.efg | 0 .../games => catalog_game_files}/bcp2.efg | 0 .../games => catalog_game_files}/bcp3.efg | 0 .../games => catalog_game_files}/bcp4.efg | 0 .../games => catalog_game_files}/bhg1.efg | 0 .../games => catalog_game_files}/bhg2.efg | 0 .../games => catalog_game_files}/bhg3.efg | 0 .../games => catalog_game_files}/bhg4.efg | 0 .../games => catalog_game_files}/bhg5.efg | 0 .../games => catalog_game_files}/caro2.efg | 0 .../games => catalog_game_files}/cent2.efg | 0 .../games => catalog_game_files}/cent2.nfg | 0 .../games => catalog_game_files}/cent3.efg | 0 .../games => catalog_game_files}/cent4.efg | 0 .../games => catalog_game_files}/cent6.efg | 0 .../games => catalog_game_files}/centcs10.efg | 0 .../games => catalog_game_files}/centcs6.efg | 0 .../games => catalog_game_files}/condjury.efg | 0 .../games => catalog_game_files}/coord2.efg | 0 .../games => catalog_game_files}/coord2.nfg | 0 .../games => catalog_game_files}/coord2ts.efg | 0 .../games => catalog_game_files}/coord3.efg | 0 .../games => catalog_game_files}/coord3.nfg | 0 .../games => catalog_game_files}/coord333.nfg | 0 .../games => catalog_game_files}/coord4.efg | 0 .../games => catalog_game_files}/coord4.nfg | 0 .../games => catalog_game_files}/cross.efg | 0 .../games => catalog_game_files}/cs.efg | 0 .../games => catalog_game_files}/csg1.nfg | 0 .../games => catalog_game_files}/csg2.nfg | 0 .../games => catalog_game_files}/csg3.nfg | 0 .../games => catalog_game_files}/csg4.nfg | 0 .../games => catalog_game_files}/deg1.nfg | 0 .../games => catalog_game_files}/deg2.nfg | 0 .../games => catalog_game_files}/e01.efg | 0 .../games => catalog_game_files}/e01.nfg | 0 .../games => catalog_game_files}/e02.efg | 0 .../games => catalog_game_files}/e02.nfg | 0 .../games => catalog_game_files}/e03.efg | 0 .../games => catalog_game_files}/e04.efg | 0 .../games => catalog_game_files}/e04.nfg | 0 .../games => catalog_game_files}/e05.efg | 0 .../games => catalog_game_files}/e06.efg | 0 .../games => catalog_game_files}/e07.efg | 0 .../games => catalog_game_files}/e07.nfg | 0 .../games => catalog_game_files}/e08.efg | 0 .../games => catalog_game_files}/e09.efg | 0 .../games => catalog_game_files}/e10.efg | 0 .../games => catalog_game_files}/e10a.efg | 0 .../games => catalog_game_files}/e13.efg | 0 .../games => catalog_game_files}/e16.efg | 0 .../games => catalog_game_files}/e17.efg | 0 .../games => catalog_game_files}/e18.efg | 0 .../games => catalog_game_files}/g1.efg | 0 .../games => catalog_game_files}/g1.nfg | 0 .../games => catalog_game_files}/g2.efg | 0 .../games => catalog_game_files}/g2.nfg | 0 .../games => catalog_game_files}/g3.efg | 0 .../games => catalog_game_files}/g3.nfg | 0 .../games => catalog_game_files}/holdout.efg | 0 .../games => catalog_game_files}/holdout7.efg | 0 .../games => catalog_game_files}/hs1.efg | 0 .../games => catalog_game_files}/jury_mr.efg | 0 .../games => catalog_game_files}/jury_un.efg | 0 .../games => catalog_game_files}/km1.efg | 0 .../games => catalog_game_files}/km2.efg | 0 .../games => catalog_game_files}/km3.efg | 0 .../games => catalog_game_files}/km6.efg | 0 .../games => catalog_game_files}/loopback.nfg | 0 .../games => catalog_game_files}/mixdom.nfg | 0 .../games => catalog_game_files}/mixdom2.nfg | 0 .../games => catalog_game_files}/montyhal.efg | 0 .../games => catalog_game_files}/my_2-1.efg | 0 .../games => catalog_game_files}/my_2-4.efg | 0 .../games => catalog_game_files}/my_2-8.efg | 0 .../games => catalog_game_files}/my_3-3a.efg | 0 .../games => catalog_game_files}/my_3-3b.efg | 0 .../games => catalog_game_files}/my_3-3c.efg | 0 .../games => catalog_game_files}/my_3-3d.efg | 0 .../games => catalog_game_files}/my_3-3e.efg | 0 .../games => catalog_game_files}/my_3-4.efg | 0 .../games => catalog_game_files}/myerson.efg | 0 .../myerson_fig_4_2.efg | 0 .../games => catalog_game_files}/nim.efg | 0 .../games => catalog_game_files}/nim7.efg | 0 .../games => catalog_game_files}/oneill.nfg | 0 .../games => catalog_game_files}/palf.efg | 0 .../games => catalog_game_files}/palf2.efg | 0 .../games => catalog_game_files}/palf3.efg | 0 .../games => catalog_game_files}/pbride.efg | 0 .../games => catalog_game_files}/pd.nfg | 0 .../games => catalog_game_files}/perfect1.nfg | 0 .../games => catalog_game_files}/perfect2.nfg | 0 .../games => catalog_game_files}/perfect3.nfg | 0 .../games => catalog_game_files}/poker.efg | 0 .../games => catalog_game_files}/poker.nfg | 0 .../games => catalog_game_files}/poker2.efg | 0 .../games => catalog_game_files}/pvw.efg | 0 .../games => catalog_game_files}/pvw2.efg | 0 .../games => catalog_game_files}/sh3.efg | 0 .../games => catalog_game_files}/sh3.nfg | 0 .../games => catalog_game_files}/spence.efg | 0 .../games => catalog_game_files}/stengel.nfg | 0 .../games => catalog_game_files}/sww1.efg | 0 .../games => catalog_game_files}/sww1.nfg | 0 .../games => catalog_game_files}/sww2.efg | 0 .../games => catalog_game_files}/sww3.efg | 0 .../games => catalog_game_files}/tim.efg | 0 .../games => catalog_game_files}/todd1.nfg | 0 .../games => catalog_game_files}/todd2.nfg | 0 .../games => catalog_game_files}/todd3.nfg | 0 .../games => catalog_game_files}/ttt.efg | 0 .../games => catalog_game_files}/vd.efg | 0 .../games => catalog_game_files}/vd.nfg | 0 .../games => catalog_game_files}/w_ex1.efg | 0 .../games => catalog_game_files}/w_ex2.efg | 0 .../games => catalog_game_files}/wilson1.efg | 0 .../games => catalog_game_files}/wink3.nfg | 0 .../games => catalog_game_files}/winkels.nfg | 0 .../games => catalog_game_files}/work1.efg | 0 .../games => catalog_game_files}/work2.efg | 0 .../games => catalog_game_files}/work3.efg | 0 .../games => catalog_game_files}/yamamoto.nfg | 0 .../games => catalog_game_files}/zero.nfg | 0 src/pygambit/catalog_update.py | 10 +- tests/test_catalog.py | 4 +- 154 files changed, 164 insertions(+), 164 deletions(-) rename src/pygambit/{contrib/games => catalog_game_files}/2s2x2x2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2smp.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2.agg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2a.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2const.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2x2-nau.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2x2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2x2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2x2x2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/2x2x2x2x2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/3x3x3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/4cards.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/5x4x3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/8x2x2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/8x8.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/BSS_S_085.Weighted.agg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/Bayesian-Coffee-3-2-2-3.bagg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/GenRPS5.agg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/artist1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/artist2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/badgame1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/badgame2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bagwell.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bayes1a.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bayes2a.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bcp2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bcp3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bcp4.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bhg1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bhg2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bhg3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bhg4.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/bhg5.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/caro2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/cent2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/cent2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/cent3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/cent4.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/cent6.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/centcs10.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/centcs6.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/condjury.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord2ts.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord333.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord4.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/coord4.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/cross.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/cs.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/csg1.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/csg2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/csg3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/csg4.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/deg1.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/deg2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e01.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e01.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e02.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e02.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e03.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e04.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e04.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e05.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e06.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e07.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e07.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e08.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e09.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e10.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e10a.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e13.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e16.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e17.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/e18.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/g1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/g1.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/g2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/g2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/g3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/g3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/holdout.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/holdout7.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/hs1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/jury_mr.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/jury_un.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/km1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/km2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/km3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/km6.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/loopback.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/mixdom.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/mixdom2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/montyhal.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_2-1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_2-4.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_2-8.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_3-3a.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_3-3b.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_3-3c.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_3-3d.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_3-3e.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/my_3-4.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/myerson.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/myerson_fig_4_2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/nim.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/nim7.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/oneill.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/palf.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/palf2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/palf3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/pbride.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/pd.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/perfect1.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/perfect2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/perfect3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/poker.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/poker.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/poker2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/pvw.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/pvw2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/sh3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/sh3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/spence.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/stengel.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/sww1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/sww1.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/sww2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/sww3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/tim.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/todd1.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/todd2.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/todd3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/ttt.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/vd.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/vd.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/w_ex1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/w_ex2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/wilson1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/wink3.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/winkels.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/work1.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/work2.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/work3.efg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/yamamoto.nfg (100%) rename src/pygambit/{contrib/games => catalog_game_files}/zero.nfg (100%) diff --git a/Makefile.am b/Makefile.am index 30d35a65a..b0680a7d7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -101,151 +101,151 @@ EXTRA_DIST = \ src/gui/bitmaps/zoom1.xpm \ src/gui/bitmaps/gambitrc.rc \ src/README.rst \ - src/pygambit/contrib/games/2s2x2x2.efg \ - src/pygambit/contrib/games/2smp.efg \ - src/pygambit/contrib/games/2x2.nfg \ - src/pygambit/contrib/games/2x2a.nfg \ - src/pygambit/contrib/games/2x2const.nfg \ - src/pygambit/contrib/games/2x2x2-nau.nfg \ - src/pygambit/contrib/games/2x2x2.efg \ - src/pygambit/contrib/games/2x2x2.nfg \ - src/pygambit/contrib/games/2x2x2x2.nfg \ - src/pygambit/contrib/games/2x2x2x2x2.nfg \ - src/pygambit/contrib/games/3x3x3.nfg \ - src/pygambit/contrib/games/4cards.efg \ - src/pygambit/contrib/games/5x4x3.nfg \ - src/pygambit/contrib/games/8x2x2.nfg \ - src/pygambit/contrib/games/8x8.nfg \ - src/pygambit/contrib/games/artist1.efg \ - src/pygambit/contrib/games/artist2.efg \ - src/pygambit/contrib/games/badgame1.efg \ - src/pygambit/contrib/games/badgame2.efg \ - src/pygambit/contrib/games/bagwell.efg \ - src/pygambit/contrib/games/bayes1a.efg \ - src/pygambit/contrib/games/bayes2a.efg \ - src/pygambit/contrib/games/bcp2.efg \ - src/pygambit/contrib/games/bcp3.efg \ - src/pygambit/contrib/games/bcp4.efg \ - src/pygambit/contrib/games/bhg1.efg \ - src/pygambit/contrib/games/bhg2.efg \ - src/pygambit/contrib/games/bhg3.efg \ - src/pygambit/contrib/games/bhg4.efg \ - src/pygambit/contrib/games/bhg5.efg \ - src/pygambit/contrib/games/caro2.efg \ - src/pygambit/contrib/games/cent2.efg \ - src/pygambit/contrib/games/cent2.nfg \ - src/pygambit/contrib/games/cent3.efg \ - src/pygambit/contrib/games/cent4.efg \ - src/pygambit/contrib/games/cent6.efg \ - src/pygambit/contrib/games/centcs10.efg \ - src/pygambit/contrib/games/centcs6.efg \ - src/pygambit/contrib/games/condjury.efg \ - src/pygambit/contrib/games/coord2.efg \ - src/pygambit/contrib/games/coord2.nfg \ - src/pygambit/contrib/games/coord2ts.efg \ - src/pygambit/contrib/games/coord3.efg \ - src/pygambit/contrib/games/coord3.nfg \ - src/pygambit/contrib/games/coord333.nfg \ - src/pygambit/contrib/games/coord4.efg \ - src/pygambit/contrib/games/coord4.nfg \ - src/pygambit/contrib/games/cross.efg \ - src/pygambit/contrib/games/cs.efg \ - src/pygambit/contrib/games/csg1.nfg \ - src/pygambit/contrib/games/csg2.nfg \ - src/pygambit/contrib/games/csg3.nfg \ - src/pygambit/contrib/games/csg4.nfg \ - src/pygambit/contrib/games/deg1.nfg \ - src/pygambit/contrib/games/deg2.nfg \ - src/pygambit/contrib/games/e01.efg \ - src/pygambit/contrib/games/e01.nfg \ - src/pygambit/contrib/games/e02.efg \ - src/pygambit/contrib/games/e02.nfg \ - src/pygambit/contrib/games/e03.efg \ - src/pygambit/contrib/games/e04.efg \ - src/pygambit/contrib/games/e04.nfg \ - src/pygambit/contrib/games/e05.efg \ - src/pygambit/contrib/games/e06.efg \ - src/pygambit/contrib/games/e07.efg \ - src/pygambit/contrib/games/e07.nfg \ - src/pygambit/contrib/games/e08.efg \ - src/pygambit/contrib/games/e09.efg \ - src/pygambit/contrib/games/e10.efg \ - src/pygambit/contrib/games/e10a.efg \ - src/pygambit/contrib/games/e13.efg \ - src/pygambit/contrib/games/e16.efg \ - src/pygambit/contrib/games/e17.efg \ - src/pygambit/contrib/games/e18.efg \ - src/pygambit/contrib/games/g1.efg \ - src/pygambit/contrib/games/g1.nfg \ - src/pygambit/contrib/games/g2.efg \ - src/pygambit/contrib/games/g2.nfg \ - src/pygambit/contrib/games/g3.efg \ - src/pygambit/contrib/games/g3.nfg \ - src/pygambit/contrib/games/holdout.efg \ - src/pygambit/contrib/games/holdout7.efg \ - src/pygambit/contrib/games/hs1.efg \ - src/pygambit/contrib/games/jury_mr.efg \ - src/pygambit/contrib/games/jury_un.efg \ - src/pygambit/contrib/games/km1.efg \ - src/pygambit/contrib/games/km2.efg \ - src/pygambit/contrib/games/km3.efg \ - src/pygambit/contrib/games/km6.efg \ - src/pygambit/contrib/games/loopback.nfg \ - src/pygambit/contrib/games/mixdom.nfg \ - src/pygambit/contrib/games/mixdom2.nfg \ - src/pygambit/contrib/games/montyhal.efg \ - src/pygambit/contrib/games/my_2-1.efg \ - src/pygambit/contrib/games/my_2-4.efg \ - src/pygambit/contrib/games/my_2-8.efg \ - src/pygambit/contrib/games/my_3-3a.efg \ - src/pygambit/contrib/games/my_3-3b.efg \ - src/pygambit/contrib/games/my_3-3c.efg \ - src/pygambit/contrib/games/my_3-3d.efg \ - src/pygambit/contrib/games/my_3-3e.efg \ - src/pygambit/contrib/games/my_3-4.efg \ - src/pygambit/contrib/games/myerson.efg \ - src/pygambit/contrib/games/myerson_fig_4_2.efg \ - src/pygambit/contrib/games/nim.efg \ - src/pygambit/contrib/games/nim7.efg \ - src/pygambit/contrib/games/oneill.nfg \ - src/pygambit/contrib/games/palf.efg \ - src/pygambit/contrib/games/palf2.efg \ - src/pygambit/contrib/games/palf3.efg \ - src/pygambit/contrib/games/pbride.efg \ - src/pygambit/contrib/games/pd.nfg \ - src/pygambit/contrib/games/perfect1.nfg \ - src/pygambit/contrib/games/perfect2.nfg \ - src/pygambit/contrib/games/perfect3.nfg \ - src/pygambit/contrib/games/poker.efg \ - src/pygambit/contrib/games/poker.nfg \ - src/pygambit/contrib/games/poker2.efg \ - src/pygambit/contrib/games/pvw.efg \ - src/pygambit/contrib/games/pvw2.efg \ - src/pygambit/contrib/games/sh3.efg \ - src/pygambit/contrib/games/sh3.nfg \ - src/pygambit/contrib/games/spence.efg \ - src/pygambit/contrib/games/stengel.nfg \ - src/pygambit/contrib/games/sww1.efg \ - src/pygambit/contrib/games/sww1.nfg \ - src/pygambit/contrib/games/sww2.efg \ - src/pygambit/contrib/games/sww3.efg \ - src/pygambit/contrib/games/tim.efg \ - src/pygambit/contrib/games/todd1.nfg \ - src/pygambit/contrib/games/todd2.nfg \ - src/pygambit/contrib/games/todd3.nfg \ - src/pygambit/contrib/games/ttt.efg \ - src/pygambit/contrib/games/vd.efg \ - src/pygambit/contrib/games/vd.nfg \ - src/pygambit/contrib/games/w_ex1.efg \ - src/pygambit/contrib/games/w_ex2.efg \ - src/pygambit/contrib/games/wilson1.efg \ - src/pygambit/contrib/games/wink3.nfg \ - src/pygambit/contrib/games/winkels.nfg \ - src/pygambit/contrib/games/work1.efg \ - src/pygambit/contrib/games/work2.efg \ - src/pygambit/contrib/games/work3.efg \ - src/pygambit/contrib/games/yamamoto.nfg \ - src/pygambit/contrib/games/zero.nfg + 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/pyproject.toml b/pyproject.toml index 30dcc9074..b8b8d9c9c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ include-package-data = true [tool.setuptools.package-data] pygambit = [ "catalog.yml", - "contrib/games/*", + "catalog_game_files/*", ] [tool.setuptools.dynamic] diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 1f1809eac..f0671f908 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -6,7 +6,7 @@ import pygambit as gbt -_GAMEFILES_DIR = files(__package__) / "contrib" / "games" +_GAMEFILES_DIR = files(__package__) / "catalog_game_files" class CatalogGame: @@ -47,8 +47,8 @@ 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 CatalogGameFromContrib or its subclasses - if cls.__name__ == "CatalogGameFromContrib" or issubclass(cls, CatalogGameFromContrib): + # 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 @@ -56,7 +56,7 @@ def __init_subclass__(cls, **kwargs): cls._extract_description(cls.game) -class CatalogGameFromContrib(CatalogGame): +class CatalogGameFromFile(CatalogGame): """ Base class for catalog games loaded from files. This class serves as a template for specific games in the catalog. @@ -64,7 +64,7 @@ class CatalogGameFromContrib(CatalogGame): """ game_file: str - """Filename of the game file in contrib/games directory.""" + """Filename of the game file in catalog_game_files directory.""" def __new__(cls) -> gbt.Game: if getattr(cls, "game", None) is None: @@ -167,8 +167,8 @@ def get_all_subclasses(cls): all_subclasses = [] for subclass in cls.__subclasses__(): - # Don't include CatalogGameFromContrib in result - if subclass.__name__ in ["CatalogGameFromContrib"]: + # Don't include CatalogGameFromFile in result + if subclass.__name__ in ["CatalogGameFromFile"]: all_subclasses.extend(get_all_subclasses(subclass)) continue @@ -261,9 +261,9 @@ def _load_catalog_from_yaml() -> dict[str, dict]: return yaml.safe_load(f) or {} -def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: +def _generate_game_classes_from_catalog(catalog: dict[str, dict]) -> None: """ - Dynamically generate CatalogGameFromContrib subclasses from YAML + Dynamically generate CatalogGameFromFile subclasses from YAML and attach them to this module's namespace. """ module = sys.modules[__name__] @@ -292,7 +292,7 @@ def _generate_contrib_game_classes(catalog: dict[str, dict]) -> None: cls = type( class_name, - (CatalogGameFromContrib,), + (CatalogGameFromFile,), class_attrs, ) @@ -313,4 +313,4 @@ def load_coded_games(): # Generate classes at import time _catalog_data = _load_catalog_from_yaml() -_generate_contrib_game_classes(_catalog_data) +_generate_game_classes_from_catalog(_catalog_data) diff --git a/src/pygambit/contrib/games/2s2x2x2.efg b/src/pygambit/catalog_game_files/2s2x2x2.efg similarity index 100% rename from src/pygambit/contrib/games/2s2x2x2.efg rename to src/pygambit/catalog_game_files/2s2x2x2.efg diff --git a/src/pygambit/contrib/games/2smp.efg b/src/pygambit/catalog_game_files/2smp.efg similarity index 100% rename from src/pygambit/contrib/games/2smp.efg rename to src/pygambit/catalog_game_files/2smp.efg diff --git a/src/pygambit/contrib/games/2x2.agg b/src/pygambit/catalog_game_files/2x2.agg similarity index 100% rename from src/pygambit/contrib/games/2x2.agg rename to src/pygambit/catalog_game_files/2x2.agg diff --git a/src/pygambit/contrib/games/2x2.nfg b/src/pygambit/catalog_game_files/2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2.nfg rename to src/pygambit/catalog_game_files/2x2.nfg diff --git a/src/pygambit/contrib/games/2x2a.nfg b/src/pygambit/catalog_game_files/2x2a.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2a.nfg rename to src/pygambit/catalog_game_files/2x2a.nfg diff --git a/src/pygambit/contrib/games/2x2const.nfg b/src/pygambit/catalog_game_files/2x2const.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2const.nfg rename to src/pygambit/catalog_game_files/2x2const.nfg diff --git a/src/pygambit/contrib/games/2x2x2-nau.nfg b/src/pygambit/catalog_game_files/2x2x2-nau.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2-nau.nfg rename to src/pygambit/catalog_game_files/2x2x2-nau.nfg diff --git a/src/pygambit/contrib/games/2x2x2.efg b/src/pygambit/catalog_game_files/2x2x2.efg similarity index 100% rename from src/pygambit/contrib/games/2x2x2.efg rename to src/pygambit/catalog_game_files/2x2x2.efg diff --git a/src/pygambit/contrib/games/2x2x2.nfg b/src/pygambit/catalog_game_files/2x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2.nfg rename to src/pygambit/catalog_game_files/2x2x2.nfg diff --git a/src/pygambit/contrib/games/2x2x2x2.nfg b/src/pygambit/catalog_game_files/2x2x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2x2.nfg rename to src/pygambit/catalog_game_files/2x2x2x2.nfg diff --git a/src/pygambit/contrib/games/2x2x2x2x2.nfg b/src/pygambit/catalog_game_files/2x2x2x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/2x2x2x2x2.nfg rename to src/pygambit/catalog_game_files/2x2x2x2x2.nfg diff --git a/src/pygambit/contrib/games/3x3x3.nfg b/src/pygambit/catalog_game_files/3x3x3.nfg similarity index 100% rename from src/pygambit/contrib/games/3x3x3.nfg rename to src/pygambit/catalog_game_files/3x3x3.nfg diff --git a/src/pygambit/contrib/games/4cards.efg b/src/pygambit/catalog_game_files/4cards.efg similarity index 100% rename from src/pygambit/contrib/games/4cards.efg rename to src/pygambit/catalog_game_files/4cards.efg diff --git a/src/pygambit/contrib/games/5x4x3.nfg b/src/pygambit/catalog_game_files/5x4x3.nfg similarity index 100% rename from src/pygambit/contrib/games/5x4x3.nfg rename to src/pygambit/catalog_game_files/5x4x3.nfg diff --git a/src/pygambit/contrib/games/8x2x2.nfg b/src/pygambit/catalog_game_files/8x2x2.nfg similarity index 100% rename from src/pygambit/contrib/games/8x2x2.nfg rename to src/pygambit/catalog_game_files/8x2x2.nfg diff --git a/src/pygambit/contrib/games/8x8.nfg b/src/pygambit/catalog_game_files/8x8.nfg similarity index 100% rename from src/pygambit/contrib/games/8x8.nfg rename to src/pygambit/catalog_game_files/8x8.nfg diff --git a/src/pygambit/contrib/games/BSS_S_085.Weighted.agg b/src/pygambit/catalog_game_files/BSS_S_085.Weighted.agg similarity index 100% rename from src/pygambit/contrib/games/BSS_S_085.Weighted.agg rename to src/pygambit/catalog_game_files/BSS_S_085.Weighted.agg diff --git a/src/pygambit/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 src/pygambit/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/src/pygambit/contrib/games/GenRPS5.agg b/src/pygambit/catalog_game_files/GenRPS5.agg similarity index 100% rename from src/pygambit/contrib/games/GenRPS5.agg rename to src/pygambit/catalog_game_files/GenRPS5.agg diff --git a/src/pygambit/contrib/games/artist1.efg b/src/pygambit/catalog_game_files/artist1.efg similarity index 100% rename from src/pygambit/contrib/games/artist1.efg rename to src/pygambit/catalog_game_files/artist1.efg diff --git a/src/pygambit/contrib/games/artist2.efg b/src/pygambit/catalog_game_files/artist2.efg similarity index 100% rename from src/pygambit/contrib/games/artist2.efg rename to src/pygambit/catalog_game_files/artist2.efg diff --git a/src/pygambit/contrib/games/badgame1.efg b/src/pygambit/catalog_game_files/badgame1.efg similarity index 100% rename from src/pygambit/contrib/games/badgame1.efg rename to src/pygambit/catalog_game_files/badgame1.efg diff --git a/src/pygambit/contrib/games/badgame2.efg b/src/pygambit/catalog_game_files/badgame2.efg similarity index 100% rename from src/pygambit/contrib/games/badgame2.efg rename to src/pygambit/catalog_game_files/badgame2.efg diff --git a/src/pygambit/contrib/games/bagwell.efg b/src/pygambit/catalog_game_files/bagwell.efg similarity index 100% rename from src/pygambit/contrib/games/bagwell.efg rename to src/pygambit/catalog_game_files/bagwell.efg diff --git a/src/pygambit/contrib/games/bayes1a.efg b/src/pygambit/catalog_game_files/bayes1a.efg similarity index 100% rename from src/pygambit/contrib/games/bayes1a.efg rename to src/pygambit/catalog_game_files/bayes1a.efg diff --git a/src/pygambit/contrib/games/bayes2a.efg b/src/pygambit/catalog_game_files/bayes2a.efg similarity index 100% rename from src/pygambit/contrib/games/bayes2a.efg rename to src/pygambit/catalog_game_files/bayes2a.efg diff --git a/src/pygambit/contrib/games/bcp2.efg b/src/pygambit/catalog_game_files/bcp2.efg similarity index 100% rename from src/pygambit/contrib/games/bcp2.efg rename to src/pygambit/catalog_game_files/bcp2.efg diff --git a/src/pygambit/contrib/games/bcp3.efg b/src/pygambit/catalog_game_files/bcp3.efg similarity index 100% rename from src/pygambit/contrib/games/bcp3.efg rename to src/pygambit/catalog_game_files/bcp3.efg diff --git a/src/pygambit/contrib/games/bcp4.efg b/src/pygambit/catalog_game_files/bcp4.efg similarity index 100% rename from src/pygambit/contrib/games/bcp4.efg rename to src/pygambit/catalog_game_files/bcp4.efg diff --git a/src/pygambit/contrib/games/bhg1.efg b/src/pygambit/catalog_game_files/bhg1.efg similarity index 100% rename from src/pygambit/contrib/games/bhg1.efg rename to src/pygambit/catalog_game_files/bhg1.efg diff --git a/src/pygambit/contrib/games/bhg2.efg b/src/pygambit/catalog_game_files/bhg2.efg similarity index 100% rename from src/pygambit/contrib/games/bhg2.efg rename to src/pygambit/catalog_game_files/bhg2.efg diff --git a/src/pygambit/contrib/games/bhg3.efg b/src/pygambit/catalog_game_files/bhg3.efg similarity index 100% rename from src/pygambit/contrib/games/bhg3.efg rename to src/pygambit/catalog_game_files/bhg3.efg diff --git a/src/pygambit/contrib/games/bhg4.efg b/src/pygambit/catalog_game_files/bhg4.efg similarity index 100% rename from src/pygambit/contrib/games/bhg4.efg rename to src/pygambit/catalog_game_files/bhg4.efg diff --git a/src/pygambit/contrib/games/bhg5.efg b/src/pygambit/catalog_game_files/bhg5.efg similarity index 100% rename from src/pygambit/contrib/games/bhg5.efg rename to src/pygambit/catalog_game_files/bhg5.efg diff --git a/src/pygambit/contrib/games/caro2.efg b/src/pygambit/catalog_game_files/caro2.efg similarity index 100% rename from src/pygambit/contrib/games/caro2.efg rename to src/pygambit/catalog_game_files/caro2.efg diff --git a/src/pygambit/contrib/games/cent2.efg b/src/pygambit/catalog_game_files/cent2.efg similarity index 100% rename from src/pygambit/contrib/games/cent2.efg rename to src/pygambit/catalog_game_files/cent2.efg diff --git a/src/pygambit/contrib/games/cent2.nfg b/src/pygambit/catalog_game_files/cent2.nfg similarity index 100% rename from src/pygambit/contrib/games/cent2.nfg rename to src/pygambit/catalog_game_files/cent2.nfg diff --git a/src/pygambit/contrib/games/cent3.efg b/src/pygambit/catalog_game_files/cent3.efg similarity index 100% rename from src/pygambit/contrib/games/cent3.efg rename to src/pygambit/catalog_game_files/cent3.efg diff --git a/src/pygambit/contrib/games/cent4.efg b/src/pygambit/catalog_game_files/cent4.efg similarity index 100% rename from src/pygambit/contrib/games/cent4.efg rename to src/pygambit/catalog_game_files/cent4.efg diff --git a/src/pygambit/contrib/games/cent6.efg b/src/pygambit/catalog_game_files/cent6.efg similarity index 100% rename from src/pygambit/contrib/games/cent6.efg rename to src/pygambit/catalog_game_files/cent6.efg diff --git a/src/pygambit/contrib/games/centcs10.efg b/src/pygambit/catalog_game_files/centcs10.efg similarity index 100% rename from src/pygambit/contrib/games/centcs10.efg rename to src/pygambit/catalog_game_files/centcs10.efg diff --git a/src/pygambit/contrib/games/centcs6.efg b/src/pygambit/catalog_game_files/centcs6.efg similarity index 100% rename from src/pygambit/contrib/games/centcs6.efg rename to src/pygambit/catalog_game_files/centcs6.efg diff --git a/src/pygambit/contrib/games/condjury.efg b/src/pygambit/catalog_game_files/condjury.efg similarity index 100% rename from src/pygambit/contrib/games/condjury.efg rename to src/pygambit/catalog_game_files/condjury.efg diff --git a/src/pygambit/contrib/games/coord2.efg b/src/pygambit/catalog_game_files/coord2.efg similarity index 100% rename from src/pygambit/contrib/games/coord2.efg rename to src/pygambit/catalog_game_files/coord2.efg diff --git a/src/pygambit/contrib/games/coord2.nfg b/src/pygambit/catalog_game_files/coord2.nfg similarity index 100% rename from src/pygambit/contrib/games/coord2.nfg rename to src/pygambit/catalog_game_files/coord2.nfg diff --git a/src/pygambit/contrib/games/coord2ts.efg b/src/pygambit/catalog_game_files/coord2ts.efg similarity index 100% rename from src/pygambit/contrib/games/coord2ts.efg rename to src/pygambit/catalog_game_files/coord2ts.efg diff --git a/src/pygambit/contrib/games/coord3.efg b/src/pygambit/catalog_game_files/coord3.efg similarity index 100% rename from src/pygambit/contrib/games/coord3.efg rename to src/pygambit/catalog_game_files/coord3.efg diff --git a/src/pygambit/contrib/games/coord3.nfg b/src/pygambit/catalog_game_files/coord3.nfg similarity index 100% rename from src/pygambit/contrib/games/coord3.nfg rename to src/pygambit/catalog_game_files/coord3.nfg diff --git a/src/pygambit/contrib/games/coord333.nfg b/src/pygambit/catalog_game_files/coord333.nfg similarity index 100% rename from src/pygambit/contrib/games/coord333.nfg rename to src/pygambit/catalog_game_files/coord333.nfg diff --git a/src/pygambit/contrib/games/coord4.efg b/src/pygambit/catalog_game_files/coord4.efg similarity index 100% rename from src/pygambit/contrib/games/coord4.efg rename to src/pygambit/catalog_game_files/coord4.efg diff --git a/src/pygambit/contrib/games/coord4.nfg b/src/pygambit/catalog_game_files/coord4.nfg similarity index 100% rename from src/pygambit/contrib/games/coord4.nfg rename to src/pygambit/catalog_game_files/coord4.nfg diff --git a/src/pygambit/contrib/games/cross.efg b/src/pygambit/catalog_game_files/cross.efg similarity index 100% rename from src/pygambit/contrib/games/cross.efg rename to src/pygambit/catalog_game_files/cross.efg diff --git a/src/pygambit/contrib/games/cs.efg b/src/pygambit/catalog_game_files/cs.efg similarity index 100% rename from src/pygambit/contrib/games/cs.efg rename to src/pygambit/catalog_game_files/cs.efg diff --git a/src/pygambit/contrib/games/csg1.nfg b/src/pygambit/catalog_game_files/csg1.nfg similarity index 100% rename from src/pygambit/contrib/games/csg1.nfg rename to src/pygambit/catalog_game_files/csg1.nfg diff --git a/src/pygambit/contrib/games/csg2.nfg b/src/pygambit/catalog_game_files/csg2.nfg similarity index 100% rename from src/pygambit/contrib/games/csg2.nfg rename to src/pygambit/catalog_game_files/csg2.nfg diff --git a/src/pygambit/contrib/games/csg3.nfg b/src/pygambit/catalog_game_files/csg3.nfg similarity index 100% rename from src/pygambit/contrib/games/csg3.nfg rename to src/pygambit/catalog_game_files/csg3.nfg diff --git a/src/pygambit/contrib/games/csg4.nfg b/src/pygambit/catalog_game_files/csg4.nfg similarity index 100% rename from src/pygambit/contrib/games/csg4.nfg rename to src/pygambit/catalog_game_files/csg4.nfg diff --git a/src/pygambit/contrib/games/deg1.nfg b/src/pygambit/catalog_game_files/deg1.nfg similarity index 100% rename from src/pygambit/contrib/games/deg1.nfg rename to src/pygambit/catalog_game_files/deg1.nfg diff --git a/src/pygambit/contrib/games/deg2.nfg b/src/pygambit/catalog_game_files/deg2.nfg similarity index 100% rename from src/pygambit/contrib/games/deg2.nfg rename to src/pygambit/catalog_game_files/deg2.nfg diff --git a/src/pygambit/contrib/games/e01.efg b/src/pygambit/catalog_game_files/e01.efg similarity index 100% rename from src/pygambit/contrib/games/e01.efg rename to src/pygambit/catalog_game_files/e01.efg diff --git a/src/pygambit/contrib/games/e01.nfg b/src/pygambit/catalog_game_files/e01.nfg similarity index 100% rename from src/pygambit/contrib/games/e01.nfg rename to src/pygambit/catalog_game_files/e01.nfg diff --git a/src/pygambit/contrib/games/e02.efg b/src/pygambit/catalog_game_files/e02.efg similarity index 100% rename from src/pygambit/contrib/games/e02.efg rename to src/pygambit/catalog_game_files/e02.efg diff --git a/src/pygambit/contrib/games/e02.nfg b/src/pygambit/catalog_game_files/e02.nfg similarity index 100% rename from src/pygambit/contrib/games/e02.nfg rename to src/pygambit/catalog_game_files/e02.nfg diff --git a/src/pygambit/contrib/games/e03.efg b/src/pygambit/catalog_game_files/e03.efg similarity index 100% rename from src/pygambit/contrib/games/e03.efg rename to src/pygambit/catalog_game_files/e03.efg diff --git a/src/pygambit/contrib/games/e04.efg b/src/pygambit/catalog_game_files/e04.efg similarity index 100% rename from src/pygambit/contrib/games/e04.efg rename to src/pygambit/catalog_game_files/e04.efg diff --git a/src/pygambit/contrib/games/e04.nfg b/src/pygambit/catalog_game_files/e04.nfg similarity index 100% rename from src/pygambit/contrib/games/e04.nfg rename to src/pygambit/catalog_game_files/e04.nfg diff --git a/src/pygambit/contrib/games/e05.efg b/src/pygambit/catalog_game_files/e05.efg similarity index 100% rename from src/pygambit/contrib/games/e05.efg rename to src/pygambit/catalog_game_files/e05.efg diff --git a/src/pygambit/contrib/games/e06.efg b/src/pygambit/catalog_game_files/e06.efg similarity index 100% rename from src/pygambit/contrib/games/e06.efg rename to src/pygambit/catalog_game_files/e06.efg diff --git a/src/pygambit/contrib/games/e07.efg b/src/pygambit/catalog_game_files/e07.efg similarity index 100% rename from src/pygambit/contrib/games/e07.efg rename to src/pygambit/catalog_game_files/e07.efg diff --git a/src/pygambit/contrib/games/e07.nfg b/src/pygambit/catalog_game_files/e07.nfg similarity index 100% rename from src/pygambit/contrib/games/e07.nfg rename to src/pygambit/catalog_game_files/e07.nfg diff --git a/src/pygambit/contrib/games/e08.efg b/src/pygambit/catalog_game_files/e08.efg similarity index 100% rename from src/pygambit/contrib/games/e08.efg rename to src/pygambit/catalog_game_files/e08.efg diff --git a/src/pygambit/contrib/games/e09.efg b/src/pygambit/catalog_game_files/e09.efg similarity index 100% rename from src/pygambit/contrib/games/e09.efg rename to src/pygambit/catalog_game_files/e09.efg diff --git a/src/pygambit/contrib/games/e10.efg b/src/pygambit/catalog_game_files/e10.efg similarity index 100% rename from src/pygambit/contrib/games/e10.efg rename to src/pygambit/catalog_game_files/e10.efg diff --git a/src/pygambit/contrib/games/e10a.efg b/src/pygambit/catalog_game_files/e10a.efg similarity index 100% rename from src/pygambit/contrib/games/e10a.efg rename to src/pygambit/catalog_game_files/e10a.efg diff --git a/src/pygambit/contrib/games/e13.efg b/src/pygambit/catalog_game_files/e13.efg similarity index 100% rename from src/pygambit/contrib/games/e13.efg rename to src/pygambit/catalog_game_files/e13.efg diff --git a/src/pygambit/contrib/games/e16.efg b/src/pygambit/catalog_game_files/e16.efg similarity index 100% rename from src/pygambit/contrib/games/e16.efg rename to src/pygambit/catalog_game_files/e16.efg diff --git a/src/pygambit/contrib/games/e17.efg b/src/pygambit/catalog_game_files/e17.efg similarity index 100% rename from src/pygambit/contrib/games/e17.efg rename to src/pygambit/catalog_game_files/e17.efg diff --git a/src/pygambit/contrib/games/e18.efg b/src/pygambit/catalog_game_files/e18.efg similarity index 100% rename from src/pygambit/contrib/games/e18.efg rename to src/pygambit/catalog_game_files/e18.efg diff --git a/src/pygambit/contrib/games/g1.efg b/src/pygambit/catalog_game_files/g1.efg similarity index 100% rename from src/pygambit/contrib/games/g1.efg rename to src/pygambit/catalog_game_files/g1.efg diff --git a/src/pygambit/contrib/games/g1.nfg b/src/pygambit/catalog_game_files/g1.nfg similarity index 100% rename from src/pygambit/contrib/games/g1.nfg rename to src/pygambit/catalog_game_files/g1.nfg diff --git a/src/pygambit/contrib/games/g2.efg b/src/pygambit/catalog_game_files/g2.efg similarity index 100% rename from src/pygambit/contrib/games/g2.efg rename to src/pygambit/catalog_game_files/g2.efg diff --git a/src/pygambit/contrib/games/g2.nfg b/src/pygambit/catalog_game_files/g2.nfg similarity index 100% rename from src/pygambit/contrib/games/g2.nfg rename to src/pygambit/catalog_game_files/g2.nfg diff --git a/src/pygambit/contrib/games/g3.efg b/src/pygambit/catalog_game_files/g3.efg similarity index 100% rename from src/pygambit/contrib/games/g3.efg rename to src/pygambit/catalog_game_files/g3.efg diff --git a/src/pygambit/contrib/games/g3.nfg b/src/pygambit/catalog_game_files/g3.nfg similarity index 100% rename from src/pygambit/contrib/games/g3.nfg rename to src/pygambit/catalog_game_files/g3.nfg diff --git a/src/pygambit/contrib/games/holdout.efg b/src/pygambit/catalog_game_files/holdout.efg similarity index 100% rename from src/pygambit/contrib/games/holdout.efg rename to src/pygambit/catalog_game_files/holdout.efg diff --git a/src/pygambit/contrib/games/holdout7.efg b/src/pygambit/catalog_game_files/holdout7.efg similarity index 100% rename from src/pygambit/contrib/games/holdout7.efg rename to src/pygambit/catalog_game_files/holdout7.efg diff --git a/src/pygambit/contrib/games/hs1.efg b/src/pygambit/catalog_game_files/hs1.efg similarity index 100% rename from src/pygambit/contrib/games/hs1.efg rename to src/pygambit/catalog_game_files/hs1.efg diff --git a/src/pygambit/contrib/games/jury_mr.efg b/src/pygambit/catalog_game_files/jury_mr.efg similarity index 100% rename from src/pygambit/contrib/games/jury_mr.efg rename to src/pygambit/catalog_game_files/jury_mr.efg diff --git a/src/pygambit/contrib/games/jury_un.efg b/src/pygambit/catalog_game_files/jury_un.efg similarity index 100% rename from src/pygambit/contrib/games/jury_un.efg rename to src/pygambit/catalog_game_files/jury_un.efg diff --git a/src/pygambit/contrib/games/km1.efg b/src/pygambit/catalog_game_files/km1.efg similarity index 100% rename from src/pygambit/contrib/games/km1.efg rename to src/pygambit/catalog_game_files/km1.efg diff --git a/src/pygambit/contrib/games/km2.efg b/src/pygambit/catalog_game_files/km2.efg similarity index 100% rename from src/pygambit/contrib/games/km2.efg rename to src/pygambit/catalog_game_files/km2.efg diff --git a/src/pygambit/contrib/games/km3.efg b/src/pygambit/catalog_game_files/km3.efg similarity index 100% rename from src/pygambit/contrib/games/km3.efg rename to src/pygambit/catalog_game_files/km3.efg diff --git a/src/pygambit/contrib/games/km6.efg b/src/pygambit/catalog_game_files/km6.efg similarity index 100% rename from src/pygambit/contrib/games/km6.efg rename to src/pygambit/catalog_game_files/km6.efg diff --git a/src/pygambit/contrib/games/loopback.nfg b/src/pygambit/catalog_game_files/loopback.nfg similarity index 100% rename from src/pygambit/contrib/games/loopback.nfg rename to src/pygambit/catalog_game_files/loopback.nfg diff --git a/src/pygambit/contrib/games/mixdom.nfg b/src/pygambit/catalog_game_files/mixdom.nfg similarity index 100% rename from src/pygambit/contrib/games/mixdom.nfg rename to src/pygambit/catalog_game_files/mixdom.nfg diff --git a/src/pygambit/contrib/games/mixdom2.nfg b/src/pygambit/catalog_game_files/mixdom2.nfg similarity index 100% rename from src/pygambit/contrib/games/mixdom2.nfg rename to src/pygambit/catalog_game_files/mixdom2.nfg diff --git a/src/pygambit/contrib/games/montyhal.efg b/src/pygambit/catalog_game_files/montyhal.efg similarity index 100% rename from src/pygambit/contrib/games/montyhal.efg rename to src/pygambit/catalog_game_files/montyhal.efg diff --git a/src/pygambit/contrib/games/my_2-1.efg b/src/pygambit/catalog_game_files/my_2-1.efg similarity index 100% rename from src/pygambit/contrib/games/my_2-1.efg rename to src/pygambit/catalog_game_files/my_2-1.efg diff --git a/src/pygambit/contrib/games/my_2-4.efg b/src/pygambit/catalog_game_files/my_2-4.efg similarity index 100% rename from src/pygambit/contrib/games/my_2-4.efg rename to src/pygambit/catalog_game_files/my_2-4.efg diff --git a/src/pygambit/contrib/games/my_2-8.efg b/src/pygambit/catalog_game_files/my_2-8.efg similarity index 100% rename from src/pygambit/contrib/games/my_2-8.efg rename to src/pygambit/catalog_game_files/my_2-8.efg diff --git a/src/pygambit/contrib/games/my_3-3a.efg b/src/pygambit/catalog_game_files/my_3-3a.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3a.efg rename to src/pygambit/catalog_game_files/my_3-3a.efg diff --git a/src/pygambit/contrib/games/my_3-3b.efg b/src/pygambit/catalog_game_files/my_3-3b.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3b.efg rename to src/pygambit/catalog_game_files/my_3-3b.efg diff --git a/src/pygambit/contrib/games/my_3-3c.efg b/src/pygambit/catalog_game_files/my_3-3c.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3c.efg rename to src/pygambit/catalog_game_files/my_3-3c.efg diff --git a/src/pygambit/contrib/games/my_3-3d.efg b/src/pygambit/catalog_game_files/my_3-3d.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3d.efg rename to src/pygambit/catalog_game_files/my_3-3d.efg diff --git a/src/pygambit/contrib/games/my_3-3e.efg b/src/pygambit/catalog_game_files/my_3-3e.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-3e.efg rename to src/pygambit/catalog_game_files/my_3-3e.efg diff --git a/src/pygambit/contrib/games/my_3-4.efg b/src/pygambit/catalog_game_files/my_3-4.efg similarity index 100% rename from src/pygambit/contrib/games/my_3-4.efg rename to src/pygambit/catalog_game_files/my_3-4.efg diff --git a/src/pygambit/contrib/games/myerson.efg b/src/pygambit/catalog_game_files/myerson.efg similarity index 100% rename from src/pygambit/contrib/games/myerson.efg rename to src/pygambit/catalog_game_files/myerson.efg diff --git a/src/pygambit/contrib/games/myerson_fig_4_2.efg b/src/pygambit/catalog_game_files/myerson_fig_4_2.efg similarity index 100% rename from src/pygambit/contrib/games/myerson_fig_4_2.efg rename to src/pygambit/catalog_game_files/myerson_fig_4_2.efg diff --git a/src/pygambit/contrib/games/nim.efg b/src/pygambit/catalog_game_files/nim.efg similarity index 100% rename from src/pygambit/contrib/games/nim.efg rename to src/pygambit/catalog_game_files/nim.efg diff --git a/src/pygambit/contrib/games/nim7.efg b/src/pygambit/catalog_game_files/nim7.efg similarity index 100% rename from src/pygambit/contrib/games/nim7.efg rename to src/pygambit/catalog_game_files/nim7.efg diff --git a/src/pygambit/contrib/games/oneill.nfg b/src/pygambit/catalog_game_files/oneill.nfg similarity index 100% rename from src/pygambit/contrib/games/oneill.nfg rename to src/pygambit/catalog_game_files/oneill.nfg diff --git a/src/pygambit/contrib/games/palf.efg b/src/pygambit/catalog_game_files/palf.efg similarity index 100% rename from src/pygambit/contrib/games/palf.efg rename to src/pygambit/catalog_game_files/palf.efg diff --git a/src/pygambit/contrib/games/palf2.efg b/src/pygambit/catalog_game_files/palf2.efg similarity index 100% rename from src/pygambit/contrib/games/palf2.efg rename to src/pygambit/catalog_game_files/palf2.efg diff --git a/src/pygambit/contrib/games/palf3.efg b/src/pygambit/catalog_game_files/palf3.efg similarity index 100% rename from src/pygambit/contrib/games/palf3.efg rename to src/pygambit/catalog_game_files/palf3.efg diff --git a/src/pygambit/contrib/games/pbride.efg b/src/pygambit/catalog_game_files/pbride.efg similarity index 100% rename from src/pygambit/contrib/games/pbride.efg rename to src/pygambit/catalog_game_files/pbride.efg diff --git a/src/pygambit/contrib/games/pd.nfg b/src/pygambit/catalog_game_files/pd.nfg similarity index 100% rename from src/pygambit/contrib/games/pd.nfg rename to src/pygambit/catalog_game_files/pd.nfg diff --git a/src/pygambit/contrib/games/perfect1.nfg b/src/pygambit/catalog_game_files/perfect1.nfg similarity index 100% rename from src/pygambit/contrib/games/perfect1.nfg rename to src/pygambit/catalog_game_files/perfect1.nfg diff --git a/src/pygambit/contrib/games/perfect2.nfg b/src/pygambit/catalog_game_files/perfect2.nfg similarity index 100% rename from src/pygambit/contrib/games/perfect2.nfg rename to src/pygambit/catalog_game_files/perfect2.nfg diff --git a/src/pygambit/contrib/games/perfect3.nfg b/src/pygambit/catalog_game_files/perfect3.nfg similarity index 100% rename from src/pygambit/contrib/games/perfect3.nfg rename to src/pygambit/catalog_game_files/perfect3.nfg diff --git a/src/pygambit/contrib/games/poker.efg b/src/pygambit/catalog_game_files/poker.efg similarity index 100% rename from src/pygambit/contrib/games/poker.efg rename to src/pygambit/catalog_game_files/poker.efg diff --git a/src/pygambit/contrib/games/poker.nfg b/src/pygambit/catalog_game_files/poker.nfg similarity index 100% rename from src/pygambit/contrib/games/poker.nfg rename to src/pygambit/catalog_game_files/poker.nfg diff --git a/src/pygambit/contrib/games/poker2.efg b/src/pygambit/catalog_game_files/poker2.efg similarity index 100% rename from src/pygambit/contrib/games/poker2.efg rename to src/pygambit/catalog_game_files/poker2.efg diff --git a/src/pygambit/contrib/games/pvw.efg b/src/pygambit/catalog_game_files/pvw.efg similarity index 100% rename from src/pygambit/contrib/games/pvw.efg rename to src/pygambit/catalog_game_files/pvw.efg diff --git a/src/pygambit/contrib/games/pvw2.efg b/src/pygambit/catalog_game_files/pvw2.efg similarity index 100% rename from src/pygambit/contrib/games/pvw2.efg rename to src/pygambit/catalog_game_files/pvw2.efg diff --git a/src/pygambit/contrib/games/sh3.efg b/src/pygambit/catalog_game_files/sh3.efg similarity index 100% rename from src/pygambit/contrib/games/sh3.efg rename to src/pygambit/catalog_game_files/sh3.efg diff --git a/src/pygambit/contrib/games/sh3.nfg b/src/pygambit/catalog_game_files/sh3.nfg similarity index 100% rename from src/pygambit/contrib/games/sh3.nfg rename to src/pygambit/catalog_game_files/sh3.nfg diff --git a/src/pygambit/contrib/games/spence.efg b/src/pygambit/catalog_game_files/spence.efg similarity index 100% rename from src/pygambit/contrib/games/spence.efg rename to src/pygambit/catalog_game_files/spence.efg diff --git a/src/pygambit/contrib/games/stengel.nfg b/src/pygambit/catalog_game_files/stengel.nfg similarity index 100% rename from src/pygambit/contrib/games/stengel.nfg rename to src/pygambit/catalog_game_files/stengel.nfg diff --git a/src/pygambit/contrib/games/sww1.efg b/src/pygambit/catalog_game_files/sww1.efg similarity index 100% rename from src/pygambit/contrib/games/sww1.efg rename to src/pygambit/catalog_game_files/sww1.efg diff --git a/src/pygambit/contrib/games/sww1.nfg b/src/pygambit/catalog_game_files/sww1.nfg similarity index 100% rename from src/pygambit/contrib/games/sww1.nfg rename to src/pygambit/catalog_game_files/sww1.nfg diff --git a/src/pygambit/contrib/games/sww2.efg b/src/pygambit/catalog_game_files/sww2.efg similarity index 100% rename from src/pygambit/contrib/games/sww2.efg rename to src/pygambit/catalog_game_files/sww2.efg diff --git a/src/pygambit/contrib/games/sww3.efg b/src/pygambit/catalog_game_files/sww3.efg similarity index 100% rename from src/pygambit/contrib/games/sww3.efg rename to src/pygambit/catalog_game_files/sww3.efg diff --git a/src/pygambit/contrib/games/tim.efg b/src/pygambit/catalog_game_files/tim.efg similarity index 100% rename from src/pygambit/contrib/games/tim.efg rename to src/pygambit/catalog_game_files/tim.efg diff --git a/src/pygambit/contrib/games/todd1.nfg b/src/pygambit/catalog_game_files/todd1.nfg similarity index 100% rename from src/pygambit/contrib/games/todd1.nfg rename to src/pygambit/catalog_game_files/todd1.nfg diff --git a/src/pygambit/contrib/games/todd2.nfg b/src/pygambit/catalog_game_files/todd2.nfg similarity index 100% rename from src/pygambit/contrib/games/todd2.nfg rename to src/pygambit/catalog_game_files/todd2.nfg diff --git a/src/pygambit/contrib/games/todd3.nfg b/src/pygambit/catalog_game_files/todd3.nfg similarity index 100% rename from src/pygambit/contrib/games/todd3.nfg rename to src/pygambit/catalog_game_files/todd3.nfg diff --git a/src/pygambit/contrib/games/ttt.efg b/src/pygambit/catalog_game_files/ttt.efg similarity index 100% rename from src/pygambit/contrib/games/ttt.efg rename to src/pygambit/catalog_game_files/ttt.efg diff --git a/src/pygambit/contrib/games/vd.efg b/src/pygambit/catalog_game_files/vd.efg similarity index 100% rename from src/pygambit/contrib/games/vd.efg rename to src/pygambit/catalog_game_files/vd.efg diff --git a/src/pygambit/contrib/games/vd.nfg b/src/pygambit/catalog_game_files/vd.nfg similarity index 100% rename from src/pygambit/contrib/games/vd.nfg rename to src/pygambit/catalog_game_files/vd.nfg diff --git a/src/pygambit/contrib/games/w_ex1.efg b/src/pygambit/catalog_game_files/w_ex1.efg similarity index 100% rename from src/pygambit/contrib/games/w_ex1.efg rename to src/pygambit/catalog_game_files/w_ex1.efg diff --git a/src/pygambit/contrib/games/w_ex2.efg b/src/pygambit/catalog_game_files/w_ex2.efg similarity index 100% rename from src/pygambit/contrib/games/w_ex2.efg rename to src/pygambit/catalog_game_files/w_ex2.efg diff --git a/src/pygambit/contrib/games/wilson1.efg b/src/pygambit/catalog_game_files/wilson1.efg similarity index 100% rename from src/pygambit/contrib/games/wilson1.efg rename to src/pygambit/catalog_game_files/wilson1.efg diff --git a/src/pygambit/contrib/games/wink3.nfg b/src/pygambit/catalog_game_files/wink3.nfg similarity index 100% rename from src/pygambit/contrib/games/wink3.nfg rename to src/pygambit/catalog_game_files/wink3.nfg diff --git a/src/pygambit/contrib/games/winkels.nfg b/src/pygambit/catalog_game_files/winkels.nfg similarity index 100% rename from src/pygambit/contrib/games/winkels.nfg rename to src/pygambit/catalog_game_files/winkels.nfg diff --git a/src/pygambit/contrib/games/work1.efg b/src/pygambit/catalog_game_files/work1.efg similarity index 100% rename from src/pygambit/contrib/games/work1.efg rename to src/pygambit/catalog_game_files/work1.efg diff --git a/src/pygambit/contrib/games/work2.efg b/src/pygambit/catalog_game_files/work2.efg similarity index 100% rename from src/pygambit/contrib/games/work2.efg rename to src/pygambit/catalog_game_files/work2.efg diff --git a/src/pygambit/contrib/games/work3.efg b/src/pygambit/catalog_game_files/work3.efg similarity index 100% rename from src/pygambit/contrib/games/work3.efg rename to src/pygambit/catalog_game_files/work3.efg diff --git a/src/pygambit/contrib/games/yamamoto.nfg b/src/pygambit/catalog_game_files/yamamoto.nfg similarity index 100% rename from src/pygambit/contrib/games/yamamoto.nfg rename to src/pygambit/catalog_game_files/yamamoto.nfg diff --git a/src/pygambit/contrib/games/zero.nfg b/src/pygambit/catalog_game_files/zero.nfg similarity index 100% rename from src/pygambit/contrib/games/zero.nfg rename to src/pygambit/catalog_game_files/zero.nfg diff --git a/src/pygambit/catalog_update.py b/src/pygambit/catalog_update.py index 6756a1996..098b9ba38 100644 --- a/src/pygambit/catalog_update.py +++ b/src/pygambit/catalog_update.py @@ -8,7 +8,7 @@ import pygambit as gbt _CATALOG_YAML = Path(__file__).parent / "catalog.yml" -_GAMEFILES_DIR = Path(__file__).parent / "contrib/games" +_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" @@ -91,7 +91,7 @@ def update_makefile(): for entry in catalog.values(): file_name = entry.get("file") if file_name: - game_files.append(f"src/pygambit/contrib/games/{file_name}") + game_files.append(f"src/pygambit/catalog_game_files/{file_name}") game_files.sort() @@ -134,8 +134,8 @@ def update_makefile(): efg_files = list(_GAMEFILES_DIR.rglob("*.efg")) nfg_files = list(_GAMEFILES_DIR.rglob("*.nfg")) - print(f"Found {len(efg_files)} .efg files in contrib/games") - print(f"Found {len(nfg_files)} .nfg files in contrib/games") + 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) @@ -144,7 +144,7 @@ def update_makefile(): catalog = yaml.load(f) or {} file_names = [entry["file"] for entry in catalog.values() if "file" in entry] - # Iterate through contrib/games and update the catalog + # Iterate through catalog_game_files and update the catalog # with new/missing entries new_entries_counter = 0 new_entries = {} diff --git a/tests/test_catalog.py b/tests/test_catalog.py index fbae02f71..930747b26 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -101,10 +101,10 @@ def test_games_filter_by_custom_metadata(self): assert game_class.test_suite is True def test_games_excludes_base_classes(self): - """games() should not include base classes like CatalogGameFromContrib.""" + """games() should not include base classes like CatalogGameFromFile.""" result = gbt.catalog.games() assert "CatalogGame" not in result - assert "CatalogGameFromContrib" not in result + assert "CatalogGameFromFile" not in result def test_games_includes_coded_games(self): """games() should include manually coded games.""" From ff1c8e3530bb952ce894300baac89db789c1c606 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 16:01:50 +0000 Subject: [PATCH 145/170] Update links to catalog examples in CLI docs --- doc/tools.enummixed.rst | 2 +- doc/tools.enumpoly.rst | 2 +- doc/tools.enumpure.rst | 2 +- doc/tools.gnm.rst | 2 +- doc/tools.ipa.rst | 2 +- doc/tools.lcp.rst | 2 +- doc/tools.liap.rst | 2 +- doc/tools.logit.rst | 2 +- doc/tools.lp.rst | 2 +- doc/tools.simpdiv.rst | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) 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 From 22b0da9e0af5deb8951dbaf23038081a82d2a32f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 16:15:50 +0000 Subject: [PATCH 146/170] Update OneShotTrust example with better docstring --- src/pygambit/catalog_games.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/pygambit/catalog_games.py b/src/pygambit/catalog_games.py index 272783d0a..6228baa9f 100644 --- a/src/pygambit/catalog_games.py +++ b/src/pygambit/catalog_games.py @@ -3,16 +3,30 @@ class OneShotTrust(gbt.catalog.CatalogGame): """ - 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. + One-shot trust game with binary actions, originally from Kreps (1990). """ test_suite = True @staticmethod def _game(unique_NE_variant: bool = False): + """ + 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. + """ g = gbt.Game.new_tree( players=["Buyer", "Seller"], title="One-shot trust game, after Kreps (1990)" ) From 5ebe0e03428beb257094b27d8ef4e136a63f1641 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 16:19:10 +0000 Subject: [PATCH 147/170] remove commented code --- src/pygambit/catalog.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index f0671f908..f0b49b398 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -151,8 +151,6 @@ def games( >>> 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 """ - # Import manually coded games to ensure they are registered in the catalog - # _load_coded_games() # Filter by extensive-form if filtering by tree-specific attributes if ( From b5c653dad2ecc9fe198e3307ea4f77e7293153ff Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Thu, 22 Jan 2026 17:08:23 +0000 Subject: [PATCH 148/170] improve docstring handling for catalog API docs --- src/pygambit/catalog.py | 14 +++++++++++++- src/pygambit/catalog_games.py | 5 +++++ tests/test_catalog.py | 7 +++++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index f0b49b398..08a2b0d7b 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -31,7 +31,11 @@ def __new__(cls, *args, **kwargs) -> gbt.Game: @staticmethod def _game() -> gbt.Game: - """Override this method in subclasses to define the game.""" + """ + Examples + -------- + >>> {classname}() # Gets the game instance + """ raise NotImplementedError("Subclasses must implement _game() method") @classmethod @@ -40,6 +44,14 @@ def _extract_description(cls, game: gbt.Game) -> None: cleaned_docstring = "" if cls.__doc__: cleaned_docstring = inspect.cleandoc(cls.__doc__) + # 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 if len(cleaned_docstring) > 0: game.description = cleaned_docstring diff --git a/src/pygambit/catalog_games.py b/src/pygambit/catalog_games.py index 6228baa9f..cecfb3aab 100644 --- a/src/pygambit/catalog_games.py +++ b/src/pygambit/catalog_games.py @@ -26,6 +26,11 @@ def _game(unique_NE_variant: bool = False): ------- gbt.Game The constructed extensive-form game. + + Examples + -------- + >>> OneShotTrust._game(unique_NE_variant=False) # Constructs the standard game + >>> OneShotTrust._game(unique_NE_variant=True) # Constructs the game with unique NE variant """ g = gbt.Game.new_tree( players=["Buyer", "Seller"], title="One-shot trust game, after Kreps (1990)" diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 930747b26..14d497ad6 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -39,7 +39,10 @@ def test_custom_game_subclass_extracts_metadata(self): def test_can_get_game_description_from_docstring(self): """CatalogGame should get description from docstring over game description.""" - assert ExampleGameWithDocstring().description == "Alternative test game description." + assert ( + "Alternative test game description." in + ExampleGameWithDocstring().description + ) def test_catalog_py_game_with_parameters(self): """ @@ -56,8 +59,8 @@ def test_catalog_yml_game_instantiation(self): def test_catalog_yml_game_description(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().description - == "A simple implementation of a two person Prisoner's Dilemma game." ) From 082d2124094a7455cff5ef15c5b42ebdf9543ea1 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 23 Jan 2026 13:57:32 +0000 Subject: [PATCH 149/170] Improve docstring and game description handling for catalog games --- src/pygambit/catalog.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 08a2b0d7b..3af3a65aa 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -40,19 +40,24 @@ def _game() -> gbt.Game: @classmethod def _extract_description(cls, game: gbt.Game) -> None: - """Extract game description from docstring and apply to game.""" + """Create game description and docstring from catalog.""" cleaned_docstring = "" if cls.__doc__: cleaned_docstring = inspect.cleandoc(cls.__doc__) - # 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) + # 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 - if len(cleaned_docstring) > 0: + if cleaned_docstring: + combined_docstring = cleaned_docstring + "\n\n" + game_docstring + else: + combined_docstring = game_docstring + else: + combined_docstring = cleaned_docstring + cls.__doc__ = combined_docstring + if cleaned_docstring and len(cleaned_docstring) > 0: game.description = cleaned_docstring def __init_subclass__(cls, **kwargs): From c93a7429002f3809ccff4b7d45f0ea38fb3abcb5 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 23 Jan 2026 14:03:11 +0000 Subject: [PATCH 150/170] tidy game example docstrings --- src/pygambit/catalog.py | 2 +- src/pygambit/catalog_games.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 3af3a65aa..21371d82f 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -34,7 +34,7 @@ def _game() -> gbt.Game: """ Examples -------- - >>> {classname}() # Gets the game instance + >>> {classname}() # Return the Game """ raise NotImplementedError("Subclasses must implement _game() method") diff --git a/src/pygambit/catalog_games.py b/src/pygambit/catalog_games.py index cecfb3aab..b8384388b 100644 --- a/src/pygambit/catalog_games.py +++ b/src/pygambit/catalog_games.py @@ -29,8 +29,8 @@ def _game(unique_NE_variant: bool = False): Examples -------- - >>> OneShotTrust._game(unique_NE_variant=False) # Constructs the standard game - >>> OneShotTrust._game(unique_NE_variant=True) # Constructs the game with unique NE variant + >>> 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, after Kreps (1990)" From e86e6dcfd00624eefb53ed825e2afd929c48cf20 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 23 Jan 2026 15:00:04 +0000 Subject: [PATCH 151/170] add docstring function --- src/pygambit/catalog.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 21371d82f..371148c81 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -38,25 +38,36 @@ def _game() -> gbt.Game: """ raise NotImplementedError("Subclasses must implement _game() method") + @classmethod + def _img_docstring(cls) -> str: + """Return the image docstring for the game.""" + # file = f"/_static/catalog_drawn/{cls.__name__}.png" + file = "/_static/gambit.png" + docstring = f""" + .. figure:: {file} + :align: center + :width: 400px + + {cls.__name__} game diagram + """ + return docstring + @classmethod def _extract_description(cls, game: gbt.Game) -> None: """Create game description and docstring from catalog.""" cleaned_docstring = "" if cls.__doc__: cleaned_docstring = inspect.cleandoc(cls.__doc__) + cleaned_docstring += "\n" + 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__) - if cleaned_docstring: - combined_docstring = cleaned_docstring + "\n\n" + game_docstring - else: - combined_docstring = game_docstring - else: - combined_docstring = cleaned_docstring - cls.__doc__ = combined_docstring + cleaned_docstring += "\n\n" + game_docstring + cls.__doc__ = cleaned_docstring if cleaned_docstring and len(cleaned_docstring) > 0: game.description = cleaned_docstring From 3475cfb99f6cd47df79ba410c13c46b121556691 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 23 Jan 2026 15:40:28 +0000 Subject: [PATCH 152/170] improve testing of docstring handling --- src/pygambit/catalog.py | 7 +++++-- tests/test_catalog.py | 26 ++++++++++++++------------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 371148c81..23e2411c8 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -58,6 +58,10 @@ def _extract_description(cls, game: gbt.Game) -> None: cleaned_docstring = "" if cls.__doc__: cleaned_docstring = inspect.cleandoc(cls.__doc__) + game.description = cleaned_docstring + else: + cleaned_docstring = game.description + if len(cleaned_docstring) > 0: cleaned_docstring += "\n" cleaned_docstring += cls._img_docstring() # If the class has a _game function, concatenate its docstring to the class docstring @@ -67,9 +71,8 @@ def _extract_description(cls, game: gbt.Game) -> None: # For games from file, sub in the class name game_docstring = game_docstring.replace("{classname}", cls.__name__) cleaned_docstring += "\n\n" + game_docstring + game.description += "\n\n" + game_docstring cls.__doc__ = cleaned_docstring - if cleaned_docstring and len(cleaned_docstring) > 0: - game.description = cleaned_docstring def __init_subclass__(cls, **kwargs): """Extract metadata when subclass is defined (if not a file-based game).""" diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 14d497ad6..f4b02132a 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -7,6 +7,7 @@ class ExampleGame(gbt.catalog.CatalogGame): @staticmethod def _game(some_param: bool = False): + """Desc2""" if some_param: g = gbt.Game.new_tree( players=["A", "B"], title="Test game T" @@ -15,13 +16,13 @@ def _game(some_param: bool = False): g = gbt.Game.new_tree( players=["A", "B"], title="Test game F" ) - g.description = "Test game description." + g.description = "Desc1" return g class ExampleGameWithDocstring(ExampleGame): """ - Alternative test game description. + Desc3 """ @@ -33,16 +34,17 @@ def test_catalog_game_not_instantiable(self): with pytest.raises(NotImplementedError): gbt.catalog.CatalogGame() - def test_custom_game_subclass_extracts_metadata(self): - """Custom CatalogGame subclasses should extract metadata from _game().""" - assert ExampleGame().description == "Test game description." - - def test_can_get_game_description_from_docstring(self): - """CatalogGame should get description from docstring over game description.""" - assert ( - "Alternative test game description." in - ExampleGameWithDocstring().description - ) + def test_description_handling_from_game(self): + """Custom CatalogGame subclass should extract description from game object.""" + assert "Desc1" in ExampleGame().description + # Also should include _game docstring + assert "Desc2" in ExampleGame().description + + def test_description_handling_from_docstring(self): + """Custom CatalogGame subclass should get docstring over game description.""" + assert "Desc3" in ExampleGameWithDocstring().description + # Also should include _game docstring + assert "Desc2" in ExampleGameWithDocstring().description def test_catalog_py_game_with_parameters(self): """ From d2610d6af9f006ff29556eef7165063ac4c70e71 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Fri, 23 Jan 2026 16:39:09 +0000 Subject: [PATCH 153/170] Add code for adding images for catalog games in the future --- src/pygambit/catalog.py | 31 +++++++++++++++++-------------- src/pygambit/catalog_update.py | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 23e2411c8..8776ff07f 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -38,19 +38,19 @@ def _game() -> gbt.Game: """ raise NotImplementedError("Subclasses must implement _game() method") - @classmethod - def _img_docstring(cls) -> str: - """Return the image docstring for the game.""" - # file = f"/_static/catalog_drawn/{cls.__name__}.png" - file = "/_static/gambit.png" - docstring = f""" - .. figure:: {file} - :align: center - :width: 400px - - {cls.__name__} game diagram - """ - return docstring + # 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 _extract_description(cls, game: gbt.Game) -> None: @@ -63,7 +63,10 @@ def _extract_description(cls, game: gbt.Game) -> None: cleaned_docstring = game.description if len(cleaned_docstring) > 0: cleaned_docstring += "\n" - cleaned_docstring += cls._img_docstring() + + # 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: diff --git a/src/pygambit/catalog_update.py b/src/pygambit/catalog_update.py index 098b9ba38..c731112d5 100644 --- a/src/pygambit/catalog_update.py +++ b/src/pygambit/catalog_update.py @@ -125,6 +125,23 @@ def update_makefile(): 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() @@ -179,4 +196,8 @@ def update_makefile(): # 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.") From 98971b5986a7ed036bb5e82e0b95b9a1f070e045 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 10:36:38 +0000 Subject: [PATCH 154/170] Add catalog to navbar, removing sample games --- doc/catalog.rst | 12 ++++++++++++ doc/index.rst | 2 +- doc/pygambit.api.rst | 6 ++++-- 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 doc/catalog.rst 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/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 d75b0bd91..fc36aac62 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -327,8 +327,10 @@ Computation of quantal response equilibria LogitQREMixedBehaviorFitResult -Game catalog -~~~~~~~~~~~~ +.. _pygambit-catalog: + +Catalog of games +~~~~~~~~~~~~~~~~ .. currentmodule:: pygambit.catalog From d1f5f1aa16dc7294ef3a6a40eb399a935c37bdd4 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 11:49:04 +0000 Subject: [PATCH 155/170] Ensure all catalog games returned by games() have a cached game and test that attempting to subclass CatalogGame without a _game func raises an error --- src/pygambit/catalog.py | 5 +++++ tests/test_catalog.py | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 8776ff07f..387b52057 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -204,6 +204,11 @@ def get_all_subclasses(cls): 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 diff --git a/tests/test_catalog.py b/tests/test_catalog.py index f4b02132a..c3789276f 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -65,6 +65,12 @@ def test_catalog_yml_game_description(self): gbt.catalog.PrisonersDilemma().description ) + 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.""" @@ -75,6 +81,12 @@ def test_games_returns_list_of_strings(self): 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() + print(len(result)) + 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) From 98a2bf02469c8d5c630dce8b300084fb3fb3b006 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 11:49:41 +0000 Subject: [PATCH 156/170] remove print --- tests/test_catalog.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index c3789276f..c8d142057 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -84,7 +84,6 @@ def test_games_returns_list_of_strings(self): def test_invalid_games_not_included(self): """games() should not return classes marked as valid_game=False.""" result = gbt.catalog.games() - print(len(result)) assert "InvalidGame" not in result def test_games_filter_by_game_type(self): From db34daabad50c4162ccd9a05e271f8f63d55fc8f Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 13:37:37 +0000 Subject: [PATCH 157/170] initial updating the games catalog page --- doc/developer.catalog.rst | 12 ++++++++++++ doc/developer.contributing.rst | 2 ++ doc/developer.rst | 1 + 3 files changed, 15 insertions(+) create mode 100644 doc/developer.catalog.rst diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst new file mode 100644 index 000000000..09049e525 --- /dev/null +++ b/doc/developer.catalog.rst @@ -0,0 +1,12 @@ +.. _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 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. + +pip install ruamel.yaml 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 From 2eac93986da2ea4a8ecf22ed802f1b62151dd6f0 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 14:37:05 +0000 Subject: [PATCH 158/170] Add game files to the catalog --- doc/developer.catalog.rst | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index 09049e525..e6f9cc5c5 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -4,9 +4,64 @@ 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 be able to submit pull request via GitHub; you may wish to first review the :ref:`contributor guidelines `. +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. +Alternatively, you can define games for the catalog in *pygambit* code. +You may wish to first review the :ref:`pygambit ` docs pages. -pip install ruamel.yaml +.. 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:** + + On a new branch, 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 you edit the name of the entry, re-run the update script in the step above. + + 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 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. Sumbmit a pull request to GitHub with all changes + + .. warning:: + Make sure you commit all changed files e.g. run `git add --all ` before committing and pushing. From cf6d95b33554156f0f46df3af60dc2732bdf903c Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 14:40:04 +0000 Subject: [PATCH 159/170] docs fixes --- doc/developer.catalog.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index e6f9cc5c5..9e54576d9 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -57,11 +57,11 @@ You may wish to first review the :ref:`pygambit ` docs pages. .. code-block:: yaml MyGame: - file: my_game.efg - metadata: + file: my_game.efg + metadata: my_metadata: 17 6. Sumbmit a pull request to GitHub with all changes .. warning:: - Make sure you commit all changed files e.g. run `git add --all ` before committing and pushing. + Make sure you commit all changed files e.g. run `git add --all` before committing and pushing. From 52fe839522e37d8daba6eac59e33e28dfb1ca8d9 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 15:01:03 +0000 Subject: [PATCH 160/170] add type hint to OST game --- src/pygambit/catalog_games.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygambit/catalog_games.py b/src/pygambit/catalog_games.py index b8384388b..31ec6bb4a 100644 --- a/src/pygambit/catalog_games.py +++ b/src/pygambit/catalog_games.py @@ -8,7 +8,7 @@ class OneShotTrust(gbt.catalog.CatalogGame): test_suite = True @staticmethod - def _game(unique_NE_variant: bool = False): + 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 From f62ce59dcc6418e001af14b0d349660fbfa7ff62 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 15:22:46 +0000 Subject: [PATCH 161/170] add Code games for the catalog --- doc/developer.catalog.rst | 112 +++++++++++++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 2 deletions(-) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index 9e54576d9..7cf89fdbe 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -39,7 +39,6 @@ You may wish to first review the :ref:`pygambit ` docs pages. - 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 you edit the name of the entry, re-run the update script in the step above. 5. **[Optional] Add custom metadata to catalog games:** @@ -52,7 +51,7 @@ You may wish to first review the :ref:`pygambit ` docs pages. my_metadata: int | None = None """Explanation of my_metadata field.""" - Open `src/pygambit/catalog.yml` and the field under the `metadata` for the game you added, and any others that shouldn't have the default. + 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 @@ -63,5 +62,114 @@ You may wish to first review the :ref:`pygambit ` docs pages. 6. Sumbmit 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:** + + Open `src/pygambit/catalog_games.py` and 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. + Docstrings will appear in the Catalog API reference pages. + + .. code-block:: python + + class MyGame(gbt.catalog.CatalogGame): + """ + High level description of game, originally from Author (2000). + """ + + @staticmethod + def _game(some_param: bool = False) -> gbt.Game: + """ + Additional game description, describing variants. + + 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, after 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 + + 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 + + ... + """ + High level description of game, originally from Author (2000). + """ + my_metadata = 17 + ... + + 6. Sumbmit 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. From 77a7757e5f3570402130a5312fea687650a53e71 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 15:58:21 +0000 Subject: [PATCH 162/170] Tidy handling of docstring vs description --- src/pygambit/catalog.py | 4 +--- src/pygambit/catalog_games.py | 8 ++++---- tests/test_catalog.py | 32 ++++++++++++++++++-------------- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 387b52057..04ad51453 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -58,9 +58,8 @@ def _extract_description(cls, game: gbt.Game) -> None: cleaned_docstring = "" if cls.__doc__: cleaned_docstring = inspect.cleandoc(cls.__doc__) - game.description = cleaned_docstring else: - cleaned_docstring = game.description + cleaned_docstring = game.title + "\n\n" + game.description if len(cleaned_docstring) > 0: cleaned_docstring += "\n" @@ -74,7 +73,6 @@ def _extract_description(cls, game: gbt.Game) -> None: # For games from file, sub in the class name game_docstring = game_docstring.replace("{classname}", cls.__name__) cleaned_docstring += "\n\n" + game_docstring - game.description += "\n\n" + game_docstring cls.__doc__ = cleaned_docstring def __init_subclass__(cls, **kwargs): diff --git a/src/pygambit/catalog_games.py b/src/pygambit/catalog_games.py index 31ec6bb4a..67ca736cc 100644 --- a/src/pygambit/catalog_games.py +++ b/src/pygambit/catalog_games.py @@ -2,9 +2,7 @@ class OneShotTrust(gbt.catalog.CatalogGame): - """ - One-shot trust game with binary actions, originally from Kreps (1990). - """ + test_suite = True @staticmethod @@ -33,8 +31,10 @@ def _game(unique_NE_variant: bool = False) -> gbt.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, after Kreps (1990)" + 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")) diff --git a/tests/test_catalog.py b/tests/test_catalog.py index c8d142057..0310e0ae8 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -7,7 +7,7 @@ class ExampleGame(gbt.catalog.CatalogGame): @staticmethod def _game(some_param: bool = False): - """Desc2""" + """_game docstring""" if some_param: g = gbt.Game.new_tree( players=["A", "B"], title="Test game T" @@ -16,13 +16,13 @@ def _game(some_param: bool = False): g = gbt.Game.new_tree( players=["A", "B"], title="Test game F" ) - g.description = "Desc1" + g.description = "Game description" return g class ExampleGameWithDocstring(ExampleGame): """ - Desc3 + Class docstring """ @@ -34,17 +34,21 @@ def test_catalog_game_not_instantiable(self): with pytest.raises(NotImplementedError): gbt.catalog.CatalogGame() - def test_description_handling_from_game(self): - """Custom CatalogGame subclass should extract description from game object.""" - assert "Desc1" in ExampleGame().description - # Also should include _game docstring - assert "Desc2" in ExampleGame().description + 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.""" - assert "Desc3" in ExampleGameWithDocstring().description - # Also should include _game docstring - assert "Desc2" in ExampleGameWithDocstring().description + """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" not in ExampleGameWithDocstring.__doc__ def test_catalog_py_game_with_parameters(self): """ @@ -58,11 +62,11 @@ 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(self): + 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().description + gbt.catalog.PrisonersDilemma.__doc__ ) def test_catalog_game_missing_game_method(self): From b89484629a482681aee685945b8a8c693b418f1a Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 16:12:33 +0000 Subject: [PATCH 163/170] Further improve title and description handling --- src/pygambit/catalog.py | 21 +++++++++++---------- tests/test_catalog.py | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 04ad51453..6dc032d60 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -26,7 +26,7 @@ class CatalogGame: def __new__(cls, *args, **kwargs) -> gbt.Game: """Create a game instance by calling the _game() method.""" cls.game = cls._game(*args, **kwargs) - cls._extract_description(cls.game) + cls._build_docstring(cls.game) return cls.game @staticmethod @@ -53,15 +53,16 @@ def _game() -> gbt.Game: # return docstring @classmethod - def _extract_description(cls, game: gbt.Game) -> None: - """Create game description and docstring from catalog.""" - cleaned_docstring = "" + def _build_docstring(cls, game: gbt.Game) -> None: + """Create the docstring for the catalog entry.""" + cleaned_docstring = "**Title:** " + game.title + "\n\n**Description:** " + # If the subclass has a docstring, use this as description if cls.__doc__: - cleaned_docstring = inspect.cleandoc(cls.__doc__) + cleaned_docstring += inspect.cleandoc(cls.__doc__) + # Otherwise create one from the game else: - cleaned_docstring = game.title + "\n\n" + game.description - if len(cleaned_docstring) > 0: - cleaned_docstring += "\n" + cleaned_docstring += game.description + cleaned_docstring += "\n" # TODO: Add images for catalog games # cleaned_docstring += cls._img_docstring() @@ -85,7 +86,7 @@ def __init_subclass__(cls, **kwargs): # Load game and extract metadata immediately when class is defined cls.game = cls._game() - cls._extract_description(cls.game) + cls._build_docstring(cls.game) class CatalogGameFromFile(CatalogGame): @@ -128,7 +129,7 @@ def __init_subclass__(cls, **kwargs): # Load game and extract metadata immediately when class is defined cls.game = cls._load_game() - cls._extract_description(cls.game) + cls._build_docstring(cls.game) def games( diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 0310e0ae8..62c8d8b64 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -48,7 +48,7 @@ def test_description_handling_from_docstring(self): assert "Class docstring" in ExampleGameWithDocstring.__doc__ assert "Game description" not in ExampleGameWithDocstring.__doc__ assert "_game docstring" in ExampleGameWithDocstring.__doc__ - assert "Test game F" not in ExampleGameWithDocstring.__doc__ + assert "Test game F" in ExampleGameWithDocstring.__doc__ def test_catalog_py_game_with_parameters(self): """ From 3db8933259d4bd0afbe9a063a0a6f41dbb43903b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 16:26:40 +0000 Subject: [PATCH 164/170] remove catalog section from tutorial 2 --- doc/tutorials/02_extensive_form.ipynb | 314 ++------------------------ 1 file changed, 24 insertions(+), 290 deletions(-) diff --git a/doc/tutorials/02_extensive_form.ipynb b/doc/tutorials/02_extensive_form.ipynb index 7aa32a64e..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,7 +72,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 24, "id": "3cd94917", "metadata": {}, "outputs": [ @@ -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,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 26, "id": "45638fda-7e25-4c8e-b709-24b05780581b", "metadata": {}, "outputs": [ @@ -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,7 +295,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 28, "id": "ce41e9fe-cca4-46fb-8e9d-b2c27342e5ef", "metadata": {}, "outputs": [ @@ -473,7 +473,7 @@ "" ] }, - "execution_count": 7, + "execution_count": 28, "metadata": {}, "output_type": "execute_result" } @@ -496,7 +496,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 29, "id": "716e9b9a", "metadata": {}, "outputs": [], @@ -512,7 +512,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 30, "id": "b3408c55-714e-4a6f-b598-e338839442e4", "metadata": {}, "outputs": [ @@ -666,7 +666,7 @@ "" ] }, - "execution_count": 9, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } @@ -685,7 +685,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 31, "id": "695b1aad", "metadata": {}, "outputs": [], @@ -701,7 +701,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 32, "id": "09bedb3a-aac7-46e6-ae93-c47932c746d4", "metadata": {}, "outputs": [ @@ -869,7 +869,7 @@ "" ] }, - "execution_count": 11, + "execution_count": 32, "metadata": {}, "output_type": "execute_result" } @@ -888,7 +888,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 33, "id": "0704ef86", "metadata": {}, "outputs": [], @@ -904,7 +904,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 34, "id": "cba0e562-2989-4dae-a0f0-b121635ba032", "metadata": {}, "outputs": [ @@ -1080,7 +1080,7 @@ "" ] }, - "execution_count": 13, + "execution_count": 34, "metadata": {}, "output_type": "execute_result" } @@ -1096,273 +1096,7 @@ "source": [ "Nodes without an outcome attached are assumed to have payoffs of zero for all players.\n", "\n", - "Therefore, adding the outcome to this latter terminal node is not strictly necessary in Gambit, but it is useful to be explicit for readability.\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": 14, - "id": "3207441f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "['Coord2',\n", - " 'Coord3',\n", - " 'Coord4',\n", - " 'E04',\n", - " 'Hs1',\n", - " 'Km1',\n", - " 'Km2',\n", - " 'My_3_3a',\n", - " 'My_3_3b',\n", - " 'My_3_3c',\n", - " 'My_3_3d',\n", - " 'My_3_3e',\n", - " 'OneShotTrust',\n", - " 'Sh3',\n", - " 'Sww1']" - ] - }, - "execution_count": 14, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "gbt.catalog.games(\n", - " num_players=2,\n", - " num_infosets=2\n", - ")" - ] - }, - { - "cell_type": "markdown", - "id": "af175f99", - "metadata": {}, - "source": [ - "Some games in the catalog have configurable parameters.\n", - "For example, the `OneShotTrust` game has the `unique_NE_variant` parameter, which is explained in the game's description:" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "2933061a-a2eb-4ca7-8ce3-f350bc0678ce", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The unique_NE_variant makes Trust a dominant strategy, replacing the\n", - "non-singleton equilibrium component from the standard version of the game\n", - "where the Buyer plays \"Not Trust\" and the seller can play any mixture with\n", - "< 0.5 probability on Honor with a unique NE where the Buyer plays Trust and\n", - "the Seller plays Abuse.\n" - ] - } - ], - "source": [ - "g_variant = gbt.catalog.OneShotTrust(unique_NE_variant=True)\n", - "print(g_variant.description)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "4556c0bf", - "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", - "" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "draw_tree(g_variant)" + "Therefore, adding the outcome to this latter terminal node is not strictly necessary in Gambit, but it is useful to be explicit for readability." ] }, { @@ -1382,7 +1116,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 35, "id": "37c51152", "metadata": {}, "outputs": [], @@ -1400,7 +1134,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 36, "id": "0d86a750", "metadata": {}, "outputs": [], @@ -1419,7 +1153,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 37, "id": "1bab777f-8a0b-4f1e-9c0c-270690288243", "metadata": {}, "outputs": [], @@ -1431,7 +1165,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 38, "id": "2b715221-e427-4092-ad2f-9f4f2b548fa4", "metadata": {}, "outputs": [], From 505044e061d48bdfc658f280ba5fcf2396a7b3ab Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 16:34:08 +0000 Subject: [PATCH 165/170] Add how to update the description --- doc/developer.catalog.rst | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index 7cf89fdbe..3b065b8a8 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -37,8 +37,16 @@ You may wish to first review the :ref:`pygambit ` docs pages. 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. + 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:** From 4d482b7c6c6f2d8718847790eaf2a578307b67c7 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 16:46:59 +0000 Subject: [PATCH 166/170] tidy developer.catalog.rst --- doc/developer.catalog.rst | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index 3b065b8a8..e83d1c66b 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -20,7 +20,7 @@ You may wish to first review the :ref:`pygambit ` docs pages. 2. **Add the game file:** - On a new branch, commit the new game to `src/pygambit/catalog_game_files` + Create a new branch in the `gambit` repo and commit the new game to `src/pygambit/catalog_game_files` 3. **Update the catalog:** @@ -35,7 +35,7 @@ You may wish to first review the :ref:`pygambit ` docs pages. .. note:: Run this script in a Python environment where `pygambit` itself is also :ref:`installed ` - 4. **[Optional] Edit the catalog entry** + 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. @@ -68,7 +68,7 @@ You may wish to first review the :ref:`pygambit ` docs pages. metadata: my_metadata: 17 - 6. Sumbmit a pull request to GitHub with all changes + 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. @@ -92,19 +92,15 @@ You may wish to first review the :ref:`pygambit ` docs pages. Open `src/pygambit/catalog_games.py` and 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. - Docstrings will appear in the Catalog API reference pages. .. code-block:: python class MyGame(gbt.catalog.CatalogGame): - """ - High level description of game, originally from Author (2000). - """ @staticmethod def _game(some_param: bool = False) -> gbt.Game: """ - Additional game description, describing variants. + Docstring for _game function. Parameters ---------- @@ -123,8 +119,9 @@ You may wish to first review the :ref:`pygambit ` docs pages. >>> MyGame(some_param=True) # Constructs the alternate game """ g = gbt.Game.new_tree( - players=["Buyer", "Seller"], title="My game, after Author (2000)" + 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")) @@ -139,6 +136,9 @@ You may wish to first review the :ref:`pygambit ` docs pages. 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. @@ -167,14 +167,11 @@ You may wish to first review the :ref:`pygambit ` docs pages. .. code-block:: python - ... - """ - High level description of game, originally from Author (2000). - """ - my_metadata = 17 + class MyGame(gbt.catalog.CatalogGame): + my_metadata = 17 ... - 6. Sumbmit a pull request to GitHub with all changes + 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. From 3a85cbee628705254530c8bd018943347d420fb1 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 16:49:35 +0000 Subject: [PATCH 167/170] reference branch creation --- doc/developer.catalog.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index e83d1c66b..b0e42c154 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -89,7 +89,8 @@ You may wish to first review the :ref:`pygambit ` docs pages. 2. **Create a new Python class for your game:** - Open `src/pygambit/catalog_games.py` and add a subclass of `CatalogGame` with your code implemented as the `_game` function, + 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. From 811d6bd1dc2503f0bbf2da05e002d789710119dc Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Mon, 26 Jan 2026 16:59:30 +0000 Subject: [PATCH 168/170] load from catalog in starting points tutorial --- .../advanced_tutorials/starting_points.ipynb | 90 ++++++++++--------- 1 file changed, 50 insertions(+), 40 deletions(-) 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" }, From 331f4d42806a00819d0ac0e0bc97f4b9c394e73b Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 27 Jan 2026 09:27:25 +0000 Subject: [PATCH 169/170] refactor build_docstring --- src/pygambit/catalog.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/pygambit/catalog.py b/src/pygambit/catalog.py index 6dc032d60..6049baa92 100644 --- a/src/pygambit/catalog.py +++ b/src/pygambit/catalog.py @@ -55,13 +55,18 @@ def _game() -> gbt.Game: @classmethod def _build_docstring(cls, game: gbt.Game) -> None: """Create the docstring for the catalog entry.""" - cleaned_docstring = "**Title:** " + game.title + "\n\n**Description:** " - # If the subclass has a docstring, use this as description + # 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__: - cleaned_docstring += inspect.cleandoc(cls.__doc__) - # Otherwise create one from the game - else: - cleaned_docstring += game.description + desc = inspect.cleandoc(cls.__doc__) + if desc: + cleaned_docstring += "\n\n**Description:** " + desc + cleaned_docstring += "\n" # TODO: Add images for catalog games From bef688b7cd1a26753624769d14cb73ca2de29776 Mon Sep 17 00:00:00 2001 From: Ed Chalstrey Date: Tue, 27 Jan 2026 09:37:12 +0000 Subject: [PATCH 170/170] Explain metadata --- doc/developer.catalog.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index b0e42c154..264e3d37b 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -11,6 +11,9 @@ You can add games to the catalog saved in a valid representation :ref:`format ` 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