- PVSM.RU - https://www.pvsm.ru -
Я пишу эту статью по просьбам в комментариях к статье “Как я стал чемпионом Robocode” [1] и продолжая начатое в ней дело по привлечению внимания к Robocode русскоговорящих разработчиков. Robocode — это игра для программистов, в которой задача заключается в разработке системы управления танком. Для затравки приведу несколько роликов, чтобы показать о чём вообще пойдёт разговор:
В этой статье я подразумеваю, что читатель знаком с общими принципами алгоритмики и объектно-ориентированного программирования, а так же знаком с языком Java, либо способен спроецировать свои знания других языков на него.
В Robocode есть 4 официальных турнира:
В дуэли и схватке есть 4 весовых категории:
Я же рассмотрю первые шаги в написании робота для дуэли в общей весовой категории. По факту, робот из этой статьи влазит в микро-категорию, но оптимизация размера байткода это целая наука, знание которой необходимо для разработки успешных наноботов и которая в этой статье не раскрыта ни коим образом.
Для того чтобы начать программировать своего чемпиона, вам как минимум потребуется скачать и установить:
Так же я настоятельно рекомендую скачать и установить:
Т.к. вместе с этим разделом статья получается слишком уж большой, а раздел является достаточно продвинутым и не должен быть прочитан и понят для того чтобы сделать первые шаги, я его вынес в отдельную статью, которую опубликую в скором времени.
Вообще в игре есть целая иерархия классов, от которых можно отнаследоваться для написания своего робота. Есть “простой” класс robocode.Robot, который за раз может выполнять только одну команду, т.е. либо двигаться, либо поворачивать, либо стрелять (есть, однако, и бонус — такие роботы не получают повреждения от столкновений со стенами). Но на мой взгляд, программирование таких роботов наоборот сложнее и я рекомендую (и в дальнейшем буду исходить из предположения, что вы последовали моей рекомендации) наследоваться от класса robocode.AdvancedRobot, который за один ход может управлять всеми частями сразу. Так же ещё небезинтересен класс robocode.TeamRobot, который позволяет создавать команды, но это отдельная тема для отдельной статьи.
Робот управляется множеством команд, но я приведу только наиболее важные:
В начале каждого хода робот получает набор событий, произошедших во время выполнения прошлой команды. Событий достаточно много и я приведу только наиболее важные из них:
Если вы по каким-то причинам решили не использовать ант для сборки, то этот раздел можете пропустить. Вы можете выкачать репозиторий отсюда: https://github.com/alexey-zhidkov/HabrahabrTutorial [9], либо руками создать у себя следующую структуру папок:
/HabrahabrTutorial + - /src - build.properties - build.xml
Файл build.properties имеет следующие содержание:
bin.dir = bin builds.dir = builds robocode.dir = ; путь к домашней директории Robocode, по умолчанию C:Robocode 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 в корне проекта, у вас должен появиться файл HabrahabrTutorialbuildsru.jdev.habrahabr.HabrahabrTutorial_0.1.jar, в котором должен быть файл rujdevhabrahabrHabrahabrTutorial.properties.
Создайте в выбранной вами среде разработки проект в директории из предыдущего раздела, подключите к нему библиотеку ${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. Это мой первый туториал, по этому буду рад конструктивной критике
Автор: jdev
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/11560
Ссылки в тексте:
[1] “Как я стал чемпионом Robocode”: http://habrahabr.ru/post/147741/
[2] http://www.youtube.com/watch?v=dqHmp_kMz-U: http://www.youtube.com/watch?v=dqHmp_kMz-U
[3] http://www.youtube.com/watch?v=eqlPbtO3rQY: http://www.youtube.com/watch?v=eqlPbtO3rQY
[4] http://www.youtube.com/watch?v=zx7xFJiBGZQ: http://www.youtube.com/watch?v=zx7xFJiBGZQ
[5] http://www.youtube.com/watch?v=EJPskFGvGi8: http://www.youtube.com/watch?v=EJPskFGvGi8
[6] Java: http://java.com/ru/download/
[7] Robocode: http://sourceforge.net/projects/robocode/files/robocode/
[8] Ant: http://ant.apache.org/bindownload.cgi
[9] https://github.com/alexey-zhidkov/HabrahabrTutorial: https://github.com/alexey-zhidkov/HabrahabrTutorial
Нажмите здесь для печати.