def __init__(self, bot, ctx): super().__init__(ctx) self.deck = Deck() self.bot = bot self.announcer = BlackjackPlayerAnnouncer(bot) self.ai_generator = AiUserGenerator(bot) self.dealer = self.init_dealer() self._turn_timer = TurnTimer(bot, self) self.standing_players = [ ] # Players to compare with the dealer's hand at the end of the game. self.payouts = [] self.id = GAME_ID['BLACKJACK'] self.payout_handler = PayoutHandler(self) self.max_num_players = 5
def __init__(self, ctx, bot): super().__init__(ctx) self.bot = bot self.title = "game of Bombtile" self.id = GAME_ID["BOMBTILE"] self._turn_timer = TurnTimer(bot, self) self.min_players = 2 self.max_players = 5 self.grid_values = [] # The hidden tile symbols. self.visible_grid = [] # The tiles that users see. self._payouts = [] self.num_cells = None # TBD -- Depends on number of players. self.num_columns = None # TBD self.num_rows = None # TBD self.grid_handler = None # TBD self.announcer = None # TBD -- Relies on grid dimensions. self.ai_generator = AiUserGenerator(bot)
class Bombtile(GameCore): """ A game where players take turns revealing tiles. Most tiles are empty, with several containing multipliers that increase your wager (and therefore increase your payout or loss). If a player reveals the "bomb" tile, they lose their wager to the rest of the players. """ def __init__(self, ctx, bot): super().__init__(ctx) self.bot = bot self.title = "game of Bombtile" self.id = GAME_ID["BOMBTILE"] self._turn_timer = TurnTimer(bot, self) self.min_players = 2 self.max_players = 5 self.grid_values = [] # The hidden tile symbols. self.visible_grid = [] # The tiles that users see. self._payouts = [] self.num_cells = None # TBD -- Depends on number of players. self.num_columns = None # TBD self.num_rows = None # TBD self.grid_handler = None # TBD self.announcer = None # TBD -- Relies on grid dimensions. self.ai_generator = AiUserGenerator(bot) def add_user(self, user) -> None: player = BombtilePlayer(user) super().add_player(player) super().add_user(user) async def add_ai(self) -> None: # Get from a random pool of AI "users" ai_user = self.ai_generator.get_ai_user() ai_player = BombtilePlayer.create_ai(ai_user) super().add_player(ai_player) await self.ctx.send(f"{ai_player.name} has joined the game.") def is_max_num_players(self) -> bool: return len(self.players) == self.max_players async def run(self) -> None: if self.__can_start_game(): self.__initialize_grid() # Grid dimensions are a dependency of feedback, because feedback uses a string representation of the grid. # Hence the ordering. await self.__initialize_feedback() super().start_game() random.shuffle(self.players) await self.announcer.announce_current_turn() await self._turn_timer.run() else: await self.ctx.send("Bombtile needs at least two players to start." ) async def flip(self, coordinates) -> None: """ The player action that reveals a tile. """ y = coordinates[0] x = coordinates[1] tile = self.__reveal_tile(y, x) await self.announcer.render_grid() await self.__check_multiplier(tile) await self.__check_game_end(tile) def get_payouts(self) -> List[dict]: return self._payouts def is_turn(self, user) -> bool: return user == self.get_current_player().user async def resolve_afk(self) -> None: await self.announcer.announce_afk() await self.__auto_flip_tile() def is_flippable_tile(self, coordinates: List[int]) -> bool: y = coordinates[0] x = coordinates[1] return self.visible_grid[y][x] is NEUTRAL_TILE def get_current_player(self) -> BombtilePlayer: return self.players[0] async def __initialize_feedback(self) -> None: self.announcer = BombtileAnnouncer(self) await self.announcer.announce_start() def __can_start_game(self) -> bool: num_players = len(self.players) return num_players >= 2 def __initialize_grid(self) -> None: # Number of cells in the grid are dependent on the number of players. self.num_cells = 3 * len(self.players) self.__initialize_grid_dimensions() self.__initialize_tile_values() self.__initialize_visible_tiles() def __initialize_visible_tiles(self) -> None: # All tiles start blank to the users. tiles = [] for i in range(self.num_cells): tiles.append(NEUTRAL_TILE) self.visible_grid = self.grid_handler.generate_grid(tiles) def __initialize_grid_dimensions(self) -> None: """ The grid tends to be wider than it is tall. """ if len(self.players) > 3: self.num_columns = len(self.players) self.num_rows = self.num_cells // len(self.players) else: self.num_columns = self.num_cells // len(self.players) self.num_rows = len(self.players) self.grid_handler = GridHandler(self.num_columns, self.num_rows) def __initialize_tile_values(self) -> None: self.grid_values.append(BOMB) self.__add_multipliers() self.__add_empty_tiles() random.shuffle(self.grid_values) self.grid_values = self.grid_handler.generate_grid(self.grid_values) def __add_empty_tiles(self) -> None: tiles_remaining = self.num_cells - len(self.grid_values) for i in range(tiles_remaining): self.grid_values.append(EMPTY_TILE) async def __check_multiplier(self, tile) -> None: if tile is not BOMB and tile is not EMPTY_TILE: # Then the tile must be a multiplier multiplier = tile['value'] self.get_current_player().update_multiplier(multiplier) await self.announcer.announce_multiplier() async def __check_game_end(self, tile) -> None: if tile is BOMB: await self.end_game() else: await self.__next_turn() async def end_game(self) -> None: await self.announcer.report_loss() await self.__resolve_payouts() super().end_game() async def __next_turn(self) -> None: self.__requeue_player() self._turn_timer.refresh_turn_timer() if await self.__is_final_tile(): return await self.announcer.announce_current_turn() await self.__check_ai_turn() async def __check_ai_turn(self) -> None: """ If the current player is an AI, automatically flip a tile. """ if self.get_current_player().is_ai: await asyncio.sleep(2.0) await self.__auto_flip_tile() async def __auto_flip_tile(self) -> None: """ Flips a random neutral tile. """ valid_tiles = self.__get_neutral_tiles() random_tile = roll(valid_tiles) await self.flip(random_tile) def __get_neutral_tiles(self) -> List[List[int]]: """ Get a list of all indices for flippable tiles. """ neutral_tiles = [] for x in range(self.num_columns): for y in range(self.num_rows): if self.is_flippable_tile([y, x]): neutral_tiles.append([y, x]) return neutral_tiles def __requeue_player(self) -> None: # If the game is not over, put the player at the end of the queue after their turn. player = self.players.pop(0) self.players.append(player) async def __is_final_tile(self) -> bool: """ If a player is stuck with the last remaining tile, it gets auto flipped on their turn. """ tiles = self.__get_neutral_tiles() if len(tiles) == 1: player = self.get_current_player() await self.announcer.auto_reveal(player) await self.flip(tiles[0]) return True async def __resolve_payouts(self) -> None: loser = self.players.pop(0) for winner in self.players: await self.__resolve_payout(winner, loser) async def __resolve_payout(self, winner, loser) -> None: to_user = winner.user amount = winner.wager * winner.get_multiplier() * loser.get_multiplier( ) from_user = loser.user await self.announcer.report_payout(winner, amount) self.__add_payout(to_user, amount, from_user) def __add_payout(self, to_user, amount: int, from_user) -> None: self._payouts.append({ 'to_user': to_user, 'amount': amount, 'from_user': from_user }) def __reveal_tile(self, y: int, x: int) -> dict: self.visible_grid[y][x] = self.grid_values[y][x] return self.grid_values[y][x] def __add_multipliers(self) -> None: """ Multiplier tiles multiply your wager by the shown amount. Eg. if you reveal a TWO tile, you could lose x2 more gold, or win x2 more gold. """ num_players = len(self.players) num_multipliers = random.randint(num_players - 1, num_players) for i in range(num_multipliers): self.__add_random_multiplier() def __add_random_multiplier(self) -> None: possible_multipliers = [TWO, TWO, TWO, THREE, THREE, FIVE] multiplier = roll(possible_multipliers) self.grid_values.append(multiplier)
class Blackjack(GameCore): def __init__(self, bot, ctx): super().__init__(ctx) self.deck = Deck() self.bot = bot self.announcer = BlackjackPlayerAnnouncer(bot) self.ai_generator = AiUserGenerator(bot) self.dealer = self.init_dealer() self._turn_timer = TurnTimer(bot, self) self.standing_players = [ ] # Players to compare with the dealer's hand at the end of the game. self.payouts = [] self.id = GAME_ID['BLACKJACK'] self.payout_handler = PayoutHandler(self) self.max_num_players = 5 def init_dealer(self) -> BlackjackDealer: # TODO let players host blackjack games user = self.ai_generator.get_ai_user() return BlackjackDealer(user, self) async def start_game(self): self.__dispense_cards() await self.__show_player_cards() await self.dealer.show_face_up() await self.__check_initial_dealer_cards() super().start_game() await self._turn_timer.run() def add_user(self, user): super().add_user(user) self.add_player(user) def add_player(self, user) -> None: player = BlackjackPlayer(user) super().add_player(player) def is_turn(self, user): first_in_queue = self.__get_current_player().user return user is first_in_queue async def resolve_afk(self) -> None: player = self.__get_current_player() is_last_player = len(self.players) == 1 if player.afk > 0 or is_last_player: await self.bot.say( f"{player.name} is away. They have forfeited the match.") self.forfeit() else: await self.__requeue_afk_player() await self.__next_turn() async def __requeue_afk_player(self): player = self.players.pop(0) self.players.append(player) await self.bot.say( f"{player.name} seems to be away. Skipping their turn...") def forfeit(self): """ A player who forfeits or quits loses their hands' wagers. """ player = self.__get_current_player() for hand in player.get_hands(): self.payout_handler.add_bust(player, hand) self.__knock_out_current_player() async def hit(self) -> None: hand = self.__get_current_player().get_active_hand() new_card = self.deck.draw_card() hand.hit(new_card) await self.announcer.report_hit(hand, new_card) await self.__check_hit_bust(hand) async def stand_current_hand(self) -> None: hand = self.__get_current_player().get_active_hand() hand.end_turn() await self.announcer.progressing() await self.__check_next_hand() async def attempt_double_down(self) -> None: hand = self.__get_current_player().get_active_hand() if self._double_down(hand): wager = hand.get_wager() await self.announcer.double_down_success(wager) await self.stand_current_hand() else: await self.announcer.double_down_fail() async def attempt_split(self) -> None: player = self.__get_current_player() hand = player.get_active_hand() if player.split_hand(): await self.announcer.split_successful(hand) else: await self.announcer.split_fail() def get_payouts(self): return self.payouts def _double_down(self, hand: PlayerHand) -> bool: """ Double wager, then draw and finish. """ if hand.can_double_down(): card = self.deck.draw_card() hand.double_down(card) return True async def end_game(self) -> None: await self.payout_handler.resolve_outcomes() self.payouts = self.payout_handler.get_payouts() super().end_game() def __dispense_cards(self) -> None: for player in self.players: self.__add_initial_cards(player) self.__add_initial_cards(self.dealer) def __add_initial_cards(self, participant): starting_hand = participant.get_active_hand() num_cards = 2 for i in range(num_cards): card = self.deck.draw_card() starting_hand.add_card(card) async def __show_player_cards(self) -> None: for player in self.players: hand = player.get_active_hand() await self.announcer.player_cards(player.name, hand) async def __check_initial_dealer_cards(self) -> None: if await self.dealer.has_blackjack(): await self.end_game() else: await self.__next_turn() async def __next_turn(self) -> None: are_player_turns_remaining = self.players if are_player_turns_remaining: await self.__next_player_turn() else: await self.__check_dealer_turn() await self.end_game() async def __check_dealer_turn(self) -> None: self._turn_timer.refresh_turn_timer() are_players_standing = self.standing_players if are_players_standing: await self.dealer.make_move() else: await self.announcer.no_players_left() async def __next_player_turn(self) -> None: self._turn_timer.refresh_turn_timer() current_player = self.__get_current_player() player_name = current_player.name hand = current_player.get_active_hand() await self.announcer.next_turn(player_name, hand) async def __check_next_hand(self) -> None: active_hand = self.__get_current_player().get_active_hand() if active_hand: await self.announcer.next_hand_options(active_hand) else: self.__current_player_stand() await self.__next_turn() self._turn_timer.refresh_turn_timer() def __current_player_stand(self) -> None: """ When a player stands, their turn ends. Hands are compared at the end of the game. """ player = self.players.pop(0) self.standing_players.append(player) async def __check_hit_bust(self, hand: BlackjackHand) -> None: if not hand.is_bust(): await self.announcer.ask_hit_again() return await self.announcer.declare_player_bust() await self.__bust_current_hand() async def __bust_current_hand(self) -> None: player = self.__get_current_player() hand_to_bust = player.bust_active_hand() self.payout_handler.add_bust(player, hand_to_bust) await self.__check_knock_out() async def __check_knock_out(self) -> None: """ A player is removed from the game if they have no valid hands remaining. """ if self.__get_current_player().get_hands(): await self.__check_next_hand() else: self.__knock_out_current_player() await self.__next_turn() def __get_current_player(self) -> BlackjackPlayer: """ The player whose turn it is. """ return self.players[0] def __knock_out_current_player(self) -> None: del self.players[0]