Создаем робота на ХабраWars

    Всем привет!
    Наверно многие уже слышали про проект под названием HabraWars, если коротко — это игра для программеров, в которой вы сами пишите искусственный интеллект для собственного робота на JS.

    Я думаю что это будет не первая моя статья на данную тему, хотя бы потому, что здесь я не собираюсь раскрывать всю тему, а лишь ее часть. Но сначала, я думаю, нужно сформировать некую концепцию робота… Сразу говорю, что в js я далеко не спец и вообще мои знания этого языка, на мой взгляд, довольно скудны… Итак, как-же должен выглядеть наш робот изнутри, а изнутри у него должна быть логика, как бы это очевидно не звучало, но логика это довольно сложная штука, она будет управлять роботом, задавая вопросы(типа: «Летит ли в меня(робота) снаряд») и основываясь на ответах вызывать функции, отвечающие за действия робота… Но начну я не с логики(я сам еще не начинал даже ее писать:)), а начну с функций, отвечающих за выполнение действий, порученных логикой.

    Значит так, начнем. В этом посте будет обсуждаться именно атака нашего робота(в следующем наверно увороты от пуль, читай защита). Накидываем для начала такой сухой код:
    1. this.enemyId = 0;
    2.  
    3. this.action = function(my_state, enemies, shots, params){
    4.   if(!this.enemyId){// Если мы еще не выбрали врага, то есть это первая итерация данной функции
    5.     /*
    6.     Получаем ID ближайшего врага и записываем его в переменную, которая не изменится при построении
    7.     следующего фрейма игры...
    8.     */
    9.     this.enemyId = this.getNearestEnemy(
    10.       enemies,
    11.       {x: my_state.x, y: my_state.y},
    12.       my_state.angle
    13.     );
    14.   }
    15.   for(var key in enemies){
    16.     if(enemies[key]['id'] == this.enemyId) var enemy = enemies[key];
    17.   }
    18.   if(typeof(enemy) == 'undefined'){// Если наш враг уже умер, то нужно находить нового...
    19.     this.enemyId = this.getNearestEnemy(
    20.       enemies,
    21.       {x: my_state.x, y: my_state.y},
    22.       my_state.angle
    23.     );
    24.     
    25.     for(var key in enemies){
    26.       if(enemies[key]['id'] == this.enemyId) var enemy = enemies[key];
    27.     }
    28.   }
    29.   /*
    30.   Этот метод(который ниже) мы создадим чтобы он нам говорил в какую сторону
    31.   нужно отклониться, чтобы нацелиться на врага, он будет возвращать
    32.   -1 если нужно повернуться по часовой стрелке, и 1 если нужно повернуться
    33.   против часовой стрелки... ну или 0 если мы уже нацелены...
    34.   напомню для тех кто забыл школьный курс геометрии: ноль градусов
    35.   располжены там где 3 часа на стенных часах, соответственно 90 градусов
    36.   на 12-ти часах...
    37.   */
    38.   var angle = this.getAngleDeflection(
    39.     {x: my_state.x, y: my_state.y},
    40.     {x: enemy.x, y: enemy.y},
    41.     my_state.angle
    42.   );
    43.   /*
    44.   Дистанцию до врага можно вычислить по формуле архимеда: c^2 = a^2 + b^2 - сумма
    45.   квадратов катетов равна квадрату гипотенузы... => c = sqrt(a^2 + b^2)
    46.   */
    47.   var distanceToEnemy = Math.sqrt(
    48.     Math.pow(enemy.x - my_state.x, 2)
    49.     + Math.pow(enemy.y - my_state.y, 2)
    50.   );
    51.   return [
    52.     0.5,
    53.     angle,
    54.     this.fire(
    55.       {x: my_state.x, y: my_state.y},
    56.       {x: enemy.x, y: enemy.y},
    57.       my_state.angle,
    58.       distanceToEnemy
    59.     ),
    60.     distanceToEnemy
    61.   ];
    62. };
    * This source code was highlighted with Source Code Highlighter.


    Надеюсь с комментами в коде все понятно… Итак мы имеем несколько методов, которые еще не описаны, но использованы, значит нужно их написать, вот что мы щас будем писать:
    • getNearestEnemy(enemies, myCoords, myAngle)
    • getAngleDeflection(myCoords, enemyCoords, myAngle)
    • fire(myCoords, enemyCoords, myAngle, distance)


    Описываем метод getNearestEnemy(enemies, myCoords, myAngle):
    1. this.getNearestEnemy = function(enemies, myCoords, myAngle){
    2.   var howLong = Array(); // В этом массиве будут содержаться ID врагов и его дальность от нас
    3.   var distance = Array(); // В этом массиве будут содержаться ID и дальность самого близеого к нам врага
    4.   for(var i = 0; i < enemies.length; i++){
    5.     howLong[i] = Array();
    6.     /*
    7.     Получаем направление в градусах от нас до врага
    8.     */
    9.     var direction = get_direction(myCoords.x, myCoords.y, enemies[i]['x'], enemies[i]['y']);
    10.     /*
    11.     Теперь разницу между нашем направлением и направлением в сторону врага. Эта разница тоже имеет значение,
    12.     т.к. на поворот тоже затрачивается время
    13.     */
    14.     direction = angle_size(direction, myAngle);
    15.     /*
    16.     Теперь суммируем расстояние и разницу углов...
    17.     */
    18.     howLong[i]['distance'] = Math.sqrt(
    19.       Math.pow(enemies[i]['x'] - myCoords.x, 2)
    20.       + Math.pow(enemies[i]['y'] - myCoords.y, 2)
    21.     ) + direction;
    22.     howLong[i]['id'] = enemies[i]['id'];
    23.   }
    24.   for(var i = 0; i < howLong.length; i++){// Здесь отбриаем ID самого ближайшего робота
    25.     if(!i) distance = howLong[i];
    26.     else if(distance['distance'] > howLong[i]['distance']) distance = howLong[i];
    27.   }
    28.   return distance['id'];
    29. };
    * This source code was highlighted with Source Code Highlighter.


    Теперь метод getAngleDeflection(myCoords, enemyCoords, myAngle):
    1. this.getAngleDeflection = function(myCoords, enemyCoords, myAngle){// Ну здесь вроде все и так понятно)
    2.   return get_angle_control(
    3.     myAngle,
    4.     get_direction(myCoords['x'], myCoords['y'], enemyCoords['x'], enemyCoords['y'])
    5.   );
    6. };
    * This source code was highlighted with Source Code Highlighter.


    И последний метод — fire(myCoords, enemyCoords, myAngle, distance), он нам говорит стрелять или нет(например если энергия у нас 100 процентов но робот еще не успел нацелиться то стрелять НЕ надо):
    1. this.fire = function(myCoords, enemyCoords, myAngle, distance){
    2.   var direction = get_direction(myCoords['x'], myCoords['y'], enemyCoords['x'], enemyCoords['y']);
    3.   var angle = angle_size(direction, myAngle); // Приравниваем переменной разность углов
    4.   if(distance != 0 && angle != 0){// Если какая-то из переменных равна нулю, то теоретически стрелять можно
    5.     /*
    6.     10 делим на произведение расстояния и разности углов, получаем коэффициент "нацеленности"
    7.     */
    8.     if(10/(distance*angle) > 0.6) return true;
    9.     else return false;
    10.   }else return true;
    11. }
    * This source code was highlighted with Source Code Highlighter.


    Все готово, теперь можно собрать нашего робота по кусочкам:
    1. this.getNearestEnemy = function(enemies, myCoords, myAngle){
    2.   var howLong = Array();
    3.   var distance = Array();
    4.   for(var i = 0; i < enemies.length; i++){
    5.     howLong[i] = Array();
    6.     var direction = get_direction(myCoords.x, myCoords.y, enemies[i]['x'], enemies[i]['y']);
    7.     direction = angle_size(direction, myAngle);
    8.     howLong[i]['distance'] = Math.sqrt(
    9.       Math.pow(enemies[i]['x'] - myCoords.x, 2)
    10.       + Math.pow(enemies[i]['y'] - myCoords.y, 2)
    11.     ) + direction;
    12.     howLong[i]['id'] = enemies[i]['id'];
    13.   }
    14.   for(var i = 0; i < howLong.length; i++){
    15.     if(!i) distance = howLong[i];
    16.     else if(distance['distance'] > howLong[i]['distance']) distance = howLong[i];
    17.   }
    18.   return distance['id'];
    19. };
    20.  
    21. this.getAngleDeflection = function(myCoords, enemyCoords, myAngle){
    22.   return get_angle_control(
    23.     myAngle,
    24.     get_direction(myCoords['x'], myCoords['y'], enemyCoords['x'], enemyCoords['y'])
    25.   );
    26. };
    27.  
    28. this.fire = function(myCoords, enemyCoords, myAngle, distance){
    29.   var direction = get_direction(myCoords['x'], myCoords['y'], enemyCoords['x'], enemyCoords['y']);
    30.   var angle = angle_size(direction, myAngle);
    31.   if(distance != 0 && angle != 0){
    32.     if(10/(distance*angle) > 0.6) return true;
    33.     else return false;
    34.   }else return true;
    35. }
    36.  
    37. this.enemyId = 0;
    38.  
    39. this.action = function(my_state, enemies, shots, params){
    40.   if(!this.enemyId){
    41.     this.enemyId = this.getNearestEnemy(
    42.       enemies,
    43.       {x: my_state.x, y: my_state.y},
    44.       my_state.angle
    45.     );
    46.   }
    47.   for(var key in enemies){
    48.     if(enemies[key]['id'] == this.enemyId) var enemy = enemies[key];
    49.   }
    50.   if(typeof(enemy) == 'undefined'){
    51.     this.enemyId = this.getNearestEnemy(
    52.       enemies,
    53.       {x: my_state.x, y: my_state.y},
    54.       my_state.angle
    55.     );
    56.     
    57.     for(var key in enemies){
    58.       if(enemies[key]['id'] == this.enemyId) var enemy = enemies[key];
    59.     }
    60.   }
    61.   var angle = this.getAngleDeflection(
    62.     {x: my_state.x, y: my_state.y},
    63.     {x: enemy.x, y: enemy.y},
    64.     my_state.angle
    65.   );
    66.   var distanceToEnemy = Math.sqrt(
    67.     Math.pow(enemy.x - my_state.x, 2)
    68.     + Math.pow(enemy.y - my_state.y, 2)
    69.   );
    70.   return [
    71.     0.5,
    72.     angle,
    73.     this.fire(
    74.       {x: my_state.x, y: my_state.y},
    75.       {x: enemy.x, y: enemy.y},
    76.       my_state.angle,
    77.       distanceToEnemy
    78.     ),
    79.     distanceToEnemy
    80.   ];
    81. };
    * This source code was highlighted with Source Code Highlighter.


    Конечно это не всё и можно еще много чего придумать, например частенько если робот стреляет из далека, то пуля не долетает, это можно исправить зная направление движения врага…

    Комментарии 10

      0
      getAngleDeflaction — ?? а что это? 0_о
        0
        вот цитата из, скажем так, мануала на habrawars:
        Управление, возвращаемое роботом
        Функция action должна возвращать массив из 4 элементов:

        •скорость — от 0 до 1, скорость выше 0.5 расходует энергию робота;
        •направление поворота — от -1 до 1 градуса;
        •выстрел — true, если нужно произвести выстрел (игнорируется при энергии < 100);
        •дальность выстрела — положительное число, задающее дальность полета снаряда (игнорируется, если выстрел не производится).
        Пример: return [0.5, -1, true, 350];

        Вобщем эта функция занимается тем, что говорит: на -1, 0 или 1 градус повернуться…
          0
          Я немного не про то.

          Просто немного непонятно, почему тратя столько времени и сил на продумывания различных вариаций и коли уж решили называть функции используя англоязычные термины — то неплохо было бы посмотреть — как же все таки правильно писать :)

          я так понимаю, что
          Deflaction должен быть все таки Deflection? ;)
            0
            блин, и в правду ошибся, все это время думал что надо писать через `a`))) спасибо что указали)
        0
        Проверка на distance != 0 в функции this.fire выглядит несколько странно, т.к.:
        а) роботы не могут проходить друг сквозь друга, поэтому меньше 30 оно не станет;
        б) если заменить 0 на 30 — ваш робот будет стрелять, даже если смотрит в противоположную от цели сторону, если подойдет к врагу вплотную. В остальном вроде прилично, добавить только поправку на движение — будет совсем замечательно =)
          0
          Я так понимаю, эта проверка, чтобы не поделить случайно на ноль.
          Мало ли что может прийти в функцию в качестве параметра.
          Просто дополнительная защита, чтобы робот не упал во время боя даже в случае небольшой ошибки.
            0
            Тогда, возможно, проверку стоит сделать в начале функции на distance >= params.robot_radius (при меньшем значении стрелять бессмысленно — себе повреждений подконтрольный робот нанесет больше)
              0
              ага, MiXei4 прав, тоесть это задумывалась как проверка чтоб, мало-ли что, не поделить на ноль…
              а насчет вашего предложения — это да, было бы лучше, даже еще можно написать что-то вроде этого: distance >= params.robot_radius + 5
              т.к. повреждается робот, даже если заряд попадает в районе примерно 5-8 пикселей…
                0
                Радиус действия бомбы 45 пикс., следовательно робот повреждается, если бомба упала от края робота ближе 30пикс.

                Но если мы не будем стрелять в этом случае, то при двух оставшихся роботах вплотную друг к другу ваш проиграет :)
                  0
                  ну да, я же говорил что логика — сложная штука)) чтоб организовать хоть чуть-чуть похожее на человека, поведение у робота, надо написать строк где-то так тыщи три…
                  кстати, есть идея такая: вобщем мы ведь тут имеем дело с жабаскриптом, так-что можем перехватывать нажатия клавиш на клаве… так почему бы не сделать управляемого робота? надо будет попробовать в ближайшее время, единственное — конечно такое чудо никогда не допустят не до каких соревнований

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

        Самое читаемое