Partage
  • Partager sur Facebook
  • Partager sur Twitter

Conception jeu de Tic Tac Toe en OO

    7 juillet 2015 à 19:39:31

    Bonjour, Tout d'abord merci de lire ce post. J'ai commencé la conception d'un jeu de morpion (Tic Tac Toe) en essayant de respecter le plus possibles les principes du SOLID + l'encapsulation + la loi de Déméter (cette dernière que je n'ai pas trop bien compris comme le 'I' et le 'D' de SOLID) dont j'ai trouvé les définitions sur ce site. J'utilise l'IDE Visual Studio 2015 RC avec le compilateur qui va avec (l'un des plus à jours d'après certains de ce forum). Mes questions se portent donc sur la conception et notamment sur ces points :

    • Les nom des fonctions, j'ai essayé de faire de l'anglais au prix, je crois, que le nom de certaines fonctions ne respectent pas leur sémantique.
    • Certaines fonctions font peut-être trop de choses (je pense plus particulièrement à TicTacToeShowGrid::showWinner()).
    • Si cela à du sens d'utilisé une classe TicTacToeGrid qui s'occupe de la grille et du jeu mais à "bas niveau" et une classe TicTacToeShowGridqui s'occupe de l'affichage et des saisies utilisateurs (plutôt "haut niveau").
    • Et d'autres remarques que vous avez à faire.

    Je souhaiterais ensuite rajouter une IA mais je n'ai absolument aucune foutre couille idée de comment faire. Ajouter une fonction qui utiliserait des noms pour les joueurs et une qui ferait en sorte que le joueur qui commence est choisi au hasard. J'attend cependant de voir si cette partie du code marche. EDIT : En fait je sais qu'elle marche puisque je l'ai testé mais c'est pour voir si c'est bien fait. Place au code (s'il est trop long dites le je me ferais un compte GitHub :p ) :

    Le main.cpp :

     #include "TicTacToeShowGrid.h"
    

    int main() {

    std::cout << "Bienvenue dans le TicTacToeGame !\n";
    TicTacToeShowGrid game{};
    while (game.play() != END)	//Boucle de jeu
    	game.play();
    
    return 0;
    

    }

    </pre>

    Le Matrice.h : Classe Template donc sans .cpp

    #ifndef MATRICE_H
    

    define MATRICE_H

    include <vector />

    include <cassert />

    //Classe template Matrice, tableau bidimensionnell

    template<typename t=""> class Matrice { public :</typename>

    //Constructor :
    Matrice(std::size_t h, std::size_t w, T const& t = T{});
    //Member function :
    std::size_t height() const;	//retourn la hauteur du tableau
    std::size_t width() const;	//Retourne la largeur du tableau
    void assign(T const& t);	//Assigne  des valeurs à l'ensemble du tableau m_data
    
    							//Surcharge de l'opérateur () pour accéde aux éléments du vecteur comme si c'était un tableau bidimensionnell
    T& operator()(std::size_t row, std::size_t column);
    T operator()(std::size_t row, std::size_t column) const;	
    

    private :

    //Attributs :
    std::size_t m_height;	//Hauteur du tableau
    std::size_t m_width;	//Largeur du tableau
    std::vector<T> m_data;	//Vecteur contenant les données
    

    };

    endif //MATRICE_H

    template<typename t=""> Matrice<t>::Matrice(std::size_t h, std::size_t w, T const & t = T{})</t></typename>

    : m_height{ h }, 
      m_width{ w },
      m_data{ std::vector<T>(h * w, t)}
    

    {}

    template<typename t=""> std::size_t Matrice<t>::height() const {</t></typename>

    return m_height;
    

    }

    template<typename t=""> std::size_t Matrice<t>::width() const {</t></typename>

    return m_width;
    

    }

    template<typename t=""> void Matrice<t>::assign(T const& t) {</t></typename>

    m_data.assign(m_height * m_width, t);
    

    }

    template<typename t=""> T& Matrice<t>::operator()(std::size_t row, std::size_t column) {</t></typename>

    assert(row < m_height && column < m_width && "Out of range");	//assert si l'on est sorti des limites du tableau
    return m_data[row * m_width + column];
    

    }

    template<typename t=""> T Matrice<t>::operator()(std::size_t row, std::size_t column) const {</t></typename>

    assert(row < m_height && column < m_width && "Out of range");	//assert si l'on est sorti des limites du tableau
    return m_data[row * m_width + column];
    

    }

    </pre> enum_project.h : Dossier des 2 enum
    #ifndef ENUM_PROJECT
    

    define ENUM_PROJECT

    enum contentCase { //Enum sue ce que peuvent contenir les cases d'un plateau !

    EMPTY,	//Vide,
    CROSS,	//Une croix,
    SHAPE	//Un cercle
    

    };

    enum game { //Enum sur l'état du jeu. Il est soit :

    END,	//Finis,
    PLAYING	//En cours, encore en train de jouer (d'où le -ING)
    

    };

    endif //ENUM_PROJECT

    </pre>

    Le TicTacToeGrid.h :

    #ifndef TIC_TAC_TOE_GRID_H
    

    define TIC_TAC_TOE_GRID_H

    include "Matrice.h"

    include "enum_project.h"

    include <string />

    include <memory />

    class TicTacToeGrid { public :

    //Constructor :
    TicTacToeGrid(std::shared_ptr<Matrice<enum contentBox>> gridPtr);
    //Members Functions :
    void reset();											//Met toutes les cases à Empty
    void putCross(std::size_t raw, std::size_t column);		//Place une croix à la case indiquée
    void putShape(std::size_t raw, std::size_t column);		//Place un cercle à la case insiquée
    std::string showGrid() const;							//Retourne un string représentant la grille de morpion
    

    private :

    //Member Function :
    char analyseBox(std::size_t, std::size_t column) const;  //Name?	//Fonction qui retourne un caractère correspondant à ce qui occpe la case : CROSS: 'X', SHAPE: 'O', EMPTY: ' '
    																	//Utilisé par la fonction showGrid()
    //Attributs :
    std::shared_ptr<Matrice<enum contentBox>> m_grid;			//Pointeur sur une matrice de type "contentCase" (EMPTY, CROSS, SHAPE)
    														
    

    };

    endif //TIC_TAC_TOE_GRID_H

    </pre>

    Le TicTacToeGrid.cpp :

    #include "TicTacToeGrid.h"
    

    TicTacToeGrid::TicTacToeGrid(std::shared_ptr<Matrice<enum contentbox="">> gridPtr)</enum>

    : m_grid{ gridPtr }	//Construit la matrice du morpion 3*3 toutes les cases EMPTY
    

    {}

    void TicTacToeGrid::reset() { //reset le morpion = met tout à EMPTY : pas utiliser pour l'instant

    m_grid->assign(EMPTY);
    

    }

    void TicTacToeGrid::putCross(std::size_t raw, std::size_t column) { //Met une croix aux coos indiqués

    (*m_grid)(raw, column) = CROSS;
    

    }

    void TicTacToeGrid::putShape(std::size_t raw, std::size_t column) { //Met un cercle aux coos indiqués

    (*m_grid)(raw, column) = SHAPE;
    

    }

    std::string TicTacToeGrid::showGrid() const { //Retoune un string représentant la grille de morpion ex :

    std::string strGrid{};										//+-+-+-+
    for (auto i = 0u; i < m_grid->height(); i++) {				//| |X| |
    	strGrid += "\n+-+-+-+\n|";								//+-+-+-+
    	for (auto j = 0u; j < m_grid->width(); j++) {			//|O|X| |
    		strGrid += analyseBox(i, j);						//+-+-+-+
    		strGrid += "|";										//| |O| |
    	}														//+-+-+-+
    }
    strGrid += "\n+-+-+-+\n";
    
    return strGrid;
    

    }

    char TicTacToeGrid::analyseBox(std::size_t raw, std::size_t column) const { //Retourne un caractère pour une ce qui occupe une case

    switch ((*m_grid)(raw, column)) {
    case EMPTY :
    	return ' ';
    case CROSS :
    	return 'X';
    case SHAPE :
    	return 'O';
    default :
    	return 'Z';
    
    }
    

    }

    </pre> Le Turn.h :
    #ifndef TURN_H
    

    define TURN_H

    include "enum_project.h"

    include <random />

    class Turn { public :

    //Constructor :
    Turn();
    //Members Functions :
    void next();									//Passe au tour suivant (incrémente m_turn + change m_playerTurn
    enum contentCase playerTurn() const;			//Retourne quel est le joueur qui doit jouer sous la forme CROSS ou SHAPE
    unsigned int turn() const;						//Retourn à quel tour on est
    

    private :

    //Member function :
    void whoStart();					//Non encore implémenté (générerea aléatoirement un joueur qui commencera
    //Attributs :
    enum contentCase m_playerTurn;
    unsigned int m_turn;
    

    };

    endif //TURN_H

    </pre>

    Le turn.cpp :

    #include "Turn.h"
    

    Turn::Turn()

    : m_turn{}
    

    {

    m_playerTurn = CROSS;
    

    }

    void Turn::next() {

    if (m_playerTurn == CROSS)			//Inverse les joueurs
    	m_playerTurn = SHAPE;			//Si CROSS alors m_playerTurn devient SHAPE
    else if (m_playerTurn == SHAPE)		//Si SHAPE alors m_playerTurn devient CROSS
    	m_playerTurn = CROSS;
    m_turn++;							//Incrémente le tour
    

    }

    enum contentCase Turn::playerTurn() const {

    return m_playerTurn;
    

    }

    unsigned int Turn::turn() const {

    return m_turn;
    

    }

    </pre> Le Referee.h :
    #ifndef REFEREE_H
    

    define REFEREE_H

    include <vector />

    include <memory />

    include "enum_project.h"

    include "Matrice.h"

    include "Turn.h"

    class Referee { public :

    //Constructor :
    Referee(std::shared_ptr<Matrice<enum contentBox>> gridPtr, std::shared_ptr<Turn> turnPtr);
    //Members functions :
    bool isBoxTaken(unsigned int const& choice);	//Name ?		//Vérifie si la case indiquée est déjà occupée renvoie "true" si oui et "false" si non + ajoute au vecteur m_choices si non
    enum contentBox winnerIs() const;				//Name ?		//Retourne le gagnant sour cette forme : CROSS si ce sont les croix, SHAPE si ce sont les cercles, EMPTY si ce n'est personne
    bool Referee::isFinish() const;									//Renvois "true" si la partie est finis "false" sinon
    

    private :

    //Attributs :
    std::vector<unsigned int> m_choices;							//Vecteur des cases déjà utilisés
    std::shared_ptr<Matrice<enum contentBox>> m_grid;				//Pointeur sur la grille de morpion
    std::shared_ptr<Turn> m_turn;									//Pointeur sur un objet gérant le déroulement du jeu
    

    };

    endif //REFEREE_H

    </pre>

    Le Referee.cpp :

    #include "Referee.h"
    

    Referee::Referee(std::shared_ptr<Matrice<enum contentbox="">> gridPtr, std::shared_ptr<turn> turnPtr)</turn></enum>

    : m_choices{}
    , m_grid{ gridPtr }
    , m_turn{ turnPtr }
    

    {}

    bool Referee::isBoxTaken(unsigned int const & choice) { //Vérifie si la case saisie est déjà occupée

    	for (auto i : m_choices) {												//Parcours le vecteur "m_choices" pour vérifié si la case n'a pas déjà été jouée
    		if (choice == i)
    			return false;
    	}
    	m_choices.emplace_back(choice);											//Ajoute le choix du joueur au vecteur pour qu'il ne puisse plus être utilisé
    	return true;
    }
    

    enum contentBox Referee::winnerIs() const { //Renvois le gagnant

    	for (auto i = 0u; i < 2; i++) {
    		if ((*m_grid)(i, 0) == (*m_grid)(i, 1) && (*m_grid)(i, 0) == (*m_grid)(i, 2))			//Vérifie les lignes de la grille de morpion
    			return (*m_grid)(i, 0);																//Retourne CROSS, SHAPE ou EMPTY en fonct de ceux alignés
    		else if ((*m_grid)(0, i) == (*m_grid)(1, i) && (*m_grid)(0, i) == (*m_grid)(2, i))		//Vérifie les colonnes de la grille de morpion
    			return (*m_grid)(0, i);																//Retourne CROSS, SHAPE ou EMPTY en fonct de ceux alignés
    	}
    	if (((*m_grid)(0, 0) == (*m_grid)(1, 1) && (*m_grid)(0, 0) == (*m_grid)(2, 2)) || ((*m_grid)(0, 2) == (*m_grid)(1, 1) && (*m_grid)(0, 2) == (*m_grid)(2, 0))) //Vérifie les 2 diagonales
    		return (*m_grid)(1, 1);																	//Retourne CROSS, SHAPE ou EMPTY en fonct de ceux alignés
    	else
    		return EMPTY;
    }
    

    bool Referee::isFinish() const {

    if (m_turn->turn() > 8 || winnerIs() != EMPTY)
    	return true;
    else
    	return false;
    

    }

    </pre> Le TicTacToeShowGrid.h :
    #include "Referee.h"
    

    include "Matrice.h"

    include "enum_project.h"

    include <iostream />

    include <vector />

    include <memory />

    class TicTacToeShowGrid { public :

    //Constructor :
    TicTacToeShowGrid();
    //Member Function :
    enum game play();		//Fonction principal de jeu utilisé dans la game loop, Renvoi une Enum game : END ou PLAYING
    

    private :

    //Members Functions :
    void showGrid() const;											//Affiche la grille de morpion dans la console
    unsigned int takeChoice();				//Name ?				//Récupère le choix de l'utilisateur et le vérifie (fait appel à "isCaseTaken()")
    void putSymbol(unsigned int & choice);	//Name ?				//Utilise le choix de l'utilisateur pour placer soit une croix soit un cercle à la case indiquée 
    void showWinner() const;				//Name ?				//Affiche le gagnat dans la console
    //Attributs :
    std::shared_ptr<Turn> m_turn;  
    std::shared_ptr<Matrice<enum contentBox>> mainMatrice;			  //Matrice servant à initialiser "m_tttGrid" et "m_reference"
    Referee m_referee;												//Objet représentant l'arbitre
    TicTacToeGrid m_tttGrid;										//Objet TicTacToeGrid représentant la grille 													
    

    };

    endif //TIC_TAC_TOE_SHOW_GRID_H

    </pre>

    Le TicTacToeShowGrid.cpp :

    #include "TicTacToeShowGrid.h"
    

    TicTacToeShowGrid::TicTacToeShowGrid()

    : m_turn{ std::make_shared<Turn>() }
    , mainMatrice{ std::make_shared<Matrice<enum contentBox>>(3, 3, EMPTY)}
    , m_tttGrid{ mainMatrice }
    , m_referee{ mainMatrice, m_turn }
    

    {}

    enum game TicTacToeShowGrid::play() {

    showGrid();																//Affiche la grille de morpion
    
    if (m_referee.isFinish()) {													//Affiche le gagnant et termine la partie s'il y en a
    	showWinner();
    	return END;
    }
    
    else {
    	auto player = m_turn->playerTurn();									//Créée une enum contentCase sur le joueur qui doit jouer à ce tour
    
    	std::cout << "C'est au joueur " << player << " de jouer\n";
    	std::cout << "Choisissez ou cocher selon ce modele :\n";			//Affiche le modèle à respecter avec les touches à indiquer pour placer un(e) croix/cercle
    	std::cout << "+-+-+-+\n";
    	std::cout << "|1|2|3|\n";
    	std::cout << "+-+-+-+\n";
    	std::cout << "|4|5|6|\n";
    	std::cout << "+-+-+-+\n";
    	std::cout << "|7|8|9|\n";
    	std::cout << "+-+-+-+\n";
    
    	auto choice = takeChoice();											
    	putSymbol(choice);
    
    	m_turn->next();														//Passe au tour suivant
    
    	return PLAYING;														//Indique la partie continue
    }
    

    }

    //--------------------------------------------------------------------------PRIVATE--------------------------------------------------------------------------------------------\

    void TicTacToeShowGrid::showGrid() const { //Affiche la grille dans la console est utilisé par "play()"

    auto str = m_tttGrid.showGrid();
    std::cout << str;
    

    }

    unsigned int TicTacToeShowGrid::takeChoice() { //Récupère le choix du joueur est utilisé par "play()"

    unsigned int choice{};
    //Check the choice
    while (!(std::cin >> choice) || choice < 1 || choice > 9 || !m_referee.isBoxTaken(choice)) {			//Continuer tant que :  
    	if (std::cin.fail()) {																		//Le flux est invalide,
    		std::cout << "Saisie incorrecte, recommencez : \n";
    		std::cin.clear();
    		std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    	}
    	else if (choice < 1 || choice > 8)															//"choice" n'est pas compris entre 1 et 9 (pour correspondre à la grille)
    		std::cout << "Le nombre n'est pas inclut entre 1 et 9.\nRecommencez.\n";
    	else																						//La case indiquée par l'utilisateur est déjà occupé
    		std::cout << "Cette case est deje occupee." << std::endl;
    }
    
    return choice;
    

    }

    void TicTacToeShowGrid::putSymbol(unsigned int & choice) { //Transforme le choix en coo et utilise "putCross()" ou "putShape()" de TicTacToeGrid

    choice -= 1;
    if(m_turn->playerTurn() == CROSS)										//Si c'est le tour de CROSS -> on ajoute une croix à la case indiquée
    	m_tttGrid.putCross(choice / 3, choice % 3);							//Formule mathématique qui convertit une coo à 1 chiffre en une à 2 chiffres
    else																	//Si c'est le tour de CROSS -> on ajoute une croix à la case indiquée
    	m_tttGrid.putShape(choice / 3, choice % 3);							//Formule mathématique qui convertit une coo à 1 chiffre en une à 2 chiffres
    

    }

    void TicTacToeShowGrid::showWinner() const { //Affiche le gagnant

    if (m_referee.winnerIs() != EMPTY)
    	std::cout << "Le gagnant est : " << m_referee.winnerIs() << std::endl;
    else
    	std::cout << "C'est une egalite.\n";
    

    }

    </pre>

    -
    Edité par Bouli1515 15 juillet 2015 à 21:09:40

    • Partager sur Facebook
    • Partager sur Twitter
      7 juillet 2015 à 20:11:55

      Ce n'est pas tant le respect de SOLID ou autre qui me gène, mais les fonctions membre TicTacToeShowGrid::interpretChoiceShape et
      TicTacToeShowGrid::interpretChoiceCross.


      Tu as de la duplication de code dans ces 2 fonctions, et le switch est inutile(et moche).

      Je pense qu'une bonne manière de résoudre le problème est de te demander, comment tu t'y prendrais si tu voulais permettre de jouer sur des grilles 4x4, 5x5.

      Surement d'autres choses à voir, mais c'est vraiment un truc qui m'a sauté aux yeux.

      -
      Edité par GurneyH 7 juillet 2015 à 20:12:21

      • Partager sur Facebook
      • Partager sur Twitter
      Zeste de Savoir, le site qui en a dans le citron !
        7 juillet 2015 à 20:49:09

        Merci beaucoup @GurneyH pour ta réponse. Je vois effectivement qu'il ya un moyen de faire plus simple qu'un switch tout dégueulasse. Cependant pour la duplication de code j'ai conçu la classe comme ça. Mais je pense pourvoi modifier ça si cela n'est pas bien j'édite le code du premier post pour ça. Merci encore et A++

        • Partager sur Facebook
        • Partager sur Twitter
          7 juillet 2015 à 21:06:01

          m_tttGrid.putShape((choice-1)/3, (choice-1)%3);

          -
          Edité par Faellan 7 juillet 2015 à 21:06:20

          • Partager sur Facebook
          • Partager sur Twitter
          Bla bla bla
            7 juillet 2015 à 21:08:50

            GurneyH a écrit:

            Ce n'est pas tant le respect de SOLID ou autre qui me gène, mais les fonctions membre TicTacToeShowGrid::interpretChoiceShape et
            TicTacToeShowGrid::interpretChoiceCross.


            Tu as de la duplication de code dans ces 2 fonctions, et le switch est inutile(et moche).

            Je pense qu'une bonne manière de résoudre le problème est de te demander, comment tu t'y prendrais si tu voulais permettre de jouer sur des grilles 4x4, 5x5.

            Surement d'autres choses à voir, mais c'est vraiment un truc qui m'a sauté aux yeux.

            -
            Edité par GurneyH il y a 28 minutes


            Je te rejoins là dessus, la conception devrait être basée sur une grille rectangulaire quelconque.

            Ensuite dire qu'on respecte SOLID, c'est bien, le faire effectivement, c'est mieux. Une classe TicTacToeShowGrid si on se réfère à son nom, est censée afficher une grille, pas interpréter quoi que ce soit et certainement pas une configuration de partie pour déterminer une condition de victoire, là tu violes clairement le SRP (le S de SOLID), il n'est en aucun cas du ressort d'une classe ShowGrid d'interpréter les mouvements de la partie, ça c'est le rôle d'une classe Arbitre (dans un match c'est l'arbitre en définitive qui décide si un coup est valide, et qui déclare le vainqueur). Je te conseille de faire une recherche sur le forum, lmghs a posté il y un ou deux ans un lien sur une implémentation  très intéressante de Tic Tac Toe, dans laquelle il introduit la notion  de centre décision, astuce de conception particulièrement fûtée qui lui permet de gérer Joueur local/joueur réseau/IA avec la même abstraction (sans compter que le code de lmghs est toujours riche d'enseignements pour qui veut apprendre et/ou se perfectionner).

            -
            Edité par int21h 7 juillet 2015 à 21:18:57

            • Partager sur Facebook
            • Partager sur Twitter
            Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
              7 juillet 2015 à 21:29:01

              J'ai apporté la modif suggéré par @GurneyH et Faellan. Merci @int21h, donc tu suggères de créer une classe arbitre qui vérifie si les coups sont valides et de qui à gagner. Cependant la TicTacToeShowGrid doit elle, s'occuper de l'interface (en tous cas c'était comme cela que je la concevais) des entrées/sorties. Un meilleur nom aurait peut-être été TicTacToeInterface. Cette classe contiendrait une instance de TicTacToeInterface, une instance de Turn et une instance de Arbitre ? Je vais regarder l'implémentation de @Imghs que tu me conseilles et voir ce que ça peut m'inspirer.

              -
              Edité par Bouli1515 7 juillet 2015 à 21:29:19

              • Partager sur Facebook
              • Partager sur Twitter
                7 juillet 2015 à 21:33:57

                Salut,

                Bouli1515 a écrit:

                Bonjour,
                Tout d'abord merci de lire ce post.
                J'ai commencé la conception d'un jeu de morpion (Tic Tac Toe) en essayant de respecter le plus possibles les principes du SOLID + l'encapsulation + la loi de Déméter (cette dernière que je n'ai pas trop bien compris comme le 'I' et le 'D' de SOLID)<snip>

                Ben, on va déjà commencer par cela alors :D

                La loi de Déméter est relativement simple : Si tu as une classe A qui utilise en interne une instance de la classe B, sous une forme qui pourrait sans doute être proche de

                class A{
                /* ... */
                private:
                    B b_;
                }

                Tu dois veiller à fournir des comportements (des fonctions membres publiques ???) à ta classe A qui ne nécessiteront pas que l'utilisateur (de ta classe A) connaisse la classe B pour utiliser ta classe A.

                Dit comme cela, ca peut sembler surprenant, alors un petit exemple va peut être t'aider à comprendre...

                • Imagine que tu veuille modéliser une voiture.  On se doute que ta voiture, va avoir besoin d'un réservoir pour pouvoir fonctionner.  Du coup, tu vas sans doute créer une classe "Revervoir" qui fournit un certain nombre de services, comme :
                • de connaitre sa capacité maximale
                • de connaitre la quantité de carburant qu'il contient (à un instant T)
                • de rajouter "une certaine quantité" de carburant
                • de savoir si le réservoir est vide
                • d'utiliser "une certaine quantité" de carburant

                Au final, tu auras donc une classe (je vais utiliser des termes francais pour une fois, histoire que ce soit plus facile à comprendre ;) ) Reservoir qui pourrait prendre une forme proche de

                class Reservoir{
                public:
                    Reservoir(Type quantiteMax);
                    void ajoute(Type aAjouter);
                    void utilise(Type aUtiliser);
                    bool estPlein() const;
                    bool estVide() const;
                    Type quantiteMax() const;
                    Type quantite() const;
                /* ... */
                };

                (on pourrait partir sur le principe que Type est un float ou un double; mais ce pourrait aussi être un type spécifique permettant de représenter des litres ;) )

                revenons donc à notre voitrue...  On sait qu'elle a besoin d'un réservoir.  Mais la loi de déméter dit que celui qui manipulera la classe Voiture n'a aucun besoin de connaitre la classe Reservoir pour pouvoir manipuler la classe Voiture. 

                Et c'est en effet le cas : Dans la réalité, tu sais par où tu va rajouter du carburant (pa la trappe qui se trouve à gauche... ou à droite), tu sais où ce trouve la jauge d'essence (qui te permet de te faire une idée de "ce qu'il reste" de carburant), le moteur sait retirer du carburant du réservoir et, si tu en as un, l'ordinateur de bord sera peut être en mesure de te donner quelques informations sympa grâce aux informations qu'il collecte au travers du réservoir (comme la consomation "instantanée", la consomation moyenne, la distance que tu peux encore parcourrir, ...)

                En un mot comme en cent, tu n'as jamais besoin d'accéder effectivement au réservoir pour pouvoir manipuler ta classe Voiture. (bon, d'accord, y a peut être un garagiste qui devra parfois changer un réservoir, mais c'est pas du ressors de l'utilisateur de la voiture :D)

                Ta voiture va donc exposer un certain nombre de comportements en relation avec la présence (supposée) d'un réservoir (ajouterCarburant, carburantRestant, carburantRatio, kmRestant, ...) mais, si cela se trouve, la classe voiture n'utilise absolument pas la classe Réservoir en interne, et cela doit pouvoir continuer à fonctionner. 

                C'est la raison pour laquelle il n'y a aucun sens à exposer mutateur sur le réservoir (setReservoir) ni même, d'ailleur, un accesseur sur le réservoir (Reservoir getReservoir()): l'utilisateur de ta classe Voiture n'a absolument aucune raison d'accéder directement au réservoir de la voiture ;)

                Le I de SOLID est mis pour ISP ou Interface Segregation Principle (ou, si tu préfères en francais : Principe de Ségrégation des Interfaces).  L'idée de ce principe est relativement simple : plus l'interface d'une classe est compliquée (comprend : plus les fonctions membre publiques sont nombreuses), plus il est difficile de manipuler une instance de la classe en question.

                Mais surtout : il arrive régulièrement que plusieurs classes qui n'ont absolument aucun lien entre elles présentent malgré tout certaines fonctions communes.

                Lorsque l'on parle de l'interface d'une classe, on englobe l'ensemble des fonctions membres publiques que cette classe expose, mais aussi l'ensemble des fonctions libres qui font références ou qui utilisent une instance de la classe en question, ce qui implique que l'interface "étendue" d'une classe est forcément particulièrement complexe ;)

                Prenons n'importe quel jeu avec des murs, des personnages et des objets à ramasser. Selon le LSP, il n'y a aucun lien entre ces trois types d'éléments, ce qui fait que tu ne peux pas les placer dans une hiérarchie commune.  Et pourtant, tu voudras systématiquement afficher les murs, les personnages et les objets à ramasser.  Il exposeront donc sans doute tous une fonction commune  (draw() ??? ).

                L'idée est donc de créer "quelque chose" qui n'exposera que la fonction draw(); de créer ce que l'on appelle une "interface" qui pourra être "implémentée" par "n'importe quel type devant pouvoir être tracé"

                En java (par exemple), il existe les mots clés interface et implements pour modéliser cette notion d'interface; mais il faut en fait savoir que java ment à se développeur en leur faisant croire que le mot clé implements a un effet différent du mot clé inherits car, au sens du LSP, ces deux mots clés font exactement la même chose : permettre de considérer une classe donnée comme étant un sous type d'une classe de base ;)

                C++ ne propose pas cette notion "artificielle" d'interface, mais il est tout à fait possible de créer ce que l'on pourrait appeler une "classe interface" en évitant d'avoir une "hiérarchie monolithique" constituée de "tout ce qui doit être tracé".  On peut utiliser les template.

                Ainsi, si tu crées une interface Drawable proche de

                template <typename T>
                class Drawable{
                public:
                    Drawable(T const & t):t_(t){}
                    void draw() const{
                        /* ... */
                    }
                private:
                    /* permet de récupérer l'élément à tracer, quel qu'en soit le type */
                    T const &t_;
                };

                nous pourrions faire dériver nos classe Mur, Personnage et Objet de cette classe sous une forme proche de

                class Mur : public Drawable<Mur>{
                public:
                    Mur():Drawable<Mur>(*this){}
                };
                class Personnage : public Drawable<Personnage>{
                public:
                    Personnage():Drawable<Personnage>(*this){}
                };
                class Objet : public Drawable<Objet>{
                public:
                    Objet():Drawable<Objet>(*this){}
                };

                et "le tour serait joué" : les classes Mur, Personnage et Objet exposent bel et bien une fonction draw() "commune",  mais n'ont absolument aucun lien entre elles ;)

                note : en pratique, on n'utilisera sans doute de préférence une approche basée sur un ECS pour RPG et autres FTS.  je n'ai pris cet exemple que pour... l'exemple justement :D

                Enfin, le D de SOLID est mis pour DIP ou Dependancies Inversion Principle (Principe d'inversion des dépendances, en français).  Ce principe part de la constatation que tu as systématiquement des dépendances entre les différents éléments de ton projet et, souvent, entre ton projet et "d'autres projets" (comme la bibliothèque standard ou n'importe quelle bibliothèque externe).

                Ainsi, tu ne t'en es sans doute pas rendu compte, mais la fonction draw que je présentais la tout de suite subit une dépendance forte : elle dépend du "contexte d'affichage" qui sera utilisé pour effectuer le rendu : Que l'affichage soit réellement effectué en utilisant SDL, SFML, OpenGL ou la bibliothèque standard ne fait en théorie aucune différence : il faut afficher les éléments... Sauf que, en pratique, on affichera les éléments de manière différente en fonction du contexte d'affichage utilisé :P.

                Du coup, si tu places toutes les fonctions relatives à SDL / SFML / OpenGL ou à la bibliothèque standard pour provoquer l'affichage dans la fonction draw, et que tu décide de changer le contexte d'affichage dans trois mois (comprend : passer d'un de ces contextes d'affichage à un autre), tu seras sans doute très embêté à cause de la somme de travail que cela peut représenter et, du coup, tu décideras sans doute de remettre le changement "sine die" :p

                Le but du DIP est de rendre ce genre de décision plus facile en t'incitant à faire en sorte de respecter deux assertions :

                1. Les modules de haut niveau ne doivent pas dépendre des modules de bas niveau. Les deux doivent dépendre d'abstractions.
                2. Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre des abstractions.

                Autrement dit, la fonction draw dont je parlais plus haut devrait être modifiée pour... accepter une "abstraction représentant le contexte d'affichage" et devrait donc demander au contexte d'affichage de tracer ... l'élément qui doit l'être.

                A final, notre classe Drawable devrait donc prendre une forme proche de

                /* la classe Context est une abstraction permettant de représenter "n'importe quel
                 * contexte d'affichage
                 */
                template <typename T>
                class Drawable{
                public:
                    Drawable(T const & t):t_(t){}
                    void draw(Context & c) const{
                        /* c'est l'abstraction qui affiche l'abstraction
                         * représentant un élément affichable
                         */
                        c.draw(t_);
                    }
                private:
                    /* permet de récupérer l'élément à tracer, quel qu'en soit le type */
                    T const &t_;
                };

                De cette manière, tu peux créer des contexte pour n'importe quel système d'affichage, et tu n'as "plus qu'à choisir" celui que tu veux au moment de la création du concept d'affichage pour... faire en sorte que ton jeu utilise l'une ou l'autre technologie d'affichage ;)

                • Partager sur Facebook
                • Partager sur Twitter
                Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                  7 juillet 2015 à 21:46:37

                  Merci @Koala01 pour cette longue explication, tu as éclairci beaucoup de choses. Ces 2 derniers principes du SOLID sont cepenadant difficilement applicable dans de petits projets ?

                  • Partager sur Facebook
                  • Partager sur Twitter
                    7 juillet 2015 à 22:10:32

                    @int21h : Je crois que j'ai trouvé le lien Je regarde ça à toute !

                    • Partager sur Facebook
                    • Partager sur Twitter
                      7 juillet 2015 à 23:35:52

                      Pourquoi ne le seraient ils pas? n'oublie pas que tu vas recycler une bonne partie du code que tu vas écrire au cours de ta vie, j'utilise encore dans du code que j'écris aujourd'hui des bouts de code que j'ai ecris il y a plus de 20 ans. Si je peux le faire, c'est parce que je sais que lorsque j'ai écris ce code, j'ai respecté certains principes qui font que ce code était juste à l'époque et que ça n'a pas changé, il est toujours juste et le restera. J'ai comme ça des stocks conséquents de fonctions et de classes que je recycle, et tous les programmeurs expérimentés ont un stock considérable de bouts de code adaptés à leurs besoins qu'ils réutilisent sans cesse (avec parfois un bon paquet de NIH inside, il faut aussi savoir faire le ménage de temps en temps :p ). Ce code vient de tous les projets auxquels j'ai participé, les grands comme les petits, que je sois sur un projet de 50K lignes ou de 5M lignes, les règles sont les mêmes. Si tu n'arrives pas à respecter les règles sur un petit projet, comment peux tu espérer arriver à les respecter sur un gros?

                      -
                      Edité par int21h 7 juillet 2015 à 23:47:40

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Mettre à jour le MinGW Gcc sur Code::Blocks. Du code qui n'existe pas ne contient pas de bug
                        8 juillet 2015 à 5:18:29

                        Bouli1515 a écrit:

                        Merci @Koala01 pour cette longue explication, tu as éclairci beaucoup de choses. Ces 2 derniers principes du SOLID sont cepenadant difficilement applicable dans de petits projets ?


                        Oui, mais non...

                        Oui, parce que, d'une certaine manière, le fait de respecter l'ISP et le DIP pourrait sans doute inciter certaines personnes à s'écrier un grand YAGNI (You ain't gonna need it ou, si tu préfères tu n'a pas besoin de cela)ou un grand KISS (Keep It Simple, Stupid ou, si tu préfères Garde cela simple, idiot).

                        Mais non parce qu'il faut savoir que même les plus petits projets sont destinés à voir évoluer leur besoins.  Parfois de manière tout à fait prévisible (ex : tu commence ton tic-tac-toe avec un affichage "console", mais, par la suite, tu voudras surement le rendre plus "user friendly" en utilisant une bibliothèque d'affichage 2D ou 3D), parfois de manière relativement imprévue (le fameux "tiens, ce serait pas mal si...").

                        Sans compter, comme int21h l'a fait remarquer, le fait que tu en arriveras très rapidement à te dire que "tiens, mais tel code est particulièrement intéressant, je risque de le réutiliser régulièrement"...

                        Or, il n'y a rien à faire, si tu veux pouvoir faire évoluer ton code ou -- comme l'a fait remarquer int12h -- pouvoir envisager de réutiliser ton code pour dans des situations que tu n'avais sans doute même pas envisagées au moment où tu l'as écrit, il n'y a rien à faire : le respect des principes SOLID dans leur ensemble s'avère être la clé du succès.

                        Alors, bien sur, on n'y arrive pas forcément du premier coup, mais l'idée est que tu dois d'abord essayer de faire quelque chose qui fonctionne, en respectant impérativement l'OCP et le LSP et en essayant de respecter au mieux le SRP.  Mais, très rapidement, tu te rendra compte que la responsabilité de telle classe est "trop générique" (comprend : correspond en réalité à deux, trois ou parfois quatre --si pas plus --responsabilités distinctes) et tu rentreras alors dans une phase de refactoring dans laquelle tu feras ton possible pour séparer clairement ces responsabilités; en respectant sans doute beaucoup mieux le SRP, et en respectant de facto d'avantage l'ISP.

                        Car, à vrai dire, il n'y a pas de miracle, hein ? plus tu as des responsabilités simples et clairement établies, moins il te faut de fonctions pour arriver à remplir ces responsabilité et plus tu peux avoir une interface "minimaliste" pour exposer les comportements dont tu as besoin ;)

                        De même, tu en arriveras à suivre un raisonnement très proche de celui que j'ai suivi en ce qui concerne la fonction draw.  Très souvent, tu le suivra d'ailleurs parce que tu auras eu une idée du genre "tiens ce serait pas mal si ... (je décidais d'utiliser une bibliothèque 2D pour l'affichage, pour rester dans l'exemple".

                        Mais c'est à ce moment là que tout l'intérêt d'avoir au moins un aperçu des "grandes lignes du projet"; d'avoir une idée des "évolutions possibles / probables / certaines" entre en jeu.  Car, si tu arrives à suivre un raisonnement proche de "je vais développer mon tic-tac-toe, puis, si j'ai le temps, j'essayerais d'utiliser SFML pour l'affichage", tu en viendras "naturellement" à introduire la notion de concept d'affichage dans ta réflexion et tu auras tout aussi naturellement tendance à prévoir une abstraction pour représenter cette notion.  Et voilà comment le DIP sera respecté ;)

                        Je sais que certains jugent parfois ma manière de faire un peu exagérée, mais, j'ai l'habitude d'utiliser les mots choc "un verbe (ou groupe verbal)== une fonction; un nom (ou groupe nominal) == un type".  Mais, tu respectes scrupuleusement cette règle, tu te rendras compte que tu respecteras tous les principes SOLID (hormis LSP, malheureusement) sans même avoir besoin d'y réfléchir.  A une condition toutefois : il faut que tu arrives à exprimer très clairement quels sont tes besoins, quitte à partir de besoins "complexes" et à les préciser de manière à avoir au final toute une série de besoins "simplifiés à l'extrême" ;)

                        A titre d'exemple, en respectant cette simple règle, j'ai fait un jeu d'échecs en mode console en moins de 3000 lignes de code pour l'étude de cas de mon livre.  Il y a énormément de choses que je n'ai pas prises en compte (je ne me suis pas du tout intéressé à l'IA, ni à l'historique des déplacements, et encore moins à la sauvegarde d'une partie en cours), mais je sais que le code que j'ai écrit est tout à fait capable d'évoluer dans n'importe quelle direction ;)

                        • Partager sur Facebook
                        • Partager sur Twitter
                        Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                          8 juillet 2015 à 20:05:54

                          Re,

                          Vous utilisez donc parfois des classe et des fonctions que vous avez écrit il y a plusieurs années (à la base pas du tout pour le même projet). Juste il y avait combien de fonction ou de classe dans ton jeu d'échec @Koala01 ?

                          Pour en revenir au programme, il a pas mal bougé :

                          • D'abord j'ai changé tous ce qui contenait "case" par "box" ce qui est plus logique en anglais. 
                          • Ensuite, j'ai ajouté une classe Referee (Arbitre, merci Reverso ;) ) qui se charge de vérifier si la case saisie est déjà occupée (fonction isBoxTaken() qui existait déjà), si un joueur a gagné (fonction winnerIs() qui existait déjà aussi) et si la partie est finie (fonction isFinish(), cette fonction n'existait pas avant la fin de la partie se décidait aussi dans winnerIs() comme d'après @Koala01, "un verbe (ou groupe verbal)== une fonction; un nom (ou groupe nominal) == un type"). 
                          • Utilisé beaucoup de std::shared_ptr, je ne sais pas si c'est très bien, mais mes classe TicTacToeGrid et Referee ont toutes deux besoins d'un accès à la matrice de base, et Referee a besoin de la même instance de Turn que TicTacToeShowGrid.

                          J'ai directement changé le poste d'origine sinon la page va vite devenir illisible...

                          EDIT : ce code bug. Je ne pouvais pas le testé pour des raisons confuses mais il plante au lancement. Sans doute un problème avec les pointeurs...

                          -
                          Edité par Bouli1515 8 juillet 2015 à 22:38:10

                          • Partager sur Facebook
                          • Partager sur Twitter
                            9 juillet 2015 à 0:03:07

                            Bouli1515 a écrit:

                            Re,

                            Vous utilisez donc parfois des classe et des fonctions que vous avez écrit il y a plusieurs années (à la base pas du tout pour le même projet). Juste il y avait combien de fonction ou de classe dans ton jeu d'échec @Koala01 ?

                            Je n'ai jamais compté le nombre de fontions, mais j'ai donné quelque statistiques sur mon code dans mon livre :

                            • 67 fichiers représentant
                            • 4741 lignes au total dont
                            • 2461 lignes composées uniquement de code
                            • 27 lignes vides
                            • 2244 lignes de commentaires (quasiment exclusivement des cartouches pour la génération automatique de documentation ;) )
                            • 9 lignes composées de code et de commentaires

                            Si cela t'intéresse, le code est disponible sous licence GPL v3 sur github ;)

                            Pour en revenir au programme, il a pas mal bougé :

                            • D'abord j'ai changé tous ce qui contenait "case" par "box" ce qui est plus logique en anglais. 
                            • Ensuite, j'ai ajouté une classe Referee (Arbitre, merci Reverso ;) ) qui se charge de vérifier si la case saisie est déjà occupée (fonction isBoxTaken() qui existait déjà), si un joueur a gagné (fonction winnerIs() qui existait déjà aussi) et si la partie est finie (fonction isFinish(), cette fonction n'existait pas avant la fin de la partie se décidait aussi dans winnerIs() comme d'après @Koala01, "un verbe (ou groupe verbal)== une fonction; un nom (ou groupe nominal) == un type"). 
                            • Utilisé beaucoup de std::shared_ptr, je ne sais pas si c'est très bien, mais mes classe TicTacToeGrid et Referee ont toutes deux besoins d'un accès à la matrice de base, et Referee a besoin de la même instance de Turn que TicTacToeShowGrid.

                            J'ai directement changé le poste d'origine sinon la page va vite devenir illisible...

                            EDIT : ce code bug. Je ne pouvais pas le testé pour des raisons confuses mais il plante au lancement. Sans doute un problème avec les pointeurs...

                            -
                            Edité par Bouli1515 il y a environ 1 heure

                            C'est toujours bien d'utiliser les pointeurs intelligents.  Mais les shared_ptr ne doivent être réellement utilisés --selon moi -- que si la propriété réelle du pointeur est partagée(*).

                            Dans ton cas, il n'y a sans doute qu'une seule des deux classes qui puisse décider de créer un nouveau jeton (et il n'y a qu'une classe qui puisse décider de tous les supprimer ;) ); si tant est que l'utilisation de pointeurs se justifie (ce qui est loin d'être acquis :P) les unique_ptr devraient suffire, vu que l'autre classe n'est qu"'utilisatrice" des pointeurs ;)(**)

                            (*)(**)Je sais que certains sur le forum ont un avis différent du mien et préfèrent utiliser les shared_ptr "par défaut", mais à titre personnel, je préfère utiliser les unique_ptr "tant qu'il n'est pas prouvé que j'ai vraiment besoin des shared_ptr" ;)

                            • Partager sur Facebook
                            • Partager sur Twitter
                            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                              9 juillet 2015 à 18:12:19

                              Re, J'ai utilisé des std::shared_ptr car pour moi ce sont eux que l'on utilise si on a plusieurs pointeurs qui pointent sur le même objet. Mais tu penses que je devrais plutôt les utiliser si ils sont propriétaires de l'objet pointé. Je n'ai qu'un propriétaire de tous les pointeurs dans ce code : la classe TicTacToeShowGrid, c'est bien cela ? Mais dans ce cas mes pointeurs observateurs, ils doivent être des shared-ptr ou des unique_ptr ? En fait je crois que ce programme a des défauts de conception, j'ai presque envie de tout recommencer depuis zéro en simplifiant et en tirant partie de tout ce qui a été dit. Mais avant je tiens quand même à corriger ce bogue, j'ai trouvé (de manière un peu sale : j'ai bourré le code de std::clog pour voir où ça bloquait :p ) l'endroit du problème : Lors de l'appel de la fonction TicTacToeGrid::showGrid() (TicTacToeGrid.cpp; ligne 19 à 31), qui fait elle même appelle à Matrice<T>::height() (ligne 21) en fait plus généralement ce sont tous les appels de fonctions membres de la classe Matrice qui coince. Je pense que c'est du à l'utilisation du shared_ptr m_grid. Voila c'est à peu près tous ce j'ai réussi à savoir. En fait je ne sais pas vraiment me servir du débogueur de VSC++, donc je n'ai pas pu en tirer grand chose... Merci de votre aide !

                              • Partager sur Facebook
                              • Partager sur Twitter
                                9 juillet 2015 à 18:16:36

                                > En fait je ne sais pas vraiment me servir du débogueur de VSC++

                                Quoi ???

                                T’appuies sur F5, et roule ma poule.

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                                  9 juillet 2015 à 18:23:00

                                  Bacelar a écrit :

                                  T'appuies sur F5, et roule ma poule.

                                  Je crois que c'est pas le plus dur :p . Le tout est d'interpréter les résultats. Le message d'erreur que j'obtiens est :

                                  Microsoft Visual Studio a écrit :

                                  Exception non gérée à 0x0135F3E6 dans TicTacToe.exe : 0xC0000005 : Violation d'accès lors de la lecture de l'emplacement 0x00000000.

                                  Ppppppppp... Je vois pas trop apparemment j'essaye d'accéder à quelque chose dont j'ai pas le droit.

                                  -
                                  Edité par Bouli1515 9 juillet 2015 à 18:23:55

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    9 juillet 2015 à 18:43:06

                                    Au le beau déférencement de nullptr. :lol:

                                    Le plus sympa, c'est pas le message d'erreur, il est quand même mieux que gdb de base. :-°

                                    Normalement, il t'indique la ligne du code qui a merdé ainsi que la valeur des différentes variables au moment de l'arrêt.

                                    Sinon, pour le message d'erreur, bin c'est le message le plus fréquent quand on gère la mémoire à la truelle. :diable:

                                    >0x0135F3E6

                                    C'est l'adresse du code machine qui à fait planté le programme, te prends pas la tête avec, le débuggeur t'affiche déjà en surbrillance la ligne du code source qui correspond à cette instruction machine.

                                    >0xC0000005

                                    Ça, c'est le code d'erreur, 5 en faites, car 0xC, c'est le préfixe des erreurs pour les distingués des codes retours "normaux". et 05, c'est General Protection Fault, l'équivalent du SEGFAULT Unix => pointeur merdeux.

                                    >Violation d'accès lors de la lecture de l'emplacement 0x00000000.

                                    Ton programme à tout simplement essaye de lire en 0x00000000, ce qui est nullptr et, sous Windows, les premiers 64ko de mémoire virtuelle sont une "zone de mort", pour détecter le déférencement de nullptr ou de nullptr+un indice de décalage positif petit.

                                    Pour les indices négatifs, ça fait le tour des adresses => zone "haute" de la mémoire et la zone "haute" de la mémoire c'est juste pour le Kernel, donc comme ton code est en Ring différent de 0 (Ring c'est le mode d'exécution du CPU, sous Windows Ring 0 = Kernel donc plein pouvoir, Ring 4 = mode User t'es qu'une victime sur l'autel des bugs), donc zone "haute" de la mémoire = "zone de mort".

                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                    Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                                      9 juillet 2015 à 18:57:27

                                      Merci pour tes explication @Bacelar pour tout te dire j'avais (essayé de) vérifié si il y avait pas un petit nullptrdans le tas. J'avais fait un test :

                                      if(machin == nulptr)
                                      
                                      std::clog << "Machin est nulptrr"</pre> 
                                      

                                      dans un des constructeurs mais il m'avait rien affiché je m'étais donc dit que ça devait pas être ça. Du coup doit il y avoir un std::shared_ptrinitialiser avec nullptrdans mon histoire... Le compilateur se réserve-t-il le droit de changer l'ordre des initialisations dans la liste d'initialisation d'un constructeur ? J'avais fait affiché des messages dans la console dès qu'un de mes objets étaient construits (c'est peut-être pas très académique comme moyen de débogage...) et j'avais remarqué que les messages n'étaient pas dans le même ordre que celui que j'avais donné dans la liste d’initialisation.

                                      </pre>

                                      -
                                      Edité par Bouli1515 9 juillet 2015 à 18:57:49

                                      • Partager sur Facebook
                                      • Partager sur Twitter
                                        9 juillet 2015 à 19:14:13

                                        Si mes souvenir sont bons, le compilateur respect l'ordre de déclaration des champs dans la classe et pas l'ordre d'apparition dans la liste d'initialisation.

                                        Mais qui aurait l'idée de changer  cet ordre. :-°

                                        • Partager sur Facebook
                                        • Partager sur Twitter
                                        Je recherche un CDI/CDD/mission freelance comme Architecte Logiciel/ Expert Technique sur technologies Microsoft.
                                          9 juillet 2015 à 19:44:10

                                          En effet, qui aurait l'idée de changer cet ordre :-°

                                          Je crois que j'ai 3-4 petites choses à faire...

                                          • Partager sur Facebook
                                          • Partager sur Twitter
                                            9 juillet 2015 à 19:54:13

                                            Il se peut que je n'ai pas initialiser les variables membres de TicTacShowGrid dans le même ordre que je les ai déclarer dans le .h :-°

                                            Pour ma défense, je tiens à dire que c'était une propriété que j'ignorais : 

                                            Les attributs sont initialisés dans l'ordre de leur déclaration (dans le .h) et non dans celle de la liste d'initialisation du constructeur. 

                                            Merci @Bacelar !

                                            Bug résolu ! (j'édite le poste principal)

                                            -
                                            Edité par Bouli1515 9 juillet 2015 à 19:54:55

                                            • Partager sur Facebook
                                            • Partager sur Twitter
                                              11 juillet 2015 à 1:32:15

                                              OCP et  LSP sont primordiaux pour organiser les points des variations. Sur un tictactoe, à moins de faire des trucs très compliqués, il n'y en a pas tant. Sur mon implémentation, ils apparaissent qu'au niveau des centres de décision.

                                              Le SRP est encore plus primordial, et ce tout le temps. Niveau complexité des fonctions, des classes, des fichiers, des modules.

                                              L'ISP, j'ai une vision légèrement différente de Koala01, je l'associe plus fortement aux interfaces à la Java. On reste d'accord sur le fait que l'interface (au sens services offerts) d'une classe doit avoir une cohésion forte et suivre le SRP. Je vois l'ISP comme la transposition du SRP aux interface javaiennes : chaque interface doit être minimaliste et focalisée sur un service/rôle bien précis. Quitte à ce que plus tard une classe implémente (au sens Java) plusieurs interfaces minimalistes et orthogonales.

                                              Le DIP, c'est toujours une histoire d'interfaces javaiennes. Si tu as une hiérarchie, alors toutes tes signatures et tous tes liens entre classes doivent reposer sur le type statique de l'interface et jamais sur celui des types fils concrets.
                                              Vous noterez que je pose en prérequis: "si on a une hiérarchie". Je ne vais pas imposer artificiellement une hiérarchie pour suivre le DIP et ce même si je suis en présence d'entités. C'est de l'overengineering, et je suis dans le camp de YAGNIstes -- les frameworks de refactoring nous aiderons pour extraire éventuellement des interfaces depuis des classes concrètes, et écrire les signatures de fonctions pour travailler sur les types interfaces. Corolaire de mon avant dernière phase, le DIP (et même l'ISP OO -- quoique je considère que l'ISP soit transposable aux concepts de la STL) n'est pas applicable aux classes à sémantique de valeur. Je préviens, car j'ai déjà croisé des DIP forcés sur des classes valeurs. Et c'est vraiment une sur-complexité inimaginable dans ces cas.

                                              Pour mon implémentation de pseudo tictactoe qui fut évoquée (merci :)): https://github.com/LucHermitte/tictactoe

                                              • Partager sur Facebook
                                              • Partager sur Twitter
                                              C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.

                                              Conception jeu de Tic Tac Toe en OO

                                              × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                              × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                              • Editeur
                                              • Markdown