Robo code game challenge
Code Game Challenge — захватывающий формат соревнований, который вызывает у студентов большой интерес. В рамках IV Всероссийской молодёжной школы по робототехнике, встраиваемым системам и компьютерному зрению (http://roboschool.org), которая прошла в ноябре в Волгограде, мы хотели преподнести участникам что-то новое. Так родилась идея RoboCGC — совместить роботов и CGC. Подробности под катом.
Итак, для начала, кому интересно, что из себя представляет классический CGC, но он недостаточно осведомлен: почитать можно здесь и здесь.
Что же такое RoboCGC? Это классический Code Game Challenge, в котором стратегии участников запускаются не только в симуляторе, но и на реальных роботах. В остальном же он ничем не отличается — участники точно так же реализуют программу для управления роботом, и производят ее запуск в симуляторе для оценки качества работы. В симуляторе им доступны базовые стратегии, которые предоставили организаторы соревнований, и стратегии других участников.
На этот раз в игровом мире перед участниками стояла задача управления роботом, оснащённым лазером. Лазер зафиксирован и сонаправлен с роботом. Залп (снаряд) лазера обладает бесконечной скоростью. Само орудие имеет три состояния:
- перезарядка — от момента выстрела до момента, когда орудие можно снова заряжать, должно пройти время перезарядки;
- простой — после того как орудие перезарядилось, но не начало накапливать заряд, оно простаивает;
- накопление заряда — по команде пользователя орудие из режима простоя переходит в режим накопления заряда.
Как только заряд орудия достигнет максимального значения, выстрел произойдет автоматически. Кроме того, пользователь может в любой момент от начала заряда и до автоматического выстрела произвести выстрел принудительно. Урон, наносимый при попадании, линейно зависит от накопленного заряда.
На карте присутствуют два робота (один на каждую команду) и несколько препятствий. Препятствия подвижны — робот может их толкать.
Теперь перейдем к самой системе.
Система состоит из нескольких компонент:
Рассмотрим каждый из модулей отдельно
Пользовательская стратегия
Интерфейс класса пользователя очень прост и состоит всего из одной функции, которая вызывается на каждом такте игрового мира. В функцию передается информация об игровом мире, включающая в себя информацию о положении юнитов и их состоянии, информацию о положении препятствий, а также информацию об игровом мире (игровое время, размеры поля и т.п.).
#pragma once
#include "Common/IWorld.h"
using namespace GameWorld::Strategy;
namespace Client
{
namespace Strategy
{
class Player
{
public:
void Move(IWorld* world);
};
}
}
Пользователь может работать в рамках одного файла, т.к. на сервер отправляется один файл для компиляции. На сервере этот файл компилируется вместе с оберткой, которая обеспечивает взаимодействие с симулятором.
Ранее мы использовали модель DLL — пользовательские стратегии компилировались в отдельные DLL и загружались в симулятор, а далее симулятор вызывал методы этой DLL. На этот раз мы перешли к модели отдельных процессов — в этом случае мы получаем несколько преимуществ: более свободный контроль времени выполнения и потребления памяти (в случае нарушения ограничений мы просто завершаем процесс со стратегией, не опасаясь, что в симуляторе может испортиться куча или останутся неосвобожденные ресурсы). Взаимодействие между клиентским кодом и модулем игрового мира осуществляется через сокеты.
Последовательность работы пользовательского кода следующая:
Для передачи сообщений мы использовали выравненные структуры — самый просто способ без дополнительной сериализации/десериализации.
Заголовок пакета с информацией об игровом мире выглядит так:
struct StateHeader
{
int TotalUnits; // количество юнитов
int TotalObstacles; // количество препятствий
int Tick; // игровое время
double Width; // ширина игрового мира
double Height; // высота игрового мира
int CurrentTeam; // текущая команда
int TotalTeams; // количество команд
int IsFinished; // флаг завершения
};
Информация об одном юните задаётся структурой:
struct UnitDTO
{
double X; // положение по оси Х
double Y; // положение по оси Y
double Angle; // угол поворота
double Radius; // радиус
int Charge; // состояние орудия
int HP; // запас здоровья
int TeamId; // идентификатор команды
int Id; // идентификатор юнита
};
Информация о препятствии:
struct ObstacleDTO
{
double X; // положение по оси Х
double Y; // положение по оси Y
double Angle; // угол поворота
double Radius; // радиус
int Id; // идентификатор препятствия
};
Заголовок пакета с информацией о ходе игрока:
struct ControlHeader
{
int TotalUnits; // количество юнитов, которые совершали действия
int IsCrashed; // флаг «вылетевшей» стратегии
char Name[31]; // имя команды
};
Информация о действиях юнита:
struct UnitControlDTO
{
double LeftControl; // управляющее воздействие для левого двигателя
double RightControl; // управляющее воздействие для правого двигателя
bool IsStartCharging;// флаг начала заряда орудия
bool Fire; // флаг выстрела
int UnitId; // идентификатор юнита
};
Стартовый модуль
Данный модуль отвечает за чтение конфигурации, создание соответствующего окружения (симулятор или аппаратная платформа), старт пользовательских стратегий и осуществление игрового цикла.
Описание игрового мира задаётся конфигурационными файлами, с помощью которых можно установить физические характеристики робота (радиус, колесная база, диаметр колеса и т.п.), параметры симуляции (размер поля, длительность симуляции, количество игроков, количество юнитов у каждого игрока, количество препятствий, характеристики пушки и т.п.), стартовые параметры (начальные положения роботов и препятствий), конфигурация аппаратной платформы (IP-адреса роботов, маркеры для роботов и препятствий).
После чтения конфигурации создается окружение — либо симулятор, либо аппаратная платформа. Оба окружения имеют одинаковый интерфейс, поэтому работа с ними выполняется одинаково:
class IEnvironment
{
public:
virtual void Initialize(boost::shared_ptr world) = 0;
virtual void Step() = 0;
virtual bool CloseRequested() = 0;
virtual bool Paused() = 0;
virtual bool Idle() = 0;
};
Далее запускаются процессы пользовательских стратегий, и начинается ожидание готовности окружения (для чего это нужно, опишу ниже). Как только подготовка окружения завершается, происходит отправка стартового сообщения стратегиям и запускается цикл симуляции, внутри которого каждой стратегии отправляется сообщение с состоянием игрового мира и ожидается ответ с реакцией пользователя. На этом этапе контролируется время выполнения пользовательского кода — если ответ не пришел в установленное время, пользовательская стратегия завершает работу. После получения ответов от всех стратегий происходит их обработка — данные передаются в окружение, где они обрабатываются, и состояние игрового мира обновляется. Затем происходит переход к следующей итерации.
Модуль симуляции
Модуль симуляции используется участниками для проверки своей стратегии во время проведения очной части соревнования (т.е. написания стратегий). Симулятор имеет простую графику и производит симуляцию физики (столкновения, попадания) и т.п.
Модуль аппаратного взаимодействия
Самый сложный и интересный модуль. Этот модуль передает команды на роботов, а также получает их текущие координаты.
Вся логика выполняется на компьютере, где запущена система, на роботов отправляются только результирующие команды (на уровне управления двигателями и установки состояния светодиодов). Для связи с роботами используется WiFi соединение, к которому подключен компьютер-сервер и оба робота.
Для получения текущих координат над игровым полем расположена камера, а на всех подвижных объектах наклеены маркеры. Во время симуляции происходит распознавание маркеров и определение их позиции.
В качестве камеры мы использовали Intel RealSense первого поколения. С камерой второго поколения возникли проблемы при работе с длинным USB-кабелем, поэтому обратились именно к первой версии.
Так как в отличие от симуляторов, нам необходимо дождаться готовности роботов (убедиться, что робот подключены), мы ввели промежуточную стадию состояния окружения — как только роботы подключены, окружение посылает сообщение о готовности.
Кроме поиска маркеров и определения их позиций, модуль также производит рендеринг объектов дополненной реальности. Во время соревнования картинка с камеры транслировалась на проектор. Помимо изображения с камеры, на него выводилось состояние роботов и отрисовывались выстрелы.
Робот
Робот собран на базе Intel Galileo. Дополнительно был установлен MotorShiled для управления двигателями и модуль WiFi. Также для управления светодиодами была изготовлена плата со сдвиговым регистром (после установки MotorShield«a на Arduino остаётся очень мало свободных пинов). В качестве актуаторов использовались два двигателя постоянного тока, работающих в диапазоне 3.3 — 9 В. Питалось это все от li-po аккумулятора на 7.4 В х 1500 мА.
Турнирное ПО
Кроме этого мы подготовили турнирное ПО — клиент и сервер, которые использовались во время очной части соревнования. Сервер позволял отправлять пользовательские стратегии на компиляцию и получать скомпилированную стратегию, а также получать скомпилированные стратегии других участников и производить симуляцию с их участием.
Установка в сборе:
В ходе разработки и проведения соревнований мы столкнулись с рядом проблем, часть из которых мы решили, часть осталась нам для работы над ошибками.
Камеры
Изначально мы планировали использовать камеру, которая стояла бы с одной стороны от поля
Однако в процессе тестирования мы поняли, что объекты, расположенные в дальнем конце поля, очень плохо распознаются. Поэтому нужно было либо сильно поднимать камеру, что бы изменить угол, либо вешать ее над полем. В итоге остановились на последнем варианте.
Освещение
Большой проблемой стало мерцающее освещение. Очень сильно не хотелось делать дополнительную обработку изображения для компенсации мерцания газоразрядных ламп. В итоге проблему «подперли» костылем — перешли к 25 кадрам (т.е. к частоте, кратной частоте мерцания) и добавили небольшую корректировку по пороговой контрастности, которую можно было менять «на лету».
WiFi
Изначально в нашем распоряжении были два WiFi модуля без антенн. В целом для разработки этого хватало, но при первом же запуске стало ясно, что для дальнейшей работы этот вариант не подойдет — сигнал постоянно терялся, пакеты приходили с большой задержкой. В итоге оснастили роботы антеннами.
Позиционирование
Поскольку система неоднократно собиралась и разбиралась и расположение частей (поля относительно камеры) постоянно менялось, мы добавили процедуру калибровки — расставляя маркеры по заранее определённым позициям после монтажа установки, мы производили калибровку и сохраняли ее в файле конфигурации для последующих запусков.
Физика
Самая большая проблема. Физика в симуляторе в итоге отличалась от физики реального робота. Это отличие складывалось из нескольких составляющих:
- Временной лаг — в симуляторе участники получали сразу актуальную позицию, в случае с аппаратной платформой они получали положение робота с некоторым запозданием, что приводило к «перерегулированию», очень много роботов крутились влево-вправо, потому что не могли попасть в нужный угол.
- • Отличие самой физики — физика передвижения препятствий в симуляторе и в реальности не соответствовали (мы немного промахнулись с массой, и реальные препятствия двигались сложнее).
Несмотря на все трудности, студентов заинтересовало наше мероприятие, и мы планируем развивать платформу, чтобы устранить существующие проблемы и реализовать все наши задумки.
Существенным ограничение на игровую механику было отсутствие возможности визуализировать снаряды. Это можно решить, разместив над полем проектор, который бы проецировал дополнительные объекты на игровое поле. Это открывает широкий спектр механик: мы сможем добавить бонусы, снаряды с конечной скоростью, индикацию здоровья и других состояний. Плюс ко всему это интересная задача.
Так же хотелось бы увеличить размеры игрового поля, но здесь мы были ограничены одной камерой. Поэтому в планах есть использование нескольких камер.