New job: multi target maze + flush of printer queue

This commit is contained in:
Dejvino 2025-12-23 22:49:21 +01:00
parent b9715a8032
commit 0754f82ab6
3 changed files with 259 additions and 1 deletions

12
jobs/flush.py Normal file
View File

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

239
jobs/maze_multitarget.py Normal file
View File

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

View File

@ -1,3 +1,4 @@
import time
from escpos.printer import Usb, Dummy from escpos.printer import Usb, Dummy
from escpos.exceptions import USBNotFoundError from escpos.exceptions import USBNotFoundError
from jobs.math_homework import MathHomeworkJob from jobs.math_homework import MathHomeworkJob
@ -7,6 +8,8 @@ from jobs.maze import MazeJob
from jobs.division_cipher import DivisionCipherJob from jobs.division_cipher import DivisionCipherJob
from jobs.decimal_division import DecimalDivisionJob from jobs.decimal_division import DecimalDivisionJob
from jobs.joke import JokeJob from jobs.joke import JokeJob
from jobs.maze_multitarget import MazeMultitargetJob
from jobs.flush import FlushJob
# ========================================== # ==========================================
# CONFIGURATION # CONFIGURATION
@ -59,7 +62,10 @@ JOBS = [
MazeJob(), MazeJob(),
DivisionCipherJob(), DivisionCipherJob(),
DecimalDivisionJob(), DecimalDivisionJob(),
JokeJob() JokeJob(),
MazeMultitargetJob(),
# keep this last:
FlushJob()
] ]
def run_tui(): def run_tui():
@ -117,6 +123,7 @@ def run_tui():
print(f"Print Error: {e}") print(f"Print Error: {e}")
finally: finally:
if not isinstance(p, Dummy): if not isinstance(p, Dummy):
time.sleep(0.5)
p.close() p.close()
if __name__ == '__main__': if __name__ == '__main__':