Trasmissione dei dati tra stati concreti?

6

Ho un'implementazione dello stato machine che è piuttosto semplice, ma ho un problema di progettazione a cui non conosco una soluzione elegante. Ecco alcuni codici in parte pseudo per illustrare.

class MainRoomState:State

    public override void HandleJoinSuccess(string gameName, List<LobbyPlayer> players)
    {

        Context.CurrentState = new LobbyState(Context);
        //the players list needs to be in the LobbyState
        //that data is specific to the LobbyState
    }

Fondamentalmente, l'evento che attiva la modifica dello stato fornisce anche dati che appartengono al nuovo stato, e non so se c'è un buon modo per farlo oltre a una classe condivisa nell'oggetto Contesto genitore, o maneggiando il costruttore per questo caso speciale.

Modifica - in modo più dettagliato

Ho il mio stato configurato per incapsulare i dati specifici dello stato e la logica, che include, in questo caso, una schermata della GUI che appartiene allo stato corrente. Questo sembra essere un modo più elegante di gestire non solo la separazione della logica, ma anche i dati specifici dello stato.

Ecco come appare il mio stato astratto,

public abstract class GameState
{

    public GameState(Game context)
    {
        Context = context;
        Net = context.Net;
    }

    //Incoming events
    public abstract void HandleConnected();
    public abstract void HandleDisconnected(string reason);
    public abstract void HandleNetworkNotification(string msg);
    public abstract void HandleGameStarting();
    //there's about 20 more methods not shown here 

Ecco la mia classe di gioco, che funziona principalmente come titolare di dati, ma controlla anche il timer del ciclo di gioco, gli eventi principali di attivazione, ecc.

public class Game
{
    public Game()
    {
       //...bunch of init code here
       CurrentState = new DisconnectedState(this);
    }
    public GameState CurrentState
    {
        get;
        set;
    }

    //more methods, etc
}

Vedrai che ho optato per il contenimento composito di CurrentState anziché implementare i metodi pass-through. Ciò era dovuto principalmente al fatto che non mi piaceva il disordine nel passare le chiamate quando non avevo davvero bisogno di farlo. Inoltre, l'intero stato memorizza un riferimento a Context, piuttosto che passarlo per metodo.

Ho due domande / problemi con il modello.

  1. Ogni stato concreto DEVE implementare in qualche modo, OGNI metodo nello stato astratto. Questo è un ridicolo esagerato, e attualmente la maggior parte dei miei stati usa solo il 20-40% dei metodi, lasciandomi un paio di pagine di questo:

public override void HandleGameStarting() { //not handled in this state }

  1. Il mio secondo problema è che ogni stato ha i propri dati che devono solo esistere mentre si trovano in quello stato. Non voglio memorizzare un enorme conglomerato di dati nell'oggetto Contesto quando un modo molto più incapsulato di gestirlo sarebbe metterlo nello stato.

Osservando il tuo esempio, suppongo che una risposta alla mia domanda iniziale sia che potrei passare correttamente i metodi allo stato attuale e fare semplicemente una specie di modifica dei dati,

Gioco di classe

public override void HandleJoinSuccess(string gameName, List<LobbyPlayer> players)
{

    CurrentState = new LobbyState(this);
    CurrentState.HandleData(gameName,players);
    //the players list needs to be in the LobbyState
    //that data is specific to the LobbyState
}
    
posta Kal_Torak 04.02.2012 - 09:39
fonte

1 risposta

5

Penso che potresti trovarti un po 'impantanato nel significato di stato inglese, rispetto al modello di stato (o alla macchina a stati finiti, che è in realtà una rappresentazione schematica di un modello di stato). Entrambi sono appropriati qui, ma non dovrebbero essere confusi.

Lo State Pattern è qualcosa che dovrebbe, in base a vari stimoli comuni, operare sullo stato del gioco in modo diverso in momenti diversi del gioco. Quindi, come hai giustamente concluso, il palco della lobby (che presumo sia mentre le persone si uniscono a un gioco) è uno stato in questo contesto.

Lo stato del gioco è un elenco di giocatori, pezzi, carte, dadi, qualsiasi altra cosa e tutte le informazioni necessarie per simularli. Potresti chiamarlo contesto.

Ogni stato dovrebbe ricevere un messaggio UserClicked dall'applicazione che riceve il contesto e le coordinate o l'area cliccata e dovrebbe operare su quelle di conseguenza. Le informazioni che stai cercando di dare la proprietà dell'oggetto State non appartengono a questo, appartiene al gioco stesso, insieme allo Stato.

Lo stato può anche avere un metodo UserPressedKey, un metodo TimerTicked o qualsiasi altro tipo di stimolo su cui dovrebbe agire. Ma ognuno di questi dovrebbe agire sullo stato del gioco piuttosto che essere lo stato di gioco.

Un metodo importante dello stato sarà ScreenRefresh, che disegnerà il contesto per l'utente.

Ecco un esempio molto approssimativo:

abstract class ApplicationState {

    void Begin(GameContext context);
    void UserClicked(GameContext context, int x, int y);
    void UserPressedKey(GameContext context, char key);
    void TimerTicked(GameContext context);
    void ScreenRefresh(GameContext context);

    protected void OnStateChanged(ApplicationState newState) {

        // inform interested parties that state has changed,
        // using the observer pattern

    }

}

class LobbyState : ApplicationState
{
    void Begin(GameContext context) {

        context.Players = new User[context.NoOfPlayers];

    }

    void UserClicked(GameContext context, int x, int y) {

        // Find a displayed player box which contains click coords

        int index = -1;
        for (i=0; i < context.Players.Length; i++) {
            if (PlayerBox[i].Contains(x, y)) {
                index = i;
            }
        }

        if (index > -1) {

            if (context.Player[index] == null) {
                context.Player[index] = context.ActivePlayer;
            } else if (context.Player[index] == context.ActivePlayer()) {
                context.Player[index] = null;
            } else {
                ErrorSound.Play();
            }

        } else if (CloseIcon.Contains(x, y)) {

            OnStateChanged(new ExittingState());

        } else {

            ErrorSound.Play();

        }

    }

    void UserPressedKey(GameContext context, char key) {

         if (key = 'J' or key = 'j') {

             if (! context.AddPlayerRandomly(context.ActiveUser)) {
                 ErrorSound.Play();
             }

             if (context.GameIsNowFull()) {
                 OnStateChanged(new InitializingState());
             }

         } else if key = Esc {

             if (!context.RemovePlayer(context.ActiveUser)) {
                 ErrorSound.Play();
             }

         } else {

             ErrorSound.Play();

         }

    }

    void TimerTicked(GameContext context) {

        // there is no use for a game timer while
        // we're trying to fill the game, but you
        // want one to tick anyway, because the
        // application doesn't know which state
        // it is in.

    }

    void ScreenRefresh(GameContext context) {

       DrawPlayerBoxes(context.Screen, context.Players);
       DrawExitIcon(context.Screen);

    }

}

class InitializingState : ApplicationState {

    private double waitSpinnerAngle = 0;

    void Begin(GameContext context) {

        context.RandomizePlayOrder();
        context.ShuffleCards();

        // all other game initialization rules here

        OnContextChanged(new PlayerUpState(context.Players[0]));

    }

    void UserClicked(GameContext context, int x, int y) {

        // Not responding to user input for a moment
        ErrorSound.Play();

    }

    void UserPressedKey(GameContext context, char key) {

        // Not responding to user input for a moment
        ErrorSound.Play();

    }

    void TimerTicked(GameContext context) {

        waitSpinnerAngle += 0.05;
        if (waitSpinnerAngle > 1) waitSpinnerAngle = 0;

    }

    void ScreenRefresh(GameContext context) {

         DrawWaitSpinner(waitSpinnerAngle);

    }

}

Vedi dove sto andando con questo? L'Applicazione stessa è quindi semplice come impostare lo stato iniziale, agganciare un ascoltatore all'evento OnStateChanged e chiamare Begin. Quando l'ascoltatore sente che l'evento è chiamato, sgancia il tuo vecchio stato, aggancia il nuovo e chiama di nuovo Begin.

Tutto il resto viene attivato da eventi del mouse, della tastiera o del timer, passato direttamente allo stato corrente senza conoscenza dello stato in cui si trova attualmente il gioco. Se stai utilizzando questo gioco su una rete, avrai anche bisogno di un evento per i cambi di stato ricevuti da altri giocatori.

Ogni cosa nel tuo Stato e Contesto è ora molto verificabile da unità e viene osservata la separazione delle preoccupazioni. Sebbene, tra le altre cose, potresti voler rifattorizzare il metodo LobbyState # UserClicked.

Ricorda, questo è solo un esempio gratuito e ottieni quello che hai pagato per questo. Non provare ad applicarlo direttamente al tuo gioco. Usalo per capire come dovrebbe funzionare lo State Pattern nel contesto di un gioco.

    
risposta data 04.02.2012 - 12:02
fonte

Leggi altre domande sui tag