Я создаю шахматный движок. У меня есть интерфейс Piece, Rook, Bishop и т. Д. Реализует этот класс. Моя доска представляет собой массив Piece [] []. Допустим, белый игрок хочет переместить слона. Координаты места назначения и табличку передаю епископу. Слон проверяет, находится ли пункт назначения на той же диагонали, затем он спрашивает доску, нет ли фигур между его положением и полем назначения. Это нормально с точки зрения ООП? Спасибо
Шахматный движок. Можно ли передавать ссылку на объект доски фигуре?
Ответы (3)
Это немного деликатно.
Точка зрения ООП
С точки зрения ООП, можно было бы задаться вопросом, должна ли Board быть Piece[][]
массивом в первую очередь. Хороший, чистый, объектно-ориентированный дизайн здесь, вероятно, будет включать в себя что-то вроде
interface Board {
Piece get(int r, int c);
// Should this be here? See below...
void set(int r, int c, Piece p);
}
И тогда возникает один важный вопрос: поместится ли Слон «себя» в целевую позицию данной доски? Или, сосредотачиваясь на точке зрения ООП: является ли доска, переданная части, изменяемой или она «только для чтения»? Можно представить себе класс MaliciousBishop
, который, получив Board
, убивает короля противника, занимая позицию короля.
С очень высокоуровневой абстрактной точки зрения ООП можно было бы задаться вопросом, должен ли Piece
вообще иметь какой-либо интеллект. Piece
- тупой кусок пластика. Ему ничего не известно о шахматных правилах. Вы (игрок) можете разместить эту фигуру где угодно, подчиняясь или игнорируя правила шахмат. Так что подчиняться или даже проверять какие-либо правила - это, конечно, не работа пьесы. Можно сказать, что подчинение правилам - это то, что ожидается от игрока, а обеспечение соблюдения правил - это работа вышестоящего интеллекта. (возможно, какой-нибудь класс "ChessGameManager").
Простой подход, который кажется (!) Подходит для объектно-ориентированной шахматной реализации, состоит в том, чтобы иметь такие классы, как
abstract class Piece {
Color getColor() { ... }
Point getPosition() { ... }
abstract void doMove(...) { ... }
}
class Bishop extends Piece {
void doMove(....) { ... }
}
// + other classes extending "Piece"
Но учтите, что это не всегда может быть лучшим подходом и, возможно, не всегда достаточным. В частности, вы должны иметь очень четкое представление о том, как взаимодействуют ваши Engine
, Board
, Piece
и Player
классы и каковы их обязанности. (Поразмыслив над этим некоторое время, вы, вероятно, придете к выводу, что вам также понадобится класс Move
...). В общем, проверка правильности хода намного сложнее, чем кажется на первый взгляд. Вы упомянули, что для хода слона вы проверяете, верна ли целевая позиция и нет ли других фигур между ними. Но этот ход по-прежнему недействителен, если этот ход заставляет собственного короля оставаться под шахом. Проверить это можно только «двигателем», а не по частям. Другие вещи, о которых люди склонны забывать (и которые связаны с информацией о всем состоянии игры и, таким образом, вряд ли могут быть обработаны с помощью одного элемента), - это Castling или На проходе движется.
Шахматный движок точки зрения
Для шахматного движка есть несколько требований, которые делают хороший объектно-ориентированный подход особенно сложным. Если вы намереваетесь написать эффективный шахматный движок, то ваша доска, скорее всего, будет массивом long
значений, которые управляются поразрядными операциями ....
(Примечание: если вы разработали свой Board
класс как интерфейс, как было предложено выше, то вы все равно могли бы сохранить красивое, высокоуровневое, объектно-ориентированное представление на это представление с высокой производительностью, которое используется для самого движка. Мое эмпирическое правило: всегда моделируйте все как интерфейс вначале. Легко сделать его более конкретным после этого)
Итак, в зависимости от того, хотите ли вы написать
- хорошие, объектно-ориентированные шахматы для двух игроков-людей, с проверкой правил или
- шахматный движок
Возможно, вы захотите по-другому подойти к некоторым частям игрового дизайна.
РЕДАКТИРОВАТЬ: Вы наверняка наткнетесь на это, когда будете искать информацию о программировании шахмат (движка), но я хотел бы отметить, что https://www.chessprogramming.org/Main_Page предлагает много справочной информации. Опять же: это не совсем объектно-ориентированный дизайн, а больше о мельчайших деталях шахматных движков.
...extends Piece
подход) есть веские аргументы, и вам следует внимательно обдумать варианты. Не делать фигуры глупыми - тоже нормально, но подумайте о случаях, которые я упомянул (рокировка и т. д.).
- person Marco13; 18.04.2015
С точки зрения ООП слон (ладья и т. Д.) Должен иметь возможность сказать, какой для него правильный ход - то есть, если данное поле находится на той же диагонали. Также он мог сказать доске, что он не может «пропускать» другие фигуры (IIRC только рыцарь может это делать, поэтому рыцарь может отменить это).
Опять же, никакая фигура не может перейти на поле с другой фигурой того же цвета, также ни один ход не должен подвергать опасности (шаху) короля. Эти ограничения должны проверяться вашим классом GameController (или каким-либо базовым классом, который инкапсулирует эту логику), потому что они действуют для всех частей.
Если GameController проверил, пусто ли целевое поле, а затем спросил фигуру, может ли она туда переместиться, сама фигура не должна была бы знать ваш массив доски, и общая логика была бы сосредоточена в контроллере.
Извините за мой плохой шахматный словарный запас :)
С точки зрения дизайна у вас есть два (а может и больше) варианта, которые следует рассмотреть:
- Доска - это своего рода менеджер правил, и она должна знать, как фигуры могут действовать - есть ограничение - доска должна знать каждого участника, поскольку в шахматах ограниченное количество типов, это не проблема.
Доска - это только заполнитель / система координат для фигур. При таком подходе вы можете сэкономить много кода, имея абстрактный класс (или интерфейс, как вы написали, но между частями будет много общих атрибутов, поэтому абстрактный класс выглядит лучше для меня) для Piece, и каждый тип фрагмента будет расширяться / реализовать это. Пример:
public abstract class Piece { private int row; private int column; // or other method to store position private boolean isBlack // or enum for type // contructor, getters, setters etc... public abstract boolean canMove(int newX, int newY); /* some other abstract methods if you need */ }
И позже
public class Bishop extends Piece { @Override public boolean canMove(int newX, int newY) { if( /*check if new points aare on diagonal */) return true; else return false; } } public class Knight extends Piece { @Override public boolean canMove(int newX, int newY) { if( /*check if L shaped with prev pos */) return true; else return false; } }
Я бы выбрал второй вариант. Это больше ООП +, это дает больше гибкости в хранении частей. В игровом классе у вас могут быть методы, которые принимают в качестве аргумента Piece и передают все, что расширяет класс Piece, Bishops, Knight и т. Д., И полиморфизм сделает эту работу за вас. В случае первого варианта вам, вероятно, потребуется использовать какой-нибудь переключатель / футляр.
Конечно, вам также понадобятся другие классы для игроков, состояния игры и т. Д.
Отвечая на ваш вопрос - можно переходить к доске. В моем предложении фигура знает, где она стоит, поэтому ей нужно только знать, какая новая предлагаемая позиция, чтобы проверить, не превышает ли она размер доски и является ли она допустимой для своего типа. Поскольку игровой контроллер проверяет наличие столкновений, вам на самом деле доска не нужна?
canMove()
вам также нужно проверить, нет ли другой фигуры того же игрока, так что вам понадобится доступ к другим частям.
- person Kuba; 18.04.2015