我认为这里的问题是您没有清楚地说明哪些类将处理哪些任务。我将描述我认为是每个类应该做什么的很好的描述,然后我将给出一个通用代码示例来说明这些想法。我们将看到代码的耦合较少,因此它实际上没有循环引用。
让我们从描述每个类的功能开始。
本GameState
类应该只包含关于游戏的当前状态信息。它不应包含有关游戏的过去状态或将来可能发生的动作的任何信息。它只应包含有关棋盘上哪些棋子上的棋子,西洋双陆棋中哪些棋子上的棋子数和类型的信息。该GameState
会必须包含一些额外的信息,如关于在国际象棋或关于步步高加倍的立方体易位信息。
该Move
班是有点棘手。我要说的是,我可以指定要播放的动作,方法是指定该动作GameState
所产生的结果。因此,您可以想象一下,仅可将举动实施为GameState
。但是,例如在go中,您可以想象通过指定板上的单个点来指定移动要容易得多。我们希望我们的Move
班级足够灵活以处理这两种情况。因此,Move
该类实际上将成为具有方法的接口,该方法需要执行pre-move GameState
并返回新的post-move GameState
。
现在,RuleBook
班级负责了解有关规则的所有信息。这可以分为三部分。它需要知道最初的名字GameState
,它需要知道哪些动作是合法的,并且需要能够判断其中一名玩家是否赢了。
您也可以创建一个GameHistory
班级来跟踪所有已执行的动作以及GameStates
已发生的所有动作。需要一个新的类,因为我们认为一个GameState
人不应该负责了解所有GameState
先于它的。
这将结束我将讨论的类/接口。您也有一Board
堂课。但是我认为不同游戏中的棋盘差异很大,很难看到棋盘通常可以做什么。现在,我将继续给出通用接口并实现通用类。
首先是GameState
。由于此类完全取决于特定的游戏,因此没有通用的Gamestate
接口或类别。
接下来是Move
。如我所说,这可以用一个接口来表示,该接口具有一个采用移动前状态并产生移动后状态的方法。这是此接口的代码:
package boardgame;
/**
*
* @param <T> The type of GameState
*/
public interface Move<T> {
T makeResultingState(T preMoveState) throws IllegalArgumentException;
}
注意,有一个类型参数。这是因为,例如,a ChessMove
将需要了解预移动的详细信息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
,即“我们现在在这里,下一步可以做什么?”,该怎么办?