Квест с крестоносцем (Kelvintaph Crusader) доставил мне массу удовольствия. Здесь пришлось действительно подумать и разложить весь поединок по полочкам, перед тем как мне удалось написать программу.
http://codecombat.com/play/level/kelvintaph-crusader
Вот как выглядит начальная сцена.
В нижней части локации ваш герой должен одолеть отряд Brawler-ов, которых поддерживает батарея катапульт. Параллельно с битвой внизу необходимо отдавать приказы элитному отряду ваших союзников в верхней части лабиринта, чтобы те сумели одолеть ведьму и её приспешников.
Основной задачей является выжить самому и сохранить живым паладина. А особый бонус предлагается за сохранение всего отряда.
Задачу со своим персонажем я решил быстро. А вот паладина мне сохранить не удавалось, пока я не вспомнил обо всех способностях, которыми он обладает. Кроме лечения, он может ещё прикрываться щитом. Когда я это учел и разработал порядок действий для каждого члена отряда, задача была решена.
Схватка с отрядом Brawler-ов.
Здесь нужно было использовать катапульты. Они при удачном попадании уничтожают сразу весь отряд пехоты противника. Дальше остаётся разломать катапульты и встать на «крестик». Когда ваш герой достигает выхода, открывается ледяная дверца в верхней части лабиринта.
Чтобы попадание было удачным нужно вовремя отбежать от летящих ядер. Позиции ядер можно получить из общего массива выпущенных снарядов:
1 |
var m = this.findEnemyMissiles(); |
Если ядра высоко — то отбегать не нужно, для этого проверяем координату z.
Есть один нюанс в управлении главным персонажем. Если ему отдать команду attack, управление не будет передано в программу, пока герой не выполнит команду. Тем временем ваши союзники могут простаивать, ожидая указаний. Чтобы справиться с данной проблемой, я использую функцию heroAttack(enemy) — см текст программы.
Схватка союзников.
Сценарий действий союзников, описан в специальном массиве tar, в котором каждый из них получил список приоритетных целей. Кроме имен врагов, в списке встречаются команды — shield и move.
Вся работа по сценарию осуществляется в функции comm().
Первостепенной задачей было уничтожить ведьму Yzzrith, поэтому лучники и паладин сразу атакуют её. Два воина тем временем сдерживают огров. После гибели чародейки, паладин закрывается щитом, а лучники фокусируют свой огонь сначала на ограх, потом на скелетах.
Уничтожив всех врагов, отряд перемещается к выходу в обход капкана.
Текст программы.
Я использую javascript.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
//массив со сценарием действий союзников var tar = [ {hero: "Antary", targers: ["Yzzrith", "shield", "Oni", "Trogdor", "Skully", "Rusty", "move:50:38", "move:78:40"] }, {hero: "Gorgin", targers: ["Oni", "Trogdor", "Rusty", "Skully", "move:50:38", "move:78:40"]}, {hero: "Scott", targers: ["Trogdor", "Rusty", "Skully", "move:50:38", "move:78:40"]}, {hero: "Brian", targers: ["Yzzrith", "Oni", "Trogdor", "Rusty", "Skully", "move:50:38", "move:78:40" ]}, {hero: "Mirana", targers: ["Yzzrith", "Oni", "Trogdor" , "Rusty", "Skully", "move:50:38", "move:78:40" ]}, ]; //поиск бойца, которому нужно лечение this.findMostWounded = function(healer) { var lowestHealth = 9999; var lowestFriend = null; var friends = this.findFriends(); for(var f=0; f < friends.length; f++) { var friend = friends[f]; if(friend.health > 0 && friend.health < friend.maxHealth && lowestHealth > friend.health) { lowestHealth = friend.health; lowestFriend = friend; } } return lowestFriend; }; //управление союзниками this.comm = function() { var fr = this.findFriends(); //перебираем союзников for (var k = 0; k < fr.length; k++) { var f = fr[k]; //паладин, по возможности, выполняет лечение if (f.type == "paladin") { var toheal = this.findMostWounded(f); if (toheal && f.canCast("heal", toheal)) { this.command(f, "cast", "heal", toheal); continue; } } //поиск сценария var targetlist = null; for (var j = 0; j < tar.length; j++) { if (String(f) == tar[j].hero) { targetlist = tar[j].targers; break; } } //флаг, что действие выполнено в рамках сценария var enFound = false; var en = f.findEnemies(); //цикл команд в выбранном сценарии for (var h = 0; h < targetlist.length; h++) { var sub_command = targetlist[h].split(':'); //команда - перемещение (move) if (sub_command[0] == "move") { if (Math.abs(f.pos.x - Number(sub_command[1])) < 1 && Math.abs(f.pos.y - Number(sub_command[2])) < 1) { tar[j].targers[h] = ""; continue; } else { this.command(f, "move", {'x': Number(sub_command[1]), 'y': Number(sub_command[2])}); enFound = true; break; } } //использовать щит if (targetlist[h] == "shield" && en.length > 0) { this.command(f, "shield"); enFound = true; break; } //иначе перебор врагов for (var g = 0; g < en.length; g++) { if (String(en[g]) == targetlist[h]) { if (en[g].health <= 0) { //удалить команду - враг мертв tar[j].targers[h] = ""; enFound = true; break; } if (en[g].health > 0) { this.command(f, "attack", en[g]); enFound = true; break; } } } if (enFound) break; } //если действия по сценарию закончились if (!enFound) { var e = f.findNearest(en); if (e) this.command(f, "attack", e); else this.command(f, "move", {'x': 78, 'y': 40}); } } }; //"правильная" атака главный героем this.heroAttack = function(en) { if (en) { //используем навык прыжка if (this.distanceTo(en) > 15 && this.isReady("jump")) { this.jumpTo(en); //двигаемся к цели } else if (this.distanceTo(en) > 8) { this.move(en.pos); //атакуем } else this.attack(en); } }; //основной цикл loop { var enemies = this.findByType("brawler"); //пока браулеры живы - пытаемся их подставить под удар катапульт if (enemies.length > 0) { var m = this.findEnemyMissiles(); if ((m.length > 0 && this.distanceTo(m[0]) < 15 && m[0].z < 5 ) || (this.distanceTo(enemies[0]) < 15) ) { this.move({'x': this.pos.x - 15, 'y': this.pos.y + 10}); } else { this.move({'x': 41, 'y': 15}); } } else { //расправляемся с катапультами var enemy = this.findNearest(this.findEnemies()); if (enemy) this.heroAttack(enemy); else this.move({'x': 78, 'y': 14}); } //командуем союзниками this.comm(); } |
К слову надо сказать, что один из воинов у меня дохнет, совсем чуть-чуть не успевая получить лечение от паладина. Так что сценарий нуждается в небольшой коррекции.
Прошел я этот уровень на все задания. Причем за героя вообще элементарно… ему достаточно было просто бежать по прямой до точки — катапульты сами всех (и себя) поубивали
А вот с юнитами пришлось повозиться. После кучи разных последовательностей действий и наблюдений, что делают враги, такой порядок:
Лучники атакуют ведьму, потом Трогдора, Расти и Скулли
Паладин пытается лечить, если не выходит, то атакует Трогдора, Расти и Скулли
Солдаты атакуют ведьму, а когда она умирает — бегут на точку, уводя за собой Они
В моем плане было потом добивать Они (паладин и лучники с остатками успешно справляются в таком раскладе). Но оказалось, что задание засчитывается, когда солдаты добегают до точки… что собственно даже соответствует условию: там сказано, что хотя бы один союзник должен сбежать, что и происходит.
Давно это было. Сейчас на leetcode развлекаюсь. :)