Цель игры - выжить, управляя танком. Победителем считается последний умерший танк.
Вся игровая деятельность происходит на одной из нескольких карт. Карта поделена на координаты, отсчет координат идет с левого нижнего угла карты, начинается с 0. Карта всегда по периметру ограничена неразрушаемыми барьерами.
На карте расположены разрушаемые и неразрушаемые объекты, в том числе танки игроков.
Объекты бывают следующих типов:
- Танк
- Барьер
- Неразрушаемый барьер
- Вода
Каждый объект занимает одну ячейку, два объекта не могут находится на одинаковых координатах.
Игроки могут передвигаться и стрелять. В результате стрельбы на карте создается пуля, которая движется с заданной скоростью по направлению стрельбы. Пуля появляется на соседней клетке от игрока по направлению выстрела. У одного игрока не может одновременно быть более одной пули.
При попадании пули в объект, пуля исчезает, а у разрушаемого объекта отнимается жизнь. Неразрушаемые объекты игнорируют пулю.
Разрушаемые объекты имеют счетчик жизней и считаются живыми, пока их счетчик жизней превышает значение 0. После того, как их счетчик жизни опускается до 0, объекты удаляются с карты.
Так же танки получают урон от зоны. Зона представляет область, ограниченную радиусом зоны с центром в центре карты. Все танки, находящиеся за пределами радиуса зоны получают урон размером в одну жизнь за ход. Радиус зоны уменьшается на один каждый ход.
Игра происходит по ходам. Между ходами возможно получать состояние игры:
- Информацию о расположении и жизнях всех объектов.
- Информацию расположении и направлении движения пуль.
Ход игрока представляет из себя набор действий (на текущий момент набор имеет размер 1). Каждое действие — это либо выстрел, либо движение. У обоих типов действий требуется передавать направление действия: вверх, вниз, влево либо вправо.
Действия всех игроков применяются в случайном порядке в рамках хода.
Обработка событий происходит в цикле из действий игроков и обработки пуль: проверяются попадания, затем производится передвижение пуль, затем опять проверяются попадания. На каждое действие пользователей пуля движется на определенное количество клеток, устанавливаемое в настройках (2 на текущий момент).
В случае некорректного действия игрока (передвижение на занятую клетку, попытка создать вторую пулю) это действие игнорируется.
Для работы игры необходим установленный docker и docker-compose.
Игра распространяется в виде docker-compose файла, содержащего сервисы, необходимые для игры и пример пары ботов.
Для запуска необходимо любым способом скачать файл docker-compose.yml в консоли перейти в папку, содержащую этот файл и ввести команду docker-compose up
. Команда произведёт автоматическое скачивание необходимых для игры образов и запустит их с настроенными параметрами.
Для пользователей Windows
Существует баг на текущей стабильной версии docker for win, в связи с чем, им так же надо скачать файл .env и разместить его в одной папке с docker-compose.yml
.
После старта всех необходимых сервисов, для запуска игры необходимо открыть страничку http://localhost:5006/. На этой странице располагается окно игры. Для запуска игры необходимо ввести имена образов ботов (образы, введение по умолчанию уже скачаны при запуске команды docker-compose up
) и нажать кнопку Start. Битва запустится.
Для остановки сервисов игры можно закрыть консоль.
Во время проведения боя действуют следующие горячие клавиши:
a
- включить/отключить автоматическое проигрывание
space
- отключить автоматическое проигрывание и перейти на следующий ход
n
- переключить состояние игры на ход вперед
b
- переключить состояние игры на ход назад
lshift+n
- переключить состояние игры на 10 ходов вперед
lshift+b
- переключить состояние игры на 10 ходов назад
Возможны следующие настройки локального стенда:
- Не удалять контейнеры ботов после завершения игры. По умолчанию контейнеры ботов автоматически удаляются после завершения игры (не связано с отображением игры в браузере). Для исследования логов/файлов контейнера можно изменить переменную окружения
ContainerSettings__DontRemoveContainers
сервисаfight
в значениеtrue
и контейнеры не будут автоматически удаляться - Время на прогрев. При проведении турнира будет время разогрева контейнеров будет установлено для всех одинаковое, указано ниже в документации. Но для бота может не быть смысла так долго ждать перед каждым запуском, для этого можно задать время на разогрев стенда с помощью переменной окружения
BattleSettings__ContainersWarmSeconds
сервисаfight
.
Бот представляет из себя docker-образ, содержащий программу, взаимодействую с игрой посредством консоли (stdin, stdout). В stdin пишется текущее состояние игры, и ожидается в ответ набор действий пользователя в течении ограниченного времени. Команды и состояние игры разделяются символами новой строки (\n
).
В случае нарушения формата ответа, превышения допустимого времени ответа, отсутствия ответа, выполнение приложения завершается, танк игрока перестает выполнять какие-либо действия.
Перед начало игры всем ботам дается некоторое время на "прогрев" - бот запускается, но ему не поступают команды.
Взаимодействие происходит с помощью объектов, сериализованных в формате JSON.
В stdin пишется состояние игры, сериализованный объект типа:
{
"ContentsInfo": [CellContentInfo,CellContentInfo],
"BulletsInfo": [BulletInfo, BulletInfo],
"ZoneRadius": 119
}
Он содержит информацию обо всех объектах на карте, пулях и радиусе зоны.
Поле ZoneRadius
указывает текущий размер зоны.
Поле BulletsInfo
содержит информацию обо всех пулях на карте. Информация представлена в формате
{
"Coordinates":{"X":17,"Y":3},
"Direction":2,
"OwnerId":"vblz/RandomBot"
}
где
Coordinates
- координаты пули на текущий ход,
Direction
- направление движения пули,
OwnerId
- идентификатор владельца пули.
Поле ContentsInfo
содержит информацию о объектах на карте в формате
{
"Coordinates":{"X":0,"Y":0},
"HealthCount":255,
"Type":2,
"UserId":"r2"
}
где
Coordinates
- координаты объекта на текущий ход,
HealthCount
- количество жизней объекта
Type
- тип объекта,
UserId
- идентификатор танка, присутствует только у танков, у остальных объектов это поле следует игнорировать. Так же это поле может отсутствовать в JSON.
Для совершения хода игрок отправляет ASCII строку с массивом своих действий. На текущий момент ход игрока состоит из одного действия фомата
[UserAction]
где
UserAction
действие игрока.
Формат UserAction
действия игрока
{
"Type": 0,
"Direction": 0
}
где
Type
- тип хода игрока,
Direction
- направление хода игрока.
Время прогрева (от старта контейнера до первого хода): 20 секунд
Память, доступная контейнеру: 256mb
Время на ответ ходом игрока: 100 ms
В образе бота должен быть указан способ связи с автором для награждения. Способ указания авторства описан в регламенте проведения мероприятия.
Для примера произведем сборку бота со своим именем. Для примера сборку произведем над исходниками RandomBot.
Для этого необходимо скачать любым способом папку с ботом и сделать в ней необходимые изменения для игры (например, реализовать игровую логику). После того, как появилась уверенность, что c# проект собирается, можно приступить к сборке docker образа.
Для сборки образа docker необходимо открыть консоль и перейти в ней в папку с ботом.
Для примера, дадим боту тег my_name/mega_destructor
. Для этого выполним команду
docker build -t my_name/mega_destructor .
которая запустит сборку образа в контексте текущей папке, используя Dockerfile, располагающийся в текущей папке. После успешной сборки, можно запустить битву (перезапускать сервисы нет необходимости, достаточно обновить страницу с игрой), ввести имя только что собранного контейнера и следить за ходом боя.
Для отладки предлагается использовать стандартный поток ошибок приложения (stderr). С stderr можно работать по аналогии со stdout - писать произвольный текст. Данные с потока stderr будут выводится в консоль браузера при проигрывании игры в формате Bot {тег образа бота}: {содержимое stderr}
.
Следует отметить следующие ограничения:
stderr
собирается только между отправкой состояния вstdin
и получением символа перевода строки вstdout
, или другими словами, надо в начале писать отладочную информацию, затем ответ- чтение из
stderr
занимает время из ограничения на время получения ответа - docker не гарантирует очередность вывода
stdout
иstderr
, в связи с чем предлагается для обхода этого ограничения вставлять между выводом вstderr
иstdout
небольшую задержку (Thred.Sleep
или подобное)
enum Direction
{
Up = 0,
Down = 1,
Left = 2,
Right = 3
}
enum UserActionType
{
Move = 0,
Shoot = 1
}
enum CellContentType
{
Tank = 0,
Barrier = 1,
NotDestroyable = 2,
Water = 3,
Spawn = 4
}
{"ContentsInfo":[{"Coordinates":{"X":0,"Y":0},"HealthCount":255,"Type":2},{"Coordinates":{"X":1,"Y":0},"HealthCount":255,"Type":2},{"Coordinates":{"X":2,"Y":0},"HealthCount":1,"Type":1},{"Coordinates":{"X":11,"Y":2},"HealthCount":255,"Type":2},{"Coordinates":{"X":16,"Y":2},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":2},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":3},"HealthCount":255,"Type":2},{"Coordinates":{"X":7,"Y":3},"HealthCount":2,"Type":1},{"Coordinates":{"X":11,"Y":3},"HealthCount":255,"Type":2},{"Coordinates":{"X":20,"Y":3},"HealthCount":3,"Type":0,"UserId":"r1"},{"Coordinates":{"X":21,"Y":3},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":4},"HealthCount":255,"Type":2},{"Coordinates":{"X":1,"Y":4},"HealthCount":255,"Type":2},{"Coordinates":{"X":5,"Y":4},"HealthCount":1,"Type":1},{"Coordinates":{"X":10,"Y":4},"HealthCount":255,"Type":2},{"Coordinates":{"X":11,"Y":4},"HealthCount":255,"Type":2},{"Coordinates":{"X":14,"Y":4},"HealthCount":1,"Type":1},{"Coordinates":{"X":21,"Y":4},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":5},"HealthCount":255,"Type":2},{"Coordinates":{"X":1,"Y":5},"HealthCount":255,"Type":2},{"Coordinates":{"X":2,"Y":5},"HealthCount":255,"Type":2},{"Coordinates":{"X":11,"Y":5},"HealthCount":255,"Type":2},{"Coordinates":{"X":20,"Y":5},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":5},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":6},"HealthCount":255,"Type":2},{"Coordinates":{"X":1,"Y":6},"HealthCount":255,"Type":2},{"Coordinates":{"X":2,"Y":6},"HealthCount":255,"Type":2},{"Coordinates":{"X":7,"Y":6},"HealthCount":255,"Type":3},{"Coordinates":{"X":8,"Y":6},"HealthCount":255,"Type":3},{"Coordinates":{"X":9,"Y":6},"HealthCount":255,"Type":3},{"Coordinates":{"X":19,"Y":6},"HealthCount":255,"Type":2},{"Coordinates":{"X":20,"Y":6},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":6},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":7},"HealthCount":255,"Type":2},{"Coordinates":{"X":1,"Y":7},"HealthCount":255,"Type":2},{"Coordinates":{"X":8,"Y":7},"HealthCount":255,"Type":3},{"Coordinates":{"X":9,"Y":7},"HealthCount":255,"Type":3},{"Coordinates":{"X":16,"Y":7},"HealthCount":1,"Type":1},{"Coordinates":{"X":19,"Y":7},"HealthCount":255,"Type":2},{"Coordinates":{"X":20,"Y":7},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":7},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":8},"HealthCount":255,"Type":2},{"Coordinates":{"X":3,"Y":8},"HealthCount":1,"Type":1},{"Coordinates":{"X":9,"Y":8},"HealthCount":255,"Type":3},{"Coordinates":{"X":17,"Y":8},"HealthCount":2,"Type":1},{"Coordinates":{"X":20,"Y":8},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":8},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":9},"HealthCount":255,"Type":2},{"Coordinates":{"X":13,"Y":9},"HealthCount":255,"Type":2},{"Coordinates":{"X":14,"Y":9},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":9},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":10},"HealthCount":255,"Type":2},{"Coordinates":{"X":5,"Y":10},"HealthCount":255,"Type":2},{"Coordinates":{"X":7,"Y":10},"HealthCount":2,"Type":1},{"Coordinates":{"X":13,"Y":10},"HealthCount":255,"Type":2},{"Coordinates":{"X":14,"Y":10},"HealthCount":255,"Type":2},{"Coordinates":{"X":15,"Y":10},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":10},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":11},"HealthCount":255,"Type":2},{"Coordinates":{"X":5,"Y":11},"HealthCount":255,"Type":2},{"Coordinates":{"X":6,"Y":11},"HealthCount":255,"Type":2},{"Coordinates":{"X":15,"Y":11},"HealthCount":255,"Type":2},{"Coordinates":{"X":16,"Y":11},"HealthCount":255,"Type":2},{"Coordinates":{"X":18,"Y":11},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":11},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":1,"Y":12},"HealthCount":3,"Type":0,"UserId":"r2"},{"Coordinates":{"X":4,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":5,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":6,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":7,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":16,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":17,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":18,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":19,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":20,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":12},"HealthCount":255,"Type":2},{"Coordinates":{"X":0,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":1,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":2,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":3,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":4,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":5,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":6,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":7,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":8,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":9,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":10,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":11,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":12,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":13,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":14,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":15,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":16,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":17,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":18,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":19,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":20,"Y":13},"HealthCount":255,"Type":2},{"Coordinates":{"X":21,"Y":13},"HealthCount":255,"Type":2}],"BulletsInfo":[{"Coordinates":{"X":17,"Y":3},"Direction":2,"OwnerId":"r1"}],"ZoneRadius":94}
Примеры JSON хода игрока
[{"Type":1,"Direction":3}]
[{"Type":0,"Direction":0}]