Index de l'article

  

Remarque : Il est conseillé de coder soi-même les modifications indiquées ci-dessous. Cependant, vous pouvez vous rendre à la fin de cet article pour trouver le lien de téléchargement de la solution complète.

 

Quelques commentaires sur la façon de remplir les squelettes :

  • La classe GameElement contient un attribut int type, qui permet de catégoriser les éléments. Cette catégorisation va permettre de différencier plus facilement les éléments lorsqu'une méthode retourne une liste de GameElement.
  • Attribuer un type à un élément se fait automatiquement pour les classes existantes de boardifier-console.
  • Pour celles créés par le développeur, le constructeur doit obligatoirement enregistrer un nouveau type dans ElementTypes, grâce à la méthode register().
  • Cette méthode prend en paramètre un nom et une valeur entière. Par convention (et pour ne pas écraser celles qui sont déjà définies par boardifier-console), les valeurs doivent être >=50.
  • On peut ensuite utiliser ce nom et la méthode ElementTypes.getType() pour fixer le type de l'élément. 
  • Quand on crée soit même des sous-classes de GameElement, il faut qu'elles contiennent tous les attributs nécessaires à la gestion de l'état de l'élément.
  • Par exemple, aux dames, un pion peut devenir une reine. Il faut donc définir un attribut booléen qui indique si le pion est normal ou bien une reine et des méthodes permettant de manipuler cet attribut.
  • Dans le cas de "The Hole", un pion a un numéro, qui doit donc être définit comme attribut.
  • Pour certains jeux, un changement des valeurs de ces attributs d'état doit provoquer un changement de rendu visuel de l' élément (= son "look" dans la suite). Par exemple, un pion qui devient reine doit changer d'aspect visuel.
  • Dans ce cas, les méthodes qui manipulent les attributs d'état DOIVENT appeler la méthode addChangeFaceEvent() afin de signaler à la partie vue qu'il faudra mettre à jour le look de l'élément. Sans cet appel explicite, on ne constaterait aucun changement visuel à l'écran.

 

  • Quand un stage commence, la "machinerie" de boardifier-console va automatiquement créer une instance du stage (donc de HoleStageModel) grâce à son nom.
  • Ensuite, elle appelle la méthode getDefaultFactory() de cette instance. Le rôle de cette fonction est de retourner une instance de la classe qui crée les éléments du stage.
  • C'est pourquoi dans le cas présent, le code de getDefaultFactory() retourne une instance de HoleStageFactory.
  • Enfin, la machinerie appelle la fonction setup() de cette instance.

 

  • la classe HoleStageFactory contient un attribut stageModel, qui a la même valeur que la paramètre gameStageModel, mais dont on force le type comme étant celui de HoleStageModel.
  • Cette manipulation est juste un tour de passe-passe pour éviter plein de transtypages dans la méthode setup(), lorsque l'on voudra accéder aux attributs/méthodes de la classe HoleStageModel.

 Pour la classe Pawn :

  • Comme dit plus haut, il suffit d'enregistrer un type pour les pions, par exemple avec la valeur 50, puis d'initialiser les attributs int color et int number.
  • A noter qu'il n'y a pas besoin de méthode pour changer la valeurs de ces 2 attributs puisque les pions ne changent jamais d'état. Dans "The Hole", la classe Pawn n'a donc jamais besoin d'appeler addChangeEvent().

 

Pour la classe HoleBoard :

  • La méthode setValidCells(int number) qui va mettre à jour le tableau 2D de booléens reachableCells (hérité de ContainerElement).
  • reachableCells est automatiquement créé avec la même taille que la grille, donc 3x3 pour "The Hole".
  • L'objectif de la méthode est de mettre true dans les cases de reachableCells qui correspondent à une case valide si on voulait poser un pion dont la valeur est number.
  • Pour éviter d'avoir un code trop long, cette méthode appelle computeValidCells() pour obtenir la liste de ce cases.
 

 

Pour la classe HolePawnPot :

  • Il n'y a aucune fonctionnalité particulière. Le code du squelette est donc complet.

 

Pour la classe HoleStageModel :

  • Pour les méthodes, il faut créer des setters pour chacun des attributs afin que HoleStageFactory puisse les manipuler.
  • Cependant, il ne faut pas oublier que chaque élément du stage doit être ajouté dans les listes mentionnées auparavant, grâce aux méthodes addElement() et addGrid(). C'est pourquoi les setters doivent suivre un schéma bien particulier.
  • Par exemple, pour les setters de HoleBoard et blackPawns, cela donne :
    public void setBoard(HoleBoard board) {
        this.board = board;
        addContainer(board);
    }
    public void setBlackPawns(Pawn[] blackPawns) {
        this.blackPawns = blackPawns;
        for(int i=0;i<blackPawns.length;i++) {
            addElement(blackPawns[i]);
        }
    }

 

  • Il faut ensuite définir un ou plusieurs des 4 callbacks qui sont automatiquement appelés lors de certaines opérations et qui permettent d'influer sur l'état du jeu.
  • Par défaut, ces 4 callbacks ne font rien mais ils peuvent être changés grâce aux méthodes onSelectionChange(), onPutInContainer(), onRemoveFromContainer() et onMoveInContainer().
  • Pour faire simple, les 4 méthodes mentionnées ci-dessus prennent en paramètre une fonction lambda (ou fonction fléchée) qui définit les instructions a exécuter lorsque, respectivement, un élément a été sélectionné, un élément a été inséré/supprimé/déplacer dans un conteneur.
  • Pour "The Hole - console", il n'y a qu'une seule situation qui nécessite de changer le comportement nul par défaut : un pion est supprimé de son pool et mis dans la grille 3x3, ce qui implique de vérifier si tous les pions ont été placés et que la partie est terminée.
  • Il faut donc utiliser onPutInGrid() pour changer le callback associé.
  • On peut par exemple le faire dans une méthode setupCallbacks() que l'on appelle à la fin du constructeur :
    private void setupCallbacks() { 
        onPutInContainer( (element, containerDest, rowDest, colDest) -> {
            if (containerDest != board) return; // if not put in board, do nothing
            Pawn p = (Pawn) element;
            if (p.getColor() == 0) {
                blackPawnsToPlay--;
            }
            else {
                redPawnsToPlay--;
            }
            if ((blackPawnsToPlay == 0) && (redPawnsToPlay == 0)) {
                computePartyResult();
            }
        });
    }
  •  Reste à définir la méthode computePartyResult() qui va calculer qui gagne et mettre fin à la partie :
    private void computePartyResult() {
        int idWinner = -1;
        // compute winner
        // ... to fulfill
        
        // set the winner
        model.setIdWinner(idWinner);
        // stop de the stage
        model.stopStage();
    }

 

Pour la classe HoleStageFactory :

  •  Il suffit de compléter la méthode setup() pour qu'elle crée tous les éléments déclarés dans HoleStageModel et les assigne grâce aux différents setters.
  • Par exemple :
    public void setup() {
        // create the text , top-left char is in (0,0)
        TextElement text = new TextElement(stageModel.getCurrentPlayerName(), stageModel);
        text.setLocation(0,0);
        stageModel.setPlayerName(text);
        // create the board, top-left char is in (0,1)
        stageModel.setBoard(new HoleBoard(0, 1, stageModel));
        //create the black pot, top-left char is in (18,0)
        HolePawnPot blackPot = new HolePawnPot(18,0, stageModel);
        stageModel.setBlackPot(blackPot);
        // create the black pawns
        Pawn[] blackPawns = new Pawn[4];
        for(int i=0;i<4;i++) {
            blackPawns[i] = new Pawn(i + 1, Pawn.PAWN_BLACK, stageModel);
        }
        stageModel.setBlackPawns(blackPawns);
        // assign black pawns to their pot
        for (int i=0;i<4;i++) {
            blackPot.addElement(blackPawns[i], i,0);
        }

        // to fulfill for the red pot in (25,0), and red pawns 
        // ...
    }

 

Remarques :

  • Dans l'exemple ci-dessus, on positionne le texte, le panneau 3x3, les pots, ... comme si on avait une zone de pixels, mais avec des caractères au lieu de pixel. Par exemple, les coordonnées (18,0) représentent la colonne 18 et ligne 0 dans cette zone.
  • A noter que les coordonnées des éléments qui sont (dé)placés dans un conteneur n'ont par défaut pas besoin d'être spécifiées. En effet, quand on utilise une action PutInContainerAction sur un élément, ou bien que l'on utilise addElement() pour mettre cet élément dans une conteneur, celui-ci va être mis par défaut dans un état particulier. Quand la partie contrôle doit mettre à jour le visuel, elle vérifie si des éléments sont dans cet état particulier, et si c'est le cas, elle calcule automatiquement leur position dans la case du conteneur. C'est ce qui se passe pour les pions de "The Hole - console".