Vincent Barrault

IA de Morpion en HTML

IA de Morpion en HTML

Table des matières

Introduction

J'ai souvent entendu dire que le HTML n'était pas un langage. Et c'est vrai, ce n'est pas un langage de programmation. Mais cela ne veut pas dire que l'on ne peut pas faire des trucs cool avec.

Récemment, je me suis demandé s'il était possible de créer une IA de jeu en HTML. J'ai donc pris un jeu simple, le morpion ou tic tac toe en anglais. Pour rappel, le but du jeu est simple, sur une grille de 3x3, il faut réussir à aligner 3 croix ou 3 cercles.

L'approche directe

Je commence à faire une page HTML avec une grille de morpion

./tictactoe.html
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>TicTacToe</title>
        <link rel="stylesheet" href="./style.css">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>

    <body>
        <div id="game">
            <div class="cell"><img src="./cross.svg" /></div>
            <div class="cell"><img src="./round.svg" /></div>
            <div class="cell"></div>
            <div class="cell"></div>
            <div class="cell"></div>
            <div class="cell"></div>
            <div class="cell"></div>
            <div class="cell"></div>
            <div class="cell"></div>
        </div>
    </body>
</html>
./style.css
#game {
    margin: 20px;
    display: inline-grid;
    grid-template-columns: auto auto auto;
    grid-template-rows: auto auto auto;
    grid-auto-flow: row;
    background: black;
    grid-gap: 5px;
}
.cell {
    height: 100px;
    width: 100px;
    padding: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: white;
}

Et comme je ne peux pas rajouter du contenu (HTML) dans la page sans JS, je dois passer par une autre page pour afficher un autre état dans le jeu.

Je commence à nommer mes pages comme ceci:

---------.html
xo-------.html
--ox-----.html
.
.
.

Il y a 9 cases, donc neuf tirets, et l'état de chaque case est définie par - si la case est vide, x si la case est remplie par une croix et o si la case est remplie par un rond.

A la fin, ma grille ressemble à cela:

./---------.html
<div id="game">
    <a href="./x--------.html" class="cell"></a>
    <a href="./-x-------.html" class="cell"></a>
    <a href="./--x------.html" class="cell"></a>
    <a href="./---x-----.html" class="cell"></a>
    <a href="./----x----.html" class="cell"></a>
    <a href="./-----x---.html" class="cell"></a>
    <a href="./------x--.html" class="cell"></a>
    <a href="./-------x-.html" class="cell"></a>
    <a href="./--------x.html" class="cell"></a>
</div>

L'approche réaliste

Au Morpion, il peut y avoir des milliers de possibilités. Certaines d'entre elles ne sont pas atteignables, mais au bout du compte, créer toutes les pages à la main prendrait des heures. Je vais donc me simplifier la vie en créant un script qui va générer toutes les possibilités.

composer init
composer require twig/twig
./generate.php
<?php

// ini_set('xdebug.max_nesting_level', 9999);

require_once './vendor/autoload.php';

$gameTemplate = <<<EOD
<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>TicTacToe</title>
        <link rel="stylesheet" href="./style.css">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>

    <body>
        {% if game.isLoss %}
            <div class="result">Perdu ! <a href="---------.html">Recommencer</a></div>
        {% elseif game.isDraw %}
            <div class="result">Null ! <a href="---------.html">Recommencer</a></div>
        {% else %}
            <div class="turn">A toi de jouer</div>
        {% endif %}

        <div id="game">
            {% for cell in game.cells %}
                <a href="{{ cell.next }}" class="cell">
                    {% if cell.isTakenByX %}
                        <img src="./cross.svg" />
                    {% elseif cell.isTakenByO %}
                        <img src="./round.svg" />
                    {% endif %}
                </a>
            {% endfor %}
        </div>
    </body>
</html>
EOD;

$loader = new \Twig\Loader\ArrayLoader([
    'game' => $gameTemplate,
]);

$board = Board::fromString('---------');
writeFile("./$board->str.html", $twig->render('game', ['game' => $board]));

function writeFile(string $fileName, string $content): void
{
    try {
        @file_put_contents($fileName, $content);
    } catch (\Exception $e) {
        var_dump($e);
    }
}

Et voilà, je ne développe pas les détails sur l'IA car ce n'est pas le sujet mais j'ai utilisé un simple algorithme min/max à tous les tours du joueur "cercle" et j'ai dupliqué toutes les possibilitées à tous les tours du joueur "croix".

Vous pouvez retrouver le morpion ici.