mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-28 17:29:37 +00:00
init-commit
This commit is contained in:
commit
18a552597a
3461 changed files with 1150579 additions and 0 deletions
198
internbootcamp/libs/arrowmaze/maze_generator.py
Executable file
198
internbootcamp/libs/arrowmaze/maze_generator.py
Executable file
|
|
@ -0,0 +1,198 @@
|
|||
import random
|
||||
|
||||
ARROW_SYMBOLS = {
|
||||
(-1, 0): '↑',
|
||||
( 1, 0): '↓',
|
||||
( 0, -1): '←',
|
||||
( 0, 1): '→',
|
||||
(-1, -1): '↖',
|
||||
(-1, 1): '↗',
|
||||
( 1, -1): '↙',
|
||||
( 1, 1): '↘',
|
||||
}
|
||||
|
||||
# Inverse mapping from symbol back to (dr, dc) if needed:
|
||||
# REVERSE_ARROW = {v: k for k, v in ARROW_SYMBOLS.items()}
|
||||
|
||||
def generate_arrow_maze(n, m, start, end, max_attempts=10000, max_solution_step=20, seed=None):
|
||||
"""
|
||||
Generate an n×m arrow maze with a guaranteed path from `start` to `end`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : int
|
||||
Number of rows.
|
||||
m : int
|
||||
Number of columns.
|
||||
start : (int, int)
|
||||
(row, col) of the start cell (0-based).
|
||||
end : (int, int)
|
||||
(row, col) of the end cell (0-based).
|
||||
max_attempts : int
|
||||
Maximum attempts to try building a path in case of random backtracking failures.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[list[str]]
|
||||
A 2D grid of strings. Each cell is one of the 8 arrow symbols or '○' for the end cell.
|
||||
Guaranteed at least one path under the "move multiple steps" rule.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If a path cannot be generated within `max_attempts`.
|
||||
"""
|
||||
if seed != None:
|
||||
random.seed(seed)
|
||||
|
||||
# Basic checks
|
||||
if not (0 <= start[0] < n and 0 <= start[1] < m):
|
||||
raise ValueError("Start position out of bounds.")
|
||||
if not (0 <= end[0] < n and 0 <= end[1] < m):
|
||||
raise ValueError("End position out of bounds.")
|
||||
if start == end:
|
||||
raise ValueError("Start and end cannot be the same cell.")
|
||||
|
||||
# We will first build a path (a list of cells) from start to end using random DFS-like backtracking.
|
||||
# Then we will fill the "arrows" along that path to ensure a valid path.
|
||||
# Finally, fill in random arrows for the other cells.
|
||||
|
||||
# For convenience, store the path of cells as a list of (row, col).
|
||||
path = []
|
||||
global now_step_number
|
||||
|
||||
# We will do a backtracking function. The function attempts to build a path from "current" cell to "end".
|
||||
# If it succeeds, it returns True and has the path in the `path`.
|
||||
# If it fails, it returns False.
|
||||
|
||||
def in_bounds(r, c):
|
||||
return 0 <= r < n and 0 <= c < m
|
||||
|
||||
def backtrack(current):
|
||||
"""Attempt to build a path from `current` to `end` via random expansions."""
|
||||
# Add the current cell to path
|
||||
global now_step_number
|
||||
now_step_number += 1
|
||||
path.append(current)
|
||||
|
||||
# If current == end, we've made the path successfully
|
||||
if current == end:
|
||||
return True
|
||||
|
||||
if now_step_number > max_solution_step:
|
||||
path.pop()
|
||||
now_step_number -= 1
|
||||
return False
|
||||
|
||||
# Try random directions in a shuffled order
|
||||
directions = list(ARROW_SYMBOLS.keys())
|
||||
random.shuffle(directions)
|
||||
|
||||
# For each direction, try steps of size 1.. up to max possible in that direction
|
||||
for (dr, dc) in directions:
|
||||
# The maximum step we can take in this direction so we don't go out of bounds
|
||||
max_step = 1
|
||||
while True:
|
||||
nr = current[0] + (max_step * dr)
|
||||
nc = current[1] + (max_step * dc)
|
||||
if not in_bounds(nr, nc):
|
||||
break
|
||||
max_step += 1
|
||||
# Now max_step - 1 is the largest valid step
|
||||
|
||||
if max_step <= 1:
|
||||
# We can't move in this direction at all
|
||||
continue
|
||||
|
||||
# We can choose any step in range [1, max_step-1]
|
||||
step_sizes = list(range(1, max_step))
|
||||
random.shuffle(step_sizes)
|
||||
|
||||
for step in step_sizes:
|
||||
nr = current[0] + step * dr
|
||||
nc = current[1] + step * dc
|
||||
# Check if the next cell is not yet in path (avoid immediate loops)
|
||||
if (nr, nc) not in path:
|
||||
# Recurse
|
||||
if backtrack((nr, nc)):
|
||||
return True
|
||||
# else skip because it's already in path (avoid cycles)
|
||||
|
||||
# If no direction/step led to a solution, backtrack
|
||||
path.pop()
|
||||
now_step_number -= 1
|
||||
return False
|
||||
|
||||
# Try multiple times to build a path (sometimes random choices fail to find a path)
|
||||
attempts = 0
|
||||
success = False
|
||||
while attempts < max_attempts:
|
||||
path.clear()
|
||||
now_step_number = 0
|
||||
if backtrack(start):
|
||||
success = True
|
||||
break
|
||||
attempts += 1
|
||||
|
||||
if not success:
|
||||
raise ValueError("Could not generate a path with random backtracking after many attempts.")
|
||||
|
||||
# Now `path` is our sequence of cells from start to end.
|
||||
# Next we build the grid of arrows. We'll mark all cells with random arrows first, then override the path.
|
||||
|
||||
grid = [[None for _ in range(m)] for _ in range(n)]
|
||||
|
||||
# Assign random arrows to every cell initially
|
||||
directions_list = list(ARROW_SYMBOLS.values())
|
||||
for r in range(n):
|
||||
for c in range(m):
|
||||
grid[r][c] = random.choice(directions_list)
|
||||
|
||||
# Mark the end cell with '○'
|
||||
er, ec = end
|
||||
grid[er][ec] = '○'
|
||||
|
||||
# Now override the path cells (except the end).
|
||||
# If path[i] = (r1, c1) leads to path[i+1] = (r2, c2),
|
||||
# we find direction = (r2-r1, c2-c1) -> arrow symbol.
|
||||
# We put that symbol in grid[r1][c1]. The last cell in the path is the end cell → '○'.
|
||||
|
||||
for i in range(len(path) - 1):
|
||||
r1, c1 = path[i]
|
||||
r2, c2 = path[i+1]
|
||||
dr = r2 - r1
|
||||
dc = c2 - c1
|
||||
symbol = ARROW_SYMBOLS.get((dr, dc), None)
|
||||
if symbol is None:
|
||||
# This should never happen if (dr, dc) is one of the 8 directions.
|
||||
# But we might have multi-steps combined. We only store the "macro step" as if it were a single arrow.
|
||||
# Because in the puzzle, moving multiple steps in the same direction is allowed in one arrow cell.
|
||||
# So the direction is the *normalized* version, i.e., sign of dr/dc if non-zero.
|
||||
# For example, if dr=2, dc=-2 => direction is (1, -1) or (1, -1) repeated.
|
||||
# Let's define a quick normalization.
|
||||
ndr = 0 if dr == 0 else (dr // abs(dr))
|
||||
ndc = 0 if dc == 0 else (dc // abs(dc))
|
||||
symbol = ARROW_SYMBOLS.get((ndr, ndc))
|
||||
grid[r1][c1] = symbol
|
||||
|
||||
# print("standard path we select:",path)
|
||||
|
||||
return grid
|
||||
|
||||
|
||||
# ---------------------------
|
||||
# Example usage / testing code
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example: generate a 6x8 maze, start at (0,0), end at (5,7).
|
||||
# You can change these freely.
|
||||
n, m = 10, 8
|
||||
start = (0, 0)
|
||||
end = (5, 7)
|
||||
|
||||
# For reproducibility, remove or change for more randomness
|
||||
maze = generate_arrow_maze(n, m, start, end, max_solution_step=15, seed=0)
|
||||
|
||||
# Print the generated maze
|
||||
for row in maze:
|
||||
print(" ".join(row))
|
||||
251
internbootcamp/libs/arrowmaze/maze_solver.py
Executable file
251
internbootcamp/libs/arrowmaze/maze_solver.py
Executable file
|
|
@ -0,0 +1,251 @@
|
|||
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]]"
|
||||
|
||||
|
||||
218
internbootcamp/libs/arrowmaze/maze_validor.py
Executable file
218
internbootcamp/libs/arrowmaze/maze_validor.py
Executable file
|
|
@ -0,0 +1,218 @@
|
|||
import re
|
||||
|
||||
def parse_candidate_path(answer_str):
|
||||
"""
|
||||
Parse a candidate_path string in the format:
|
||||
"[[1 0 0,0 0 0,0 0 2]]"
|
||||
into a 2D list of integers.
|
||||
"""
|
||||
# 1) Remove the outer brackets "[[" and "]]"
|
||||
# 2) Split into rows by comma
|
||||
# 3) Each row is space-separated integers
|
||||
# Example input: "[[1 0 0,0 0 0,0 0 2]]"
|
||||
|
||||
# Trim leading/trailing brackets
|
||||
trimmed = answer_str.strip()
|
||||
if trimmed.startswith("[["):
|
||||
trimmed = trimmed[2:]
|
||||
if trimmed.endswith("]]"):
|
||||
trimmed = trimmed[:-2]
|
||||
|
||||
# Now we have something like: "1 0 0,0 0 0,0 0 2"
|
||||
# Split by commas to get rows
|
||||
row_strs = trimmed.split(",")
|
||||
|
||||
grid_of_ints = []
|
||||
for row_str in row_strs:
|
||||
# row_str might look like "1 0 0" or "0 0 2"
|
||||
row_str = row_str.strip()
|
||||
if not row_str:
|
||||
continue
|
||||
# split by spaces
|
||||
vals = row_str.split()
|
||||
row_ints = [int(v) for v in vals]
|
||||
grid_of_ints.append(row_ints)
|
||||
return grid_of_ints
|
||||
|
||||
def arrow_maze_validator(
|
||||
grid, start_position, answer
|
||||
):
|
||||
"""
|
||||
Validate whether a candidate_path in puzzle's format (e.g. "[[1 0 0,0 0 0,0 0 2]]")
|
||||
is a correct solution to the arrow maze.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
grid : list[list[str]]
|
||||
A 2D grid of arrow symbols or '○'.
|
||||
Example:
|
||||
[
|
||||
['→', '↙', '↓'],
|
||||
['↖', '↓', '↙'],
|
||||
['↑', '←', '○'],
|
||||
]
|
||||
start_position : (int, int)
|
||||
(row, col) of the starting cell.
|
||||
answer : list
|
||||
The proposed solution in the format "[[...]]"
|
||||
0 => not on path
|
||||
1 => first visited cell
|
||||
2 => second visited cell
|
||||
etc.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the path is valid, False otherwise.
|
||||
"""
|
||||
|
||||
# Directions dictionary: maps arrow symbol -> (dr, dc)
|
||||
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
|
||||
|
||||
def in_bounds(r, c):
|
||||
return 0 <= r < rows and 0 <= c < cols
|
||||
|
||||
|
||||
#$candidate_grid = parse_candidate_path(answer_str)
|
||||
candidate_grid = answer
|
||||
|
||||
# Sanity check: the candidate_grid should match the same dimensions as 'grid'
|
||||
if len(candidate_grid) != rows:
|
||||
return False
|
||||
for row_vals in candidate_grid:
|
||||
if len(row_vals) != cols:
|
||||
return False
|
||||
|
||||
# 2. Extract the labeled cells: (label, (row, col))
|
||||
# We only care about label > 0
|
||||
labeled_cells = []
|
||||
for r in range(rows):
|
||||
for c in range(cols):
|
||||
label = candidate_grid[r][c]
|
||||
if label > 0:
|
||||
labeled_cells.append((label, (r, c)))
|
||||
|
||||
# If no labeled cells, invalid
|
||||
if not labeled_cells:
|
||||
return False
|
||||
|
||||
# 3. Sort by label ascending
|
||||
labeled_cells.sort(key=lambda x: x[0]) # sort by label number
|
||||
# This gives us an ordered path: [ (1, (r1,c1)), (2, (r2,c2)), ... ]
|
||||
|
||||
# 4. The path in terms of coordinates:
|
||||
path = [cell_coord for _, cell_coord in labeled_cells]
|
||||
|
||||
# 5. Check that label "1" is at start_position
|
||||
if path[0] != start_position:
|
||||
return False
|
||||
|
||||
# 6. Validate each consecutive step in path
|
||||
for i in range(len(path) - 1):
|
||||
(r1, c1) = path[i]
|
||||
(r2, c2) = path[i + 1]
|
||||
|
||||
if not in_bounds(r1, c1) or not in_bounds(r2, c2):
|
||||
return False
|
||||
|
||||
# If the current cell is the end symbol '○' but we still have more steps, invalid
|
||||
if grid[r1][c1] == '○':
|
||||
return False
|
||||
|
||||
# Arrow in the current cell:
|
||||
arrow_symbol = grid[r1][c1]
|
||||
if arrow_symbol not in DIRECTIONS:
|
||||
return False # not an arrow and not the end symbol
|
||||
|
||||
(dr, dc) = DIRECTIONS[arrow_symbol]
|
||||
delta_r = r2 - r1
|
||||
delta_c = c2 - c1
|
||||
|
||||
# Must move in a positive integer multiple of (dr, dc).
|
||||
if dr == 0 and dc == 0:
|
||||
return False # shouldn't happen with valid arrows
|
||||
|
||||
# Horizontal or vertical
|
||||
if dr == 0:
|
||||
# vertical movement is zero => must move horizontally
|
||||
# check we didn't move in row, must move in col
|
||||
if delta_r != 0:
|
||||
return False
|
||||
# direction must match sign of dc
|
||||
if dc > 0 and delta_c <= 0:
|
||||
return False
|
||||
if dc < 0 and delta_c >= 0:
|
||||
return False
|
||||
elif dc == 0:
|
||||
# horizontal movement is zero => must move in row
|
||||
if delta_c != 0:
|
||||
return False
|
||||
if dr > 0 and delta_r <= 0:
|
||||
return False
|
||||
if dr < 0 and delta_r >= 0:
|
||||
return False
|
||||
else:
|
||||
# diagonal
|
||||
if delta_r == 0 or delta_c == 0:
|
||||
return False # can't be diagonal if one is zero
|
||||
if (dr > 0 and delta_r <= 0) or (dr < 0 and delta_r >= 0):
|
||||
return False
|
||||
if (dc > 0 and delta_c <= 0) or (dc < 0 and delta_c >= 0):
|
||||
return False
|
||||
# check integer multiples
|
||||
if (delta_r % dr) != 0 or (delta_c % dc) != 0:
|
||||
return False
|
||||
factor_r = delta_r // dr
|
||||
factor_c = delta_c // dc
|
||||
if factor_r != factor_c or factor_r <= 0:
|
||||
return False
|
||||
|
||||
# 7. Check last labeled cell is the '○' cell
|
||||
last_r, last_c = path[-1]
|
||||
if not in_bounds(last_r, last_c):
|
||||
return False
|
||||
if grid[last_r][last_c] != '○':
|
||||
return False
|
||||
|
||||
# If all checks pass, it's a valid solution
|
||||
return True
|
||||
|
||||
|
||||
# ------------------------------
|
||||
# Example usage:
|
||||
if __name__ == "__main__":
|
||||
# A small 3×3 arrow maze with start=(0, 0):
|
||||
# Grid (3x3):
|
||||
# → ↙ ↓
|
||||
# ↖ ↓ ↙
|
||||
# ↑ ← ○
|
||||
grid = [
|
||||
['→', '↙', '↓'],
|
||||
['↖', '↓', '↙'],
|
||||
['↑', '←', '○'],
|
||||
]
|
||||
start_position = (0, 0)
|
||||
|
||||
# Example candidate_path string:
|
||||
# "[[1 0 0,0 0 0,0 0 2]]"
|
||||
# This claims:
|
||||
# row=0 col=0 => 1 (start)
|
||||
# row=2 col=2 => 2 (end)
|
||||
candidate_path_str = "[[1 0 2,0 0 0,0 0 3]]"
|
||||
|
||||
is_valid = arrow_maze_validator(
|
||||
grid, start_position, candidate_path_str
|
||||
)
|
||||
print("Is the candidate path valid?", is_valid)
|
||||
Loading…
Add table
Add a link
Reference in a new issue