class Game: def __init__(self) -> None: self.new_game() # Set the game options to the defaults self.options = { key: value.default for key, value in GAME_OPTIONS.items() } def new_game(self) -> None: self.state = GameState.NO_GAME # The players participating in the game self.players = [] # The players participating in the current hand self.in_hand = [] # The index of the current dealer self.dealer_index = 0 # The index of the first person to bet in the post-flop rounds self.first_bettor = 0 # The deck that we're dealing from self.cur_deck = None # The five cards shared by all players self.shared_cards = [] # Used to keep track of the current value of the pot, and who's in it self.pot = PotManager() # The index of the player in in_hand whose turn it is self.turn_index = -1 # The last time that the blinds were automatically raised self.last_raise = None # Adds a new player to the game, returning if they weren't already playing def add_player(self, user: discord.User) -> bool: if self.is_player(user): return False self.players.append(Player(user)) print("GameJoin {}".format(user.name)) return True # Returns whether a user is playing in the game def is_player(self, user: discord.User) -> bool: for player in self.players: if player.user == user: return True return False # Removes a player from being able to bet, if they folded or went all in def leave_hand(self, to_remove: Player) -> None: for i, player in enumerate(self.in_hand): if player == to_remove: index = i break else: # The player who we're removing isn't in the hand, so just # return return self.in_hand.pop(index) # Adjust the index of the first person to bet and the index of the # current player, depending on the index of the player who just folded if index < self.first_bettor: self.first_bettor -= 1 if self.first_bettor >= len(self.in_hand): self.first_bettor = 0 if self.turn_index >= len(self.in_hand): self.turn_index = 0 # Returns some messages to update the players on the state of the game def status_between_rounds(self) -> List[str]: messages = [] for player in self.players: messages.append("**{}** has $*{}*.".format(player.user.name, player.balance)) messages.append( "**{}** is the current dealer. Message ?deal to deal when you're ready." .format(self.dealer.user.mention)) print("---{} DEALER.".format(self.dealer.user.name)) return messages # Moves on to the next dealer def next_dealer(self) -> None: self.dealer_index = (self.dealer_index + 1) % len(self.players) # Returns the current dealer @property def dealer(self) -> Player: return self.players[self.dealer_index] @property def cur_bet(self) -> int: return self.pot.cur_bet # Returns the player who is next to move @property def current_player(self) -> Player: return self.in_hand[self.turn_index] # Starts a new game, returning the messages to tell the channel def start(self) -> List[str]: self.state = GameState.NO_HANDS self.dealer_index = 0 for player in self.players: player.balance = self.options["buy-in"] # Reset the blind to be the starting blind value self.options["blind"] = self.options["starting-blind"] return ["The game has begun!"] + self.status_between_rounds() # Starts a new round of Hold'em, dealing two cards to each player, and # return the messages to tell the channel def deal_hands(self) -> List[str]: # Shuffles a new deck of cards self.cur_deck = Deck() # Start out the shared cards as being empty self.shared_cards = [] # Deals hands to each player, setting their initial bets to zero and # adding them as being in on the hand self.in_hand = [] for player in self.players: player.cards = (self.cur_deck.draw(), self.cur_deck.draw()) player.cur_bet = 0 player.placed_bet = False self.in_hand.append(player) self.state = GameState.HANDS_DEALT messages = ["The hands have been dealt!"] # Reset the pot for the new hand self.pot.new_hand(self.players) if self.options["blind"] > 0: messages += self.pay_blinds() self.turn_index -= 1 return messages + self.next_turn() # Makes the blinds players pay up with their initial bets def pay_blinds(self) -> List[str]: messages = [] # See if we need to raise the blinds or not raise_delay = self.options["raise-delay"] if raise_delay == 0: # If the raise delay is set to zero, consider it as being turned # off, and do nothing for blinds raises self.last_raise = None elif self.last_raise is None: # Start the timer, if it hasn't been started yet self.last_raise = datetime.now() elif datetime.now() - self.last_raise > timedelta(minutes=raise_delay): messages.append("**Blinds are being doubled this round!**") self.options["blind"] *= 2 self.last_raise = datetime.now() blind = self.options["blind"] # Figure out the players that need to pay the blinds if len(self.players) > 2: small_player = self.players[(self.dealer_index + 1) % len(self.in_hand)] big_player = self.players[(self.dealer_index + 2) % len(self.in_hand)] # The first player to bet pre-flop is the player to the left of the big blind self.turn_index = (self.dealer_index + 3) % len(self.in_hand) # The first player to bet post-flop is the first player to the left of the dealer self.first_bettor = (self.dealer_index + 1) % len(self.players) else: # In heads-up games, who plays the blinds is different, with the # dealer playing the small blind and the other player paying the big small_player = self.players[self.dealer_index] big_player = self.players[self.dealer_index - 1] # Dealer goes first pre-flop, the other player goes first afterwards self.turn_index = self.dealer_index self.first_bettor = self.dealer_index - 1 messages.append("**{}** has paid the small blind of $*{}*.".format( small_player.name, blind)) if self.pot.pay_blind(small_player, blind): messages.append("***{} is all in!***".format(small_player.name)) tmp = self.leave_hand(small_player) messages.append("**{}** has paid the big blind of $*{}*.".format( big_player.name, blind * 2)) if self.pot.pay_blind(big_player, blind * 2): messages.append("***{} is all in!***".format(big_player.name)) tmp = self.leave_hand(big_player) return messages # Returns messages telling the current player their options def cur_options(self) -> List[str]: messages = [ "It is **{}'s** turn. ".format(self.current_player.user.mention), "**{}** currently has ".format(self.current_player.user.name), "$*{}*. ".format(self.current_player.balance), "The pot is currently $***{}***.".format(self.pot.value) ] if self.pot.cur_bet > 0: messages.append( "The current bet to meet is $*{}*, and **{}** has bet $*{}*.". format(self.cur_bet, self.current_player.name, self.current_player.cur_bet)) else: messages.append("The current bet to meet is $*{}*.".format( self.cur_bet)) if self.current_player.cur_bet == self.cur_bet: messages.append("Message ?check, ?raise or ?fold.") elif self.current_player.max_bet > self.cur_bet: messages.append("Message ?call, ?raise or ?fold.") else: messages.append("Message ?all-in or ?fold.") return messages # Advances to the next round of betting (or to the showdown), returning a # list messages to tell the players def next_round(self) -> List[str]: messages = [] if self.state == GameState.HANDS_DEALT: messages.append("Dealing the flop:") self.shared_cards.append(self.cur_deck.draw()) self.shared_cards.append(self.cur_deck.draw()) self.shared_cards.append(self.cur_deck.draw()) self.state = GameState.FLOP_DEALT elif self.state == GameState.FLOP_DEALT: messages.append("Dealing the turn:") self.shared_cards.append(self.cur_deck.draw()) self.state = GameState.TURN_DEALT elif self.state == GameState.TURN_DEALT: messages.append("Dealing the river:") self.shared_cards.append(self.cur_deck.draw()) self.state = GameState.RIVER_DEALT elif self.state == GameState.RIVER_DEALT: return self.showdown() messages.append(" ".join(str(card) for card in self.shared_cards)) self.pot.next_round() self.turn_index = self.first_bettor return messages + self.cur_options() # Finish a player's turn, advancing to either the next player who needs to # bet, the next round of betting, or to the showdown def next_turn(self) -> List[str]: if self.pot.round_over(): if self.pot.betting_over(): return self.showdown() else: return self.next_round() else: self.turn_index = (self.turn_index + 1) % len(self.in_hand) return self.cur_options() def showdown(self) -> List[str]: while len(self.shared_cards) < 5: self.shared_cards.append(self.cur_deck.draw()) messages = [ "We have reached the end of betting. " "All cards will be revealed." ] messages.append(" ".join(str(card) for card in self.shared_cards)) for player in self.pot.in_pot(): messages.append("**{}'s** hand: **{} {}**".format( player.user.mention, player.cards[0], player.cards[1])) winners = self.pot.get_winners(self.shared_cards) for winner, winnings in sorted(winners.items(), key=lambda item: item[1]): hand_name = str(best_possible_hand(self.shared_cards, winner.cards)) messages.append("**{}** wins $***{}*** with a **{}**.".format( winner.user.mention, winnings, hand_name)) print("{} WINS +{}".format(winner.user.name, winnings)) winner.balance += winnings # Remove players that went all in and lost i = 0 while i < len(self.players): player = self.players[i] if player.balance > 0: i += 1 else: messages.append( "**{}** has been knocked out of the game!".format( player.user.mention)) print("{} OUT.".format(player.user.name)) self.players.pop(i) if len(self.players) == 1: # There's only one player, so they win messages.append( "**{}** wins the game! Congratulations!".format( self.players[0].user.mention)) print("WINNER {}\n".format(self.players[0].name)) self.state = GameState.NO_GAME return messages if i <= self.dealer_index: self.dealer_index -= 1 # Go on to the next round self.state = GameState.NO_HANDS self.next_dealer() messages += self.status_between_rounds() return messages # Make the current player check, betting no additional money def check(self) -> List[str]: self.current_player.placed_bet = True print("---{} CHECKED.".format(self.current_player.name)) return ["{} checks.".format(self.current_player.name) ] + self.next_turn() # Has the current player raise a certain amount def raise_bet(self, amount: int) -> List[str]: self.pot.handle_raise(self.current_player, amount) messages = [ "**{}** raises by $*{}*.".format(self.current_player.name, amount) ] print("---{} RAISE +{}.".format(self.current_player.name, amount)) if self.current_player.balance == 0: messages.append("***{} is all in!***".format( self.current_player.name)) print("---{} WENT ALL IN.".format(self.current_player.name)) self.leave_hand(self.current_player) self.turn_index -= 1 return messages + self.next_turn() # Has the current player match the current bet def call(self) -> List[str]: self.pot.handle_call(self.current_player) messages = ["**{}** calls.".format(self.current_player.name)] print("---{} CALLED.".format(self.current_player.name)) if self.current_player.balance == 0: messages.append("***{} is all in!***".format( self.current_player.name)) print("---{} WENT ALL IN.".format(self.current_player.name)) self.leave_hand(self.current_player) self.turn_index -= 1 return messages + self.next_turn() def all_in(self) -> List[str]: if self.pot.cur_bet > self.current_player.max_bet: return self.call() else: return self.raise_bet(self.current_player.max_bet - self.cur_bet) # Has the current player fold their hand def fold(self) -> List[str]: messages = ["**{}** has folded.".format(self.current_player.name)] print("---{} FOLDED.".format(self.current_player.name)) self.pot.handle_fold(self.current_player) self.leave_hand(self.current_player) # If only one person is left in the pot, give it to them instantly if len(self.pot.in_pot()) == 1: winner = list(self.pot.in_pot())[0] messages += [ "**{}** wins $***{}***!".format(winner.user.mention, self.pot.value) ] winner.balance += self.pot.value self.state = GameState.NO_HANDS self.next_dealer() return messages + self.status_between_rounds() # If there's still betting to do, go on to the next turn if not self.pot.betting_over(): self.turn_index -= 1 return messages + self.next_turn() # Otherwise, have the showdown immediately return self.showdown() # Send a message to each player, telling them what their hole cards are async def tell_hands(self, client: discord.Client): for player in self.players: await client.send_message( player.user, str(player.cards[0]) + " " + str(player.cards[1]))
class Room(): def __init__(self, room_name, blind, buyin, room_id): self.room_name = room_name if isinstance(blind, int) and blind < 100: self.blind = blind else: raise Exception('blind must be int and small than 100') if isinstance(buyin, int): self.buyin = buyin else: raise Exception('buyin must be int') self.lock = Lock() self.room_id = room_id self.stage = 1 #未开局/翻牌/转牌/河牌/开牌 self.players = [] self.change_banker = False self.banker = 1 self.speak = 0 #all speak self.queue = Queue() self.poker_engine = PokerEngine() self.public = Deck() self.players_cache = {} def AddPlayer(self, player_name): if player_name not in self.players_cache: player = Player(player_name, self.buyin, len(self.players) + 1) self.players_cache[player_name] = player else: player = self.players_cache[player_name] for player_in_room in self.players: if player_in_room.player_name == player_name: return False player.player_id = len(self.players) + 1 player.active = False player.ready = False self.players.append(player) return True def DelPlayer(self, player_name): idx = -1 for player_idx, player in enumerate(self.players): if player.player_name == player_name: idx = player_idx if idx == -1: raise Exception('can not find player {}'.format(player_name)) self.players_cache[player_name] = self.players[idx - 1] del self.players[idx - 1] for player_idx, player in enumerate(self.players): player.player_id = player_idx def GetRoomInfo(self): rsp = RoomResponse() rsp.code = 0 rsp.room_name = self.room_name rsp.room_id = self.room_id rsp.blind = self.blind rsp.buyin = self.buyin return rsp def PushAction(self, user_name, action): self.queue.push((user_name, action)) def GetStatus(self, user_name): rsp = RoomStatus() rsp.stage = self.stage for player in self.players: #print('get status find player {}, id = {}'.format(player.player_name,player.player_id)) if player.player_name == user_name or self.stage == 1: rsp.players.append(player.GetStatus(show_hands=True)) else: rsp.players.append(player.GetStatus(show_hands=False)) for suit, value in self.public.get_pokers(): rsp.public.append(Poker(suit=suit, value=value)) rsp.banker = self.banker rsp.speak = self.speak #print('get status = {}'.format(rsp)) return rsp def active_player(self): res = 0 for player in self.players: if player.ready: res += 1 return res def find_player(self, player_name): for player in self.players: if player.player_name == player_name: return player def run(self): try: self._run() except: print(traceback.format_exc()) def _run(self): target = 0 btn = 0 ptr = 0 while True: while len(self.players) == 0: time.sleep(1) if self.stage == 1: while not (len(self.players) >= 2 and self.active_player() == len(self.players)): try: user_name, action = self.queue.get() print('recieve action:{} {}'.format(user_name, action)) if action['action'] == 'quit': self.DelPlayer(user_name) continue elif action['action'] != 'ready': continue player = self.find_player(user_name) player.ready = True print('set player {} ready'.format(player.player_name)) except queue.Empty: for player in self.players: if not player.ready: self.DelPlayer(player.player_name) continue for player in self.players: if player.counter < self.blind * 2: player.BuyIn(self.buyin) #print('begin stage 2') self.stage = 2 elif self.stage == 2: if self.change_banker: self.banker = self.next_id(self.banker) self.poker_engine.wash() #TODO:发牌和清除状态 for player in self.players: player.hands.clear() player.hands.draw(self.poker_engine) player.hands.draw(self.poker_engine) player.active = True self.public.clear() sb_idx = self.next_id(self.banker) sb = self.players[sb_idx - 1] sb.PutBlind(self.blind) bb_idx = self.next_id(sb_idx) bb = self.players[bb_idx - 1] bb.PutBlind(self.blind * 2) ptr = self.next_id(bb_idx) btn = ptr target = self.blind * 2 first_ptr_flag = True #print('ptr = {}, btn = {}'.format(ptr, btn)) while ptr != btn or (ptr == btn and first_ptr_flag): if ptr == btn and first_ptr_flag: first_ptr_flag = False if self.players[ptr - 1].active: self.speak = ptr new_target = self.players[ptr - 1].Speak( target, self.queue) if new_target != target: target = new_target btn = ptr ptr = self.next_id(ptr) self.stage = 3 elif self.stage == 3: for i in range(3): self.public.draw(self.poker_engine) ptr = self.next_id(self.banker) btn = ptr first_ptr_flag = True while ptr != btn or (ptr == btn and first_ptr_flag): if ptr == btn and first_ptr_flag: first_ptr_flag = False if self.players[ptr - 1].active: self.speak = ptr new_target = self.players[ptr - 1].Speak( target, self.queue) if new_target != target: target = new_target btn = ptr ptr = self.next_id(ptr) self.stage = 4 elif self.stage == 4: self.public.draw(self.poker_engine) ptr = self.next_id(self.banker) btn = ptr first_ptr_flag = True while ptr != btn or (ptr == btn and first_ptr_flag): if ptr == btn and first_ptr_flag: first_ptr_flag = False if self.players[ptr - 1].active: self.speak = ptr new_target = self.players[ptr - 1].Speak( target, self.queue) if new_target != target: target = new_target btn = ptr ptr = self.next_id(ptr) self.stage = 5 elif self.stage == 5: self.public.draw(self.poker_engine) ptr = self.next_id(self.banker) btn = ptr first_ptr_flag = True while ptr != btn or (ptr == btn and first_ptr_flag): if ptr == btn and first_ptr_flag: first_ptr_flag = False if self.players[ptr - 1].active: self.speak = ptr new_target = self.players[ptr - 1].Speak( target, self.queue) if new_target != target: target = new_target btn = ptr ptr = self.next_id(ptr) self.stage = 6 elif self.stage == 6: #按牌型大小排序 winners = CalWinners(self.players, self.public) for idx, win_player in enumerate(winners): if win_player.active: #如果玩家没有fold for lose_player_idx in range( len(winners) - 1, idx - 1, -1): lose_player = winners[lose_player_idx] if win_player.pool >= lose_player.pool: money = lose_player.pool else: money = win_player.pool win_player.counter += money lose_player.pool -= money print('player {} got {} from {}'.format( win_player.player_name, money, lose_player.player_name)) self.stage = 1 self.change_banker = True self.speak = 0 #all speak for player in self.players: player.ready = False def next_id(self, idx, times=1): for i in range(times): idx = idx + 1 if idx > len(self.players): idx = 1 return idx def pre_id(self, idx, times=1): for i in range(times): idx = idx - 1 if idx < 1: idx = len(self.players) return idx