Каждый год команды 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? Ознакомьтесь с другими моими пошаговыми руководствами, доступными здесь.