satyr

А вот, например, вакансия.

У нас же здесь явно могут оказаться релевантные люди, кому это может оказаться интересным. Короче, образовалась вот такая вакансия в нашей конторе: нужен человек, которому я смог бы передать один из своих текущих проектов. Там никакого особенного рокетсаенса, но проект нужный, важный, и ответственный.

От кандидата требуется:
  • Знание, или устойчивое желание и возможность освоить Erlang + OTP.
  • Знание основных паттернов проектирования систем обмена сообщениями.
  • Опыт сетевого программирования.
  • Навыки работы с linux.

Плюсами будут:
  • Опыт проектирования и разработки высоконагруженных сервисов.
  • Опыт работы с большими объёмами данных.
  • Знание функциональной парадигмы программирования.
  • Знание языков (или желание освоить) Elm и Rust.
  • Опыт работы с:
    • RabbitMQ
    • Apache Cassandra
    • Redis
    • cowboy http server
  • Навыки работы с freebsd.

Работа на полный рабочий день: по желанию либо в офисе (Москва, ул. Верейская), либо удалённо, с регулярным посещением офиса для совещаний. Белая зп, 130-250к (вилка большая, крутому и матёрому спецу тоже будем рады).

Если интересна вакансия, пишите мне на почту (с резюме в аттаче), если знаете кого-то, кому может быть она интересна — рассказывайте! Общие вопросы пишите лучше сюда в комменты, чтобы мне не копипастить по письмам ответы.
satyr

Не заметил слона

Я периодически на досуге стараюсь решать всяческие программистские микро-задачки, на которые набредаю в интернете. Чисто для поддержания минимальной формы, чтобы к icfcp мозг не успел засохнуть.

На этот раз обнаружил вот такую: http://wunderfund.io/dev_job — это типа тестовая задача для вакансии разработчика в какой-то мутной конторе.

Ну я, типа, скачал данные, повозился достаточно продолжительное время с решением (мне вообще туго даются задачки олимпиадного типа, которые решаются через динамическое программирование), расчитал ответ, сабмичу — хуй. Думаю: наверно чё-то упустил в задании. Всё внимательно перечитываю, да нет, вроде всё верно. Рефрешу страницу, перекачиваю данные (мож изменились), пересчитываю заново, сабмичу — опять хуй.

Тут меня уже, надо сказать, одолела злоба. Особенно подбешивало описание задачи в разделе вакансии: "Если ты нам подходишь, решение задачки не составит для тебя труда." :) А тут, получается, что решение для меня составляет труд, и моя кандидатура не подходит на вакансию какого-то рядового плюсового разработчика, например.

Ну ладно. Потратил дополнительное время, нагенерил рандомных небольших тесткейсов и сравнил результаты с эталонами, которые получил из брутфорсной реализации. Всё верно же. Ок, опять рефрешу страницу, сабмичу результат — опять хуй.

Ну ок, в итоге совсем забиваю (и так дофига времени потратил) и, в расстроенных чувствах, ухожу спать.

А сегодня меня мне вдруг в голову пришла мысль. Я её проверил, и мои самые страшные подозрения подтвердились!

Короче, дело было так:

Я скачиваю .csv-шку с сайта и открываю её штатным маковским Numbers. Вижу примерно такое:



Ну хорошо, думаю, мак глупый, хочет в качестве разделителя столбцов точку с запятой согласно локали, а там просто запятая. Ну там данные простые, поэтому я не мучаюсь с форматированием, а просто тупо беру и забиваю в коде в данные всё, что после запятой (типа, второй столбец — 7, 688, 682 и тд).

Сегодня до меня дошло посмотреть в исходник .csv-шки. Оказывается, это нихрена не 7 и 688, а 7000 и 6880 =) А табличный процессор терминирующие нули радостно откусил, потому что, согласно тем же настройкам локали, запятая является разделителем дробной части числа. И "0,7000" оказалось не двумя столбцами с "0" и "7000", а одним столбцом с числом "0.7".

Короче, с правильными данными результат у меня-таки сошёлся. Но самое обидное даже не в этом, а в том, что с этими данными никакой код можно было вообще не писать — достаточно просто на них пристально посмотреть и воспользоваться калькулятором :(
satyr

Медицинская карта

Тыщу лет уже не был в районной поликлинике. Вроде пока особо ничем не болею, поэтому по мелким рандомным поводам предпочитаю посещать платную, чисто чтобы время сэкономить. А там у них всех компы, и медицинская карта в электронном виде, в бд. И за пару лет у меня накопилась там полноценная история болезни: все визиты врачей, со всеми жалобами, и тд.

Выглядит эта история примерно так:
  1. Всего три обращения к врачам.
  2. Все три обращения — к хирургу.
  3. Список жалоб:
    • Укус котом.
    • Укус котом.
    • Укус котом.

В примерно таком виде:


Я полагаю, там уже заподозрили неладное.
satyr

Новые маршруты

У меня тут офис переехал, соответственно, пришлось искать новую дорогу, чтобы доехать на велике.

Раньше было вот так:



Почти весь маршрут получался по паркам и набережным, всегда везде почищено, где-то на 2/3 пути с велодорожками. Порядка 13 км, по времени минут 45, что быстрее метро и автомобиля (если включать время на поиск парковки), но медленнее мотоцикла (рекорд был 9 минут по пробкам).

А щас контора переехала в противоположный угол Москвы, и сегодня я типа ездил на разведку. Надо сказать, что этот район вообще какой-то адовый в плане транспортной связности, и какой-то вменяемый маршрут построить крайне проблематично, особенно, если пытаться избегать магистралей и прочих многолюдных мест.

По-факту вышло пока как-то так:



Это даже меньше по расстоянию, чем было (~10 км), но тупо вдвое дольше — около полутора часов. Дофига отрезков, где приходится ехать медленно. В районе матвеевского (там где долина реки Раменки) по нынешней погоде ваще только пешком. Много мест, где приходится форсировать сугробы. Короче, надо ещё разбираться, пока ничего не понятно :)

satyr

Смертоносные свиньи жидкого герметизатора

Подумалось чёто про бигдату. Гугл, например, ходит и прилежно индексирует интернет, в котором, благодаря нашим друзьям сеошникам и прочим спамерам, процентов 50 текстов выглядит как-то так:



Ну как бы ладно, благодаря поведенческим факторам, поисковики в выдаче научились подобный мусор не показывать. Но ведь потом по этим данным ходит разная бигдата, тщательно подсчитывая статистику нграмм, строя всяческие индексы, подготавливая данные для автоматических переводчиков, пейджранков и тд. Насколько бы продуктивней всё это работало бы в чистом интернете?

Вот, к примеру, очень многие пытаются побигдатить твиттер. Там же чудовищный поток данных, сотни миллионов живых активных пользователей, терабайты твитов в сутки, всё открыто, ходи и забирай по официальному API. Народ пытается предсказывать курсы акций и валют, очередные оранжевые революции, какие-то тренды, чёрти что. Но блин эти данные настолько катастрофически низкого качества, что что-то толком намайнить по ним невозможно.

Основная масса потока, это разнообразный спам и другие роботы, а львиная часть остального — твиты, вида: "@LAWBDM пожалуйста *осторожно погладила по голове*" и "ну почему почему напиши плес пожалуйста я же писала напиши веду себя как телка господи аааа*". Можно, конечно, майнить по конкретным аккаунтам, но выборка получается очень смещённая, ничего по этому предсказать нельзя.

Похоже, что у нас в 21 веке появилось много полезных инструментов для бигдаты, но вот самой даты в интернете что-то нет. Наверно, единственно полезное её применение — это обработка научных данных (с телескопов там, датчиков/зондов и тд).
satyr

Post rollback

Собирал давеча chromium из исходников. На этапе линковки ld выжрал 14 гигабайт оперативки и породил бинарь аж на шесть гигабайт :)

Я всё ходил, сочинял какой именно мне пост по этому поводу сделать (полный праведного возмущения и остроумного сарказма, разумеется), но потом выяснилось, что я собирался с дебажной информацией (ключом "-g"), и без него всё оказывается в рамках приличия.

Так что расходимся.
satyr

Moscow Metal Meeting

Посетил в субботу сабж. Торжественно заявляю, что у нас, походу, научились, наконец, организовывать мероприятия такого уровня. Ну, по крайней мере, лично я впервые вижу опенейр аж на шесть команд вообще без всяких накладок: тайминг, расписание, очереди, жратва/пиво, мерчендайз, сортиры — с этим всегда у нас были традиционные проблемы. Где-то больше, где-то меньше, но щас как-то ваще всё настолько ровно прошло, как будто в европу понаехал :)

Безусловно, такая динамика не может не радовать.
satyr

Ещё об обработке ошибок в Rust

С обработкой ошибок в Rust надо явно что-то придумывать. Мне категорически нравится, что в языке нет исключений, но без синтаксического сахара вроде хаскелевской конструкции do код с аккуратной обработкой ошибок визуально превращается в простыню с перекладыванием значений из одних типов в другие.

Давайте рассмотрим процесс на простом примере: допустим, бинарю на вход подаётся (или не подаётся) параметр такого вида:

~ $ ./a.out --sample 2,3,4.5,6.0,7

Соответственно, если всё хорошо, в программе мы хотим получить какой-то вектор действительных чисел.

Вроде всё предельно просто, что может пойти не так? Если отсутствие параметра — это штатная ситуация, то вариантов ошибок не так много: по-сути, только одна: кривые данные на входе, которые нельзя распарсить в число.

Итак, getopts вернул нам Option<String>, как нам получить из него Vec<f64>? Очевидно, что можно гопническим методом:

fn parse_sample_v0(param: Option<String>) -> Option<Vec<f64>> {
    param.map(|values| values.split(',').map(|v| v.parse().unwrap()).collect())
}

Понятно, что программа вылетит в panic на unwrap, если на вход подать мусор. Но есть множество случаев, когда это вполне допустимо (тем более, при панике будет какая-никакая диагностика, из которой можно понять причину проблемы). Удобно при прототипировании, или при разработке небольших утилит.

Однако, такой подход уже недопустим когда надо сделать библиотеку, или в других случаях, когда нельзя валить программу. Какая тогда должна быть правильная сигнатура? Вероятно, какая-то такая:

#[derive(Debug)]
enum ParamError {
    SampleValue(String, ParseFloatError),
}

fn parse_sample_v1(param: Option<String>) -> Result<Option<Vec<f64>>, ParamError> {

Тоесть результат выполнения функции может быть один из следующих вариантов:
  • Успех: результата нет (параметр не был задан).
  • Успех: результат Vec<f64> есть (параметр задан).
  • Ошибка: параметр задан, но не может быть распаршен.
    • В этом случае надо пробросить наверх информацию о том, какое именно число в векторе не распарсилось, и ошибку из метора parse.

Реализация функции с такой сигнатурой "в лоб" приводит к достаточно развесистому дереву условий:

fn parse_sample_v1(param: Option<String>) -> Result<Option<Vec<f64>>, ParamError> {
    if let Some(values) = param {
        let mut result = Vec::new();
        for value in values.split(',') {
            match value.parse() {
                Ok(v) => 
                    result.push(v),
                Err(e) =>
                    return Err(ParamError::SampleValue(value.to_owned(), e)),
            }
        }
        Ok(Some(result))
    } else {
        Ok(None)
    }
}

Понятно, что постоянно писать такие простыни все быстро задоблаются (их же надо повторять на всех уровнях стека для аккуратной обработки ошибок), поэтому в стандартной библиотеке есть какое-то количество инструментов, уменьшающих размер кода.

Воспользуемся реализацией FromIterator для Result, и всяким около-монадическим добром типа Result::map:

fn parse_sample_v2(param: Option<String>) -> Result<Option<Vec<f64>>, ParamError> {
    param.map(|value| value.split(',')
              .map(|value| value.parse().map_err(|e| ParamError::SampleValue(value.to_owned(), e)))
              .collect::<Result<_, _>>())
        .map(|r| r.map(|values| Some(values)))
        .unwrap_or(Ok(None))
}

Как переписать ещё короче, я не знаю.

И, хотя код здесь сократился в три раза, всё равно получается плохо. Потому что, по-сути, в этом сниппете из пяти строк кода, значимых реально две. А последние три суть церемониальный код, не делающий ничего, кроме переоборачивания типов в другие типы. Причём, с высокой долей вероятности, весь этот код будет тупо выкинут транслятором при инлайнинге, и весь его смысл чисто формальный: преобразовать один ADT в другой, путём приписывания пары конструкторов. Я, конечно, не уверен, но у меня есть смутное ощущение, что компилятор в силах с этим справиться сам :)

Как здесь сделать правильно — я не знаю. Без поддежки HKT в языке не хватает абстракции, чтобы сделать "красивые" монады, а без них непонятно как правильно реализовать сахар типа do. В языке существует ещё механизм из синергии макроса try! с трейтом-преобразователем From, для конвертации одних типов ошибок в другие. Но он реально упрощает только случаи с "плоским" кодом (в пределах одной функции с явными циклами), но как только у нас появляются замыкания (цепи итераторов или модификация внутри деревьев Option/Result), вариант с try! уже не помогает.

Просто я к чему: люди уже сильно избалованы подходом, когда можно тупо писать happy path, и вообще класть болт на обработку ошибок. Но для этого нужны исключения, а для них нужен рантайм и сборщик мусора, от чего в Rust как раз пытаются уйти. Я не знаю, правильно ли будет активно перенимать опыт из хаскеля (насколько я знаю, там аккуратно обрабатывать ошибки тоже не очень любят ;)) — не факт, что для нефункционального языка это получится удобно. Скорее всего, нужно просто брать текущую систему, и специальными расширениями в самом языке уменьшать количество церемониального кода, которое требуется сейчас для корректной обработки ошибок. Например, внести какую-нибудь волшебную директиву "удовлетворить компилятор", которая сама преобразует заданный тип в такой, который нужен согласно сигнатуре :)
satyr

ICFP 2015 ((ктулху фтагн))

Ну что, успешно упоролись свежим icfpc в этом году, спасибо всей команде за участие, было весело!
Репо солюшна вот здесь: https://bitbucket.org/skobochka/icfpc-2015/overview

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

Как обычно, пару дней до соревнования все безуспешно пытались угадать к чему надо готовиться, но это каждый раз пропащее дело. В этот раз, согласно всем имеющимся в наличии намёкам, я предположил, что будет что-то из серии: "учёные, пытаясь предотвратить гибель пчёл в стране, что-то набедокурили со своими программами для квантового суперкомпьютера и пробудили древнее зло".

В итоге, историю я фактически угадал, но вспоминать криптографию и программировать квантовые компьютеры было не надо.

Собственно, задачу копипастить мне лень, надо читать введение от оргов (оно стоит того) и спеку (всё кратко и понятно).

А развёрнутая ретроспектива событий получилась какая-то такая (восстановлено по логам конфы):

Пятница.


  • За пять минут до старта контеста я ушёл к стоматологу, и задание пытался читать краем глаза уже из-под бор-машины :)

  • Вернулся через час, все участники пока осознавали правила. Но sectoid уже освоил самбишн решений.

  • Ещё через полчаса я решил развести бюрократию в виде плана действий в корне проекта. Чтоб типа всё как у взрослых: разные подзадачи, там, каждый выбирает себе что-то и закрепляет за собой. Изначальная идея была в том, что в команде уже 5 человек, поэтому нужна какая-то минимальная координация.

  • Но через некоторое время актуальность предыдущего пункта резко упала, потому что grepz снялся с конкурса по уважительным причинам, и нас осталось четверо.

  • Далее наступил какой-то ад: уже через полтора часа после начала контеста на доске почёта какие-то команды стали зарабатывать по 600000 очков и выше. Меня в этот момент начала мучать лёгкая паника.

  • turtle отправился делать генератор случайных чисел.

  • sectoid и jtootf оказались знатоками Лавкрафта, и моментально нашли фразу власти из 51 символа: Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn! Лол, как выяснилось в последний день, вместо восклицательного знака нужно было ставить точку :)

  • 17:30 — мы с turtle всё ещё мучаем ГСЧ.

  • Доску почёта продолжает нещадно колбасить -- орги, как обычно, правят баги "по-живому". Кому-то снимают очки, у кого-то, наоборот, они рандомно зарабатываются сотнями тысяч.

  • 18:15 — sectoid принёс нам первые очки вручную сфабрикованным решением.

  • 18:45 — у нас всё ещё нет никакого кода, мы лениво прикидываем, кто чем бы хотел заняться.

  • 19:10 — всё ещё никакого кода, но jtootf нашёл какой-то симулятор гексо-тетриса на тикле, и решил на его основе сделать визуализатор.

  • 19:30 — появился первый код: sectoid написал сабмитилку, а я сделал базовый скелет проекта, структуры данных и загружалку входных данных.

  • План на лайтнинг я на тот момент видел так: надо строго сделать всю сопутствующую мелочёвку (карту, фигуры, координаты, движение / вращение, проверка валидности всех положений, спавн новых фигур), написать какой-нибудь колхозный брутфорс для нахождения места, куда надо уложить появляющуюся на поле фигуру, и реализовать базовый A* для построения скрипта команд с какой-нибудь простенькой эвристикой. Ну и какой-никакой транслятор команд.

  • Далее задачи распределилсь так: sectoid отправился реализовать движение и вращение фигур, а я всё остальное :) turtle допиливал ГСЧ, а jtootf взялся сделать визуализацию.

  • 20:10 — орги, наконец-то, справились со своей доской подсчёта, и она стала выглядеть по-человечьи, без многосоттысячных результатов у каких-то рандомных бомжей.

  • Решаем с sectoid'ом в трансляторе команд реализовать микрооптимизацию: если вдруг каким-то магическим образом собралась последовательность, соответствующая заклинанию, то заменять её на заклинание (мощная идея, правда?).

  • 21:00 — орги пригрозили, что любые команды в скрипте, которые воспоследуют после того, как игра будет закончена (на карте не останется валидных мувов), то решение аннулируется с нулём очков. Так вот, куда пропали все лидеры из рейтинга ;)

  • turtle отправился писать Makefile для проекта, а я к этому моменту изобразил какой-то нищебродский A*. Проблема была в том, что пока не было механики вращения и движения фигурок, поэтому его не на чем было отлаживать. Поэтому я пошёл писать какое-то крайне позорное, но быстрое решение для поиска места, куда следует класть порождённую фигуру. "Стратегия" такая: запихнуть фигуру максимально низко в стакан, прижать максимально сильно к левому краю, повернув таким образом, чтобы справа осталось максимум места. Лол, конечно, но нужно было иметь хоть какую-то реализацию, нежели не иметь никакой.

  • 21:00 — jtootf тем временем спрограммировал на тикле какой-то рендерер гексагонального поля, и поехал домой.

  • То же самое порывался сделать sectoid, но потом передумал, решив сначала добить фигурки. Хех, как он ошибался.

  • Выясняется, что координатная сетка на поле не для щенков. Чётные ряды сдвинуты в право относительно нечётных, поэтому оперировать координатами в терминах (row, column) невероятно мучительно. Сами фигуры задаются при этом относительно своей локальной системы координат.

  • Я сходу порождаю очередную бомжацкую идею: давайте перейдём от описания фигуры в терминах пивот + списка координат её ячеек к такому виду: сдвигаем пивот в (0,0), а все ячейки кодируем последовательностями команд движения, которые надо выполнить, чтобы попасть в них. К счастью, меня не поддержали.

  • 23:00 — у нас всё ещё пока нихрена нет. Sectoid таки поехал домой.

  • Судя по лидерборду, народ где-то нашёл уже готовые тетрисовские солверы и адаптировал их под задачу. Опять приступ паники :)

  • 00:00 — sectoid приехал домой, и мы дальше взмыленно обсуждаем, как правильно быть с координатами.

  • 00:30 — всё ещё обсуждаем.

  • 00:45 — всё ещё обсуждаем.

  • 00:48 — turtle внезапно обнаруживает, а я внезапно читаю материал по этой ссылке, на которую sectoid уже несколько раз ссылался. Оказывается же можно использовать кубические координаты. Мало того, sectoid их уже использовал для поворота, лол.

  • Короче, переходим на кубические координаты. Жизнь снова заиграла красками. А для доступа к полю преобразовываем их обратно.

  • Бла-бла-бла.

  • 01:40 — готовы движения и повороты фигур! Я бросаюсь доводить A* и выбор цели для фигуры, turtle программирует сгорание рядов при заполнении, sectoid пошёл делать транслятор скрипта движений.

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

  • 03:20 — sectoid берёт таск про спавн фигур (согласно заданию, их надо центрировать), turtle делает бинарь с поддержкой cmd args. У меня в этот момент очередная тупая проблема с тем, как сравнивать две фигуры на доске на эквивалентность (сортировать и сравнивать список ячеек долго, хочется быстро).

  • 03:49 — jtootf напрограммировал базовый визуализатор, но пока непонятно как кормить его данными.

  • 04:23 — я спрограммировал брутфорсный выбор позиции (на отъебись, конечно, но как-то он заработал).

  • 04:30 — у sectoid тем временем затык со спавном: фигура никак не хочет правильно центрироваться.

  • Чем позже становится, тем всё происходящее происходит всё медленнее и медленнее :) На какие-то элементарные вещи тратится непонятная куча времени.

  • 05:00 — первый код в game-loop.

  • 05:27 — на правах местного оракула, произношу пророчество:

    • swizard: подозреваю, что щас будет как-то так:

    • swizard: мы сделаем решение, засабмитим, и получим 0 очков

    • swizard: и далее надо долго дебажить что не так


  • 05:35 — делаю первый пуш кода game-loop.

  • Внезапно обнаружил, что не хватает сжигания рядов. Вроде turtle делал.

  • 05:45 — сжигание рядов есть, но не работает. Быстренько пишу консольный визуализатор, чтобы хотя б видеть что происходит.

  • 06:00 — сжигание рядов всё ещё не работает.

  • Тем временем, jtootf находит на картах подсказки-заклинания: R'lyeh и Yuggoth.

  • 06:12 — сжигание рядов всё ещё не работает. Да что ж такое-то.

  • 06:24 — пока turtle ремонтирует сжигалку, я не выдержал и написал параллельную версию, чтобы общий процесс разблокировался, и можно было что-то делать дальше.

  • 06:24 — оказывается, sectoid тоже психанул и написал свою версию сжигателя рядов :) Итого у нас три имплементации одного и того же.

  • 06:37 — ура, есть наконец решатель! Натравливаем его на все доступные таски.

  • 06:48 — разумеется, баги. Эксепшны, выходы за границы массива, и прочая срань. Ремонтировать баги в семь утра тоже тот ещё кайф :)

  • 06:59 — победили, есть 800 жалких очков на первой карте! На остальных, правда, нуль, ну да и чёрт с ним. Зато можно с чистой совестью идти спать.

  • 07:10 -- делаю крестьянскую визуализацию на базе репла и (break) чтобы воочию понаблюдать перед сном за падающими фигурками. Наблюдаю, и иду спать.

Суббота


  • 11:45 — подъём =) Пробуем взять лайтнинг.

  • 11:55 — начинаем сеанс багфиксинга. Надо понять за что нам везде ставят нуль баллов.

  • 12:00 — я начинаю лихорадочно делать покадровый вывод игры, чтобы его можно было скормить визуализатору имени jtootf.

  • 12:26 — запушил возможность записи и вывода фильма в процессе игры. Прошу sectoid'а конвертнуть вывод в какой-нибудь json, чтобы jtootf мог его подхватить. Сам сел фиксить производительность солвера.

  • 12:38 — ускорил решение в три раза, сел дебажить решение глазами через свой крестьянский визуализатор, который брейками :) Не вижу никакого криминала, игра идёт б/м корректно.

  • 12:53 — sectoid тем временем никак не может совладать с cl-json, чтобы получить корректный выхлоп. В итоге отказывается от таска =) Я забираю обратно.

  • 13:12 — что-то тоже обламываюсь сходу с cl-json, плюс ещё какая-то мелкая срань навалилась. А ведь основная проблема-то -- починить 0 очков на карте.

  • 13:32 — побеждаю cl-json и часть мелкой срани. Всё ещё непонятно, почему 0 очков.

  • 13:45 — выпиливаем из зависимостей lift =) Который год уже пытаемся писать какие-то юнит-тесты, и каждый раз облом. Не до них, код слишком быстро меняется и вообще устаревает.

  • 13:54 — втыкаю в визуализатор, в упор не понимаю, что системе не нравится в нашей игре. На вид всё замечательно.

  • 14:03 — пошагово сравниваем наше решение с видео с игрой, которое выложили орги. Порядок выпадающих фигур правильный, фиксирующие ходы (freezing moves) в порядке. Всё ещё непонятно, где засада.

  • 14:26 — turtle изучает игру дальше, я занимаюсь какой-то ерундой. Что-то ускоряю, где-то что-то переделываю.

  • 14:45 — отчаиваемся, сабмитим к лайтнингу решение, которое у нас уже есть. Которое с нулём очков.

  • 14:53 — выясняется, что на 6 задаче у нас аж три с половиной тыщи очков. Значит, на каких-то конфигурациях оно всё же работает :)

  • jtootf: "ну да, мы всё-таки на три места выше команды DNIWE ;)"

  • 14:58:27 — turtle находит этот долбанный баг! Косяк со спавном фигуры -- она появляется не в том месте, где это оговорено правилами. У нас целых полторы минуты на его исправление =)

  • 15:00 — лайтнинг успешно просран.

  • 15:05 — новый план: отремонтировать баг, начать стабильно получать очки, и идти досыпать.

  • 15:30 — sectoid ремонтирует свой код, а я, по-традиции, параллельно пишу свою версию его же.

  • 15:33 — sectoid починил код, я написал свою версию. Сравниваем -- ни тот ни тот не работает как следует =)

  • 15:44 — sectoid повторно починил свой код, а я, в свою очередь, починил свой. На первый вид оба работают, но оба не всегда.

  • 15:45 — turtle пошёл в баню.

  • 16:08 — я вроде отладил свой вариант спавна, сходу ошибок больше не нашёл. Осваиваю сабмит, чтобы сделать проверку.

  • 16:54 — наконец-то не ноль в лидерборде! Sectoid тем временем добил свою версию, и порывается тоже засабмитить, но я предлагаю сначала прогнать всё на всех картах и тупо сравнить ответы двух наших спавнилок.

  • 17:01 — проверка обломалась :)

  • 17:25 — разобрались, косяк на моей стороне =) В честной борьбе побеждает sectoid, его решение уходит в game-loop.

  • Всё, сабмиты пошли, расходимся по своим делам (моё дело -- вздремнуть пару часов).

  • 18:00 - 20:30 — перерыв на сон и ужин.

  • Тем временем, у нас уже три заклинания, помимо того, что дали в задании: Ia! Ia!, R'lyeh и Yuggoth.

  • 21:00 — начинаю вяло читать всякие ссылки про тетрис и искуственный интелект в нём.

  • Параллельно меня опять "осеняет" (прямо сезон откровений), что мы, в отличие от классического тетриса, знаем всю игру наперёд — какие и когда будут выпадать фигуры.

  • jtootf тем временем регулярно накидывает разные ссылки на разные материалы по теме.

  • sectoid неспеша пытается набрутфорсить больше заклинаний.

  • И только к 21:45 я начинаю заниматься чем-то полезным: перетряхивать свой A*, чтобы он научился пользоваться заклинаниями для переходов по графу.

  • jtootf с sectoid активно вспоминают лавкрафта в поисках новых заклинаний, а я отвлёкся на какую-то ерунду в плане фикса производительности.

  • Примерно в 22:30 часть народа разошлось спать, часть куда-то подевалось, я остался один. Разговаривал сам с собой в конфе =)

  • 01:39 — я спрограммировал поддержку заклинаний в A*. Попробовал засабмитить, но из-за какого-то бага на борде мне показало 0 слов силы, хотя очки засчитали.

  • В конфе появился jtootf, вовремя, а то я уже устал сам с собой разговаривать.

  • 01:45 — Тем временем мой солюшн стал потихоньку набирать очки. Команда в общем зачёте поднялась на 38-е место.

  • 01:56 — 33 место.

  • 03:00 — 24 место в турнирке!

  • 05:00 — 8-е место на нулевой карте, 19 место в общем зачёте.

  • 05:32 — 13-е место в общем зачёте.

  • 05:40 — я отваливаюсь спать на рекорде: 11-е место в общей турнирке.

Воскресенье


  • 13:20 — врываюсь в игру.

  • Потихоньку собираюсь с силами начать нормальный солвер. Параллельно прощупываю почву, не хочет ли кто-нибудь спрограммировать его вместо меня =)

  • 13:30 — sectoid там понаэкспериментировал с разными кандидатами в заклинания, в итоге одно добавилось, одно забраковалось (R'lyeh). Какое-то время я разъясняю ситуацию с багом на лидерборде, в итоге ръльех реабилитирован :)

  • turtle тем временем в процессе делает распараллеливание обсчёта разных seed-ов в game-loop, чтобы они играли независимо на разных ядрах.

  • В процессе у меня ломается сборка, и я начинаю активно ныть в конфу о необходимости ветвиться. Sectoid активно поддерживает, turtle активно сопротивляется :)

  • 14:00 — на борде нам, наконец, зачли все 4 заклинания. Починили багу походу.

  • 14:20 — turtle пушит параллельный game-loop.

  • 14:41 — я сходил позавтракать, возвращаюсь -- а солвер всё ещё никто не пишет =) Ладно, сел программировать, решил для начала закодить вот этот вариант.

  • 15:09 — понабежал sectoid с идеей, что можно собирать одно длинное заклинание из ходов нескольких фигур.

  • Чуток пообсуждали идею: в целом, мне, например, всё понравилось, но я просто совсем хз как это ровно закодить. Надо же, получается, как-то тащить контекст через ходы всех фигур, включая "freeze move" — ход, который лочит фигуру. Он, получается, тоже должен участвовать в общем деле.

  • 15:30 — я мучаю солвер, sectoid пишет тулзу для jtootf для упрощения поиска новых заклинаний.

  • 16:30 — я сделал estimator, который берёт поле и фигуру и оценивает позицию. Привинтил к визуализатору и запряг turtle оценить вменяемость скоринга.

  • Сам в это время сел делать брутфорсный поисковик позиций для нужд дебага. Суть токова: перебрать нахрен все клетки карты, и в каждую клетку попробовать запихнуть текущую фигуру. Если не получается, повращать её и опять попробовать запихнуть. Для всех мест, куда удалось запихнуть, собрать estimate score и запомнить позицию с максимумом. Короче, самый что ни на есть bleeding edge мировой науки, и вот это всё =)

  • 17:00 — turtle даёт добро на estimate.

  • 17:30 — мержу солвер в мастер и ухожу обедать. Sectoid тем временем сел писать расчёт официального скоринга.

  • 18:00 — пустили параллельный обсчёт и сабмит всех задач на борду с новым солвером.

  • turtle тем временем забрал солвер себе и пошёл экспериментировать с весами разных факторов.

  • 19:06 — досабмитились решения: 18 место в зачёте.

  • sectoid пошёл делать генетический алгоритм для автоматического подбора весов факторов.

  • 19:25 — jtootf вбрасывает в конфу линк на алгоритм "Iterative deepening A*", а я этого не заметил :( Блин тока щас по логам увидел, жесть. Это же верное направление.

  • 19:40 — начинаем с turtle классический вялотекущий срач по поводу гигиены разработки (не трогать грязными руками мастер, если собираешься всё ломать).

  • 20:05 — заканчиваем срач.

  • Я опять закопался в производительность.

  • 21:10 — sectoid добил ГА, подобрать нормальные коэффициенты не удалось :(

  • Я тем временем ради интереса выполнил поиск на гитхабе по ключевому слову "Yuggoth" и нашёл ненулевое количество открытых репо, в которых были файлы с заклинаниями =) Запустили проверку по всем тем, которых у нас не было, но облом -- это всё не заклинания.

  • 22:00 — ищем по инерции ещё заклинания.

  • до 23:00 занимаемся какой-то ерундой. Sectoid дописывает скоринг (не работает), я пробую чуток улучшить генерацию freeze move, чтобы оно, по-возможности, заканчивало заклинание, а не просто лочило фигуру. Заодно проверил, что будет, если эвристику расстояния до цели в A* развернуть обратно: стараться выбирать наиболее длинные маршруты (в надежде, что в маршруты влезет больше заклинаний). Ничего не вышло.

  • 23:00 — прошу turtle собраться, взять свои руками выставленные коэффициенты, свой новый фактор, и довести решение до рабочего состояния, чтобы можно было объективно сравнить с тем, что есть. Если будет явное улучшение — заменим.

  • 00:03 — устал чёто сильно, пошёл погулять.

  • 01:24 — вернулся, врываюсь обратно.

  • Пока гулял, подумал, что целесообразно нарыть в инете какой-нибудь словарик по вселенной лавкрафта, и тупо зарядит батчем на нулевую карту. Если 0 — весь батч выкидывается, если не ноль — ура, в этом батче есть заклинание.

  • Параллельно привычно слазил на гитхаб, посмотреть, как там дела у конкурентов (да, я подлый, знаю). Принёс в берлогу добычу: заклинание Yogsothoth :)

  • На этой оптимистичной ноте стартовала очередная итерация массовой рыбалки на предмет заклинаний.

  • 02:33 — sectoid разгадал подсказку оргов про "Conway. Cocke. Hopcroft. Backus.", итого +1 заклинание: John Bigboote.

  • 02:55 — вылавливаем ещё одно заклинание: tsathoggua

  • 03:07 — вылавливаем Planet 10

  • 03:10 — я пытаюсь найти подсказки в картах. На 24-ой карте обнаруживается закладка: выпадающие в процессе игры фигурки складываются в строку: icfp2015 :) Я уж думал, что это заклинание, но нет.

  • 03:17 — решаем заканчивать на сегодня: собираем все наши найденные заклинания, и запускаем два раза сабмит всех карт: один раз решателем из мастера, второй -- решателем с коэффициентами от turtle. Конечная идея заключается в том, чтобы выбрать лучший вариант.

  • 03:20 — не, не заканчиваем, рыбачим дальше =)

  • 04:06 — turtle, а затем и пришедший ему на помощь sectoid больно бьются об рандомное поведение dymanic variables в кл при использовании в бинаре и с тредами.

  • 04:30 — проблема решена. Но какая-то новая проблема у turtle с бинарём.

  • 04:40 — нет, проблема не решена =) Я пока от безысходности снова ухожу в профилирование.

  • 05:30 — наконец всё работает, turtle сабмитит оба варианта решений.

  • 05:44 — какие-то результаты для сравнения. Нуууу, так — где-то получше, где-то похуже, но в общем поровну примерно.

  • Короче, решаем, что мейнстримом теперь будут параметры turtle, и вмерживаем в мастер. Финальный сабмит, и идём спать. Я лично уже физически валюсь с ног.

  • 06:10 — ложусь физически в кровать и какое-то время сонно размышляю, что нам делать с 24-ой задачей — она слишком здоровая, наш солвер её никогда в жизни не обсчитает. В этот момент меня озаряет, и я чётко понимаю, что я осёл =) Ну конечно же, надо искать позиции тем же самым A*, которым я ищу маршруты, потому что это банальный BFS traversal, и ему можно тривиально ограничить глубину просмотра. А при обходе графа тупо оценивать каждую позицию текущим нашим estimate, и трекать лучший результат. В итоге, на больших картах алгоритм сможет всегда находить решения, пусть иногда и неоптимальные по эвристикам, но неплохие.

  • 06:30 — не выдерживаю, беру ноут и начинаю программировать прямо в кровати — вставать уже сил нет никаких. Параллельно произвожу мощный рефакторинг, и, в итоге, ближе к 10 утра (когда сел ноут) у меня есть решатель в бранче a-star-forever, который умеет быстро решать большие карты номер 14 и 24. На обычных он, правда, даёт результат хуже, чем текущий солвер в мастере, но я надеюсь после 12 дня его настроить.

  • 10:00 — засыпаю, наконец.

  • 12:00 — просыпаюсь, врываюсь в игру.

  • Вкрадце рассказываю о новом решателе, и мы садимся его настраивать.

  • 12:15 — расчехляем ГА имени sectoid, и пытаемся улучшить коэффициенты факторов.

  • 12:30 — у turtle падает инет, у меня тоже падает инет, разваливается конфа, начинается разнообразная возня с резервными каналами, etc. Как всегда, всё крайне вовремя :)

  • Дальше полноценных логов конфы нет, потому что часть общения переместилась то в обычный джаббер, то в скайп, и в итоге мы как-то собрались в скайповой конфе.

  • Ближе к 14:00 удалось подобрать неплохие коэффициенты для солвера a-star-forever

  • После 14:30 внезапно выяснилось, что у меня там какой-то баг, из-за чего использование заклинаний приводит к невалидному решению с нулём очков. Ремонтировать уже некогда, поэтому мы откатываемся на вариант в мастере.

  • И, под самый финал, на сладкое, я неожиданно совершаю косяк и случайно затираю сабмит 14-й задачи в рейтинге. Пересчитать мастером мы её уже не успеваем (результата надо ждать пару часов), поэтому в конце контеста наша команда резко проваливается на 57-ю позицию =)

Ну вот как-то так, в целом. Было занимательно, ещё раз всем огромное спасибо! Выводов на этот раз нет, какой в них смысл, всё равно им не следуем :)

Отчёт turtle можно почитать тут.