Первые шаги в Robocode

    Я пишу эту статью по просьбам в комментариях к статье “Как я стал чемпионом Robocode” и продолжая начатое в ней дело по привлечению внимания к Robocode русскоговорящих разработчиков. Robocode — это игра для программистов, в которой задача заключается в разработке системы управления танком. Для затравки приведу несколько роликов, чтобы показать о чём вообще пойдёт разговор:




    Введение


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

    В Robocode есть 4 официальных турнира:
    • Дуэль, 2 робота на поле 800 на 600
    • Схватка (Melee), 10 роботов на поле 1000 на 1000
    • Команды (Teams), 2 команды по 5 роботов на поле 1200 на 1200
    • Двойная дуэль (TwinDuel), 2 команды по 2 робота на поле 800 на 800

    В дуэли и схватке есть 4 весовых категории:
    • Нано <= 249 байт исполняемого кода
    • Микро <= 749 байт исполняемого кода
    • Мини <= 1499 байт исполняемого кода
    • Мега (Общая) — без ограничений

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

    Подготовка


    Для того чтобы начать программировать своего чемпиона, вам как минимум потребуется скачать и установить:
    • Последнюю версию Java (однако на данный момент игра официально поддерживает только Java 6, поэтому роботов надо компилировать для этой версии)
    • Последнюю версию Robocode (опять же, в официальных соревнованиях не поддерживается последняя версия, поэтому надо следить за тем, чтобы робот не использовал API, отсутствующие в поддерживаемой на данный момент версии).

    Так же я настоятельно рекомендую скачать и установить:
    • Последнюю версию любой современной Java IDE (не буду здесь ни кого рекламировать, если есть вопросы — прошу в личку)
    • Систему сборки Ant


    Физика игры


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

    UPD: опубликовал статью о физике http://habrahabr.ru/post/147956/

    Управление роботом и взаимодействие с внешним миром


    Вообще в игре есть целая иерархия классов, от которых можно отнаследоваться для написания своего робота. Есть “простой” класс robocode.Robot, который за раз может выполнять только одну команду, т.е. либо двигаться, либо поворачивать, либо стрелять (есть, однако, и бонус — такие роботы не получают повреждения от столкновений со стенами). Но на мой взгляд, программирование таких роботов наоборот сложнее и я рекомендую (и в дальнейшем буду исходить из предположения, что вы последовали моей рекомендации) наследоваться от класса robocode.AdvancedRobot, который за один ход может управлять всеми частями сразу. Так же ещё небезинтересен класс robocode.TeamRobot, который позволяет создавать команды, но это отдельная тема для отдельной статьи.

    Робот управляется множеством команд, но я приведу только наиболее важные:
    • setAhead — двигаться вперёд на максимальной скорости (и торможением, если в данный момент робот движется назад) на указанное количество пикселей. Если аргумент отрицательный, то робот движется назад
    • setFire и setFireBullet — выстрелить с заданной мощностью.
    • setTurnRightRadians, setTurnLeftRadians, setTurnGunRightRadians, setTurnGunLeftRadians, setTurnRadarRightRadians, setTurnRadarLeftRadians — поворот соответствующих частей, в соответствующем направлении на заданный угол в радианах (есть методы без суфикса Radians, которые на вход принимают угол в градусах). *RightRadians — поворачивает соответсвующую часть по часовой стрелке, *LeftRadians, соответственно, против часовой стрелке.


    В начале каждого хода робот получает набор событий, произошедших во время выполнения прошлой команды. Событий достаточно много и я приведу только наиболее важные из них:
    • StatusEvent — состояние робота (положение, скорость, направления частей и т.п.).
    • DeathEvent — робот только что был уничтожен.
    • BulletHitEvent — пуля робота попала по другому роботу.
    • HitByBulletEvent — по роботу попала пуля противника.
    • HitRobotEvent — произошло столкновение с противником.
    • HitWallEvent — произошло столкновение со стеной.
    • ScannedRobotEvent — последним ходом был обнаружен противник. Данное событие содержит некоторую информацию о противнике (всё что необхоидмо для того чтобы вычислить его положение, его энергию, направление его движения.


    Подготовка сборки


    Если вы по каким-то причинам решили не использовать ант для сборки, то этот раздел можете пропустить. Вы можете выкачать репозиторий отсюда: https://github.com/aleksey-zhidkov/HabrahabrTutorial, либо руками создать у себя следующую структуру папок:
    /HabrahabrTutorial
    + - /src
       - build.properties
       - build.xml
    


    Файл build.properties имеет следующие содержание:
    bin.dir = bin
    builds.dir = builds
    robocode.dir = ; путь к домашней директории Robocode, по умолчанию C:\\Robocode (обращаю ваше внимание, что это формат java properties и слэши должны быть икранированы)
    robocode.jar = ${robocode.dir}\\libs\\robocode.jar


    Файл build.xml имеет следующие содержание:
    <?xml version="1.0" encoding="UTF-8"?>
    
    <project name="HabrahabrTutorial" basedir="."
             default="release">
    
        <property file="build.properties"/>
        <property name="robot.version" value="0.1"/>
        <property name="robot.package" value="ru.jdev.habrahabr"/>
        <property name="robot.path" value="ru/jdev/habrahabr"/>
        <property name="robot.name" value="HabrahabrTutorial"/>
    
        <path id="src.files">
            <pathelement location="src"/>
        </path>
    
        <target name="init">
            <mkdir dir="${bin.dir}"/>
        </target>
    
        <target name="compile" depends="init" description="Compiles source files">
    
            <javac destdir="${bin.dir}" debug="on" debuglevel="lines,vars,source" optimize="yes" target="1.6">
                <src refid="src.files"/>
    
                <classpath>
                    <pathelement location="${robocode.jar}"/>
                </classpath>
            </javac>
    
        </target>
    
        <target name="clean" description="Deletes all previous build artifacts">
            <delete dir="${bin.dir}"/>
        </target>
    
        <target name="release" depends="clean, compile">
            <copy todir="${bin.dir}">
                <fileset dir="src"/>
            </copy>
    
            <echo file="${bin.dir}/${robot.path}/${robot.name}.properties">robocode.version=1.7.3
                robot.java.source.included=true
                robot.version=${robot.version}
                robot.author.name=Alexey jdev Zhidkov
                robot.classname=${robot.package}.${robot.name}
                robot.name=${robot.name}
                robot.description=Tutorial robot for habrahabr.ru
            </echo>
    
            <jar destfile="${builds.dir}\${robot.package}.${robot.name}_${robot.version}.jar" compress="true">
                <fileset dir="${bin.dir}"/>
            </jar>
    
            <copy todir="${robocode.dir}\robots\">
                <fileset file="${builds.dir}\${robot.package}.${robot.name}_${robot.version}.jar"/>
            </copy>
    
            <delete includeEmptyDirs="true">
                <fileset dir="${bin.dir}" includes="**/*"/>
            </delete>
    
        </target>
    
    </project>
    


    Если вы всё сделали правильно, то после выполнения команды ant в корне проекта, у вас должен появиться файл HabrahabrTutorial\builds\ru.jdev.habrahabr.HabrahabrTutorial_0.1.jar, в котором должен быть файл ru\jdev\habrahabr\HabrahabrTutorial.properties (для тех кто далёк от Java, поясню, что jar — это обычный зип архив, который можно открыть любым архиватором).

    Создание робота


    Создайте в выбранной вами среде разработки проект в директории из предыдущего раздела, подключите к нему библиотеку ${robocode_home}/libs/robocode.jar. Затем настройте среду так, чтобы либо она сама компилировала код в папку с роботами, либо чтобы она при сборке использовала скрипт из предыдущего раздела. Наконец, создайте новый класс ru.jdev.habrahabr.HabrahabrTutorial со следующим кодом:

    package ru.jdev.habrahabr;
    
    import robocode.AdvancedRobot;
    
    public class HabrahabrTutorial extends AdvancedRobot {
    
        @Override
        public void run() {
            while (true) {
                /**
                 * Вызовом этого метода робот сообщает движку, что он закончил вычисления и отдал все команды на текущий ход
                 * Этот вызов блокируется до начала следующего кода
                 */
                execute();
            }
        }
    }
    


    Запустите игру, выберите Battle -> New и убедитесь, что в списке роботов появился ru.jdev.habrahabr.HabrahabrTutorial.

    Шаг первый: Выход после смерти, вычисление позиции противника, управление радаром и рисование.


    Приводим код к следующему виду (здесь все комментарии приведены прямо в коде. Так же, здесь и далее добавленные строки или методы отмечены комментарием /*+*/, изменённые строки или методы отмечены комментарием /*~*/):
    package ru.jdev.habrahabr;
    
    import robocode.AdvancedRobot;
    import robocode.DeathEvent;
    import robocode.ScannedRobotEvent;
    import robocode.util.Utils;
    
    import java.awt.*;
    import java.awt.geom.Point2D;
    
    import static java.lang.Math.signum;
    import static java.lang.Math.toRadians;
    
    public class HabrahabrTutorial extends AdvancedRobot {
    
        /*+*/private static final double RADIANS_5 = toRadians(5);
    
        /*+*/private boolean isAlive = true;
    
        /*+*/private double enemyX = -1;
        /*+*/private double enemyY = -1;
    
        @Override
        public void run() {
            /*+*/setTurnRadarRightRadians(Double.POSITIVE_INFINITY); // пока противник не найден бесконечно крутим радар в право
            /*~*/while (isAlive) { // в принципе это не обязательно и можно оставить true, не я предпочитаю избегать бесконечных циклов
    
                /*+*/if (enemyX > -1) { // если противник обнаружен
                    /*+*/final double radarTurn = getRadarTurn();
                    /*+*/setTurnRadarRightRadians(radarTurn);
                    /*+*/}
    
                /**
                 * Вызовом этого метода робот сообщает движку, что он закончил вычисления и отдал все команды на текущий ход
                 * Этот вызов блокируется до начала следующего кода
                 */
                execute();
            }
        }
    
        /*+*/private double getRadarTurn() {
            // роботу жизненно необходимо постоянно видеть противника
            // считаем абсолютный угол до противника:
            final double alphaToEnemy = angleTo(getX(), getY(), enemyX, enemyY);
            // считаем направление, на который надо повернуть радар, чтобы противник остался в фокусе (Utils, это встренный в Robocode класс):
            final double sign = (alphaToEnemy != getRadarHeadingRadians())
                    ? signum(Utils.normalRelativeAngle(alphaToEnemy - getRadarHeadingRadians()))
                    : 1;
    
            // добавляем 5 градусов поворта для надёжности и получаем результирующий угол
            return Utils.normalRelativeAngle(alphaToEnemy - getRadarHeadingRadians() + RADIANS_5 * sign);
            // В принципе, прямо здесь можно вызвать setTurnRadarRightRadians, но я противник функций с сайд эффектами и стараюсь
            // минимизировать их количество
        }
    
        @Override
        /*+*/public void onScannedRobot(ScannedRobotEvent event) {
            /** ScannedRobotEvent не содержит в себе явно положения противника, однако, его легко вычислить, зная направление
             * своего корпуса, беаринг (по сути угол относительный чего-то, в данном случае относительно корпуса) и расстояние до противника
             */
    
            // абсолютный угол до противника
            final double alphaToEnemy = getHeadingRadians() + event.getBearingRadians();
    
            // а далее элементарная геометрия
            enemyX = getX() + Math.sin(alphaToEnemy) * event.getDistance();
            enemyY = getY() + Math.cos(alphaToEnemy) * event.getDistance();
        }
    
        @Override
        /*+*/public void onDeath(DeathEvent event) {
            isAlive = false;
        }
    
        @Override
        /*+*/public void onPaint(Graphics2D g) {
            // убеждаемся, что вычислили позицию противника верно
            // для того чтобы увидеть что мы ресуем, необходимо во время битвы на правой понели кликнуть по имени робота
            // и в появившемся окне нажать кнопку Paint
    
            if (enemyX > -1) {
                g.setColor(Color.WHITE);
                g.drawRect((int) (enemyX - getWidth() / 2), (int) (enemyY - getHeight() / 2), (int) getWidth(), (int) getHeight());
            }
        }
    
        /**
         * В Robocode немного извращённые углы - 0 смотрит на север и далее по часовой стрелке:
         * 90 - восток, 180 - юг, 270 - запад, 360 - север.
         * <p/>
         * Из-за этого приходится писать собственный метод вычисления угла между двумя точками.
         * Вообще говоря, математика никогда не была моим коньком, поэтому, возможно, существует лучшее решение
         */
        /*+*/private static double angleTo(double baseX, double baseY, double x, double y) {
            double theta = Math.asin((y - baseY) / Point2D.distance(x, y, baseX, baseY)) - Math.PI / 2;
            if (x >= baseX && theta < 0) {
                theta = -theta;
            }
            return (theta %= Math.PI * 2) >= 0 ? theta : (theta + Math.PI * 2);
        }
    
    }
    


    Шаг второй: Начинаем движение


    Реализуем зачатки рандомного орбитального движения. Орбитальное значит, что в случае если противник будет стоят на месте, а рандом будет выдавать постоянно одно направление, то наш танк будет наворачить круги вокруг противника. А случайная составляющая сделает наш танк чуть более сложной целью. Для реализации движения добавим два метода:
    private double getDistance() {
            // вычесление дистанции движения элементарно
            return 200 - 400 * random();
        }
    
        private double getBodyTurn() {
            // а вот вычисление угла поворота посложее
            final double alphaToMe = angleTo(enemyX, enemyY, getX(), getY());
    
            // определяем угловое направление относительно противника (по часовой стрелке, либо против) ...
            final double lateralDirection = signum((getVelocity() != 0 ? getVelocity() : 1) * Math.sin(Utils.normalRelativeAngle(getHeadingRadians() - alphaToMe)));
            // получаем желаемое направление движения
            final double desiredHeading = Utils.normalAbsoluteAngle(alphaToMe + Math.PI / 2 * lateralDirection);
            // нормализуем направление по скорости
            final double normalHeading = getVelocity() >= 0 ? getHeadingRadians() : Utils.normalAbsoluteAngle(getHeadingRadians() + Math.PI);
            // и возвращаем угол поворта
            return Utils.normalRelativeAngle(desiredHeading - normalHeading);
        }
    


    А внутри условия, что противник обнаружен в основном цикле добавим следующие строки:
                    setTurnRadarRightRadians(radarTurn);
    
    /*+*/                final double bodyTurn = getBodyTurn();
    /*+*/                setTurnRightRadians(bodyTurn);
    /*+*/
    /*+*/                if (getDistanceRemaining() == 0) {
    /*+*/                    final double distance = getDistance();
    /*+*/                    setAhead(distance);
    /*+*/                }
                }
    


    Шаг третий: Огонь!


    Мы реализуем простейший алгоритм прицеливания, который стреляет по текущему положению противника. Для достижения этой цели робот будет всегда держать противника под прицелом и стрелять при первой возможности. Реализуется поставленная задача одним методом:
    private double getGunTurn() {
           // вычисления тривиальны: считаем на какой угол надо повернуть пушку, чтобы она смотрела прямо на противника:
           return Utils.normalRelativeAngle(angleTo(getX(), getY(), enemyX, enemyY) - getGunHeadingRadians());
       }
    


    И добавлением трёх строк в основной цикл:
                        setAhead(distance);
                    }
    
    /*+*/                final double gunTurn = getGunTurn();
    /*+*/                setTurnGunRightRadians(gunTurn);
    /*+*/                setFire(2);
                }
    


    Что дальше


    А дальше добро пожаловать на робовики, либо пишите в комментариях, что вам интересна эта тема и я постепенно постараюсь осветить все основные техники игры. Спасибо всем, кто осилил этот пост до конца.

    P.S. Это мой первый туториал, по этому буду рад конструктивной критике
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 23

      0
      Стоит под кат убрать.
        0
        меня всегда интересовало — каким образом это вообще могло появиться до ката? ведь при попытке поста полотна больше N символов — мне лично хабр просто не дает это сделать — говорит «уберите под кат»
          +1
          Да, я начал текст с <habracut />, но почему-то не помогло. Подскажите как, пожалуйста
            –1
            Начали весь текст??? Хабракат надо ставить в том месте, в котором хотите получить «разрез».
              0
              Можно сообщать о баге.
                +2
                Нет, не надо — это была ошибка в ДНК и он уже исправлена:)
              0
              Поставте в том месте до которого текст будет отображен в ленте.
                0
                Теги Хабрахабра

                Используется только в текстах постов, скрывает под кат часть текста, следующую за тегом (будет написано «Читать дальше»).
                />
                Так можно превратить надпись «Читать дальше» в любой текст.
                  0
                  Хм, теги хабраката и в комментариях работают.
                  +1
                  Спасибо всем за разъяснение
                +1
                Блин, спасибо, сам не разобрался
                  +1
                  У всех прошу прощения, за не отсутсвие ката поначалу
                    0
                    Ну и запишем это в качестве первого пункта «конструктивной критики»:) Ещё раз у всех пострадавших прошу прощения:)
                    0
                    Спасибо за отличный материал.
                    Первый вопрос — как же посмотреть и протестировать то, что получилось?
                    Может я невнимательно читал.
                      +2
                      Для этого вы должны скачать и установить java и robocode. Далее могут быть варианты, но самый простой следующий:
                      1) скачать Ant
                      2) выкачать код из репозитория
                      3) указать в build.properties путь к Robocode
                      4) выполнить команду ant в корне проекта
                      5) запустить robocode
                      6) выбрать пункт меню Battle -> New
                      7) в списке роботов 2 раза кликнуть по ru.jdev.habrahabr.HabrahabrTutorial и по любому другому боту, например sample.Crazy
                      8) нажать Start Battle
                      0
                      Лет 5 назад баловался робокодом все хорошо, но времени жрет…
                        0
                        Да, есть немного:) еще и не отпускает при этом — тут недавно возвращался чемпион то ли 2003, то ли 2004 года, не помню точно
                          0
                          Ещё есть игрушка «Сode invaders», которая, как мне кажется, поинтересней будет.
                            0
                            Бесспорно есть и возможно интереснее — на вкус и цвет фломастеры разные:)

                            Но быстрый гуглинг не выдал мне активного коммюнити «Code invaders» и активных официальных соревнований (в Robocode, кстати, сегодня сменился чемпион, в первый раз 4 года) — а это для меня крайне важно, потому как в «песочнице» сидеть тихо самому с собою мне скучно. Да и вообще, сходу, не понял как начать играть (для сравнения, по запросу Robocode — первая же выдача это оф. страница игры, там ссылка на robowiki, а там уже вообще всё что надо для игры)

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

                            Но, тем не менее, спасибо за то что рассказали об этой игре — после того как стану настоящим чемпионом Robocode обязательно рассмотрю её в качестве следующего убийцы свободного времени:)
                            0
                            Спасибо за статью. Возник вопрос. Где можно посмотреть, какая версия Java и какая версия Robocode может быть использована для разработки своего робота, чтобы робот без проблем мог участвовать в соревнованиях?
                              0
                              В первую очередь, вынужден принести свои извинения за ошибку в статье — Java 7 не не поддерживатеся Robocode, а не требуется для неё, поэтому люди могут запускать клиентов на Java 6 и будут проблемы совместимости (пруф, официальной информации о поддерживаемой версии мне найти не удалось.

                              Официальный клиент мне тоже найти не удалось, но здесь можно выбрать (кликнуть по нему) любого робота и в открывшейся странице кликнуть по любой из ссылок «battles», в результате вы попадёте на страницу подобную этой и в последней колонки можно посмотреть используемые в данный момент версии (сейчас, например 1.7.3.0 и 1.7.3.2)
                              0
                              Как-то всё это сложно у вас описано.

                              Для того чтобы просто попробовать и начать, не нужна никакая IDE — в Robocode есть простейший редактор кода с подсветкой синтаксиса, и возможностью вызова компилятора. Дальше уже когда почувствуешь что к чему — выберешь редактор по вкусу, ну а компилировать и из командной строки можно.

                              Не нужен для начала никакой ant — для упаковки робота в том же приложении Robocode есть команда в меню. Для ряда первых релизов этого вполне достаточно.

                              В общем, для начала достаточно почитать Robowiki (заразиться идеей) поставить Java, поставить Robocode — и вперёд.
                                0
                                Пожалуй соглашусь — перестарался. Лучшее — враг хорошего:)

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