Шахматный движок. Можно ли передать ссылку на объект платы?

Я создаю шахматный движок. У меня есть интерфейс Piece, Rook, Bishop и т. Д. Реализует этот класс. Моя доска - это массив Piece[][]. Допустим, белый игрок хочет переместить своего слона. Я передаю координату пункта назначения и ссылку на доску слону. Епископ проверяет, находится ли пункт назначения на той же диагонали, а затем спрашивает доску, нет ли между его позицией и квадратом назначения каких-либо фигур. С точки зрения ООП это нормально? Спасибо

3 ответа

Решение

Это немного деликатно.

Точка зрения ООП

С точки зрения ООП можно задаться вопросом, должен ли Правление быть 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 учебный класс...). В общем, проверка того, является ли ход действительным, намного сложнее, чем кажется на первый взгляд. Вы упомянули, что для хода епископа вы проверяете, является ли целевая позиция действительной и что между ними нет других фигур. Но ход все еще недействителен, если ход заставляет своего короля оставаться под контролем. Это может быть проверено только "двигателем", и вряд ли самой деталью. Другие вещи, которые люди склонны забывать (и которые включают в себя информацию обо всем игровом состоянии и, таким образом, вряд ли могут быть обработаны одной пьесой), - это ходы Каслинга или Эн пассанта.

Точка зрения шахматного двигателя

Для шахматного движка есть несколько требований, которые делают хороший, объектно-ориентированный подход особенно сложным. Если вы намереваетесь написать эффективный шахматный движок, то ваша доска, скорее всего, будет массивом long значения, которыми манипулируют с помощью побитовых операций....

(Примечание: если вы разработали Board класс как интерфейс, как предложено выше, тогда вы все равно можете сохранить хорошее высокоуровневое объектно-ориентированное представление для этого высоко оптимизированного представления, которое используется для самого движка. Мое эмпирическое правило: всегда сначала все моделируйте как интерфейс. Это легко сделать потом более конкретным)


Так что в зависимости от того, хотите ли вы написать

  • хорошие, объектно-ориентированные шахматы для двух игроков, с проверкой правил или
  • шахматный движок

Вы можете по-разному решать некоторые части игрового дизайна.


РЕДАКТИРОВАТЬ: вы, конечно, наткнуться на это, когда вы ищете информацию о программировании шахматы (движок), но я хотел бы отметить, что https://chessprogramming.wikispaces.com/ предлагает много справочной информации. Опять же: это на самом деле не о дизайне ОО, а больше о мелочах шахматных движков.

С точки зрения ООП, слон (ладья и т. Д.) Должен быть в состоянии сказать, что является для него законным поворотом, то есть если данное поле находится на той же диагонали. Также он может сказать доске, что он не может "пропускать" другие фигуры (IIRC только рыцарь может сделать это, поэтому рыцарь может переопределить это).

С другой стороны, ни одна фигура не может двигаться на поле с другим кусочком того же цвета, также никакие движения не должны подвергать опасности (проверять) короля. Эти ограничения должны быть проверены вашим классом GameController (или некоторым базовым классом, который инкапсулирует эту логику), потому что они сохраняются для всех частей.

Если GameController проверит, является ли целевое поле пустым, а затем спросит фигуру, может ли она перемещаться туда, самой фигуре не нужно будет знать массив вашей доски, и общая логика будет сосредоточена в контроллере.

Извините за мой плохой шахматный словарь:)

С точки зрения дизайна у вас есть два (или, возможно, больше) вариантов для рассмотрения:

  1. Совет - это своего рода менеджер правил, и он должен знать, как могут действовать фигуры - есть ограничение - Совет должен знать каждого актера, поскольку у шахмат ограниченное количество типов, это не проблема.
  2. Доска - это только заполнитель / система координат для фигур. При таком подходе вы можете сэкономить много кода, имея абстрактный класс (или интерфейс, как вы написали, но между частями будет много общих атрибутов, поэтому абстрактный класс для меня выглядит лучше), и каждый тип части будет расширяться / реализовать это. Пример:

    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 и т. Д., И полиморфизм сделает эту работу за вас. В случае первого варианта вам, вероятно, потребуется использовать какой-нибудь переключатель / кейс.

Конечно, вам также понадобятся другие классы для игроков, состояния игры и т. Д.

Чтобы ответить на ваш вопрос - это нормально, чтобы передать доску на куски. В моем предложении кусок знает, где он находится, поэтому ему нужно только знать, что является новой предлагаемой позицией, чтобы проверить, не превышает ли она размер доски и является ли она законной для ее типа. Поскольку игровой контроллер проверяет наличие столкновений, вам на самом деле не нужна доска?

Другие вопросы по тегам