Каждый год команды SANS и Counter Hack Challenges проводят мое любимое соревнование по захвату флага (CTF) — SANS Holiday Hack Challenge. 2020 SANS Holiday Hack Challenge с участием KringleCon 3: French Hens! проходил в недавно отреставрированном замке Санты на Северном полюсе с 10 декабря 2020 года по 11 января 2021 года. Это пошаговое руководство по завершающему этапу мероприятия.

В столовой замка Санта-Клауса я встретил Рибба Бонбоуфорда, который познакомил меня с терминальным испытанием «Концепции программирования». Эта аркадная игра, созданная в духе старой школы, была призвана освежить ваши навыки работы с JavaScript, проходя через каждый вход в Kringlecon и собирая леденцы на палочке, украденные озорными манчкинами.

Для начала Рибб дал несколько советов. У каждого уровня были свои ограничения на количество строк кода и количество вызовов объекта elf, которые я мог сделать, что требовало от меня написания максимально эффективного кода. Каждый из 6 уровней, необходимых для выполнения задачи, описан ниже.

1-й уровень

На уровне 1 мне нужно было собрать только один леденец. Я мог использовать не более 2 строк кода и не более 2 вызовов эльфов.

Мое решение уровня 1 было простым и понятным.

elf.moveLeft(10) 
elf.moveUp(10)

Уровень 2

Уровень 2 стал более сложным. Мне по-прежнему нужно было подобрать только один леденец, но у меня также было препятствие, которое я должен был преодолеть. На моем пути был рычаг, который нужно было решить, прежде чем я смог добраться до леденца. Цель этого рычага указана ниже. Я мог использовать не более 5 строк кода и не более 5 вызовов эльфов.

Lever Objective: Add 2 to the returned numeric value of running the function elf.get_lever(0).

Мое решение уровня 2 указано здесь и описано ниже.

elf.moveLeft(6) 
elf.pull_lever(elf.get_lever(0) + 2) 
elf.moveLeft(4)
elf.moveUp(10)

В первой строке кода я перемещаюсь влево на 6 делений, что приводит моего эльфа к месту, занятому рычагом. Нахождение в пространстве, занятом рычагом, подготовило меня ко второй строке кода, поскольку elf.pull_lever() может быть вызвано только тогда, когда оно находится в том же месте, что и рычаг.

Во второй строке кода я вызываю elf.get_lever() для объекта рычага 0. Поскольку моей целью было добавить 2 к значению, возвращаемому рычагом, я добавил 2 и сразу же передал это в elf.pull_lever(). После того, как рычаг был успешно решен, я смог продолжить движение к входу в Кринглекон, подобрав по пути леденец.

Уровень 3

На уровне 3 у меня было 3 леденца на палочке, разбросанных по карте. Кроме того, я мог использовать не более 4 строк кода и не более 4 вызовов эльфов. Использование команд elf.move, как я делал раньше, потребовало бы гораздо большего количества строк кода и вызовов эльфов, чем мне было позволено, что вынуждало меня использовать другой подход.

Чтобы завершить уровень 3, я более подробно изучил доступные эльфийские вызовы и нашел один под названием elf.moveTo(), который позволял вам перемещаться непосредственно к объекту, на который ссылается его индекс. Например, если бы я хотел сразу перейти к леденцу с индексом 2, я мог бы вызвать elf.moveTo(lollipop[2]). Я использовал это, чтобы пройти уровень 3, двигаясь последовательно к каждому леденцу, последний из которых находился у входа. Чтобы уменьшить количество строк и количество вызовов elf, я решил использовать цикл for для выполнения каждого вызова elf.moveTo(), поскольку менялся только индекс леденца. Код показан ниже.

for (i = 0; i < 3; i++) { 
    elf.moveTo(lollipop[i])
} 
elf.moveUp(1)

Уровень 4

На уровне 4 мне нужно было подобрать только один леденец, но у меня было много препятствий в виде бочек, которые ограничивали путь, по которому я мог добраться до леденца. Кроме того, я мог использовать не более 7 строк кода и не более 6 вызовов эльфов.

На этом уровне я не мог использовать вызов elf.moveTo() для перехода непосредственно к леденцу, потому что эта операция может сделать только 2 движения, одно в направлении x и одно в направлении y, чтобы добраться до нужного пункта назначения. Этот лабиринт занял бы гораздо больше, чем 2 хода, так что elf.moveTo() не вариант. Вместо этого я снова вернулся к циклам.

Прокладывая путь, по которому мне нужно было идти, я понял, что мне нужно будет идти немного влево, много вверх, немного влево и много вниз, а затем я повторю эту схему еще раз, чтобы пройти через лабиринт. Это казалось отличной возможностью использовать цикл for для повторения действий. Я понял, что могу повторить эту последовательность 3 раза, чтобы добраться от конца лабиринта до входа, так как это требовало от меня идти немного влево и много вверх. Я также смог использовать цикл for, потому что бочки мешали мне идти дальше, чем я хотел. Например, я мог бы использовать код elf.moveLeft(3) в самом начале головоломки, но все равно переместился бы только на одну клетку из-за препятствия. Таким образом, при указании, на сколько клеток мне нужно переместиться, я указал максимальное количество клеток, которое моему эльфу потребуется пройти при любом движении вверх, влево или вниз. Код показан ниже.

for (i = 0; i < 3; i++) { 
    elf.moveLeft(3)
    elf.moveUp(11) 
    elf.moveLeft(3) 
    elf.moveDown(11)
}

Уровень 5

На 5 уровне мне нужно было собрать два леденца на палочке и победить своего первого манчкина, используя не более 10 строк кода и 5 призывов эльфов. Неспособность превратить враждебного манчкина в дружелюбного приведет к тому, что вы потеряете уровень, несмотря на то, сколько леденцов вы собрали.

Чтобы сделать манчкина дружелюбным, вы должны выполнить вызов JavaScript, сначала запросив вызов, используя вызов elf.ask_munch(), чтобы запросить данные, а затем ответив вызовом elf.tell_munch(), чтобы предоставить правильный ответ. Для уровня 5 задача манчкина требовала возврата массива, содержащего только числа, которые были в исходном массиве манчкина. Например, если вызов elf.ask_munch(0) вернул массив [1, 3, "a", "b", 4], то мне пришлось бы вернуть elf.tell_munch([1, 3, 4]), чтобы успешно решить задачу.

Чтобы решить этот уровень, я сначала попросил манчкина решить задачу и создал свой массив результатов. Для этого я сохранил предоставленный манчкином массив в переменной и создал свой результирующий массив. Затем я перебрал каждый объект в предоставленном манчкином массиве, чтобы проверить, не является ли значение числом, используя функцию Number.isNan() и попытавшись сначала преобразовать значение из массива в числовой объект. Если результат был ложным, что указывало на то, что значение является числом, я добавлял его в свой массив результатов. Затем я использовал функцию elf.moveTo(), чтобы перейти непосредственно к манчкину. Судя по опыту использования этой команды на предыдущих уровнях, казалось, что она движется в направлении y, а затем в направлении x. К счастью, мои леденцы были в тех самых местах, где я должен был идти по этому пути, поэтому я смог использовать этот единственный вызов, чтобы собрать оба леденца и заставить манчкина ответить на его вызов. Я дал манчкину свой результат, сделав его дружелюбным, а затем направился ко входу. Код ниже.

var munch_data = elf.ask_munch(0) 
var result = []
for (i = 0; i < munch_data.length; i++) {
    if (!Number.isNaN(Number(munch_data[i]))) {
        result.push(munch_data[i]) 
    }
} 
elf.moveTo(munchkin[0]) 
elf.tell_munch(result) 
elf.moveUp(2)

Уровень 6

На уровне 6 мне нужно было собрать 4 леденца, пройти лабиринт и победить манчкина, чтобы добраться до входа, используя не более 15 строк кода и не более 7 призывов эльфов. На этом уровне мне нужно было принять решение. Я мог либо решить задачу манчкина напрямую, либо я мог пойти своим путем и решить задачу с рычагом, которая опустила бы мост через яму и заставила бы манчкина упасть в яму.

Сначала я прошел лабиринт, чтобы собрать леденцы. Поскольку каждый из леденцов был разделен одним перемещением x и одним перемещением y, я смог использовать вызов функции elf.moveTo() в цикле for для перехода к каждому леденцу.

Я решил выполнить задание с рычагом, поэтому снова использовал функцию elf.moveTo(), чтобы перейти к рычагу. Эта задача с рычагом предоставила массив, и моя задача состояла в том, чтобы вернуть массив со строкой правило манчкинов, вставленной в начало массива. Например, если бы рычаг дал мне массив [1, 2, 3, "c", "d", 4], я бы вернул массив ["munchkins rule", 1, 2, 3, "c", "d", 4]. Чтобы завершить это, я использовал функцию JavaScript splice, чтобы вставить строку “munchkins rule” в индекс 0 в массиве. Кроме того, указав второй 0 в вызове функции, я указал, что не хочу, чтобы какие-либо из существующих элементов удалялись из массива. Разобравшись с рычагом, я направился к манчкину, но к тому времени, когда я туда добрался, он уже упал в яму. Затем я двинулся ко входу, чтобы решить задачу. Код ниже.

for (i = 0; i < 4; i++) { 
    elf.moveTo(lollipop[i])
}
elf.moveTo(lever[0])
var lever_data = elf.get_lever(0) 
lever_data.splice(0, 0, "munchkins rule") 
elf.pull_lever(lever_data) 
elf.moveTo(munchkin[0])
elf.moveUp(2)

И так далее….

После прохождения 6-го уровня я решил последнюю задачу. Тем не менее, есть еще несколько уровней, доступных для продолжения проверки ваших знаний JavaScript. Если вы решили какую-либо из них, прокомментируйте свое решение ниже!

Хотите узнать больше о конкурсе SANS Holiday Hack Challenge 2020? Ознакомьтесь с другими моими пошаговыми руководствами, доступными здесь.