diff --git a/Medevio/60 ScansProcessing/corrections.json b/Medevio/60 ScansProcessing/corrections.json index f99dde6..07cf1ba 100644 --- a/Medevio/60 ScansProcessing/corrections.json +++ b/Medevio/60 ScansProcessing/corrections.json @@ -1,4 +1,4 @@ -[ +¨w[ { "original": "505228025 2026-05-14 Titlbachová, Božena [Žádanka předoperační vyšetření GYNA] [Předop. vyšetření, dg. N890, malý výkon A, anestezie CA].pdf", "corrected": "505228025 2026-05-14 Titlbachová, Božena [žádanka předoperační vyšetření] [gynekologie, dg. N890, malý výkon A, anestezie CA].pdf" diff --git a/SběrDatRůzné/DailyCalcudoku/NOTES.md b/SběrDatRůzné/DailyCalcudoku/NOTES.md new file mode 100644 index 0000000..39b2a91 --- /dev/null +++ b/SběrDatRůzné/DailyCalcudoku/NOTES.md @@ -0,0 +1,57 @@ +# DailyCalcudoku — technické poznámky + +## Přehled skriptů + +| Skript | Popis | +|--------|-------| +| `preskumaj_calcudoku.py` | Průzkumný — vytáhne `gameLevels` z JS kontextu stránky | +| `stahni_calcudoku.py` | Stáhne data z webu a uloží do MySQL (celý rok najednou) | +| `vykresli_calcudoku.py` | Generuje PDF z dat v MySQL (reportlab, vektorové) | + +## Zdroj dat + +Stránka: https://www.solitaire.org/daily-calcudoku/ + +Stejná architektura jako Kakuro/Str8ts — `game.php` načte `gameLevels` s daty pro celý rok (366 dní × 4 velikosti). Klíče `"MM-DD"`, bez roku. Data se nemění přes rok — stačí jednorázové stažení v lednu. + +## Obtížnosti (velikosti mřížek) + +| Klíč | Rozměr | Číslice | +|------|--------|---------| +| 4x4 | 4×4 | 1–4 | +| 5x5 | 5×5 | 1–5 | +| 6x6 | 6×6 | 1–6 | +| 8x8 | 8×8 | 1–8 | + +## Datová struktura `gameLevels` + +Každý záznam je pole dvou stringů: `[cages, solution]` + +### Cages (definice klecí) + +Klece oddělené `|`, každá ve formátu `target,operator,cells`: +``` +3,*,a1b1|8,+,a2b2a3|2,/,c4d4 +``` +- `target` = cílová hodnota +- `operator` = `+`, `-`, `*`, `/` +- `cells` = seznam buněk (sloupec=písmeno a–h, řádek=číslo 1–8) + +### Solution (řešení) + +Flat string číslic, řádek po řádku: +``` +1342213442133421 (4×4 = 16 znaků) +``` + +## MySQL tabulka `puzzle.puzzles` + +Sdílená tabulka s ostatními puzzle. Pro Calcudoku: +- `game_type` = `'calcudoku'` +- `difficulty` = `'4x4'` / `'5x5'` / `'6x6'` / `'8x8'` +- `puzzle` = cage definice (cages string) +- `solution` = flat string řešení +- `extra` = `{"grid_size": 4}` / `5` / `6` / `8` +- `source` = `'solitaire.org'` + +Stav: celý rok 2026 naplněn (1460 řádků = 365 dní × 4 velikosti). diff --git a/SběrDatRůzné/DailyCalcudoku/preskumaj_calcudoku.py b/SběrDatRůzné/DailyCalcudoku/preskumaj_calcudoku.py new file mode 100644 index 0000000..2dc1ef2 --- /dev/null +++ b/SběrDatRůzné/DailyCalcudoku/preskumaj_calcudoku.py @@ -0,0 +1,149 @@ +""" +Průzkumný skript: připojí se na solitaire.org/daily-calcudoku/ a vytáhne +surová JS data o puzzle (gameLevels, Game objekt). +""" + +import asyncio +import json +import sys + +sys.stdout.reconfigure(encoding="utf-8") + +from playwright.async_api import async_playwright + +URL = "https://www.solitaire.org/daily-calcudoku/" + + +async def main(): + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + context = await browser.new_context(viewport={"width": 1280, "height": 900}) + + page = await context.new_page() + print(f"Načítám {URL} ...") + await page.goto(URL, wait_until="networkidle", timeout=60_000) + + game_url = None + for frame in page.frames: + if frame.url != page.url and frame.url.strip() not in ("", "about:blank"): + game_url = frame.url + print(f" Nalezen iframe: {game_url}") + break + + if not game_url: + iframe_src = await page.get_attribute("iframe", "src") + if iframe_src: + game_url = iframe_src if iframe_src.startswith("http") else f"https://www.solitaire.org{iframe_src}" + print(f" Iframe src z DOM: {game_url}") + + await page.close() + + game_page = await context.new_page() + target_url = game_url if game_url else URL + print(f"Načítám hru: {target_url} ...") + await game_page.goto(target_url, wait_until="networkidle", timeout=60_000) + + # 1) gameLevels + print("\n=== gameLevels ===") + game_levels = await game_page.evaluate("""() => { + if (typeof gameLevels !== 'undefined') return JSON.stringify(gameLevels, null, 2); + return null; + }""") + if game_levels: + print(game_levels[:5000]) + if len(game_levels) > 5000: + print(f"... (celkem {len(game_levels)} znaků)") + else: + print(" gameLevels není definováno") + + # 2) Game objekt — klíče + print("\n=== Game objekt — klíče ===") + game_keys = await game_page.evaluate("""() => { + if (typeof Game !== 'undefined') return Object.keys(Game); + return null; + }""") + if game_keys: + print(json.dumps(game_keys, indent=2)) + else: + print(" Game není definováno") + + # 3) Game — datové vlastnosti + print("\n=== Game — datové vlastnosti ===") + game_data = await game_page.evaluate("""() => { + if (typeof Game === 'undefined') return null; + const result = {}; + for (const key of Object.keys(Game)) { + const val = Game[key]; + if (typeof val !== 'function') { + try { result[key] = JSON.parse(JSON.stringify(val)); } + catch(e) { result[key] = String(val); } + } + } + return result; + }""") + if game_data: + txt = json.dumps(game_data, indent=2, ensure_ascii=False) + print(txt[:3000]) + else: + print(" žádná data") + + # 4) Další globální proměnné + print("\n=== Další globální proměnné ===") + globals_check = await game_page.evaluate("""() => { + const names = ['puzzleData', 'dailyPuzzle', 'gameData', 'levels', + 'puzzle', 'boardData', 'board', 'grid', 'cells', + 'calcudokuData', 'calcudokuLevels', 'cages', 'groups']; + const found = {}; + for (const name of names) { + if (typeof window[name] !== 'undefined') { + found[name] = typeof window[name]; + } + } + return found; + }""") + print(json.dumps(globals_check, indent=2)) + + # 5) gameLevels — struktura klíčů a ukázka + print("\n=== gameLevels — struktura ===") + structure = await game_page.evaluate("""() => { + if (typeof gameLevels === 'undefined') return null; + const result = {}; + for (const diff of Object.keys(gameLevels)) { + const keys = Object.keys(gameLevels[diff]); + result[diff] = { + count: keys.length, + first_keys: keys.slice(0, 3), + last_keys: keys.slice(-3), + sample_value: gameLevels[diff][keys[0]] + }; + } + return result; + }""") + if structure: + print(json.dumps(structure, indent=2, ensure_ascii=False)[:5000]) + else: + print(" žádná struktura") + + # 6) Dnešní data + print("\n=== Dnešní data (05-08) ===") + today_data = await game_page.evaluate("""() => { + const key = '05-08'; + const result = {}; + if (typeof gameLevels === 'undefined') return null; + for (const diff of Object.keys(gameLevels)) { + if (gameLevels[diff] && gameLevels[diff][key]) { + result[diff] = gameLevels[diff][key]; + } + } + return result; + }""") + if today_data: + print(json.dumps(today_data, indent=2, ensure_ascii=False)[:5000]) + else: + print(" žádná data pro dnešek") + + await browser.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/SběrDatRůzné/DailyCalcudoku/stahni_calcudoku.py b/SběrDatRůzné/DailyCalcudoku/stahni_calcudoku.py new file mode 100644 index 0000000..8af794f --- /dev/null +++ b/SběrDatRůzné/DailyCalcudoku/stahni_calcudoku.py @@ -0,0 +1,96 @@ +""" +Stáhne daily Calcudoku puzzle data ze solitaire.org a uloží do MySQL. +""" + +import asyncio +import json +import sys + +sys.stdout.reconfigure(encoding="utf-8") +sys.stderr.reconfigure(encoding="utf-8") + +from datetime import date, timedelta +from pathlib import Path + +from playwright.async_api import async_playwright + +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Knihovny")) +from mysql_db import connect_mysql + +URL = "https://www.solitaire.org/daily-calcudoku/" +DIFFICULTIES = ["4x4", "5x5", "6x6", "8x8"] + + +async def fetch_all_levels() -> dict: + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + context = await browser.new_context(viewport={"width": 1280, "height": 900}) + + page = await context.new_page() + print(f"Načítám {URL} ...") + await page.goto(URL, wait_until="networkidle", timeout=60_000) + + game_url = None + for frame in page.frames: + if frame.url != page.url and frame.url.strip() not in ("", "about:blank"): + game_url = frame.url + break + + if not game_url: + iframe_src = await page.get_attribute("iframe", "src") + if iframe_src: + game_url = iframe_src if iframe_src.startswith("http") else f"https://www.solitaire.org{iframe_src}" + + await page.close() + + game_page = await context.new_page() + target_url = game_url if game_url else URL + print(f"Načítám hru: {target_url} ...") + await game_page.goto(target_url, wait_until="networkidle", timeout=60_000) + + raw = await game_page.evaluate("() => JSON.stringify(gameLevels)") + await browser.close() + + return json.loads(raw) + + +def save_to_mysql(game_levels: dict, start_date: date, end_date: date): + conn = connect_mysql(database="puzzle") + cur = conn.cursor() + + inserted = 0 + d = start_date + while d <= end_date: + mmdd = d.strftime("%m-%d") + date_str = d.strftime("%Y-%m-%d") + for diff in DIFFICULTIES: + if diff not in game_levels or mmdd not in game_levels[diff]: + continue + cages, solution = game_levels[diff][mmdd] + grid_size = int(diff.split("x")[0]) + cur.execute( + "INSERT IGNORE INTO puzzles (game_type, difficulty, puzzle_date, puzzle, solution, extra, source) " + "VALUES (%s, %s, %s, %s, %s, %s, %s)", + ("calcudoku", diff, date_str, cages, solution, + json.dumps({"grid_size": grid_size}), "solitaire.org"), + ) + if cur.rowcount > 0: + inserted += 1 + d += timedelta(days=1) + + cur.close() + conn.close() + return inserted + + +async def main(): + game_levels = await fetch_all_levels() + total = sum(len(game_levels.get(d, {})) for d in DIFFICULTIES) + print(f"gameLevels: {total} záznamů") + + inserted = save_to_mysql(game_levels, date(2026, 1, 1), date(2026, 12, 31)) + print(f"MySQL: vloženo {inserted} nových řádků (celý rok 2026)") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/SběrDatRůzné/DailyCalcudoku/vykresli_calcudoku.py b/SběrDatRůzné/DailyCalcudoku/vykresli_calcudoku.py new file mode 100644 index 0000000..ec02fe6 --- /dev/null +++ b/SběrDatRůzné/DailyCalcudoku/vykresli_calcudoku.py @@ -0,0 +1,208 @@ +""" +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() diff --git a/SběrDatRůzné/DailyKakuro/NOTES.md b/SběrDatRůzné/DailyKakuro/NOTES.md new file mode 100644 index 0000000..84d9776 --- /dev/null +++ b/SběrDatRůzné/DailyKakuro/NOTES.md @@ -0,0 +1,66 @@ +# DailyKakuro — technické poznámky + +## Přehled skriptů + +| Skript | Popis | +|--------|-------| +| `preskumaj_kakuro.py` | Průzkumný — vytáhne `gameLevels` z JS kontextu stránky | +| `stahni_kakuro.py` | Stáhne data z webu a uloží do MySQL (celý rok najednou) | +| `vykresli_kakuro.py` | Generuje PDF z dat v MySQL (reportlab, vektorové) | + +## Zdroj dat + +Stránka: https://www.solitaire.org/daily-kakuro/ + +Stejná architektura jako Str8ts — `game.php` načte `gameLevels` s daty pro celý rok (366 dní × 4 obtížnosti). Klíče `"MM-DD"`, bez roku. Data se nemění přes rok — stačí jednorázové stažení v lednu. + +## Obtížnosti a velikosti mřížek + +| Obtížnost | Rozměr mřížky | +|-----------|---------------| +| easy | 10×10 (data 9×9 + prepend) | +| medium | 10×10 | +| hard | 12×12 (data 11×11 + prepend) | +| expert | 12×12 | + +## Datová struktura `gameLevels` + +Každý záznam je jeden string — řádky oddělené čárkou: +``` +"098400970,986230849,850049095,613007810,..." +``` + +Při zpracování se: +1. Ke každému řádku přidá `0` na začátek +2. Přidá se nulový řádek na začátek (horní okraj) + +Význam číslic: +- `0` = černá/nápovědní buňka +- `1`–`9` = číslo řešení + +**Součty se neukládají** — vypočítají se z řešení: sečtou se čísla ve směru doprava/dolů až po další nulu. + +Data obsahují rovnou **řešení**, ne zadání. Zadání = prázdné bílé buňky + součty v černých. + +## MySQL tabulka `puzzle.puzzles` + +Sdílená tabulka se Str8ts. Pro Kakuro: +- `game_type` = `'kakuro'` +- `puzzle` = surový string (řešení + struktura) +- `solution` = stejný string (data jsou řešení) +- `extra` = `{"grid_size": 10}` nebo `{"grid_size": 12}` +- `source` = `'solitaire.org'` + +Stav: celý rok 2026 naplněn (1460 řádků = 365 dní × 4 obtížnosti). + +## Generování PDF (`vykresli_kakuro.py`) + +- Knihovna: **reportlab** — vektorová grafika +- Nápovědní buňky (černé): tmavé pozadí + diagonála + součty (bílý text) + - Součet doprava → horní pravý trojúhelník + - Součet dolů → dolní levý trojúhelník +- Font: ArialBold (podpora české diakritiky) + +## Bonus: `Game.combos` + +Na stránce je i tabulka `Game.combos` — možné kombinace číslic pro každý součet a počet buněk. Např. součet 17 ze 2 buněk = `"8+9"`. Dá se použít jako nápověda pro řešení. diff --git a/SběrDatRůzné/DailyKakuro/preskumaj_kakuro.py b/SběrDatRůzné/DailyKakuro/preskumaj_kakuro.py new file mode 100644 index 0000000..2a7ac1b --- /dev/null +++ b/SběrDatRůzné/DailyKakuro/preskumaj_kakuro.py @@ -0,0 +1,141 @@ +""" +Průzkumný skript: připojí se na solitaire.org/daily-kakuro/ a vytáhne +surová JS data o puzzle (gameLevels, Game objekt). +""" + +import asyncio +import json +import sys + +sys.stdout.reconfigure(encoding="utf-8") + +from playwright.async_api import async_playwright + +URL = "https://www.solitaire.org/daily-kakuro/" + + +async def main(): + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + context = await browser.new_context(viewport={"width": 1280, "height": 900}) + + page = await context.new_page() + print(f"Načítám {URL} ...") + await page.goto(URL, wait_until="networkidle", timeout=60_000) + + game_url = None + for frame in page.frames: + if frame.url != page.url and frame.url.strip() not in ("", "about:blank"): + game_url = frame.url + print(f" Nalezen iframe: {game_url}") + break + + if not game_url: + iframe_src = await page.get_attribute("iframe", "src") + if iframe_src: + game_url = iframe_src if iframe_src.startswith("http") else f"https://www.solitaire.org{iframe_src}" + print(f" Iframe src z DOM: {game_url}") + + await page.close() + + game_page = await context.new_page() + target_url = game_url if game_url else URL + print(f"Načítám hru: {target_url} ...") + await game_page.goto(target_url, wait_until="networkidle", timeout=60_000) + + # 1) gameLevels + print("\n=== gameLevels ===") + game_levels = await game_page.evaluate("""() => { + if (typeof gameLevels !== 'undefined') return JSON.stringify(gameLevels, null, 2); + return null; + }""") + if game_levels: + print(game_levels[:5000]) + if len(game_levels) > 5000: + print(f"... (celkem {len(game_levels)} znaků)") + else: + print(" gameLevels není definováno") + + # 2) Game objekt — klíče + print("\n=== Game objekt — klíče ===") + game_keys = await game_page.evaluate("""() => { + if (typeof Game !== 'undefined') return Object.keys(Game); + return null; + }""") + if game_keys: + print(json.dumps(game_keys, indent=2)) + else: + print(" Game není definováno") + + # 3) Game — datové vlastnosti + print("\n=== Game — datové vlastnosti ===") + game_data = await game_page.evaluate("""() => { + if (typeof Game === 'undefined') return null; + const result = {}; + for (const key of Object.keys(Game)) { + const val = Game[key]; + if (typeof val !== 'function') { + try { result[key] = JSON.parse(JSON.stringify(val)); } + catch(e) { result[key] = String(val); } + } + } + return result; + }""") + if game_data: + txt = json.dumps(game_data, indent=2, ensure_ascii=False) + print(txt[:3000]) + else: + print(" žádná data") + + # 4) Další globální proměnné + print("\n=== Další globální proměnné ===") + globals_check = await game_page.evaluate("""() => { + const names = ['puzzleData', 'dailyPuzzle', 'gameData', 'levels', + 'puzzle', 'boardData', 'board', 'grid', 'cells', + 'kakuroData', 'kakuroLevels']; + const found = {}; + for (const name of names) { + if (typeof window[name] !== 'undefined') { + found[name] = typeof window[name]; + } + } + return found; + }""") + print(json.dumps(globals_check, indent=2)) + + # 5) Game.printDaily zdrojový kód + print("\n=== Game.printDaily — zdrojový kód ===") + print_src = await game_page.evaluate("""() => { + if (typeof Game !== 'undefined' && typeof Game.printDaily === 'function') { + return Game.printDaily.toString(); + } + return null; + }""") + if print_src: + print(print_src[:3000]) + else: + print(" Game.printDaily neexistuje") + + # 6) Dnešní data — zkus první záznam + print("\n=== Dnešní data (05-08) ===") + today_data = await game_page.evaluate("""() => { + const key = '05-08'; + const result = {}; + if (typeof gameLevels === 'undefined') return null; + for (const diff of Object.keys(gameLevels)) { + if (gameLevels[diff] && gameLevels[diff][key]) { + result[diff] = gameLevels[diff][key]; + } + } + return result; + }""") + if today_data: + print(json.dumps(today_data, indent=2, ensure_ascii=False)[:5000]) + else: + print(" žádná data pro dnešek") + + await browser.close() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/SběrDatRůzné/DailyKakuro/stahni_kakuro.py b/SběrDatRůzné/DailyKakuro/stahni_kakuro.py new file mode 100644 index 0000000..e0ef8ee --- /dev/null +++ b/SběrDatRůzné/DailyKakuro/stahni_kakuro.py @@ -0,0 +1,103 @@ +""" +Stáhne daily Kakuro puzzle data ze solitaire.org a uloží do MySQL. +PDF generování bude doplněno později. +""" + +import asyncio +import json +import sys + +sys.stdout.reconfigure(encoding="utf-8") +sys.stderr.reconfigure(encoding="utf-8") + +from datetime import date +from pathlib import Path + +from playwright.async_api import async_playwright + +sys.path.insert(0, str(Path(__file__).parent.parent.parent / "Knihovny")) +from mysql_db import connect_mysql + +URL = "https://www.solitaire.org/daily-kakuro/" +DIFFICULTIES = ["easy", "medium", "hard", "expert"] + + +async def fetch_all_levels() -> dict: + async with async_playwright() as p: + browser = await p.chromium.launch(headless=True) + context = await browser.new_context(viewport={"width": 1280, "height": 900}) + + page = await context.new_page() + print(f"Načítám {URL} ...") + await page.goto(URL, wait_until="networkidle", timeout=60_000) + + game_url = None + for frame in page.frames: + if frame.url != page.url and frame.url.strip() not in ("", "about:blank"): + game_url = frame.url + break + + if not game_url: + iframe_src = await page.get_attribute("iframe", "src") + if iframe_src: + game_url = iframe_src if iframe_src.startswith("http") else f"https://www.solitaire.org{iframe_src}" + + await page.close() + + game_page = await context.new_page() + target_url = game_url if game_url else URL + print(f"Načítám hru: {target_url} ...") + await game_page.goto(target_url, wait_until="networkidle", timeout=60_000) + + raw = await game_page.evaluate("() => JSON.stringify(gameLevels)") + await browser.close() + + return json.loads(raw) + + +def grid_size(data_str: str) -> int: + """Vrátí rozměr mřížky z raw dat (počet sloupců prvního řádku + 1 pro prepended 0).""" + first_row = data_str.split(",")[0] + return len(first_row) + 1 + + +def save_to_mysql(game_levels: dict, start_date: date, end_date: date): + conn = connect_mysql(database="puzzle") + cur = conn.cursor() + + inserted = 0 + d = start_date + while d <= end_date: + mmdd = d.strftime("%m-%d") + date_str = d.strftime("%Y-%m-%d") + for diff in DIFFICULTIES: + if diff not in game_levels or mmdd not in game_levels[diff]: + continue + data_str = game_levels[diff][mmdd] + size = grid_size(data_str) + cur.execute( + "INSERT IGNORE INTO puzzles (game_type, difficulty, puzzle_date, puzzle, solution, extra, source) " + "VALUES (%s, %s, %s, %s, %s, %s, %s)", + ("kakuro", diff, date_str, data_str, data_str, + json.dumps({"grid_size": size}), "solitaire.org"), + ) + if cur.rowcount > 0: + inserted += 1 + d += __import__("datetime").timedelta(days=1) + + cur.close() + conn.close() + return inserted + + +async def main(): + today = date.today() + game_levels = await fetch_all_levels() + print(f"gameLevels: {sum(len(game_levels.get(d, {})) for d in DIFFICULTIES)} záznamů") + + inserted = save_to_mysql(game_levels, date(2026, 1, 1), date(2026, 12, 31)) + print(f"MySQL: vloženo {inserted} nových řádků (celý rok 2026)") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/SběrDatRůzné/DailyKakuro/vykresli_kakuro.py b/SběrDatRůzné/DailyKakuro/vykresli_kakuro.py new file mode 100644 index 0000000..af16644 --- /dev/null +++ b/SběrDatRůzné/DailyKakuro/vykresli_kakuro.py @@ -0,0 +1,161 @@ +""" +Vykreslí Kakuro puzzle do PDF z dat v MySQL tabulce puzzles. +""" + +import json +import os +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_kakuro.pdf" + + +def parse_grid(data_str: str) -> list[str]: + rows = ["0" + r for r in data_str.split(",")] + rows.insert(0, "0" * len(rows[0])) + return rows + + +def sum_hor(grid, x, y): + s = 0 + w = len(grid[0]) + for i in range(x + 1, w): + if grid[y][i] == "0": + break + s += int(grid[y][i]) + return s if s > 0 else 0 + + +def sum_vert(grid, x, y): + s = 0 + h = len(grid) + for i in range(y + 1, h): + if grid[i][x] == "0": + break + s += int(grid[i][x]) + return s if s > 0 else 0 + + +def draw_kakuro(c: Canvas, x0: float, y0: float, cell: float, + data_str: str, title: str = "", show_solution: bool = False): + grid = parse_grid(data_str) + h = len(grid) + w = len(grid[0]) + + clue_font = max(cell * 0.3, 5) + num_font = max(cell * 0.5, 6) + + if title: + c.setFont("ArialBold", 12) + c.drawString(x0, y0 + 5, title) + + for gy in range(h): + for gx in range(w): + cx = x0 + gx * cell + cy = y0 - (gy + 1) * cell + ch = grid[gy][gx] + + if ch == "0": + # Černá buňka + c.setFillColor(colors.Color(0.15, 0.15, 0.15)) + c.rect(cx, cy, cell, cell, fill=1, stroke=0) + + sv = sum_vert(grid, gx, gy) + sh = sum_hor(grid, gx, gy) + + if sv or sh: + # Diagonála + c.setStrokeColor(colors.white) + c.setLineWidth(0.5) + c.line(cx, cy + cell, cx + cell, cy) + c.setStrokeColor(colors.black) + + c.setFillColor(colors.white) + c.setFont("ArialBold", clue_font) + + if sh: + # Součet doprava — horní pravý trojúhelník + c.drawString(cx + cell * 0.52, cy + cell * 0.55, str(sh)) + + if sv: + # Součet dolů — dolní levý trojúhelník + c.drawString(cx + cell * 0.08, cy + cell * 0.08, str(sv)) + + else: + # Bílá buňka + if show_solution: + c.setFillColor(colors.black) + c.setFont("ArialBold", num_font) + c.drawCentredString(cx + cell / 2, cy + cell * 0.3, ch) + + # Mřížka + c.setStrokeColor(colors.black) + for i in range(h + 1): + c.setLineWidth(0.8) + c.line(x0, y0 - i * cell, x0 + w * cell, y0 - i * cell) + for i in range(w + 1): + c.setLineWidth(0.8) + c.line(x0 + i * cell, y0, x0 + i * cell, y0 - h * cell) + + +def main(): + conn = connect_mysql(database="puzzle") + cur = conn.cursor() + cur.execute( + "SELECT difficulty, puzzle, extra FROM puzzles " + "WHERE game_type='kakuro' AND puzzle_date='2026-05-08' " + "ORDER BY FIELD(difficulty, 'easy', 'medium', 'hard', 'expert') " + "LIMIT 1" + ) + row = cur.fetchone() + cur.close() + conn.close() + + if not row: + print("Žádná data.") + return + + difficulty, data_str, extra = row + grid = parse_grid(data_str) + grid_size = len(grid[0]) + + 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í (bez čísel) + x0 = (page_w - board) / 2 + y0 = page_h - 2 * cm + draw_kakuro(c, x0, y0, cell, data_str, + f"Kakuro {difficulty.capitalize()} — 2026-05-08", show_solution=False) + + # Řešení (s čísly) + y0_sol = y0 - board - 3 * cm + draw_kakuro(c, x0, y0_sol, cell, data_str, + f"Řešení", show_solution=True) + + c.save() + print(f"PDF uloženo: {OUTPUT}") + + +if __name__ == "__main__": + main() diff --git a/SběrDatRůzné/DailyStr8ts/NOTES.md b/SběrDatRůzné/DailyStr8ts/NOTES.md index 1158a29..c1a0cf0 100644 --- a/SběrDatRůzné/DailyStr8ts/NOTES.md +++ b/SběrDatRůzné/DailyStr8ts/NOTES.md @@ -1,13 +1,21 @@ # DailyStr8ts — technické poznámky -Skript `stahni_str8ts.py` stahuje denní Str8ts puzzle jako PDF ze https://www.solitaire.org/daily-str8ts/ a odesílá emailem. +## Přehled skriptů + +| Skript | Popis | +|--------|-------| +| `stahni_str8ts.py` | Stahuje denní puzzle jako PDF z webu, odesílá emailem | +| `preskumaj_str8ts.py` | Průzkumný — vytáhne `gameLevels` z JS kontextu stránky | +| `vykresli_puzzle.py` | Generuje vlastní PDF z dat v MySQL (reportlab, vektorové) | +| `vykresli_velikosti.py` | Testovací — jedno puzzle v 17 velikostech (18–2 cm) | +| `create_puzzles_table.sql` | DDL pro tabulku `puzzle.puzzles` | + +## Stahování z webu (`stahni_str8ts.py`) **Výstup:** `U:\Dropbox\!!!Days\Downloads Z230\yyyy-mm-dd Daily Str8ts puzzle.pdf` **Příjemci:** vladimir.buzalka@buzalka.cz, alica.buzalkova@buzalka.cz -## Jak to funguje (klíčové) - Stránka `game.php` má funkci `Game.printDaily()` která: 1. Vytvoří skrytý `