Дизайн шахматной игры с использованием объектно-ориентированных принципов
Я хотел бы знать, правильный ли мой подход и как его можно улучшить? Кроме того, есть ли способ избавиться от отношения между Piece
и Board
? В настоящий момент я сохраняю положение предмета как в куске, так и на доске. Есть ли способ изменить это?
Я рассмотрел Game
, чтобы содержать экземпляр Board
и два Players
(один черный, один белый). Части содержат соединение с Советом, потому что для того, чтобы определить, являются ли они действительными, нам нужно знать отношение к другим частям.
Могу ли я использовать шаблоны проектирования для этого? Должен ли я использовать интерфейсы вместо суперкласса?
Game.java
public class Game {
private Board board = new Board();
private Player white;
private Player black;
public Game() {
super();
}
public void setColorWhite(Player player) {
this.white = player;
}
public void setColorBlack(Player player) {
this.black = player;
}
public Board getBoard() {
return board;
}
public void setBoard(Board board) {
this.board = board;
}
public Player getWhite() {
return white;
}
public void setWhite(Player white) {
this.white = white;
}
public Player getBlack() {
return black;
}
public void setBlack(Player black) {
this.black = black;
}
public boolean initializeBoardGivenPlayers() {
if(this.black == null || this.white == null)
return false;
this.board = new Board();
for(int i=0; i<black.getPieces().size(); i++){
board.getSpot(black.getPieces().get(i).getX(), black.getPieces().get(i).getY()).occupySpot(black.getPieces().get(i));
}
return true;
}
}
Player.java
public class Player {
public final int PAWNS = 8;
public final int BISHOPS = 2;
public final int ROOKS = 2;
public boolean white;
private List<Piece> pieces = new ArrayList<>();
public Player(boolean white) {
super();
this.white = white;
}
public List<Piece> getPieces() {
return pieces;
}
public void initializePieces(){
if(this.white == true){
for(int i=0; i<PAWNS; i++){ // draw pawns
pieces.add(new Pawn(true,i,2));
}
pieces.add(new Rook(true, 0, 0));
pieces.add(new Rook(true, 7, 0));
pieces.add(new Bishop(true, 2, 0));
pieces.add(new Bishop(true, 5, 0));
pieces.add(new Knight(true, 1, 0));
pieces.add(new Knight(true, 6, 0));
pieces.add(new Queen(true, 3, 0));
pieces.add(new King(true, 4, 0));
}
else{
for(int i=0; i<PAWNS; i++){ // draw pawns
pieces.add(new Pawn(true,i,6));
}
pieces.add(new Rook(true, 0, 7));
pieces.add(new Rook(true, 7, 7));
pieces.add(new Bishop(true, 2, 7));
pieces.add(new Bishop(true, 5, 7));
pieces.add(new Knight(true, 1, 7));
pieces.add(new Knight(true, 6, 7));
pieces.add(new Queen(true, 3, 7));
pieces.add(new King(true, 4, 7));
}
}
}
Board.java
public class Board {
private Spot[][] spots = new Spot[8][8];
public Board() {
super();
for(int i=0; i<spots.length; i++){
for(int j=0; j<spots.length; j++){
this.spots[i][j] = new Spot(i, j);
}
}
}
public Spot getSpot(int x, int y) {
return spots[x][y];
}
}
Spot.java
public class Spot {
int x;
int y;
Piece piece;
public Spot(int x, int y) {
super();
this.x = x;
this.y = y;
piece = null;
}
public void occupySpot(Piece piece){
//if piece already here, delete it, i. e. set it dead
if(this.piece != null)
this.piece.setAvailable(false);
//place piece here
this.piece = piece;
}
public boolean isOccupied() {
if(piece != null)
return true;
return false;
}
public Piece releaseSpot() {
Piece releasedPiece = this.piece;
this.piece = null;
return releasedPiece;
}
}
Piece.java
public class Piece {
private boolean available;
private int x;
private int y;
public Piece(boolean available, int x, int y) {
super();
this.available = available;
this.x = x;
this.y = y;
}
public boolean isAvailable() {
return available;
}
public void setAvailable(boolean available) {
this.available = available;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean isValid(Board board, int fromX, int fromY, int toX, int toY){
if(toX == fromX && toY == fromY)
return false; //cannot move nothing
if(toX < 0 || toX > 7 || fromX < 0 || fromX > 7 || toY < 0 || toY > 7 || fromY <0 || fromY > 7)
return false;
return true;
}
}
King.java
public class King extends Piece{
public King(boolean available, int x, int y) {
super(available, x, y);
// TODO Auto-generated constructor stub
}
@Override
public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
if(super.isValid(board, fromX, fromY, toX, toY) == false)
return false;
if(Math.sqrt(Math.pow(Math.abs((toX - fromX)),2)) + Math.pow(Math.abs((toY - fromY)), 2) != Math.sqrt(2)){
return false;
}
return false;
}
}
Knight.java
public class Knight extends Piece{
public Knight(boolean available, int x, int y) {
super(available, x, y);
}
@Override
public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
if(super.isValid(board, fromX, fromY, toX, toY) == false)
return false;
if(toX != fromX - 1 && toX != fromX + 1 && toX != fromX + 2 && toX != fromX - 2)
return false;
if(toY != fromY - 2 && toY != fromY + 2 && toY != fromY - 1 && toY != fromY + 1)
return false;
return true;
}
}
Bishop.java
public class Bishop extends Piece{
public Bishop(boolean available, int x, int y) {
super(available, x, y);
// TODO Auto-generated constructor stub
}
@Override
public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
if(super.isValid(board, fromX, fromY, toX, toY) == false)
return false;
if(toX - fromX == toY - fromY)
return true;
return false;
}
}
Rook.java
public class Rook extends Piece{
public Rook(boolean available, int x, int y) {
super(available, x, y);
// TODO Auto-generated constructor stub
}
@Override
public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
if(super.isValid(board, fromX, fromY, toX, toY) == false)
return false;
if(toX == fromX)
return true;
if(toY == fromY)
return true;
return false;
}
}
Queen.java
public class Queen extends Piece{
public Queen(boolean available, int x, int y) {
super(available, x, y);
}
@Override
public boolean isValid(Board board, int fromX, int fromY, int toX, int toY) {
if(super.isValid(board, fromX, fromY, toX, toY) == false)
return false;
//diagonal
if(toX - fromX == toY - fromY)
return true;
if(toX == fromX)
return true;
if(toY == fromY)
return true;
return false;
}
}
6 ответов
Не предлагая глубокий обзор кода (поскольку у меня не так много специфических знаний Java), давайте посмотрим, что полный «ход» влечет за собой в шахматы:
- Игрок выбирает кусок для перемещения.
- Piece делает юридический ход в соответствии со своими правилами перемещения.
- В дополнение к правилам, основанным исключительно на движении, существует также логика захвата, поэтому епископ не может двигаться от a1-h8, если есть часть, сидящая на c3.
- Если игрок был ранее проверен, и этот ход не удаляет чек, он должен быть отменен.
- Если перемещение выставляет проверку, оно должно быть отменено /запрещено.
- Если игрок захватывает кусок, удалите кусок (включая en passant!).
- Если кусок является пешкой, достигающей заднего ранга, продвигайте ее.
- Если этот ход является рокировкой, установите соответствующее положение ладьи соответственно. Но король и ладья могут только запираться, если они не двигались, поэтому вам нужно следить за этим. И если король движется через чек в замок, это тоже запрещено.
- Если ход приводит к тупику или матчу, игра заканчивается.
Может быть более четное (?). Это сложный шаг, больше, чем просто подсчет и последующее занятие пробелов.
Поэтому моей общей интуицией было бы просто позвонить:
Game.move(currentSpot, NewSpot);
И метод move будет содержать весь код для проверки шагов выше:
- Проверить
Piece.isValidMove(currentSpot, newSpot);
- возможно, здесь нужна логика роллинга, так как король перемещает более 1 места, а лад прыгает с короля) - Проверьте
Player.isChecked()
(это просто сахар дляPlayer.Pieces["King"].CanBeCaptured()
- более интересная логика здесь!) li> - Проверьте, содержит ли
newSpot
кусок, и если да,newSpot.Piece.Remove()
; - Постройте некоторую логику для вызова
Piece.CheckEnPassant()
(Piece заложена, первый ход, 2 шага, мимо вражеской пешки, которые переехали в позицию захвата на предыдущем ходу - получайте удовольствие от этого!) -
Piece.CheckPromote()
(Piece закладывается, перемещается на задний план противоположного игрока) - Проверьте, есть ли
Game.isOver()
, который проверяетGame.isStaleMate()
иGame.isCheckMate()
.
Ваш класс Board очень анемичен, вы используете его только в своем коде как прокси-объект для массива пятен. Вы могли бы просто создать Совет как массив Spots in Game. В любом случае вы уже можете удалить его из своей логики, поскольку вся ваша логика полностью основана на Xs и Ys, которые вы проходите.
UPDATE
Я удалю все ваши свойства позиции из куска. Вы используете его только как прокси, чтобы выяснить, какое место занимает кусок во время инициализации. Вместо этого удалите Player.initializePieces()
и просто инициализируйте доску с помощью частей в нужном месте (Board.Spot.Piece = King и т. Д.), А затем пусть игроки выбирают цвет.
Мои комментарии касаются дизайна игры. Я вижу, что обязанности сущностей смешаны во многих местах.
- Игрок не должен инициализировать фрагменты. Мы можем перенести ответственность на борт. Совету и кускам не нужно знать о игроках.
- Части не должны обрабатывать перемещение. Pieces может предоставить список возможных шагов для достижения пути назначения, но на плате должен быть выбран допустимый путь.
- Совет должен проверить наличие условия «Проверить соответствие».
- Игра должна отслеживать перемещение игроков в историю и выбор цвета куска.
- Класс игрока должен содержать только данные игрока.
Ниже приведена диаграмма грубого класса
Некоторые быстрые снимки
- a
if
, написанное какif (booleanVariable==true)
можно упростить доif (booleanVariable)
- у вас не должно быть общедоступных переменных типа
public boolean white;
- нет конструктора
Game
,Board
,Player
иPiece
должен вызыватьsuper()
, потому что они, очевидно, не наследуют /не расширяют какой-либо класс.
Некоторые быстрые кадры дизайна
- шахматной игре нужна доска, 2 игрока и 32 штуки.
- части являются частью Правления.
- Игрок перемещает кусок по правилам
- правила привязаны к типу детали и расположению фигур на доске.
- эти правила должны оцениваться каким-либо объектом, либо
Game
, либоRuleEvaluator
.
Вместо использования логического значения для флага, если что-то есть (и, следовательно, подразумевает, что это не другое), рассмотрите enum
; в этом случае вы ничего не покупаете с точки зрения гибкости (поскольку это не так, как если бы это было когда-либо, скажем, красное или фиолетовое или что-то еще), но это сделает ваш код более понятным.
В игре вместо того, чтобы устанавливать черно-белый отдельно, у вас должен быть один setPlayers(Player black, Player white)
, или еще лучше, чтобы плата была фабрикой, которая обеспечивает черный и белый Player
s - если есть даже причина иметь объект Player в первую очередь.
Пара дополнений ко всем остальным:
- оба
Player
иBoard
должны знать, где находятся части, как их, так и противники. Не могу подумать, что лучший способ разложить это, но подумайте, как они будут разговаривать друг с другом с минимальным дублированием. Я думаю, что имеет смысл поставить его подBoard
, а затемPlayer
сохранить ссылку наBoard
и наследовать или комбинировать свои методы: -
*.isValid()
проверять только от-и-coords, они в настоящее время не проверяют, есть ли промежуточные дружественные или вражеские части, или действительно, если на месте занятыми. Я думаю, вы должны сделать это на одной функции на уровне Совета, это будет очень уродливо, если вы сделаете это для каждой части. Вам может понадобиться вспомогательная функция (итератор?) Для каждой части для генерацииinterveningSpots(fromX,fromY,toX,toY,pieceType)
, поэтомуBoard.validMove()
может проверить, они заняты. -
имя
Piece.isValid()
запутанно, это может означать либоvalidMove()
, либоvalidPosition()
. Лично я бы переименовал егоvalidMove()
. (Нам понадобитсяvalidPosition()
, если вы когда-либо выполняете продвижение по службе, но опять же, это будет реализовано на уровне Совета, а не на Piece или Player). -
King.isValid()
, кажется, всегда возвращает false - ошибка? -
Queen/Bishop/Rook.isValid()
в настоящее время разрешают пустые нулевые перемещения(toX==fromX && toY==fromY)
. Это может звучать как nitpicking, но a) он может ошибочно позволить вам уклониться от помощника, «ничего не делая». B) он, вероятно, испортит рекурсию в любом AI, который вы или кто-то захотите добавить. -
подсказка для более эффективного и компактного кода для
King.isValid()
: вам не нужно брать sqrt; просто проверьте, что dist2 = (dx ^ 2 + dy ^ 2) либо 1, либо 2. И вам не нужно abs (dx), так как квадрат отрицательного числа положителен. Итак:@Override public boolean isValid(...) { if(!super.isValid(...)) { return false; int dist2 = Math.pow((toX - fromX), 2) + Math.pow((toY - fromY), 2); return (dist2 == 1 || dist2 == 2); }
Вот несколько способов упростить вашу логику:
- Как уже упоминалось, вместо записи
if (condition == true)
, напишитеif (condition)
. - Аналогично, вместо
if (condition == false)
, напишитеif (!condition)
. -
Если вы соблазняетесь написать:
if (condition) return true; return false;
... вместо этого напишите условие
return condition;
. -
Аналогично, вместо:
if (condition) return false; return true;
... написать
return (!condition);
. -
Если у вас возникает соблазн написать:
if (condition1) return true; if (condition1) return true; return false;
... вместо
return condition1 || condition2;
В общем, упростите логику, если она увеличится, уточните.