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