Объектно-ориентированный дизайн для шахматной игры

Я пытаюсь понять, как проектировать и думать объектно-ориентированным образом, и хочу получить от сообщества ответы на эту тему. Ниже приведен пример шахматной игры, которую я хочу создать в стиле OO. Это очень широкий дизайн, и я сосредоточен на этом этапе, чтобы определить, кто несет ответственность за то, что сообщения и как объекты взаимодействуют друг с другом, чтобы имитировать игру. Пожалуйста, укажите, есть ли элементы плохого дизайна (высокая сцепность, плохая сплоченность и т.д.) И как их улучшить.

В шахматной игре есть следующие классы

  • Совет
  • Игрок
  • шт
  • Площадь
  • ChessGame

Совет состоит из квадратов, и поэтому Совет может быть ответственным за создание и управление квадратными объектами. Каждая часть также находится на квадрате, поэтому каждая часть также имеет ссылку на квадрат, на котором он находится. (Имеет ли это смысл?). Затем каждый кусок отвечает за то, чтобы переместиться с одного квадрата на другой. Класс игрока содержит ссылки на все принадлежащие ему части и также отвечает за их создание (должен ли игрок создавать пьесы?). У игрока есть метод takeTurn, который, в свою очередь, вызывает метод movePiece, который принадлежит кусочку класса, который изменяет местоположение части из ее текущего местоположения в другое место. Теперь я смущен тем, за что должен отвечать класс Совета. Я предположил, что это необходимо, чтобы определить текущее состояние игры и узнать, когда игра окончена. Но когда часть изменит его местоположение, как обновить доску? должен ли он поддерживать отдельный массив квадратов, на которых существуют кусочки, и которые обновляются, когда куски движутся?

Кроме того, ChessGame создает объекты Board и player, которые, в свою очередь, создают квадраты и куски соответственно и запускают симуляцию. Вкратце, это может означать, что код в ChessGame может выглядеть как

Player p1 =new Player();
Player p2 = new Player();

Board b = new Board();

while(b.isGameOver())
{
  p1.takeTurn(); // calls movePiece on the Piece object
  p2.takeTurn();

}

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

Ответ 1

Я на самом деле просто написал полную реализацию С# на шахматной доске, куски, правила и т.д. Вот примерно, как я ее смоделировал (фактическая реализация удалена, так как я не хочу получать все удовольствие от вашей кодировки):

public enum PieceType {
    None, Pawn, Knight, Bishop, Rook, Queen, King
}

public enum PieceColor {
    White, Black
}

public struct Piece {
    public PieceType Type { get; set; }
    public PieceColor Color { get; set; }
}

public struct Square {
    public int X { get; set; }
    public int Y { get; set; }

    public static implicit operator Square(string str) {
        // Parses strings like "a1" so you can write "a1" in code instead
        // of new Square(0, 0)
    }
}

public class Board {
    private Piece[,] board;

    public Piece this[Square square] { get; set; }

    public Board Clone() { ... }
}

public class Move {
    public Square From { get; }
    public Square To { get; }
    public Piece PieceMoved { get; }
    public Piece PieceCaptured { get; }
    public PieceType Promotion { get; }
    public string AlgebraicNotation { get; }
}

public class Game {
    public Board Board { get; }
    public IList<Move> Movelist { get; }
    public PieceType Turn { get; set; }
    public Square? DoublePawnPush { get; set; } // Used for tracking valid en passant captures
    public int Halfmoves { get; set; }

    public bool CanWhiteCastleA { get; set; }
    public bool CanWhiteCastleH { get; set; }
    public bool CanBlackCastleA { get; set; }
    public bool CanBlackCastleH { get; set; }
}

public interface IGameRules {
    // ....
}

Основная идея заключается в том, что Game/Board/etc просто хранит состояние игры. Вы можете манипулировать ими, например, настройте позицию, если это то, что вы хотите. У меня есть класс, который реализует мой интерфейс IGameRules, который отвечает за:

  • Определение того, какие действия действительны, включая рокировку и en passant.
  • Определение того, действителен ли определенный шаг.
  • Определение, когда игроки находятся в чеке/матче/тупике.
  • Выполнение ходов.

Отключение правил от классов игры/доски также означает, что вы можете легко реализовать варианты. Все методы интерфейса правил принимают объект Game, который они могут проверять, чтобы определить, какие действия действительны.

Обратите внимание, что я не сохраняю информацию о игроке на Game. У меня есть отдельный класс Table, который отвечает за хранение метаданных игры, таких как, кто играл, когда игра состоялась и т.д.

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

Ответ 2

Вот моя идея, для довольно простой шахматной игры:

class GameBoard {
 IPiece config[8][8];  

 init {
  createAndPlacePieces("Black");
  createAndPlacePieces("White");
  setTurn("Black");

 }

 createAndPlacePieces(color) {
   //generate pieces using a factory method
   //for e.g. config[1][0] = PieceFactory("Pawn",color);
 }

 setTurn(color) {
   turn = color;
 }

 move(fromPt,toPt) {
  if(getPcAt(fromPt).color == turn) {
    toPtHasOppositeColorPiece = getPcAt(toPt) != null && getPcAt(toPt).color != turn;
    possiblePath = getPcAt(fromPt).generatePossiblePath(fromPt,toPt,toPtHasOppositeColorPiece);
   if(possiblePath != NULL) {
      traversePath();
      changeTurn();
   }
  }
 } 

}

Interface IPiece {
  function generatePossiblePath(fromPt,toPt,toPtHasEnemy);
}

class PawnPiece implements IPiece{
  function generatePossiblePath(fromPt,toPt,toPtHasEnemy) {
    return an array of points if such a path is possible
    else return null;
  }
}

class ElephantPiece implements IPiece {....}