IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Réalisation d'un jeu de bataille navale en C++


précédentsommairesuivant

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 :

Image non disponible

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.

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
    // 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.


précédentsommairesuivant

Copyright © 2006 Pierre Schwartz. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.