|
Scenergy
#02
31 декабря 1999 |
|
Coding - разбор интро Daingy от Cryss/Razzlers.

Секреты DAINGY
Вот, попросили меня тут написать про мою
интру DAINGY, которая, как известно,
заняла третье место на demoparty CC'999.
Честно говоря, я уже толком и не помню,
как там все устроено, но попытаюсь по ходу
этой статьи вспомнить все тонкости и
раскрыть вам суть некоторых процедур.
Сразу хочу оговориться, что я не
утверждаю, что использованные в интре
методы являются самыми крутыми и не
подлежащими дальнейшей оптимизации. Более
того, сейчас некоторые моменты мне кажутся
весьма неудачно выполненными, но что
сделано, то сделано, тем более, что
конечный результат, по-моему, весьма
неплох. Ну а теперь приступим
непосредственно к рассмотрению исходников
DAINGY.
Прежде всего необходимо описать
распределение памяти в порядке возрастания
адресов, итак:
[line](этот адрес рассчитывается исходя
из адреса chunks)-процедура построения
в теневом буфере линии изображения,
повернутого на некоторый угол.
[chunks]-здесь расположены текстуры.
[sin]-синусоида.
[#8000]-само изображение, длина=#100,
высота=#1000, 1 байт=1 pixel.
[tenbuf]-буфер кадра.
tenbuf EQU #fa00
chunks EQU #7600
sin EQU #7900
line EQU chunks-(linep2-linep1)*32-1
Адреса sin и chunks выбраны таким
образом, чтобы разность между ними была
равна #300, так как здесь используется
одна процедура заливки байтом посредством
LDIR для обнуления участка памяти и для
установления атрибутов, если немного
посидеть и поразбираться, то можно
понять...
Традиционно программа начинается с
decrunching'а. Рассмотрим более подробно
все его этапы.
Сначала идет построение процедуры
поворота одной линии изображения.
ORG #6200
decrun LD de,line
LD a,32
d1 LD hl,linep1
LD bc,linep2-linep1
LDIR
DEC a
JR nz,d1
LDI;ставим команду RET
Далее идет построение chunks по таблице.
Замечу, что они имеют размер 8*4 pixels, и
составлены из пар стандартных чанкoв 4*4.
LD b,32
CALL raspch
CALL raspch
INC hl
DJNZ $-7
Подпрограмма raspch расположена в конце
текста, но для наглядности приведу ее
сейчас:
raspch XOR a
RLD
LD c,A
RLCA
RLCA
RLCA
RLCA
OR c
JR linep2-2;переход на кусок
;кода
;LD (de),A
;INC de
;RET
;тем самым
;экономится один
;байт.
Думаю, в пояснениях этот фрагмент не
нуждается, отмечу лишь, что таблица
содержит все 16*4=64 байта чанков, но так
как в каждом байте значимыми являются
только 4 бита, то она приведена к размеру
64/2=32 байта. Можно было сделать табличку
и меньше, но данный способ компенсируется
минимизацией процедур распаковки.
Следующий кусок кода обеспечивает
заполнение нулями области chunks, коды
которых больше чем #0f.
EX de,HL
XOR a
CALL fill2
Этого можно было и не делать при условии,
что память при запуске не была забита
разным мусором.
Теперь пришло время сформировать
некоторое подобие синуса:
LD l,C;на входе имеем
;следующие значения:
INC a ;C=0
;А=0
;DE=sin+#7f
;H=sin&h-старший байт
;адреса sin
LD c,6
msin2b EX af,AF'
LD a,C
ADD a,7
LD b,A
EX af,AF'
msin2a LD (hl),A
LD (de),A
INC l
DEC e
ADD a,C
BIT 6,l
JR nz,makstr
DJNZ msin2a
DEC c
JR msin2b
Этот способ придуман мной лично и
является самым коротким из всех мне
известных. Попробую объяснить его суть.
Как нетрудно заметить, разность между
текущим и последующим значениями в функции
синуса изменяется от максимума до минимума
при изменении аргумента от 0 до PI/2.
Фактически, эта разность равна значению
первой производной функции sin (x) при
стремлении разности аргументов к нулю. Так
вот, если мы будем некоторую переменную в
цикле увеличивать на константу, которую
время от времени нужно уменьшать (причем
чем больше константа, тем больше раз мы на
нее увеличиваем переменную), то мы получим
некоторое подобие четверти периода синуса.
Этим и занимается вышеприведенный фрагмент
кода. В данном случае мы получаем
полупериод синусоиды с амплитудой
изменяющейся от #01 до #ef без учета
знака.
Дальше идет формирование основной
надписи, вращение над которой вы и можете
лицезреть после запуска DAINGY. Здесь все
достаточно тривиально, разве что для
уменьшения объема кода буквы строки
расположены в формате (letter-#20)*4.
Используется стандартный фонт,
расположенный по адресу #3d00, литеры
которого утолщаются в горизонтальном
направлении.
makstr LD ix,string
LD de,#8000
ff1 LD l,(ix)
INC ix
LD bc,#3d00
LD h,C
ADD hl,HL
ADD hl,BC
EX de,HL
LD b,8
ff3 PUSH bc
PUSH hl
EX de,HL
LD a,(HL)
RRCA
OR (hl)
EX de,HL
INC de
LD b,8
ff2 RLCA
LD c,0
JR nc,$+4
LD c,15
LD (hl),C
INC h
LD (hl),C
INC l
LD (hl),C
DEC h
LD (hl),C
INC l
DJNZ ff2
POP hl
INC h
INC h
POP bc
DJNZ ff3
LD de,#f010
ADD hl,DE
EX de,HL
INC e ;проверка
DEC e ;на конец
JR nz,ff1;строки
Пришло время установить цвет BORDER'а и
ATTRIBUTES в обоих экранах
OUT (#fe),a;А=0
CALL fill
Подпрограмма fill выглядит следующим
образом:
fill LD a,#55
CALL fill1
LD a,#57+8
fill1 OUT (#fd),a
LD a,65
LD hl,#d800
fill2 LD d,H
LD e,L
INC de
LD (hl),A
LD bc,sin+#7e-chunks-64;
LDIR ;BC>=#300 след-но
RET ;атрибуты мы установили
;правда с небольшим
;избытком...
На этом процесс подготовки заканчивается,
и начинается основной цикл, повторяющийся
256 раз, т.к. при достижении этого участка
регистр B содержит 0.
start PUSH bc
CALL wave;построение очередной
;фазы так называемой
;"бегущей волны"
Вообще-то сюда нужно было сразу вставить
текст этой подпрограммы, тем самым
сэкономив 4 байта, но впопыхах я забыл это
сделать...
ugol LD a,(kx+1);координата X
ADD a,#97-#f0;в зависимости
;от нее
;рассчитывается
;приращение по
;одной из осей
LD e,A
CALL takesin ;dy=1*sin(alpha)
LD c,A
LD a,E
RLCA
RLCA
AND #7f
LD l,A
LD d,(HL)
LD a,#f0;попробуйте XOR а
RLD
CPL
LD (hl),D
LD (plus+1),a;устанавливаем
;основной уровень
;интенсивности фона
LD a,E
RLCA
ADD a,C;A=beta
EXX
CALL takesin;исходя из этого
;рассчитывается
;приращение по другой оси
LD c,A; dx=1*cos(beta)
Этот участок кода формирует траекторию
"полета", приближения, возникающие в
результате того, что при вычислении
приращений значения аргументов alpha и
beta не всегда отличаются на #40, и ,
вдобавок ко всему, устанавливает текущий
цвет фона.
После получения приращений в BC и BC'
осуществляем поворот надписи, с занесением
полученного результата в буфер по адресу
tenbuf.
kx LD a,#10;координата X
INC a
LD (kx+1),a;A=X+1
LD h,A
JR nz,$+7;если X=#100, то
;начинаем процесс затемнения
LD a,#c6;ADD A,n
LD (zatemn),a
EXX
LD de,tenbuf
w2 PUSH bc
PUSH bc
EXX
PUSH hl
PUSH bc
EXX
PUSH hl
CALL line
POP hl
POP bc
ADD hl,BC
EXX
POP hl
POP de
SBC hl,DE
EXX
POP bc
DEC bc;изменяем одно из
;приращений для
;внесения искажения.
DEC bc
LD a,D
OR a
JR nz,w2;если не достигли
;конца буфера, то
;повторяем поворот
;для следующей линии
Итак, поворот мы осуществили, теперь
настало время создать некую иллюзию
трехмерности, путем создания боковых
граней у букв. Делается это весьма просто:
поочередно мы смотрим каждый столбец в
tenbuf, как только встречается байт,
отличный от нуля, начинаем заполнять все
следующие байты столбца значением, каждый
раз уменьшающимся на единицу. Делается это
до тех пор, пока не получим отрицательное
число, либо не достигнем нижней границы
буфера.
Вот текст этой процедуры:
LD c,15
LD de,32
LD l,D
m3d6 LD h,tenbuf&h
m3d4 LD a,(HL)
LD b,C
OR a
JR z,m3d1
m3d2 ADD hl,DE
JR nc,m3d4
JR m3d5
m3d3 LD a,(HL)
OR a
JR nz,m3d2
m3d1 DEC b
JP m,$+4
LD (hl),B
ADD hl,DE
JR nc,m3d3
m3d5 INC l
BIT 5,l
JR z,m3d6
Данный метод построения имеет один очень
существенный недостаток: если буква
целиком не помещается в буфере, то мы
будем наблюдать резкий разрыв боковых
граней, уходящих за пределы буфера. Чтобы
сгладить этот эффект я затемняю верхнюю
часть изображения, изменяя яркость от 0 до
максимума начиная от верхней границы.
LD hl,tenbuf
PUSH hl
LD c,15
LD b,61
de3a LD a,(HL)
SUB c
JR nc,$+3
XOR a
LD (hl),A
INC hl
DJNZ de3a
DEC c
JR nz,de3a-2
А для того, чтобы плавно появить и в
конце-концов затемнить картинку я просто
сдвигаю информацию о чанках.
LD hl,chunks
LD d,H
INC d
swet LD a,64
LD e,A
LD c,64
LDIR
zatemn SUB 4
JR c,$+5
LD (swet+1),a
Тот же эффект можно было получить
непосредственно при выводе, но тогда бы и
без того невысокая скорость работы
несколько уменьшилась.
Но так или иначе все преобразования кадра
выполнены и осталось только его вывести на
экран. Замечу, что я не "раскручивал"
для ускорения программу вывода, а выполнял
ее просто циклом.
HALT
LD b,D;в B устанавливаем
;старший байт адреса
;информации о чанках
LD a,#55+8
XOR 10
LD ($-3),a
OUT (#fd),a
LD de,#c000
POP hl ;в HL получаем
;адрес tenbuf
ch4 PUSH de
LD a,32
ch1 EX af,AF'
LD a,(HL)
plus ADD a,0 ;прибавляя здесь
;некоторое значение
;мы устанавливаем
CP 16 ;цвет фона и
JR c,$+5;получаем яркостные
CPL ;разводы на буквах
AND 15
RLCA
RLCA
LD c,A
PUSH de
!ASSM 3
LD a,(BC);этот фрагмент
INC bc ;кода
LD (de),A;повторяется
INC d ;3 раза
!CONT
LD a,(BC)
LD (de),A
POP de
INC e
INC hl
EX af,AF'
DEC a
JR nz,ch1;проверка на конец
;линии
POP de
LD a,D
ADD a,4
LD d,A
AND 7
JR nz,ch4
LD a,E
ADD a,32
LD e,A
JR c,ch2
LD a,D
SUB 8
LD d,A
ch2 LD a,H;проверка на конец
;изображения
OR a
JR nz,ch4
st1 POP bc
DEC b
JP nz,start;проверка
;окончания
;основного
;цикла
LD hl,10072;выход
EXX ;из
RET ;интро
Далее идут различные подпрограмки и
таблички, вкратце рассмотрим и их.
Табличное вычисление синуса
takesin LD l,A
LD h,sin&h
RES 7,l
RLCA
LD a,(HL)
LD b,0
RET nc
NEG
DEC b
RET
на входе : в A-аргумент для синуса
на выходе: в A-младший байт синуса
B-старший байт синуса
Данные для надписи.
string DB #28,#8c,#c8,#e4
DB #cc,#cc,#3c,#c8
DB #e8,#b0,#28,#8c
DB #8c,#64,#64,#64
Строка "*CRYSS/RZL*CC999"
Итерация поворота для одного пиксела
linep1 ADD hl,BC;смещaемся по Y
LD a,H
EXX
AND %00010
;используется для
;некоторого
;увеличения картинки
RRCA ;в высоту
ADD a,#80
LD d,A
ADD hl,BC;смещаемся по X
LD e,H
LD a,(DE)
EXX
LD (de),A;занесение пиксела
INC de ;в tenbuf
linep2 RET
Описание chunks.
DB 0,0,#80,0,#80,#20,#a0,#20
DB #a0,#a0,#a4,#a0,#a4,#a1
DB #a5,#a1,#a5,#a5,#e5,#a5
DB #e5,#b5,#f5,#b5,#f5,#f5
DB #fd,#f5,#fd,#f7,#ff,#ff
raspch см. выше
fill см. выше
"Бегущая волна"
wave LD hl,#8000;координаты
PUSH hl ;"фронта волны"
LD b,16
wave1 LD a,B
CP 9 ;проверка на
wave2 JR c,wave3 ;"пик волны"
LD c,A
LD a,17
SUB c
INC l
JR $+3
wave3 DEC l
XOR (hl) ;преобразование
LD (hl),A;изображения под
;"фронтом волны"
INC h ;переход к
;следующей линии
DJNZ wave1
POP hl
INC l ;изменение
LD (wave+1),hl;координат
RET ;"фронта"
Ну вот вроде бы и все, что касается кода
DAINGY. Возможно я и забыл описать
кой-какие тонкости, но общая идея должна
быть вам понятна, а это, как мне кажется,
и есть основная задача этой статьи. Ведь
для дальнейшего развития нашего любимого
SPECCY мы должны делиться нашими знаниями
и опытом, чтобы облегчить и ускорить выход
нового качественного софта, чтобы
продвигать в свет новые методы кодинга, да
и вообще, чтобы просто повышать уровень
интеллекта.
И напоследок, хочется снять завесу тайны
со слова "DAINGY". Если вы попытаетесь
просто найти его в словаре, то у вас
ничего не выйдет, так как это аббревиатура
двух английских слов: dainty и gyration,
что в переводе означает "изящное
вращение". А теперь попытайтесь составить
аббревиатуру из этих слов, но на русском
языке... Догадались? Если да, то вы по
достоинству оцените наши старания в
подборе оригинального названия для интры.
За сим прощаюсь с вами до встречи в наших
новых программах.
BYE!
(c) Cryss/Razzlers
Другие статьи номера:
Похожие статьи:
В этот день... 13 ноября