Skip to content
Open
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
42 changes: 21 additions & 21 deletions games/efg/holdout.efg
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
EFG 2 R "McKelvey-Palfrey (JET 77), 7 stage version of holdout game" { "Player 1" "Player 2" }
""

c "" 1 "Choose 1's type" { "High" 1/2 "Low" 1/2 } 0
c "" 2 "Choose 2's type" { "High" 3/5 "Low" 2/5 } 0
c "" 1 "Choose 1's type 1" { "High" 1/2 "Low" 1/2 } 0
c "" 2 "Choose 2's type 2" { "High" 3/5 "Low" 2/5 } 0
p "" 1 1 "" { "H" "G" } 0
p "" 2 1 "" { "H" "G" } 0
c "" 3 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 3 "Discount 3" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 2 "" { "H" "G" } 0
p "" 2 2 "" { "H" "G" } 0
c "" 4 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 4 "Discount 4" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 3 "" { "H" "G" } 0
p "" 2 3 "" { "H" "G" } 0
c "" 5 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 5 "Discount 5" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 4 "" { "H" "G" } 0
p "" 2 4 "" { "H" "G" } 0
c "" 6 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 6 "Discount 6" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 5 "" { "H" "G" } 0
p "" 2 5 "" { "H" "G" } 0
c "" 7 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 7 "Discount 7" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 6 "" { "H" "G" } 0
p "" 2 6 "" { "H" "G" } 0
c "" 8 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 8 "Discount 8" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 7 "" { "H" "G" } 0
p "" 2 7 "" { "H" "G" } 0
Expand Down Expand Up @@ -60,27 +60,27 @@ t "" 3 "" { 1/2, 1 }
t "" 4 "" { 1/2, 1/2 }
p "" 1 1 "" { "H" "G" } 0
p "" 2 8 "" { "H" } 0
c "" 9 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 9 "Discount 9" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 2 "" { "H" "G" } 0
p "" 2 8 "" { "H" } 0
c "" 10 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 10 "Discount 10" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 3 "" { "H" "G" } 0
p "" 2 8 "" { "H" } 0
c "" 11 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 11 "Discount 11" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 4 "" { "H" "G" } 0
p "" 2 8 "" { "H" } 0
c "" 12 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 12 "Discount 12" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 5 "" { "H" "G" } 0
p "" 2 8 "" { "H" } 0
c "" 13 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 13 "Discount 13" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 6 "" { "H" "G" } 0
p "" 2 8 "" { "H" } 0
c "" 14 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 14 "Discount 14" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 7 "" { "H" "G" } 0
p "" 2 8 "" { "H" } 0
Expand All @@ -99,30 +99,30 @@ p "" 2 8 "" { "H" } 0
t "" 3 "" { 1/2, 1 }
p "" 2 8 "" { "H" } 0
t "" 3 "" { 1/2, 1 }
c "" 15 "Choose 2's type" { "High" 3/5 "Low" 2/5 } 0
c "" 15 "Choose 2's type 15" { "High" 3/5 "Low" 2/5 } 0
p "" 1 8 "" { "H" } 0
p "" 2 1 "" { "H" "G" } 0
c "" 16 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 16 "Discount 16" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 8 "" { "H" } 0
p "" 2 2 "" { "H" "G" } 0
c "" 17 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 17 "Discount 17" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 8 "" { "H" } 0
p "" 2 3 "" { "H" "G" } 0
c "" 18 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 18 "Discount 18" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 8 "" { "H" } 0
p "" 2 4 "" { "H" "G" } 0
c "" 19 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 19 "Discount 19" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 8 "" { "H" } 0
p "" 2 5 "" { "H" "G" } 0
c "" 20 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 20 "Discount 20" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 8 "" { "H" } 0
p "" 2 6 "" { "H" "G" } 0
c "" 21 "Discount" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
c "" 21 "Discount 21" { "" 1/10 "" 9/10 } 1 "" { 0, 0 }
t "" 0
p "" 1 8 "" { "H" } 0
p "" 2 7 "" { "H" "G" } 0
Expand Down
12 changes: 9 additions & 3 deletions src/gtdraw/gambit_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ def gambit_layout_to_ef(
nodes_with_normalised_offsets = {}
for node, node_coords in layout.items():
nodes_with_normalised_offsets[node] = (node_coords.offset - midpoint) * xshift_multiplier

# Now, build the node lines in the .ef string
for node, node_coords in layout.items():

Expand Down Expand Up @@ -174,14 +174,20 @@ def gambit_layout_to_ef(

# Add probability if the parent is a chance player
if node.parent.player.is_chance:
prob = str(node.prior_action.prob).split("/")
if hasattr(pygambit.Infoset, "action_probs"):
prob_value = node.parent.infoset.action_probs[
node.prior_action.label
]
else:
prob_value = node.prior_action.prob
prob = str(prob_value).split("/")
if len(prob) == 2:
ef += f"~(\\frac{{{prob[0]}}}{{{prob[1]}}})"
elif len(prob) == 1:
ef += f"~{prob[0]}"
else:
# Throw error for unexpected probability format
raise ValueError(f"Unexpected probability format: {node.prior_action.prob}")
raise ValueError(f"Unexpected probability format: {prob_value}")
ef += " "

# Add payoffs to terminal nodes, if applicable
Expand Down
6 changes: 3 additions & 3 deletions tests/test_drawtree.py
Original file line number Diff line number Diff line change
Expand Up @@ -1364,9 +1364,9 @@ def _make_pygambit_game():
g = pygambit.Game.new_tree(players=["Alice", "Bob"], title="integration_test")
g.append_move(g.root, g.players["Alice"], ["Left", "Right"])
g.append_move(g.root.children["Left"], g.players["Bob"], ["Up", "Down"])
g.set_outcome(g.root.children["Left"].children["Up"], g.add_outcome([1, 0]))
g.set_outcome(g.root.children["Left"].children["Down"], g.add_outcome([0, 1]))
g.set_outcome(g.root.children["Right"], g.add_outcome([2, 2]))
g.set_outcome(g.root.children["Left"].children["Up"], g.add_outcome("up", [1, 0]))
g.set_outcome(g.root.children["Left"].children["Down"], g.add_outcome("down", [0, 1]))
g.set_outcome(g.root.children["Right"], g.add_outcome("right", [2, 2]))
return g


Expand Down
75 changes: 55 additions & 20 deletions tests/test_gambit_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Terminal nodes without outcomes
"""

import io
import os
import tempfile

Expand All @@ -32,8 +33,8 @@ def _simple_game(title="test_game"):
"""Create a minimal 2-player game: Alice chooses Left/Right, terminal payoffs."""
g = pygambit.Game.new_tree(players=["Alice", "Bob"], title=title)
g.append_move(g.root, g.players["Alice"], ["Left", "Right"])
g.set_outcome(g.root.children["Left"], g.add_outcome([1, 0]))
g.set_outcome(g.root.children["Right"], g.add_outcome([0, 1]))
g.set_outcome(g.root.children["Left"], g.add_outcome("left", [1, 0]))
g.set_outcome(g.root.children["Right"], g.add_outcome("right", [0, 1]))
return g


Expand All @@ -42,9 +43,9 @@ def _asymmetric_game(title="asym_game"):
g = pygambit.Game.new_tree(players=["Alice", "Bob"], title=title)
g.append_move(g.root, g.players["Alice"], ["Left", "Right"])
g.append_move(g.root.children["Left"], g.players["Bob"], ["Up", "Down"])
g.set_outcome(g.root.children["Left"].children["Up"], g.add_outcome([1, 0]))
g.set_outcome(g.root.children["Left"].children["Down"], g.add_outcome([0, 1]))
g.set_outcome(g.root.children["Right"], g.add_outcome([2, 2]))
g.set_outcome(g.root.children["Left"].children["Up"], g.add_outcome("up", [1, 0]))
g.set_outcome(g.root.children["Left"].children["Down"], g.add_outcome("down", [0, 1]))
g.set_outcome(g.root.children["Right"], g.add_outcome("right", [2, 2]))
return g


Expand Down Expand Up @@ -128,8 +129,8 @@ def test_player_lines(self):
def test_player_names_spaces_replaced(self):
g = pygambit.Game.new_tree(players=["Player One", "Player Two"], title="sp")
g.append_move(g.root, g.players["Player One"], ["A", "B"])
g.set_outcome(g.root.children["A"], g.add_outcome([1, 0]))
g.set_outcome(g.root.children["B"], g.add_outcome([0, 1]))
g.set_outcome(g.root.children["A"], g.add_outcome("a", [1, 0]))
g.set_outcome(g.root.children["B"], g.add_outcome("b", [0, 1]))
ef = gambit_layout_to_ef(
g, save_to=os.path.join(tempfile.gettempdir(), "sp.ef")
)
Expand Down Expand Up @@ -213,14 +214,22 @@ def test_non_root_has_xshift(self):


class TestChanceNodes:
"""Chance-move construction differs across supported pygambits:
17.0.0a1 replaced ``append_move`` with the chance player plus ``set_chance_probs``
by the single call ``append_event``.
Drop the 16.x lines once gtdraw no longer supports pygambit 16."""

def test_fractional_probability(self):
g = pygambit.Game.new_tree(players=["Alice", "Bob"], title="frac")
g.append_move(g.root, g.players.chance, ["H", "T"])
g.set_chance_probs(g.root.infoset, ["1/2", "1/2"])
if hasattr(g, "append_event"): # pygambit >= 17
g.append_event(g.root, ["H", "T"], ["1/2", "1/2"])
else: # pygambit 16.x
g.append_move(g.root, g.players.chance, ["H", "T"])
g.set_chance_probs(g.root.infoset, ["1/2", "1/2"])
g.append_move(g.root.children["H"], g.players["Alice"], ["A", "B"])
g.set_outcome(g.root.children["H"].children["A"], g.add_outcome([1, 0]))
g.set_outcome(g.root.children["H"].children["B"], g.add_outcome([0, 1]))
g.set_outcome(g.root.children["T"], g.add_outcome([2, 2]))
g.set_outcome(g.root.children["H"].children["A"], g.add_outcome("a", [1, 0]))
g.set_outcome(g.root.children["H"].children["B"], g.add_outcome("b", [0, 1]))
g.set_outcome(g.root.children["T"], g.add_outcome("t", [2, 2]))

ef = gambit_layout_to_ef(
g, save_to=os.path.join(tempfile.gettempdir(), "fc.ef")
Expand All @@ -232,10 +241,13 @@ def test_fractional_probability(self):

def test_whole_number_probability(self):
g = pygambit.Game.new_tree(players=["Alice"], title="whole")
g.append_move(g.root, g.players.chance, ["X", "Y"])
g.set_chance_probs(g.root.infoset, ["1", "0"])
g.set_outcome(g.root.children["X"], g.add_outcome([5]))
g.set_outcome(g.root.children["Y"], g.add_outcome([0]))
if hasattr(g, "append_event"): # pygambit >= 17
g.append_event(g.root, ["X", "Y"], ["1", "0"])
else: # pygambit 16.x
g.append_move(g.root, g.players.chance, ["X", "Y"])
g.set_chance_probs(g.root.infoset, ["1", "0"])
g.set_outcome(g.root.children["X"], g.add_outcome("x", [5]))
g.set_outcome(g.root.children["Y"], g.add_outcome("y", [0]))

ef = gambit_layout_to_ef(
g, save_to=os.path.join(tempfile.gettempdir(), "wh.ef")
Expand All @@ -245,6 +257,25 @@ def test_whole_number_probability(self):
assert "~1 " in content
assert "~0 " in content

def test_legacy_file_labels(self):
g = pygambit.read_efg(io.StringIO("""
EFG 2 R "legacy" { "Alice" }
""

c "" 1 "" { "" 1/4 "" 3/4 } 0
t "" 1 "o1" { 5 }
t "" 2 "o2" { 0 }
"""))
ef = gambit_layout_to_ef(
g, save_to=os.path.join(tempfile.gettempdir(), "lg.ef")
)
content = _read_ef(ef)
os.unlink(ef)
assert "player 0" in content
branch_lines = [line for line in content.splitlines() if "from 1 " in line]
assert "\\frac{1}{4}" in branch_lines[0]
assert "\\frac{3}{4}" in branch_lines[1]


# ---------------------------------------------------------------------------
# Information sets
Expand All @@ -257,9 +288,11 @@ def test_iset_line_generated(self):
g.append_move(g.root, g.players["Alice"], ["Left", "Right"])
g.append_move(g.root.children["Left"], g.players["Bob"], ["Up", "Down"])
g.append_infoset(g.root.children["Right"], g.root.children["Left"].infoset)
up = g.add_outcome("up", [1, 0])
down = g.add_outcome("down", [0, 1])
for c in g.root.children:
g.set_outcome(c.children["Up"], g.add_outcome([1, 0]))
g.set_outcome(c.children["Down"], g.add_outcome([0, 1]))
g.set_outcome(c.children["Up"], up)
g.set_outcome(c.children["Down"], down)

ef = gambit_layout_to_ef(
g, save_to=os.path.join(tempfile.gettempdir(), "is.ef")
Expand All @@ -276,9 +309,11 @@ def test_iset_nodes_have_no_inline_player(self):
g.append_move(g.root, g.players["Alice"], ["L", "R"])
g.append_move(g.root.children["L"], g.players["Bob"], ["U", "D"])
g.append_infoset(g.root.children["R"], g.root.children["L"].infoset)
u = g.add_outcome("u", [1, 0])
d = g.add_outcome("d", [0, 1])
for c in g.root.children:
g.set_outcome(c.children["U"], g.add_outcome([1, 0]))
g.set_outcome(c.children["D"], g.add_outcome([0, 1]))
g.set_outcome(c.children["U"], u)
g.set_outcome(c.children["D"], d)

ef = gambit_layout_to_ef(
g, save_to=os.path.join(tempfile.gettempdir(), "inp.ef")
Expand Down
Loading