Если не мошенничество важнее самой игры, постарайтесь создать и представить свою игру так, чтобы она выглядела как решение математической задачи. Таким образом, сервер передаст экземпляр проблемы клиенту (пример A: шахматная доска, которая должна быть выиграна за 3 хода, пример B: случайно сгенерированный уровень с геометрической чертой), и пользователь должен будет решить ее и отправить обратно решение (пример A: выигрышные ходы, пример b: точные метки времени и интенсивность прыжков, чтобы избежать препятствий)
При таком подходе ключом является то, что сервер не отправляет один и тот же уровень дважды, иначе обманщик может заранее спланировать и «спроектировать» свое решение. Кроме того, игровая информация должна генерироваться случайным образом на сервере, а не отправляться с помощью seed, иначе мошенник может подделать seed и со временем разработать свое решение.
Заданное время для действительных представлений также должно отслеживаться на сервере, чтобы у них было только время «воспроизведения» и время «проектирования». Если мошенник достаточно хорош, чтобы разработать решение так быстро, как честные игроки могут выиграть игру, тогда они достаточно талантливы, чтобы честно выиграть игру и заслужить свои очки.
Вернувшись на сервер, вам нужно будет проверить, что представленное решение действительно для этого экземпляра.
Конечно, этот подход требует много дополнительной работы: больше экземпляров игр (в идеале бесконечных и неповторяющихся), генерация на стороне сервера, проверка представлений на стороне сервера, ограничение времени и т. Д.
Примечание: я знаю этот подход был уже предложен в нескольких решениях несколько лет назад, я хотел добавить свой скромный вклад.
Я не знаю, самый простой ли это, но вот пример использования обратных продолжений без какого-либо вызова set!
или аналогичного:
(apply
(lambda (k i) (if (> i 5) i (k (list k (* 2 i)))))
(call/cc (lambda (k) (list k 1))))
Это должно оценить на 8
.
Немного интереснее:
(apply
(lambda (k i n) (if (= i 0) n (k (list k (- i 1) (* i n)))))
(call/cc (lambda (k) (list k 6 1))))
, который вычисляет 6!
(то есть, он должен оценивать как 720
).
Вы даже можете сделать то же самое с let *
:
(let* ((ka (call/cc (lambda (k) `(,k 1)))) (k (car ka)) (a (cadr ka)))
(if (< a 5) (k `(,k ,(* 2 a))) a))
(Чувак, подсветка синтаксиса stackoverflow на схеме сильно не работает.)
Вот лучшее, что я придумал:
AssertEqual(Eval("((call/cc (lambda (k) k)) (lambda (x) 5))", 5);
Ничего удивительного, но это обратное продолжение, которое я затем "вызываю" с фактической функцией, которую хочу вызвать, функцией, которая возвращает число 5.
А, еще я придумал это как хороший пример модульного теста:
AssertEqual(Eval("((call/cc call/cc) (lambda (x) 5))", 5);
Я согласен с Джейкобом Б - я не думаю, что это так полезно без изменяемого состояния ... но было бы по-прежнему интересоваться контрпримером.
Думаю, вы правы - без мутации обратные продолжения не делают ничего такого, чего не могут делать прямые продолжения.