178 lines
5.8 KiB
Python
178 lines
5.8 KiB
Python
"""
|
||
Vygeneruje A4 cheat sheet pro Calcudoku 4x4 — kombinace sčítání, násobení, dělení.
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
from itertools import combinations
|
||
from math import prod
|
||
from pathlib import Path
|
||
|
||
sys.stdout.reconfigure(encoding="utf-8")
|
||
|
||
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
|
||
|
||
_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 / "cheatsheet_4x4.pdf"
|
||
|
||
DIGITS = range(1, 5)
|
||
PAGE_W, PAGE_H = A4
|
||
MARGIN = 1.8 * cm
|
||
|
||
# Barvy sekcí
|
||
COL_HEADER_BG = colors.Color(0.20, 0.35, 0.55)
|
||
COL_HEADER_FG = colors.white
|
||
ROW_HEADER_BG = colors.Color(0.88, 0.92, 0.97)
|
||
ROW_ALT_BG = colors.Color(0.96, 0.97, 1.00)
|
||
ROW_WHITE_BG = colors.white
|
||
SECTION_COLORS = {
|
||
"+": colors.Color(0.18, 0.48, 0.30), # zelená
|
||
"*": colors.Color(0.55, 0.20, 0.20), # červená
|
||
"/": colors.Color(0.55, 0.40, 0.10), # oranžová
|
||
}
|
||
SECTION_LABELS = {"+": "SČÍTÁNÍ +", "*": "NÁSOBENÍ ×", "/": "DĚLENÍ ÷"}
|
||
|
||
|
||
def build_table(op: str) -> dict:
|
||
"""Vrátí {výsledek: {n_buněk: [combo, ...]}} pro daný operátor."""
|
||
results: dict = {}
|
||
for n in range(2, len(DIGITS) + 1):
|
||
for combo in combinations(DIGITS, n):
|
||
if op == "+":
|
||
val = sum(combo)
|
||
elif op == "*":
|
||
val = prod(combo)
|
||
elif op == "/":
|
||
# dělení: největší / zbytek (seřazeno desc)
|
||
s = sorted(combo, reverse=True)
|
||
val = s[0]
|
||
for x in s[1:]:
|
||
val //= x
|
||
# jen celočíselné výsledky odpovídající skutečnému dělení
|
||
check = s[0]
|
||
for x in s[1:]:
|
||
if check % x != 0:
|
||
val = None
|
||
break
|
||
check //= x
|
||
if val is None:
|
||
continue
|
||
results.setdefault(val, {}).setdefault(n, []).append(combo)
|
||
return results
|
||
|
||
|
||
def combo_str(combo: tuple, op: str) -> str:
|
||
sym = {"+" : "+", "*": "×", "/": "÷"}[op]
|
||
return sym.join(str(d) for d in combo)
|
||
|
||
|
||
def draw_section(c: Canvas, op: str, x: float, y: float, width: float) -> float:
|
||
"""Vykreslí sekci pro jeden operátor. Vrátí y-souřadnici konce sekce."""
|
||
table = build_table(op)
|
||
all_ns = sorted({n for sub in table.values() for n in sub})
|
||
|
||
col_label_w = 1.1 * cm
|
||
col_w = (width - col_label_w) / len(all_ns)
|
||
row_h = 0.72 * cm
|
||
header_h = 0.8 * cm
|
||
|
||
section_color = SECTION_COLORS[op]
|
||
|
||
# Nadpis sekce
|
||
c.setFillColor(section_color)
|
||
c.rect(x, y - 0.75 * cm, width, 0.75 * cm, fill=1, stroke=0)
|
||
c.setFillColor(colors.white)
|
||
c.setFont("ArialBold", 13)
|
||
c.drawString(x + 0.3 * cm, y - 0.55 * cm, SECTION_LABELS[op])
|
||
y -= 0.75 * cm
|
||
|
||
# Záhlaví sloupců (počet buněk)
|
||
c.setFillColor(COL_HEADER_BG)
|
||
c.rect(x, y - header_h, width, header_h, fill=1, stroke=0)
|
||
c.setFillColor(COL_HEADER_FG)
|
||
c.setFont("ArialBold", 9)
|
||
c.drawCentredString(x + col_label_w / 2, y - header_h + 0.22 * cm, "výsledek")
|
||
for i, n in enumerate(all_ns):
|
||
cx = x + col_label_w + i * col_w + col_w / 2
|
||
c.drawCentredString(cx, y - header_h + 0.22 * cm, f"{n} buňky" if n < 4 else "4 buňky")
|
||
y -= header_h
|
||
|
||
# Řádky
|
||
for ridx, result in enumerate(sorted(table.keys())):
|
||
bg = ROW_ALT_BG if ridx % 2 == 0 else ROW_WHITE_BG
|
||
c.setFillColor(bg)
|
||
c.rect(x, y - row_h, width, row_h, fill=1, stroke=0)
|
||
|
||
# Výsledek
|
||
c.setFillColor(section_color)
|
||
c.rect(x, y - row_h, col_label_w, row_h, fill=1, stroke=0)
|
||
c.setFillColor(colors.white)
|
||
c.setFont("ArialBold", 11)
|
||
c.drawCentredString(x + col_label_w / 2, y - row_h + 0.18 * cm, str(result))
|
||
|
||
# Kombinace
|
||
c.setFillColor(colors.Color(0.1, 0.1, 0.1))
|
||
for i, n in enumerate(all_ns):
|
||
cx = x + col_label_w + i * col_w
|
||
combos = table[result].get(n, [])
|
||
if not combos:
|
||
# šedá pomlčka
|
||
c.setFillColor(colors.Color(0.75, 0.75, 0.75))
|
||
c.setFont("Arial", 9)
|
||
c.drawCentredString(cx + col_w / 2, y - row_h + 0.18 * cm, "—")
|
||
c.setFillColor(colors.Color(0.1, 0.1, 0.1))
|
||
else:
|
||
# více kombinací pod sebou
|
||
line_h = row_h / len(combos)
|
||
for ci, combo in enumerate(combos):
|
||
text = combo_str(combo, op)
|
||
ty = y - ci * line_h - line_h + 0.16 * cm
|
||
c.setFont("ArialBold" if len(combos) == 1 else "Arial", 9 if len(combos) > 1 else 10)
|
||
c.drawCentredString(cx + col_w / 2, ty, text)
|
||
|
||
y -= row_h
|
||
|
||
# Spodní linka sekce
|
||
c.setStrokeColor(section_color)
|
||
c.setLineWidth(1.2)
|
||
c.line(x, y, x + width, y)
|
||
c.setLineWidth(0.5)
|
||
|
||
return y
|
||
|
||
|
||
def main():
|
||
c = Canvas(str(OUTPUT), pagesize=A4)
|
||
width = PAGE_W - 2 * MARGIN
|
||
|
||
y = PAGE_H - 1.4 * cm
|
||
|
||
# Hlavní nadpis
|
||
c.setFillColor(colors.Color(0.12, 0.12, 0.30))
|
||
c.setFont("ArialBold", 18)
|
||
c.drawCentredString(PAGE_W / 2, y - 0.6 * cm, "Calcudoku 4×4 — Cheat Sheet")
|
||
c.setFont("Arial", 9)
|
||
c.setFillColor(colors.Color(0.4, 0.4, 0.4))
|
||
c.drawCentredString(PAGE_W / 2, y - 1.05 * cm, "Číslice 1–4, v každé kleci bez opakování")
|
||
y -= 1.5 * cm
|
||
|
||
gap = 0.45 * cm
|
||
for op in ("+", "*", "/"):
|
||
y = draw_section(c, op, MARGIN, y, width)
|
||
y -= gap
|
||
|
||
c.save()
|
||
print(f"PDF uloženo: {OUTPUT}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|