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
6 changes: 5 additions & 1 deletion pypokerengine/api/emulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,16 @@ def exclude_short_of_money_players(table, ante, sb_amount):

def _steal_money_from_poor_player(table, ante, sb_amount):
players = table.seats.players
# count players holding chips before ante zeroing to detect heads-up
pre_ante_active = len([p for p in players if p.stack > 0])
# exclude player who cannot pay ante
for player in [p for p in players if p.stack < ante]: player.stack = 0
if players[table.dealer_btn].stack == 0: table.shift_dealer_btn()

search_targets = players + players + players
search_targets = search_targets[table.dealer_btn+1:table.dealer_btn+1+len(players)]
# heads-up: dealer button posts the small blind. otherwise sb sits left of the button
sb_offset = 0 if pre_ante_active == 2 else 1
search_targets = search_targets[table.dealer_btn+sb_offset:table.dealer_btn+sb_offset+len(players)]
# exclude player who cannot pay small blind
sb_player = _find_first_elligible_player(search_targets, sb_amount + ante)
sb_relative_pos = search_targets.index(sb_player)
Expand Down
10 changes: 8 additions & 2 deletions pypokerengine/engine/dealer.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ def play_round(self, round_count, blind_amount, ante, table):
while True:
self.__message_check(msgs, state["street"])
if state["street"] != Const.Street.FINISHED: # continue the round
action, bet_amount = self.__publish_messages(msgs)
result = self.__publish_messages(msgs)
assert(result is not None) # ongoing round always ends on an ask message
action, bet_amount = result
state, msgs = RoundManager.apply_action(state, action, bet_amount)
else: # finish the round after publish round result
self.__publish_messages(msgs)
Expand Down Expand Up @@ -111,12 +113,16 @@ def __exclude_short_of_money_players(self, table, ante, sb_amount):

def __steal_money_from_poor_player(self, table, ante, sb_amount):
players = table.seats.players
# count players holding chips before ante zeroing to detect heads-up
pre_ante_active = len([p for p in players if p.stack > 0])
# exclude player who cannot pay ante
for player in [p for p in players if p.stack < ante]: player.stack = 0
if players[table.dealer_btn].stack == 0: table.shift_dealer_btn()

search_targets = players + players + players
search_targets = search_targets[table.dealer_btn+1:table.dealer_btn+1+len(players)]
# heads-up: dealer button posts the small blind. otherwise sb sits left of the button
sb_offset = 0 if pre_ante_active == 2 else 1
search_targets = search_targets[table.dealer_btn+sb_offset:table.dealer_btn+sb_offset+len(players)]
# exclude player who cannot pay small blind
sb_player = self.__find_first_elligible_player(search_targets, sb_amount + ante)
sb_relative_pos = search_targets.index(sb_player)
Expand Down
10 changes: 5 additions & 5 deletions pypokerengine/engine/round_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ def __deal_holecard(self, deck, players):

@classmethod
def __start_street(self, state):
next_player_pos = state["table"].next_ask_waiting_player_pos(state["table"].sb_pos()-1)
state["next_player"] = next_player_pos
street = state["street"]
if street == Const.Street.PREFLOP:
return self.__preflop(state)
elif street == Const.Street.FLOP:
# postflop the first player to act sits left of the button (sb in 3+ player, bb heads-up)
state["next_player"] = state["table"].next_ask_waiting_player_pos(state["table"].dealer_btn)
if street == Const.Street.FLOP:
return self.__flop(state)
elif street == Const.Street.TURN:
return self.__turn(state)
Expand All @@ -88,8 +88,8 @@ def __start_street(self, state):

@classmethod
def __preflop(self, state):
for i in range(2):
state["next_player"] = state["table"].next_ask_waiting_player_pos(state["next_player"])
# preflop the first player to act sits left of the big blind (utg in 3+ player, sb heads-up)
state["next_player"] = state["table"].next_ask_waiting_player_pos(state["table"].bb_pos())
return self.__forward_street(state)

@classmethod
Expand Down
47 changes: 25 additions & 22 deletions tests/pypokerengine/api/emulator_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,26 @@ def test_blind_structure(self):
game_state = attach_hole_card_from_deck(game_state, "pwtwlmfciymjdoljkhagxa")
self.emu.set_game_rule(2, 10, 5, 0)
self.emu.set_blind_structure({5: { "ante": 5, "small_blind": 60 } })
p1 = TestPlayer([("fold", 0), ('raise', 55), ('call', 0)])
p2 = TestPlayer([("call", 15), ("call", 55), ('fold', 0)])
p1 = TestPlayer([])
p2 = TestPlayer([("fold", 0), ("fold", 0)])
self.emu.register_player("tojrbxmkuzrarnniosuhct", p1)
self.emu.register_player("pwtwlmfciymjdoljkhagxa", p2)

game_state, events = self.emu.run_until_round_finish(game_state)
self.eq(65, game_state["table"].seats.players[0].stack)
self.eq(135, game_state["table"].seats.players[1].stack)
self.eq(120, game_state["table"].seats.players[0].stack)
self.eq(80, game_state["table"].seats.players[1].stack)

game_state, events = self.emu.start_new_round(game_state)
game_state, events = self.emu.run_until_round_finish(game_state)
self.eq(120, game_state["table"].seats.players[0].stack)
self.eq(80, game_state["table"].seats.players[1].stack)
self.eq(125, game_state["table"].seats.players[0].stack)
self.eq(75, game_state["table"].seats.players[1].stack)

# round 5 uses the updated blind level (small_blind 60); player[1]
# can no longer cover the big blind, so the game finishes.
game_state, events = self.emu.start_new_round(game_state)
self.eq("event_game_finish", events[0]["type"])
self.eq(0, game_state["table"].seats.players[0].stack)
self.eq(80, game_state["table"].seats.players[1].stack)
self.eq(125, game_state["table"].seats.players[0].stack)
self.eq(0, game_state["table"].seats.players[1].stack)

def test_blind_structure_update(self):
self.emu.set_game_rule(2, 8, 5, 3)
Expand Down Expand Up @@ -143,8 +145,8 @@ def test_apply_action_start_next_round(self):

game_state, events = self.emu.apply_action(game_state, "raise", 20)
self.eq("event_ask_player", events[-1]["type"])
self.eq(100, game_state["table"].seats.players[0].stack)
self.eq(70, game_state["table"].seats.players[1].stack)
self.eq(110, game_state["table"].seats.players[0].stack)
self.eq(60, game_state["table"].seats.players[1].stack)

@raises(Exception)
def test_apply_action_when_game_finished(self):
Expand All @@ -166,7 +168,7 @@ def test_run_until_round_finish(self):
game_state = attach_hole_card_from_deck(game_state, "pwtwlmfciymjdoljkhagxa")
self.emu.set_game_rule(2, 10, 5, 0)
p1 = TestPlayer([("fold", 0)])
p2 = TestPlayer([("call", 15)])
p2 = TestPlayer([("call", 15), ("fold", 0)])
self.emu.register_player("tojrbxmkuzrarnniosuhct", p1)
self.emu.register_player("pwtwlmfciymjdoljkhagxa", p2)

Expand All @@ -181,7 +183,7 @@ def test_run_until_round_finish_when_already_finished(self):
game_state = attach_hole_card_from_deck(game_state, "pwtwlmfciymjdoljkhagxa")
self.emu.set_game_rule(2, 10, 5, 0)
p1 = TestPlayer([("fold", 0)])
p2 = TestPlayer([("call", 15)])
p2 = TestPlayer([("call", 15), ("fold", 0)])
self.emu.register_player("tojrbxmkuzrarnniosuhct", p1)
self.emu.register_player("pwtwlmfciymjdoljkhagxa", p2)
game_state, events = self.emu.run_until_round_finish(game_state)
Expand Down Expand Up @@ -220,8 +222,8 @@ def test_run_until_game_finish(self):

game_state, events = self.emu.run_until_game_finish(game_state)
self.eq("event_game_finish", events[-1]["type"])
self.eq(114, game_state["table"].seats.players[0].stack)
self.eq(86, game_state["table"].seats.players[1].stack)
self.eq(126, game_state["table"].seats.players[0].stack)
self.eq(74, game_state["table"].seats.players[1].stack)

def test_run_until_game_finish_when_one_player_is_left(self):
uuids = ["ruypwwoqwuwdnauiwpefsw", "sqmfwdkpcoagzqxpxnmxwm", "uxrdiwvctvilasinweqven"]
Expand All @@ -230,17 +232,18 @@ def test_run_until_game_finish_when_one_player_is_left(self):
game_state = reduce(lambda state, item: attach_hole_card(state, item[0], item[1]), zip(uuids, holecards), game_state)
sb_amount, ante = 5, 7
self.emu.set_game_rule(3, 10, sb_amount, ante)
p1_acts = [("fold",0), ("call", 10), ('call', 0), ('call', 10), ("fold",0)]
# p3 raises big in round 3 (heads-up, p3=btn/SB); p1 folds out of chips by round 5
p1_acts = [("fold",0), ("fold",0), ("fold",0)]
p2_acts = []
p3_acts = [("raise", 10)]
p3_acts = [("raise", 200)]
players = [TestPlayer(acts) for acts in [p1_acts, p2_acts, p3_acts]]
[self.emu.register_player(uuid, player) for uuid, player in zip(uuids, players)]
game_state["table"].deck.deck.append(Card.from_str("C7"))
game_state, events = self.emu.run_until_game_finish(game_state)
self.eq("event_game_finish", events[-1]["type"])
self.eq(0, game_state["table"].seats.players[0].stack)
self.eq(0, game_state["table"].seats.players[1].stack)
self.eq(292, game_state["table"].seats.players[2].stack)
self.eq(294, game_state["table"].seats.players[2].stack)

def test_run_until_game_finish_when_final_round(self):
uuids = ["ruypwwoqwuwdnauiwpefsw", "sqmfwdkpcoagzqxpxnmxwm", "uxrdiwvctvilasinweqven"]
Expand Down Expand Up @@ -287,11 +290,11 @@ def test_start_new_round(self):
self.eq(4, game_state["round_count"])
self.eq(1, game_state["table"].dealer_btn)
self.eq(0, game_state["street"])
self.eq(0, game_state["next_player"])
self.eq(1, game_state["next_player"])
self.eq("event_new_street", events[0]["type"])
self.eq("event_ask_player", events[1]["type"])
self.eq("preflop", events[0]["street"])
self.eq("tojrbxmkuzrarnniosuhct", events[1]["uuid"])
self.eq("pwtwlmfciymjdoljkhagxa", events[1]["uuid"])

def test_start_new_round_exclude_no_money_players(self):
uuids = ["ruypwwoqwuwdnauiwpefsw", "sqmfwdkpcoagzqxpxnmxwm", "uxrdiwvctvilasinweqven"]
Expand Down Expand Up @@ -378,9 +381,9 @@ def test_generate_initial_game_state(self):

state, events = self.emu.start_new_round(state)
self.eq(0, state["table"].dealer_btn)
self.eq(1, state["table"].sb_pos())
self.eq(0, state["table"].bb_pos())
self.eq(1, state["next_player"])
self.eq(0, state["table"].sb_pos())
self.eq(1, state["table"].bb_pos())
self.eq(0, state["next_player"])
state, events = self.emu.apply_action(state, "call", 10)
self.eq(1, state["next_player"])

Expand Down
12 changes: 6 additions & 6 deletions tests/pypokerengine/api/game_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ def test_start_poker(self):
result = G.start_poker(config)
p1, p2 = [result["players"][i] for i in range(2)]
self.eq("p1", p1["name"])
self.eq(110, p1["stack"])
self.eq(90, p1["stack"])
self.eq("p2", p2["name"])
self.eq(90, p2["stack"])
self.eq(110, p2["stack"])

def test_start_poker_with_ante(self):
config = G.setup_config(1, 100, 10, 15)
Expand All @@ -24,9 +24,9 @@ def test_start_poker_with_ante(self):
result = G.start_poker(config)
p1, p2 = [result["players"][i] for i in range(2)]
self.eq("p1", p1["name"])
self.eq(125, p1["stack"])
self.eq(75, p1["stack"])
self.eq("p2", p2["name"])
self.eq(75, p2["stack"])
self.eq(125, p2["stack"])

def test_set_blind_structure(self):
config = G.setup_config(1, 100, 10)
Expand All @@ -35,8 +35,8 @@ def test_set_blind_structure(self):
config.set_blind_structure({ 1: { "ante":5, "small_blind":10 } })
result = G.start_poker(config)
p1, p2 = [result["players"][i] for i in range(2)]
self.eq(115, p1["stack"])
self.eq(85, p2["stack"])
self.eq(85, p1["stack"])
self.eq(115, p2["stack"])

def test_start_poker_validation_when_no_player(self):
config = G.setup_config(1, 100, 10)
Expand Down
13 changes: 8 additions & 5 deletions tests/pypokerengine/engine/dealer_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ def test_publish_msg(self):
"receive_round_result_message"
]

# heads-up: btn (player[1]) posts the small blind and is asked to act first
for i, expected in enumerate(first_player_expected):
self.eq(expected, algos[0].received_msgs[i])
for i, expected in enumerate(second_player_expected):
self.eq(expected, algos[1].received_msgs[i])
for i, expected in enumerate(second_player_expected):
self.eq(expected, algos[0].received_msgs[i])

def test_play_a_round(self):
algos = [FoldMan() for _ in range(2)]
Expand All @@ -57,8 +58,9 @@ def test_play_a_round(self):
self.dealer.table.dealer_btn = 1
summary = self.dealer.start_game(1)
player_state = summary["message"]["game_information"]["seats"]
self.eq(95, player_state[0]["stack"])
self.eq(105, player_state[1]["stack"])
# heads-up: btn (player[1]) posts small blind, player[0] is the big blind
self.eq(105, player_state[0]["stack"])
self.eq(95, player_state[1]["stack"])

def test_play_two_round(self):
algos = [FoldMan() for _ in range(2)]
Expand Down Expand Up @@ -113,8 +115,9 @@ def test_exclude_short_of_money_player_when_ante_on(self):
self.eq(fetch_stacks(result), [1060, 0, 0, 1025, 40])
result = dealer.start_game(3)
self.eq(fetch_stacks(result), [1100, 0, 0, 985, 0])
# round 4 is heads-up (only p0 and p3 remain); p3 (btn) posts SB per HU rules
result = dealer.start_game(4)
self.eq(fetch_stacks(result), [1060, 0, 0, 1025, 0])
self.eq(fetch_stacks(result), [1140, 0, 0, 945, 0])

def test_exclude_short_of_money_player_when_ante_on2(self):
dealer = Dealer(5, 100, 20)
Expand Down