Skip to content
This repository was archived by the owner on Jul 16, 2024. It is now read-only.

Commit a5dfa50

Browse files
committed
refactoring useages of the deck class and card class
1 parent 3c6bb8b commit a5dfa50

File tree

4 files changed

+112
-111
lines changed

4 files changed

+112
-111
lines changed

pluribus/games/short_deck/state.py

+105-101
Original file line numberDiff line numberDiff line change
@@ -123,20 +123,13 @@ def __init__(
123123
for player in self.players:
124124
player.is_turn = False
125125
self.current_player.is_turn = True
126-
# TODO add attribute of public_cards, that can be supplied by
127-
# convenience method
128-
self._public_cards = public_cards
129126
if public_cards:
130127
assert len(public_cards) in {3, 4, 5}
131128
self._public_cards = public_cards
132129
# only want to do these actions in real game play, as they are slow
133130
if self.real_time_test:
134131
# must have offline strategy loaded up
135132
self._starting_hand_probs = self._initialize_starting_hands()
136-
# TODO: We might not need this
137-
cards_in_deck = self._table.dealer.deck._cards_in_deck
138-
self._evals = [c.eval_card for c in cards_in_deck]
139-
self._evals_to_cards = {i.eval_card: i for i in cards_in_deck}
140133

141134
def __repr__(self):
142135
"""Return a helpful description of object in strings and debugger."""
@@ -329,34 +322,30 @@ def _increment_stage(self):
329322
def _normalize_bayes(self):
330323
"""Normalize probability of reach for each player"""
331324
n_players = len(self.players)
332-
for player in range(n_players):
333-
total_prob = sum(self._starting_hand_probs[player].values())
334-
for starting_hand, prob in self._starting_hand_probs[player].items():
335-
self._starting_hand_probs[player][starting_hand] = prob / total_prob
325+
for p_i in range(n_players):
326+
total_prob = sum(self._starting_hand_probs[p_i].values())
327+
for starting_hand, prob in self._starting_hand_probs[p_i].items():
328+
self._starting_hand_probs[p_i][starting_hand] = prob / total_prob
336329

330+
# TODO: figure out typing for dicts..
337331
def _update_hole_cards_bayes(self, offline_strategy: Dict):
338-
"""Get probability of reach for each pair of hole cards for each player"""
332+
"""Get probability of reach for each starting hand for each player"""
339333
n_players = len(self._table.players)
340334
player_indices: List[int] = [p_i for p_i in range(n_players)]
341335
for p_i in player_indices:
336+
# TODO: might make since to put starting hands in the deck class
342337
for starting_hand in self._starting_hand_probs[p_i].keys():
338+
339+
starting_hand = list(
340+
starting_hand
341+
)
343342
# TODO: is this bad?
344343
if "p_reach" in locals():
345344
del p_reach
346345
action_sequence: Dict[str, List[str]] = collections.defaultdict(list)
347-
previous_betting_stage = "pre_flop"
348-
first_action_round = False
349346
for idx, betting_stage in enumerate(self._history.keys()):
350-
# import ipdb;
351-
# ipdb.set_trace()
352347
n_actions_round = len(self._history[betting_stage])
353348
for i in range(n_actions_round):
354-
# if i == 0:
355-
# betting_stage = previous_betting_stage
356-
# elif i == n_actions_round - 1:
357-
# previous_betting_stage = betting_stage
358-
# else:
359-
# betting_stage = betting_round
360349
action = self._history[betting_stage][i]
361350
while action == 'skip':
362351
i += 1 # action sequences don't end in skip
@@ -368,114 +357,113 @@ def _update_hole_cards_bayes(self, offline_strategy: Dict):
368357
ph = i % n_players
369358
if p_i != ph:
370359
prob_reach_all_hands = []
371-
num_hands = 0
372360
for opp_starting_hand in self._starting_hand_probs[
373361
p_i
374362
].keys():
375-
# TODO: clean this up
376-
public_evals = [
377-
c.eval_card
378-
for c in self._public_information[betting_stage]
379-
]
380-
if len(set(opp_starting_hand).union(set(public_evals)).union(set(starting_hand))) < \
381-
len(opp_starting_hand) + len(starting_hand) + len(public_evals):
363+
opp_starting_hand = list(
364+
opp_starting_hand
365+
)
366+
publics = self._public_information[betting_stage]
367+
if len(
368+
set(opp_starting_hand).union(
369+
set(publics)
370+
).union(set(starting_hand))
371+
) < len(
372+
opp_starting_hand
373+
) + len(
374+
starting_hand
375+
) + len(
376+
publics
377+
):
382378
prob = 0
383-
num_hands += 1
384379
else:
385-
num_hands += 1
386-
387-
public_cards = self._public_information[
380+
publics = self._public_information[
388381
betting_stage
389382
]
390-
public_cards_evals = [c.eval_card for c in public_cards]
391383
infoset = self._info_set_helper(
392384
opp_starting_hand,
393-
public_cards_evals,
385+
publics,
394386
action_sequence,
395387
betting_stage,
396388
)
397-
# check to see if the strategy exists, if not equal probability
398-
# TODO: is this hacky? problem with defaulting to 1 / 3, is that it
399-
# doesn't work for calculations that need to be made with the object's values
389+
# check to see if the strategy exists,
390+
# if not equal probability
391+
# TODO: is this overly hacky?
392+
# Problem with defaulting to 1 / 3, is that it
393+
# it doesn't work for calculations that
394+
# need to be made with the object's values
400395

401396
try: # TODO: with or without keys
402397
prob = offline_strategy[infoset][action]
403398
except KeyError:
404399
prob = 1 / len(self.legal_actions)
405400
prob_reach_all_hands.append(prob)
406-
# import ipdb;
407-
# ipdb.set_trace()
408-
prob2 = sum(prob_reach_all_hands) / num_hands
401+
total_opp_prob_h = sum(prob_reach_all_hands) /\
402+
len(prob_reach_all_hands)
409403
if "p_reach" not in locals():
410-
p_reach = prob2
404+
p_reach = total_opp_prob_h
411405
else:
412-
p_reach *= prob2
406+
p_reach *= total_opp_prob_h
413407
elif p_i == ph:
414-
public_evals = [
415-
c.eval_card
416-
for c in self._public_information[betting_stage]
417-
]
418-
if len(set(starting_hand).union(set(public_evals))) < (
419-
len(public_evals) + 2
408+
publics = self._public_information[betting_stage]
409+
if len(
410+
set(starting_hand).union(
411+
set(publics)
412+
)
413+
) < (
414+
len(publics) + 2
420415
):
421-
prob = 0
416+
total_prob = 0
422417
else:
423-
public_cards = self._public_information[betting_stage]
424-
public_cards_evals = [c.eval_card for c in public_cards]
418+
publics = self._public_information[betting_stage]
425419
infoset = self._info_set_helper(
426420
starting_hand,
427-
public_cards_evals,
421+
publics,
428422
action_sequence,
429423
betting_stage,
430424
)
431425
# TODO: Check this
432426
try:
433-
prob = offline_strategy[infoset][action]
427+
total_prob = offline_strategy[infoset][action]
434428
except KeyError:
435-
prob = 1 / len(self.legal_actions)
429+
total_prob = 1 / len(self.legal_actions)
436430
if "p_reach" not in locals():
437-
p_reach = prob
431+
p_reach = total_prob
438432
else:
439-
p_reach *= prob
433+
p_reach *= total_prob
440434
action_sequence[betting_stage].append(action)
441435
self._starting_hand_probs[p_i][tuple(starting_hand)] = p_reach
442436
self._normalize_bayes()
443-
# TODO: delete this? at least for our purposes we don't need it again
444437

445438
def deal_bayes(self):
446-
start = time.time()
439+
# TODO: Not sure if I need this yet
447440
lut = self.info_set_lut
448441
self.info_set_lut = {}
449442
new_state = copy.deepcopy(self)
450443
new_state.info_set_lut = self.info_set_lut = lut
451-
end = time.time()
452-
print(f"Took {start - end} to load")
453-
454444
players = list(range(len(self.players)))
455445
random.shuffle(players)
456-
457-
# TODO should contain the current public cards/heros real hand, if exists
458-
card_evals_selected = []
459-
460-
for player in players:
461-
# does this maintain order?
462-
starting_hand_eval = new_state._get_starting_hand(player)
463-
len_union = len(set(starting_hand_eval).union(set(card_evals_selected)))
464-
len_individual = len(starting_hand_eval) + len(card_evals_selected)
446+
cards_selected = []
447+
# TODO: this might be made better by selecting the first player's
448+
# cards, then normalizing the second and third, etc..
449+
for p_i in players:
450+
starting_hand = new_state._get_starting_hand(p_i)
451+
len_union = len(set(starting_hand).union(set(cards_selected)))
452+
len_individual = len(starting_hand) + len(cards_selected)
465453
while len_union < len_individual:
466-
starting_hand_eval = new_state._get_starting_hand(player)
467-
len_union = len(set(starting_hand_eval).union(set(card_evals_selected)))
468-
len_individual = len(starting_hand_eval) + len(card_evals_selected)
469-
for card_eval in starting_hand_eval:
470-
card = new_state._evals_to_cards[card_eval]
471-
new_state.players[player].add_private_card(card)
472-
card_evals_selected += starting_hand_eval
473-
cards_selected = [new_state._evals_to_cards[c] for c in card_evals_selected]
454+
starting_hand = new_state._get_starting_hand(p_i)
455+
len_union = len(set(starting_hand).union(set(cards_selected)))
456+
len_individual = len(starting_hand) + len(cards_selected)
457+
# TODO: pull this into a helper method, maybe it should
458+
# be in the dealer class..
459+
for card in starting_hand:
460+
new_state.players[p_i].add_private_card(card)
461+
cards_selected += starting_hand
474462
cards_selected += new_state._public_cards
475463
for card in cards_selected:
476464
new_state._table.dealer.deck.remove(card)
477465
return new_state
478-
# TODO add convenience method to supply public cards
466+
# TODO add convenience method to supply public cards
479467

480468
def load_game_state(self, offline_strategy: Dict, action_sequence: list):
481469
"""
@@ -498,23 +486,31 @@ def load_game_state(self, offline_strategy: Dict, action_sequence: list):
498486

499487
def _get_starting_hand(self, player_idx: int):
500488
"""Get starting hand based on probability of reach"""
501-
starting_hand_idxs = list(range(len(self._starting_hand_probs[player_idx].keys())))
502-
starting_hands_probs = list(self._starting_hand_probs[player_idx].values())
503-
starting_hand_idx = np.random.choice(starting_hand_idxs, 1, p=starting_hands_probs)[0]
504-
starting_hand = list(self._starting_hand_probs[player_idx].keys())[starting_hand_idx]
489+
starting_hands = list(self._starting_hand_probs[player_idx].keys())
490+
# hacky for using tuples as keys
491+
starting_hands_idxs = list(range(len(starting_hands)))
492+
starting_hands_probs = list(self._starting_hand_probs[
493+
player_idx
494+
].values())
495+
starting_hand_idx = np.random.choice(
496+
starting_hands_idxs,
497+
1,
498+
p=starting_hands_probs
499+
)[0]
500+
starting_hand = list(starting_hands[starting_hand_idx])
505501
return starting_hand
506502

507503
def _initialize_starting_hands(self):
508504
"""Dictionary of starting hands to store probabilities in"""
509505
assert self.betting_stage == "pre_flop"
510-
# TODO: make this abstracted for n_players
511-
starting_hand_probs = {0: {}, 1: {}, 2: {}}
506+
starting_hand_probs = {}
512507
n_players = len(self.players)
513508
starting_hands = self._get_card_combos(2)
514509
for p_i in range(n_players):
510+
starting_hand_probs[p_i] = {}
515511
for starting_hand in starting_hands:
516512
starting_hand_probs[p_i][
517-
tuple([c.eval_card for c in starting_hand])
513+
starting_hand
518514
] = 1
519515
return starting_hand_probs
520516

@@ -523,15 +519,23 @@ def _info_set_helper(
523519
):
524520
# didn't want to combine this with the other, as we may want to modularize soon
525521
"""Get the information set for the current player."""
526-
cards = sorted(hole_cards, reverse=True,)
527-
cards += sorted(public_cards, reverse=True,)
528-
eval_cards = tuple(cards)
522+
cards = sorted(
523+
hole_cards,
524+
key=operator.attrgetter("eval_card"),
525+
reverse=True,
526+
)
527+
cards += sorted(
528+
public_cards,
529+
key=operator.attrgetter("eval_card"),
530+
reverse=True,
531+
)
532+
eval_cards = tuple([int(c) for c in cards])
529533
try:
530534
cards_cluster = self.info_set_lut[betting_stage][eval_cards]
531535
except KeyError:
532536
if not self.info_set_lut:
533537
raise ValueError("Pickle luts must be loaded for info set.")
534-
elif eval_cards not in self.info_set_lut[self._betting_stage]:
538+
elif eval_cards not in self.info_set_lut[betting_stage]:
535539
raise ValueError("Cards {cards} not in pickle files.")
536540
else:
537541
raise ValueError("Unrecognised betting stage in pickle files.")
@@ -548,7 +552,7 @@ def _info_set_helper(
548552

549553
def _get_card_combos(self, num_cards):
550554
"""Get combinations of cards"""
551-
return list(combinations(self._poker_engine.table.dealer.deck._cards_in_deck, num_cards))
555+
return list(combinations(self.cards_in_deck, num_cards))
552556

553557
@property
554558
def community_cards(self) -> List[Card]:
@@ -560,6 +564,11 @@ def private_hands(self) -> Dict[ShortDeckPokerPlayer, List[Card]]:
560564
"""Return all private hands."""
561565
return {p: p.cards for p in self.players}
562566

567+
@property
568+
def cards_in_deck(self):
569+
"""Returns current cards in deck"""
570+
return self._table.dealer.deck._cards_in_deck
571+
563572
@property
564573
def initial_regret(self) -> Dict[str, float]:
565574
"""Returns the default regret for this state."""
@@ -590,11 +599,6 @@ def n_players_started_round(self) -> bool:
590599
"""Return n_players that started the round."""
591600
return self._n_players_started_round
592601

593-
# @property
594-
# def first_move_of_current_round(self) -> bool:
595-
# """Return boolfor first move of current round."""
596-
# return self._first_move_of_current_round
597-
598602
@property
599603
def player_i(self) -> int:
600604
"""Get the index of the players turn it is."""
@@ -603,11 +607,11 @@ def player_i(self) -> int:
603607
@player_i.setter
604608
def player_i(self, _: Any):
605609
"""Raise an error if player_i is set."""
606-
raise ValueError(f"The player_i property should not be set.")
610+
raise ValueError("The player_i property should not be set.")
607611

608612
@property
609613
def betting_round(self) -> int:
610-
"""Algorithm 1 of pluribus supp. material references betting_round."""
614+
"""Return 0 indexed betting round"""
611615
try:
612616
betting_round = self._betting_stage_to_round[self._betting_stage]
613617
except KeyError:
@@ -631,7 +635,7 @@ def info_set(self) -> str:
631635
key=operator.attrgetter("eval_card"),
632636
reverse=True,
633637
)
634-
eval_cards = tuple([card.eval_card for card in cards])
638+
eval_cards = tuple([int(card) for card in cards])
635639
try:
636640
cards_cluster = self.info_set_lut[self._betting_stage][eval_cards]
637641
except KeyError:

pluribus/poker/card.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ def __eq__(self, other):
7474
def __ne__(self, other):
7575
return int(self) != int(other)
7676

77+
def __hash__(self):
78+
return hash(int(self))
79+
7780
@property
7881
def eval_card(self) -> EvaluationCard:
7982
"""Return an `EvaluationCard` for use in the `Evaluator`."""
@@ -178,4 +181,3 @@ def from_dict(x: Dict[str, Union[int, str]]):
178181
if set(x) != {"rank", "suit"}:
179182
raise NotImplementedError(f"Unrecognised dict {x}")
180183
return Card(rank=x["rank"], suit=x["suit"])
181-

pluribus/poker/deck.py

+3-7
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ def pick(self, random: bool = True) -> Card:
6464

6565
def remove(self, card):
6666
"""Remove a specific card from the deck"""
67-
# TODO: is there any reason for them to be?
68-
# Maybe better to assert it's not
69-
c = card.eval_card
70-
cards_in_deck = [x.eval_card for x in self._cards_in_deck]
71-
if c in cards_in_deck:
72-
index = cards_in_deck.index(c)
73-
self._dealt_cards.append(self._cards_in_deck.pop(index))
67+
if card in self._cards_in_deck:
68+
self._cards_in_deck.remove(card)
69+
self._dealt_cards.append(card)

0 commit comments

Comments
 (0)