Files
ordinaceprojekt/SběrDatRůzné/DailyCalcudoku/vykresli_calcudoku.py
T
Vladimir Buzalka c9903646f1 notebookvb
2026-05-08 13:30:47 +02:00

209 lines
6.5 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í Calcudoku 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_calcudoku.pdf"
OP_DISPLAY = {"+": "+", "-": "", "*": "×", "/": "÷"}
def parse_cages(cages_str: str) -> list[dict]:
cages = []
for part in cages_str.split("|"):
target, op, cells_str = part.split(",", 2)
cells = [(ord(m[0]) - ord("a"), int(m[1]) - 1)
for m in re.findall(r"([a-h])(\d)", cells_str)]
cages.append({"target": int(target), "op": op, "cells": cells})
return cages
def parse_solution(solution_str: str, grid_size: int) -> list[list[int]]:
return [[int(solution_str[r * grid_size + c])
for c in range(grid_size)]
for r in range(grid_size)]
def build_cage_map(cages: list[dict], grid_size: int) -> list[list[int]]:
cage_map = [[-1] * grid_size for _ in range(grid_size)]
for i, cage in enumerate(cages):
for col, row in cage["cells"]:
cage_map[row][col] = i
return cage_map
def cage_label(cage: dict) -> str:
if len(cage["cells"]) == 1:
return str(cage["target"])
return f"{cage['target']}{OP_DISPLAY.get(cage['op'], cage['op'])}"
def top_left_cell(cage: dict) -> tuple[int, int]:
return min(cage["cells"], key=lambda c: (c[1], c[0]))
def draw_calcudoku(c: Canvas, x0: float, y0: float, cell: float,
cages: list[dict], cage_map: list[list[int]],
grid_size: int, title: str = "",
solution: list[list[int]] | None = None):
label_font = max(cell * 0.28, 6)
num_font = max(cell * 0.45, 7)
thin = 0.4
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 - grid_size * cell, grid_size * cell, grid_size * cell, fill=1, stroke=0)
# Řešení
if solution:
c.setFillColor(colors.black)
c.setFont("ArialBold", num_font)
for row in range(grid_size):
for col in range(grid_size):
cx = x0 + col * cell + cell / 2
cy = y0 - (row + 1) * cell + cell * 0.3
c.drawCentredString(cx, cy, str(solution[row][col]))
# Popisky klecí
c.setFillColor(colors.Color(0.15, 0.15, 0.15))
c.setFont("ArialBold", label_font)
for cage in cages:
col, row = top_left_cell(cage)
label = cage_label(cage)
lx = x0 + col * cell + cell * 0.06
ly = y0 - row * cell - label_font * 1.1
c.drawString(lx, ly, label)
# Tenké vnitřní čáry
c.setStrokeColor(colors.Color(0.75, 0.75, 0.75))
c.setLineWidth(thin)
for i in range(1, grid_size):
c.line(x0, y0 - i * cell, x0 + grid_size * cell, y0 - i * cell)
c.line(x0 + i * cell, y0, x0 + i * cell, y0 - grid_size * cell)
# Tlusté hrany mezi různými klecemi
c.setStrokeColor(colors.black)
c.setLineWidth(thick)
# Vnější okraj
bx = x0
by = y0 - grid_size * cell
bw = grid_size * cell
bh = grid_size * cell
c.rect(bx, by, bw, bh, fill=0, stroke=1)
# Horizontální hrany (mezi řádky row a row+1)
for row in range(grid_size - 1):
col_start = None
for col in range(grid_size):
border = cage_map[row][col] != cage_map[row + 1][col]
if border and col_start is None:
col_start = col
elif not border and col_start is not None:
lx1 = x0 + col_start * cell
lx2 = x0 + col * cell
ly = y0 - (row + 1) * cell
c.line(lx1, ly, lx2, ly)
col_start = None
if col_start is not None:
lx1 = x0 + col_start * cell
lx2 = x0 + grid_size * cell
ly = y0 - (row + 1) * cell
c.line(lx1, ly, lx2, ly)
# Vertikální hrany (mezi sloupci col a col+1)
for col in range(grid_size - 1):
row_start = None
for row in range(grid_size):
border = cage_map[row][col] != cage_map[row][col + 1]
if border and row_start is None:
row_start = row
elif not border and row_start is not None:
lx = x0 + (col + 1) * cell
ly1 = y0 - row_start * cell
ly2 = y0 - row * cell
c.line(lx, ly1, lx, ly2)
row_start = None
if row_start is not None:
lx = x0 + (col + 1) * cell
ly1 = y0 - row_start * cell
ly2 = y0 - grid_size * cell
c.line(lx, ly1, lx, ly2)
def main():
conn = connect_mysql(database="puzzle")
cur = conn.cursor()
cur.execute(
"SELECT difficulty, puzzle, solution, extra FROM puzzles "
"WHERE game_type='calcudoku' AND puzzle_date='2026-05-08' "
"ORDER BY FIELD(difficulty, '4x4', '5x5', '6x6', '8x8') "
"LIMIT 1"
)
row = cur.fetchone()
cur.close()
conn.close()
if not row:
print("Žádná data.")
return
difficulty, cages_str, solution_str, extra_json = row
extra = json.loads(extra_json)
grid_size = extra["grid_size"]
cages = parse_cages(cages_str)
cage_map = build_cage_map(cages, grid_size)
solution = parse_solution(solution_str, grid_size)
page_w, page_h = A4
board_cm = 11
cell = board_cm * cm / grid_size
board = grid_size * cell
c = Canvas(str(OUTPUT), pagesize=A4)
# Zadání
x0 = (page_w - board) / 2
y0 = page_h - 2 * cm
draw_calcudoku(c, x0, y0, cell, cages, cage_map, grid_size,
f"Calcudoku {difficulty} — 2026-05-08")
# Řešení
y0_sol = y0 - board - 3 * cm
draw_calcudoku(c, x0, y0_sol, cell, cages, cage_map, grid_size,
"Řešení", solution=solution)
c.save()
print(f"PDF uloženo: {OUTPUT}")
if __name__ == "__main__":
main()