NOLIST
ORG #A000
RUN program_begin
;; This program visualizes the relation between movement speed and frame rate.
;; Higher speeds combined with small frame rates lead to undesirable effects
;; like jumping movement, trails, and ragged object borders. These effects
;; cannot be avoided by double-buffering. Only a higher frame rate will
;; reduce them.
;;
;; See:
;; - <https://en.wikipedia.org/wiki/Stroboscopic_effect>
;; - <https://en.wikipedia.org/wiki/Wagon-wheel_effect>
ga_port EQU #7F00
ppi_port_b EQU #F500
os__key_code__escape EQU #FC
os__key_code__cursor_left EQU #F2
os__key_code__cursor_right EQU #F3
number_of_sprites EQU 23
program_begin:
;; Set mode 2 and clear screen.
LD A,2 : CALL os__scr_set_mode
;; Print row and column headers.
LD HL,row_and_column_headers : CALL print_string ;; Print headers.
LD B,1 ;; Sprite counter. Count upwards.
prach__loop: ;;
LD H,4 : LD L,B : INC L : INC L : CALL os__txt_set_cursor ;; Set cursor to (4|counter+2).
LD A,B : CALL pad_number_with_space : CALL print_number ;; Print counter with padding.
LD A,10 : CALL os__txt_set_column ;; Set cursor to (10|*).
LD HL,50 : LD A,B : CALL divide ;; Calculate 50/counter.
PUSH AF ;;
LD A,L : CALL pad_number_with_space : CALL print_number ;; Print quotient with padding.
LD A,"." : CALL os__txt_output ;; Add a decimal point.
POP AF ;;
ADD A,A : LD C,A : ADD A,A : ADD A,A : ADD A,C ;; Calculate remainder*10. Maximum remainder is 16 for 50/17.
LD C,A : LD A,B : SRL A : ADD A,C ;; Calculate remainder*10+counter/2 for rounding.
LD L,A : LD A,B : CALL divide ;; Calculate (remainder*10+counter/2)/counter.
LD A,L : CALL print_number ;; Print one decimal place.
INC B : LD A,B : CP A,number_of_sprites+1 : JR NZ,prach__loop ;; Loop while counter less or equal 23.
;; Main loop.
main_loop:
;; Wait for VSYNC.
LD B, ppi_port_b / 256 ;;
vsyncIsAlreadyActive: IN A,(C) : RRA : JR C,vsyncIsAlreadyActive ;; Wait for end of VSYNC.
waitForBeginOfVSync: IN A,(C) : RRA : JR NC,waitForBeginOfVSync ;; Now wait for begin of VSYNC.
;; Wait longer until screen refresh has passed drawing area.
HALT : HALT : HALT : HALT : HALT : HALT ;; Wait for 2+5*52 = 262 pixel lines.
LD B,144 : DJNZ $ : NOP ;; Wait for 144*4/64 = 576/64 = 9 more pixel lines.
;; For debugging: mark begin of sprite update.
IF 1 ;;
DEFS 5 ;; Align raster to begin of non-border region.
LD BC, ga_port + #10 : OUT (C),C ;; Select pen of border.
LD C,#55 : OUT (C),C ;; Set ink "bright blue".
LD A,8 : DEC A : JR NZ,$-1 : NOP ;; In combination with other commands: Wait 40 chars.
LD C,#44 : OUT (C),C ;; Set ink "dark blue".
ENDIF ;;
;; Update sprite position.
LD HL,(position) : LD DE,(speed) : ADD HL,DE ;; Update position.
LD A,H : AND A,#3F : LD H,A ;; Restrict to a maximum byte offset of 63.
LD (position),HL ;; Store position.
;; Prepare bit pattern for new position. This is at least used by the fastest row.
ADD HL,HL : ADD HL,HL : ADD HL,HL : LD A,H : AND A,#07 ;; Extract bit position.
LD BC,#FF00 ;; Start with set block at right.
JR Z,pbp__skip ;; Skip for offset 0.
pbp__loop: ;;
SRL B : RR C ;; Shift right.
DEC A : JR NZ,pbp__loop ;;
pbp__skip: ;;
LD IYH,B : LD IYL,C ;; Store bit pattern in IY.
;; Update sprites.
LD B,number_of_sprites ;; Counter.
LD DE,#0800 ;; Offset from one scanline to next.
LD HL, 80*2 + 15 + #C000 ;; Set sprite base address on screen to third row, character 15.
LD IX,sprite_table ;; Pointer to current sprite descriptor.
ml__sprite_loop: ;;
LD A,(IX+sprite__counter) : DEC A : JR NZ,mlsl__not_at_zero ;; Decrement sprite counter. If not zero, skip sprite processing.
;; ;;
PUSH HL ;;
LD A,(IX+sprite__position) ;; Get old sprite position in bytes.
ADD A,L : LD L,A : JR NC,$+3 : INC H ;; Add to screen address.
XOR A,A ;; Set A to eight background pixels.
ADD HL,DE ;; Delete old sprite in region 2x4 bytes, starting at scanline 2.
REPEAT 2 ;;
ADD HL,DE : LD (HL),A : INC HL : LD (HL),A ;;
ADD HL,DE : LD (HL),A : DEC HL : LD (HL),A ;;
REND ;;
POP HL ;;
;; ;;
LD A,(position+1) : LD (IX+sprite__position),A ;; Get new sprite position. Store in sprite descriptor.
;; ;;
PUSH BC : PUSH HL ;;
ADD A,L : LD L,A : JR NC,$+3 : INC H ;; Add nes sprite position to screen address.
LD B,IYH : LD C,IYL ;; Set BC to bit pattern.
ADD HL,DE ;; Draw new sprite in region 2x4 bytes, starting at scanline 2.
REPEAT 2 ;;
ADD HL,DE : LD (HL),B : INC HL : LD (HL),C ;;
ADD HL,DE : LD (HL),C : DEC HL : LD (HL),B ;;
REND ;;
POP HL : POP BC ;;
;; ;;
LD A,number_of_sprites+1 : SUB A,B ;; Reset sprite counter. Calculate number_of_sprites + 1 - loop_counter.
;; ;;
mlsl__not_at_zero: ;;
LD (IX+sprite__counter),A ;; Store updated or reset sprite counter.
LD A,L : ADD A,80 : LD L,A : JR NC,$+3 : INC H ;; Go to next row on screen.
INC IX : INC IX ;; Go to next sprite descriptor.
DJNZ ml__sprite_loop ;;
;; For debugging: mark end of sprite update.
IF 1 ;;
LD BC, ga_port + #10 : OUT (C),C ;; Select pen of border.
LD C,#55 : OUT (C),C ;; Set ink "bright blue".
LD A,32 : DEC A : JR NZ,$-1 : NOP ;; Wait 128 chars.
LD C,#44 : OUT (C),C ;; Set ink "dark blue".
ENDIF ;;
;; Check keys.
LD HL, 36 * 256 + 1 : CALL os__txt_set_cursor ;; Set cursor to position of speed.
LD HL,(speed) ;; Load speed.
CALL os__km_read_char : JP NC,main_loop ;; Get last pressed key. Continue loop, if nothing was pressed.
CP A,os__key_code__escape : JR Z,ml__leave ;; Leave loop, if <Esc> has been pressed.
CP A,os__key_code__cursor_left : JR NZ,$+3 : DEC HL ;; Left cursor key decreases speed.
CP A,os__key_code__cursor_right : JR NZ,$+3 : INC HL ;; Right cursor key increases speed.
LD A,H : OR A,L : JR NZ,$+3 : INC HL ;; Minimum speed is 0|01 = 1.
LD A,H : CP 2 : JR C,$+3 : DEC HL ;; Maximum speed is 15|31 = 511.
LD (speed),HL ;; Store updated speed.
LD B,L ;; Store fraction in B.
ADD HL,HL : ADD HL,HL : ADD HL,HL : LD A,H ;; Extract integer speed.
CALL pad_number_with_space : CALL print_number ;; Print integer speed.
LD A,"|" : CALL os__txt_output ;; Print separator.
LD A,B : AND A,#1F ;; Extract fractional speed.
CALL pad_number_with_zero : CALL print_number ;; Print fractional speed.
JP main_loop
ml__leave:
;; Set mode 1 and clear screen.
LD A,1 : CALL os__scr_set_mode
;; Program end.
RST #00 ;; Option 1: Reset system.
;;DI : HALT ;; Option 2: Halt system.
;;RET ;; Option 3: Return to caller.
divide:
;; Divide 16-bit by 8-bit number.
;; Source: <https://wikiti.brandonw.net/index.php?title=Z80_Routines:Math:Division>
;; Input:
;; HL - dividend / numerator
;; A - divisor / denominator
;; Output:
;; HL - quotient
;; A - remainder
;; * - Everting else preserved.
PUSH BC ;;
LD C,A ;;
XOR A,A ;; Collected bits
LD B,16 ;; Number of bits, loop counter.
d__loop: ;;
ADD HL,HL : RLA ;; Shift HL to the left, transfer topmost bit to A.
JR C,d__found_set_bit ;;
CP A,C ;; If the current bit is not set, and also not enough bits have
JR C,d__smaller_than_divisor ;; been collected in A, go on with collecting more bits.
d__found_set_bit: ;;
SUB A,C ;; Reduce collected bits.
INC L ;; Build up quotient. This will not interfere with the use as dividend.
d__smaller_than_divisor: ;;
DJNZ d__loop ;;
POP BC ;;
RET ;;
binary_to_bcd:
;; Use "double dabble" algorithm to convert a binary number to BCD format.
;; See <https://en.wikipedia.org/wiki/Double_dabble> for more details.
;; Input:
;; A - Binary number in range 0..99.
;; Output:
;; A - BCD number in range 0..99.
;; * - Everything else preserved.
PUSH BC : PUSH HL ;;
LD H,0 : LD L,A : SLA L ;; Store binary number (7 bits) on right/lower side.
LD B,7 ;; Number of bits to process; loop counter.
btb__loop: ;;
LD A,H : AND A,#0F ;; Get lower BCD digit.
CP A,5 : JR C,$+4 : ADD A,3 ;; If greater or equal to 5, add 3.
LD C,A ;; Save in C.
LD A,H : AND A,#F0 ;; Get higher BCD digit.
CP A,5*16 : JR C,$+4 : ADD A,3*16 ;; If greater or equal to 5, add 3.
ADD A,C : LD H,A ;; Store both BCD digits in H.
ADD HL,HL ;; Shift bits.
DJNZ btb__loop ;;
LD A,H ;; Move result to A.
POP HL : POP BC ;;
RET ;;
print_string:
;; Input:
;; HL - Address of a zero-terminated string.
;; Output:
;; HL - Changed.
;; A - Changed.
;; * - Everyting else preserved.
ps__loop: ;;
LD A,(HL) : INC HL ;; Read next character.
OR A,A : RET Z ;; If zero, leave routine.
CALL os__txt_output ;; Print character.
JR ps__loop ;;
print_number:
;; Input:
;; A - Number.
;; Output:
;; A - Changed.
;; * - Everyting else preserved.
CALL binary_to_bcd ;; Convert to BCD.
PUSH AF ;; Save lower digit.
SRL A : SRL A : SRL A : SRL A ;; Shift upper to lower digit.
CALL NZ,pn__x ;; Print out upper digit, if not zero.
POP AF ;; Restore lower digit.
pn__x: ;;
AND A,#0F : ADD A,"0" : CALL os__txt_output ;; Mask lower digit, convert to character, and print.
RET ;; Return to above call or to caller.
pad_number_with_space:
;; Input:
;; A - Number.
;; Output:
;; * - Everyting preserved.
CP A,10 : RET NC
PUSH AF
LD A," " : CALL os__txt_output
POP AF
RET
pad_number_with_zero:
;; Input:
;; A - Number.
;; Output:
;; * - Everyting preserved.
CP A,10 : RET NC
PUSH AF
LD A,"0" : CALL os__txt_output
POP AF
RET
os__scr_set_mode EQU #BC0E
;; Input:
;; A - Screen mode in range [0..2].
os__txt_set_column:
;; Input:
;; A - Column, one-based.
;; Output:
;; * - Everyting preserved.
PUSH AF : PUSH HL
CALL #BB6F
POP HL : POP AF
RET
os__txt_set_cursor:
;; Input:
;; H - Row, one-based.
;; L - Column, one-based.
;; Output:
;; * - Everyting preserved.
PUSH AF : PUSH HL
CALL #BB75
POP HL : POP AF
RET
os__txt_output EQU #BB5A
;; Input:
;; A - Character to write at current cursor location.
;; Output:
;; * - Everyting preserved.
os__km_read_char EQU #BB09
;; Input:
;; -
;; Output:
;; flag.C - If C, then key is returned.
;; A - If C, this is the returned key.
;; * - Everyting else preserved.
row_and_column_headers:
;; 12345678901234567890123456789012345678901234567890123456789012345678901234567890
DEFB " delay FPS speed [px/frame] : 1|00 (use keys LEFT and RIGHT)", 13, 10
DEFB "[frames] [Hz]", 0
speed:
;; This stores the current speed in 1/256 bytes/frame.
;; Range is 1..511.
DEFW 32 ;; Start value 32 corresponds to 1 pixel/frame.
position:
;; This stores the current sprite position in 1/256 bytes.
;; Range is 0..63*256.
DEFW 0
sprite_table:
sprite__counter EQU 0
sprite__position EQU 1
REPEAT number_of_sprites
DEFB 1 ;; Current counter. Counting downwards.
DEFB 0 ;; Sprite position in bytes. Range 0..63.
REND
EX DE,HL : LD BC,0 :source_skip : ADD HL,BC : EX DE,HL ;; 8
LD A,E : ADD A,0 :source_skip : LD E,A : JR NC,$+3 : INC D ;; 7
LD A,E : ADD A,0 :source_skip : LD E,A ;; 4
Page created in 0.046 seconds with 12 queries.