Итоги конкурса по тестовому заданию для программистов от ZeptoLab. Новое тестовое задание

    Долгожданные итоги конкурса сил Android и iOS developer-ов на место в Dream-Team команде ZeptoLab, наконец, подведены. За эти полгода мы что обещали – сделали: подросли в 2 раза и концептуально оформили нашу обитель:
    image

    Как это было

    image

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

    Напомним, наше тестовое задание для программистов состояло в разработке прототипа игры «Арканоид» с использованием Objective-C для iOS и NDK для Android.

    По присланным заданиям под iOS

    Начнем с краткого обзора Арканойдов под iOS (их было прислано ощутимо больше) — самых запоминающихся и показательных:


    image
    1. Оригинальная идея.
    Удобное управление.
    Хорошая обработка коллизий.
    Ближе к краям платформы мяч меняет свое направление

    image
    2. Красивая графика. Удобное управление. Очень быстрый геймплей. Хорошая обработка коллизий.

    image
    3. Простая графика, основанная на примитивах.
    Удобное управление.
    Хорошая обработка коллизий.
    Жест pinch влияет на
    скорость игры.
    По мере прохождения
    снизу выплывает текстура
    с надписью «Wanna go Zepto»

    image
    4. Единственное задание выполненное в 3D и присланное нам =)

    image
    5. Использование атласов.
    Покадровая анимация.
    Удобное управление.
    Красивая графика.

    image
    6. Красивое тестовое задание, выполненное в стиле old school тетриса.
    Подобающее звуковое сопроводждение.
    Удобное управление.

    image
    7. Очень красивое тестовое задание, выглядит практически как законченный проект.
    Удобное управление.
    Хорошая обработка коллизий.
    Быстрый геймплей.

    image
    8. Оригинальное задание и единственное, у которого есть сюжет (волшебник с посохом защищает город от орков).
    Приятная покадровая анимация персонажей.

    Частые ошибки:

    1. Утечки памяти. К сожалению, практически во всех заданиях, которые были сделаны без использования технологии Automatic Reference Counting, были утечки памяти.
    2. Обработка коллизий. Во многих заданиях мяч застревал в битке или в «кирпичах». Заметней всего это происходило в очень плотно заставленных уровнях. Причем чаще всего это было видно на первом же запуске. Практически во всех заданиях обработка зависела от framerate'а.
    3. Управление. Иногда управление вешалось на swipe gesture или акселерометр, и битком становилось очень трудно управлять. Этот пункт, конечно, нельзя в полной мере занести в ошибки, поэтому мы за него не штрафовали. Но, тем не менее, на наш взгляд, самое удобное управление, когда биток строго следует за пальцем.
    4. Архитектура приложения. Тут иногда встречалось две крайности. Либо когда основной код всего приложения находился в одном классе, либо наоборот, создавалось под полсотни классов, что для данного приложения совершенно излишне.
    5. Грубые ошибки в коде. Они чаще всего связаны с нарушением принципов ООП. Благо таких ошибок было крайне мало.
    6. Иногда встречались задания, выполненные с помощью различных сторонних framework'ов, например Cocos2D. Такие задания сразу отсеивались.
    7. Также были задания, которые не хотели запускаться без дополнительных манипуляций. Это чаще всего связано с настройками проекта. Например, ресурсы были добавлены с глобальными путями. Если такую ошибку удавалось поправить в течение 5 минут, то задание не снималось с конкурса.
    8. Разрешение. В коде часто встречалось много magic number'ов с привязкой к одному конкретному разрешению. На других разрешениях, соответственно, все съезжало.
    9. Рестарт. Одно приложение при проигрыше просто становилось на паузу. Чтобы перезапустить уровень, нужно было перезапускать все приложение.
    Это тоже не совсем ошибка, но это усложняет оценку такого задания.

    Из приятного:

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

    По присланным заданиям под Android

    Несмотря на то, что степень доступности IDE для разработки под Android в известной степени выше, а требования к аппаратной части по сравнению с XCode для iOS ниже, тестовых заданий под эту ОС было прислано около четверти от всего потока. Отчасти это можно объяснить отсутствием нормального интегрированного отладчика для нативной среды в дефолтной IDE (мы все имеем в виду Eclipse), отчасти – требованием к программисту знать как минимум 2 языка программирования: C++ и Java, или быстро уметь в них разобраться.

    О том, что многие разбирались в разработке под Android впервые, было ясно из присланного исходного кода. Также многие программисты честно об этом упомянули в письмах и на собеседовании. Мы не судили такие тестовые задания строже, как и не делали разработчикам поблажек за то, что это – их первый проект под Android. Все были в равных условиях.

    Работающие впервые с нативной средой в качестве основы для проекта использовали примеры, поставляемые с Android NDK – отличный способ получить рабочий проект, чтобы было от чего отталкиваться. Более опытные программисты использовали свои библиотеки и наработки, которые тянут другие библиотеки. В итоге объем исходного кода проекта подчас вырастал на порядки, а мы имели достаточно материала для анализа. К слову сказать, объем исходного кода в самом «легком» проекте – 14 килобайт против 1,6 мегабайта (только кода!) в самом «тяжелом». Кстати, автор самого легкого кода к нашей команде таки присоединился

    Одним из основных требований к программе была корректная работа под любым экранным разрешением. В случае с Android это требование существеннее по сравнению с iOS, т.к. список разрешений устройств «слегка» больше: (полезная статейка на эту тему: opensignalmaps.com/reports/fragmentation.php) и для всех них необходимо выполнить 2 условия:

    1. Инвариантность физики и геймплея в целом. Проще говоря, если записать действия пользователя (например, касания экрана, если был выбран такой способ управления) в относительных координатах и воспроизвести их на другом устройстве, то прохождение уровня не должно измениться.
    2. Приемлемые изменения в визуальной части.
    Разные авторы справлялись с этими условиями по-разному.

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

    Некоторые программисты реализовали размер игрового поля в пикселях для самого маленького разрешения, а на больших устройствах выводили игровое поле в центр или угол экрана.

    Кстати, сама задача отображения шарика c использованием OpenGL-кода на разных устройствах нетривиальна сама по себе. Некоторые использовали текстуру, которая при увеличении размеров экрана теряла наглядность; некоторые рисовали шарик программно. Кстати, будем рады услышать комментарии небезраличных хабражителей на эту тему: как бы вы нарисовали OpenGL-шарик без учета первого ограничения (инвариантности геймплея)?

    Немного о самом коде
    Ниже скриншоты из некоторых присланных заданий под Android:

    image
    Неплохая реализация Арканоида.
    Автору следует обратить внимание
    на поведение шарика при
    отталкивании от блоков,
    сохранение углов при
    коллизиях. Также в подобных
    играх желательно скрывать тулбар.
    У игры проблемы с рендером на
    Sony Xperia Play с прошивкой 2.3.2.

    image

    Довольно интересная реализация. Мы в данном случае управляем не битой, а гравитацией с помощью акселерометра. Понравился высокий FPS и отсутствие дерганий в анимации. Геймплей получился довольно интересным, и в то же время в рамках условия. Автору следует обратить внимание на форму шарика на некоторых устройствах и проблемы с запуском на Samsung Galaxy Note с прошивкой 2.3.6 и Google Nexus S с прошивкой 4.0.4.

    image
    Каноническая реализация Арканоида. Классический геймплей и строгое следование условию задачи. Бита управляется акселерометром. Из плюсов можно отметить довольно плавный рендеринг. Из минусов — неточности в обработке коллизий с нижней частью биты и возможность случая, когда изначально направление шарика задается строго вертикально — в этом случае уровень становится непроходим.

    image
    Добротная работа, одна из лучших реализаций. Удобное управление, приятная графика и хороший геймплей в целом. Приятно порадовало наличие анимации разрушающихся блоков. Автору стоит посмотреть на поведение шарика при большом угле отлета от стен в случаях, когда игрок несколько раз подряд отбивает шарик краем биты, увеличивая угол траектории, а также обратить внимание на неработоспособность приложения на Samsung Galaxy Note с прошивкой 2.3.6.

    image
    Еще одна хорошая работа. Здесь все элементы рисуются пиксельными шейдерами, а при прохождении уровня проигрывается old school'ный шейдерный эффект а-ля «привет из 90-х». Из плюсов также можно отметить плавность геймплея и высокий FPS. Программа работала на всех проверяемых устройствах. Автору следует поднять биту и опустить тач-зону, поскольку палец закрывает небольшую биту.

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

    Кто-то выполнял ее так (iOS):

    const int level[LD_HEIGHT][LD_WIDTH] = {
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
    {0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,1},
    {0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,1},
    {0,0,0,0,0,0,0,1,1,1,1,1,1,0,0,1},
    {0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,1},
    {0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,1},
    {0,0,0,0,0,1,1,1,1,1,1,1,1,1,0,1},
    {0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,1},
    {0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1},
    {0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,0},
    {0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,0,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0},
    {0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0},
    {0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0},
    {0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0},
    {0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0},
    {0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0}};

    Кто-то так (Android):
    for (int i = 0; i < MAP_W*MAP_H; ++i)
    (Blocks[i] = new block())->init((i % MAP_W — MAP_W/2)*0.2, (i / MAP_W)*0.2, i % 3 > 0? 1: 0, (i+2) % 4 > 0? 0.7: 0, (i + 3) % 5 > 0? 0.9: 0);

    А кто-то даже так (iOS):

    // map generation

    Map[0,0] = 1;
    Map[0,1] = 2;
    Map[0,2] = 4;
    Map[0,3] = 1;
    Map[0,4] = 2;
    Map[0,5] = 0;
    Map[0,6] = 3;
    Map[0,7] = 2;
    Map[1,0] = 1;
    Map[1,1] = 3;
    Map[1,2] = 0;
    Map[1,3] = 1;
    Map[1,4] = 2;
    Map[1,5] = 3;
    Map[1,6] = 4;
    Map[1,7] = 2;
    Map[2,0] = 1;
    Map[2,1] = 0;
    Map[2,2] = 2;
    Map[2,3] = 3;
    Map[2,4] = 4;
    Map[2,5] = 3;
    Map[2,6] = 0;
    Map[2,7] = 2;

    Map[4,7] = 1;

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

    Также, раз уж коснулись этой темы, нам интересно мнение хабражителей относительно такого кода: в каких случаях он имеет право на существование, а в каких его стоит заменить циклом (код для iOS):

    [player_pic addFrame:[UIImage imageNamed:@«0.png»]];
    [player_pic addFrame:[UIImage imageNamed:@«1.png»]];
    [player_pic addFrame:[UIImage imageNamed:@«2.png»]];
    [player_pic addFrame:[UIImage imageNamed:@«3.png»]];
    [player_pic addFrame:[UIImage imageNamed:@«4.png»]];
    [player_pic addFrame:[UIImage imageNamed:@«5.png»]];

    [player_pic addFrame:[UIImage imageNamed:@«12.png»]];

    Сравнивая платформы iOS и Android, можно сказать, что вариативность кода под iOS ярче выражена – нечитаемые примеры действительно нечитаемы, спагетти-код оправдывает свое название, а отлично оформленные задания выглядят по-настоящему прекрасно.

    Также хочется отметить менее творческий подход к постановке задачи Android-программистами по сравнению с iOS, где были и разбиваемые при помощи биты-сигареты человеческие легкие (правда, идея оказалась заимствованной), и клон тетриса, и даже проект с фентезийной сюжетной линией.

    В целом по коду: сравнивая задания под iOS и под Android, увидели примерно следующее: код для iOS менее документирован и достаточно часто грязноват. У кого-то остались автогенерируемые комментарии в функциях, которые изначально были заглушками, а у кого-то закомментированы большие модули целиком.

    На обеих платформах порадовало отсутствие KO-комментариев вроде

    Void initGame(); // инициализируем игру

    но вот комментариев по существу могло быть и больше.

    О неточностях в реализации

    Задача для программиста ставилась так: «управляя битой … разбить кирпич, находящийся в определенном месте экрана». Многие рисовали уровни, состоящие из нескольких кирпичей, были даже 3d-варианты, и был даже вариант с настоящей бейсбольной битой.
    Тестовое задание – это не техническое задание, буквоедством мы не занимались, т.к. обе стороны понимают, для чего тестовое задание нужно. Если программист делал что-то более сложное или интересное, это являлось только плюсом. Однако строгое следование сформулированному заданию тоже было плюсом, и само по себе многое говорит о человеке. Но вот следование уже выбранному направлению оценивалось серьезно, и оценивались в первую очередь именно баги в выбранном варианте геймплея.

    Баги

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

    Типичные ошибки в логике:

    1. Определение направления отлета шарика от кирпича. Как оказалось, эта тривиальная задача требовала тщательного тестирования, поскольку ошибки были практически у всех. Наиболее распространенные — две:
    • отлет шарика в противоположных направлениях по осям X и Y, если тот попадает в угол стыка двух кирпичей.
    • отлет шарика от кирпича под углом, отличным от угла падения.

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

    Заключение

    Ниже описание идеального задания, как его видим мы:

    1. Коллизии. В идеале здесь следует сделать систему непрерывного отслеживания столкновений, чтобы не возникало никаких проблем связанных с низким fps и т.п.
    2. Отсутствие утечек памяти. iOS 5.0 упрощает работу с памятью своей технологией — Automatic Reference Counting.
    3. Хорошая архитектура приложения. Тут хорошо бы показать, что вы знаете, что такое ООП, и как его правильно использовать.
    4. Удобное управление. В данном задании, как нам кажется, самым удобным управлением является строгое следование платформы за пальцем. Причем платформа не должна перекрываться пальцем (ее можно поднять от нижнего края экрана). Touch зона должна действовать по всему экрану, чтобы управление не терялось при вертикальных передвижениях пальца. Multitouch здесь совершенно ни к чему, поэтому его следует отключить во избежание ошибок.
    5. Индивидуальность. Если в задании есть некая изюминка – это плюс.

    На этом мы официально заявляем о закрытии конкурса по «Арканоиду». Но так как наша Dream Team компания растет необратимо, мы объявляем новый набор программистов под iOS и Android платформы (как Lead-ов, так и рядовых, но талантливых) и предлагаем новое задание:

    Необходимо сделать прототип игры Pacman, в самом простом варианте: есть лабиринт, в котором находится наш персонаж, и монстр который пытается его догнать.
    Если мы собрали всю еду, разбросанную по лабиринту — мы выиграли, если же нас коснулся монстр — проиграли. Способ управления персонажем — на ваше усмотрение.

    Требования к прототипу:
    — Отрисовка должна быть реализована с помощью OpenGL (любой версии);
    — Игра должна быть написана на Objective-C или С++ в случае iOS, или с использованием NDK (С++) в случае Android (программа должна быть целиком на С++, на Java может быть только обвязка кода), без применения каких-либо сторонних библиотек (вроде Cocos2D или GLKit);


    По времени выполнения мы со своей стороны не ограничиваем никак, ибо талантливые программисты нам нужны будут всегда. Оперативность, бесспорно, прибавит конкурентоспособности, но лучше остаться довольным своей работой, чем просто быть первым. Вопросы, предложения и готовые задания будем ждать на job@zeptolab.com.

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

    Если вдруг у вас сейчас нет какого-то из перечисленных навыков, но желания присоединиться к команде разработчиков ZeptoLab-a не убавилось — любой из этих инструментов можно достаточно быстро изучить (один из наших (уже наших ) Android – разработчиков выучил программирование под Android за месяц (хорошо зная С++) и прислал нам одно из лучших заданий). Поэтому, как говорится, было бы желание :)

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

    В целом, все.

    Если есть, чем поделиться на тему – как говорится, тоже welcome.

    Не прощаемся.

    ZeptoTeam
    ZeptoLab 46,50
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 19
    • +3
      3 369px × 2 246px
      Маленькие у вас фотографии. Надо как минимум 8000 px по меньшему краю, тогда любителя мобильных девайсов будут вам очень благодарны.
      • +2
        В RSS классно выглядит
        • +1
          спасибо, пофиксили
        • 0
          Отличный способ массового просеивания кандидатов.
          Было бы полезно еще потом посмотреть какой процент из отобранных людей оказались реально хорошими програмерами.
        • 0
          Так сколько людей взяли? И каково соотношение полушивших работу к приславшим задание?
          • 0
            > За весь период (без малого полгода) просмотра заданий к нашей команде присоединилось в совокупности 10% их авторов.
            • 0
              А в абсолютных числах? Или секрет?
          • 0
            Вашу ж… Вам Windows Phone программисты вообще никогда не понадобятся?
            • 0
              Написал здесь, ибо полгода назад задал этот вопрос на мыло и мне ответили: «В данный момент мы не заинтересованы в программистах под Windows Phone.» :(
              • 0
                На Marketplace ваша игра разлеталась бы, как горячие пирожки Mush, например. И я не понимаю, почему вы до сих пор не там.
            • НЛО прилетело и опубликовало эту надпись здесь
              • 0
                Неплохой дизайн. Олдскул. Тетрис. Я б залайкал, что называется. Ловите плюсик… Сами знаете куда… :)
                • 0
                  Переделать уже сейчас смысла нет, но сделать новое задание и показать, на что способны уже сейчас никто ведь не запрещает :)
                • 0
                  >>За этот же период мы успели увидеть достаточно работ, чтобы в очередной раз только подтвердить правило: стоящих специалистов сильно мало, и они прячутся.
                  Гуру не будет тратить 12 часов чтобы написать вам арканоид. Вы ищите мегареализаторов среди неопытных специалистов. Для них это дримтим согласен.
                  • 0
                    Очень понравился пиксельный шейдер заднего плана на последней картинке (и 3-й с конца). Поделитесь кодом! Столкнулась с проблемой, что на IPhone4 пиксельные шейдеры сильно просаживают fps. Даже простейший шейдер на весь экран с функцией синуса(к примеру) почти полностью загружает GPU. Может кто-то поделится советом или опытом как оптимизировать работу с openGL ES2?

                    P.S. — пожалуйста, не надо объяснять, почему самые сильные тормоза на IPhone4.
                    • +1
                      Насчет последней — вариация на тему кристаллов, www.shadertoy.com/view/Xs2GDR
                      Насчет третьей с конца — это не шейдер, просто картинка.
                      • 0
                        Спасибо! Предпалагаю вариация очень существенная, т.к. итерационный цикл внутри, да еще с неоднокраными вызовами sin и cos — однозначно гибельная вещь для пиксельного шейдера на iphone.
                        • 0
                          Конечно, он сильно заоптимизирован. Но все равно на четверке пиксельные шейдеры тормозят так, что с производственным качеством что-то сложное на весь экран сделать не получается.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое