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.