Лабиринт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 (просто 5, без минуса, направление вверх/вниз определяяется автоматически). Можно и просто уйти из комнаты, пользоваться локальными джамперами совсем не обязательно.

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

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

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

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

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

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 Числовое представление текущего ряда. Число 8.{с чем-то}, без номера ряда и этажа.
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 ÷ + КИНВ

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

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

Процедура вычисления видеоизображения текущего ряда

C адреса 88 (Ra). Пусть Y - номер ряда, Z - номер этажа. Тогда число, определяющее видеоизображения текущего ряда вычисляется по формуле C / sin(Z) * Y, где 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 и используется КЗН. Для остальных, не больше семи, будет вычитаться ноль (7 - 7), что не меняет число.

Процедура генерации случайного положения

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

Начало программы

Почему В/О в начале, уже упоминалось, но напомним. Первое, чтобы процедура сужения диапазона начиналась в адреса 81, и код операции использовался в цикле процедуры генерации случайного положения как адрес. Второе, чтобы начало основного цикла программы приходилось на адрес 07, для возврата на который удобно использовать регистр R2, где в результате бинарной операции будет 8.{с чем-то}.

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

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

Основной цикл программы

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

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

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

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

ОперацияRX, при RY=0RX, при RY=1RX, при RY=4RX, при RY=5
-1.5707963-0.57079632.42920373.4292037
Кx≥02 ушлиушли2.42920373.4292037
Fsin ушлиушли0.65364363-0.28366226
КЗН ушлиушли1-1

А команда пользователя, которая для данного случая равна пяти, в результате вычислений дошла в стеке до регистра Z, а значит после всех этих вычислений уже в RY, поэтому мы знак числа просто умножаем на эту 5.

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

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

Адрес 63 - это окончание игры. Сюда попадаем напрямую при проигрыше или с адреса 10 при выигрыше. Благодаря тому, что после С/П идёт процедура, которая по В/О вернёт на начало, для начала новой игры можно просто продолжить и нажать С/П.