17 KiB
Escampe — Rapport (version finale)
Université Paris-Saclay — Polytech APP5 — Année 2025-2026 — « IA et contraintes »
Binôme : Ethan Puyaubreau & Antonin Russac — 30 mai 2026
Joueur : escampe.JoueurPuyaubreauRussac
Ce fichier est le miroir Markdown du rapport. La version PDF mise en page (
dist/Puyaubreau_Russac_rapport.pdf) est générée depuisreport/rapport.htmlparpython tools/make_report_pdf.py(PyMuPDF, sans dépendance externe).
Sommaire
- Présentation et règles
- Analyse des caractéristiques du jeu (Q1–Q7)
- Modélisation : la classe
EscampeBoard - Intégration au tournoi : protocole de l'arbitre
- Placement d'ouverture
- Moteur de décision
- Heuristique d'évaluation
- Gestion du temps réel
- Performances et tests
- Compilation, exécution et livrables
- Sources et bibliographie
- Conclusion et difficultés rencontrées
1. Présentation et règles
Escampe se joue sur un plateau de 36 cases (6×6). Chaque case porte un liseré simple, double ou triple. Chaque joueur dispose d'une licorne et de cinq paladins (noirs ou blancs). Lignes numérotées de 1 à 6, colonnes de A à F. Le but est de prendre la licorne adverse.
Règle caractéristique — la contrainte de liseré : la pièce jouée doit partir d'une case dont le liseré est identique à celui de la case d'arrivée du coup adverse précédent. Le liseré de départ fixe le nombre de pas (1, 2 ou 3), orthogonaux, sans traverser ni revisiter de case. On ne capture qu'en se posant, au dernier pas, sur la licorne adverse (paladins imprenables). Sans coup possible, on passe son tour. Toute la difficulté consiste donc à "coincer" son adversaire.
Déroulement : Noir place ses six pièces sur les deux lignes d'un bord (spécifié dans l'énnoncé : haut ou bas) ; Blanc sur le bord opposé ; Blanc joue le premier coup.
2. Analyse des caractéristiques du jeu (Q1–Q7)
Q1 — Modélisation d'un état
Plateau int[6][6] (board[ligne][colonne], ligne 0 = ligne 1 en bas, colonne
0 = A). Chaque case : EMPTY, WHITE_LICORNE, WHITE_PALADIN, BLACK_LICORNE,
BLACK_PALADIN. État hors-plateau : lastTileType (liseré imposé, -1 = libre),
currentPlayer, blackPlaced/whitePlaced, blackRows (bord de Noir).
- Avantages : accès O(1), copie immédiate pour l'arbre de recherche,
sérialisation triviale, et surtout
make/unmakesans allocation (clé de la vitesse, §6). - Inconvénient : la contrainte de liseré est un état séparé à maintenir
(encapsulé dans
play).
Carte des liserés TILE_MAP (figure 4, ligne 1 en bas) :
A B C D E F
6 3 2 2 1 3 2
5 1 3 1 3 1 2
4 2 1 3 2 3 1
3 2 3 1 2 1 3
2 3 1 3 1 3 2
1 1 2 2 3 1 2
Vérifié : cette carte est identique, case pour case, à celle utilisée en interne par l'arbitre (extraite par réflexion de la classe de jeu du serveur), et cohérente avec l'exemple tactique de la figure 6. Point critique : une carte divergente aurait produit des coups jugés illégaux.
Q2 — Détection de fin de partie
Partie finie dès qu'une licorne disparaît (seul cas, pas de nul). Balayage O(1)
(gameOver) ; le moteur détecte la capture au moment où elle est jouée.
Q3 — Sources de difficulté et facteur de branchement
Difficultés : contrainte de liseré (mobilité variable), dépendance entre tours (la case d'arrivée détermine les options adverses), asymétrie du plateau, risque de blocage / pass forcé.
Facteur de branchement. Borne théorique lâche estimée en partie 1 : ~120. La
mesure réelle (utilitaire escampe.Branching, 30 000 parties aléatoires) est bien
plus basse car la contrainte de liseré ne laisse jouables que les pièces du bon
liseré :
| Situation | Branchement max observé |
|---|---|
| Coup contraint (un liseré imposé) | 45 |
| Coup libre (1er coup ou après pass) | 49 |
| Branchement moyen (toutes positions) | ≈ 8,9 |
Le branchement effectif modeste explique les profondeurs élevées atteintes par l'alpha-bêta (§6).
Q4 — Coups imparables
Pas d'« imparable » universel garanti dès le départ : la contrainte de liseré peut toujours bloquer une menace, car l'adversaire choisit sa case d'arrivée et donc le liseré qu'il impose au tour suivant. En revanche, certaines configurations créent un zugzwang partiel — l'adversaire est forcé d'imposer précisément le liseré qui autorise la capture.
Exemple concret (figure 6 de l'énoncé). Noir vient de poser sa pièce en D4 (liseré double, TILE_MAP = 2). Blanc doit donc jouer depuis une case à liseré double. Il choisit F6–E5 (F6 est à liseré double). Noir doit maintenant jouer depuis une case à liseré simple (E5 = 1) : son seul coup raisonnable est A1–A2. A2 est à liseré triple (TILE_MAP = 3). Blanc joue alors C2×C1 : le paladin en C2 (liseré 3) effectue les trois pas C2→D2→D1→C1 et capture la licorne noire. La séquence F6–E5 / A1–A2 / C2×C1 est donc un « imparable local » : dès que Noir est forcé d'atterrir en A2, la capture est inévitable.
Ce type de combinaison est inexploitable de façon générale depuis le début de la partie (trop de degrés de liberté), mais notre alpha-bêta le détecte et le joue dès qu'il est à portée d'horizon.
Q5 — Critères pour l'heuristique
Cinq critères identifiés : distance à la licorne adverse, mobilité différentielle, contrôle du liseré imposé, protection de sa licorne, avancée. Retenu en pratique (§7) : proximité des paladins à la licorne adverse (attaque) et éloignement des paladins adverses de notre licorne (défense) — le reste est largement pris en charge par la recherche.
Q6 — Stratégie selon la phase
- Début (placement) : irréversible ; protéger la licorne, garantir de toujours pouvoir jouer (§5).
- Milieu : manœuvrer pour menacer la licorne adverse en contrôlant le liseré imposé ; chercher le zugzwang partiel.
- Fin : dès qu'une capture est à portée, le calcul tactique prime.
Q7 — Majorant du nombre de coups et gestion du temps
Aucune pièce ne disparaît avant la capture finale : borne raisonnable ~400–600 demi-coups. Pour tenir les 300 s/joueur : approfondissement itératif, alpha-bêta, budget par coup dérivé du temps restant (§8).
3. Modélisation : la classe EscampeBoard
EscampeBoard implémente Partie1 (setFromFile/saveToFile,
isValidMove, possiblesMoves, play, gameOver). Conventions de l'arbitre :
coup "B1-D1", placement "C6/A6/B5/D5/E6/F5" (licorne en tête), pass "E".
Format fichier : 6 lignes de plateau (bas→haut), N/n B/b -, encadrées
d'un numéro ; lignes % = commentaires (où l'on stocke l'état hors-plateau pour
un rechargement fidèle).
Génération des coups : DFS avec retour arrière (exactement N pas, intermédiaires
vides, dernière case vide ou licorne adverse). possiblesMoves filtre le bon
liseré et renvoie ["E"] si bloqué. Une méthode main illustre placements,
liseré, pass, round-trip fichier, capture.
Bug latent corrigé en partie 3 : un placement légal sur une seule ligne faisait planter le calcul du bord de Noir (supposait deux lignes). Le bord est désormais déduit de la ligne de la licorne.
4. Intégration au tournoi : protocole de l'arbitre
JoueurPuyaubreauRussac implements IJoueur enveloppe un EscampeBoard tenu à jour
à chaque coup (le nôtre via play, l'adverse via mouvementEnnemi). Trois
adaptations, dont deux vérifiées par analyse du jar obfusqué :
- Couleurs :
IJoueuren entiers (NOIR=1,BLANC=-1),EscampeBoarden"noir"/"blanc". - Pass =
"E", pas"PASSE": le Javadoc d'IJoueurdit"PASSE", mais la classe serveur testemove.equals("E")(et"PASSE"est absent du jar). Envoyer"PASSE"= défaite sur coup illégal. - Carte des liserés identique au serveur (cf. Q1).
Machine à états : placement et coups passent par le même canal. Premier
choixMouvement = placement, suivants = coups ; phase détectée via
blackPlaced/whitePlaced. Séquence (déduite de Solo) :
Noir : choixMouvement(placement) -> mvtEnnemi(placement Blanc)
-> mvtEnnemi(1er coup Blanc) -> choixMouvement(coup) -> ...
Blanc : mvtEnnemi(placement Noir) -> choixMouvement(placement)
-> choixMouvement(1er coup, Blanc rejoue) -> mvtEnnemi(coup Noir) -> ...
Exécution (3 processus) :
java -cp escampeobf.jar escampe.ServeurJeu 1234 1
java -cp Puyaubreau_Russac.jar escampe.ClientJeu escampe.JoueurPuyaubreauRussac localhost 1234
java -cp escampeobf.jar escampe.ClientJeu escampe.JoueurAleatoire localhost 1234
5. Placement d'ouverture
Constat issu de l'auto-jeu : une licorne mal placée peut se retrouver seule pièce jouable et bloquée sur le liseré imposé → passes successifs → perte d'initiative. Trois principes :
- Licorne dans un coin — un coin n'a que 2 voisines, donc 2 cases d'attaque.
- Murs — on occupe ces 2 voisines par des paladins : licorne incapturable tant que les murs tiennent.
- Couverture des liserés — les 3 paladins restants sur des liserés 1, 2 et 3 distincts : jamais de pass forcé, jamais besoin de bouger un mur ou la licorne.
Dispositions retenues (Blanc joue le bord complémentaire de Noir) :
Bord bas A1/A2/B1/E1/F1/C2 Bord haut A6/A5/B6/C5/F5/E6
licorne A1, murs A2/B1, licorne A6, murs A5/B6,
mobiles E1·F1·C2 = liserés 1·2·3 mobiles C5·F5·E6 = liserés 1·2·3
Nous n'utilisons pas de bibliothèque d'ouvertures de coups. La raison principale est que la contrainte de liseré rend l'arbre d'ouverture très sensible au placement adverse : une séquence pré-calculée contre un placement différent du nôtre perdrait toute pertinence dès le deuxième coup. De plus, le branchement moyen (~8,9) est suffisamment faible pour que l'alpha-bêta atteigne la profondeur 12–15 dès les premiers coups, rendant une bibliothèque peu utile. Le placement fixe ci-dessus suffit à garantir une position solide et reproductible dès le début.
6. Moteur de décision
Negamax + élagage alpha-bêta + approfondissement itératif (Moteur), sur une
copie du plateau. Capture de licorne = nœud terminal WIN - ply (gagner vite).
Astuces de performance :
- Coups en entier (case =
ligne*6+colonne, coup =départ*36+arrivée) : pas de chaîne dans la boucle chaude. - DFS sur masque de bits
long(36 cases ⊂ 64 bits) : ensembles visité/ atteignable en masques, sans allocation par appel. make/unmakesans allocation : un petit jeton d'annulation → millions de nœuds sans pression GC.- Buffers de coups pré-alloués, un par profondeur.
- Ordonnancement : capture de licorne essayée en premier ; meilleur coup d'une itération replacé en tête de la suivante.
Cohérence : le chemin « entier » du moteur double le chemin « chaîne » vérifié.
VerifMoves(§9) prouve qu'ils produisent les mêmes coups et états — optimiser n'a pas changé les règles.
Performance mesurée : ~4–5 M nœuds/s ; profondeur 12–15 demi-coups en 6 s (plus dans les positions étroites). Les gains forcés annoncés se concrétisent par une capture.
7. Heuristique d'évaluation
Matériel constant → évaluation purement positionnelle, du point de vue du joueur au trait, à partir des distances de Manhattan :
- Attaque : proximité de nos paladins à la licorne adverse — terme somme (pression globale) + terme minimum (l'attaquant le plus proche pèse plus) ;
- Défense : éloignement des paladins adverses de notre licorne — mêmes termes, signe opposé.
Avec les poids retenus (somme = 2, minimum = 8) :
eval = 2·Σ(10−d_attaque) − 2·Σ(10−d_défense)
+ 8·(10−min d_attaque) − 8·(10−min d_défense)
Heuristiques testées et choix (réglage par auto-jeu déterministe + matchs vs aléatoire) : (a) somme seule → jeu trop diffus ; (b) somme + minimum (retenue) → le terme minimum fortement pondéré oriente les paladins vers la licorne adverse et améliore le taux de capture ; (c) terme défensif symétrique conservé (évite d'exposer notre licorne). Le fort poids du minimum reflète que c'est l'attaquant le plus avancé qui décide d'une prise.
Limite assumée : poids validés contre l'aléatoire et en auto-jeu, faute d'adversaires IA tiers. Les tactiques à court terme sont gérées par la recherche, ce qui rend le joueur robuste malgré une évaluation simple.
8. Gestion du temps réel
Limite arbitre 300 s/joueur/partie → enveloppe interne 280 s (~20 s de marge). Budget par coup :
tranche = clamp( temps_restant / 12 , 120 ms , 6000 ms )
La division par le temps restant décroît géométriquement : budget jamais épuisable. Plafond 6 s (pas de surinvestissement en ouverture), plancher 120 ms, mode « panique » pour les dernières secondes. L'approfondissement itératif rend le meilleur coup déjà trouvé dès l'expiration de la tranche (temps contrôlé toutes les 2048 explorations de nœuds).
Mesures (auto-jeu équilibré, plein budget) : max ≈ 6,0 s/coup (le plafond), cumul max ≈ 36 s/joueur sur une partie complète — très loin des 300 s. Réglage conservateur, augmentable sans risque.
9. Performances et tests
| Test | Garantit | Résultat |
|---|---|---|
VerifMoves |
chemin entier ≡ chemin chaîne (coups + make/unmake) | 3 000 parties · 142 165 positions · 1 281 985 contrôles · 0 divergence |
RulesTest |
règles directes (pas/liseré, capture, imprenabilité, non-traversée, pass, fin, placement) | 21 / 21 |
Matchs arbitrés vs JoueurAleatoire |
protocole de bout en bout, légalité | 7 / 7 victoires, 0 illégal, 0 exception (2 couleurs) |
| Démo IA vs IA (serveur réel) | partie complète moteur vs moteur, pass | 21 coups, fin propre par capture |
Bench / Branching |
vitesse, profondeur, branchement | ~4–5 M nœuds/s ; prof. 12–15 ; branchement max 49 / moyen ≈ 8,9 |
Séparation des rôles : VerifMoves (moteur ≡ EscampeBoard), RulesTest
(EscampeBoard ≡ règles), parties arbitrées (dialogue correct avec l'arbitre
réel). Aucun coup illégal sur l'ensemble des parties jouées.
10. Compilation, exécution et livrables
build.sh produit dans dist/ les trois livrables de la version finale :
Puyaubreau_Russac.jar jar exécutable (Main-Class : escampe.ClientJeu)
mainClass jar:Puyaubreau_Russac.jar
clientClass:escampe.ClientJeu
mainClass:escampe.JoueurPuyaubreauRussac
Puyaubreau_Russac.tgz archive : Puyaubreau_Russac/ { src/escampe/*.java, mainClass, jar }
Seules les classes de production entrent dans le jar ; les utilitaires de test
(VerifMoves, RulesTest, Bench, Branching) en sont exclus. Le multijoueur
(humain vs humain, humain vs IA, local ou distant) est documenté dans
MULTIJOUEUR.md.
11. Sources et bibliographie
- Énoncé du cours (Université Paris-Saclay, Polytech APP5, 2025-2026) : règles,
carte des liserés (figure 4), interface
Partie1, classes fournies (IJoueur,ClientJeu,Solo,Applet, serveur). - Algorithmes classiques, pour inspiration sans copie de code : alpha-bêta (Knuth & Moore, 1975) ; minimax/negamax/approfondissement itératif (Russell & Norvig, AIMA) ; masques de bits et ordonnancement de coups (Chess Programming Wiki).
- Déclaration : aucun programme d'Escampe externe recopié. La seule
rétro-ingénierie porte sur le jar d'arbitre fourni avec le sujet, pour confirmer
le protocole (pass
"E") et la carte des liserés (documentation ambiguë).
12. Conclusion et difficultés rencontrées
Le joueur conduit une partie de façon autonome, dialogue correctement avec l'arbitre, ne produit jamais de coup illégal et respecte très confortablement la contrainte de temps. Difficultés principales :
- Obfuscation du serveur : lever l'ambiguïté du pass (
"E"vs"PASSE") et confirmer la carte des liserés a nécessité l'analyse du jar — décisif pour ne pas perdre sur coup illégal. - Interface obfusquée vs nos sources : le joueur aléatoire du jar n'implémente
pas notre
IJoueur; les tests contre lui passent par le réseau. - Avantage du trait : en miroir, Blanc garde l'initiative via la contrainte de liseré — propriété du jeu.
- Réglage de l'heuristique sans adversaires : validé contre l'aléatoire et en auto-jeu.
Pistes d'amélioration : table de transposition (Zobrist), bibliothèque d'ouvertures de placement, terme de mobilité différentielle, recherche de quiescence sur les menaces de capture.