Это злоупотребление системой типов? - PullRequest
3 голосов
/ 08 октября 2009

Я работаю над хобби-проектом игры Шахматы в стиле барокко . Для тех, кто не играл в нее, у нее те же основные правила, что и в шахматах, но методы передвижения и захвата разные.

Естественно, я создал стандартные классы для игры: GameState, Board, Square и класс, предназначенный для каждой фигуры, наследуемой от BasePiece.

Каждая часть имеет 2 основных виртуальных метода, GetPossibleMoves(Board board) и GetCapturedSquares(Board board, Square toSquare).

Теперь одна из фигур, Имитатор , захватывает фигуры, "подражая" фигуре, которую она захватывает. Например, длинный прыгун может захватывать фигуры, перепрыгивая через них. Это означает, что Имитатор может перепрыгнуть через вражеских Длинных Прыгунов, чтобы захватить их (но не может перепрыгнуть ничего другого).

Я выполнил функциональность GetCapturedSquares() для всех произведений, кроме Имитатора (который, безусловно, самый сложный из произведений).

Мой основной алгоритм для Imitator был:

  • найти все квадраты с вражескими фигурами на них
  • за каждую вражескую фигуру ...
    • создать смоделированный предмет в том же месте, что и Имитатор
    • найти действительные снимки, если имитируемая фигура движется к выбранному квадрату
    • проверить, есть ли вражеский квадрат в этом списке захваченных квадратов

Поскольку я уже написал код для движений других фигур, я подумал, что просто создаю новые экземпляры и использую их методы GetCapturedSquares() в зависимости от того, какой тип фигур был врагом. Для этого я настроил Dictionary, как вы можете видеть здесь, который отображает System.Type на экземпляр объекта указанного типа:

var typeToPiece = new Dictionary<Type, BasePiece>() 
{
    {typeof(Pincer), new Pincer() { Color = this.Color, CurrentSquare = this.CurrentSquare}},
    {typeof(Withdrawer), new Withdrawer() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
    {typeof(Coordinator), new Coordinator() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
    {typeof(LongLeaper), new LongLeaper() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
    {typeof(King), new King() { Color = this.Color, CurrentSquare = this.CurrentSquare }},
};
//...
var possibleMoves = typeToPiece[enemySquare.Occupant.GetType()].GetPossibleMoves(board, toSquare);

От этого я чувствую себя грязно внутри. Более уместно создать enum или string, который представляет тип куска в качестве ключа словаря, или это действительно не имеет значения? Есть ли другой способ справиться с этим? Я придерживаюсь мнения, что все в порядке, но мне интересно услышать ваши мысли.

Ответы [ 4 ]

7 голосов
/ 09 октября 2009

Я думаю, вам следует добавить abstract метод к BasePiece, который "клонирует" текущий фрагмент и возвращает имитированный фрагмент.

Вы бы override использовали этот метод для каждого типа фигуры. Чтобы поделиться кодом между ними, вы можете добавить метод protected в базовый класс, который копирует общие свойства в переданный ему экземпляр:

abstract class BasePiece {
   protected BasePiece(BasePiece pieceToClone) {
      this.Color = pieceToClone.Color;
      this.CurrentSquare = pieceToClone.CurrentSquare;
   }
   public abstract BasePiece GetSimulatedClone();
}

class King : BasePiece {
   protected King(King pieceToClone) : base(pieceToClone) { }
   public King() { }
   public override BasePiece GetSimulatedClone() {
       return new King(this);
   }
}

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

1 голос
/ 09 октября 2009

Не касаясь этой конкретной проблемы, нет ничего плохого в идее словаря, который позволяет вам искать объекты по их типу. На самом деле .NET FCL уже предоставляет такой тип - он называется KeyedByTypeCollection<T>. Вероятно, лучше для таких вещей, потому что он гарантирует, что ключ объекта является его типом, возвращаемым GetType() (а не каким-то другим случайным типом), и не позволит вам добавить два объекта одного типа.

1 голос
/ 09 октября 2009

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

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

  • для каждого типа фигуры (или типа двигателя)
    • создать смоделированный предмет в том же месте, что и Имитатор
    • найти действительные снимки, если имитируемая фигура движется к выбранному квадрату
    • хранит только те захваты, где обитатель вражеского квадрата относится к типу фигуры, которую вы проверяете

Это небольшая разница, но она значительно сократит объем необходимых расчетов.

Обновление

Комментарий рекурсива заставил меня понять, что, возможно, я недостаточно ясно представлял, как будет работать уровень Mover. Идея состоит в том, чтобы иметь KingMover, PincerMover и так далее, которые знают о ходах для определенного типа фигуры. Поскольку они привязаны к типу фигуры, а не к самой фигуре, они могут даже быть одиночками. Каждый тип куска может иметь поле Mover, указывающее на его движитель, и тогда либо ваша бизнес-логика может напрямую вызывать метод GetPossibleMoves этого Mover, либо метод GetPossibleMoves вашего куска может просто вызывать метод Mover, передавая себя в качестве аргумента. ImitatorMover будет знать, запрашивать ли у другого типа движителя его возможные ходы, а затем фильтровать эти ходы в зависимости от того, будут ли они атаковать часть типа, связанного с этим двигателем.

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

1 голос
/ 09 октября 2009

Это немного более элегантно, на мой взгляд:

    private abstract class Piece {}
    private class King : Piece { }
    private class Imitator : Piece { }

    private void main(object sender, EventArgs e) {
        Piece x;
        x = CreateNewPiece(new King());
        x = CreateNewPiece(new Imitator());
    }

    private T CreateNewPiece<T>(T piece) where T : Piece, new() {
        return new T();
    }

Для создания экземпляра переменной типа используется общее ограничение new().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...