Des modèles pour modéliser des jeux de société? [fermé]

93

Pour le plaisir, j'essaye d'écrire un des jeux de société préférés de mon fils sous forme de logiciel. Finalement, je prévois de créer une interface utilisateur WPF dessus, mais en ce moment, je construis la machine qui modélise les jeux et ses règles.

En faisant cela, je continue à voir des problèmes que je pense communs à de nombreux jeux de société, et peut-être que d'autres les ont déjà résolus mieux que moi.

(Notez que l'IA pour jouer au jeu et les modèles autour de hautes performances ne m'intéressent pas.)

Jusqu'à présent, mes modèles sont:

  • Plusieurs types immuables représentant des entités dans la boîte de jeu, par exemple des dés, des pions, des cartes, un plateau, des espaces sur le plateau, de l'argent, etc.

  • Un objet pour chaque joueur, qui contient les ressources des joueurs (ex: argent, score), leur nom, etc.

  • Un objet qui représente l'état du jeu: les joueurs, à qui c'est le tour, la disposition des pièces sur le plateau, etc.

  • Une machine d'état qui gère la séquence des tours. Par exemple, de nombreux jeux ont un petit pré-jeu où chaque joueur lance pour voir qui joue en premier; c'est l'état de départ. Quand le tour d'un joueur commence, il roule d'abord, puis il bouge, puis il doit danser sur place, puis les autres joueurs devinent de quelle race de poulet ils sont, puis ils reçoivent des points.

Y a-t-il un état de la technique dont je peux profiter?

EDIT: Une chose que j'ai réalisé récemment est que l'état du jeu peut être divisé en deux catégories:

  • État de l'artefact du jeu . «J'ai 10 $» ou «ma main gauche est en bleu».

  • État de la séquence de jeu . "J'ai roulé deux fois en double; le suivant me met en prison". Une machine à états peut avoir un sens ici.

EDIT: Ce que je recherche vraiment ici, c'est la meilleure façon de mettre en œuvre des jeux multijoueurs au tour par tour comme Chess ou Scrabble ou Monopoly. Je suis sûr que je pourrais créer un tel jeu en travaillant simplement du début à la fin, mais, comme d'autres modèles de conception, il existe probablement des moyens de rendre les choses beaucoup plus fluides qui ne sont pas évidentes sans une étude approfondie. C'est ce que j'espère.

Jay Bazuzi
la source
3
Vous construisez une sorte de mashup Hokey Pokey, Monopoly, charades?
Anthony Mastrean
Vous aurez besoin d'une machine à états pour toute règle qui repose sur l'état (err ...) comme la règle des trois doubles pour Monopoly. Je publierais une réponse plus complète mais je n'ai aucune expérience en la matière. Je pourrais cependant pontifier à ce sujet.
MSN

Réponses:

115

il semble que ce soit un fil de 2 mois que je viens de remarquer maintenant, mais que diable. J'ai déjà conçu et développé le cadre de jeu pour un jeu de société commercial en réseau. Nous avons eu une expérience très agréable en travaillant avec.

Votre jeu peut probablement être dans un nombre (presque) infini d'états en raison des permutations de choses comme combien d'argent le joueur A a, combien d'argent le joueur B a, etc. Par conséquent, je suis à peu près sûr que vous voulez pour rester à l'écart des machines d'État.

L'idée derrière notre cadre était de représenter l'état du jeu comme une structure avec tous les champs de données qui, ensemble, fournissent l'état complet du jeu (c'est-à-dire: si vous vouliez sauvegarder le jeu sur le disque, vous écrivez cette structure).

Nous avons utilisé le modèle de commande pour représenter toutes les actions de jeu valides qu'un joueur pouvait effectuer. Voici un exemple d'action:

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
};

Ainsi, vous voyez que pour décider si un mouvement est valide, vous pouvez construire cette action et appeler sa fonction IsLegal, en passant l'état actuel du jeu. S'il est valide et que le joueur confirme l'action, vous pouvez appeler la fonction Apply pour modifier réellement l'état du jeu. En vous assurant que votre code de jeu ne peut modifier l'état du jeu qu'en créant et en soumettant des actions légales (en d'autres termes, la famille de méthodes Action :: Apply est la seule chose qui modifie directement l'état du jeu), alors vous vous assurez que votre jeu l'état ne sera jamais invalide. De plus, en utilisant le modèle de commande, vous permettez de sérialiser les mouvements souhaités de votre joueur et de les envoyer sur un réseau pour être exécutés sur les états de jeu des autres joueurs.

Il a fini par y avoir un piège avec ce système qui s'est avéré être une solution assez élégante. Parfois, les actions comportaient deux phases ou plus. Par exemple, le joueur peut atterrir sur une propriété en monopole et doit maintenant prendre une nouvelle décision. Quel est l'état du jeu entre le moment où le joueur a lancé les dés et avant qu'il ne décide d'acheter une propriété ou non? Nous avons géré des situations comme celle-ci en présentant un membre "Action Context" de notre état de jeu. Le contexte de l'action serait normalement nul, indiquant que le jeu n'est actuellement dans aucun état spécial. Lorsque le joueur lance les dés et que l'action de lancer des dés est appliquée à l'état du jeu, il se rend compte que le joueur a atterri sur une propriété qui ne lui appartient pas et peut créer un nouveau "PlayerDecideToPurchaseProperty" contexte d'action contenant l'index du joueur dont nous attendons une décision. Au moment où l'action RollDice est terminée, notre état de jeu indique qu'il attend actuellement que le joueur spécifié décide s'il achète ou non une propriété. Il est désormais facile pour toutes les autres actions de la méthode IsLegal de renvoyer false, à l'exception des actions "BuyProperty" et "PassPropertyPurchaseOpportunity", qui ne sont légales que lorsque l'état du jeu a le contexte d'action "PlayerDecideToPurchaseProperty".

Grâce à l'utilisation de contextes d'action, il n'y a jamais un seul moment dans la durée de vie du jeu de société où la structure de l'état du jeu ne représente pas complètement exactement ce qui se passe dans le jeu à ce moment-là. C'est une propriété très souhaitable de votre système de jeu de société. Il vous sera beaucoup plus facile d'écrire du code lorsque vous pourrez trouver tout ce que vous voulez savoir sur ce qui se passe dans le jeu en examinant une seule structure.

En outre, il s'étend très bien aux environnements en réseau, où les clients peuvent soumettre leurs actions sur un réseau à une machine hôte, qui peut appliquer l'action à l'état de jeu "officiel" de l'hôte, puis renvoyer cette action à tous les autres clients pour demandez-leur de l'appliquer à leurs états de jeu répliqués.

J'espère que cela a été concis et utile.

Andrew Top
la source
4
Je ne pense pas que ce soit concis, mais c'est utile! J'ai voté pour.
Jay Bazuzi
Heureux que cela ait été utile ... Quelles parties n'étaient pas concises? Je serais heureux de faire une modification de clarification.
Andrew Top
Je construis un jeu au tour par tour en ce moment et cet article a été vraiment utile!
Kiv
J'ai lu que Memento est le modèle à utiliser pour annuler ... Memento vs Command Pattern pour annuler, vos pensées plz ..
zotherstupidguy
C'est la meilleure réponse que j'ai lue dans Stackoverflow jusqu'à présent. MERCI!
Papipo
18

La structure de base de votre moteur de jeu utilise le modèle d'état . Les objets de votre boîte de jeu sont des singletons de différentes classes. La structure de chaque état peut utiliser un modèle de stratégie ou la méthode de modèle .

Une Factory permet de créer les joueurs qui sont insérés dans une liste de joueurs, un autre singleton. L'interface graphique surveillera le moteur de jeu en utilisant le modèle Observer et interagira avec celui-ci en utilisant l'un des nombreux objets Command créés à l'aide du modèle de commande . L'utilisation d'Observer et de Command peut être utilisée dans le contexte d'une vue passive Mais à peu près n'importe quel modèle MVP / MVC peut être utilisé en fonction de vos préférences. Lorsque vous enregistrez le jeu, vous devez récupérer un souvenir de son état actuel

Je recommande de regarder certains des modèles sur ce site et de voir si l'un d'entre eux vous saisit comme point de départ. Encore une fois, le cœur de votre plateau de jeu sera une machine à états. La plupart des jeux seront représentés par deux états avant le jeu / configuration et le jeu réel. Mais vous pouvez avoir plus d'états si le jeu que vous modélisez a plusieurs modes de jeu distincts. Les états n'ont pas besoin d'être séquentiels, par exemple le wargame Axis & Battles a un plateau de bataille que les joueurs peuvent utiliser pour résoudre des batailles. Il y a donc trois états avant le jeu, le plateau principal, le plateau de bataille, le jeu basculant continuellement entre le plateau principal et le plateau de combat. Bien entendu, la séquence de tours peut également être représentée par une machine à états.

RS Conley
la source
15

Je viens de terminer la conception et l'implémentation d'un jeu basé sur l'état utilisant le polymorphisme.

Utilisation d'une classe abstraite de base appelée GamePhasequi a une méthode importante

abstract public GamePhase turn();

Cela signifie que chaque GamePhaseobjet contient l'état actuel du jeu, et un appel à turn()regarde son état actuel et renvoie le suivantGamePhase .

Chaque béton GamePhasea des constructeurs qui détiennent l' état du jeu entier . Chaque turn()méthode contient un peu des règles du jeu. Bien que cela répartisse les règles, cela permet de rapprocher les règles connexes. Le résultat final de chacun turn()est simplement de créer le suivantGamePhase et de passer à l'état complet à la phase suivante.

Ceci permet turn() d'être très flexible. En fonction de votre jeu, un état donné peut se ramener à de nombreux types de phases. Cela forme un graphique de toutes les phases du jeu.

Au plus haut niveau, le code à piloter est très simple:

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

Ceci est extrêmement utile car je peux maintenant facilement créer n'importe quel état / phase du jeu pour le test

Maintenant, pour répondre à la deuxième partie de votre question, comment cela fonctionne-t-il en multijoueur? Dans certains GamePhases qui nécessitent une entrée de l'utilisateur, un appel de turn()demanderait le courant de Playerleur Strategyétat / phase actuel. Strategyest juste une interface de toutes les décisions possibles qu'un Playerpeut prendre. Cette configuration permet également Strategyd'être implémentée avec l'IA!

Andrew Top a également déclaré:

Votre jeu peut probablement être dans un nombre (presque) infini d'états en raison des permutations de choses comme combien d'argent le joueur A a, combien d'argent le joueur B a, etc. Par conséquent, je suis à peu près sûr que vous voulez pour rester à l'écart des machines d'État.

Je pense que cette déclaration est très trompeuse, alors qu'il est vrai qu'il y a beaucoup d'états de jeu différents, il n'y a que quelques phases de jeu. Pour gérer son exemple, il ne s'agirait que d'un paramètre entier pour les constructeurs de mes concrets GamePhase.

Monopole

Un exemple de certains GamePhaseserait:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail, Go, etc.)
  • JoueurTrades
  • PlayerPurchasesProperty
  • JoueurAchatsMaisons
  • JoueurAchatsHôtels
  • PlayerPaysRent
  • PlayerBankrupts
  • (Toutes les cartes Chance et Coffre communautaire)

Et certains états de la base GamePhasesont:

  • Liste des joueurs
  • Joueur actuel (à qui revient)
  • Argent / propriété du joueur
  • Maisons / Hôtels sur Propriétés
  • La position du joueur

Et puis certaines phases enregistreraient leur propre état si nécessaire, par exemple PlayerRolls enregistrerait le nombre de fois qu'un joueur a lancé des doubles consécutifs. Une fois que nous quittons la phase PlayerRolls, nous ne nous soucions plus des lancers consécutifs.

De nombreuses phases peuvent être réutilisées et liées entre elles. Par exemple, le GamePhase CommunityChestAdvanceToGocréerait la phase suivante PlayerLandsOnGoavec l'état actuel et le renverrait. Dans le constructeur du PlayerLandsOnGojoueur actuel serait déplacé vers Go et leur argent serait incrémenté de 200 $.

Pyrolistique
la source
8

Bien sûr, il existe de nombreuses, nombreuses, nombreuses, nombreuses, nombreuses, nombreuses, nombreuses ressources sur ce sujet. Mais je pense que vous êtes sur la bonne voie en divisant les objets et en les laissant gérer ses propres événements / données, etc.

Lorsque vous faites des jeux de société basés sur des tuiles, vous trouverez agréable d'avoir des routines à mapper entre le tableau de bord et les lignes / colonnes et inversement, ainsi que d'autres fonctionnalités. Je me souviens de mon premier jeu de société (il y a longtemps) lorsque je me suis demandé comment obtenir des rangées / colonnes à partir de boardarray 5.

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

Nostalgie. ;)

Quoi qu'il en soit, http://www.gamedev.net/ est un bon endroit pour obtenir des informations. http://www.gamedev.net/reference/

Stefan
la source
Pourquoi n'utilisez-vous pas simplement un tableau à 2 dimensions? Ensuite, le compilateur peut gérer cela pour vous.
Jay Bazuzi
Mon excuse est que c'était il y a longtemps. ;)
Stefan
1
gamedev a une tonne de trucs, mais je n'ai pas vraiment vu ce que je cherchais.
Jay Bazuzi
quelle langue utilisiez-vous?
zotherstupidguy
Basic, Basica, QB, QuickBasic et ainsi de suite. ;)
Stefan
5

La plupart des documents que je peux trouver en ligne sont des listes de références publiées. La section Publications de Game Design Patterns contient des liens vers des versions PDF des articles et des thèses. Beaucoup d'entre eux ressemblent à des articles académiques tels que Design Patterns for Games . Il existe également au moins un livre disponible sur Amazon, Patterns in Game Design .

Eric Weilnau
la source
3

Three Rings propose des bibliothèques Java LGPL. Nenya et Vilya sont les bibliothèques pour les choses liées au jeu.

Bien sûr, cela aiderait si votre question mentionnait des restrictions de plate-forme et / ou de langue que vous pourriez avoir.

jmucchiello
la source
«Finalement, je prévois de créer une interface utilisateur WPF» - cela signifie .NET. Du moins, pour autant que je sache.
Mark Allen
Soupe à l'alphabet, je ne suis pas au courant.
jmucchiello
Oui, je fais .NET, mais ma question n'est pas spécifique à la langue ou à la plate-forme.
Jay Bazuzi
2

Je suis d'accord avec la réponse de Pyrolistical et je préfère sa façon de faire les choses (je viens de parcourir les autres réponses cependant).

Par coïncidence, j'ai également utilisé sa dénomination "GamePhase". Fondamentalement, ce que je ferais dans le cas d'un jeu de société au tour par tour, c'est que votre classe GameState contienne un objet de la GamePhase abstraite comme mentionné par Pyrolistical.

Disons que les états du jeu sont:

  1. Rouleau
  2. Bouge toi
  3. Acheter / Ne pas acheter
  4. Prison

Vous pourriez avoir des classes dérivées concrètes pour chaque état. Avoir des fonctions virtuelles au moins pour:

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

Dans la fonction StartPhase (), vous pouvez définir toutes les valeurs initiales d'un état, par exemple en désactivant l'entrée de l'autre joueur et ainsi de suite.

Lorsque roll.EndPhase () est appelé, assurez-vous que le pointeur GamePhase est défini sur l'état suivant.

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

Dans ce MovePhase :: StartPhase (), vous définiriez par exemple les mouvements restants du joueur actif au montant obtenu lors de la phase précédente.

Maintenant, avec cette conception en place, vous pouvez régler votre problème "3 x double = prison" à l'intérieur de la phase Roll. La classe RollPhase peut gérer son propre état. Par exemple

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.
}

Je diffère de Pyrolistical en ce qu'il devrait y avoir une phase pour tout, y compris lorsque le joueur atterrit sur un coffre communautaire ou quelque chose comme ça. Je gérerais tout cela dans le MovePhase. En effet, si vous avez trop de phases séquentielles, le joueur se sentira très probablement trop «guidé». Par exemple, s'il y a une phase où le joueur peut UNIQUEMENT acheter des propriétés et ensuite SEULEMENT acheter des hôtels et ensuite SEULEMENT acheter des maisons, c'est comme s'il n'y avait pas de liberté. Il suffit de regrouper toutes ces pièces en une seule BuyPhase et de donner au joueur la liberté d'acheter tout ce qu'il veut. La classe BuyPhase peut gérer assez facilement les achats légaux.

Enfin, abordons le plateau de jeu. Bien qu'un tableau 2D convienne, je recommanderais d'avoir un graphique de tuiles (où une tuile est une position sur le plateau). Dans le cas du monopole, il s'agirait plutôt d'une liste à double lien. Ensuite, chaque tuile aurait un:

  1. previousTile
  2. nextTile

Il serait donc beaucoup plus facile de faire quelque chose comme:

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

La fonction AdvanceTo peut gérer vos animations étape par étape ou ce que vous voulez. Et aussi décrémenter les mouvements restants bien sûr.

Les conseils de RS Conley sur le modèle d'observateur pour l'interface graphique sont bons.

Je n'ai pas beaucoup posté auparavant. J'espère que cela aide quelqu'un.

Reasurria
la source
1

Y a-t-il un état de la technique dont je peux profiter?

Si votre question n'est pas spécifique à la langue ou à la plate-forme. alors je vous recommande de considérer les modèles AOP pour l'état, le souvenir, la commande, etc.

Quelle est la réponse .NET à AOP ???

Essayez également de trouver des sites Web intéressants tels que http://www.chessbin.com

zotherstupidguy
la source