TD 2 • IPlayer asynchrone
Cartouche
| Champ | Valeur |
|---|---|
| Auteur·e | Élise |
| Édition | 2024-03-04 |
| Durée | 2 séances |
| Taille des équipes | 1~2 personnes login des auteur·rice·s dans author.txt |
| Rendu | via git, dépôt 2023_morpion, branch TD 2, droits en lecture à delivery_collector |
Préambule
Notation
5 points par étape
Un rendu sale et du code qui ne respecte pas la norme peut vous faire perdre une partie ou la totalité des points acquis.
Dépôt de référence
Le code source sur lequel vous allez intervenir se trouve ici : https://git.ecole-89.com/eriizu/2023_morpion.
Introduction
Le dépôt qui vous est fournit contient un jeu du morpion fonctionnel. Le jeu peut s’afficher dans une ou plusieurs fenêtres SFML, ainsi que dans le terminal, à condition de changer quelques lignes dans le main.cpp.
Ce code source à plusieurs problèmes. En l’état si vous fermez une des fenêtres graphiques, le jeu ne peut pas s’arrêter et l’autre fenêtre est bloquée. C’est pas super comme comportement.
De la même façon : si on essaie de fermer une fenêtre dont ça n’est pas le tour de jouer : ça ne marche pas tant que l’autres personne n’a pas jouer (la faisant jouer inutilement.)
Ces problèmes sont dû au fait que chaque fenêtre bloque tant que l’utilisateur n’a pas joué.
Un problème dans la même lignée : quand une fenêtre attends que son joueur joue : elle prend 100% du cœur de CPU sur lequel elle s’exécute. Cela est dû au fait que tant qu’aucun coup n’a été fait on demande en boucle à la fenêtre si elle a des événements. Une solution classique lorsqu’on a un programme qui n’a rien a faire en l’attente de quelque chose : c’est de temporiser. Lui apprendre la patience. Lui apprendre à supporter l’ennui. Pour ce faire on peut utiliser :
#include <chrono>
#include <thread>
// [...]
std::this_thread::sleep_for(std::chrono::miliseconds(100));
Rien que d’attendre 100 ms, ça permet à notre processeur de respirer et à notre programme de partager un peu les resources.
Classes
Le dépôt contient quelques classes que vous allez devoir étudier, chacune avec un rôle bien précis. Pour comprendre leurs interactions, il vous suffit d’étudier le fichier main.cpp.
MorpionGame
Cette classe se concentre exclusivement sur la logique de jeu. Elle utilise deux énumérations :
MorpionGame::StartWithpour choisir qui commence lorsque vous construisez une partie :- valeur possibles :
PX,PO;
- valeur possibles :
MorpionGame::Statusqui permet de voir dans quel état est la partie :PXTurn,POTurnsi c’est le tour de quelqu’un,PXWin,POWinsi quelqu’un a gagné,Drawen cas d’égalité.
L’état de la partie est accessible avec .status().
On peut faire jouer quelqu’un en donnant son symbole ‘x’ ou ‘o’ et l’index sur le plateau de jeu où placer le pion à la fonction .play(char, unsigned int). La case en haut à gauche est la case 0 et en bas à droit la case 8.
On peut récupérer la board au format d’un std::array<char, 9> grâce à la fonction .array().
Enfin, la fonction .done() vous permet de vérifier si la partie est terminée.
IPlayer
L’interface IPlayer prévoit les fonctions membres :
set_board_statepour envoyer le plateau de jeu à un joueur ;set_player_symbolpour dire à un joueur s’il est x ou o ;get_movepour demander au joueur une action à faire ;set_drawpour indiquer au joueur une égalité ;set_winpour indiquer au joueur qui a gagné.
GfxPlayer et TermPlayer implémentent IPlayer.
Consignes
Étape 1 : fermeture d’une fenêtre
Faites en sorte qu’il soit possible d’interrompre une partie : si le programme remarque la fermeture d’une fenêtre ou la eof sur un flux (pour TermReader), faite s’arrêter la partie.
Une piste pour y arriver serait d’utiliser un mécanisme similaire au .done de la classe MorpionGame. Vous êtes libres de faire les choses comme vous le souhaitez.
Vous avez le droit de modifier :
main.cpp;IPlayer.hpp;GfxPlayer.{cpp,hpp};TermPlayer.{cpp,hpp}.
Étape 2 : fermeture à tout moment
Pour cette étape vous devez faire en sorte que si on ferme la fenêtre d’une personne dont ça n’est pas le tour, la partie s’arrête quand même tout de suite et proprement.
La solution se trouve dans la capacité à demander à un IPLayer de jouer sans bloquer lors de l’attente. La boucle principale peut à chaque tour de cycle consulter chaque IPlayer pour voir s’il est encore actif avec sa méthode .done().
Dans le IPlayer actuellement on a la fonction get_move qui demande à jouer et qui bloque tant qu’il n’y a pas de coup qui a été fait.
Dans l’idéal on a besoin de quelque chose comme :
ask_for_movequi indique au joueur qu’il doit jouer ;get_movequi vérifie sans bloquer si le joueur à joué ;donequi permet de s’avoir si le joueur est toujours là ou si sa fenêtre à quitté.
Vous avez le droit de modifier :
main.cpp;IPlayer.hpp;GfxPlayer.{cpp,hpp};- votre propre classe d’exécution d’une partie si vous en avez une.
Étape 3 : inspirez ~ expirez
Si vous ne l’avez pas encore fait : assurez vous que votre programme, dans l’attente d’un évènement, ne se mette pas à utiliser 100% d’un coeur de CPU.
Faites patienter votre programme entre deux tours de boucles avec std::this_thread::sleep_for.
Étape 4 : TermPlayer asynchrone
En utilisant ce qu’on a vu lors du TD 1 • Lecture asynchrone rendez le TermPlayer asynchrone pour que lui non plus ne bloque pas lorsqu’on attend que son joueur choisisse quel coup faire.