Ich denke, das Problem dabei ist, dass Sie nicht klar beschrieben haben, welche Aufgaben von welchen Klassen zu erledigen sind. Ich werde beschreiben, was meiner Meinung nach eine gute Beschreibung dessen ist, was jede Klasse tun sollte, und dann ein Beispiel für allgemeinen Code geben, der die Ideen veranschaulicht. Wir werden sehen, dass der Code weniger gekoppelt ist und daher keine Zirkelverweise enthält.
Beginnen wir mit der Beschreibung der einzelnen Klassen.
Die GameState
Klasse sollte nur Informationen über den aktuellen Stand des Spiels enthalten. Es sollte keine Informationen darüber enthalten, in welchem Zustand sich das Spiel in der Vergangenheit befindet oder welche zukünftigen Züge möglich sind. Es sollte nur Informationen darüber enthalten, welche Figuren sich auf welchen Feldern im Schach befinden oder wie viele und welche Arten von Dame sich auf welchen Punkten im Backgammon befinden. Das GameState
muss einige zusätzliche Informationen enthalten, wie zum Beispiel Informationen über das Rochieren im Schach oder über den Verdopplungswürfel im Backgammon.
Der Move
Unterricht ist etwas knifflig. Ich würde sagen, dass ich einen Zug angeben kann, der gespielt werden soll, indem ich festlege, welcher Zug aus dem Spiel GameState
resultiert. Sie können sich also vorstellen, dass ein Umzug nur als GameState
. In go (zum Beispiel) könnte man sich jedoch vorstellen, dass es viel einfacher ist, einen Zug zu bestimmen, indem man einen einzelnen Punkt auf dem Brett angibt. Wir möchten, dass unsere Move
Klasse flexibel genug ist, um beide Fälle zu behandeln. Daher wird die Move
Klasse tatsächlich eine Schnittstelle mit einer Methode sein, die einen Vorversatz ausführt GameState
und einen neuen Nachversatz zurückgibt GameState
.
Jetzt ist die RuleBook
Klasse dafür verantwortlich, alles über die Regeln zu wissen. Dies kann in drei Dinge unterteilt werden. Es muss wissen, was die Initiale GameState
ist, es muss wissen, welche Züge legal sind, und es muss in der Lage sein zu erkennen, ob einer der Spieler gewonnen hat.
Sie könnten auch eine GameHistory
Klasse bilden, um alle Bewegungen zu verfolgen, die gemacht worden sind und alle GameStates
, die geschehen sind. Eine neue Klasse ist notwendig, weil wir entschieden haben, dass eine einzelne GameState
nicht dafür verantwortlich sein sollte, alle GameState
s zu kennen, die davor standen.
Dies schließt die Klassen / Interfaces ab, die ich diskutieren werde. Du hast auch eine Board
Klasse. Ich denke jedoch, dass Boards in verschiedenen Spielen so unterschiedlich sind, dass es schwer zu erkennen ist, was generell mit Boards gemacht werden kann. Jetzt werde ich generische Interfaces geben und generische Klassen implementieren.
Erstens ist GameState
. Da diese Klasse vollständig vom jeweiligen Spiel abhängig ist, gibt es keine generische Gamestate
Schnittstelle oder Klasse.
Weiter ist Move
. Wie ich bereits sagte, kann dies mit einer Schnittstelle dargestellt werden, die über eine einzige Methode verfügt, die einen Zustand vor dem Verschieben annimmt und einen Zustand nach dem Verschieben erzeugt. Hier ist der Code für diese Schnittstelle:
package boardgame;
/**
*
* @param <T> The type of GameState
*/
public interface Move<T> {
T makeResultingState(T preMoveState) throws IllegalArgumentException;
}
Beachten Sie, dass es einen Typparameter gibt. Dies liegt zum Beispiel daran, dass ein ChessMove
Wille etwas über die Einzelheiten des Vorzugs wissen muss ChessGameState
. So zum Beispiel der Klasse Erklärung ChessMove
wäre
class ChessMove extends Move<ChessGameState>
,
wo du schon eine ChessGameState
Klasse definiert hättest .
Als nächstes werde ich die generische RuleBook
Klasse diskutieren . Hier ist der Code:
package boardgame;
import java.util.List;
/**
*
* @param <T> The type of GameState
*/
public interface RuleBook<T> {
T makeInitialState();
List<Move<T>> makeMoveList(T gameState);
StateEvaluation evaluateState(T gameState);
boolean isMoveLegal(Move<T> move, T currentState);
}
Auch hier gibt es einen Typparameter für die GameState
Klasse. Da die RuleBook
wissen soll, wie der Ausgangszustand ist, haben wir eine Methode entwickelt, um den Ausgangszustand anzugeben. Da RuleBook
wissen soll, welche Züge legal sind, haben wir Methoden, um zu testen, ob ein Zug in einem bestimmten Staat legal ist, und um eine Liste der legalen Züge für einen bestimmten Staat zu erstellen. Schließlich gibt es eine Methode zur Auswertung der GameState
. Beachten Sie RuleBook
, dass der Spieler nur dafür verantwortlich sein sollte, zu beschreiben, ob der eine oder andere Spieler bereits gewonnen hat, aber nicht, wer sich in der Mitte eines Spiels in einer besseren Position befindet. Zu entscheiden, wer in einer besseren Position ist, ist eine komplizierte Sache, die in eine eigene Klasse verschoben werden sollte. Daher ist die StateEvaluation
Klasse eigentlich nur eine einfache Aufzählung, die wie folgt lautet:
package boardgame;
/**
*
*/
public enum StateEvaluation {
UNFINISHED,
PLAYER_ONE_WINS,
PLAYER_TWO_WINS,
DRAW,
ILLEGAL_STATE
}
Zuletzt beschreiben wir die GameHistory
Klasse. Diese Klasse ist dafür verantwortlich, sich alle im Spiel erreichten Positionen sowie die gespielten Züge zu merken. Die Hauptsache, die es tun sollte, ist, ein Video aufzunehmen, Move
wie es abgespielt wurde. Sie können auch Funktionen zum Rückgängigmachen von Move
s hinzufügen . Ich habe eine Implementierung unten.
package boardgame;
import java.util.ArrayList;
import java.util.List;
/**
*
* @param <T> The type of GameState
*/
public class GameHistory<T> {
private List<T> states;
private List<Move<T>> moves;
public GameHistory(T initialState) {
states = new ArrayList<>();
states.add(initialState);
moves = new ArrayList<>();
}
void recordMove(Move<T> move) throws IllegalArgumentException {
moves.add(move);
states.add(move.makeResultingState(getMostRecentState()));
}
void resetToNthState(int n) {
states = states.subList(0, n + 1);
moves = moves.subList(0, n);
}
void undoLastMove() {
resetToNthState(getNumberOfMoves() - 1);
}
T getMostRecentState() {
return states.get(getNumberOfMoves());
}
T getStateAfterNthMove(int n) {
return states.get(n + 1);
}
Move<T> getNthMove(int n) {
return moves.get(n);
}
int getNumberOfMoves() {
return moves.size();
}
}
Schließlich könnten wir uns vorstellen, eine Game
Klasse zu bilden, um alles zusammenzubinden. Diese Game
Klasse soll Methoden aufzeigen, die es den Leuten ermöglichen, zu sehen, was der Strom GameState
ist, zu sehen, wer, wenn jemand einen hat, welche Züge gespielt werden können, und einen Zug zu spielen. Ich habe eine Implementierung unten
package boardgame;
import java.util.List;
/**
*
* @author brian
* @param <T> The type of GameState
*/
public class Game<T> {
GameHistory<T> gameHistory;
RuleBook<T> ruleBook;
public Game(RuleBook<T> ruleBook) {
this.ruleBook = ruleBook;
final T initialState = ruleBook.makeInitialState();
gameHistory = new GameHistory<>(initialState);
}
T getCurrentState() {
return gameHistory.getMostRecentState();
}
List<Move<T>> getLegalMoves() {
return ruleBook.makeMoveList(getCurrentState());
}
void doMove(Move<T> move) throws IllegalArgumentException {
if (!ruleBook.isMoveLegal(move, getCurrentState())) {
throw new IllegalArgumentException("Move is not legal in this position");
}
gameHistory.recordMove(move);
}
void undoMove() {
gameHistory.undoLastMove();
}
StateEvaluation evaluateState() {
return ruleBook.evaluateState(getCurrentState());
}
}
Beachten Sie in dieser Klasse, dass der RuleBook
nicht dafür verantwortlich ist, zu wissen, was der Strom GameState
ist. Das ist der GameHistory
Job des. Der Game
fragt also nach dem GameHistory
aktuellen Stand und gibt diese Information an, RuleBook
wann der Game
Bedarf besteht, zu sagen, was die legalen Schritte sind oder ob jemand gewonnen hat.
Der Sinn dieser Antwort ist jedenfalls, dass Sie, sobald Sie eine vernünftige Entscheidung getroffen haben, wofür jede Klasse verantwortlich ist, jede Klasse auf eine kleine Anzahl von Verantwortlichkeiten fokussieren und jede Verantwortung einer eindeutigen Klasse zuweisen, die Klassen neigen dazu, sich zu entkoppeln, und alles wird leicht zu codieren. Hoffentlich geht das aus den Codebeispielen hervor, die ich gegeben habe.
RuleBook
z. B. dasState
als Argument nehmen und das gültige zurückgeben würdenMoveList
, dh "hier sind wir jetzt, was kann als nächstes getan werden?"