Секреты LPRINT, часть 1
Я уже писал об использовании трюка с LPRINT в статье об апрельском демо "Making of Back to Basics". Но поскольку снова звучат просьбы о пояснениях, расскажу об этом трюке поподробнее.
Начнем с самого начала. Впервые трюк с LPRINT я встретил в пиратском кассетном загрузчике игры TIGER ROAD (тот самый релиз):
К тому времени я уже достаточно наигрался с бейсиком, делал бегущие строчки (подсмотренные в другом загрузчике), разобрался с большинством команд и пытался делать игры, задавая переопределяя символы через UDG и рисуя лабиринты типа игры KLAD. То есть я уже ощущал возможности и ограничения бейсика. Так вот, когда я увидел LPRINT в действии в том загрузчике, — я был в шоке. Как так? Каким образом? Это был нереальный хак, позволявший выводить символы, растянутые по вертикали в 8 раз в любую треть экрана. Ведь это было не документировано!
Вдоволь напрактиковавшись с этим трюком тогда в 90-х, я не нашел ему никакого применения. Т.к. не понимал ничего — ни принципа его работы, ни устройства экранной памяти спектрума. Циферки в POKE 23681,64 были для меня чистой МАГИЕЙ.
В следующий раз я столкнулся с LPRINT в 2011 году в демо BBB by JtN/4D. Надписи в этом демо так же печатались по середине экрана. Но был и спецэффект — они хаотично прорезались пустыми линиями.
Это натолкнуло меня на мысль, что кроме стандартного пзу-шного шрифта можно выводить еще блочные символы Graphics Mode и, пожалуй, не только их!
Первое настоящее переосмысление случилось в апреле 2015 года. В чем для меня была суть LPRINT, как я понимал его до переосмысления? Очень просто:
В системную переменную 23681 записывались значения в цикле 64-71 для первой трети экрана, 72-79 для второй, 80-87 для третей. После каждой записи выводилась строка через LPRINT:
10 FOR Q=72 TO 79 20 POKE 23681,Q 30 LPRINT " HELLO WORLD!" 40 NEXT Q
Что было дальше? Первое, что пришло в голову — а что будет, если на каждой итерации цикла выводить разные строки? И что такое 72-79?
Если вспомнить структуру и адресацию экранной области, то 72 — это старший байт адреса начала средней трети экрана. 73 — это старший байт следующего 256-байтного блока в средней трети, который идет на 1 строчку пикселей ниже предыдущего блока. То есть вся треть экрана состоит из 8 блоков по 256 байт и, соответственно, в ячейке 23681 адресуются эти самые блоки. LPRINT же занимается тем, что строку символов он печатает в буфер (по умолчанию расположенный с адреса 23296 — тот самый буфер принтера). Если мы перенаправляем вектор печати в экранную область, то тем самым мы печатаем 1 строку символов в один из 256-байтных блоков той или иной трети экрана. Сообразно структуре экрана данные ложатся в 8 разных пиксельных строк выбранной трети экрана с заданным смещением по вертикали в пикселях, в зависимости от старшего байта экранного адреса, который мы записали в системную переменную 23681.
Если же каждую итерацию цикла выводить разные строки, то в каждую треть мы можем вывести произвольный спрайт размером 256x64 пикселей. Для этого нам понадобится использовать системную переменную CHARS (23606,23607), строковую переменную с символами с кодами от 32 до 63, например и специальным образом перекодированный спрайт.
Системная переменная CHARS, в отличие от UDG, позволяет нам не задавать набор дополнительных 21 символов, а переопределять весь основной шрифт из 96 символов. Хотя для вывода в одну треть экрана нам будет достаточно и первых 32 символов (при выводе полного экрана конечно можно экономить на одном POKE и задействовать все 96 символов, выводить сразу 3 блока — по одному в каждую треть, что потребует немного иной перекодировки данных).
Код вывода спрайта 256x64 в первую треть экрана будет выглядеть следующим образом:
10 CLEAR 49151: LOAD "SCREEN" CODE 49152
20 FOR a=64 TO 71
30 POKE 23607,191+a-64: POKE 23681,a: LPRINT " !"+CHR$ 34+"#$%&'()*+,-./0123456789:;<=>?"
40 NEXT a
Итак, далее нам необходима перекодировка экранного спрайта в формат для вывода через LPRINT. Она требуется потому что данные для вывода на экран берутся из символов. И если экран кодируется линейными блоками по 256 байт, то шрифт в памяти — это спрайт размером 1хN байт. То есть нам надо взять каждую треть экрана и перекодировать байты в другом порядке: первый символ нашего нового шрифта мы берем из адресов 16384 до 16384+255 с шагом 32, далее таким же образом вся первая строка до 16384+31, затем смещение на 256 байт от начала экрана (на 1 строчку пикселей вниз) и снова таким же образом 32 символа. Таким образом на бейсике перекодировщик выглядит следующим образом:
10 CLEAR 49151: LET C=49152
20 FOR q=16384 TO 16384+6143 STEP 256
30 FOR w=q TO q+31
40 FOR e=w TO w+255 STEP 32
50 POKE c,PEEK e: POKE e,0: LET c=c+1
60 NEXT e: NEXT w: NEXT q
После чего готовый блок 6144 байт мы можем выгрузить с адреса 49152. Такой метод я использовал для вывода полноэкранных картинок уже в Back 2 Basics.
Оставалось придумать, каким образом вывести атрибуты, чтобы был полноценный вывод полноэкранной картинки.
Первой мыслью было закодировать атрибуты управляющими кодами в обычный PRINT и напечатать поверх. Но у меня что-то не срослось с печатью в нижние две строки экрана (с этим я разобрался только в YSKB) и пришлось думать дальше.
В результате нашлось очень простое решение. Ведь если мы переставляем вектор LPRINT в экранную область, то кто нам запрещает выставить вектор в область атрибутов?
Более того, вывод атрибутов получается довольно быстрый — всего за 3 связки POKE+POKE+LPRINT. А если задать 3 строковых переменных для 96 символов, то снова экономим одно POKE.
Перекодировка атрибутов немного проще перекодировки пикселей:
10 CLEAR 49151: LET c=49152+6144
20 FOR q=22528 TO 22528+767 STEP 256
30 FOR w=q TO q+31
40 FOR e=w TO w+255 STEP 32
50 POKE c,PEEK e: POKE e,0: LET c=c+1
60 NEXT e: NEXT w: NEXT q
Таким образом сделан вывод полноэкранных картинок в B2B и YSKB:
10 CLEAR 49151: LOAD "" CODE 49152
20 LET a$=" !"+CHR$ 34+"#$%&'()*+,-./0123456789:;<=>?"
30 FOR a=1 TO 27
40 POKE 23607,190+a: POKE 23681,63+a: LPRINT a$
50 NEXT a
Забегая дальше, можно сказать, что таким образом можно перебрасывать произвольные данные в памяти. Но надо учитывать, что данные перебрасываются не линейно, а неким образом «транспонируются», т.е. меняется порядок байтов.
Закономерный вопрос — что становится с блоком данных при серии подобных перебросок? Ответ: после 8ой переброски блок принимает исходный порядок байтов, т.е. всего мы имеем 8 различных «транспонированных» состояний. Имея буфер в 256 байт и достаточное количество времени мы можем перебрасывать произвольные блоки данных, не меняя порядка байтов внутри.
Учитывая зацикленность «транспонирования» через LPRINT можно написать конвертор картинок в описанный выше формат через сам же LPRINT. Работать он будет быстрее, чем приведенные выше процедуры.
Продолжение следует.
PS картинка в шапке была больше для привлечения внимания :) Но она и вправду выводилась в демо с помощью LPRINT и переключения теневого экрана.
22 комментария
«Я человек простой. Не вижу сиськи — всё равно ставлю лайк!»