Coup de l'ordinateur montré, aucun coup possible, dés et videau touchables

Fusion du travail du sous-agent (branche coup-adverse-travail) avec la charte
graphique de main ; plan supprimé à la clôture, son résultat est repris dans
la merge request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Pierre MARTIN
2026-11-03 05:06:09 +01:00
co-authored by Claude Opus 5.5
23 changed files with 699 additions and 431 deletions
+59
View File
@@ -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.
+55
View File
@@ -0,0 +1,55 @@
# 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.
+2
View File
@@ -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é |
+2 -2
View File
@@ -21,7 +21,7 @@ garde-fous d'architecture (0d.1), déploiement sur https://ustagammon.sans.pub.
| 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` |
| 0d.2+ | Fluidité de la partie : supprimer ce qui interrompt le jeu sans nécessité (bouton « Nouvelle partie » toujours visible, confirmations, étapes inutiles) : demande de Pierre | ⏳ | |
**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.
@@ -30,7 +30,7 @@ par coup de l'IA en niveau standard, tous les tests au vert.
| Étape | Contenu | État |
| --- | --- | --- |
| 1. V1 Jouer et comprendre | Plateau soigné, niveaux, coach, analyse, branches, tutoriel, prompts, sauvegarde | ⏳ |
| 1. V1 Jouer et comprendre | Charte graphique appliquée à la PWA (ADR 0010, `docs/identite/charte/`), plateau soigné, niveaux, conseil du maître après le coup, bilan de fin de partie, premières leçons numérotées (ADR 0011), mode immersif en paysage (ADR 0012), analyse, branches, tutoriel, prompts, sauvegarde | ⏳ |
| 2. V1.5 Apprendre | Parcours 2, exercices, progression | ⏳ |
| 3. V2 Perfectionnement et IA | Parcours 3 et 4, rollouts, révision espacée | ⏳ |
+18
View File
@@ -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
-220
View File
@@ -1,220 +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.
**Ajout : aucun coup possible** :
- Domaine : `Game.roll()` ne fait plus passer le tour. `Game.cannotMove()`
dit, d'après `legalMoves` du moteur, que le jet ne permet aucun coup.
`Game.skipTurn()` enregistre alors le coup vide (même événement
`MovePlayed` sans demi-coup qu'avant, format inchangé) ; il est refusé s'il
existe un coup. Le nom `skipTurn` évite la confusion avec « passer » (le
double), qui est `drop` dans le glossaire.
- Interface : bouton « Continuer » (`#skip`), message « Aucun coup possible
avec 6-5. », consigne « Touchez « Continuer » : le tour passe à
l'ordinateur. », dés estompés et barrés (`.die-blocked`, `.die-strike`).
Quand l'ordinateur ne peut pas jouer, ses dés restent barrés avec « L'ordinateur
ne peut pas jouer 5-5. » jusqu'à l'action suivante. Ces deux messages se
déduisent du journal : ils survivent à un rechargement.
- Tests : scénario « Aucun coup possible avec votre jet » (pion sur la barre
face aux six cases tenues, 6-5), tests unitaires `cannotMove`/`skipTurn`
(faux moteur), partie complète adaptée. Dans le navigateur, « Continuer »
a été demandé 1 fois (Chromium) et 3 fois (Firefox) : message et deux dés
barrés vérifiés à chaque fois. Le jet sans coup de l'ordinateur est vérifié
quand il survient (pas survenu dans ces deux parties).
**Ajout : toucher les dés et le videau** :
- Quand vous pouvez lancer, deux dés « en attente » (faces vides, contour en
pointillés) apparaissent au milieu de la moitié gauche. La moitié droite
garde le dernier jet de l'ordinateur, qui doit rester visible. Quand vous
pouvez doubler (`Game.canDouble()`, comme le bouton), le videau est souligné.
- Les toucher clique le bouton correspondant (`#roll`, `#double`) : mêmes
conditions, même effet. Hors de ces moments, aucune zone n'est posée.
- Zones à toucher : `role="button"`, `aria-label` (« Lancer les dés »,
« Doubler »), au moins 88 unités, soit 47 px sur le plateau de 344 px d'un
téléphone de 360 px. Les boutons restent pour le clavier ; les zones n'ont
pas de `tabindex`, pour ne pas doubler l'ordre de tabulation.
- Navigateur : dés touchés 10 fois (Chromium) et 14 fois (Firefox), videau
touché pour doubler, taille et rôle vérifiés ; aucune zone pendant la
saisie d'un coup.
**Résultats finaux** (dernier commit vérifié) : `make lint` sans problème ;
`make test` 10/10 ; `make test-browser` vert sous Chromium et Firefox, sans
violation CSP ni erreur dans la page. Le coup de l'ordinateur a été montré 21
et 30 fois. Dans Firefox, un coup revu a frappé deux pions, tous deux signalés sur la
barre.
**Limites** :
- Un premier essai de `make test-browser` a échoué dans `docker build`
(« parent snapshot … does not exist »), sans lien avec le code
(probablement une autre construction Docker en parallèle). L'essai suivant
est passé.
- Firefox affiche des tours de l'ordinateur de 3 à 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.
- Les dés en attente sont à gauche alors que le jet lancé s'affiche à
droite : c'est un choix d'interface, à revoir avec la charte si besoin.
- Sur une case de plus de cinq pions, c'est le cinquième pion dessiné qui
glisse (le nombre reste affiché sur la pile).