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

Design Patterns Discussion :

[Etat] [Java] état des cases d'un jeu


Sujet :

Design Patterns

  1. #1
    Membre à l'essai
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 41
    Points : 19
    Points
    19
    Par défaut [Etat] [Java] état des cases d'un jeu
    Bonjour,

    Je suis en train de concevoir un petit jeu Tic Tac Toe dans le but de m'exercer à utiliser les patterns, à essayer de comprendre pourquoi dans tel ou tel cas il faut les utiliser ou pas. Pour l'instant, j'en suis à me demander si le State Pattern est vraiment utile pour distinguer une case marquée par un X/O d'une case vierge. Et s'il est (utile), est-ce la bonne méthode pour l'implémenter.

    Voilà les parties de codes concernées:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    public interface SquareState {
    	public static final SquareState BLANK_STATE = new BlankState();
    	public static final SquareState MARKED_STATE = new MarkedState();
    	
    	public void mark(Square square, Mark mark) throws IllegalStateException;
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    public class MarkedState implements SquareState {
    	
    	@Override
    	public void mark(Square square, Mark mark) throws IllegalStateException {
    		throw new IllegalStateException();
    	}
    }
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class BlankState implements SquareState {
    
    	@Override
    	public void mark(Square square, Mark mark) throws IllegalStateException {
    		square.setMark(mark);
    		square.setState(SquareState.MARKED_STATE);
    	}
    }
    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
    public class Square {
    	private SquareState state = SquareState.BLANK_STATE;
    	private Mark mark;
    	
    	public void mark(Mark mark) throws IllegalStateException {
    		state.mark(this, mark);
    	}
    	
    	public void setState(SquareState state) {
    		this.state = state; 
    	}
    	
    	public Mark getMark() {
    		return mark;
    	}
    	
    	public void setMark(Mark mark) {
    		this.mark = mark;
    	}
    }
    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
    public class Player {
    	private Mark mark;
    	private Board board;
    		
    	public Player(Mark mark, Board board) {
    		this.mark = mark;
    		this.board = board;
    	}
    	
    	public void takeTurn() {
    		try {
    			Point point = new Point('a', '1');
    			board.markSquare(point, mark);
    		} catch (IllegalStateException e) {
    			System.err.println("Illegal ("+board.getSquaresLeft()+")");
    		}
    	}
    }
    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
    public class Board {
    	private static final int MAX_ROWS = 3;
    	private static final int MAX_COLUMNS = 3;
    	private static final int MAX_SQUARES = MAX_ROWS*MAX_COLUMNS;
    	private Map<Point, Square> squares = new HashMap<Point, Square>();
    	private int squaresLeft = MAX_SQUARES;
    	
    	public Board() {
    		buildSquares();
    	}
    	
    	public void markSquare(Point point, Mark mark) throws IllegalStateException {
    		Square square = getSquare(point);
    		square.mark(mark);
    		--squaresLeft;
    	}
    
    	public int getSquaresLeft() {
    		return squaresLeft;
    	}
    }
    Il y a une ligne qui me dérange, c'est le "--squaresLeft". J'aurai voulu la placer dans la méthode mark() de l'état mais il aurait alors fallu passer en paramètre le plateau à la méthode mark() que la case qui elle même l'aurait passée à celle de l'état, du coup je me suis dit que lancer une exception serait plus judicieux. Mais est-ce la bonne façon de faire, j'attends vos avis avec impatience.

    Merci

  2. #2
    Membre habitué
    Profil pro
    Inscrit en
    Août 2006
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 89
    Points : 170
    Points
    170
    Par défaut
    Bonjour,

    Concernant l'utilisation ou pas d'un design pattern, je crois malheureusement qu'il n'y a pas de réponse universelle, ça dépend toujours du projet lui-même et de ses possibles évolutions.
    Donc c'est une bonne idée d'expérimenter son utilisation.

    Dans votre cas, je pense que l'utilisation du pattern Etat n'est pas nécessaire (pour les fonctionnalités actuelles en tout cas).
    Je pense qu'un booléen indiquant si la case est cochée (un membre 'isMarked' dans la classe 'Square' par exemple) est suffisant.

    Ce pattern deviendrait utile si le nombre de méthodes dépendantes de l'état (le nombre de méthodes dans la classe 'SquareState') venait à augmenter ou si les transitions entre états venaient à se complexifier.

    En revanche votre implémentation du pattern Etat a l'air correcte.


    Concernant la variable squaresLeft, c'est évidemment la classe 'Board' qui doit gérer ses modifications, pas à la classe 'Square' et encore moins à la classe 'SquareState' même indirectement.
    Donc modifier cette variable doit effectivement se faire dans la classe 'Board', en réponse à l'opération 'mark()' sur la case.
    Sur l'implémentation, je ne suis pas expert Java, donc je ne pourrais pas vraiment donner de conseil sur l'utilisation d'exceptions ou autres.
    Je pense en tout cas que l'implémentation actuelle est meilleure que celle suggérant de modifier la variable dans la méthode 'mark()' de l'état.

  3. #3
    Rédacteur
    Avatar de pseudocode
    Homme Profil pro
    Architecte système
    Inscrit en
    Décembre 2006
    Messages
    10 062
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Architecte système
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2006
    Messages : 10 062
    Points : 16 081
    Points
    16 081
    Par défaut
    Pour la gestion des exceptions, il y a plusieurs écoles de pensée. Pour ma part, je considère les exceptions comme devant signaler seulement une rupture de contrat (erreur dans une pré-condition, un invariant ou une post-condition).

    Si l'appelant a respecté le contrat d'appel, il n'y a pas de raison de lui retourner une exception. Une valeur de retour parait plus appropriée.

    Pour le pattern Etat, je pense qu'il n'est vraiment intéressant que lorsque la gestion des états devient complexe. Typiquement lorsque la gestion par un switch/case est trop lourde, ou pas assez évolutive. Dans ce cas, le pattern Etat permet de séparer/isoler chaque comportement. Un peu a l'instar du pattern Bridge/Adapter pour la création.
    ALGORITHME (n.m.): Méthode complexe de résolution d'un problème simple.

  4. #4
    Membre à l'essai
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 41
    Points : 19
    Points
    19
    Par défaut
    Merci pour vos réponses

    Je vais donc retirer l'utilisation du pattern dans ce cas et faire au plus simple avec une petit condition pour savoir si la case est déjà marquée ou non.

    Toujours concernant le state pattern, j'ai lu ici ou là qu'on l'utilisait aussi plus globalement au niveau du jeu. Par exemple, ici on l'utiliserait dans la classe TicTacToe qui gère la boucle générale donc le changement de tour, voir s'il y a un gagnant ou si le jeu est bloqué. Seulement, je ne comprends pas trop comment m'y prendre pour l'implémenter. Quels états y aurait-il alors ?

  5. #5
    Membre habitué
    Profil pro
    Inscrit en
    Août 2006
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 89
    Points : 170
    Points
    170
    Par défaut
    Bonjour,

    Merci pseudocode pour vos informations éclairées!

    Gaspoute, je pense que votre problème vient du fait que vous essayez d'implémenter un Tic Tac Toe autour d'un pattern Etat, au lieu d'implémenter un Tic Tac Toe tout court.
    Je sais que ce n'est pas votre but à la base, mais je pense que c'est la seule façon de bien identifier de quels états vous aurez besoin.

    Lorsque vous identifiez dans votre analyse que telle ou telle partie de votre programme/fonctionnalité doit se comporter différemment selon certaines conditions ou contraintes, le pattern Etat peut probablement être utilisé. Il est alors facile d'identifier les états : un par comportement.

    En gros, la première étape est de répondre à la question 'Qu'est-ce qui peut être décomposé en états ?', et non pas 'quels sont les états ?'.
    Une fois cette première étape franchie, la réponse à votre question sera évidente.

    Sinon concernant cette partie :
    le changement de tour, voir s'il y a un gagnant ou si le jeu est bloqué
    Attention à ne pas vous perdre entre les notions d'états et de transitions entre états.
    Là vous ne décrivez pas d'états, mais des transitions entre états.

    Dans votre première implémentation, vos états étaient 'Blank' et 'Marked', pas 'c'est la première fois que la case est utilisée' ou 'la case cliquée est invalide'.
    Votre première implémentation avait donc deux états 'Blank' et 'Marked', avec une transition 'le joueur clique sur la case'.

  6. #6
    Membre à l'essai
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 41
    Points : 19
    Points
    19
    Par défaut
    OK merci

    Les états du jeu global seraient plutôt "fini" et "en cours". Dans ce cas, pour que l'état soit "fini", la transition serait soit parce que le jeu est bloqué soit parce qu'il y a un gagnant ?

  7. #7
    Membre habitué
    Profil pro
    Inscrit en
    Août 2006
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 89
    Points : 170
    Points
    170
    Par défaut
    Bonjour,

    Je pense que vous êtes sur une bonne piste.
    J'ajouterai une transition de 'fini' vers 'en cours', juste pour ne pas avoir à relancer le programme lorsque la partie est terminée .
    Pour l'instant votre nombre d'états reste un peu léger pour mettre en avant l'intérêt du pattern, mais il offre à votre jeu une grande évolutivité.

    On peut facilement imaginer :
    • Un menu de pause pour éventuellement recommencer une partie
    • Un écran de démarrage permettant aux joueurs de choisir les croix ou les ronds, ou encore de lancer une partie contre une ia
    • Un mode 'replay' permettant de revoir une partie précédemment jouée
    • etc...


    Bref, une fois votre programme de base terminé (avec vos deux états 'fini' et 'en cours'), je vous suggère d'expérimenter l'ajout d'un ou plusieurs nouveaux états (pas forcément un de ceux précédemment indiqués).
    Essayez alors de réfléchir (idéalement de le faire) à comment vous auriez fait pour ajouter ces fonctionnalités dans le tout premier programme que vous nous avez présenté.
    Cela vous permettra de mieux cerner les avantages et inconvénients de ce pattern.

  8. #8
    Membre à l'essai
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 41
    Points : 19
    Points
    19
    Par défaut
    Je vais expérimenter ça merci

    Cependant j'ai une question. J'essaie le plus possible de séparer présentation et domaine mais avec vos exemples d'états ne fait-on pas le contraire ? A moins que vous voyez plutôt ces états comme des options du jeu ?

  9. #9
    Membre à l'essai
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 41
    Points : 19
    Points
    19
    Par défaut
    Bonjour,

    Je remonte un peu le sujet.

    Sait-on éviter la "duplication" des méthodes du contexte passé en paramètre aux classes représentant les états ?

    Dans le cas des Square, deux méthodes font la même chose si ce n'est qu'une passe par l'état et donc appelle l'autre indirectement: markSquare() et setMark().

    L'utilisateur pourrait être confus. N'y aurait-il pas moyen en utilisant deux interfaces, une définissant les méthodes visibles par l'utilisateur et une autre ajoutant celles appelées par les différents états ? C'est peut-être du chipotage pour rien.

    Merci

  10. #10
    Membre habitué
    Profil pro
    Inscrit en
    Août 2006
    Messages
    89
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Août 2006
    Messages : 89
    Points : 170
    Points
    170
    Par défaut
    Bonjour,

    J'avoue ne pas avoir très bien compris l'explication de votre problème

    Votre classe principale (Game?) possède une méthode setMark() permettant de cocher une case?
    Et comme cette classe a des états, l'état 'en cours' possède lui aussi une méthode (markSquare()) qui permet de cocher une case en appelant Game.setMark()?

    Votre problème est donc que ces deux méthodes soient visibles depuis le reste du programme? (visible par "l'utilisateur"?)

    En supposant que mon interprétation soit correcte, en C++ il y a la notion de "friend" qui permet que les membres et méthodes privées d'une classe soient accessibles depuis une autre classe qu'elle a déclarée comme amie.
    Je ne sais pas s'il y a un équivalent en Java par contre...

  11. #11
    Membre à l'essai
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 41
    Points : 19
    Points
    19
    Par défaut
    Par exemple, une classe Game a deux états JOINING et OPEN. Dans le premier, on peut encore ajouter des utilisateurs, dans le deuxième le nombre d'utilisateurs max est atteint, donc le jeu peut commencer et on ne peut plus ajouter d'utilisateur.

    J'ai deux méthodes dans Game qui ont ces formes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    public void joinPlayer(Player player) {
        state.joinPlayer(this, player);
    }
    
    public void insertPlayer(Player player) {
        players.add(player);
    }
    La première fait appel à l'état et la deuxième est appelée par l'état (indirectement par joinPlayer()). L'état JOINING ressemble à ceci:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    public void joinPlayer(Game game, Player player) {
        game.insertPlayer(player);
        if (game.getExpectedPlayerCount() == 0) {
            game.setState(OPEN);
        }
    }
    joinPlayer() et insertPlayer() sont toutes les deux visibles par l'utilisateur alors qu'il n'y en a qu'une qu'il peut réellement utiliser, celle qui dépend de l'état.

    Je pensais donc cacher la deuxième au moyen d'une interface ou peut-être est-ce inutile ?

    Je considère mon jeu un peu comme une boite noire, que je mets à disposition uniquement les méthodes qui peuvent être appelées par celui ou celle qui utilisera mon jeu dans son application, un peu comme une API.

    J'ai également une autre question: que se passe-t-il avec les états lorsque plusieurs classes héritent de Game et que ces classes possèdent des méthodes en plus qui doivent dépendre des états de Game ?

    Par exemple, Checkers et Monopoly. L'une peut déplacer un pion et la deuxième peut lancer des dés, toutes les deux peuvent uniquement s'appliquer lorsque la partie est lancée.

    Merci

  12. #12
    Rédacteur
    Avatar de pseudocode
    Homme Profil pro
    Architecte système
    Inscrit en
    Décembre 2006
    Messages
    10 062
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Architecte système
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2006
    Messages : 10 062
    Points : 16 081
    Points
    16 081
    Par défaut
    Citation Envoyé par Gaspoute Voir le message
    joinPlayer() et insertPlayer() sont toutes les deux visibles par l'utilisateur alors qu'il n'y en a qu'une qu'il peut réellement utiliser, celle qui dépend de l'état.
    (...)
    Je considère mon jeu un peu comme une boite noire, que je mets à disposition uniquement les méthodes qui peuvent être appelées par celui ou celle qui utilisera mon jeu dans son application, un peu comme une API.
    Dans ce cas, je te conseille de commencer par écrire l'interface que tu souhaites pour ton API.

    C'est seulement ensuite que tu trouveras la meilleure architecture technique pour ton application: classe multi-interfaces, builder, delegate, state, ...
    ALGORITHME (n.m.): Méthode complexe de résolution d'un problème simple.

  13. #13
    Membre à l'essai
    Profil pro
    Inscrit en
    Septembre 2006
    Messages
    41
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Septembre 2006
    Messages : 41
    Points : 19
    Points
    19
    Par défaut
    Pour l'instant j'en suis à ça:
    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
    public class Game {
    	private GameState state = GameState.JOINING;
    	private Map<Session, Player> players = new HashMap<Session, Player>();
    	private Iterator<Player> iterator;
    	private int expectedPlayerCount;
    
    	public Game(int expectedPlayerCount) {
    		this.expectedPlayerCount = expectedPlayerCount;
    	}
    
    	public void joinPlayer(Session session) {
    		players.put(session, new Player(session));
    		--expectedPlayerCount;
    		if (expectedPlayerCount == 0) {
    			playGame();
    		}
    	}
    
    	public void playGame() {
    		setState(GameState.IN_PROGRESS);
    		playRound();
    	}
    
    	public void playRound() {
    		iterator = players.values().iterator();
    	}
    
    	public void playTurn(Session session) {
    		Player player = players.get(session);
    		player.playTurn(this);
    	}
    
    	public void changeTurn() {
    		if (isOver()) {
    			endGame();
    		} else {
    			if (iterator.hasNext()) {
    				Player player = iterator.next();
    				player.takeTurn();
    			} else {
    				playRound();
    			}
    		}
    	}
    
    	public void endGame() {
    		setState(GameState.ENDED);
    	}
    
    	public void setState(GameState state) {
    		this.state = state; 
    	}
    }
    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
    public class Player {
    	private PlayerState state = PlayerState.WAITING_FOR_TURN;
    	private Session session;
    
    	public Player(Session session) {
    		this.session = session;
    	}
    
    	public void takeTurn() {
    		setState(PlayerState.PLAYING);
    	}
    
    	public void playTurn(Game game) {
    		if (getState() == PlayerState.PLAYING) {
    			/* Play his turn */
    			setState(PlayerState.WAITING_FOR_TURN);
    			game.changeTurn();
    		}
    	}
    
    	public void setState(PlayerState state) {
    		this.state = state;
    	}
    
    	public PlayerState getState() {
    		return state;
    	}
    }
    Game est une super-classe. Une série de jeux à plateau hériteront de cette classe. La méthode playTurn() dépendera du jeu, par exemple pour les dames, ça sera move(int x, int y). Je voulais juste avoir un aperçu. J'ai utilisé des enum pour les états, étant donné qu'avec l'héritage ça complique un peu les choses. Notamment si chaque jeu a des méthodes différentes pour jouer un tour.

    Les méthodes accessibles sont donc joinPlayer() et playTurn(), qui changera de signature selon le jeu, ici c'est juste à titre d'exemple.

    Je ne sais pas si dans ce contexte, vous voyez le problème que j'ai à cause des états et de l'héritage.

    Merci

Discussions similaires

  1. Java 7 : petit état des lieux du projet Lambda...
    Par adiGuba dans le forum Général Java
    Réponses: 2
    Dernier message: 28/08/2010, 10h44
  2. Memoriser l'etat des cases a cocher d'un formulaire avec pagination
    Par belaggoun2000 dans le forum Général JavaScript
    Réponses: 8
    Dernier message: 26/02/2010, 15h23
  3. [Dojo] Grid: sauvegarde de l'état des case à cocher du menu "PlaceHolder"
    Par laminfodev dans le forum Bibliothèques & Frameworks
    Réponses: 1
    Dernier message: 27/11/2009, 01h15
  4. Jeu 2D avec des cases
    Par Zoloom dans le forum Interfaces Graphiques en Java
    Réponses: 5
    Dernier message: 08/02/2008, 17h33
  5. taille dynamique des cases dans état
    Par exter666 dans le forum Access
    Réponses: 14
    Dernier message: 09/09/2005, 15h30

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