<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[The Rubber Ducky]]></title><description><![CDATA[Embarquez pour un voyage exceptionnel dans le monde du dev.]]></description><link>https://tech.cpignon.fr/</link><image><url>https://tech.cpignon.fr/favicon.png</url><title>The Rubber Ducky</title><link>https://tech.cpignon.fr/</link></image><generator>Ghost 5.87</generator><lastBuildDate>Wed, 08 Jul 2026 08:47:17 GMT</lastBuildDate><atom:link href="https://tech.cpignon.fr/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[CheckM8 : Créez votre propre entraîneur d’échecs en ligne (version 1)]]></title><description><![CDATA[<h2 id="1-introduction">1. Introduction</h2>
<p>Les &#xE9;checs sont un jeu exigeant o&#xF9; la pratique et l&#x2019;analyse sont essentielles pour progresser. Que ce soit pour tester des ouvertures, rejouer une partie apr&#xE8;s une d&#xE9;faite ou simplement s&#x2019;entra&#xEE;ner &#xE0; r&#xE9;fl&#xE9;</p>]]></description><link>https://tech.cpignon.fr/checkm8-creez-votre-propre-entraineur-dechecs-en-ligne-version-1/</link><guid isPermaLink="false">67badc3cc3eeb30001f622ad</guid><dc:creator><![CDATA[Camille Pignon]]></dc:creator><pubDate>Sun, 23 Mar 2025 19:51:07 GMT</pubDate><media:content url="https://tech.cpignon.fr/content/images/2025/02/1BvBPEISUW47oO-lPNk04lQ-scaled.jpeg" medium="image"/><content:encoded><![CDATA[<h2 id="1-introduction">1. Introduction</h2>
<img src="https://tech.cpignon.fr/content/images/2025/02/1BvBPEISUW47oO-lPNk04lQ-scaled.jpeg" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)"><p>Les &#xE9;checs sont un jeu exigeant o&#xF9; la pratique et l&#x2019;analyse sont essentielles pour progresser. Que ce soit pour tester des ouvertures, rejouer une partie apr&#xE8;s une d&#xE9;faite ou simplement s&#x2019;entra&#xEE;ner &#xE0; r&#xE9;fl&#xE9;chir &#xE0; des positions sp&#xE9;cifiques, il est crucial de pouvoir jouer r&#xE9;guli&#xE8;rement. Cependant, on n&#x2019;a pas toujours un adversaire disponible, m&#xEA;me si des plateformes comme <strong>chess.com</strong> ou <strong>lichess</strong> permettent aujourd&#x2019;hui de trouver une partie en quelques secondes.</p>
<p>Dans cet article, nous allons voir comment cr&#xE9;er une application web pour jouer aux &#xE9;checs contre une intelligence artificielle, en int&#xE9;grant le moteur <strong>Stockfish</strong> directement dans le navigateur gr&#xE2;ce &#xE0; <strong>Vue 3</strong> et <strong>WebAssembly</strong>. Nous allons d&#xE9;tailler la conception fonctionnelle et technique avant de passer &#xE0; la r&#xE9;alisation. Si vous souhaitez directement coder, vous pouvez sauter ces sections et aller directement &#xE0; la partie <em><strong>R&#xE9;alisation</strong></em>.</p>
<h2 id="2-conception">2. Conception</h2>
<h3 id="21-d%C3%A9finition-fonctionnelle">2.1. D&#xE9;finition fonctionnelle</h3>
<p>La premi&#xE8;re partie de notre conception va &#xEA;tre fonctionnelle et va consister &#xE0; d&#xE9;terminer ce que notre client web peut faire.</p>
<h4 id="211-exigences-fonctionnelles">2.1.1. Exigences fonctionnelles</h4>
<p>Plongeons tout de suite dans le vif du sujet. Dans la premi&#xE8;re version du projet, un utilisateur doit pouvoir :</p>
<ol>
<li>Acc&#xE9;der &#xE0; une page web contenant un &#xE9;chiquier ;</li>
<li>Commencer une nouvelle partie en choisissant son c&#xF4;t&#xE9; (Blancs ou Noirs) ;</li>
<li>Savoir &#xE0; qui est le tour de jouer ;</li>
<li>D&#xE9;placer les pi&#xE8;ces quand c&apos;est son tour ;</li>
<li>Obtenir une r&#xE9;ponse du bot apr&#xE8;s chaque coup ;</li>
<li>&#xCA;tre alert&#xE9; quand la partie est termin&#xE9;e.</li>
</ol>
<p>Ces exigences repr&#xE9;sentent le minimum pour que l&apos;utilisateur puisse jouer contre notre bot. Dans cette version, nous nous cantonnerons &#xE0; ces fonctionnalit&#xE9;s et nous les enrichirons dans les versions suivantes.</p>
<h4 id="212-user-stories-et-crit%C3%A8res-dacceptation">2.1.2. User stories et crit&#xE8;res d&apos;acceptation</h4>
<p>Transformons ces exigences en <em>user stories</em> et en <em>crit&#xE8;res d&apos;acceptation</em>. Cela nous donne :</p>
<blockquote>
<h6 id="us-1-en-tant-quutilisateur-je-veux-pouvoir-acc%C3%A9der-%C3%A0-une-page-web-contenant-un-%C3%A9chiquier-afin-de-mentra%C3%AEner">US-1 : En tant qu&apos;utilisateur, je veux pouvoir acc&#xE9;der &#xE0; une page web contenant un &#xE9;chiquier afin de m&apos;entra&#xEE;ner.</h6>
</blockquote>
<ul>
<li><strong>AC-1-1 :</strong> L&apos;application met &#xE0; disposition de l&apos;utilisateur une page web sur laquelle est affich&#xE9;e un &#xE9;chiquier.</li>
<li><strong>AC-1-2 :</strong> Les coordonn&#xE9;es des cases sont affich&#xE9;es : <em>a, b, ..., h</em> pour les colonnes et <em>1, 2, ..., 8</em> pour les rang&#xE9;es.</li>
</ul>
<blockquote>
<h6 id="us-2-en-tant-quutilisateur-je-veux-pouvoir-d%C3%A9placer-mes-pi%C3%A8ces-quand-cest-mon-tour-afin-de-jouer-mon-coup">US-2 : En tant qu&apos;utilisateur, je veux pouvoir d&#xE9;placer mes pi&#xE8;ces quand c&apos;est mon tour afin de jouer mon coup.</h6>
</blockquote>
<ul>
<li><strong>AC-2-1 :</strong> Lorsque c&apos;est le tour de l&apos;utilisateur, les pi&#xE8;ces de sa couleur sont d&#xE9;placables. Les pi&#xE8;ces de l&apos;autre couleur ne peuvent pas &#xEA;tre d&#xE9;plac&#xE9;es.</li>
<li><strong>AC-2-2 :</strong> Lorsque l&apos;utilisateur saisit une pi&#xE8;ce, les coups l&#xE9;gaux sont affich&#xE9;s sur l&apos;&#xE9;chiquier.</li>
<li><strong>AC-2-3 :</strong> L&apos;utilisateur valide son coup en rel&#xE2;chant la pi&#xE8;ce. Il n&apos;est alors plus possible de revenir en arri&#xE8;re.</li>
<li><strong>AC-2-4 :</strong> Lorsque l&apos;utilisateur a valid&#xE9; son coup, celui-ci est mis en &#xE9;vidence en affichant la case de d&#xE9;part et celle d&apos;arriv&#xE9;e d&apos;une couleur diff&#xE9;rente des autres cases.</li>
</ul>
<blockquote>
<h6 id="us-3-en-tant-quutilisateur-je-veux-pouvoir-obtenir-une-r%C3%A9ponse-du-bot-apr%C3%A8s-avoir-jou%C3%A9-un-coup">US-3 : En tant qu&apos;utilisateur, je veux pouvoir obtenir une r&#xE9;ponse du bot apr&#xE8;s avoir jou&#xE9; un coup.</h6>
</blockquote>
<ul>
<li><strong>AC-3-1 :</strong> La validation du coup par l&apos;utilisateur d&#xE9;clenche la r&#xE9;flexion du bot.</li>
<li><strong>AC-3-2 :</strong> Le bot r&#xE9;pond apr&#xE8;s un d&#xE9;lai raisonnable (&lt; 3sec).</li>
<li><strong>AC-3-3 :</strong> Le coup jou&#xE9; par le bot est &#xE9;galement mis en &#xE9;vidence (de la m&#xEA;me fa&#xE7;on que pour l&apos;utilisateur).</li>
<li><strong>AC-3-4 :</strong> L&apos;utilisateur ne peut d&#xE9;placer aucune pi&#xE8;ce pendant que c&apos;est au tour du bot de jouer : ni les siennes ni celles de son adversaire.</li>
</ul>
<blockquote>
<h6 id="us-4-en-tant-quutilisateur-je-veux-pouvoir-savoir-%C3%A0-tout-moment-%C3%A0-qui-est-le-tour-de-jouer">US-4 : En tant qu&apos;utilisateur, je veux pouvoir savoir &#xE0; tout moment &#xE0; qui est le tour de jouer.</h6>
</blockquote>
<ul>
<li><strong>AC-4-1 :</strong> Un message texte est affich&#xE9; au-dessus de l&apos;&#xE9;chiquier pour indiquer &#xE0; qui est le tour de jouer. Celui-ci est : <em>Tour courant : (Blancs|Noirs)</em> en fonction de la couleur du joueur &#xE0; qui c&apos;est le tour.</li>
</ul>
<blockquote>
<h6 id="us-5-en-tant-quutilisateur-je-veux-pouvoir-%C3%AAtre-inform%C3%A9-lorsque-la-partie-est-termin%C3%A9e-afin-de-savoir-si-jai-gagn%C3%A9-ou-perdu">US-5 : En tant qu&apos;utilisateur, je veux pouvoir &#xEA;tre inform&#xE9; lorsque la partie est termin&#xE9;e afin de savoir si j&apos;ai gagn&#xE9; ou perdu.</h6>
</blockquote>
<ul>
<li><strong>AC-5-1 :</strong> Un message texte s&apos;affiche entre le message qui indique &#xE0; qui est le tour de jouer et l&apos;&#xE9;chiquier pour indiquer que la partie est termin&#xE9;e.</li>
<li><strong>AC-5-2 :</strong> Si l&apos;utilisateur a gagn&#xE9; la partie, le message est : <em>Bravo ! Vous avez gagn&#xE9; la partie !</em></li>
<li><strong>AC-5-3 :</strong> Si le bot a gagn&#xE9; la partie, le message est : <em>Zut ! Vous avez perdu pour cette fois !</em></li>
<li><strong>AC-5-4 :</strong> S&apos;il y a pat, le message est : <em>Match nul ! C&apos;est un pat !</em></li>
<li><strong>AC-5-5 :</strong> S&apos;il y a un nul, le message est : <em>Match nul !</em></li>
<li><strong>AC-5-6 :</strong> Lorsque la partie est termin&#xE9;e, il n&apos;est plus possible de d&#xE9;placer les pi&#xE8;ces sur l&apos;&#xE9;chiquier (quelle que soit la couleur).</li>
</ul>
<blockquote>
<h6 id="us-6-en-tant-quutilisateur-je-veux-pouvoir-commencer-une-nouvelle-partie-et-choisir-mon-c%C3%B4t%C3%A9-blancs-ou-noirs-afin-de-rejouer-contre-lia-et-mentra%C3%AEner-avec-un-c%C3%B4t%C3%A9-ou-lautre">US-6 : En tant qu&apos;utilisateur, je veux pouvoir commencer une nouvelle partie et choisir mon c&#xF4;t&#xE9; (Blancs ou Noirs) afin de (re)jouer contre l&apos;IA et m&apos;entra&#xEE;ner avec un c&#xF4;t&#xE9; ou l&apos;autre.</h6>
</blockquote>
<ul>
<li><strong>AC-6-1 :</strong> Sous l&apos;&#xE9;chiquier, deux boutons <strong>Nouvelle partie (Blancs)</strong> et <strong>Nouvelle partie (Noirs)</strong> sont pr&#xE9;sents et permettent de commencer une nouvelle partie en jouant respectivement les Blancs ou les Noirs.</li>
<li><strong>AC-6-2 :</strong> Lorsque l&apos;utilisateur arrive sur la page, il a les Blancs par d&#xE9;faut.</li>
<li><strong>AC-6-3 :</strong> Si une partie &#xE9;tait d&#xE9;j&#xE0; en cours, les pi&#xE8;ces sont repositionn&#xE9;s dans l&apos;&#xE9;tat de d&#xE9;part du jeu.</li>
<li><strong>AC-6-4 :</strong> Si l&apos;utilisateur choisit de recommencer une nouvelle partie en jouant les Noirs alors l&apos;&#xE9;chiquier est retourn&#xE9; de fa&#xE7;on &#xE0; ce que les pi&#xE8;ces Noirs soient en bas de l&apos;&#xE9;chiquier.</li>
<li><strong>AC-6-5 :</strong> Si l&apos;utilisateur choisit de jouer les Noirs alors le clic sur ce bouton d&#xE9;clenche aussi le premier coup du bot (puisque ce sont les Blancs qui commencent).</li>
</ul>
<h4 id="213-maquettage">2.1.3. Maquettage</h4>
<p>Maintenant que nous avons d&#xE9;fini et sp&#xE9;cifi&#xE9; le p&#xE9;rim&#xE8;tre fonctionnel, nous pouvons passer &#xE0; la partie maquettage.</p>
<p>Ici, rien de compliqu&#xE9;. Nous optons pour une interface vraiment simple pour cette premi&#xE8;re version. Il n&apos;y a qu&apos;un seul &#xE9;cran pr&#xE9;sent&#xE9; ci-dessous :<br>
<br></p>
<div style="margin: auto">
    <img src="https://tech.cpignon.fr/content/images/2025/02/chessbot-wireframe-v1.00.drawio-1.png" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)">
<span style="display: block; text-align: center; font-size: 0.8em"><em>Figure 1 - Maquette wireframe de la page web</em></span>
</div>
<br>
<p>Sur cette maquette, nous retrouvons notre &#xE9;chiquier avec les deux boutons pour commencer une nouvelle partie juste en dessous. Au-dessus du plateau, nous avons l&apos;indicateur textuel du tour actuel ainsi que le message de fin de partie qui appara&#xEE;t quand celle-ci est finie.</p>
<h3 id="22-conception-technique">2.2. Conception technique</h3>
<p>Nous allons maintenant nous attaquer &#xE0; l&apos;architecture technique de notre application. Elle d&#xE9;taille les choix technologiques et d&#xE9;crit les int&#xE9;ractions entre les diff&#xE9;rentes briques du syst&#xE8;me et avec l&apos;ext&#xE9;rieur.</p>
<h4 id="221-pile-technologique">2.2.1. Pile technologique</h4>
<p>Voici la liste des technos que nous allons utiliser pour r&#xE9;aliser ce projet :</p>
<table>
<thead>
<tr>
<th>Technologie</th>
<th>Cat&#xE9;gorie</th>
<th>R&#xF4;le</th>
<th>Pourquoi ?</th>
</tr>
</thead>
<tbody>
<tr>
<td><img src="https://tech.cpignon.fr/content/images/2025/03/Vue.js.png" width="64" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)"> <a href="https://vuejs.org/?ref=tech.cpignon.fr">Vue 3 + <br>TypeScript</a><br>v3.5.13</td>
<td>Frontend</td>
<td>Interface utilisateur</td>
<td>&#x1F539; R&#xE9;actif et performant <br> &#x1F539; Typage statique pour robustesse</td>
</tr>
<tr>
<td><img src="https://tech.cpignon.fr/content/images/2025/03/lichess-icon-1023x1024-di4awn0a.png" width="64" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)"> <a href="https://github.com/vitogit/vue3-chessboard?ref=tech.cpignon.fr">vue3-chessboard</a><br>v1.3.3</td>
<td>&#xC9;chiquier <br>Moteur de r&#xE8;gles</td>
<td>Affichage des pi&#xE8;ces</td>
<td>&#x1F539; Int&#xE9;gr&#xE9; &#xE0; Vue 3 <br> &#x1F539; Bas&#xE9; sur lichess chessground <br>&#xA0; &#xA0; &#xA0; et chess.js <br>&#x1F539; Drag &amp; drop intuitif</td>
</tr>
<tr>
<td><img src="https://tech.cpignon.fr/content/images/2025/03/Stockfish_icon_-2010-2020-.png" width="64" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)"> <a href="https://stockfishchess.org/?ref=tech.cpignon.fr">Stockfish</a><br>v16.0.0</td>
<td>IA</td>
<td>Analyse des positions</td>
<td>&#x1F539; Moteur puissant et open-source <br> &#x1F539; Fonctionne en Web Worker</td>
</tr>
<tr>
<td><img src="https://tech.cpignon.fr/content/images/2025/03/Nginx_logo.svg.png" width="64" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)"> <a href="https://www.nginx.com/?ref=tech.cpignon.fr">nginx</a><br>v1.27.4</td>
<td>Serveur Web</td>
<td>Gestion des requ&#xEA;tes</td>
<td>&#x1F539; Performant et l&#xE9;ger <br> &#x1F539; Optimis&#xE9; pour fichiers statiques</td>
</tr>
<tr>
<td><img src="https://tech.cpignon.fr/content/images/2025/03/97_Docker_logo_logos-512.webp" width="64" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)"> <a href="https://www.docker.com/?ref=tech.cpignon.fr">Docker</a><br>v28.0.1</td>
<td>Conteneurisation</td>
<td>Ex&#xE9;cution isol&#xE9;e</td>
<td>&#x1F539; Portabilit&#xE9; maximale <br> &#x1F539; D&#xE9;ploiement rapide</td>
</tr>
<tr>
<td><img src="https://tech.cpignon.fr/content/images/2025/03/1635982328-docker-compose.png" width="64" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)"> <a href="https://docs.docker.com/compose/?ref=tech.cpignon.fr">Docker Compose</a><br>v2.33.1</td>
<td>Orchestration</td>
<td>Gestion multi-services</td>
<td>&#x1F539; Configuration simplifi&#xE9;e <br> &#x1F539; D&#xE9;ploiement en une commande</td>
</tr>
</tbody>
</table>
<h6 id="stockfish-et-les-web-workers">Stockfish et les Web Workers</h6>
<p>Dans notre application <strong>CheckM8</strong>, Stockfish tourne dans un Web Worker afin de ne pas bloquer l&#x2019;interface utilisateur lors des calculs.<br>
Un moteur d&#x2019;&#xE9;checs, comme Stockfish, effectue des calculs intensifs pour analyser des positions et trouver le meilleur coup : il est essentiel de l&apos;<strong>ex&#xE9;cuter en arri&#xE8;re-plan</strong> pour garantir une exp&#xE9;rience fluide.</p>
<p>Un <strong>Web Worker</strong> est un script JavaScript qui s&apos;ex&#xE9;cute <strong>ind&#xE9;pendamment du thread principal du navigateur</strong>. Il permet d&#x2019;ex&#xE9;cuter des t&#xE2;ches lourdes (comme des calculs intensifs) sans affecter la r&#xE9;activit&#xE9; de l&#x2019;interface.</p>
<p><strong>Caract&#xE9;ristiques principales d&#x2019;un Web Worker :</strong></p>
<ul>
<li>Il tourne dans un thread s&#xE9;par&#xE9; du navigateur.</li>
<li>Il ne peut pas manipuler directement le DOM (pas d&apos;acc&#xE8;s aux &#xE9;l&#xE9;ments HTML).</li>
<li>Il communique avec le thread principal via la fonction <em>postMessage()</em>.</li>
<li>Il est souvent utilis&#xE9; pour le calcul intensif, les IA, ou le traitement de donn&#xE9;es.</li>
</ul>
<p>Gr&#xE2;ce &#xE0; cette approche, <strong>CheckM8 peut ex&#xE9;cuter Stockfish sans ralentir les interactions du joueur</strong>, garantissant ainsi une exp&#xE9;rience de jeu fluide et r&#xE9;active.</p>
<h4 id="222-mod%C3%A9lisation-du-syst%C3%A8me">2.2.2. Mod&#xE9;lisation du syst&#xE8;me</h4>
<h6 id="diagramme-de-contexte-syst%C3%A8me">Diagramme de contexte syst&#xE8;me</h6>
<p>Pour avoir une <strong>vision globale du syst&#xE8;me</strong> et de comment il va int&#xE9;ragir avec l&apos;ext&#xE9;rieur, r&#xE9;alisons le diagramme de contexte syst&#xE8;me pour notre application. Ce diagramme est le premier niveau du mod&#xE8;le C4 (<a href="https://c4model.com/?ref=tech.cpignon.fr">https://c4model.com/</a>).<br>
<br></p>
<div style="margin: auto">
    <img src="https://tech.cpignon.fr/content/images/2025/03/chessbot-system-context-diagram-1.png" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)">
    <span style="display: block; font-size: 0.8em; text-align: center"><em>Figure 2 - Diagramme de contexte syst&#xE8;me</em></span>
</div>
<br>
<p>Dans le cas &#xE0; l&apos;&#xE9;tude, ce diagramme est extr&#xEA;ment simple puisque notre application n&apos;int&#xE9;ragit pas avec des services externes. Si nous avions d&#xE9;l&#xE9;gu&#xE9; &#xE0; une API externe le fait de r&#xE9;pondre aux coups de l&apos;utilisateur par exemple, cette API aurait &#xE9;t&#xE9; repr&#xE9;sent&#xE9;e ici.<br>
Ici, nous avons donc juste notre <strong>utilisateur</strong> qui int&#xE9;ragit avec <strong>CheckM8</strong>, notre application, et c&apos;est tout !</p>
<br>
<h6 id="diagramme-des-conteneurs">Diagramme des conteneurs</h6>
<p>Allons un cran plus loin dans notre conception avec le deuxi&#xE8;me niveau du mod&#xE8;le C4 : le diagramme des conteneurs ! Celui-ci d&#xE9;taille <strong>les diff&#xE9;rentes briques (ou conteneurs) qui composent le syst&#xE8;me</strong> &#xE0; l&apos;&#xE9;tude.<br>
<br></p>
<div style="margin: auto">
    <img src="https://tech.cpignon.fr/content/images/2025/03/checkm8-container-diagram.png" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)">
    <span style="display: block; font-size: 0.8em; text-align: center"><em>Figure 3 - Diagramme de conteneurs</em></span>
</div>
<br>
<p>On y voit la SPA <strong>Vue 3</strong> qui est mise &#xE0; disposition des utilisateurs par un serveur web (ici <strong>nginx</strong>) conteneuris&#xE9;.</p>
<br>
<h5 id="diagramme-de-s%C3%A9quence-dun-tour-de-jeu">Diagramme de s&#xE9;quence d&apos;un tour de jeu</h5>
<p>Pour avoir une meilleure id&#xE9;e de l&apos;encha&#xEE;nement des actions lorsque l&apos;utilisateur joue un coup, nous pouvons dessiner le diagramme de s&#xE9;quence qui montre le d&#xE9;roulement d&apos;un tour de jeu. Il d&#xE9;crit ce qu&apos;il se passe &#xE0; partir de l&apos;action de l&apos;utilisateur jusqu&apos;&#xE0; la r&#xE9;ponse de Stockfish.<br>
<br></p>
<div style="margin: auto">
    <img src="https://tech.cpignon.fr/content/images/2025/03/checkm8-sequence-diagram-v2-3.png" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)">
    <span style="display: block; font-size: 0.8em; text-align: center"><em>Figure 4 - Diagramme de s&#xE9;quence d&apos;un tour de jeu</em></span>
</div>
<p>Les &#xE9;tapes sont :</p>
<h6 id="1-le-joueur-joue-un-coup">1. <strong>Le joueur joue un coup</strong></h6>
<p>L&#x2019;utilisateur d&#xE9;place une pi&#xE8;ce sur l&#x2019;&#xE9;chiquier via <strong>vue3-chessboard</strong> (ex: e2 &#x2192; e4).</p>
<h6 id="2-%C3%A9mission-de-l%E2%80%99%C3%A9v%C3%A9nement-move">2. <strong>&#xC9;mission de l&#x2019;&#xE9;v&#xE9;nement <em>move</em></strong></h6>
<p><strong>vue3-chessboard</strong> met &#xE0; jour l&apos;&#xE9;tat de la partie et &#xE9;met l&#x2019;&#xE9;v&#xE9;nement <em>move</em> lorsqu&#x2019;un coup valide est jou&#xE9;.<br>
Cet &#xE9;v&#xE9;nement est &#xE9;cout&#xE9; par <strong>Vue.js</strong>, o&#xF9; un handler attach&#xE9; intercepte l&#x2019;&#xE9;v&#xE9;nement.</p>
<h6 id="3-r%C3%A9cup%C3%A9ration-de-la-position-fen">3. <strong>R&#xE9;cup&#xE9;ration de la position FEN</strong></h6>
<p><strong>Vue.js</strong> utilise l&#x2019;API de <strong>vue3-chessboard</strong> pour r&#xE9;cup&#xE9;rer la FEN (<em>Forsyth-Edwards Notation</em>) de la position apr&#xE8;s le coup.<br>
La FEN est une repr&#xE9;sentation textuelle de l&#x2019;&#xE9;chiquier. C&apos;est cette valeur que nous allons utiliser pour communiquer avec Stockfish.</p>
<h6 id="4-envoi-de-la-position-fen-%C3%A0-stockfish">4. <strong>Envoi de la position FEN &#xE0; Stockfish</strong></h6>
<p><strong>Vue.js</strong> transmet la FEN au <strong>Web Worker ex&#xE9;cutant Stockfish</strong>.<br>
Depuis le <strong>Vue.js</strong> la commande <em>&quot;position fen &lt;...&gt;&quot;</em> est envoy&#xE9;e &#xE0; <strong>Stockfish</strong> puis le calcul est lanc&#xE9; avec <em>&quot;go movetime 1000&quot;</em> pour demander le meilleur coup en 1 seconde (ou un autre temps si on veut contr&#xF4;ler la force de Stockfish).</p>
<h6 id="5-stockfish-calcule-la-r%C3%A9ponse">5. <strong>Stockfish calcule la r&#xE9;ponse</strong></h6>
<p><strong>Stockfish</strong> analyse la position et d&#xE9;termine le meilleur coup en fonction du temps allou&#xE9;.</p>
<h6 id="6-stockfish-r%C3%A9pond-via-onmessage">6. <strong>Stockfish r&#xE9;pond via <em>onmessage</em></strong></h6>
<p><strong>Stockfish</strong> &#xE9;met un message <em>bestmove</em> (sous la forme <em>&quot;bestmove e7e5&quot;</em>) via l&apos;&#xE9;v&#xE8;nement <em>onmessage</em> du <strong>Web Worker</strong>.</p>
<h6 id="7-mise-%C3%A0-jour-de-l%E2%80%99%C3%A9chiquier">7. <strong>Mise &#xE0; jour de l&#x2019;&#xE9;chiquier</strong></h6>
<p><strong>Vue.js</strong> analyse le message et met &#xE0; jour <strong>vue3-chessboard</strong> avec le coup retourn&#xE9; par <strong>Stockfish</strong> (ex: e7 &#x2192; e5).<br>
L&#x2019;&#xE9;chiquier affiche automatiquement le coup jou&#xE9; par l&#x2019;IA.</p>
<p>Ces &#xE9;tapes se r&#xE9;p&#xE8;tent en boucle jusqu&apos;&#xE0; la fin de la partie.</p>
<h2 id="3-r%C3%A9alisation">3. R&#xE9;alisation</h2>
<p>C&apos;est enfin le moment de passer au d&#xE9;veloppement de notre application !</p>
<h3 id="31-cr%C3%A9er-le-projet-vue-3">3.1. Cr&#xE9;er le projet Vue 3</h3>
<p>Comme d&#xE9;crit dans la partie pr&#xE9;c&#xE9;dente, nous allons utiliser le framework <strong>Vue 3</strong> pour d&#xE9;velopper notre client web. La premi&#xE8;re &#xE9;tape est donc de cr&#xE9;er le projet Vue.</p>
<p>Pour cela, depuis le r&#xE9;pertoire de notre choix, cr&#xE9;ons un r&#xE9;pertoire <strong>checkm8</strong> et placons nous &#xE0; l&apos;int&#xE9;rieur de ce r&#xE9;pertoire avec la commande :</p>
<pre><code class="language-bash">mkdir checkm8 &amp;&amp; cd checkm8
</code></pre>
<p>Ex&#xE9;cutons la commande suivante pour initier un nouveau projet Vue utilisant Typescript :</p>
<pre><code class="language-bash">npm create vite@latest checkm8-frontend -- --template vue-ts
</code></pre>
<p>Ensuite, d&#xE9;placons nous dans le r&#xE9;pertoire <em>checkm8-frontend</em> puis installons les d&#xE9;pendances :</p>
<pre><code class="language-bash">npm install
</code></pre>
<p>Enfin, pour tester que la cr&#xE9;ation du projet est r&#xE9;ussie, lancons le projet avec la commande :</p>
<pre><code class="language-bash">npm run dev
</code></pre>
<p>L&apos;application Vue devrait se lancer sur le port 5173 (par d&#xE9;faut) et &#xEA;tre accessible &#xE0; l&apos;adresse <a href="http://localhost:5173/?ref=tech.cpignon.fr">http://localhost:5173</a>.</p>
<p>Avant de d&#xE9;marrer les d&#xE9;veloppements, nous allons tout d&apos;abord :</p>
<ol>
<li>Supprimer le composant d&apos;exemple <em>HelloWorld.vue</em> &#x2192; Ouvrez donc le r&#xE9;pertoire <em>src/components</em> et supprimez le fichier <em>HelloWorld.vue</em> (vous pouvez le remplacer par un fichier <em>.gitkeep</em> pour que le r&#xE9;pertoire <em>components</em> reste versionn&#xE9;)</li>
<li>Supprimer le fichier <em>vue.svg</em> du r&#xE9;pertoire <em>src/assets</em> (l&#xE0; encore vous pouvez le remplacer par un fichier <em>.gitkeep</em> pour que le r&#xE9;pertoire <em>assets</em> reste versionn&#xE9;)</li>
<li>Changer le titre de la page &#x2192; Ouvrez le fichier <em>index.html</em> &#xE0; la racine du projet et changer le contenu de la balise <code>&lt;title&gt;</code> pour <strong>CheckM8</strong></li>
</ol>
<p><strong>N.B. Nous allons garder le CSS g&#xE9;n&#xE9;r&#xE9; en exemple pour cette premi&#xE8;re version donc faites attention &#xE0; ne pas l&apos;effacer (fichier <em>style.css</em> &#xE0; la racine du projet).</strong></p>
<h3 id="32-afficher-un-plateau-sur-notre-page-web">3.2. Afficher un plateau sur notre page web</h3>
<p>La premi&#xE8;re &#xE9;tape va &#xEA;tre d&apos;afficher notre plateau d&apos;&#xE9;checs sur la page principale de l&apos;application.</p>
<h4 id="321-installer-vue3-chessboard">3.2.1. Installer <em>vue3-chessboard</em></h4>
<p>Pour cela, nous commencons par installer le paquet NPM <strong>vue3-chessboard</strong> en lancant la commande :</p>
<pre><code class="language-bash">npm install vue3-chessboard
</code></pre>
<h4 id="322-int%C3%A9grer-le-composant-vue3-chessboard-sur-notre-page-web">3.2.2. Int&#xE9;grer le composant <em>vue3-chessboard</em> sur notre page web</h4>
<p><strong>N.B. Pour cette premi&#xE8;re version, nous allons centraliser toute la logique et l&apos;affichage dans <em>App.vue</em> afin de garder une structure simple et faciliter le d&#xE9;veloppement initial. Dans les prochaines &#xE9;volutions, nous refactoriserons ce fichier en composants modulaires pour am&#xE9;liorer la maintenabilit&#xE9; et la lisibilit&#xE9; du code.</strong></p>
<p>Tout d&apos;abord, vidons les trois parties du fichier <em>App.vue</em> : tout ce qu&apos;il y a entre les balises <code>&lt;script setup lang=&quot;ts&quot;&gt;&lt;/script&gt;</code>, <code>&lt;template&gt;&lt;/template&gt;</code> et <code>&lt;style scoped&gt;&lt;/scoped&gt;</code>. Nous avons maintenant un composant pr&#xEA;t pour que nous y ajoutions notre code.</p>
<p>Nous allons commencer par mettre les imports suivants dans la partie <code>&lt;script setup lang=&quot;ts&quot;&gt;&lt;/script&gt;</code> :<br>
<br></p>
<pre><code class="language-typescript">import { reactive } from &apos;vue&apos;;
import { TheChessboard } from &apos;vue3-chessboard&apos;;
import &apos;vue3-chessboard/style.css&apos;;
import type { BoardApi, BoardConfig } from &apos;vue3-chessboard&apos;;
</code></pre>
<p>Juste en dessous, nous allons d&#xE9;clarer deux variables qui vont nous permettre d&apos;int&#xE9;ragir avec le composant Vue <strong>vue3-chessboard</strong> :</p>
<pre><code class="language-typescript">let boardAPI: BoardApi;

const boardConfig: BoardConfig = reactive({
  coordinates: true,
  orientation: &apos;white&apos;,
  position: &apos;start&apos;,
});
</code></pre>
<ul>
<li>
<p>La variable <code>boardAPI</code> permet d&apos;appeler l&apos;API <strong>vue3-chessboard</strong> pour, par exemple, r&#xE9;cup&#xE9;rer la notation FEN de l&apos;&#xE9;tat actuel de la partie, faire bouger une pi&#xE8;ce automatiquement, remettre l&apos;&#xE9;chiquier dans sa position de d&#xE9;part en fin de jeu, etc.</p>
</li>
<li>
<p>La variable <code>boardConfig</code> d&#xE9;crit la configuration du composant. Ici, nous sp&#xE9;cifions que :</p>
<ol>
<li>Nous voulons afficher les coordonn&#xE9;es des cases</li>
<li>Le plateau doit &#xEA;tre positionn&#xE9; de fa&#xE7;on &#xE0; ce que les Blancs soient en face de l&apos;utilisateur (couleur par d&#xE9;faut)</li>
<li>Les pi&#xE8;ces doivent &#xEA;tre dans la position de d&#xE9;part du jeu</li>
<li>Les pi&#xE8;ces ne doivent pas &#xEA;tre d&#xE9;pla&#xE7;ables (cet attribut est modifi&#xE9; de mani&#xE8;re r&#xE9;active quand Stockfish a fini de s&apos;initialiser)</li>
</ol>
</li>
</ul>
<p>Nous allons &#xE9;galement impl&#xE9;menter une fonction <code>onBoardCreated</code> pour initialiser la variable <code>boardAPI</code> lorsque le composant a &#xE9;t&#xE9; instanci&#xE9; :</p>
<pre><code class="language-typescript">function onBoardCreated(api: BoardApi) {
  boardAPI = api;
}
</code></pre>
<p>Ensuite, il ne nous manque plus qu&apos;&#xE0; appeler le composant Vue dans notre template :</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;h2&gt;CheckM8&lt;/h2&gt;

  &lt;!-- Plateau --&gt;
  &lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;&gt;
  &lt;/TheChessboard&gt;
&lt;/template&gt;
</code></pre>
<p>La <em>prop</em> <strong>reactive-config</strong> nous permet de dire &#xE0; <strong>vue3-chessboard</strong> que nous voulons que notre configuration <strong>boardConfig</strong> soit reactive. C&apos;est-&#xE0;-dire que nous pourrons modifier les valeurs de cet objet &#xE0; la vol&#xE9;e et elles seront propag&#xE9;es au composant.</p>
<p>Et voil&#xE0; ! Si vous lancez le frontend (<code>npm run dev</code>), vous devriez voir notre &#xE9;chiquier en plein milieu de la page !</p>
<p>Voici le code de notre fichier <em>App.vue</em> pour le moment :</p>
<pre><code class="language-html">&lt;script setup lang=&quot;ts&quot;&gt;
import { reactive } from &apos;vue&apos;;
import { TheChessboard } from &apos;vue3-chessboard&apos;;
import &apos;vue3-chessboard/style.css&apos;;
import type { BoardApi, BoardConfig } from &apos;vue3-chessboard&apos;;

let boardAPI: BoardApi;

const boardConfig: BoardConfig = reactive({
  coordinates: true,
  orientation: &apos;white&apos;,
  position: &apos;start&apos;,
});

function onBoardCreated(api: BoardApi) {
  boardAPI = api;
}
&lt;/script&gt;

&lt;template&gt;
  &lt;h2&gt;CheckM8&lt;/h2&gt;

  &lt;!-- Plateau --&gt;
  &lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;&gt;
  &lt;/TheChessboard&gt;
&lt;/template&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
<p>Avec ces quelques lignes de code, nous avons d&#xE9;j&#xE0; mis en oeuvre les <em>user stories</em> :</p>
<blockquote>
<h6 id="us-1-en-tant-quutilisateur-je-veux-pouvoir-acc%C3%A9der-%C3%A0-une-page-web-contenant-un-%C3%A9chiquier-afin-de-mentra%C3%AEner">US-1 : En tant qu&apos;utilisateur, je veux pouvoir acc&#xE9;der &#xE0; une page web contenant un &#xE9;chiquier afin de m&apos;entra&#xEE;ner.</h6>
</blockquote>
<blockquote>
<h6 id="us-2-en-tant-quutilisateur-je-veux-pouvoir-d%C3%A9placer-mes-pi%C3%A8ces-quand-cest-mon-tour-afin-de-jouer-mon-coup">US-2 : En tant qu&apos;utilisateur, je veux pouvoir d&#xE9;placer mes pi&#xE8;ces quand c&apos;est mon tour afin de jouer mon coup.</h6>
</blockquote>
<p>Nous r&#xE9;pondons &#xE0; leurs crit&#xE8;res d&apos;acceptation (<strong>AC-1-1</strong>, <strong>AC-1-2</strong>, <strong>AC-2-1</strong>, <strong>AC-2-2</strong>, <strong>AC-2-3</strong> et <strong>AC-2-4</strong>). La plupart d&apos;entre elles sont g&#xE9;r&#xE9;es automatiquement par le composant <strong>vue3-chessboard</strong>.</p>
<p>N&#xE9;anmoins, pour l&apos;instant, l&apos;utilisateur peut jouer les deux c&#xF4;t&#xE9;s &#xE0; lui tout seul. Il nous faudra donc bloquer le d&#xE9;placement des pi&#xE8;ces de l&apos;autre couleur lorsque c&apos;est au bot de jouer. Il s&apos;agit du crit&#xE8;re d&apos;acceptation <strong>AC-3-4</strong> que nous prendrons en compte lorsque nous impl&#xE9;menterons la r&#xE9;ponse de Stockfish (<em>user story</em> <strong>US-3 : En tant qu&apos;utilisateur, je veux pouvoir obtenir une r&#xE9;ponse du bot apr&#xE8;s avoir jou&#xE9; un coup</strong>).</p>
<h3 id="33-obtenir-une-r%C3%A9ponse-de-stockfish-apr%C3%A8s-un-coup">3.3. Obtenir une r&#xE9;ponse de Stockfish apr&#xE8;s un coup</h3>
<h4 id="331-installation-de-stockfish">3.3.1. Installation de Stockfish</h4>
<p>La premi&#xE8;re &#xE9;tape pour mettre en place Stockfish dans notre application est de l&apos;installer. Placez-vous dans le r&#xE9;pertoire du frontend si ce n&apos;est pas d&#xE9;j&#xE0; le cas et lancez la commande :</p>
<pre><code class="language-bash">npm install stockfish
</code></pre>
<h4 id="332-int%C3%A9gration-via-un-web-worker">3.3.2. Int&#xE9;gration via un Web Worker</h4>
<p>Pour utiliser un Web Worker, il faut passer l&apos;URL du script &#xE0; appeler en param&#xE8;tre du constructeur. Pour ce faire, nous allons r&#xE9;cup&#xE9;rer deux fichiers depuis le r&#xE9;pertoire de Stockfish <em>node_modules/stockfish/src</em> :</p>
<ul>
<li><em>stockfish-nnue-16.js</em></li>
<li><em>stockfish-nnue-16.wasm</em></li>
</ul>
<p><strong>Attention :</strong> les noms peut varier selon la version de Stockfish utilis&#xE9;e.</p>
<p>Copions ensuite ces fichiers dans un sous-r&#xE9;pertoire <em>stockfish</em> du r&#xE9;pertoire <em>public</em> de notre frontend Vue.</p>
<p>Une fois que c&apos;est fait, dans notre fichier <em>App.vue</em>, nous allons ajouter le code suivant sous la d&#xE9;claration de nos variables <code>boardAPI</code> et <code>boardConfig</code> :</p>
<pre><code class="language-typescript">let worker: Worker;

onMounted(() =&gt; {
  // Cr&#xE9;ation d&apos;un nouvel objet Stockfish
  worker = new Worker(
    new URL(
      &apos;/stockfish/stockfish-nnue-16.js&apos;,
      import.meta.url
    ),
    { type: &apos;module&apos; }
  );
  
  // Abonnement &#xE0; l&apos;&#xE9;v&#xE8;nement &apos;onmessage&apos; du moteur Stockfish
  worker.onmessage = onStockfishMessage;

  // Initialisation du moteur
  worker.postMessage(&apos;uci&apos;);
  worker.postMessage(&apos;isready&apos;);
  worker.postMessage(&apos;setoption name Skill Level value 0&apos;);
  worker.postMessage(&apos;ucinewgame&apos;);
});
</code></pre>
<p><strong>D&#xE9;tail du code :</strong><br>
Nous commencons par d&#xE9;clarer une variable <em>worker</em> de type <strong>Worker</strong> : notre Web Worker.</p>
<p>Ensuite, dans le hook <em>onMounted</em> de Vue, nous d&#xE9;finissons un callback dans lequel :</p>
<ul>
<li>Nous instancions un Worker en passant en param&#xE8;tre l&apos;URL vers le script .js de Stockfish (situ&#xE9; dans notre r&#xE9;pertoire <em>public</em>) &#x2192; <strong>Attention</strong> : le fichier .wasm doit &#xEA;tre pr&#xE9;sent avec le .js dans le r&#xE9;pertoire <em>public</em> sinon une erreur s&apos;affichera dans la console du navigateur</li>
<li>Nous nous abonnons &#xE0; l&apos;&#xE9;v&#xE8;nement <em>onmessage</em> du Worker qui ex&#xE9;cute Stockfish &#x2192; nous impl&#xE9;menterons le contenu de la fonction de gestion de cet &#xE9;v&#xE8;nement <em>onStockfishMessage</em> juste apr&#xE8;s</li>
<li>Nous initialisons Stockfish :
<ol>
<li>Envoi de la commande <em>uci</em> (Universal Chess Interface) pour indiquer &#xE0; Stockfish qu&apos;on veut l&apos;utiliser en mode UCI &#x2192; Stockfish r&#xE9;pondra <em>uciok</em> pour indiquer qu&apos;il a accept&#xE9; le mode UCI</li>
<li>Envoi de la commande <em>isready</em> pour demander &#xE0; Stockfish de confirmer qu&apos;il est pr&#xEA;t &#xE0; recevoir des commandes &#x2192; Stockfish r&#xE9;pondra <em>readyok</em></li>
<li>Envoi de la commande <em>setoption name Skill Level value 0</em> pour ajuster la force de Stockfish au minimum. L&apos;&#xE9;chelle de force du moteur va de 0 (faible) &#xE0; 20 (niveau ma&#xEE;tre)</li>
<li>Enfin, envoi de la commande <em>ucinewgame</em> pour r&#xE9;initialiser l&apos;&#xE9;tat du moteur et indiquer que l&apos;on souhaite commencer une nouvelle partie</li>
</ol>
</li>
</ul>
<p><strong>Important ! N&apos;oubliez pas d&apos;ajouter l&apos;import de <em>onMounted</em> :</strong></p>
<pre><code class="language-typescript">import { reactive, onMounted } from &apos;vue&apos;;
</code></pre>
<p>Si vous avez suivi les consignes pr&#xE9;c&#xE9;dentes, vous devriez avoir une erreur dans votre code : <code>Cannot find name &apos;onStockfishMessage&apos;</code>. Nous n&apos;avons pas encore d&#xE9;clar&#xE9; cette fonction : il nous faut la cr&#xE9;er. C&apos;est elle qui sera appel&#xE9; lorsque Stockfish enverra un message via le Web Worker et qui contiendra les traitements pour g&#xE9;rer la r&#xE9;ponse.</p>
<p>Sous la fonction <code>onBoardCreated</code> d&#xE9;finie plus haut dans cet article, ajoutez cette fonction et laissons la vide pour le moment :</p>
<pre><code class="language-typescript">function onStockfishMessage(event: any) {
}
</code></pre>
<p>Ceci conclut l&apos;int&#xE9;gration de Stockfish !</p>
<h4 id="333-gestion-des-coups-utilisateur-et-r%C3%A9ponse-de-stockfish">3.3.3. Gestion des coups utilisateur et r&#xE9;ponse de Stockfish</h4>
<p>Maintenant que Stockfish a &#xE9;t&#xE9; int&#xE9;gr&#xE9; et initialis&#xE9; &#xE0; l&apos;arriv&#xE9;e sur la page, nous allons pouvoir g&#xE9;rer le fait de r&#xE9;pondre au coup jou&#xE9; par l&apos;utilisateur.</p>
<p>Pour cela, nous impl&#xE9;mentons la fonction <em>onPlayerMove</em> :</p>
<pre><code class="language-typescript">function onPlayerMove() {
  const turnColor = boardAPI.getTurnColor();

  // Appel &#xE0; Stockfish pour calculer le prochain coup
  // si c&apos;est son tour de jouer
  if (turnColor !== boardConfig.orientation) {
    boardConfig.viewOnly = true;
    askStockfishMove();
  } else {
    boardConfig.viewOnly = false;
  }
}
</code></pre>
<p>Cette fonction sera appel&#xE9;e apr&#xE8;s un coup (par l&apos;utilisateur ou par Stockfish).</p>
<p><strong>D&#xE9;tail de la fonction <em>onPlayerMove</em> :</strong></p>
<ul>
<li>Tout d&apos;abord, nous r&#xE9;cup&#xE9;rons la couleur dont c&apos;est le tour (<em>white</em> ou <em>black</em>)</li>
<li>Nous v&#xE9;rifions ensuite si c&apos;est au tour de Stockfish de jouer &#x2192; <em>boardConfig.orientation</em> indique les pi&#xE8;ces qui sont en bas de l&apos;&#xE9;chiquier = la couleur de l&apos;utilisateur</li>
<li>Si c&apos;est le cas, nous effectuons deux actions :
<ol>
<li>En premier lieu, nous d&#xE9;sactivons les int&#xE9;ractions avec le composant pour emp&#xEA;cher que l&apos;utilisateur d&#xE9;place les pi&#xE8;ces adverses</li>
<li>Nous appelons la fonction <em>askStockfishMove</em> qui va demander &#xE0; Stockfish de calculer le prochain coup et que nous allons impl&#xE9;menter tout de suite</li>
</ol>
</li>
<li>Si ce n&apos;est pas au tour de Stockfish de jouer, cela signifie que le moteur vient de faire son coup et que c&apos;est au tour de l&apos;utilisateur, nous r&#xE9;activons donc les int&#xE9;ractions avec le composant <strong>vue3-chessboard</strong></li>
</ul>
<p>Impl&#xE9;mentation de la fonction <em>askStockfishMove</em> :</p>
<pre><code class="language-typescript">function askStockfishMove() {
  // Passage de l&apos;&#xE9;tat de la partie &#xE0; Stockfish pour un nouveau calcul
  worker.postMessage(`position fen ${boardAPI.getFen()}`);
  
  // Lancement du calcul pour 1 seconde
  worker.postMessage(&apos;go movetime 1000&apos;);
}
</code></pre>
<p><strong>D&#xE9;tails de la fonction <em>askStockfishMove</em></strong> :</p>
<ul>
<li>Dans cette fonction, nous envoyons un premier message au Web Worker pour indiquer la position actuelle de l&apos;&#xE9;chiquier</li>
<li>Puis nous envoyons un second message pour demander &#xE0; Stockfish de lancer le calcul pendant 1000 millisecondes = 1 seconde (<strong>Vous pouvez utiliser une autre valeur pour laisser plus ou moins de temps &#xE0; Stockfish pour r&#xE9;fl&#xE9;chir</strong>)</li>
</ul>
<p>L&apos;&#xE9;tape suivante est de g&#xE9;rer la r&#xE9;ception des messages de Stockfish dans la fonction <em>onStockfishMessage</em>. Nous allons tout d&apos;abord ajouter les imports suivants en haut de notre partie Typescript :</p>
<pre><code class="language-typescript">import type {
  BoardApi,
  BoardConfig,
  Move,
  PieceColor,
  Promotion
} from &apos;vue3-chessboard&apos;;
import type { Key } from &apos;chessground/types&apos;;
</code></pre>
<p>Les ajouts sont les types <strong>Move</strong> et <strong>Promotion</strong> depuis <strong>vue3-chessboard</strong> et <strong>Key</strong> depuis <strong>chessground/types</strong> qui est une d&#xE9;pendance de <strong>vue3-chessboard</strong>.</p>
<p>Apr&#xE8;s quoi nous pouvons &#xE9;crire notre fonction :</p>
<pre><code class="language-typescript">function onStockfishMessage(event: any) {
  if (!boardAPI) {
    throw new Error(&apos;vue3-chessboard is not initialized.&apos;);
  }

  const msg: string = event.data;
  // On log les messages de Stockfish dans la console 
  // (pour l&apos;instant utile pour du d&#xE9;bogage)
  console.log(msg);
  
  // Si Stockfish est pr&#xEA;t, on active les int&#xE9;ractions
  // avec le composant &apos;vue3-chessboard&apos;
  if (msg === &apos;readyok&apos;) {
    boardConfig.viewOnly = false;
  }

  if (msg.includes(&apos;bestmove&apos;)) {
    const tokens = msg.split(&apos; &apos;);

    // V&#xE9;rification pr&#xE9;sence valeur de bestmove
    if (tokens.length &lt; 2) {
      console.error(&apos;Best move not found.&apos;);
      return;
    }

    // R&#xE9;cup&#xE9;ration de la valeur du meilleur coup &#xE0; jouer
    const bestMove = tokens[1];

    // V&#xE9;rification du format du best move
    if (!bestMove || bestMove.length &lt; 4) {
      console.error(&apos;Incorrect best move format.&apos;);
      return;
    }

    // Application du coup calcul&#xE9;
    const moveObj: Move = { 
      from: bestMove.substring(0, 2) as Key,
      to: bestMove.substring(2, 4) as Key 
    };

    // Si la valeur de bestMove fait 5 caract&#xE8;res,
    // c&apos;est que la pi&#xE8;ce est promue
    if (bestMove.length === 5) {
      moveObj.promotion = bestMove[4] as Promotion;
    }

    // D&#xE9;placement de la pi&#xE8;ce sur l&apos;&#xE9;chiquier
    boardAPI.move(moveObj);
  }
}
</code></pre>
<p><strong>D&#xE9;tail de la fonction <em>onStockfishMessage</em> :</strong></p>
<ul>
<li>La premi&#xE8;re chose que nous faisons dans cette fonction est de v&#xE9;rifier que l&apos;API du composant <strong>vue3-chessboard</strong> est disponible (c&apos;est-&#xE0;-dire que l&apos;&#xE9;v&#xE8;nement <em>board-created</em> a bien &#xE9;t&#xE9; appel&#xE9; et donc que notre composant est bien instanci&#xE9;) &#x2192; nous avons en effet besoin de cette API pour appliquer le coup retourn&#xE9; par Stockfish sur l&apos;&#xE9;chiquier</li>
<li>Nous r&#xE9;cup&#xE9;rons le message envoy&#xE9; par Stockfish avec <code>const msg: string = event.data;</code></li>
<li>Nous loggons ce message (ne doit pas &#xEA;tre conserv&#xE9; en prod mais tr&#xE8;s utile pour faire du d&#xE9;bogage pendant les d&#xE9;veloppements</li>
<li>Nous effectuons un premier test pour v&#xE9;rifier si le message envoy&#xE9; par Stockfish contient la string <em>readyok</em> &#x2192; si c&apos;est le cas, alors les int&#xE9;ractions avec le composant <strong>vue3-chessboard</strong> sont activ&#xE9;es (permet d&apos;emp&#xEA;cher l&apos;utilisateur de d&#xE9;placer des pi&#xE8;ces tant que le moteur n&apos;est pas compl&#xE8;tement initialis&#xE9;)</li>
<li>Puis nous testons si le message envoy&#xE9; par Stockfish contient la string <em>bestmove</em>.<br>
En effet, la r&#xE9;ponse finale de Stockfish a notre commande <em>go movetime &lt;DURATION&gt;</em> avec le meilleur coup &#xE0; jouer est de la forme : <em>bestmove &lt;move&gt; ponder &lt;predicted_user_move&gt;</em> (exemple : <em>bestmove e7e5 ponder g1f3</em>). Il s&apos;agit du dernier message envoy&#xE9; par Stockfish</li>
<li>Si le message contient <em>bestmove</em> alors Stockfish a r&#xE9;pondu avec son coup : nous d&#xE9;coupons la cha&#xEE;ne renvoy&#xE9;e sur le caract&#xE8;re &lt;espace&gt;</li>
<li>La valeur du coup &#xE0; jouer est contenu dans le second token, nous testons donc que nous avons bien au moins deux tokens dans notre r&#xE9;ponse &#x2192; il est tr&#xE8;s improbable que le cas o&#xF9; le moteur r&#xE9;ponde un message <em>bestmove</em> sans valeur existe mais dans l&apos;optique d&apos;avoir un code robuste, nous effectuons tout de m&#xEA;me la v&#xE9;rification</li>
<li>Nous r&#xE9;cup&#xE9;rons ensuite la valeur du coup &#xE0; jouer (qui est donc dans le second token)</li>
<li>Nous effectuons une autre v&#xE9;rification pour nous assurer que le format est bon : il doit y avoir au moins 4 caract&#xE8;res (exemple : <em>e2e4</em>)</li>
<li>Une fois la r&#xE9;ponse de Stockfish valid&#xE9;e, nous appliquons le coup : nous extrayons la position de d&#xE9;part (<em>from</em>) et d&apos;arriv&#xE9;e (<em>to</em>) de la valeur r&#xE9;cup&#xE9;r&#xE9;e et nous cr&#xE9;ons une instance d&apos;un objet <em>Move</em></li>
<li>S&apos;il y a un cinqui&#xE8;me caract&#xE8;re dans la valeur du coup, c&apos;est qu&apos;il s&apos;agit d&apos;une promotion. Exemple : <em>a7a8q</em> qui indique que le pion en <em>a7</em> atteint le c&#xF4;t&#xE9; adverse <em>a8</em> et qu&apos;il est promu en une reine (<em>q</em> pour Queen) &#x2192; nous ajoutons donc un attribut <em>promotion</em> a notre objet <em>Move</em> avec la nouvelle valeur de la pi&#xE8;ce</li>
<li>Enfin, nous appliquons le coup gr&#xE2;ce &#xE0; l&apos;API <strong>vue3-chessboard</strong> en passant l&apos;objet de type <em>Move</em> cr&#xE9;&#xE9; pr&#xE9;c&#xE9;demment</li>
</ul>
<p>Il ne nous reste plus qu&apos;&#xE0; modifier notre template pour d&#xE9;finir notre fonction <em>onPlayerMove</em> comme handler de l&apos;&#xE9;v&#xE8;nement <em>move</em> de <strong>vue3-chessboard</strong> :</p>
<pre><code class="language-html">&lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;
    @move=&quot;onPlayerMove&quot;&gt;
&lt;/TheChessboard
</code></pre>
<p>Ca y est ! Nous pouvons jouer un coup et Stockfish r&#xE9;pondra !</p>
<p>Voici le code complet dans l&apos;&#xE9;tat actuel :</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;h2&gt;CheckM8&lt;/h2&gt;
  &lt;p&gt;Tour courant : {{ currentTurn }}&lt;/p&gt;
  &lt;p v-if=&quot;gameOverMessage&quot;&gt;{{ gameOverMessage }}&lt;/p&gt;

  &lt;!-- Plateau --&gt;
  &lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;
    @move=&quot;onPlayerMove&quot;
    @checkmate=&quot;onCheckmate&quot;
    @stalemate=&quot;onStalemate&quot;
    @draw=&quot;onDraw&quot;&gt;
  &lt;/TheChessboard&gt;
&lt;/template&gt;

&lt;script setup lang=&quot;ts&quot;&gt;
import { reactive, onMounted } from &apos;vue&apos;;
import { TheChessboard } from &apos;vue3-chessboard&apos;;
import &apos;vue3-chessboard/style.css&apos;;
import type {
  BoardApi,
  BoardConfig,
  Move,
  PieceColor,
  Promotion
} from &apos;vue3-chessboard&apos;;
import type { Key } from &apos;chessground/types&apos;;

let boardAPI: BoardApi;

const boardConfig: BoardConfig = reactive({
  coordinates: true,
  orientation: &apos;white&apos;,
  position: &apos;start&apos;,
  viewOnly: true,
});

let worker: Worker;

onMounted(() =&gt; {
  // Cr&#xE9;ation d&apos;un nouvel objet Stockfish
  worker = new Worker(
    new URL(
      &apos;/stockfish/stockfish-nnue-16.js&apos;,
      import.meta.url
    ),
    { type: &apos;module&apos; }
  );
  
  // Abonnement &#xE0; l&apos;&#xE9;v&#xE8;nement &apos;onmessage&apos; du moteur Stockfish
  worker.onmessage = onStockfishMessage;

  // Initialisation du moteur
  worker.postMessage(&apos;uci&apos;);
  worker.postMessage(&apos;isready&apos;);
  worker.postMessage(&apos;setoption name Skill Level value 0&apos;);
  worker.postMessage(&apos;ucinewgame&apos;);
});

function onBoardCreated(api: BoardApi) {
  boardAPI = api;
}

function onPlayerMove() {
  // Mise &#xE0; jour de qui joue
  const turnColor = boardAPI.getTurnColor();

  // Appel &#xE0; Stockfish pour calculer le prochain coup 
  // si c&apos;est son tour de jouer
  if (turnColor !== boardConfig.orientation) {
    boardConfig.viewOnly = true;
    askStockfishMove();
  } else {
    boardConfig.viewOnly = false;
  }
}

function askStockfishMove() {
  // Passage de l&apos;&#xE9;tat de la partie &#xE0; Stockfish pour un nouveau calcul
  worker.postMessage(`position fen ${boardAPI.getFen()}`);
  
  // Lancement du calcul pour 1 seconde
  worker.postMessage(&apos;go movetime 1000&apos;);
}

function onStockfishMessage(event: any) {
  if (!boardAPI) {
    throw new Error(&apos;vue3-chessboard is not initialized.&apos;);
  }

  const msg: string = event.data;
  console.log(msg);

  // Si Stockfish est pr&#xEA;t, on active les int&#xE9;ractions
  // avec le composant &apos;vue3-chessboard&apos;
  if (msg === &apos;readyok&apos;) {
    boardConfig.viewOnly = false;
  }

  if (msg.includes(&apos;bestmove&apos;)) {
    const tokens = msg.split(&apos; &apos;);

    // V&#xE9;rification pr&#xE9;sence valeur de bestmove
    if (tokens.length &lt; 2) {
      console.error(&apos;Best move not found.&apos;);
      return;
    }

    // R&#xE9;cup&#xE9;ration de la valeur du meilleur coup &#xE0; jouer
    const bestMove = tokens[1];

    // V&#xE9;rification du format du best move
    if (!bestMove || bestMove.length &lt; 4) {
      console.error(&apos;Incorrect best move format.&apos;);
      return;
    }

    // Application du coup calcul&#xE9;
    const moveObj: Move = { 
      from: bestMove.substring(0, 2) as Key,
      to: bestMove.substring(2, 4) as Key 
    };

    // Si la valeur de bestMove fait 5 caract&#xE8;res,
    // c&apos;est que la pi&#xE8;ce est promue
    if (bestMove.length === 5) {
      moveObj.promotion = bestMove[4] as Promotion;
    }

    // D&#xE9;placement de la pi&#xE8;ce sur l&apos;&#xE9;chiquier
    boardAPI.move(moveObj);
  }
}
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
<p>La <em>user story</em> associ&#xE9;e &#xE0; ces modifications est l&apos;<strong>US-3</strong> :</p>
<blockquote>
<h6 id="us-3-en-tant-quutilisateur-je-veux-pouvoir-obtenir-une-r%C3%A9ponse-du-bot-apr%C3%A8s-avoir-jou%C3%A9-un-coup">US-3 : En tant qu&apos;utilisateur, je veux pouvoir obtenir une r&#xE9;ponse du bot apr&#xE8;s avoir jou&#xE9; un coup.</h6>
</blockquote>
<p>Apr&#xE8;s nos derniers ajouts :</p>
<ul>
<li>Un coup jou&#xE9; par l&apos;utilisateur d&#xE9;clenche bien un appel &#xE0; Stockfish (<strong>AC-3-1</strong>)</li>
<li>Le moteur a un temps limit&#xE9; pour r&#xE9;pondre (dans notre cas d&#xE9;fini &#xE0; 1 seconde) (<strong>AC-3-2</strong>)</li>
<li>Le coup effectu&#xE9; par Stockfish est mis en &#xE9;vidence (g&#xE9;r&#xE9; automatiquement par <strong>vue3-chessboard</strong>) (<strong>AC-3-3</strong>)</li>
<li>L&apos;utilisateur ne peut d&#xE9;placer aucune pi&#xE8;ce pendant que c&apos;est au tour du bot de jouer (<strong>AC-3-4</strong>)</li>
</ul>
<p>Nous validons donc bien les crit&#xE8;res d&apos;acceptation associ&#xE9;s : nous avons fini l&apos;impl&#xE9;mentation de cette <em>user story</em>.</p>
<h3 id="34-afficher-le-tour-courant">3.4. Afficher le tour courant</h3>
<p>L&apos;objectif maintenant est de rajouter un texte au-dessus de l&apos;&#xE9;chiquier pour indiquer &#xE0; qui est le tour courant (<em>Blancs</em> ou <em>Noirs</em>). Cette information est utile pour le joueur pour savoir si c&apos;est &#xE0; lui de jouer ou si le bot est en train de r&#xE9;fl&#xE9;chir.</p>
<p>Nous allons cr&#xE9;er un attribut <code>ref</code> qui va contenir le texte &#xE0; afficher (<em>Blancs</em> ou <em>Noirs</em>) en fonction du tour. Dans notre partie script, ajoutons <code>ref</code> &#xE0; la liste des imports de <strong>vue</strong> (<code>import { ref, reactive, onMounted } from &apos;vue&apos;;</code>) et d&#xE9;finissions la variable suivante :</p>
<pre><code class="language-typescript">const currentTurnLabel = ref&lt;string&gt;(&apos;&apos;);
</code></pre>
<p>Nous rajoutons &#xE9;galement une fonction utilitaire pour mettre &#xE0; jour le libell&#xE9; du tour courant :</p>
<pre><code class="language-typescript">function updateTurnColor(turnColor: PieceColor) {
  currentTurnLabel.value = turnColor === &apos;white&apos; ? &apos;Blancs&apos; : &apos;Noirs&apos;;
}
</code></pre>
<p>Ajoutons maintenant les mises &#xE0; jour aux endroits ad&#xE9;quats :</p>
<ul>
<li>Quand le composant <strong>vue3-chessboard</strong> est cr&#xE9;&#xE9;, nous initialisons la valeur. La fonction <em>onBoardCreated</em> devient :</li>
</ul>
<pre><code class="language-typescript">function onBoardCreated(api: BoardApi) {
  boardAPI = api;
  updateTurnColor(boardAPI.getTurnColor());
}
</code></pre>
<p>A chaque fois qu&apos;un joueur (utilisateur ou Stockfish) joue un coup, il faut aussi mettre &#xE0; jour le tour courant. La fonction <em>onPlayerMove</em> devient :</p>
<pre><code class="language-typescript">function onPlayerMove() {
  // Mise &#xE0; jour de qui joue
  const turnColor = boardAPI.getTurnColor();
  updateTurnColor(turnColor);

  // Appel &#xE0; Stockfish pour calculer le prochain coup 
  // si c&apos;est son tour de jouer
  if (turnColor !== boardConfig.orientation) {
    boardConfig.viewOnly = true;
    askStockfishMove();
  } else {
    boardConfig.viewOnly = false;
  }
}
</code></pre>
<p>Enfin, il faut ajouter le message dans le template :</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;h2&gt;CheckM8&lt;/h2&gt;
  &lt;p&gt;Tour courant : {{ currentTurnLabel }}&lt;/p&gt;

  &lt;!-- Plateau --&gt;
  &lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;
    @move=&quot;onPlayerMove&quot;&gt;
  &lt;/TheChessboard&gt;
&lt;/template&gt;
</code></pre>
<p><strong>Important :</strong> le tour courant aurait pu &#xEA;tre g&#xE9;r&#xE9; par un attribut <strong>computed</strong>. C&apos;est mieux car l&apos;attribut se met alors &#xE0; jour d&#xE8;s que les attributs r&#xE9;actifs qu&apos;il utilise sont modifi&#xE9;s et il n&apos;y a plus besoin de mise &#xE0; jour manuelle comme nous le faisons ici.<br>
Alors pourquoi ai-je choisi de mettre &#xE0; jour manuellement le tour courant et d&apos;utiliser un <strong>ref</strong> plut&#xF4;t qu&apos;un <strong>computed</strong> ?<br>
Et bien tout simplement parce que le tour courant d&#xE9;pend de <strong>boardAPI</strong> qui n&apos;est pas un attribut r&#xE9;actif. Il faudrait le rendre r&#xE9;actif pour que cela marche en le d&#xE9;clarant comme ceci :</p>
<pre><code class="language-typescript">const boardAPI: Ref&lt;BoardApi | null&gt; = ref(null);
</code></pre>
<p>En effet, une variable r&#xE9;active doit avoir une valeur d&apos;assign&#xE9;e d&#xE8;s sa d&#xE9;claration. Dans la suite, il faut l&apos;utiliser comme ceci : <code>boardAPI.value</code> en v&#xE9;rifiant &#xE0; chaque fois que la variable n&apos;est pas nulle (exemple : <code>boardAPI.value?.move(moveObj)</code>.</p>
<p>Au final, les modifications sont plus lourdes qu&apos;une simple mise &#xE0; jour manuelle (un seul attribut qui nous int&#xE9;resse pour l&apos;instant, plus d&apos;endroits impact&#xE9;s et code moins lisible).<br>
Il m&apos;a donc paru plus simple de cr&#xE9;er un attribut <strong>ref</strong> pour le moment. Quand le code grossira et deviendra plus complexe, il faudra potentiellement revoir l&apos;utilisation de <strong>computed</strong>.</p>
<p>Nous avons maintenant le tour courant qui s&apos;affiche au-dessus de l&apos;&#xE9;chiquier ce qui compl&#xE8;te l&apos;<strong>US-4</strong> :</p>
<blockquote>
<h6 id="us-4-en-tant-quutilisateur-je-veux-pouvoir-savoir-%C3%A0-tout-moment-%C3%A0-qui-est-le-tour-de-jouer">US-4 : En tant qu&apos;utilisateur, je veux pouvoir savoir &#xE0; tout moment &#xE0; qui est le tour de jouer.</h6>
</blockquote>
<h3 id="35-g%C3%A9rer-la-fin-de-partie">3.5. G&#xE9;rer la fin de partie</h3>
<p>A ce stade nous pouvons jouer une partie contre Stockfish du d&#xE9;but &#xE0; la fin et <strong>vue3-chessboard</strong> - gr&#xE2;ce &#xE0; <strong>chess.js</strong> - g&#xE8;re tout seul l&apos;&#xE9;tat du jeu (incluant la fin de partie). N&#xE9;anmoins il nous manque tout de m&#xEA;me un retour visuel pour indiquer &#xE0; l&apos;utilisateur qui a gagn&#xE9; la partie.</p>
<p>Cr&#xE9;ons un attribut <code>ref</code> pour stocker le message de fin de partie :</p>
<pre><code class="language-typescript">const gameOverMessage = ref&lt;string&gt;(&apos;&apos;);
</code></pre>
<p>Ensuite, d&#xE9;clarons trois nouvelles fonctions (une pour chacun des cas suivants : <em>Echec et mat</em>, <em>Pat</em> et <em>Nul</em>) :</p>
<pre><code class="language-typescript">function onCheckmate(isMated: PieceColor) {
  // Cas &quot;Echec et mat&quot;
  // Si le joueur mat&#xE9; est l&apos;utilisateur
  if (isMated === boardConfig.orientation) {
    gameOverMessage.value = &apos;Zut ! Vous avez perdu pour cette fois !&apos;;
  } else {
    gameOverMessage.value = &apos;Bravo ! Vous avez gagn&#xE9; la partie !&apos;;
  }
}

function onStalemate() {
  // Cas &quot;Pat&quot;
  gameOverMessage.value = `Match nul ! C&apos;est un pat !`;
}

function onDraw() {
  // Cas &quot;Nul&quot;
  gameOverMessage.value = `Match nul !`;
}
</code></pre>
<p><strong>D&#xE9;tail de la fonction <em>onCheckmate</em> :</strong><br>
Nous regardons qui est le joueur mat&#xE9; :</p>
<ul>
<li>S&apos;il s&apos;agit de l&apos;utilisateur, c&apos;est qu&apos;il a perdu ! Nous affectons le message <em>Zut ! Vous avez perdu pour cette fois !</em> &#xE0; l&apos;attribut <code>gameOverMessage</code></li>
<li>S&apos;il s&apos;agit de Stockfish, c&apos;est que l&apos;utilisateur a gagn&#xE9; ! Nous affectons donc cette fois le message : <em>Bravo ! Vous avez gagn&#xE9; la partie !</em> &#xE0; l&apos;attribut <code>gameOverMessage</code></li>
</ul>
<p><strong>D&#xE9;tail de la fonction <em>onStalemate</em> :</strong><br>
S&apos;il y a pat, nous affectons le message : <em>Match nul ! C&apos;est un pat !</em> &#xE0; l&apos;attribut <code>gameOverMessage</code>.</p>
<p><strong>D&#xE9;tail de la fonction <em>onDraw</em> :</strong><br>
S&apos;il y a nul (hors pat), nous affectons tout simplement le message : <em>Match nul !</em> &#xE0; l&apos;attribut <code>gameOverMessage</code>.</p>
<p>Il ne reste plus qu&apos;&#xE0; modifier le template pour attacher ces handlers aux &#xE9;v&#xE8;nements &#xE9;mis par le composant <strong>vue3-chessboard</strong> :</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;h2&gt;CheckM8&lt;/h2&gt;
  &lt;p&gt;Tour courant : {{ currentTurnLabel }}&lt;/p&gt;
  &lt;p v-if=&quot;gameOverMessage&quot;&gt;{{ gameOverMessage }}&lt;/p&gt;

  &lt;!-- Plateau --&gt;
  &lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;
    @move=&quot;onPlayerMove&quot;
    @checkmate=&quot;onCheckmate&quot;
    @stalemate=&quot;onStalemate&quot;
    @draw=&quot;onDraw&quot;&gt;
  &lt;/TheChessboard&gt;
&lt;/template&gt;
</code></pre>
<p>Avec ces nouvelles modifications :</p>
<ul>
<li>Nous avons bien un message qui s&apos;affiche lorsque la partie est termin&#xE9;e (de mani&#xE8;re conditionnelle gr&#xE2;ce &#xE0; un <code>v-if</code>) (<strong>AC-5-1</strong>)</li>
<li>Nous avons bien un message pour chaque cas de fin partie possible (<strong>AC-5-2</strong> &#xE0; <strong>AC-5-5</strong>)</li>
<li>L&apos;utilisateur ne peut d&#xE9;placer aucune pi&#xE8;ce lorsque la partie est finie (g&#xE9;r&#xE9;e automatiquement par <strong>vue3-chessboard</strong>) (<strong>AC-5-6</strong>)</li>
</ul>
<p>Cela valide donc l&apos;<strong>US-5</strong> :</p>
<blockquote>
<h6 id="us-5-en-tant-quutilisateur-je-veux-pouvoir-%C3%AAtre-inform%C3%A9-lorsque-la-partie-est-termin%C3%A9e-afin-de-savoir-si-jai-gagn%C3%A9-ou-perdu">US-5 : En tant qu&apos;utilisateur, je veux pouvoir &#xEA;tre inform&#xE9; lorsque la partie est termin&#xE9;e afin de savoir si j&apos;ai gagn&#xE9; ou perdu.</h6>
</blockquote>
<h3 id="36-commencerrecommencer-une-nouvelle-partie">3.6. Commencer/recommencer une nouvelle partie</h3>
<p>Derni&#xE8;re fonctionnalit&#xE9; de cette premi&#xE8;re version : nous allons maintenant nous focaliser sur l&apos;<strong>US-6</strong> qui consiste &#xE0; pouvoir commencer/recommencer une nouvelle partie en choisissant l&apos;un ou l&apos;autre des camps.</p>
<h4 id="361-cr%C3%A9ation-dune-fonction-pour-r%C3%A9initialiser-la-partie">3.6.1. Cr&#xE9;ation d&apos;une fonction pour r&#xE9;initialiser la partie</h4>
<p>Tout d&apos;abord, nous allons cr&#xE9;er deux nouvelles fonctions <code>restartGame</code> et <code>resetGame</code> qui vont contenir la logique pour r&#xE9;initialiser le jeu :</p>
<pre><code class="language-typescript">function restartGame(orientation: PieceColor) {
  // Application de l&apos;orientation et reset de la partie
  boardConfig.orientation = orientation;
  resetGame();
  if (orientation === &apos;black&apos;) {
    // Comme les Blancs commencent, 
    // on demande tout de suite &#xE0; Stockfish de jouer
    askStockfishMove();
  }
}

function resetGame() {
  // R&#xE9;initialisation de l&apos;&#xE9;chiquier
  boardAPI.resetBoard();

  // R&#xE9;initialisation des messages
  gameOverMessage.value = &apos;&apos;;
  updateTurnColor(boardAPI.getTurnColor());

  // D&#xE9;sactivation des d&#xE9;placements 
  // en attendant que Stockfish soit pr&#xEA;t
  boardConfig.viewOnly = true;

  // R&#xE9;initialisation du moteur Stockfish
  worker.postMessage(&apos;ucinewgame&apos;);
  worker.postMessage(&apos;isready&apos;);
}
</code></pre>
<p><strong>D&#xE9;tail de la fonction <em>restartGame</em> :</strong><br>
Nous commencons, sur la premi&#xE8;re ligne, par d&#xE9;finir l&apos;orientation de l&apos;&#xE9;chiquier en fonction du choix du joueur :</p>
<ul>
<li><em>white</em> : l&#x2019;&#xE9;chiquier est affich&#xE9; normalement (Blancs en bas)</li>
<li><em>black</em> : l&#x2019;&#xE9;chiquier est retourn&#xE9; (Noirs en bas)</li>
</ul>
<p>Ensuite nous faisons appel &#xE0; une autre fonction <em>resetGame</em> dont le r&#xF4;le est de r&#xE9;initialiser l&apos;&#xE9;tat de la partie : remise de l&apos;&#xE9;chiquier dans l&apos;&#xE9;tat de d&#xE9;part et r&#xE9;initialisation des variables d&apos;&#xE9;tat.</p>
<p>Enfin, nous avons une condition en fonction de l&apos;orientation du plateau : si l&apos;utilisateur joue les Noirs, il faut demander &#xE0; Stockfish de jouer le premier coup car, dans une partie classique, ce sont toujours les Blancs qui commencent &#x2192; nous appelons donc la fonction <code>askStockfishMove</code> que nous avons d&#xE9;finie plus haut dans cet article.</p>
<p><strong>D&#xE9;tail de la fonction <em>resetGame</em> :</strong><br>
La premi&#xE8;re instruction demande au composant <strong>vue3-chessboard</strong> de r&#xE9;initialiser l&apos;&#xE9;chiquier et l&apos;&#xE9;tat du jeu.</p>
<p>Ensuite nous effa&#xE7;ons le message de fin de partie et nous mettons &#xE0; jour le tour courant (celui-ci vaudra <em>Blancs</em> puisque les Blancs commencent dans une partie d&apos;&#xE9;checs classique).</p>
<p>Nous d&#xE9;sactivons les int&#xE9;ractions avec le composant <strong>vue3-chessboard</strong> de fa&#xE7;on &#xE0; emp&#xEA;cher l&apos;utilisateur de d&#xE9;placer les pi&#xE8;ces pendant la r&#xE9;initialisation de Stockfish.</p>
<p>Enfin, en dernier, nous indiquons &#xE0; Stockfish que nous d&#xE9;marrons une nouvelle partie (ce qui entra&#xEE;ne une r&#xE9;initialisation interne) et nous lui demandons de nous indiquer quand il est pr&#xEA;t.</p>
<h4 id="362-ajout-des-boutons-sur-linterface">3.6.2. Ajout des boutons sur l&apos;interface</h4>
<p>Il ne nous manque plus qu&apos;&#xE0; rajouter deux boutons sous notre &#xE9;chiquier dans notre <code>template</code> pour faire appel &#xE0; notre fonction <em>restartGame</em> :</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;h2&gt;CheckM8&lt;/h2&gt;

  &lt;!-- Plateau --&gt;
  &lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;&gt;
  &lt;/TheChessboard&gt;
    
  &lt;!-- Boutons pour r&#xE9;initialiser la partie --&gt;
  &lt;button @click=&quot;restartGame(&apos;white&apos;)&quot;&gt;Nouvelle partie (Blancs)&lt;/button&gt;
  &lt;button @click=&quot;restartGame(&apos;black&apos;)&quot;&gt;Nouvelle partie (Noirs)&lt;/button&gt;
&lt;/template&gt;
</code></pre>
<p>En relancant le serveur de d&#xE9;veloppement avec la commande <code>npm run dev</code>, quand l&apos;utilisateur arrive sur la page il a les Blancs par d&#xE9;faut et peut jouer directement (<strong>AC-6-2</strong>).<br>
Nous avons &#xE9;galement les deux boutons <strong>Nouvelle partie (Blancs)</strong> et <strong>Nouvelle partie (Noirs)</strong> pour recommencer une nouvelle partie (<strong>AC-6-1</strong>).<br>
Le clic sur l&apos;un de ces boutons remet les pi&#xE8;ces dans leur position initiale (<strong>AC-6-3</strong>) et si l&apos;utilisateur choisit de jouer les Noirs l&apos;&#xE9;chiquier est en plus retourn&#xE9; (<strong>AC-6-4</strong>).</p>
<p>Nous avons donc compl&#xE8;tement impl&#xE9;ment&#xE9; la <em>user story</em> :</p>
<blockquote>
<h6 id="us-6-en-tant-quutilisateur-je-veux-pouvoir-commencer-une-nouvelle-partie-et-choisir-mon-c%C3%B4t%C3%A9-blancs-ou-noirs-afin-de-jouer-contre-une-ia-et-pratiquer-lun-c%C3%B4t%C3%A9-comme-lautre">US-6 : En tant qu&apos;utilisateur, je veux pouvoir commencer une nouvelle partie et choisir mon c&#xF4;t&#xE9; (Blancs ou Noirs) afin de jouer contre une IA et pratiquer l&apos;un c&#xF4;t&#xE9; comme l&apos;autre.</h6>
</blockquote>
<h3 id="code-final">Code Final</h3>
<p>Voici le code final du fichier <em>App.vue</em> de notre projet <strong>CheckM8</strong>, l&apos;application pour s&apos;entra&#xEE;ner aux &#xE9;checs :</p>
<pre><code class="language-html">&lt;template&gt;
  &lt;h2&gt;CheckM8&lt;/h2&gt;
  &lt;p&gt;Tour courant : {{ currentTurnLabel }}&lt;/p&gt;
  &lt;p v-if=&quot;gameOverMessage&quot;&gt;{{ gameOverMessage }}&lt;/p&gt;

  &lt;!-- Plateau --&gt;
  &lt;TheChessboard
    :board-config=&quot;boardConfig&quot;
    reactive-config
    @board-created=&quot;onBoardCreated&quot;
    @move=&quot;onPlayerMove&quot;
    @checkmate=&quot;onCheckmate&quot;
    @stalemate=&quot;onStalemate&quot;
    @draw=&quot;onDraw&quot;&gt;
  &lt;/TheChessboard&gt;

  &lt;!-- Bouton pour r&#xE9;initialiser la partie --&gt;
  &lt;button @click=&quot;restartGame(&apos;white&apos;)&quot;&gt;Nouvelle partie (Blancs)&lt;/button&gt;
  &lt;button @click=&quot;restartGame(&apos;black&apos;)&quot;&gt;Nouvelle partie (Noirs)&lt;/button&gt;
&lt;/template&gt;

&lt;script setup lang=&quot;ts&quot;&gt;
import { ref, reactive, onMounted } from &apos;vue&apos;;
import { TheChessboard } from &apos;vue3-chessboard&apos;;
import &apos;vue3-chessboard/style.css&apos;;
import type {
  BoardApi,
  BoardConfig,
  Move,
  PieceColor,
  Promotion
} from &apos;vue3-chessboard&apos;;
import type { Key } from &apos;chessground/types&apos;;

let boardAPI: BoardApi;

const boardConfig: BoardConfig = reactive({
  coordinates: true,
  orientation: &apos;white&apos;,
  position: &apos;start&apos;,
  viewOnly: true,
});

const gameOverMessage = ref&lt;string&gt;(&apos;&apos;);
const currentTurnLabel = ref&lt;string&gt;(&apos;&apos;);

let worker: Worker;

onMounted(() =&gt; {
  // Cr&#xE9;ation d&apos;un nouvel objet Stockfish
  worker = new Worker(
    new URL(
      &apos;/stockfish/stockfish-nnue-16.js&apos;,
      import.meta.url
    ),
    { type: &apos;module&apos; }
  );
  
  // Abonnement &#xE0; l&apos;&#xE9;v&#xE8;nement &apos;onmessage&apos; du moteur Stockfish
  worker.onmessage = onStockfishMessage;

  // Initialisation du moteur
  worker.postMessage(&apos;uci&apos;);
  worker.postMessage(&apos;isready&apos;);
  worker.postMessage(&apos;setoption name Skill Level value 0&apos;);
  worker.postMessage(&apos;ucinewgame&apos;);
});

function onBoardCreated(api: BoardApi) {
  boardAPI = api;
  updateTurnColor(boardAPI.getTurnColor());
}

function onPlayerMove() {
  // Mise &#xE0; jour de qui joue
  const turnColor = boardAPI.getTurnColor();
  updateTurnColor(turnColor);

  // Appel &#xE0; Stockfish pour calculer le prochain coup 
  // si c&apos;est son tour de jouer
  if (turnColor !== boardConfig.orientation) {
    boardConfig.viewOnly = true;
    askStockfishMove();
  } else {
    boardConfig.viewOnly = false;
  }
}

function askStockfishMove() {
  // Passage de l&apos;&#xE9;tat de la partie &#xE0; Stockfish pour un nouveau calcul
  worker.postMessage(`position fen ${boardAPI.getFen()}`);
  
  // Lancement du calcul pour 1 seconde
  worker.postMessage(&apos;go movetime 1000&apos;);
}

function onStockfishMessage(event: any) {
  if (!boardAPI) {
    throw new Error(&apos;vue3-chessboard is not initialized.&apos;);
  }

  const msg: string = event.data;
  console.log(msg);

  // Si Stockfish est pr&#xEA;t, on active les int&#xE9;ractions
  // avec le composant &apos;vue3-chessboard&apos;
  if (msg === &apos;readyok&apos;) {
    boardConfig.viewOnly = false;
  }

  if (msg.includes(&apos;bestmove&apos;)) {
    const tokens = msg.split(&apos; &apos;);

    // V&#xE9;rification pr&#xE9;sence valeur de bestmove
    if (tokens.length &lt; 2) {
      console.error(&apos;Best move not found.&apos;);
      return;
    }

    // R&#xE9;cup&#xE9;ration de la valeur du meilleur coup &#xE0; jouer
    const bestMove = tokens[1];

    // V&#xE9;rification du format du best move
    if (!bestMove || bestMove.length &lt; 4) {
      console.error(&apos;Incorrect best move format.&apos;);
      return;
    }

    // Application du coup calcul&#xE9;
    const moveObj: Move = { 
      from: bestMove.substring(0, 2) as Key,
      to: bestMove.substring(2, 4) as Key 
    };

    // Si la valeur de bestMove fait 5 caract&#xE8;res,
    // c&apos;est que la pi&#xE8;ce est promue
    if (bestMove.length === 5) {
      moveObj.promotion = bestMove[4] as Promotion;
    }

    // D&#xE9;placement de la pi&#xE8;ce sur l&apos;&#xE9;chiquier
    boardAPI.move(moveObj);
  }
}

function onCheckmate(isMated: PieceColor) {
  // Si le joueur mat&#xE9; est l&apos;utilisateur
  if (isMated === boardConfig.orientation) {
    gameOverMessage.value = &apos;Zut ! Vous avez perdu pour cette fois !&apos;;
  } else {
    gameOverMessage.value = &apos;Bravo ! Vous avez gagn&#xE9; la partie !&apos;;
  }
}

function onStalemate() {
  gameOverMessage.value = `Match nul ! C&apos;est un pat !`;
}

function onDraw() {
  gameOverMessage.value = `Match nul !`;
}

function restartGame(orientation: PieceColor) {
  // Application de l&apos;orientation et reset de la partie
  boardConfig.orientation = orientation;
  resetGame();
  if (orientation === &apos;black&apos;) {
    // Comme les Blancs commencent, 
    // on demande tout de suite &#xE0; Stockfish de jouer
    askStockfishMove();
  }
}

function resetGame() {
  // R&#xE9;initialisation de l&apos;&#xE9;chiquier
  boardAPI.resetBoard();

  // R&#xE9;initialisation des messages
  gameOverMessage.value = &apos;&apos;;
  updateTurnColor(boardAPI.getTurnColor());

  // D&#xE9;sactivation des d&#xE9;placements 
  // en attendant que Stockfish soit pr&#xEA;t
  boardConfig.viewOnly = true;

  // R&#xE9;initialisation du moteur Stockfish
  worker.postMessage(&apos;ucinewgame&apos;);
  worker.postMessage(&apos;isready&apos;);
}

function updateTurnColor(turnColor: PieceColor) {
  currentTurnLabel.value = turnColor === &apos;white&apos; ? &apos;Blancs&apos; : &apos;Noirs&apos;;
}
&lt;/script&gt;

&lt;style scoped&gt;
&lt;/style&gt;
</code></pre>
<p>Vous pouvez une nouvelle fois acc&#xE9;der &#xE0; l&apos;application et v&#xE9;rifier vous-m&#xEA;mes les fonctionnalit&#xE9;s en lancant le serveur de d&#xE9;veloppement avec la commande :</p>
<pre><code class="language-bash">npm run dev
</code></pre>
<h2 id="4-d%C3%A9ploiement">4. D&#xE9;ploiement</h2>
<p>Maintenant que le d&#xE9;veloppement de notre projet est termin&#xE9;e, attaquons la partie d&#xE9;ploiement !</p>
<p><strong>Important :</strong> Nous allons faire un simple d&#xE9;ploiement en local : il faudrait rajouter la gestion du HTTPS avec un certificat SSL pour un d&#xE9;ploiement en production.</p>
<p>Allons-y ! Voici le diagramme de d&#xE9;ploiement pour notre application <strong>CheckM8</strong> :</p>
<div style="margin: auto">
    <img src="https://tech.cpignon.fr/content/images/2025/03/checkm8-deployment-diagram-v1.png" alt="CheckM8 : Cr&#xE9;ez votre propre entra&#xEE;neur d&#x2019;&#xE9;checs en ligne (version 1)">
    <span style="display: block; font-size: 0.8em; text-align: center"><em>Figure 5 - Diagramme de d&#xE9;ploiement</em></span>
</div>
<h3 id="41-copie-automatique-des-scripts-stockfish-dans-public">4.1. Copie automatique des scripts Stockfish dans public</h3>
<p>Pour &#xE9;viter de devoir copier les scripts de Stockfish &#xE0; la main nous allons rajouter une commande <em>postinstall</em> dans la partie <em>scripts</em> du fichier <em>package.json</em> de notre frontend :</p>
<pre><code class="language-json">{
  &quot;scripts&quot;: {
    &quot;postinstall&quot;: &quot;mkdir -p public/stockfish &amp;&amp; cp node_modules/stockfish/src/stockfish-nnue-16.* public/stockfish/&quot;
  }
}
</code></pre>
<p>Cette commande va permettre d&apos;automatiquement copier tous les scripts du r&#xE9;pertoire d&apos;installation de Stockfish commencant par <em>stockfish-nnue-16.</em> dans le r&#xE9;pertoire <em>public/stockfish/</em> du projet Vue.</p>
<h3 id="42-configuration-de-nginx">4.2. Configuration de nginx</h3>
<p>Notre frontend &#xE9;tant servi par un nginx, nous allons cr&#xE9;er un fichier <em>nginx.conf</em> &#xE0; la racine de notre frontend Vue et &#xE9;crire dedans la configuration suivante :</p>
<pre><code class="language-nginx">server {
    listen 80;
    server_name localhost;

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri /index.html;
    }

    location /stockfish/ {
        root /usr/share/nginx/html;
        types { application/wasm wasm; }
        add_header Cross-Origin-Opener-Policy same-origin;
        add_header Cross-Origin-Embedder-Policy require-corp;
        add_header Cross-Origin-Resource-Policy cross-origin;
    }

    location ~* \.(wasm|js|css|html|png|jpg|jpeg|gif|ico|svg|woff2?|ttf|otf|eot|json|txt)$ {
        expires 6M;
        access_log off;
        add_header Cache-Control &quot;public&quot;;
    }
}
</code></pre>
<p><strong>D&#xE9;tail du fichier <em>nginx.conf</em> :</strong></p>
<ul>
<li>Au d&#xE9;but de la configuration, nous d&#xE9;finissions les param&#xE8;tres g&#xE9;n&#xE9;raux du serveur :
<ul>
<li>nginx &#xE9;coute sur le port 80</li>
<li>Le dossier racine est d&#xE9;fini comme <em>/usr/share/nginx/html</em> : c&apos;est l&#xE0; que sont stock&#xE9;s les fichiers de l&apos;application Vue apr&#xE8;s le build</li>
<li><em>index.html</em> est utilis&#xE9;e comme page d&apos;accueil</li>
</ul>
</li>
<li>Nous d&#xE9;finissions ensuite une r&#xE8;gle pour les requ&#xEA;tes dont le pr&#xE9;fixe est <em>/</em> :
<ul>
<li>Si un utilisateur acc&#xE8;de directement &#xE0; une page autre que l&#x2019;accueil, Nginx redirige la requ&#xEA;te vers <em>index.html</em>, permettant &#xE0; Vue de prendre en charge la navigation c&#xF4;t&#xE9; client</li>
</ul>
</li>
<li>Puis nous ajoutons des r&#xE8;gles pour les requ&#xEA;tes vers le r&#xE9;pertoire <em>/stockfish/</em> :
<ul>
<li>On s&#x2019;assure que Nginx cherche bien les fichiers de Stockfish dans <em>/usr/share/nginx/html/stockfish/</em> en remettant la r&#xE8;gle <code>root /usr/share/nginx/html;</code></li>
<li>Le type MIME <em>application/wasm</em> est d&#xE9;clar&#xE9;, garantissant la reconnaissance correcte des fichiers .wasm par le navigateur</li>
<li>Des en-t&#xEA;tes HTTP sont ajout&#xE9;es pour &#xE9;viter l&#x2019;erreur <em>SharedArrayBuffer is not defined</em> :
<ul>
<li><strong>Cross-Origin-Opener-Policy: same-origin</strong> &#x2192; assure que l&#x2019;application s&#x2019;ex&#xE9;cute dans un environnement s&#xE9;curis&#xE9;</li>
<li><strong>Cross-Origin-Embedder-Policy: require-corp</strong> &#x2192; autorise le chargement de WebAssembly</li>
<li><strong>Cross-Origin-Resource-Policy: cross-origin</strong> &#x2192; permet aux fichiers .wasm d&#x2019;&#xEA;tre accessibles m&#xEA;me en provenance d&#x2019;une autre origine</li>
</ul>
</li>
</ul>
</li>
<li>Enfin, nous cr&#xE9;ons des r&#xE8;gles qui s&apos;appliquent &#xE0; tous les fichiers statiques :
<ul>
<li>Ils sont mis en cache pendant 6 mois pour &#xE9;viter aux utilisateurs de re-t&#xE9;l&#xE9;charger ces fichiers &#xE0; chaque visite</li>
<li>Les logs d&#x2019;acc&#xE8;s sont d&#xE9;sactiv&#xE9;s pour &#xE9;conomiser des ressources</li>
<li>La politique <em>Cache-Control</em> est d&#xE9;finie &#xE0; <em>public</em> permettant aux navigateurs de garder ces fichiers en cache</li>
</ul>
</li>
</ul>
<p>Notre configuration du serveur web est maintenant termin&#xE9;e !</p>
<h3 id="43-ecriture-du-dockerfile-du-frontend-vue">4.3. Ecriture du <em>Dockerfile</em> du frontend Vue</h3>
<p>Comme nous l&apos;avons sp&#xE9;cifi&#xE9; dans lors de la conception de l&apos;architecture du projet, nous allons utiliser Docker pour conteneuriser notre application.</p>
<p>Voici le Dockerfile pour notre frontend Vue :</p>
<pre><code class="language-docker"># &#xC9;tape 1 : Build Vue.js
FROM node:22.14 AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

# &#xC9;tape 2 : Serveur Nginx
FROM nginx:1.27-alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]
</code></pre>
<p><strong>D&#xE9;tail du Dockerfile :</strong><br>
Le Dockerfile est divis&#xE9; en deux &#xE9;tapes distinctes :</p>
<ul>
<li><strong>Etape n&#xB0;1 :</strong> build du frontend Vue en ressources statiques (transpilation du Typescript en Javascript, minimification, bundling des assets, copie dans le r&#xE9;pertoire <em>dist</em>)</li>
<li><strong>Etape n&#xB0;2 :</strong> cr&#xE9;ation du conteneur <em>nginx</em> pour servir les ressources statiques g&#xE9;n&#xE9;r&#xE9;es &#xE0; l&apos;&#xE9;tape pr&#xE9;c&#xE9;dente</li>
</ul>
<p><ins>Etape n&#xB0;1 : Build du frontend</ins></p>
<ul>
<li>Nous commencons par d&#xE9;finir l&apos;image de base (ici <em>node:22.14</em>). Il s&apos;agit de la version LTS (Long-Term Support) de Node au moment o&#xF9; j&apos;&#xE9;cris cet article</li>
<li>Nous d&#xE9;finissons <em>/app</em> (&#xE0; l&apos;int&#xE9;rieur du conteneur) comme le r&#xE9;pertoire de travail</li>
<li>Nous copions d&apos;abord uniquement les fichiers <em>package.json</em> et <em>package-lock.json</em> dans le conteneur</li>
<li>Nous lancons ensuite la commande : <code>npm install</code> pour installer les d&#xE9;pendances et copier les scripts Stockfish dans le r&#xE9;pertoire <em>/public/stockfish/</em></li>
<li>Dans l&apos;&#xE9;tape suivante, nous copions l&apos;ensemble des fichiers sources dans le conteneur</li>
<li>Puis nous lancons la commande <code>npm run build</code> pour g&#xE9;n&#xE9;rer les ressources statiques</li>
</ul>
<p><ins>Etape n&#xB0;2 : Cr&#xE9;ation du conteneur <em>nginx</em></ins></p>
<ul>
<li>Nous d&#xE9;marrons cette &#xE9;tape en d&#xE9;finissant l&apos;image de base (ici <em>nginx:1.27-alpine</em>)</li>
<li>Nous copions ensuite les fichiers g&#xE9;n&#xE9;r&#xE9;s &#xE0; l&apos;&#xE9;tape n&#xB0;1 dans le r&#xE9;pertoire <em>/usr/share/nginx/html</em></li>
<li>Enfin nous copions le fichier de configuration <em>nginx.conf</em> dans le r&#xE9;pertoire <em>/etc/nginx/conf.d/</em> et nous le renommons <em>default.conf</em></li>
</ul>
<p>Il ne reste plus qu&apos;&#xE0; exposer le port 80 et &#xE0; lancer le serveur web au premier plan avec l&apos;option <em>daemon off;</em> (un conteneur = un process).</p>
<h3 id="44-ecriture-du-fichier-docker-composeyml">4.4. Ecriture du fichier <em>docker-compose.yml</em></h3>
<p>La derni&#xE8;re &#xE9;tape de notre d&#xE9;ploiement est de cr&#xE9;er un fichier <em>docker-compose.yml</em> pour orchestrer notre unique conteneur.<br>
Cela a peu d&apos;int&#xE9;r&#xEA;t pour le moment mais lorsque nous aurons plus de services &#xE0; g&#xE9;rer, il sera essentiel d&apos;utiliser un orchestrateur (docker-compose, Swarm, k8s, k3s, etc...).</p>
<p>Nous allons donc d&apos;ores-et-d&#xE9;j&#xE0; mettre en place <em>docker-compose</em>. Pour cela, cr&#xE9;ons un fichier <em>docker-compose.yml</em> &#xE0; c&#xF4;t&#xE9; de notre projet Vue (dans le r&#xE9;pertoie <em>checkm8</em>). Celui-ci contient la configuration YAML suivante :</p>
<pre><code class="language-yaml">services:
  frontend:
    image: checkm8-frontend
    build: ./checkm8-frontend
    ports:
      - &quot;8080:80&quot;
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
    restart: always
</code></pre>
<p><strong>D&#xE9;tails du <em>docker-compose.yml</em> :</strong></p>
<ul>
<li>Nous commencons par d&#xE9;finir un service <em>frontend</em> qui correspond &#xE0; notre serveur nginx fournissant notre application Vue</li>
<li>Nous donnons ensuite un nom &#xE0; notre image : ici <em>checkm8-frontend</em></li>
<li>Puis nous indiquons le r&#xE9;pertoire dans lequel se situe le <em>Dockerfile</em> de l&apos;image : ici <em>./checkm8-frontend</em></li>
<li>Nous redirigons ensuite le port 8080 de la machine h&#xF4;te vers le port 80 du conteneur (o&#xF9; nginx &#xE9;coute) &#x2192; nous pourrons acc&#xE9;der &#xE0; l&apos;application via <a href="http://localhost:8080/?ref=tech.cpignon.fr">http://localhost:8080</a></li>
<li>Nous montons le fichier <em>nginx.conf</em> personnalis&#xE9; dans le conteneur &#x2192; avoir un volume ici nous permet de nous assurer que toute modification du fichier <em>nginx.conf</em> soit prise en compte sans devoir reconstruire l&apos;image</li>
<li>Enfin, nous ajoutons l&apos;option <code>restart: always</code> pour s&apos;assurer que nginx red&#xE9;marre automatiquement en cas de crash ou de red&#xE9;marrage du serveur</li>
</ul>
<h3 id="45-tester-notre-d%C3%A9ploiement">4.5. Tester notre d&#xE9;ploiement</h3>
<p>Pour tester notre d&#xE9;ploiement, il faut d&apos;abord construire l&apos;image Docker en lancant la commande suivante depuis le r&#xE9;pertoire o&#xF9; se trouve le fichier <em>docker-compose.yml</em> :</p>
<pre><code class="language-bash">docker compose build
</code></pre>
<p>Puis lancer les services (ici un seul service <em>frontend</em>) :</p>
<pre><code class="language-bash">docker compose up -d
</code></pre>
<p>Et voil&#xE0; ! Nous en avons termin&#xE9; avec la partie d&#xE9;ploiement !</p>
<h2 id="5-prochaines-%C3%A9tapes-possibilit%C3%A9s-dam%C3%A9lioration">5. Prochaines &#xE9;tapes / Possibilit&#xE9;s d&apos;am&#xE9;lioration</h2>
<p>Cette premi&#xE8;re version de notre application est finie pour l&apos;instant mais il y a &#xE9;norm&#xE9;ment de possibilit&#xE9;s pour l&apos;am&#xE9;liorer :</p>
<ol>
<li>Inclure une biblioth&#xE8;que CSS (Bootstrap, Tailwind CSS, etc...) pour une application plus esth&#xE9;tique</li>
<li>Charger une partie &#xE0; partir d&apos;une FEN (et pouvoir la continuer avec le bot)</li>
<li>Afficher l&apos;historique de la partie (liste coups jou&#xE9;s depuis le d&#xE9;but)</li>
<li>Revenir en arri&#xE8;re dans l&apos;historique de la partie pour reprendre &#xE0; un point donn&#xE9;</li>
<li>Ajouter une fonctionnalit&#xE9; d&apos;&quot;indice&quot; : un bouton qui permet de montrer &#xE0; l&apos;utilisateur le meilleur coup &#xE0; jouer sur le plateau</li>
<li>Analyser le coup qui vient d&apos;&#xEA;tre jou&#xE9; par le joueur et faire un retour &#xE0; l&apos;utilisateur (bon coup, mauvais coup, excellent, etc...) avec une explication d&#xE9;taill&#xE9;e</li>
<li>Limiter plus Stockfish (ELO minimum de 1320 pour la version actuellement utilis&#xE9;e) ou trouver une autre IA pour les d&#xE9;butants</li>
<li>D&#xE9;velopper une API REST pour d&#xE9;porter le calcul de Stockfish en backend et permettre aux utilisateurs de s&apos;inscrire</li>
<li>Proposer des puzzles &#xE0; r&#xE9;soudre (<a href="https://database.lichess.org/?ref=tech.cpignon.fr#puzzles">https://database.lichess.org/#puzzles</a>)</li>
</ol>
<p>Nous impl&#xE9;menterons quelques unes de ces id&#xE9;es dans la prochaine version !</p>
<h2 id="6-conclusion">6. Conclusion</h2>
<p>Nous avons vu comment concevoir et d&#xE9;velopper une application web permettant de jouer aux &#xE9;checs contre une intelligence artificielle, directement dans le navigateur. Gr&#xE2;ce &#xE0; <strong>Vue 3</strong> et <strong>WebAssembly</strong>, nous avons pu int&#xE9;grer <strong>Stockfish</strong>, un moteur d&#x2019;analyse puissant.</p>
<p>Ce projet constitue une excellente base pour s&#x2019;entra&#xEE;ner aux &#xE9;checs, exp&#xE9;rimenter diff&#xE9;rentes strat&#xE9;gies et tester des positions sp&#xE9;cifiques sans avoir besoin d&#x2019;un adversaire humain. De nombreuses am&#xE9;liorations sont possibles, comme l&#x2019;ajout de niveaux de difficult&#xE9; ajustables, la possibilit&#xE9; de charger des puzzles d&#x2019;&#xE9;checs ou encore le suivi des parties jou&#xE9;es.</p>
<h2 id="liens-utiles">Liens utiles</h2>
<ul>
<li><a href="https://www.chess.com/?ref=tech.cpignon.fr">chess.com</a> &#x2192; L&apos;une des plateformes d&#x2019;&#xE9;checs en ligne les plus populaires, offrant des parties en temps r&#xE9;el et des outils d&#x2019;analyse avanc&#xE9;s</li>
<li><a href="https://lichess.org/?ref=tech.cpignon.fr">lichess</a> &#x2192; Une plateforme d&#x2019;&#xE9;checs gratuite et open source proposant des parties en ligne, des puzzles et un moteur d&#x2019;analyse puissant</li>
<li><a href="https://vuejs.org/?ref=tech.cpignon.fr">Vue 3</a> &#x2192; Framework JavaScript progressif permettant de d&#xE9;velopper des interfaces utilisateur r&#xE9;actives et performantes</li>
<li><a href="https://github.com/qwerty084/vue3-chessboard?ref=tech.cpignon.fr">vue3-chessboard</a> &#x2192; Composant Vue.js permettant d&#x2019;int&#xE9;grer facilement un &#xE9;chiquier interactif dans une application web</li>
<li><a href="https://stockfishchess.org/?ref=tech.cpignon.fr">Stockfish</a> &#x2192; Moteur d&#x2019;&#xE9;checs open source utilis&#xE9; pour l&#x2019;analyse et le jeu automatis&#xE9;</li>
<li><a href="https://nginx.org/?ref=tech.cpignon.fr">nginx</a> &#x2192; Serveur web et reverse proxy, utilis&#xE9; pour h&#xE9;berger et distribuer des applications web</li>
<li><a href="https://www.docker.com/?ref=tech.cpignon.fr">Docker</a> &#x2192; Plateforme de conteneurisation permettant de d&#xE9;ployer et ex&#xE9;cuter des applications de mani&#xE8;re portable et isol&#xE9;e</li>
<li><a href="https://docs.docker.com/compose/?ref=tech.cpignon.fr">docker-compose</a> &#x2192; Outil facilitant l&#x2019;orchestration des conteneurs Docker en d&#xE9;finissant leur configuration dans un fichier unique</li>
</ul>
]]></content:encoded></item></channel></rss>