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

88

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

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

  • Доска
  • Игрок
  • Кусок
  • Площадь
  • Игра в шахматы

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

Кроме того, 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();

}

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

Сид
источник
3
Нитпикский комментарий: p2 не должен коллировать, takeTurn()если ход p1 завершает игру. Менее придирчивый комментарий: мне кажется более естественным назвать игроков whiteи black.
Кристофер Джонсон
Согласовано. Но, как я уже сказал, меня больше интересуют аспекты дизайна и то, какие объекты должны отвечать за какие действия и у кого какие ссылки.
Сид,
Мне понравилось, как вы указали выше в своем фрагменте. В моей реализации каждая часть имеет внутреннюю копию полной позиции, потому что она будет использовать ее в своей собственной canMove()функции. А когда ход сделан, все остальные фигуры обновляют свою внутреннюю копию доски. Я знаю, что это не оптимально, но в то время было интересно изучать C ++. Позже друг, не шахматист, сказал мне, что у него будет classesкаждая клетка вместо каждой фигуры. И этот комментарий показался мне очень интересным.
eigenfield 03

Ответы:

54

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

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 / и т. Д. Просто хранят состояние игры. Вы можете манипулировать ими, например, создать позицию, если вы этого хотите. У меня есть класс, реализующий мой интерфейс IGameRules, который отвечает за:

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

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

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

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

cdhowie
источник
1
Спасибо за подробный ответ. Однако у меня есть несколько вопросов по поводу дизайна. Например, не сразу понятно, почему Move должен быть классом. Моя единственная цель - распределять обязанности и решать, как взаимодействовать между классами максимально чисто. Я хочу знать «почему» за любым дизайнерским решением. Я не понимаю, как вы пришли к принятым дизайнерским решениям и почему они являются хорошим выбором.
Сид,
Move- это класс, позволяющий хранить всю историю
ходов в списке ходов
@cdhowie Обсуждаются ли Gameвы с разработчиком IGameRulesили вы применяете правила вне объекта? Последнее кажется неуместным, поскольку игра не может защитить собственное состояние, не так ли?
plalx
1
Это может быть глупо, но разве класс Turn in Game не должен иметь тип PieceColor вместо PieceType?
Деннис ван
1
@nikhil Они указывают, в каком направлении оба игрока могут рокироваться (в сторону файлов A и H). Эти значения изначально верны. Если белая ладья A ходит, CanWhiteCastleA становится ложным, как и ладья H. Если белый король ходит, оба становятся ложными. То же самое и с черным.
cdhowie
6

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

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 {....}
Simplefuzz
источник
0

Я недавно создал шахматную программу в PHP ( сайт нажмите здесь , источник нажмите здесь ) , и я сделал это объектно - ориентированный. Вот классы, которые я использовал.

  • ChessRulebook (статический) - я поместил generate_legal_moves()сюда весь свой код. Этому методу предоставляется доска, чья это очередь, и некоторые переменные для установки уровня детализации выходных данных, и он генерирует все допустимые ходы для этой позиции. Он возвращает список ChessMoves.
  • ChessMove - хранит все необходимое для создания алгебраической нотации , включая начальный квадрат, конечный квадрат, цвет, тип фигуры, захват, шах, мат, тип рекламной фигуры и проход. Необязательные дополнительные переменные включают разрешение неоднозначности (для ходов вроде Rae4), рокировку и доску.
  • ChessBoard - хранит ту же информацию, что и Chess FEN , включая массив 8x8, представляющий квадраты и сохраняющий ChessPieces, чей ход идет, проходное целевое поле, права рокировки, таймер полухода и таймер полного хода.
  • ChessPiece - хранит тип фигуры, цвет, квадрат и стоимость фигуры (например, пешка = 1, конь = 3, ладья = 5 и т. Д.)
  • ChessSquare - хранит рядовые, как ints.

В настоящее время я пытаюсь превратить этот код в шахматный ИИ, поэтому он должен быть БЫСТРОМ. Я оптимизировал generate_legal_moves()функцию с 1500 мс до 8 мс и все еще работаю над этим. Я извлек из этого уроки ...

  • По умолчанию не храните всю ChessBoard в каждом ChessMove. Храните доску в движении только при необходимости.
  • По возможности используйте примитивные типы, такие как int. Вот почему ChessSquareхранятся рядовые данные в виде int, а не буквенно-цифровые символы stringс удобочитаемыми обозначениями шахматных квадратов, такими как «a4».
  • Программа создает десятки тысяч ChessSquares при поиске в дереве ходов. Я, вероятно, реорганизую программу, чтобы не использовать ChessSquares, что должно дать прирост скорости.
  • Не вычисляйте ненужные переменные в своих классах. Первоначально вычисление FEN на каждой из моих шахматных досок действительно убивало скорость программы. Мне пришлось выяснить это с помощью профилировщика .

Я знаю, что это устарело, но, надеюсь, это кому-то поможет. Удачи!

КрасныйДраконВебДизайн
источник