TD 5 • Lobby
Cartouche
| Champ | Valeur |
|---|---|
| Auteur·e | Élise |
| Édition | 2024-06-04 |
| Durée | 2 séances |
| Taille des équipes | 1~2 personnes login des auteur·rice·s dans author.txt, séparés par des retours à la ligne |
| Rendu | via git, dépôt 2023_morpion, branche td5, droits en lecture à delivery_collector |
Préambule
Notation
Introduction
Notre objectif final pour le projet Morpion c’est de gérer un nombre variable et virtuellement infini de parties en simultané, là où dans le TD 4 • Arène asynchrone nous en avions un nombre fixe.
Pour ce faire on va rendre asynchrone la dernière pièce du puzzle : la partie acceptation d’une connexion du StandaloneNetCLient. Pour le moment cette classe bloque à la construction ce qui n’est pas un comportement désirable si on veut pouvoir accepter les connexions en même temps que d’autres parties ont lieu.
NetPlayerFactory
La fabrique (factory method) est un patron de conception créationnel utilisé en programmation orientée objet. Elle permet d’instancier des objets dont le type est dérivé d’un type abstrait. La classe exacte de l’objet n’est donc pas connue par l’appelant.
Plusieurs fabriques peuvent être regroupées en une fabrique abstraite permettant d’instancier des objets dérivant de plusieurs types abstraits différents.
Implémentez la fonction membre et le constructeur de la classe suivante :
class NetPlayerFactory {
// at construction, start binding and listening on a port
NetPlayerFactory(int port);
// checks if a connexion can be accepted using a sf::SocketSelector, if there is one
// create a StandaloneNetPlayer using the resulting sf::TcpSocket and return its address
// otherwise return nullptr
std::unique_ptr<IPlayer> create_one();
};
La fonction create_one doit absolument être non-bloquante.
Vous avez besoin de modifier votre StandaloneNetPlayer de sorte qu’il puisse être construit en utilisant une sf::TcpSocket déjà construite est connectée à un pair.
Boucle principale du serveur
Implémentez la boucle principale pour le mode serveur de votre programme, elle doit permettre à des joueurs de ce connecter à tout moment et dès que deux d’entre eux sont connectés ils doivent être mis dans une arène pour jouer. Lorsqu’une partie est finie vous pouvez détruire son arène et ses joueurs.
Exemple d’une boucle principale pour un serveur (qui part du principe que tous les IPlayer sont dans des unique_ptr).
int main(void)
{
std::vector<GameArena> arenas;
std::vector<std::unique_ptr<IPlayer>> _players;
NetPlayerFactory factory{1234};
while (1) {
std::for_each(arenas.begin(), arenas.end(), [](GameArena &arena) { arena.cycle_once(); });
auto new_player = factory.create_one();
if (new_player) {
_players.push_back(std::move(new_player));
// if we have an even number of player after the push back
// we can add the last 2 to a new game
if (_players.size() % 2 == 0) {
int last_idx = _players.size() -1;
arenas.emplace_back(_players[last_idx - 1], _players[last_idx]);
} else {
// tell the new player we are waiting for an oponent
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
Ptite revanche?
Implémentez la possibilité de faire des revanche à l’issue d’une partie. Il s’agit d’ajouter au IPlayer une fonction pour envoyer la demande et une seconde pour vérifier si on a une réponse.
Les TermPlayer, GfxPlayer et NetPlayer doivent tous implémenter ces fonctions membres pour afficher à l’écran ou envoyer sur le réseau la demande puis prévoir un mécanisme pour recevoir la réponse (à la façon dont est géré la demande de move.
Épreuve par le feu
Si vous êtes arrivés jusque là, soumettez-vous à l’épreuve par le feu : démarrez votre serveur de jeu et demandez à tous vos camarade de s’y connecter. Ils auront besoin de votre binaire et de le lancer en mode client pour pouvoir jouer.