Всем привет! Несколько дней назад мы выкладывали пост про задачки, которые давали на конференции 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 |
До каких клеток поля и как часто доходили игроки:
Процент правильных ответов на каждой клетке поля:
Но самое интересное — это, конечно, статистика вопросов. Оценить их сложность с учётом распределения по категориям оказалось не так просто, оценка всегда субъективна, и простой вопрос для одного пользователя оказывается сложным для другого. Олег предлагал выкинуть один из вопросов со словами «это даже уборщицы знают», но оказалось, что на многие «простые» вопросы правильные ответы знают далеко не все уборщицы, да и программисты тоже. Предлагаем вам некоторые вопросы из нашей игры — лидеры по неверным ответам, попробуйте оценить свои силы!
- Результат вызова этого кода?
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».
- Результат вызова этого кода?
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 % отвечавших. - Какой класс не
Comparable
?
- java.lang.String
- java.util.TreeSet
- java.io.File
- java.lang.Enum
ОтветЭтот, казалось бы, простой вопрос поставил в тупик многих, точнее — 76 % отвечавших. Догадайтесь сами, какой из ответов тут правильный и какой ответ был самым популярным — его выбрали 61 % игроков.
- Чему идентичен код?
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 % игроков.
- Какая строка получится в результате вызова этого кода?
Long.toHexString(0x1_0000_0000L + 0xcafe_babe)
- 1cafebabe
- cafebabe
- ffffffffcafebabe
ОтветЕсли вы выбрали второй ответ, то вы среди 22 % ответивших правильно. Этот вопрос взят из книги «Java Puzzlers: Traps, Pitfalls, and Corner Cases» за авторством Joshua Bloch и Neal Gafter. Если ответили неправильно, не расстраивайтесь, и бегом читать эту книгу!
- В JDK 8 появилась поддержка аннотаций у параметров методов. Возможно ли добавить аннотацию к параметру метода
this
?
- Нельзя
- Возможно, но только в байткоде
- Возможно, определив
this
явно первым параметром метода
ОтветКогда в JDK 8 добавляли возможность ставить аннотации на параметры методов, параметрthis
не стали обделять. Именно для этой целиthis
теперь можно явно указывать в сигнатурах методов:
class Foo { public void test(@Annotated Foo this) {} }
Хотя можно спорить о ее практической пользе, теперь это фича языка. До правильного ответа догадалось 32 % игроков.
- В JDK 8 параметр
concurrencyLevel
в конструктореConcurrentHashMap
влияет на:
- Доступный параллелизм при чтении/записи
- Начальный размер таблицы
- На оба параметра
ОтветЕсли вы выбрали вариант 2, то вы среди 15 %, кто дал правильный ответ на этот самый сложный вопрос игры. Всё дело в том, что в JDK 8 отказались от сегментов вConcurrentHashMap
, поэтомуconcurrencyLevel
потерял свой прежний смысл. Он влияет только на начальный размер таблицы, да и то лишь ограничивает снизу значениеinitialCapacity
.