mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-19 12:58:04 +00:00
198 lines
No EOL
6.7 KiB
Python
Executable file
198 lines
No EOL
6.7 KiB
Python
Executable file
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)) |