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.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__':
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user