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

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

Bonne façon de programmer ?


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    24
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 24
    Par défaut Bonne façon de programmer ?
    Bonsoir à tous !

    Après quelques galère entre mes classes qui doivent pointer chacune vers les autres, je me suis demandé si la façon suivante de programmer était correcte :

    Une classe principale gère la classe de traitement et la classe d'affichage.
    La classe de traitement contient des méthodes qui travaillent sur les objets.
    Idem pour la classe d'affichage.
    Les objets sont instanciés et initialisés par leur classe gérante respective, et
    tous les objets en quantité supérieure à 1 sont stockés dans un tableau statique de leur classe, ce qui donne l'accès aux objets partout dans le programme.

    Est-ce propre de programmer comme cela ?

    Merci d'avance !

  2. #2
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,

    Tu en dis un peu trop peu sur ton projet pour pouvoir te répondre efficacement, mais, de manière générale, tu dois absolument essayer de respecter le principe de la responsabilité unique.

    Tu sembles donc bien parti en décidant déjà de séparer la responsabilité du traitement de celle de l'affichage, mais tu devrais également séparer celle de la gestion des différents objets.

    En outre, tu devrais sans doute également essayer de séparer la responsabilité du traitement (ou de l'affichage) des différents types d'objets que tu manipule.

    De plus, la manière dont tu présente les choses laissent à penser que tu envisage, peut être, de passer par ce qui pourrait être considéré comme un "god objet": une classe de base dont héritent de manière directe ou indirecte l'ensemble de tes classes "métier".

    Or, si cette pratique semble intéressante, il faut être conscient qu'elle tend à prendre certaines libertés plus ou moins indues par rapport au LSP (au principe de substitution de Liskov), et qu'elle te place au final dans une situation où tu obtiens un projet lourd, monolithique, très difficile à maintenir et à faire évoluer.

    Je ne sais pas quel est le niveau de stabilité des besoins de ton projet, mais il faut se rappeler le fait que les besoins évoluent sans cesse, et que cela se traduit immanquablement par des évolutions au niveau de la conception, qui devront bien être implémentées .

    Plus tu arrivera à créer des petites hiérarchies indépendantes des autres, plus tu auras de chances de pouvoir faire évoluer l'ensemble de ton projet, y compris dans des directions auxquelles tu n'auras pas pensé jusqu'à présent.

    A l'inverse, plus tu auras de grosses hiérarchies monolithiques ou inter dépendantes, moins il te sera possible d'envisager les évolutions sans être tenté de "tout casser" pour pouvoir les intégrer

    Tu es donc sans doute sur la bonne voie, mais il me semble vraiment intéressant de pousser tes raisonnements beaucoup plus loin

    Au fait, ceci s'apparente beaucoup plus aux bonnes pratiques de conception qu'aux bonne pratiques de programmation... mais ce n'est qu'un détail
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    24
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 24
    Par défaut
    Merci pour ta réponse !

    Alors en fait c'est beaucoup moins gros que ça comme projet.

    C'est un projet d'algorithmique de 1ère année d'école d'ingénieur.

    Le sujet est de créer un jeu de dames, avec une IA.

    J'en suis venu à la conclusion de créer des tableaux d"objets statiques pour les raison suivantes :
    • J'ai besoin de pouvoir gérer les évènements graphiques dans la classe de traitement, et comme les cases graphiques sont des éléments pointés, mais ne pointe vers rien, je n'ai pas trouver comment revenir en arrière sans passer par du statique.
    • Les classes doivent pouvoir interagir entre elles simplement.
    • Les objets sont en quantité finie et connue à la compilation.


    D'après ta réponse, assez complèxe pour mon niveau, j'en déduis donc que ce n'est pas la meilleure façon de coder dans une perspective d'évolution, mais que ce n'est pas non plus mal vu.

    Est-ce bien ça ?

    Merci encore !

  4. #4
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    AAAAhhhh... l'éternel problème du jeu de dames ou d'échecs...

    Le problème est que ta manière d'envisager les choses dénote surtout un problème de conception :

    Reprenons, si tu veux bien depuis le début.

    Un jeu de dame, c'est quoi C'est un jeu de société qui se joue à deux sur un plateau composés de 64 cases: 32 blanches et autant de noirs alternées.

    Chaque joueur dispose, à la base, de 12 pions placés de part et d'autres du damier sur les cases blanches exclusivement.

    Chaque joueur doit déplacer un pion selon un nombre limité de règles à son tour en essayant de prendre les pions de l'adversaire.

    Le perdant est celui qui n'a plus de pion.

    On pourrait croire que la pièce maitresse du système est le pion, or il n'en est rien, car c'est plutôt la partie:

    C'est en effet la partie qui :
    1. (ré) initialise le tout
    2. gère les deux joueurs (dont une IA éventuelle)
    3. gère le damier
    4. gère la fin de partie
    5. s'adresse à un "système indépendant" d'affichage qu'il doit tracer le joli damier à l'écran.

    La seule responsabilité du joueur est donc de gérer ses propres pions, ce qui implique de:
    • leur demander leur position
    • leur demander s'ils peuvent aller vers une position donnée
    • prendre la décision de les déplacer vers une position donnée
    Il devra "juste" pouvoir signaler à la partie qu'il a fini son travail, de manière à ce que celle-ci puisse demander au système d'affichage d'actualiser l'affichage

    La seule responsabilité du damier est... de gérer les différentes cases, et donc:
    • de leur demander si ce sont des cases valides (seules les cases blanches répondent oui à cette question )
    • de leur demander s'il y a un pion sur elles, et, le cas échéant, la couleur et l'état de celui-ci
    • leur indiquer que l'on place (ou que l'on retire) un pion d'une couleur donnée dessus

    La responsabilité du pion se limite à gérer sa position (attention: je parle de position, et non de case du damier), à savoir
    • être capable d'indiquer s'il est sur une position valide (oui s'il est toujours sur le damier, non s'il a été "mangé")
    • être capable de déterminer s'il peut aller sur une autre position (il aura besoin du damier pour ce faire)
    • être capable de... se déplacer (comprend: on doit pouvoir lui indiquer une nouvelle position)

    Il dispose en outre de deux qualités intrinsèques:
    • Sa couleur, qui détermine le sens général du déplacement qu'il peut entreprendre
    • son "état" (pion simple ou reine) qui détermine les règles de déplacement (il peut sauter plusieurs cases ou non)

    La seule responsabilité de la case, c'est de savoir s'il y a un pion sur elle.

    Si c'est le cas, elle doit pouvoir en indiquer la couleur et l'état.

    Elle dispose en outre d deux qualité intrinsèques qui sont:
    • sa position
    • sa "validité", représentée par sa couleur


    Enfin, il y a le système d'affichage, qui est totalement indépendant (par rapport au reste)

    Sa responsabilité est d'actualiser l'affichage à la demande de la partie.

    Cela consiste en deux possibilités:
    • Actualiser le tracé du damier (il faudra donc que la partie le lui fournisse)
    • afficher un message indiquant le vainqueur
    Ah, j'ai failli oublier: il y a bien sur les concept de couleur (blanc et noir), d'état (pion / dame), et de position.

    Pour ce qui est de la couleur, nous pourrions très bien nous contenter d'un booléen où true représenterait le blanc (ou le noir) et false l'autre couleur, sauf que...

    Il faut prévoir une troisième possibilité: le fait que ce ne soit pas une couleur

    Il en ira d'ailleurs de même avec l'état de la pièce, où les deux choix auxquels nous pensons directement sont "pion" et "dame", mais où il faut envisager que ce ne soit pas une pièce
    J'imagine très bien ta réaction "comment ca, pas une couleur et pas une pièce ", et pourtant:

    Un des services que l'on attend de la case est de nous indiquer la couleur de la pièce qui se trouve dessus, mais... encore faut il qu'il y ait effectivement une pièce dessus

    Nous pouvons toujours forcer l'utilisateur à interroger la case dans un ordre précis, sous la forme de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    si (case a une pièce)
        si  pièce est blanche
            si pièce est un pion
            sinon (pièce est une dame)
            fin si
         sinon (pièce est noire)
         fin si
    fin si
    Mais il sera beaucoup plus facile de prévoir une valeur, tant pour la couleur que pour l'état du pion qui représente le fait que... il n'y ait pas de pion sur la case.

    Ainsi, si on veut simplement savoir si la pièce est une dame, il nous suffira de récupérer l'information d'état du pion qui se trouve dessus.

    Si nous obtenons l'information correspondant à "pas une pièce", il n'y a rien dessus, sinon, nous obtiendrons l'information correspondant à l'état réel de la pièce

    Et le principe est, bien évidemment, le même pour la couleur

    Nous aurions donc deux énumérations proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    enum Couleur
    {
        aucuneCouleur = 0,
        blanc,
        noir
    };
    enum Etat
    {
        aucunePiece = 0,
        pion,
        dame
    };
    Le concept de position enfin permet de représenter les coordonnées sur l'axe des X (colonnes du damier) et sur l'axe des Y (lignes du damiers).

    Grace à ces coordonnées, il est possible de déterminer si c'est une position valide (comprise entre 1 et 8 inclus tant sur l'axe des x que sur l'axe des y).

    Une position est, par nature, immuable: une fois qu'une position est créée, elle continue d'exister avec les coordonnées que nous lui avons données. Si on change de position, nous devons... avoir une instance différente

    Cela se traduira sans doute par une classe proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Position
    {
        public:
            Position(int x, int y): x_(x),y_(y){}
            int x() const{return x_;}
            int y() const{return y_;}
            bool estValid() const
            {
                return x>0 && x<9 && y>0 && y<9;
            }
        private:
            int x;
            int y;
    };
    La case sera composée de tout ce qu'il faut pour:
    déterminer si elle est accessible (grace à sa couleur)
    récupérer sa position sur le damier
    indiquer si elle est couverte par un pion, et, le cas échéant, le l'état et la couleur de celui-ci.

    Nous aurions donc quelque chose ressemblant à:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    class Case
    {
        public:
        /* La position d'une case permet d'en déterminer sa couleur...
         * nous allons donc simplement construire la case en lui indiquant
         * ses coordonnées et les informations relatives à la pièce qui se
         * trouve dessus. Par défaut, ces dernières informations représentent
         * le fait qu'il n'y a pas de pièce :D
         */
            Case(int posx, int posy, Piece * p= NULL): position_(posx,posy),
            piece_(p)
            {
                piece_.couleur=cpiece;
                piece_.etat=epiece;
            }
           /* il faudra un peu chipoter pour déterminer la couleur de la case,
            * mais la simple logique peut suffire:
            * si la position est invalide, ce n'est pas une couleur, autrement
            * si la formule (y-1)*8 + (x-1) donne une valeur paire, c'est une
            * case noire, sinon, c'est une case blanche :D
            */
            Couleur couleur() const
            {
                if(!position_.isValid())
                    return aucuneCouleur;
                if( (y-1)*8+x-1%2 == 0)
                    return noir;
                 return blanc;
            }
            /* il faut savoir si on peut poser une pièce sur la case...
             * on ne peut le faire que si la couleur de la case est blanc
             */
            bool estValide() const{return couleur==blanc;}
            /* il faut pouvoir savoir s'il y a une pièce, sa couleur et son état
            */
            bool estCouvert() const{return piece_!=0;}
            Couleur couleurPiece() const
            { 
                if(!piece_)
                    return aucuneCouleur;
                return piece_->couleur();
            }
            Etat etatPiece() const
            { 
                if(!piece_)
                    return aucunePiece;
                return piece_->etat();
            }
            /* et il faut pouvoir modifier la présence (ou l'absence) de piece
             */
            void placerPiece(Piece * p=NULL)
            {
                /* on refuse de placer une pièce s'il y en a déjà une :D */
                assert( (piece_==NULL && p!=NULL) || 
                        (piece_!=NULL && p==NULL) );
                piece_=p
            }
            /* c'est vraiment pour l'expressivité :D */
            void retirerPiece(){placerPiece();}
        private:
            Position position_;
            Piece * piece_;
    };
    Les pièces sont un peu particulières, dans le sens où il y a deux types de pièces bien précis, respectant des règles différentes .

    Nous pourrions donc nous dire que "une pièce est une pièce" et décider de tout baser sur la simple logique de "si l'état est un pion, faire, sinon, faire parce que c'est une dame", mais cela va complexifier inutilement le code

    Le plus facile est, selon moi, de gérer les pièces de manière polymorphes, car nous allons leur demander de faire exactement la même chose, bien que la manière dont elle le feront dépendra de leur état.

    Nous aurions donc une hiérarchie proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    class Piece
    {
        public:
            Piece(Couleur c, int posx, int posy):couleur_(c), position_(posx,posy)
            {
                /* quand on crée une pièce, sa position doit être valide ;) */
                assert(position_.estValide());
            }
            /* pour assurer la destruction polymorphe ;) */
            virtual ~Piece(){}
            /* les comportement qui ne changent pas */
            Couleur couleur() const{return couleur_;}
            Position const & position(){return position_;}
            bool estEnJeu() const {return position_.estValide();}
            void tuEsPris() {moveTo();}
            void moveTo(Position const & newPos=Position(0,0) )
            {position_=newPos;}
            /* et ceux qu'il faudra adapter à la situation */
            virtual Etat etat() const = 0;
            virtual bool peutAllerSur(Damier const&, Position const &) const;
    };
    class Pion : public Piece
    {
        /*redéfinition des fonctions membres qui en ont besoin */
    };
    class Dame : public Piece
    {
       /* redéfinition des fonctions membres qui en ont besoin */
    };
    Les règles relatives aux déplacements et aux prises seront implémentée dans les fonction peutAllerSur deplaceToiVers


    Tu auras, de même, deux types de joueurs particuliers: le joueur "humain" et le joueur "non humain" (l'AI).

    Ils disposeront tous les deux d'un nombre identiques de pions à la base, positionné en fonction du coté du damier qui est le leur.

    Quand c'est leur tour, on leur transmet le damier en paramètre, afin qu'ils puissent le transmettre aux pions qu'ils envisagent de déplacer (ils en ont besoin pour déterminer s'ils peuvent effectivement bouger vers une position donnée ).

    Par contre, la logique qui sous tend à leur tour est différente: le joueur "humain" utilisera les introductions du joueur pour déterminer ce qu'il fait, alors que l'AI devra... évaluer le meilleur coup

    Une fois qu'ils ont déterminé le pion qu'ils veulent jouer et la position à laquelle ils souhaitent le placer, ils demanderont au damier d'effectuer le déplacement, en indiquant s'il échoit aux pièces au dessus desquelles on passe qu'elle sont prises

    Une fois qu'ils ont fini leur coup, il ne leur reste plus qu'à le signaler à la "Partie"

    La partie va donc créer un damier, deux joueurs (selon les demandes de l'utilisateur) et, pourquoi pas, un gestionnaire d'affichage.

    Elle s'occupera de (ré) initialiser le damier et les joueurs en début puis basculera d'un joueur à l'autre chaque fois qu'elle recevra le signal "fini de jouer".

    Lorsqu'elle recevra ce signal, elle évaluera si la partie est terminée et, selon le cas, demandera au gestionnaire d'affichage de retracer le damier (qu'elle devra passer en paramètre évidemment ) ou... d'afficher le vainqueur

    Mais je vais essayer de te laisser un peu te débrouiller quand même, et puis, j'ai déjà écrit un véritable roman...

    C'est donc à toi de jouer
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  5. #5
    Membre averti
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    24
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2006
    Messages : 24
    Par défaut
    Waou, ça c'est de la réponse.

    J'ai pas tout compris, mais ça m'éclaire un peu ^^

    Je vais te dire ce que j'ai compris :
    - Les modifications de case s'effectue via la classe qui gère la grille.
    - Les déplacements de pièces sont calculés au niveau des classes Pion et Dame (en bout de chaine donc), avec la grille passée en paramètre.
    - La classe principale gère les tours.

    Parcontre je n'ai pas compris comment passer de l'évènement graphique à la pièce ?

    Je te remercie pour ton roman ^^

  6. #6
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    En fait, tu n'as absolument pas besoin de passer l'événement graphique à la pièce: tu transmet la grille à ton affichage, et il se débrouille

    Pour savoir ce qu'il doit afficher, il dispose de l'interface de la grille, qui lui dira exactement ce qu'il doit afficher et où cela

    Tu aurais donc quelque chose comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Game
    {
        public:
            /* permet d'indiquer à la partie qu'il est temps d'actualiser l'affichage 
             */ 
            void needRedraw()
            {
                drawer.draw(plate);
            }
        private:
            Gamer * first; // le premier joueur (on ne sais pas si c'est un humain
                           // ou si c'est l'IA)
            Gamer * second; // le deuxième joueur (pareil :D )
            Drawer drawer; // le gestionnaire d'affichage
            Plate plate; // le damier proprement dit
            /* un moyen quelconque de déterminer si la partie continue */
    };
    A partir de là, le gestionnaire d'affichage va tester l'état de chaque case (il faut, bien sur, fournir dans la classe Plate le moyen d'y accéder, de préférence directement (pour respecter Demeter) ), à savoir: sa couleur, la couleur de la pièce qui se trouve dessus (s'il y en a une) et son état (s'il y en a une aussi ), sans doute ligne par ligne, et il aura toutes les infomations qui lui sont nécessaires pour afficher le damier, et toutes les pièces qui se trouvent dessus .

    Maintenant, il t'appartient de déterminer le meilleur moment pour appeler cette fonction needRedraw (ou la fonction draw du gestionnaire d'affichage, mais cela risque de placer une dépendance bien mal venue ) .

    Mais le plus facile est sans doute d'intégrer cet appel dans la boucle principale, sous une forme proche de:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void Game::play()
    {
        Gamer * current=first;
        while(! finished()) // à définir toi même :D
        {
            first->yourTurn(plate); // on attend que le joueur courent joue
            needRedraw(plate); // on demande l'actualisation de l'affichage
            current=(current==first ? second:first); // on change le joueur courent
        }
    }
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. [AJAX] Bonne façon de programmer avec Ajax ?
    Par kayoum dans le forum AJAX
    Réponses: 2
    Dernier message: 05/10/2010, 16h47
  2. [Installation] PHP et CVS : la bonne façon de travailler ?
    Par deuspi dans le forum CVS
    Réponses: 11
    Dernier message: 22/03/2007, 09h30
  3. [Tableaux] Améliorer ma façon de programmer
    Par Invité dans le forum Langage
    Réponses: 11
    Dernier message: 28/11/2006, 17h56
  4. [Checkbox] La bonne façon et comment faire
    Par Meewix dans le forum Langage
    Réponses: 9
    Dernier message: 19/10/2006, 09h23
  5. Listeners: bonne manière de programmer?
    Par ®om dans le forum Langage
    Réponses: 8
    Dernier message: 26/07/2006, 22h42

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo