Любые шаблоны для моделирования настольных игр?

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

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

(Обратите внимание, что AI для игры в игру, а шаблоны вокруг высокой производительности мне не интересны.)

Пока мои шаблоны:

  • Несколько неизменяемых типов, представляющих объекты в игровом поле, например. кости, шашки, карты, доска, пробелы на доске, деньги и т.д.

  • Объект для каждого игрока, который содержит ресурсы игроков (например, деньги, оценка), их имя и т.д.

  • Объект, представляющий состояние игры: игроки, которые его поворачивают, это расположение пиков на доске и т.д.

  • Конечный автомат, управляющий чередой. Например, во многих играх есть небольшая пред-игра, где каждый игрок бросается посмотреть, кто идет первым; что начальное состояние. Когда начинается поворот игрока, сначала они катятся, затем двигаются, затем им приходится танцевать на месте, а затем другие игроки угадывают, какая у них курица, затем они получают очки.

Есть ли какой-то уровень техники, который я могу использовать?

EDIT: Недавно я понял, что состояние игры можно разделить на две категории:

  • Состояние артефакта игры. "У меня есть 10 долларов" или "моя левая рука на синем".

  • Состояние последовательности игр. "Я дважды удвоился, а следующий посадил меня в тюрьму". Здесь может иметь смысл конечный автомат.

EDIT:. Я действительно ищу здесь лучший способ реализовать многопользовательские пошаговые игры, такие как Chess или Scrabble или Monopoly. Я уверен, что смогу создать такую ​​игру, просто работая над ней, и начнет ее завершать, но, как и другие шаблоны проектирования, возможно, есть некоторые способы сделать вещи более плавными, которые не очевидны без тщательного изучения. Это то, на что я надеюсь.

Ответ 1

Кажется, это 2-месячный поток, который я только что заметил сейчас, но что за черт. Я разработал и разработал структуру игрового процесса для коммерческой, сетевой настольной игры. У нас был очень приятный опыт работы с ним.

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

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

Мы использовали Command Pattern, чтобы представить все действительные игровые действия, которые мог сделать игрок. Вот пример:

class RollDice : public Action
{
  public:
  RollDice(int player);

  virtual void Apply(GameState& gameState) const; // Apply the action to the gamestate, modifying the gamestate
  virtual bool IsLegal(const GameState& gameState) const; // Returns true if this is a legal action
};

Итак, вы видите, что для того, чтобы решить, является ли действие действительным, вы можете построить это действие, а затем вызвать его функцию IsLegal, передав текущее состояние игры. Если это действительно так, и игрок подтверждает действие, вы можете вызвать функцию Apply, чтобы фактически изменить состояние игры. Убедившись, что ваш код геймплея может только изменить состояние игры, создав и отправив законные действия (так, другими словами, семейство методов Action:: Apply - это единственное, что непосредственно изменяет состояние игры), то вы гарантируете, что ваша игра состояние никогда не будет недопустимым. Кроме того, с помощью шаблона команды вы можете сериализовать ваш плеер желаемыми ходами и отправлять их по сети, которая будет выполняться в других игровых состояниях.

В итоге появилась одна из них с этой системой, которая оказалась довольно элегантным решением. Иногда действия имели бы две или более фазы. Например, игрок может приземлиться на собственность в Монополии и должен теперь принять новое решение. Каково состояние игры между тем, когда игрок свернул кости, и прежде чем они решат приобрести недвижимость или нет? Мы управляли ситуациями, подобными этому, показывая участника Action Context нашего игрового состояния. Контекст действия обычно будет нулевым, что указывает на то, что игра в настоящее время не находится в каком-либо специальном состоянии. Когда игрок бросает кубики, и действие качки в кости применяется к состоянию игры, он поймет, что игрок приземлился на принадлежащее ему имущество и может создать новый контекст действия PlayerDecideToPurchaseProperty, который содержит индекс игрока мы ждем решения от. К моменту завершения действия RollDice наше состояние игры означает, что он в настоящее время ждет, пока указанный игрок не решит, покупать ли недвижимость. Теперь для всех других действий метод IsLegal легко вернуть false, за исключением действий "BuyProperty" и "PassPropertyPurchaseOpportunity", которые являются законными только в том случае, если в игровом состоянии есть контекст действия PlayerDecideToPurchaseProperty.

Благодаря использованию контекстов действий никогда не бывает ни одной точки в жизни игровой настольной игры, где структура государственной игры не полностью отражает ТОЧНО, что происходит в игре в этот момент времени. Это очень желательное свойство вашей настольной игровой системы. Вам будет намного проще писать код, когда вы сможете найти все, что захотите узнать о том, что происходит в игре, исследуя только одну структуру.

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

Надеюсь, это было кратким и полезным.

Ответ 2

Основная структура вашего игрового движка использует State Pattern. Элементы вашего игрового поля singletons различных классов. Структура каждого состояния может использовать шаблон стратегии или метод шаблона.

A Factory используется для создания игроков, которые вставляются в список игроков, еще один синглтон. GUI будет следить за Game Engine, используя шаблон наблюдателя и взаимодействовать с ним, используя один из нескольких объектов Command, созданных с помощью Command Pattern. Использование Observer и Command может использоваться в контексте Пассивный просмотр Но практически любой шаблон MVP/MVC можно использовать в зависимости от вашего предпочтения. Когда вы сохраняете игру, вам нужно захватить memento текущего состояния

Я рекомендую просмотреть некоторые шаблоны на этом сайте и посмотреть, хватит ли кто-нибудь из вас в качестве отправной точки. Снова сердце вашей игровой доски будет государственной машиной. Большинство игр будут представлены в двух штатах: перед игрой/настройкой и реальной игрой. Но вы можете иметь больше состояний, если игра, которую вы моделируете, имеет несколько различных режимов игры. Государства не обязательно должны быть последовательными, например, в Wararg Axis и Battles есть битва, которую игроки могут использовать для разрешения битв. Таким образом, есть три состояния перед игрой, основная доска, борт с игрой, постоянно переключающейся между основной доской и бортовой доской. Конечно, последовательность поворота также может быть представлена ​​конечным автоматом.

Ответ 3

Я только что закончил разработку и реализацию государственной игры с использованием полиморфизма.

Использование базового абстрактного класса с именем GamePhase, имеющего один важный метод

abstract public GamePhase turn();

Это означает, что каждый объект GamePhase содержит текущее состояние игры, а вызов turn() просматривает текущее состояние и возвращает следующий GamePhase.

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

Это позволяет turn() быть очень гибким. В зависимости от вашей игры данное состояние может переходить на разные фазы. Это формирует график всех фаз игры.

На самом высоком уровне код для его запуска очень прост:

GamePhase state = ...initial phase
while(true) {
    // read the state, do some ui work
    state = state.turn();
}

Это чрезвычайно полезно, поскольку теперь я могу легко создать любое состояние/фазу игры для тестирования

Теперь, чтобы ответить на вторую часть вашего вопроса, как это работает в мультиплеере? В пределах определенного GamePhase, требующего ввода пользователя, вызов из turn() будет запрашивать текущий Player их Strategy с учетом текущего состояния/фазы. Strategy - это просто интерфейс всех возможных решений, которые может сделать a Player. Эта настройка также позволяет реализовать Strategy с помощью AI!

Также Андрей Топ сказал:

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

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

Монополия

Пример некоторой GamePhase будет:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail, Go и т.д.)
  • PlayerTrades
  • PlayerPurchasesProperty
  • PlayerPurchasesHouses
  • PlayerPurchasesHotels
  • PlayerPaysRent
  • PlayerBankrupts
  • (карты всех шансов и сообщества)

И некоторые состояния в базе GamePhase:

  • Список игроков
  • Текущий игрок (который поворачивается)
  • Деньги/собственность игрока
  • Дома/Отели по свойствам
  • Позиция игрока

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

Много фаз могут быть повторно использованы и связаны друг с другом. Например, GamePhase CommunityChestAdvanceToGo создаст следующую фазу PlayerLandsOnGo с текущим состоянием и вернет ее. В конструкторе PlayerLandsOnGo текущий игрок будет перемещен в Go, и их деньги будут увеличены на 200 долларов.

Ответ 4

Конечно, есть много, много, много, много, много, много, много, ресурсов по этой теме. Но я думаю, что вы на правильном пути разделяете объекты и позволяете им обрабатывать свои собственные события/данные и т.д.

При создании настольных игр на плитке вам будет удобно иметь подпрограммы для сопоставления между массивом досок и строками/столбцами и обратно, а также другими функциями. Я помню свою первую настольную игру (давно давным-давно), когда я боролся с тем, как получить row/col from boardarray 5.

1  2  3  
4 (5) 6  BoardArray 5 = row 2, col 2
7  8  9  

Ностальжи.;)

В любом случае http://www.gamedev.net/ - это хорошее место для информации. http://www.gamedev.net/reference/

Ответ 5

Большая часть материалов, которые я могу найти в Интернете, - это списки опубликованных ссылок. Раздел публикаций Шаблоны дизайна игр содержит ссылки на PDF-версии статей и тезисов. Многие из них выглядят как академические документы, такие как Шаблоны дизайна для игр. Существует также, по крайней мере, одна книга, доступная от Amazon, Шаблоны в дизайне игр.

Ответ 6

Three Rings предлагает библиотеки LGPL'd Java. Ненья и Виля - это библиотеки для игр.

Конечно, это помогло бы, если бы в вашем вопросе упоминались ограничения на платформу и/или язык, которые вы могли бы иметь.

Ответ 7

Я согласен с Pyrolistical ответом, и я предпочитаю его способ делать вещи (я просто просмотрел другие ответы, хотя).

Кстати, я также использовал свое имя "GamePhase". В принципе, я бы сделал в случае пошаговой настольной игры, чтобы ваш класс GameState содержал объект абстрактного GamePhase, как упоминалось Pyrolistical.

Допустим, что состояния игры:

  • Рулон
  • Переместить
  • Купить/Не покупать
  • Тюрьма

У вас могут быть конкретные производные классы для каждого состояния. Имейте виртуальные функции, по крайней мере, для:

StartPhase();
EndPhase();
Action();

В функции StartPhase() вы можете установить все начальные значения для состояния, например, отключить вход другого игрока и т.д.

Когда вызывается roll.EndPhase(), убедитесь, что указатель GamePhase установлен в следующее состояние.

phase = new MovePhase();
phase.StartPhase();

В этом MovePhase:: StartPhase() вы, например, установите оставшиеся ходы активного игрока на количество, прошедшее на предыдущей фазе.

Теперь с этой конструкцией вы можете решить проблему "3 x double = jail" внутри фазы "Ролл". Класс RollPhase может обрабатывать свое собственное состояние. Например

GameState state; //Set in constructor.
Die die;         // Only relevant to the roll phase.
int doublesRemainingBeforeJail;
StartPhase()
{
    die = new Die();
    doublesRemainingBeforeJail = 3;
}

Action()
{
    if(doublesRemainingBeforeJail<=0)
    {
       state.phase = new JailPhase(); // JailPhase::StartPhase(){set moves to 0};            
       state.phase.StartPhase();
       return;
    }

    int die1 = die.Roll();
    int die2 = die.Roll();

    if(die1 == die2)
    {
       --doublesRemainingBeforeJail;
       state.activePlayer.AddMovesRemaining(die1 + die2);
       Action(); //Roll again.
    }

    state.activePlayer.AddMovesRemaining(die1 + die2);
    this.EndPhase(); // Continue to moving phase. Player has X moves remaining.
}

Я отличаюсь от Pyrolistical тем, что должна быть фаза для всего, в том числе, когда игрок приземляется на сундук Сообщества или что-то в этом роде. Я бы обработал все это в MovePhase. Это связано с тем, что, если у вас слишком много последовательных фаз, игрок, скорее всего, будет слишком "ориентироваться". Например, если есть фаза, где игрок может ТОЛЬКО покупать недвижимость, а затем ТОЛЬКО покупает отели, а затем ТОЛЬКО покупает дома, то, как будто нет свободы. Просто захлопните все эти части в один BuyPhase и дайте игроку свободу покупать все, что он хочет. Класс BuyPhase может легко справиться с тем, какие покупки являются законными.

Наконец, позвоните на игровое поле. Хотя 2D-массив в порядке, я бы рекомендовал иметь график плитки (где плитка - это позиция на доске). В случае монополии это скорее будет двусвязный список. Тогда каждая плитка будет иметь:

  • previousTile
  • nextTile

Так было бы намного проще сделать что-то вроде:

While(movesRemaining>0)
  AdvanceTo(currentTile.nextTile);

Функция AdvanceTo может обрабатывать ваши пошаговые анимации или все, что вам нравится. А также, конечно, уменьшение ходов.

Совет RS Conley по шаблону наблюдателя для графического интерфейса является хорошим.

Я не писал много раньше. Надеюсь, это поможет кому-то.

Ответ 8

Есть ли какой-то уровень техники, который я могу использовать?

Если ваш вопрос не зависит от языка или платформы. то я бы рекомендовал вам рассмотреть шаблоны АОП для состояния, памяти, команды и т.д.

Что ответ .NET для AOP???

Также попробуйте найти интересные веб-сайты, такие как http://www.chessbin.com