Pour le plaisir, j'essaie d'écrire l'un des jeux de société préférés de mon fils comme un logiciel. Finalement, je m'attends à construire 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 de voir des problèmes que je pense communs à de nombreux jeux de société, et peut-être que d'autres les ont déjà mieux résolus que moi.
(Notez que l'IA pour jouer au jeu et les schémas autour des hautes performances ne m'intéressent pas.)
Jusqu'à présent, mes modèles sont les suivants:
Plusieurs types immuables représentant des entités dans la boîte de jeu, par exemple dés, pions, cartes, un tableau, des espaces sur le tableau, de l'argent, etc.
Un objet pour chaque joueur, qui contient les ressources des joueurs (par exemple l'argent, le score), leur nom, etc.
Un objet qui représente l'état du jeu: les joueurs, qui est le tour, la disposition des pièces sur le plateau, etc.
Une machine d'état qui gère la séquence de virages. Par exemple, de nombreux jeux ont un petit avant-match où chaque joueur lance pour voir qui va en premier; c'est l'état de départ. Lorsque le tour d'un joueur commence, il roule d'abord, puis il se déplace, puis il doit danser sur place, puis les autres joueurs devinent de quelle race de poulet il s'agit, puis ils reçoivent des points.
Y a-t-il un art antérieur dont je peux profiter?
EDIT: Une chose que j'ai réalisée récemment est que l'état du jeu peut être divisé en deux catégories:
État d'artefact du je. "J'ai 10 $" ou "ma main gauche est en bleu".
État de la séquence du je. "J'ai roulé deux fois deux fois; le suivant me met en prison". Une machine d'état peut avoir un sens ici.
EDIT: Ce que je recherche vraiment ici, c'est la meilleure façon d'implémenter des jeux multijoueurs au tour par tour comme les échecs ou le scrabble ou Monopoly. Je suis sûr que je pourrais créer un tel jeu en le parcourant 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. Voilà ce que j'espère.
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é en réseau commercial. Nous avons eu une expérience très agréable en travaillant avec.
Votre jeu peut probablement être dans une quantité (presque) infinie d'états en raison des permutations de choses comme combien d'argent le joueur A a, combien d'argent le joueur B a, et etc ... Par conséquent, je suis presque sûr que vous voulez pour rester loin des machines d'état.
L'idée derrière notre framework était de représenter l'état du jeu en tant que structure avec tous les champs de données qui, ensemble, fournissent l'état complet du jeu (c'est-à-dire: si vous vouliez enregistrer le jeu sur le disque, vous écrivez cette structure).
Nous avons utilisé le Command Pattern pour représenter toutes les actions de jeu valides qu'un joueur pourrait 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
};
Vous voyez donc que pour décider si un coup est valide, vous pouvez construire cette action puis appeler sa fonction IsLegal, en passant dans l'état de jeu actuel. S'il est valide et que le joueur confirme l'action, vous pouvez appeler la fonction Appliquer 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 (donc 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 s'est avéré être un problème avec ce système qui s'est avéré avoir une solution assez élégante. Parfois, les actions comportaient deux phases ou plus. Par exemple, le joueur peut atterrir sur une propriété de Monopoly 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 celui où il décide ou non d'acheter une propriété? Nous avons géré des situations comme celle-ci en mettant en vedette un membre "Contexte d'action" de notre état de jeu. Le contexte d'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 lancement 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 contexte d'action "PlayerDecideToPurchaseProperty" qui contient l'index du joueur nous attendons une décision de. 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 d'acheter ou non une propriété. Il est désormais facile pour la méthode IsLegal de toutes les autres actions 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 point dans la durée de vie du jeu de plateau où la structure d'état du jeu ne représente pas exactement ce qui se passe dans le jeu à ce moment-là. C'est une propriété très souhaitable de votre système de jeux 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 faire écho de cette action à tous les autres clients à demandez-leur de l'appliquer à leurs états de jeu répliqués.
J'espère que cela a été concis et utile.
La structure de base de votre moteur de jeu utilise le State Pattern . Les éléments de votre boîte de jeu sont singletons de différentes classes. La structure de chaque état peut utiliser modèle de stratégie ou la méthode de modèle .
Un Factory est utilisé pour 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 d'observateur et interagira avec celui-ci en utilisant l'un des nombreux objets de commande créés à l'aide du modèle de commande . L'utilisation d'Observer and Command peut être utilisée avec dans le contexte d'un Passive View Mais à peu près n'importe quel modèle MVP/MVC pourrait être utilisé selon vos préférences. Lorsque vous enregistrez le jeu, vous devez saisir un memento de son état actuel
Je recommande de regarder certains des modèles sur ce site et de voir si l'un d'eux vous attrape comme point de départ. Encore une fois, le cœur de votre plateau de jeu va être une machine d'état. La plupart des jeux seront représentés par deux états d'avant-match/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 ne doivent pas être séquentiels, par exemple, le Wargame Axis & Battles a un tableau de bataille que les joueurs peuvent utiliser pour résoudre les batailles. Il y a donc trois états d'avant-match, le plateau principal, le plateau de combat, le jeu passant continuellement du plateau principal au plateau de combat. Bien sûr, la séquence de virages peut également être représentée par une machine d'état.
Je viens de terminer la conception et l'implémentation d'un jeu basé sur un état utilisant le polymorphisme.
Utiliser une classe abstraite de base appelée GamePhase
qui a une méthode importante
abstract public GamePhase turn();
Cela signifie que chaque objet GamePhase
contient l'état actuel du jeu, et un appel à turn()
examine son état actuel et renvoie le prochain GamePhase
.
Chaque béton GamePhase
a des constructeurs qui détiennent l'état de jeu entier. Chaque méthode turn()
contient un peu des règles du jeu. Bien que cela propage les règles, il maintient les règles connexes proches les unes des autres. Le résultat final de chaque turn()
crée simplement le GamePhase
suivant et passe à l'état complet dans la phase suivante.
Cela permet à turn()
d'être très flexible. Selon votre jeu, un état donné peut se ramifier à de nombreux types de phases différents. 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();
}
C'est extrêmement utile car je peux maintenant créer facilement n'importe quel état/phase du jeu pour le tester
Maintenant, pour répondre à la deuxième partie de votre question, comment cela fonctionne-t-il en multijoueur? Dans certains GamePhase
qui nécessitent une entrée utilisateur, un appel de turn()
demanderait au Player
actuel son Strategy
étant donné l'état/la phase actuelle. Strategy
n'est qu'une interface de toutes les décisions possibles qu'un Player
peut prendre. Cette configuration permet également à Strategy
d'être implémenté avec l'IA!
Andrew Top a également déclaré:
Votre jeu peut probablement être dans une quantité (presque) infinie d'états en raison des permutations de choses comme combien d'argent le joueur A a, combien d'argent le joueur B a, et etc ... Par conséquent, je suis presque sûr que vous voulez pour rester loin des machines d'état.
Je pense que cette déclaration est très trompeuse, bien qu'il soit vrai qu'il existe de nombreux é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 béton GamePhase
.
Voici quelques exemples de GamePhase
:
Et certains états de la base GamePhase
sont:
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, GamePhase
CommunityChestAdvanceToGo
créerait la phase suivante PlayerLandsOnGo
avec l'état actuel et la renverrait. Dans le constructeur de PlayerLandsOnGo
, le joueur actuel serait déplacé vers Go et son argent serait incrémenté de 200 $.
Bien sûr, il y a beaucoup, beaucoup, beaucoup, beaucoup, beaucoup, beaucoup, beaucoup de ressources sur ce sujet. Mais je pense que vous êtes sur la bonne voie en divisant les objets et en les laissant gérer leurs propres événements/données, etc.
Lorsque vous faites des jeux de société en mosaïque, vous trouverez agréable d'avoir des routines pour mapper entre le tableau de bord et la ligne/col et inversement, ainsi que d'autres fonctionnalités. Je me souviens de mon premier jeu de société (il y a très longtemps) lorsque je me suis débattu avec la façon d'obtenir des lignes/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/
La plupart des documents que je peux trouver en ligne sont des listes de références publiées. La section des publications de Game Design Patterns contient des liens vers PDF versions des articles et des thèses. Beaucoup d'entre eux ressemblent à des articles académiques comme Design Patterns for Games . Il existe également au moins un livre disponible sur Amazon, Patterns in Game Design .
Three Rings propose des bibliothèques LGPL'd Java. Nenya et Vilya sont les bibliothèques pour les jeux liés au jeu.
Bien sûr, il serait utile que votre question mentionne des restrictions de plate-forme et/ou de langue que vous pourriez avoir.
Je suis d'accord avec la réponse de Pyrolistical et je préfère sa façon de faire les choses (j'ai juste survolé les autres réponses cependant).
Par coïncidence, j'ai également utilisé son nom "GamePhase". Fondamentalement, ce que je ferais dans le cas d'un jeu de plateau 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:
Vous pouvez 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, etc.
Lorsque roll.EndPhase () est appelé, assurez-vous que le pointeur GamePhase est défini sur l'état suivant.
phase = new MovePhase();
phase.StartPhase();
Dans cette MovePhase :: StartPhase (), vous devez par exemple définir les mouvements restants du joueur actif sur le 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 le coffre communautaire ou quelque chose. 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 ne peut acheter que des propriétés, puis uniquement des hôtels et ensuite uniquement des maisons, c'est comme s'il n'y avait pas de liberté. Il suffit de claquer 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 facilement gérer les achats légaux.
Enfin, abordons le plateau de jeu. Bien qu'un tableau 2D soit bien, je recommanderais d'avoir un graphique de tuiles (où une tuile est une position sur le tableau). Dans le cas du monopole, ce serait plutôt une liste à double liaison. Chaque tuile aurait alors:
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 tout ce que vous aimez. 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.
Y a-t-il un art antérieur dont je peux profiter?
Si votre question n'est pas spécifique à la langue ou à la plate-forme. alors je vous recommanderais 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 sympas tels que http://www.chessbin.com