ゲームの各部分が_abstract Piece
_クラスから継承するチェスゲームを設計しようとしています。作品はいくつかのことを行います:
以前、私は検証をピース自体の一部にすることを考えていました。これにより、各ピースが実装する必要があるabstract validateMove()
メソッドが存在しますが、次の場合、これによりコードが大幅に変更される可能性があります。将来、特定のピースの移動検証メカニズムを変更することにします。これは、Head First Designの本に記載されているflyメソッドがないRubber duckの場合に似ています。
したがって、私はこれを作成することを考えましたインターフェイスと呼ばれるMoveValidator、これはピース固有の実装を持ち、各ピースはバリデーター自体ではなく変数の1つとしてバリデーターを持ちますまたは検証自体を実装します。
したがって、Pawn
はその構成の1つとしてPawnMoveValidator
を持ちます。
だから私は次の抽象的なピースクラスを持っています
_@RequiredArgsConstructor
public abstract class Piece {
@NonNull
@Getter
private final PieceColor pieceColor;
@NonNull
@Getter
private final MoveValidator moveValidator;
@Getter
@Setter
private Point currentLocation;
private boolean basicValidation(Cell[][] board, Point src, Point dest) {
.........
.........
}
public boolean validateMove(Cell[][] board, Point src, Point dest) {
return basicValidation(board, src, dest) && moveValidator.validateMove(board, src, dest);
}
}
_
Cell.Java
_@RequiredArgsConstructor
public class Cell {
@NonNull
@Getter
private final CellColor cellColor;
@NonNull
@Getter
private final Integer row;
@NonNull
@Getter
private final Integer col;
}
_
Point.Java
_@RequiredArgsConstructor
public class Point {
@NonNull
@Getter
private final Integer x;
@NonNull
@Getter
private final Integer y;
}
_
MoveValidator.Java
_public interface MoveValidator {
validateMove(Cell[][] board, Point src, Point dest);
}
_
ここでいくつか質問があります。
Piece
に実装する抽象メソッドがない場合でも、抽象クラスのままにすべきですか、それとも具象クラスになるべきですか?Pawn
クラスには、それが最初の移動かどうかを示すフラグがあります。その場合、PawnMoveValidator
が移動を検証したい場合、フラグの状態を知る必要があります。 Pawnクラスにバリデーター自体を実装させずに、それをどのように行うことができますか?御時間ありがとうございます
これは良いデザインですか、それともさらに改善できますか?
私にとって、デザインは常に改善することができます。少なくとも、現在のデザインでは解決できないムーブメントの問題がいくつかあります。
ピースに実装する抽象メソッドがない場合でも、それは抽象クラスのままであるのか、それとも具象クラスになるのか?
クラスに抽象メソッドがない場合、インスタンス化できない原因は何ですか?一部のコードを再利用する場合は、コンポジションを使用します。いくつかのタイピングの制約を満たしたい場合は、インターフェースを使用してください。個人的には、抽象クラスがある場合にのみ抽象クラスを使用します。
特定のピースの移動検証で、それが実装されている特定のピースの状態の知識が必要な場合はどうなりますか?例:ポーンは2つのセルを前方に移動したいと考えています。 Pawnクラスには、それが最初の移動かどうかを示すフラグがあります。その場合、PawnMoveValidatorが移動を検証したい場合は、フラグの状態を知る必要があります。 Pawnクラスにバリデーター自体を実装させずに、それをどのように行うことができますか?
残念ながら、移動が有効かどうかを決定するプロセスには、多くのことが関与しています。
一部のルールには、ボードの現在の状態に関する情報が必要です。
いくつかのルールは、現在の部分とおそらく関連部分(キャスティング)に関する情報を必要とします:
ご覧のとおり、移動とは1つのセルから別のセルに1つのピースを移動することだけではありません。キャスティングでは、1回の移動で2つの駒があります。
要約すると、現在のデザインは私には非常に明確です。ただし、すべてのルールを使用して完全なチェス盤をモデル化するには、さらに作業が必要になる場合があります。私はいくつかの提案があります:
Board
を持ち、Move
メソッドでvalidate
オブジェクトを受け入れます。あなたはデザインを必要以上に複雑にしています。ボードは、駒ではなく移動の検証を処理します。これは、作品はそれがどんな種類の作品であるかを知っているだけだからです。それがどこにあるか、またはボードがどのように見えるかはわかりません。
どのようなメリットがありますか?コードは非常に単純になります。
MoveValidator
は必要ありません。短い関数です場所は、作品に固有の品質ではありません。ボードとの関係でこの作品にのみ存在するものです。 ピースの場所を要求しないでください。ボード上の場所でピースを要求します。これは重要な違いです。
チェスのゲームを終了する条件の1つは、チェス盤が同じ状態に3回入ることです。これは引き分けになります。
このルールを正しく実装する最も簡単な方法は、ボードの以前のすべての状態を追跡することです。これは思ったよりも簡単です。ボードのコピーを作成するのが簡単な場合は、移動する前にボードのコピーを保存するだけです。この条件をチェックするときは、現在の状態を以前の状態のリストと比較するだけです。
同じ色の2頭の馬がボード上の位置を切り替えたが、それ以外は何も変更されなかった場合、ボードは同じままです。つまり、最初からこれらの馬が異なるとは考えないでください。
まとめると、これはファイル数、コード行数が少なくなり、パフォーマンスが向上することを意味します。
次のようにコードを書き直すことができます。
public class Piece {
public enum Color {
Black, White
}
public enum Type {
Pawn, King, Queen, Rook, Knight, Bishop
}
@NonNull
@Getter
private Color color;
@NonNull
@Getter
private Type type;
public Piece(Color color, Type type) {
this.color = color;
this.type = type;
}
public Piece(Piece p) {
this.color = p.color;
this.type = p.type;
}
@Override
public boolean equals(Object o) {
Piece p = (Piece)o;
return p.color == color && p.type == type;
}
// Gets the point value of the piece
public int pointValue() {
switch(type) {
case Pawn: return 1;
case King: return 1000;
case Queen: return 9;
case Rook: return 5;
case Knight: return 3;
case Bishop: return 3;
}
}
// Check if a move between two points is legal for the piece under
// normal circumstances
// X is the column, Y is the row. X and Y are from 0 to 7
public boolean moveLegal(int startX, int startY, int endX, int endY) {
int dx = endX - startX;
int dy = endY - startY;
// If the color is black, flip how the board looks
// So that the home row is always 0 and the row with pawns is 1
if(color == Black) {
startY = 7 - startY;
}
// You can't move a piece off the board
if(endX < 0 || endX > 7) return false;
if(endY < 0 || endY > 7) return false;
// Not moving a piece isn't a legal move
if(dx == 0 && dy == 0)
return false;
// If the change in position is always positive it simplifies the math
if(dx < 0) dx = -dx;
if(dy < 0) dy = -dy;
switch(type) {
case Pawn:
// A pawn can move forward 1, unless it's in the first row
return dy == 1 || (dy == 2 && startY == 1);
case Rook:
// It can move horizontally or vertically, but not both
return (dx != 0 && dy == 0) || (dx == 0 && dy != 0);
case Knight:
// A night can do a diagonal hop
return (dx == 1 && dy == 2) || (dx == 2 && dy == 1);
case King:
return dx == 1 || dy == 1;
case Queen:
return dx == 0 || dy == 0 || dx == dy;
case Bishop:
return dx == dy;
}
}
}
ご覧のとおり、ピースはまだ移動方法を知っていますが、an MoveValidator
やその他の複雑なソリューションは必要ありません。ピースの種類ごとにクラスを分ける必要はありません。また、ピースが現在の場所であることを知るためのピースも必要ありません。ボードは、いくつかの単純なメンバー関数でそれを処理します。
public class Board {
// Set up board in constructor
Piece[][] pieces;
private boolean bad(int index) {
return index < 0 || index > 7;
}
public boolean isEmpty(int x, int y) {
return pieces[x][y] == null;
}
public boolean moveValid(int startX, int startY, int endX, int endY) {
// If the move is off the board, it's invalid
if(bad(startX) || bad(startY) || bad(endX) || bad(endY))
return false;
// You can't move where there is no piece
if(isEmpty(startX, startY))
return false;
Piece piece = pieces[startX, startY];
// Check that the endpoint doesn't contain a piece of the same color
if(!isEmpty(endX, endY))
if(pieces[endX][endY].getColor() == piece.getColor())
return false;
// If the piece can't move that way, it's not valid
if(!piece.moveLegal(startX, startY, endX, endY))
return false;
// To-do: check that cells in-between are empty
}
}