1. Code
  2. Game Development

Lassen Sie Ihre Spieler ihre Fehler im Spiel mit dem Befehlsmuster rückgängig machen

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.
Scroll to top

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.

  1. 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, wenn undo() aufgerufen wird.
  2. Speichern Sie nur die Informationen, die sich zwischen den Zuständen ändern. Ändern Sie nur diese gespeicherten Informationen, wenn execute() oder undo() 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.

Undo and redo in game development with the command patternUndo and redo in game development with the command patternUndo and redo in game development with the command pattern

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.