Esempio n. 1
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))
Esempio n. 2
0
 def test_get_copy_of(self):
     for fleet in self.sample_fleets:
         with self.subTest():
             fleet_orig = copy.deepcopy(fleet)
             fleet_copy = Fleet.get_copy_of(fleet)
             self.assertFalse(fleet_copy is fleet)
             self.assertEqual(fleet_copy, fleet)
             self.assertEqual(fleet_copy, fleet_orig)
Esempio n. 3
0
    def test_try_to_cover_all_ship_fields_to_be(self,
                                                mocked_decide_how_to_proceed):
        board_repr = ("╔═════════════════════╗\n"
                      "║  x   x   .   x   x  ║(1)\n"
                      "║  x   x   x   x   .  ║(1)\n"
                      "║  x   x   x   x   x  ║(2)\n"
                      "║  x   .   x   x   x  ║(2)\n"
                      "║  x   x   x   x   x  ║(3)\n"
                      "╚═════════════════════╝\n"
                      "  (2) (1) (4) (1) (4) ")
        fleet = Fleet({4: 1, 3: 1, 2: 1})

        puzzle = Puzzle(parse_board(board_repr), Fleet.get_copy_of(fleet))
        positions = {Position(1, 5)}
        positions_orig = set(positions)
        puzzle.try_to_cover_all_ship_fields_to_be(positions)
        self.assertEqual(positions, positions_orig)
        mocked_decide_how_to_proceed.assert_not_called()

        puzzle = Puzzle(parse_board(board_repr), Fleet.get_copy_of(fleet))
        positions = {
            Position(2, 3),
            Position(4, 3),
            Position(3, 5),
            Position(5, 5)
        }
        positions_orig = set(positions)
        puzzle.try_to_cover_all_ship_fields_to_be(positions)
        self.assertEqual(
            puzzle,
            Puzzle(
                parse_board("╔═════════════════════╗\n"
                            "║  x   .   .   .   x  ║(1)\n"
                            "║  .   .   O   .   .  ║(0)\n"
                            "║  .   .   O   .   O  ║(0)\n"
                            "║  .   .   O   .   O  ║(0)\n"
                            "║  x   .   O   .   O  ║(1)\n"
                            "╚═════════════════════╝\n"
                            "  (2) (1) (0) (1) (1) "),
                Fleet({2: 1}),
            ),
        )
        self.assertEqual(positions, positions_orig)
        mocked_decide_how_to_proceed.assert_called_once()

        mocked_decide_how_to_proceed.reset_mock()
        puzzle = Puzzle(parse_board(board_repr), Fleet.get_copy_of(fleet))
        positions = {Position(5, 3), Position(5, 5)}
        positions_orig = set({Position(5, 3), Position(5, 5)})
        puzzle.try_to_cover_all_ship_fields_to_be(positions)
        self.assertEqual(mocked_decide_how_to_proceed.call_count, 6)
        mocked_decide_how_to_proceed.assert_has_calls(
            [
                unittest.mock.call(
                    Puzzle(
                        parse_board("╔═════════════════════╗\n"
                                    "║  x   .   .   .   x  ║(1)\n"
                                    "║  .   .   O   .   .  ║(0)\n"
                                    "║  .   .   O   .   O  ║(0)\n"
                                    "║  .   .   O   .   O  ║(0)\n"
                                    "║  x   .   O   .   O  ║(1)\n"
                                    "╚═════════════════════╝\n"
                                    "  (2) (1) (0) (1) (1) "),
                        Fleet({2: 1}),
                    )),
                unittest.mock.call(
                    Puzzle(
                        parse_board("╔═════════════════════╗\n"
                                    "║  x   x   .   x   x  ║(1)\n"
                                    "║  x   x   x   .   .  ║(1)\n"
                                    "║  x   .   .   .   O  ║(1)\n"
                                    "║  .   .   O   .   O  ║(0)\n"
                                    "║  x   .   O   .   O  ║(1)\n"
                                    "╚═════════════════════╝\n"
                                    "  (2) (1) (2) (1) (1) "),
                        Fleet({4: 1}),
                    )),
                unittest.mock.call(
                    Puzzle(
                        parse_board("╔═════════════════════╗\n"
                                    "║  x   .   .   x   x  ║(1)\n"
                                    "║  x   .   x   .   .  ║(1)\n"
                                    "║  x   .   x   .   O  ║(1)\n"
                                    "║  .   .   .   .   O  ║(1)\n"
                                    "║  .   O   O   .   O  ║(0)\n"
                                    "╚═════════════════════╝\n"
                                    "  (2) (0) (3) (1) (1) "),
                        Fleet({4: 1}),
                    )),
                unittest.mock.call(
                    Puzzle(
                        parse_board("╔═════════════════════╗\n"
                                    "║  x   .   .   .   x  ║(1)\n"
                                    "║  .   .   O   .   .  ║(0)\n"
                                    "║  x   .   O   .   .  ║(1)\n"
                                    "║  .   .   O   .   O  ║(0)\n"
                                    "║  x   .   O   .   O  ║(1)\n"
                                    "╚═════════════════════╝\n"
                                    "  (2) (1) (0) (1) (2) "),
                        Fleet({3: 1}),
                    )),
                unittest.mock.call(
                    Puzzle(
                        parse_board("╔═════════════════════╗\n"
                                    "║  x   x   .   x   x  ║(1)\n"
                                    "║  x   .   .   .   .  ║(1)\n"
                                    "║  x   .   O   .   .  ║(1)\n"
                                    "║  .   .   O   .   O  ║(0)\n"
                                    "║  x   .   O   .   O  ║(1)\n"
                                    "╚═════════════════════╝\n"
                                    "  (2) (1) (1) (1) (2) "),
                        Fleet({4: 1}),
                    )),
                unittest.mock.call(
                    Puzzle(
                        parse_board("╔═════════════════════╗\n"
                                    "║  x   x   .   .   x  ║(1)\n"
                                    "║  x   x   x   .   .  ║(1)\n"
                                    "║  x   x   x   .   x  ║(2)\n"
                                    "║  x   .   .   .   .  ║(2)\n"
                                    "║  .   .   O   O   O  ║(0)\n"
                                    "╚═════════════════════╝\n"
                                    "  (2) (1) (3) (0) (3) "),
                        Fleet({
                            4: 1,
                            2: 1
                        }),
                    )),
            ],
            any_order=True,
        )
        self.assertEqual(positions, positions_orig)
Esempio n. 4
0
    def test_mark_ship_group(self, mocked_decide_how_to_proceed):
        board_repr = ("╔═════════════════════╗\n"
                      "║  x   x   x   x   x  ║(1)\n"
                      "║  x   x   x   x   .  ║(1)\n"
                      "║  x   x   x   x   x  ║(2)\n"
                      "║  x   .   x   x   x  ║(2)\n"
                      "║  x   x   x   x   x  ║(3)\n"
                      "╚═════════════════════╝\n"
                      "  (2) (1) (4) (1) (4) ")
        fleet = Fleet({4: 1, 3: 2, 1: 2})

        puzzle = Puzzle(parse_board(board_repr), Fleet.get_copy_of(fleet))
        ship_group = {
            Ship(Position(1, 3), 4, Series.COLUMN),
            Ship(Position(2, 3), 4, Series.COLUMN),
        }
        with self.assertRaises(InvalidShipPlacementException):
            puzzle.mark_ship_group(ship_group)

        # test when board is overmarked
        puzzle = Puzzle(parse_board(board_repr), Fleet.get_copy_of(fleet))
        ship_group = {
            Ship(Position(5, 2), 1, Series.ROW),
            Ship(Position(5, 4), 1, Series.ROW),
        }
        ship_group_orig = set(ship_group)
        puzzle.mark_ship_group(ship_group)
        self.assertEqual(
            puzzle.board,
            parse_board("╔═════════════════════╗\n"
                        "║  x   .   x   .   x  ║(1)\n"
                        "║  x   .   x   .   .  ║(1)\n"
                        "║  x   .   x   .   x  ║(2)\n"
                        "║  .   .   .   .   .  ║(2)\n"
                        "║  .   O   .   O   .  ║(1)\n"
                        "╚═════════════════════╝\n"
                        "  (2) (0) (4) (0) (4) "),
        )
        self.assertEqual(ship_group, ship_group_orig)
        mocked_decide_how_to_proceed.assert_not_called()

        mocked_decide_how_to_proceed.reset_mock()
        puzzle = Puzzle(parse_board(board_repr), Fleet.get_copy_of(fleet))
        fleet = Fleet({4: 1, 3: 2, 1: 2})
        ship_group = {
            Ship(Position(3, 3), 3, Series.COLUMN),
            Ship(Position(3, 5), 3, Series.COLUMN),
        }
        ship_group_orig = set(ship_group)
        puzzle.mark_ship_group(ship_group)
        self.assertEqual(
            puzzle,
            Puzzle(
                parse_board("╔═════════════════════╗\n"
                            "║  x   x   x   x   x  ║(1)\n"
                            "║  x   .   .   .   .  ║(1)\n"
                            "║  .   .   O   .   O  ║(0)\n"
                            "║  .   .   O   .   O  ║(0)\n"
                            "║  x   .   O   .   O  ║(1)\n"
                            "╚═════════════════════╝\n"
                            "  (2) (1) (1) (1) (1) "),
                Fleet({
                    4: 1,
                    1: 2
                }),
            ),
        )
        self.assertEqual(ship_group, ship_group_orig)
        mocked_decide_how_to_proceed.assert_called_once()
Esempio n. 5
0
        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,
                    )