Compare commits
24
Commits
9b79b54880
...
etape-1
@@ -1,7 +1,7 @@
|
||||
# CLAUDE.md — Règles du projet
|
||||
|
||||
Ce fichier est lu par Claude Code au début de chaque session. Il fixe le cadre du projet.
|
||||
Le cadrage complet est dans `docs/cadrage.md`, l'étape en cours dans `docs/etape-0.md`.
|
||||
Le cadrage complet est dans `docs/cadrage.md`, le travail en cours dans `docs/feuille-de-route.md`.
|
||||
|
||||
**En début de session**, lire `docs/organisation.md`, `docs/feuille-de-route.md`, la fin
|
||||
de `docs/journal.md`, l'index des ADR (`docs/adr/README.md`) et les plans en cours dans `docs/plans/`, puis reprendre là où en
|
||||
|
||||
+8
-1
@@ -27,8 +27,15 @@ Les contenus pédagogiques (leçons, textes) sont sous licence CC BY-SA 4.0.
|
||||
(Juan Pablo del Peral, Huerta Tipográfica), licence
|
||||
SIL Open Font License 1.1 — https://github.com/google/fonts/tree/main/ofl/alegreyasans
|
||||
Ses tracés (graisse Bold) composent le logotype, dans `docs/identite/logo/`.
|
||||
Cinq graisses (Regular, Italic, Medium, Bold, ExtraBold) sont embarquées
|
||||
dans l'application (`web/fonts/`, avec la licence `OFL.txt`), réduites aux
|
||||
caractères latins et converties en woff2 par fontTools (143 Ko en tout).
|
||||
- **Logo et icônes d'Ustagammon** (`docs/identite/logo/`), licence
|
||||
CC BY-SA 4.0 ; publiés dans `icons/` du site (favicon, icônes de la PWA,
|
||||
logo de la barre haute).
|
||||
- **fontTools** (MIT), utilisé par `docs/identite/generer-logo.py` pour
|
||||
convertir le texte du logotype en tracés.
|
||||
convertir le texte du logotype en tracés, et pour produire les fichiers
|
||||
woff2 de la police ; avec **Brotli** (MIT), qui les compresse.
|
||||
|
||||
## Outils de développement
|
||||
|
||||
|
||||
@@ -115,9 +115,17 @@ deploy: site ## Sert l'application sur le réseau local (Ctrl+C pour arrêter)
|
||||
|
||||
deploy-web: deploy ## Comme make deploy (ancien nom, étape 0b)
|
||||
|
||||
# Fichiers de docs/identite/logo/ publiés dans icons/ (manifeste, favicon,
|
||||
# barre haute).
|
||||
SITE_ICONS := icone.svg icone-192.png icone-512.png icone-maskable-192.png icone-maskable-512.png \
|
||||
apple-touch-icon-180.png favicon.svg favicon-32.png \
|
||||
logo-horizontal-couleur.svg logo-horizontal-sombre.svg symbole-couleur.svg symbole-sombre.svg
|
||||
|
||||
# Le site tel qu'il sera publié, reconstruit entièrement à chaque appel :
|
||||
# - web/ à la racine : la page de l'application, son style, le manifeste, les
|
||||
# icônes, le Web Worker du moteur, et les pages de test sous moteur/ ;
|
||||
# - web/ à la racine : la page de l'application, son style, sa police, le
|
||||
# manifeste, le Web Worker du moteur, et les pages de test sous moteur/ ;
|
||||
# - sous icons/, le logo et les icônes de la charte, copiés depuis
|
||||
# docs/identite/logo/ (une seule source, ADR 0010) ;
|
||||
# - l'application compilée (build/web/src/) sous app/ ;
|
||||
# - le moteur sous wasm/ ;
|
||||
# - les copies du glossaire et des scénarios sous docs/, lues par l'aide de
|
||||
@@ -128,6 +136,8 @@ site: engine-wasm web ## Assemble le site publié dans build/site/
|
||||
rm -rf $(BUILD_DIR)/site
|
||||
mkdir -p $(BUILD_DIR)/site/wasm $(BUILD_DIR)/site/docs
|
||||
cp -r web/. $(BUILD_DIR)/site/
|
||||
mkdir -p $(BUILD_DIR)/site/icons
|
||||
cp $(addprefix docs/identite/logo/,$(SITE_ICONS)) $(BUILD_DIR)/site/icons/
|
||||
cp -r $(BUILD_DIR)/web/src $(BUILD_DIR)/site/app
|
||||
cp $(BUILD_DIR)/wasm/engine.mjs $(BUILD_DIR)/wasm/engine.wasm $(BUILD_DIR)/wasm/engine.data \
|
||||
$(BUILD_DIR)/wasm/positions.json $(BUILD_DIR)/site/wasm/
|
||||
|
||||
@@ -52,3 +52,9 @@ construire ou tester ; rien n'est embarqué dans l'application.
|
||||
Validés par Pierre avec l'ADR 0009, dans Docker, jamais embarqués :
|
||||
TypeScript (`tsc`, Apache-2.0), Biome (MIT ou Apache-2.0),
|
||||
`@cucumber/cucumber` (MIT), `dependency-cruiser` (MIT).
|
||||
|
||||
### 2026-11-03 — Playwright pour les tests dans le navigateur
|
||||
|
||||
Validé par Pierre : Playwright 1.48.2 (Apache-2.0), image
|
||||
`mcr.microsoft.com/playwright:v1.48.2-noble`, utilisé seulement par
|
||||
`make test-browser` (Chromium et Firefox sans écran). Rien n'est embarqué.
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# 0011. Leçons numérotées, conseil du maître après le coup, bilan de fin de partie
|
||||
|
||||
- **Statut** : accepté
|
||||
- **Date** : 2026-11-03
|
||||
- **Décideurs** : Pierre
|
||||
|
||||
## Contexte
|
||||
|
||||
Le cadrage (`docs/cadrage.md`) prévoit pour la V1 un coach en direct, un écran
|
||||
de fin de partie et des leçons par concept, sans en fixer la forme. Les
|
||||
maquettes des écrans (https://claude.ai/artifact/CQw6vbDpi6XQqVWQa4sANe),
|
||||
faites avec la charte graphique (ADR 0010), en ont proposé une. Pierre en a
|
||||
retenu trois éléments le 2026-11-03.
|
||||
|
||||
Un point des maquettes contredisait le cadrage : le conseil apparaissait avant
|
||||
que le joueur ne joue, en donnant la réponse. Pierre a tranché pour un
|
||||
commentaire après le coup.
|
||||
|
||||
## Décision
|
||||
|
||||
- **Leçons numérotées** : un parcours de leçons dans un ordre fixe
|
||||
(« Leçon 1 », « Leçon 2 »…). Chaque leçon affiche son état (à commencer,
|
||||
« 3 exercices sur 5 », terminée) et sa progression. L'accueil propose de
|
||||
continuer la leçon en cours.
|
||||
- **Conseil du maître** : c'est la forme du coach en direct du cadrage. Il
|
||||
commente le coup **après** que le joueur l'a joué, jamais avant. Il donne
|
||||
l'appréciation du moteur avec un mot et une icône (bon coup, imprécision,
|
||||
erreur), puis une phrase du maître qui dit quel coup il aurait joué et
|
||||
pourquoi, avec un lien pour en savoir plus. L'écart d'équité n'apparaît que
|
||||
sur demande. Il reste désactivable, comme le prévoit le cadrage.
|
||||
- **Bilan de fin de partie** : le résultat, le décompte des coups par
|
||||
appréciation (bons coups, imprécisions, erreurs), « le coup à revoir »
|
||||
(la plus grosse erreur, avec sa position, le coup joué et le meilleur coup)
|
||||
et le prompt prêt à copier pour une IA.
|
||||
|
||||
Les maquettes de référence sont les écrans « Plateau de jeu », « Leçon et
|
||||
aide » et « Fin de partie » du canevas ci-dessus.
|
||||
|
||||
## Alternatives écartées
|
||||
|
||||
- **Conseil avant le coup, avec la réponse** : le joueur n'essaie plus par
|
||||
lui-même, et on n'apprend pas en recopiant.
|
||||
- **Indice avant le coup, sans la réponse, en plus du commentaire après** :
|
||||
écarté pour l'instant au profit d'un seul mécanisme simple. L'« indice » de
|
||||
la liste des fonctions du cadrage reste à concevoir séparément.
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Le commentaire après le coup demande une analyse du coup joué par le moteur
|
||||
(classement des coups et écart avec le meilleur). Elle doit tenir dans le
|
||||
budget de temps du téléphone, en arrière-plan, sans retarder le tour
|
||||
suivant.
|
||||
- Les seuils entre « bon coup », « imprécision » et « erreur » sont à fixer,
|
||||
en s'appuyant sur ceux de GNU Backgammon (doubtful, bad, very bad).
|
||||
- Les phrases du maître doivent rester exactes : elles s'appuient sur les
|
||||
coups et les chiffres du moteur, jamais sur une explication inventée.
|
||||
- Les leçons sont des contenus sous CC BY-SA 4.0, à écrire et à faire relire
|
||||
par un joueur fort (cadrage, risque « contenus longs à produire ») ; leur
|
||||
progression est stockée localement.
|
||||
@@ -0,0 +1,79 @@
|
||||
# 0012. Mode immersif : le plateau en paysage, sur demande
|
||||
|
||||
- **Statut** : accepté
|
||||
- **Date** : 2026-11-03
|
||||
- **Décideurs** : Pierre
|
||||
|
||||
## Contexte
|
||||
|
||||
Sur l'écran de jeu en portrait, le plateau n'occupe qu'environ 35 % de l'écran
|
||||
d'un téléphone (pions de 24 px sur un écran de 390 px de large). Pierre le
|
||||
trouve peu immersif et a demandé un mode où le plateau prend le maximum de
|
||||
l'écran.
|
||||
|
||||
Deux pistes ont été maquettées
|
||||
(https://claude.ai/artifact/CQw6vbDpi6XQqVWQa4sANe, rangée « Mode immersif ») :
|
||||
le téléphone en paysage, plateau sur toute la hauteur ; ou le portrait avec le
|
||||
plateau tourné d'un quart de tour. Toutes deux donnent environ 57 % de l'écran
|
||||
au plateau et des pions de 30 px : le petit côté de l'écran limite la taille
|
||||
des pions dans les deux cas.
|
||||
|
||||
## Décision
|
||||
|
||||
- Un **mode immersif en paysage** : le plateau occupe toute la hauteur, dans
|
||||
son orientation habituelle. À gauche : le menu, les comptes de points et la
|
||||
consigne. À droite : l'aide et les actions. Le fond est en noyer jusqu'aux
|
||||
bords de l'écran.
|
||||
- On y entre **par un geste explicite** : un bouton « Plein écran » sur l'écran
|
||||
de jeu en portrait. On en sort par le menu ou par le retour du téléphone.
|
||||
- Le bouton force la vue paysage même si le téléphone est tenu droit ou si sa
|
||||
rotation automatique est verrouillée : passage en plein écran
|
||||
(`requestFullscreen`), puis verrouillage de l'orientation
|
||||
(`screen.orientation.lock("landscape")`).
|
||||
- Si le verrouillage est refusé (navigateur ou système qui ne le permet pas,
|
||||
iPhone), l'app affiche la vue paysage tournée d'un quart de tour dans l'écran
|
||||
portrait. Le résultat est le même en main.
|
||||
- L'écran de jeu en portrait reste la vue par défaut, pour jouer d'une main.
|
||||
|
||||
## Alternatives écartées
|
||||
|
||||
- **Plateau tourné en portrait** : il change la lecture du plateau (numéros de
|
||||
côté, camps à gauche et à droite) et risque de dérouter les débutants habitués
|
||||
à une vraie table.
|
||||
- **Bascule automatique quand on tourne le téléphone** : elle ne marche pas si
|
||||
la rotation est verrouillée, et elle surprend en cours de partie. Elle reste
|
||||
possible en plus du bouton, plus tard.
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Le rendu du plateau doit s'adapter à deux mises en page (portrait et paysage)
|
||||
sans changer le domaine ni le moteur : seule la vue change (ADR 0007).
|
||||
- `screen.orientation.lock` n'est accepté qu'en plein écran, et pas partout :
|
||||
la solution de repli par rotation CSS est obligatoire, et elle doit être
|
||||
testée (`make test-browser`).
|
||||
- Le plein écran masque la barre d'état du téléphone, ce qui libère encore un
|
||||
peu de place.
|
||||
|
||||
## Amendements
|
||||
|
||||
### 2026-11-03 — Vue paysage dès la rotation, plateau = écran
|
||||
|
||||
Décidé par Pierre après son premier essai sur le téléphone, où la vue paysage
|
||||
à deux colonnes (plateau limité à 70 % de la largeur, texte à droite) lui a
|
||||
paru « vraiment pas optimale ».
|
||||
|
||||
- Dès que le téléphone est en paysage, même sans plein écran, l'app applique
|
||||
la mise en page « plateau = écran » sur la hauteur réelle (`100dvh`, barre
|
||||
d'adresse comprise). La mise en page à deux colonnes disparaît.
|
||||
- Dans cette vue, les contrôles sont dans le plateau : comptes de points sur la
|
||||
barre, videau au milieu de la barre, dés et consigne dans la bande centrale,
|
||||
pictogrammes (menu, annuler, plein écran) dans la colonne des sorties
|
||||
(maquette « Immersif : plein écran, contrôles dans le plateau » du canevas
|
||||
ci-dessus). La géométrie du plateau suit les proportions de l'écran ; les
|
||||
numéros des flèches font au moins 12 px.
|
||||
- Le bouton « Plein écran » garde son rôle : plein écran, verrouillage en
|
||||
paysage, repli par rotation CSS.
|
||||
|
||||
La bascule automatique écartée plus haut était celle qui forçait
|
||||
l'orientation ; ici, seule la mise en page suit l'orientation choisie par le
|
||||
joueur.
|
||||
@@ -17,3 +17,5 @@ l'ancien passe au statut « remplacé par NNNN ». Voir l'ADR 0001.
|
||||
| [0008](0008-glossaire-et-scenarios-source-unique.md) | Glossaire et scénarios : une source unique pour le code, l'aide et les tests | accepté |
|
||||
| [0009](0009-socle-pwa-typescript.md) | Socle : PWA en TypeScript, moteur en WebAssembly | accepté |
|
||||
| [0010](0010-identite-visuelle.md) | Identité visuelle : symbole, logotype, palette | accepté |
|
||||
| [0011](0011-lecons-conseil-et-bilan.md) | Leçons numérotées, conseil du maître après le coup, bilan de fin de partie | accepté |
|
||||
| [0012](0012-mode-immersif.md) | Mode immersif : le plateau en paysage, sur demande | accepté |
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# Étape 0 — Prototype technique
|
||||
|
||||
**But** : prouver que la chaîne technique fonctionne avant d'investir dans le design, et trancher entre deux socles : PWA (moteur en WebAssembly) ou Android natif.
|
||||
|
||||
**Règle** : chaque sous-étape se termine par une démonstration que le développeur peut lancer lui-même avec une commande `make`.
|
||||
|
||||
---
|
||||
|
||||
## 0a. Le moteur en ligne de commande
|
||||
|
||||
**Objectif** : interroger le moteur GNU Backgammon sur une position, depuis le PC.
|
||||
|
||||
À faire :
|
||||
1. Récupérer les sources du moteur (choisir entre gnubg, gnubg-nn ou le moteur extrait par CBG ; justifier le choix dans un ADR, `docs/adr/`).
|
||||
2. Définir une **interface C minimale** du moteur (`engine/include/engine.h`, identifiants en anglais : ADR 0007), indépendante de toute interface graphique :
|
||||
- initialiser le moteur (charger réseaux, base de fin de partie, table d'équité de match) ;
|
||||
- lister les coups légaux pour une position et des dés ;
|
||||
- donner le meilleur coup et le classement des coups avec leur équité, pour un niveau d'évaluation donné ;
|
||||
- donner la décision de videau (pas de double, double/prend, double/passe, avec équités) ;
|
||||
- évaluer une position (probabilités de gain, gammon, backgammon).
|
||||
Les positions entrent et sortent au format **GNU Position ID + Match ID**.
|
||||
3. Écrire `engine-cli`, un petit programme qui expose ces fonctions en ligne de commande, avec une sortie JSON.
|
||||
4. Générer un jeu de **positions de référence** (au moins 30 : ouvertures, contact, course, sortie, décisions de videau) avec le GNU Backgammon de bureau dans Docker, et stocker les résultats attendus dans `tests/reference/`.
|
||||
5. `make test` compare `engine-cli` aux références (même meilleur coup, équités à une tolérance près, à fixer).
|
||||
|
||||
**Démo** : `make engine && ./build/engine-cli bestmove <position_id> <match_id> 31` affiche le meilleur coup en JSON ; `make test` passe.
|
||||
|
||||
---
|
||||
|
||||
## 0b. Le test WebAssembly (décisif)
|
||||
|
||||
**Objectif** : savoir si le moteur tourne assez vite et léger dans le navigateur du téléphone.
|
||||
|
||||
À faire :
|
||||
1. Compiler le même moteur et la même interface C en WebAssembly avec Emscripten (dans Docker).
|
||||
2. Charger le moteur dans un **Web Worker** (jamais sur le fil d'affichage).
|
||||
3. Page HTML minimale : un champ Position ID, des dés, un bouton « meilleur coup », l'affichage du résultat et du temps de calcul.
|
||||
4. Rejouer les positions de référence dans le navigateur : les résultats doivent concorder avec 0a.
|
||||
5. `make deploy-web` sert la page sur le réseau local pour l'ouvrir depuis le Fairphone 4.
|
||||
6. `make bench` (ou une page de mesure) produit un petit rapport : temps par coup selon le niveau d'évaluation, taille téléchargée, mémoire utilisée.
|
||||
|
||||
**Critères de réussite (à ajuster après mesure)** :
|
||||
|
||||
| Mesure sur le Fairphone 4 | Seuil |
|
||||
| --- | --- |
|
||||
| Meilleur coup, niveau rapide (0-ply GNU) | moins de 0,3 s |
|
||||
| Meilleur coup, niveau fort (2-ply GNU) | moins de 3 s |
|
||||
| Décision de videau, niveau fort | moins de 3 s |
|
||||
| Taille totale à télécharger (wasm + données, compressé) | moins de 40 Mo |
|
||||
| Résultats identiques aux références de 0a | 100 % |
|
||||
|
||||
**Démo** : la page ouverte sur le Fairphone donne le meilleur coup d'une position de référence, avec le temps de calcul affiché.
|
||||
|
||||
---
|
||||
|
||||
## 0c. Android natif (seulement si 0b échoue)
|
||||
|
||||
**Objectif** : la même démonstration en application Android.
|
||||
|
||||
À faire :
|
||||
1. Compiler le moteur pour arm64 avec le NDK (dans Docker), en s'appuyant sur la configuration de CBG.
|
||||
2. Application Android minimale, construite en ligne de commande (Gradle sans Android Studio, `-j2`, démon Gradle limité en mémoire).
|
||||
3. Même écran de test et mêmes mesures qu'en 0b.
|
||||
4. `make deploy` installe l'APK sur le Fairphone via adb.
|
||||
|
||||
---
|
||||
|
||||
## 0d. Plateau minimal et décision
|
||||
|
||||
1. Sur le socle retenu, un **plateau minimal** : une partie complète jouable contre le moteur, coups et videau décidés par le moteur, dés tirés par l'app.
|
||||
2. Rédiger un ADR (`docs/adr/`) : socle retenu (PWA ou Android natif), mesures obtenues, raisons, risques restants.
|
||||
|
||||
**Critère de fin de l'étape 0** : une partie complète jouée sur le Fairphone 4, moins de 2 s par coup de l'IA en niveau standard, tous les tests au vert.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Étape 1 — V1 « Jouer et comprendre »
|
||||
|
||||
Document temporaire : il décrit l'étape en cours et sera supprimé à sa clôture,
|
||||
après report de ce qui est durable dans les ADR et le journal.
|
||||
|
||||
**Critère de fin** (cadrage) : un débutant apprend et joue seul ; un joueur de
|
||||
club comprend ses erreurs. Livré avec un plan de test (`docs/guide-test-etape-1.md`)
|
||||
que Pierre déroule sur le Fairphone 4.
|
||||
|
||||
**Préalable** : les sous-étapes 0e (retours du premier test) de la feuille de
|
||||
route, dont la revue UX validée par Pierre.
|
||||
|
||||
## Périmètre
|
||||
|
||||
Repris du cadrage (modules, écrans clés, IA générative en V1) et des ADR 0010,
|
||||
0011 et 0012. Ce qui n'y figure pas est hors de l'étape 1.
|
||||
|
||||
| Sous-étape | Contenu | Dépend de |
|
||||
| --- | --- | --- |
|
||||
| 1a. Moteur pour l'analyse | Niveaux de jeu (plis et bruit de GNU Backgammon : débutant à expert), classement des coups d'un jet avec équités, analyse d'un coup joué et d'une décision de videau, chance d'un jet ; exposés par `engine.h`, `engine-cli` et le Worker, avec positions de référence comparées à gnubg de bureau | — |
|
||||
| 1b. Parties enregistrées | Stockage IndexedDB derrière l'interface de stockage existante : plusieurs parties, reprise automatique, branches (« Rejouer d'ici » crée une branche, l'originale reste intacte et rechargeable), export SGF avec variantes | — |
|
||||
| 1c. Match et réglages | Partie libre ou match en N points, règle Crawford, règle Jacoby, choix du niveau de l'ordinateur, conseil du maître activable ou non ; écran de nouvelle partie | 1a, 1b |
|
||||
| 1d. Conseil du maître | Après chaque coup du joueur, jamais avant (ADR 0011) : appréciation du moteur (bon coup, imprécision, erreur, gaffe) avec mot, icône et couleur, phrase du maître fondée sur les chiffres du moteur, coup recommandé montré sur demande ; aussi pour les décisions de videau ; calcul en arrière-plan sans retarder le jeu | 1a |
|
||||
| 1e. Fin de partie et analyse | Bilan (ADR 0011) : résultat, coups par appréciation, taux d'erreur comparé à la chance, « le coup à revoir » ; écran d'analyse : graphe d'équité (toucher un point ouvre la position), liste des erreurs filtrable, visualiseur pas à pas coup joué contre meilleur coup, « Rejouer d'ici » | 1a, 1b, 1d |
|
||||
| 1f. Prompts à copier | Gabarits versionnés et testés (`docs/prompts/` ou équivalent, source unique), bouton « Demander à une IA » sur le plateau, le conseil du maître, l'analyse et la fin de partie ; le moteur décide, l'IA explique | 1d, 1e |
|
||||
| 1g. Accueil, tutoriel et leçons | Accueil (reprendre, nouvelle partie, prochaine leçon, progression résumée), tutoriel des règles interactif et illustré (0e.5), cinq premières leçons numérotées (ADR 0011) avec positions et « à vous de jouer », progression locale ; contenus CC BY-SA 4.0 en brouillon, à relire par un joueur fort | 0e.5, 1a |
|
||||
| 1h. Progression | Historique des parties et courbe du taux d'erreur | 1b, 1e |
|
||||
| 1i. Plateau soigné | Dessin du plateau aligné sur la charte (`docs/identite/charte/board.js`), animations des pions, glisser-déposer en plus du toucher, double toucher pour le seul coup évident | 0e.3 |
|
||||
| 1j. Plan de test | `docs/guide-test-etape-1.md` : parcours du débutant et du joueur de club sur le téléphone, avec ce qu'il faut rapporter | toutes |
|
||||
|
||||
## Orientation donnée par Pierre
|
||||
|
||||
- **Un coach bienveillant, mis en avant mais discret** (2026-11-03) : le
|
||||
conseil du maître et les conseils sont une signature de l'app, présents à
|
||||
chaque partie, sans jamais interrompre le jeu ni culpabiliser le joueur. Le
|
||||
ton des phrases du maître est encourageant ; l'espace qu'il occupe est
|
||||
réservé dans la mise en page (revue UX 0e.2), pas en fenêtre surgissante.
|
||||
|
||||
## Questions pour Pierre (ne bloquent pas le démarrage)
|
||||
|
||||
- **Indice** : le cadrage le liste dans le module Jeu, l'ADR 0011 l'a laissé
|
||||
« à concevoir séparément ». Il n'est pas dans l'étape 1 sans décision.
|
||||
- **Questionnaire de premier lancement** (choix du parcours) : le parcours 1
|
||||
seul existe en V1 ; proposé : un simple choix « Je découvre » / « Je connais
|
||||
les règles » qui oriente vers le tutoriel ou la partie.
|
||||
- **Seuils des appréciations** : proposés d'après ceux de GNU Backgammon
|
||||
(douteux 0,04, mauvais 0,08, très mauvais 0,16 d'équité), à calibrer.
|
||||
- **Relecture des leçons** par un joueur fort de l'Atelier du Huit.
|
||||
|
||||
## Ordre de réalisation
|
||||
|
||||
1a et 1b d'abord (fondations, sans conflit avec l'interface), en parallèle des
|
||||
sous-étapes 0e. Puis 1c, 1d, 1e, 1f dans cet ordre ; 1g après la validation des
|
||||
maquettes d'accueil et de tutoriel par Pierre ; 1h et 1i ; 1j en dernier. Chaque
|
||||
sous-étape a son plan dans `docs/plans/` et sa merge request.
|
||||
+18
-16
@@ -3,34 +3,36 @@
|
||||
Le travail restant, dans l'ordre. Une ligne réalisée est retirée : ce qui a été
|
||||
fait se lit dans les merge requests et le journal, ce qui a été décidé dans les
|
||||
ADR (`docs/adr/`). Les versions et leur contenu sont définis dans
|
||||
`docs/cadrage.md` ; le détail de l'étape en cours est dans `docs/etape-0.md`.
|
||||
`docs/cadrage.md`.
|
||||
|
||||
Légende : 🔄 en cours · ⏳ à venir · ⏸ conditionnel
|
||||
|
||||
## Étape 0 — Prototype technique
|
||||
## Étape 0 — Prototype technique : terminée
|
||||
|
||||
Acquis : socle du dépôt, organisation (ADR 0001, 0003, 0006), source du moteur
|
||||
(ADR 0005 : gnubg officiel, commit 14b6798), gnubg de référence et 32 positions
|
||||
de référence (0a.2), moteur embarqué et `engine-cli` (0a.3), comparaison
|
||||
automatique aux références, tolérance 0,002 (0a.4), moteur en WebAssembly avec
|
||||
page de test, page de mesure et guide de test (0b), mesures sur le Fairphone 4 et
|
||||
socle PWA en TypeScript (ADR 0009 ; 0c Android natif abandonnée), domaine
|
||||
« partie » en journal d'événements avec scénario exécuté sur le vrai moteur et
|
||||
garde-fous d'architecture (0d.1), déploiement sur https://ustagammon.sans.pub.
|
||||
Close le 2026-11-03 : partie complète jouée par Pierre sur le Fairphone 4, coups
|
||||
de l'ordinateur rapides, tous les tests au vert. Acquis : moteur gnubg officiel
|
||||
(ADR 0005) comparé à 32 positions de référence, moteur en WebAssembly, socle
|
||||
PWA en TypeScript (ADR 0009 ; Android natif abandonné), domaine « partie » en
|
||||
journal d'événements, plateau jouable hors ligne avec la charte graphique,
|
||||
publié sur https://ustagammon.sans.pub. Le détail est dans le journal et les
|
||||
merge requests.
|
||||
|
||||
## Retours du premier test sur téléphone (avant l'étape 1)
|
||||
|
||||
Demandes de Pierre du 2026-11-03, traitées avant d'ouvrir l'étape 1.
|
||||
|
||||
| Sous-étape | Contenu | État | Plan |
|
||||
| --- | --- | --- | --- |
|
||||
| 0d.2 | Plateau minimal jouable (PWA hors ligne), publié ; reste la vérification avec Pierre sur le Fairphone 4 (`docs/guide-test-etape-0.md`, section « Étape 0d ») | 🔄 | |
|
||||
| 0d.2+ | Montrer le coup de l'ordinateur (animation, traces, « Revoir ») : retour de Pierre | 🔄 | `plans/2026-11-03-coup-adverse.md` |
|
||||
|
||||
**Critère de fin** : une partie complète jouée sur le Fairphone 4, moins de 2 s
|
||||
par coup de l'IA en niveau standard, tous les tests au vert.
|
||||
| 0e.1 | Saisie du coup : pion à destination unique joué en un toucher, coup à valider et annulable demi-coup par demi-coup, dés joués marqués, dés des deux côtés touchables, videau confirmé ; pages de test du moteur retirées | 🔄 | `plans/2026-11-03-saisie-du-coup.md` |
|
||||
| 0e.3 | Révision UX arbitrée (session « identité », sur délégation de Pierre) : portrait stable, bandeaux de compte de points, menu en feuille et nouvelle partie annulable, vibrations, écran allumé, paysage « plateau = écran » dès la rotation (ADR 0012 amendé) | ⏳ | `plans/2026-11-03-revision-ux.md` |
|
||||
| 0e.4 | Position dans l'adresse : chaque position du plateau a un lien direct (nouvel ADR pour le format) | ⏳ | |
|
||||
| 0e.5 | Page d'accueil, installation en un toucher (« Ajouter à l'écran d'accueil », proposée par l'app), tutoriel des règles très didactique avec plateaux illustrés, aide réécrite sur un ton plus humain ; textes à valider par Pierre | ⏳ | |
|
||||
|
||||
## Étapes suivantes
|
||||
|
||||
| Étape | Contenu | État |
|
||||
| --- | --- | --- |
|
||||
| 1. V1 Jouer et comprendre | Plateau soigné, niveaux, coach, analyse, branches, tutoriel, prompts, sauvegarde | ⏳ |
|
||||
| 1. V1 Jouer et comprendre | Détail et ordre dans `docs/etape-1.md` : moteur pour l'analyse, parties enregistrées et branches, match et réglages, conseil du maître, fin de partie et analyse, prompts, accueil, tutoriel et leçons, progression, plateau soigné, plan de test | 🔄 |
|
||||
| 2. V1.5 Apprendre | Parcours 2, exercices, progression | ⏳ |
|
||||
| 3. V2 Perfectionnement et IA | Parcours 3 et 4, rollouts, révision espacée | ⏳ |
|
||||
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
# Guide de test : Ustagammon sur le Fairphone 4 (étape 0)
|
||||
|
||||
Document temporaire de l'étape 0 : il sera supprimé avec `docs/etape-0.md`.
|
||||
|
||||
Deux tests sur le téléphone :
|
||||
|
||||
- **Étape 0d : jouer une partie** (section suivante), sur le site publié ;
|
||||
- **Étape 0b : mesurer le moteur** (fait le 2026-11-02), sur une page
|
||||
servie par le PC.
|
||||
|
||||
## Étape 0d : jouer une partie sur le téléphone
|
||||
|
||||
Le but : installer l'application sur le Fairphone 4, jouer une partie
|
||||
complète contre l'ordinateur, vérifier qu'elle marche sans réseau, et
|
||||
rapporter ce que vous avez constaté. Comptez une demi-heure.
|
||||
|
||||
### Ouvrir et installer l'application
|
||||
|
||||
1. Sur le Fairphone, ouvrez Chrome et allez sur https://ustagammon.sans.pub.
|
||||
2. Attendez que le plateau s'affiche et que le message « Chargement du
|
||||
moteur… » laisse place à une consigne (par exemple « À vous : lancez les
|
||||
dés. » ou « L'ordinateur réfléchit… »). La première fois, le moteur se
|
||||
télécharge (environ 7 Mo) : quelques secondes en Wi-Fi.
|
||||
3. Installez l'application : menu de Chrome (les trois points), puis
|
||||
« Installer l'application » ou « Ajouter à l'écran d'accueil ». Une icône
|
||||
(deux pions sur du bois) apparaît sur l'écran d'accueil.
|
||||
4. Fermez Chrome et ouvrez Ustagammon depuis cette icône : l'application
|
||||
s'ouvre en plein écran, sans barre d'adresse.
|
||||
|
||||
### Jouer une partie complète
|
||||
|
||||
Suivez le scénario « Jouer une partie contre l'ordinateur »
|
||||
(`docs/scenarios/jouer-une-partie.feature`), qui est aussi l'aide de
|
||||
l'application : bouton « Aide », en haut.
|
||||
|
||||
1. Une partie commence d'elle-même au premier lancement, par le jet
|
||||
d'ouverture (un dé chacun) : le message dit qui commence. « Nouvelle
|
||||
partie », en haut, en recommence une.
|
||||
2. Quand c'est à vous : touchez « Lancer les dés » (ou « Doubler », pour
|
||||
proposer de doubler avant de lancer).
|
||||
3. Pour jouer : touchez un pion entouré de vert, puis l'une des cases
|
||||
d'arrivée en surbrillance ; recommencez pour chaque dé. Pour sortir un
|
||||
pion, touchez le bac à droite, en bas. « Annuler » remet les pions du coup
|
||||
en cours à leur place.
|
||||
4. Le coup est envoyé dès qu'il est complet ; l'ordinateur joue ensuite, et
|
||||
le message dit son coup et son temps de calcul, par exemple
|
||||
« L'ordinateur a joué 6-4 : 24/18 13/9 (0,3 s) ». Les petits points
|
||||
orange marquent ses cases de départ et d'arrivée (dans sa numérotation :
|
||||
sa case 24 est votre case 1).
|
||||
5. Si l'ordinateur vous propose de doubler : « Prendre » ou « Passer ».
|
||||
Essayez au moins une fois de doubler vous-même.
|
||||
6. En fin de partie, le message donne le gagnant, la sorte de victoire
|
||||
(simple, gammon ou backgammon) et les points.
|
||||
|
||||
En cours de partie, fermez l'application (balayez-la dans la liste des
|
||||
applications ouvertes), puis rouvrez-la : la partie doit reprendre là où vous
|
||||
en étiez.
|
||||
|
||||
### Vérifier le hors ligne
|
||||
|
||||
1. Après avoir ouvert l'application au moins une fois (attendez une
|
||||
vingtaine de secondes après le premier chargement, le temps que tout soit
|
||||
mis de côté), fermez-la.
|
||||
2. Passez le téléphone en mode avion.
|
||||
3. Rouvrez Ustagammon depuis son icône : le plateau, l'ordinateur et l'aide
|
||||
doivent fonctionner comme avant.
|
||||
4. Repassez en mode normal.
|
||||
|
||||
### Ce qu'il faut rapporter
|
||||
|
||||
Dans la conversation avec Claude Code :
|
||||
|
||||
- les **temps des coups de l'ordinateur** : ceux affichés dans les messages
|
||||
(le plus long que vous avez vu), et votre ressenti (attente gênante ou
|
||||
non) ;
|
||||
- si la partie est allée **jusqu'au bout**, et son résultat ;
|
||||
- si la **reprise** après fermeture et le **mode avion** ont marché ;
|
||||
- tout ce qui gêne : plateau trop petit, case difficile à toucher, texte
|
||||
incompréhensible, bouton qui ne répond pas, couleurs peu lisibles (thème
|
||||
clair ou sombre du téléphone) ;
|
||||
- une **capture d'écran** (bouton marche + volume bas) de tout problème.
|
||||
|
||||
Si vous le pouvez, refaites l'essai dans Firefox sur le téléphone (sans
|
||||
l'installer) et dites si les temps de l'ordinateur sont nettement plus longs.
|
||||
|
||||
## Étape 0b : mesurer le moteur
|
||||
|
||||
Le but : ouvrir sur le téléphone une page web où le moteur de backgammon
|
||||
calcule, puis lancer une mesure et coller son rapport dans la conversation.
|
||||
Comptez un quart d'heure.
|
||||
|
||||
### 1. Préparer le PC
|
||||
|
||||
1. Ouvrez un terminal dans le dossier du projet (`~/Code/ustagammon`).
|
||||
2. Si ce n'est pas déjà fait, tapez `make setup` puis Entrée. Cela prépare les
|
||||
outils. La première fois, c'est long ; ensuite, c'est immédiat.
|
||||
3. Le PC et le téléphone doivent être sur **le même Wi-Fi** (la même box).
|
||||
|
||||
### 2. Lancer la page
|
||||
|
||||
1. Tapez `make deploy` puis Entrée.
|
||||
2. La première fois, le moteur se compile : quelques minutes.
|
||||
3. Le terminal affiche deux adresses, par exemple :
|
||||
|
||||
```
|
||||
Application : http://192.168.1.21:8080/
|
||||
Test du moteur : http://192.168.1.21:8080/moteur/
|
||||
Page de mesure : http://192.168.1.21:8080/moteur/bench.html
|
||||
```
|
||||
|
||||
Vos chiffres peuvent être différents : notez ceux de votre écran.
|
||||
4. Laissez ce terminal ouvert pendant tout le test. Pour arrêter la page à la
|
||||
fin : Ctrl+C dans le terminal.
|
||||
|
||||
### 3. Essayer un meilleur coup
|
||||
|
||||
1. Sur le Fairphone, ouvrez le navigateur (Chrome ou Firefox).
|
||||
2. Tapez l'adresse du **test du moteur** dans la barre d'adresse, exactement
|
||||
comme affichée (avec `http://` et `:8080`). Les pages de test sont aussi
|
||||
publiées : https://ustagammon.sans.pub/moteur/ et
|
||||
https://ustagammon.sans.pub/moteur/bench.html.
|
||||
3. Attendez le message « Moteur prêt, chargé en … ms ».
|
||||
4. Dans « Exemple », choisissez une position, par exemple « middlegame-1 ».
|
||||
5. Dans « Niveau », choisissez « fort (2 plis) ».
|
||||
6. Touchez « Meilleur coup ».
|
||||
7. La page affiche le coup conseillé et le **temps de calcul**. Notez ce temps
|
||||
si vous voulez, il sera aussi dans le rapport.
|
||||
|
||||
### 4. Lancer la mesure
|
||||
|
||||
1. En bas de la page, touchez « Page de mesure » (ou tapez l'adresse de la
|
||||
page de mesure).
|
||||
2. Touchez « Lancer la mesure ».
|
||||
3. Attendez « Mesure terminée » : de une à quelques minutes. Pendant ce temps,
|
||||
gardez l'écran allumé et la page ouverte, sans changer d'application.
|
||||
4. Un tableau résume les résultats : « OK » si le seuil est respecté,
|
||||
« dépassé » sinon.
|
||||
|
||||
### 5. Envoyer le rapport
|
||||
|
||||
1. Touchez « Copier les résultats ».
|
||||
2. Si la page dit « Rapport copié », c'est bon.
|
||||
3. Sinon, le texte du rapport est sélectionné : faites un appui long dessus,
|
||||
puis « Copier ».
|
||||
4. Collez le rapport dans la conversation avec Claude Code (sur le téléphone,
|
||||
ou envoyez-le vous sur le PC).
|
||||
|
||||
Si vous en avez le temps, refaites la mesure une deuxième fois (rechargez la
|
||||
page de mesure) et collez aussi ce second rapport : cela montre si les temps
|
||||
sont stables. Si les deux navigateurs sont installés, faites la mesure dans
|
||||
Chrome **et** dans Firefox : sur le PC, Firefox s'est montré nettement plus
|
||||
lent, et le rapport indique le navigateur utilisé.
|
||||
|
||||
### Si la page ne s'ouvre pas
|
||||
|
||||
- **Vérifiez l'adresse** : `http://` (et non `https://`), les chiffres exacts,
|
||||
et `:8080` à la fin.
|
||||
- **Vérifiez le Wi-Fi** : le téléphone doit être sur le même réseau que le PC,
|
||||
pas sur la 4G ni sur un Wi-Fi invité.
|
||||
- **Vérifiez que la page tourne** : sur le PC, ouvrez
|
||||
`http://localhost:8080/` dans un navigateur. Si elle ne s'ouvre pas non
|
||||
plus, relancez `make deploy` et lisez le message d'erreur.
|
||||
- **Le pare-feu du PC** peut bloquer le téléphone. S'il est actif
|
||||
(`sudo ufw status` affiche « Status: active »), autorisez le port le temps
|
||||
du test : `sudo ufw allow 8080/tcp`, puis à la fin `sudo ufw delete allow
|
||||
8080/tcp`.
|
||||
- **« Le moteur n'a pas pu se charger »** : notez le message exact et
|
||||
collez-le dans la conversation. Essayez aussi l'autre navigateur (Chrome ou
|
||||
Firefox), à jour.
|
||||
- **Le port 8080 est déjà pris** : un autre programme l'utilise. Lancez
|
||||
`make deploy WEB_PORT=8081` et utilisez `:8081` dans l'adresse.
|
||||
@@ -47,6 +47,24 @@ python3 -I docs/identite/generer-logo.py build/police/AlegreyaSans-Bold.ttf docs
|
||||
Les PNG sont des rendus des SVG par Chrome sans interface
|
||||
(`google-chrome --headless --screenshot`), à la taille indiquée dans leur nom.
|
||||
|
||||
## Police de l'application
|
||||
|
||||
`web/fonts/` contient Alegreya Sans en cinq graisses, réduite aux caractères
|
||||
latins (français compris) et convertie en woff2, soit 143 Ko au lieu de
|
||||
1,3 Mo en TTF. Pour la refaire, depuis les TTF de
|
||||
https://github.com/google/fonts/tree/main/ofl/alegreyasans (Python 3,
|
||||
fontTools et brotli, par exemple dans l'image `emscripten/emsdk`) :
|
||||
|
||||
```sh
|
||||
pip install fonttools brotli
|
||||
for f in Regular Italic Medium Bold ExtraBold; do
|
||||
python3 -m fontTools.subset AlegreyaSans-$f.ttf \
|
||||
--output-file=web/fonts/AlegreyaSans-$f.woff2 --flavor=woff2 \
|
||||
--unicodes="U+0020-007E,U+00A0-00FF,U+0152-0153,U+0178,U+02BC,U+02C6,U+02DC,U+2009,U+2011,U+2013-2014,U+2018-201E,U+2022,U+2026,U+202F,U+2039-203A,U+20AC,U+2192,U+2212" \
|
||||
--layout-features='*' --name-IDs='*' --name-languages='*'
|
||||
done
|
||||
```
|
||||
|
||||
## Charte graphique
|
||||
|
||||
La charte complète (couleurs des deux thèmes, typographie, composants) est dans
|
||||
|
||||
@@ -110,3 +110,29 @@ Une entrée datée par session : ce qui a été fait, décidé, et la suite.
|
||||
- Les maquettes proposent des éléments de produit qui ne sont pas encore
|
||||
décidés (leçons, « Conseil du maître », bilan de fin de partie) : à valider
|
||||
par Pierre avant toute mise en œuvre.
|
||||
|
||||
## 2026-11-03 (matin) : fin de l'étape 0, retours du test sur téléphone
|
||||
|
||||
**Fait**
|
||||
- Pierre a joué une partie complète sur le Fairphone 4 : « très rapide de la
|
||||
part de l'ordinateur ». Avec les tests au vert, le critère de fin de l'étape 0
|
||||
est atteint. Le guide de test et `docs/etape-0.md`, temporaires, sont
|
||||
supprimés ; leurs décisions sont dans les ADR 0005 et 0009.
|
||||
- Playwright validé par Pierre (amendement de l'ADR 0004, MR !20).
|
||||
- Douze retours de Pierre après l'essai, plus l'installation en un toucher et
|
||||
les retours haptiques, rangés en sous-étapes 0e dans la feuille de route
|
||||
(MR !20). Lancés : la saisie du coup (0e.1) et une revue UX par un designer
|
||||
UI (0e.2).
|
||||
- Coordination avec la session « identité » : elle propose et maquette le
|
||||
design (mode immersif, compte de points, accueil, tutoriel), Pierre tranche,
|
||||
la session technique implémente. Numéros des flèches à 12 px minimum.
|
||||
- Décision de Pierre : la vue paysage « plateau = écran » s'applique dès la
|
||||
rotation du téléphone, sans plein écran (amendement de l'ADR 0012).
|
||||
- Mode avion vérifié par Pierre sur le téléphone : l'application fonctionne
|
||||
hors ligne.
|
||||
- Étape 1 découpée dans `docs/etape-1.md` (sous-étapes 1a à 1j).
|
||||
- Nouvel objectif fixé par Pierre : traiter les retours 0e (les siens et ceux
|
||||
du designer), puis réaliser l'étape 1, livrée avec un plan de test.
|
||||
|
||||
**Reste à faire**
|
||||
- Firefox sur le téléphone reste à mesurer.
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
# Montrer le coup que l'ordinateur vient de jouer
|
||||
|
||||
- **Date** : 2026-11-03
|
||||
- **Étape** : 0d (retour de Pierre sur le plateau publié)
|
||||
- **Statut** : terminé
|
||||
- **Exécutant** : sous-agent general-purpose, dans un worktree isolé
|
||||
|
||||
## Objectif
|
||||
|
||||
Après le coup de l'ordinateur, le joueur voit ce qui a bougé sans lire la
|
||||
notation. Choix de Pierre : **animation puis traces**.
|
||||
|
||||
## Consignes
|
||||
|
||||
1. **Animation** : quand l'ordinateur joue, ses pions glissent un par un, un
|
||||
demi-coup après l'autre (environ 0,4 s chacun), de la case de départ à la
|
||||
case d'arrivée. Un pion frappé part sur la barre de façon visible. Si le
|
||||
système demande moins d'animations (`prefers-reduced-motion`), pas
|
||||
d'animation : on passe directement aux traces.
|
||||
2. **Traces** : une fois l'animation finie, et jusqu'à la prochaine action du
|
||||
joueur (lancer les dés, doubler, prendre, passer, toucher un pion) :
|
||||
- une flèche de chaque case de départ vers sa case d'arrivée ;
|
||||
- les pions déplacés entourés ;
|
||||
- à la case de départ d'un pion parti, un pion « fantôme » (contour
|
||||
seul) ;
|
||||
- un pion frappé signalé sur la barre.
|
||||
Les petits points orange actuels sont remplacés par ces traces.
|
||||
3. **Bouton « Revoir »** à côté des boutons d'action : il rejoue l'animation du
|
||||
dernier coup de l'ordinateur, puis remet les traces. Il disparaît dès que
|
||||
le joueur agit.
|
||||
4. **Où vit la logique** : la liste des demi-coups du dernier coup de
|
||||
l'ordinateur et les positions intermédiaires viennent du domaine (le journal
|
||||
contient déjà `MovePlayed` avec ses demi-coups ; ajouter si besoin une
|
||||
fonction qui donne la suite des plateaux intermédiaires, à partir du
|
||||
moteur, sans réimplémenter de règle : par exemple `checkHalfMoves` ou le
|
||||
décodage de position à chaque étape). L'interface ne fait que dessiner.
|
||||
Les garde-fous d'architecture existants doivent passer.
|
||||
5. **Textes** dans `app/src/ui/texts.ts` ; mots du glossaire.
|
||||
6. **Scénario** (ADR 0008) : ajouter à `docs/scenarios/jouer-une-partie.feature`
|
||||
un scénario « Voir le coup de l'ordinateur », écrit en intentions (par
|
||||
exemple : quand l'ordinateur a joué, alors son coup vous est montré case par
|
||||
case, et vous pouvez le revoir), avec ses étapes Cucumber sur le domaine.
|
||||
7. **Tests** :
|
||||
- tests unitaires de ce qui est calculable hors navigateur (traces et
|
||||
plateaux intermédiaires d'un coup avec et sans pion frappé, coup depuis la
|
||||
barre, sortie de pions) ;
|
||||
- `make test-browser` (`tests/browser/play.mjs`) : après un coup de
|
||||
l'ordinateur, les flèches et les pions entourés sont présents ; « Revoir »
|
||||
rejoue puis les remet ; tout disparaît à l'action suivante ; capture
|
||||
d'écran `coup-adverse` ;
|
||||
- `make lint`, `make test` et `make test-browser` verts.
|
||||
|
||||
Contraintes :
|
||||
- `CLAUDE.md`, ADR 0007, 0008, 0009 : simplicité, pas de framework ni de
|
||||
bibliothèque d'animation (animations CSS ou `requestAnimationFrame`),
|
||||
identifiants en anglais, commentaires et textes en français ;
|
||||
- aucune règle du jeu réimplémentée ;
|
||||
- l'animation ne doit pas retarder le moteur ni bloquer la saisie au-delà de
|
||||
sa durée ; le temps de calcul affiché reste celui du moteur ;
|
||||
- ne pas publier, ne pas pousser, ne pas créer de merge request ;
|
||||
- commits petits, en français, avec la ligne
|
||||
`Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
|
||||
|
||||
Rapport attendu : ce qui a été fait, captures d'écran (chemins), résultat des
|
||||
tests, difficultés, écarts au plan.
|
||||
|
||||
## Ajout : aucun coup possible
|
||||
|
||||
Demandé par Pierre en cours de tâche (inspiré de l'application CBG).
|
||||
|
||||
1. Quand le jet du joueur ne permet aucun coup, le tour ne passe plus tout
|
||||
seul à l'ordinateur. Les dés restent affichés, estompés ou barrés, avec un
|
||||
message clair (« Aucun coup possible avec 6-6. ») et un bouton
|
||||
« Continuer ». Le tour passe à l'ordinateur seulement quand le joueur le
|
||||
touche.
|
||||
2. Quand l'ordinateur ne peut pas jouer son jet, ses dés et un message
|
||||
(« L'ordinateur ne peut pas jouer 5-5. ») restent visibles jusqu'à la
|
||||
prochaine action du joueur, comme les traces d'un coup.
|
||||
3. Le domaine reste la seule source, sans règle réimplémentée ; textes dans
|
||||
`texts.ts`.
|
||||
4. Scénario « Aucun coup possible avec votre jet » avec ses étapes sur le
|
||||
domaine et des dés imposés. Dans `play.mjs`, si le cas survient, vérifier
|
||||
que « Continuer » est demandé, sans forcer de cas artificiel.
|
||||
|
||||
Les changements CSS se limitent aux nouvelles classes : un autre agent
|
||||
travaille sur la charte.
|
||||
|
||||
## Ajout : toucher les dés et le videau
|
||||
|
||||
Demandé par Pierre en cours de tâche.
|
||||
|
||||
1. Toucher les dés lance les dés quand c'est au joueur de lancer (même effet
|
||||
que « Lancer les dés »). Sans dés à lancer affichés, la zone des dés du
|
||||
joueur doit être touchable (dés « en attente »).
|
||||
2. Toucher le videau propose de doubler quand le joueur en a le droit (mêmes
|
||||
conditions, venues du domaine). Le reste du temps, toucher dés ou videau ne
|
||||
fait rien.
|
||||
3. Les boutons restent. Dés et videau touchables : cible d'au moins 44 px,
|
||||
retour visuel discret, `role="button"` et `aria-label`.
|
||||
4. Tests : dans `play.mjs`, lancer au moins une fois en touchant les dés et
|
||||
doubler au moins une fois en touchant le videau.
|
||||
|
||||
## Critères de réussite
|
||||
|
||||
- Dans Chromium et Firefox sans interface, après chaque coup de l'ordinateur :
|
||||
animation (ou traces directes en mouvement réduit), traces visibles, « Revoir »
|
||||
fonctionnel, traces effacées à l'action suivante.
|
||||
- Le scénario « Voir le coup de l'ordinateur » passe dans `make test`.
|
||||
- `make lint`, `make test` et `make test-browser` passent.
|
||||
|
||||
## Résultat
|
||||
|
||||
Terminé le 2026-11-03, branche `coup-adverse-travail`.
|
||||
|
||||
**Domaine** (`app/src/domain/game/game.ts`) : `Game.lastMoveSteps()` donne le
|
||||
dernier coup joué demi-coup par demi-coup (`MoveSteps` : camp, demi-coups,
|
||||
plateaux successifs, pions frappés). Les plateaux intermédiaires viennent du
|
||||
moteur : `checkHalfMoves` sur les premiers demi-coups depuis la position
|
||||
d'avant le coup, puis `board`. Un pion frappé se lit sur le plateau du moteur
|
||||
(la barre adverse grossit), aucune règle n'est rejouée. Le format des
|
||||
événements ne change pas.
|
||||
|
||||
**Interface** :
|
||||
- `board-model.ts` : `moveTrace()` calcule les traces (une flèche par
|
||||
demi-coup, pions arrivés, pions partis, pions frappés). Un pion qui fait
|
||||
étape (13/7 7/4) ne laisse rien sur la case d'étape. Testé sous Node.
|
||||
- `board-view.ts` : traces dessinées en laiton (`--last-move`, comme le
|
||||
demande la charte) : flèches avec pointe, pions arrivés entourés, pion
|
||||
fantôme en pointillés à la case de départ, pion frappé entouré en
|
||||
pointillés sur la barre. Les petits points orange ont disparu. La
|
||||
géométrie des piles est regroupée dans `slotCenter()`. Le pion du dessus de
|
||||
chaque endroit porte `data-checker`. `slideChecker()` le fait glisser avec
|
||||
`Element.animate` (API du navigateur, sans bibliothèque).
|
||||
- `app.ts` : après le tour de l'ordinateur, le plateau est redessiné à
|
||||
chaque étape et le pion arrivé glisse en 0,4 s depuis sa case de départ. Un
|
||||
pion frappé glisse en même temps vers la barre. Ensuite viennent les traces
|
||||
et le bouton « Revoir ». Tout s'efface à l'action suivante (lancer,
|
||||
doubler, prendre, passer, nouvelle partie, toucher un pion). Avec
|
||||
`prefers-reduced-motion: reduce`, les traces s'affichent directement.
|
||||
L'animation commence une fois le moteur fini : le temps affiché reste celui
|
||||
du moteur. Les touches sont ignorées pendant l'animation (au plus 0,4 s par
|
||||
demi-coup). Une partie reprise juste après le coup de l'ordinateur montre
|
||||
ses traces sans l'animer.
|
||||
- Textes `replay` et `replayTitle` dans `texts.ts`, bouton `#replay` dans
|
||||
`web/index.html`.
|
||||
|
||||
**Scénario** « Voir le coup de l'ordinateur » dans
|
||||
`docs/scenarios/jouer-une-partie.feature`. Il part d'une position où le seul
|
||||
coup permis de l'ordinateur avec 3-1 frappe votre blot (13/10* 10/9), avec le
|
||||
videau chez vous pour qu'il ne double pas. Ses étapes vérifient les plateaux
|
||||
case par case, le pion frappé qui part sur la barre et un second appel
|
||||
identique (« revoir »).
|
||||
|
||||
**Tests** :
|
||||
- `app/tests/move-steps.test.ts` (vrai moteur) : coup simple, pion frappé,
|
||||
coup depuis la barre qui frappe en rentrant, sortie jusqu'à la fin de
|
||||
partie, coup de l'ordinateur, aucun coup.
|
||||
- `app/tests/ui.test.ts` : traces d'un coup simple, d'un pion qui fait
|
||||
étape, d'une rentrée avec un pion frappé, d'une sortie.
|
||||
- `tests/browser/play.mjs` : après chaque action, les traces sont là si le
|
||||
dernier coup est celui de l'ordinateur (autant de flèches que de
|
||||
demi-coups, au moins un pion entouré et un fantôme, « Revoir » visible), et
|
||||
absentes sinon. « Revoir » est essayé deux fois. La première fois, des
|
||||
animations tournent et les flèches disparaissent pendant l'animation, puis
|
||||
les mêmes traces reviennent ; une capture `coup-adverse` est prise. La
|
||||
seconde fois, en mouvement réduit, aucune animation ne tourne et les traces
|
||||
reviennent.
|
||||
- Résultats : `make lint` sans problème ; `make test` 10/10 ; `make
|
||||
test-browser` vert sous Chromium (coup de l'ordinateur montré 26 fois) et
|
||||
Firefox (43 fois), sans violation CSP ni erreur dans la page.
|
||||
|
||||
**Limites** :
|
||||
- Aucune des deux parties du navigateur n'a vu l'ordinateur frapper (0 pion
|
||||
frappé signalé). L'animation vers la barre n'est donc vérifiée que par le
|
||||
scénario et les tests unitaires (plateaux et traces), pas dans un
|
||||
navigateur.
|
||||
- Firefox a affiché un tour de l'ordinateur à 4,4 s au plus. Ce temps est
|
||||
mesuré avant l'animation, qui n'y est pour rien : c'est le moteur sous
|
||||
Firefox sur le PC de 2011. Chromium : 0,6 s au plus.
|
||||
- Sur une case de plus de cinq pions, c'est le cinquième pion dessiné qui
|
||||
glisse (le nombre reste affiché sur la pile).
|
||||
@@ -0,0 +1,60 @@
|
||||
# Moteur pour l'analyse et les niveaux (1a)
|
||||
|
||||
- **Date** : 2026-11-03
|
||||
- **Étape** : 1a (`docs/etape-1.md`)
|
||||
- **Statut** : en cours
|
||||
- **Exécutant** : sous-agent general-purpose, dans un worktree
|
||||
|
||||
## Objectif
|
||||
|
||||
Donner au domaine tout ce dont l'étape 1 a besoin du moteur : niveaux de jeu de
|
||||
l'ordinateur, analyse d'un coup joué et d'une décision de videau, chance d'un
|
||||
jet. Tout vient de GNU Backgammon, comparé à gnubg de bureau.
|
||||
|
||||
## Consignes
|
||||
|
||||
ADR 0005, 0007 et la méthode de comparaison de `tests/reference/` (README).
|
||||
Ne pas modifier `engine/gnubg/` (copie fidèle). Pas de nouvelle dépendance.
|
||||
|
||||
1. **Niveaux de jeu** : les niveaux de GNU Backgammon (débutant, amateur,
|
||||
intermédiaire, avancé, expert, champion du monde…), avec leurs plis, filtres
|
||||
et bruit, repris des réglages prédéfinis de gnubg (pas inventés). Le bruit
|
||||
doit être déterministe (option « deterministic » de gnubg) pour que les
|
||||
tests soient reproductibles. API C (`engine.h`), `engine-cli`, adaptateur
|
||||
WebAssembly et interface `Engine` du domaine (ajout seulement : ne pas
|
||||
toucher au reste de `app/src/domain/game/`, qu'un autre agent modifie).
|
||||
2. **Analyse d'un coup joué** : pour une position, un jet et un coup joué,
|
||||
l'équité du coup joué, celle du meilleur coup, l'écart, et le rang du coup
|
||||
joué, au niveau d'analyse demandé (comme l'analyse de partie de gnubg).
|
||||
3. **Analyse d'une décision de videau** : équités sans double, double/prend,
|
||||
double/passe, et l'écart de la décision prise (double, pas de double,
|
||||
prendre, passer), comme gnubg.
|
||||
4. **Chance d'un jet** : la chance (luck) d'un jet au sens de gnubg, et son
|
||||
classement (très chanceux… très malchanceux) avec les seuils de gnubg.
|
||||
5. **Seuils d'appréciation** : exposer les seuils de gnubg (douteux, mauvais,
|
||||
très mauvais) comme constantes documentées ; l'interface les traduira.
|
||||
6. **Références** : ajouter des positions de référence évaluées par gnubg de
|
||||
bureau (Docker existant, `make reference`) pour l'analyse d'un coup, d'une
|
||||
décision de videau et la chance, comparées par `make test` (natif et
|
||||
WebAssembly) avec la tolérance existante. Les niveaux avec bruit : vérifier
|
||||
au moins que le coup choisi est reproductible et identique à celui de gnubg
|
||||
de bureau sur quelques positions.
|
||||
7. Glossaire : fiches pour les nouveaux termes (niveau, chance, appréciation,
|
||||
équité si absente…), reliées au code.
|
||||
8. Mesurer sous Node (`make bench` ou équivalent) le temps d'une analyse de
|
||||
coup au niveau prévu pour le conseil du maître (2 plis), pour vérifier
|
||||
qu'elle tient en arrière-plan sur le téléphone (repère : ADR 0009).
|
||||
|
||||
Machine ancienne : `-j2`, ne recompiler le moteur que si nécessaire.
|
||||
`make lint` et `make test` doivent passer. Commits fréquents ; `git add` avec
|
||||
chemins explicites ; ne pas pousser, pas de MR.
|
||||
|
||||
## Critères de réussite
|
||||
|
||||
- `engine-cli` répond aux nouvelles commandes en JSON.
|
||||
- Les nouvelles références concordent avec gnubg de bureau, natif et WebAssembly.
|
||||
- `make lint` et `make test` passent.
|
||||
|
||||
## Résultat
|
||||
|
||||
À remplir à la clôture.
|
||||
@@ -0,0 +1,63 @@
|
||||
# Révision UX : portrait stable, compte de points, menu, paysage « plateau = écran »
|
||||
|
||||
- **Date** : 2026-11-03
|
||||
- **Étape** : 0e.3 (feuille de route)
|
||||
- **Statut** : prévu (après la fusion de 0e.1, qui touche les mêmes fichiers)
|
||||
- **Exécutant** : sous-agent general-purpose, dans un worktree
|
||||
|
||||
## Objectif
|
||||
|
||||
Appliquer la revue UX (0e.2) telle qu'arbitrée par la session « identité » sur
|
||||
délégation de Pierre le 2026-11-03, et l'amendement de l'ADR 0012.
|
||||
|
||||
## Références
|
||||
|
||||
- Maquettes : https://claude.ai/artifact/CQw6vbDpi6XQqVWQa4sANe, rangées
|
||||
« Révision UX » en bas du canevas.
|
||||
- Règles : section « Interaction » du design system, copiée dans
|
||||
`docs/identite/charte/interaction.md` (durées, courbes, motifs de vibration,
|
||||
touches) et composant Icons (10 pictogrammes).
|
||||
- Rapport du designer UI : conservé dans la description de la MR qui clôt ce
|
||||
plan.
|
||||
|
||||
## Décisions à appliquer
|
||||
|
||||
1. **Portrait stable** (maquette « Portrait révisé ») :
|
||||
- une barre haute avec le symbole et trois pictogrammes, libellés de 13 px dessous : Aide, Plein écran, Menu ;
|
||||
- le bandeau de l'ordinateur, puis le plateau, puis le bandeau du joueur ;
|
||||
- une ligne réservée de 44 px : le dernier coup de l'ordinateur avec ↻, ou la ligne du maître ;
|
||||
- la consigne ;
|
||||
- une barre d'actions collée en bas, l'action secondaire à gauche et la principale à droite (« Jouer ce coup »). Toucher les dés valide aussi ;
|
||||
- la durée de calcul affichée disparaît.
|
||||
2. **Compte de points** :
|
||||
- en portrait, des bandeaux. Les chiffres sont en 20 px gras, alignés. Le bandeau de celui qui a la main est en accent-soft (« Vous · à vous de jouer »). L'écart s'écrit « mène de 8 », en muted, du côté de celui qui mène. Toucher le chiffre ouvre une bulle avec le texte de la fiche du glossaire (source unique, ADR 0008) ;
|
||||
- en paysage, des pastilles aux bouts de la barre.
|
||||
3. **Menu en feuille** qui monte du bas :
|
||||
- il contient Nouvelle partie, Plein écran, Aide et glossaire, Thème, et les interrupteurs Vibrations et « Écran allumé pendant la partie » ;
|
||||
- « Nouvelle partie » agit sans confirmation. Un bandeau « Nouvelle partie commencée. Reprendre l'ancienne » reste 10 s avec une barre de temps, et l'ancienne partie se restaure depuis son journal ;
|
||||
- plus de `window.confirm`.
|
||||
4. **Pas de passage automatique du tour** quand aucun coup n'est possible : les dés sont barrés avec le message, et le joueur passe en touchant les dés (ou Espace). Ajouter `overscroll-behavior: none` et `user-select: none`. L'écran reste allumé pendant une partie (Wake Lock), activé par défaut, avec un interrupteur.
|
||||
5. **Paysage, « le plateau est l'écran »** (ADR 0012 amendé) :
|
||||
- la vue s'applique dès la rotation, sur `100dvh` ;
|
||||
- la géométrie suit l'écran, par une fonction pure testée unitairement, d'après les formules du composant Board des maquettes ;
|
||||
- une colonne de commandes à gauche dans le cadre : menu en haut, videau au milieu, ↶ en bas, remplacé par ↻ quand le coup de l'ordinateur est montré ;
|
||||
- les sorties restent libres ;
|
||||
- numéros des flèches à 12 px au moins ;
|
||||
- la fin de partie s'affiche dans un panneau centré sur un voile ;
|
||||
- le plein écran s'ouvre sur un geste (pictogramme ou menu), avec `requestFullscreen` puis `screen.orientation.lock` et un repli par rotation CSS. Ce n'est pas le réglage par défaut de l'app installée.
|
||||
6. **Vibrations** (`navigator.vibrate`, si disponible) aux quatre moments, avec les motifs d'`interaction.md` : pion posé, pion frappé, double proposé par l'ordinateur, fin de partie. Elles sont activées par défaut, rien au lancer des dés, et elles doublent toujours un signal visible.
|
||||
7. **Pictogrammes** du composant Icons, en SVG dans le site (CSP : pas de style en ligne).
|
||||
8. **Consigne** : forme longue pendant les trois premières parties, puis forme courte.
|
||||
9. **Mouvement** :
|
||||
- durées de 90, 160 et 240 ms, avec la courbe cubic-bezier(0.2, 0, 0, 1) ;
|
||||
- l'ordinateur joue pion par pion : 160 ms par pion, 120 ms de pause ;
|
||||
- `prefers-reduced-motion` est respecté.
|
||||
10. **Clavier** (priorité basse) : prévoir dans l'architecture une table d'actions commune au toucher et au clavier. Les touches d'`interaction.md` peuvent venir dans une MR ultérieure.
|
||||
|
||||
## Consignes
|
||||
|
||||
ADR 0007 : ni le domaine ni le moteur ne changent pour la mise en page. ADR 0008 : scénarios Gherkin pour les comportements visibles nouveaux (nouvelle partie annulable, menu). Aucune nouvelle dépendance. `tests/browser/play.mjs` étendu aux deux orientations, avec des captures Playwright en 412×915, 915×412 et 844×320, en thème clair et sombre, rangées dans un dossier indiqué par la session principale pour la revue design. `make lint`, `make test` et `make test-browser` passent.
|
||||
|
||||
## Résultat
|
||||
|
||||
À remplir à la clôture.
|
||||
@@ -0,0 +1,65 @@
|
||||
# Saisie du coup, videau confirmé, pages de test du moteur retirées
|
||||
|
||||
- **Date** : 2026-11-03
|
||||
- **Étape** : 0e (retours du premier test de Pierre sur le téléphone)
|
||||
- **Statut** : en cours
|
||||
- **Exécutant** : sous-agent general-purpose, dans un worktree
|
||||
|
||||
## Objectif
|
||||
|
||||
Rendre la saisie du coup aussi confortable que dans CBG, d'après les retours de
|
||||
Pierre du 2026-11-03 (points 1, 6, 7, 8, 9 et 11 de son message), et retirer du
|
||||
site les pages de test du moteur.
|
||||
|
||||
## Consignes
|
||||
|
||||
Règles de code : ADR 0007 (domaine pur, aucune règle de backgammon
|
||||
réimplémentée : tout vient du moteur via le domaine). Glossaire et scénarios :
|
||||
ADR 0008 (chaque comportement visible a son scénario Gherkin dans
|
||||
`docs/scenarios/`, chaque nouveau terme sa fiche). Aucune nouvelle dépendance.
|
||||
|
||||
1. **Pages de test du moteur retirées** (« supprimer la partie test du moteur
|
||||
totalement ») : plus de `/moteur/` sur le site ni dans `make deploy`.
|
||||
`make bench` (mesure sous Node) reste : déplacer ce dont il a besoin hors de
|
||||
`web/`. Mettre à jour `tests/test_site.sh`, le Makefile, `deploy/`, le
|
||||
service worker, `docs/guide-test-etape-0.md` et ajouter un amendement daté à
|
||||
l'ADR 0009.
|
||||
2. **Un seul coup possible, le pion avance** : quand le joueur touche un pion
|
||||
(ou une case) et qu'une seule case d'arrivée est possible pour ce pion, le
|
||||
demi-coup est joué aussitôt, sans second toucher. S'il y en a plusieurs, la
|
||||
sélection actuelle reste.
|
||||
3. **Coup à valider, annulable** : quand tous les demi-coups sont joués (ou
|
||||
qu'aucun autre n'est possible), le coup n'est pas envoyé tout de suite. Le
|
||||
joueur le valide en touchant ses dés (ou un bouton « Valider »). Tant qu'il
|
||||
ne l'a pas validé, « Annuler » défait le dernier demi-coup, un par un. Le
|
||||
journal d'événements ne reçoit `MovePlayed` qu'à la validation.
|
||||
4. **Dés joués marqués** : chaque dé déjà utilisé par un demi-coup est
|
||||
visiblement estompé. Pour un double, quatre dés, marqués un par un.
|
||||
5. **Dés de l'adversaire touchables** : toucher la zone des dés de l'un ou
|
||||
l'autre côté du plateau lance les dés quand le joueur peut lancer (et valide
|
||||
le coup en attente de validation).
|
||||
6. **Confirmation du videau** : toucher le videau ou « Doubler » ouvre une
|
||||
confirmation courte (« Doubler à 2 ? L'ordinateur pourra prendre ou
|
||||
passer. » : Doubler / Annuler) avant d'envoyer le double.
|
||||
|
||||
Tests : scénarios Gherkin mis à jour ou ajoutés (validation du coup, annulation
|
||||
demi-coup par demi-coup, confirmation du videau), tests unitaires, et
|
||||
`tests/browser/play.mjs` adapté (Playwright est validé par Pierre). Lancer
|
||||
`make lint`, `make test` et `make test-browser`.
|
||||
|
||||
Rapporter : ce qui a été fait, les écarts, les résultats des trois commandes,
|
||||
et un brouillon de description de MR (7 sections, voir `docs/organisation.md`).
|
||||
Commits fréquents sur la branche du worktree ; ne pas pousser, ne pas ouvrir de
|
||||
MR : la session principale s'en charge.
|
||||
|
||||
## Critères de réussite
|
||||
|
||||
- `make lint`, `make test` et `make test-browser` passent.
|
||||
- Dans le navigateur : un pion à destination unique avance en un toucher ; un
|
||||
coup complet attend la validation ; « Annuler » défait un demi-coup à la
|
||||
fois ; les dés joués sont estompés ; le videau demande confirmation.
|
||||
- `https://…/moteur/` n'existe plus dans le site construit (`make site`).
|
||||
|
||||
## Résultat
|
||||
|
||||
À remplir à la clôture.
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/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)))
|
||||
+4
-3
@@ -29,10 +29,11 @@ for f in "${fichiers[@]}"; do
|
||||
if grep -q $'\r' "$f"; then
|
||||
signaler "$f : fins de ligne Windows (CRLF)"
|
||||
fi
|
||||
# Copies fidèles de GNU Backgammon (sources et données) : on ne
|
||||
# reformate pas leurs espaces, pour rester identique à l'original.
|
||||
# Copies fidèles de GNU Backgammon (sources et données) et de la licence
|
||||
# de la police : on ne reformate pas leurs espaces, pour rester
|
||||
# identique à l'original.
|
||||
case "$f" in
|
||||
engine/gnubg/* | engine/data/*) continue ;;
|
||||
engine/gnubg/* | engine/data/* | web/fonts/OFL.txt) continue ;;
|
||||
esac
|
||||
if grep -nE '[[:blank:]]+$' "$f" >/dev/null; then
|
||||
signaler "$f : espaces en fin de ligne (lignes $(grep -nE '[[:blank:]]+$' "$f" | cut -d: -f1 | paste -sd, -))"
|
||||
|
||||
+25
-3
@@ -43,8 +43,28 @@ check "app/main.js" "text/javascript"
|
||||
check "app/ui/app.js" "text/javascript"
|
||||
check "app/domain/game/game.js" "text/javascript"
|
||||
check "manifest.webmanifest" "application/manifest\+json"
|
||||
check "icons/icon-192.png" "image/png"
|
||||
check "icons/icon-512.png" "image/png"
|
||||
# Charte graphique (ADR 0010) : icônes et logo copiés de docs/identite/logo/,
|
||||
# police Alegreya Sans et sa licence.
|
||||
check "icons/favicon.svg" "image/svg\+xml"
|
||||
check "icons/favicon-32.png" "image/png"
|
||||
check "icons/apple-touch-icon-180.png" "image/png"
|
||||
check "icons/logo-horizontal-couleur.svg" "image/svg\+xml"
|
||||
check "icons/logo-horizontal-sombre.svg" "image/svg\+xml"
|
||||
check "icons/symbole-couleur.svg" "image/svg\+xml"
|
||||
check "icons/symbole-sombre.svg" "image/svg\+xml"
|
||||
cmp -s <(curl -s "$base/icons/icone-512.png") docs/identite/logo/icone-512.png \
|
||||
|| report "/icons/icone-512.png diffère de docs/identite/logo/"
|
||||
for weight in Regular Italic Medium Bold ExtraBold; do
|
||||
check "fonts/AlegreyaSans-$weight.woff2" "font/woff2"
|
||||
done
|
||||
check "fonts/OFL.txt" "text/plain"
|
||||
# Chaque fichier désigné par la page (icônes, police, style) est servi.
|
||||
page="$(curl -s "$base/")"
|
||||
while read -r ref; do
|
||||
curl -sf -o /dev/null "$base/$ref" || report "index.html : $ref introuvable"
|
||||
done < <(grep -oE '(href|src|srcset)="[^"]*"' <<<"$page" | sed -E 's/^[a-z]+="(.*)"$/\1/')
|
||||
grep -qF "url(\"fonts/AlegreyaSans-Regular.woff2\")" <(curl -s "$base/app.css") \
|
||||
|| report "app.css : police Alegreya Sans non déclarée"
|
||||
check "sw.js" "text/javascript"
|
||||
check "engine-worker.mjs" "text/javascript"
|
||||
check "wasm/engine.mjs" "text/javascript"
|
||||
@@ -65,6 +85,8 @@ cmp -s <(curl -s "$base/docs/glossaire/videau.md") docs/glossaire/videau.md \
|
||||
# Le manifeste désigne des icônes servies, et l'application à la racine.
|
||||
manifest="$(curl -s "$base/manifest.webmanifest")"
|
||||
grep -q '"start_url": "./"' <<<"$manifest" || report "manifeste : start_url absent"
|
||||
grep -q '"theme_color": "#4A2E1F"' <<<"$manifest" || report "manifeste : theme_color autre que le noyer"
|
||||
grep -q '"purpose": "maskable"' <<<"$manifest" || report "manifeste : pas d'icône maskable"
|
||||
while read -r icon; do
|
||||
curl -sf -o /dev/null "$base/$icon" || report "manifeste : icône $icon introuvable"
|
||||
done < <(grep -o '"src": "[^"]*"' <<<"$manifest" | sed 's/"src": "\(.*\)"/\1/')
|
||||
@@ -73,7 +95,7 @@ done < <(grep -o '"src": "[^"]*"' <<<"$manifest" | sed 's/"src": "\(.*\)"/\1/')
|
||||
sw="$(curl -s "$base/sw.js")"
|
||||
grep -qE '^const VERSION = "[0-9a-f]{16}";' <<<"$sw" || report "sw.js : version absente"
|
||||
for file in ./ index.html app/main.js wasm/engine.data wasm/engine.wasm moteur/ \
|
||||
docs/glossaire/videau.md version.json; do
|
||||
docs/glossaire/videau.md version.json fonts/AlegreyaSans-Bold.woff2 icons/favicon.svg; do
|
||||
grep -qF " \"$file\"," <<<"$sw" || report "sw.js : $file absent de la liste à mettre en cache"
|
||||
done
|
||||
listed="$(grep -c '^ "' <<<"$sw")"
|
||||
|
||||
+396
-110
@@ -1,81 +1,191 @@
|
||||
/* Style de l'application. Palette provisoire et sobre (bois, ivoire, un
|
||||
* accent vert), en attendant l'identité définitive : toutes les couleurs et
|
||||
* tailles sont des variables, redéfinies pour le thème sombre. */
|
||||
/* Style de l'application, selon la charte graphique (ADR 0010,
|
||||
* docs/identite/charte/) : les couleurs, tailles et rayons sont les
|
||||
* variables de tokens.json, sous le même nom, redéfinies pour le thème
|
||||
* sombre ; les composants reprennent bundle.css. */
|
||||
|
||||
/* ------------------------------------------------------------ Police */
|
||||
|
||||
/* Alegreya Sans (SIL Open Font License 1.1, voir fonts/OFL.txt), réduite
|
||||
* aux caractères latins et convertie en woff2 (voir docs/identite/README.md).
|
||||
* Embarquée dans le site, donc dans le cache du service worker : elle
|
||||
* fonctionne hors ligne. */
|
||||
@font-face {
|
||||
font-family: "Alegreya Sans";
|
||||
src: url("fonts/AlegreyaSans-Regular.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Alegreya Sans";
|
||||
src: url("fonts/AlegreyaSans-Italic.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Alegreya Sans";
|
||||
src: url("fonts/AlegreyaSans-Medium.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Alegreya Sans";
|
||||
src: url("fonts/AlegreyaSans-Bold.woff2") format("woff2");
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Alegreya Sans";
|
||||
src: url("fonts/AlegreyaSans-ExtraBold.woff2") format("woff2");
|
||||
font-weight: 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ Couleurs */
|
||||
|
||||
:root {
|
||||
--text: #2b211a;
|
||||
--muted: #6b5d50;
|
||||
--background: #f7f2e8;
|
||||
--accent: #2e6b4f;
|
||||
--background: #f5efe5;
|
||||
--surface: #fffcf6;
|
||||
--surface-sunken: #ece3d4;
|
||||
--text: #2b1d15;
|
||||
--muted: #6b5c50;
|
||||
--border: #ded2c0;
|
||||
--border-strong: #8e7d6a;
|
||||
--accent: #a8432b;
|
||||
--on-accent: #ffffff;
|
||||
--border: #cbbfae;
|
||||
--error: #a3261b;
|
||||
--accent-soft: #f1d9cf;
|
||||
--focus: #a8432b;
|
||||
--error: #8c1c3a;
|
||||
--verdict-good: #2f6b4f;
|
||||
--verdict-doubtful: #7e5a0f;
|
||||
--verdict-bad: #8c1c3a;
|
||||
--brand-noyer: #4a2e1f;
|
||||
--brand-ivoire: #f3ead8;
|
||||
--brand-laiton: #8f6a1c;
|
||||
|
||||
--frame: #6b4426;
|
||||
--bed: #e6cfa6;
|
||||
--bar: #5c3a20;
|
||||
--tray: #57371e;
|
||||
--point-a: #9a6439;
|
||||
--point-b: #c79b69;
|
||||
--point-number: #f3e7d0;
|
||||
--checker-player: #fbf6ea;
|
||||
--checker-player-edge: #7d6a52;
|
||||
--checker-computer: #2b221c;
|
||||
--checker-computer-edge: #0d0a08;
|
||||
--count-player: #2b211a;
|
||||
--count-computer: #fbf6ea;
|
||||
--die-player: #fbf6ea;
|
||||
--pip-player: #2b211a;
|
||||
--die-computer: #2b221c;
|
||||
--pip-computer: #fbf6ea;
|
||||
--cube: #fbf6ea;
|
||||
--cube-text: #2b211a;
|
||||
--highlight: #2e6b4f;
|
||||
--target: rgba(46, 107, 79, 0.35);
|
||||
--last-move: #d9822b;
|
||||
--frame: #4a2e1f;
|
||||
--bed: #e9dcc4;
|
||||
--bar: #3f2619;
|
||||
--tray: #3a2418;
|
||||
--point-a: #6b4430;
|
||||
--point-b: #c9ae88;
|
||||
--point-number: #f3ead8;
|
||||
--checker-player: #f7f0e2;
|
||||
--checker-player-edge: #8c7a62;
|
||||
--checker-computer: #2e1c12;
|
||||
--checker-computer-edge: #120a05;
|
||||
--count-player: #2b1d15;
|
||||
--count-computer: #f3ead8;
|
||||
--die-player: #f7f0e2;
|
||||
--pip-player: #2b1d15;
|
||||
--die-computer: #2e1c12;
|
||||
--pip-computer: #f3ead8;
|
||||
--cube: #f7f0e2;
|
||||
--cube-text: #2b1d15;
|
||||
--highlight: #a8432b;
|
||||
--target: rgba(168, 67, 43, 0.32);
|
||||
/* Dernier coup joué (marques et traces) : le laiton, rien d'autre. */
|
||||
--last-move: var(--brand-laiton);
|
||||
|
||||
--font-size: 17px;
|
||||
/* Numéros des flèches. La charte les pose sur le cadre (point-number,
|
||||
* ivoire) ; notre plateau les pose sur le fond, où l'ivoire ne se lirait
|
||||
* pas : texte secondaire en thème clair (4,7:1 sur bed), point-number en
|
||||
* thème sombre (6,3:1). */
|
||||
--point-label: var(--muted);
|
||||
|
||||
--shadow-raised: 0 1px 2px rgba(43, 29, 21, 0.1);
|
||||
|
||||
--font-sans: "Alegreya Sans", "Gill Sans", "Segoe UI", system-ui, sans-serif;
|
||||
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-6: 24px;
|
||||
--space-8: 32px;
|
||||
--button-height: 48px;
|
||||
--radius: 8px;
|
||||
--page-width: 40rem;
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 12px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
/* Hauteur de la barre haute : boutons de 44 px et marges. */
|
||||
--topbar-height: 60px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--text: #ece4d6;
|
||||
--muted: #b3a796;
|
||||
--background: #1b1714;
|
||||
--accent: #6fc3a0;
|
||||
--on-accent: #10221a;
|
||||
--border: #4d443b;
|
||||
--error: #f08a7e;
|
||||
--background: #17110d;
|
||||
--surface: #221913;
|
||||
--surface-sunken: #2c211a;
|
||||
--text: #efe5d5;
|
||||
--muted: #a99886;
|
||||
--border: #3a2d24;
|
||||
--border-strong: #7d6b5b;
|
||||
--accent: #d0694b;
|
||||
--on-accent: #1a110c;
|
||||
--accent-soft: #4a2519;
|
||||
--focus: #e58a6e;
|
||||
--error: #f08fa6;
|
||||
--verdict-good: #7ccba4;
|
||||
--verdict-doubtful: #e0b04f;
|
||||
--verdict-bad: #f08fa6;
|
||||
--brand-laiton: #d6a64a;
|
||||
|
||||
--frame: #3a2616;
|
||||
--bed: #6d5136;
|
||||
--bar: #2f1f12;
|
||||
--tray: #2c1d11;
|
||||
--point-a: #4a3120;
|
||||
--point-b: #8b6943;
|
||||
--point-number: #d9cbb4;
|
||||
--checker-player: #efe6d2;
|
||||
--frame: #2a1a10;
|
||||
--bed: #4a3626;
|
||||
--bar: #24170e;
|
||||
--tray: #22150d;
|
||||
--point-a: #3a2519;
|
||||
--point-b: #7e6142;
|
||||
--point-number: #cdbfa8;
|
||||
--checker-player: #ede3cf;
|
||||
--checker-player-edge: #8e7c64;
|
||||
--checker-computer: #17110d;
|
||||
--checker-computer: #140d09;
|
||||
--checker-computer-edge: #a8957d;
|
||||
--highlight: #7fd6b0;
|
||||
--target: rgba(127, 214, 176, 0.35);
|
||||
--last-move: #f0a050;
|
||||
--count-player: #2b1d15;
|
||||
--count-computer: #f3ead8;
|
||||
--die-player: #ede3cf;
|
||||
--pip-player: #2b1d15;
|
||||
--die-computer: #140d09;
|
||||
--pip-computer: #ede3cf;
|
||||
--cube: #ede3cf;
|
||||
--cube-text: #2b1d15;
|
||||
--highlight: #e07a5a;
|
||||
--target: rgba(224, 122, 90, 0.38);
|
||||
|
||||
--point-label: var(--point-number);
|
||||
|
||||
--shadow-raised: 0 1px 2px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------- Page */
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-text-size-adjust: 100%;
|
||||
text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0 auto;
|
||||
max-width: var(--page-width);
|
||||
padding: 0 0.5rem 1rem;
|
||||
font: var(--font-size) / 1.45 system-ui, sans-serif;
|
||||
padding: 0 var(--space-4) var(--space-6);
|
||||
font-family: var(--font-sans);
|
||||
font-size: 17px;
|
||||
line-height: 25px;
|
||||
color: var(--text);
|
||||
background: var(--background);
|
||||
}
|
||||
@@ -84,78 +194,148 @@ body {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
/* Anneau de focus clavier, sur tout élément interactif. */
|
||||
:focus-visible {
|
||||
outline: 3px solid var(--focus);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------- Barre haute */
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
gap: var(--space-2);
|
||||
align-items: center;
|
||||
padding: 0.5rem 0;
|
||||
min-height: var(--topbar-height);
|
||||
padding-block: var(--space-2);
|
||||
}
|
||||
|
||||
.topbar h1 {
|
||||
.topbar .brand {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.topbar .brand img {
|
||||
display: block;
|
||||
width: auto;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ Boutons */
|
||||
|
||||
/* Ronds comme des pions ; un seul bouton principal (brique) par écran,
|
||||
* les autres en contour. */
|
||||
button {
|
||||
min-height: var(--button-height);
|
||||
padding: 0 1rem;
|
||||
padding: 0 20px;
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
font-weight: 700;
|
||||
color: var(--on-accent);
|
||||
background: var(--accent);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: var(--radius);
|
||||
border: 2px solid var(--accent);
|
||||
border-radius: var(--radius-pill);
|
||||
cursor: pointer;
|
||||
/* Pas de délai ni de zoom sur un double toucher. */
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
button.secondary {
|
||||
color: var(--text);
|
||||
background: transparent;
|
||||
border-color: var(--border);
|
||||
border-color: var(--border-strong);
|
||||
}
|
||||
|
||||
.topbar button {
|
||||
min-height: 40px;
|
||||
padding: 0 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
@media (hover: hover) {
|
||||
button.secondary:hover:not(:disabled) {
|
||||
background: var(--surface-sunken);
|
||||
}
|
||||
}
|
||||
|
||||
/* Retour visuel au toucher. */
|
||||
button:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.topbar button {
|
||||
min-height: 44px;
|
||||
padding: 0 14px;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.actions button {
|
||||
flex: 1;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* --------------------------------------------- Messages sous le plateau */
|
||||
|
||||
/* Compte de points : information secondaire, discrète. */
|
||||
.pips {
|
||||
margin: 0.25rem 0 0;
|
||||
font-size: 0.85rem;
|
||||
margin: var(--space-2) 0 0;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
font-variant-numeric: tabular-nums lining-nums;
|
||||
}
|
||||
|
||||
.info,
|
||||
.prompt {
|
||||
min-height: 1.45em;
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
|
||||
/* Ce qui vient de se passer (coup de l'ordinateur, jet d'ouverture) :
|
||||
* secondaire, mais lisible. */
|
||||
.info {
|
||||
min-height: 25px;
|
||||
margin: var(--space-3) 0 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.prompt {
|
||||
font-weight: 600;
|
||||
.info:empty {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* L'ordinateur réfléchit : trois points qui apparaissent l'un après l'autre. */
|
||||
/* La consigne du moment : ce qui doit ressortir de l'écran. Fond d'accent
|
||||
* léger quand c'est au joueur, neutre pendant que l'ordinateur réfléchit. */
|
||||
.prompt {
|
||||
min-height: calc(25px + 2 * var(--space-3));
|
||||
margin: var(--space-3) 0;
|
||||
padding: var(--space-3) var(--space-4);
|
||||
font-weight: 700;
|
||||
background: var(--accent-soft);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.prompt.thinking,
|
||||
body.busy .prompt {
|
||||
font-weight: 400;
|
||||
background: var(--surface-sunken);
|
||||
}
|
||||
|
||||
/* Fin de partie : le résultat en titre. */
|
||||
#game:has(#again:not([hidden])) .prompt {
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
/* L'ordinateur réfléchit : trois points qui apparaissent l'un après
|
||||
* l'autre (la seule animation en boucle). */
|
||||
.prompt.thinking::after {
|
||||
content: "";
|
||||
animation: thinking 1.2s steps(4, end) infinite;
|
||||
@@ -168,6 +348,17 @@ button:disabled {
|
||||
75% { content: "..."; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.prompt.thinking::after {
|
||||
animation: none;
|
||||
content: "…";
|
||||
}
|
||||
|
||||
button:active:not(:disabled) {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ Plateau */
|
||||
|
||||
.board {
|
||||
@@ -176,7 +367,23 @@ button:disabled {
|
||||
height: auto;
|
||||
aspect-ratio: 640 / 520;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
font-family: var(--font-sans);
|
||||
/* Alegreya Sans dessine par défaut des chiffres elzéviriens (de hauteurs
|
||||
* variables) : sur le plateau, des chiffres alignés se lisent mieux. */
|
||||
font-variant-numeric: tabular-nums lining-nums;
|
||||
}
|
||||
|
||||
/* Téléphone en portrait : le plateau, héros de l'écran, prend la largeur
|
||||
* presque entière (8 px de marge au lieu de 16) ; chaque pixel compte pour
|
||||
* toucher un pion. */
|
||||
@media (max-width: 40rem) and (orientation: portrait) {
|
||||
.board {
|
||||
width: calc(100% + 2 * (var(--space-4) - var(--space-2)));
|
||||
margin-inline: calc(var(--space-2) - var(--space-4));
|
||||
}
|
||||
}
|
||||
|
||||
.board-frame { fill: var(--frame); }
|
||||
@@ -187,8 +394,9 @@ button:disabled {
|
||||
.point-b { fill: var(--point-b); }
|
||||
|
||||
.point-number {
|
||||
fill: var(--muted);
|
||||
fill: var(--point-label);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
@@ -206,14 +414,24 @@ button:disabled {
|
||||
stroke: var(--checker-computer-edge);
|
||||
}
|
||||
|
||||
/* Filet intérieur des pions (bundle.css), prêt pour le jour où le plateau
|
||||
* le dessine. */
|
||||
.checker-ring {
|
||||
fill: none;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.ring-player { stroke: var(--checker-player-edge); }
|
||||
.ring-computer { stroke: var(--checker-computer-edge); }
|
||||
|
||||
.checker.movable {
|
||||
stroke: var(--highlight);
|
||||
stroke-width: 4;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.checker.selected {
|
||||
stroke: var(--highlight);
|
||||
stroke-width: 7;
|
||||
stroke-width: 6;
|
||||
}
|
||||
|
||||
.checker-count {
|
||||
@@ -254,8 +472,8 @@ button:disabled {
|
||||
stroke-dasharray: 5 4;
|
||||
}
|
||||
|
||||
.die-player { fill: var(--die-player); stroke: var(--checker-player-edge); }
|
||||
.die-computer { fill: var(--die-computer); stroke: var(--checker-computer-edge); }
|
||||
.die-player { fill: var(--die-player); stroke: var(--checker-player-edge); stroke-width: 1.5; }
|
||||
.die-computer { fill: var(--die-computer); stroke: var(--checker-computer-edge); stroke-width: 1.5; }
|
||||
.pip-player { fill: var(--pip-player); }
|
||||
.pip-computer { fill: var(--pip-computer); }
|
||||
|
||||
@@ -286,7 +504,7 @@ button:disabled {
|
||||
.cube-value {
|
||||
fill: var(--cube-text);
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
font-weight: 800;
|
||||
text-anchor: middle;
|
||||
}
|
||||
|
||||
@@ -297,70 +515,138 @@ button:disabled {
|
||||
|
||||
/* --------------------------------------------------------------- Aide */
|
||||
|
||||
.help {
|
||||
max-width: var(--page-width);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.help h2 {
|
||||
margin-top: 1.5rem;
|
||||
font-size: 1.2rem;
|
||||
margin: var(--space-8) 0 var(--space-2);
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.help h3 {
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 1rem;
|
||||
margin: var(--space-6) 0 var(--space-1);
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.help p {
|
||||
margin: var(--space-2) 0;
|
||||
}
|
||||
|
||||
.steps {
|
||||
padding-left: 1.25rem;
|
||||
margin: var(--space-2) 0;
|
||||
padding-left: var(--space-6);
|
||||
}
|
||||
|
||||
.steps li + li {
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
.steps li::marker {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Glossaire : une liste de définitions, sur une carte. */
|
||||
.glossary {
|
||||
margin: var(--space-3) 0 0;
|
||||
padding: var(--space-1) var(--space-4);
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-raised);
|
||||
}
|
||||
|
||||
.glossary dt {
|
||||
margin-top: 0.6rem;
|
||||
font-weight: 600;
|
||||
padding-top: var(--space-3);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.glossary dd {
|
||||
margin: 0;
|
||||
padding-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.glossary dd + dt {
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.about {
|
||||
margin-top: 2rem;
|
||||
font-size: 0.85rem;
|
||||
margin-top: var(--space-8);
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Une cible tactile confortable pour le lien. */
|
||||
.about a {
|
||||
color: var(--accent);
|
||||
display: inline-block;
|
||||
padding-block: 12px;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------- Mode paysage */
|
||||
|
||||
/* Téléphone tourné : le plateau prend toute la hauteur disponible, et les
|
||||
* messages et boutons passent dans une colonne à sa droite. */
|
||||
/* Téléphone tourné : le plateau prend toute la hauteur de l'écran, à
|
||||
* gauche ; la barre haute, les messages et les boutons passent dans une
|
||||
* colonne à sa droite. */
|
||||
@media (orientation: landscape) and (max-height: 40rem) {
|
||||
body {
|
||||
max-width: none;
|
||||
padding: 0 var(--space-4);
|
||||
}
|
||||
|
||||
.topbar {
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
#game {
|
||||
/* Le jeu en deux colonnes : #game laisse ses enfants dans la grille de
|
||||
* la page. Quand l'aide est affichée, #game est caché, la première
|
||||
* colonne n'a plus de largeur et l'aide prend toute la place. */
|
||||
body {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(12rem, 1fr);
|
||||
grid-template-rows: auto auto auto 1fr;
|
||||
column-gap: 1rem;
|
||||
grid-template-columns: auto minmax(13rem, 1fr);
|
||||
grid-template-rows: auto auto auto auto auto 1fr;
|
||||
column-gap: var(--space-4);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
#game {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
min-height: 0;
|
||||
padding-block: var(--space-2) 0;
|
||||
}
|
||||
|
||||
.board {
|
||||
grid-row: 1 / 5;
|
||||
grid-column: 1;
|
||||
grid-row: 1 / -1;
|
||||
width: auto;
|
||||
/* Hauteur de l'écran moins la barre du haut (boutons de 40 px). */
|
||||
height: calc(100vh - 3.5rem);
|
||||
height: calc(100dvh - 3.5rem);
|
||||
height: calc(100vh - 2 * var(--space-2));
|
||||
height: calc(100dvh - 2 * var(--space-2));
|
||||
max-width: 70vw;
|
||||
margin-block: var(--space-2);
|
||||
}
|
||||
|
||||
.pips,
|
||||
.info,
|
||||
.prompt,
|
||||
.actions {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.pips {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.help {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
||||
Copyright 2013 The Alegreya Sans Project Authors (https://github.com/huertatipografica/Alegreya-Sans)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.1 KiB |
@@ -1,7 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<!-- Icône provisoire (pas un logo) : deux pions sur du bois. Les PNG sont
|
||||
produits à partir des mêmes formes par scripts/make-icons.py. -->
|
||||
<rect width="512" height="512" fill="#6b4426"/>
|
||||
<circle cx="300" cy="300" r="120" fill="#2b221c" stroke="#0d0a08" stroke-width="10"/>
|
||||
<circle cx="212" cy="212" r="120" fill="#fbf6ea" stroke="#7d6a52" stroke-width="10"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 449 B |
+18
-4
@@ -4,17 +4,31 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="theme-color" content="#6b4426">
|
||||
<meta name="theme-color" content="#4A2E1F">
|
||||
<title>Ustagammon</title>
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<link rel="icon" href="icons/icon.svg" type="image/svg+xml">
|
||||
<link rel="icon" href="icons/favicon.svg" type="image/svg+xml">
|
||||
<link rel="icon" href="icons/favicon-32.png" sizes="32x32" type="image/png">
|
||||
<link rel="apple-touch-icon" href="icons/apple-touch-icon-180.png">
|
||||
<link rel="preload" href="fonts/AlegreyaSans-Regular.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="fonts/AlegreyaSans-Bold.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<script type="module" src="app/main.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="busy">
|
||||
<!-- Les textes des boutons et des messages viennent de app/src/ui/texts.ts. -->
|
||||
<header class="topbar">
|
||||
<h1>Ustagammon</h1>
|
||||
<!-- Logo de la charte (docs/identite/logo/, copié dans icons/ par make
|
||||
site) : horizontal, ou symbole seul quand la place manque (téléphone
|
||||
étroit, colonne du mode paysage) ; version sombre en thème sombre. -->
|
||||
<h1 class="brand">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark) and (max-width: 24.5rem), (prefers-color-scheme: dark) and (orientation: landscape) and (max-height: 40rem)" srcset="icons/symbole-sombre.svg">
|
||||
<source media="(max-width: 24.5rem), (orientation: landscape) and (max-height: 40rem)" srcset="icons/symbole-couleur.svg">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="icons/logo-horizontal-sombre.svg">
|
||||
<img src="icons/logo-horizontal-couleur.svg" alt="Ustagammon">
|
||||
</picture>
|
||||
</h1>
|
||||
<button id="help-button" type="button" class="secondary"></button>
|
||||
<button id="new-game" type="button" class="secondary"></button>
|
||||
</header>
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
"scope": "./",
|
||||
"display": "standalone",
|
||||
"orientation": "any",
|
||||
"background_color": "#f7f2e8",
|
||||
"theme_color": "#6b4426",
|
||||
"background_color": "#F5EFE5",
|
||||
"theme_color": "#4A2E1F",
|
||||
"icons": [
|
||||
{ "src": "icons/icon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png" },
|
||||
{ "src": "icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
|
||||
{ "src": "icons/icon.svg", "sizes": "any", "type": "image/svg+xml" }
|
||||
{ "src": "icons/icone-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "icons/icone-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
|
||||
{ "src": "icons/icone-maskable-192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" },
|
||||
{ "src": "icons/icone-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
|
||||
{ "src": "icons/icone.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
// Sur les positions de référence (tests/reference/positions.json) : temps du
|
||||
// meilleur coup à 0 et 2 plis pour les positions avec dés, temps de la
|
||||
// décision de videau à 2 plis pour les autres. Le rapport compare aux seuils
|
||||
// de l'étape 0b (docs/etape-0.md).
|
||||
// de l'étape 0b (ADR 0009).
|
||||
|
||||
// Seuils de l'étape 0b. Un temps respecte son seuil si son maximum, sur
|
||||
// toutes les positions, est en dessous (la médiane est donnée en plus).
|
||||
|
||||
Reference in New Issue
Block a user