(((S (K (S ((S (K S)) ((S (K (S I))) K))))) ((S (K K)) K)) awesome! was ICFPC)
ICFP Contest 2011 точно войдет в список лучших ICFP контестов всех времен. Причина тому - интересное непутанное задание, которое при этом довольно сложное и хорошо продуманное. Я, как и в прошлом году, принимал участие в составе команды THIRTEEN. Отчёты от команды можно найти на сайте
http://thirteen.kharkov.ru(сейчас там только один
отчёт). Здесь постараюсь дать описание контеста со своей точки зрения.
Для меня контест начинался вдали от команды(я находился в командировке в Лондоне). Прочтение задания вызвало бурную радость(я как раз готовился к контесту написанием парсера и эвалуатора для лямбда-калькулюса). Из-за удаленности от команды я решил код не писать, а сосредоточиться на изучении того, что мы можем сделать с помощью карт. Это оказалось правильным решением - мои коллеги написали всё намного быстрее и лучше, чем с моим участием :) Первая ночь прошла в попытках использовать Y комбинатор для создания рекурсивных функций. К сожалению, strict evaluation не давал этого сделать. И даже Y комбинаторы для applicative order не работали. Также не получалось и организовать рекурсию через SII. Утром пятницы я отправился на работу, но потратил большую часть рабочего времени на исследование системы. Результатом этого стала "недорекурсия" через get ячейки. А по окончанию рабочего дня пришло лучшее понимание того, как работает вычисление в предложенной системе, и это дало возможность вывести настоящую рекурсию по аналогии с выводом Y комбинатора. Это позволило писать рекурсивные функции, пробегающие по всем ячейкам. К счастью, организаторы ограничили количество аппликаций одной тысячей(к счастью - потому что увеличение количества аппликаций до 3839 позволило бы уничтожать противника где-то на 85 ходу). И из-за этого ограничения рекурсивные функции пробегались только по нескольким ячейкам и самоуничтожались после применения.
К этому моменту в Харькове был написан конвертер из лямбда выражений в SKI выражения и конвертер из SKI выражений в программы. В ночь с пятницы на субботу я пытался оптимизировать конвертер и генерировать с его помощью выражения, вызывающие рекурсивный обход ячеек. Результат: больше 89 ячеек за один запуск для односложных команд при помощи настоящей рекурсии не обойти. Ночью же вылетел в Харьков(и наконец выспался в самолете). По прилету обнаружил, что коллеги вовсю думают над однотактными и двухтактными пушками. Идея была в том, чтобы создать пушку, которая будет уничтожать вражескую ячейку и восстанавливать после этого vitality. Двухтактная пушка перенаправлялась на следующую ячейку двумя тактами, однотактная(гениальное изобретение Sas/Rst) - одним. К вечеру пришло осознание того, что нам нужно писать хитрого стратега, который будет решать, какие выражения применять. San создал инфраструктуру для написания ботов-стратегов, и к этому моменту был написан язык для описания действий(by San/Dzhulgakov). Я продолжал в душе лелеять надежду на использование рекурсивных выражений и начал создавать ботов и стравливать их в турнире. Конечным результатом этого процесса к 10 утра воскресенья стал SuicideBot(убийца-камикадзе) и, главное, ZombaBot - бот, который за ~150 ходов уничтожал ~40 ячеек противника, а еще за ~90 ходов стирал всю информацию из них. Начальная стратегия использовала настоящую рекурсию(синтаксис языка не приводится):
power@0 = 8192;
@1 = (S (attack 0 0) (attack 1 0) (get 0));
pois@2 = ^x. (help x x @power);
part@3 = ^n.^f. (f (S @pois succ n) f);
part_func@4 = ^y. (@part (y 0) ) @part;
@5 = (dec 0) (zombie 0 @part_func);
newpois@6 = ^xx. (zombie xx I);
newpart@7 = ^nn.^ff. (ff (S @newpois succ nn) ff);
newpart_func@8 = (@newpart 219) @newpart;
Также в это время формируется SanBot(by San) - консервативный бот, который умеет защищаться, уничтожая "горячие" ячейки противника. В свободное время он собирает более быстрые атакующие программы и переключается на них. San постоянно развивал своего бота, тренируясь на кошках - на SuicideBot и ZombaBot. В схватках с ZombaBot он научился закрывать порталы для зомби, а по дороге научился воскрешаться, восстанавливать команды из кеша, красть константы. ZombaBot тоже не стоял на месте. Sas подхватил идею с использованием copy для организации рекурсии и придумал, как с ее помощью сделать обход ячеек. В конечном итоге, этот способ и оказался самым эффективным способом обхода. Он дал возможность ZombaBotу полностью уничтожать противника за 4 запуска рекурсивного зомби, за 198 тактов. К этому времени submissionы SanBotа показали, что он слишком консервативен, и поэтому не столь эффективен. Воспользовавшись наработками, я добавил еще и UndeadRiseBotа - бота, который пытался уничтожить противника за 198 ходов, а после этого переключался на SanBotа. Этот бот оказался весьма эффективным - он крошил противников на неофициальном сервере и даже побеждал самого SanBotа, потому что использовал его же механизмы лечения. Мы продолжали развивать эту идею и оптимизировать код. Результат(язык получился довольно мощный и выразительный, а к концу контеста Dzhulgakov добавил в него и средства для ручной оптимизации):
power@0 = 8192;
@24 = (attack 0 0) (get 0);
@24 = (attack 1 0) (get 0);
pois@1 = ^x. (help x x @power);
!clear @0;
part@0 = \x. ((@pois x) (copy (K 0 x)) (succ x));
!clear @1;
part_func@1 = ^z.^y. ((get 0) (y z));
@128 = (S zombie @part_func);
// optional dec 0; here is done by UndeadRiseBot
@128 += @@ 0;
address@2 = 66;
@129 ~ zombie1 = (zombie 0 (@part_func @address));
// optional dec 0; here is done by UndeadRiseBot
!continue zombie1;
@2 += dbl @@;
@130 ~ zombie2 = (zombie 0 (@part_func @address));
// optional dec 0; here is done by UndeadRiseBot
!continue zombie2;
!clear @2;
@2 = 198;
@131 ~ zombie3 = (zombie 0 (@part_func @address));
// optional dec 0; here is done by UndeadRiseBot
!continue zombie3;
Этот вариант уничтожал противника за 177 ходов. В копилке наших знаний есть еще парочка оптимизаций, которые должны уменьшить код до 171-172 ходов. Хочется верить, что это минимальная программа, которая уничтожает безвольного противника.
Всё это время Sas собирал своего бота - SasBot, который использовал супероружие - зомбопушку (убить+стереть). Он добился того, что своей пушкой уничтожал и SanBotа, и UndeadRiseBotа. Но, к сожалению, он был готов слишком поздно и не очень хорошо справлялся с реальными противниками на официальном сервере, поэтому в последний момент было решено сабмитить UndeadRiseBota, который в душе - SanBot.
Выводы по окончанию контеста: по результатам битв на неофициальном сервере видно, что наш бот неплох, но далеко не лучший. Что особенно интересно - есть сильные боты, которые используют сходную с нами тактику, но при этом побеждают нас. Это значит, что мы просто не отладили бота окончательно. Еще стоит пожалеть о судьбе SasBotа. Он был готов слишком поздно, чтобы его достижения использовать в SanBot/UndeadRiseBot, и не был готов к submission самостоятельно. Основной вывод - нужно было начинать создавать ботов и описывать стратегии раньше. Тогда к концу контеста у нас бы было лучшее понимание того, какая тактика оптимальна.
Как я уже говорил выше - один из лучших ICFP contests за всё время, что я о нём знаю. Спасибо организаторам - великолепное задание и отличная организация. Спасибо каждому из команды THIRTEEN. Мне кажется, мы отлично сыгрались и можем и будем таким составом выигрывать :)