warren

Roguelike с постоянной прогрессией, на Lua, управляемый из терминала. Генерируемые забеги, классы, мутаторы и "legacy" между партиями - с headless-режимом и проверкой детерминизма.

warren
TL;DR

Roguelike с постоянным прогрессом на Lua, управляемый из терминала. Генерируемые забеги, классы и мутаторы, система наследия между партиями. Ключ: забег одновременно случаен и полностью детерминирован, потому что один и тот же seed должен давать ровно один и тот же забег.

Введение

Roguelike живет за счет случайности: что ни забег, то другая раскладка уровней, другие предметы, другие выборы. Но будь эта случайность по-настоящему случайной, игру нельзя было бы ни тестировать, ни честно сравнивать результаты. warren решает этот парадокс seed'ом, который делает мир свежим для игрока и повторяемым для машины.

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

Случайный для игрока, повторяемый для машины

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

rng.lua · lua
local rng = Rng.new(seed)
local room = rng:pick(roomTemplates)
local loot = rng:pick(lootTable)

Ключ в том, что каждый бросок идет через этот один генератор, питаемый seed'ом. Достаточно, чтобы один-единственный вызов потянулся за случайностью извне, и детерминизм рассыпается, поэтому весь забег должен дисциплинированно проходить через один и тот же источник.

Детерминизм как инструмент тестирования

Раз seed полностью определяет забег, его можно воспроизвести без человека. У warren есть headless-режим, который играет сам с собой на заданном seed и проверяет, что результат каждый раз одинаков. Это превращает лотерею в нечто, что можно регрессировать: если изменение в коде ломает детерминизм, тест ловит это сразу.

➜
Совет

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

Наследие, или след прошлых забегов

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

Разделение этих двух слоев здесь существенно. Seed правит одним забегом, наследие переносится между ними, и эти две вещи не должны смешиваться, иначе прогресс загрязнил бы повторяемость отдельной партии.

Что идет от seed'а, а что от наследия

ЭлементИсточник
Раскладка уровнейseed
Предметы и событияseed
Разблокированные классынаследие, постоянно
Мутаторы, меняющие правиланаследие, постоянно

Больше проектов

Другие работы из той же категории - посмотрите, как мы решаем похожие задачи.

Есть похожий проект?

Напишите нам - смета бесплатна и приходит в течение часа.