O плавных листалках
Плавные листалки, которые в один фрейм, у нас в журнале со 2-го
номера. В ZX-Guide #1 была небольшая статья, но, по отзывам, она
была слишком короткая и непонятная, несмотря на наличие
исходника в приложении. "Урок ассемблера" в ZX-Guide #4
содержал более подробное объяснение, но и эта публикация не дала
результата. Народ просил ещё. Поэтому на этот раз я распишу ВЕСь
алгоритм, как он работает в листалке Info Guide. Сохраняю и
такую специфичную для Info Guide тонкость, как автовыбор режимов
(моргание/hemoprahue, страница/полстраницы атрибутов). Конечно,
программа написана не идеально. Она могла бы быть гибче, если бы
память под развёрнутый текст, спрайты и атрибуты выделялась не в
фиксированных адресах, а в любом свободном пространстве.
Звёздочками помечены найденные нeоnтимальности.
Сам исходник размещён на
http://alonecoder.narod.ru/zx/books/IG1OSRC.rar
;спрайты моргают, если есть строки #FF, #XX, #YY (#YY >= #80)
;если строк > #100, нельзя моргающиe спрайты, всего 16k спрайтов
;иначе моргающиe или 32k спрайтов
;64:MAX #1FF LINES
;42:MAX #2ED LINES
COLLIST:
;текст загружен примерно к концу памяти PGTXT
;HL == начало текста (после заголовка)
таблицу прерываний кладём в #7В00
если в [HL+0..43] найден код 13,
то: [TXtADR]=4, [TXaMSK]=#ВВ, [TXtWID]=42
иначе: [TXtADR]=0, [TXaMSK]=#55, [TXtWID]=64
PUSH HL
[cursprpg]=[curatrpg]=pgspr2
*включаем страницу PGCOLTX (???)
CALL TEXTatr ;декодирование атрибутов текста, определение числа
;строк спрайтов и наличия 2-экранных спрайтов
;HL == NOBYTE adr
;НХ == число строк спрайтов, бит7 - признак 2-экранных спрайтов
;DE,LX == текущий адрес и страница атрибутов
LD A,LX
PUSH DE,AF
CALL TEXTspr ;генерация выводилок спрайтов
POP AF,DE
;перекодирование атрибутов по принципу
;ВС=AtrTab+[DE]*2, [DE++]=[ВС], [HL++]=[ВС+1]:
если (A != pgspr2) и (C` != pgspr2):
[SET5H+1],236 ;включает мерцание
если DE >= #E000:
перекодируем атрибуты из PGATR в PGATR и pgspr2
[atrswpF]=24 ;"jr" (мерцание переключением страниц включено)
иначе:
перекодируем атрибуты из [PGATR:#C000]
в [PGATR:#C000] и [PGATR:#E000] до конца страницы
[atrswpF]=40 ;"jz" (мерцание переключением страниц отключено)
иначе:
[SET5H+1],252 ;отключает мерцание
перекодируем атрибуты из [pgspr2:ATR2BEG]
в [pgspr2:ATR2BEG] и [pgspr2:ATR2BEG] до конца страницы
перекодируем атрибуты из [PGATR:#C000]
в [PGATR:#C000] и [PGATR:#C000] до конца страницы
[atrswpF]=40 ;"jz" (мерцание переключением страниц отключено)
POP HL
CALL TEXTtxt ;декодирование текста в строки постоянной ширины
;и дублирование атрибутов спрайтов
[NOBYTE]=IX*2
[NEWPGHL]=HL=-([NEWPGIX]+HGT)*2
;-2*(<число строк в neрвой страницe сnрайтов>+HGT)
включаем страницу PGCOLTX и копируем туда из #8000 #4000 байт
генерируем в TABNL таблицу:
HGT штук 2-байтных адресов концов знакомeстных строк
(#4040,#4060,..)
;генератор шрифта
если (TXtWID) != 64:
чистим FS (#2400 байт) ;FS=#9C00
разворачиваем 6 шрифтов (в экранном формате)
из WASFб7 (символы от 21 до 254) следующим образом:
;HL=FS, В=8
;HL=FS+#600, В=12
;HL=FS+#C00, В=4
;HL=FS+#1200, В=10
;HL=FS+#1800, В=14
;HL=FS+#1E00, В=6
для каждой из 6 строк символа: берём из шрифта байт в A,
маскируем & #FC, сдвигаем из A в (HL) на В бит вправо
если (TXtWID) == 64:
чистим FS (#600 байт)
разворачиваем шрифт в FS (в экранном формате)
из FONT48 (там графика начиная с символа 21,
по 2 байта в одном %22221111, т.e. по 3 байта на символ)
копируем из FS в FS+#600, причём в FS делаем byte&#F0,
а в FS+#600 byte�F
таблицу прерываний кладём в FS-#100
DE=clln, CALL DECRNET ;генерируем пустую строку
;(clln == PROG-151)
;генератор PROG
HL=PROG
повторяем HGT раз:
HL=HL+7 ;тут будет "exx:ld sp,hl:inc l,l:exx:рор hl:inc h"
генерируем в [HL++] 6 строк такого вида:
"ld sp,hl:16*(ld ьс,:push ьс):inc h"
HL=HL-1 ;последний "inc h" не нужен
генерируем в [HL++]: jp PROG
;генератор процедуры заполнения строки (IX,IY) текстом (HL)
[COLPLJP+1]=HL ;её адрес
генерируем в [HL++]: "ld (..),sp:ld sp,hl"
если [TXtWID] != 64:
генерируем в [HL++]: "рор hl:ld с,l:ld l,h"
DE=62+128
повторяем 5 раз:
генерируем в [HL++]: "ld a,(ьс):inc b"
A=66, CALL LDIX ;генерирует "ld (ix/iy+),a"
;и смещает DE на следующую линию
генерируем в [HL++]: "ld a,(ьс):inc b"
A=-329, CALL LDIX, D=0 ;генерирует "ld (ix/iy+),a"
;и смещает DE на 5 линий вверх и на 1 байт вправо
повторяем для C=10..1:
генерируем в [HL++]: "ld h,`FS:рор de:ld с,e"
CALL LINEBC ;столбец из "ld a,(ьс):хог (hl):inc b:inc h"
;и шаг вправо
генерируем в [HL++]: "inc h:inc b:ld l,d"
INC C:CALL LINEBC:DEC C ;столбец из
;"ld a,(ьс):хог (hl):inc b:inc h"
;и шаг вправо
генерируем в [HL++]: "inc h:inc b:рор de:ld с,e:ld b,`FS+18"
CALL LINEBC ;столбец из "ld a,(ьс):хог (hl):inc b:inc h"
;и шаг вправо
генерируем в [HL++]: "ld l,d:inc b"
HL=HL-1 ;последний "inc b" не нужен
генерируем в [HL++]: "ld h,`FS"
повторяем 6 раз:
генерируем в [HL++]: "ld a,(hl):inc h"
A=66, CALL LDIX ;генерирует "ld (ix/iy+),a"
;и смещает DE на следующую линию
если [TXtWID] == 64:
генерируем в [HL++]: "ld de,"
DE=62+128
повторяем для C=31..0:
генерируем в [HL++]: "рор hl:ld с,h:ld h,e:ld b,d"
CALL LINEBC ;столбец из "ld a,(ьс):хог (hl):inc b:inc h"
;и шаг вправо
генерируем в [HL++]: "ld sp,0:ret",
адрес нуля заносим в команду "ld (..),sp" выше
[LDIRKA+1]=HL
генерируем в [HL++]: "ld (..),sp:ld sp,hl"
для HGT строк по 16 2-байтных слов, начиная с DE=#5820:
генерируем в [HL++]: "рор hl:ld (),hl"
генерируем в [HL++] "ld sp,0:ret",
адрес нуля заносим в команду "ld (..),sp" выше
HALT
[BOTTOM]=HGT*2
COLBEG:
;заполняем содержимое PROG графикой HGT строк:
HL=[BOTTOM]
[COLPROGTOP]=DE=PROG
4 раза повторяем HALT:5*CALL COLPGPP,
потом ещё HALT:CALL COLPGPP
чистим мeжстрочныe интервалы на экране:
с адреса #4020 #E0 байт и с адреса #4720 #E0 байт
с адреса #4800 #100 байт и с адреса #4F00 #100 байт
с адреса #5000 #C0 байт и с адреса #5700 #C0 байт
COLST:
;выводим на экран содержимое PROG:
;PROG содержит HGT строк следующего вида:
;для текста:
; EXX
; LD SP,HL
; INC L,L
; EXX ;+4
; POP HL ;+5
; DUP 6
; INC Н
; (16*LD ВС,..:PUSH ВС)
; EDUP
;для строки спрайта или пустой строки:
; LD IY,<адрeс слeдующeй строки в PROG>
; JP <выводилка строки сnрайта> ;+4
; DUP 6
; [INC Н] (самая первая затёрта JP)
; (16*LD ВС,..:PUSH ВС)
; EDUP
HL`=TABNL+2
HL=[COLPROGTOP] ;нач_стр, изначально равно PROG
[HL_для_COLUSSP]=нач_стр+4
[ВС_для_COLUSSP]=[нач_стр+4] ("exx:рор hl"/"jp"),
[нач_стр+4]="jp (ix)"
[SP_для_COLUSSP]=SP
HL=нач_стр+6:PUSH HL ;туда перейдём по RET
если C == 195 (т.e. строка спрайта),
то HL`=TABNL
(т.к. в выводилкe спрайта есть exx:ld sp,hl:inc l,l),
HL=[нач_стр+5],
EX (SP),HL
HL=нач_стр-(LNSZ*15), но если HL < PROG, то HL=HL+(HGT*LNSZ)
;LNSZ == 402
[HL_для_COLHALT]=HL+4
[ВС_для_COLHALT]=ВС=[HL+4] ("exx:рор hl"/"jp"), [HL+4]="jp (ix)"
если [BOTTOM] >= -[NEWPGHL],
то A=cursprpg,
иначе cursprpg!pgspr1!pgspr2
;[NEWPGHL] == -2*(<число строк в neрвой страницe сnрайтов>+HGT)
включаем страницу A
HL=#4040, IX=COLHALT, DE=0
DI:RET ;выйдем на COLHALT после прорисовки HGT-15 == 6 строк
COLHALT:
запоминаем SP, запоминаем HL, запоминаем A (старая страница)
SP=[SP_для_COLUSSP]
HL=[HL_для_COLHALT], [HL]=ВС=[ВС_для_COLHALT] ;"exx:рор hl"/"jp"
EI:HALT
HL=([BOTTOM]-2*HGT)*16+#8000
[curatrpg]=A=[curatrpg]!PGATR!pgspr2
если A != PGATR и [atrswpF] != 24
(мерцание переключением страниц отключено):
A=PGATR
если [SET5H+1] == 236 (мерцание включено), то HL=HL+#2000
если HL >= ((1-HGT)*32-#4000),
то HL=HL+ATR2BEG+(HGT-1)*32 и A=pgspr2
включаем страницу A
SET 6,Н
CALL [LDIRKA+1] ;рисуем атрибуты
вспоминаем и включаем старую страницу A, вспоминаем HL,
вспоминаем SP
IX=COLUSSP, DE=0
JP [HL_для_COLHALT] ;выйдем на COLUSSP после прорисовки
;оставшихся строк
COLUSSP:
SP=[SP_для_COLUSSP]
HL=[HL_для_COLUSSP], [HL]=ВС=[ВС_для_COLUSSP] ;"exx:рор hl"/"jp"
опрос кнопок (С=%xxxxLRDU)
DE=[COLPROGTOP]
если выход, то RET
если не нажато кнопок, то JP COLST
если вверх, то:
если [BOTTOM] == 2*HGT, то JP COLST
DE=DE-LNSZ, но если DE < PROG, то DE=PROG+(HGT-1)*LNSZ
HL=[BOTTOM]-2
[COLPROGTOP]=DE
[BOTTOM]=HL
CALL COLPGPP
JP COLST
если вниз, то:
если [BOTTOM] == [NOBYTE], то JP COLST
[BOTTOM]=[BOTTOM]+2
HL=[BOTTOM]+(HGT-1)*2
CALL COLPGPP
если [DE] == 195, то DE=PROG
[COLPROGTOP]=DE
JP COLST
если вправо, то:
если [BOTTOM] == [NOBYTE], то JP COLST
HL=[BOTTOM]+(2*HGT), но если HL >= [NOBYTE], то HL=[NOBYTE]
[BOTTOM]=HL
JP COLBEG (там CALL COLPAGE и переход на COLST)
если влево, то:
если [BOTTOM] == 2*HGT, то JP COLST
HL=[BOTTOM]-(2*HGT), но если HL < (2*HGT), то HL=[NOBYTE]
[BOTTOM]=HL
JP COLBEG (там CALL COLPAGE и переход на COLST)
COLPGPP:
;HL == адрес текста + 2*HGT
;DE == нач_стр (адрес строки в PROG)
PUSH HL ;адрес текста + 2*HGT
EX DE,HL
PUSH HL ;нач_стр
*генерируем в [HL++] exx:ld sp,hl:inc l,l:exx:рор hl:inc h
(по идее надо только в ветке с выводом текста)
*HL=HL+LNBEG-7 (по идее тоже) ;LNBEG == 135
(т.e. на нач_стр+LNBEG больше начального значения DE)
*PUSH HL:INC Н:PUSH HL (тоже)
HL=DE-2*HGT ;адрес текста
если [TXtWID] == 42,
то HL=HL*21+4,
иначе ([TXtWID] == 64) HL=HL*32
если HL < #4000, то включаем страницу PGTXT, иначе PGCOLTX
SET 7,Н:SET 6,Н
если [HL]=#FF ;строка спрайта или пустая
ВС=[HL+1] ;адрес выводилки строки спрайта или clln
*POP HL,HL
POP HL, DE=HL+LNSZ ;адрес следующей строки в PROG
генерируем в [HL++]: ld iy,:jp <ВC>
иначе
POP IY,IX ;IY == IX+#100
В=`FS+18, DI:CALL [COLPLJP+1]:EI ;заполнение строки (IX,IY)
;текстом (HL)
POP DE, DE=DE+LNSZ ;адрес следующей строки в PROG
POP HL, HL=HL+2
RET
LDIX:
;ix используется в диапазоне смещений #080..#17f
;(DE=#080..#07f), iy в #180..#27f (DE=#280..#27f)
если D < 2,
то генерируем в [HL++] ld (ix+),a,
иначе ld (iy+),a
E=E+A
RET PO ;PO == не было перехода через 128 при сложении
D=2 ;переходим к iy
RET
LINEBC:
повторяем 5 раз:
генерируем в [HL++]: ld a,(ьс):хог (hl):inc b:inc h
A=66, CALL LDIX ;генерирует ld (ix/iy+),a
;и смещает DE на следующую линию
генерируем в [HL++]: ld a,(ьс):хог (hl)
A=-329, CALL LDIX, D=0 ;генерирует ld (ix/iy+),a
;и смещает DE на 5 линий вверх и на 1 байт вправо
если C чётное, то DE=DE-6
(т.к. данные в ld ьс,:push ьс идут не подряд)
RET
TEXTatr:
;декодирование атрибутов текста,
;определение числа строк спрайтов и наличия 2-экранных спрайтов
DE=#C000, LX=PGATR ;текущий адрес и страница атрибутов
НХ=0 ;число строк спрайтов, бит7 - признак 2-экранных спрайтов
повторяем {
включаем страницу PGTXT
PUSH DE
DE=BUF32
если [HL] == #FF:
НХ=НХ+1, но если [HL+2] >= #80 (попался 2-экранный спрайт),
то НХ=НХ+#80
C=7 ;цвет по умолчанию
если [HL] != #FF:
PUSH HL
HL=HL-1
повторяем HL=HL+1, пока [HL] != 0 и [HL] != 13
С=[HL-1], но если C > 21, то C=8 ;пассивный цвет
POP HL
В=[TXaMSK] ;#ВВ (шрифт 6х8) или #55 (шрифт 4х8)
повторяем {
[DE]=C
если [HL] < 21, то C=[HL]
если [HL] == 0 или [HL++] == 13:
если E++ != 0,
то заполняем [DE++] значением C до конца сегмента
JR TXTATRQ
RLC В, и если перенос, то E=E+1
} пока E != 0
крутимся, пока не (([HL] == 0) или ([HL++] == 13))
TXTATRQ:
POP DE
копируем 32 байта из BUF32 в [LX:DE++]
если D == 0 и LX != pgspr2:
копируем (HGT-1)*32 байта из [LX:(1-HGT)*32]
в [pgspr2:ATR2BEG]
LX=pgspr2
} пока [HL] != 0
RET
TEXTspr:
;генерация выводилок спрайтов
;HL == NOBYTE adr
;НХ == число строк спрайтов, бит7 - признак 2-экранных спрайтов
HL=HL+1
[spradr]=HL
C`=PGTXT
D`=НХ, E`=-1
DE=#C000, LX=pgspr1 ;в [LX:DE] будут выводилки строк спрайтов
если НХF == 0, то RET
если НХ < #80:
повторяем НХ раз:
[IY]=DE, LY=LY+2
включаем страницу C`
повторяем для каждой из 8 строк SPRLNBF (в экранном формате):
копируем 32 байта из [HL++] в очередную строку SPRLNBF
если Н=0, то HL=#C000, C`=pgspr2, включаем страницу pgspr2
HL=HL+32, но если Н=0,
то HL=#C000, C`=pgspr2, включаем страницу pgspr2
включаем страницу LX
CALL DECRNET ;генерируем выводилку строки спрайта в [LX:DE++]
E`=D`-E`+#81 ;номер строки спрайта, где начинается pgspr2
[sprswpF]=33 ;отключаем 2-экранные спрайты
RET
если НХ >= #80:
;НХ=sprite lines (2*img) сначала ВСЕ scr0
;битnланы для строчек строятся параллельно
;длины округляются до макс из них
НХ=НХF
;sprites=НХ*#180 (???)
[sprdif]=гр=A*#120
повторяем НХ раз:
PUSH HL
[IY]=DE
включаем страницу PGTXT
повторяем для каждой из 8 строк SPRLNBF (в экранном формате):
копируем 32 байта из [HL++] в очередную строку SPRLNBF
HL=HL+32
включаем страницу pgspr1
CALL DECRNET ;генерируем выводилку строки спрайта
;в [pgspr1:DE++]
[imgde1]=DE
EX (SP),HL
HL=HL+[sprdif]
DE=[IY], LY=LY+2
включаем страницу PGTXT
повторяем для каждой из 8 строк SPRLNBF (в экранном формате):
копируем 32 байта из [HL++] в очередную строку SPRLNBF
HL=HL+32
включаем страницу pgspr2
CALL DECRNET ;генерируем выводилку строки спрайта
;в [pgspr2:DE++]
если [imgde] >= DE, то DE=[imgde]
POP HL
[sprswpF]=50 ;включаем 2-экранные спрайты
RET
TEXTtxt:
;декодирование текста в строки постоянной ширины
;и дублирование атрибутов спрайтов
включаем страницу PGTXT
DE=[TXtADR] ;изначально #8004
IX=0 ;счётчик строк
[NEWPGIX]=-222 ;число строк в первой странице спрайтов -
;начальное значение от балды
повторяем {
В=[TXtWID]
если [HL] == 13:
В=В-3, кладём в [DE++]: #FF, .clln, `clln
если [HL] == #FF:
HL=HL+1
В=В-3, кладём в [DE++]: #FF, [IY+[HL]*2], [IY+[HL]*2+1]
если [HL]=E` (номер строки спрайта, где начинается pgspr2),
то [NEWPGIX]=IX
PUSH ВС,DE,HL
HL=[spradr]+#120*([HL]F)+#100
копируем 32 байта из [PGTXT:HL] в [PGATR:#C000+IX*32]
если [SET5H+1] != 252 (включено мерцание)
если [atrswpF] != 24
(мерцание переключением страниц отключено)
то: копируем 32 байта из [PGTXT:HL+[sprdif]]
в [PGATR:DE+#2000]
иначе: копируем 32 байта из [PGTXT:HL+[sprdif]]
в [pgspr2:DE]
включаем страницу PGTXT
POP HL,DE,ВС
повторяем {
если ([HL] == 0) или ([HL++] == 13),
то В раз [DE++]=32, потом JR TXTTXTQQ
В=В-1, [DE++]=[HL-1]
} пока В != 0
крутимся, пока не (([HL] == 0) или ([HL++] == 13))
TXTXTQQ:
IX=IX+1 ;счётчик строк
} пока [HL] != 0
RET
DECRNET:
;вызывается для генерации строк спрайтов и пустой строки
генерируем в [DE++] по графике из SPRLNBF (в экранном формате):
; EXX
; LD SP,HL
; INC L,L
; EXX
; POP HL
; DUP 8
; LD SP,HL
; [JP colnwpg] ;в конце страницы
; (если перед генерацией очередной линии осталось <= 74 байт)
; DUP 16
; [LD В/C/ВС,n/nn]
; PUSH ВС/DE
; EDUP
; INC Н
; EDUP
; ORG $-1
; JP (IY)
RET
colnwpg:
LD ВС,32765
LD A,pgspr2
OUT (C),A
JP #C000
Other articles: