Речь пойдет о web-реализации популярной карточной игры "Мафия". Она писалась для развлечения и получения опыта в разработке игр. Первая версия была написана за две недели свободного от работы времени и за такое же время переписана до второй версии. Плюс такой игры – отсутствие ведущего.

Отталкиваясь от целей разработки, я принял решения о реализации/нереализации фич.
Что точно нужно было сделать:

  • Работающая по минимому игра, повторяющая правила классической игры
  • Озвучка команд ведущего на клиентских устройствах
  • Продолжение игры даже после перезагрузки вкладки браузера

Что делать не планировалось или можно было отложить:

  • Регистрация в игре
  • Интерфейс для администрирования
  • Постоянное хранение данных об игре в базе данных
  • Синхронизация времени между устройствами

Backend


https://github.com/mrsuh/mafia-backend
Написан на Go. Хранит в себе состояние игры и отвечает за её логику.

Во время игры можно обратиться к серверу, чтобы узнать полную информацию:

curl 'http://127.0.0.1:8000/info?game=23' | python -m json.tool

Вывод информации об игре
{
"event": "greet_mafia",
"event_status": 2,
"id": 23,
"is_over": false,
"iter": 1,
"players": [
{
"addr": "172.18.0.1:51438",
"createdAt": "2018-09-23T14:39:29.631475779Z",
"id": 33309,
"name": "Anton",
"role": 4
},
{
"addr": "172.18.0.1:51440",
"createdAt": "2018-09-23T14:39:32.867080927Z",
"id": 5457,
"name": "username:0",
"role": 2
},
{
"addr": "172.18.0.1:51442",
"createdAt": "2018-09-23T14:39:32.882463945Z",
"id": 14214,
"name": "username:2",
"role": 1
},
{
"addr": "172.18.0.1:51444",
"createdAt": "2018-09-23T14:39:32.895209072Z",
"id": 63759,
"name": "username:1",
"role": 3
}
],
"win": 0
}


Или узнать состояние сервера:

curl 'http://127.0.0.1:8000/health' | python -m json.tool

Вывод информации о состоянии сервера
{
"runtime.MemStats.Alloc": 764752,
"runtime.MemStats.NumGC": 0,
"runtime.MemStats.Sys": 4165632,
"runtime.MemStats.TotalAlloc": 764752,
"runtime.NumGoroutine": 14
}


Для определения активен ли еще игрок backend посылает heartbeat. Если игрок не ответил после определенного интервала, то он выбывает из игры. В то же время, если игрок переподключился до окончания интервала(пропала сеть), то он может продолжить игру.

Для стабильной работы backend был покрыт Unit тестами со стандартной библиотекой Go, где проверяются основные сценарии работы.


go test mafia-backend/src -cover
ok      mafia-backend/src       1.315s  coverage: 70.7% of statements

Frontend


https://github.com/mrsuh/mafia-frontend
Написан на чистом JS и собран с помощью Grunt.
Не несет в себе никакой логики.

При возникновении события с backend рендерит нужную страницу, отображает данные, которые ему прислали и проигрывает звук нового события.

Frontend хранит ID игры и игрока в LocalStorage или строке запроса браузера (если необходимо запустить в одном браузере несколько вкладок для разных игроков). Полное отсутствие логики, а также хранение основных параметров игры дают возможность даже после перезагрузки страницы восстановить состояние игры.

Браузер запрещает автовоспроизведение звуков без участия пользователя (например, нажатия на кнопку). Чтобы воспроизводить звуки на каждое событие, которе приходит с backend был сделан всего 1 JavaScript объект Audio. Каждый игрок должен нажать кнопку для начала игры и в этот момент объект Audio становится активным (доступным для воспроизведения), и впоследствии у него можно менять параметр src для воспроизведения разных звуков без участия пользователя.

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

http://127.0.0.1?master=1&test=1&sound=0&testUsersCount=5

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

Протокол взаимодействия


Протокол WebSocket был выбран по причине необходимости постоянного двухстороннего обмена данными между backend и frontend и его поддержки обоими языками.

События игры


Вся игра разделена на события:

События
  • game
    • create
    • join
    • start
    • over
    • reconnect

  • day
    • start

  • night
    • start

  • citizens-greeting
    • start
    • role
    • end

  • mafia-greeting
    • start
    • players
    • end

  • court
    • start
    • players
    • end

  • mafia
    • start
    • players
    • end

  • doctor
    • start
    • players
    • end

  • girl
    • start
    • players
    • end

  • sherif
    • start
    • players
    • end



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

Docker


Всю игру можно поднять с помощью Docker:
docker-compose.yml

version: '3'

services:
    mafia-frontend:
        image: mrsuh/mafia-frontend:latest
        container_name: mafia_frontend
        ports:
            - 9080:80

    mafia-backend:
        image: mrsuh/mafia-backend:latest
        container_name: mafia_backend
        ports:
            - 8000:8000

Достаточно установить Docker (если вы этого еще не сделали), скопировать к себе текст docker-compose.yml и выполнить команду:

docker-compose up

После этого можно открывать вкладку с игрой в браузере:

http://127.0.0.1:9080

Заключение


Вот тут можно посмотреть что итоге получилось (скорость воспроизведения увеличена в 1.5 раза).


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

P.S.: Спасибо Лере за озвучку игры.

Комментарии (9)


  1. ThisMan
    05.10.2018 14:03

    Я так понимаю, что отсутствие чата намекает на то, что играть нужно будет все равно локально? Просто с запущенным сервером. Иначе это просто "угадай и ткни"


    1. mrsuh Автор
      05.10.2018 14:10

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


  1. mrsuh Автор
    05.10.2018 14:09

    deleted


  1. lunside
    05.10.2018 15:58

    Зачем приложение для игры в мафию офлайн?


    1. mrsuh Автор
      05.10.2018 15:59
      +1

      Таким образом роль ведущего будет выполнять приложение, а не еще один игрок.


      1. lunside
        05.10.2018 16:39

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


        1. catanfa
          05.10.2018 23:14

          lunside что ключевого в роли ведущего? Он действует по инструкции, ничего не добавляя от себя и не влияя на результат, по крайней мере в тех правилах, с которыми мне приходилось играть.


          1. anthonio
            06.10.2018 10:18

            Хм. Если так рассуждать, то на концертах у ведущего тоже инструкция: выйти, объявить следующий номер и уйти. Однако...


            1. destroy
              06.10.2018 12:16

              А что, он делает что-то другое?