Files
escampe-ia/report/rapport.html
Ethan Puyaubreau 052a3bf978 Rapport : intègre les ajouts du binôme + passe de relecture
- Sommaire, exemple tactique Q4 (figure 6) et note « pas de bibliothèque
  d'ouvertures » repris du commit d'Antonin et portés dans report/rapport.html
  (source du PDF), jusque-là seulement dans RAPPORT.md.
- Exemple Q4 vérifié contre TILE_MAP : liserés D4/F6=2, E5=1, A2/C2=3 et chemin
  de capture C2→D2→D1→C1 (3 pas = liseré de C2) tous corrects.
- Relecture du style sur tout le rapport ; correction de deux coquilles
  (« énnoncé », ancre de sommaire). HTML et RAPPORT.md tenus en miroir.
- PDF régénéré (9 pages, sommaire inclus) ; chiffres mesurés inchangés.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:39:15 +02:00

468 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>Escampe — Rapport (version finale)</title>
</head>
<body>
<!-- ====================== PAGE DE TITRE ====================== -->
<div class="cover">
<div class="cover-univ">Université Paris-Saclay — Polytech APP5 — Année 2025-2026</div>
<div class="cover-course">IA et contraintes</div>
<h1 class="cover-title">Devoir Escampe</h1>
<div class="cover-sub">Conception et réalisation d'un joueur artificiel</div>
<div class="cover-sub">Rapport — version finale</div>
<div class="cover-authors">
Ethan&nbsp;Puyaubreau &nbsp;&amp;&nbsp; Antonin&nbsp;Russac
</div>
<div class="cover-date">30&nbsp;mai&nbsp;2026</div>
<div class="cover-meta">
Joueur : <code>escampe.JoueurPuyaubreauRussac</code><br>
Encadrement : Yue&nbsp;Ma (yue.ma@universite-paris-saclay.fr)
</div>
</div>
<!-- ====================== SOMMAIRE ====================== -->
<h2 class="toc-title">Sommaire</h2>
<ol class="toc">
<li>Présentation et règles</li>
<li>Analyse des caractéristiques du jeu (Q1 à Q7)</li>
<li>Modélisation : la classe <code>EscampeBoard</code></li>
<li>Intégration au tournoi : le protocole de l'arbitre</li>
<li>Placement d'ouverture</li>
<li>Moteur de décision</li>
<li>Heuristique d'évaluation</li>
<li>Gestion du temps réel</li>
<li>Performances et tests</li>
<li>Compilation, exécution et livrables</li>
<li>Sources et bibliographie</li>
<li>Conclusion et difficultés rencontrées</li>
</ol>
<!-- ====================== 1. INTRODUCTION ====================== -->
<h2>1. Présentation et règles</h2>
<p>Escampe se joue sur un plateau de 36&nbsp;cases (6×6). Chaque case porte un
liseré <em>simple</em>, <em>double</em> ou <em>triple</em>. Chaque joueur dispose
d'une <strong>licorne</strong> et de cinq <strong>paladins</strong> (noirs ou
blancs). Les lignes vont de 1 à 6, les colonnes de A à F, et le but est de
<strong>prendre la licorne adverse</strong>.</p>
<p>Ce qui fait l'originalité du jeu, c'est la <strong>contrainte de liseré</strong> :
la pièce que l'on joue doit partir d'une case dont le liseré est le même que celui
de la case où l'adversaire vient de poser sa pièce. Ce liseré de départ fixe aussi
le nombre de pas (1, 2 ou 3), orthogonaux, sans traverser ni repasser sur une case
déjà visitée. On ne capture qu'en s'arrêtant, au dernier pas, sur la licorne
adverse&nbsp;; les paladins, eux, sont imprenables. Un joueur qui ne peut rien jouer
passe son tour. Toute la difficulté revient donc à coincer l'adversaire en lui
imposant des liserés qui le bloquent.</p>
<p>Pour le déroulement, Noir place d'abord ses six pièces sur les deux lignes d'un
bord de son choix (haut ou bas), puis Blanc fait de même sur le bord opposé, et
c'est <strong>Blanc qui joue le premier coup</strong>. Le rapport reprend nos choix
de modélisation (parties&nbsp;1 et&nbsp;2) puis détaille la conception du joueur pour
le tournoi (partie&nbsp;3), chiffres à l'appui.</p>
<!-- ====================== 2. ANALYSE Q1-Q7 ====================== -->
<h2>2. Analyse des caractéristiques du jeu</h2>
<p>Nous reprenons les sept questions de la première partie, cette fois à la lumière
du code que nous avons réellement écrit.</p>
<h3>Q1 — Modélisation d'un état</h3>
<p>Le plateau est un tableau <code>int[6][6]</code> : <code>board[ligne][colonne]</code>,
avec <code>ligne&nbsp;0</code> = ligne&nbsp;1 (en bas) et <code>colonne&nbsp;0</code> = A.
Chaque case vaut une constante de pièce (<code>EMPTY</code>,
<code>WHITE_LICORNE</code>, <code>WHITE_PALADIN</code>, <code>BLACK_LICORNE</code>,
<code>BLACK_PALADIN</code>). Quatre informations que le tableau ne porte pas, mais
dont la règle a besoin, sont gardées à côté :</p>
<ul>
<li><code>lastTileType</code> : le liseré imposé au coup suivant (<code>-1</code> quand il n'y a pas de contrainte) ;</li>
<li><code>currentPlayer</code> : le joueur au trait ;</li>
<li><code>blackPlaced</code>, <code>whitePlaced</code> : la fin des phases de placement ;</li>
<li><code>blackRows</code> : le bord choisi par Noir, qui détermine celui de Blanc.</li>
</ul>
<p>Le tableau d'entiers donne un accès en O(1) à n'importe quelle case et se copie
sans effort, ce qui compte pour l'arbre de recherche ; il se sérialise aussi
directement vers le format de fichier. Surtout, il autorise un schéma
<code>make</code>/<code>unmake</code> qui n'alloue rien (voir §6). Le seul point
gênant est que la contrainte de liseré vit en dehors du tableau : il faut penser à
la mettre à jour à chaque coup, ce que nous centralisons dans <code>play</code>.</p>
<p>La carte des liserés est figée dans la constante <code>TILE_MAP</code>, recopie
de la figure&nbsp;4 de l'énoncé (ligne&nbsp;1 en bas) :</p>
<pre class="grid"> 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</pre>
<p class="note">Nous avons extrait par réflexion la carte qu'utilise l'arbitre dans
sa propre classe de jeu, et elle coïncide case pour case avec la nôtre (elle colle
aussi à l'exemple de la figure&nbsp;6). La vérification valait le coup : une carte
fausse aurait fait rejeter nos coups par l'arbitre.</p>
<h3>Q2 — Détection de fin de partie</h3>
<p>La partie s'arrête dès qu'une des deux licornes quitte le plateau ; il n'y a pas
d'autre cas, donc pas de nul. Le test (<code>gameOver</code>) est un simple balayage
en O(1). En recherche, le moteur n'attend même pas ce balayage : il repère la
capture à l'instant où le coup la produit (§6).</p>
<h3>Q3 — Sources de difficulté et facteur de branchement</h3>
<p>Quatre choses rendent le jeu retors : la contrainte de liseré, qui fait varier
fortement la mobilité ; la dépendance entre tours, puisque la case d'arrivée qu'on
choisit dicte les pièces que l'adversaire pourra bouger ; l'asymétrie du plateau,
avec des zones riches en liserés triples (mobiles) et d'autres en liserés simples ;
et le risque qu'une pièce, voire un joueur entier, se retrouve bloqué et doive
passer.</p>
<p>Côté <strong>facteur de branchement</strong>, nous avions avancé en première
partie une borne théorique de l'ordre de 120 (six pièces, jusqu'à une vingtaine de
destinations sur un liseré triple). En pratique c'est beaucoup moins, parce que la
contrainte de liseré ne laisse jouables que les pièces du bon type. Une simulation
de 30&nbsp;000 parties aléatoires (utilitaire <code>escampe.Branching</code>) donne :</p>
<table>
<tr><th>Situation</th><th>Branchement maximal observé</th></tr>
<tr><td>Coup contraint (un liseré imposé)</td><td>45</td></tr>
<tr><td>Coup libre (1<sup>er</sup> coup ou après un pass)</td><td>49</td></tr>
<tr><td>Branchement moyen (toutes positions)</td><td>≈ 8,9</td></tr>
</table>
<p>Avec une moyenne sous 10, l'alpha-bêta descend profond en quelques secondes (§6).</p>
<h3>Q4 — Coups imparables</h3>
<p>Il n'y a pas de coup gagnant à coup sûr dès le départ : comme l'adversaire choisit
sa case d'arrivée, donc le liseré qu'il nous impose, il peut toujours désamorcer une
menace au mauvais moment. Ce qui existe, en revanche, ce sont des positions de
<strong>zugzwang partiel</strong>, où il est forcé d'imposer précisément le liseré
qui ouvre la capture.</p>
<p>L'énoncé en donne un cas net (figure&nbsp;6). Noir vient de jouer en D4, une case
à liseré double, donc Blanc doit partir d'une case double : il choisit
<strong>F6&nbsp;&nbsp;E5</strong> (F6 est double). Noir est alors contraint de jouer
depuis un liseré simple comme E5, et son seul coup raisonnable est
<strong>A1&nbsp;&nbsp;A2</strong>. Or A2 est à liseré triple : Blanc enchaîne
<strong>C2&nbsp;×&nbsp;C1</strong>, son paladin en C2 parcourant les trois pas
C2&nbsp;&nbsp;D2&nbsp;&nbsp;D1&nbsp;&nbsp;C1 pour prendre la licorne noire. La
séquence est imparable localement : une fois Noir poussé en A2, il ne peut plus
empêcher la prise.</p>
<p>Ce genre de combinaison ne se construit pas mécaniquement depuis l'ouverture, il y
a trop de degrés de liberté&nbsp;; mais notre alpha-bêta la trouve et la joue dès
qu'elle entre dans son horizon de recherche.</p>
<h3>Q5 — Critères pour l'heuristique</h3>
<p>Cinq critères nous semblaient pertinents : la distance à la licorne adverse, la
mobilité différentielle, le contrôle du liseré qu'on impose, la protection de sa
propre licorne et l'avancée des pièces. Au final (§7), l'évaluation retenue tient
surtout à deux d'entre eux, la proximité de nos paladins à la licorne adverse
(attaque) et l'éloignement des paladins adverses de la nôtre (défense)&nbsp;; le
reste, la recherche s'en charge assez bien toute seule.</p>
<h3>Q6 — Stratégie selon la phase</h3>
<ul>
<li><strong>Ouverture (placement)</strong> : c'est irréversible, donc on sécurise
d'emblée la licorne et on s'arrange pour pouvoir toujours jouer (§5).</li>
<li><strong>Milieu</strong> : on manœuvre pour menacer la licorne adverse tout en
gardant la main sur le liseré qu'on impose, en visant le zugzwang partiel.</li>
<li><strong>Finale</strong> : dès qu'une capture est en vue, c'est le calcul
tactique qui décide.</li>
</ul>
<h3>Q7 — Majorant du nombre de coups et gestion du temps</h3>
<p>Aucune pièce ne disparaît avant la prise finale, donc une partie peut traîner.
En bornant le branchement par tour sur quelques dizaines de tours, on arrive à un
ordre de grandeur de 400 à 600 demi-coups. Pour rester dans les 300&nbsp;s par
joueur, on s'appuie sur l'approfondissement itératif, l'élagage alpha-bêta et un
budget par coup calculé à partir du temps restant (§8).</p>
<!-- ====================== 3. MODELISATION (PARTIE 2) ====================== -->
<h2>3. Modélisation : la classe <code>EscampeBoard</code></h2>
<p><code>EscampeBoard</code> implémente l'interface fournie <code>Partie1</code>
(<code>setFromFile</code>/<code>saveToFile</code>, <code>isValidMove</code>,
<code>possiblesMoves</code>, <code>play</code>, <code>gameOver</code>) et suit les
conventions de l'arbitre : coup régulier <code>"B1-D1"</code>, placement
<code>"C6/A6/B5/D5/E6/F5"</code> avec la licorne en tête puis les cinq paladins, et
pass <code>"E"</code>.</p>
<p>Le format de fichier reprend celui de l'énoncé : six lignes de plateau du bas vers
le haut, avec <code>N/n</code> pour le noir, <code>B/b</code> pour le blanc et
<code>-</code> pour le vide, chaque ligne encadrée de son numéro&nbsp;; les lignes en
<code>%</code> sont des commentaires. Nous y rangeons justement l'état hors-plateau
(liseré courant, joueur, bord de Noir), de sorte qu'une sauvegarde se recharge à
l'identique.</p>
<p>Pour générer les coups, on part d'une case et on énumère les arrivées par un
parcours en profondeur avec retour arrière : exactement N&nbsp;pas (N = liseré de
départ), cases intermédiaires vides, et case finale soit vide soit occupée par la
licorne adverse, auquel cas c'est une capture. <code>possiblesMoves</code> ne garde
que les pièces sur le bon liseré et renvoie <code>["E"]</code> quand plus rien n'est
jouable. Une méthode <code>main</code> fait la démonstration sur des exemples :
placements, contrainte de liseré, pass, aller-retour fichier et capture.</p>
<p class="note">Un bug s'était glissé là et nous l'avons corrigé en partie&nbsp;3 :
un placement légal mais aligné sur une seule ligne faisait planter le calcul du bord
de Noir, qui supposait toujours deux lignes. Le bord se déduit maintenant de la ligne
de la licorne.</p>
<!-- ====================== 4. PROTOCOLE ====================== -->
<h2>4. Intégration au tournoi : le protocole de l'arbitre</h2>
<p>Le joueur <code>escampe.JoueurPuyaubreauRussac</code> implémente l'interface
fournie <code>IJoueur</code> et garde à jour un <code>EscampeBoard</code> à chaque
coup, le nôtre comme celui de l'adversaire reçu par <code>mouvementEnnemi</code>.
Trois détails ont demandé une adaptation, et deux d'entre eux ont dû être
<strong>confirmés en regardant dans le jar de l'arbitre</strong>, qui est obfusqué :</p>
<ul>
<li><strong>Les couleurs</strong> : <code>IJoueur</code> raisonne en entiers
(<code>NOIR&nbsp;=&nbsp;1</code>, <code>BLANC&nbsp;=&nbsp;-1</code>) alors que
<code>EscampeBoard</code> utilise les chaînes <code>"noir"</code> et <code>"blanc"</code>.</li>
<li><strong>Le pass se note <code>"E"</code>, pas <code>"PASSE"</code></strong> :
le Javadoc d'<code>IJoueur</code> annonce <code>"PASSE"</code>, mais le serveur teste
bel et bien <code>move.equals("E")</code>, et <code>"PASSE"</code> n'apparaît nulle
part dans le jar. Suivre le Javadoc nous aurait coûté la partie sur coup illégal.</li>
<li><strong>La carte des liserés</strong> doit être celle du serveur (cf. Q1).</li>
</ul>
<p>Placement et coups passent par le même canal
(<code>choixMouvement</code>/<code>mouvementEnnemi</code>) : le premier
<code>choixMouvement</code> renvoie un placement, les suivants des coups, la phase
se lisant sur <code>blackPlaced</code>/<code>whitePlaced</code>. En lisant la classe
<code>Solo</code> fournie, on reconstitue l'ordre des appels :</p>
<pre class="grid">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) → ...</pre>
<p>Comme on rejoue chaque coup sur l'<code>EscampeBoard</code> interne dans cet ordre,
le joueur au trait reste synchronisé avec l'arbitre sans traitement particulier.</p>
<p>Côté lancement, il faut trois processus, le serveur et deux clients :</p>
<pre class="grid">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</pre>
<!-- ====================== 5. PLACEMENT ====================== -->
<h2>5. Placement d'ouverture</h2>
<p>Le placement ne se rejoue pas, donc autant le soigner. Le constat est venu de
l'auto-jeu : une licorne mal posée peut devenir la seule pièce jouable sur le liseré
imposé, et se retrouver bloquée, ce qui force des passes à répétition et abandonne
l'initiative. Trois principes répondent à ça :</p>
<ol>
<li><strong>La licorne dans un coin.</strong> Un coin n'a que deux voisines, donc
seulement deux cases d'où l'adversaire peut venir la prendre.</li>
<li><strong>Deux murs.</strong> On occupe ces deux voisines avec des paladins, et
la licorne devient imprenable tant que les murs tiennent, puisqu'on ne peut pas
finir son dernier pas sur une case occupée.</li>
<li><strong>Trois liserés couverts.</strong> Les trois paladins restants se posent
sur des cases de liserés 1, 2 et 3 différents. Quel que soit le liseré imposé, il
reste une pièce mobile, et on n'a jamais à passer ni à déranger un mur ou la
licorne.</li>
</ol>
<p>Voici les deux dispositions retenues (Blanc prend le bord opposé à Noir) ; nous en
avons vérifié la légalité et les trois propriétés ci-dessus :</p>
<pre class="grid">Bord bas A1/A2/B1/E1/F1/C2 Bord haut A6/A5/B6/C5/F5/E6
A B C D E F A B C D E F
2 n . . . . . 6 N b . . b .
1 N n . n n n 5 b . b . . b
(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)</pre>
<p>Nous n'avons pas de bibliothèque d'ouvertures. Elle aurait peu de valeur ici : la
contrainte de liseré rend toute séquence pré-calculée caduque dès que le placement
adverse diffère du cas prévu, souvent au deuxième coup. Et comme le branchement moyen
(~8,9) laisse l'alpha-bêta atteindre la profondeur 12 à 15 d'entrée, le placement fixe
ci-dessus suffit à démarrer sur une position saine et reproductible.</p>
<!-- ====================== 6. MOTEUR ====================== -->
<h2>6. Moteur de décision</h2>
<p>Le choix du coup repose sur un <strong>negamax</strong> avec <strong>élagage
alpha-bêta</strong> et <strong>approfondissement itératif</strong> (classe
<code>Moteur</code>). La recherche travaille sur une copie du plateau, jamais sur
l'état réel. Une capture de licorne compte comme une feuille de valeur
<code>WIN&nbsp;-&nbsp;ply</code>, ce qui pousse à gagner tôt plutôt que tard.</p>
<p>Plusieurs choix tirent la vitesse vers le haut :</p>
<ul>
<li><strong>Coups codés sur un entier</strong> (case = <code>ligne×6+colonne</code>,
coup = <code>départ×36+arrivée</code>), pour ne manipuler aucune chaîne dans la
boucle chaude.</li>
<li><strong>DFS sur masque de bits</strong> : les 36 cases tiennent dans un
<code>long</code>, et les ensembles « visité » et « atteignable » sont de simples
masques, sans tableau alloué à chaque appel.</li>
<li><strong><code>make</code>/<code>unmake</code> sans allocation</strong> : un petit
jeton suffit à défaire un coup, donc on explore des millions de nœuds sans solliciter
le ramasse-miettes.</li>
<li><strong>Buffers de coups réservés</strong> à l'avance, un par profondeur.</li>
<li><strong>Ordre des coups</strong> : on essaie d'abord toute prise de licorne (coupure
immédiate), et on remet en tête le meilleur coup de l'itération précédente.</li>
</ul>
<p class="note">Le moteur a sa propre génération de coups en entiers, en parallèle de
celle, vérifiée, d'<code>EscampeBoard</code> en chaînes. Pour être sûr qu'elles ne
divergent pas en silence, le test <code>VerifMoves</code> (§9) confronte les deux et
exige les mêmes coups et les mêmes états : c'est ce qui nous garantit qu'optimiser
n'a pas modifié les règles au passage.</p>
<p>En pratique, le moteur explore de l'ordre de <strong>4 à 5&nbsp;millions de nœuds
par seconde</strong>. En milieu de partie, l'approfondissement itératif atteint
<strong>12 à 15 demi-coups</strong> en 6&nbsp;s, davantage dans les positions
étroites. Quand il annonce un gain forcé, la capture a bien lieu dans les parties de
contrôle.</p>
<!-- ====================== 7. HEURISTIQUE ====================== -->
<h2>7. Heuristique d'évaluation</h2>
<p>Le matériel ne bouge pas (paladins imprenables, licornes en place jusqu'à la
prise), donc évaluer une position non terminale revient à juger un placement.
L'évaluation se fait du point de vue du joueur au trait, à partir de distances de
Manhattan, et combine deux idées :</p>
<ul>
<li>la <strong>pression d'attaque</strong>, c'est-à-dire la proximité de nos paladins
à la licorne adverse, avec un terme de somme (pression d'ensemble) et un terme de
minimum (le paladin le plus proche pèse plus lourd) ;</li>
<li>la <strong>sécurité</strong>, soit l'éloignement des paladins adverses de notre
licorne, avec les deux mêmes termes mais de signe opposé.</li>
</ul>
<p>Avec les poids retenus (2 pour les sommes, 8 pour les minimums) :</p>
<pre class="grid">eval = 2·Σ(10d_attaque) 2·Σ(10d_défense)
+ 8·(10min d_attaque) 8·(10min d_défense)</pre>
<p>Pour régler ces poids, nous avons fait jouer le moteur contre lui-même et contre le
joueur aléatoire fourni, en comparant trois variantes. Avec la somme seule (a), le jeu
restait trop diffus et le moteur tardait à concentrer une menace. La somme plus le
mininum (b), que nous avons gardée, recentre les paladins vers la licorne adverse grâce
au fort poids du minimum et fait monter le taux de capture. L'ajout d'un terme défensif
symétrique (c) a été conservé aussi : il évite d'exposer notre licorne sans pénaliser
l'attaque. Ce poids élevé sur le minimum traduit une réalité du jeu, où c'est le paladin
le plus avancé qui conclut une prise.</p>
<p class="note">Une limite que nous assumons : faute d'autres IA disponibles avant le
tournoi, ces poids sont calés contre l'aléatoire et en auto-jeu, pas contre des joueurs
forts. Cela dit, les prises à courte échéance relèvent de la recherche, ce qui rend le
joueur solide même avec une évaluation aussi simple.</p>
<!-- ====================== 8. TEMPS ====================== -->
<h2>8. Gestion du temps réel</h2>
<p>L'arbitre laisse 300&nbsp;s par joueur et par partie. Nous travaillons sous une
enveloppe interne de <strong>280&nbsp;s</strong>, soit une vingtaine de secondes de
marge. Le budget d'un coup est une fraction du temps restant, bornée des deux côtés :</p>
<pre class="grid">tranche = clamp( temps_restant / 12 , 120 ms , 6000 ms )</pre>
<p>Diviser le temps restant le fait décroître géométriquement, si bien que le budget
ne peut pas s'épuiser, même sur une partie qui s'éternise. Le plafond de 6&nbsp;s évite
de gaspiller du temps en ouverture, le plancher de 120&nbsp;ms garantit un minimum de
réflexion, et un mode « panique » couvre les toutes dernières secondes. Comme la
recherche est itérative, le meilleur coup déjà trouvé est disponible dès que la tranche
expire, le temps étant relu toutes les 2048 explorations de nœuds.</p>
<p>En mesure (auto-jeu équilibré, plein budget), le coup le plus long approche le
plafond, environ <strong>6&nbsp;s</strong>, et le cumul sur une partie entière plafonne
vers <strong>36&nbsp;s</strong> par joueur, loin des 300&nbsp;s. Le réglage est prudent
et on pourrait l'ouvrir davantage sans risque.</p>
<!-- ====================== 9. PERFS & TESTS ====================== -->
<h2>9. Performances et tests</h2>
<p>Chaque maillon de la chaîne est contrôlé contre une référence indépendante.</p>
<table>
<tr><th>Test</th><th>Ce qu'il garantit</th><th>Résultat</th></tr>
<tr>
<td><code>VerifMoves</code></td>
<td>Génération en entiers (moteur) identique à la génération en chaînes (vérifiée) :
mêmes coups, même <code>make</code>/<code>unmake</code></td>
<td>3 000 parties · 142 165 positions · 1 281 985 contrôles · <strong>0 divergence</strong></td>
</tr>
<tr>
<td><code>RulesTest</code></td>
<td>Règles vérifiées directement : nombre de pas = liseré, capture au dernier pas,
paladins imprenables, non-traversée, contrainte de liseré, pass forcé, fin, zones de placement</td>
<td><strong>21 / 21</strong></td>
</tr>
<tr>
<td>Matchs arbitrés vs <code>JoueurAleatoire</code></td>
<td>Protocole de bout en bout (placement, liseré, pass, couleurs) et légalité</td>
<td><strong>7 / 7 victoires</strong>, 0 coup illégal, 0 exception (les deux couleurs)</td>
</tr>
<tr>
<td>Démo IA vs IA (serveur réel)</td>
<td>Partie complète moteur contre moteur, gestion des pass</td>
<td>21 coups, fin propre par capture</td>
</tr>
<tr>
<td><code>Bench</code> / <code>Branching</code></td>
<td>Vitesse, profondeur, facteur de branchement</td>
<td>≈ 45 M nœuds/s ; profondeur 1215 ; branchement max 49, moyen ≈ 8,9</td>
</tr>
</table>
<p>Les rôles ne se recouvrent pas : <code>VerifMoves</code> montre que le moteur colle
à <code>EscampeBoard</code>, <code>RulesTest</code> que <code>EscampeBoard</code>
respecte les règles, et les parties arbitrées que l'ensemble dialogue correctement avec
le vrai arbitre. Sur toutes les parties jouées, aucun coup illégal n'a été produit.</p>
<!-- ====================== 10. BUILD ====================== -->
<h2>10. Compilation, exécution et livrables</h2>
<p>Le script <code>build.sh</code> fabrique dans <code>dist/</code> les trois livrables
de la version finale :</p>
<pre class="grid">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 de rendu : répertoire Puyaubreau_Russac/
contenant src/escampe/*.java + mainClass + le jar</pre>
<p>Le jar ne contient que les classes de production (le joueur, le moteur, le plateau et
les classes fournies)&nbsp;; les utilitaires de test (<code>VerifMoves</code>,
<code>RulesTest</code>, <code>Bench</code>, <code>Branching</code>) restent dehors. Le
jeu en multijoueur, humain contre humain ou humain contre notre IA, en local comme à
distance, est décrit dans <code>MULTIJOUEUR.md</code>.</p>
<!-- ====================== 11. SOURCES ====================== -->
<h2>11. Sources et bibliographie</h2>
<ul>
<li>L'<strong>énoncé du cours</strong> (Université Paris-Saclay, Polytech APP5,
2025-2026) pour les règles d'Escampe, la carte des liserés (figure&nbsp;4),
l'interface <code>Partie1</code> et les classes fournies
(<code>IJoueur</code>, <code>ClientJeu</code>, <code>Solo</code>,
<code>Applet</code>, serveur).</li>
<li>Des <strong>algorithmes classiques</strong>, comme inspiration et sans copie de
code : l'élagage alpha-bêta (Knuth et Moore, <em>An Analysis of Alpha-Beta
Pruning</em>, 1975), le minimax, le negamax et l'approfondissement itératif
(Russell et Norvig, <em>Artificial Intelligence: A Modern Approach</em>), ainsi que
les représentations par masques de bits et l'ordonnancement de coups
(<em>Chess Programming Wiki</em>).</li>
<li>Pour être clairs : nous n'avons recopié aucun programme d'Escampe existant. La
seule rétro-ingénierie a porté sur le jar d'arbitre fourni avec le sujet, et
uniquement pour confirmer le protocole (le pass <code>"E"</code>) et la carte des
liserés, deux points que la documentation laissait dans le flou.</li>
</ul>
<!-- ====================== 12. CONCLUSION ====================== -->
<h2>12. Conclusion et difficultés rencontrées</h2>
<p>Le joueur mène une partie tout seul, dialogue correctement avec l'arbitre, ne joue
jamais de coup illégal et tient le temps très largement. Les principaux obstacles ont
été les suivants :</p>
<ul>
<li><strong>L'obfuscation du serveur.</strong> Trancher l'ambiguïté du pass
(<code>"E"</code> et non <code>"PASSE"</code>) et confirmer la carte des liserés a
demandé de fouiller le jar, sans quoi on perdait sur coup illégal.</li>
<li><strong>L'interface obfusquée face à nos sources.</strong> Le joueur aléatoire du
jar n'implémente pas notre <code>IJoueur</code>, donc les tests contre lui passent par
le réseau, où seules des chaînes circulent.</li>
<li><strong>L'avantage du trait.</strong> En miroir, Blanc, qui joue le premier, garde
l'initiative via la contrainte de liseré&nbsp;; c'est une propriété du jeu, pas une
question de force du moteur.</li>
<li><strong>Le réglage de l'heuristique sans sparring-partner</strong>, calé faute de
mieux contre l'aléatoire et en auto-jeu.</li>
</ul>
<p>Si nous devions continuer, plusieurs pistes se présentent : une table de transposition
(hachage de Zobrist), une bibliothèque d'ouvertures de placement, un terme de mobilité
différentielle dans l'évaluation et une recherche de quiescence sur les menaces de
capture.</p>
</body>
</html>