Moteur de jeu Awalé en Python avec architecture modulaire.
Le projet couvre les règles complètes du jeu, quatre agents IA (Aléatoire, Minimax, Alpha-Beta, Q-Learning) et une interface web interactive connectée à une API REST FastAPI.
Projet développé dans le cadre du Club IA & IoT — IFRI.
- Architecture
- Installation
- Lancement
- Moteur de jeu
- Agents IA
- API REST
- Interface Web
- Entraînement Q-Learning
- Règles implémentées
- Roadmap
AwaleAI/
├── engine/
│ ├── board.py # Modélisation du plateau (12 cases)
│ ├── rules.py # Règles, distribution des graines, coups valides
│ ├── game.py # Moteur de jeu : captures, scores, fin de partie
│ └── env.py # Environnement RL (AwaleEnv) — base Phase 3
│
├── agents/
│ ├── difficulty.py # Routage agent/profondeur (point d'entrée unique)
│ ├── random/
│ │ └── random_agent.py # Agent aléatoire
│ ├── heuristic/
│ │ └── heuristic.py # Fonction d'évaluation statique
│ ├── minimax/
│ │ └── minimax.py # Minimax avec SearchState léger
│ └── alpha_beta/
│ └── elagage.py # Alpha-Beta négamax + évaluation avancée
│
├── awale/
│ └── ai/
│ └── qlearning.py # Agent Q-Learning persistant + curriculum
│
├── models/
│ ├── q_table_latest.json # Dernier checkpoint Q-Learning
│ └── q_table_best.json # Meilleur checkpoint Q-Learning
│
├── api/
│ └── server.py # API REST FastAPI (5 endpoints)
│
├── web/
│ ├── index.html # Interface HTML — plateau, configuration, modale
│ ├── app.js # Client JS — UX complète (toasts, animations, modale)
│ └── styles.css # Styles — pits, graines, granaires, responsive
│
├── main.py # Point d'entrée CLI — match IA vs IA en console
├── train_qlearning.py # Lancement du curriculum d'entraînement
├── Les règles d'Awale.md # Règles officielles du jeu
├── requirements.txt # Dépendances Python
├── vercel.json # Configuration déploiement Vercel
└── pyproject.toml # Entrypoint Vercel
Prérequis : Python 3.10+
pip install -r requirements.txtDépendances :
| Paquet | Version minimale | Rôle |
|---|---|---|
fastapi |
0.110.0 | Framework API REST |
uvicorn[standard] |
0.29.0 | Serveur ASGI |
pydantic |
2.0.0 | Validation des schémas de requêtes |
# 1. Démarrer le backend
uvicorn api.server:app --reload --port 8000
# 2. Ouvrir l'interface dans un navigateur
# Double-cliquer sur web/index.html
# ou ouvrir http://localhost:8000 si servi via Vercel localpython main.pyLe programme propose de choisir un niveau de difficulté pour chaque joueur, puis simule la partie en console avec détection de répétition de position.
python train_qlearning.pyLance le pipeline curriculum complet (≈ 4 500 parties). Les modèles sont sauvegardés dans models/.
Représente le plateau Awalé : 12 cases, 4 graines chacune à l'initialisation.
board.holes # list[int] de longueur 12
# indices 0–5 → Joueur 1 (rangée du bas)
# indices 6–11 → Joueur 2 (rangée du haut, affichée de droite à gauche)| Méthode | Description |
|---|---|
copy() |
Retourne une copie indépendante du plateau |
display() |
Affiche le plateau en vue miroir (J2 en haut, J1 en bas) |
Valide les coups et distribue les graines. Toutes les méthodes sont @staticmethod.
| Méthode | Description |
|---|---|
is_valid_move(board, hole, player) |
Vérifie que la case existe, n'est pas vide et appartient au bon joueur |
sow(board, hole) |
Distribue les graines en sens +1 % 12, saute la case de départ si tour complet (règle Kroo), retourne le dernier index |
get_valid_moves(board, player) |
Retourne les coups légaux — applique la règle de nourrissage par simulation |
Convention d'index : le semis incrémente l'index modulo 12 (sens anti-horaire dans la vue plateau : bas de gauche à droite, puis haut de droite à gauche).
Orchestre une partie complète.
| Attribut | Type | Description |
|---|---|---|
board |
Board |
Plateau courant |
score_p1 |
int |
Graines capturées par le joueur 1 |
score_p2 |
int |
Graines capturées par le joueur 2 |
current_player |
int |
Joueur actif (1 ou 2) |
| Méthode | Description |
|---|---|
play_move(hole) |
Valide, sème, capture, met à jour le score, change de joueur |
capture(last_hole) |
Remonte depuis last_hole, capture tant que la case contient 2 ou 3 graines dans le camp adverse |
is_game_over() |
True si un joueur a ≥ 25 graines ou si le joueur courant n'a plus de coup valide |
get_winner() |
Retourne 1, 2 ou None (égalité) — idempotent, ne modifie pas l'état |
get_children() |
Génère tous les états enfants pour les agents de recherche |
display() |
Affiche le plateau et les scores en console |
Couche d'abstraction autour de Game pour les agents d'apprentissage par renforcement.
| Méthode | Description |
|---|---|
reset() |
Démarre une nouvelle partie, retourne l'état initial sous forme de liste |
state() |
Retourne le plateau courant sous forme de liste |
Module centralisé exposant deux points d'entrée :
# Entrée CLI — niveau prédéfini
choose_move(game, level: str) -> int
# Entrée Web UI — agent + profondeur libres
choose_move_by_agent(game, agent: str, depth: int | None) -> tuple[int, int | None]Niveaux prédéfinis (CLI) :
| Niveau | Agent | Profondeur |
|---|---|---|
"facile" |
Aléatoire | — |
"moyen" |
Minimax | 2 |
"difficile" |
Alpha-Beta | 5 |
"expert" |
Alpha-Beta | 8 |
"qlearning" |
Q-Learning | — |
Agents disponibles (Web UI) :
| Agent | Identifiant | Profondeurs valides |
|---|---|---|
| Aléatoire | random |
— |
| Minimax | minimax |
1 – 8 |
| Alpha-Beta | alphabeta |
1 – 12 |
| Q-Learning | qlearning |
— |
L'agent Q-Learning est chargé paresseusement (_get_qlearning_agent()) : la Q-Table est lue depuis models/q_table_latest.json uniquement à la première utilisation.
random_move(game) -> intChoisit uniformément un coup valide parmi les coups légaux du joueur courant.
Fonction d'évaluation statique utilisée par Minimax. Toutes les méthodes sont @staticmethod.
heuristic.evaluate(game, player) -> float| Composante | Poids | Description |
|---|---|---|
| Différence de captures | 1.0 |
score_joueur − score_adversaire |
| Mobilité | 3.0 |
Coups valides joueur − coups valides adversaire |
| Graines dans le camp adverse | 0.1 |
Pression offensive |
Algorithme Minimax pur avec profondeur configurable.
Minimax(depth=2).choose_move(board, player, score1=0, score2=0) -> tuple[int, int]
# Retourne : (case jouée, nombre de nœuds explorés)- Utilise
SearchState(structure légère) pour simuler les coups sans copier l'objetGame - Reçoit les vrais scores courants (
score1,score2) pour une évaluation correcte - Bris d'égalité aléatoire via
_shuffled_moves() - Évaluation terminale via
heuristic.evaluate()
Alpha-Beta avec négamax et élagage α-β.
best_move(game, depth=5) -> tuple[int, int]
# Retourne : (case jouée, nombre de nœuds explorés)Fonction d'évaluation propre (evaluate) :
| Composante | Poids | Description |
|---|---|---|
| Différence de captures | 100 |
Priorité maximale |
| Opportunités de capture | 10 |
Cases adverses contenant 1 ou 2 graines (menaces) |
| Mobilité | 5 |
Coups valides joueur − adversaire |
| Contrôle du plateau | 0.5 |
Total des graines dans son propre camp |
Détecte les fins de partie immédiates (victoire +999999 / défaite -999999 / égalité 0) et court-circuite la recherche dès qu'une victoire est trouvée.
Agent Q-Learning tabulaire avec encodage symétrique et curriculum d'entraînement progressif.
QLearningAgent().choose_move(game, greedy=True) -> intEncodage de l'état :
"mes_trous|trous_adversaire|score_moi|score_lui"
Grâce à cette symétrie, la même Q-Table fonctionne pour les deux joueurs. Les actions sont encodées en indices relatifs (0–5) plutôt qu'absolus (0–11).
Hyperparamètres :
| Paramètre | Valeur | Description |
|---|---|---|
ALPHA |
0.15 | Taux d'apprentissage |
GAMMA |
0.95 | Facteur d'actualisation |
EPSILON_START |
1.00 | Taux d'exploration initial |
EPSILON_MIN |
0.05 | Taux d'exploration plancher |
Récompenses :
| Événement | Récompense |
|---|---|
| Victoire | +100 |
| Défaite | −100 |
| Égalité | 0 |
| Capture | +2 par graine |
| Capture adverse | −2 par graine |
| Pénalité temporelle | −0.5 par tour |
Curriculum d'entraînement (run_curriculum) :
| Phase | Adversaire | Parties |
|---|---|---|
| 1 | Agent aléatoire | 1 000 |
| 2.1 – 2.5 | Alpha-Beta profondeur 1 à 5 | 500 × 5 |
| 3 | Self-play | 1 000 |
Un checkpoint "champion" est sauvegardé après chaque phase si le taux de victoire s'améliore.
Backend FastAPI exposant cinq endpoints.
Lancement :
uvicorn api.server:app --reload --port 8000| Endpoint | Méthode | Description |
|---|---|---|
/api/game/start |
POST |
Démarre une partie, retourne l'état initial |
/api/game/move |
POST |
Applique un coup humain, retourne le nouvel état |
/api/game/ai-move |
POST |
Fait jouer l'IA (agent + profondeur libres), retourne état + télémétrie |
/api/agents |
GET |
Liste les agents disponibles et leurs profondeurs valides |
/ |
GET |
Health check |
État de jeu retourné (tous les endpoints sauf /api/agents) :
{
"board": [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
"scores": { "player1": 0, "player2": 0 },
"granaries": { "player1": 0, "player2": 0 },
"current_player": "player1",
"game_over": false,
"winner": null,
"valid_moves": [0, 1, 2, 3, 4, 5]
}Payload POST /api/game/start :
{
"player1": { "name": "Alice", "type": "human" },
"player2": { "name": "IA", "type": "ai" }
}Payload POST /api/game/move :
{ "pit_index": 3, "player": "player1" }Payload POST /api/game/ai-move :
{ "player": "player1", "agent": "alphabeta", "depth": 6, "board": [...] }Réponse POST /api/game/ai-move :
{
"game_state": { "board": [...], "scores": {...}, "current_player": "player2", "game_over": false, "valid_moves": [...] },
"telemetry": { "computation_time": 42.1, "depth": 6, "nodes_explored": 8312, "win_rate": null, "pit_played": 3, "agent": "alphabeta" }
}Note : La session de jeu est stockée en mémoire côté serveur (
_game). Un seul jeu actif à la fois par instance du serveur.
Interface jouable dans un navigateur, sans build ni installation.
Icônes Font Awesome 6 (CDN). Aucune dépendance JS additionnelle.
L'URL de l'API est détectée automatiquement :
localhost→http://localhost:8000- Production →
https://awale-ai-backend.onrender.com
Chaque joueur est configurable indépendamment avant le lancement d'une partie :
| Paramètre | Valeurs |
|---|---|
| Type | Humain / IA |
| Nom | Texte libre |
| Modèle IA | Aléatoire / Minimax / Alpha-Beta / Q-Learning |
| Profondeur | Select dynamique avec libellés (Facile → Maître) — masqué pour les agents sans profondeur |
Libellés de profondeur :
| Plage (Alpha-Beta) | Libellé |
|---|---|
| 1 – 2 | Facile |
| 3 – 4 | Moyen |
| 5 – 6 | Difficile |
| 7 – 9 | Expert |
| 10 – 12 | Maître |
- Rendu des graines par case (positions calculées en cercle, mises en cache dans
_granaryCache) - Cases jouables : anneau doré (
pit--playable), cases invalides grisées (pit--disabled) - Animation du semis case par case à 110 ms/étape (
animateSow) - Flash rouge (
pit--captured) sur les cases capturées après semis - Granaires animés avec graines positionnées aléatoirement en cache
- Indicateur de joueur actif : bordure dorée sur la score-box du joueur courant
| Bouton | Comportement |
|---|---|
| Nouvelle partie | Appelle POST /api/game/start, lance l'IA si les deux joueurs sont IA |
| Arrêter la partie | Réinitialise le plateau côté client sans appel API, reste actif même pendant les coups IA |
Apparaît automatiquement 600 ms après la dernière animation :
- Victoire : icône trophée doré, couronne devant le nom du vainqueur, confettis CSS
- Match nul : icône
handshake, message adapté, pas de confettis - Scores des deux joueurs avec encadré doré sur le gagnant
- Bloc Performance IA (si une IA a joué) : modèle, niveau d'analyse, meilleur temps, temps moyen, coup le plus rapide, coups évalués
- Boutons Rejouer et Fermer — fermable aussi via
Escapeou clic sur l'overlay
Notifications contextuelles en bas à droite, disparition automatique :
| Situation | Type |
|---|---|
| Partie démarrée | Succès (vert) |
| Serveur inaccessible | Erreur (rouge) |
| Partie arrêtée | Avertissement (orange) |
| Coup refusé par l'API | Avertissement (orange) |
| Erreur IA / réseau | Erreur (rouge) |
- Barre de temps de réflexion animée avec couleur adaptative :
- vert < 200 ms
- or < 800 ms
- rouge ≥ 800 ms
- Niveau d'analyse : profondeur + libellé humain (ex.
6 — Difficile) - Coups évalués : nombre formaté avec séparateurs (
12 345) - Taux de victoire : affiché uniquement pour Q-Learning si disponible
- Message d'aide initial jusqu'au premier coup IA
Pastille colorée avec indicateur d'état :
| État | Couleur | Animation |
|---|---|---|
| En attente | Gris | — |
| Tour joueur | Bleu | — |
| IA réfléchit | Violet | pulsation |
| Victoire | Vert | — |
| Égalité | Ambre | — |
| Erreur | Rouge | — |
Liste scrollable des coups joués avec : numéro de tour, joueur (badge J1/J2), case jouée, graines capturées.
python train_qlearning.pyLe script lance run_curriculum(agent) qui enchaîne les phases suivantes :
Phase 1 — vs Random : 1 000 parties
Phase 2.1 — vs AB depth=1 : 500 parties
Phase 2.2 — vs AB depth=2 : 500 parties
Phase 2.3 — vs AB depth=3 : 500 parties
Phase 2.4 — vs AB depth=4 : 500 parties
Phase 2.5 — vs AB depth=5 : 500 parties
Phase 3 — Self-Play : 1 000 parties
À chaque changement de phase, epsilon remonte de +0.15 (relance l'exploration).
Après chaque phase, une évaluation "Champion" sur 100 parties décide si q_table_best.json est mis à jour.
Fichiers produits :
| Fichier | Contenu |
|---|---|
models/q_table_latest.json |
Dernier état de la Q-Table |
models/q_table_best.json |
Meilleur taux de victoire obtenu |
- Distribution circulaire en sens
+1 % 12(anti-horaire sur le plateau physique) - La case de départ est sautée si le nombre de graines est > 11 (règle Kroo)
- Capture : si la dernière case atteinte contient exactement 2 ou 3 graines dans le camp adverse, ces graines sont capturées ; la capture remonte les cases précédentes tant que la condition est remplie
- Règle de nourrissage : un coup n'est valide que s'il ne laisse pas l'adversaire sans graines, sauf si aucun tel coup n'existe
- Fin de partie : lorsqu'un joueur dépasse 24 graines capturées, ou lorsque le joueur courant n'a plus de coup valide
- En cas de blocage, les graines restantes sur le plateau sont attribuées à l'adversaire du joueur bloqué
- Égalité si les deux joueurs finissent avec exactement 24 graines
| Phase | Objectif | Statut |
|---|---|---|
| Phase 1 | Moteur de jeu : plateau, règles, captures, scores | ✅ Terminé |
| Phase 2 | IA classique + Interface : Aléatoire, Heuristique, Minimax, Alpha-Beta, API REST, Web UI | ✅ Terminé |
| Phase 3 | IA par renforcement : Q-Learning tabulaire + curriculum | ✅ Terminé |
| Phase 4 | Finalisation : tests automatisés, benchmark inter-agents, documentation complète, démo | 🔲 À venir |