Bence buradaki problem, hangi görevlerin hangi sınıflar tarafından yerine getirileceğinin net bir tanımını vermemiş olmanızdır. Her sınıfın ne yapması gerektiğinin iyi bir açıklaması olduğunu düşündüğümü anlatacağım, sonra fikirleri gösteren bir genel kod örneği vereceğim. Kodun daha az eşlendiğini göreceğiz ve bu nedenle döngüsel referansları yoktur.
Her sınıfın ne yaptığını tarif etmeye başlayalım.
GameStateSınıf yalnızca oyunun mevcut durumu hakkında bilgi içermelidir. Oyunun geçmiş durumları ya da gelecekteki hareketlerin mümkün olabileceği hakkında herhangi bir bilgi içermemelidir. Sadece satrançta hangi karelerde ne olduğu, ya da tavlada hangi noktalarda ne kadar ve hangi tür dama olduğu hakkında bilgi içermelidir. Bu GameStateirade, satrançta oyun oynama ya da tavlada ikişer küp hakkında bilgi gibi, bazı ekstra bilgiler içermelidir.
MoveSınıf biraz zordur. Hareketi oynamanın sonucunu belirterek oynayacak bir hamle belirleyebileceğimi söyleyebilirim GameState. Böylece bir hareketin sadece bir olarak uygulanabileceğini hayal edebilirsiniz GameState. Ancak, (örneğin), hareket halindeyken tahtada tek bir nokta belirterek bir hareketi belirtmenin daha kolay olduğunu hayal edebilirsiniz. MoveSınıfımızın her ikisini de davalara taşıyacak kadar esnek olmasını istiyoruz . Bu nedenle, Movesınıf aslında bir hamle öncesi GameStateve yeni bir hamle sonrası dönen bir yöntem ile bir arayüz olacak GameState.
Şimdi RuleBooksınıf kuralları hakkında her şeyi bilmek sorumludur. Bu üç şeye ayrılabilir. İlk olanın ne GameStateolduğunu bilmesi, hangi hamlenin yasal olduğunu bilmesi ve oyunculardan birinin kazanıp kazanmadığını anlayabilmesi gerekiyor.
Ayrıca GameHistory, yapılan tüm hareketleri ve GameStatesolanları takip etmek için bir sınıf oluşturabilirsiniz . Yeni bir sınıf gereklidir, çünkü bir GameStatekişinin GameStatekendisinden önce gelenleri bilmekle sorumlu olmayacağına karar verdik .
Bu tartışacağım sınıfları / arayüzleri sonlandırıyor. Ayrıca bir Boardsınıfın var. Ancak farklı oyunlardaki tahtaların yeterince farklı olduğunu düşünüyorum; tahtalarla ne yapılabileceğini görmek zor. Şimdi genel arayüzler vermeye ve genel sınıfları uygulamaya devam edeceğim.
İlk GameState. Bu sınıf tamamen oyuna bağlı olduğundan, genel bir Gamestatearayüz veya sınıf yoktur.
Sonraki olduğunu Move. Dediğim gibi, bu, hareket öncesi durumu alan ve hareket sonrası durumu üreten tek bir metoda sahip bir arayüz ile gösterilebilir. İşte bu arayüzün kodu:
package boardgame;
/**
*
* @param <T> The type of GameState
*/
public interface Move<T> {
T makeResultingState(T preMoveState) throws IllegalArgumentException;
}
Bir tür parametresi olduğuna dikkat edin. Bunun nedeni, örneğin, ChessMovehamle öncesi işlemin özellikleri hakkında bir bilgiye ihtiyaç duyulması gerektiğidir ChessGameState. Yani, örneğin, sınıf beyanı ChessMoveolacaktır
class ChessMove extends Move<ChessGameState>,
zaten bir ChessGameStatesınıf tanımladığınız yer .
Sonra genel RuleBooksınıfı tartışacağım . İşte kod:
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);
}
Yine GameStatesınıf için bir type parametresi var . Yana RuleBookbaşlangıç durumu ne olduğunu bilmek gerekiyordu, biz başlangıç durumunu vermek için bir yöntem koyduk. Yana RuleBookhamle yasal olduğunu bilmek gerekiyordu bir hareket belli bir durumda yasal ve belirli bir devlet için yasal hamle listesini vermek, biz teste yöntemleri var. Son olarak, değerlendirmek için bir yöntem var GameState. Dikkat RuleBooksadece bir veya diğer oyuncular zaten kazanmışsa açıklayan sorumlu olmalıdır, ancak bir oyunun orta daha iyi bir konumda olduğunu kim. Kimin daha iyi bir pozisyonda olduğuna karar vermek, kendi sınıfına taşınması gereken karmaşık bir şeydir. Bu nedenle, StateEvaluationsınıf aslında aşağıdaki gibi verilen basit bir numaradır:
package boardgame;
/**
*
*/
public enum StateEvaluation {
UNFINISHED,
PLAYER_ONE_WINS,
PLAYER_TWO_WINS,
DRAW,
ILLEGAL_STATE
}
Son olarak, GameHistorysınıfı tanımlayalım . Bu sınıf, oyunda ulaşılan tüm pozisyonları ve oynanan hamleleri hatırlamaktan sorumludur. Yapması gereken en önemli şey Move, oynanan bir kaydı kaydetmektir . Ayrıca geri almak için işlevler ekleyebilirsiniz Move. Aşağıda bir uygulamam var.
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();
}
}
Sonunda, Gameher şeyi birbirine bağlamak için bir sınıf yapmayı hayal edebiliriz . Bu Gamesınıfın, insanlar için akımın ne olduğunu görmesini mümkün kılan, GameStatehangisinin varsa, hangi hamlelerin oynanabileceğini görebilecek ve hamle oynayabilecek yöntemleri görmesi gerekiyor. Aşağıda bir uygulama var
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());
}
}
Bu sınıfta RuleBook, akımın ne olduğunu bilmekle sorumlu olmadığına dikkat edin GameState. Bu GameHistoryiş. Yani Gamesorar GameHistorygeçerli durumdur ve bu bilgiyi verir ihtiyaçları yasal hamle veya kimse kazandı eğer ne olduğunu söylemek.RuleBookGame
Her neyse, bu cevabın amacı, bir defada her bir sınıfın neyin sorumlu olduğunu belirledikten sonra, her bir sınıfı az sayıda sorumluluk üzerine odakladığınız ve her sorumluluğu kendine özgü bir sınıfa, ardından sınıflara verdiğinizdir. ayrıştırılma eğilimindedir ve her şeyin kodlanması kolaydır. Umarım verdiğim kod örneklerinde açıkça görülmektedir.
RuleBookmeselaStatetartışmayı kabul edip, geçerliyseMoveList, örneğin "şimdi buradayız, daha sonra ne yapılabilir?"