Skip to content
Пошаговая игра в танчики
C# Dockerfile Go Other
Branch: master
Clone or download

Latest commit

Fetching latest commit…
Cannot retrieve the latest commit at this time.

Files

Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
BotsExamples
DebugTools
FightServer
GameLogic
ImageService
StorageService
.dockerignore
.env
.gitattributes
.gitignore
LICENSE
README.md
TankFight.sln
docker-compose.build.yml
docker-compose.yml
map_example.png

README.md

TankFight

Элементы и механика игры

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

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

Map image

На карте расположены разрушаемые и неразрушаемые объекты, в том числе танки игроков.

Объекты бывают следующих типов:

  • Танк
  • Барьер
  • Неразрушаемый барьер
  • Вода

Каждый объект занимает одну ячейку, два объекта не могут находится на одинаковых координатах.

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

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

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

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

Игра

Игра происходит по ходам. Между ходами возможно получать состояние игры:

  1. Информацию о расположении и жизнях всех объектов.
  2. Информацию расположении и направлении движения пуль.

Ход игрока представляет из себя набор действий (на текущий момент набор имеет размер 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

Требования к образу Docker

В образе бота должен быть указан способ связи с автором для награждения. Способ указания авторства описан в регламенте проведения мероприятия.

Сборка собственного бота

Для примера произведем сборку бота со своим именем. Для примера сборку произведем над исходниками 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 или подобное)

Используемые перечисления

Direction

enum Direction
{
    Up = 0,
    Down = 1,
    Left = 2,
    Right = 3
}

UserActionType

enum UserActionType
{
	Move = 0,
	Shoot = 1
}

CellContentType

enum CellContentType
	{
		Tank = 0,
		Barrier = 1,
		NotDestroyable = 2,
		Water = 3,
		Spawn = 4
}

Примеры сообщений

Пример JSON состояния игры

{"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}]
You can’t perform that action at this time.