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>
448 lines
25 KiB
HTML
448 lines
25 KiB
HTML
<!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 Puyaubreau & Antonin Russac
|
||
</div>
|
||
<div class="cover-date">30 mai 2026</div>
|
||
<div class="cover-meta">
|
||
Joueur : <code>escampe.JoueurPuyaubreauRussac</code><br>
|
||
Encadrement : Yue 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 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 1
|
||
et 2) puis la conception du joueur artificiel pour le tournoi (partie 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 0</code> = ligne 1 (bas) et <code>colonne 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 4 de l'énoncé (ligne 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 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 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 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 < 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 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 400–600 demi-coups. Pour
|
||
tenir la contrainte de temps (300 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 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 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 = 1</code>, <code>BLANC = -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 - 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 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 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 = 2, minimum = 8) :</p>
|
||
<pre class="grid">eval = 2·Σ(10−d_attaque) − 2·Σ(10−d_défense)
|
||
+ 8·(10−min d_attaque) − 8·(10−min 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 s par joueur et par partie. Nous nous
|
||
fixons une <strong>enveloppe interne de 280 s</strong> (≈ 20 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 s évite de surinvestir en ouverture ; un plancher de 120 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 s</strong> (le plafond), <strong>cumul maximal
|
||
≈ 36 s</strong> par joueur sur une partie complète — très loin des 300 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>≈ 4–5 M nœuds/s ; profondeur 12–15 ; 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 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 & Moore, <em>An Analysis of Alpha-Beta
|
||
Pruning</em>, 1975) ; minimax, negamax et approfondissement itératif
|
||
(Russell & 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>
|