class ChooseStartingPositionController(Controller): _instance = None def __init__(self, current_player: PlayerModel): super().__init__(current_player) if ChooseStartingPositionController._instance: self._current_player = current_player # raise Exception("ChooseStartingPositionController is a singleton") self.current_player = current_player self.game_board_sprite = GameBoard.instance() self.choose_prompt = RectLabel( 500, 30, 350, 75, Color.GREY, 0, Text(pygame.font.SysFont('Agency FB', 30), "Choose Starting Position", Color.GREEN2)) self.choose_prompt.change_bg_image(WOOD) self.choose_prompt.add_frame(FRAME) self.wait_prompt = RectLabel( 500, 400, 300, 50, Color.GREY, 0, Text(pygame.font.SysFont('Agency FB', 30), "Wait for your turn!", Color.GREEN2)) self.wait_prompt.change_bg_image(WOOD) self.wait_prompt.add_frame(FRAME) self.game_board_sprite.top_ui.add(self.choose_prompt) self.game_board_sprite.top_ui.add(self.wait_prompt) ChooseStartingPositionController._instance = self @classmethod def instance(cls): return cls._instance def _apply_hover(self): for i in range(len(self.game_board_sprite.grid.grid)): for j in range(len(self.game_board_sprite.grid.grid[i])): tile_model = GameStateModel.instance().game_board.get_tile_at( j, i) tile_sprite = self.game_board_sprite.grid.grid[i][j] success = self.run_checks(tile_model) if success and not tile_sprite.highlight_color: tile_sprite.highlight_color = Color.GREEN elif not success: tile_sprite.highlight_color = None def send_event_and_close_menu(self, tile_model: TileModel, menu_to_close: Interactable): pass def run_checks(self, tile_model: TileModel) -> bool: if GameStateModel.instance().state != GameStateEnum.PLACING_PLAYERS: return False if self.current_player != GameStateModel.instance().players_turn: return False # Check if any Players are in this tile if GameStateModel.instance().get_players_on_tile( tile_model.row, tile_model.column): return False if tile_model.space_kind == SpaceKindEnum.INDOOR: return False return True def process_input(self, tile_sprite: TileSprite): if not GameStateModel.instance().players_turn.has_pos: tile_model = GameStateModel.instance().game_board.get_tile_at( tile_sprite.row, tile_sprite.column) if not self.run_checks(tile_model): return event = ChooseStartingPositionEvent(tile_model.row, tile_model.column) self.choose_prompt.kill() if Networking.get_instance().is_host: Networking.get_instance().send_to_all_client(event) else: Networking.get_instance().send_to_server(event) def update(self, event_queue: EventQueue): if self.current_player == GameStateModel.instance().players_turn: self.wait_prompt.kill() if GameStateModel.instance().state == GameStateEnum.PLACING_PLAYERS: self._apply_hover()
class VehiclePlacementController(Controller): """Class for controlling inputs during the placing vehicles phase. Displays prompts.""" _instance = None def __init__(self, current_player: PlayerModel): super().__init__(current_player) if VehiclePlacementController._instance: self._current_player = current_player # raise Exception(f"{VehiclePlacementController.__name__} is a singleton!") self.choose_engine_prompt = RectLabel( 500, 30, 350, 75, Color.GREY, 0, Text(pygame.font.SysFont('Agency FB', 30), "Choose Engine Position", Color.GREEN2)) self.choose_engine_prompt.change_bg_image(WOOD) self.choose_engine_prompt.add_frame(FRAME) self.choose_ambulance_prompt = RectLabel( 500, 30, 350, 75, Color.GREY, 0, Text(pygame.font.SysFont('Agency FB', 30), "Choose Ambulance Position", Color.GREEN2)) self.choose_ambulance_prompt.change_bg_image(WOOD) self.choose_ambulance_prompt.add_frame(FRAME) self.wait_prompt = RectLabel( 500, 580, 350, 75, Color.GREY, 0, Text(pygame.font.SysFont('Agency FB', 30), "Host Is Placing Vehicles...", Color.GREEN2)) self.wait_prompt.change_bg_image(WOOD) self.wait_prompt.add_frame(FRAME) self.game_board_sprite = GameBoard.instance() self.ambulance_placed = False self.engine_placed = False VehiclePlacementController._instance = self @classmethod def instance(cls): return cls._instance def run_checks(self, tile_model: TileModel) -> bool: game: GameStateModel = GameStateModel.instance() if game.state != GameStateEnum.PLACING_VEHICLES: return False if not Networking.get_instance().is_host: return False if tile_model.space_kind == SpaceKindEnum.INDOOR: return False return True def send_event_and_close_menu(self, tile_model: TileModel, menu_to_close: Interactable): game_board: GameBoardModel = GameStateModel.instance().game_board spot_list = game_board.engine_spots if self.ambulance_placed else game_board.ambulance_spots parking_spot = [spot for spot in spot_list if tile_model in spot] if not parking_spot: return parking_spot = parking_spot[0] vehicle = game_board.engine if self.ambulance_placed else game_board.ambulance event = VehiclePlacedEvent(vehicle, parking_spot) if Networking.get_instance().is_host: Networking.get_instance().send_to_all_client(event) else: Networking.get_instance().client.send(event) if not self.ambulance_placed: self.ambulance_placed = True elif not self.engine_placed: self.engine_placed = True def _check_highlight(self, tile_model: TileModel, vehicle_type: str): if vehicle_type == "AMBULANCE": return tile_model.space_kind == SpaceKindEnum.AMBULANCE_PARKING elif vehicle_type == "ENGINE": return tile_model.space_kind == SpaceKindEnum.ENGINE_PARKING def _apply_highlight(self, vehicle_type: str): game_state = GameStateModel.instance() for i in range(len(self.game_board_sprite.grid.grid)): for j in range(len(self.game_board_sprite.grid.grid[i])): tile_model = game_state.game_board.get_tile_at(j, i) tile_sprite = self.game_board_sprite.grid.grid[i][j] success = self._check_highlight(tile_model, vehicle_type) if success and not tile_sprite.highlight_color: tile_sprite.highlight_color = Color.GREEN elif not success: tile_sprite.highlight_color = None def process_input(self, tile_sprite: TileSprite): game_state: GameStateModel = GameStateModel.instance() tile_model = game_state.game_board.get_tile_at(tile_sprite.row, tile_sprite.column) if not self.run_checks(tile_model): return self.send_event_and_close_menu(tile_model, None) def enable_prompts(self): if Networking.get_instance().is_host: self.game_board_sprite.top_ui.add(self.choose_engine_prompt) self.game_board_sprite.top_ui.add(self.choose_ambulance_prompt) else: self.game_board_sprite.top_ui.add(self.wait_prompt) def update(self, event_queue: EventQueue): game: GameStateModel = GameStateModel.instance() if not game.state == GameStateEnum.PLACING_VEHICLES: return if self.engine_placed: self.choose_engine_prompt.kill() if self.ambulance_placed: self.choose_ambulance_prompt.kill() ambulance: VehicleModel = GameStateModel.instance( ).game_board.ambulance engine: VehicleModel = GameStateModel.instance().game_board.engine if ambulance.orientation != VehicleOrientationEnum.UNSET and engine.orientation != VehicleOrientationEnum.UNSET: self.wait_prompt.kill() self.choose_ambulance_prompt.kill() self.choose_engine_prompt.kill() vehicle_type = "ENGINE" if self.ambulance_placed else "AMBULANCE" self._apply_highlight(vehicle_type)
class FireDeckGunController(Controller): _instance = None def __init__(self, current_player: PlayerModel): super().__init__(current_player) if FireDeckGunController._instance: self._current_player = current_player # raise Exception("Victim Controller is a singleton") self._game: GameStateModel = GameStateModel.instance() self.player = current_player self.board: GameBoardModel = self._game.game_board self.engine = self.board.engine FireDeckGunController._instance = self self.label = None self.input1 = None self.input2 = None self.input3 = None self.input4 = None self.max_input = 0 @classmethod def instance(cls): return cls._instance def process_input(self, tile_sprite: TileSprite): assoc_model = self.board.get_tile_at(tile_sprite.row, tile_sprite.column) button = None if self.run_checks(assoc_model): button = tile_sprite.fire_deck_gun_button if button: tile_sprite.fire_deck_gun_button.enable() if self._current_player.role == PlayerRoleEnum.DRIVER: button.on_click(self.driver_menu_popup, assoc_model) else: button.on_click(self.send_event_and_close_menu, assoc_model, button) else: tile_sprite.fire_deck_gun_button.disable() def run_checks(self, tile_model: TileModel) -> bool: """ Determines whether or not it is possible to perform this event. :return: True if it possible to perform this event. False otherwise. """ # Doge cannot fire the deck gun if self.player.role == PlayerRoleEnum.DOGE: return False if not self.player == GameStateModel.instance().players_turn: return False ap_deduct = 2 if self.player.role == PlayerRoleEnum.DRIVER else 4 if not TurnEvent.has_required_AP(self.player.ap, ap_deduct): return False # If the player is not located in the # same space as the engine, they cannot # fire the deck gun. engine_orient = self.engine.orientation if engine_orient == VehicleOrientationEnum.HORIZONTAL: on_first_spot = self.player.row == self.engine.row and self.player.column == self.engine.column on_second_spot = self.player.row == self.engine.row and self.player.column == self.engine.column + 1 if not on_first_spot and not on_second_spot: return False elif engine_orient == VehicleOrientationEnum.VERTICAL: on_first_spot = self.player.row == self.engine.row and self.player.column == self.engine.column on_second_spot = self.player.row == self.engine.row + 1 and self.player.column == self.engine.column if not on_first_spot and not on_second_spot: return False engine_quadrant = self._determine_quadrant(self.engine.row, self.engine.column) tile_input_quadrant = self._determine_quadrant(tile_model.row, tile_model.column) # If there are players present in the # quadrant, the deck gun cannot be fired. # tile input gotta be on quadrant adjacent to engine if self._are_players_in_quadrant( engine_quadrant) or tile_input_quadrant != engine_quadrant: return False return True @staticmethod def _determine_quadrant(row, column) -> QuadrantEnum: """ Determines the quadrant to which the row and column belong to. :param row: :param column: :return: Quadrant in which the row and column are located. """ if row < 4 and column < 5: return QuadrantEnum.TOP_LEFT elif row < 4 and column >= 5: return QuadrantEnum.TOP_RIGHT elif row >= 4 and column < 5: return QuadrantEnum.BOTTOM_LEFT else: return QuadrantEnum.BOTTOM_RIGHT @staticmethod def _determine_quadrant_player(row, column) -> QuadrantEnum: """ Determines the quadrant to which the row and column belong to. :param row: :param column: :return: Quadrant in which the row and column are located. """ if 4 > row > 0 and 5 > column > 0: return QuadrantEnum.TOP_LEFT elif 4 > row > 0 and 5 <= column < 9: return QuadrantEnum.TOP_RIGHT elif 4 <= row < 7 and 5 > column > 0: return QuadrantEnum.BOTTOM_LEFT elif 4 <= row < 7 and 5 <= column < 9: return QuadrantEnum.BOTTOM_RIGHT def _are_players_in_quadrant(self, quadrant: QuadrantEnum) -> bool: """ Determines whether there are any players in the given quadrant. :param quadrant: Quadrant that we are interested in. :return: True if there are players in the quadrant, False otherwise. """ for player in self._game.players: logger.info( f"Player assoc_quadrant: {self._determine_quadrant_player(player.row, player.column)}" ) logger.info( f"Player row: {player.row}, Player column: {player.column}") logger.info(f"Quadrant to compare: {quadrant}") if quadrant == self._determine_quadrant_player( player.row, player.column): return True return False def send_event_and_close_menu(self, tile_model: TileModel, menu_to_close: Interactable, row: int = -1, column: int = -1): event = FireDeckGunEvent(row=row, column=column) if not self.run_checks(tile_model): menu_to_close.disable() return if Networking.get_instance().is_host: Networking.get_instance().send_to_all_client(event) else: Networking.get_instance().client.send(event) if menu_to_close: menu_to_close.disable() def _set_target_tile(self, row: int = -1, column: int = -1): """ Set the tile which will be the target for the firing of the deck gun. :return: """ engine_quadrant = self._determine_quadrant(self.engine.row, self.engine.column) if row == -1: target_row = GameStateModel.instance().roll_red_dice() else: target_row = row if column == -1: target_column = GameStateModel.instance().roll_black_dice() else: target_column = column target_quadrant = self._determine_quadrant(target_row, target_column) # If the roll gives a tile in the engine's # quadrant, that will become the target tile. if target_quadrant == engine_quadrant: target_tile = GameStateModel.instance().game_board.get_tile_at( target_row, target_column) return target_tile else: # Flipping the red dice involves # subtracting the roll value from 7. flipped_row = 7 - target_row # Try out the following combinations # and see if any of them are in the # engine's quadrant: # 1. flipping the row, same column # 2. same row, flipping the column # 3. flipping the row, flipping the column new_target_quadrant = self._determine_quadrant( flipped_row, target_column) if new_target_quadrant == engine_quadrant: target_tile = GameStateModel.instance().game_board.get_tile_at( flipped_row, target_column) return target_tile flipped_column = GameStateModel.instance( ).determine_black_dice_opposite_face(target_column) new_target_quadrant = self._determine_quadrant( target_row, flipped_column) if new_target_quadrant == engine_quadrant: target_tile = GameStateModel.instance().game_board.get_tile_at( target_row, flipped_column) return target_tile new_target_quadrant = self._determine_quadrant( flipped_row, flipped_column) if new_target_quadrant == engine_quadrant: target_tile = GameStateModel.instance().game_board.get_tile_at( flipped_row, flipped_column) return target_tile # $$$$$$$$$$$$$$$$$ # Shouldn't be able to reach this point!! # One of the cases above should have worked. # $$$$$$$$$$$$$$$$$ logger.error("Possible issue with dice flipping! Stop!!") raise FlippingDiceProblemException() def driver_menu_popup(self, tile_model: TileModel): decision: int = 0 targetTile: TileModel = self._set_target_tile() red_dice = targetTile.row black_dice = targetTile.column boardSprite: GameBoard = GameBoard.instance().top_ui self.label = RectLabel( 200, 400, 600, 200, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), f"Roll: {red_dice}, {black_dice}", Color.GREEN2)) self.label.change_bg_image(WOOD) self.label.add_frame(FRAME) self.input1 = RectButton( 200, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Accept Roll", Color.GREEN2)) self.input1.change_bg_image(WOOD) self.input1.add_frame(FRAME) self.input2 = RectButton( 350, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Re-Roll Black Dice", Color.GREEN2)) self.input2.change_bg_image(WOOD) self.input2.add_frame(FRAME) self.input3 = RectButton( 500, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Re-Roll Red Dice", Color.GREEN2)) self.input3.change_bg_image(WOOD) self.input3.add_frame(FRAME) self.input4 = RectButton( 650, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Re-Roll Both Die", Color.GREEN2)) self.input4.change_bg_image(WOOD) self.input4.add_frame(FRAME) self.input1.on_click(self.input1_process, tile_model, red_dice, black_dice) self.input2.on_click(self.input2_process, tile_model, red_dice, black_dice) self.input3.on_click(self.input3_process, tile_model, red_dice, black_dice) self.input4.on_click(self.input4_process, tile_model, red_dice, black_dice) boardSprite.add(self.label) boardSprite.add(self.input1) boardSprite.add(self.input2) boardSprite.add(self.input3) boardSprite.add(self.input4) def input1_process(self, tile: TileModel, red_dice: int, black_dice: int): self.kill_all() self.max_input = 0 self.send_event_and_close_menu(tile, self.input1, red_dice, black_dice) def input2_process(self, tile: TileModel, red_dice: int, black_dice: int): self.max_input += 1 board_sprite: GameBoard = GameBoard.instance().top_ui self.kill_all() new_tile: TileModel = self._set_target_tile(red_dice) if self.max_input == 2: self.max_input = 0 self.send_event_and_close_menu(tile, self.input1, new_tile.row, new_tile.column) else: black_dice = new_tile.column self.label = RectLabel( 200, 400, 600, 200, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), f"Roll: {red_dice}, {black_dice}", Color.GREEN2)) self.label.change_bg_image(WOOD) self.label.add_frame(FRAME) self.input1 = RectButton( 200, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Accept Roll", Color.GREEN2)) self.input1.change_bg_image(WOOD) self.input1.add_frame(FRAME) self.input3 = RectButton( 350, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Re-Roll Red Dice", Color.GREEN2)) self.input3.change_bg_image(WOOD) self.input3.add_frame(FRAME) self.input1.on_click(self.input1_process, tile, red_dice, new_tile.column) self.input3.on_click(self.input3_process, tile, red_dice, new_tile.column) board_sprite.add(self.label) board_sprite.add(self.input1) board_sprite.add(self.input3) def input3_process(self, tile: TileModel, red_dice: int, black_dice: int): self.max_input += 1 board_sprite: GameBoard = GameBoard.instance().top_ui self.kill_all() new_tile: TileModel = self._set_target_tile(-1, black_dice) if self.max_input == 2: self.max_input = 0 self.send_event_and_close_menu(tile, self.input1, new_tile.row, new_tile.column) else: red_dice = new_tile.row self.label = RectLabel( 200, 400, 600, 200, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), f"Roll: {red_dice}, {black_dice}", Color.GREEN2)) self.label.change_bg_image(WOOD) self.label.add_frame(FRAME) self.input1 = RectButton( 200, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Accept Roll", Color.GREEN2)) self.input1.change_bg_image(WOOD) self.input1.add_frame(FRAME) self.input2 = RectButton( 350, 350, 150, 50, Color.BLACK, 0, Text(pygame.font.SysFont('Agency FB', 25), "Re-Roll Black Dice", Color.GREEN2)) self.input2.change_bg_image(WOOD) self.input2.add_frame(FRAME) self.input1.on_click(self.input1_process, tile, new_tile.row, black_dice) self.input2.on_click(self.input2_process, tile, new_tile.row, black_dice) board_sprite.add(self.label) board_sprite.add(self.input1) board_sprite.add(self.input2) def input4_process(self, tile: TileModel, red_dice: int, black_dice: int): self.kill_all() new_tile: TileModel = self._set_target_tile() self.send_event_and_close_menu(tile, self.input1, new_tile.row, new_tile.column) def kill_all(self): self.input1.kill() self.input2.kill() self.input3.kill() self.input4.kill() self.label.kill()