Lassen Sie Ihre Spieler ihre Fehler im Spiel mit dem Befehlsmuster rückgängig machen
German (Deutsch) translation by Alex Grigorovich (you can also view the original English article)
Viele rundenbasierte Spiele enthalten eine Schaltfläche zum Rückgängigmachen, mit der Spieler Fehler, die sie während des Spiels machen, rückgängig machen können. Diese Funktion wird besonders relevant für die Entwicklung von mobilen Spielen, bei denen die Berührung eine ungeschickte Berührungserkennung haben kann. Anstatt sich auf ein System zu verlassen, bei dem Sie den Benutzer fragen: "Möchten Sie diese Aufgabe wirklich ausführen?" Bei jeder Aktion ist es viel effizienter, Fehler machen zu lassen und die Möglichkeit zu haben, ihre Aktion leicht rückgängig zu machen. In diesem Tutorial sehen wir uns am Beispiel eines Tic-Tac-Toe-Spiels an, wie dies mit dem Befehlsmuster implementiert wird.
Hinweis: Obwohl dieses Tutorial mit Java geschrieben wurde, sollten Sie in der Lage sein, dieselben Techniken und Konzepte in fast jeder Spieleentwicklungsumgebung zu verwenden. (Es ist auch nicht auf Tic-Tac-Toe-Spiele beschränkt!)
Vorschau des Endergebnisses
Das Endergebnis dieses Tutorials ist ein Tic-Tac-Toe-Spiel, das unbegrenzte Rückgängig- und Wiederholen-Operationen bietet.
Applet kann nicht geladen werden? Sehen Sie sich das Gameplay-Video auf YouTube an:
Sie können die Demo auch in der Befehlszeile ausführen, indem Sie TicTacToeMain als Hauptklasse für die Ausführung verwenden. Führen Sie nach dem Extrahieren der Quelle die folgenden Befehle aus:
1 |
javac *.java |
2 |
java TicTacToeMain |
Schritt 1: Erstellen Sie eine grundlegende Implementierung von Tic-Tac-Toe
Für dieses Tutorial betrachten Sie eine Implementierung von Tic-Tac-Toe. Obwohl das Spiel extrem trivial ist, können die Konzepte in diesem Tutorial auf viel komplexere Spiele angewendet werden.
Der folgende Download (der sich vom endgültigen Quelldownload unterscheidet) enthält den grundlegenden Code für ein Tic-Tac-Toe-Spielmodell, das keine Rückgängig- oder Wiederherstellungsfunktion enthält. Es ist Ihre Aufgabe, diesem Tutorial zu folgen und diese Funktionen hinzuzufügen. Laden Sie die Basis TicTacToeModel.java herunter.
Beachten Sie insbesondere die folgenden Methoden:
1 |
public void placeX(int row, int col) { |
2 |
assert(playerXTurn); |
3 |
assert(spaces[row][col] == 0); |
4 |
spaces[row][col] = 1; |
5 |
playerXTurn = false; |
6 |
}
|
1 |
public void placeO(int row, int col) { |
2 |
assert(!playerXTurn); |
3 |
assert(spaces[row][col] == 0); |
4 |
spaces[row][col] = 2; |
5 |
playerXTurn = true; |
6 |
}
|
Diese Methoden sind die einzigen Methoden für dieses Spiel, die den Status des Spielrasters ändern. Sie werden das sein, was Sie ändern werden.
Wenn Sie kein Java-Entwickler sind, können Sie den Code wahrscheinlich trotzdem verstehen. Es ist hier kopiert, wenn Sie nur darauf verweisen möchten:
1 |
/** The game logic for a Tic-Tac-Toe game. This model does not have
|
2 |
* an associated User Interface: it is just the game logic.
|
3 |
*
|
4 |
* The game is represented by a simple 3x3 integer array. A value of
|
5 |
* 0 means the space is empty, 1 means it is an X, 2 means it is an O.
|
6 |
*
|
7 |
* @author aarnott
|
8 |
*
|
9 |
*/
|
10 |
public class TicTacToeModel { |
11 |
//True if it is the X player’s turn, false if it is the O player’s turn
|
12 |
private boolean playerXTurn; |
13 |
//The set of spaces on the game grid
|
14 |
private int[][] spaces; |
15 |
|
16 |
/** Initialize a new game model. In the traditional Tic-Tac-Toe
|
17 |
* game, X goes first.
|
18 |
*
|
19 |
*/
|
20 |
public TicTacToeModel() { |
21 |
spaces = new int[3][3]; |
22 |
playerXTurn = true; |
23 |
}
|
24 |
|
25 |
/** Returns true if it is the X player's turn.
|
26 |
*
|
27 |
* @return
|
28 |
*/
|
29 |
public boolean isPlayerXTurn() { |
30 |
return playerXTurn; |
31 |
}
|
32 |
|
33 |
/** Returns true if it is the O player's turn.
|
34 |
*
|
35 |
* @return
|
36 |
*/
|
37 |
public boolean isPlayerOTurn() { |
38 |
return !playerXTurn; |
39 |
}
|
40 |
|
41 |
/** Places an X on a space specified by the row and column
|
42 |
* parameters.
|
43 |
*
|
44 |
* Preconditions:
|
45 |
* -> It must be the X player's turn
|
46 |
* -> The space must be empty
|
47 |
*
|
48 |
* @param row The row to place the X on
|
49 |
* @param col The column to place the X on
|
50 |
*/
|
51 |
public void placeX(int row, int col) { |
52 |
assert(playerXTurn); |
53 |
assert(spaces[row][col] == 0); |
54 |
spaces[row][col] = 1; |
55 |
playerXTurn = false; |
56 |
}
|
57 |
|
58 |
/** Places an O on a space specified by the row and column
|
59 |
* parameters.
|
60 |
*
|
61 |
* Preconditions:
|
62 |
* -> It must be the O player's turn
|
63 |
* -> The space must be empty
|
64 |
*
|
65 |
* @param row The row to place the O on
|
66 |
* @param col The column to place the O on
|
67 |
*/
|
68 |
public void placeO(int row, int col) { |
69 |
assert(!playerXTurn); |
70 |
assert(spaces[row][col] == 0); |
71 |
spaces[row][col] = 2; |
72 |
playerXTurn = true; |
73 |
}
|
74 |
|
75 |
/** Returns true if a space on the grid is empty (no Xs or Os)
|
76 |
*
|
77 |
* @param row
|
78 |
* @param col
|
79 |
* @return
|
80 |
*/
|
81 |
public boolean isSpaceEmpty(int row, int col) { |
82 |
return (spaces[row][col] == 0); |
83 |
}
|
84 |
|
85 |
/** Returns true if a space on the grid is an X.
|
86 |
*
|
87 |
* @param row
|
88 |
* @param col
|
89 |
* @return
|
90 |
*/
|
91 |
public boolean isSpaceX(int row, int col) { |
92 |
return (spaces[row][col] == 1); |
93 |
}
|
94 |
|
95 |
/** Returns true if a space on the grid is an O.
|
96 |
*
|
97 |
* @param row
|
98 |
* @param col
|
99 |
* @return
|
100 |
*/
|
101 |
public boolean isSpaceO(int row, int col) { |
102 |
return (spaces[row][col] == 2); |
103 |
}
|
104 |
|
105 |
/** Returns true if the X player won the game. That is, if the
|
106 |
* X player has completed a line of three Xs.
|
107 |
*
|
108 |
* @return
|
109 |
*/
|
110 |
public boolean hasPlayerXWon() { |
111 |
//Check rows
|
112 |
if(spaces[0][0] == 1 && spaces[0][1] == 1 && spaces[0][2] == 1) return true; |
113 |
if(spaces[1][0] == 1 && spaces[1][1] == 1 && spaces[1][2] == 1) return true; |
114 |
if(spaces[2][0] == 1 && spaces[2][1] == 1 && spaces[2][2] == 1) return true; |
115 |
//Check columns
|
116 |
if(spaces[0][0] == 1 && spaces[1][0] == 1 && spaces[2][0] == 1) return true; |
117 |
if(spaces[0][1] == 1 && spaces[1][1] == 1 && spaces[2][1] == 1) return true; |
118 |
if(spaces[0][2] == 1 && spaces[1][2] == 1 && spaces[2][2] == 1) return true; |
119 |
//Check diagonals
|
120 |
if(spaces[0][0] == 1 && spaces[1][1] == 1 && spaces[2][2] == 1) return true; |
121 |
if(spaces[0][2] == 1 && spaces[1][1] == 1 && spaces[2][0] == 1) return true; |
122 |
//Otherwise, there is no line
|
123 |
return false; |
124 |
}
|
125 |
|
126 |
/** Returns true if the O player won the game. That is, if the
|
127 |
* O player has completed a line of three Os.
|
128 |
*
|
129 |
* @return
|
130 |
*/
|
131 |
public boolean hasPlayerOWon() { |
132 |
//Check rows
|
133 |
if(spaces[0][0] == 2 && spaces[0][1] == 2 && spaces[0][2] == 2) return true; |
134 |
if(spaces[1][0] == 2 && spaces[1][1] == 2 && spaces[1][2] == 2) return true; |
135 |
if(spaces[2][0] == 2 && spaces[2][1] == 2 && spaces[2][2] == 2) return true; |
136 |
//Check columns
|
137 |
if(spaces[0][0] == 2 && spaces[1][0] == 2 && spaces[2][0] == 2) return true; |
138 |
if(spaces[0][1] == 2 && spaces[1][1] == 2 && spaces[2][1] == 2) return true; |
139 |
if(spaces[0][2] == 2 && spaces[1][2] == 2 && spaces[2][2] == 2) return true; |
140 |
//Check diagonals
|
141 |
if(spaces[0][0] == 2 && spaces[1][1] == 2 && spaces[2][2] == 2) return true; |
142 |
if(spaces[0][2] == 2 && spaces[1][1] == 2 && spaces[2][0] == 2) return true; |
143 |
//Otherwise, there is no line
|
144 |
return false; |
145 |
}
|
146 |
|
147 |
/** Returns true if all the spaces are filled or one of the players has
|
148 |
* won the game.
|
149 |
*
|
150 |
* @return
|
151 |
*/
|
152 |
public boolean isGameOver() { |
153 |
if(hasPlayerXWon() || hasPlayerOWon()) return true; |
154 |
//Check if all the spaces are filled. If one isn’t the game isn’t over
|
155 |
for(int row = 0; row < 3; row++) { |
156 |
for(int col = 0; col < 3; col++) { |
157 |
if(spaces[row][col] == 0) return false; |
158 |
}
|
159 |
}
|
160 |
//Otherwise, it is a “cat’s game”
|
161 |
return true; |
162 |
}
|
163 |
|
164 |
}
|
Schritt 2: Verstehen Sie das Befehlsmuster
Das Command-Muster ist ein Entwurfsmuster, das häufig bei Benutzeroberflächen verwendet wird, um die von Schaltflächen, Menüs oder anderen Widgets ausgeführten Aktionen von den Codedefinitionen der Benutzeroberfläche für diese Objekte zu trennen. Dieses Konzept der Trennung von Aktionscode kann verwendet werden, um jede Änderung am Status eines Spiels zu verfolgen, und Sie können diese Informationen verwenden, um die Änderungen rückgängig zu machen.
Die einfachste Version des Command-Musters ist die folgende Schnittstelle:
1 |
public interface Command { |
2 |
public void execute(); |
3 |
}
|
Jede Aktion, die vom Programm ausgeführt wird, die den Zustand des Spiels ändert - wie das Platzieren eines X in einem bestimmten Feld - implementiert die Command-Schnittstelle. Wenn die Aktion ausgeführt wird, wird die Methode execute() aufgerufen.
Sie haben wahrscheinlich bemerkt, dass diese Benutzeroberfläche nicht die Möglichkeit bietet, Aktionen rückgängig zu machen. Alles, was es tut, ist, das Spiel von einem Zustand in einen anderen zu bringen. Die folgende Verbesserung ermöglicht die Implementierung von Aktionen, um Rückgängig-Funktionen anzubieten.
1 |
public interface Command { |
2 |
public void execute(); |
3 |
public void undo(); |
4 |
}
|
Das Ziel beim Implementieren eines Command besteht darin, dass die undo()-Methode jede von der Methode execute ausgeführte Aktion rückgängig macht. Folglich kann die Methode execute() auch die Möglichkeit bieten, eine Aktion zu wiederholen.
Das ist die Grundidee. Es wird klarer, wenn wir spezifische Befehle für dieses Spiel implementieren.
Schritt 3: Erstellen Sie einen Befehlsmanager
Um eine Rückgängig-Funktion hinzuzufügen, erstellen Sie eine CommandManager-Klasse. Der CommandManager ist für das Verfolgen, Ausführen und Rückgängigmachen von Command-Implementierungen verantwortlich.
(Denken Sie daran, dass die Command-Schnittstelle die Methoden bereitstellt, um Änderungen von einem Zustand eines Programms in einen anderen vorzunehmen und auch umzukehren.)
1 |
public class CommandManager { |
2 |
private Command lastCommand; |
3 |
|
4 |
public CommandManager() {} |
5 |
|
6 |
public void executeCommand(Command c) { |
7 |
c.execute(); |
8 |
lastCommand = c; |
9 |
}
|
10 |
|
11 |
...
|
12 |
|
13 |
}
|
Um einen Command auszuführen, wird dem CommandManager eine Command-Instanz übergeben, und er führt den Command aus und speichert dann den zuletzt ausgeführten Command zur späteren Bezugnahme.
Das Hinzufügen der Rückgängig-Funktion zum CommandManager erfordert lediglich die Anweisung, den zuletzt ausgeführten Command rückgängig zu machen.
1 |
public boolean isUndoAvailable() { |
2 |
return lastCommand != null; |
3 |
}
|
4 |
|
5 |
public void undo() { |
6 |
assert(lastCommand != null); |
7 |
lastCommand.undo(); |
8 |
lastCommand = null; |
9 |
}
|
Dieser Code ist alles, was für einen funktionsfähigen CommandManager erforderlich ist. Damit es ordnungsgemäß funktioniert, müssen Sie einige Implementierungen der Command-Schnittstelle erstellen.
Schritt 4: Implementierungen der Command-Schnittstelle erstellen
Das Ziel des Command-Musters für dieses Tutorial besteht darin, jeden Code, der den Status des Tic-Tac-Toe-Spiels ändert, in eine Command-Instanz zu verschieben. Sie ändern nämlich den Code in den Methoden placeX() und placeO().
Fügen Sie innerhalb der TicTacToeModel-Klasse zwei neue innere Klassen namens PlaceXCommand bzw. PlaceOCommand hinzu, die jeweils die Command-Schnittstelle implementieren.
1 |
public class TicTacToeModel { |
2 |
|
3 |
...
|
4 |
|
5 |
private class PlaceXCommand implements Command { |
6 |
|
7 |
public void execute() { |
8 |
...
|
9 |
}
|
10 |
|
11 |
public void undo() { |
12 |
...
|
13 |
}
|
14 |
|
15 |
}
|
16 |
|
17 |
private class PlaceOCommand implements Command { |
18 |
|
19 |
public void execute() { |
20 |
...
|
21 |
}
|
22 |
|
23 |
public void undo() { |
24 |
...
|
25 |
}
|
26 |
|
27 |
}
|
28 |
|
29 |
}
|
Die Aufgabe einer Command-Implementierung besteht darin, einen Zustand zu speichern und über eine Logik zu verfügen, um entweder in einen neuen Zustand überzugehen, der sich aus der Ausführung des Command ergibt, oder zum Zurückgehen in den Anfangszustand vor der Ausführung des Command. Es gibt zwei einfache Möglichkeiten, diese Aufgabe zu erfüllen.
- Speichern Sie den gesamten vorherigen Zustand und den nächsten Zustand. Setzt den aktuellen Zustand des Spiels auf den nächsten Zustand, wenn
execute()aufgerufen wird, und setzt den aktuellen Zustand des Spiels auf den gespeicherten vorherigen Zustand, wennundo()aufgerufen wird. - Speichern Sie nur die Informationen, die sich zwischen den Zuständen ändern. Ändern Sie nur diese gespeicherten Informationen, wenn
execute()oderundo()aufgerufen wird.
1 |
//Option 1: Storing the previous and next states
|
2 |
private class PlaceXCommand implements Command { |
3 |
private TicTacToeModel model; |
4 |
//
|
5 |
private int[][] previousGridState; |
6 |
private boolean previousTurnState; |
7 |
private int[][] nextGridState; |
8 |
private boolean nextTurnState; |
9 |
//
|
10 |
private PlaceXCommand (TicTacToeModel model, int row, int col) { |
11 |
this.model = model; |
12 |
//
|
13 |
previousTurnState = model.playerXTurn; |
14 |
//Copy the entire grid for both states
|
15 |
previousGridState = new int[3][3]; |
16 |
nextGridState = new int[3][3]; |
17 |
for(int i = 0; i < 3; i++) { |
18 |
for(int j = 0; j < 3; j++) { |
19 |
//This is allowed because this class is an inner
|
20 |
//class. Otherwise, the model would need to
|
21 |
//provide array access somehow.
|
22 |
previousGridState[i][j] = m.spaces[i][j]; |
23 |
nextGridState[i][j] = m.spaces[i][j]; |
24 |
}
|
25 |
}
|
26 |
//Figure out the next state by applying the placeX logic
|
27 |
nextGridState[row][col] = 1; |
28 |
nextTurnState = false; |
29 |
}
|
30 |
//
|
31 |
public void execute() { |
32 |
model.spaces = nextGridState; |
33 |
model.playerXTurn = nextTurnState; |
34 |
}
|
35 |
//
|
36 |
public void undo() { |
37 |
model.spaces = previousGridState; |
38 |
model.playerXTurn = previousTurnState; |
39 |
}
|
40 |
}
|
Die erste Option ist etwas verschwenderisch, aber das bedeutet nicht, dass es sich um schlechtes Design handelt. Der Code ist einfach und es sei denn, die Zustandsinformationen sind extrem groß, um die Abfallmenge muss man sich keine Sorgen machen.
Sie werden sehen, dass im Fall dieses Tutorials die zweite Option besser ist, aber dieser Ansatz wird nicht immer für jedes Programm die beste sein. In den meisten Fällen ist jedoch die zweite Option der richtige Weg.
1 |
//Option 2: Storing only the changes between states
|
2 |
private class PlaceXCommand implements Command { |
3 |
private TicTacToeModel model; |
4 |
private int previousValue; |
5 |
private boolean previousTurn; |
6 |
private int row; |
7 |
private int col; |
8 |
//
|
9 |
private PlaceXCommand(TicTacToeModel model, int row, int col) { |
10 |
this.model = model; |
11 |
this.row = row; |
12 |
this.col = col; |
13 |
//Copy the previous value from the grid
|
14 |
this.previousValue = model.spaces[row][col]; |
15 |
this.previousTurn = model.playerXTurn; |
16 |
}
|
17 |
//
|
18 |
public void execute() { |
19 |
model.spaces[row][col] = 1; |
20 |
model.playerXTurn = false; |
21 |
}
|
22 |
//
|
23 |
public void undo() { |
24 |
model.spaces[row][col] = previousValue; |
25 |
model.playerXTurn = previousTurn; |
26 |
}
|
27 |
}
|
Die zweite Option speichert nur die vorgenommenen Änderungen und nicht den gesamten Zustand. Bei Tic-Tac-Toe ist es effizienter und nicht merklich aufwendiger, diese Option zu nutzen.
Die innere Klasse PlaceOCommand ist ähnlich geschrieben - versuchen Sie es selbst!
Schritt 5: Alles zusammenfügen
Um Ihre Command-Implementierungen PlaceXCommand und PlaceOCommand verwenden zu können, müssen Sie die TicTacToeModel-Klasse ändern. Die Klasse muss einen CommandManager verwenden und Command-Instanzen verwenden, anstatt Aktionen direkt anzuwenden.
1 |
public class TicTacToeModel { |
2 |
private CommandManager commandManager; |
3 |
//
|
4 |
...
|
5 |
//
|
6 |
public TicTacToeModel() { |
7 |
...
|
8 |
//
|
9 |
commandManager = new CommandManager(); |
10 |
}
|
11 |
//
|
12 |
...
|
13 |
//
|
14 |
public void placeX(int row, int col) { |
15 |
assert(playerXTurn); |
16 |
assert(spaces[row][col] == 0); |
17 |
commandManager.executeCommand(new PlaceXCommand(this, row, col)); |
18 |
}
|
19 |
//
|
20 |
public void placeO(int row, int col) { |
21 |
assert(!playerXTurn); |
22 |
assert(spaces[row][col] == 0); |
23 |
commandManager.executeCommand(new PlaceOCommand(this, row, col)); |
24 |
}
|
25 |
//
|
26 |
...
|
27 |
}
|
Die TicTacToeModel-Klasse funktioniert jetzt genauso wie vor Ihren Änderungen, Sie können jedoch auch die Rückgängig-Funktion verfügbar machen. Fügen Sie dem Modell eine undo()-Methode hinzu und fügen Sie auch eine check-Methode canUndo hinzu, damit die Benutzeroberfläche irgendwann verwenden kann.
1 |
public class TicTacToeModel { |
2 |
//
|
3 |
...
|
4 |
//
|
5 |
public boolean canUndo() { |
6 |
return commandManager.isUndoAvailable(); |
7 |
}
|
8 |
//
|
9 |
public void undo() { |
10 |
commandManager.undo(); |
11 |
}
|
12 |
|
13 |
}
|
Sie haben jetzt ein voll funktionsfähiges Tic-Tac-Toe-Spielmodell, das Rückgängig unterstützt!
Schritt 6: Nehmen Sie es weiter
Mit ein paar kleinen Änderungen am CommandManager können Sie die Unterstützung für Wiederherstellungsvorgänge sowie eine unbegrenzte Anzahl von Rückgängig- und Wiederherstellungsvorgängen hinzufügen.
Das Konzept hinter einer Wiederherstellungsfunktion ist so ziemlich das gleiche wie bei einer Rückgängig-Funktion. Sie speichern nicht nur den zuletzt ausgeführten Command, sondern auch den zuletzt rückgängig gemachten Command. Sie speichern diesen Command, wenn ein Rückgängig aufgerufen wird, und löschen ihn, wenn ein Command ausgeführt wird.
1 |
public class CommandManager { |
2 |
|
3 |
private Command lastCommandUndone; |
4 |
|
5 |
...
|
6 |
|
7 |
public void executeCommand(Command c) { |
8 |
c.execute(); |
9 |
lastCommand = c; |
10 |
lastCommandUndone = null; |
11 |
}
|
12 |
|
13 |
public void undo() { |
14 |
assert(lastCommand != null); |
15 |
lastCommand.undo(); |
16 |
lastCommandUndone = lastCommand; |
17 |
lastCommand = null; |
18 |
}
|
19 |
|
20 |
public boolean isRedoAvailable() { |
21 |
return lastCommandUndone != null; |
22 |
}
|
23 |
|
24 |
public void redo() { |
25 |
assert(lastCommandUndone != null); |
26 |
lastCommandUndone.execute(); |
27 |
lastCommand = lastCommandUndone; |
28 |
lastCommandUndone = null; |
29 |
}
|
30 |
}
|
Beim Hinzufügen mehrerer Rückgängig- und Wiederherstellungsvorgänge wird ein Stapel rückgängiger und wiederherstellbarer Aktionen gespeichert. Wenn eine neue Aktion ausgeführt wird, wird sie dem Undo-Stack hinzugefügt und der Redo-Stack wird gelöscht. Wenn eine Aktion rückgängig gemacht wird, wird sie zum Wiederherstellen-Stack hinzugefügt und aus dem Rückgängig-Stack entfernt. Wenn eine Aktion wiederholt wird, wird sie aus dem Wiederherstellungsstapel entfernt und dem Rückgängig-Stapel hinzugefügt.


Das obige Bild zeigt ein Beispiel für die Stacks in Aktion. Der Redo-Stack enthält zwei Elemente von Befehlen, die bereits rückgängig gemacht wurden. Wenn die neuen Befehle PlaceX(0,0) und PlaceO(0,1) ausgeführt werden, wird der Redo-Stack gelöscht und zum Undo-Stack hinzugefügt. Wenn ein PlaceO(0,1) rückgängig gemacht wird, wird es von der Spitze des Rückgängig-Stapels entfernt und auf dem Wiederholen-Stapel platziert.
So sieht das im Code aus:
1 |
public class CommandManager { |
2 |
|
3 |
private Stack<Command> undos = new Stack<Command>(); |
4 |
private Stack<Command> redos = new Stack<Command>(); |
5 |
|
6 |
public void executeCommand(Command c) { |
7 |
c.execute(); |
8 |
undos.push(c); |
9 |
redos.clear(); |
10 |
}
|
11 |
|
12 |
public boolean isUndoAvailable() { |
13 |
return !undos.empty(); |
14 |
}
|
15 |
|
16 |
public void undo() { |
17 |
assert(!undos.empty()); |
18 |
Command command = undos.pop(); |
19 |
command.undo(); |
20 |
redos.push(command); |
21 |
}
|
22 |
|
23 |
public boolean isRedoAvailable() { |
24 |
return !redos.empty(); |
25 |
}
|
26 |
|
27 |
public void redo() { |
28 |
assert(!redos.empty()); |
29 |
Command command = redos.pop(); |
30 |
command.execute(); |
31 |
undos.push(command); |
32 |
}
|
33 |
}
|
Jetzt haben Sie ein Tic-Tac-Toe-Spielmodell, mit dem Sie Aktionen bis zum Beginn des Spiels rückgängig machen und erneut wiederholen können.
Wenn Sie sehen möchten, wie dies alles zusammenpasst, holen Sie sich den endgültigen Quell-Download, der den vollständigen Code aus diesem Tutorial enthält.
Abschluss
Sie haben vielleicht bemerkt, dass der endgültige CommandManager, den Sie geschrieben haben, für alle Command-Implementierungen funktioniert. Dies bedeutet, dass Sie einen CommandManager in Ihrer bevorzugten Sprache programmieren, einige Instanzen der Command-Schnittstelle erstellen und ein vollständiges System für Rückgängig/Wiederholen vorbereiten können. Die Rückgängig-Funktion kann eine großartige Möglichkeit sein, den Benutzern zu ermöglichen, Ihr Spiel zu erkunden und Fehler zu machen, ohne sich zu schlechten Entscheidungen verpflichtet zu fühlen.den
Vielen Dank für Ihr Interesse an diesem Tutorial!
Als weiteren Denkanstoß sollten Sie Folgendes beachten: Das Command-Muster zusammen mit dem CommandManager ermöglicht es Ihnen, jede Zustandsänderung während der Ausführung Ihres Spiels zu verfolgen. Wenn Sie diese Informationen speichern, können Sie Wiederholungen der Ausführung des Programms erstellen.



