Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 24 additions & 2 deletions src/pygambit/hsel.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,19 @@ def _node_last_action(node: c_GameNode, player: str) -> object:
"""The label of the last action `player` took on the path to `node`
(a raw handle, not a `Node`), wherever it fell -- `None` if `player`
hasn't acted yet. The `c_GameNode`-only core of `_last_action`, used
directly by `HistoryView`, which must never hold a `Node`."""
directly by `HistoryView`, which must never hold a `Node`.

Raises
------
KeyError
If no player in the game has label `player`.
TypeError
If `player` is not a `str`.
ValueError
If `player` is an empty string or all spaces.
"""
game: Game = Game._wrap(node.deref().GetGame())
game._resolve_player(player, "last_action")
current: c_GameNode = node
parent: c_GameNode = current.deref().GetParent()
while parent != cython.cast(c_GameNode, NULL):
Expand Down Expand Up @@ -373,7 +385,17 @@ class HistoryView:

def last_action(self, player: str) -> str | None:
"""The label of the last action `player` took on the path to this
history, wherever it fell -- `None` if `player` hasn't acted yet."""
history, wherever it fell -- `None` if `player` hasn't acted yet.

Raises
------
KeyError
If no player in the game has label `player`.
TypeError
If `player` is not a `str`.
ValueError
If `player` is an empty string or all spaces.
"""
return _node_last_action(self._node, player)

@property
Expand Down
60 changes: 60 additions & 0 deletions tests/test_hsel.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,63 @@ def test_get_histories_requires_selector():
game.get_histories(())
with pytest.raises(TypeError):
game.get_histories("U")


def test_last_action_raises_on_invalid_player():
game = gbt.ExtensiveGame(players=["A", "B"])
game.append_move(gbt.H.path(), "A", ["U", "D"])
game.append_move(gbt.H.plays, "B", ["x", "y"])

def key(h):
with pytest.raises(KeyError):
h.last_action("NoSuchPlayer")
return None

game._get_groups(gbt.H.plays.by(key))


def test_last_action_raises_on_non_str_player():
game = gbt.ExtensiveGame(players=["A"])
game.append_move(gbt.H.path(), "A", ["U", "D"])

def key(h):
with pytest.raises(TypeError):
h.last_action(1)
return None

game._get_groups(gbt.H.plays.by(key))


def test_last_action_raises_on_empty_player():
game = gbt.ExtensiveGame(players=["A"])
game.append_move(gbt.H.path(), "A", ["U", "D"])

def key(h):
with pytest.raises(ValueError):
h.last_action(" ")
return None

game._get_groups(gbt.H.plays.by(key))


def test_with_recall_raises_on_invalid_player():
game = gbt.ExtensiveGame(players=["A", "B"])
game.append_move(gbt.H.path(), "A", ["U", "D"])
game.append_move(gbt.H.plays, "B", ["x", "y"])

grouped = gbt.H.path().by(lambda h: None).with_recall("NoSuchPlayer").plays
with pytest.raises(KeyError):
game._get_groups(grouped)


def test_with_recall_refines_key_by_last_action():
game = gbt.ExtensiveGame(players=["A", "B"])
game.append_move(gbt.H.path(), "A", ["U", "D"])
game.append_move(gbt.H.plays, "B", ["x", "y"])

grouped = gbt.H.path().by(lambda h: None).with_recall("A").plays
groups = game._get_groups(grouped)
assert {k: [h.actions for h in v] for k, v in groups.items()} == {
(None, "U"): [("U", "x"), ("U", "y")],
(None, "D"): [("D", "x"), ("D", "y")],
}
Loading