async def get_ships(self, pn): self.pn = pn print("Player {} pick your ships!".format(pn)) print( "Use: R C D for input (R: row; C: column; D = A for across, D for down)" ) b = Board() for ship, size in ships: possibles = [ p for p in possible_placements(b, size) if all({(c.x + dx, c.y + dy) not in b.sea for dx in (-1, 0, 1) for dy in (-1, 0, 1) for c in p.cells}) ] place = random.choice(possibles) b.add_ship(place.x, place.y, place.dx, place.dy, place.size) print('Adding {} at ({}, {}) - ({}, {})'.format( ship, place.x, place.y, place.x + place.dx * (place.size - 1), place.y + place.dy * (place.size - 1))) assert await self.game.add_ship(pn, place.x, place.y, place.dx, place.dy, place.size) print() await self.display() print() print("Your final board:") await self.display()
def __init__(self, **kwargs): self.grid_size = kwargs.get('grid_size', 10) self.max_index = kwargs.get('N', 10) self.end_game = False self.left_board = Board(self.max_index, self.grid_size, draw_all_ships=False, offset_x=10, offset_y=30) self.right_board = Board(self.max_index, self.grid_size, draw_all_ships=False, offset_x=30 + self.grid_size * self.max_index, offset_y=30) if VERBOSE: self.number_of_players = 0 else: self.number_of_players = self.welcome_screen() if self.number_of_players == 2: self.left_player = Human(self.left_board, arrow_keymap) self.right_player = Human(self.right_board, wasd_keymap) elif self.number_of_players == 1: self.left_player = Human(self.left_board, arrow_keymap) self.right_player = Enemy(self.right_board) else: self.left_player = Enemy(self.left_board) self.right_player = Enemy(self.right_board) pyxel.init(*SCREEN_SIZE, caption=DEFAULT_CAPTION, fps=FPS, quit_key=pyxel.KEY_Q) pyxel.run(self.update, self.draw)
def test_ship_must_lie_on_board(): b = Board() with pytest.raises(ValueError): b.add_ship(0, 0, -1, 0, 2) with pytest.raises(ValueError): b.add_ship(1, 1, 0, -1, 3) with pytest.raises(ValueError): b.add_ship(9, 9, 1, 0, 2) with pytest.raises(ValueError): b.add_ship(0, 0, 0, 1, 11)
def test_hit(): b = Board() b.add_counter(5, 5) assert b.potshot(5, 5) == Board.HIT assert b.defeated() b.display()
def parse_board(board_repr): visible_grid_repr = "" number_of_ship_fields_to_mark_in_rows = [] repr_string_lines = board_repr.split("\n") for line_number, line in enumerate(repr_string_lines[1:], 1): if re.fullmatch(r"^╚═+╝$", line): number_of_ship_fields_to_mark_in_columns = [ int(x) for x in re.findall(r"\d+", repr_string_lines[line_number + 1]) ] break match = re.fullmatch(r"^║ (.+) ║\((\d+)\)$", line) visible_grid_repr += match.group(1) + "\n" number_of_ship_fields_to_mark_in_rows.append(int(match.group(2))) visible_grid = parse_fieldtypegrid(visible_grid_repr.strip("\n")) grid = FieldTypeGrid( [[FieldType.SEA] * (len(visible_grid[0]) + 2)] + [[FieldType.SEA, *row, FieldType.SEA] for row in visible_grid] + [[FieldType.SEA] * (len(visible_grid[0]) + 2)] ) return Board( grid, { Series.ROW: [0, *number_of_ship_fields_to_mark_in_rows, 0], Series.COLUMN: [0, *number_of_ship_fields_to_mark_in_columns, 0], }, )
def mark_subfleet_of_biggest_remaining_ships(self) -> None: """Determine the size of the largest ship remaining in the Puzzle fleet and mark the entire subfleet of those ships onto the Puzzle board. If the board offers more available "slots" in which all subfleet ships can be marked, then branch the puzzle solving by marking the subfleet in each possible slot combination. If no ships are remaining in the fleet, that means a new puzzle solution has been found. """ if self.fleet.has_ships_remaining(): ship_size = self.fleet.longest_ship_size max_possible_subfleet = self.board.get_possible_ships_of_size( ship_size) if len(max_possible_subfleet) == self.fleet.size_of_subfleet( ship_size): with contextlib.suppress(InvalidShipPlacementException): self.mark_ship_group(max_possible_subfleet) else: for possible_subfleet in itertools.combinations( max_possible_subfleet, self.fleet.size_of_subfleet(ship_size)): puzzle_branch = Puzzle(Board.get_copy_of(self.board), Fleet.get_copy_of(self.fleet)) with contextlib.suppress(InvalidShipPlacementException): puzzle_branch.mark_ship_group(set(possible_subfleet)) else: self.__class__.solutions.append(self.board.repr(False))
async def new_player(self, plr): if len(self.players) < 2: self.players.append(plr) # Keep tabs on this player's id number pn = plr.pn = len(self.players) self.boards[pn] = Board() # This player isn't yet ready. We track this because multiple # players can join in parallel; when each becomes ready, we check # to determine if everyone is good to go. plr.ready = False # Tell the player who they are, and ask them for their ship placements. await plr.get_ships(pn) # Once they're ready, check if everyone else is too. plr.ready = True # Is everyone ready? if len(self.players) > 1 and all(p.ready for p in self.players): # Could just use "await self.game_loop()" here. asyncio.create_task(self.game_loop()) else: await plr.print("Too many players already connected to this game.") await plr.exit(0)
def load_puzzle(cls, path: Optional[pathlib.Path] = None) -> "Puzzle": """Read the puzzle input file and create a Puzzle object from its data. Args: path (Optional[pathlib.Path]): Path to the input file. Defaults to None, in which case the default input file location is used. Returns: battleships.puzzle.Puzzle: Newly created Puzzle object. """ if path: input_data = Puzzle.parse_input_data_from_file(path) else: input_data = Puzzle.parse_input_data_from_file( params.INPUT_FILE_PATH) return Puzzle( Board.parse_board( input_data.grid, input_data.solution_ship_fields_in_rows, input_data.solution_ship_fields_in_cols, ), input_data.fleet, )
def test_board(): b = Board() b.add_counter(0, 0) b.add_counter(9, 9) b.display() assert not b.defeated()
def test___init__(self): board = Board( unittest.mock.sentinel.grid, unittest.mock.sentinel.number_of_ship_fields_to_mark_in_series, ) self.assertTrue(board.grid is unittest.mock.sentinel.grid) self.assertTrue( board.number_of_ship_fields_to_mark_in_series is unittest.mock.sentinel.number_of_ship_fields_to_mark_in_series )
def test___eq__(self): other_board = parse_board(self.sample_board_repr) other_board_orig = Board.get_copy_of(other_board) self.assertTrue(self.sample_board.__eq__(other_board)) self.assertEqual(other_board, other_board_orig) other_board.grid[1][1] = FieldType.UNKNOWN other_board_orig = Board.get_copy_of(other_board) self.assertFalse(self.sample_board.__eq__(other_board)) self.assertEqual(other_board, other_board_orig) other_board.grid[1][1] = FieldType.SEA other_board_orig = Board.get_copy_of(other_board) self.assertTrue(self.sample_board.__eq__(other_board)) self.assertEqual(other_board, other_board_orig) other_board.number_of_ship_fields_to_mark_in_series[Series.ROW][6] = 5 other_board_orig = Board.get_copy_of(other_board) self.assertFalse(self.sample_board.__eq__(other_board)) self.assertEqual(other_board, other_board_orig) self.assertFalse(self.sample_board.__eq__(self.sample_board.grid))
def test_ship_must_not_be_adjacent_to_another(): b = Board() b.add_ship(5, 5, 0, 0, 1) for dx in -1, 0, 1: for dy in -1, 0, 1: with pytest.raises(ValueError): b.add_ship(5 + dx, 5 + dy, 0, 0, 1)
def test_parse_board(self): input_grid = parse_fieldtypegrid( " . . . . . . . . . . \n" " x . . . O . . . . . \n" " . . O . O . . x . . \n" " . . O . O . x . . O \n" " x . O . . . x . . . \n" " . . . . O . x . . . \n" " . x . . O . x . . . \n" " x . . x . . . . O O \n" " . . . . . . . . . . \n" " O . O O O O . O O . " ) input_number_of_ship_fields_to_mark_in_rows = [0, 2, 2, 6, 1, 5, 1, 2, 5, 7] input_number_of_ship_fields_to_mark_in_cols = [2, 1, 5, 4, 9, 5, 5, 1, 2, 3] input_grid_orig = input_grid input_number_of_ship_fields_to_mark_in_rows_orig = ( input_number_of_ship_fields_to_mark_in_rows ) input_number_of_ship_fields_to_mark_in_cols_orig = ( input_number_of_ship_fields_to_mark_in_cols ) parsed_board = Board.parse_board( input_grid, input_number_of_ship_fields_to_mark_in_rows, input_number_of_ship_fields_to_mark_in_cols, ) self.assertEqual(self.sample_board, parsed_board) self.assertFalse(input_grid is parsed_board.grid) self.assertEqual(input_grid, input_grid_orig) self.assertFalse( input_number_of_ship_fields_to_mark_in_rows is parsed_board.number_of_ship_fields_to_mark_in_series[Series.ROW] ) self.assertEqual( input_number_of_ship_fields_to_mark_in_rows, input_number_of_ship_fields_to_mark_in_rows_orig, ) self.assertFalse( input_number_of_ship_fields_to_mark_in_cols is parsed_board.number_of_ship_fields_to_mark_in_series[Series.COLUMN] ) self.assertEqual( input_number_of_ship_fields_to_mark_in_cols, input_number_of_ship_fields_to_mark_in_cols_orig, )
class Game: def __init__(self, **kwargs): self.grid_size = kwargs.get('grid_size', 10) self.max_index = kwargs.get('N', 10) self.end_game = False self.left_board = Board(self.max_index, self.grid_size, draw_all_ships=False, offset_x=10, offset_y=30) self.right_board = Board(self.max_index, self.grid_size, draw_all_ships=False, offset_x=30 + self.grid_size * self.max_index, offset_y=30) if VERBOSE: self.number_of_players = 0 else: self.number_of_players = self.welcome_screen() if self.number_of_players == 2: self.left_player = Human(self.left_board, arrow_keymap) self.right_player = Human(self.right_board, wasd_keymap) elif self.number_of_players == 1: self.left_player = Human(self.left_board, arrow_keymap) self.right_player = Enemy(self.right_board) else: self.left_player = Enemy(self.left_board) self.right_player = Enemy(self.right_board) pyxel.init(*SCREEN_SIZE, caption=DEFAULT_CAPTION, fps=FPS, quit_key=pyxel.KEY_Q) pyxel.run(self.update, self.draw) @staticmethod def welcome_screen(linewidth=80, fillchar='='): print("Battleships!".center(linewidth, fillchar)) print("\n\n") pre = "! Human Error Detected !\n" while True: try: humans = int(input("How many HUMAN players (0, 1, 2)?")) except ValueError: print(pre + "Integer number of humans only") continue if 0 <= humans <= 2: return humans elif humans < 0: print(pre + "Real quantities of humans only") elif humans > 2: print(pre + "Excess of humans, cannot comply") def game_over(self): winner = 'left' if not self.left_board.ships else 'right' sx, sy = SCREEN_SIZE sx //= 3 sy = self.grid_size * self.max_index + 32 blurb = [ 'Game Over!', f'Player {winner} wins!', f'Player One Score : {self.left_board.score():03}', f'Player Two Score : {self.right_board.score():03}' ] blurb = "\n".join(blurb) pyxel.text(sx, sy, blurb, TEXT_COLOUR) def update(self): if self.end_game: return self.left_player.update() self.right_player.update() if not self.left_board.ships or not self.right_board.ships: self.end_game = True def draw(self): pyxel.cls(BACKGROUND_COLOUR) if self.end_game: self.game_over() self.left_board.draw() self.right_board.draw() self.left_player.draw() self.right_player.draw()
def test_near_miss(): b = Board() b.add_counter(3, 3) assert Board.NEAR == b.potshot(2, 2) assert not b.defeated()
def test_get_copy_of(self): sample_board_orig = copy.deepcopy(self.sample_board) actual_copy = Board.get_copy_of(self.sample_board) self.assertFalse(actual_copy is self.sample_board) self.assertEqual(actual_copy, self.sample_board) self.assertEqual(self.sample_board, sample_board_orig)
def test_miss(): b = Board() b.add_counter(3, 3) assert Board.MISS == b.potshot(1, 2) assert not b.defeated()
def find_puzzles( # pylint: disable=W9015 ship_group: Set[Ship], covered_positions: Set[Position], positions_to_cover: List[Position], available_coverings: Dict[Ship, Set[Position]], puzzle: Puzzle, ) -> None: """Build a list of possible Puzzle objects created by branching a given Puzzle object and covering all given positions with a single ship from a given ship set. Depth-First Search (DFS) algorithm is used. Args: ship_group (Set[battleships.ship.Ship]): Group of previously selected ships from available_coverings. covered_positions (Set[battleships.grid.Position]): Set of positions covered by ships in ship_group. positions_to_cover (List[battleships.grid.Position]): Positions remaining to be covered by ships. available_coverings(Dict[ battleships.ship.Ship, Set[battleships.grid.Position ]]): Mapping of remaining ships and sets of positions that each ship covers. Does not contain any of the ships in ship_group. The sets of ships do not contain any of the positions in covered_positions. puzzle (battleships.puzzle.Puzzle): Current Puzzle object. """ if not positions_to_cover: puzzles.append(puzzle) return remaining_position = positions_to_cover[0] ship_candidates = [ ship for ship, positions in available_coverings.items() if remaining_position in positions ] if not ship_candidates: return for ship_candidate in ship_candidates: if puzzle.board.can_fit_ship( ship_candidate ) and not puzzle.ship_group_exceeds_fleet([ship_candidate]): ship_candidate_positions = available_coverings[ ship_candidate] new_positions_to_cover = [ position for position in positions_to_cover if position not in ship_candidate_positions ] new_coverings = { ship: {*ship_positions} for ship, ship_positions in available_coverings.items() if ship != ship_candidate } new_puzzle = Puzzle(Board.get_copy_of(puzzle.board), Fleet.get_copy_of(puzzle.fleet)) new_puzzle.board.mark_ship_and_surrounding_sea( ship_candidate) new_puzzle.board.mark_sea_in_series_with_no_rem_ship_fields( ) new_puzzle.fleet.remove_ship_of_size(ship_candidate.size) find_puzzles( ship_group.union({ship_candidate}), covered_positions.union(ship_candidate_positions), new_positions_to_cover, new_coverings, new_puzzle, )