Подходы к созданию скриптового языка описания настольных игр

    Так уж случилось, что игры я писал лишь для себя, и профессионально этим никогда не занимался.
    А вот опыт писать DSL (Domain Specific language) для уменьшения рутины написания совершенно разного кода хоть какой-то есть.
    Именно этим и хочется поделится: как упорядочить необъятное.



    Наш хороший хабр-юзер GlukKazan пишет много статей о том какие есть замечательные продукты для создания различных досочных игр. Такие как Zillions of Games и Axiom Development Kit.
    Однако эти программы не универсальны. И всегда хочется улучшить их. Но данные продукты не свободны, поэтому приходится писать программный продукт заново.
    GlukKazan работает над открытым проектом Dagaz, о чём делится отличными статьями (например тут: Dagaz: Новое начало).

    Итак, предположим, мы хотим создать универсальный игровой движок для настольных игр, и его основой мы хотим видеть скриптовый язык, который помогает растолковать движку правила игры.
    Каким мы хотим его видеть?
    1) Язык должен быть универсальным, насколько можно, дабы описать почти любые правила игры.
    2) Тем не менее, язык должен быть как можно проще, минимум конструкций.
    3) Описание правил должны быть легки для чтения игроделу и для написания своих игр
    4) Для большинства случаев игры можно писать, дополняя/изменяя уже написанные
    5) Коммуникация (АПИ) со скриптом должна быть насколько простой, насколько это возможно. Так, что бы можно легко писать ботов и ИИ.
    На первый взгляд кажется, что потраченные усилия вообще никому не нужны будут, поскольку рутину не уменьшить, проще писать игры сразу готовыми.
    Но это не так.
    Всё куда проще!

    Демиурги и чёрная коробка


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

    Не ставьте ограничений там, где их нет


    Например, проект Dagaz (да и его предшественники Axiom и ZoG) делает акцент на досочных играх. Однако даже человеку объяснить чем досочная игра отличается от недосочной — достаточно сложно. Что уж говорить, описать это в точных определениях на каком-то языке программирования ещё сложнее.
    Поэтому первое и главное правило — не надо ставить ограничений там, где их нет!
    Мы будем рассматривать не досочные игры, а настольные.
    Давайте глянем на следующий список, которые мы хотим описать и играть при помощи нашего движка.
    • Шахматы
    • Сплют
    • Каркассон
    • Точки
    • Пятнашки
    • Домино
    • Перекрашивание кристаллов
    • Дурак

    Они ну очень разные на первый взгляд. Что же их всех объединяет? Неужели хоть что-то?
    Да. Объединяет их то, что
    1. В один момент времени всегда ходит 1 игрок
    2. Почти всегда игрок может думать бесконечно долго (отдельно рассмотрим, когда будут ограничения по времени на ход)
    3. Каждый раз существует ограниченное количество возможных действий. Игра Точки является исключением (ибо точку можно ставить где угодно на бесконечной доске). Для удобства мы несколько урежем Точки, а не изменим движок

    Всё, только эти ограничения на движок!
    Итого, по сути мы хотим иметь движок, который должен уметь спрашивать 2 основных вопроса: кто сейчас ходит, какие возможные действия он может сделать.
    IN:  who
    OUT: Player1
    
    IN:  actions
    OUT: [SomeAction1, SomeAction2 Param21, ...]
    

    И движок должен уметь совершать одно действие — совершить выбранное действие.
    action SomeAction3
    

    Ну, может ещё одно действие — выбрать предварительное действие (например, если закончится отведённое для хода время, а ход ещё не сделан, дабы не заканчивать игру или выбирать случайное действие, можно выбрать предварительное действие в таком случае).
    preaction SomeAction5
    

    Не надо ставить больше никаких ограничений, ведь их всё равно кому-то захочется обойти.

    Думайте шире


    А как же быть, если мы пишем Покер? Да и в Дурака можно подкинуть карту, а можно не подкинуть. Сказать Пас, или выбрать свою масть — это тоже действие. Выставить поле в Каркассоне — это тоже действие.
    А тетрис куда? Вот тетрис, наверное, будет делать очень сложно, ибо это реал-таймовая игра, и вряд ли надо. Доработать движок можно легко, но не актуально.

    Не кастрируйте скриптовый язык


    Не рекомендую следовать принципу — я получу все данные из скрипта и буду моделировать в движке. Ибо таким образом, проще использовать .ini файл как конфигурацию, а не городить скриптовый язык, так как толку будет столько же.
    А как взять карту для Дурака, когда отбился? Просто следующим будем снова ходить мы и будет лишь 1 вариант хода — взять карту. Если что можно добавить значения действия авто — то бишь не спрашивать у игрока какой вариант выбирать.
    IN:  who
    OUT: Player3
    # атакует 8й  бубна (хотя лучше было бы двумя 10ками - бубовой и чирвой)
    IN:  actions
    OUT: [Attack 8Diamonds, Attack 10Diamonds 10Hearts, ...]
    IN:  action Attack 8Diamonds
    OUT: action Attack 8Diamonds
    IN:  who
    OUT: Player4
    # забрать со стола, перевести или отбиться? Перевёл
    IN:  actions
    OUT: [TakeTable, PassShowOnly QueenDiamonds, Defend QueenDiamonds, Pass 6Diamonds, PassShowOnly 6Diamonds,...]
    IN:  action Pass 6Diamonds
    OUT: action Pass 6Diamonds
    IN:  who
    OUT: Player1
    # предлагает отбить королём или тузом, забрать стол или перевести
    IN:  actions
    OUT: [TakeTable, Defend KingDiamonds 6Diamonds, Defend KingDiamonds 8Diamonds, Defend AceDiamonds 6Diamonds,  Defend AceDiamonds 8Diamonds,  PassShowOnly KingDiamonds, Pass KingDiamonds, PassShowOnly AceDiamonds, Pass AceDiamonds]
    IN:  action Defend AceDiamonds 6Diamonds
    OUT: action Defend AceDiamonds 6Diamonds
    IN:  who
    OUT: Player3
    # предлагает подкинуть 3му игроку
    IN:  actions
    OUT: [Pass, Attack AceHearts]
    IN:  action Pass
    OUT: action Pass
    IN:  who
    OUT: Player2
    # предлагает спасовать, даже автоматически, нечего подкидывать
    IN:  actions
    OUT: [auto Pass]
    IN:  action Pass
    OUT: action Pass
    IN:  who
    OUT: Player4
    # пасуем
    IN:  actions
    OUT: [auto Pass]
    IN:  action Pass
    OUT: action Pass
    IN:  who
    OUT: Player1
    # предлагает отбить королём или забрать всё
    IN:  actions
    OUT: [Defend KingDiamonds, TakeTable]
    IN:  action Defend KingDiamonds
    OUT: action Defend KingDiamonds
    IN:  who
    OUT: Player3
    #берёт из колоды 1 карту, кто ходил и не хватает
    IN:  actions
    OUT: [auto Take AceHearts]
    IN:  action Take AceHearts
    OUT: action Take AceHearts
    IN:  who
    OUT: Player1
    #берёт из колоды 2 карты кто отбивался
    IN:  actions
    OUT: [auto Take 6Hearts 10Clubs]
    IN:  action Take 6Hearts 10Clubs
    OUT: action Take 6Hearts 10Clubs
    

    Пусть движок ничего не знает об игре. Он всего лишь конвейер для запросов и красивого отображения. Не более, но и не менее.

    Куда же без настроек


    Насколько бы простым не была коммуникация со скриптом, без настроек обойтись никак не удастся.
    В Дурака можно играть вдвоём, втроём, вчетвером, впятером и даже вшестером. Можно играть каждый сам за себя, 2х2, 3х3.
    Нам необходимо посылать настройки. Добавляем ещё одну команду. Ок, две, надо же и проверить состояние
    set players = 4
    
    set groups = 2x2
    
    set startFrom = Player3
    
    IN:  get name
    OUT: name = 15-puzzle
    


    Игра прежде всего


    Необходимо помнить, что мы создаём игры. А любые игры объединяет то, что
    • Игра может быть не начата
    • Игра может идти
    • Игра может быть завершена и иметь итоговый результат

    Создадим ещё несколько команд, которые отражают новые способности — загрузить игру, начать игру, узнать результат игры
    IN:  load /path/chess.game
    OUT: load /path/chess.game
    
    IN:  start
    OUT: start
    
    IN:  result
    OUT: Finished ; Win Player1; Details Player1 78, Player2 38
    OUT: Loaded /path/chess.game
    OUT: Started
    


    Отделить мух от котлет. Язык визуализации


    Не стоит гнаться за излишней универсальностью.
    Будем помнить, что ботам визуализация по барабану, а вот человек очень придирчив, и хочет видеть очень красивую картинку.
    Главный вывод из этого — языки визуализации и языки обмена сообщениями в общем случае различны. Гнаться за универсальностью скриптового языка не стоит, задачи ведь разные.
    Язык сообщений визуализации не надо подгонять под язык обмена.
    Пусть вы создали, что на каждый необходимый для визуализации объект есть формат визуализации (далее ФВ), который описывает какую картинку (или какие картинки) следует отобразить, в какой части экрана, что с чем наложится.
    Нам вначале необходимо отобразить всё. Значит, необходима команда визуализация ситуации, которая выдаёт список объектов в формате ФВ.
    Ведь мы должны увидеть шахматную доску с фигурами, или что нам раздали для Дурака.
    IN:  view
    OUT: [ (Piece1, ФВ), (King, ФВ), ..... ]
    

    Кстати, визуализировать зачастую надо то, что не фигурирует в качестве фигур в самой игре — например для Дурака надо отобразить количество закрытых карт у соперников, а так же колоду.
    Кроме того, необходимо знать как визуализировать возможные ходы.
    То есть должна быть команда визуализировать действия, который содержит список всех возможных действий, и к каждому из них в одном из двух вариантов новое описание: полное или частичное (с добавлением необходимых фигур отображения, и убирания фигур из имеющихся).
    Например, в Дурака необходимо убрать из колоды выбранную карту, зато эта карта должна появиться на столе.
    IN:  view actions
    OUT: [(Attack 8Diamonds, Diff [remove Card5, add (Card5, ФВ), ... ]), ... ]
    

    А в шахматах выбранная фигура должна стать «выбранного» цвета, и на месте выбранной клетки появится эта же фигура. В случае атаки, к тому же, должна исчезнуть битая фигура.
    Кроме того, необходимо знать, когда надо отображать то или иное возможное действие.
    Собственно, либо в предыдущую команду засунуть, либо новую команду добавить.
    IN:  view actions
    OUT: [(Attack 8Diamonds, Diff [remove Card5, add (Card5, ФВ), ... ],   OnChoose Card5), 
    (Pass, Diff [add PassWord],   OnChoose PassLabel), 
    ... ]
    

    Язык искусственного интеллекта

    Модули ИИ желательно писать отдельно, но интегрировать в общий скрипт.
    Разговор ИИ в принципе будет не сильно сложный.
    IN:  analize
    OUT: [75 Pass, 44 Attack 6Diamonds, 59 Attack 8Diamonds]
    IN:   analize quick
    OUT: [10 Pass, 5 Attack 6Diamonds, 20 Attack 8Diamonds]
    

    Реализовать подобное куда сложнее, чем описать. Вполне возможно понадобятся форки.

    Обоюдное понимание


    Необходимо, чтобы движок понимал бы, совершает ли он ошибки в разговоре со скриптом, или нет. Связь должна быть обоюдной, особенно когда взаимодействие представляет клиент-серверное приложение.
    По сути необходимо одна команда: последняя принятая команда.
    IN:  it
    OUT: result
    

    Этой же командой следует отвечать и тогда, когда ответ не важен. То есть установках, принятых действиях.
    А то кто-то поставил лимит времени на обдумывание, игрок проспит это время, походит, а движок уже походил случайно за игрока.
    Не забываем, что и движок может ответить ошибкой.
    IN: action TakeTable
    OUT: ERROR action is out list
    
    IN:  who
    OUT: ERROR game result is Finished
    


    Не боги горшки обжигают


    Что же, мы достаточно много проследили за тем, что должен уметь наш скрипт, а на что нам наплевать.
    Вся, не, ВСЯ логика ложится на скрипт. Так и только так мы добьёмся полной универсальности любых настольных игр.
    Любой компромисс тут ведёт к ограничениям, из-за которых не раз и не два придётся плясать с бубнами.
    Но мы же хотели лёгкий, простой язык! А нам предлагают всё-всё положить на игроделов.
    Если нельзя, но очень хочется, то можно! Сложно должно быть программисту, а не игроделу. И так будет.

    Будьте игроделами


    Будьте немного игроделом сами!
    Одно из самых удивительных свойств Си, который меня до сих пор поражает, это то, что тип string — библиотечная функция.
    Не надо ждать, пока игроделы придумают что такое доска, поле или колода карт. Напишите на скриптовом языке сами эти понятия.
    Обычные люди просто напишут, возмём доску, и создадим тут такие поля.
    Именно поэтому и необходимо, чтобы все написанные скрипты можно было поверх изменять.
    Захотелось для игры не статическую доску, а динамическую, пожалуйста, изменяем доску так, что её состояние будет проверятся каждый раз на ход.
    # DynamicBoard
    import StaticBord
    
    let board {
      constuctor {let board = prevous board}
      on_player_change {let board = recalculate}
    }
    


    Кесарю кесарево, а Богу Богово


    Даже не смотря на кучу написанных библиотек самим программистом, скриптовый код станет облегчённым, но не лёгким.
    Как убрать ненужные куски кода?
    Необходимо понять, что нам нужен подъязык, DSL этого скриптового языка, написанном на самом языке.
    Звучит страшно, однако не всё есть чёрт, что малюют.
    Для меня одной из самых красивых библиотек является парсер Parsec для Хаскеля. Это куда красивее реализации строк в Си.
    Строки хоть планировали включать в язык, а вот парсер писать не планировали, когда составляли Хаскель.
    По сути, там создали лишь средствами языка 2 уровня подъязыка для составления парсера (на самом деле там ещё есть не менее 4 дополнительных уровней).
    1) Уровень токенов/символов. На этот малопонятный большинству уровень почти никто не лезет писать свои функции, библиотечных функций хватает с головой.
    Например,
    конец ли строки — eof
    взять 1 токен/символ — anyToken
    Попробовать парсер, если упадёт, сделать вид, что он не потреблял токены — try p
    Или: попробовать парсер, если он упадёт без потребления токенов, применить второй парсер — p1 <|> p2
    Посмотреть вперёд: попробовать парсер, сделать вид, что он не употребил токенов, если парсер удачный — lookAhead p
    (в реальности там намного больше функций).
    Комбинацией этих токенистических функций позволяет порождать очень сложные парсеры.
    2) Уровень парсеров. Итак интуитивно понятный, дополненный целым зоопарком функций: такими как
    много парсеров — many p
    много, хотя бы 1 парсер — many1 p
    парсер разделённый — p `sepBy` sepp
    строчка — string
    между парсерами — between p1 p2


    Собственно, когда приходит юзер библиотеки, то видит, что «всё построено до нас», есть уже готовый конструктор, просто бери и собирай то, что тебе нужно и на том уровне, который тебя интересует.
    То же самое касается и нашего языка. Не должен игродел думать как писать функцию фильтра, она должна быть там уже написана.
    Просто бери и пользуйся, так должен говорить скриптовый язык своим юзерам!
    Если хочу шашки, но чтобы в ячейку помещалось 1 или 2 шашки — надо мне пересоздать лишь описание ячейки.
    Хочу квадратную доску — загружаю себе квадратную, хочу шестиугольную — загружу 6-угольную.
    Надо мне, чтобы в зависимости от снятия пешек с доски уничтожались поля самой доски — делаем доску динамической и следим за уничтожением пешек.

    Суп быстрого приготовления или нет предела совершенству


    Мы все любим вкусно поесть, но вот вкусно готовить могут не все. Это требует умений и времени.
    Игроделы такие любители супов.
    Им надо ещё облегчить долю.
    Необходим в нашем скиптовом языке ещё… мета-язык. Выдохните, просто шаблоны игр.
    Шахмато-подобные-игры, Шашки-подобные-игры, дурак-подобные-игры, карточные-игры,…
    Дайте возможность просто сконфигурировать готовые решения, а не заниматься программированием.
    import ChessLikeGame;
    
    let params = {
     previous params;
     players = 4;
     board_length = 75
     symmetric start positions = true;
     start_white_positions = [A1, C1,D1, ....]
    }
    


    Заключение


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

    Нет никакой разницы между шахматами, Балдой и дураком.
    Кто ходит? Игрок 1
    IN:  who
    OUT: Player1
    

    Нет разницы между точками и пятнашками кристалл-перекрашиванием. Есть ограниченный выбор действий каждый раз.
    # chess
    action Castle King Rook2
    
    # checkers
    action Attack Men7 D2 F4
    
    # splut
    action Pull Troll E5 D5
    
    # points
    action Add 10-14
    
    # carcassonn
    action Put Title1Tree Place8-17
    
    #15-puzzle
    action Push 8
    
    # dominos
    action Put Title1-6 Place4-1 Left
    
    # crystall painting
    action Color Yellow
    
    # durak
    action New QuenHearts
    

    Это просто надо понять, и тогда станет ясно, как это объяснить компьютеру.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 29

      +7
      Поймал себя на мысли, что не понимаю, нужно ли в реальной жизни такое обобщение. Скорее всего борьба с таким «движком» будет стоить больше сил, чем если бы ты начинал с чистого листа.
        +1
        До определённого момента это верно, но посмотрите на тот же ZoG. 2300 игр, не считая тех, что идут в поставке! Все писать с чистого листа? Можно конечно, но придётся раз за разом делать одно и то же с лёгкими (а иногда и не очень лёгкими) вариациями. Да, разработчик может использовать свои прежние наработки, но в том то и дело, что эти две тысячи с лишним игр писали не разработчики! Эти люди не имели доступа к исходному коду ZoG, пользуясь лишь его DSL. В общем, подход жизнеспособен, хоть и ориентирован, в большей степени на гиков. Такие проекты следует рассматривать не как коллекции игр, а как возможность разработки новых, людьми не имеющими доступа к исходникам. Нужно ли это? Не знаю. Я занимаюсь этим для себя.
          0
          а почему не сделать внутренний DSL?
            0
            Насколько я понимаю, это именно то, что предлагается в данной статье. Сам я, по ряду соображений, решил (пока) делать внешний DSL.
            По существующим продуктам: ZoG — внешний DSL, Axiom — можно сказать внутренний.
              0
              В данной статье — внешний

              embedded (or internal) domain-specific languages, implemented as libraries which exploit the syntax of their host general purpose language or a subset thereof, while adding domain-specific language elements (data types, routines, methods, macros etc.).
                0
                В личной переписке, Vitter предлагал мне использовать внутренний DSL на основе Хаскеля
          +1
          В этом и состоит мудрость — обеспечить баланс желаний и возможностей.
          Монстры игровых движков, такие как Unreal Engine 4 и Cocos2d — это всего лишь визуализаторы (+ физика объектов).
          Всю логику вы пишите сами.
          Единственное, эти движки предназначены для реал-таймовых игр, поэтому большое внимание уделяется событиям.
          И этими движками пользуются.
          0
          Несомненно, интересный проект с точки зрения программирования, но, по-моему, совершенно невостребованный в жизни. Не умеющие программировать люди не станут делать свою версию настольных игр, ибо их и так куча, а умеющие люди предпочтят написать игру сами, с блекджеком и лунным модулем. ИМХО, подойдёт разве что для тех кто придумывает свои настольные игры, и хочет сравнительно быстро их испытать.
            0
            я когда увидел Майнкрафт, подумал — «кому это интересно?»
            А люди там компьютер (размером с небоскрёб) создают средствами самой игры: из камней и лавы ))))
            Нотч уже миллионер.

            И вы помните, какую следующую игру хотел сделать Нотч?
            0x10c
            Там планировлось создать «компьютер» внутри игры, так, что основой игры стало бы программирование космических кораблей.
              0
              Да, прототипирование новых настольных игр — это, пожалуй, самое важное применение таких проектов. Иногда даже автоматизация разработки концепций новых игр. И да, эта деятельность никогда не будет массовой и вряд ли принесёт доход.

              Кстати, игры на ZRF писали даже дети, не знакомые с другими языками программирования.
                +1
                Очень востребованный. Ибо среди «игроделов» (который в резюме называют себя game designer'ами) достаточно много людей которые могут придумывать правила игры и гораздо меньше тех, кто готов эти правила воплощать в законченную разработку. Им нужен внятный интерфейс с разработчиками-программистами, которые будут превращать это всё в готовый продукт (при поддержке художников и иже с ними).
                Мне кажется что подобный скриптовый язык практически идеально годится на роль этой прослойки. Игроделы будут писать внутреннюю логику игры, а программисты — движок визуализации, клиент-серверный обмен и т.д.
                  0
                  Хочу напомнить, что речь идёт о довольно узком классе игр. Например для реализации арканоида или RTS такой движок вряд-ли подойдёт.
                  Хотя, надо отдать должное ZoG. На ней было реализовано, например вот это.
                    0
                    Видео

                      0
                      С одной стороны — довольно узкий. С другой — это могут быть всякие мини-игры, встроенные в большие проекты. Вспомните, например, игру на взлом компьютера из SystemShock2. Я не уверен полностью, т.к. наблюдаю GameDev со стороны, но мне кажется что удобный движок мог бы увеличить количество подоных мини-игр, которые, на мой взгляд, сущетсвенно добавляют антуража.
                  +2
                  Дам ряд своих комментариев к статье:

                  1. Полностью согласен с тем, что термины «досочные игры» или даже «настольные игры» неудачны. Речь скорее идёт о «дискретных» играх
                  2. Не всегда «в один момент времени ходит один игрок». Есть игры с «кооперативным» ходом (например «Камень-ножницы-бумага»), впрочем, пока, у меня и в мыслях нет как такое можно было бы реализовать в DSL, спасибо за наводку
                  3. То что, в этой статье, называется «движок» я называю генератором хода. Помимо него есть описание доски (также stateless) и игровое состояние (например текущее расположение фигур), всё это вместе — «движок» в моём понимании и уже поверх него навешиваются AI и визуализаторы
                  4. Динамическое изменение доски — это довольно сложное решение (поскольку хочется иметь stateless описание). В ZoG поступили проще: на «убитые» поля выставляются нейтральные фигуры, препятствующие движению игроков, но возможно полноценная динамика потребуется.
                  5. Parsec — просто замечательная иллюстрация DSL!
                  6. Идея шаблонов игр интересна, спасибо
                    +2
                    Стесняюсь спросить, слышали ли вы о GDL ( en.wikipedia.org/wiki/Game_Description_Language )?
                    Кроме того, ваши требования к языку довольно утопичны: <<чтобы можно было легко писать ботов и ИИ>>. Писать ботов для игры по её формальному описанию — целая наука (см., например, курс General Game Playing на Coursera). Или вы не это имели в виду?
                      0
                      GDL упоминался здесь, но я как то не догадался поискать на Wiki.
                      Спасибо за ссылку. По поводу утопичности — не обязательно писать ботов по описанию игры, достаточно, чтобы бот имел доступ к API генератора ходов и состоянию. Бот запрашивает список ходов и выбирает наилучший (для реализации минимакса придется хранить иерархию состояний).
                        0
                        Ну дык, сгенерировать список ходов по описанию обычно не сложно. Вот выбрать наилучший — это как раз и есть целая наука. Ведь, как правило, выбрать совсем наилучший вычислительно невозможно, тут и возникает проблема выбора стратегий, «хороших» ходов и так далее.
                          0
                          ZoG с этим справляется (правда не так чтобы очень хорошо). Axiom — только при наличии оценочной функции или engine на ForthScript-е (в противном случае, пытается выполнить полный поиск до цели). В общем, и та и другая работают как раз в этом ключе, но не идеально.
                      +2
                      Есть ещё графический язык для моделирования гемплея — www.jorisdormans.nl/machinations/
                      Посмотрите, может оттуда ещё вдохновение почерпнёте.
                        0
                        Очень грамотная декомпозиция задачи, не ожидал увидеть настолько действенный подход. Спасибо за интересную статью.
                          +3
                          Уже давно пишу игры (www.buho21.com). Разработал более-менее универсальную платформу с плагинной архитектурой, позволяющую интегрировать игры. Предложенная идея в основном правильная, хотя и не нуждается в отдельном скриптовом языке. При помощи обычного языка программирования (в моем случае Java), правильно организованного API и ООП можно достичь желаемого результата.

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

                          Проблема начинается, когда модель игры плохо состыковывается с моделью GUI. Например, в шахматах состояние доски логично хранить ввиде матрицы клеток, где фигуры — значения в клетках. GUI использует обратную модель: обычно список визуальных компонентов фигур, которые хранят внутри свои координаты на доске. Необходимо писать достаточно нетривиальный код, связывающий эти две модели. Хороший способ избежать этого — сразу использовать модель, схожую с GUI. Например для карточных игр универсальная модель — это хранить список из всех карт колоды, с указанием, где она находится в данный момент (в колоде, на руках у пользователя, в «бите», на столе в такой то позиции, etc). Для визуализации хода достаточно лишь вычислить координаты позиции для карты и анимировать соответствующий компонент.

                          Скелет для контроллера настольных игр достаточно прост в первом приближении. Лучший способ описания контроллера и всей логики игры — конечный автомат (finite state machine). Есть куча библиотек на этот счет, я разработал свою. Но потом оказывается, то GUI для визуального отображения ходов (обычно с анимацией) необходима расширенная информация от контроллера о последнем изменении состояния. Например в случае шахмат — это не просто действие пользователя (какая фигура куда сходила), а был ли ход шахом, рокировкой, съеданием. Поэтому частично логику GUI контроллер частично управляет логикой GUI посредством генерации событий после изменения состояния. В других играх ход игрока может генерировать целый каскад событий. Например в игре в покер ситуация, где игрок пошел all-in (одно единственное событие игрока), открываются карты обоих игроков, затем последовательно все карты флопа, затем производится сравнение комбинаций, затем распределяется выигрыш и после этого дилится новый кон.

                          В следующем приближении обнаружится, что для многих игр, особенно карточных, состояние игры не одинаково для всех игроков — каждый игрок должен видеть только свои карты и карты на столе. И требуется один особый «серверный» контроллер, которому доступно состояние всех карт (если держать полное состояние на клиенте — моментально сломают). Модель игры становится асимметричной, появляются исключительно «серверные» ходы: сдать карту, бросить кубик, etc. Логика должна уметь синхронизировать состояние серверой модели с состояниеми моделей игроков.

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

                          Вобщем, что я хотел сказать. Задача написания универсального движка для игр не сводится к созданию языка для описания игровой логики, а к созданию иерархии шаблонов общим функционалом. И написаны они могут быть написаны на любом языке. Для этого нужно проанализировать большое количество игр, разбить на классы (карточные, досочные, настольные). Для каждого из компонентов (GUI, контроллер и модель) выделить абстрактный функционал и написать расширяемые шаблоны. Тем не менее, для любой игры 80% времени всегда будет занимать создание GUI.
                            +1
                            >> Вобщем, что я хотел сказать. Задача написания универсального движка для игр не сводится к созданию языка для описания игровой логики, а к созданию иерархии шаблонов общим функционалом. И написаны они могут быть написаны на любом языке.

                            Это только до тех пор пока в вашей игре мало логики. В своё время, давным давно, передо мной такой вопрос встал, когда я кодил французскую карточную игру Таро, примерно похоже на преферанс играемый всегда «в закрытую», тобишь «стоя». Сами по себе правила уже весьма не короткие. А когда встал вопрос о примитивном AI который хотя бы мог соблюдать простейшие правила, «Под игрока с Симака», «со второго короля не ходят, если он не марьяж». Быстро выяснилось, что написание простейшего Пролог-подобного языка на котором все эти правила задаются в декларативной форме и потом кодинг уже на нём в разы быстрее, чем пытаться что-то придумать в рамках более традиционного программирования.
                              +1
                              Я написал несколько игр с испанской колодой. При правильном шаблонизировании описание новой игры достаточно простое, даже изменения в GUI минимальны. Также программировал покер, несколько вариантов шашек, и очень много занимался шахматами. На мой взгляд, лучший способ — это разделить логику правил и геймплей. Например, логика покера — это поиск комбинаций и операции с банком. Логику можно описывать на любом языке, хоть на хаскеле, хоть на джаваскрипте. Геймплей — это общий граф состояний игры, описывающий передачу хода между пользователями, завершение раунда, завершения игры, промежуточные состояния, etc, и обычно сильно связанный с визуализацией в GUI. Описывать его лучше через конечный автомат — в декларативной форме на порядок усложнится отладка.

                              AI-это отдельная тема. Любой AI состоит из функции генерации ходов и оценочной функции. Генерация ходов не представляет собой проблемы, если описан конечный автомат. А вот оценка позиции и хода представляет собой отдельную задачу, которая не имеет ничего общего с описательной частью игры. Она программируется совершенно отдельно, напр. минимаксным алгоритмом. Более того, во многих случаях для AI приходится генерировать отдельно упрощенный граф состояний (для которого не важны промежуточные состояния GUI), а иногда использовать и другую и модель данных.
                                0
                                Ну да, отладка требует написания своих необычных логеров, действительно было сложнее, чем в хорошем обычном коде, но проще, чем в лапшекоде, с которым часто приходится работать. Но зато типичные шаблонные ситуации, которые нужно распознать как при оценке позиции так и просто при проверке соблюдения правил, пишутся, и что важно, читаются сильно проще и быстрее. Надо будет тоже когда-нибудь статью про тот мой движок статью написать, когда время будет.

                                Не у всех, как у нас с вами, был случай повозиться с логическими играми. В наше время клепание интерфейсиков больше времени занимает.
                            0
                            Когда я играл в Дурака в детстве, то можно было и подкидывать одновременно и бить на выбор и т.п., т.е. утверждение «В один момент времени всегда ходит 1 игрок» для такого Дурака — не верно.
                            Конечно же, реализовать такую игру гораздо сложнее, особенно если добавить режим мультиплеера. Там уже задачи из разряда синхронизации сразу нескольких процессов, разделенные ресурсы и подобное.
                              0
                              При игре в «Дурака» весь этот сложный ход можно разбить на последовательность более элементарных ходов. Порядок передачи ходов будет довольно сложным, но ни одна пара элементарных ходов не будет выполняться одновременно (в отличии от игры «Камень-ножницы-бумага», в которой одновременность хода двух игроков принципиальна).
                                0
                                Данный конкретный пример я рассмотрел в статье.
                                По правилам — любой игрок может подкинуть карту.
                                Это можно эмулировать тем, что после каждого отбивания карты, все остальные игроки случайным образом спрашиваются — подкинуть или нет.
                                Качество эмуляции близко к 99%.
                                Но тут я согласен с тем, о чём я и говорил — любые ограничения захочется обойти. Чем их меньше, тем лучше.
                                  0
                                  Точно, если спрашивать игроков случайным образом, будет близко к живому оригиналу.
                                  Вместо «кто первый, тот и успел» будет выбор игрока случайным образом, по-прежнему сохранится элемент такой себе хаотичности и все также можно позволить подкидывать больше положенного, чтобы потом бить на выбор.

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

                                  Я попробовал практически все варианты Дурака из App Store, везде на данный момент реализован простой подход, когда игроки подкидывают строго по часовой стрелке.

                              Only users with full accounts can post comments. Log in, please.