Qualche modello per modellare i giochi da tavolo? [chiuso]


93

Per divertimento, sto cercando di scrivere uno dei giochi da tavolo preferiti di mio figlio come un pezzo di software. Alla fine mi aspetto di costruire una UI WPF su di essa, ma in questo momento sto costruendo la macchina che modella i giochi e le sue regole.

Mentre lo faccio, continuo a vedere problemi che penso siano comuni a molti giochi da tavolo, e forse altri li hanno già risolti meglio di me.

(Nota che l'intelligenza artificiale per giocare e gli schemi intorno alle alte prestazioni non sono interessanti per me.)

Finora i miei schemi sono:

  • Diversi tipi immutabili che rappresentano entità nella scatola del gioco, ad esempio dadi, dama, carte, un tabellone, spazi sul tabellone, denaro, ecc.

  • Un oggetto per ogni giocatore, che contiene le risorse dei giocatori (es. Denaro, punteggio), il loro nome, ecc.

  • Un oggetto che rappresenta lo stato del gioco: i giocatori, chi è il turno, la disposizione dei pezzi sul tabellone, ecc.

  • Una macchina a stati che gestisce la sequenza dei turni. Ad esempio, molti giochi hanno un piccolo pre-gioco in cui ogni giocatore tira per vedere chi inizia per primo; questo è lo stato iniziale. Quando il turno di un giocatore inizia, prima rotola, poi si muove, poi deve ballare sul posto, poi gli altri giocatori indovinano di che razza di pollo sono, quindi ricevono punti.

C'è qualche arte precedente di cui posso trarre vantaggio?

EDIT: Una cosa che ho capito di recente è che lo stato del gioco può essere suddiviso in due categorie:

  • Stato degli artefatti del gioco . "Ho $ 10" o "la mia mano sinistra è blu".

  • Stato della sequenza di gioco . "Ho tirato il doppio due volte; il prossimo mi mette in prigione". Una macchina a stati può avere senso qui.

EDIT: Quello che sto davvero cercando qui è il modo migliore per implementare giochi multiplayer a turni come Chess o Scrabble o Monopoly. Sono sicuro che potrei creare un gioco del genere semplicemente lavorandoci dall'inizio alla fine, ma, come altri Design Pattern, ci sono probabilmente alcuni modi per far andare le cose molto più agevolmente che non sono ovvie senza uno studio accurato. Questo è quello che spero.


3
Stai costruendo una specie di Hokey Pokey, Monopoli, mashup di sciarade?
Anthony Mastrean

Avrai bisogno di una macchina a stati per qualsiasi regola che si basi sullo stato (err ...) come la regola dei tre doppi per Monopoli. Pubblicherei una risposta più completa ma non ho esperienza nel farlo. Potrei pontificare al riguardo però.
MSN

Risposte:


115

sembra che questo sia un thread di 2 mesi che ho notato solo ora, ma che diamine. Ho già progettato e sviluppato la struttura di gioco per un gioco da tavolo commerciale in rete. Abbiamo avuto un'esperienza molto piacevole lavorandoci.

Il tuo gioco può probabilmente trovarsi in una quantità infinita di stati a causa delle permutazioni di cose come quanti soldi ha il giocatore A, quanti soldi ha il giocatore B, ecc ... Pertanto, sono abbastanza sicuro che tu voglia stare lontano dalle macchine statali.

L'idea alla base del nostro framework era di rappresentare lo stato del gioco come struttura con tutti i campi di dati che insieme forniscono lo stato completo del gioco (cioè: se vuoi salvare il gioco su disco, scrivi quella struttura).

Abbiamo utilizzato il modello di comando per rappresentare tutte le azioni di gioco valide che un giocatore poteva compiere. Ecco un'azione di esempio:

class RollDice : public Action
{
  public:
  RollDice(int player);

  virtual void Apply(GameState& gameState) const; // Apply the action to the gamestate, modifying the gamestate
  virtual bool IsLegal(const GameState& gameState) const; // Returns true if this is a legal action
};

Quindi vedi che per decidere se una mossa è valida, puoi costruire quell'azione e quindi chiamare la sua funzione IsLegal, passando nello stato di gioco corrente. Se è valido e il giocatore conferma l'azione, puoi chiamare la funzione Applica per modificare effettivamente lo stato del gioco. Assicurandoti che il tuo codice di gioco possa modificare lo stato del gioco solo creando e inviando Azioni legali (quindi, in altre parole, la famiglia di metodi Azione :: Applica è l'unica cosa che modifica direttamente lo stato del gioco), assicurati che il tuo gioco lo stato non sarà mai valido. Inoltre, utilizzando il modello di comando, è possibile serializzare le mosse desiderate del giocatore e inviarle su una rete per essere eseguite negli stati di gioco di altri giocatori.

Alla fine c'è stato un trucco con questo sistema che si è rivelato una soluzione abbastanza elegante. A volte le azioni avrebbero due o più fasi. Ad esempio, il giocatore può atterrare su una proprietà a Monopoli e ora deve prendere una nuova decisione. Qual è lo stato del gioco tra il momento in cui il giocatore ha lanciato i dadi e prima di decidere se acquistare o meno una proprietà? Abbiamo gestito situazioni come questa presentando un membro "Contesto d'azione" del nostro stato di gioco. Il contesto dell'azione normalmente sarebbe nullo, indicando che il gioco non è attualmente in uno stato speciale. Quando il giocatore lancia i dadi e l'azione di lancio dei dadi viene applicata allo stato del gioco, si renderà conto che il giocatore è atterrato su una proprietà non posseduta e può creare una nuova "PlayerDecideToPurchaseProperty" contesto dell'azione che contiene l'indice del giocatore da cui stiamo aspettando una decisione. Quando l'azione RollDice è stata completata, il nostro stato di gioco indica che è attualmente in attesa che il giocatore specificato decida se acquistare una proprietà non lo è. Ora è facile che il metodo IsLegal di tutte le altre azioni restituisca false, ad eccezione delle azioni "BuyProperty" e "PassPropertyPurchaseOpportunity", che sono legali solo quando lo stato del gioco ha il contesto dell'azione "PlayerDecideToPurchaseProperty".

Attraverso l'uso di contesti d'azione, non c'è mai un singolo punto nella vita del gioco da tavolo in cui la struttura dello stato del gioco non rappresenta ESATTAMENTE ciò che sta accadendo nel gioco in quel momento. Questa è una proprietà molto desiderabile del tuo sistema di gioco da tavolo. Sarà molto più facile scrivere codice quando potrai trovare tutto ciò che vorresti sapere su ciò che sta accadendo nel gioco esaminando solo una struttura.

Inoltre, si estende molto bene agli ambienti di rete, dove i client possono inviare le loro azioni su una rete a una macchina host, che può applicare l'azione allo stato di gioco "ufficiale" dell'host, e quindi ripetere quell'azione a tutti gli altri client per chiedi loro di applicarlo ai loro stati di gioco replicati.

Spero che questo sia stato conciso e utile.


4
Non credo sia conciso, ma è utile! Votato.
Jay Bazuzi

Sono contento che sia stato utile ... Quali parti non erano concise? Sarei felice di fare una modifica di chiarimento.
Andrew Top

Sto costruendo un gioco a turni in questo momento e questo post è stato davvero utile!
Kiv,

Ho letto che Memento è il pattern da usare per annullare ... Memento vs Command Pattern per annullare, i tuoi pensieri plz ..
zotherstupidguy

Questa è la migliore risposta che ho letto finora in Stackoverflow. GRAZIE!
Papipo

18

La struttura di base del tuo motore di gioco utilizza lo State Pattern . Gli oggetti della tua scatola di gioco sono singoli di varie classi. La struttura di ogni stato può utilizzare Strategy Pattern o Template Method .

Una Factory viene utilizzata per creare i giocatori che vengono inseriti in un elenco di giocatori, un altro singleton. La GUI terrà sotto controllo il motore di gioco utilizzando il pattern Observer e interagirà con questo utilizzando uno dei numerosi oggetti Command creati utilizzando il pattern di comando . L'uso di Observer e Command può essere utilizzato nel contesto di una vista passiva, ma è possibile utilizzare quasi tutti i pattern MVP / MVC a seconda delle preferenze. Quando salvi il gioco devi prendere un ricordo del suo stato attuale

Consiglio di esaminare alcuni dei modelli su questo sito e vedere se qualcuno di loro ti prende come punto di partenza. Anche in questo caso il cuore del tuo tabellone di gioco sarà una macchina a stati. La maggior parte dei giochi sarà rappresentata da due stati pre-partita / configurazione e dal gioco vero e proprio. Ma puoi avere più stati se il gioco che stai modellando ha diverse modalità di gioco distinte. Gli stati non devono essere sequenziali, ad esempio il wargame Axis & Battles ha un tabellone di battaglia che i giocatori possono utilizzare per risolvere le battaglie. Quindi ci sono tre stati pre-gioco, tabellone principale, tabellone di battaglia con il gioco che passa continuamente tra il tabellone principale e il tabellone di battaglia. Ovviamente la sequenza dei turni può essere rappresentata anche da una macchina a stati.


15

Ho appena finito di progettare e implementare un gioco basato sullo stato usando il polimorfismo.

Utilizzando una classe astratta di base chiamata GamePhaseche ha un metodo importante

abstract public GamePhase turn();

Ciò significa che ogni GamePhaseoggetto mantiene lo stato corrente del gioco e una chiamata a turn()guarda al suo stato corrente e restituisce il successivo GamePhase.

Ogni calcestruzzo GamePhaseha costruttori che mantengono l' intero stato del gioco. Ogni turn()metodo contiene un po 'delle regole del gioco. Anche se questo diffonde le regole, mantiene le regole correlate ravvicinate. Il risultato finale di ciascuno turn()è semplicemente creare il successivo GamePhasee passare dallo stato completo alla fase successiva.

Questo permette turn()di essere molto flessibili. A seconda del gioco, un determinato stato può diramarsi a molti diversi tipi di fasi. Questo forma un grafico di tutte le fasi del gioco.

Al livello più alto il codice da guidare è molto semplice:

GamePhase state = ...initial phase
while(true) {
    // read the state, do some ui work
    state = state.turn();
}

Questo è estremamente utile in quanto ora posso creare facilmente qualsiasi stato / fase del gioco per il test

Ora, per rispondere alla seconda parte della tua domanda, come funziona in multiplayer? Entro alcuni messaggi di posta GamePhaseelettronica che richiedono l'input dell'utente, una chiamata da turn()chiederebbe alla corrente il Playerloro Strategydato lo stato / fase corrente. Strategyè solo un'interfaccia di tutte le possibili decisioni che una Playerlattina può prendere. Questa configurazione consente anche Strategydi essere implementata con AI!

Anche Andrew Top ha detto:

Il tuo gioco può probabilmente trovarsi in una quantità infinita di stati a causa delle permutazioni di cose come quanti soldi ha il giocatore A, quanti soldi ha il giocatore B, ecc ... Pertanto, sono abbastanza sicuro che tu voglia stare lontano dalle macchine statali.

Penso che questa affermazione sia molto fuorviante, mentre è vero che ci sono molti stati di gioco diversi, ci sono solo poche fasi di gioco. Per gestire il suo esempio, tutto ciò che sarebbe un parametro intero per i costruttori dei miei concrete GamePhases.

Monopolio

Un esempio di alcuni GamePhasepotrebbe essere:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail, Go, ecc.)
  • PlayerTrades
  • PlayerPurchasesProperty
  • GiocatoreAcquistiCase
  • PlayerPurchasesHotels
  • PlayerPaysRent
  • PlayerBankrupts
  • (Tutte le carte Chance e Community Chest)

E alcuni stati nella base GamePhasesono:

  • Elenco dei giocatori
  • Giocatore attuale (chi è il turno)
  • Soldi / proprietà del giocatore
  • Case / Hotel su proprietà
  • Posizione del giocatore

E poi alcune fasi registrerebbero il proprio stato secondo necessità, ad esempio PlayerRolls registrerebbe il numero di volte in cui un giocatore ha ottenuto doppi consecutivi. Una volta lasciata la fase PlayerRolls, non ci preoccupiamo più dei lanci consecutivi.

Molte fasi possono essere riutilizzate e collegate tra loro. Ad esempio GamePhase CommunityChestAdvanceToGo, creerebbe la fase successiva PlayerLandsOnGocon lo stato corrente e la restituirà. Nel costruttore del PlayerLandsOnGogiocatore corrente verrebbe spostato su Go e il suo denaro verrebbe incrementato di $ 200.


8

Naturalmente ci sono molte, molte, molte, molte, molte, molte, molte risorse su questo argomento. Ma penso che tu sia sulla strada giusta per suddividere gli oggetti e lasciare che gestiscano i propri eventi / dati e così via.

Quando si eseguono giochi da tavolo basati su piastrelle, sarà bello avere routine per mappare tra l'array della scheda e la riga / colonna e viceversa, insieme ad altre caratteristiche. Ricordo il mio primo gioco da tavolo (molto tempo fa) quando ho lottato su come ottenere row / col da boardarray 5.

1  2  3  
4 (5) 6  BoardArray 5 = row 2, col 2
7  8  9  

Nostalgia. ;)

Ad ogni modo, http://www.gamedev.net/ è un buon posto per le informazioni. http://www.gamedev.net/reference/


Perché non usi solo un array bidimensionale? Quindi il compilatore può gestirlo per te.
Jay Bazuzi

La mia scusa è che è successo tanto, tanto tempo fa. ;)
Stefan

1
gamedev ha un sacco di cose, ma non ho visto proprio quello che stavo cercando.
Jay Bazuzi

che lingua stavi usando?
zotherstupidguy

Basic, Basica, QB, QuickBasic e così via. ;)
Stefan


3

Three Rings offre librerie Java LGPL. Nenya e Vilya sono le librerie per le cose relative ai giochi.

Naturalmente, sarebbe utile se la tua domanda menzionasse la piattaforma e / o le restrizioni linguistiche che potresti avere.


"Alla fine mi aspetto di creare un'interfaccia utente WPF", che significa .NET. Almeno, per quanto ne so.
Mark Allen

Zuppa alfabetica di cui non sono a conoscenza.
jmucchiello

Sì, sto facendo .NET, ma la mia domanda non è specifica per lingua o piattaforma.
Jay Bazuzi,

2

Sono d'accordo con la risposta di Pyrolistical e preferisco il suo modo di fare le cose (ho solo sfogliato le altre risposte però).

Casualmente ho anche usato la sua denominazione "GamePhase". Fondamentalmente quello che farei nel caso di un gioco da tavolo a turni è che la tua classe GameState contenga un oggetto astratto GamePhase come menzionato da Pyrolistical.

Diciamo che gli stati del gioco sono:

  1. Roll
  2. Mossa
  3. Acquista / Non comprare
  4. Prigione

Potresti avere classi derivate concrete per ogni stato. Avere funzioni virtuali almeno per:

StartPhase();
EndPhase();
Action();

Nella funzione StartPhase () è possibile impostare tutti i valori iniziali per uno stato, ad esempio disabilitando l'input dell'altro giocatore e così via.

Quando roll.EndPhase () viene chiamato, assicurati che il puntatore GamePhase sia impostato allo stato successivo.

phase = new MovePhase();
phase.StartPhase();

In questo MovePhase :: StartPhase () potresti ad esempio impostare le mosse rimanenti del giocatore attivo sull'ammontare ottenuto nella fase precedente.

Ora con questo progetto in atto potresti risolvere il tuo problema "3 x double = jail" all'interno della fase Roll. La classe RollPhase può gestire il proprio stato. Per esempio

GameState state; //Set in constructor.
Die die;         // Only relevant to the roll phase.
int doublesRemainingBeforeJail;
StartPhase()
{
    die = new Die();
    doublesRemainingBeforeJail = 3;
}

Action()
{
    if(doublesRemainingBeforeJail<=0)
    {
       state.phase = new JailPhase(); // JailPhase::StartPhase(){set moves to 0};            
       state.phase.StartPhase();
       return;
    }

    int die1 = die.Roll();
    int die2 = die.Roll();

    if(die1 == die2)
    {
       --doublesRemainingBeforeJail;
       state.activePlayer.AddMovesRemaining(die1 + die2);
       Action(); //Roll again.
    }

    state.activePlayer.AddMovesRemaining(die1 + die2);
    this.EndPhase(); // Continue to moving phase. Player has X moves remaining.
}

Mi differenzia da Pyrolistical in quanto dovrebbe esserci una fase per tutto, incluso quando il giocatore atterra sul forziere comunitario o qualcosa del genere. Gestirei tutto questo in MovePhase. Questo perché se hai troppe fasi sequenziali, il giocatore molto probabilmente si sentirà troppo "guidato". Ad esempio, se c'è una fase in cui il giocatore può comprare SOLO proprietà e poi SOLO alberghi e poi SOLO comprare case, è come se non ci fosse libertà. Basta inserire tutte quelle parti in un BuyPhase e dare al giocatore la libertà di acquistare tutto ciò che desidera. La classe BuyPhase può gestire abbastanza facilmente quali acquisti sono legali.

Infine affrontiamo il tabellone. Anche se un array 2D va bene, consiglierei di avere un grafico a tessere (dove una tessera è una posizione sul tabellone). Nel caso del monoppoly si tratterebbe piuttosto di una lista doppiamente collegata. Quindi ogni tessera avrebbe un:

  1. previousTile
  2. nextTile

Quindi sarebbe molto più facile fare qualcosa come:

While(movesRemaining>0)
  AdvanceTo(currentTile.nextTile);

La funzione AdvanceTo può gestire le tue animazioni passo passo o qualunque cosa tu voglia. E ovviamente diminuisci anche le mosse rimanenti.

Il consiglio di RS Conley sul pattern dell'osservatore per la GUI è buono.

Non ho pubblicato molto prima. Spero che questo aiuti qualcuno.


1

C'è qualche arte precedente di cui posso trarre vantaggio?

Se la tua domanda non è specifica della lingua o della piattaforma. quindi consiglierei di prendere in considerazione modelli AOP per stato, ricordo, comando, ecc.

Qual è la risposta .NET a AOP ???

Prova anche a trovare alcuni siti Web interessanti come http://www.chessbin.com

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.