Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2a462cf
test_node.py: replace integer indexing with labels
d-kad Jun 9, 2026
1312032
test_outcomes.py: replace integer indexing with labels
d-kad Jun 9, 2026
23e402a
test_strategic.py: replace integer indexing with labels
d-kad Jun 9, 2026
01bf5cb
test_players.py: replace integer indexing with labels
d-kad Jun 9, 2026
3da2101
test_extensive.py: replace integer indexing with labels
d-kad Jun 9, 2026
a4d9922
test_game.py: replace integer indexing with labels
d-kad Jun 9, 2026
20c575a
games.py: replace integer indexing with labels
d-kad Jun 9, 2026
b87b711
nash.py: replace integer indexing with labels
d-kad Jun 9, 2026
655328b
test_mixed.py: replace integer indexing with iteration
d-kad Jun 9, 2026
09624e1
test_node.py: replace integer indexing with labels
d-kad Jun 9, 2026
1cc650d
test_infosets.py: replace integer indexing with labels
d-kad Jun 9, 2026
b1a2a8a
test_behav.py: replace integer indexing with labels
d-kad Jun 9, 2026
43e3982
test_actions.py: replace integer indexing with labels
d-kad Jun 9, 2026
62c57d5
Update tests for KeyError on missing action label in NodeChildren
d-kad Jun 11, 2026
50f93e0
docstring polishes
d-kad Jun 11, 2026
5dade61
Replace integer indexing of players, strategies, and infosets with
d-kad Jun 11, 2026
d7f8c01
Consolidate and extend tests for label-only collection indexing
d-kad Jun 11, 2026
b802aa3
Remove integer indexing from game collections
d-kad Jun 11, 2026
d38a8b5
add Changelog and api.rst
d-kad Jun 11, 2026
50a14d7
Strip surrounding whitespace from label lookup strings
d-kad Jun 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@
unsaved work. (#12)
- Created and documented right-click context menu in the normal form pivot table, which makes the
mechanism for deleting a strategy more clear. (#855)
- In `pygambit`, indexing game object collections by integer position has been removed.
The collections `Game.players`, `Game.outcomes`, `Game.strategies`, `Game.infosets`,
`Game.actions`, `Player.strategies`, `Player.infosets`, `Player.actions`, `Infoset.actions`,
and `Infoset.members`, as well as `Node.children`, are indexed by string label, and indexing by
an integer now raises `TypeError`.
Indexing a `Game` by a contingency (e.g. `game[0, 1]`) is unchanged.

### Removed
- Built-in plotting of logit QRE for strategic games has been removed in the GUI (#809)
Expand Down
1 change: 1 addition & 0 deletions doc/pygambit.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ Information about the game
Game.infosets
Game.nodes
Game.contingencies
Game.__getitem__
Game.subgames
Game.minimal_subgame

Expand Down
13 changes: 7 additions & 6 deletions doc/tutorials/01_quickstart.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,14 @@
"metadata": {},
"outputs": [],
"source": [
"g.players[0].label = \"Tom\"\n",
"g.players[0].strategies[0].label = \"Cooperate\"\n",
"g.players[0].strategies[1].label = \"Defect\"\n",
"tom, jerry = g.players\n",
"tom.label = \"Tom\"\n",
"jerry.label = \"Jerry\"\n",
"\n",
"g.players[1].label = \"Jerry\"\n",
"g.players[1].strategies[0].label = \"Cooperate\"\n",
"g.players[1].strategies[1].label = \"Defect\""
"for player in g.players:\n",
" cooperate, defect = player.strategies\n",
" cooperate.label = \"Cooperate\"\n",
" defect.label = \"Defect\""
]
},
{
Expand Down
9 changes: 5 additions & 4 deletions doc/tutorials/03_stripped_down_poker.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,8 @@
"outputs": [],
"source": [
"# Remember that Bob has a single information set\n",
"for action in g.players[\"Bob\"].infosets[0].actions:\n",
"(bob_infoset,) = g.players[\"Bob\"].infosets\n",
"for action in bob_infoset.actions:\n",
" print(\n",
" f\"When Bob plays {action.label} his expected payoff is {eqm.action_value(action)}\"\n",
" )"
Expand All @@ -520,7 +521,7 @@
"metadata": {},
"outputs": [],
"source": [
"for node in g.players[\"Bob\"].infosets[0].members:\n",
"for node in bob_infoset.members:\n",
" print(\n",
" f\"Bob's belief in reaching the {node.parent.prior_action.label} -> \"\n",
" f\"{node.prior_action.label} node is: {eqm.belief(node)}\"\n",
Expand All @@ -544,7 +545,7 @@
"metadata": {},
"outputs": [],
"source": [
"eqm.infoset_prob(g.players[\"Bob\"].infosets[0])"
"eqm.infoset_prob(bob_infoset)"
]
},
{
Expand All @@ -562,7 +563,7 @@
"metadata": {},
"outputs": [],
"source": [
"for node in g.players[\"Bob\"].infosets[0].members:\n",
"for node in bob_infoset.members:\n",
" print(\n",
" f\"The probability that the node {node.parent.prior_action.label} -> \"\n",
" f\"{node.prior_action.label} is reached is: {eqm.realiz_prob(node)}. \",\n",
Expand Down
10 changes: 5 additions & 5 deletions doc/tutorials/interoperability_tutorials/openspiel.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,8 @@
"gbt_matrix_rps_game.title = \"Rock-Paper-Scissors\"\n",
"\n",
"for player in gbt_matrix_rps_game.players:\n",
" player.strategies[0].label = \"Rock\"\n",
" player.strategies[1].label = \"Paper\"\n",
" player.strategies[2].label = \"Scissors\"\n",
" for strategy, label in zip(player.strategies, [\"Rock\", \"Paper\", \"Scissors\"], strict=True):\n",
" strategy.label = label\n",
"\n",
"gbt_matrix_rps_game"
]
Expand Down Expand Up @@ -356,11 +355,12 @@
"outputs": [],
"source": [
"p1_payoffs, p2_payoffs = gbt_prisoners_dilemma_game.to_arrays(dtype=float)\n",
"p1, p2 = gbt_prisoners_dilemma_game.players\n",
"ops_prisoners_dilemma_game = pyspiel.create_matrix_game(\n",
" gbt_prisoners_dilemma_game.title,\n",
" \"Classic Prisoner's Dilemma\", # description\n",
" [strategy.label for strategy in gbt_prisoners_dilemma_game.players[0].strategies],\n",
" [strategy.label for strategy in gbt_prisoners_dilemma_game.players[1].strategies],\n",
" [strategy.label for strategy in p1.strategies],\n",
" [strategy.label for strategy in p2.strategies],\n",
" p1_payoffs,\n",
" p2_payoffs\n",
")"
Expand Down
39 changes: 38 additions & 1 deletion src/pygambit/gambit.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import warnings
import typing

import cython
import typing

from .error import *

Expand Down Expand Up @@ -74,6 +73,44 @@ def _to_number(value: typing.Any) -> c_Number:
return c_Number(value.encode("ascii"))


@cython.cfunc
def _resolve_by_label(collection, label, scope: str, kind: str, kind_plural: str):
"""Resolve a member of a game collection by its text label.

Game collections are accessed by label, not by position. Lookup is by exact
label match; leading/trailing whitespace is stripped from `label` before comparison.
Failure modes:
* an ``int`` raises ``TypeError`` (integer indexing was removed in 16.7.0);
* any other non-``str`` raises ``TypeError``;
* an empty or all-whitespace label raises ``ValueError``;
* a label matching no member raises ``KeyError``;
* a label matching more than one member raises ``ValueError``.

The `label` parameter is left unannotated: a concrete type annotation is compiled by Cython
into an enforced argument check, raising a generic ``TypeError``
before this function's migration message runs.
"""
if isinstance(label, int):
raise TypeError(
f"{scope} {kind_plural} cannot be indexed by position; reference a "
f"{kind} by its label, or iterate over the collection. "
f"(Integer indexing was removed in 16.7.0.)"
)
if not isinstance(label, str):
raise TypeError(
f"{kind} must be referenced by a str label, not {label.__class__.__name__}"
)
stripped_label = label.strip()
if not stripped_label:
raise ValueError(f"{kind} label cannot be empty or all whitespace")
matches = [x for x in collection if x.label == stripped_label]
if not matches:
raise KeyError(f"{scope} has no {kind} with label '{label}'")
if len(matches) > 1:
raise ValueError(f"{scope} has multiple {kind_plural} with label '{label}'")
return matches[0]


PlayerReference = Player | str
StrategyReference = Strategy | str
InfosetReference = Infoset | str
Expand Down
Loading
Loading