A. Architecture globale▲
A-I. Architecture en couches▲
Si on veut pouvoir séparer les différents aspects du jeu, il va nous falloir une architecture en couches. Le jeu sera donc organisé en 3 couches : la couche réseau, la couche de manipulation des données et la couche d'affichage. On peut même voir cette architecture comme l'architecture OSI d'un réseau : les cinq premières couches correspondent à la couche réseau (on se base sur la couche 5 : socket), la couche 6 correspond à la couche de manipulation des données et la dernière couche (7, présentation) s'occupe de l'interface graphique. Il suffira de remplacer une couche par une autre pour changer un aspect du jeu. Par contre nous avons une contrainte : les couches doivent être définies comme des interfaces implémentées par les versions des couches.
Je vais détailler la création de deux versions de la couche de présentation : une version en utilisant SDL et une version utilisant Irrlicht.
Voici le schéma de notre architecture :
La réflexion doit se faire sur la manipulation des données du jeu et non sur la manière de les afficher. Il faut déterminer tous les états dans lesquels le jeu pourra se trouver. La couche de présentation permettra les opérations suivantes :
- choisir si le joueur veut créer une partie réseau ou bien en rejoindre une ;
- positionner les bateaux sur la grille de jeu ;
- choisir la case de la grille à attaquer ;
- afficher le résultat du jeu.
La classe abstraite pour notre couche de présentation devra donc proposer ces différentes méthodes. Et elles seront implémentées dans les classes filles. Ainsi, toute classe implémentant ces méthodes pourra être utilisée comme couche de présentation. On pourra tout à fait imaginer une couche qui gère tout par des fichiers et qui n'affiche rien. Ça n'influera pas le moins du monde.
A-II. Écriture abstraite du jeu▲
Le jeu doit pouvoir être conçu sans avoir à tenir compte de la couche de présentation. Tout ce qu'on a le droit de faire, c'est d'appeler les méthodes virtuelles de l'interface.
créer la couche de présentation comme dérivée de l'interface
choisir le mode client ou serveur
chacun positionne ses bateaux sur sa grille
selon ce mode client ou serveur, créer ou rejoindre une partie
tant que les deux joueurs ont encore des bateaux
lancer le jeu, avec des attaques à tour de rôle entre les joueurs
afficher le résultat perdu ou gagnéA-II-1. Classe abstraite de la couche de présentation▲
Voici la description de l'interface pour la couche de présentation :
class Gfx{
public:
virtual int choose_client_server() = 0;
virtual int positionner_bateaux(Flotte&) = 0;
virtual std::pair<int,int> choisir_attaque(Essais, Flotte)=0;
virtual void affiche_gagne()=0;
virtual void affiche_perdu()=0;
virtual void update(Essais, Flotte)=0;
virtual ~Gfx(){}
};On remarquera que nulle part il n'est question de SDL ou de Irrlicht. C'est une classe abstraite. Les liens avec la couche inférieure (la couche de manipulation des données) sont faits par les passages de paramètres de type Essais et Flotte.
A-II-2. Classes de gestion des données▲
Les deux classes Flotte et Essais sont les classes permettant de gérer toutes les données du jeu. Flotte gère tous les bateaux du joueur, leur position, leurs dégâts. Tandis que Essais gère ce que le joueur a déjà attaqué chez l'adversaire et ce que l'adversaire a déjà essayé chez le joueur.
Le code de ces deux classes est très classique, il ne contient que de la manipulation et du stockage d'informations :
class Flotte{
public:
std::vector<Bateau> _lb;
void placer_aleatoirement();
int get_effectif();
int get_effectif_depart();
private:
int effectif_depart;
};La classe Flotte contient une fonction intéressante : placer_aleatoirement. Toujours dans un souci de simplicité je place les bateaux aléatoirement sur la grille. On se contente de chercher un arrangement des bateaux de telle sorte qu'ils ne sortent pas de la grille et qu'ils ne se chevauchent pas.
La classe Bateau est toute aussi triviale. Elle stocke la position, l'orientation, la taille et les dégâts d'un bateau ainsi que les fonctions associées. J'ai choisi de ne stocker que la position du début d'un bateau. La taille et l'orientation nous permettent de déduire les autres positions.
class Bateau{
public:
Bateau();
Bateau(std::string&, std::pair<int,int>, int, bool);
// touché ou dans l'eau ?
bool isHit(std::pair<int,int>&);
// modifie un point du bateau. si tout est touché, on renvoie true = coulé
bool setDegats(std::pair<int,int>&);
// accesseurs, modificateurs
// ...
private:
std::string _nom;
std::pair<int,int> _pos1;
// pour se souvenir de l'orientation du bateau
bool _horizontal;
int _taille;
std::vector<bool> _degats;
};La classe Essais est un conteneur de tableaux, rien de très extraordinaire :
class Essais{
public:
Essais();
// renvoie le nombre de coups au but
int get_effectif_adverse();
// renvoie un booléen pour savoir si un pair est dans la map
bool is_in(const std::string&, std::pair<int,int>&);
// ajoute un élément dans un vector de la map
void add(const std::string&, std::pair<int,int>&);
// renvoie la taille d'un vector
int get_size(const std::string&);
// renvoie un élément de l'un des vectors
std::pair<int,int> get(const std::string&, int);
private:
// les données. plusieurs vectors référencés par leur nom
std::map<std::string, std::vector< std::pair < int,int > > > _d;
};Je vous fais grâce du code de ces fonctions, tout se retrouve dans l'archive contenant les fichiers sources.
Avec simplement ces classes, on peut écrire tout le jeu (du moins les couches manipulation des données et présentation).
A-II-3. Écriture un peu moins abstraite du jeu▲
Je vous remets le schéma général du jeu :
créer la couche de présentation comme dérivée de l'interface
choisir le mode client ou serveur
chacun positionne ses bateaux sur sa grille
selon ce mode client ou serveur, créer ou rejoindre une partie
tant que les deux joueurs ont encore des bateaux
lancer le jeu, avec des attaques à tour de rôle entre les joueurs
afficher le résultat perdu ou gagnéSi on met ça en C++, ça va nous donner quelque chose comme :
// création de la couche de présentation
Gfx *ma_gui = new IrrInterface; // ici une interface Irrlicht
// choix client ou serveur
int server = ma_gui->choose_client_server();
switch(server){
case 0:{ // serveur
Server s;
// création du serveur, attente du client, échange d'informations
// ...
// le serveur attend que le client ait positionné ses bateaux
s.wait();
// le serveur positionne ses bateaux sur sa grille
if (ma_gui->positionner_bateaux(f)==1){ // on souhaite quitter
break;
}
// déroulement du jeu, détaillé par la suite
// ...
// quelqu'un a perdu, on affiche un écran
// if perdu
ma_gui->affiche_perdu();
//else
ma_gui->affiche_gagne();
break;
}
case 1:{ // client
Client c;
// création du client, connexion au serveur, échange d'informations
// ...
// le client positionne ses bateaux sur sa grille
ma_gui->positionner_bateaux(f);
// le client informe le serveur qu'il a placé ses bateaux
c.ok();
// déroulement du jeu, détaillé par la suite
// ...
// quelqu'un a perdu, on affiche un écran
// if perdu
ma_gui->affiche_perdu();
//else
ma_gui->affiche_gagne();
break;
}
case 2 : // quitter
break;
}
delete ma_gui;Bien, arrivés ici, le jeu tourne (enfin, sur le papier), les données sont correctement mises à jour (dans la classe de manipulation des données - Flotte et Essais), on peut maintenant s'occuper un peu du réseau pour gérer la couche basse.



