mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-19 12:58:04 +00:00
251 lines
No EOL
8.5 KiB
Python
Executable file
251 lines
No EOL
8.5 KiB
Python
Executable file
from collections import deque
|
|
|
|
def solve_arrow_maze(grid, start=(0, 0)):
|
|
"""
|
|
Solve the arrow maze puzzle where each cell has an arrow (↑, ↗, →, ↘, ↓, ↙, ←, ↖),
|
|
or the '○' end symbol. From each cell, you can move 1 or more steps in that cell's
|
|
arrow direction (if it's not the '○' cell).
|
|
|
|
Parameters
|
|
----------
|
|
grid : list[list[str]]
|
|
2D list of strings. Each element is one of:
|
|
'↑', '↗', '→', '↘', '↓', '↙', '←', '↖', or '○' (end).
|
|
start : (int, int)
|
|
(row, column) index of the starting cell.
|
|
|
|
Returns
|
|
-------
|
|
str
|
|
A string with the answer in the format:
|
|
"[[r1, r2, r3, ...]]"
|
|
Where each row is space-separated and rows are separated by commas.
|
|
- The position of each cell on the path that is an inflection point
|
|
(including start, any direction change, and the end) is labeled
|
|
in ascending order of encountering the direction changes.
|
|
- Cells not on the path are labeled '0'.
|
|
"""
|
|
|
|
# Map from arrow symbol to row, col deltas
|
|
DIRECTIONS = {
|
|
'↑': (-1, 0),
|
|
'↓': ( 1, 0),
|
|
'←': ( 0, -1),
|
|
'→': ( 0, 1),
|
|
'↖': (-1, -1),
|
|
'↗': (-1, 1),
|
|
'↙': ( 1, -1),
|
|
'↘': ( 1, 1),
|
|
}
|
|
|
|
rows = len(grid)
|
|
cols = len(grid[0]) if rows > 0 else 0
|
|
if rows == 0 or cols == 0:
|
|
raise ValueError("Grid must not be empty.")
|
|
|
|
# Locate the end cell (just for validation); not strictly needed,
|
|
# but good to confirm the puzzle has a valid end.
|
|
end_cell = None
|
|
for r in range(rows):
|
|
for c in range(cols):
|
|
if grid[r][c] == '○':
|
|
end_cell = (r, c)
|
|
break
|
|
if end_cell is not None:
|
|
break
|
|
if not end_cell:
|
|
raise ValueError("No '○' (end) cell found in the grid.")
|
|
|
|
# Check start in bounds
|
|
if not (0 <= start[0] < rows and 0 <= start[1] < cols):
|
|
raise ValueError("Start position is out of grid bounds.")
|
|
|
|
# BFS to find any path from start to end
|
|
queue = deque()
|
|
queue.append(start)
|
|
visited = set()
|
|
visited.add(start)
|
|
|
|
history = []
|
|
|
|
# Parent dict for reconstructing path: parent[(r, c)] = (pr, pc)
|
|
parent = {}
|
|
|
|
# Helper to check in-bounds
|
|
def in_bounds(r, c):
|
|
return 0 <= r < rows and 0 <= c < cols
|
|
|
|
found_end = False
|
|
|
|
while queue and not found_end:
|
|
|
|
if found_end == False:
|
|
candidates_needed2explore = ""
|
|
for item in queue:
|
|
r,c = item
|
|
candidates_needed2explore = candidates_needed2explore + f"({r},{c}),"
|
|
history.append("The candidate positions need to explore are: "+candidates_needed2explore +"\n")
|
|
|
|
r, c = queue.popleft()
|
|
if found_end == False:
|
|
history.append(f"select position ({r},{c}) '{grid[r][c]}' to explore.\n")
|
|
|
|
if grid[r][c] == '○':
|
|
# Already at end
|
|
found_end = True
|
|
break
|
|
|
|
# Current cell arrow
|
|
if grid[r][c] not in DIRECTIONS:
|
|
# If it's an invalid symbol (not arrow, not '○'), skip
|
|
raise ValueError
|
|
dr, dc = DIRECTIONS[grid[r][c]]
|
|
|
|
# Try moving k steps in that direction
|
|
step = 1
|
|
while True:
|
|
nr = r + step * dr
|
|
nc = c + step * dc
|
|
if not in_bounds(nr, nc):
|
|
history.append(f"Chose step={step}. Position ({nr},{nc}) is out of the bounds. So let's explore next node.\n")
|
|
# Out of bounds or invalid => stop exploring further steps
|
|
break
|
|
if (nr, nc) not in visited:
|
|
history.append(f"Chose step={step}. Add position ({nr},{nc}) to candidates.")
|
|
visited.add((nr, nc))
|
|
parent[(nr, nc)] = (r, c)
|
|
queue.append((nr, nc))
|
|
if grid[nr][nc] == '○':
|
|
#history.append((nr, nc, f"checking ({nr},{nc}) and ({nr},{nc}) is the end point"))
|
|
history[-1] = history[-1]+ f"Check position ({nr},{nc}). Position ({nr},{nc}) is the end point!\n"
|
|
found_end = True
|
|
break
|
|
else:
|
|
#history.append((nr, nc, f"check ({nr},{nc}) and ({nr},{nc}) not the end point"))
|
|
history[-1] = history[-1]+ f"Check position ({nr},{nc}). Position ({nr},{nc}) is not the end point.\n"
|
|
else:
|
|
history.append(f"Chose step={step}. Position ({nr},{nc}) has been explored. So skip the position ({nr},{nc}).\n")
|
|
|
|
step += 1
|
|
|
|
if found_end:
|
|
break
|
|
|
|
|
|
# If we never found the end, puzzle is unsolvable from this start
|
|
if not found_end:
|
|
history.append(f"Fail! Candidates are empty! We explore all posibility but can't find the solution. So No path can be found to the end cell '○'.")
|
|
#raise ValueError("No path found to the end cell '○'.")
|
|
return False, history
|
|
else:
|
|
history.append(f"Find the solution! Now backtrack the full path.\n")
|
|
|
|
# Reconstruct path from the end to the start
|
|
path = []
|
|
cur = end_cell
|
|
while cur in parent or cur == start:
|
|
path.append(cur)
|
|
if cur == start:
|
|
history.append("Back to the start node. Here is the final answer:\n")
|
|
break
|
|
cur_for_history = cur
|
|
cur = parent[cur]
|
|
history.append(f"Track ({cur_for_history[0]},{cur_for_history[1]}) and find the parent node is ({cur[0]},{cur[1]}).\n")
|
|
path.reverse() # Now it goes from start -> end
|
|
|
|
# Prepare the result grid (same dimensions), fill with 0
|
|
result = [[0]*cols for _ in range(rows)]
|
|
|
|
# Label inflection points:
|
|
# - Start cell gets the first label (1).
|
|
# - Every time the direction changes from one cell to the next, we increment the label.
|
|
# - End cell gets labeled last.
|
|
inflection_label = 0
|
|
prev_dir = None
|
|
|
|
for i, (r, c) in enumerate(path):
|
|
symbol = grid[r][c]
|
|
# For the end cell '○', we treat it as a different "direction" to ensure it is labeled
|
|
current_dir = symbol if symbol in DIRECTIONS else '○'
|
|
|
|
if current_dir != prev_dir:
|
|
inflection_label += 1
|
|
result[r][c] = inflection_label
|
|
prev_dir = current_dir
|
|
else:
|
|
# It's on the path, but not a new inflection => could set to something else if needed
|
|
# The puzzle examples do NOT label those. We leave them as 0 in the final output
|
|
pass
|
|
|
|
# If you do *not* want the end cell labeled, remove the above logic that sets it.
|
|
|
|
# Convert result 2D array into the puzzle's required string format
|
|
# "[[r1, r2, ...], [r1, r2, ...], ...]"
|
|
# However, from the examples, the format seems to be:
|
|
# "[[1 0 2,0 0 0,0 0 3]]"
|
|
# i.e. each row is "space-separated" and the rows are separated by commas.
|
|
rows_strings = []
|
|
for rr in range(rows):
|
|
row_str = " ".join(str(result[rr][cc]) for cc in range(cols))
|
|
rows_strings.append(row_str)
|
|
final_answer = "[[" + ",".join(rows_strings) + "]]"
|
|
history.append(final_answer)
|
|
|
|
history = ''.join(history)
|
|
|
|
return result, history
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Example usage:
|
|
|
|
if __name__ == "__main__":
|
|
# Example 1:
|
|
grid1 = [
|
|
['→', '↙', '↓'],
|
|
['↖', '↓', '↙'],
|
|
['↑', '←', '○'],
|
|
]
|
|
ans1, history = solve_arrow_maze(grid1, start=(0, 0))
|
|
print("Example 1 Answer:", ans1)
|
|
|
|
print(history)
|
|
# Expect something like "[[1 0 2,0 0 0,0 0 3]]"
|
|
# depending on how you label inflections
|
|
|
|
# Example 2:
|
|
grid2 = [
|
|
['↘', '↙', '↓'],
|
|
['↖', '↓', '↙'],
|
|
['↑', '←', '○'],
|
|
]
|
|
ans2, history = solve_arrow_maze(grid2, start=(0, 0))
|
|
print("Example 2 Answer:", ans2)
|
|
|
|
print(history)
|
|
# Expect something like "[[1 0 0,0 0 0,0 0 2]]"
|
|
|
|
# Example 2:
|
|
grid3 = [
|
|
['↓', '↙', '↑'],
|
|
['→', '↖', '↓'],
|
|
['↗', '→', '○'],
|
|
]
|
|
ans3, history = solve_arrow_maze(grid3, start=(0, 0))
|
|
print("Example 3 Answer:", ans3)
|
|
|
|
print(history)
|
|
# Expect something like "[[1 0 0,0 0 0,0 0 2]]"
|
|
|
|
grid3 = [
|
|
['↓', '↙', '↑',"↑"],
|
|
['→', '↖', '↘',"↙"],
|
|
['↙', '→', '↑',"○"],
|
|
]
|
|
ans3, history = solve_arrow_maze(grid3, start=(0, 0))
|
|
print("Example 3 Answer:", ans3)
|
|
|
|
print(history)
|
|
# Expect something like "[[1 0 0,0 0 0,0 0 2]]"
|
|
|
|
|