InternBootcamp/internbootcamp/libs/campsite/campsite_generator.py
2025-05-23 15:27:15 +08:00

174 lines
7.5 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))