Важная информация
RSS лента

akasaka

Псевдо-глюки для Спектрума

Рейтинг: 1.88. Голосов: 8.
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

Смотрим...



Ярко, красочно! Но вот слишком быстро

Напишем небольшую процедурку, которая будет тратить такты впустую:
Код:
_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
А затем заменим первый LDDR на конструкцию, которая будет копировать мягонько:

Код:
		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 отсюда
Метки: Нет Добавить / редактировать метки
Категории
Без категории

Комментарии

  1. Аватар для UA3MQJ
    Спасибо! Помню в детстве тоже делал LDIR из ПЗУ в экранную область.
  2. Аватар для PATHNK
    Наконец-то я узнал почему образуются эти странные полосы красного цвета при сбросе. Действительно странно, почему 2? Логично записать как минимум FF а потом 00 с проверкой. Спасибо за статью.
  3. Аватар для goodboy
    очередное изобретение велосипеда
    https://files.zxdemo.org/files/Hercules.zip
  4. Аватар для goodboy
    вот ещё одна имитация
    http://zxaaa.untergrund.net/get.php?...b21ed49f72abad
    (смотреть интро, клавиши не нажимать)

Трекбэков