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 GameStateKlasse 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 GameStatemuss einige zusätzliche Informationen enthalten, wie zum Beispiel Informationen über das Rochieren im Schach oder über den Verdopplungswürfel im Backgammon.
Der MoveUnterricht 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 GameStateresultiert. 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 MoveKlasse flexibel genug ist, um beide Fälle zu behandeln. Daher wird die MoveKlasse tatsächlich eine Schnittstelle mit einer Methode sein, die einen Vorversatz ausführt GameStateund einen neuen Nachversatz zurückgibt GameState.
Jetzt ist die RuleBookKlasse dafür verantwortlich, alles über die Regeln zu wissen. Dies kann in drei Dinge unterteilt werden. Es muss wissen, was die Initiale GameStateist, 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 GameHistoryKlasse 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 GameStatenicht dafür verantwortlich sein sollte, alle GameStates zu kennen, die davor standen.
Dies schließt die Klassen / Interfaces ab, die ich diskutieren werde. Du hast auch eine BoardKlasse. 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 GamestateSchnittstelle 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 ChessMoveWille etwas über die Einzelheiten des Vorzugs wissen muss ChessGameState. So zum Beispiel der Klasse Erklärung ChessMovewäre
class ChessMove extends Move<ChessGameState>,
wo du schon eine ChessGameStateKlasse definiert hättest .
Als nächstes werde ich die generische RuleBookKlasse 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 GameStateKlasse. Da die RuleBookwissen soll, wie der Ausgangszustand ist, haben wir eine Methode entwickelt, um den Ausgangszustand anzugeben. Da RuleBookwissen 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 StateEvaluationKlasse 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 GameHistoryKlasse. 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, Movewie es abgespielt wurde. Sie können auch Funktionen zum Rückgängigmachen von Moves 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 GameKlasse zu bilden, um alles zusammenzubinden. Diese GameKlasse soll Methoden aufzeigen, die es den Leuten ermöglichen, zu sehen, was der Strom GameStateist, 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 RuleBooknicht dafür verantwortlich ist, zu wissen, was der Strom GameStateist. Das ist der GameHistoryJob des. Der Gamefragt also nach dem GameHistoryaktuellen Stand und gibt diese Information an, RuleBookwann der GameBedarf 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.
RuleBookz. B. dasStateals Argument nehmen und das gültige zurückgeben würdenMoveList, dh "hier sind wir jetzt, was kann als nächstes getan werden?"