Очевидный способ сделать игру — позволить телефону решать. Игрок заканчивает, приложение считает очки и сообщает серверу, что произошло. Быстро, просто, и у сервера нет никакого мнения.
Это значит и другое: результат — это то, что скажет приложение. А приложение — программа на чужом компьютере, и её можно изменить.
Что мы делаем вместо этого
Каждая игра записывает сделанные ходы — не результат, а ходы. По завершении этот список уходит на наши серверы, и сервер играет ту же партию с той же стартовой позиции, применяя те же правила к тем же ходам.
Если он приходит к вашему результату, партия принимается, и награда считается по серверной копии. Если нет — партия отклоняется и ничего не начисляется.
Движок — один и тот же код с обеих сторон, и он детерминирован: при одном зерне и одних ходах должна получиться одна и та же доска. Это требование определяет удивительно многое в устройстве. Всё случайное должно исходить из зерна, которое есть и у сервера. Всё, что читает часы, должно читать время, о котором обе стороны договорились. Правило, которое ведёт себя иначе на быстром устройстве, чем на медленном, — правило, которого у нас быть не может.
Чего это стоит
Нескольких миллисекунд на завершённую партию и настоящего ограничения на то, как можно писать игры. Ещё это находит наши собственные ошибки, что менее приятно, чем звучит.
Пример — из честных. Урожай распределялся обходом ключей JSON-объекта. Две копии одного содержимого — одна из файла, другая из базы — возвращали эти ключи в разном порядке, и склад наполнялся посередине процесса. Те же правила, те же ходы, другой итог. У честных игроков партии отклонялись с сообщением про поселение, где 590 грузов вместо 591. В игре ничто не выглядело сломанным; повтор просто не соглашался сам с собой.
Для любого модульного теста эта ошибка была невидима: обе половины проходили по отдельности. Понадобилось, чтобы две стороны получали данные разными путями.
Что это даёт
Таблицу лидеров, в которую нельзя написать напрямую, и награду, которую нельзя выдумать. Это одно и то же свойство, сказанное дважды: вы получаете то, что говорит повтор.
Это позволяет и быть точными, когда что-то не так. Отклонённая партия — не туманное обвинение, а расхождение в известной точке, и почти всегда это оборвавшееся соединение, а не действия игрока.