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