Joueur IA Escampe (Puyaubreau/Russac) — version finale

Joueur alpha-bêta + iterative deepening pour le tournoi APP5 « IA et contraintes ».

- src/escampe/ : joueur (IJoueur), moteur (alpha-bêta + DFS bitmask, make/unmake
  sans allocation), modèle EscampeBoard (Partie1), utilitaires de test.
- Protocole arbitre vérifié (pass="E", carte des liserés identique au serveur,
  machine à états placement/jeu) ; 7/7 victoires vs joueur aléatoire, 0 illégal.
- Vérifications : VerifMoves (int≡String, 0 divergence/142k positions),
  RulesTest (21/21), Branching (facteur de branchement mesuré).
- Rapport : report/rapport.html + tools/make_report_pdf.py (PyMuPDF) → PDF, RAPPORT.md.
- Livrables buildés inclus (dist/ : jar, mainClass, tgz, rapport PDF) + lib/escampeobf.jar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 16:00:29 +02:00
commit e508efa14f
50 changed files with 6521 additions and 0 deletions

447
report/rapport.html Normal file
View File

@@ -0,0 +1,447 @@
<!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>
<!-- ====================== 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> (couleur noire
ou blanche). Les lignes sont numérotées de 1 à 6, les colonnes de A à F. Le but
est de <strong>prendre la licorne adverse</strong>.</p>
<p>La règle caractéristique du jeu est une <strong>contrainte de liseré</strong> :
la pièce que l'on joue doit partir d'une case dont le liseré est <em>identique</em>
à celui de la case d'arrivée du coup adverse précédent. Le liseré de la case de
départ fixe en outre 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 — les paladins sont imprenables. Si un joueur ne peut rien jouer, il passe
son tour.</p>
<p>Le déroulement : Noir place ses six pièces sur les deux lignes d'un bord
(haut ou bas) ; Blanc fait de même sur le bord opposé ; <strong>Blanc joue le
premier coup</strong>. Ce rapport décrit nos choix de modélisation (parties&nbsp;1
et&nbsp;2) puis la conception du joueur artificiel pour le tournoi (partie&nbsp;3),
avec les mesures qui justifient nos choix.</p>
<!-- ====================== 2. ANALYSE Q1-Q7 ====================== -->
<h2>2. Analyse des caractéristiques du jeu</h2>
<p>Nous reprenons ici les sept questions de la première partie, en les étayant
par l'implémentation finalement réalisée.</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 (bas) et <code>colonne&nbsp;0</code> = A.
Chaque case contient une constante de pièce (<code>EMPTY</code>,
<code>WHITE_LICORNE</code>, <code>WHITE_PALADIN</code>, <code>BLACK_LICORNE</code>,
<code>BLACK_PALADIN</code>). L'état complémentaire, indispensable à la règle, est
maintenu hors du plateau :</p>
<ul>
<li><code>lastTileType</code> : liseré imposé au coup suivant (<code>-1</code> = aucune contrainte) ;</li>
<li><code>currentPlayer</code> : joueur au trait ;</li>
<li><code>blackPlaced</code>, <code>whitePlaced</code> : fin des phases de placement ;</li>
<li><code>blackRows</code> : le bord choisi par Noir (en déduit celui de Blanc).</li>
</ul>
<p><strong>Avantages.</strong> Accès O(1) à toute case ; copie immédiate de l'état
pour l'arbre de recherche ; sérialisation triviale ; surtout, un schéma
<code>make/unmake</code> sans aucune allocation (essentiel pour la vitesse, §6).
<strong>Inconvénient.</strong> La contrainte de liseré est un état séparé qu'il
faut maintenir explicitement à chaque coup ; nous l'encapsulons dans <code>play</code>.</p>
<p>La carte des liserés est une constante <code>TILE_MAP</code> reproduisant 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">Fait vérifié : cette carte est <em>identique</em>, case pour case,
à celle utilisée en interne par l'arbitre du tournoi — nous l'avons extraite par
réflexion de la classe de jeu du serveur fourni. Elle est aussi cohérente avec
l'exemple tactique de la figure&nbsp;6 de l'énoncé. Une carte divergente aurait
produit des coups jugés illégaux : ce point était critique.</p>
<h3>Q2 — Détection de fin de partie</h3>
<p>La partie est finie dès qu'une des deux licornes a disparu du plateau (seul cas
de fin, pas de match nul). La vérification est un simple balayage O(1) du plateau
(<code>gameOver</code>) ; le moteur, lui, détecte la capture directement au moment
où elle est jouée (§6).</p>
<h3>Q3 — Sources de difficulté et facteur de branchement</h3>
<p>Les principales sources de difficulté sont :</p>
<ul>
<li>la <strong>contrainte de liseré</strong>, qui limite fortement et variablement la mobilité ;</li>
<li>la <strong>dépendance entre tours</strong> : la case d'arrivée choisie détermine les pièces que l'adversaire pourra jouer ;</li>
<li>l'<strong>asymétrie</strong> du plateau (zones riches en liserés triples, donc mobiles, vs zones simples) ;</li>
<li>le risque de <strong>blocage</strong> d'une pièce, voire d'un joueur (pass forcé).</li>
</ul>
<p><strong>Facteur de branchement.</strong> En première partie nous avions estimé
une borne théorique de l'ordre de 120 (6&nbsp;pièces × jusqu'à ~20 destinations
sur liseré triple). La mesure réelle est bien plus basse, car la contrainte de
liseré ne laisse jouables que les pièces du bon liseré. Sur 30&nbsp;000 parties
aléatoires simulées (utilitaire <code>escampe.Branching</code>) :</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, aucune contrainte)</td><td>49</td></tr>
<tr><td>Branchement moyen (toutes positions)</td><td>≈ 8,9</td></tr>
</table>
<p>Le branchement effectif modeste (moyenne &lt;&nbsp;10) explique qu'une recherche
alpha-bêta atteigne des profondeurs élevées en quelques secondes (§6).</p>
<h3>Q4 — Coups imparables</h3>
<p>Il n'existe pas de coup « imparable » universel garanti dès le départ : la
contrainte de liseré peut toujours empêcher l'exécution d'une menace au mauvais
moment. En revanche, certaines configurations créent un <strong>zugzwang
partiel</strong> où l'adversaire ne peut éviter d'imposer le liseré qui nous
arrange — l'énoncé en donne l'exemple (figure&nbsp;6 : le paladin blanc en C2 prend
la licorne en C1 dès que Noir est forcé d'imposer un liseré double). Construire de
tels pièges est un axe stratégique ; notre recherche les exploite implicitement
quand ils sont à portée d'horizon.</p>
<h3>Q5 — Critères pour l'heuristique</h3>
<p>Nous avions identifié cinq critères : distance à la licorne adverse, mobilité
différentielle, contrôle du liseré imposé, protection de sa propre licorne, et
avancée sur le plateau. L'heuristique finalement retenue (§7) s'appuie sur la
<strong>proximité des paladins à la licorne adverse</strong> (pression d'attaque)
et l'<strong>éloignement des paladins adverses de notre licorne</strong>
(sécurité) — les autres critères sont, en pratique, largement pris en charge par
la recherche elle-même.</p>
<h3>Q6 — Stratégie selon la phase</h3>
<ul>
<li><strong>Début (placement)</strong> : irréversible et déterminant. On protège
la licorne et on garantit de toujours pouvoir jouer (§5).</li>
<li><strong>Milieu</strong> : manœuvre pour construire des menaces sur la licorne
adverse tout en contrôlant le liseré imposé ; recherche de zugzwang partiel.</li>
<li><strong>Fin</strong> : dès qu'une capture est à portée, le calcul tactique
(recherche profonde) prime.</li>
</ul>
<h3>Q7 — Majorant du nombre de coups et gestion du temps</h3>
<p>Aucune pièce ne disparaît avant la capture finale ; une partie peut donc
théoriquement s'étirer. En bornant le branchement par tour et en comptant quelques
dizaines de tours, une borne raisonnable se situe vers 400600 demi-coups. Pour
tenir la contrainte de temps (300&nbsp;s par joueur et par partie), nous combinons
<strong>approfondissement itératif</strong>, <strong>élagage alpha-bêta</strong> et
un <strong>budget par coup</strong> dérivé du temps restant (§8).</p>
<!-- ====================== 3. MODELISATION (PARTIE 2) ====================== -->
<h2>3. Modélisation : la classe <code>EscampeBoard</code></h2>
<p><code>EscampeBoard</code> (≈ 860 lignes) 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>. Les conventions de l'arbitre sont respectées à la lettre :</p>
<ul>
<li>coup régulier <code>"B1-D1"</code> ;</li>
<li>placement <code>"C6/A6/B5/D5/E6/F5"</code> (licorne en tête, puis les 5 paladins) ;</li>
<li>pass <code>"E"</code>.</li>
</ul>
<p><strong>Format de fichier.</strong> Six lignes de plateau (bas vers haut),
caractères <code>N/n</code> (licorne/paladin noir), <code>B/b</code> (blanc),
<code>-</code> (vide), chaque ligne encadrée d'un numéro ; toute autre ligne
commence par <code>%</code> (commentaire). Nous y ajoutons en commentaires l'état
hors-plateau (liseré courant, joueur, bord de Noir) afin que la sauvegarde soit
fidèlement rechargeable.</p>
<p><strong>Génération des coups.</strong> Depuis une case, on énumère les
destinations par un parcours en profondeur (DFS) avec retour arrière : exactement
N&nbsp;pas (N = liseré de départ), cases intermédiaires vides, dernière case vide ou
occupée par la licorne adverse (capture). <code>possiblesMoves</code> filtre les
pièces sur le bon liseré et renvoie <code>["E"]</code> si aucun coup n'est possible.
Une méthode <code>main</code> illustre placements, contrainte de liseré, pass,
round-trip fichier et capture.</p>
<p class="note">Bug latent corrigé en partie&nbsp;3 : un placement légal mais
disposé sur une <em>seule</em> ligne faisait planter le calcul du bord de Noir
(il supposait deux lignes distinctes). Le bord est désormais déduit de façon
robuste à partir 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 enveloppe un <code>EscampeBoard</code> tenu à jour
à chaque coup (le nôtre comme celui de l'adversaire, via <code>mouvementEnnemi</code>).
Trois points d'adaptation, dont deux <strong>vérifiés par analyse du jar de
l'arbitre</strong> car l'infrastructure fournie est obfusquée :</p>
<ul>
<li><strong>Couleurs.</strong> <code>IJoueur</code> parle en entiers
(<code>NOIR&nbsp;=&nbsp;1</code>, <code>BLANC&nbsp;=&nbsp;-1</code>) ;
<code>EscampeBoard</code> en chaînes <code>"noir"</code>/<code>"blanc"</code>.</li>
<li><strong>Pass = <code>"E"</code>, et non <code>"PASSE"</code>.</strong> Le
Javadoc d'<code>IJoueur</code> indique <code>"PASSE"</code>, mais la classe de
jeu du serveur teste <code>move.equals("E")</code> (et <code>"PASSE"</code>
n'apparaît nulle part dans le jar). Envoyer <code>"PASSE"</code> aurait valu une
défaite sur coup illégal.</li>
<li><strong>Carte des liserés</strong> identique à celle du serveur (cf. Q1).</li>
</ul>
<p><strong>Machine à états.</strong> Le placement et les coups transitent par le
même canal <code>choixMouvement</code>/<code>mouvementEnnemi</code>. Le premier
appel à <code>choixMouvement</code> renvoie donc un <em>placement</em>, les suivants
des coups ; la phase est détectée via <code>blackPlaced</code>/<code>whitePlaced</code>.
La séquence (déduite de la classe <code>Solo</code> fournie) est :</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>En appliquant chaque coup à l'<code>EscampeBoard</code> interne dans cet ordre,
le joueur au trait reste naturellement synchronisé avec l'arbitre.</p>
<p><strong>Exécution.</strong> Trois processus (serveur + 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 est irréversible : nous l'avons conçu à partir d'un constat issu de
l'auto-jeu — une licorne mal placée peut se retrouver <em>seule pièce jouable et
bloquée</em> sur le liseré imposé, forçant des passes successifs qui livrent
l'initiative à l'adversaire. Trois principes y répondent :</p>
<ol>
<li><strong>Licorne dans un coin.</strong> Un coin n'a que deux cases voisines :
seulement deux cases d'où l'adversaire peut l'atteindre.</li>
<li><strong>Murs.</strong> On occupe ces deux voisines par des paladins. La
licorne devient <em>incapturable</em> tant que les murs tiennent (impossible de
franchir le dernier pas sur une case occupée).</li>
<li><strong>Couverture des liserés.</strong> Les trois paladins restants sont
placés sur des cases de liserés <strong>1, 2 et 3 distincts</strong> : quel que
soit le liseré imposé, on dispose toujours d'une pièce mobile — jamais de pass
forcé, jamais besoin de déplacer un mur ou la licorne.</li>
</ol>
<p>Dispositions retenues (légalité et propriétés vérifiées) ; pour Blanc, on joue
le bord complémentaire de celui de Noir :</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>
<!-- ====================== 6. MOTEUR ====================== -->
<h2>6. Moteur de décision</h2>
<p>La décision 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 s'effectue sur une <em>copie</em> du plateau,
jamais sur l'état réel. Capturer la licorne adverse est traité comme un nœud
terminal de valeur <code>WIN&nbsp;-&nbsp;ply</code> (gagner vite plutôt que tard).</p>
<p><strong>Astuces de performance.</strong></p>
<ul>
<li><strong>Coups encodés en entier</strong> (case = <code>ligne×6+colonne</code>,
coup = <code>départ×36+arrivée</code>) : aucune chaîne manipulée dans la boucle
chaude.</li>
<li><strong>DFS sur masque de bits <code>long</code></strong> : les 36 cases
tiennent dans un <code>long</code> ; les ensembles « visité » et « atteignable »
sont des masques — pas d'allocation de tableau par appel.</li>
<li><strong><code>make</code>/<code>unmake</code> sans allocation</strong> : un
petit jeton d'annulation suffit à défaire un coup, ce qui permet d'explorer des
millions de nœuds sans pression sur le ramasse-miettes.</li>
<li><strong>Buffers de coups pré-alloués</strong>, un par profondeur.</li>
<li><strong>Ordonnancement</strong> : tout coup capturant la licorne est essayé
en premier (coupure immédiate) ; le meilleur coup d'une itération est replacé en
tête à l'itération suivante.</li>
</ul>
<p class="note">Cohérence des deux chemins. Le chemin « entier » du moteur double
le chemin « chaîne » vérifié de <code>EscampeBoard</code>. Pour exclure toute
divergence silencieuse entre ces deux implémentations des règles, un test croisé
(<code>VerifMoves</code>, §9) vérifie qu'ils produisent exactement les mêmes coups
et les mêmes états — c'est la garantie qu'optimiser n'a pas changé les règles.</p>
<p><strong>Performance mesurée.</strong> Environ <strong>4 à 5&nbsp;millions de
nœuds par seconde</strong>. En milieu de partie, l'approfondissement itératif
atteint une profondeur de <strong>12 à 15 demi-coups</strong> en 6&nbsp;s (davantage
dans les positions étroites). Les annonces de gain forcé du moteur se matérialisent
bien par une capture effective lors des parties de contrôle.</p>
<!-- ====================== 7. HEURISTIQUE ====================== -->
<h2>7. Heuristique d'évaluation</h2>
<p>Le matériel étant constant (paladins imprenables, licornes présentes jusqu'à la
capture), l'évaluation d'une position non terminale est purement <em>positionnelle</em>,
exprimée du point de vue du joueur au trait. Elle somme, à partir des distances de
Manhattan :</p>
<ul>
<li><strong>Pression d'attaque</strong> : proximité de nos paladins à la licorne
adverse — un terme de <em>somme</em> (pression globale) et un terme de
<em>minimum</em> (l'attaquant le plus proche pèse davantage) ;</li>
<li><strong>Sécurité</strong> : éloignement des paladins adverses de notre
licorne — mêmes deux termes, de signe opposé.</li>
</ul>
<p>Concrètement, avec les poids retenus (somme&nbsp;=&nbsp;2, minimum&nbsp;=&nbsp;8) :</p>
<pre class="grid">eval = 2·Σ(10d_attaque) 2·Σ(10d_défense)
+ 8·(10min d_attaque) 8·(10min d_défense)</pre>
<p><strong>Heuristiques testées et choix.</strong> Le réglage s'est fait par
auto-jeu déterministe et matchs arbitrés contre le joueur aléatoire fourni. Nous
avons comparé : (a) <em>somme seule</em> — jeu trop diffus, le moteur tarde à
concentrer une menace ; (b) <em>somme + minimum</em> (retenue) — le terme minimum,
fortement pondéré, oriente nettement les paladins vers la licorne adverse et
améliore le taux de capture ; (c) ajout d'un terme défensif symétrique — conservé,
il évite d'exposer notre licorne sans nuire à l'attaque. Le fort poids du terme
minimum reflète que, dans ce jeu, c'est l'attaquant <em>le plus avancé</em> qui
décide d'une prise.</p>
<p class="note">Limite assumée. Faute d'adversaires IA tiers disponibles avant le
tournoi, ces poids sont validés contre l'aléatoire et en auto-jeu, non contre
d'autres joueurs forts. Les tactiques de capture à court terme sont, elles,
gérées par la recherche, ce qui rend le joueur robuste même avec une évaluation
positionnelle simple.</p>
<!-- ====================== 8. TEMPS ====================== -->
<h2>8. Gestion du temps réel</h2>
<p>La limite de l'arbitre est de 300&nbsp;s par joueur et par partie. Nous nous
fixons une <strong>enveloppe interne de 280&nbsp;s</strong> (≈ 20&nbsp;s de marge).
Le budget alloué à un coup est une fraction du temps restant, bornée :</p>
<pre class="grid">tranche = clamp( temps_restant / 12 , 120 ms , 6000 ms )</pre>
<p>La division par le temps restant décroît géométriquement : le budget ne peut
<strong>jamais</strong> être épuisé, même sur une partie très longue. Le plafond de
6&nbsp;s évite de surinvestir en ouverture ; un plancher de 120&nbsp;ms garantit un
minimum de réflexion ; un mode « panique » sécurise les toutes dernières secondes.
L'approfondissement itératif rend le meilleur coup déjà trouvé dès que la tranche
expire (le temps est contrôlé toutes les 2048 explorations de nœuds).</p>
<p><strong>Mesures</strong> (auto-jeu équilibré, plein budget) : temps
<strong>maximal par coup ≈ 6,0&nbsp;s</strong> (le plafond), <strong>cumul maximal
≈ 36&nbsp;s</strong> par joueur sur une partie complète — très loin des 300&nbsp;s.
Le réglage est volontairement conservateur et pourrait être augmenté sans risque.</p>
<!-- ====================== 9. PERFS & TESTS ====================== -->
<h2>9. Performances et tests</h2>
<p>Notre démarche de validation est empirique et redondante : chaque maillon 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>Chemin entier (moteur) ≡ chemin chaîne (vérifié) : 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 directes : 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), 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>La séparation des rôles est délibérée : <code>VerifMoves</code> prouve que le
moteur ≡ <code>EscampeBoard</code> ; <code>RulesTest</code> prouve que
<code>EscampeBoard</code> respecte les règles ; les parties arbitrées prouvent que
le tout dialogue correctement avec l'arbitre réel. Aucun coup illégal n'a été
produit sur l'ensemble des parties jouées.</p>
<!-- ====================== 10. BUILD ====================== -->
<h2>10. Compilation, exécution et livrables</h2>
<p>Le script <code>build.sh</code> produit 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>Seules les classes de production entrent dans le jar (le joueur, le moteur, le
plateau et les classes fournies) ; les utilitaires de test (<code>VerifMoves</code>,
<code>RulesTest</code>, <code>Bench</code>, <code>Branching</code>) en sont exclus.
Le jeu en multijoueur (humain contre humain, ou humain contre notre IA, en local
ou à distance) est documenté à part dans <code>MULTIJOUEUR.md</code>.</p>
<!-- ====================== 11. SOURCES ====================== -->
<h2>11. Sources et bibliographie</h2>
<ul>
<li><strong>Énoncé du cours</strong> (Université Paris-Saclay, Polytech APP5,
2025-2026) : règles d'Escampe, carte des liserés (figure&nbsp;4), interface
<code>Partie1</code>, et classes d'infrastructure fournies
(<code>IJoueur</code>, <code>ClientJeu</code>, <code>Solo</code>,
<code>Applet</code>, serveur).</li>
<li><strong>Algorithmes classiques</strong>, à titre d'inspiration et sans copie
de code : élagage alpha-bêta (Knuth &amp; Moore, <em>An Analysis of Alpha-Beta
Pruning</em>, 1975) ; minimax, negamax et approfondissement itératif
(Russell &amp; Norvig, <em>Artificial Intelligence: A Modern Approach</em>) ;
techniques de représentation par masques de bits et d'ordonnancement de coups
(<em>Chess Programming Wiki</em>).</li>
<li><strong>Déclaration.</strong> Aucun programme d'Escampe externe n'a été
recopié. La seule rétro-ingénierie effectuée porte sur le jar d'arbitre
<em>fourni avec le sujet</em>, dans le seul but de confirmer le protocole (pass
<code>"E"</code>) et la carte des liserés — points sur lesquels la documentation
était ambiguë.</li>
</ul>
<!-- ====================== 12. CONCLUSION ====================== -->
<h2>12. Conclusion et difficultés rencontrées</h2>
<p>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. Les principales difficultés ont été :</p>
<ul>
<li><strong>L'obfuscation du serveur</strong> : lever l'ambiguïté du pass
(<code>"E"</code> vs <code>"PASSE"</code>) et confirmer la carte des liserés a
demandé une analyse du jar — étape décisive pour ne pas perdre sur coup illégal.</li>
<li><strong>L'interface obfusquée vs nos sources</strong> : le joueur aléatoire du
jar n'implémente pas notre <code>IJoueur</code> ; les tests contre lui passent
donc par le réseau (seules des chaînes circulent).</li>
<li><strong>L'avantage du trait</strong> : en miroir, Blanc (premier à jouer)
conserve l'initiative via la contrainte de liseré — propriété du jeu, indépendante
de la force du moteur.</li>
<li><strong>Le réglage de l'heuristique sans adversaires</strong> : validé contre
l'aléatoire et en auto-jeu.</li>
</ul>
<p><strong>Pistes d'amélioration</strong> : table de transposition (hachage de
Zobrist), bibliothèque d'ouvertures de placement, terme de mobilité différentielle
dans l'évaluation, et recherche de quiescence sur les menaces de capture.</p>
</body>
</html>