New job: multi target maze + flush of printer queue
This commit is contained in:
parent
b9715a8032
commit
0754f82ab6
12
jobs/flush.py
Normal file
12
jobs/flush.py
Normal 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
239
jobs/maze_multitarget.py
Normal 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()
|
||||
@ -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__':
|
||||
|
||||
Loading…
Reference in New Issue
Block a user