def test_solve(self): fleet = Fleet({3: 1, 2: 2, 1: 2}) puzzle = Puzzle( parse_board("╔═════════════════════╗\n" "║ x x x x . ║(3)\n" "║ x x x x x ║(2)\n" "║ x x O x x ║(2)\n" "║ x . x x x ║(1)\n" "║ x x x x x ║(2)\n" "╚═════════════════════╝\n" " (4) (0) (2) (1) (2) "), Fleet(fleet), ) with unittest.mock.patch.object( battleships.puzzle.Puzzle, "decide_how_to_proceed") as mock_decide_how_to_proceed: puzzle.solve() self.assertEqual( puzzle.board, parse_board("╔═════════════════════╗\n" "║ x . x x . ║(3)\n" "║ x . x . x ║(2)\n" "║ x . x x x ║(3)\n" "║ x . x . x ║(1)\n" "║ x . x x x ║(2)\n" "╚═════════════════════╝\n" " (4) (0) (3) (1) (2) "), ) self.assertEqual(puzzle.fleet, fleet) mock_decide_how_to_proceed.assert_called_once_with({Position(3, 3)})
def test_load_puzzle(self): actual_puzzle = Puzzle.load_puzzle(params.INPUT_FILE_PATH) expected_puzzle = Puzzle( parse_board("╔═════════════════════════════════════════╗\n" "║ x x x x x x x x x x ║(0)\n" "║ x x x x x x x x x x ║(1)\n" "║ x x x x x x x x x x ║(3)\n" "║ x x x x x x x x x x ║(3)\n" "║ x x O x x x x x x x ║(0)\n" "║ x x x x x x x x x x ║(1)\n" "║ x x x x x x x x x x ║(2)\n" "║ x x . x . x x . x x ║(2)\n" "║ x x x x x x x x x x ║(0)\n" "║ x x x x O x x x x x ║(6)\n" "╚═════════════════════════════════════════╝\n" " (1) (1) (3) (1) (5) (1) (0) (2) (2) (2) "), Fleet({ 4: 1, 3: 2, 2: 3, 1: 4 }), ) self.assertEqual(actual_puzzle.board, expected_puzzle.board) self.assertEqual(actual_puzzle.fleet, expected_puzzle.fleet) actual_puzzle = Puzzle.load_puzzle() expected_puzzle = Puzzle( parse_board("╔═════════════════════════════════════════╗\n" "║ x x x x x x x x x x ║(0)\n" "║ x x x x x x x x x x ║(1)\n" "║ x x x x x x x x x x ║(3)\n" "║ x x x x x x x x x x ║(3)\n" "║ x x O x x x x x x x ║(0)\n" "║ x x x x x x x x x x ║(1)\n" "║ x x x x x x x x x x ║(2)\n" "║ x x . x . x x . x x ║(2)\n" "║ x x x x x x x x x x ║(0)\n" "║ x x x x O x x x x x ║(6)\n" "╚═════════════════════════════════════════╝\n" " (1) (1) (3) (1) (5) (1) (0) (2) (2) (2) "), Fleet({ 4: 1, 3: 2, 2: 3, 1: 4 }), ) self.assertEqual(actual_puzzle.board, expected_puzzle.board) self.assertEqual(actual_puzzle.fleet, expected_puzzle.fleet)
def test___eq__(self): puzzle1 = Puzzle( parse_board("╔═════════════════════╗\n" "║ x x x x x ║(0)\n" "║ x x x x . ║(2)\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({ 4: 1, 3: 2, 1: 3 }), ) puzzle2 = Puzzle( parse_board("╔═════════════════════╗\n" "║ x x x x x ║(0)\n" "║ x x x x . ║(2)\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({ 4: 1, 3: 2, 1: 3 }), ) puzzle2_orig = copy.deepcopy(puzzle2) self.assertTrue(puzzle1.__eq__(puzzle2)) self.assertEqual(puzzle2, puzzle2_orig) puzzle2.board.grid[1][1] = FieldType.SEA puzzle2_orig = copy.deepcopy(puzzle2) self.assertFalse(puzzle1.__eq__(puzzle2)) self.assertEqual(puzzle2, puzzle2_orig) puzzle2.board.grid[1][1] = FieldType.UNKNOWN puzzle2.fleet = Fleet({4: 1, 3: 1, 1: 3}) puzzle2_orig = copy.deepcopy(puzzle2) self.assertFalse(puzzle1.__eq__(puzzle2)) self.assertEqual(puzzle2, puzzle2_orig) self.assertFalse(puzzle1.__eq__("foo"))
def test_parse_input_data_from_file(self, mocked_print): with unittest.mock.patch("battleships.puzzle.open", side_effect=FileNotFoundError): with self.assertRaises(FileNotFoundError): Puzzle.parse_input_data_from_file(unittest.mock.sentinel.path) mocked_print.assert_called_once_with( "Invalid input data file path.", file=Puzzle.ofile) mocked_print.reset_mock() actual_input_data = Puzzle.parse_input_data_from_file( params.INPUT_FILE_PATH) expected_grid = parse_fieldtypegrid( " x x x x x x x x x x \n" " x x x x x x x x x x \n" " x x x x x x x x x x \n" " x x x x x x x x x x \n" " x x O x x x x x x x \n" " x x x x x x x x x x \n" " x x x x x x x x x x \n" " x x . x . x x . x x \n" " x x x x x x x x x x \n" " x x x x O x x x x x ") expected_solution_ship_fields_in_rows = [0, 1, 3, 3, 1, 1, 2, 2, 0, 7] expected_solution_ship_fields_in_cols = [1, 1, 4, 1, 6, 1, 0, 2, 2, 2] expected_fleet = Fleet({4: 1, 3: 2, 2: 3, 1: 4}) self.assertEqual(expected_grid, actual_input_data.grid) self.assertEqual( expected_solution_ship_fields_in_rows, actual_input_data.solution_ship_fields_in_rows, ) self.assertEqual( expected_solution_ship_fields_in_cols, actual_input_data.solution_ship_fields_in_cols, ) self.assertEqual(expected_fleet, actual_input_data.fleet)
def test_ship_group_exceeds_fleet(self): puzzle = Puzzle(unittest.mock.Mock(), Fleet({4: 1, 3: 2, 1: 1})) exceeding_ship_groups = ( {Ship(unittest.mock.Mock(), 2, unittest.mock.Mock())}, { Ship(unittest.mock.Mock(), 3, unittest.mock.Mock()), Ship(unittest.mock.Mock(), 3, unittest.mock.Mock()), Ship(unittest.mock.Mock(), 3, unittest.mock.Mock()), }, ) for ship_group in exceeding_ship_groups: with self.subTest(): ship_group_orig = set(ship_group) self.assertTrue(puzzle.ship_group_exceeds_fleet(ship_group)) self.assertEqual(ship_group, ship_group_orig) nonexceeding_ship_groups = ( set(), {Ship(unittest.mock.Mock(), 3, unittest.mock.Mock())}, { Ship(unittest.mock.Mock(), 4, unittest.mock.Mock()), Ship(unittest.mock.Mock(), 3, unittest.mock.Mock()), Ship(unittest.mock.Mock(), 3, unittest.mock.Mock()), Ship(unittest.mock.Mock(), 1, unittest.mock.Mock()), }, ) for ship_group in nonexceeding_ship_groups: with self.subTest(): ship_group_orig = set(ship_group) self.assertFalse(puzzle.ship_group_exceeds_fleet(ship_group)) self.assertEqual(ship_group, ship_group_orig)
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))
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)
def setUp(self): super().setUp() self.sample_fleets = ( Fleet({ 4: 1, 3: 2, 2: 3, 1: 4 }), Fleet({ 5: 7, 2: 10, 1: 1 }), Fleet({2: 1}), Fleet({}), )
def test_remove_ship_of_size(self): expected_results = (Fleet({ 4: 1, 3: 2, 2: 3, 1: 3 }), Fleet({ 5: 7, 2: 10 })) for fleet, expected_result in zip(self.sample_fleets[:2], expected_results): with self.subTest(): fleet.remove_ship_of_size(1) self.assertEqual(expected_result, fleet) for fleet in self.sample_fleets[2:]: with self.assertRaises(InvalidShipSizeException): fleet.remove_ship_of_size(1)
def parse_input_data_from_file(input_file_path: pathlib.Path) -> InputData: """Read puzzle data from the input file. Args: input_file_path (pathlib.Path): Path to file containing input data. Returns: battleships.puzzle.InputData: Parsed input data. Raises: FileNotFoundError: If the input file path is invalid or cannot be found. """ try: file = open(input_file_path, "r", encoding="utf-8") # pylint: disable=R1732 except FileNotFoundError as error: print(params.MESSAGES.INVALID_INPUT_FILE_PATH, file=Puzzle.ofile) raise error fleet = Fleet() subfleets_count = int(next(file).strip()) for _ in range(subfleets_count): ship_size, number_of_ships = (int(x) for x in next(file).strip().split()) fleet.add_ships_of_size(ship_size, number_of_ships) board_size = int(next(file).strip()) solution_ship_fields_in_rows = [ int(x) for x in next(file).strip().split() ] solution_ship_fields_in_cols = [ int(x) for x in next(file).strip().split() ] grid = FieldTypeGrid() for _ in range(board_size): grid.append([FieldType(ch) for ch in next(file).strip()]) file.close() return InputData( grid, solution_ship_fields_in_rows, solution_ship_fields_in_cols, fleet, )
def test_add_ships_of_size(self): expected_results = ( Fleet({ 4: 1, 3: 2, 2: 3, 1: 7 }), Fleet({ 5: 7, 2: 10, 1: 4 }), Fleet({ 2: 1, 1: 3 }), Fleet({1: 3}), ) for fleet, expected_result in zip(self.sample_fleets, expected_results): with self.subTest(): fleet.add_ships_of_size(1, 3) self.assertEqual(expected_result, fleet)
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)
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()
def test_mark_subfleet_of_biggest_remaining_ships(self): board_repr = ("╔═════════════════════╗\n" "║ x x x x x ║(0)\n" "║ x x x x . ║(2)\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) ") fleet1 = Fleet({}) puzzle1 = Puzzle(parse_board(board_repr), Fleet(fleet1)) puzzle1.mark_subfleet_of_biggest_remaining_ships() self.assertEqual(puzzle1.board, parse_board(board_repr)) self.assertEqual(puzzle1.fleet, fleet1) self.assertEqual( puzzle1.__class__.solutions, [ "╔═════════════════════╗\n" "║ x x x x x ║\n" "║ x x x x . ║\n" "║ x x x x x ║\n" "║ x . x x x ║\n" "║ x x x x x ║\n" "╚═════════════════════╝" ], ) fleet2 = Fleet({4: 1, 3: 2, 1: 1}) puzzle2 = Puzzle(parse_board(board_repr), Fleet(fleet2)) with unittest.mock.patch.object( battleships.puzzle.Puzzle, "mark_ship_group") as mocked_try_to_mark_ship_group: puzzle2.mark_subfleet_of_biggest_remaining_ships() self.assertEqual(puzzle2, Puzzle(parse_board(board_repr), Fleet(fleet2))) mocked_try_to_mark_ship_group.assert_called_once_with( {Ship(Position(2, 3), 4, Series.COLUMN)}) fleet3 = Fleet({3: 2, 1: 1}) puzzle3 = Puzzle(parse_board(board_repr), Fleet(fleet3)) with unittest.mock.patch.object( battleships.puzzle.Puzzle, "mark_ship_group") as mocked_try_to_mark_ship_group: puzzle3.mark_subfleet_of_biggest_remaining_ships() self.assertEqual(puzzle3, Puzzle(parse_board(board_repr), Fleet(fleet3))) self.assertEqual(mocked_try_to_mark_ship_group.call_count, 15) mocked_try_to_mark_ship_group.assert_has_calls( [ unittest.mock.call({ Ship(Position(5, 1), 3, Series.ROW), Ship(Position(5, 2), 3, Series.ROW), }), unittest.mock.call({ Ship(Position(5, 1), 3, Series.ROW), Ship(Position(5, 3), 3, Series.ROW), }), unittest.mock.call({ Ship(Position(5, 1), 3, Series.ROW), Ship(Position(2, 3), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 1), 3, Series.ROW), Ship(Position(3, 3), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 1), 3, Series.ROW), Ship(Position(3, 5), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 2), 3, Series.ROW), Ship(Position(5, 3), 3, Series.ROW), }), unittest.mock.call({ Ship(Position(5, 2), 3, Series.ROW), Ship(Position(2, 3), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 2), 3, Series.ROW), Ship(Position(3, 3), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 2), 3, Series.ROW), Ship(Position(3, 5), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 3), 3, Series.ROW), Ship(Position(2, 3), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 3), 3, Series.ROW), Ship(Position(3, 3), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(5, 3), 3, Series.ROW), Ship(Position(3, 5), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(2, 3), 3, Series.COLUMN), Ship(Position(3, 3), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(2, 3), 3, Series.COLUMN), Ship(Position(3, 5), 3, Series.COLUMN), }), unittest.mock.call({ Ship(Position(3, 3), 3, Series.COLUMN), Ship(Position(3, 5), 3, Series.COLUMN), }), ], any_order=True, )
def test_get_possible_puzzles(self): puzzle = Puzzle( parse_board("╔═════════════════════╗\n" "║ x x x x x ║(0)\n" "║ x x x x . ║(2)\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({5: 1}), ) ships_occupying_position = puzzle.board.get_possible_ships_occupying_positions( {Position(3, 3)}, puzzle.fleet.distinct_ship_sizes) ships_occupying_position_orig = copy.deepcopy(ships_occupying_position) self.assertEqual(puzzle.get_possible_puzzles(ships_occupying_position), []) self.assertEqual(ships_occupying_position, ships_occupying_position_orig) puzzle = Puzzle( parse_board("╔═════════════════════╗\n" "║ x x x x x ║(0)\n" "║ x x x x . ║(2)\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({ 4: 1, 3: 2, 1: 3 }), ) ships_occupying_position = puzzle.board.get_possible_ships_occupying_positions( {Position(2, 1), Position(3, 3), Position(5, 3)}, puzzle.fleet.distinct_ship_sizes, ) ships_occupying_position_orig = copy.deepcopy(ships_occupying_position) actual_possible_puzzles_list = puzzle.get_possible_puzzles( ships_occupying_position) self.assertEqual(ships_occupying_position, ships_occupying_position_orig) expected_possible_puzzles_list = [ Puzzle( parse_board("╔═════════════════════╗\n" "║ . . . . . ║(0)\n" "║ O . . . . ║(1)\n" "║ . . O . x ║(1)\n" "║ . . . . x ║(2)\n" "║ O O O . . ║(0)\n" "╚═════════════════════╝\n" " (0) (0) (2) (1) (4) "), Fleet({ 4: 1, 3: 1, 1: 1 }), ), Puzzle( parse_board("╔═════════════════════╗\n" "║ . . . . . ║(0)\n" "║ O . . . . ║(1)\n" "║ . . O . x ║(1)\n" "║ . . . . . ║(2)\n" "║ . O O O . ║(0)\n" "╚═════════════════════╝\n" " (1) (0) (2) (0) (4) "), Fleet({ 4: 1, 3: 1, 1: 1 }), ), Puzzle( parse_board("╔═════════════════════╗\n" "║ . . . . . ║(0)\n" "║ O . . . . ║(1)\n" "║ . . O . x ║(1)\n" "║ x . . . . ║(2)\n" "║ . . O O O ║(0)\n" "╚═════════════════════╝\n" " (1) (1) (2) (0) (3) "), Fleet({ 4: 1, 3: 1, 1: 1 }), ), Puzzle( parse_board("╔═════════════════════╗\n" "║ . . . . . ║(0)\n" "║ O . . . . ║(1)\n" "║ . . O . x ║(1)\n" "║ x . . . x ║(2)\n" "║ x . O . x ║(2)\n" "╚═════════════════════╝\n" " (1) (1) (2) (1) (4) "), Fleet({ 4: 1, 3: 2 }), ), Puzzle( parse_board("╔═════════════════════╗\n" "║ . . . . . ║(0)\n" "║ O . O . . ║(0)\n" "║ . . O . x ║(1)\n" "║ x . O . x ║(1)\n" "║ x . O . x ║(2)\n" "╚═════════════════════╝\n" " (1) (1) (0) (1) (4) "), Fleet({ 3: 2, 1: 2 }), ), Puzzle( parse_board("╔═════════════════════╗\n" "║ . . . . . ║(0)\n" "║ O . . . . ║(1)\n" "║ . . O . x ║(1)\n" "║ x . O . x ║(1)\n" "║ x . O . x ║(2)\n" "╚═════════════════════╝\n" " (1) (1) (1) (1) (4) "), Fleet({ 4: 1, 3: 1, 1: 2 }), ), ] self.assertEqual( { self.__class__.hashable_object_from_puzzle(puzzle) for puzzle in actual_possible_puzzles_list }, { self.__class__.hashable_object_from_puzzle(puzzle) for puzzle in expected_possible_puzzles_list }, )
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, )