Home > Previous Page >  Big Character Scroller Demo
Archive Search  

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