Index de l'article

 

Afin de respecter le paradigme MVC, boardifier-console repose sur les principes suivants  :

  • Chaque GameElement est associé à un ElementLook qui définit son aspect visuel (c.a.d. son "look"). Quand on définit une sous-classe de GameElement, il faut donc définir une sous-classe de ElementLook pour créer son look.
  • Comme on est en mode texte, le look est représenté par un tableau 2D de String, ce qui permet d'afficher des caractères spéciaux en couleur.
  • Le contenu de ce tableau 2D doit être définit dans une méthode render(), que chaque sous classe de ElementLook DOIT définir pour créer le rendu visuel de l'élément en fonction de son état.

Remarques :

  • render() est automatiquement appelé si l'état de l'élément a changé et que l'on a appelé addChangeFaceEvent()(cf. § sur GameElement). C'est pourquoi il est important d'écrire le code de render() pour qu'il tienne compte des états possibles de l'élément ... sauf pour certains états.
  • En effet, la gestion du changement de la visibilité de l'élément est automatisée. Il n'y a pas besoin de code spécial dans render() pour "effacer" son rendu visuel lorsque l'élément est invisible. C'est fait automatiquement.

 

Par exemple, pour créer le look des pions de "The Hole", PawnLook contient :

public class PawnLook extends ElementLook {

    public PawnLook(GameElement element) {
        super(element, 1, 1);
  }

  public protected render() {
        Pawn pawn = (Pawn)element; // just for convenience to avoid in the following lots of casts like: (Pawn)element
        if (pawn.getColor() == Pawn.PAWN_BLACK) {
            shape[0][0] = ConsoleColor.WHITE + ConsoleColor.BLACK_BACKGROUND + pawn.getNumber() + ConsoleColor.RESET;
        }
        else {
            shape[0][0] = ConsoleColor.BLACK + ConsoleColor.RED_BACKGROUND + pawn.getNumber() + ConsoleColor.RESET;
        }
    } 
}

Remarque : pour cet exemple, il y a juste besoin de définir le rendu visuel quand le pion est visible. Ce rendu ne changera jamais, contrairement aux dames, où il faudrait tester dans render() si le pion est une dame ou non afin de définir son visuel.

 

  • Pour le look des éléments "conteneurs" (c.a.d. de type ContainerElement, boardifier-console contient déjà une sous-classes de ElementLook, à savoir ContainerLook. Cette classe représente une table sans bordure, avec la possibilité d'avoir des cases jointes si le ContainerElement associé en a ou pas. La taille des cases peut être fixée ou bien être variable selon la taille des looks à l'intérieur. boardifier-console propose également 3 sous-classes plus spécialisée/optimisées  : 
    • GridLook, 
    • ClassicBoardLook
    • TableLook.
  • Les 2 premières servent uniquement lorsque le conteneur est vraiment une grille 2D, sans cases jointes. Le rendu visuel est une grille avec des cases de taille fixe, et avec ou sans bordure. La deuxième est juste une sous-classe de la première, afin d'avoir en plus une numérotation des lignes et colonnes.
  • La troisième classe permet de gérer une grille aussi bien avec que sans cases jointes, sans numérotation, avec une taille de case variable ou fixe.
  • A noter que s'il n'y a pas de cases jointes, il vaut mieux utiliser les 2 premières classes, plus performantes.
  • Ces classes gèrent elles-mêmes les looks des éléments se trouvant dans le conteneur, notamment leur position "locale".
  • Cela implique que dès qu'un élément est placé/déplacé/supprimé d'un conteneur, boardifer-console reproduit automatiquement la même action pour le look de l'élément au sein du look du conteneur. Le rendu visuel du conteneur est alors mis à jour, via l'appel de sa méthode render().
  • Pour résumer, grâce aux layouts, le développeur a juste besoin de gérer les modifications de la partie modèle, en déplaçant des éléments dans/hors de conteneurs, et boardifier-console gère automatiquement la mise à jour de la partie vue, à partir des modifications sur le modèle.

 

  • Puisque ces classes héritent de ElementLook, elles doivent définir la méthode render(). Pour les 3 sous-classes, on sépare le rendu des bordures et coordonnées du contenu, grâce aux méthodes :
    • renderInners() : recopie le tableau 2D de chaque look interne à la bonne position dans la tableau shape du look du conteneur.
    • renderBorders() : "dessine" les bordures de chaque case grâce à des caractères spéciaux, dans le tableau shape du look du conteneur.
    • renderCoords() : "dessine" les coordonnées de chaque ligne/colonne, dans le tableau shape du look du conteneur.
  • Si on veut créer une sous-classe, par exemple de GridLook, pour modifier la façon de faire le rendu visuel, il n'y a normalement aucune raison de redéfinir renderInners(). En revanche, on peut redéfinir render() et/ou renderBorders(). C'est ce qui est fait dans ce tutoriel avec la classe PawnPotLook.

 

 

  • Pour gérer tous les visuels d'un même stage, chaque sous-classe de GameStageModel doit avoir son équivalent pour la vue, sous la forme d'une sous-classe de GameStageView.
  • Cette classe n'a par elle-même aucun rôle d'affichage. C'est juste un conteneur pour tous les looks des éléments utilisés dans un même stage.
  • Toutes les sous-classes de GameStageView doivent redéfinir la méthode createLooks() afin de créer les looks des différents éléments du modèle.
  • Pour afficher réellement les looks stockés dans un GameStageView, boardifier-console utilise la classe RootPane qui représente une sorte de panneau qui peut être affiché à l'écran. RootPane contient une méthode update() qui parcourt tous les éléments à afficher afin de remplir ce panneau aux bonnes coordonnées texte.
  • Ce RootPane est lui-même inclut dans une View, qui gère le contenu de la fenêtre du jeu et notamment sa mise à jour lorsque le contrôleur le demande.
  • Dans boardifer-console, View est quasi une coquille vide. Cependant, elle permet d'avoir la même structuration de classe que dans boardifier, ce qui facilite le passage d'un jeu du mode texte à graphique.

 

Pour PawnPotLook, la redéfinition de la méthode renderBorders() permet de changer le visuel par défaut de la grille.

Enfin, pour HoleStageView, il suffit d'instancier les classes de look, avec les dimensions prévues par le maquettage, et d'utiliser addLook() pour ajouter ces instances au visuel du stage. Ce qui donne :

package view;
// imports
// ...
public class HoleStageView extends GameStageView {
    public HoleStageView(String name, GameStageModel gameStageModel) {
        super(name, gameStageModel);
    }

    @Override
    public void createLooks() {
        HoleStageModel model = (HoleStageModel)gameStageModel;

        // create a TextLook for the text element
        addLook(new TextLook(model.getPlayerName()));
        // create a ClassicBoardLook (with borders and coordinates) for the main board.
        addLook(new ClassicBoardLook(2, 4, model.getBoard(), 1, 1, true));
        // create looks for both pots
        addLook(new BlackPawnPotLook(model.getBlackPot()));
        addLook(new RedPawnPotLook(2, 4, model.getRedPot()));
        // create looks for all pawns
        for(int i=0;i<4;i++) {
            addLook(new PawnLook(model.getBlackPawns()[i]));
            addLook(new PawnLook(model.getRedPawns()[i]));
        }
    }
}