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>
This commit is contained in:
2026-05-30 20:39:15 +02:00
parent cfc1ff0b72
commit 052a3bf978
7 changed files with 534 additions and 448 deletions

View File

@@ -23,56 +23,75 @@
</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> (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>
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>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>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>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>
<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 ici les sept questions de la première partie, en les étayant
par l'implémentation finalement réalisée.</p>
<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 (bas) et <code>colonne&nbsp;0</code> = A.
Chaque case contient une constante de pièce (<code>EMPTY</code>,
<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>). L'état complémentaire, indispensable à la règle, est
maintenu hors du plateau :</p>
<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> : 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>
<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><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>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 une constante <code>TILE_MAP</code> reproduisant la
figure&nbsp;4 de l'énoncé (ligne&nbsp;1 en bas) :</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
@@ -80,143 +99,143 @@ figure&nbsp;4 de l'énoncé (ligne&nbsp;1 en bas) :</p>
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>
<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 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
elle est jouée (§6).</p>
<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>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>
<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, aucune contrainte)</td><td>49</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>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>
<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'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>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>
<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>,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>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>
<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>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>
<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 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>
<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> (≈ 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><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><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>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><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>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">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>
<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 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>
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>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>
<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><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>
<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>En appliquant chaque coup à l'<code>EscampeBoard</code> interne dans cet ordre,
le joueur au trait reste naturellement synchronisé avec l'arbitre.</p>
<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><strong>Exécution.</strong> Trois processus (serveur + deux clients) :</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>
@@ -224,24 +243,24 @@ java -cp escampeobf.jar escampe.ClientJeu escampe.JoueurAleatoire
<!-- ====================== 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>
<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>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>
<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>Dispositions retenues (légalité et propriétés vérifiées) ; pour Blanc, on joue
le bord complémentaire de celui de Noir :</p>
<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 .
@@ -249,120 +268,122 @@ le bord complémentaire de celui de Noir :</p>
(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>La décision repose sur un <strong>negamax</strong> avec <strong>élagage
<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 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>
<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><strong>Astuces de performance.</strong></p>
<p>Plusieurs choix tirent la vitesse vers le haut :</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 p-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 repla en
tête à l'itération suivante.</li>
<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édente.</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 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><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>
<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 é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>
<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><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>
<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>Concrètement, avec les poids retenus (somme&nbsp;=&nbsp;2, minimum&nbsp;=&nbsp;8) :</p>
<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><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>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">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>
<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>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>
<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>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>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><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>
<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>Notre démarche de validation est empirique et redondante : chaque maillon est
contrôlé contre une référence indépendante.</p>
<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>Chemin entier (moteur) ≡ chemin chaîne (vérifié) : mêmes coups, même
<code>make</code>/<code>unmake</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 directes : pas = liseré, capture au dernier pas, paladins
imprenables, non-traversée, contrainte de liseré, pass forcé, fin, zones de placement</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), légalité</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>
@@ -373,75 +394,74 @@ contrôlé contre une référence indépendante.</p>
<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>
<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>
<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> produit dans <code>dist/</code> les trois
livrables de la version finale :</p>
<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>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>
<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><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
<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><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
<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><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>
<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 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>
<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> : 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>
<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><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>
<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>