Телепорт

Пролог

…оказывается у моего друга был телепорт! Индивидуальный! Да, таких почти нет, только госкорпорации владеют. Я только слышал, что есть частные. А уж заряды к ним стоят вообще космически. Но вот он прислал email и подарок и всё оказывается правдой.

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

Так вот про заряды. Я слышал домыслы, что есть станции по их производству. И теперь, судя по письму, не только есть, но мой друг был хранителем входа (точка, откуда можно телепортировать в хранилище). Сообщил что отправился разбираться с грабителями, но на всякий случай оставил мне сообщение и подарок, если вовремя не вернётся.

И вот он подарок – телепорт и немного зарядов. Так же пространственные координаты планеты, где находится хранилище (по счастью кислородная). Точка входа уже разружена, навёл справки, так что на всякий случай перемещусь на саму планету. Заряды очень нужны. Без них телепорт превращается в безполезную и опасную (если криминал узнает) игрушку.

Так, читаем инструкцию от друга. Этажей четыре, на каждом хранилище. Самое опасное – стражи (какие-то энергетические образования, пропускают только своих). Своих по историческим причинами (кто-то сделал эти станции, но человечеству это неизвестно) давно нет. За давностью или в силу экономии энергии стражи реагируют только на прохождение между комнатами, стараясь приблизиться к нарушителю. По счастью есть какие-то технологические люки, причем можно до них дотянуться и снизу. Так что перемещаясь между этажами (на каждом свой страж), и вычисляя приблизитель, кто где, можно их обмануть.

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

Так, я на месте. Снаружи. Хорошая новость – станция есть. Причем одна стена разрушена, но при этом, судя но сигналам, осталась рабочей (не самоликвидировалась). Это значит можно будет зайти/выйти без проблем.

Плохая новость – стражи действуют и самое неприятное – почти все лестницы разрушены (с подняться проблемы). Видимо внутри была драчка с применением каких-то разрушителей. Но и тут есть положительный момент – через дыры в переходах стало видно стражей. Можно не вычислять стражей, их просто видно. Ну что же, попробуем…

Задача

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

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

Здание четырёхэтажное, по 8 комнат на каждом этаже. Левая комната на каждом этаже – хранилище. Вы начинаете на первом этаже в правой части здания. Текущее положение игрока и стража определяется видеоизображением на экране ПМК: число с целой частью и шестнадцатеричными цифрами в дробной. Целая часть обозначает этаж, на котором вы находитесь. Каждая цифра дробной части обозначает комнаты слева направо, начиная после хранилища (хранилище самое левое и находится на месте цифры этажа):

Цифра Значение
- Пустая комната (пол)
L Комната с лестницей
8 Комната, где находится игрок
9 Комната с лестницей, где находится игрок
E Страж в пустой комнате
Страж в комнате с лестницей (прячется)
C Игрок и страж в одной комнате вместе
Г Игрок и страж в одной комнате с лестницей

Вот типичное отображение: 2.-Е-8--L . Этаж второй. Игрок в пятой комнате (слева), страж в третьей, лестница в конце в восьмой.

Текущий уровень заряда для телепортации отображается в регистре Y (также хранится в регистре Rc). Вначале равен пяти. При телепортации уменьшается. При входе в хранилище – увеличивается на 10. Будьте внимательны – хранилище выдаёт заряды только восемь раз за игру (все хранилища суммарно, независимо от этажа).

На каждом этаже свой страж, они двигаются независимо друг от друга. Когда страж в хранилище (под цифрой этажа), его отображение отсутствует. Когда игрок в хранилище – его изображение также отсутствует.

Лестницы есть на каждом этаже, кроме последнего четвёртого (нет лестниц на крышу). Их количество и положение генерируется случайным образом в начале каждой игры. В среднем одна, реже две на этаж. Но т. к. генерируется случайно, то иногда бывает и больше. Чем больше – тем легче играть (обойти стража). Лестниц в хранилище не бывает.

Начало

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

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

Регистр Значение
R6 Псевдослучайное число. Можно оставить нулевым.
R8 -92 или -2 (так короче)
Ra 48
Rb 9.8888888
Re 8-------. План пустого этажа. Формируется так: 5, В↑, 9, ÷, КИНВ, ВП, 7.

Затем нажимаем В/О, С/П для генерации плана здания и установки игрока в начальное положение.

Игра

Для выполнения действия нужно ввести цифру команду и нажать С/П. Команда движения определяет направление положением цифр на клавиатуре относительно центра: 2,4,5,6,8. И отдельно 0 (нуль-переход или телепортация).

Команда Действие
0 Телепортация в хранилище. Затрачиваются заряды в зависимости от расстояния (в комнатах) до хранилища. Если выполнить из самой правой – семь. Телепортация внутри хранилища игнорируется.
2 Ход вниз на этаж. Возможно везде, кроме первого этажа, где игнорируется. Фактически любое число ≤ 3 воспринимается как ход вниз. Спускаться можно и в хранилище!
4 Ход влево. Из хранилища (из самой левой комнаты) ход игнорируется.
5 Выманивание. Игрок не двигается (открывая/закрывая межкомнатные двери, но не двигается), а страж приближается к нему.
6 Ход вправо. Для завершения игры нужно выйти правее границы здания на любом этаже.
8 Ход вверх. При отсутствии лестницы ход игнорируется. Фактически любое число ≥ 7 воспринимается как ход вверх.

Дробные числа как команды игнорируются (воспринимаются как ошибочный ввод, когда перед С/П не указали команду). Можно использовать для просмотра текущего положения (если видеоизображение не на экране). Например, Fπ, С/П просто покажет текущую ситуацию, ничего не меняя.

При движении по этажу (влево/вправо) страж двигается в сторону игрока (страж реагируется на переходы). Если он оказывается в той же ячейке, что и игрок, то уничтожает нарушителя – ЕГ0ГГ .

При движении между этажами стражи спят (т. к. двери не открываются, а страж пробуждается только при открытии дверей). Таким образом возможно даже попасть в одну комнату вместе со стражем. Но безопасно потом можно будет уйти верх (при наличии лестницы), вниз или телепортироваться в хранилище.

При телепортации страж не двигается.

Окончание игры

Нормальное – выход в правой части здания. В этом случае при остановке покажет текущий уровень зарядов. Уровень заряда > 5 означает, что ваша миссия удачна (набрали больше, чем вначале). Для начала новой игры нажмите С/П.

Если произошла трагедия ( ЕГ0ГГ ), то нажав В↑ можно узнать причину. Если ноль – страж поймал, если < 0, то не хватило заряда при телепортации и игрок пропал в подпространстве. Для начала новой игры нажмите С/П.


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

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

R0 Количество попыток использования хранилища минус один. Инициализируется программно значением из Rb.
R1…R4 План 1…4 этажей. Генерируется программно. Каждый этаж представляется числом, где целая часть обозначает положение стража на этаже: [0…7], а дробная часть представляет план лестниц на этаже, состоящая из нулей и единиц в нужном разряде. Каждой единице соответствует лестница. Лестниц на крышу нет, поэтому R4 всегда целое.
R5 Рабочий регистр. Обычно хранит 10m, где m – положение игрока на этаже: [0…7]. Но при телепортации хранит значение сдвига влево (отрицательное число).
R6 Псевдослучайное число, используемое для генерации плана здания. Программный генератор псевдослучайных чисел использует формулу Xi+1 = {Xi × 7 + π}.
R7 Положение игрока на этаже: [0…7]. Инициализируется программно семёркой.
R8 Константа −92 – адрес блока движения стража по этажу. Отрицательность значения используется для контроля выхода игрока из здания (завершение игры).
R9 Номер этажа, на котором находится игрок: [1…4]. Инициализируется программно единицей.
Ra Константа 48 – адрес блока программы по отображению текущего положения игрока и остановкой с ожиданием команды. По сути начало основного цикла программы (шесть косвенных переходов в программе используют этот регистр).
Rb Константа 9.8888888. Все цифры, кроме первой, используется как битовая маска для выделения цифр, содержащих бит 3 (цифры 8 и 9). Целая часть – начальное значение счётчика использования хранилища (зарядки телепорта). Получается максимум 9 − 1 = 8 раз на игру. Если это кажется недостаточным, то можно указать значение 18.888888 или 28.888888 и т. п.
Накопленный объём зарядов для телепорта. Инициализируется программно пятёркой (цифра по адресу 03).
Rd Рабочий регистр. Хранит последнее видеоизображение. Фактически используется как битовая маска для проверки возможности подняться по лестнице (только бит 0 у цифр важен, остальные не учитываются). Отличие от значения в КП→x9 в том, что целая часть всегда есть.
Re Константа 8-------. Используется как битовая маска при генерации видеоизображения. И как адрес перехода на процедуру выдачи ошибки при фатальных ходах игрока.

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

 # |  00 01 02 03 04 05 06 07 08 09
 00 |  Cx x→П4 x→П1 5 x→Пc FLn x→П9 П→x6 К{x} 7
 10 |  x→П7 × Fπ + x→П6 П→xb x→П0 К К{x} Кx≠04
 20 |  8 ÷ Кx→П1 П→x1 Кx≥07 К{x} Кx=0a FВx 5
 30 |  Кx≠08 F1/x К[x] Fx=0 77 FВx Fx≥0 43 КП→x5 П→x5
 40 |  П→xd К К{x} КЗН П→x9 + Кx≠0a x→П9 4 КП→x9
 50 |  К[x] F10x ÷ КП→x9 + П→x7 F10x x→П5 2 +
 60 |  К П→xe К x→Пd П→xc П→x9 П→xd <-> F ВП
 70 |  С/П Fx=0 25 П→x7 Кx≠0a /-/ x→П5 П→x7 + Кx≥0a
 80 |  x→П7 Кx=08 П→x5 FL0 86 Кx<08 П→xc + Кx≥0e x→Пc
 90 |  П→x5 Кx≥0a П→x7 КП→x9 Ftg-1 К[x] Кx≠0e КП→x9 +
 A0 |  Кx→П9 КП→x7 Кx<0a П→xc С/П

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

Иерархически программу можно представить так:

Теперь разберём каждый блок детальней.

Блок начальной инициализации

Который сразу после В/О С/П. Т. к. на 4-м этаже нет люков в потолке, то нет и лестниц. Поэтому R4 зануляем.

Также зануляем R1, который будет использоваться как отрицательный счётчик цикла. Эта хитрость связана с использованием особенностей косвенной адресации.

Начальное значение уровня зарядов (5) вносим в Rc. Можно модифицировать программу, указав другое, но допустимыми значениями являются только числа из диапазона [3…7].

Далее идёт установка начального значения номера этажа (=1), но необычно, через FLn. В этом случае будет единица с чем-то, но это с чем-то при косвенной адресации по R9 будет отброшено. Причина использования именно функции, а не просто единицы будет объяснена позднее. Также становить понятны граничные значения на начальное значение уровня заряда – чтобы логарифм дал единицу.

Далее по адресам [07…14] генерируется очередное псевдослучайное число по формуле Xi+1 = {Xi × 7 + π}. Причём коэффициент умножения (7) также сохраняется в R7, как начальное положения игрока на этаже. Псевдослучайное число храниться вместе с целой частью, чтобы были заполнены все цифры мантиссы.

Битовая конъюнкция с числом из Rb оставит восьмёрки на тех местах, где были цифры 8 или 9. В среднем они встречаются в 1/5 случае, а значит на этаже будет в среднем 7/5 лестниц, что соответствует почти все сломаны. Одновременно инициализируется R0, как начальное значение счётчика использования хранилища.

Исходно стражи находятся в хранилище, т. е. с нулевой координатой, поэтому оставляем только дробную часть. А команда Кx≠04 проверяет, что сгенерировалась хотя бы одна лестница. Если этаж получился пустой, то начнём генерацию сначала переходя на адреса 01 (R4 = 0, косвенно +1) с текущим нулевым регистром X. Как альтернативу можно использовать Кx≠09.

Далее цифры 8 в числе превращаются в единички, путём деления на 8.

Стоит пояснить, куда же идёт сохранение. При начальном значении R1 = 0 косвенная адресация превратит его в −99999999, что соответствует регистру R3. В следующий цикл в −99999998, что соответствует R2, потом в −99999997, что соответствует R1, т. е. счётчик будет перезаписан планом первого этажа. Детально можно ознакомиться в документе Недокументированные возможности. Т. к. до последнего случая счётчик всё время отрицательный, то команды П→x1, Кx≥07 как раз проверяют это факт. Причём начало цикла (адрес 07) удачно совпадает с начальным значением R7.

На этом блок начальной инициализации закончен. Хочу только пояснить, что последующие две команды другого блока безусловно отправят программу на адрес в Ra, т. к. план этажа – это не нулевое дробное число.

1. Блок отображения

Начинается с адреса 48 (Ra). В качестве бита положения стража используется 4 (бит 2). Для его фиксации в нужном разряде можно использовать два подхода:

  1. Использовать 10n + 4, где n = [0…7] – это положения стража на этаже (целая часть плана этажа).
  2. Использовать 4 / 10n. В этом случае для битовых операций нужно ещё добавить целую часть.

В программе используется второй подход, потому что нам нужно будет к числу добавить план лестниц этажа, которая представляется дробным числом. От извлечённого через Rd плана этажа берётся целая часть, возводиться в степень, а потом 4 делиться на него. В случае, если начальное положение стража = 0, то 100 = 1 и результат будет равен 4 (страж не виден, но число содержит целую часть). Если страж уже вышел, то и целая часть числа уже будет не нулевая. Поэтому последующее сложение КП→x9 + всегда будет содержать незначащую для битовых операций целую часть.

Далее выполняется аналогичное действие для положения игрока, но используется подход 1 (для разнообразия). Положения игрока для отображения задаётся двойкой (бит 1), точнее отсутствием такого бита. Промежуточное 10m, кроме того, сохраняется в R5 – оно нам пригодится. Бит 1 прибавляется к итогу командой по адресу 60.

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

А далее идёт шаманство, которое так же описано в другом разделе документа Недокументированные возможности, вписывающее номер этажа в первый разряд. Замечу, что в пошаговом режиме это не сработает и номер этажа останется только в X1. Начальное П→xc – уровень заряда, перед остановкой попадёт в регистр Y.

2. Блок разбора команды игрока

Быстро пропустим проверку на телепортацию (адреса [71…72]) и сразу перейдём к адресу 25. Тут идёт защита от дурака – проверка случайного нажатия С/П без ввода цифры команды. В этом случае в регистре X останется видеоизображение, содержащее дробную часть. Именно этой проверкой и занимаются три первые команды.

Затем от числа команды отнимается 5. Результат и последующий выбор процедуры проще отобразить таблицей, чтобы понять, что получится.

Команда\операция 5, F1/x К[x]
2 -3 -1/3 0, в X1 число < 0
4 -1 -1 -1
5 0 – это сразу уйдёт на движение стража
6 1 1 1
8 3 1/3 0, в X1 число > 0

Фактически, вместо 2 может быть любое число < 4, а вместо 8 число > 6. Это дополнительная защита от дурака – программа учитывает все возможные варианты ввода. Далее идёт переход (по нулевому результату) на блок движения между этажами или по ±1 – на блок движения по этажу.

3. Блок обработки движения между этажами (вверх/вниз)

Начиная с адреса 35. Как указано выше, для нас важен только знак числа в X1, поэтому после восстановления X1→X делается проверка. Вариант X < 0 будет обработан автоматически позднее, потому что вниз пойти можно почти всегда, а вот вариант с вверх посложней.

Нужно проверить, что есть лестница. Для этого сначала получаем бит в позиции игрока. Т. к. в R5 было сохранено 10m, то 10m + 1 даст нужный бит, поэтому сначала идёт косвенное извлечение из R5, чтобы в нём прошло +1, а затем этот бит умножается на бит лестниц из сохранённого ранее в Rd. Знак числа от дробной части даст 1 (для лестницы) или ноль, при её отсутствии. На 4-м этаже лестниц нет, так что выход на крышу проверять отдельно не нужно.

Этот же фрагмент идёт и для хода вниз, только тут сразу знак даёт −1 для отрицательных значений. Полученное значение 0 или ±1 складывается с номером этажа. При этом делается проверка, что бы мы не провалились в подвал, т. е. чтобы номер этажа был не нулевой. Окончание этого блока, опять удачно попадает без перехода на блок отображения.

4. Блок обработки движения по этажу (влево/вправо)

С адреса 77. Тут вообще просто. Вспоминая, что команда игрока будет преобразована в ±1, это значение просто складывается с текущим положением. Дополнительная проверка делается, чтобы игрок из хранилища не ушёл влево (в минус).

5. Блок телепортации

Начиная с адреса 73. Его функции также встречаются в блоке обработки работы хранилища.

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

6. Блок обработки работы хранилища

С адреса 81. Первым делом проверяется, что текущее положения игрока получилось нулевым (в хранилище). Если нет, то переход на блок движения стража (с адреса в R8). Вторым делом извлекается содержимое R5. Если мы зашли в хранилище ногами, то в R5 будет 10, как 101, потому что до этого положения игрока на этаже было равно единице. Если как результат телепортации, то в диапазоне [-7…-1].

Сначала делается проверка, что количество попыток получения заряда не исчерпано. Если счётчик достигнет единицы, то выполниться команда Кx<08, которая пропустит только при телепортации (R5 < 0), но проигнорирует зарядку (R5 = 10), переходя сразу на блок движения стража. Из-за того, что проверка делается ДО, количество попыток на единицу меньше начального значения в R0.

Проверка суммы заряда по адресу 89 Кx≥0e, используется только при телепортации, т. к. при далёкой телепортации мы можем получить и минус. Что приведёт к переходу на ошибку. Для обычного движения проверка всегда отработает успешно, потому что мы только что добавили 10.

Проверка по адресу [90…91] позволит сразу уйти на начало цикла программы при телепортации (R5 < 0), и удачно перейти на блок движения стража, при обычном входе в хранилище (R5 = 10).

7. Блок движения стража

С адреса 92 (регистр R8). Страж поймает игрока, если расстояние от него до нового положения игрока ≤1. Положение игрока в R7, а стража – целая часть от КП→x9.

Тут нам на помощь приходят знания тригонометрии, а именно функции arctg, которая для чисел в диапазоне (−π/2…+π/2), что приблизительно [-1.5…1.5], даёт результат меньше единицы (по модулю). И эта функция сохраняется знак. Это то, что нам нужно (вот почему важно Р-ГРД-Г переключить в радианы). Более того, мы можем не убирать дробную часть плана этажа, т. к. и с ней результат уложиться в этот диапазон. С учётом этого после команд Ftg-1 К[x] останется ±1, если страж далеко от игрока, или ноль, если рядом. В этом случае идёт переход на блок ошибки. А если всё хорошо, то эта ±1 прибавляется к плану этажа.

Завершающая проверка КП→x7 используется тот факт, что в R8 число отрицательное (в других регистрах < 8 – нет), поэтому если игрок вышел из здания (положение R7 = 8), то так мы узнаем об этом.

При выходе просто показываем уровень заряда (наши достижения). Если затем нажать С/П, то будет выполнение короткой и длинной побочной ветви программы, которые опять же описаны здесь: Недокументированные возможности. Но команда Кx≥07 по адресу 24 вернёт программу в основную ветвь исполнения. Это очередная защита от дурака (на начало без В/О).

8. Блок обработки ошибки

Да, всего одна команда. Так уж получилось (снова удачно), что маска пустого этажа из Re заканчивается на AA (--), а малой ветви исполнения (адрес AA уже вне диапазона) соответствует адрес 05. Тот самый логарифм. Вот он зачем. На него мы переходим с регистром X ≤ 0, т. е. логарифм всегда выдаст ошибку. А команда В↑ поможет показать, из-за чего именно ошибка.

Более того, при ошибке одна команда по адресу AB (адрес 06) будет пропущена (это тоже описано в упомянутом выше документе) и следующим будет адрес AC (или 00), но уже в длинной ветви исполнения, которая, как и ранее, прервётся по Кx≥07 на 24-м адресе. Это значит, после ошибки можно смело сразу нажимать С/П для начала новой игры. Снова защита от дурака, чтобы программа не выполнялась после фактического завершения.

История программы

Идею программы я взял с другой, которую позднее выложили на ресурс Замок с призраками. Хотя та программа и содержит незначительные ошибки и отсутствие некоторых проверок (как и последующий ремикс, который уже при первом запуске не показывает положения игрока), главное недовольство вызывало то, что игра была фактически на двоих. Один должен был придумать план замка, который пройти возможно, тщательно вбить с кучей начальных параметров. А другой один разочек пройти (для второго разочка снова нужен первый или нудно вбивать все параметры, частично раскрывая информацию).

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

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

Несмотря на это, практика показала, что всё равно трудно проходить. Так и появился телепорт. Сначала на произвольное, задаваемое игроком, расстояние. Но тогда получалось слишком легко. Игрок при необходимости просто перепрыгивал стража. Оставил только телепортацию в хранилище.

Часть идей отбрасывалась, например индикацию факта нахождения игрока в хранилище, как не очень нужную, но требующую дополнительных команд. Часть функционала добавлялась, например, удерживания стража на месте при телепортации.

Некоторые лабиринты генерируются такими, что можно бесконечно заряжаться. Так появилось ограничения на количество попыток использования хранилища.

И каждый раз приходилось выдумывать новые способы оптимизировать, чтобы влезть в 105 адресов и 15 регистров. Каждое удачно – несколько часов раздумий и многократных переделок программы. И если в игровом плане программа может показаться не сложной, то основное удовольствие я получил, когда смог таки сделать то, что получилось.