- 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>
468 lines
27 KiB
HTML
468 lines
27 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>
|
||
|
||
<!-- ====================== 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 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 ; 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 1 et 2) puis détaille la conception du joueur pour
|
||
le tournoi (partie 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 0</code> = ligne 1 (en bas) et <code>colonne 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 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">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 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 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 6). Noir vient de jouer en D4, une case
|
||
à liseré double, donc Blanc doit partir d'une case double : il choisit
|
||
<strong>F6 – 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 – A2</strong>. Or A2 est à liseré triple : Blanc enchaîne
|
||
<strong>C2 × C1</strong>, son paladin en C2 parcourant les trois pas
|
||
C2 → D2 → D1 → 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é ; 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) ; 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 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 ; 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 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 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 = 1</code>, <code>BLANC = -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 - 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 millions de nœuds
|
||
par seconde</strong>. En milieu de partie, l'approfondissement itératif atteint
|
||
<strong>12 à 15 demi-coups</strong> en 6 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·Σ(10−d_attaque) − 2·Σ(10−d_défense)
|
||
+ 8·(10−min d_attaque) − 8·(10−min 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 s par joueur et par partie. Nous travaillons sous une
|
||
enveloppe interne de <strong>280 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 s évite
|
||
de gaspiller du temps en ouverture, le plancher de 120 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 s</strong>, et le cumul sur une partie entière plafonne
|
||
vers <strong>36 s</strong> par joueur, loin des 300 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>≈ 4–5 M nœuds/s ; profondeur 12–15 ; 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) ; 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 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é ; 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>
|