11"""A utility module to create/load games for the test suite."""
22
3+ import itertools
34import pathlib
45from abc import ABC , abstractmethod
5- from itertools import product
66
77import numpy as np
88
@@ -18,6 +18,26 @@ def read_from_file(fn: str) -> gbt.Game:
1818 raise ValueError (f"Unknown file extension in { fn } " )
1919
2020
21+ def create_efg_corresponding_to_bimatrix_game (
22+ A : np .ndarray , B : np .ndarray , title : str
23+ ) -> gbt .Game :
24+ """
25+ There is no direct pygambit method to create an EFG from a stategic-form game.
26+ Here we create an EFG corresponding to a bimatrix game, given by two numpy arrays.
27+ Player 1 moves first.
28+ """
29+ assert A .shape == B .shape
30+ m , n = A .shape
31+ g = gbt .Game .new_tree (players = ["1" , "2" ], title = title )
32+ actions1 = [str (i ) for i in range (m )]
33+ actions2 = [str (i ) for i in range (n )]
34+ g .append_move (g .root , "1" , actions1 )
35+ g .append_move (g .root .children , "2" , actions2 )
36+ for i , j in itertools .product (range (m ), range (n )):
37+ g .set_outcome (g .root .children [i ].children [j ], g .add_outcome ([A [i , j ], B [i , j ]]))
38+ return g
39+
40+
2141################################################################################################
2242# Normal-form (aka strategic-form) games (nfg)
2343
@@ -87,10 +107,22 @@ def create_mixed_behav_game_efg() -> gbt.Game:
87107 Game
88108 Three-player extensive form game: binary tree with 3 infomation sets, one per player,
89109 with 1, 2, and 4 nodes respectively
110+
111+ Since no information is revealed this is directly equivalent to a simultaneous move game
90112 """
91113 return read_from_file ("mixed_behavior_game.efg" )
92114
93115
116+ def create_1_card_poker_efg () -> gbt .Game :
117+ """
118+ Returns
119+ -------
120+ Game
121+ One-card two-player poker game, as used in the user guide
122+ """
123+ return read_from_file ("poker.efg" )
124+
125+
94126def create_myerson_2_card_poker_efg () -> gbt .Game :
95127 """
96128 Returns
@@ -104,6 +136,119 @@ def create_myerson_2_card_poker_efg() -> gbt.Game:
104136 return read_from_file ("myerson_2_card_poker.efg" )
105137
106138
139+ def create_kuhn_poker_efg () -> gbt .Game :
140+ """
141+ Returns
142+ -------
143+ Game
144+ Kuhn poker with 3 cards and 2 players
145+ """
146+ g = gbt .Game .new_tree (
147+ players = ["Alice" , "Bob" ], title = "Three-card poker (J, Q, K), two-player"
148+ )
149+ deals = ["JQ" , "JK" , "QJ" , "QK" , "KJ" , "KQ" ]
150+ g .append_move (g .root , g .players .chance , deals )
151+ g .set_chance_probs (g .root .infoset , [gbt .Rational (1 , 6 )]* 6 )
152+ # group the children of the root (indices of `deals`) by each player's dealt card
153+ alice_grouping = [[0 , 1 ], [2 , 3 ], [4 , 5 ]] # J, Q, K
154+ bob_grouping = [[0 , 5 ], [1 , 3 ], [2 , 4 ]] # Q, K, J
155+
156+ # Alice's first move
157+ for ij in alice_grouping :
158+ term_nodes = [g .root .children [k ] for k in ij ]
159+ g .append_move (term_nodes , "Alice" , ["Check" , "Bet" ])
160+ # Bob's move after Alice checks
161+ for ij in bob_grouping :
162+ term_nodes = [g .root .children [k ].children [0 ] for k in ij ]
163+ g .append_move (term_nodes , "Bob" , ["Check" , "Bet" ])
164+ # Alice's move if Bob's second action is bet
165+ for ij in alice_grouping :
166+ term_nodes = [g .root .children [k ].children [0 ].children [1 ] for k in ij ]
167+ g .append_move (term_nodes , "Alice" , ["Fold" , "Call" ])
168+ # Bob's move after Alice bets initially
169+ for ij in bob_grouping :
170+ term_nodes = [g .root .children [k ].children [1 ] for k in ij ]
171+ g .append_move (term_nodes , "Bob" , ["Fold" , "Call" ])
172+
173+ def calculate_payoffs (term_node ):
174+
175+ def get_path (node ):
176+ path = []
177+ while node .parent :
178+ path .append (node .prior_action .label )
179+ node = node .parent
180+ return path
181+
182+ def showdown_winner (deal ):
183+ # deal is an element of deals = ["JQ", "JK", "QJ", "QK", "KJ", "KQ"]
184+ card_values = dict (J = 0 , Q = 1 , K = 2 )
185+ a , b = deal
186+ return "Alice" if card_values [a ] > card_values [b ] else "Bob"
187+
188+ def showdown (deal , payoffs , pot ):
189+ payoffs [showdown_winner (deal )] += pot
190+ return payoffs
191+
192+ def bet (player , payoffs , pot ):
193+ payoffs [player ] += - 1
194+ pot += 1
195+ return payoffs , pot
196+
197+ path = get_path (term_node )
198+ deal = path .pop () # needed if there is a showdown
199+ payoffs = dict (Alice = - 1 , Bob = - 1 ) # ante of 1 for both players
200+ pot = 2
201+ if path .pop () == "Check" : # Alice checks
202+ if path .pop () == "Check" : # Bob checks
203+ payoffs = showdown (deal , payoffs , pot )
204+ else : # Bob bets
205+ payoffs , pot = bet ("Bob" , payoffs , pot )
206+ if path .pop () == "Fold" : # Alice folds
207+ payoffs ["Bob" ] += pot
208+ else : # Alice calls
209+ payoffs , pot = bet ("Alice" , payoffs , pot )
210+ payoffs = showdown (deal , payoffs , pot )
211+ else : # Alice bets
212+ payoffs , pot = bet ("Alice" , payoffs , pot )
213+ if path .pop () == "Fold" : # Bob
214+ payoffs ["Alice" ] += pot
215+ else : # Bob calls
216+ payoffs , pot = bet ("Bob" , payoffs , pot )
217+ payoffs = showdown (deal , payoffs , pot )
218+
219+ return tuple (payoffs .values ())
220+
221+ # create 4 possible outcomes just once
222+ payoffs_to_outcomes = {(1 , - 1 ): g .add_outcome ([1 , - 1 ], label = "Alice wins 1" ),
223+ (2 , - 2 ): g .add_outcome ([2 , - 2 ], label = "Alice wins 2" ),
224+ (- 1 , 1 ): g .add_outcome ([- 1 , 1 ], label = "Bob wins 1" ),
225+ (- 2 , 2 ): g .add_outcome ([- 2 , 2 ], label = "Bob wins 2" )}
226+
227+ for term_node in [n for n in g .nodes if n .is_terminal ]:
228+ outcome = payoffs_to_outcomes [calculate_payoffs (term_node )]
229+ g .set_outcome (term_node , outcome )
230+
231+ # Ensure infosets are in the same order as if game was written to efg and read back in
232+ g .sort_infosets ()
233+ return g
234+
235+
236+ def create_one_shot_trust_efg () -> gbt .Game :
237+ g = gbt .Game .new_tree (
238+ players = ["Buyer" , "Seller" ], title = "One-shot trust game, after Kreps (1990)"
239+ )
240+ g .append_move (g .root , "Buyer" , ["Trust" , "Not trust" ])
241+ g .append_move (g .root .children [0 ], "Seller" , ["Honor" , "Abuse" ])
242+ g .set_outcome (
243+ g .root .children [0 ].children [0 ], g .add_outcome ([1 , 1 ], label = "Trustworthy" )
244+ )
245+ g .set_outcome (
246+ g .root .children [0 ].children [1 ], g .add_outcome ([- 1 , 2 ], label = "Untrustworthy" )
247+ )
248+ g .set_outcome (g .root .children [1 ], g .add_outcome ([0 , 0 ], label = "Opt-out" ))
249+ return g
250+
251+
107252def create_centipede_game_with_chance_efg () -> gbt .Game :
108253 """
109254 Returns
@@ -198,7 +343,6 @@ def create_reduction_generic_payoffs_efg() -> gbt.Game:
198343 )
199344
200345 g .set_outcome (g .root .children [3 ], g .add_outcome ([12 , - 12 ], label = "d" ))
201-
202346 return g
203347
204348
@@ -236,6 +380,138 @@ def create_reduction_both_players_payoff_ties_efg() -> gbt.Game:
236380 return g
237381
238382
383+ def create_seq_form_STOC_paper_zero_sum_2_player_efg () -> gbt .Game :
384+ """
385+ Example from
386+
387+ Fast Algorithms for Finding Randomized Strategies in Game Trees (1994)
388+ Koller, Megiddo, von Stengel
389+ """
390+ g = gbt .Game .new_tree (players = ["1" , "2" ], title = "From STOC'94 paper" )
391+ g .append_move (g .root , g .players .chance , actions = ["1" , "2" , "3" , "4" ])
392+ g .set_chance_probs (g .root .infoset , [0.2 , 0.2 , 0.2 , 0.4 ])
393+ g .append_move (g .root .children [0 ], player = "1" , actions = ["l" , "r" ])
394+ g .append_move (g .root .children [1 ], player = "1" , actions = ["c" , "d" ])
395+ g .append_infoset (g .root .children [2 ], g .root .children [1 ].infoset )
396+ g .append_move (g .root .children [0 ].children [1 ], player = "2" , actions = ["p" , "q" ])
397+ g .append_move (
398+ g .root .children [0 ].children [1 ].children [0 ], player = "1" , actions = ["L" , "R" ]
399+ )
400+ g .append_infoset (
401+ g .root .children [0 ].children [1 ].children [1 ],
402+ g .root .children [0 ].children [1 ].children [0 ].infoset ,
403+ )
404+ g .append_move (g .root .children [2 ].children [0 ], player = "2" , actions = ["s" , "t" ])
405+ g .append_infoset (
406+ g .root .children [2 ].children [1 ], g .root .children [2 ].children [0 ].infoset
407+ )
408+
409+ g .set_outcome (
410+ g .root .children [0 ].children [0 ],
411+ outcome = g .add_outcome (payoffs = [5 , - 5 ], label = "l" ),
412+ )
413+ g .set_outcome (
414+ g .root .children [0 ].children [1 ].children [0 ].children [0 ],
415+ outcome = g .add_outcome (payoffs = [10 , - 10 ], label = "rpL" ),
416+ )
417+ g .set_outcome (
418+ g .root .children [0 ].children [1 ].children [0 ].children [1 ],
419+ outcome = g .add_outcome (payoffs = [15 , - 15 ], label = "rpR" ),
420+ )
421+ g .set_outcome (
422+ g .root .children [0 ].children [1 ].children [1 ].children [0 ],
423+ outcome = g .add_outcome (payoffs = [20 , - 20 ], label = "rqL" ),
424+ )
425+ g .set_outcome (
426+ g .root .children [0 ].children [1 ].children [1 ].children [1 ],
427+ outcome = g .add_outcome (payoffs = [- 5 , 5 ], label = "rqR" ),
428+ )
429+ g .set_outcome (
430+ g .root .children [1 ].children [0 ],
431+ outcome = g .add_outcome (payoffs = [10 , - 10 ], label = "c" ),
432+ )
433+ g .set_outcome (
434+ g .root .children [1 ].children [1 ],
435+ outcome = g .add_outcome (payoffs = [20 , - 20 ], label = "d" ),
436+ )
437+ g .set_outcome (
438+ g .root .children [2 ].children [0 ].children [0 ],
439+ outcome = g .add_outcome (payoffs = [20 , - 20 ], label = "cs" ),
440+ )
441+ g .set_outcome (
442+ g .root .children [2 ].children [0 ].children [1 ],
443+ outcome = g .add_outcome (payoffs = [50 , - 50 ], label = "ct" ),
444+ )
445+ g .set_outcome (
446+ g .root .children [2 ].children [1 ].children [0 ],
447+ outcome = g .add_outcome (payoffs = [30 , - 30 ], label = "ds" ),
448+ )
449+ g .set_outcome (
450+ g .root .children [2 ].children [1 ].children [1 ],
451+ outcome = g .add_outcome (payoffs = [15 , - 15 ], label = "dt" ),
452+ )
453+ g .set_outcome (
454+ g .root .children [3 ], outcome = g .add_outcome (payoffs = [5 , - 5 ], label = "nothing" )
455+ )
456+ g .root .children [0 ].infoset .label = "0"
457+ g .root .children [1 ].infoset .label = "1"
458+ g .root .children [0 ].children [1 ].infoset .label = "01"
459+ g .root .children [2 ].children [0 ].infoset .label = "20"
460+ g .root .children [0 ].children [1 ].children [0 ].infoset .label = "010"
461+
462+ return g
463+
464+
465+ def create_two_player_perfect_info_win_lose_efg () -> gbt .Game :
466+ g = gbt .Game .new_tree (players = ["1" , "2" ], title = "2 player perfect info win lose" )
467+ g .append_move (g .root , "2" , ["a" , "b" ])
468+ g .append_move (g .root .children [0 ], "1" , ["L" , "R" ])
469+ g .append_move (g .root .children [1 ], "1" , ["L" , "R" ])
470+ g .append_move (g .root .children [0 ].children [0 ], "2" , ["l" , "r" ])
471+ g .set_outcome (
472+ g .root .children [0 ].children [0 ].children [0 ], g .add_outcome ([1 , - 1 ], label = "aLl" )
473+ )
474+ g .set_outcome (
475+ g .root .children [0 ].children [0 ].children [1 ], g .add_outcome ([- 1 , 1 ], label = "aLr" )
476+ )
477+ g .set_outcome (g .root .children [0 ].children [1 ], g .add_outcome ([1 , - 1 ], label = "aR" ))
478+ g .set_outcome (g .root .children [1 ].children [0 ], g .add_outcome ([1 , - 1 ], label = "bL" ))
479+ g .set_outcome (g .root .children [1 ].children [1 ], g .add_outcome ([- 1 , 1 ], label = "bR" ))
480+ return g
481+
482+
483+ def create_EFG_for_nxn_bimatrix_coordination_game (n : int ) -> gbt .Game :
484+ A = np .eye (n , dtype = int )
485+ B = A
486+ title = f"{ n } x{ n } coordination game, { 2 ** n - 1 } equilibria"
487+ return create_efg_corresponding_to_bimatrix_game (A , B , title )
488+
489+
490+ def create_EFG_for_6x6_bimatrix_with_long_LH_paths_and_unique_eq () -> gbt .Game :
491+ # 6 x 6 Payoff matrix A:
492+ A = [
493+ [- 180 , 72 , - 333 , 297 , - 153 , 270 ],
494+ [- 30 , 17 , - 33 , 42 , - 3 , 20 ],
495+ [- 81 , 36 , - 126 , 126 , - 36 , 90 ],
496+ [90 , - 36 , 126 , - 126 , 36 , - 81 ],
497+ [20 , - 3 , 42 , - 33 , 17 , - 30 ],
498+ [270 , - 153 , 297 , - 333 , 72 , - 180 ],
499+ ]
500+ # 6 x 6 Payoff matrix B:
501+ B = [
502+ [72 , 36 , 17 , - 3 , - 36 , - 153 ],
503+ [- 180 , - 81 , - 30 , 20 , 90 , 270 ],
504+ [297 , 126 , 42 , - 33 , - 126 , - 333 ],
505+ [- 333 , - 126 , - 33 , 42 , 126 , 297 ],
506+ [270 , 90 , 20 , - 30 , - 81 , - 180 ],
507+ [- 153 , - 36 , - 3 , 17 , 36 , 72 ],
508+ ]
509+ A = np .array (A )
510+ B = np .array (B )
511+ title = "6x6 Long Lemke-Howson Paths, unique eq"
512+ return create_efg_corresponding_to_bimatrix_game (A , B , title )
513+
514+
239515class EfgFamilyForReducedStrategicFormTests (ABC ):
240516 """ """
241517
@@ -515,17 +791,17 @@ def _redu_strats(self, player, level):
515791 first_half = tmp [:n_half ]
516792 second_half = tmp [n_half :]
517793 # create first half suffix
518- first_half = product (first_half , first_half )
794+ first_half = itertools . product (first_half , first_half )
519795 first_half = ["" .join (t ) for t in first_half ]
520796 first_half = ["1" + t for t in first_half ] # add 1 to front
521797 # create second half suffix
522- second_half = product (second_half , second_half )
798+ second_half = itertools . product (second_half , second_half )
523799 second_half = ["" .join (t ) for t in second_half ]
524800 second_half = ["2" + t for t in second_half ] # add 2 to front
525801 return first_half + second_half # glue halves together
526802 else : # player == 3:
527803 tmp = self ._redu_strats (player = 2 , level = level - 1 )
528- tmp = product (tmp , tmp )
804+ tmp = itertools . product (tmp , tmp )
529805 tmp = ["" .join (t ) for t in tmp ]
530806 return tmp
531807 else :
0 commit comments