Big Character Scroller Demo
| Full title | Big Character Scroller Demo |
| Year of release | 2020 |
| Publisher | mat/ESI |
| Producer / Author(s) | mat/ESI |
| Memory | 16k |
| Type | Demo |
| Cost : | PD |
| Download | BCS Demo
[CRC32 71DAA649] Distribution Permission Allowed | Group 1 |
Instructions
Big Character Scroller Demo
to load enter
0 0 bload b
More information and details will listed below, many thanks to mat/ESI.
Screen shots
Video
Code for education purpose, many thanks to mat/ESI
;=================================================================
;
; BIG CHARACTERS SCROLLER (C) 2020 mat/ESI for speccy.pl
;
; WARNING! This code is not very pretty, it does what it does,
; but probably could be done cleaner, faster or take less space.
; It's provided as-is for the purpose of education.
;
; It is not a assembly language tutorial - to understand
; what is happening here you have to have at least a basic
; knowledge of Z80 assembly language and of Jupiter ACE's
; memory organisation and video workings.
;
; It does not contain all data needed to produce fully working
; "demo" effect - animations bitmaps, fonts etc. are not
; provided as they are not really necessary for the purpose
; of this file.
;
; Comments added on 2020-03-27.
;
; Any language errors, mistakes etc. in comments are not
; intentional and are probably due to haste and the fact that
; english is not my native language ;)
;
;=================================================================
; code should be compiled using pasmo Z80 assembler as it uses
; its macro directives
; code is compiled to $4000 and starts at this address
; this file compiles just fine provided you supply includes
; that are needed for it to work
org $4000
; char declration for scroller pieces
CH_empty: equ 1
CH_left: equ 2
CH_middle: equ 3
CH_right: equ 4
; chars for animated ball
CH_ball: equ $10 ; 25 chars
; separator between scroller and ball area
CH_sep: equ CH_ball+25
di
ld sp,$3fff
; make sure character 0 is empty "space"
ld hl,chars
ld de,$2c00
rept 8
ldi
endm
; load separator char data
ld hl,sep_ch
ld de,$2c00+CH_sep*8
rept 8
ldi
endm
; fill first two thirds of screen with empty char (0)
ld hl,$2400
ld de,$2401
ld bc,$200
ld (hl),0
ldir
; fill the last third with CH_empty - empty piece of scroller
ld bc,$ff
ld (hl),CH_empty
ldir
; fill separating line with separator character
ld hl,$2400+15*32
push hl
pop de
inc de
ld bc,31
ld (hl),CH_sep
ldir
; main loop - synchronized with raster
ei
loop
halt
; remove ball from previous position on screen
call remove_ball
; do the ball animation and put it in the
; new position on screen
call one_ball
; do the scroller
call one_scroll
jr loop
; width of screen - the actual size of ball animation
ball_max: equ 32-5
; ball animation variables
ball_dir: db 0
ball_pos: db 0
ball_anim: db 0
ypos: db 0
; here we remove previous version of ball from the screen
remove_ball
; self-modifying code - position of the ball is
; stored in old_ball after beeing calculated in
; one_ball
old_ball: equ $+1
ld hl,$2400
ld de,ball_max
; put empty char into screen in 5x5 rectangle
; at the position addressed by hl
; loops are unrolled for speed
xor a
rept 5
rept 5
ld (hl),a
inc hl
endm
add hl,de
endm
ret
; here is the actual ball animation code
one_ball
; first we calculate positions using sine table
; table contains half of a sine wave pattern in
; 64 positions calculated so it fit on screen
;
; the data in the table is combined - two bytes
; contain screen address for the beginning of
; line and position in pixels inside one char
; in lowest 3 bits
; current position in table
sinpos: equ $+1
ld a,0
inc a
and 63
ld (sinpos),a
; calculate offset and table address
ld h,0
ld l,a
add hl,hl
ld de,sintab
add hl,de
; get the lower byte, cut lowest three bits and
; put the resulting pixel offset into ypos
ld a,(hl)
and 7
ld (ypos),a
; get the byte again, and mask out the three lowest
; bits
ld a,(hl)
and %11111000
inc hl
; get the high byte of the address
ld h,(hl)
ld l,a
; and store whole address for later
push hl
; now check ball direction - horizontal movement
; is just a simple linear motion from edge to edge
ld a,(ball_dir)
or a
jr z,one_ball_l
; ball_pos is position on screen between 0 and
; ball_max counted from the edge of the screen
; so if we are moving from right we need to
; subtract it from ball_max to get the actual
; position
one_ball_r
ld a,(ball_pos)
ld b,a
ld a,ball_max
sub b
jr one_ball_do
one_ball_l
ld a,(ball_pos)
; position within screen line is in a, position
; of a line is on the stack, we pop it and add the
; two together
one_ball_do
ld h,0
ld l,a
pop de
add hl,de
; and store it for the next call to remove_ball
ld (old_ball),hl
; now we'll put 25 chars starting from CH_ball
; on screen in 5x5 rectangle
ld de,ball_max
ld a,CH_ball
; again - loops unrolled for speed
rept 5
rept 5
ld (hl),a
inc hl
inc a
endm
add hl,de
endm
; now it's time to do the actual ball "sprite"
; animation
; ball_anim is an animation frame number
ld a,(ball_anim)
; we multpily it by 160 - bitmap is 5x4 chars
; and it's 15 frames (the animation was repurposed
; from other project) and it's included later in
; two sets of frames - one for left-to-right and
; the other for right-to-left movement
; ball is "prescrolled" inside and it shows the
; rotation animation depending on direction
ld h,0
ld l,a
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl
push hl
pop de
add hl,hl
add hl,hl
add hl,de
; depending on ball horizontal direction
; we need to use correct bitmap address
ld a,(ball_dir)
ld de,ball_r2l
or a
jr z,one_ball_l1
ld de,ball_l2r
one_ball_l1
add hl,de
; now hl will hold the address of 160 bytes
; linear bitmap of the animation frame we need to
; copy to the actual 25 characters definition
; the ball is 32x32 pixels, but prescrolling adds
; extra 8 pixels to 40x32 and we need to fill
; 25 chars because we'll add ypos pixels of empty
; space at the top to get the vertical movement
; we point ix to the address of the first char
;
; as the bitmap data is storead linear we'll be
; copying it to five consecutive chars so we need a
; way to access it quickly - hense use of ix
ld ix,$2c00+CH_ball*8
; ypos is distance from the top (0-7 pixels) we need
; of our "sprite" inside 5x5 chars square
ld a,(ypos)
or a
ld b,8
; if it's zero we don't need to do anything and
; we'll be moving 8 pixels next
jr z,one_ball_noclear_top
; here we clear ypos lines
ld b,a
call clear_lines
; and calculate how many pixels from the bitmap we need
; to copy into first 5-chars strip (8-ypos)
ld a,(ypos)
ld b,a
ld a,8
sub b
ld b,a
one_ball_noclear_top
; and now we copy (8-ypos) lines into the first
; strip
call copy_lines
; now we do next thress strips - we just copy 8 lines
; three times - copy_lines advances hl so we don't need
; to store or recalculate it
ld b,8
ld ix,$2c00+CH_ball*8+40
call copy_lines
ld b,8
ld ix,$2c00+CH_ball*8+80
call copy_lines
ld b,8
ld ix,$2c00+CH_ball*8+120
call copy_lines
; last strip address
ld ix,$2c00+CH_ball*8+160
; and now we need to copy final ypos pixels and fill
; the rest with zeroes
ld a,(ypos)
or a
; if ypos is zero we don't need to copy anymore
jr z,one_ball_nocopy_bot
ld b,a
call copy_lines
one_ball_nocopy_bot
; and here we just clear (8-ypos) lines in last
; strip
ld a,(ypos)
ld b,a
ld a,8
sub b
ld b,a
call clear_lines
; after that we have a complete anmation frame in
; correct position on screen
; now we need to advance the animation for the next
; loop
; first is a animation frame counter - we increment
; it and if it contain 15 we need to loop the animation
; and move the position
ld a,(ball_anim)
inc a
ld (ball_anim),a
cp 15
; if not we just end the procedure
ret nz
; zero to frame counter
ld a,0
ld (ball_anim),a
; advance ball position
ld a,(ball_pos)
inc a
ld (ball_pos),a
; if we went over ball_max we need to reset and flip
; the direction
cp ball_max+1
ret nz
ld a,0
ld (ball_pos),a
ld a,(ball_dir)
cpl
ld (ball_dir),a
ret
; here are the procedures for clearing and copying data
; from linear bitmap into chars strip
; hl points to the bitmap, ix points to chars strip
; b contains line counter
; we copy data in the loop storing it into
; ix, ix+8, ix+16, ix+14 and ix+32 - five consecutive
; chars in one strip
copy_lines
ld a,(hl)
inc hl
ld (ix),a
ld a,(hl)
inc hl
ld (ix+8),a
ld a,(hl)
inc hl
ld (ix+16),a
ld a,(hl)
inc hl
ld (ix+24),a
ld a,(hl)
inc hl
ld (ix+32),a
inc ix
djnz copy_lines
ret
; clearing works the same - we just put zero to
; chars memory
clear_lines
xor a
clear_lines1
ld (ix),a
ld (ix+8),a
ld (ix+16),a
ld (ix+24),a
ld (ix+32),a
inc ix
djnz clear_lines1
ret
;===============================================================
; this is a scroller procedure
; scroller itself is similar to classic attribute scrollers
; from ZX Spectrum - the only difference is here we have
; character map instead of attributes and we can change
; characters content to get nice 1 pixel per frame animation
; instead of 8 pixels per frame
one_scroll:
; first we have couter for "small" scroll which
; is animation inside chars - it gives us background
; movement and smooth movement of text itself
;
; counter works from 8 to 0 - if we get zero
; we reset it to 8
small_scr_count: equ $+1
ld a,1
dec a
ld (small_scr_count),a
jr nz,do_small_scroll
ld a,8
ld (small_scr_count),a
; and do the animation
; we take the counter (1-8 in a), decrement it
; and subtract from 7 so we get value 0-7 for
; counter of 8-1 - this value is the actual
; animation frame number
do_small_scroll
dec a
ld c,a
ld a,7
sub c
; load frame number to hl and multiply it by 32
ld h,0
ld l,a
add hl,hl
add hl,hl
add hl,hl
add hl,hl
add hl,hl
; add pre-rotated chars address
; we have four chars (hence multiplying by 32) -
; first one is for background, second is scroller "pixel"
; moving into empty background, second is two consecutive
; "pixels" moving from right to left and the last is
; a "pixel" moving out of empty background
ld de,chars_rot
add hl,de
; now we put the 32 bytes into char data and that's the
; pixel animation done
ld de,$2c00+CH_empty*8
rept 32
ldi
endm
; we check frame counter and if it's not 8 ie. we looped
; the animation and need to do the chars moving we just
; end the procedure
ld a,(small_scr_count)
cp 8
ret nz
; here we'll do the chars scrolling to get the actual
; 8 pixels a time movement
do_big_scroll:
; here we count character columns - from 8 to zero
; if we get to zero we need to get another character from
; scroller text and calculate the table for big scroller
big_scr_count: equ $+1
ld a,1
dec a
ld (big_scr_count),a
jp nz,one_scroll0
; reset columns counter
ld a,8
ld (big_scr_count),a
; get next char, zero is the end indicator - we then
; loop to the beginning of the text
text_pos: equ $+1
ld hl,text
ld a,(hl)
inc hl
or a
jr nz,gen_big_char
ld hl,text
ld a,(hl)
inc hl
gen_big_char
ld (text_pos),hl
; normal ASCII codes so we subtract space code
sub 32
ld h,0
ld l,a
; multiply by 8
add hl,hl
add hl,hl
add hl,hl
; and add the address of the font data
ld de,font
add hl,de
; now we have to change 8 bytes of font data
; to 8x8 bytes table of chars for scroller
; here is our destination
ld de,char_buf
; 8 bytes
ld b,8
gen_big_char1
push bc
; 8 pixels in byte
ld b,8
; get the byte from memory
ld c,(hl)
gen_big_char2
; load CH_empty - empty background char to a
ld a,CH_empty
; rotate font byte to the left
rl c
; skip if it's zero
jr nc,gen_big_char_zero
; change A to CH_middle - in first position it's just
; a big "pixel"
ld a,CH_middle
gen_big_char_zero
; put it into buffer
ld (de),a
inc de
; and loop it...
djnz gen_big_char2
pop bc
inc hl
; ...twice, incrementing hl to point to next byte in
; font data
djnz gen_big_char1
; now we have our big character in buffer but it's just
; two chars for now which wouldn't be pretty if we stopped
; here - first "pixel" on the left of each piece of font
; and last on the right whould have its edges cut and
; movement would be jesrky
; now we have to do some ugly - it could probably be done
; in prittier manner (and faster), but for now we'll do
; it so it just work
; first we have to scan the buffer for "pixels" that
; are at the last column or have empty "pixel" on its
; right - for such byre we need to change it to CH_right
; end of first line of buffer to ix
ld ix,char_buf+7
; 8 lines
ld b,8
gen_right1
; store lines counter and pointer on the stack
push bc
push ix
; check if last byte in line is empty
ld a,(ix)
cp CH_empty
jr z,gen_right_nr
; if not change it to CH_right
ld (ix),CH_right
gen_right_nr
; move pointer to the left
dec ix
; pixel counter - we need to check 7 more
ld b,7
gen_right2
; is current pixel empty?
ld a,(ix)
cp CH_empty
jr z,gen_right_no
; no... is the one on its right empty?
ld a,(ix+1)
cp CH_empty
jr nz,gen_right_no
; yes - we change it to CH_right
ld (ix),CH_right
gen_right_no
; and loop the line
dec ix
djnz gen_right2
; pop the pointer and advance it by 8 to next line
pop ix
ld bc,8
add ix,bc
; pop line counter and loop
pop bc
djnz gen_right1
; now we have right edge of scroller character taken care
; of - it will scroll neetly out of its background
; let's do it again this time from left to right
; second "pixel" in line
ld ix,char_buf+1
; line counter
ld b,8
gen_left1
; store...
push bc
push ix
; 7 "pixels" in line - this time we just search for
; non-empty "pixels" with empty one on the left and
; then we need to change that empty one to CH_left
ld b,7
gen_left2
; is current "pixel" empty?
ld a,(ix)
cp CH_empty
jr z,gen_left_no
; no... is one to the left epmty?
ld a,(ix-1)
cp CH_empty
jr nz,gen_left_no
; yes - change it to CH_left
ld (ix-1),CH_left
gen_left_no
; loop the line
inc ix
djnz gen_left2
; and the lines
pop ix
ld bc,8
add ix,bc
pop bc
djnz gen_left1
; now we have 8x8 bytes buffer with character mapping
; bitmap for our next char like this:
; bits bytes
; 00000000 00000000
; 00111100 01222300
; 01000010 13000030
; 01000010 13000030
; 01111110 12222230
; 01000010 13000030
; 01000010 13000030
; 00000000 00000000
; now we need to move the data we have on screen one
; byte to the left and fill the rightmost byte with
; the first column from the buffer
one_scroll0
; second byte of screen part
ld hl,$2601
; first byte of screen part
ld de,$2600
; character buffer
ld ix,char_buf
; 8 lines
ld b,8
one_scroll1
push bc
; copy 31 bytes from (hl) to (de) moving whole line
; minus last char one char to the left
rept 31
ldi
endm
; copy one byte from buffer and advance hl and de
ld a,(ix)
ld (de),a
inc de
inc hl
; advance buffer address to next line
ld bc,8
add ix,bc
; and loop it all - we've scrolled the screen putting
; first column from buffer into the rightmost column
; on screen
pop bc
djnz one_scroll1
; now we just move the whole buffer by one byte moving
; data so next time ix will point to the second column
;
; we don't care about what happens beyound that - we
; don't need to scroll the buffer neatly
ld hl,char_buf+1
ld de,char_buf
ld bc,63
ldir
ret
; this is a scroller text - of course ;)
text
db "BIG CHARACTERS SCROLLER TEST FOR JUPITER ACE "
db "(C) 2020 MAT/ESI FOR SPECCY.PL "
db 0
; space for caharcter buffer
char_buf:
ds 64
; initial chars definitions
chars:
; empty char for character 0
db 0,0,0,0,0,0,0,0
; pre-rotated "pixels" - 4 chars, 8 frames
chars_rot
include "tiles5.asm"
; standard bitmap font - 768 bytes, 8 bytes per letter
font:
include "font.asm"
; separator character
sep_ch: db %11111111
db %10101010
db %01000100
db %00010000
db %00000000
db %00000000
db %00000000
db %11111111
; sine pattern for ball "jumping"
sintab
include "ball_sin.asm"
; bitmaps of ball animation moving from right to
; left - prescrolled and animated
ball_r2l
include "ball3_bitmap_l.asm"
; same for moving from left to right
ball_l2r
include "ball1_bitmap_l.asm"
; start address - pasmo with my own patches assembles
; Jupiter code adding small "autoloader" and generating
; .tap file with the following address as an argument
; to initiall FORTH CALL after loading data block
;
; it can also be compiled using non-patched version of
; pasmo using command:
; pasmo --bin bigscroll.asm bigscroll.bin
; which would produce binary file that then needs to be
; transfered into Jupiter's memory (or the emulator)
; starting from 16384 and then code could be started
; using:
; 16384 call
end $4000