Saya pikir masalahnya di sini adalah Anda belum memberikan deskripsi yang jelas tentang tugas apa yang harus ditangani oleh kelas mana. Saya akan menjelaskan apa yang saya pikir merupakan deskripsi yang baik tentang apa yang harus dilakukan oleh setiap kelas, kemudian saya akan memberikan contoh kode generik yang menggambarkan ide-ide tersebut. Kita akan melihat bahwa kodenya kurang digabungkan, sehingga tidak benar-benar memiliki referensi melingkar.
Mari kita mulai dengan menggambarkan apa yang dilakukan setiap kelas.
The GameState
kelas saja harus berisi informasi tentang keadaan saat permainan. Seharusnya tidak mengandung informasi apa pun tentang kondisi permainan di masa lalu atau pergerakan di masa depan yang mungkin terjadi. Seharusnya hanya berisi informasi tentang potongan apa yang ada di kotak apa dalam catur, atau berapa banyak dan jenis catur apa pada poin apa di backgammon. The GameState
harus mengandung beberapa informasi tambahan, seperti informasi tentang Castling dalam catur atau sekitar kubus dua kali lipat di backgammon.
The Move
kelas sedikit rumit. Saya akan mengatakan bahwa saya dapat menentukan langkah untuk bermain dengan menentukan GameState
hasil dari bermain langkah tersebut. Jadi Anda bisa membayangkan bahwa suatu langkah hanya dapat diimplementasikan sebagai GameState
. Namun, dalam proses (misalnya) Anda dapat membayangkan bahwa jauh lebih mudah untuk menentukan langkah dengan menentukan satu titik di papan tulis. Kami ingin Move
kelas kami cukup fleksibel untuk menangani semua kasus ini. Oleh karena itu Move
kelas sebenarnya akan menjadi antarmuka dengan metode yang mengambil pra-pindah GameState
dan mengembalikan pasca-pindah baru GameState
.
Sekarang RuleBook
kelas bertanggung jawab untuk mengetahui segala sesuatu tentang aturan. Ini dapat dipecah menjadi tiga hal. Perlu mengetahui apa yang awal GameState
, perlu tahu apa yang legal, dan perlu tahu apakah salah satu pemain telah menang.
Anda juga bisa membuat GameHistory
kelas untuk melacak semua gerakan yang telah dilakukan dan semua GameStates
yang telah terjadi. Kelas baru diperlukan karena kami memutuskan bahwa satu GameState
tidak boleh bertanggung jawab untuk mengetahui semua GameState
yang datang sebelumnya.
Ini menyimpulkan kelas / antarmuka yang akan saya bahas. Anda juga memiliki Board
kelas. Tetapi saya pikir papan di permainan yang berbeda cukup berbeda sehingga sulit untuk melihat apa yang secara umum dapat dilakukan dengan papan. Sekarang saya akan memberikan antarmuka generik dan mengimplementasikan kelas generik.
Pertama adalah GameState
. Karena kelas ini sepenuhnya tergantung pada gim tertentu, tidak ada Gamestate
antarmuka atau kelas generik .
Berikutnya adalah Move
. Seperti yang saya katakan, ini dapat diwakili dengan antarmuka yang memiliki metode tunggal yang mengambil keadaan pra-pindah dan menghasilkan keadaan pasca-pindah. Berikut ini kode untuk antarmuka ini:
package boardgame;
/**
*
* @param <T> The type of GameState
*/
public interface Move<T> {
T makeResultingState(T preMoveState) throws IllegalArgumentException;
}
Perhatikan bahwa ada parameter tipe. Ini karena, misalnya, ChessMove
perlu mengetahui tentang rincian pra-langkah ChessGameState
. Jadi, misalnya, deklarasi kelas ChessMove
akan menjadi
class ChessMove extends Move<ChessGameState>
,
di mana Anda sudah mendefinisikan ChessGameState
kelas.
Selanjutnya saya akan membahas RuleBook
kelas generik . Ini kodenya:
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);
}
Sekali lagi ada parameter tipe untuk GameState
kelas. Karena RuleBook
seharusnya mengetahui apa itu keadaan awal, kami telah menggunakan metode untuk memberikan keadaan awal. Karena yang RuleBook
seharusnya mengetahui langkah apa yang legal, kami memiliki metode untuk menguji apakah langkah tersebut legal di negara tertentu dan memberikan daftar langkah hukum untuk negara tertentu. Akhirnya, ada metode untuk mengevaluasi GameState
. Perhatikan bahwa RuleBook
seharusnya hanya bertanggung jawab untuk menggambarkan jika salah satu atau pemain lain telah menang, tetapi tidak siapa yang berada di posisi yang lebih baik di tengah permainan. Memutuskan siapa yang berada dalam posisi yang lebih baik adalah hal yang rumit yang harus dipindahkan ke kelasnya sendiri. Oleh karena itu StateEvaluation
kelas sebenarnya hanya enum sederhana yang diberikan sebagai berikut:
package boardgame;
/**
*
*/
public enum StateEvaluation {
UNFINISHED,
PLAYER_ONE_WINS,
PLAYER_TWO_WINS,
DRAW,
ILLEGAL_STATE
}
Terakhir, mari kita jelaskan GameHistory
kelasnya. Kelas ini bertanggung jawab untuk mengingat semua posisi yang dicapai dalam permainan serta gerakan yang dimainkan. Hal utama yang harus dapat dilakukan adalah merekam Move
sebagai dimainkan. Anda juga dapat menambahkan fungsionalitas untuk membatalkan Move
s. Saya memiliki implementasi di bawah ini.
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();
}
}
Akhirnya, kita bisa membayangkan membuat Game
kelas untuk mengikat semuanya. Ini Game
kelas seharusnya mengekspos metode yang memungkinkan orang untuk melihat apa yang saat ini GameState
adalah, melihat siapa, jika seseorang memiliki satu, melihat apa yang bisa dimainkan bergerak, dan bermain bergerak. Saya memiliki implementasi di bawah ini
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());
}
}
Perhatikan di kelas ini bahwa RuleBook
tidak bertanggung jawab untuk mengetahui apa arus GameState
. Itu GameHistory
pekerjaannya. Jadi mereka Game
bertanya seperti GameHistory
apa keadaan saat ini dan memberikan informasi ini pada RuleBook
saat Game
kebutuhan untuk mengatakan apa langkah hukumnya atau jika ada yang menang.
Pokoknya, inti dari jawaban ini adalah begitu Anda telah membuat penentuan yang masuk akal tentang apa yang bertanggung jawab atas masing-masing kelas, dan Anda membuat setiap kelas fokus pada sejumlah kecil tanggung jawab, dan Anda menugaskan masing-masing tanggung jawab ke kelas yang unik, kemudian kelas-kelas tersebut cenderung dipisahkan, dan semuanya menjadi mudah dikodekan. Semoga itu terlihat dari contoh kode yang saya berikan.
RuleBook
mengambil misState
sebagai argumen, dan mengembalikan yang validMoveList
, yaitu "di sinilah kita sekarang, apa yang bisa dilakukan selanjutnya?"