From 0754f82ab69d89a8842648e15b3fa459343e00e5 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Tue, 23 Dec 2025 22:49:21 +0100 Subject: [PATCH] New job: multi target maze + flush of printer queue --- jobs/flush.py | 12 ++ jobs/maze_multitarget.py | 239 +++++++++++++++++++++++++++++++++++++++ print_server.py | 9 +- 3 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 jobs/flush.py create mode 100644 jobs/maze_multitarget.py diff --git a/jobs/flush.py b/jobs/flush.py new file mode 100644 index 0000000..3aa98bc --- /dev/null +++ b/jobs/flush.py @@ -0,0 +1,12 @@ +class FlushJob: + def get_name(self): + return "Flush Printer Queue" + + def configure(self): + pass + + def run(self, printer): + # Send NUL bytes to push any buffered data without printing visible characters + printer._raw(b'\x00\x00') + # Send a newline to ensure any line-buffered data is processed + printer.text("\n") \ No newline at end of file diff --git a/jobs/maze_multitarget.py b/jobs/maze_multitarget.py new file mode 100644 index 0000000..8c89ddc --- /dev/null +++ b/jobs/maze_multitarget.py @@ -0,0 +1,239 @@ +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() diff --git a/print_server.py b/print_server.py index d668cec..314b865 100644 --- a/print_server.py +++ b/print_server.py @@ -1,3 +1,4 @@ +import time from escpos.printer import Usb, Dummy from escpos.exceptions import USBNotFoundError from jobs.math_homework import MathHomeworkJob @@ -7,6 +8,8 @@ from jobs.maze import MazeJob from jobs.division_cipher import DivisionCipherJob from jobs.decimal_division import DecimalDivisionJob from jobs.joke import JokeJob +from jobs.maze_multitarget import MazeMultitargetJob +from jobs.flush import FlushJob # ========================================== # CONFIGURATION @@ -59,7 +62,10 @@ JOBS = [ MazeJob(), DivisionCipherJob(), DecimalDivisionJob(), - JokeJob() + JokeJob(), + MazeMultitargetJob(), + # keep this last: + FlushJob() ] def run_tui(): @@ -117,6 +123,7 @@ def run_tui(): print(f"Print Error: {e}") finally: if not isinstance(p, Dummy): + time.sleep(0.5) p.close() if __name__ == '__main__':