Files
ordinaceprojekt/SběrDatRůzné/SudokuKiller/vykresli_killer_sudoku.py
T
Vladimir Buzalka c4c0d1d435 notebookvb
2026-05-08 22:06:57 +02:00

230 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Vykreslí Killer Sudoku puzzle do PDF z dat v MySQL tabulce puzzles.
"""
import json
import os
import re
import sys
from pathlib import Path
sys.stdout.reconfigure(encoding="utf-8")
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Knihovny"))
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen.canvas import Canvas
from mysql_db import connect_mysql
_fonts_dir = os.path.join(os.environ.get("WINDIR", r"C:\Windows"), "Fonts")
pdfmetrics.registerFont(TTFont("Arial", os.path.join(_fonts_dir, "arial.ttf")))
pdfmetrics.registerFont(TTFont("ArialBold", os.path.join(_fonts_dir, "arialbd.ttf")))
OUTPUT = Path(__file__).parent / "test_killer_sudoku_v6.pdf"
def parse_cages(puzzle_str: str) -> list[dict]:
cages = []
for part in puzzle_str.split("|"):
target, cells_str = part.split(",", 1)
cells = [(int(m[1]), int(m[2])) for m in re.finditer(r"r(\d)c(\d)", cells_str)]
cages.append({"sum": int(target), "cells": cells})
return cages
def build_cage_map(cages: list[dict]) -> list[list[int]]:
cage_map = [[-1] * 9 for _ in range(9)]
for i, cage in enumerate(cages):
for row, col in cage["cells"]:
cage_map[row][col] = i
return cage_map
def cage_label_cell(cage: dict) -> tuple[int, int]:
return min(cage["cells"], key=lambda c: (c[0], c[1]))
def parse_solution(solution_str: str) -> list[list[int]]:
return [[int(solution_str[r * 9 + c]) for c in range(9)] for r in range(9)]
def draw_killer_sudoku(c: Canvas, x0: float, y0: float, cell: float,
cages: list[dict], cage_map: list[list[int]],
title: str = "", solution: list[list[int]] | None = None):
label_font = max(cell * 0.22, 5)
num_font = max(cell * 0.45, 7)
thin = 0.3
cage_line = 1.0
thick = 2.2
if title:
c.setFont("ArialBold", 12)
c.drawString(x0, y0 + 5, title)
# Bílé pozadí
c.setFillColor(colors.white)
c.rect(x0, y0 - 9 * cell, 9 * cell, 9 * cell, fill=1, stroke=0)
# Řešení
if solution:
c.setFillColor(colors.Color(0.25, 0.25, 0.25))
c.setFont("Arial", num_font)
for r in range(9):
for co in range(9):
cx = x0 + co * cell + cell / 2
cy = y0 - (r + 1) * cell + cell * 0.28
c.drawCentredString(cx, cy, str(solution[r][co]))
# --- Vrstva 1: kompletní sudoku mřížka (tenké plné čáry) ---
c.setStrokeColor(colors.Color(0.55, 0.55, 0.55))
c.setLineWidth(thin)
for i in range(1, 9):
c.line(x0, y0 - i * cell, x0 + 9 * cell, y0 - i * cell)
c.line(x0 + i * cell, y0, x0 + i * cell, y0 - 9 * cell)
# --- Vrstva 2: tečkované ohraničení klecí (odsazené dovnitř buněk) ---
inset = cell * 0.10
c.setStrokeColor(colors.Color(0.2, 0.2, 0.2))
c.setLineWidth(cage_line * 0.5)
c.setDash(3, 2)
# Horizontální hrany klecí — top borders
for r in range(9):
co = 0
while co < 9:
cid = cage_map[r][co]
if not (r == 0 or cage_map[r - 1][co] != cid):
co += 1
continue
seg_start = co
while co < 9 and cage_map[r][co] == cid and (r == 0 or cage_map[r - 1][co] != cid):
co += 1
c.line(x0 + seg_start * cell + inset, y0 - r * cell - inset,
x0 + co * cell - inset, y0 - r * cell - inset)
# Horizontální hrany klecí — bottom borders
for r in range(9):
co = 0
while co < 9:
cid = cage_map[r][co]
if not (r == 8 or cage_map[r + 1][co] != cid):
co += 1
continue
seg_start = co
while co < 9 and cage_map[r][co] == cid and (r == 8 or cage_map[r + 1][co] != cid):
co += 1
c.line(x0 + seg_start * cell + inset, y0 - (r + 1) * cell + inset,
x0 + co * cell - inset, y0 - (r + 1) * cell + inset)
# Vertikální hrany klecí — left borders
for co in range(9):
r = 0
while r < 9:
cid = cage_map[r][co]
if not (co == 0 or cage_map[r][co - 1] != cid):
r += 1
continue
seg_start = r
while r < 9 and cage_map[r][co] == cid and (co == 0 or cage_map[r][co - 1] != cid):
r += 1
c.line(x0 + co * cell + inset, y0 - seg_start * cell - inset,
x0 + co * cell + inset, y0 - r * cell + inset)
# Vertikální hrany klecí — right borders
for co in range(9):
r = 0
while r < 9:
cid = cage_map[r][co]
if not (co == 8 or cage_map[r][co + 1] != cid):
r += 1
continue
seg_start = r
while r < 9 and cage_map[r][co] == cid and (co == 8 or cage_map[r][co + 1] != cid):
r += 1
c.line(x0 + (co + 1) * cell - inset, y0 - seg_start * cell - inset,
x0 + (co + 1) * cell - inset, y0 - r * cell + inset)
c.setDash()
# Tlusté 3×3 čáry + vnější okraj
c.setStrokeColor(colors.black)
c.setLineWidth(thick)
for i in range(0, 10, 3):
c.line(x0, y0 - i * cell, x0 + 9 * cell, y0 - i * cell)
c.line(x0 + i * cell, y0, x0 + i * cell, y0 - 9 * cell)
# Popisky klecí (součty) — nakonec, aby nebyly překryty čarami
c.setFillColor(colors.white)
c.setFont("ArialBold", label_font)
for cage in cages:
if not cage["cells"]:
continue
row, col = cage_label_cell(cage)
lx = x0 + col * cell + cell * 0.05
ly = y0 - row * cell - label_font * 1.05
txt = str(cage["sum"])
tw = c.stringWidth(txt, "ArialBold", label_font)
c.rect(lx - 0.5, ly - 0.5, tw + 1, label_font + 1, fill=1, stroke=0)
c.setFillColor(colors.black)
c.setFont("ArialBold", label_font)
for cage in cages:
if not cage["cells"]:
continue
row, col = cage_label_cell(cage)
lx = x0 + col * cell + cell * 0.05
ly = y0 - row * cell - label_font * 1.05
c.drawString(lx, ly, str(cage["sum"]))
def main():
conn = connect_mysql(database="puzzle")
cur = conn.cursor()
cur.execute(
"SELECT difficulty, puzzle, solution, extra FROM puzzles "
"WHERE game_type='killer_sudoku' AND extra LIKE '%%\"puzzle_number\": 31414%%' "
"LIMIT 1"
)
row = cur.fetchone()
cur.close()
conn.close()
if not row:
print("Žádná data.")
return
difficulty, puzzle_str, solution_str, extra_json = row
extra = json.loads(extra_json)
cages = parse_cages(puzzle_str)
cage_map = build_cage_map(cages)
solution = parse_solution(solution_str)
page_w, page_h = A4
board_cm = 11
cell = board_cm * cm / 9
board_px = 9 * cell
c = Canvas(str(OUTPUT), pagesize=A4)
# Zadání
x0 = (page_w - board_px) / 2
y0 = page_h - 2 * cm
draw_killer_sudoku(c, x0, y0, cell, cages, cage_map,
f"Killer Sudoku (difficulty {difficulty}) — {extra.get('puzzle_number', '')}")
# Řešení
y0_sol = y0 - board_px - 3 * cm
draw_killer_sudoku(c, x0, y0_sol, cell, cages, cage_map,
"Řešení", solution=solution)
c.save()
print(f"PDF uloženo: {OUTPUT}")
if __name__ == "__main__":
main()