Ich entwickle ein Brettspiel mit einer Spielklasse, die den Spielfluss steuert, und Spielern, die der Spielklasse zugeordnet sind. Das Brett ist nur eine visuelle Klasse, aber die Kontrolle über die Bewegungen liegt ganz beim Spiel.
Der Spieler kann versuchen, falsche Bewegungen auszuführen, die dazu führen, dass das Spiel dem Spieler mitteilt, dass diese Art von Bewegung nicht zulässig ist (und den Grund dafür angeben). In einigen Fällen kann der Spieler jedoch einfach eine Bewegung ausführen und erkennen, dass dies nicht die beste Bewegung ist, oder einfach das Klicken auf das Brett verpassen oder einfach einen anderen Ansatz ausprobieren.
Das Spiel kann gespeichert und geladen werden, sodass bei einem Ansatz alle Daten vor der Bewegung gespeichert werden können und kein Rollback zulässig ist, der Benutzer jedoch die letzte automatisch gespeicherte Runde laden kann. Dies sieht nach einem netten Ansatz aus, beinhaltet jedoch eine Benutzerinteraktion, und das Neuzeichnen des Boards kann für den Benutzer mühsam sein. Gibt es einen besseren Weg, um solche Dinge zu tun, oder ist die Architektur in dieser Sache wirklich wichtig?
Die Spielklasse und die Spielerklasse sind nicht kompliziert, daher ist es eine gute Idee, die Klassen zu klonen, oder die Spieldaten von den Spielregeln zu trennen, um einen besseren Ansatz zu finden, oder das Speichern / Laden (auch automatisch bei einem angeforderten Rollback) ist in Ordnung?
UPDATE: Wie dieses Spiel funktioniert: Es hat ein Hauptbrett, auf dem Sie Züge machen (einfache Züge), und ein Spielerbrett, das auf die Züge reagiert, macht auf dem Hauptbrett. Es hat auch Reaktionsbewegungen entsprechend den Bewegungen anderer Spieler und auf sich selbst. Sie können auch reagieren, wenn Sie nicht an der Reihe sind und Dinge auf Ihrem Brett tun. Vielleicht kann ich nicht jede Bewegung rückgängig machen, aber ich mag die Idee des Rückgängigmachens / Wiederherstellens, die in einer der aktuellen Antworten schwebt.
quelle
Antworten:
Warum nicht die Historie aller durchgeführten Bewegungen (sowie aller anderen nicht deterministischen Ereignisse) speichern? Auf diese Weise können Sie jederzeit einen bestimmten Spielstatus rekonstruieren.
Dies benötigt deutlich weniger Speicherplatz als das Speichern aller Spielzustände und ist relativ einfach zu implementieren.
quelle
Das Erstellen eines Rückgängig-Systems ist konzeptionell ziemlich einfach. Sie müssen nur die Änderungen verfolgen. Sie benötigen einen Stapel und einen Objekttyp, der einen Teil des Status des Spiels beschreibt. Ihr Stapel sollte ein Stapel von Arrays / Listen von Spielstatusobjekten sein.
Der Trick ist, keine Bewegungen aufzuzeichnen, die Leute machen . Ich sehe das in den anderen Antworten, und es kann getan werden, aber es ist viel komplizierter. Notieren Sie stattdessen, wie das Spielbrett aussah, bevor der Zug ausgeführt wurde. Wenn Sie beispielsweise ein Stück verschieben, haben Sie zwei Änderungen vorgenommen: Das Stück hat ein Quadrat verlassen, und das Stück wurde auf ein neues Quadrat gelegt. Wenn Sie ein Array erstellen, das zeigt, wie diese beiden Quadrate vor Beginn des Zugs ausgesehen haben, können Sie den Zug rückgängig machen, indem Sie diese beiden Quadrate einfach so wiederherstellen, wie sie vor dem Zug waren.
Oder wenn Ihr Spielstatus in den Figuren und nicht in den Quadraten gehalten wird, zeichnen Sie die Position der Figur auf, bevor sie sich bewegt. (Und wenn Ihre Figur mit anderen Figuren interagiert, z. B. mit einer Schachaufnahme, notieren Sie, wo sich diese anderen Figuren befanden, bevor sie geändert wurden.)
Wenn eine Bewegung passiert:
Wenn der Benutzer Rückgängig sagt:
quelle
Eine gute Möglichkeit, dies zu implementieren, besteht darin, Ihre Bewegungen in Form von Befehlsobjekten zu kapseln. Stellen Sie sich eine Befehlsschnittstelle mit den Methoden
move(Position newPosition)
und vorundo
. Eine konkrete Klasse kann diese Schnittstelle so implementieren, dass sie für diemove
Methode die aktuelle Position auf der Platine speichern kann (Position ist eine Klasse, die wahrscheinlich die Zeilen- und Spaltenwerte enthält) und dann eine Bewegung zu der durch identifizierten Zeile und Spalte ausführen kannnewPosition
. Danach fügt die Methode den Befehl (this
) einem globalen Stapel von Befehlsobjekten hinzu.Wenn der Benutzer nun zum vorherigen Schritt zurückkehren muss, entfernen Sie einfach die letzte Befehlsinstanz vom Stapel und rufen Sie ihre
undo
Methode auf. Auf diese Weise können Sie auch auf so viele Schritte zurückgreifen, wie Sie benötigen.Ich hoffe, das hilft.
quelle
Ein alter Thread wird gestoßen, aber der einfachste Weg, dieses Problem zu lösen, besteht zumindest in komplexen Szenarien darin, einfach alles zu kopieren, bevor der Benutzer arbeitet ...
... klingt verschwenderisch, oder? Außer wenn es wirklich verschwenderisch ist, besteht Ihr nächster Schritt darin, das Kopieren billiger zu machen, damit Teile, die im nächsten Schritt nicht geändert werden, nicht tief kopiert werden.
In meinem Fall arbeite ich oft mit Gigabyte an Daten, aber der Benutzer berührt oft nur Megabyte für eine Operation, so dass das Kopieren von allem normalerweise extrem teuer und lächerlich verschwenderisch wäre. Aber ich habe diese Datenstrukturen eingerichtet, die sich um flache Daten drehen Kopieren, was sich nicht ändert, und ich finde, dass dies die einfachste Lösung ist, und es eröffnet auch viele neue Möglichkeiten, wie unveränderliche persistente Datenstrukturen, sichereres Multithreading, Knotennetzwerke, die etwas eingeben und etwas Neues ausgeben, zerstörungsfreie Bearbeitung, Instanzierung Objekte (die beispielsweise ihre teuren Netzdaten flach kopieren können, aber den Klon haben, haben ihre eigene einzigartige Position in der Welt, während sie kaum Speicherplatz beanspruchen) usw.
Jedes Projekt ist seitdem diesem Grundmuster gefolgt, anstatt jede kleine Zustandsänderung sorgfältig aufzeichnen zu müssen und für Dutzende verschiedener Datentypen, die nicht in ein homogenes Modell passen (Bild- / Texturmalerei, Eigenschaftsänderungen, Netzänderungen, Gewichtsänderungen, hierarchische Szenenänderungen, Shader-Änderungen, die nicht eigenschaftsbezogen waren, wie das Austauschen untereinander, Umschlagänderungen usw. usw. usw.). Wenn dies in Bezug auf Speicher und Zeit zu verschwenderisch ist, entwickeln wir kein ausgefalleneres Rückgängig-System, sondern versuchen, das Kopieren so zu optimieren, dass Teilkopien in den teuersten Datenstrukturen erstellt werden können. Damit bleibt es ein Optimierungsdetail, bei dem Sie sofort eine korrekt funktionierende Rückgängig-Implementierung haben können, die immer korrekt bleibt, und immer korrekt zu bleiben, ist fantastisch, wenn in der Vergangenheit
Die andere Möglichkeit besteht darin, die Deltas (Änderungen) einzeln aufzuzeichnen, und das habe ich früher getan, aber für sehr komplexe Großsoftware war es oft sehr fehleranfällig und langwierig, da es für einen Plugin-Entwickler sehr einfach war um zu vergessen, dass einige Änderungen im Rückgängig-Stapel aufgezeichnet wurden, als brandneue Konzepte in das System eingeführt wurden.
Unabhängig davon, ob Sie den gesamten Spielstatus kopieren oder Deltas aufzeichnen, besteht eine Sache darin, Zeiger (unter der Annahme von C oder C ++) nach Möglichkeit zu vermeiden, da dies die Dinge erheblich komplexiert. Wenn Sie Indizes verwenden, wird stattdessen alles einfacher, da Indizes nicht mit Kopien ungültig werden (entweder des gesamten Anwendungsstatus oder eines Teils davon). Wenn Sie beispielsweise bei einer Diagrammdatenstruktur diese vollständig kopieren oder nur Deltas aufzeichnen möchten, wenn Benutzer neue Verbindungen herstellen oder Verbindungen im Diagramm trennen, möchte der Status Verknüpfungen zu den alten Knoten speichern, auf die Sie stoßen können Probleme mit der Ungültigmachung und solchen Dingen. Es ist viel einfacher, wenn die Verbindungen relative Indizes in einem Array verwenden, da es viel einfacher ist, zu verhindern, dass diese Indizes ungültig werden, als Zeiger.
quelle
Ich würde eine Rückgängig-Liste der (gültigen) Züge führen, die der Spieler gemacht hat.
Jedes Element in der Liste sollte genügend Informationen enthalten, um den Spielstatus auf den Stand vor dem Zug wiederherzustellen. Abhängig von der Anzahl der vorhandenen Spielzustände und der Anzahl der Rückgängig-Schritte, die Sie unterstützen möchten, kann dies eine Momentaufnahme des Spielzustands oder Informationen sein, die der Spiel-Engine mitteilen, wie der Zug rückgängig gemacht werden soll.
quelle