diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 1378ecef40..0000000000 --- a/.flake8 +++ /dev/null @@ -1,14 +0,0 @@ -[flake8] -max-line-length = 99 -indent-width = 4 -extend-ignore = E203 -include = - setup.py - src/pygambit - tests -exclude = - .git - __pycache__ - contrib - build - dist diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ac27a84869..30ceb64412 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,17 @@ updates: schedule: # Check for updates to GitHub Actions every week interval: "weekly" + groups: + github-actions: + patterns: + - "*" + + # Maintain Python build and test dependencies + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + groups: + python-dependencies: + patterns: + - "*" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 48f092c06e..1ef97e45cd 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,6 +4,13 @@ on: push: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + jobs: clang-format: name: clang-format @@ -11,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v7 - name: Run clang-format style check for C/C++ - uses: jidicula/clang-format-action@v4.18.0 + uses: jidicula/clang-format-action@654a770daa28443dd111d133e4083e21c1075674 # v4.18.0 with: clang-format-version: '17' check-path: 'src' @@ -24,7 +31,6 @@ jobs: - uses: actions/checkout@v7 - name: Install dependencies run: | - sudo apt-key adv --fetch-keys http://repos.codelite.org/CodeLite.asc sudo apt-get update sudo apt-get install -y automake autoconf libwxgtk3.2-dev clang-tidy - run: aclocal @@ -38,17 +44,7 @@ jobs: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v7 - - uses: chartboost/ruff-action@v1 - - flake8: - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 - with: - python-version: "3.14" - - uses: py-actions/flake8@v2 + - uses: astral-sh/ruff-action@4919ec5cf1f49eff0871dbcea0da843445b837e6 # v3 cython-lint: runs-on: ubuntu-latest @@ -74,6 +70,8 @@ jobs: uses: actions/setup-python@v7 with: python-version: "3.14" + cache: pip + cache-dependency-path: pyproject.toml - name: Install pygambit (editable, picks up catalog/games directly) run: | python -m pip install -v -e ".[doc]" diff --git a/.github/workflows/osxbinary.yml b/.github/workflows/osxbinary.yml index 343f2dd31c..e3a53eac51 100644 --- a/.github/workflows/osxbinary.yml +++ b/.github/workflows/osxbinary.yml @@ -8,23 +8,43 @@ on: - cron: '0 6 * * 4' workflow_dispatch: +permissions: + contents: read + jobs: - macos-13: - runs-on: macos-14 - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + macos: + name: macOS DMG (wxWidgets 3.2) + runs-on: macos-15 steps: - uses: actions/checkout@v7 + - name: Find latest wxWidgets 3.2.x release + run: | + version=$(git ls-remote --tags --refs https://github.com/wxWidgets/wxWidgets 'v3.2.*' \ + | sed 's|.*refs/tags/v||' | grep -E '^3\.2\.[0-9]+$' | sort -V | tail -1) + test -n "${version}" + echo "Using wxWidgets ${version}" + echo "WX_VERSION=${version}" >> "$GITHUB_ENV" - name: Install dependencies + run: brew install automake autoconf + - name: Cache static wxWidgets build + id: wx-cache + uses: actions/cache@v6 + with: + path: ~/wx-static + key: wx-static-${{ runner.os }}-${{ runner.arch }}-${{ env.WX_VERSION }} + - name: Build static wxWidgets + if: steps.wx-cache.outputs.cache-hit != 'true' run: | - brew install automake autoconf - curl -L -O https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.8/wxWidgets-3.2.8.tar.bz2 - tar xjf wxWidgets-3.2.8.tar.bz2 - cd wxWidgets-3.2.8 + curl -L -O "https://github.com/wxWidgets/wxWidgets/releases/download/v${WX_VERSION}/wxWidgets-${WX_VERSION}.tar.bz2" + tar xjf "wxWidgets-${WX_VERSION}.tar.bz2" + cd "wxWidgets-${WX_VERSION}" mkdir build-release cd build-release - ../configure --disable-shared --disable-sys-libs - make -j4 - sudo make install + ../configure --prefix="$HOME/wx-static" --disable-shared --disable-sys-libs + make -j"$(sysctl -n hw.ncpu)" + make install + - name: Put wxWidgets on PATH + run: echo "$HOME/wx-static/bin" >> "$GITHUB_PATH" - run: aclocal - run: automake --add-missing - run: autoconf @@ -35,7 +55,7 @@ jobs: - run: make dist - uses: actions/upload-artifact@v7 with: - name: artifact-osx-14 + name: artifact-osx path: "*.dmg" - uses: actions/upload-artifact@v7 with: diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index a3b36eac14..f3524db756 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -4,13 +4,20 @@ on: push: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + jobs: linux: runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: - python-version: ['3.10', '3.14'] + python-version: ['3.11', '3.14', '3.15'] steps: - uses: actions/checkout@v7 @@ -18,6 +25,9 @@ jobs: uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true + cache: pip + cache-dependency-path: pyproject.toml - name: Set up dependencies run: | python -m pip install --upgrade pip @@ -34,8 +44,8 @@ jobs: - name: Run tests run: pytest --run-tutorials - macos-14: - runs-on: macos-14 + macos-latest: + runs-on: macos-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name strategy: matrix: @@ -47,12 +57,14 @@ jobs: uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml - name: Set up dependencies run: | python -m pip install --upgrade pip - name: Build extension run: | - python -m pip install -v .[test,doc] + python -m pip install -v ".[test,doc]" pip install "gtdraw @ git+https://github.com/gambitproject/gtdraw.git@main" - name: Run tests run: pytest --run-tutorials @@ -70,12 +82,14 @@ jobs: uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml - name: Set up dependencies run: | python -m pip install --upgrade pip - name: Build extension run: | - python -m pip install -v .[test,doc] + python -m pip install -v ".[test,doc]" pip install "gtdraw @ git+https://github.com/gambitproject/gtdraw.git@main" - name: Run tests run: pytest --run-tutorials @@ -93,13 +107,15 @@ jobs: uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml - name: Set up dependencies run: | python -m pip install --upgrade pip - name: Build extension shell: bash run: | - python -m pip install -v .[test,doc] + python -m pip install -v ".[test,doc]" pip install "gtdraw @ git+https://github.com/gambitproject/gtdraw.git@main" - name: Run tests run: pytest --run-tutorials diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8b8c1e9d9c..4ddaf41c01 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,14 +15,18 @@ jobs: - uses: actions/checkout@v7 - name: Extract changelog for this version + env: + REF_NAME: ${{ github.ref_name }} run: | - VERSION="${{ github.ref_name }}" + VERSION="${REF_NAME}" python3 build_support/releases/extract_changelog.py "${VERSION#v}" - name: Determine prerelease status id: version + env: + REF_NAME: ${{ github.ref_name }} run: | - VERSION="${{ github.ref_name }}" + VERSION="${REF_NAME}" if [[ "${VERSION#v}" =~ -(alpha|beta|rc) ]]; then echo "prerelease_flag=--prerelease" >> "$GITHUB_OUTPUT" else @@ -32,8 +36,11 @@ jobs: - name: Create GitHub release env: GH_TOKEN: ${{ github.token }} + REF_NAME: ${{ github.ref_name }} + PRERELEASE_FLAG: ${{ steps.version.outputs.prerelease_flag }} run: | - gh release create "${{ github.ref_name }}" \ - --title "Gambit ${{ github.ref_name }}" \ + # shellcheck disable=SC2086 # the flag must vanish when empty + gh release create "${REF_NAME}" \ + --title "Gambit ${REF_NAME}" \ --notes-file release_notes.md \ - ${{ steps.version.outputs.prerelease_flag }} + ${PRERELEASE_FLAG} diff --git a/.github/workflows/tools.yml b/.github/workflows/tools.yml index 39fad82e81..a9c1f7a4d1 100644 --- a/.github/workflows/tools.yml +++ b/.github/workflows/tools.yml @@ -4,15 +4,22 @@ on: push: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + jobs: linux: + name: Linux (wxWidgets 3.2) runs-on: ubuntu-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v7 - name: Install dependencies run: | - sudo apt-key adv --fetch-keys http://repos.codelite.org/CodeLite.asc sudo apt-get update sudo apt-get install -y automake autoconf libwxgtk3.2-dev - name: Configure build with autotools @@ -33,12 +40,13 @@ jobs: sudo make install macos: + name: macOS (wxWidgets 3.3) runs-on: macos-15 if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name steps: - uses: actions/checkout@v7 - name: Install dependencies - run: brew install automake autoconf wxwidgets + run: brew install automake autoconf wxwidgets@3.3 - run: aclocal - run: automake --add-missing - run: autoconf @@ -46,6 +54,7 @@ jobs: - run: make windows: + name: Windows (wxWidgets 3.2) runs-on: windows-latest if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name defaults: @@ -53,7 +62,7 @@ jobs: shell: msys2 {0} steps: - uses: actions/checkout@v7 - - uses: msys2/setup-msys2@v2 + - uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2 with: install: mingw-w64-x86_64-toolchain make automake autoconf - run: pacman -S --noconfirm mingw-w64-x86_64-wxwidgets3.2 diff --git a/.github/workflows/wheel-smoke.yml b/.github/workflows/wheel-smoke.yml index 622faa5c28..b61d5cee04 100644 --- a/.github/workflows/wheel-smoke.yml +++ b/.github/workflows/wheel-smoke.yml @@ -1,13 +1,32 @@ - name: pygambit wheel smoke test on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + jobs: - windows-cp314: - runs-on: windows-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + smoke: + name: ${{ matrix.name }} + strategy: + fail-fast: false + matrix: + include: + - name: Windows cp314 + os: windows-latest + build: cp314-win_amd64 + - name: Linux cp314 + os: ubuntu-latest + build: cp314-manylinux_x86_64 + - name: macOS cp314 + os: macos-15 + build: cp314-macosx_arm64 + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v7 @@ -19,8 +38,8 @@ jobs: run: | python -m pip install --upgrade pip pip install cibuildwheel - - name: Build and smoke-test cp314 Windows wheel + - name: Build and smoke-test wheel run: | python -m cibuildwheel --output-dir wheelhouse/ env: - CIBW_BUILD: "cp314-win_amd64" + CIBW_BUILD: ${{ matrix.build }} diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index fecbdf1310..28dcad1f9f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -6,16 +6,19 @@ on: tags: - 'v*' schedule: - - cron: '0 6 * * 4' + - cron: '0 6 1 * *' workflow_dispatch: +permissions: + contents: read + jobs: wheels: strategy: fail-fast: false matrix: - os: [windows-latest, ubuntu-latest, macos-14] - python-tag: [cp310, cp311, cp312, cp313, cp314] + os: [windows-latest, ubuntu-latest, macos-15] + python-tag: [cp311, cp312, cp313, cp314, cp315] runs-on: ${{ matrix.os }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 311bf43f41..c48ece4b02 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,10 +10,6 @@ repos: rev: v0.14.5 hooks: - id: ruff -- repo: https://github.com/pycqa/flake8 - rev: 7.3.0 - hooks: - - id: flake8 - repo: https://github.com/MarcoGorelli/cython-lint rev: v0.18.1 hooks: diff --git a/ChangeLog b/ChangeLog index 61b96d1dc3..0fda659d47 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,6 +10,8 @@ distribution over full histories (discussion #1094) ### Changed +- Python 3.10 is no longer supported; pygambit now requires Python 3.11 or later, and + Python 3.15 is supported. - Refined the perturbation approach in `path.cc` (logit and hp solvers) to better handle paths on games with bifurcations. (#492) - Nash solver algorithms now return a method-specific `Result` class, rather than a generic class, diff --git a/doc/developer.build.rst b/doc/developer.build.rst index 98717f736e..fc3d2e9ea1 100644 --- a/doc/developer.build.rst +++ b/doc/developer.build.rst @@ -100,6 +100,16 @@ Install build tools and dependencies When building for a different target (32-bit) substitute the corresponding MinGW packages (``mingw-w64-i686-*``). +.. _wxwidgets-versions: + +Supported wxWidgets versions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The GUI requires wxWidgets 3.2 or later. Official macOS binaries are built against +the latest stable 3.2.x release. Continuous integration also checks that Gambit builds +successfully against the latest 3.3.x release (the ``wxwidgets@3.3`` Homebrew formula), +but 3.3.x is not yet used for released binaries. + .. _cli-gui-from-source: Install CLI and GUI from source 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 4dcb49b1e2..b931587e19 100644 --- a/doc/tutorials/advanced_tutorials/agent_versus_non_agent_regret.ipynb +++ b/doc/tutorials/advanced_tutorials/agent_versus_non_agent_regret.ipynb @@ -129,8 +129,8 @@ "metadata": {}, "outputs": [], "source": [ - "starting_profile_double = g.mixed_strategy_profile(data=[[0,1,0],[1,0]], rational=False)\n", - "starting_profile_rational = g.mixed_strategy_profile(data=[[0,1,0],[1,0]], rational=True)\n", + "starting_profile_double = g.mixed_strategy_profile(data=[[0, 1, 0], [1, 0]], rational=False)\n", + "starting_profile_rational = g.mixed_strategy_profile(data=[[0, 1, 0], [1, 0]], rational=True)\n", "print(\"Max regret of starting profile:\", starting_profile_rational.max_regret())\n", "print(\"Liapunov value of starting profile:\", starting_profile_rational.liap_value())" ] @@ -180,7 +180,9 @@ "metadata": {}, "outputs": [], "source": [ - "candidate_eq_rat = g.mixed_strategy_profile(data=[[0,\"1/2\",\"1/2\"],[\"1/3\",\"2/3\"]], rational=True)\n", + "candidate_eq_rat = g.mixed_strategy_profile(\n", + " data=[[0, \"1/2\", \"1/2\"], [\"1/3\", \"2/3\"]], rational=True\n", + ")\n", "print(\"Liap value:\", candidate_eq_rat.liap_value())\n", "print(\"Max regret:\", candidate_eq_rat.max_regret())" ] diff --git a/doc/tutorials/advanced_tutorials/h_selector_prototype.ipynb b/doc/tutorials/advanced_tutorials/h_selector_prototype.ipynb index f94497a382..9e57bb362e 100644 --- a/doc/tutorials/advanced_tutorials/h_selector_prototype.ipynb +++ b/doc/tutorials/advanced_tutorials/h_selector_prototype.ipynb @@ -147,7 +147,7 @@ } }, "outputs": [], - "source": "def winner(h):\n match h.actions[2:]:\n case (\"Check\", \"Check\") | (\"Check\", \"Bet\", \"Call\") | (\"Bet\", \"Call\"):\n return \"Alice\" if CARD_VALUE[h.actions[0]] > CARD_VALUE[h.actions[1]] else \"Bob\"\n case (\"Check\", \"Bet\", \"Fold\"):\n return \"Bob\"\n case (\"Bet\", \"Fold\"):\n return \"Alice\"\n\ndef pot_size(h):\n match h.actions[2:]:\n case (\"Check\", \"Check\") | (\"Check\", \"Bet\", \"Fold\") | (\"Bet\", \"Fold\"):\n return 1\n case (\"Check\", \"Bet\", \"Call\") | (\"Bet\", \"Call\"):\n return 2\n\nfor (win, amount), group in g._get_groups(H.plays.by(lambda h: (winner(h), pot_size(h)))).items():\n lose = \"Bob\" if win == \"Alice\" else \"Alice\"\n g.make_outcome(\n selector_for_histories(group), {win: amount, lose: -amount}, f\"{win} wins {amount}\"\n )\n\nprint(\"Total outcomes created:\", len(g.get_outcomes()))" + "source": "def winner(h):\n match h.actions[2:]:\n case (\"Check\", \"Check\") | (\"Check\", \"Bet\", \"Call\") | (\"Bet\", \"Call\"):\n return \"Alice\" if CARD_VALUE[h.actions[0]] > CARD_VALUE[h.actions[1]] else \"Bob\"\n case (\"Check\", \"Bet\", \"Fold\"):\n return \"Bob\"\n case (\"Bet\", \"Fold\"):\n return \"Alice\"\n\n\ndef pot_size(h):\n match h.actions[2:]:\n case (\"Check\", \"Check\") | (\"Check\", \"Bet\", \"Fold\") | (\"Bet\", \"Fold\"):\n return 1\n case (\"Check\", \"Bet\", \"Call\") | (\"Bet\", \"Call\"):\n return 2\n\n\nfor (win, amount), group in g._get_groups(H.plays.by(lambda h: (winner(h), pot_size(h)))).items():\n lose = \"Bob\" if win == \"Alice\" else \"Alice\"\n g.make_outcome(\n selector_for_histories(group), {win: amount, lose: -amount}, f\"{win} wins {amount}\"\n )\n\nprint(\"Total outcomes created:\", len(g.get_outcomes()))" }, { "cell_type": "markdown", @@ -248,7 +248,7 @@ } }, "outputs": [], - "source": "g = gbt.ExtensiveGame(players=[\"Player 1\", \"Player 2\"], title=\"Untimeable (Jakobsen et al. 2016)\")\n\ng.append_event(H.path(), dict.fromkeys([\"1\", \"2\"], gbt.Rational(1, 2)))\n\ng.append_move(H.path(\"1\"), \"Player 2\", [\"1\", \"2\"])\ng.append_move(H.path(\"2\"), \"Player 1\", [\"1\", \"2\"])\n\ng.append_infoset(H.path(\"1\", ...), H.path(\"2\"))\ng.append_infoset(H.path(\"2\", ...), H.path(\"1\"))\n\ndef outcome_key(h):\n p1_guess = h.last_action(\"Player 1\")\n p2_guess = h.last_action(\"Player 2\")\n return (p1_guess == h[0].action, p2_guess != h[0].action)\n\nfor (p1_ok, p2_ok), group in g._get_groups(H.plays.by(outcome_key)).items():\n g.make_outcome(\n selector_for_histories(group), {\"Player 1\": int(p1_ok), \"Player 2\": int(p2_ok)},\n f\"P1 {'correct' if p1_ok else 'wrong'}, P2 {'correct' if p2_ok else 'wrong'}\",\n )\n\ndraw(g)\nprint(\"is_perfect_recall:\", g.is_perfect_recall)" + "source": "g = gbt.ExtensiveGame(players=[\"Player 1\", \"Player 2\"], title=\"Untimeable (Jakobsen et al. 2016)\")\n\ng.append_event(H.path(), dict.fromkeys([\"1\", \"2\"], gbt.Rational(1, 2)))\n\ng.append_move(H.path(\"1\"), \"Player 2\", [\"1\", \"2\"])\ng.append_move(H.path(\"2\"), \"Player 1\", [\"1\", \"2\"])\n\ng.append_infoset(H.path(\"1\", ...), H.path(\"2\"))\ng.append_infoset(H.path(\"2\", ...), H.path(\"1\"))\n\n\ndef outcome_key(h):\n p1_guess = h.last_action(\"Player 1\")\n p2_guess = h.last_action(\"Player 2\")\n return (p1_guess == h[0].action, p2_guess != h[0].action)\n\n\nfor (p1_ok, p2_ok), group in g._get_groups(H.plays.by(outcome_key)).items():\n g.make_outcome(\n selector_for_histories(group), {\"Player 1\": int(p1_ok), \"Player 2\": int(p2_ok)},\n f\"P1 {'correct' if p1_ok else 'wrong'}, P2 {'correct' if p2_ok else 'wrong'}\",\n )\n\ndraw(g)\nprint(\"is_perfect_recall:\", g.is_perfect_recall)" }, { "cell_type": "markdown", diff --git a/doc/tutorials/interoperability_tutorials/openspiel.ipynb b/doc/tutorials/interoperability_tutorials/openspiel.ipynb index 5705214529..f94d32bca6 100644 --- a/doc/tutorials/interoperability_tutorials/openspiel.ipynb +++ b/doc/tutorials/interoperability_tutorials/openspiel.ipynb @@ -117,8 +117,8 @@ "metadata": {}, "outputs": [], "source": [ - "print(state.legal_actions(0)) # Player 0 (row) actions\n", - "print(state.legal_actions(1)) # Player 1 (column) actions" + "print(state.legal_actions(0)) # Player 0 (row) actions\n", + "print(state.legal_actions(1)) # Player 1 (column) actions" ] }, { @@ -248,6 +248,7 @@ " plt.legend()\n", " plt.show()\n", "\n", + "\n", "plot_rps_dynamics([0.3, 0.3, 0.4])" ] }, @@ -384,6 +385,7 @@ "matrix_pd_payoffs = game_payoffs_array(ops_prisoners_dilemma_game)\n", "pd_dyn = dynamics.SinglePopulationDynamics(matrix_pd_payoffs, dynamics.replicator)\n", "\n", + "\n", "def plot_pd_dynamics(proportions, steps=100, alpha=0.1):\n", " x = np.array(proportions)\n", " y = []\n", @@ -398,6 +400,7 @@ " plt.legend()\n", " plt.show()\n", "\n", + "\n", "plot_pd_dynamics([0.8, 0.2])" ] }, @@ -590,18 +593,18 @@ "outputs": [], "source": [ "for cur_episode in range(30000):\n", - " if cur_episode % 10000 == 0:\n", - " print(f\"Episodes: {cur_episode}\")\n", + " if cur_episode % 10000 == 0:\n", + " print(f\"Episodes: {cur_episode}\")\n", "\n", - " time_step = env.reset()\n", - " while not time_step.last():\n", - " player_id = time_step.observations[\"current_player\"]\n", - " agent_output = agents[player_id].step(time_step)\n", - " time_step = env.step([agent_output.action])\n", + " time_step = env.reset()\n", + " while not time_step.last():\n", + " player_id = time_step.observations[\"current_player\"]\n", + " agent_output = agents[player_id].step(time_step)\n", + " time_step = env.step([agent_output.action])\n", "\n", - " # Episode is over, step all agents with final info state.\n", - " for agent in agents:\n", - " agent.step(time_step)\n", + " # Episode is over, step all agents with final info state.\n", + " for agent in agents:\n", + " agent.step(time_step)\n", "\n", "print(f\"Episodes: {cur_episode+1}\")" ] @@ -624,13 +627,13 @@ "time_step = env.reset()\n", "\n", "while not time_step.last():\n", - " print(\"\")\n", - " print(env.get_state)\n", + " print(\"\")\n", + " print(env.get_state)\n", "\n", - " player_id = time_step.observations[\"current_player\"]\n", - " agent_output = agents[player_id].step(time_step, is_evaluation=True)\n", - " print(f\"Agent {player_id} chooses {env.get_state.action_to_string(agent_output.action)}\")\n", - " time_step = env.step([agent_output.action])\n", + " player_id = time_step.observations[\"current_player\"]\n", + " agent_output = agents[player_id].step(time_step, is_evaluation=True)\n", + " print(f\"Agent {player_id} chooses {env.get_state.action_to_string(agent_output.action)}\")\n", + " time_step = env.step([agent_output.action])\n", "\n", "print(\"\")\n", "print(env.get_state)\n", @@ -720,25 +723,25 @@ "state = ops_one_card_poker.new_initial_state()\n", "while not state.is_terminal():\n", "\n", - " # Store legal actions of current player in a dict\n", - " legal_actions = {}\n", - " for action in state.legal_actions():\n", - " legal_actions[action] = state.action_to_string(state.current_player(), action)\n", - "\n", - " # If player is chance, choose an action according to probability\n", - " if state.is_chance_node():\n", - " outcomes_with_probs = state.chance_outcomes()\n", - " action_list, prob_list = zip(*outcomes_with_probs, strict=True)\n", - " action = np.random.choice(action_list, p=prob_list)\n", - " print(\"Dealt card: \", legal_actions[action])\n", - " state.apply_action(action)\n", - "\n", - " # Regular players pick a random legal action.\n", - " else:\n", - " action = np.random.choice(state.legal_actions())\n", - " print(players[state.current_player()], \" action: \", legal_actions[action])\n", - " state.apply_action(action)\n", - " print()\n", + " # Store legal actions of current player in a dict\n", + " legal_actions = {}\n", + " for action in state.legal_actions():\n", + " legal_actions[action] = state.action_to_string(state.current_player(), action)\n", + "\n", + " # If player is chance, choose an action according to probability\n", + " if state.is_chance_node():\n", + " outcomes_with_probs = state.chance_outcomes()\n", + " action_list, prob_list = zip(*outcomes_with_probs, strict=True)\n", + " action = np.random.choice(action_list, p=prob_list)\n", + " print(\"Dealt card: \", legal_actions[action])\n", + " state.apply_action(action)\n", + "\n", + " # Regular players pick a random legal action.\n", + " else:\n", + " action = np.random.choice(state.legal_actions())\n", + " print(players[state.current_player()], \" action: \", legal_actions[action])\n", + " state.apply_action(action)\n", + " print()\n", "\n", "print(\"Alice receives: \", state.player_return(0), \", Bob receives: \", state.player_return(1))" ] diff --git a/pyproject.toml b/pyproject.toml index 52d3a7b4f3..162feee407 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "pygambit" dynamic = ["version"] description = "The package for computation in game theory" readme = "src/README.rst" -requires-python = ">=3.10" +requires-python = ">=3.11" license = "GPL-2.0-or-later" authors = [ {name = "Theodore Turocy", email = "ted.turocy@gmail.com"}, @@ -17,11 +17,11 @@ keywords = ["game theory", "Nash equilibrium"] classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Science/Research", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Implementation :: CPython", "Topic :: Scientific/Engineering :: Mathematics" ] @@ -77,8 +77,8 @@ Changelog = "https://github.com/gambitproject/gambit/blob/master/ChangeLog" [tool.ruff] line-length = 99 indent-width = 4 -target-version = "py310" -include = ["setup.py", "src/pygambit/**/*.py", "tests/**/*.py, doc/tutorials/*.ipynb"] +target-version = "py311" +include = ["setup.py", "src/pygambit/**/*.py", "tests/**/*.py", "doc/tutorials/*.ipynb"] [tool.ruff.lint] select = [ @@ -90,8 +90,21 @@ select = [ "I", # isort "Q", # prefer double quotes "W291", # trailing-whitespace + "W293", # blank-line-with-whitespace + "W391", # too-many-newlines-at-end-of-file + "W605", # invalid-escape-sequence + # pycodestyle whitespace and blank-line rules, still preview-only in ruff + "E111", "E112", "E113", "E114", "E115", "E116", "E117", + "E201", "E202", "E211", + "E221", "E222", "E223", "E224", "E225", "E227", "E228", + "E231", "E251", "E252", + "E261", "E262", "E265", "E266", + "E271", "E272", "E273", "E274", "E275", + "E301", "E302", "E303", "E304", "E305", "E306", ] ignore = ["E203"] +preview = true +explicit-preview-rules = true fixable = ["ALL"] unfixable = []