Il metodo attende l'azione dell'utente

4

Ho un problema strutturale per un'applicazione su cui sto lavorando. Nell'interesse della piena divulgazione, è per un incarico universitario, quindi non sto cercando te per darmi una risposta, ma aiutami a pensare attraverso l'approccio migliore.

Sto costruendo un gioco usando un approccio OO. Ho una classe astratta chiamata Player da cui derivano diversi tipi. Questi rappresentano il computer e i giocatori umani. Quindi ho una classe HumanPlayer derivata che deve fornire un'implementazione per un metodo chiamato ChooseCard. Il modo in cui l'utente seleziona una carta è cliccando su una carta in una GUI. Quindi la carta verrà identificata dall'utente attraverso un gestore di eventi.

La parte rilevante del mio codice è elencata di seguito. Al momento la classe di gioco scorre attraverso ciascun giocatore e chiama il metodo PlayCard su ciascun giocatore. Non sono sicuro di come programmare il metodo ChooseCard nella classe HumanPlayer in modo che possa attendere che l'utente selezioni una scheda da riprodurre, ma l'interfaccia utente rimane reattiva. Idealmente, voglio essere in grado di trattare ciascuno dei tipi di giocatori derivati lo stesso (cioè come il giocatore della classe base).

Questa domanda chiede fondamentalmente la stessa cosa che io sono ma Non mi piacciono molto le risposte.

La risposta a questa domanda è interessante, utilizzando la classe TaskCompletionSource.

Non sono fisso su questa struttura se esiste una soluzione superiore, anche se devo mantenere la struttura della classe.

modifiche Aggiunto un diagramma di classe per i modelli della mia applicazione e uno screenshot di come appare il tabellone (al momento).

Ecco il mio pensiero attuale: L'utente farà clic sulla carta che desidera giocare. L'evento click si propagherà al CardViewModel associato (a questo punto l'azione potrebbe essere gestita al livello Model o ViewModel). Ora CardViewModel (o Card ) non sa chi è interessato a sapere che l'utente l'ha selezionato. La parte interessata sarà PlayerViewModel / HumanPlayer a cui appartiene la Card , ma saranno interessati solo quando si aspettano una risposta da parte dell'utente. Cioè quando viene chiamato HumanPlayer.PlayCard () .

Quindi penso che CardViewModel / Card dovrebbe generare un evento a cui l'oggetto HumanPlayer può iscriversi quando è interessato all'utente che effettua una selezione di carte .

Posso rendere HumanPlayer.ChooseCard () iscriversi agli eventi CardSelected di ognuna delle carte. Non sono sicuro di come far aspettare il metodo senza bloccare fino a quando uno di quegli eventi non viene attivato.

abstract public class Player
{
    public IReadOnlyList<Card> Cards { get { return cards.AsReadOnly(); } }

    public Card PlayCard()
    {
        var cardToPlay = ChooseCard();
        // ...
    }

    protected abstract Card ChooseCard();
}

public class HumanPlayer : Player
{
    protected override Card ChooseCard()
    {
        // TODO: request a card from the user.
        throw new NotImplementedException();
    }
}

public class Game
{
    private List<Player> livingPlayers = new List<Player>();

    public void PlayRound()
    {
        var cardsPlayedByPlayers = GetCardsPlayed();
        // ...
    }

    private Dictionary<Player, Card> GetCardsPlayed()
    {
        var cardsPlayedByPlayers = new Dictionary<Player, Card>();

        foreach (var player in livingPlayers.Where(p => !p.IsDead))
        {
            // Player.PlayCard() called here getting chosen card from the player.
            cardsPlayedByPlayers.Add(player, player.PlayCard());
        }

        return cardsPlayedByPlayers;
    }
}

Aggiornamento:

Ho messo il codice finale su GitHub qui da quando il compito è terminato. Ora puoi vedere chiaramente il codice completo e se vuoi criticarlo, sarei curioso di sentire i tuoi pensieri.

    
posta Rossco 29.09.2014 - 01:21
fonte

3 risposte

2

Hai l'organizzazione del codice "inside-out". Quello che stai cercando di fare è rispondere a un giocatore che ha scelto una carta, ma invece lo stai forzando nella costruzione dell'oggetto che è un posto scomodo da bloccare (la maggior parte delle persone non si aspetta che i costruttori siano "pesanti") .

Ecco le tue opzioni all'interno del ciclo di gioco:

1) Quando valuti il ciclo di gioco, fallo da un thread separato oltre al thread dell'interfaccia utente e blocca quel thread finché l'interfaccia utente non ha finito di scegliere una carta. Questo ti permette di usare 'chooseCard' come metodo sull'oggetto player - lo rimuoveresti dal costruttore e rendi pubblico il metodo.

2) Se si valuta il ciclo di gioco sul thread principale, allora non si tratta realmente di oggetti giocatore ma di oggetti che rappresentano l'input (probabilmente lo si sta già facendo). In tal caso, è necessario implementare un delegato o una funzione di callback che consenta al circuito di gioco di reagire a un evento "scelto dalla carta" anziché bloccarlo in un elenco di oggetti per ciascuno di essi. Quindi, il ciclo di gioco manterrebbe nel thread principale lo stato di tutte le carte scelte e le "processerà" solo dopo che tutti i partecipanti avranno risposto.

Tra i due, quest'ultimo è probabilmente più quello che stai cercando se non sei un fan del multi-threading.

    
risposta data 29.09.2014 - 23:00
fonte
1

Il modo in cui lo faresti è usando un delegato. Anziché attendere l'input dell'utente all'interno della classe, i delegati ti consentono di attivare un evento personalizzato e fare in modo che la GUI gestisca le cose per te.

Di seguito è riportato uno schema per l'impostazione di uno:

  • Definisci prima un delegato pubblico in modo che sia accessibile dalla GUI.
  • Quindi in ChooseCard() , aggiungi una chiamata al delegato. Questo dirà alla GUI di gestire le specifiche di scelta di una carta.
  • Nella GUI, aggiungi un riferimento al delegato. Questa parte è simile all'aggiunta di un riferimento a un gestore di eventi.
  • Una volta che il riferimento è stato fatto, puoi aggiungere un blocco di codice per lanciare un MessageBox o una sorta di notifica che chiederà all'utente il loro input. Di nuovo, questa parte è simile ai gestori di eventi.
  • Infine, quando l'utente effettua una selezione, passa i dati alla classe Player .
risposta data 29.09.2014 - 02:10
fonte
0

grazie per tutti i tuoi suggerimenti Sembra che il consenso sia utilizzare i thread per gestire le cose. Al momento ho una soluzione che al momento non utilizza thread e mi piacerebbe una critica. Mi sono reso conto che se stavi giocando nella vita reale devi aspettare che TUTTI i giocatori siano pronti a giocare. Quindi ho bisogno di una proprietà IsReady per ogni giocatore. Dal momento che sto implementando INPC praticamente su tutto ciò che l'oggetto del Gioco può essere a conoscenza di quando lo stato di pronto di ogni giocatore cambia. Ecco il mio codice così com'è ora:

abstract public class Player
{
    public IReadOnlyList<Card> Cards { get { return cards.AsReadOnly(); } }

    public Card PlayCard()
    {
        if (!IsReady)
        {
            throw new InvalidOperationException("Cannot play card when player is not ready.");
        }

        var cardToPlay = ChooseCard();
        // ...
    }

    public abstract bool IsReady { get; }
    protected abstract Card ChooseCard();
}

public class HumanPlayer : Player
{
    protected override Card ChooseCard()
    {
        // return the selected card.
    }

    public override bool IsReady
    {
        get 
        { 
            // return true if a card is selected.
        }
    }
}

public class Game
{
    private List<Player> livingPlayers = new List<Player>();

    public bool CanPlayRound { 
        get
        {
            // check that all Player.IsReady returns true.
        }
    }

    public void PlayRound()
    {
        var cardsPlayedByPlayers = GetCardsPlayed();
        // ...
    }

    private Dictionary<Player, Card> GetCardsPlayed()
    {
        var cardsPlayedByPlayers = new Dictionary<Player, Card>();

        foreach (var player in livingPlayers.Where(p => !p.IsDead))
        {
            // Player.PlayCard() called here getting chosen card from the player.
            cardsPlayedByPlayers.Add(player, player.PlayCard());
        }

        return cardsPlayedByPlayers;
    }
}
    
risposta data 01.10.2014 - 06:33
fonte

Leggi altre domande sui tag