Joueur IA Escampe (Puyaubreau/Russac) — version finale
Joueur alpha-bêta + iterative deepening pour le tournoi APP5 « IA et contraintes ». - src/escampe/ : joueur (IJoueur), moteur (alpha-bêta + DFS bitmask, make/unmake sans allocation), modèle EscampeBoard (Partie1), utilitaires de test. - Protocole arbitre vérifié (pass="E", carte des liserés identique au serveur, machine à états placement/jeu) ; 7/7 victoires vs joueur aléatoire, 0 illégal. - Vérifications : VerifMoves (int≡String, 0 divergence/142k positions), RulesTest (21/21), Branching (facteur de branchement mesuré). - Rapport : report/rapport.html + tools/make_report_pdf.py (PyMuPDF) → PDF, RAPPORT.md. - Livrables buildés inclus (dist/ : jar, mainClass, tgz, rapport PDF) + lib/escampeobf.jar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
125
tools/make_report_pdf.py
Normal file
125
tools/make_report_pdf.py
Normal file
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Génère le rapport PDF à partir de report/rapport.html, avec PyMuPDF (fitz).
|
||||
|
||||
Aucune dépendance externe : ni pandoc, ni LaTeX, ni navigateur. On utilise
|
||||
l'API fitz.Story (rendu HTML/CSS -> PDF multi-pages) puis une seconde passe
|
||||
pour le pied de page et les numéros de page.
|
||||
|
||||
python tools/make_report_pdf.py
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import fitz # PyMuPDF
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
HTML = os.path.join(ROOT, "report", "rapport.html")
|
||||
OUT = os.path.join(ROOT, "dist", "Puyaubreau_Russac_rapport.pdf")
|
||||
|
||||
# Feuille de style : mise en page A4 sobre, titres colorés, tables et blocs <pre>
|
||||
# en Courier pour aligner diagrammes et carte des liserés.
|
||||
CSS = """
|
||||
* { font-family: serif; }
|
||||
body { font-size: 10.5pt; line-height: 1.45; color: #1a1a1a; }
|
||||
|
||||
h1, h2, h3, .cover-title, .cover-course, .cover-sub, th { font-family: sans-serif; }
|
||||
|
||||
h2 { font-size: 15pt; color: #1c3d5a; margin: 18pt 0 6pt 0;
|
||||
border-bottom: 1.5px solid #1c3d5a; padding-bottom: 2pt; }
|
||||
h3 { font-size: 12pt; color: #2a6f97; margin: 12pt 0 3pt 0; }
|
||||
p { margin: 5pt 0; text-align: justify; }
|
||||
ul, ol { margin: 4pt 0 4pt 0; }
|
||||
li { margin: 2pt 0; }
|
||||
|
||||
code { font-family: monospace; font-size: 9.5pt; color: #8a2846; }
|
||||
|
||||
pre.grid { font-family: monospace; font-size: 9pt; line-height: 1.25;
|
||||
background: #f4f6f8; border: 1px solid #d3dae0; border-radius: 3px;
|
||||
padding: 7pt; margin: 6pt 0; color: #14213d; white-space: pre; }
|
||||
|
||||
table { border-collapse: collapse; width: 100%; margin: 7pt 0; font-size: 9.5pt; }
|
||||
th { background: #1c3d5a; color: #ffffff; text-align: left; padding: 4pt 6pt; }
|
||||
td { border: 1px solid #c5ccd3; padding: 4pt 6pt; vertical-align: top; }
|
||||
tr:nth-child(even) td { background: #f4f6f8; }
|
||||
|
||||
p.note { background: #fff8e6; border-left: 3px solid #e0a526;
|
||||
padding: 5pt 8pt; margin: 7pt 0; font-size: 9.8pt; }
|
||||
|
||||
/* Éviter qu'un bloc préformaté ou une table soit coupé entre deux pages. */
|
||||
pre.grid, table, tr { page-break-inside: avoid; }
|
||||
h2, h3 { page-break-after: avoid; }
|
||||
.cover { page-break-after: always; }
|
||||
|
||||
/* Page de titre */
|
||||
.cover { text-align: center; padding-top: 40pt; }
|
||||
.cover-univ { font-size: 10pt; color: #555; margin-bottom: 30pt; }
|
||||
.cover-course { font-size: 13pt; color: #2a6f97; letter-spacing: 1pt; }
|
||||
.cover-title { font-size: 30pt; color: #1c3d5a; margin: 8pt 0 0 0; }
|
||||
.cover-sub { font-size: 12pt; color: #333; margin: 4pt 0; }
|
||||
.cover-authors{ font-size: 15pt; color: #14213d; margin-top: 34pt; font-family: sans-serif; }
|
||||
.cover-date { font-size: 11pt; color: #555; margin-top: 6pt; }
|
||||
.cover-meta { font-size: 9.5pt; color: #555; margin-top: 34pt; line-height: 1.6; }
|
||||
"""
|
||||
|
||||
MARGIN = 48 # marge en points (1pt = 1/72")
|
||||
FOOTER = "Escampe — Puyaubreau / Russac — version finale"
|
||||
|
||||
|
||||
def build():
|
||||
with open(HTML, "r", encoding="utf-8") as f:
|
||||
html = f.read()
|
||||
|
||||
os.makedirs(os.path.dirname(OUT), exist_ok=True)
|
||||
|
||||
mediabox = fitz.paper_rect("a4")
|
||||
where = mediabox + (MARGIN, MARGIN, -MARGIN, -MARGIN)
|
||||
|
||||
# 1) Rendu du flux HTML en pages PDF via Story.
|
||||
writer = fitz.DocumentWriter(OUT)
|
||||
story = fitz.Story(html=html, user_css=CSS)
|
||||
more = 1
|
||||
pages = 0
|
||||
while more:
|
||||
dev = writer.begin_page(mediabox)
|
||||
more, _ = story.place(where)
|
||||
story.draw(dev)
|
||||
writer.end_page()
|
||||
pages += 1
|
||||
writer.close()
|
||||
|
||||
# 2) Seconde passe : pied de page + numéros « page X / N ».
|
||||
doc = fitz.open(OUT)
|
||||
n = doc.page_count
|
||||
for i, page in enumerate(doc, start=1):
|
||||
y = page.rect.height - 26
|
||||
page.insert_text((MARGIN, y), FOOTER, fontname="helv",
|
||||
fontsize=7.5, color=(0.45, 0.45, 0.45))
|
||||
label = f"page {i} / {n}"
|
||||
w = fitz.get_text_length(label, fontname="helv", fontsize=7.5)
|
||||
page.insert_text((page.rect.width - MARGIN - w, y), label,
|
||||
fontname="helv", fontsize=7.5, color=(0.45, 0.45, 0.45))
|
||||
doc.saveIncr()
|
||||
doc.close()
|
||||
return pages, n
|
||||
|
||||
|
||||
def verify():
|
||||
"""Contrôle que les accents survivent au rendu (round-trip texte)."""
|
||||
doc = fitz.open(OUT)
|
||||
full = "".join(p.get_text() for p in doc)
|
||||
doc.close()
|
||||
# Mots accentués présents tels quels dans report/rapport.html.
|
||||
probes = ["liseré", "Présentation", "élagage", "Modélisation",
|
||||
"stratégique", "approfondissement", "Puyaubreau"]
|
||||
missing = [s for s in probes if s not in full]
|
||||
return missing, len(full)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pages, n = build()
|
||||
missing, chars = verify()
|
||||
print(f"PDF écrit : {OUT}")
|
||||
print(f"Pages : {n} | texte extrait : {chars} caractères")
|
||||
if missing:
|
||||
print("ATTENTION, chaînes accentuées introuvables après rendu :", missing)
|
||||
sys.exit(1)
|
||||
print("Accents vérifiés (round-trip OK).")
|
||||
Reference in New Issue
Block a user