L'application est à la racine du site : plateau en SVG, saisie au toucher (pion puis case d'arrivée, proposées à partir du moteur), boutons du jet, du videau et de nouvelle partie, coup de l'ordinateur affiché avec son temps, fin de partie, reprise depuis localStorage, aide lue dans les copies de docs/. Le moteur tourne dans le Web Worker existant ; main.ts est le seul assembleur (nouvelles règles dependency-cruiser). Le service worker met tout le site en cache, avec une empreinte du contenu comme version. Les pages de test du moteur passent sous /moteur/. Politique de sécurité du contenu dans le Caddyfile ; make test-browser joue une partie dans Chromium et Firefox sans écran (Playwright, hors de make test). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
||
# Produit les icônes PNG de l'application (web/icons/icon-192.png et
|
||
# icon-512.png) à partir des formes de web/icons/icon.svg : un fond de bois
|
||
# et deux pions. Icônes provisoires, en attendant l'identité définitive.
|
||
#
|
||
# Python seul, sans bibliothèque : chaque pixel est échantillonné 4 × 4 fois
|
||
# pour adoucir les bords. À relancer seulement si les formes changent, dans
|
||
# la chaîne C (Python 3) :
|
||
# docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/src" -w /src \
|
||
# ustagammon/build-c:2 python3 scripts/make-icons.py
|
||
#
|
||
# Copyright (C) 2026 les auteurs d'Ustagammon.
|
||
# Licence : GPLv3 ou ultérieure (voir LICENSE).
|
||
|
||
import struct
|
||
import zlib
|
||
|
||
BACKGROUND = (0x6B, 0x44, 0x26)
|
||
# Pions dessinés dans l'ordre : (centre x, centre y, rayon, remplissage,
|
||
# bord), en unités d'une icône de 512 pixels ; bord de 10 unités.
|
||
CHECKERS = [
|
||
(300, 300, 120, (0x2B, 0x22, 0x1C), (0x0D, 0x0A, 0x08)),
|
||
(212, 212, 120, (0xFB, 0xF6, 0xEA), (0x7D, 0x6A, 0x52)),
|
||
]
|
||
EDGE = 10
|
||
SAMPLES = 4
|
||
|
||
|
||
def color_at(x, y):
|
||
"""Couleur d'un point de l'icône, en unités de 512."""
|
||
color = BACKGROUND
|
||
for cx, cy, r, fill, edge in CHECKERS:
|
||
d2 = (x - cx) ** 2 + (y - cy) ** 2
|
||
if d2 <= (r + EDGE / 2) ** 2:
|
||
color = fill if d2 <= (r - EDGE / 2) ** 2 else edge
|
||
return color
|
||
|
||
|
||
def render(size):
|
||
scale = 512 / size
|
||
rows = []
|
||
for py in range(size):
|
||
row = bytearray([0]) # filtre PNG « aucun »
|
||
for px in range(size):
|
||
total = [0, 0, 0]
|
||
for sy in range(SAMPLES):
|
||
for sx in range(SAMPLES):
|
||
x = (px + (sx + 0.5) / SAMPLES) * scale
|
||
y = (py + (sy + 0.5) / SAMPLES) * scale
|
||
for i, c in enumerate(color_at(x, y)):
|
||
total[i] += c
|
||
row += bytes(round(t / SAMPLES**2) for t in total)
|
||
rows.append(bytes(row))
|
||
return b"".join(rows)
|
||
|
||
|
||
def png(size, pixels):
|
||
def chunk(kind, data):
|
||
body = kind + data
|
||
return struct.pack(">I", len(data)) + body + struct.pack(">I", zlib.crc32(body))
|
||
|
||
header = struct.pack(">IIBBBBB", size, size, 8, 2, 0, 0, 0) # RVB, 8 bits
|
||
return (
|
||
b"\x89PNG\r\n\x1a\n"
|
||
+ chunk(b"IHDR", header)
|
||
+ chunk(b"IDAT", zlib.compress(pixels, 9))
|
||
+ chunk(b"IEND", b"")
|
||
)
|
||
|
||
|
||
for size in (192, 512):
|
||
with open(f"web/icons/icon-{size}.png", "wb") as f:
|
||
f.write(png(size, render(size)))
|