mirror of
https://github.com/InternLM/InternBootcamp.git
synced 2026-04-19 12:58:04 +00:00
174 lines
7.5 KiB
Python
Executable file
174 lines
7.5 KiB
Python
Executable file
|
||
import random
|
||
|
||
def generate_campsite(n, m, k, seed=None, random_rate=0.2):
|
||
"""
|
||
Generate an n×m Campsite puzzle with at least one valid solution.
|
||
|
||
1) Randomly place k tents in the grid with no two tents adjacent
|
||
orthogonally or diagonally.
|
||
2) Place trees so that each tent is orthogonally adjacent to at least one tree.
|
||
(We can add extra random trees to keep the puzzle interesting.)
|
||
3) Fill remaining cells with 'X'.
|
||
4) Compute row and column constraints, i.e., how many tents in each row/column.
|
||
5) Return the puzzle grid (with 'T' = tree, 'X' = empty) and the row/column constraints.
|
||
|
||
:param n: number of rows
|
||
:param m: number of columns
|
||
:param k: number of tents to place
|
||
:param seed: random seed for reproducibility (optional)
|
||
:return: (grid, row_constraints, col_constraints)
|
||
where grid is an n×m list of lists with 'T' or 'X'.
|
||
row_constraints is a list of length n,
|
||
col_constraints is a list of length m.
|
||
"""
|
||
if seed is not None:
|
||
random.seed(seed)
|
||
|
||
# -------------------------------------------------------------------------
|
||
# STEP 1: Randomly place k tents with no adjacency among them
|
||
# -------------------------------------------------------------------------
|
||
# We'll build our final "solution" using 'C' for tents, 'T' for trees, 'X' for empty.
|
||
solution = [['X'] * m for _ in range(n)]
|
||
|
||
# Keep track of tent locations
|
||
tent_positions = []
|
||
|
||
# We want to place k tents so that no two tents touch (8-direction adjacency).
|
||
# We'll keep trying random positions until we place all k (or we exhaust attempts).
|
||
# In a real puzzle generator, you often do more sophisticated logic to ensure
|
||
# you can place all k tents in large grids, but let's keep this straightforward.
|
||
|
||
# A function to check if a tent can be placed at (r, c)
|
||
def can_place_tent(r, c):
|
||
if solution[r][c] != 'X':
|
||
return False
|
||
# Check 8-direction adjacency
|
||
for dr in (-1, 0, 1):
|
||
for dc in (-1, 0, 1):
|
||
rr, cc = r + dr, c + dc
|
||
if 0 <= rr < n and 0 <= cc < m:
|
||
if solution[rr][cc] == 'C':
|
||
return False
|
||
return True
|
||
|
||
all_cells = [(r, c) for r in range(n) for c in range(m)]
|
||
random.shuffle(all_cells)
|
||
|
||
placed = 0
|
||
idx = 0
|
||
# Try to place k tents in random positions
|
||
while placed < k and idx < len(all_cells):
|
||
r, c = all_cells[idx]
|
||
if can_place_tent(r, c):
|
||
solution[r][c] = 'C'
|
||
tent_positions.append((r, c))
|
||
placed += 1
|
||
idx += 1
|
||
|
||
# If we failed to place k tents, raise an error
|
||
if placed < k:
|
||
#raise ValueError(f"Could not place {k} tents without adjacency conflicts. Try a smaller k.")
|
||
#return False, f"Could not place {k} tents without adjacency conflicts. Try a smaller k."
|
||
print( f"Could not place {k} tents without adjacency conflicts. This case place {placed} tents")
|
||
|
||
# -------------------------------------------------------------------------
|
||
# STEP 2: Place trees so that each tent has at least one tree neighbor
|
||
# -------------------------------------------------------------------------
|
||
# For each tent, check if it already has a tree neighbor. If not, place a tree
|
||
# in at least one valid neighbor cell. We can optionally place more trees to
|
||
# make the puzzle more interesting.
|
||
directions_orth = [(-1, 0), (1, 0), (0, -1), (0, 1)]
|
||
|
||
for (r, c) in tent_positions:
|
||
# Check if there's already at least one tree neighbor
|
||
has_tree_neighbor = False
|
||
for dr, dc in directions_orth:
|
||
rr, cc = r + dr, c + dc
|
||
if 0 <= rr < n and 0 <= cc < m:
|
||
if solution[rr][cc] == 'T':
|
||
has_tree_neighbor = True
|
||
break
|
||
|
||
# If we don't have a tree neighbor, place at least one tree
|
||
if not has_tree_neighbor:
|
||
# Gather valid spots around (r, c) where a tree can be placed
|
||
valid_spots = []
|
||
for dr, dc in directions_orth:
|
||
rr, cc = r + dr, c + dc
|
||
if 0 <= rr < n and 0 <= cc < m:
|
||
# "T" or "X" or "C" – if there's a tent, we can't override it.
|
||
if solution[rr][cc] == 'X':
|
||
valid_spots.append((rr, cc))
|
||
# If valid_spots is empty, we have an impossible puzzle. Normally,
|
||
# it shouldn't be empty because the tent wasn't originally placed
|
||
# adjacent to another tent.
|
||
if not valid_spots:
|
||
raise RuntimeError("No valid spot to place a required tree! Puzzle generation error.")
|
||
# Place a tree at exactly one of these spots (or random choice)
|
||
rr, cc = random.choice(valid_spots)
|
||
solution[rr][cc] = 'T'
|
||
|
||
# Optionally: place extra random trees for puzzle variety
|
||
# For demonstration, 20% chance to place a tree on any empty X,
|
||
# so that the puzzle looks less minimal. Comment out if undesired.
|
||
for r in range(n):
|
||
for c in range(m):
|
||
if solution[r][c] == 'X':
|
||
if random.random() < random_rate:
|
||
solution[r][c] = 'T'
|
||
|
||
# -------------------------------------------------------------------------
|
||
# STEP 3: Now we have a "solution" with tents (C), trees (T), and some X's
|
||
# We want to produce the puzzle's T/X grid (the solver must find
|
||
# where 'C' should go).
|
||
# -------------------------------------------------------------------------
|
||
# To create the puzzle for the end user, we remove 'C' from the final puzzle
|
||
# and replace them with 'X'. So the puzzle that the user sees only has 'T' and 'X'.
|
||
|
||
puzzle = []
|
||
for r in range(n):
|
||
row = []
|
||
for c in range(m):
|
||
if solution[r][c] == 'C':
|
||
# Remove the tent from the puzzle the user sees
|
||
# so the user can solve for the tents
|
||
row.append('X')
|
||
else:
|
||
row.append(solution[r][c])
|
||
puzzle.append(row)
|
||
|
||
# -------------------------------------------------------------------------
|
||
# STEP 4: Compute row/col constraints from the actual solution with tents
|
||
# -------------------------------------------------------------------------
|
||
row_constraints = [0] * n
|
||
col_constraints = [0] * m
|
||
for r in range(n):
|
||
for c in range(m):
|
||
if solution[r][c] == 'C':
|
||
row_constraints[r] += 1
|
||
col_constraints[c] += 1
|
||
|
||
# Return puzzle (trees + X), row/col constraints,
|
||
# and optionally the *solution* grid if you want to verify.
|
||
return puzzle, row_constraints, col_constraints, solution
|
||
|
||
# ------------------------------------------------------------------------------
|
||
# EXAMPLE USAGE
|
||
# ------------------------------------------------------------------------------
|
||
if __name__ == "__main__":
|
||
# Example: Generate a 5×7 puzzle with 6 tents, with a fixed random seed for reproducibility
|
||
gen_puzzle, row_cons, col_cons, solution = generate_campsite(n=5, m=7, k=6, seed=None, random_rate=0.2)
|
||
|
||
# Print puzzle in a readable way
|
||
print("Puzzle Layout (T/X):")
|
||
for row in gen_puzzle:
|
||
print(" ".join(row))
|
||
|
||
print("\nRow Constraints:", row_cons)
|
||
print("Col Constraints:", col_cons)
|
||
|
||
print("\n(For debugging) One valid solution used to generate the puzzle (C = tent):")
|
||
for row in solution:
|
||
print(" ".join(row))
|
||
|