Всем привет! Несколько дней назад мы выкладывали пост про задачки, которые давали на конференции Joker 2018. Но это еще не всё! В этом году специально для Joker мы сделали целую игру с не менее интересными задачками по Java (и не только), про которую и расскажем сегодня.

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

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


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


С механикой разобрались, теперь надо придумать вопросы. Они должны быть трёх категорий сложности, от самых простых до хардкора. Формулировки вопросов должны быть предельно короткими, читать простыни текста не будет времени. Варианты ответов должны быть такими, чтобы не давать слишком много подсказок. Ну и сами вопросы должны быть интересными и практическими. При этом их должно было быть достаточно много, чтобы они не слишком быстро повторялись. В результате совместных усилий удалось придумать 130 вопросов про структуры данных, алгоритмы, «java-пазлеры», хардкорные вопросы по внутренностям JVM, и даже несколько вопросов про Docker.


Есть проблема: игра выводится на большой экран, и те, кто стоят рядом, за несколько игр запомнят большую часть ответов. Сначала казалось, что нам понадобятся сотни вопросов, чтобы свести возможность запоминания к минимуму. Но, поразмыслив, поняли, что есть варианты попроще. Для начала, убрали управление мышкой, оставив только клавиатуру. Теперь стоящие рядом видят вопрос, но не видят, какой ответ выбрал игрок. Добавили перемешивание вариантов ответов, усложнив запоминание. Для каждого вопроса добавили несколько похожих, но немного отличающихся формулировкой. Конечно, запомнить ответы всё равно можно, и это было видно по заметно улучшившимся результатам на второй день конференции. Многие пользователи проводили за игрой десятки минут, пытаясь перебить рекорд других. Но тут всё как в жизни — усердие вознаграждается.

С возникновения идеи до придумывания вопросов и реализации прошло две недели. Разумеется, всё на Java. Использовался Spring boot и Gradle. WEB-интерфейс сделан на Angular. В качестве хранилища использована встроенная база данных H2, которая «из коробки» поставляется с web-интерфейсом, что очень удобно. Конфигурация стенда — два MacBook, картинка с которых дублируется на двух телевизорах. Для удобства настройки приложение удаленно развернули в нашем облаке (https://habr.com/company/odnoklassniki/blog/346868/).


Мы давно выучили: ни одна фича не должна разрабатываться без сбора статистики. Разумеется, и к игре мы прикрутили детальную статистику, которой можем поделиться.

Всего в игру за два дня сыграли 811 раз. Статистика ответов в зависимости от сложности вопроса:

DIFFICULTY
COUNT
CORRECT_PERCENT
1
3552
61
2
2031
49
3
912
46

До каких клеток поля и как часто доходили игроки:


Процент правильных ответов на каждой клетке поля:


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

  1. Результат вызова этого кода?

    System.out.println(1/0d)

    • Выбросит ArithmeticException
    • Напечатает «Infinity»
    • Напечатает «NaN»
    • Напечатает 0

    Ответ
    Это кажется очень простым вопросом. Тут простая арифметика, какой может быть подвох, почему же правильный ответ дали только 28 % игроков? Деление целого числа на 0 приводит в Java к ArithmeticException. Но целые ли тут числа? Посмотрим внимательно. Что там за «d» после 0? Эта буква означает, что перед нами не целочисленная константа 0, а значение типа double. И получается, что выражение идентично 1.0/0.0. А это уже деление с плавающей точкой на ноль, результат которого равен Double.POSITIVE_INFINITY. Значит, правильный ответ — «b».
  2. Результат вызова этого кода?

    System.out.println(
        Long.MAX_VALUE==(long)Float.MAX_VALUE
    );
    

    • напечатает true
    • напечатает false
    • выбросит ArithmeticException

    Ответ
    Для начала, нужно понять, что больше: Float.MAX_VALUE или Long.MAX_VALUE? Хотя float имеет меньший диапазон значений, чем double, всё равно его максимальное значение примерно на 20 порядков выходит за диапазон возможных значений long. Но как в данном случае сработает приведение типов? Можно гадать, но лучше запустить код. А еще лучше — открыть Java Language Specification, раздел про Narrowing Primitive Conversion, и прочитать, что если значение числа с плавающей точкой слишком велико и выходит за диапазон доступных значений целочисленного типа, то результат конверсии равен максимальному значению, которое можно представить с помощью целочисленного типа. Т.е. результат конверсии равен Long.MAX_VALUE. Правильный ответ дали 27 % отвечавших.
  3. Какой класс не Comparable?

    • java.lang.String
    • java.util.TreeSet
    • java.io.File
    • java.lang.Enum

    Ответ
    Этот, казалось бы, простой вопрос поставил в тупик многих, точнее — 76 % отвечавших. Догадайтесь сами, какой из ответов тут правильный и какой ответ был самым популярным — его выбрали 61 % игроков.
  4. Чему идентичен код?

    Object o = Math.min(-1, Double.MIN_VALUE)

    • Object o = -1
    • Object o = Double.MIN_VALUE
    • Object o = -1.0

    Ответ
    Минимальное значение double уж точно меньше -1, что тут опять может быть не так? Разумеется, всё не так просто, иначе мы не спрашивали бы. Оказывается, Double.MIN_VALUE содержит не совсем то, что ожидается, а именно «constant holding the smallest positive nonzero value», согласно документации. Правильнее его было бы назвать Double.MIN_POSITIVE_VALUE. Double опять обвел вокруг пальца! Правильный ответ: Object o = -1.0, и так ответили всего 22 % игроков.
  5. Какая строка получится в результате вызова этого кода?

    Long.toHexString(0x1_0000_0000L + 0xcafe_babe)

    • 1cafebabe
    • cafebabe
    • ffffffffcafebabe

    Ответ
    Если вы выбрали второй ответ, то вы среди 22 % ответивших правильно. Этот вопрос взят из книги «Java Puzzlers: Traps, Pitfalls, and Corner Cases» за авторством Joshua Bloch и Neal Gafter. Если ответили неправильно, не расстраивайтесь, и бегом читать эту книгу!
  6. В JDK 8 появилась поддержка аннотаций у параметров методов. Возможно ли добавить аннотацию к параметру метода this?

    • Нельзя
    • Возможно, но только в байткоде
    • Возможно, определив this явно первым параметром метода

    Ответ
    Когда в JDK 8 добавляли возможность ставить аннотации на параметры методов, параметр this не стали обделять. Именно для этой цели this теперь можно явно указывать в сигнатурах методов:

    class Foo {
        public void test(@Annotated Foo this) {}
    }

    Хотя можно спорить о ее практической пользе, теперь это фича языка. До правильного ответа догадалось 32 % игроков.
  7. В JDK 8 параметр concurrencyLevel в конструкторе ConcurrentHashMap влияет на:

    • Доступный параллелизм при чтении/записи
    • Начальный размер таблицы
    • На оба параметра

    Ответ
    Если вы выбрали вариант 2, то вы среди 15 %, кто дал правильный ответ на этот самый сложный вопрос игры. Всё дело в том, что в JDK 8 отказались от сегментов в ConcurrentHashMap, поэтому concurrencyLevel потерял свой прежний смысл. Он влияет только на начальный размер таблицы, да и то лишь ограничивает снизу значение initialCapacity.

По многочисленным просьбам мы выложили игру на наш сайт, где вы можете сыграть прямо сейчас!

Комментарии (3)


  1. time2rfc
    29.10.2018 14:19

    Интересно всегда было узнать % прошедших задачу к пришедшим после этого работать в компанию.


  1. MisterParser
    30.10.2018 19:33

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


  1. dsdolzhenko
    31.10.2018 13:06

    Как всегда, один из лучших стендов на Joker. Так держать!