Zum Spaß versuche ich, eines der Lieblingsbrettspiele meines Sohnes als Software zu schreiben. Irgendwann erwarte ich, eine WPF-Benutzeroberfläche darüber zu erstellen, aber im Moment baue ich die Maschine, die die Spiele und ihre Regeln modelliert.
Dabei sehe ich immer wieder Probleme, von denen ich denke, dass sie vielen Brettspielen gemeinsam sind, und vielleicht haben andere sie bereits besser gelöst als ich.
(Beachten Sie, dass KI für das Spielen des Spiels und Muster für hohe Leistung für mich nicht interessant sind.)
Bisher sind meine Muster:
Verschiedene unveränderliche Typen, die Entitäten in der Spielbox darstellen, z. B. Würfel, Steine, Karten, ein Brett, Felder auf dem Brett, Geld usw.
Ein Objekt für jeden Spieler, das die Ressourcen des Spielers (z. B. Geld, Punktzahl), seinen Namen usw. enthält.
Ein Objekt, das den Status des Spiels darstellt: die Spieler, wer an der Reihe ist, das Layout der Teile auf dem Brett usw.
Eine Zustandsmaschine, die die Abbiegefolge verwaltet. Zum Beispiel haben viele Spiele ein kleines Vorspiel, in dem jeder Spieler würfelt, um zu sehen, wer zuerst geht. Das ist der Startzustand. Wenn ein Spieler an der Reihe ist, würfelt er zuerst, bewegt sich dann, muss an Ort und Stelle tanzen, dann raten andere Spieler, um welche Hühnerrasse es sich handelt, und erhalten dann Punkte.
Gibt es einen Stand der Technik, den ich nutzen kann?
EDIT: Eine Sache, die ich kürzlich erkannt habe, ist, dass der Spielstatus in zwei Kategorien unterteilt werden kann:
Spielartefaktstatus . "Ich habe 10 Dollar" oder "Meine linke Hand ist blau".
Status der Spielsequenz . "Ich habe zweimal Doppel gewürfelt; der nächste bringt mich ins Gefängnis". Eine Zustandsmaschine kann hier sinnvoll sein.
EDIT: Was ich hier wirklich suche, ist der beste Weg, um rundenbasierte Multiplayer-Spiele wie Chess oder Scrabble oder Monopoly zu implementieren. Ich bin mir sicher, dass ich ein solches Spiel erstellen könnte, indem ich es von Anfang bis Ende durcharbeite, aber wie bei anderen Designmustern gibt es wahrscheinlich einige Möglichkeiten, die Dinge viel reibungsloser zu gestalten, die ohne sorgfältiges Studium nicht offensichtlich sind. Darauf hoffe ich.
quelle
Antworten:
Es scheint, dass dies ein 2 Monate alter Thread ist, den ich gerade bemerkt habe, aber was solls. Ich habe zuvor das Gameplay-Framework für ein kommerzielles, vernetztes Brettspiel entworfen und entwickelt. Wir hatten eine sehr angenehme Erfahrung damit zu arbeiten.
Ihr Spiel kann sich wahrscheinlich in einer (nahezu) unendlichen Anzahl von Zuständen befinden, aufgrund der Permutationen von Dingen wie wie viel Geld Spieler A hat, wie viel Geld Spieler B hat und so weiter. Daher bin ich mir ziemlich sicher, dass Sie es wollen sich von Staatsmaschinen fernhalten.
Die Idee hinter unserem Framework war es, den Spielstatus als Struktur mit allen Datenfeldern darzustellen, die zusammen den vollständigen Spielstatus liefern (dh wenn Sie das Spiel auf der Festplatte speichern möchten, schreiben Sie diese Struktur aus).
Wir haben das Befehlsmuster verwendet , um alle gültigen Spielaktionen darzustellen, die ein Spieler ausführen kann. Hier wäre eine Beispielaktion:
Um zu entscheiden, ob ein Zug gültig ist, können Sie diese Aktion erstellen und dann die IsLegal-Funktion aufrufen, die den aktuellen Spielstatus übergibt. Wenn es gültig ist und der Spieler die Aktion bestätigt, können Sie die Apply-Funktion aufrufen, um den Spielstatus tatsächlich zu ändern. Indem Sie sicherstellen, dass Ihr Gameplay-Code den Spielstatus nur durch Erstellen und Übermitteln legaler Aktionen ändern kann (mit anderen Worten, die Action :: Apply-Methodenfamilie ist das einzige, was den Spielstatus direkt ändert), stellen Sie sicher, dass Ihr Spiel Zustand wird niemals ungültig sein. Mithilfe des Befehlsmusters können Sie außerdem die gewünschten Züge Ihres Spielers serialisieren und über ein Netzwerk senden, um sie in den Spielzuständen anderer Spieler auszuführen.
Es gab ein Problem mit diesem System, das sich als ziemlich elegante Lösung herausstellte. Manchmal haben Aktionen zwei oder mehr Phasen. Zum Beispiel kann der Spieler auf einem Grundstück in Monopoly landen und muss nun eine neue Entscheidung treffen. Wie ist der Spielstatus zwischen dem Würfeln des Spielers und der Entscheidung, eine Immobilie zu kaufen oder nicht? Wir haben Situationen wie diese bewältigt, indem wir ein "Action Context" -Mitglied unseres Spielstatus vorgestellt haben. Der Aktionskontext ist normalerweise null, was darauf hinweist, dass sich das Spiel derzeit in keinem speziellen Zustand befindet. Wenn der Spieler würfelt und die Würfelwurfaktion auf den Spielstatus angewendet wird, erkennt er, dass der Spieler auf einem nicht im Besitz befindlichen Grundstück gelandet ist, und kann eine neue "PlayerDecideToPurchaseProperty" erstellen. Aktionskontext, der den Index des Spielers enthält, von dem wir auf eine Entscheidung warten. Bis die RollDice-Aktion abgeschlossen ist, zeigt unser Spielstatus an, dass er derzeit darauf wartet, dass der angegebene Spieler entscheidet, ob er eine Immobilie kauft oder nicht. Es ist jetzt für alle IsLegal-Methoden aller Aktionen einfach, false zurückzugeben, mit Ausnahme der Aktionen "BuyProperty" und "PassPropertyPurchaseOpportunity", die nur zulässig sind, wenn der Spielstatus den Aktionskontext "PlayerDecideToPurchaseProperty" enthält.
Durch die Verwendung von Aktionskontexten gibt es nie einen einzigen Punkt in der Lebensdauer des Brettspiels, an dem die Struktur des Spielstatus nicht genau das darstellt, was zu diesem Zeitpunkt im Spiel passiert. Dies ist eine sehr wünschenswerte Eigenschaft Ihres Brettspielsystems. Es wird für Sie viel einfacher sein, Code zu schreiben, wenn Sie alles finden, was Sie jemals über das Geschehen im Spiel wissen möchten, indem Sie nur eine Struktur untersuchen.
Darüber hinaus erstreckt es sich sehr gut auf Netzwerkumgebungen, in denen Clients ihre Aktionen über ein Netzwerk an einen Host-Computer senden können, der die Aktion auf den "offiziellen" Spielstatus des Hosts anwenden und diese Aktion dann an alle anderen Clients zurücksenden kann Lassen Sie sie es auf ihre replizierten Spielzustände anwenden.
Ich hoffe das war prägnant und hilfreich.
quelle
Die Grundstruktur Ihrer Spiel-Engine verwendet das Statusmuster . Die Gegenstände Ihrer Spielbox sind Singletons verschiedener Klassen. Die Struktur jedes Staates kann ein Strategiemuster oder die Vorlagenmethode verwenden .
Eine Factory wird verwendet, um die Spieler zu erstellen, die in eine Liste von Spielern eingefügt werden, ein weiterer Singleton. Die GUI überwacht die Game Engine mithilfe des Observer-Musters und interagiert mit diesem mithilfe eines von mehreren Befehlsobjekten, die mithilfe des Befehlsmusters erstellt wurden . Die Verwendung von Observer und Command kann im Kontext einer passiven Ansicht verwendet werden. Abhängig von Ihren Einstellungen kann jedoch nahezu jedes MVP / MVC-Muster verwendet werden. Wenn Sie das Spiel speichern, müssen Sie ein Andenken an den aktuellen Status abrufen
Ich empfehle, einige der Muster auf dieser Website zu überprüfen und zu prüfen, ob Sie von diesen als Ausgangspunkt herangezogen werden. Wieder wird das Herz Ihres Spielbretts eine Zustandsmaschine sein. Die meisten Spiele werden durch zwei Zustände vor dem Spiel / Setup und das eigentliche Spiel dargestellt. Sie können jedoch mehr Status haben, wenn das Spiel, das Sie modellieren, mehrere unterschiedliche Spielmodi hat. Staaten müssen nicht sequentiell sein, zum Beispiel hat das Wargame Axis & Battles ein Battle Board, mit dem die Spieler Schlachten lösen können. Es gibt also drei Zustände vor dem Spiel, Hauptbrett, Schlachtbrett, wobei das Spiel ständig zwischen Hauptbrett und Schlachtbrett wechselt. Natürlich kann die Drehfolge auch durch eine Zustandsmaschine dargestellt werden.
quelle
Ich habe gerade ein zustandsbasiertes Spiel mit Polymorphismus entworfen und implementiert.
Die Verwendung einer abstrakten Basisklasse mit dem Namen
GamePhase
hat eine wichtige MethodeDies bedeutet, dass jedes
GamePhase
Objekt den aktuellen Status des Spiels enthält und ein Aufruf,turn()
den aktuellen Status zu überprüfen und den nächsten zurückzugebenGamePhase
.Jeder Beton
GamePhase
hat Konstruktoren, die den gesamten Spielzustand halten . In jederturn()
Methode sind einige Spielregeln enthalten. Während dies die Regeln verteilt, werden verwandte Regeln eng zusammengehalten. Das Endergebnis von jedemturn()
ist nur das Erstellen des nächstenGamePhase
und das Übergehen im vollen Zustand in die nächste Phase.Dies ermöglicht
turn()
es, sehr flexibel zu sein. Abhängig von Ihrem Spiel kann ein bestimmter Status in viele verschiedene Arten von Phasen verzweigen. Dies bildet eine grafische Darstellung aller Spielphasen.Auf der höchsten Ebene ist der Code zum Fahren sehr einfach:
Dies ist äußerst nützlich, da ich jetzt problemlos jeden Status / jede Phase des Spiels zum Testen erstellen kann
Um nun den zweiten Teil Ihrer Frage zu beantworten: Wie funktioniert das im Mehrspielermodus? Innerhalb bestimmt
GamePhase
s , die eine Benutzereingabe erfordern, von einem Anrufturn()
fragen würde die aktuellePlayer
sieStrategy
angesichts der aktuelle Zustand / Phase.Strategy
ist nur eine Schnittstelle aller möglichen Entscheidungen, diePlayer
man treffen kann. Dieses Setup ermöglicht auch dieStrategy
Implementierung mit AI!Auch Andrew Top sagte:
Ich denke, diese Aussage ist sehr irreführend, obwohl es viele verschiedene Spielzustände gibt, gibt es nur wenige Spielphasen. Um sein Beispiel zu behandeln, wäre alles, was es ist, ein ganzzahliger Parameter für die Konstruktoren meiner konkreten
GamePhase
s.Monopol
Ein Beispiel für einige
GamePhase
s wäre:Und einige Staaten in der Basis
GamePhase
sind:Und dann würden einige Phasen nach Bedarf ihren eigenen Status aufzeichnen, zum Beispiel würde PlayerRolls aufzeichnen, wie oft ein Spieler aufeinanderfolgende Doppel gewürfelt hat. Sobald wir die PlayerRolls-Phase verlassen haben, kümmern wir uns nicht mehr um aufeinanderfolgende Würfe.
Viele Phasen können wiederverwendet und miteinander verknüpft werden. Zum Beispiel
GamePhase
CommunityChestAdvanceToGo
würde die nächste PhasePlayerLandsOnGo
mit dem aktuellen Status erstellt und zurückgegeben. Im Konstruktor desPlayerLandsOnGo
aktuellen Spielers würde nach Go verschoben und sein Geld würde um 200 $ erhöht.quelle
Natürlich gibt es viele, viele, viele, viele, viele, viele, viele Ressourcen zu diesem Thema. Aber ich denke, Sie sind auf dem richtigen Weg, die Objekte aufzuteilen und sie ihre eigenen Ereignisse / Daten und so weiter verarbeiten zu lassen.
Wenn Sie Brettspiele auf Kachelbasis ausführen, ist es hilfreich, Routinen zu haben, die neben anderen Funktionen zwischen dem Brettarray und Zeile / Spalte und zurück abgebildet werden können. Ich erinnere mich an mein erstes Brettspiel (vor langer, langer Zeit), als ich Schwierigkeiten hatte, Row / Col aus Boardarray 5 zu bekommen.
Nostalgie. ;)
Auf jeden Fall ist http://www.gamedev.net/ ein guter Ort für Informationen. http://www.gamedev.net/reference/
quelle
Viele der Materialien, die ich online finden kann, sind Listen veröffentlichter Referenzen. Der Publikationsbereich von Game Design Patterns enthält Links zu PDF-Versionen der Artikel und Abschlussarbeiten. Viele davon sehen aus wie wissenschaftliche Arbeiten wie Design Patterns for Games . Es gibt auch mindestens ein Buch von Amazon, Patterns in Game Design .
quelle
Three Rings bietet LGPL-Java-Bibliotheken. Nenya und Vilya sind die Bibliotheken für spielbezogene Dinge.
Natürlich wäre es hilfreich, wenn in Ihrer Frage Plattform- und / oder Sprachbeschränkungen erwähnt würden.
quelle
Ich stimme der Antwort von Pyrolistical zu und ich bevorzuge seine Art, Dinge zu tun (ich habe jedoch nur die anderen Antworten überflogen).
Zufälligerweise habe ich auch seine "GamePhase" -Namen verwendet. Grundsätzlich würde ich im Fall eines rundenbasierten Brettspiels tun, dass Ihre GameState-Klasse ein Objekt der abstrakten GamePhase enthält, wie von Pyrolistical erwähnt.
Nehmen wir an, die Spielzustände sind:
Sie könnten konkrete abgeleitete Klassen für jeden Zustand haben. Haben Sie virtuelle Funktionen mindestens für:
In der Funktion StartPhase () können Sie alle Anfangswerte für einen Status festlegen, z. B. die Eingabe des anderen Players deaktivieren und so weiter.
Wenn roll.EndPhase () aufgerufen wird, stellen Sie sicher, dass der GamePhase-Zeiger auf den nächsten Status gesetzt wird.
In dieser MovePhase :: StartPhase () würden Sie beispielsweise die verbleibenden Züge des aktiven Spielers auf den Betrag setzen, der in der vorherigen Phase gewürfelt wurde.
Mit diesem Design können Sie nun Ihr Problem "3 x double = jail" innerhalb der Roll-Phase lösen. Die RollPhase-Klasse kann ihren eigenen Status verarbeiten. Beispielsweise
Ich unterscheide mich von Pyrolistical darin, dass es für alles eine Phase geben sollte, auch wenn der Spieler auf der Community-Truhe landet oder so. Ich würde das alles in der MovePhase erledigen. Dies liegt daran, dass sich der Spieler sehr wahrscheinlich zu "geführt" fühlt, wenn Sie zu viele aufeinanderfolgende Phasen haben. Wenn es zum Beispiel eine Phase gibt, in der der Spieler NUR Immobilien kaufen und dann NUR Hotels kaufen und dann NUR Häuser kaufen kann, gibt es keine Freiheit. Schlagen Sie einfach alle diese Teile in eine BuyPhase und geben Sie dem Spieler die Freiheit, alles zu kaufen, was er will. Die BuyPhase-Klasse kann leicht genug damit umgehen, welche Käufe legal sind.
Lassen Sie uns zum Schluss das Spielbrett ansprechen. Obwohl ein 2D-Array in Ordnung ist, würde ich empfehlen, ein Kacheldiagramm zu haben (wobei eine Kachel eine Position auf dem Brett ist). Im Falle eines Monopols wäre es eher eine doppelt verknüpfte Liste. Dann hätte jede Fliese ein:
Es wäre also viel einfacher, etwas zu tun wie:
Die AdvanceTo-Funktion kann Ihre schrittweisen Animationen oder was auch immer Sie möchten verarbeiten. Und natürlich auch die verbleibenden Züge verringern.
Der Rat von RS Conley zum Beobachtermuster für die GUI ist gut.
Ich habe noch nicht viel gepostet. Hoffe das hilft jemandem.
quelle
Wenn Ihre Frage nicht sprach- oder plattformspezifisch ist. dann würde ich empfehlen, dass Sie AOP-Muster für State, Memento, Command usw. berücksichtigen.
Was ist die .NET Antwort auf AOP ???
Versuchen Sie auch, einige coole Websites wie http://www.chessbin.com zu finden
quelle