Есть шаблоны для лепки настольных игр?

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

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

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

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

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

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

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

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

Могу ли я воспользоваться каким-либо предшествующим уровнем техники?

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

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

  • Состояние игровой последовательности. «У меня дважды выпадал дубль; следующий бросил меня в тюрьму». Конечный автомат может иметь здесь смысл.

Обновлено: На самом деле я ищу здесь способ Лучший для реализации многопользовательских пошаговых игр, таких как Chess, Scrabble или Monopoly. Я уверен, что смогу создать такую ​​игру, просто проработав ее от начала до конца, но, как и в случае с другими шаблонами проектирования, вероятно, есть некоторые способы сделать все более гладкими, что неочевидно без тщательного изучения. Я на это надеюсь.

Вы строите что-то вроде Хоки-Поки, Монополии, шарад?

Anthony Mastrean 12.12.2008 00:29

Вам понадобится конечный автомат для любого правила, которое полагается на состояние (эээ ...), как правило трех двойников для монополии. Я бы отправил более полный ответ, но у меня нет опыта в этом. Хотя я мог бы понтификатировать об этом.

MSN 19.02.2009 20:51
Повышение качества Laravel с помощью принципов SOLID: Лучшие практики и примеры
Повышение качества Laravel с помощью принципов SOLID: Лучшие практики и примеры
Когда мы говорим о том, как сделать следующий шаг в качестве разработчика, мы должны понимать, что качество кода всегда является основным фокусом на...
Принципы SOLID - лучшие практики
Принципы SOLID - лучшие практики
SOLID - это аббревиатура, обозначающая пять ключевых принципов проектирования: принцип единой ответственности, принцип "открыто-закрыто", принцип...
94
2
21 634
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Три кольца предлагает библиотеки Java для LGPL. Неня и Виля - библиотеки для игровых материалов.

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

«В конце концов я собираюсь создать пользовательский интерфейс WPF» - это означает .NET. По крайней мере, насколько я могу судить.

Mark Allen 12.12.2008 00:53

Алфавитный суп, о котором я не знаю.

jmucchiello 12.12.2008 01:04

Да, я занимаюсь .NET, но мой вопрос не зависит от языка или платформы.

Jay Bazuzi 12.12.2008 01:29

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

При создании настольных игр на основе плиток вам будет приятно иметь процедуры для сопоставления между массивом досок и строкой / столбцом и обратно, а также другими функциями. Я помню свою первую настольную игру (очень давно), когда я боролся с тем, как получить строку / столбец из 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/

Почему бы вам просто не использовать двумерный массив? Тогда компилятор сделает это за вас.

Jay Bazuzi 13.12.2008 20:19

Я извиняюсь за то, что это было очень давно. ;)

Stefan 14.12.2008 13:06

У gamedev есть масса вещей, но я не совсем понял, что искал.

Jay Bazuzi 17.02.2009 09:41

какой язык вы использовали?

zotherstupidguy 23.10.2009 13:26

Basic, Basica, QB, QuickBasic и так далее. ;)

Stefan 23.10.2009 16:55

Базовая структура вашего игрового движка использует Государственный образец. Предметы вашей игровой коробки синглтоны различных классов. Структура каждого состояния может использовать Шаблон стратегии или шаблонный метод.

Фабрика используется для создания игроков, которые вставляются в список игроков, другой синглтон. Графический интерфейс пользователя будет следить за игровым движком, используя Образец наблюдателя, и взаимодействовать с ним, используя один из нескольких объектов Command, созданных с помощью Командный шаблон. Использование Observer и Command можно использовать в контексте Пассивный вид. Но в зависимости от ваших предпочтений можно использовать практически любой шаблон MVP / MVC. При сохранении игры вам нужно получить сувенир текущего состояния.

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

Ответ принят как подходящий

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

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

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

Мы использовали Командный шаблон для представления всех допустимых игровых действий, которые может совершить игрок. Вот пример действия:

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, передав текущее состояние игры. Если он действителен и игрок подтверждает действие, вы можете вызвать функцию «Применить», чтобы фактически изменить состояние игры. Убедившись, что ваш игровой код может изменять состояние игры только путем создания и отправки законных действий (другими словами, семейство методов Action :: Apply - единственное, что напрямую изменяет состояние игры), вы гарантируете, что ваша игра состояние никогда не будет недействительным. Кроме того, используя шаблон команды, вы даете возможность сериализовать желаемые ходы вашего игрока и отправлять их по сети для выполнения в игровых состояниях других игроков.

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

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

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

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

Я не думаю, что это кратко, но это полезно! Проголосовали.

Jay Bazuzi 18.02.2009 08:41

Рад, что это было полезно ... Какие части не были краткими? Я был бы рад внести уточняющую правку.

Andrew Top 18.02.2009 20:22

Прямо сейчас я создаю пошаговую игру, и этот пост был действительно полезен!

Kiv 23.09.2009 01:40

Я читал, что Memento - это шаблон для отмены ... Memento vs Command Pattern для отмены, ваши мысли, пожалуйста ..

zotherstupidguy 23.10.2009 12:50

Это лучший ответ, который я читал в Stackoverflow до сих пор. БЛАГОДАРНОСТЬ!

Papipo 28.07.2010 01:44

Я просто хотел бы отметить, что решение, к которому мы пришли и которое здесь рекомендовали, на самом деле является конечным автоматом (хотя и немного вывернутым наизнанку). ActionContextявляется - текущее состояние (например, было бы легко создать DefaultActionContext, а не null). Имея в виду вышеизложенное, я бы не рекомендовал включать какую-либо функциональность типа IsLegal, поскольку это естественным образом порождает сильно анемичную модель, в которой все поведение отделено от данных, на которые оно действует. Вместо этого достаточно просто включить метод rollDice в текущее состояние.

user3347715 31.03.2018 23:11

Многие материалы, которые я могу найти в Интернете, представляют собой списки опубликованных ссылок. В разделе публикаций Шаблоны игрового дизайна есть ссылки на PDF-версии статей и тезисов. Многие из них выглядят как научные статьи, например Шаблоны дизайна для игр. Также есть по крайней мере одна книга, доступная на Amazon, Паттерны в игровом дизайне.

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

Использование базового абстрактного класса 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 - это просто интерфейс всех возможных решений, которые может принять Player. Эта установка также позволяет реализовать Strategy с AI!

Также Эндрю Топ сказал:

Your game can probably be in a (close to) infinite amount of states because of the permutations of things like how much money player A has, how much money player B has, and etc... Therefore, I'm pretty sure you want to stay away from state machines.

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

Монополия

Примером некоторых GamePhase может быть:

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty (FreeParking, GoToJail, Go и т. д.)
  • PlayerTrades
  • PlayerPurchasesProperty
  • ИгрокПокупкиДома
  • ИгрокПокупкиОтели
  • PlayerPaysRent
  • PlayerBankrupts
  • (Все карты случайных и общественных сундуков)

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

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

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

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

Is there some prior art I can take advantage of?

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

Что .NET ответит на АОП ???

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

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

По совпадению я также использовал его название «GamePhase». В основном то, что я сделал бы в случае пошаговой настольной игры, - это чтобы ваш класс GameState содержал объект абстрактного GamePhase, как упоминалось Pyrolistical.

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

  1. Рулон
  2. Двигаться
  3. Купить / Не покупать
  4. Тюрьма

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

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

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

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

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

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

Теперь, имея такой дизайн, вы можете решить проблему «3 x double = jail» внутри фазы Roll. Класс 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-массив - это нормально, я бы рекомендовал иметь тайловый график (где тайл - это позиция на доске). В случае монополии это был бы двусвязный список. Тогда каждая плитка будет иметь:

  1. предыдущийПлитка
  2. nextTile

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

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

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

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

Раньше я мало что писал. Надеюсь, это кому-то поможет.

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