ここでの問題は、どのクラスがどのタスクを処理するのかを明確に説明していないことだと思います。各クラスが何をすべきかについての良い説明だと思うことを説明してから、アイデアを説明する汎用コードの例を示します。コードの結合性が低く、循環参照が実際にはないことがわかります。
各クラスの機能を説明するところから始めましょう。
GameState
クラスは唯一のゲームの現在の状態に関する情報が含まれている必要があります。ゲームの過去の状態や将来の動きについての情報を含めるべきではありません。チェスのどのマスにどのピースが入っているか、またはバックギャモンのどのポイントに何人のチェッカーがいるのかに関する情報のみが含まれている必要があります。GameState
チェスやバックギャモンで倍増キューブについてのキャスリングに関する情報と同様に、いくつかの追加情報が含まれている必要があります。
Move
クラスは少しトリッキーです。私は、ムーブをプレイしGameState
た結果を指定することで、プレイするムーブを指定できると言います。ですから、移動は単にとして実装できると想像できますGameState
。ただし、(たとえば)goでは、ボード上の1つのポイントを指定することで、移動を指定する方がはるかに簡単だと想像できます。Move
これらのケースのいずれかを処理できるように、クラスに十分な柔軟性が必要です。したがって、Move
実際には、クラスはpre-move GameState
を取得して新しいpost-moveを返すメソッドとのインターフェースになりますGameState
。
現在、RuleBook
クラスはルールに関するすべてを知る責任があります。これは3つのものに分類できます。イニシャルGameState
が何であるか、どの動きが合法であるかを知る必要があり、プレイヤーの一人が勝ったかどうかを知ることができる必要があります。
また、GameHistory
クラスを作成して、行われたすべての動きと発生したすべての動きを追跡することもできGameStates
ます。新しいクラスが必要なのは、その前に来たGameState
すべてのGameState
s を知ることは単一の責任ではないと判断したためです。
これで、これから説明するクラス/インターフェースを終了します。Board
クラスもあります。しかし、さまざまなゲームのボードは十分に異なっているため、ボードで一般的に何ができるかはわかりにくいと思います。次に、ジェネリックインターフェイスを提供し、ジェネリッククラスを実装します。
最初はGameState
です。このクラスは特定のゲームに完全に依存しているため、一般的なGamestate
インターフェイスやクラスはありません。
次ですMove
。前述したように、これは、移動前の状態を取得して移動後の状態を生成する単一のメソッドを持つインターフェイスで表すことができます。このインターフェイスのコードは次のとおりです。
package boardgame;
/**
*
* @param <T> The type of GameState
*/
public interface Move<T> {
T makeResultingState(T preMoveState) throws IllegalArgumentException;
}
型パラメーターがあることに注意してください。これは、たとえば、 ChessMove
pre-moveの詳細を知る必要があるためChessGameState
です。したがって、たとえば、クラス宣言はChessMove
なり
class ChessMove extends Move<ChessGameState>
、
すでにChessGameState
クラスを定義しているはずです。
次に、ジェネリックRuleBook
クラスについて説明します。コードは次のとおりです。
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);
}
再び、GameState
クラスの型パラメーターがあります。RuleBook
は初期状態が何であるかを知っているはずなので、初期状態を与えるメソッドを追加しました。RuleBook
はどの動きが合法であるかを知っているはずなので、特定の状態で合法であるかどうかをテストし、特定の状態で合法的な動きのリストを提供するメソッドがあります。最後に、を評価するメソッドがありGameState
ます。RuleBook
いずれかのプレイヤーが既に勝ったかどうかを説明する責任があるだけで、ゲームの途中で誰がより良い位置にいるのではないことに注意してください。誰がより良い立場にあるかを決定することは複雑なことであり、それを独自のクラスに移すべきです。したがって、StateEvaluation
クラスは実際には次のように指定された単なる列挙型です。
package boardgame;
/**
*
*/
public enum StateEvaluation {
UNFINISHED,
PLAYER_ONE_WINS,
PLAYER_TWO_WINS,
DRAW,
ILLEGAL_STATE
}
最後に、GameHistory
クラスについて説明しましょう。このクラスは、ゲームで到達したすべての位置とプレイされた動きを記憶する役割を果たします。できるはずの主なことはMove
、再生されたままのレコードです。を元に戻す機能を追加することもできますMove
。以下に実装があります。
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();
}
}
最後に、Game
すべてを結びつけるクラスを作成することを想像できます。このGame
クラスは、現在の内容をGameState
確認し、誰が誰を持っているのか、誰が持っているのか、どの動きをプレイできるのか、そして動きをプレイできるようにするメソッドを公開することになっています。私は以下の実装を持っています
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());
}
}
このクラスではRuleBook
、電流GameState
が何であるかを知る責任はありません。それがGameHistory
の仕事です。だから、現在の状態が何であるかをGame
尋ね、法的動きが何であるかを言う必要がGameHistory
あるRuleBook
とき、Game
または誰かが勝ったかどうかにこの情報を提供します。
とにかく、この答えのポイントは、各クラスが何に責任があるのかを合理的に決定し、各クラスを少数の責任に焦点を合わせ、各責任を一意のクラスに割り当ててから、クラス分離される傾向があり、すべてがコーディングしやすくなります。うまくいけば、それは私が与えたコード例から明らかです。
RuleBook
例えば取っState
引数として、および有効な返さMoveList
すなわち、「次何ができるのか、私たちが今いる場所ここにあるの?」