Псевдо-глюки для Спектрума
Hello, All!
Заранее прошу прощения за длинные блоки кода, но в блогах пока что сломаны спойлеры, поэтому спрятать под них не получается
Начитался я тут старых спектрумных журналов, где публиковались разные интересные исходники, и пропёрло меня самому тоже что-нибудь прислать
Эта статья, наверное, не настолько будет полезна матёрым спектрумистам, которые могут выдать байт из ПЗУ по запрошенному адресу, будучи разбуженными в три часа ночи, а простым чайникам и кофейникам вроде меня, коих в нашем деле нынче маловато
В процессе создания своей будущей демки (а какой — это вам, как и мне, еще предстоит увидеть), я написал два эффекта, имитирующих программно-аппаратные проблемы на спектруме, о них же сегодня и пойдет речь.
Первый эффект —
Глюк видеопамяти
Честно говоря, он не претендует на оригинальность, поскольку первый раз я его увидел в We Are Alive, и стало интересно реализовать его самому.
(сам эффект на 04:27)
Казалось бы, просто забиваем область экрана случайным значением... и понимаем, что генерировать такое количество псевдослучайных чисел за один инт нереально (ну или у меня руки кривые), да и выглядят они не очень красочно. Вдобавок ко всему — у GembaBoys шумом экран забивается снизу вверх, а потом появляется сразу весь экран атрибутов.
Как это у GembaBoys, по кадрам:
Через пару минут приходит озарение... А что, если генерировать случайное значение один раз, а потом использовать его как адрес в ПЗУ? Ведь для пользователя код в ПЗУ, выведенный на экран, вряд ли будет выглядеть осмысленно. Но при этом разбег рядом стоящих значений достаточно большой — выглядеть будет ярко и красочно
Пробуем так и сделать:
Код:DEFINE CLS #0D6B ; очистка экрана из ПЗУ DEFINE ATTR_AREA 22528 ; область атрибутов DEFINE ATTR_SZ 767 ; размер атрибутов DEFINE SCREEN_AREA 16384 ; область экрана DEFINE SCREEN_SZ 6144 ; размер экрана OH_SHIT: ; подпрограмма эффекта CALL CLS ; чистим экран LD A,7 OUT (#FE),A ; ставим серый бордюр LD H,0 LD A,R LD L,A ; ставим псевдослучайное значение HL, причем H=0, L=[0..255] LD DE,SCREEN_AREA+SCREEN_SZ-1 ; Конечный адрес экрана в DE LD BC,SCREEN_SZ ; длина экрана в BC ADD HL,BC ; перемещаем псевдослучайный указатель в ПЗУ на размер_экрана вперёд (а то вдруг в ноль попали) LDDR ; заполнили экран LD H,0 LD A,R LD L,A ; еще один случайный адрес LD DE,ATTR_AREA+ATTR_SZ ; теперь в область атрибутов LD BC,ATTR_SZ+1 ; заполняем её целиком значениями из ПЗУ ADD HL,BC ; смещаем псевдослучайный указатель на длину_атрибутов вперёд (а то вдруг снова в ноль попали) LDDR RET
Смотрим...
Ярко, красочно! Но вот слишком быстро
Напишем небольшую процедурку, которая будет тратить такты впустую:
А затем заменим первый LDDR на конструкцию, которая будет копировать мягонько:Код:_WASTE PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL RET
В итоге получаем готовый листинг программы:Код:LD A,25 _copy1 LD B,255 ; 25*255 = 6375 -- немного залетаем в атрибуты, но нам пойдёт, так как мы их всё равно впоследствии перепишем CALL _WASTE ; тратим время CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE _copy2 LDD ; копируем байт DJNZ _copy2 ; и не один, а 255 штук DEC A ; уменьшаем внешний счетчик JP NZ,_copy1 ; и так 25 раз
Код:DEFINE CLS #0D6B ; очистка экрана из ПЗУ DEFINE ATTR_AREA 22528 ; область атрибутов DEFINE ATTR_SZ 767 ; размер атрибутов DEFINE SCREEN_AREA 16384 ; область экрана DEFINE SCREEN_SZ 6144 ; размер экрана OH_SHIT: ; подпрограмма эффекта CALL CLS ; чистим экран LD A,7 OUT (#FE),A ; ставим серый бордюр LD H,0 LD A,R LD L,A ; ставим псевдослучайное значение HL, причем H=0, L=[0..255] LD DE,SCREEN_AREA+SCREEN_SZ-1 ; Конечный адрес экрана в DE LD BC,SCREEN_SZ ; длина экрана в BC ADD HL,BC ; перемещаем псевдослучайный указатель в ПЗУ на размер_экрана вперёд (а то вдруг в ноль попали) LD A,25 _copy1 LD B,255 ; 25*255 = 6375 -- немного залетаем в атрибуты, но нам пойдёт, так как мы их всё равно впоследствии перепишем CALL _WASTE ; тратим время CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE CALL _WASTE _copy2 LDD ; копируем байт DJNZ _copy2 ; и не один, а 255 штук DEC A ; уменьшаем внешний счетчик JP NZ,_copy1 ; и так 25 раз LD H,0 LD A,R LD L,A ; еще один случайный адрес LD DE,ATTR_AREA+ATTR_SZ ; теперь в область атрибутов LD BC,ATTR_SZ+1 ; заполняем её целиком значениями из ПЗУ ADD HL,BC ; смещаем псевдослучайный указатель на длину_атрибутов вперёд (а то вдруг снова в ноль попали) LDDR RET _WASTE PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL PUSH HL POP HL RET
И такую картинку:
Стильно, модно, молодёжно
Второй эффект уже несколько сложнее, более того, это не просто эффект, а целый набор подпрограмм, имитирующий
Вылет в бейсик-48
Думаю, многим знаком тот характерный видеоэффект с красными полосками, который происходит, когда программа внезапно берёт и сбрасывает компьютер.
Первопричина его кроется в том, что при запуске с нулевого адреса программа в ПЗУ забивает всё ОЗУ двойками, для простейшей его проверки. Если мы обратимся к документации по видеопамяти спектрума, то увидим, что 2 — это красный INK при чёрном PAPER'е.
Последующее же затухание связано с последующим проходом программы по ОЗУ, которая сравнивает записанный байт с тем, что должен быть, и очищает его.
Заранее скажу — предполагается, что эффект работает сам по себе; без музыки или чего-либо в фоне. Поэтому код его содержит местами HALT для большей реалистичности. Однако, доработка его для удаления HALTов или внедрения вызовов к плееру весьма тривиальна и на работоспособность эффекта не влияет.
Вначале запишем константы и вспомогательную процедуру, которая заполняет область атрибутов заданным значением. Эти подпрограммы хранятся у меня в одном файле с разными хорошими процедурами (а-ля DOWN HL, UP HL), и поэтому я пользуюсь ими при написании эффектов.
Код:DEFINE ATTR_AREA 22528 DEFINE ATTR_SZ 767 DEFINE SCREEN_AREA 16384 DEFINE SCREEN_SZ 6144 DEFINE CLS #0D6B ; очистка экрана из ПЗУ ; ------------------------------------------------------------------- CLR_ATTR ; Запишет в область атрибутов 0 LD A,0 CALL SET_ATTR RET ; ------------------------------------------------------------------- SET_ATTR ; -- A должен содержать атрибут для заполнения LD HL,ATTR_AREA LD B,255 ; 3 раза по 255 байт и еще 3 байта, чтобы заполнить всю область атрибутов регистром А CLLP1 LD (HL),A INC HL DJNZ CLLP1 LD B,255 CLLP2 LD (HL),A INC HL DJNZ CLLP2 LD B,255 CLLP3 LD (HL),A INC HL DJNZ CLLP3 LD B,3 CLLP4 LD (HL),A INC HL DJNZ CLLP4 RET
Также немного укоротим процедуру задержки:
Код:_WASTE PUSH HL POP HL RET
Дальше начинаем имитировать процедуру сброса:
Также, неплохо было бы иметь и какой-то свой текст после сброса в углу экрана.Код:RESET_FX: ; Имитация сброса в RANDOMIZE USR 0 CALL CLS ; очищаем экран (без этого почему-то всё по правде вылетает) LD A,2 ; прописываем везде красный INK при черном PAPERе CALL SET_ATTR LD A,7 OUT (254),A ; ставим серый бордюр ; Забиваем экран снизу вверх двойками LD A,24 ; 24 раза... _RS_L0 LD B,255 ; по 255 раз _RS_L1 LD (HL),2 ; Кладём двойку в текущий HL DEC HL ; Уменьшаем HL DJNZ _RS_L1 ; и так 255 раз DEC A ; Уменьшаем аккумулятор CP 0 ; Если закончился JR NZ, _RS_L0 ; То всё готово LD B,24 ; Но осталось еще 24 байта незаполненных _RS_L5 LD (HL),2 ; Кладём двойку в текущий HL DEC HL ; Опять уменьшаем HL DJNZ _RS_L5 ; и так 24 раза ; Затем забиваем экран сверху вниз нулями LD HL,SCREEN_AREA ; От начала экрана LD A,24 ; 24 раза... _RS_L2 LD B,255 ; по 255 раз _RS_L3 LD (HL),0 ; Кладём 0 INC HL ; Увеличиваем HL CALL _WASTE ; Немного задерживаемся, типа что-то проверяем :-) DJNZ _RS_L3 ; и так 255 раз DEC A ; Уменьшаем аккумулятор CP 0 ; Если закончился JR NZ, _RS_L2 ; то всё готово LD B,24 ; но нужно добить оставшиеся 24 байта _RS_L4 LD (HL),0 ; Кладём 0 INC HL ; Уменьшаем HL DJNZ _RS_L4 ; И так 24 раза
Добавим его в конце программы
Код:; AT 21,0 "© 2016 Genjitsu Research" PROMPT defb 22,21,0,#7F," 2016 Genjitsu Research",13 eoprompt equ $
В процедуру имитации сброса допишем:
Код:LD B,50 ; ждём несколько интов _RS_WAIT HALT DJNZ _RS_WAIT LD A,56 ; забиваем экран стандартными атрибутами CALL SET_ATTR LD DE,PROMPT ; печатаем при помощи процедуры из ПЗУ наш текст LD BC,eoprompt-PROMPT CALL 8252 RET
Проверяем...
Работает!
Но, как известно, извращенству нет предела
Поэтому, хочется ещё и имитировать печать какого-либо текста (к примеру, титров демки) на клавиатуре.
Для начала, положим что-то типа курсора в левый нижний угол экрана, и создадим переменную для хранения места курсора на экране:
Затем пишем процедуру вывода одного символа:Код:cursor_attr_pos defw 23264 RESET_FX_INPUT_MODE: ; Процедура подготовки экрана к имитации ввода с клавиатуры CALL CLS ; очищаем экран для подготовки к имитации печати текста LD A,"L" ; используем символ L, обозначающий ввод произвольного текста в бейсик-48 RST 16 ; выводим его на экран LD HL,23264 ; помещаем в HL место атрибута левого нижнего знакоместа LD (HL),184 ; добавляем ему FLASH LD (cursor_attr_pos),HL ; записываем в переменную место атрибута левого нижнего знакоместа RET
Код:RESET_FX_PUT_LETTER: ; Имитация нажатия клавиши на клавиатуре, A должен содержать печатаемый символ LD C,A ; временно перепрячем А в С LD A,23 ; в А помещаем 7 бордюр и включенный бипер OUT (#FE),A ; дёргаем бипер LD A,C ; возвращаем себе символ в регистр А RST 16 ; выводим его на экран LD A,"L" ; загружаем в А символ L, который играет роль курсора RST 16 ; выводим его на экран LD A,8 ; загружаем в А символ Backspace, чтоб при дальнейшем выводе курсор затирался новым символом RST 16 ; выводим его LD HL,(cursor_attr_pos) ; забираем из переменной предыдущее местоположение атрибута курсора LD (HL),56 ; убираем с него FLASH ; курсор всегда в нижней строке, то есть его атрибут всегда в пределах 5AE0..5AFF. LD A,L ; загружаем младший байт регистровой пары HL в аккумулятор CP #FF ; проверяем его на равенство #FF JP NZ,_NOSCRL ; если он не равен FF, то мы не прокручиваем экран, а значит курсор пока что движется прямо, следующую строку можно пропустить LD L,#DF ; однако, если мы уже вышли за пределы экрана, то нужно вернуть курсор в левый нижний угол, для чего устанавливаем L в #DF (#E0 - #01 = #DF) _NOSCRL INC HL ; увеличиваем HL на 1 LD (HL),184 ; ставим FLASH над новым местоположением курсора LD (cursor_attr_pos),HL ; записываем новое местоположение курсора LD A,7 ; в А помещаем 7 бордюр и отключенный бипер OUT (#FE),A ; отключаем бипер RETПишем простенькую программку для тестирования эффекта:Код:И процедуру, которая выводит строку, заканчивающуюся нулём, имитируя набор её на клавиатуре: RESET_FX_PUT_STR: ; Имитация ввода строки, кончающейся нулём, адрес которой содержится в HL PUSH HL ; припрячем HL CALL CLS ; очистим экран POP HL ; возвращаем HL на место RESET_FX_PUT_STR_LOOP LD A,(HL) ; загружаем символ по адресу (HL) в аккумулятор CP 0 ; если он равен нулю... RET Z ; то выходим из процедуры PUSH HL ; сохраняем HL CALL RESET_FX_PUT_LETTER ; вызываем процедуру печати одного символа POP HL ; возвращаем HL на место INC HL ; увеличиваем HL для перехода к следующему символу LD B,10 ; ждём несколько интов _RPLW HALT DJNZ _RPLW JP RESET_FX_PUT_STR_LOOP ; повторяем для следующего символа RET
И наслаждаемся:Код:ORG 16384 INCBIN "gfx/cgtest.scr" DEVICE ZXSPECTRUM128 ORG 30000 TEST EI LD B,25 CALL WAIT CALL RESET_FX LD B,50 CALL WAIT LD HL,str CALL RESET_FX_PUT_STR LD B,50 CALL WAIT CALL RESET_FX_INPUT_MODE HALT LD HL,str2 CALL RESET_FX_PUT_STR LD B,50 CALL WAIT CALL RESET_FX_INPUT_MODE LD B,50 CALL WAIT LD HL,str3 CALL RESET_FX_PUT_STR LD B,50 CALL WAIT CALL RESET_FX LD B,50 CALL WAIT CALL RESET_FX_INPUT_MODE LD B,25 CALL WAIT LD HL,str4 CALL RESET_FX_PUT_STR LD B,50 CALL WAIT RST 0 WAIT: ; -- B должно содержать количество HALTов HALT DJNZ WAIT RET str defb "ZX Spectrum 48 Basic reset imitation effect",0 str2 defb "Coded by Akasaka for his next demo and ZX-PK.RU",0 str3 defb "RANDOMIZE USR 0",0 str4 defb "This time for real :)",0 INCLUDE "general/screen.asm" ; Всякие экранные дела INCLUDE "effect/reset.asm" ; Сам эффект SAVESNA "out/reset.test.sna",TEST
Итак, в этой статье мы рассмотрели два псевдо-глюка для использования в демках.
Конечно, их можно написать оптимальнее, или доработать — к примеру, добавить определение ширины напечатанного символа в имитации набора с клавиатуры, чтобы поддерживать настоящие токены Бейсика-48.
Это моя первая статья и первые программы на ассемблере, поэтому, надеюсь, в будущем их будет больше и они будут лучше
Спасибо за внимание!
Всегда ваш, akasaka.
P.S. Картинка с Сэйлор Мун для тестирования была утащена у PheeL отсюда
Комментарии
Трекбэков
Всего трекбэков 0
Ссылка трекбэка: