Лабиринт777

Задача

Цифры в названии – это размерность лабиринта. Трехмерный лабиринт 7×7×7.

Игровая предыстория. Вы историк-археолог будущего, занимающийся исследованиями заброшенных объектов. Для путешествия к ним используете индивидуальный аппарат подпространственного перемещения – джампер. Во время очередного прыжка произошёл сбой и ваш джампер из-за крохотного временного залипания отстал от вас в месте высадки. Т. е. на место вы попали, но вот сам джампер оказался где-то рядом, но не с вами. Так как в целях безопасности он автоматически перешёл в режим невидимости, то придётся вам поискать его. К счастью, когда вы будете рядом, то он автоматически активируется.

Посмотрим, где же вы оказались. Какие-то ряды комнат со слабо видимыми энергетическими оболочками (стенками) между ними. Причём, в горизонтальный ряд они прозрачны, а между рядами нет. В ряд виднеется восемь комнат. С левой стороны на стене написан номер ряда, с правой стороны, за дополнительной недоступной комнатой и какой-то конструкцией ещё цифры – это отображается номер этажа.

Перед отправкой вы, как обычно, надели защитный костюм (скафандр), поэтому энергетические стены вам по силам преодолеть. К сожалению, разные комнаты по-разному реагируют на выход их них (вход проходит без затрат энергии). Есть обычные комнаты, выход из которых идёт без затрат и заряд скафандра, первоначально равный девяти, увеличивается на автоподзарядке на одну единицу. Есть комнаты, где разлита какая-то жидкость (лужи) и выход из этих комнат уже съедает этот накопленный заряд (по нулям). Но есть ещё комнаты с локальными джамперами, которые могут переместить на этаж выше (лестница) или ниже (яма). Причём не важно, используете вы локальный джампер или нет, преодоление барьера в таких комнатах отнимает 5-6 единиц зарядка скафандра (с учётом автоподзарядки – 4-5).

Цель – найти комнату, где активируется ваш персональный джампер. Причем нужно следить за уровнем заряда скафандра. В здании на каждом этаже 7 рядов. Всего в здании 7 этажей. Работёнка предстоит не быстрая.

Игровой процесс

Начало

Перед первым запуском внесём константы в регистры. Если используете эмулятор, то уже вбито:

Регистр Значение
R6 7
R7 64
R8 9.5555555 (быстрее 86/9)
R9 81
Ra 88
Rc Любое случайное число в диапазоне 0…1. Можно оставить ноль.

Положение переключателя Р-ГРД-Г обязательно должно быть в положении Р (начальном по умолчанию).

Начало игры: В/О С/П. ПМК отобразит положение игрока.

Игра

Теперь о том, как это выглядит. Экран ПМК отображает текущий ряд со всеми комнатами в нём, потому что у комнат в ряд стенки прозрачны. Слева отображается номер ряда, справа за отдельной недоступной обычной комнатой – номер этажа. Также положение игрока в ряду выделяется отдельно.

Для управления движением используются клавиши 2, 4, 5, 6, 8. Значение определяется положением относительно центра. Где 4, 6 – движение влево или вправо по ряду. В крайних позициях попытка движения ни к чему не приводит. 2, 8 – движение вперёд или назад по рядам. Также выход за границы ограничен. Для использования лестниц и ям используется клавиша 5. Можно и просто уйти из комнаты, пользоваться локальными джамперами совсем не обязательно.

После нанажатия клавиши направления и С/П ПМК отобразит результат нового положения игрока. Или сразу ноль, что означает, что в данном месте оказался персональный джамер и игрок успешно покинул неприветливый офис – победа. В регистре Y отображается уровень заряда скафандра. В случае, если уровень оказался недостаточным, то ПМК остановится с отрицательным числом (сколько не хватило). Игра считается проигранной. Независимо от способа окончания игры последующее С/П начнёт новую игру.

Поясним, как отображаются комнаты.

Символ Пояснение
8 Игрок в обычной комнате. При выходе заряд +1
9 Игрок в комнате с лужами. При выходе заряд +0
- Обычная команата
L Комната с лужами
C Игрок в комнате с лестницей. При выходе заряд −4
Г Игрок в комнате с ямой. При выходе заряд −5
E Комната с лестницей
Комната с ямой

Вот как выглядит положение на экране ПМК при нулевом начальном значении датчика случайных чисел. 5.ЕL8 --L-05 Здесь, слева направо, номер ряда, точка (начало ряда), лестница, лужа, игрок в обычной комнате, яма, два раза обычная комната, лужа. А затем недоступная обычная комната (конец ряда) и за ней странное сооружение (0) и номер этажа.

Для пояснения покажем весь этаж (но только один) при том же нулевом начальном значении датчика случайных чисел, в том виде, как покажет ПМК (без положения игрока). Как видите, положение ям и лестниц не симметрично.

7.LЕE --L-05
6.Е--Е--L-05
5.ЕL- --L-05
4.L -Е----05
3.-LEL----05
2.Е E-----05
1.L-L---L-05

ПМК делает небольшую проверку того, что ввели команду направления движения, а не просто случайно нажали С/П. В том случае, когда на экране отображается ряд, после нажатия С/П ещё раз отобразится то же самое изображение. Эту же проверку можно использовать, если вы случайно стёрли изображение и хотите вспомнить, где находитесь. В этом случае введите ноль и С/П.

Подсказка: для накопления заряда скафандра походите туда-сюда по обычным комнатам.

Окончание

Напомню, что по окончании игры, не важно успешно (0) или нет (минус), можно сразу нажать С/П для начала новой игры. В этом случае значение уровня заряда снова устанавливается в 9, генерируется новый лабиринт и новое случайное положение игрока и выхода в нём.

Приятной игры!


Разбор программы

Распределение регистров

R0 Рабочий регистр
R1 Положение в текущем ряду [1..7]
R2 Числовое представление текущего ряда
R3 Номер ряда на этаже [1..7]
R4 Уровень заряда скафандра
R5 Номер этажа
R6 Константа 7
R7 64 – адрес процедуры генерации случайного положения
R8 Константа 9.5555555 – битовая маска
R9 81 – адрес процедуры перевода числа в диапазон [1..7]
Ra 88 – адрес процедуры генерации плана текущего положения
Rb Не используется
Rc Случайное число (0..1)
Rd Рабочий регистр
Re Положение выхода

Текст программы

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  В/О КПП7 КППa x→Пe П→xc КПП7 x→Пc П→xe КППa
 10 |  Fx≠0 63 П→x4 П→x3 FВx F КНОП ВП 1 /-/
 20 |  FL0 16 С/П К[x] Кx≠02 П→x2 П→x1 F10x 5 +
 30 |  К К{x} ВП x→П0 <-> 5 Fx=0 46
 40 |  Fcos-1 Кx≥02 Fsin КЗН × К∣x∣ x→Пd КП→xd В↑
 50 |  FВx КЗН + КПП9 Кx→Пd Кx≠02 КП→x4 П→x4 П→x0
 60 |  x→П4 Кx<02 С/П 6 x→П0 F 7 П→xc ×
 70 |  Fπ + К{x} x→Пc 9 x→П4 × К[x] КПП9 Кx→П0
 80 |  FL0 66 Кmax FВx КЗН В/О П→x8 П→xc
 90 |  П→x5 x→П0 Fsin ÷ П→x3 × К x→П2 2
 A0 |  П→x1 F10x ÷ + КИНВ

Объяснение работы программы

Основная идея програмы – не хранение плана этажа, но вычисление его по стандартному алгоритму. Что при одних и тех же входных параметрах даёт одинаковое представление, что создаёт иллюзию того, что ПМК помнит весь лабиринт.

Для этого используется процедура, начинающаяся с адрес 88 (R7). Она на основе трех координат положения игрока и случайного числа формирует видеизображение текущего ряда по формуле C / sin(Z) * Y, где Y и Z это длина (номер ряда) и высота (номер эатаж), а C – случайное число. У каждой цифры полученного числа оставлем только биты 0 и 2 (число 5 в битовой маске из R8). Это вариант сохраняется в отдельном регистре (R2) для анализа ячейки текущего положения, а результат сначала инвертируется на трех первых битах (через вычитания из R8), а затем на всех битах, через КИНВ (предварительно добавив положение игрока в текущем ряду), чтобы получилось понятное видеизображение. Оно ещё не содержит номер ряда и номер этажа, который между делом копируется в R0 для последующего использования. Тут команда В/О перенесена на адрес 00 (A5) не из-за каких-то экономий, а просто ради подгонки адресации для R6 и R2.

Далее рассмотрим процедуру, начинающуюся с адреса 81 (R9). Да, там стоит адрес перехода по FLO, но этот также код для операции П→x6. Эта процедура предполагает, что на входе (RX) число из диапазона [0..8], а она безусловно переводит его в диапазон [1..7]. Для 8-ки понятно, что излишняя единица будет вычтена, потому что в Кmax 8 вытеснит 7, а разница и составляет единицу. А вот для нуля используется тот факт, что ПМК считает его самым большим числом, поэтому семёрка будет вычитаться из него. Для пребразования в −1 и используется КЗН.

Продолжим разбор подпрограмм. Следующая – генерация случайного положения (все три кординаты X, Y, Z) с адреса 64 (Ra). Для генератора случайных чисел используется формула N = {7 * N + π}. Благодаря тому, что R0 используется не только в цикле, но и для косвенного сохранения, получается сохранять значения в регистрах через одного – R5, R3, R1. Промежуточное сохранение 9-ки в R4 служит для начального заряда скафандра. Фактически хватило бы умножения числа на 8, но процедура в R9 (смотри выше) всё равно вернёт 8-ку в нужный диапазон, а так мы даём чуть больше начальной энергии. Тут нужно пояснить, что процедура хитро очищает стек. В цикле, за счет оператора по адресу 66, только что использованное в RX число забывается. А за счёт того, что по окончании цикла идёт переход на процедуру из R9 (такой трюк для экономии на В/О) без извлечения 7-ки из R6, то и последнее сгенерированное число тоже уберётся из стека, оставив в нём то, что было до вызова этой процедуры. Это потому, что в стеке до этого будет число меньше чем один, а процедура сгенерит число из диапазона [1..7], которое явно больше. Тоже трюк, а совместо с двойным использование команды по адресу 61 получается уже третий в одной подпрограмме.

Ну а теперь рассмотрим всё по порядку. Почему В/О в начале, уже упоминалось, но напомним – чтобы цикл процедуры Ra начинался в адреса 66 = R6, потому что R5 уже используется для другого. А также потому, что начало основного цикла приходится на адрес 07, что удобно для воврата на него по регистру R2, где в результате бинарной операции будет 8.{с чем-то}.

Далее. Сначала, с помощью КПП7 формируется координаты выхода. Затем с помощью КППa формируется видеоизображения для выхода и сохраняется для дальнейшей проверки в Re.

Затем использованное для этого случайное число сохраняется в стеке, а потом с помощью КПП7 формируется уже начальное положение игрока. Напомню, что особенностью этой процедуры является восстановление стека до выполнения. Поэтому сохранившееся в нём случайное число восстанавливаем обратно, иначе нам то же самое изображение, что и выход, не получить.

С адреса 07 начинается основной цикл программы. Для дальнейшего сравнения изображение выхода извлекается в стек из Re, затем вызывается процедура формирования изображения по текущим координатам. Если разница нулевая, то с полученным нулём идёт на выход. В обычном случае извлекаем в стек значения уровня заряда скафандра и номер ряда, а затем извлекаем из X1 сформированное изображение ряда. Далее идёт нестандартное использование возможностей ПМК, описанное в документе Недокументированные возможности в разделах про X2. В частности команда FВx не только извлекает видеоизображение из X1, но и запоминает его в недокументированном регистре R2. Именно его значение восстанавливается в RX после команды ВП, сохраняя только первый разряд, который к тому времени и есть номер ряда. Получается, что первая цифра видеоизображения будет показывать номер ряда. Такой вот трюк, который при пошаговом режиме, кстати, не работает. Значение заряда остаётся в RY. В цикле по номеру этажа убавляется номер порядка на 1, что приводит к добавлению к видеоизображению номера этажа.

После остановки мы ожидаем, что игрок введёт код команды (2, 4, 5, 6, 8). Для простой проверки, что код введён, а не осталось видеоизображение, и используется К[x] Кx≠02.

Далее, тоже с помощью трюка, получаем цифру из запомненного в R2 числа, на месте текущего X положения игрока в ряду (R1). Для этого с помощью числа вида 10…05 получаем битовую пятёрку на месте нужном месте. Для примера, для X = 2 будет 102 + 5 = 105. С помощью К выделяем только биты в нужном разряде. Дробной часть оставляет только это число, но с неизвестной степенью. А трюк с ВП (здесь игнорируемый ПМК минус используется, чтобы убрать из стека число с пятёркой в конце) восстанавливает X2 (последняя команда 5), не меняя первый разряд. Это то, что нам и нужно. Сохраняем это число (0, 1, 4 или 5) в R0 и возвращаемся к команде игрока.

Благодаря вычитанию 5, возможные команды распадаются на -1, 1 (движение по ряду), на -3, 3 (движение по рядам) и 0 – движение между этажами. Сначала его и проанализируем. Через Fcos-1 из нуля делаем что-то около 1.5 (π/2). Что при вычитании из 0 или 1 (то, что сохранилось в R0 и осталось в стеке) отбросит вариант не лестницы (4) или ямы (5). Более того, эта разность с помощью Fsin удачно получается разного знака для 4 и 5. А команда пользователя, которая для данного случая равна пяти, в результате вычислений дошла в стеке до регистра Z, а значит после всех этих вычислений уже в RY, поэтому мы знак числа просто умножаем на эту 5.

По адресу 46 на входе в RX у нас ±1, ±3 или ±5. Как удачно, у нас как раз в регистрах с этими номерами и хранятся координаты 😊. Поэтому модуль числа мы сохраняем в Rd, который и будет использовать для косвенного обращения к значениям координат, а знак числа используем для приращения координат. Предварительно неизменённое значение координаты заталкивается в стек. Потом делаем сложение и вызов КПП9, чтобы убедиться, что новые координаты в диапазоне [1..7]. А вычитание из предыдущего их значения позволяет проверить, а было ли вообще движение – проверка границ.

Финально происходит увеличение уровня заряда в R4 на 1, а потом уменьшение на цифру из текущего положения (R0). Если получается минус, то игра завершается, если нет, то возврат на основной цикл программы. Благодаря тому, что после окончательного С/П идёт процедура, которая по В/О вернёт на начало, для начала новой игры можно просто продолжить и нажать С/П.