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

253 lines
8.7 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 / "killer_sudoku_31414.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: tečkované ohraničení klecí (KRESLÍ SE JAKO PRVNÍ) ---
# Tečkované jdou ZESPODU, mřížka přes ně. Tím se zachová čistý vzhled —
# tenká mřížka překryje místa, kde tečkovaná protíná čáru mřížky.
inset = cell * 0.10
c.setStrokeColor(colors.Color(0.2, 0.2, 0.2))
c.setLineWidth(cage_line * 0.5)
c.setDash(3, 2)
has_top = [[r == 0 or cage_map[r - 1][co] != cage_map[r][co]
for co in range(9)] for r in range(9)]
has_bot = [[r == 8 or cage_map[r + 1][co] != cage_map[r][co]
for co in range(9)] for r in range(9)]
has_lft = [[co == 0 or cage_map[r][co - 1] != cage_map[r][co]
for co in range(9)] for r in range(9)]
has_rgt = [[co == 8 or cage_map[r][co + 1] != cage_map[r][co]
for co in range(9)] for r in range(9)]
def in_cg(rr, cc, cid):
return 0 <= rr <= 8 and 0 <= cc <= 8 and cage_map[rr][cc] == cid
# Top borders — slévání v řádku
# Vnější roh: zkrácení o inset. Vnitřní roh: prodloužení o inset.
for r in range(9):
co = 0
while co < 9:
if not has_top[r][co]:
co += 1
continue
cid = cage_map[r][co]
s = co
while co < 9 and cage_map[r][co] == cid and has_top[r][co]:
co += 1
# Levý konec: vnitřní roh když (r, s-1) je v kleci (cage tam pokračuje směrem nahoru)
x_s = x0 + s * cell + (-inset if in_cg(r, s - 1, cid) else inset)
# Pravý konec: vnitřní roh když (r, co) je v kleci
x_e = x0 + co * cell + (inset if in_cg(r, co, cid) else -inset)
c.line(x_s, y0 - r * cell - inset, x_e, y0 - r * cell - inset)
# Bottom borders
for r in range(9):
co = 0
while co < 9:
if not has_bot[r][co]:
co += 1
continue
cid = cage_map[r][co]
s = co
while co < 9 and cage_map[r][co] == cid and has_bot[r][co]:
co += 1
x_s = x0 + s * cell + (-inset if in_cg(r, s - 1, cid) else inset)
x_e = x0 + co * cell + (inset if in_cg(r, co, cid) else -inset)
c.line(x_s, y0 - (r + 1) * cell + inset, x_e, y0 - (r + 1) * cell + inset)
# Left borders — slévání ve sloupci
for co in range(9):
r = 0
while r < 9:
if not has_lft[r][co]:
r += 1
continue
cid = cage_map[r][co]
s = r
while r < 9 and cage_map[r][co] == cid and has_lft[r][co]:
r += 1
# Horní konec: vnitřní roh když (s-1, co) je v kleci
y_s = y0 - s * cell + (inset if in_cg(s - 1, co, cid) else -inset)
# Dolní konec: vnitřní roh když (r, co) je v kleci
y_e = y0 - r * cell + (-inset if in_cg(r, co, cid) else inset)
c.line(x0 + co * cell + inset, y_s, x0 + co * cell + inset, y_e)
# Right borders
for co in range(9):
r = 0
while r < 9:
if not has_rgt[r][co]:
r += 1
continue
cid = cage_map[r][co]
s = r
while r < 9 and cage_map[r][co] == cid and has_rgt[r][co]:
r += 1
y_s = y0 - s * cell + (inset if in_cg(s - 1, co, cid) else -inset)
y_e = y0 - r * cell + (-inset if in_cg(r, co, cid) else inset)
c.line(x0 + (co + 1) * cell - inset, y_s, x0 + (co + 1) * cell - inset, y_e)
c.setDash()
# --- Vrstva 2: kompletní sudoku mřížka (tenké plné čáry přes tečkované) ---
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 3: 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()