240 lines
8.5 KiB
Python
240 lines
8.5 KiB
Python
import random
|
|
from collections import deque
|
|
import time
|
|
|
|
class MazeMultitargetJob:
|
|
def __init__(self):
|
|
self.options = []
|
|
self.correct_index = 0
|
|
self.width = 18 * 2 + 1
|
|
self.height = 20 * 2 + 1
|
|
|
|
def get_name(self):
|
|
return "Maze with Multiple Endings"
|
|
|
|
def configure(self):
|
|
print("\n--- Configure Maze Options ---")
|
|
self.options = []
|
|
print("Enter labels for the endings (empty line to finish):")
|
|
while True:
|
|
label = input(f"Option {chr(65 + len(self.options))}: ").strip()
|
|
if not label:
|
|
if len(self.options) < 2:
|
|
print("Please enter at least 2 options.")
|
|
continue
|
|
break
|
|
self.options.append(label)
|
|
if len(self.options) >= 26:
|
|
break
|
|
|
|
print("\nWhich option is the correct one?")
|
|
for i, opt in enumerate(self.options):
|
|
print(f" [{chr(65 + i)}] {opt}")
|
|
|
|
while True:
|
|
choice = input("Correct option (letter): ").strip().upper()
|
|
if len(choice) == 1:
|
|
idx = ord(choice) - 65
|
|
if 0 <= idx < len(self.options):
|
|
self.correct_index = idx
|
|
break
|
|
print("Invalid selection.")
|
|
|
|
def run(self, printer):
|
|
# 1. Generate Perfect Maze (DFS)
|
|
# Grid: 1 = Wall, 0 = Path
|
|
grid = [[1 for _ in range(self.width)] for _ in range(self.height)]
|
|
|
|
def get_neighbors(r, c, dist=2):
|
|
ns = []
|
|
for dr, dc in [(-dist, 0), (dist, 0), (0, -dist), (0, dist)]:
|
|
nr, nc = r + dr, c + dc
|
|
if 0 < nr < self.height and 0 < nc < self.width:
|
|
ns.append((nr, nc))
|
|
return ns
|
|
|
|
# Start carving from (1, 1)
|
|
start_pos = (1, 1)
|
|
grid[start_pos[0]][start_pos[1]] = 0
|
|
stack = [start_pos]
|
|
|
|
while stack:
|
|
current = stack[-1]
|
|
r, c = current
|
|
neighbors = get_neighbors(r, c)
|
|
unvisited = []
|
|
for nr, nc in neighbors:
|
|
if grid[nr][nc] == 1:
|
|
unvisited.append((nr, nc))
|
|
|
|
if unvisited:
|
|
nr, nc = random.choice(unvisited)
|
|
# Remove wall between
|
|
wr, wc = (r + nr) // 2, (c + nc) // 2
|
|
grid[wr][wc] = 0
|
|
grid[nr][nc] = 0
|
|
stack.append((nr, nc))
|
|
else:
|
|
stack.pop()
|
|
|
|
def find_path(start, end, current_grid):
|
|
q = deque([start])
|
|
came_from = {start: None}
|
|
while q:
|
|
curr = q.popleft()
|
|
if curr == end:
|
|
break
|
|
|
|
r, c = curr
|
|
# Check neighbors (dist 1)
|
|
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
|
nr, nc = r + dr, c + dc
|
|
if 0 <= nr < self.height and 0 <= nc < self.width:
|
|
if current_grid[nr][nc] == 0 and (nr, nc) not in came_from:
|
|
came_from[(nr, nc)] = curr
|
|
q.append((nr, nc))
|
|
|
|
if end not in came_from:
|
|
return None
|
|
|
|
# Reconstruct path
|
|
path = []
|
|
curr = end
|
|
while curr:
|
|
path.append(curr)
|
|
curr = came_from[curr]
|
|
return path[::-1]
|
|
|
|
# 2. Place Endpoints
|
|
# We need len(self.options) endpoints.
|
|
endpoints = [None] * len(self.options)
|
|
|
|
# First, place the correct endpoint
|
|
attempts = 0
|
|
while endpoints[self.correct_index] is None and attempts < 1000:
|
|
r = random.randrange(1, self.height, 2)
|
|
c = random.randrange(1, self.width, 2)
|
|
if (r, c) != start_pos and grid[r][c] == 0:
|
|
endpoints[self.correct_index] = (r, c)
|
|
attempts += 1
|
|
|
|
correct_endpoint = endpoints[self.correct_index]
|
|
if not correct_endpoint:
|
|
printer.text("Error: Could not place correct endpoint.\n")
|
|
return
|
|
|
|
# Calculate true path to ensure we don't place fakes on it
|
|
true_path = find_path(start_pos, correct_endpoint, grid)
|
|
if not true_path:
|
|
printer.text("Error: No path to correct endpoint.\n")
|
|
return
|
|
true_path_set = set(true_path)
|
|
|
|
# Place fake endpoints
|
|
attempts = 0
|
|
while None in endpoints and attempts < 2000:
|
|
r = random.randrange(1, self.height, 2)
|
|
c = random.randrange(1, self.width, 2)
|
|
pt = (r, c)
|
|
if pt != start_pos and pt not in endpoints and grid[r][c] == 0:
|
|
if pt not in true_path_set:
|
|
# Fill first empty slot
|
|
for i in range(len(endpoints)):
|
|
if endpoints[i] is None:
|
|
endpoints[i] = pt
|
|
break
|
|
attempts += 1
|
|
|
|
if None in endpoints:
|
|
printer.text("Error: Could not place enough endpoints.\n")
|
|
return
|
|
|
|
# 4. Block Incorrect Paths
|
|
# Robust Multi-target isolation
|
|
|
|
fakes = [pt for i, pt in enumerate(endpoints) if i != self.correct_index]
|
|
|
|
def get_degree(r, c, current_grid):
|
|
deg = 0
|
|
for dr, dc in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
|
nr, nc = r + dr, c + dc
|
|
if 0 <= nr < self.height and 0 <= nc < self.width:
|
|
if current_grid[nr][nc] == 0:
|
|
deg += 1
|
|
return deg
|
|
|
|
for fake in fakes:
|
|
# Repeat until fake is isolated from all other targets
|
|
while True:
|
|
connected_target = None
|
|
path_to_target = None
|
|
|
|
# Prioritize connection to Start/Correct (Main), then other fakes
|
|
check_order = [start_pos, correct_endpoint] + [f for f in fakes if f != fake]
|
|
|
|
for other in check_order:
|
|
path = find_path(other, fake, grid)
|
|
if path:
|
|
connected_target = other
|
|
path_to_target = path
|
|
break
|
|
|
|
if not connected_target:
|
|
break # Isolated from everyone
|
|
|
|
# Identify segment NOT on true_path
|
|
valid_segment_start = 0
|
|
for k in range(len(path_to_target)):
|
|
if path_to_target[k] not in true_path_set:
|
|
valid_segment_start = k
|
|
break
|
|
|
|
if valid_segment_start == len(path_to_target):
|
|
break # Should not happen unless fake is ON true path
|
|
|
|
# Find last junction on the path to maximize false path length
|
|
best_cut_u_index = -1
|
|
start_search = max(0, valid_segment_start - 1)
|
|
|
|
for k in range(start_search, len(path_to_target) - 1):
|
|
u = path_to_target[k]
|
|
if get_degree(u[0], u[1], grid) > 2:
|
|
best_cut_u_index = k
|
|
|
|
if best_cut_u_index != -1:
|
|
block_index = best_cut_u_index + 1
|
|
else:
|
|
block_index = len(path_to_target) - 2
|
|
|
|
# Ensure we block a valid node
|
|
block_index = max(block_index, valid_segment_start)
|
|
to_block = path_to_target[block_index]
|
|
grid[to_block[0]][to_block[1]] = 1
|
|
|
|
# 5. Print Maze
|
|
printer.text("Najdi spravny cil!\n\n")
|
|
|
|
# Map endpoints to letters
|
|
endpoint_map = {pt: chr(65 + i) for i, pt in enumerate(endpoints)}
|
|
|
|
for r in range(self.height):
|
|
line = ""
|
|
for c in range(self.width):
|
|
if (r, c) == start_pos:
|
|
line += "S"
|
|
elif (r, c) in endpoint_map:
|
|
line += endpoint_map[(r, c)]
|
|
elif grid[r][c] == 1:
|
|
line += "█" # Full block
|
|
else:
|
|
line += " "
|
|
printer.text(line + "\n")
|
|
time.sleep(0.01)
|
|
|
|
printer.text("\nMoznosti:\n")
|
|
for i, opt in enumerate(self.options):
|
|
printer.text(f"{chr(65 + i)}: {opt}\n")
|
|
|
|
printer.text("\n\n")
|
|
printer.cut()
|