mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-22 16:49:04 +00:00
182 lines
No EOL
5.6 KiB
Python
Executable file
182 lines
No EOL
5.6 KiB
Python
Executable file
from collections import deque
|
|
|
|
def solve_nurikabe(grid, verbose=True):
|
|
"""
|
|
Nurikabe puzzle solver.
|
|
|
|
grid: 2D list, each element in {'A', 'X', '0'..'9'}
|
|
- digit: island with size hint
|
|
- 'X': island cell (without specific numeric hint)
|
|
- 'A': sea cell
|
|
verbose: bool, whether to print intermediate solving process.
|
|
|
|
Returns: a 2D list (same format) representing the solved puzzle.
|
|
|
|
Requirements:
|
|
1) Exactly one digit (size hint) per island, and the island size == that digit.
|
|
2) All sea cells are connected (single sea), and no 2x2 sea block.
|
|
3) No island lacking a digit hint.
|
|
"""
|
|
|
|
if verbose:
|
|
print("=== Nurikabe Solver: Start ===")
|
|
print("Puzzle Input:")
|
|
for row in grid:
|
|
print(" " + " ".join(row))
|
|
print()
|
|
|
|
rows = len(grid)
|
|
cols = len(grid[0]) if rows > 0 else 0
|
|
|
|
# Make a modifiable copy of the grid
|
|
solution = [row[:] for row in grid]
|
|
|
|
# Identify cells that can be changed (not digit)
|
|
fill_cells = []
|
|
for r in range(rows):
|
|
for c in range(cols):
|
|
if not solution[r][c].isdigit():
|
|
fill_cells.append((r, c))
|
|
|
|
if verbose:
|
|
print(f"[Info] There are {len(fill_cells)} fillable cells.\n")
|
|
|
|
# Helper functions
|
|
def in_bounds(r, c):
|
|
return 0 <= r < rows and 0 <= c < cols
|
|
|
|
def is_island(val):
|
|
return val == 'X' or val.isdigit()
|
|
|
|
def get_island_region(r, c):
|
|
""" BFS to get connected island region and its digit set """
|
|
queue = deque([(r, c)])
|
|
region = {(r, c)}
|
|
digits = set()
|
|
|
|
while queue:
|
|
rr, cc = queue.popleft()
|
|
val = solution[rr][cc]
|
|
if val.isdigit():
|
|
digits.add(val)
|
|
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
|
|
nr, nc = rr + dr, cc + dc
|
|
if in_bounds(nr, nc) and is_island(solution[nr][nc]) and (nr, nc) not in region:
|
|
region.add((nr, nc))
|
|
queue.append((nr, nc))
|
|
return region, digits
|
|
|
|
def check_islands():
|
|
visited = set()
|
|
for r in range(rows):
|
|
for c in range(cols):
|
|
if is_island(solution[r][c]) and (r, c) not in visited:
|
|
region, digits = get_island_region(r, c)
|
|
visited |= region
|
|
if len(digits) != 1:
|
|
return False
|
|
digit_val = next(iter(digits))
|
|
if len(region) != int(digit_val):
|
|
return False
|
|
return True
|
|
|
|
def check_sea():
|
|
sea_cells = [(r, c) for r in range(rows) for c in range(cols) if solution[r][c] == 'A']
|
|
if not sea_cells: # if puzzle requires at least some sea
|
|
return False
|
|
|
|
visited = set([sea_cells[0]])
|
|
queue = deque([sea_cells[0]])
|
|
while queue:
|
|
rr, cc = queue.popleft()
|
|
for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
|
|
nr, nc = rr + dr, cc + dc
|
|
if in_bounds(nr, nc) and solution[nr][nc] == 'A' and (nr, nc) not in visited:
|
|
visited.add((nr, nc))
|
|
queue.append((nr, nc))
|
|
|
|
if len(visited) != len(sea_cells): # sea not connected
|
|
return False
|
|
|
|
# no 2x2 all sea
|
|
for rr in range(rows - 1):
|
|
for cc in range(cols - 1):
|
|
block = [solution[rr][cc],
|
|
solution[rr][cc+1],
|
|
solution[rr+1][cc],
|
|
solution[rr+1][cc+1]]
|
|
if all(x == 'A' for x in block):
|
|
return False
|
|
|
|
return True
|
|
|
|
def is_valid():
|
|
return check_islands() and check_sea()
|
|
|
|
def backtrack(idx=0):
|
|
if idx == len(fill_cells):
|
|
# All fillable cells assigned, do final check
|
|
if verbose:
|
|
print("[*] All cells filled, validating solution...")
|
|
if is_valid():
|
|
if verbose:
|
|
print("[√] Valid solution found!\n")
|
|
return True
|
|
else:
|
|
if verbose:
|
|
print("[X] Validation failed, backtrack.\n")
|
|
return False
|
|
|
|
r, c = fill_cells[idx]
|
|
original_val = solution[r][c]
|
|
|
|
# Try 'A' or 'X'
|
|
for candidate in ['A', 'X']:
|
|
solution[r][c] = candidate
|
|
if verbose:
|
|
print(f"[Try] idx={idx}, cell=({r},{c}), set='{candidate}'")
|
|
|
|
if backtrack(idx + 1):
|
|
return True
|
|
|
|
# Undo assignment if not successful
|
|
solution[r][c] = original_val
|
|
if verbose:
|
|
print(f" -> Backtrack: reset cell=({r},{c}) to '{original_val}'")
|
|
|
|
return False
|
|
|
|
if verbose:
|
|
print("=== Start Backtracking ===\n")
|
|
success = backtrack(0)
|
|
if not success:
|
|
raise ValueError("\n[Conclusion] Puzzle has no valid solution under given constraints.")
|
|
|
|
if verbose:
|
|
print("=== Final Solved Puzzle ===")
|
|
for row in solution:
|
|
print(" ".join(row))
|
|
|
|
return solution
|
|
|
|
|
|
# ============ Example test ============ #
|
|
if __name__ == "__main__":
|
|
# puzzle = [
|
|
# ['X','X','X','X','5'],
|
|
# ['X','X','X','X','X'],
|
|
# ['1','X','X','4','X'],
|
|
# ['X','X','X','X','X'],
|
|
# ['X','X','X','X','1']
|
|
# ]
|
|
puzzle = [
|
|
['4','X','1','X','X'],
|
|
['X','X','X','X','3'],
|
|
['X','X','2','X','X'],
|
|
['X','X','X','X','X'],
|
|
['X','X','X','X','X']
|
|
]
|
|
result = solve_nurikabe(puzzle, verbose=False)
|
|
print("\n[Solved Output]:")
|
|
for row in result:
|
|
print(" ".join(row)) |