mirror of
https://github.com/UzixLS/pacemuzx.git
synced 2025-07-18 23:01:36 +03:00

The old method used a mix of black attributes and data masking to trim sprites at the screen edges, with every sprite trimmed separately. The new method no longer uses attributes, so it's compatible with the colour version. It also finds the extent of the trim block for all sprites, for more efficient clearing.
2967 lines
83 KiB
NASM
2967 lines
83 KiB
NASM
; Pac-Man hardware emulation for the Sinclair ZX Spectrum (v1.2)
|
|
;
|
|
; http://simonowen.com/spectrum/pacemuzx/
|
|
|
|
debug: equ 0 ; non-zero for border stripes showing CPU use
|
|
colour: equ 1 ; non-zero for switchable colour/mono, zero for mono-only
|
|
|
|
; Memory maps
|
|
;
|
|
; Loading (normal paging R/5/2/0):
|
|
; a000-afff - emulation code
|
|
; b000-bfff - unshifted tiles+sprites
|
|
; c000-ffff - 16K Pac-Man ROM
|
|
;
|
|
; Emulation (special paging 0/1/2/3):
|
|
; 0000-3fff - 16K Pac-Man ROM
|
|
; 4000-50ff - Pac-Man display, I/O and working RAM
|
|
; 5100-7fff - unused
|
|
; 8000-9fff - 2nd half of sprite data
|
|
; a000-afff - emulation code
|
|
; b000-bfff - look-up tables
|
|
; c000-dfff - 8K sound table
|
|
; e000-ffff - first 8K of Pac-Man ROM (unpatched)
|
|
;
|
|
; Graphics (normal paging R3/5/2/7):
|
|
; 0000-3fff - Spectrum 48K ROM
|
|
; 4000-5aff - Spectrum display (normal)
|
|
; 5b00-5bef - screen data behind sprites (normal)
|
|
; 5bf0-9fff - pre-rotated sprite graphics
|
|
; a000-afff - emulation code
|
|
; b000-bfff - look-up tables
|
|
; c000-daff - Spectrum display (alt)
|
|
; db00-dbef - screen data behind sprites (alt)
|
|
; dbf0-ffff - pre-rotated tile graphics
|
|
|
|
default_attr: equ &07 ; default = white on black
|
|
|
|
kempston: equ 31 ; Kempston joystick in bits 4-0
|
|
divide: equ 227 ; DivIDE interface
|
|
border: equ 254 ; Border colour in bits 2-0
|
|
keyboard: equ 254 ; Keyboard matrix in bits 4-0
|
|
|
|
pac_footer: equ &4000 ; credit and fruit display
|
|
pac_chars: equ &4040 ; start of main Pac-Man display (skipping the score rows)
|
|
pac_header: equ &43c0 ; 64 bytes containing the score
|
|
|
|
; address of saved sprite block followed by the data itself
|
|
spr_save_2: equ &5b00
|
|
spr_save_3: equ spr_save_2+2+2+(3*12) ; attr address, data address, 3 bytes * 12 lines
|
|
spr_save_4: equ spr_save_3+2+2+(3*12)
|
|
spr_save_5: equ spr_save_4+2+2+(3*12)
|
|
spr_save_6: equ spr_save_5+2+2+(3*12)
|
|
spr_save_7: equ spr_save_6+2+2+(3*12)
|
|
spr_save_end: equ spr_save_7+2+2+(3*12)
|
|
|
|
; pre-shifted sprite graphics
|
|
spr_data_0: equ spr_save_end
|
|
spr_data_1: equ spr_data_0 + (76*2*12) ; 11111111 11110000
|
|
spr_data_2: equ spr_data_1 + (76*2*12) ; 01111111 11111000
|
|
spr_data_3: equ spr_data_2 + (76*2*12) ; 00111111 11111100
|
|
spr_data_4: equ spr_data_3 + (76*2*12) ; 00011111 11111110
|
|
spr_data_5: equ spr_data_4 + (76*2*12) ; 00001111 11111111
|
|
spr_data_6: equ spr_data_5 + (76*3*12) ; 00000111 11111111 10000000
|
|
spr_data_7: equ spr_data_6 + (76*3*12) ; 00000011 11111111 11000000
|
|
spr_data_end: equ spr_data_7 + (76*3*12) ; 00000001 11111111 11100000
|
|
|
|
; pre-shifted tile graphics
|
|
tile_data_0: equ &8000 + spr_data_0
|
|
tile_data_6: equ tile_data_0 + (192*1*6) ; 11111100
|
|
tile_data_4: equ tile_data_6 + (192*2*6) ; 00000011 11110000
|
|
tile_data_2: equ tile_data_4 + (192*2*6) ; 00001111 11000000
|
|
end_tile_data: equ tile_data_2 + (192*1*6) ; 00111111
|
|
|
|
; sound look-up table
|
|
sound_table: equ &c000
|
|
|
|
|
|
MACRO set_border, bordcol
|
|
IF debug
|
|
ld a,bordcol
|
|
out (border),a
|
|
ENDIF
|
|
ENDM
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
org &a000
|
|
start: jr start2
|
|
|
|
; Dips at fixed address for easy poking
|
|
dip_5000: defb %11111111 ; c21tdrlu c=credit, 2=coin2, 1=coin1, t=rack test on/off ; down, right, left, up
|
|
; default: rack test off, nothing else signalled
|
|
dip_5040: defb %11111111 ; c--sdrlu c=cocktail/upright ; s=service mode on/off ; down, right, left, up (player 2)
|
|
; default: upright mode, service mode off, no controls pressed
|
|
dip_5080: defb %11001001 ; -dbbllcc d=hard/normal bb=bonus life at 10K/15K/20K/none ; ll=1/2/3/5 lives ; cc=freeplay/1coin1credit/1coin2credits/2coins1credit
|
|
; default: normal, bonus life at 10K, 3 lives, 1 coin 1 credit
|
|
|
|
start2: di
|
|
|
|
ld hl,loading_msg
|
|
call print_msg
|
|
|
|
ld a,(&5b5c) ; sysvar holding 128K paging
|
|
ex af,af' ; keep safe
|
|
ld a,%00000001 ; special paging, banks 0/1/2/3
|
|
ld bc,&1ffd ; +3 paging port
|
|
out (c),a ; attempt paging
|
|
ld a,(3) ; peek value in Pac-Man ROM
|
|
cp &ed ; as expected?
|
|
jr z,start3 ; if so, start up
|
|
|
|
ex af,af'
|
|
out (c),a ; restore 128K paging (disturbed due to partial address decoding)
|
|
ei
|
|
|
|
ld hl,plus2a3_msg
|
|
jp print_msg
|
|
|
|
; Next, move any data from load position to final location
|
|
start3: ld sp,new_stack
|
|
|
|
ld hl,&0000
|
|
ld de,&e000
|
|
ld bc,&2000
|
|
ldir ; copy 8K of ROM to &e000 (start of page 3)
|
|
|
|
call patch_rom ; patch the ROM while it's at the correct location
|
|
|
|
ld a,%00000100 ; +3 normal paging
|
|
ld bc,&1ffd
|
|
out (c),a ; restore R3/5/2/0, for ROM access at &c000
|
|
|
|
|
|
call chk_specnet ; check if Spectranet traps are enabled
|
|
jr z,no_specnet ; skip forwards if not
|
|
|
|
ld hl,specnet_msg ; prompt to disable traps
|
|
call print_msg
|
|
|
|
wait_specnet: ld bc,0 ; delay roughly half a second
|
|
delay: djnz $
|
|
djnz $
|
|
dec c
|
|
jr nz,delay
|
|
|
|
call chk_specnet ; check traps again
|
|
jr nz,wait_specnet ; jump back if they're still enabled
|
|
no_specnet:
|
|
|
|
ld a,%10000011 ; DivIDE page 3
|
|
out (divide),a ; page in at &2000, if present
|
|
|
|
ld hl,&c000
|
|
ld de,&2000
|
|
ld bc,&2000
|
|
ldir ; copy first 8K of ROM to DivIDE page 3
|
|
|
|
ld a,%10000000 ; DivIDE page 0
|
|
out (divide),a ; page in at &2000, if present
|
|
|
|
ld de,&2000
|
|
ld bc,&2000
|
|
ldir ; copy last 8K of ROM to DivIDE page 0
|
|
|
|
ld a,%01000000 ; MAPRAM
|
|
out (divide),a ; page 3 at &0000 (read-only), page 0 at &2000
|
|
|
|
ld a,(scr_page) ; normal display, page 7
|
|
ld bc,&7ffd
|
|
out (c),a ; page 7 at &c000
|
|
ld ixh,&80 ; write to alt screen
|
|
|
|
ld hl,load_tiles
|
|
ld de,tile_data_0
|
|
ld bc,&0480
|
|
ldir ; copy unshifted tile data
|
|
|
|
ld hl,load_sprites
|
|
ld de,spr_data_0
|
|
ld bc,&0720
|
|
ldir ; copy unshifted sprite data
|
|
|
|
call make_tables ; create all the look-up tables and pre-shift sprites
|
|
call page_rom ; page in sound table and ROM
|
|
call sound_init ; enable sound chip
|
|
call init_screens ; prepare both screens and sprite save areas
|
|
|
|
ld hl,&5000 ; Pac-Man I/O area
|
|
xor a
|
|
clear_io: ld (hl),a ; zero fill it
|
|
inc l
|
|
jr nz,clear_io
|
|
|
|
ld a,(dip_5000) ; set hardware dips to our defaults
|
|
ld (&5000),a
|
|
ld a,(dip_5040)
|
|
ld (&5040),a
|
|
|
|
ld a,&bf
|
|
in a,(keyboard)
|
|
bit 4,a ; Z if H pressed
|
|
|
|
ld a,(dip_5080)
|
|
jr nz,not_hard
|
|
and %10111111 ; set Hard difficulty
|
|
not_hard: ld (&5080),a
|
|
|
|
ld sp,&4c00 ; stack in spare RAM
|
|
jp 0 ; start the ROM! (and page in DivIDE, if present)
|
|
|
|
page_rom: push af
|
|
push bc
|
|
ld a,%00000001 ; +3 special paging, banks 0/1/2/3
|
|
ld bc,&1ffd
|
|
out (c),a
|
|
pop bc
|
|
pop af
|
|
ret
|
|
|
|
page_screen: push af
|
|
push bc
|
|
ld a,%00000100 ; +3 normal paging, R3/5/2/7
|
|
ld bc,&1ffd
|
|
out (c),a
|
|
pop bc
|
|
pop af
|
|
ret
|
|
|
|
|
|
patch_rom: ld a,&56 ; ED *56*
|
|
ld (&233c),a ; change IM 2 to IM 1
|
|
|
|
ld hl,&47ed
|
|
ld (&233f),hl ; change OUT (&00),A to LD I,A
|
|
ld (&3183),hl
|
|
|
|
ld a,&c3 ; JP nn
|
|
ld (&0038),a
|
|
ld hl,do_int_hook ; interrupt hook
|
|
ld (&0039),hl
|
|
|
|
ld hl,&04d6 ; SUB 4
|
|
ld (&3181),hl ; restore original instruction in patched bootleg ROMs
|
|
|
|
ld a,&01 ; to change &5000 writes to &5001, which is unused
|
|
ld (&0093),a
|
|
ld (&01d7),a
|
|
ld (&2347),a
|
|
ld (&238a),a
|
|
ld (&3194),a
|
|
ld (&3248),a
|
|
|
|
ld a,1 ; start clearing at &5001, to avoid DIP overwrite
|
|
ld (&230c),a
|
|
ld (&2353),a
|
|
ld a,7 ; shorten block clear after start adjustment above
|
|
ld (&230f),a
|
|
ld (&2357),a
|
|
|
|
ld a,&41 ; start clearing at &5041, to avoid DIP overwrite
|
|
ld (&2363),a
|
|
ld a,&3f ; shorten block clear after start adjustment above
|
|
ld (&2366),a
|
|
|
|
ld a,&b0 ; LSB of address in look-up table
|
|
ld (&3ffa),a ; skip memory test (actual code starts at &3000)
|
|
|
|
ld hl,&e0f6 ; change AND &1F to OR &E0 so ROM peeks are from unmodified copy of the ROM
|
|
ld (&2a2d),hl ; (used as random number source for blue ghost movements)
|
|
|
|
ld a,&cd ; CALL nn
|
|
ld (&2c62),a
|
|
ld hl,text_fix ; fix bit 7 being set in horizontal text screen writes
|
|
ld (&2c63),hl
|
|
|
|
ld a,&dc ; CALL C,nn
|
|
ld (&0353),a ; disable 1UP/2UP flashing to save cycles
|
|
ld (&035e),a
|
|
|
|
ret
|
|
|
|
; The ROM uses bit 7 of the display address to indicate text to the top/bottom lines, which
|
|
; is rotated compared to the main screen. It doesn't clear the bit before using the address,
|
|
; but due to RAM mirroring the arcade version still works. Our version requires a fix.
|
|
text_fix: ld e,(hl)
|
|
inc hl
|
|
ld d,(hl)
|
|
res 7,d
|
|
ret
|
|
|
|
|
|
; Do everything we need to update video/sound/input
|
|
;
|
|
do_int_hook: ld (old_stack+1),sp
|
|
ld sp,new_stack
|
|
|
|
push af
|
|
push bc
|
|
push de
|
|
push hl
|
|
ex af,af'
|
|
push af
|
|
exx
|
|
push bc
|
|
push de
|
|
push hl
|
|
push ix
|
|
|
|
call do_flip ; show last frame, page in new one
|
|
|
|
ld hl,&5062 ; sprite 1 x
|
|
inc (hl) ; offset 1 pixel left (mirrored)
|
|
ld hl,&5064 ; sprite 2 x
|
|
inc (hl)
|
|
|
|
set_border 1
|
|
call do_restore ; restore under the old sprites
|
|
set_border 2
|
|
call do_tiles ; update a portion of the background tiles
|
|
set_border 3
|
|
call do_input ; scan the joystick and DIP switches
|
|
call do_sound ; convert the sound to the AY chip
|
|
set_border 4
|
|
call flash_maze ; update maze colour if changed
|
|
call flash_pills ; flash the power pills
|
|
set_border 5
|
|
call do_save ; save under the new sprite positions
|
|
set_border 6
|
|
call do_sprites ; draw the 6 new masked sprites
|
|
set_border 7
|
|
call do_trim ; trim sprites at screen edge
|
|
set_border 0
|
|
|
|
ld hl,&5062 ; sprite 1 x
|
|
dec (hl) ; reverse change from above
|
|
ld hl,&5064 ; sprite 2 x
|
|
dec (hl)
|
|
|
|
call set_int_chain ; prepare interrupt handler chain
|
|
|
|
pop ix
|
|
pop hl
|
|
pop de
|
|
pop bc
|
|
exx
|
|
pop af
|
|
ex af,af'
|
|
pop hl
|
|
pop de
|
|
pop bc
|
|
pop af
|
|
|
|
old_stack: ld sp,0 ; self-modified by code above
|
|
int_chain: jp 0 ; address completed by set_int_chain
|
|
|
|
|
|
; Prepare the Pac-Man interrupt handler address for our return - does an IM2-style
|
|
; lookup to determine the address for normal Pac-Man interrupt processing
|
|
;
|
|
set_int_chain: ld a,i ; bus value originally written to port &00
|
|
ld l,a
|
|
ld h,&3f ; normal I value
|
|
ld a,(hl) ; handler low
|
|
inc hl
|
|
ld h,(hl) ; handler high
|
|
ld l,a
|
|
ld (int_chain+1),hl ; write into JP in interrupt handler
|
|
ret
|
|
|
|
|
|
; Flip to show the screen prepared during the last frame, and prepare to draw the next
|
|
;
|
|
do_flip: push af
|
|
push bc
|
|
|
|
ld a,(scr_page) ; current screen
|
|
xor %00001000 ; toggle active screen bit
|
|
ld (scr_page),a
|
|
ld bc,&7ffd
|
|
out (c),a ; activate
|
|
|
|
sub %00001000 ; set carry if we're viewing the normal screen
|
|
ld a,0
|
|
rra
|
|
ld ixh,a ; b7 holds b15 of drawing screen address
|
|
|
|
pop bc
|
|
pop af
|
|
ret
|
|
|
|
scr_page: defb %00000111 ; normal screen (page 5), page 7 at &c000
|
|
|
|
|
|
; Set the maze palette colour by detecting the attribute used for the maze white
|
|
; We also need to remove the ghost box door, as the real attribute wipe does.
|
|
;
|
|
flash_maze: ld a,(&4440) ; attribute of maze top-right
|
|
cp &1f ; white?
|
|
ld a,(attr_colour) ; current attribute setting
|
|
jr nz,maze_blue ; if not, draw as normal
|
|
|
|
xor %01000000 ; toggle bright
|
|
ld b,a ; save
|
|
|
|
IF colour
|
|
patch_and: and %00000111 ; keep basic colour
|
|
cp 7 ; white?
|
|
jr nz,not_white
|
|
ld b,&41 ; change to bright blue
|
|
not_white: cp 1 ; blue?
|
|
jr nz,not_blue
|
|
ld b,&07 ; change to white
|
|
not_blue:
|
|
ENDIF
|
|
ld a,&40 ; blank tile
|
|
ld (&420d),a ; clear left of ghost box door
|
|
ld (&41ed),a ; clear right of ghost box door
|
|
ld a,b
|
|
maze_blue:
|
|
call page_screen
|
|
|
|
attr_scr_lp: ld hl,&5800+5
|
|
|
|
ex af,af'
|
|
ld a,h
|
|
or ixh
|
|
ld h,a
|
|
ex af,af'
|
|
|
|
cp (hl) ; check current colour
|
|
jr z,attr_same ; skip fill if it's the correct colour
|
|
|
|
ld de,32-22
|
|
ld b,&18 ; 24 lines to fill
|
|
attr_fill_lp: ld c,22
|
|
attr_fill_lp2: ld (hl),a
|
|
inc l
|
|
dec c
|
|
jr nz,attr_fill_lp2
|
|
add hl,de
|
|
djnz attr_fill_lp
|
|
|
|
attr_same: jp page_rom
|
|
|
|
attr_colour: defb default_attr
|
|
|
|
|
|
; Set the power pill palette colour to the correct state by reading the 6 known
|
|
; pill locations, and reacting to changes in the attribute setting.
|
|
;
|
|
flash_pills: ld a,($4278) ; bottom attract screen pill location
|
|
cp &14 ; power pill?
|
|
jr z,attract_mode ; if so, we're in attract mode
|
|
|
|
ld a,(&4384) ; top-left
|
|
cp &14
|
|
ld a,(&4784)
|
|
ld hl,&4066 ; x=2, y=4
|
|
ld bc,&878f ; pre-ORed left byte data
|
|
ld de,&80c0 ; original right byte data
|
|
call z,pill_2
|
|
|
|
ld a,(&4064) ; top right
|
|
cp &14
|
|
ld a,(&4464)
|
|
ld hl,&4079 ; x=27, y=4
|
|
call z,pill_1
|
|
|
|
ld a,(&4398) ; bottom-left
|
|
cp &14
|
|
ld a,(&4798)
|
|
ld hl,&5046 ; x=2, y=24
|
|
ld bc,&878f
|
|
ld de,&80c0
|
|
call z,pill_2
|
|
|
|
ld a,(&4078) ; bottom-right
|
|
cp &14
|
|
ld a,(&4478)
|
|
ld hl,&5059 ; x=27, y=24
|
|
call z,pill_1
|
|
|
|
ret
|
|
|
|
attract_mode: ld a,($4678) ; bottom attract
|
|
ld hl,&504d ; x=11, y=24
|
|
call pill_1
|
|
|
|
ld a,(&4332) ; top attract
|
|
cp &14
|
|
ld a,(&4732)
|
|
ld bc,&0103
|
|
ld de,&e0f0
|
|
ld hl,&4ca8 ; x=5, y=18
|
|
call z,pill_2
|
|
|
|
ret
|
|
|
|
; Pill tile in 1 byte
|
|
pill_1: cp &9f
|
|
jr z,pill_1_on
|
|
cp &10
|
|
jr nz,pill_clear_1
|
|
pill_1_on:
|
|
ld a,%00000100 ; +3 normal paging, R3/5/2/7
|
|
ld bc,&1ffd
|
|
out (c),a
|
|
|
|
ld a,h
|
|
or ixh
|
|
ld h,a
|
|
|
|
ld de,&1e3f ; pill data for ends and middle
|
|
|
|
ld (hl),d
|
|
inc h
|
|
ld (hl),e
|
|
inc h
|
|
ld (hl),e
|
|
inc h
|
|
ld (hl),e
|
|
inc h
|
|
ld (hl),e
|
|
inc h
|
|
ld (hl),d
|
|
|
|
ld a,%00000001 ; +3 special paging, banks 0/1/2/3
|
|
ld bc,&1ffd
|
|
out (c),a ; page ROM
|
|
ret
|
|
; clear pill
|
|
pill_clear_1: ld a,%00000100 ; +3 normal paging, R3/5/2/7
|
|
ld bc,&1ffd
|
|
out (c),a
|
|
|
|
ld a,h
|
|
or ixh
|
|
ld h,a
|
|
|
|
xor a
|
|
ld (hl),a
|
|
inc h
|
|
ld (hl),a
|
|
inc h
|
|
ld (hl),a
|
|
inc h
|
|
ld (hl),a
|
|
inc h
|
|
ld (hl),a
|
|
inc h
|
|
ld (hl),a
|
|
|
|
ld a,%00000001 ; +3 special paging, banks 0/1/2/3
|
|
ld bc,&1ffd
|
|
out (c),a ; page ROM
|
|
ret
|
|
|
|
|
|
; Pill tile spanning 2 bytes
|
|
pill_2: cp &10
|
|
jr nz,pill_clear_2
|
|
|
|
call page_screen
|
|
|
|
ld a,h
|
|
or ixh
|
|
ld h,a
|
|
|
|
ld (hl),b
|
|
inc l
|
|
ld a,(hl)
|
|
or d
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld a,(hl)
|
|
or e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld a,(hl)
|
|
or e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld a,(hl)
|
|
or e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld a,h
|
|
and %00000111
|
|
call z,blockdown_hl
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld a,(hl)
|
|
or e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld (hl),b
|
|
inc l
|
|
ld a,(hl)
|
|
or d
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld a,%00000001 ; +3 special paging, banks 0/1/2/3
|
|
ld bc,&1ffd
|
|
out (c),a ; page ROM
|
|
ret
|
|
|
|
; clear pill
|
|
pill_clear_2: call page_screen
|
|
|
|
ld a,h
|
|
or ixh
|
|
ld h,a
|
|
|
|
ld de,&c00f ; mask
|
|
|
|
ld a,(hl)
|
|
and d
|
|
ld (hl),a
|
|
inc l
|
|
ld a,(hl)
|
|
and e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld a,(hl)
|
|
and d
|
|
ld (hl),a
|
|
inc l
|
|
ld a,(hl)
|
|
and e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld a,(hl)
|
|
and d
|
|
ld (hl),a
|
|
inc l
|
|
ld a,(hl)
|
|
and e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld a,(hl)
|
|
and d
|
|
ld (hl),a
|
|
inc l
|
|
ld a,(hl)
|
|
and e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld a,h
|
|
and %00000111
|
|
call z,blockdown_hl
|
|
|
|
ld a,(hl)
|
|
and d
|
|
ld (hl),a
|
|
inc l
|
|
ld a,(hl)
|
|
and e
|
|
ld (hl),a
|
|
dec l
|
|
inc h
|
|
|
|
ld a,(hl)
|
|
and d
|
|
ld (hl),a
|
|
inc l
|
|
ld a,(hl)
|
|
and e
|
|
ld (hl),a
|
|
|
|
ld a,%00000001 ; +3 special paging, banks 0/1/2/3
|
|
ld bc,&1ffd
|
|
out (c),a ; page ROM
|
|
ret
|
|
|
|
|
|
; Scan the input DIP switches for joystick movement and button presses
|
|
;
|
|
do_input: ld de,&ffff ; nothing pressed
|
|
|
|
ld a,&7f
|
|
in a,(keyboard)
|
|
bit 1,a ; Sym?
|
|
jr nz,not_sym
|
|
|
|
ld bc,&0501 ; 5 bits to check, first colour is blue
|
|
|
|
ld a,&fe
|
|
in a,(keyboard)
|
|
rra ; Shift?
|
|
jr c,not_bright
|
|
set 6,c ; use bright version
|
|
not_bright:
|
|
|
|
ld a,&f7
|
|
in a,(keyboard)
|
|
inp_col_lp: rra
|
|
jr nc,got_colour ; 1-5 = blue/red/magenta/green/cyan
|
|
inc c
|
|
djnz inp_col_lp
|
|
|
|
ld a,&ef
|
|
in a,(keyboard)
|
|
bit 4,a ; 6 = yellow?
|
|
jr z,got_colour
|
|
inc c
|
|
bit 3,a ; 7 = white?
|
|
jr z,got_colour
|
|
rra
|
|
jp c,input_done
|
|
|
|
got_colour: ld a,c
|
|
ld (attr_colour),a ; set to be picked up by flash_maze
|
|
ret
|
|
|
|
not_sym: ld a,&f7
|
|
in a,(keyboard)
|
|
cpl
|
|
and %00000111
|
|
jr z,not_123
|
|
rra
|
|
jr nc,not_1
|
|
res 5,e ; 1 = start 1
|
|
not_1: rra
|
|
jr nc,not_2
|
|
res 6,e ; 2 = start 2
|
|
not_2: rra
|
|
jr nc,not_123
|
|
res 5,d ; 3 = coin 1
|
|
not_123:
|
|
|
|
ld a,&fe
|
|
in a,(keyboard)
|
|
rra
|
|
jr c,no_shift
|
|
|
|
ld a,&fb
|
|
in a,(keyboard)
|
|
bit 4,a
|
|
jr nz,not_shift_t
|
|
res 4,d ; shift-t = rack test
|
|
not_shift_t:
|
|
|
|
; Shifted for Cursor keys
|
|
ld a,&f7
|
|
in a,(keyboard)
|
|
bit 4,a
|
|
jr nz,not_shift_5
|
|
res 1,d ; Shift-5 = left
|
|
not_shift_5:
|
|
ld a,&ef
|
|
in a,(keyboard)
|
|
cpl
|
|
and %00011100
|
|
jr z,read_joy
|
|
rra
|
|
rra
|
|
rra
|
|
jr nc,not_shift_8
|
|
res 2,d ; Shift-8 = right
|
|
not_shift_8: rra
|
|
jr nc,not_shift_7
|
|
res 0,d ; Shift-7 = up
|
|
not_shift_7: rra
|
|
jr nc,read_qaop
|
|
res 3,d ; Shift-6 = down
|
|
jr read_qaop
|
|
|
|
; Unshifted for Sinclair joystick
|
|
no_shift: ld a,&ef
|
|
in a,(keyboard)
|
|
cpl
|
|
and %00011111
|
|
jr z,not_67890
|
|
rra
|
|
jr nc,not_0
|
|
res 5,d ; 0 = coin 1
|
|
res 5,e ; 0 = start 1
|
|
not_0: rra
|
|
jr nc,not_9
|
|
res 0,d ; 9 = up
|
|
not_9: rra
|
|
jr nc,not_8
|
|
res 3,d ; 8 = down
|
|
not_8: rra
|
|
jr nc,not_7
|
|
res 2,d ; 7 = right
|
|
not_7: rra
|
|
jr nc,not_67890
|
|
res 1,d ; 6 = left
|
|
not_67890:
|
|
|
|
read_qaop: ld a,&fb
|
|
in a,(keyboard)
|
|
rra
|
|
jr c,not_q
|
|
res 0,d ; Q = up
|
|
not_q:
|
|
ld a,&fd
|
|
in a,(keyboard)
|
|
rra
|
|
jr c,not_a
|
|
res 3,d ; A = down
|
|
not_a:
|
|
ld a,&df
|
|
in a,(keyboard)
|
|
rra
|
|
jr c,not_p
|
|
res 2,d ; P = right
|
|
not_p: rra
|
|
jr c,not_o
|
|
res 1,d ; O = left
|
|
not_o:
|
|
ld a,&7f
|
|
in a,(keyboard)
|
|
rra
|
|
jr c,not_space
|
|
res 5,d ; space = coin 1
|
|
res 5,e ; space = start 1
|
|
not_space:
|
|
IF colour
|
|
ld a,&fe
|
|
in a,(keyboard)
|
|
bit 3,a ; C = colour
|
|
jp z,set_colour
|
|
|
|
ld a,&7f
|
|
in a,(keyboard)
|
|
bit 2,a ; M = mono
|
|
jp z,set_mono
|
|
ENDIF
|
|
|
|
; Kempston joystick
|
|
read_joy: in a,(kempston) ; read Kempston joystick
|
|
inc a
|
|
cp 2
|
|
jr c,not_fire ; ignore blank or invalid inputs
|
|
dec a
|
|
rra
|
|
jr nc,not_right
|
|
res 2,d ; right
|
|
not_right: rra
|
|
jr nc,not_left
|
|
res 1,d ; left
|
|
not_left: rra
|
|
jr nc,not_down
|
|
res 3,d ; down
|
|
not_down: rra
|
|
jr nc,not_up
|
|
res 0,d ; up
|
|
not_up: rra
|
|
jr nc,not_fire
|
|
res 5,d ; Fire = coin 1
|
|
res 5,e ; Fire = start 1
|
|
not_fire:
|
|
|
|
ld a,d ; dip including controls
|
|
cpl ; invert so set=pressed
|
|
and %00001111 ; keep only direction bits
|
|
jr z,joy_done ; skip if nothing pressed
|
|
ld c,a
|
|
neg
|
|
and c ; keep least significant set bit
|
|
cp c ; was it the only bit?
|
|
jr z,joy_done ; skip if so
|
|
|
|
ld a,(last_controls); last valid (single) controls
|
|
xor c ; check for differences
|
|
or %11110000 ; convert to mask
|
|
ld c,a
|
|
ld a,d ; current controls
|
|
or %00001111 ; release all directions
|
|
and c ; press the changed key
|
|
jr joy_multi ; apply change but don't save
|
|
|
|
joy_done: ld a,d
|
|
ld (last_controls),a; update last valid controls
|
|
input_done: ld a,d ; use original value
|
|
joy_multi: ld (&5000),a
|
|
ld a,e
|
|
ld (&5040),a
|
|
ret
|
|
|
|
last_controls: defb 0
|
|
|
|
|
|
; Check sprite visibility, returns carry if any visible, no-carry if all hidden
|
|
is_visible: ld a,&10 ; minimum x/y position to be visible
|
|
ld b,7 ; 7 sprites to check
|
|
ld hl,&5062
|
|
vis_lp: cp (hl)
|
|
ret c
|
|
inc l
|
|
cp (hl)
|
|
ret c
|
|
inc l
|
|
inc l
|
|
inc l
|
|
djnz vis_lp
|
|
ret
|
|
|
|
|
|
; Draw the background tile changes, in 1-5 steps over the 2 double-buffered screens
|
|
do_tiles: call is_visible ; set carry state for below
|
|
|
|
tile_state: ld a,ixh
|
|
bit 7,a ; alt screen? (don't disturb carry!)
|
|
|
|
jr c,tile_strips ; if any sprites are visible we'll draw in strips
|
|
jr nz,fulldraw_alt ; full screen draw (alt)
|
|
|
|
fulldraw_norm: ld b,28
|
|
ld de,pac_chars
|
|
ld hl,bak_chars1-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,bak_chars1
|
|
call do_fruit
|
|
ld hl,bak_chars1
|
|
call do_lives
|
|
ld hl,bak_chars1
|
|
call do_score1
|
|
ld hl,bak_chars1
|
|
jp do_score2
|
|
|
|
fulldraw_alt: ld b,28
|
|
ld de,pac_chars
|
|
ld hl,bak_chars2-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,bak_chars2
|
|
call do_fruit
|
|
ld hl,bak_chars2
|
|
call do_lives
|
|
ld hl,bak_chars2
|
|
call do_score1
|
|
ld hl,bak_chars2
|
|
jp do_score2
|
|
|
|
|
|
tile_strips: jp nz,strip_odd
|
|
strip_even: jp strip_0
|
|
|
|
strip_0: ld b,7
|
|
ld de,pac_chars+(32*7*0)
|
|
ld hl,bak_chars1-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_1
|
|
ld (strip_even+1),hl
|
|
ret
|
|
|
|
strip_1: ld b,7
|
|
ld de,pac_chars+(32*7*1)
|
|
ld hl,bak_chars1-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_2
|
|
ld (strip_even+1),hl
|
|
ret
|
|
|
|
strip_2: ld b,7
|
|
ld de,pac_chars+(32*7*2)
|
|
ld hl,bak_chars1-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_3
|
|
ld (strip_even+1),hl
|
|
ret
|
|
|
|
strip_3: ld b,7
|
|
ld de,pac_chars+(32*7*3)
|
|
ld hl,bak_chars1-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_4
|
|
ld (strip_even+1),hl
|
|
ret
|
|
|
|
strip_4: ld hl,bak_chars1
|
|
call do_score1
|
|
ld hl,bak_chars1
|
|
call do_score2
|
|
ld hl,bak_chars1
|
|
call do_fruit
|
|
ld hl,bak_chars1
|
|
call do_lives
|
|
ld hl,strip_0
|
|
ld (strip_even+1),hl
|
|
ret
|
|
|
|
strip_odd: jp strip_0_alt
|
|
|
|
strip_0_alt: ld b,7
|
|
ld de,pac_chars+(32*7*0)
|
|
ld hl,bak_chars2-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_1_alt
|
|
ld (strip_odd+1),hl
|
|
ret
|
|
|
|
strip_1_alt: ld b,7
|
|
ld de,pac_chars+(32*7*1)
|
|
ld hl,bak_chars2-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_2_alt
|
|
ld (strip_odd+1),hl
|
|
ret
|
|
|
|
strip_2_alt: ld b,7
|
|
ld de,pac_chars+(32*7*2)
|
|
ld hl,bak_chars2-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_3_alt
|
|
ld (strip_odd+1),hl
|
|
ret
|
|
|
|
strip_3_alt: ld b,7
|
|
ld de,pac_chars+(32*7*3)
|
|
ld hl,bak_chars2-pac_footer
|
|
add hl,de
|
|
call tile_comp
|
|
ld hl,strip_4_alt
|
|
ld (strip_odd+1),hl
|
|
ret
|
|
|
|
strip_4_alt: ld hl,bak_chars2
|
|
call do_score1
|
|
ld hl,bak_chars2
|
|
call do_score2
|
|
ld hl,bak_chars2
|
|
call do_fruit
|
|
ld hl,bak_chars2
|
|
call do_lives
|
|
ld hl,strip_0_alt
|
|
ld (strip_odd+1),hl
|
|
ret
|
|
|
|
|
|
tile_comp: call find_change ; scan block for display changes
|
|
dec sp ; restore the same return address to here
|
|
dec sp
|
|
|
|
ld (hl),a ; update with new tile value
|
|
|
|
cp 144 ; before fruit tiles?
|
|
jr c,tile_mapped
|
|
sub 63 ; relocate to account for removed fruits
|
|
cp 176-63 ; first ghost tile
|
|
jr c,tile_mapped
|
|
cp 176-63+6 ; after last ghost tile?
|
|
jr nc,tile_mapped
|
|
IF colour
|
|
patch_jr: jr tile_mapped
|
|
ENDIF
|
|
ex af,af' ; save tile
|
|
set 2,d ; switch to attributes
|
|
ld a,(de) ; fetch tile attribute
|
|
res 2,d ; switch back to data
|
|
ld c,a
|
|
ex af,af' ; restore tile
|
|
dec c ; red ghost?
|
|
jr nz,tile_mapped
|
|
add a,6 ; offset to Blinky tiles
|
|
tile_mapped:
|
|
ex af,af' ; save tile for later
|
|
push de
|
|
exx ; save to resume find
|
|
pop hl ; Pac-Man screen address of changed tile
|
|
|
|
ld a,l
|
|
and %00011111 ; column is in bits 0-4
|
|
ld b,a ; tile y
|
|
|
|
add a,a ; *2
|
|
add a,a ; *4
|
|
add a,b ; *5 (code size to check each byte)
|
|
add a,3 ; skip ld+cp+ret, so we advance pointers
|
|
ld e,a
|
|
ld d,find_change/256
|
|
push de ; return address to resume find
|
|
|
|
add hl,hl ; *2
|
|
add hl,hl ; *4
|
|
add hl,hl ; *8 (ignore overflow), H is now mirrored column number
|
|
ld a,28+2 ; 28 columns wide, offset 2 by additional rows
|
|
sub h ; unmirror the column
|
|
ld c,a ; tile x
|
|
|
|
draw_tile: ld ixl,5 ; offset to centre maze on Speccy display
|
|
draw_tile_x: ld a,b
|
|
add a,a ; *2
|
|
ld b,a
|
|
add a,a ; *4
|
|
add a,b ; *6
|
|
ld l,a
|
|
ld h,scradtab/256
|
|
ld e,(hl)
|
|
inc h
|
|
ld a,(hl)
|
|
or ixh
|
|
ld d,a ; DE holds base addr for screen line
|
|
|
|
ld b,conv_8_6/256
|
|
ld a,(bc) ; 4 tiles to 3 byte conversion for tile x
|
|
add a,e ; add screen LSB
|
|
add a,ixl ; centre maze on Speccy display
|
|
ld e,a ; DE holds addr for tile
|
|
|
|
ld a,c
|
|
ex af,af' ; save tile x, restore tile number
|
|
|
|
ld l,a
|
|
ld h,0
|
|
add hl,hl ; *2
|
|
ld b,h
|
|
ld c,l
|
|
add hl,hl ; *4
|
|
add hl,bc ; *6
|
|
|
|
ld a,%00000100 ; +3 normal paging, R3/5/2/7
|
|
ld bc,&1ffd
|
|
out (c),a
|
|
|
|
ex af,af'
|
|
rra
|
|
jr c,tile_62
|
|
rra
|
|
jr c,tile_4
|
|
|
|
; 11111100
|
|
tile_0: ld bc,tile_data_0
|
|
add hl,bc
|
|
ld bc,&0503 ; 5 lines, mask of 00000011
|
|
tile_0_lp: ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
inc hl
|
|
inc d
|
|
ld a,d
|
|
and %00000111
|
|
call z,blockdown_de
|
|
djnz tile_0_lp
|
|
ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
jr tile_exit
|
|
|
|
; 00001111 11000000
|
|
tile_4: ld bc,tile_data_4
|
|
add hl,hl
|
|
add hl,bc
|
|
ld bc,&05f0 ; 5 lines, mask of 11110000
|
|
tile_4_lp: ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
inc e
|
|
inc hl
|
|
ld a,(de)
|
|
and %00111111
|
|
or (hl)
|
|
ld (de),a
|
|
dec e
|
|
inc hl
|
|
inc d
|
|
ld a,d
|
|
and %00000111
|
|
call z,blockdown_de
|
|
djnz tile_4_lp
|
|
ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
inc e
|
|
inc hl
|
|
ld a,(de)
|
|
and %00111111
|
|
or (hl)
|
|
ld (de),a
|
|
jr tile_exit
|
|
|
|
tile_62: rra
|
|
jr c,tile_2
|
|
|
|
; 00000011 11110000
|
|
tile_6: ld bc,tile_data_6
|
|
add hl,hl
|
|
add hl,bc
|
|
ld bc,&05fc ; 5 lines, mask of 11111100
|
|
tile_6_lp: ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
inc e
|
|
inc hl
|
|
ld a,(de)
|
|
and %00001111
|
|
or (hl)
|
|
ld (de),a
|
|
dec e
|
|
inc hl
|
|
inc d
|
|
ld a,d
|
|
and %00000111
|
|
call z,blockdown_de
|
|
djnz tile_6_lp
|
|
ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
inc e
|
|
inc hl
|
|
ld a,(de)
|
|
and %00001111
|
|
or (hl)
|
|
ld (de),a
|
|
jr tile_exit
|
|
|
|
; 00111111
|
|
tile_2: ld bc,tile_data_2
|
|
add hl,bc
|
|
ld bc,&05c0 ; 5 lines, mask of 11000000
|
|
tile_2_lp: ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
inc hl
|
|
inc d
|
|
ld a,d
|
|
and %00000111
|
|
call z,blockdown_de
|
|
djnz tile_2_lp
|
|
ld a,(de)
|
|
and c
|
|
or (hl)
|
|
ld (de),a
|
|
jr tile_exit
|
|
|
|
tile_exit:
|
|
ld a,%00000001 ; +3 special paging, banks 0/1/2/3
|
|
ld bc,&1ffd
|
|
out (c),a ; page ROM
|
|
|
|
exx
|
|
ret
|
|
|
|
|
|
; Draw a 12x12 sprite (H=x, L=y, D=attr)
|
|
;
|
|
draw_spr: ld a,h
|
|
cp &10
|
|
ret c ; off bottom of screen
|
|
ld a,l
|
|
inc a ; catch 255 as invalid
|
|
cp &11
|
|
ret c ; off right of screen
|
|
|
|
ld a,d
|
|
and a ; sprite palette all black?
|
|
ret z
|
|
|
|
call page_screen
|
|
call xy_to_addr
|
|
ld a,c
|
|
and %00000111 ; shift position
|
|
|
|
ex af,af'
|
|
call map_sprite ; map sprites to the correct orientation/colour
|
|
draw_spr2:
|
|
ex de,hl
|
|
add a,a ; *2
|
|
ld l,a
|
|
ld h,0
|
|
ld a,c ; save sprite attribute
|
|
add hl,hl ; *4
|
|
ld b,h
|
|
ld c,l
|
|
add hl,hl ; *8
|
|
add hl,bc ; *12
|
|
|
|
ex af,af'
|
|
rra
|
|
jr c,rot_odd
|
|
rot_even: rra
|
|
jr c,rot_2_6
|
|
rot_0_4: rra
|
|
ld bc,spr_data_4 ; rot_4
|
|
jr c,spr_2
|
|
ld bc,spr_data_0 ; rot_0
|
|
jp spr_2
|
|
|
|
rot_2_6: rra
|
|
ld bc,spr_data_6 ; rot_6
|
|
jr c,spr_3
|
|
ld bc,spr_data_2 ; rot_2
|
|
jp spr_2
|
|
|
|
rot_odd: rra
|
|
jr c,rot_3_7
|
|
rot_1_5: rra
|
|
ld bc,spr_data_5 ; rot_5
|
|
jr c,spr_3
|
|
ld bc,spr_data_1 ; rot_1
|
|
jp spr_2
|
|
|
|
rot_3_7: rra
|
|
ld bc,spr_data_7 ; rot_7
|
|
jr c,spr_3
|
|
ld bc,spr_data_3 ; rot_3
|
|
jp spr_2
|
|
|
|
; draw a sprite using 2-byte source data (shifts 0-4)
|
|
spr_2: add hl,hl
|
|
add hl,bc
|
|
ld b,12
|
|
push de
|
|
jr spr_2_start
|
|
spr_2_lp: inc d
|
|
ld a,d
|
|
and %00000111
|
|
call z,blockdown_de
|
|
spr_2_start: ld a,(de)
|
|
or (hl)
|
|
ld (de),a
|
|
inc e
|
|
inc hl
|
|
ld a,(de)
|
|
or (hl)
|
|
ld (de),a
|
|
dec e
|
|
inc hl
|
|
djnz spr_2_lp
|
|
pop hl
|
|
IF colour
|
|
patch_jp1: ld bc,page_rom ; LD=mono, JP=colour
|
|
|
|
ex af,af'
|
|
ld c,a
|
|
ld b,h
|
|
|
|
ld a,h
|
|
rrca
|
|
rrca
|
|
rrca
|
|
and %00000011
|
|
or &58
|
|
or ixh
|
|
ld h,a
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld (hl),c
|
|
ld a,l
|
|
add a,32
|
|
ld l,a
|
|
adc a,h
|
|
sub l
|
|
ld h,a
|
|
ld (hl),c
|
|
dec l
|
|
ld (hl),c
|
|
|
|
ld a,b
|
|
and %00000111
|
|
cp 5
|
|
jp c,page_rom
|
|
|
|
ld a,l
|
|
add a,32
|
|
ld l,a
|
|
adc a,h
|
|
sub l
|
|
ld h,a
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld (hl),c
|
|
ENDIF
|
|
jp page_rom
|
|
|
|
; draw a sprite using 3-byte source data (shifts 5-7)
|
|
spr_3: push bc
|
|
ld b,h
|
|
ld c,l
|
|
add hl,hl ; *24
|
|
add hl,bc ; *36
|
|
pop bc
|
|
add hl,bc
|
|
ld b,12
|
|
push de
|
|
jr spr_3_start
|
|
spr_3_lp: inc d
|
|
ld a,d
|
|
and %00000111
|
|
call z,blockdown_de
|
|
spr_3_start: ld a,(de)
|
|
or (hl)
|
|
ld (de),a
|
|
inc e
|
|
inc hl
|
|
ld a,(de)
|
|
or (hl)
|
|
ld (de),a
|
|
inc e
|
|
inc hl
|
|
ld a,(de)
|
|
or (hl)
|
|
ld (de),a
|
|
dec e
|
|
dec e
|
|
inc hl
|
|
djnz spr_3_lp
|
|
pop hl
|
|
IF colour
|
|
patch_jp2: ld bc,page_rom ; LD=mono, JP=colour
|
|
|
|
ex af,af'
|
|
ld c,a
|
|
ld b,h
|
|
|
|
ld a,h
|
|
rrca
|
|
rrca
|
|
rrca
|
|
and %00000011
|
|
or &58
|
|
or ixh
|
|
ld h,a
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld (hl),c
|
|
inc l
|
|
ld (hl),c
|
|
ld a,l
|
|
add a,32
|
|
ld l,a
|
|
adc a,h
|
|
sub l
|
|
ld h,a
|
|
ld (hl),c
|
|
dec l
|
|
ld (hl),c
|
|
dec l
|
|
ld (hl),c
|
|
|
|
ld a,b
|
|
and %00000111
|
|
cp 5
|
|
jp c,page_rom
|
|
|
|
ld a,l
|
|
add a,32
|
|
ld l,a
|
|
adc a,h
|
|
sub l
|
|
ld h,a
|
|
|
|
ld (hl),c
|
|
inc l
|
|
ld (hl),c
|
|
inc l
|
|
ld (hl),c
|
|
ENDIF
|
|
jp page_rom
|
|
|
|
|
|
; Map an arcade tile number to our tile number, allowing for attribute differences
|
|
; and any tiles we've mapped to different locations (we have no fruit tiles)
|
|
map_sprite: ld b,0
|
|
ld a,e
|
|
srl a
|
|
rl b ; b0=flip-y
|
|
rra
|
|
rl b ; b1=flip-y, b0=flip-x
|
|
IF colour
|
|
ld e,a ; juggle the few spare registers
|
|
ld c,d ; it's still faster than stack use!
|
|
ld d,attr_map/256
|
|
ld a,(de) ; look up default sprite attribute
|
|
ld d,c
|
|
ld c,a
|
|
ld a,e
|
|
ENDIF
|
|
cp 16 ; big pac-man
|
|
ret c ; anything before is unchanged
|
|
cp 28 ; scared ghost
|
|
jr c,map_big
|
|
cp 32 ; first ghost
|
|
jr c,map_scared
|
|
cp 40 ; last ghost + 1
|
|
jr c,map_ghost
|
|
cp 44 ; first Pac-Man
|
|
ret c
|
|
cp 48 ; last Pac-Man + 1
|
|
ret nc
|
|
inc b
|
|
dec b ; mirrored?
|
|
ret z ; no, so right/down
|
|
add a,28 ; offset to left/up
|
|
ret
|
|
|
|
map_big: cp 24 ; closed mouth
|
|
ret nc
|
|
add a,48
|
|
bit 0,a ; mouth segment?
|
|
ret nz
|
|
and %00000010 ; up/down back
|
|
or %00011000 ; re-use back segments
|
|
ret
|
|
|
|
map_ghost: ; D = 01=red 03=pink 05=cyan 07=orange
|
|
IF colour
|
|
patch_sub: sub 0 ; offset to ghost with mouth (0=colour, 16=mono)
|
|
ELSE
|
|
sub 16
|
|
ENDIF
|
|
dec d ; red?
|
|
ret z
|
|
IF colour
|
|
patch_add: add a,0 ; restore original ghost sprite (0=colour, 16=mono)
|
|
ELSE
|
|
add a,16
|
|
ENDIF
|
|
bit 3,d ; transparent colour?
|
|
jr nz,map_eyes
|
|
srl d
|
|
dec d ; pink?
|
|
ld c,&43 ; red
|
|
ret z
|
|
dec d ; cyan?
|
|
ld c,&45
|
|
ret z
|
|
ld c,&44 ; green (should be orange)
|
|
ret
|
|
|
|
map_eyes: ld c,&47 ; bright white
|
|
add a,32 ; eyes offset
|
|
and %11111110 ; use only even positions
|
|
ret
|
|
|
|
map_scared:
|
|
IF colour
|
|
patch_add2: add a,2 ; use solid scared ghost, coloured blue (2=colour, 0=mono)
|
|
ENDIF
|
|
bit 1,d ; check colour
|
|
ret z ; return if normal colour (transparent)
|
|
IF colour
|
|
patch_add3: add a,0 ; use solid scared ghost for white (0=colour, 2=mono)
|
|
ELSE
|
|
add a,2
|
|
ENDIF
|
|
ld c,&47 ; white
|
|
ret
|
|
|
|
|
|
; Trim sprites that overlap the maze edges, as the real hardware does automatically
|
|
; Here we trim partial bytes, as full blocks are hidden behind black on black attrs
|
|
;
|
|
do_trim: ld hl,&5062 ; sprite x for first sprite
|
|
ld de,&ff00 ; no trim yet
|
|
ld b,7 ; 7 sprites to check
|
|
|
|
trim_lp: ld a,(hl) ; sprite x
|
|
inc l
|
|
cp &10 ; hidden?
|
|
jr c,trim_next
|
|
cp &20 ; clipped at right edge?
|
|
jr c,may_trim
|
|
cp &f0 ; clipped at left edge?
|
|
jr c,trim_next
|
|
|
|
may_trim: ld a,(hl) ; sprite y
|
|
cp &10 ; hidden?
|
|
jr c,trim_next
|
|
|
|
cp d ; compare min
|
|
jr nc,no_trim_min
|
|
ld d,a ; update min
|
|
no_trim_min: cp e ; compare max
|
|
jr c,trim_next
|
|
ld e,a ; update max
|
|
jr trim_next
|
|
|
|
trim_next: inc l ; skip sprite y
|
|
djnz trim_lp
|
|
|
|
ld a,e ; max
|
|
and a
|
|
jp z,page_rom ; zero means no trim
|
|
sub d ; subtract min to give block
|
|
add a,12 ; add sprite height to give span
|
|
ld b,a ; line count for trim
|
|
|
|
ld l,e ; top line, due to mirrored y!
|
|
ld h,conv_y/256
|
|
ld l,(hl) ; convert y to Speccy line
|
|
ld h,scradtab/256
|
|
ld c,(hl) ; LSB of line start
|
|
inc h
|
|
ld a,(hl) ; MSB of line start
|
|
add a,ixh ; current screen offset
|
|
ld h,a
|
|
ld l,c
|
|
|
|
call page_screen
|
|
|
|
ld de,&0300 ; D=left mask, E=clear byte
|
|
trim_r_lp: ld c,l ; save line start LSB
|
|
|
|
ld a,c
|
|
add a,4
|
|
ld l,a
|
|
|
|
ld (hl),e ; full byte to trim 6 pixels
|
|
inc l
|
|
ld a,(hl)
|
|
and d ; trim 6 pixels at maze left
|
|
ld (hl),a
|
|
|
|
ld a,c
|
|
add a,26 ; right tunnel offset
|
|
ld l,a
|
|
|
|
ld a,(hl)
|
|
and &fc ; trim 2 pixels at maze right
|
|
ld (hl),a
|
|
inc l
|
|
ld (hl),e ; full byte
|
|
inc l
|
|
ld (hl),e ; full byte for final 2 pixels
|
|
|
|
ld l,c ; restore original LSB
|
|
inc h ; next line
|
|
ld a,h
|
|
and %00000111
|
|
call z,blockdown_hl
|
|
|
|
djnz trim_r_lp
|
|
jp page_rom
|
|
|
|
|
|
; Clear a sprite-sized hole, used for blank tiles in our fruit and lives display
|
|
;
|
|
blank_sprite: call page_screen
|
|
|
|
ld bc,&0c00 ; 12 lines, zero fill
|
|
blank_lp: ld (hl),c
|
|
inc l
|
|
ld (hl),c
|
|
dec l
|
|
inc h
|
|
ld a,h
|
|
and %00000111
|
|
call z,blockdown_hl
|
|
djnz blank_lp
|
|
|
|
jp page_rom
|
|
|
|
|
|
; Save the background screen behind locations we're about to draw active sprites
|
|
;
|
|
do_save: ld hl,(&5062) ; pre-fetch position data as we page it out
|
|
push hl
|
|
ld hl,(&5064)
|
|
push hl
|
|
ld hl,(&5066)
|
|
push hl
|
|
ld hl,(&5068)
|
|
push hl
|
|
ld hl,(&506a)
|
|
push hl
|
|
ld hl,(&506c)
|
|
|
|
call page_screen
|
|
|
|
ld de,spr_save_7
|
|
call spr_save
|
|
|
|
pop hl
|
|
ld de,spr_save_6
|
|
call spr_save
|
|
|
|
pop hl
|
|
ld de,spr_save_5
|
|
call spr_save
|
|
|
|
pop hl
|
|
ld de,spr_save_4
|
|
call spr_save
|
|
|
|
pop hl
|
|
ld de,spr_save_3
|
|
call spr_save
|
|
|
|
pop hl
|
|
ld de,spr_save_2
|
|
call spr_save
|
|
|
|
call page_rom
|
|
ret
|
|
|
|
; Save a single sprite-sized block, if visible
|
|
spr_save: ld a,h
|
|
cp &10
|
|
ret c ; off bottom of screen
|
|
ld a,l
|
|
inc a ; catch 255 as invalid
|
|
cp &11
|
|
ret c ; off right of screen
|
|
|
|
ld a,d
|
|
or ixh
|
|
ld d,a
|
|
|
|
call xy_to_addr ; convert to Speccy display address
|
|
IF colour
|
|
push hl
|
|
|
|
ld a,h
|
|
rra
|
|
rra
|
|
rra
|
|
and %00000011
|
|
or &58
|
|
or ixh
|
|
ld h,a
|
|
|
|
ex de,hl
|
|
ld (hl),e ; attr low
|
|
inc l
|
|
ld (hl),d ; attr high
|
|
inc l
|
|
ex de,hl
|
|
|
|
pop hl
|
|
ENDIF
|
|
ex de,hl
|
|
ld (hl),e ; data low
|
|
inc l
|
|
ld (hl),d ; data high
|
|
inc l
|
|
ex de,hl ; HL=screen, DE=save
|
|
|
|
ld bc,3*12 ; 3 bytes and 12 lines
|
|
|
|
save_lp: ld a,l
|
|
ldi
|
|
ldi
|
|
ldi
|
|
ret po ; return if done
|
|
ld l,a
|
|
inc h
|
|
ld a,h
|
|
and %00000111
|
|
jp nz,save_lp
|
|
call blockdown_hl
|
|
jp save_lp
|
|
|
|
;
|
|
; Remove the previous sprites by restoring the image that was underneath them
|
|
;
|
|
do_restore: call page_screen
|
|
|
|
ld hl,spr_save_2
|
|
call spr_restore
|
|
ld hl,spr_save_3
|
|
call spr_restore
|
|
ld hl,spr_save_4
|
|
call spr_restore
|
|
ld hl,spr_save_5
|
|
call spr_restore
|
|
ld hl,spr_save_6
|
|
call spr_restore
|
|
ld hl,spr_save_7
|
|
call spr_restore
|
|
|
|
jp page_rom
|
|
|
|
|
|
; Restore a single sprite-sized block, if data was saved
|
|
spr_restore: ld a,h
|
|
or ixh
|
|
ld h,a
|
|
ld a,(hl)
|
|
and a
|
|
ret z ; no data saved
|
|
|
|
ld (hl),0 ; flag 'no restore data'
|
|
IF colour
|
|
ld e,a ; attr low
|
|
inc l
|
|
ld d,(hl) ; attr high
|
|
inc l
|
|
|
|
ex de,hl
|
|
ld a,(attr_colour) ; current maze colour
|
|
ld bc,32-2
|
|
|
|
ld (hl),a ; 1st line
|
|
inc l
|
|
ld (hl),a
|
|
inc l
|
|
ld (hl),a
|
|
add hl,bc
|
|
ld (hl),a ; 2nd line
|
|
inc l
|
|
ld (hl),a
|
|
inc l
|
|
ld (hl),a
|
|
add hl,bc
|
|
|
|
ex af,af'
|
|
ld a,h
|
|
and %01111111
|
|
cp &5b ; beyond attributes?
|
|
jr nc,restore_2 ; if so, stop painting
|
|
ex af,af'
|
|
|
|
ld (hl),a ; 3rd line
|
|
inc l
|
|
ld (hl),a
|
|
inc l
|
|
ld (hl),a
|
|
|
|
restore_2: ex de,hl
|
|
ld a,(hl)
|
|
ENDIF
|
|
ld e,a ; data low
|
|
inc l
|
|
ld d,(hl) ; data high
|
|
inc l
|
|
|
|
ld bc,3*12 ; 3 bytes of 12 lines
|
|
|
|
restore_lp: ld a,e
|
|
ldi
|
|
ldi
|
|
ldi
|
|
ret po
|
|
ld e,a
|
|
inc d
|
|
ld a,d
|
|
and %00000111
|
|
jp nz,restore_lp
|
|
call blockdown_de
|
|
jp restore_lp
|
|
|
|
|
|
; Draw the currently visible sprites, in the correct order for overlaps
|
|
; Note: sprite order changes depending on mode, so not always as listed!
|
|
;
|
|
do_sprites:
|
|
ld hl,(&506c)
|
|
ld de,(&4ffc)
|
|
call draw_spr ; fruit
|
|
|
|
ld hl,(&506a)
|
|
ld de,(&4ffa)
|
|
call draw_spr ; pacman
|
|
|
|
ld hl,(&5068)
|
|
ld de,(&4ff8)
|
|
call draw_spr ; orange ghost
|
|
|
|
ld hl,(&5066)
|
|
ld de,(&4ff6)
|
|
call draw_spr ; cyan ghost
|
|
|
|
ld hl,(&5064)
|
|
ld de,(&4ff4)
|
|
call draw_spr ; pink ghost
|
|
|
|
ld hl,(&5062)
|
|
ld de,(&4ff2)
|
|
call draw_spr ; red ghost
|
|
|
|
ret
|
|
|
|
|
|
do_score1: inc h
|
|
inc h
|
|
inc h ; advance to header area containing score
|
|
|
|
ld ixl,0 ; screen offset for left edge
|
|
|
|
ld l,&da
|
|
ld bc,&0003
|
|
call chk_digit ; 1
|
|
|
|
ld l,&d9
|
|
ld bc,&0004
|
|
call chk_digit ; U
|
|
|
|
ld l,&d8
|
|
ld bc,&0005
|
|
call chk_digit ; P
|
|
|
|
|
|
ld l,&fc
|
|
ld bc,&0100
|
|
call chk_digit ; 100,000s
|
|
|
|
ld l,&fb
|
|
ld bc,&0101
|
|
call chk_digit ; 10,000s
|
|
|
|
ld l,&fa
|
|
ld bc,&0102
|
|
call chk_digit ; 1,000s
|
|
|
|
ld l,&f9
|
|
ld bc,&0103
|
|
call chk_digit ; 100s
|
|
|
|
ld l,&f8
|
|
ld bc,&0104
|
|
call chk_digit ; 10s
|
|
|
|
ld l,&f7
|
|
ld bc,&0105
|
|
call chk_digit ; 1s
|
|
|
|
ret
|
|
|
|
;
|
|
do_score2: inc h
|
|
inc h
|
|
inc h ; advance to header area containing score
|
|
|
|
ld ixl,26 ; screen offset for left edge
|
|
|
|
ld l,&c7
|
|
ld bc,&0003
|
|
call chk_digit ; 2
|
|
|
|
ld l,&c6
|
|
ld bc,&0004
|
|
call chk_digit ; U
|
|
|
|
ld l,&c5
|
|
ld bc,&0005
|
|
call chk_digit ; P
|
|
|
|
|
|
ld l,&e9
|
|
ld bc,&0100
|
|
call chk_digit ; 100,000s
|
|
|
|
ld l,&e8
|
|
ld bc,&0101
|
|
call chk_digit ; 10,000s
|
|
|
|
ld l,&e7
|
|
ld bc,&0102
|
|
call chk_digit ; 1,000s
|
|
|
|
ld l,&e6
|
|
ld bc,&0103
|
|
call chk_digit ; 100s
|
|
|
|
ld l,&e5
|
|
ld bc,&0104
|
|
call chk_digit ; 10s
|
|
|
|
ld l,&e4
|
|
ld bc,&0105
|
|
call chk_digit ; 1s
|
|
|
|
ret
|
|
|
|
chk_digit: ld d,&43
|
|
ld e,l
|
|
ld a,(de)
|
|
cp (hl)
|
|
ret z
|
|
ld (hl),a
|
|
|
|
ex af,af'
|
|
push hl
|
|
call draw_tile_x
|
|
pop hl
|
|
ret
|
|
|
|
|
|
; Draw changes to the fruit display, which is remapped to a vertical layout
|
|
; We use the sprite versions of the tiles, for easier drawing
|
|
;
|
|
do_fruit: ld l,5 ; offset to first fruit in display and comparison buffer
|
|
ld c,&b4
|
|
push hl
|
|
call chk_fruit
|
|
pop hl
|
|
|
|
ld l,7
|
|
ld c,&a8
|
|
push hl
|
|
call chk_fruit
|
|
pop hl
|
|
|
|
ld l,9
|
|
ld c,&9c
|
|
push hl
|
|
call chk_fruit
|
|
pop hl
|
|
|
|
ld l,11
|
|
ld c,&90
|
|
push hl
|
|
call chk_fruit
|
|
pop hl
|
|
|
|
ld l,13
|
|
ld c,&84
|
|
push hl
|
|
call chk_fruit
|
|
pop hl
|
|
|
|
ld l,15
|
|
ld c,&78
|
|
push hl
|
|
call chk_fruit
|
|
pop hl
|
|
|
|
ld l,17
|
|
ld c,&6c
|
|
push hl
|
|
call chk_fruit
|
|
pop hl
|
|
|
|
ret
|
|
|
|
chk_fruit: ld b,scradtab/256
|
|
ld a,(bc)
|
|
add a,&1d ; Speccy line offset for fruit column
|
|
ld e,a
|
|
inc b
|
|
ld a,(bc)
|
|
or ixh
|
|
ld d,a
|
|
|
|
ld b,pac_footer/256 ; tile MSB for fruit
|
|
ld c,l
|
|
ld a,(bc) ; live fruit
|
|
cp (hl) ; has it changed?
|
|
ret z ; return if not
|
|
|
|
ld (hl),a ; update buffered copy
|
|
ex de,hl
|
|
ex af,af'
|
|
|
|
push hl
|
|
call blank_sprite
|
|
pop hl
|
|
|
|
xor a ; no rotation, for sprite drawing later
|
|
ex af,af'
|
|
cp &40 ; blank?
|
|
ret z
|
|
|
|
sub &91 ; subtract cherry tile number
|
|
srl a ; /2
|
|
srl a ; /4 tiles per fruit, to give fruit sprite number (cherry=0)
|
|
IF colour
|
|
push hl
|
|
ld l,a
|
|
ld h,attr_map/256
|
|
ld c,(hl)
|
|
pop hl
|
|
ENDIF
|
|
call page_screen
|
|
jp draw_spr2
|
|
|
|
;
|
|
; Draw changes to the number of remaining lives
|
|
;
|
|
do_lives: ld l,&1b
|
|
ld c,&b4
|
|
push hl
|
|
call chk_life
|
|
pop hl
|
|
|
|
ld l,&19
|
|
ld c,&a8
|
|
push hl
|
|
call chk_life
|
|
pop hl
|
|
|
|
ld l,&17
|
|
ld c,&9c
|
|
push hl
|
|
call chk_life
|
|
pop hl
|
|
|
|
ld l,&15
|
|
ld c,&90
|
|
push hl
|
|
call chk_life
|
|
pop hl
|
|
|
|
ld l,&13
|
|
ld c,&84
|
|
push hl
|
|
call chk_life
|
|
pop hl
|
|
|
|
ret
|
|
|
|
; Draw either a blank or a left-facing Pac-Man sprite
|
|
chk_life: ld b,scradtab/256
|
|
ld a,(bc)
|
|
add a,&02 ; Speccy line offset for lives column
|
|
ld e,a
|
|
inc b
|
|
ld a,(bc)
|
|
or ixh
|
|
ld d,a
|
|
|
|
ld b,&40
|
|
ld c,l
|
|
ld a,(bc)
|
|
cp (hl)
|
|
ret z
|
|
|
|
ld (hl),a
|
|
ex de,hl
|
|
ex af,af'
|
|
|
|
push hl
|
|
call blank_sprite
|
|
pop hl
|
|
|
|
xor a ; no rotation, for sprite drawing later
|
|
ex af,af'
|
|
cp &40 ; blank?
|
|
ret z
|
|
|
|
call page_screen
|
|
ld a,72 ; sprite for left-facing Pac-Man
|
|
ld c,&46 ; yellow attribute
|
|
jp draw_spr2
|
|
|
|
|
|
; Build the sound table and initialise the AY-3-8912 chip
|
|
;
|
|
sound_init: ld bc,sound_table
|
|
|
|
sound_lp: ld a,b ; map entry address to freq
|
|
and &3f
|
|
rra
|
|
ld d,a
|
|
ld a,c
|
|
rra
|
|
ld e,a ; freq (divisor) now in DE
|
|
|
|
ld hl,0
|
|
exx
|
|
ld de,&da7a ; dividend in DEHL
|
|
ld hl,&8000 ; 111861 << 15 = 0xda7a8000
|
|
ld b,16
|
|
and a
|
|
div_lp: adc hl,hl ; shift up for next division
|
|
rl e
|
|
rl d
|
|
exx
|
|
adc hl,hl ; include new bit
|
|
sbc hl,de ; does it divide?
|
|
jr nc,div_ok
|
|
add hl,de ; add back if not, setting carry
|
|
div_ok: exx
|
|
ccf ; set carry if it divided
|
|
djnz div_lp
|
|
adc hl,hl ; include final bit
|
|
|
|
ld a,h
|
|
ex af,af'
|
|
ld a,l
|
|
exx
|
|
ld (bc),a ; note LSB
|
|
inc c
|
|
ex af,af'
|
|
ld (bc),a ; note MSB
|
|
inc bc ; freq++
|
|
ex af,af'
|
|
|
|
xor c
|
|
and %00000111
|
|
out (border),a ; flash the border to show we're busy
|
|
|
|
bit 5,b
|
|
jr z,sound_lp
|
|
|
|
xor a
|
|
out (border),a
|
|
|
|
ld hl,sinit_data
|
|
ld de,&ffbf
|
|
ld c,&fd
|
|
sinit_lp: ld a,(hl)
|
|
and a
|
|
ret m
|
|
ld b,d
|
|
out (c),a
|
|
inc hl
|
|
ld a,(hl)
|
|
inc hl
|
|
ld b,e
|
|
out (c),a
|
|
jr sinit_lp
|
|
|
|
; Sound init: set volumes to zero, enable tones A+B+C, end
|
|
sinit_data: defb &08,0, &09,0, &0a,0, &07,%00111000, &ff
|
|
|
|
|
|
; Map the current sound chip frequencies to the AY
|
|
;
|
|
do_sound: ld hl,&5051 ; voice 0 freq and volume
|
|
ld a,(&5045) ; voice 0 waveform
|
|
call map_sound
|
|
xor a
|
|
call play_sound
|
|
|
|
ld hl,&5051+5 ; voice 1 freq and volume
|
|
ld a,(&504a) ; voice 1 waveform
|
|
call map_sound
|
|
ld a,1
|
|
call play_sound
|
|
|
|
ld hl,&5051+5+5 ; voice 2 freq and volume
|
|
ld a,(&504f) ; voice 2 waveform
|
|
call map_sound
|
|
ld a,2
|
|
call play_sound
|
|
|
|
ret
|
|
|
|
map_sound: ld b,a ; save waveform
|
|
|
|
ld a,(hl)
|
|
and %00001111
|
|
add a,a
|
|
add a,a
|
|
add a,a
|
|
add a,a
|
|
ld e,a
|
|
inc hl
|
|
ld a,(hl)
|
|
and %00001111
|
|
ld d,a
|
|
inc hl
|
|
ld a,(hl)
|
|
add a,a
|
|
add a,a
|
|
add a,a
|
|
add a,a
|
|
or d
|
|
ld d,a
|
|
or e ; check for zero frequency
|
|
inc hl
|
|
inc hl
|
|
ld a,(hl) ; volume
|
|
ex de,hl
|
|
|
|
jr nz,not_silent
|
|
xor a ; zero frequency gives silence
|
|
not_silent: ex af,af' ; save volume for caller
|
|
|
|
ld a,b
|
|
cp 5 ; waveform used when eating ghost?
|
|
jr z,eat_sound ; if so, don't divide freq by 8
|
|
srl h
|
|
rr l
|
|
srl h
|
|
rr l
|
|
srl h
|
|
rr l
|
|
eat_sound:
|
|
ld a,h
|
|
or &c0 ; MSB of sound table
|
|
ld h,a
|
|
res 0,l
|
|
|
|
ld a,(hl) ; pick up LSB
|
|
inc hl
|
|
ld h,(hl) ; pick up MSB
|
|
ld l,a
|
|
|
|
ret
|
|
|
|
; Update a single voice, setting the note number and volume
|
|
play_sound: ld de,&ffbf ; sound register port MSB
|
|
ld c,&fd ; LSB
|
|
|
|
add a,a ; 2 registers per tone
|
|
ld b,d
|
|
out (c),a ; tone low
|
|
ld b,e
|
|
out (c),l
|
|
|
|
inc a
|
|
ld b,d
|
|
out (c),a ; tone high
|
|
ld b,e
|
|
out (c),h
|
|
|
|
rra
|
|
or %00001000
|
|
ld b,d
|
|
out (c),a ; volume
|
|
ex af,af'
|
|
ld b,e
|
|
out (c),a ; volume data
|
|
|
|
ret
|
|
|
|
|
|
; Create the look-up tables used to speed up various calculations
|
|
;
|
|
make_tables: ld hl,conv_8_6
|
|
xor a
|
|
conv_86_lp: ld (hl),a ; 0
|
|
inc l
|
|
ld (hl),a ; 0
|
|
inc a
|
|
inc l
|
|
ld (hl),a ; 1
|
|
inc a
|
|
inc l
|
|
ld (hl),a ; 2, etc. (repeating pattern)
|
|
inc a
|
|
inc l
|
|
jr nz,conv_86_lp
|
|
|
|
; note: HL re-used from above
|
|
ld de,conv_y
|
|
ld bc,conv_x
|
|
mirror_lp: xor a
|
|
sub c ; mirror y-axis
|
|
ld l,a
|
|
ld a,(hl) ; map to Speccy coords
|
|
ld (de),a
|
|
xor a
|
|
sub e ; mirror x-axis
|
|
ld l,a
|
|
ld a,(hl) ; map to Speccy coords
|
|
add a,34 ; centre on display
|
|
ld (bc),a
|
|
inc e
|
|
inc c
|
|
jr nz,mirror_lp
|
|
|
|
|
|
ld hl,tile_data_4
|
|
ld de,tile_data_6
|
|
exx
|
|
ld hl,tile_data_0
|
|
ld de,tile_data_2
|
|
ld c,192 ; 192 tiles
|
|
tilerot_lp: ld b,6 ; 6 lines per tile
|
|
tilerot_lp2: ld a,(hl)
|
|
inc hl
|
|
srl a
|
|
rra
|
|
ld (de),a ; >> 6
|
|
inc de
|
|
exx
|
|
ld c,0
|
|
rra
|
|
rr c
|
|
rra
|
|
rr c
|
|
ld (hl),a ; >> 4
|
|
inc l
|
|
ld (hl),c
|
|
inc hl
|
|
ex de,hl
|
|
rra
|
|
rr c
|
|
rra
|
|
rr c
|
|
ld (hl),a ; >> 2
|
|
inc l
|
|
ld (hl),c
|
|
inc hl
|
|
ex de,hl
|
|
exx
|
|
djnz tilerot_lp2
|
|
dec c
|
|
jr nz,tilerot_lp
|
|
|
|
|
|
ld hl,spr_data_5
|
|
ld de,76*3*12
|
|
exx
|
|
ld hl,spr_data_0
|
|
ld de,76*2*12
|
|
|
|
ld c,76 ; 76 sprites
|
|
spr_rot_lp: push bc
|
|
ld b,12 ; 12 lines per sprite
|
|
spr_rot_lp2: push bc
|
|
|
|
ld c,(hl) ; take a line from spr_data_0
|
|
inc hl
|
|
ld a,(hl)
|
|
dec hl
|
|
|
|
push hl ; save
|
|
ld b,4 ; four more 2-byte shifted versions
|
|
spr_rot_lp3: add hl,de ; next shifted copy
|
|
srl c ; >> 1
|
|
rra
|
|
ld (hl),c ; spr_data_1 to spr_data_4
|
|
inc hl
|
|
ld (hl),a
|
|
dec hl
|
|
djnz spr_rot_lp3
|
|
|
|
pop hl ; restore spr_data_0 position
|
|
inc hl ; advance to next line
|
|
inc hl
|
|
|
|
ex af,af' ; preserve A and carry from final rra above
|
|
ld a,c ; copy for exx
|
|
exx
|
|
ld c,a ; restore C
|
|
ex af,af' ; restore A and carry
|
|
ld b,0 ; extra shift register
|
|
rr b ; recover carry
|
|
ex af,af'
|
|
|
|
ld a,3 ; three 3-byte shifted versions
|
|
push hl ; save
|
|
spr_rot_lp4: ex af,af'
|
|
|
|
srl c ; >> 5 to 7
|
|
rra
|
|
rr b
|
|
|
|
ld (hl),c ; spr_data_5 to spr_data_7
|
|
inc hl
|
|
ld (hl),a
|
|
inc hl
|
|
ld (hl),b
|
|
dec hl
|
|
dec hl
|
|
add hl,de ; next shifted copy
|
|
|
|
ex af,af'
|
|
dec a
|
|
jr nz,spr_rot_lp4
|
|
|
|
pop hl ; restore spr_data_5 position
|
|
inc hl ; advance to next line
|
|
inc hl
|
|
inc hl
|
|
exx ; back to spr_data_0
|
|
|
|
pop bc
|
|
djnz spr_rot_lp2 ; complete lines
|
|
|
|
pop bc
|
|
dec c
|
|
jr nz,spr_rot_lp ; complete sprites
|
|
|
|
|
|
ld hl,scradtab
|
|
ld de,&4000 ; Speccy screen base
|
|
ld b,&c0 ; 192 lines
|
|
scrtab_lp: ld (hl),e
|
|
inc h
|
|
ld (hl),d
|
|
dec h
|
|
inc l
|
|
inc d
|
|
ld a,d
|
|
and %00000111
|
|
call z,blockdown_de
|
|
djnz scrtab_lp
|
|
|
|
ret
|
|
|
|
; Map a Pac-Man screen coordinate to a Speccy display address, scaling down from 8x8 to 6x6 as we go
|
|
;
|
|
xy_to_addr: ld b,conv_y/256
|
|
ld c,h
|
|
ld a,(bc) ; look up y coord
|
|
ld h,conv_x/256
|
|
ld c,(hl) ; look up x coord
|
|
|
|
ld h,scradtab/256
|
|
ld l,a
|
|
ld b,(hl)
|
|
inc h
|
|
ld a,(hl)
|
|
or ixh
|
|
ld h,a
|
|
ld l,b
|
|
ld a,c
|
|
and %11111000
|
|
rra
|
|
rra
|
|
rra
|
|
add a,l
|
|
ld l,a
|
|
ret
|
|
|
|
blockdown_hl: ld a,l
|
|
add a,32
|
|
ld l,a
|
|
ret c
|
|
|
|
ld a,h
|
|
sub 8
|
|
ld h,a
|
|
ret
|
|
|
|
blockdown_de: ld a,e
|
|
add a,32
|
|
ld e,a
|
|
ret c
|
|
|
|
ld a,d
|
|
sub 8
|
|
ld d,a
|
|
ret
|
|
|
|
|
|
; Check that Spectranet traps are disabled, if one is connected
|
|
chk_specnet: ld hl,&3ff9 ; PAGEIN
|
|
ld a,(hl) ; save currently paged value at trap
|
|
push af
|
|
push hl
|
|
|
|
ld hl,&4000 ; first byte in (display) RAM after trap
|
|
ld c,(hl) ; save current value
|
|
ld (hl),&c9 ; RET
|
|
call &3ff9 ; attempt page-in
|
|
ld (hl),c ; restore display byte
|
|
|
|
pop hl
|
|
pop af
|
|
cp (hl) ; did the paging change?
|
|
ret z ; return Z if not (no Spectranet or traps disabled)
|
|
jp &007c ; exit via RET in Speccy ROM to page out
|
|
|
|
|
|
; Clear both screens and sprite save areas, and set default attrs
|
|
init_screens: call page_screen
|
|
|
|
ld b,2 ; 2 screens to prepare
|
|
scrinit_lp: push bc
|
|
|
|
ld hl,&4000 ; Speccy display
|
|
ld de,&4001
|
|
ld bc,&1800 ; display length
|
|
|
|
ld a,h
|
|
or ixh ; adjust for current screen
|
|
ld h,a
|
|
ld d,a
|
|
|
|
ld (hl),l ; clear display data
|
|
ldir
|
|
|
|
ld bc,&0300 ; &300 bytes to fill
|
|
ld a,(attr_colour)
|
|
ld (hl),a ; fill display attrs
|
|
ldir
|
|
|
|
ld bc,spr_save_end-spr_save_2
|
|
ld (hl),l ; clear sprite restore data
|
|
ldir
|
|
|
|
call do_flip ; switch to other display
|
|
pop bc
|
|
djnz scrinit_lp ; finish both screens
|
|
|
|
jp page_rom
|
|
|
|
|
|
; Display a message using the ROM routines
|
|
; String in HL (null-terminated), paging expected to be correct
|
|
;
|
|
print_msg: push hl
|
|
call &0d6b ; CLS
|
|
ld a,2 ; main screen
|
|
call &1601 ; CHAN-OPEN
|
|
pop hl
|
|
|
|
msg_lp: ld a,(hl)
|
|
and a
|
|
ret z
|
|
rst 16 ; PRINT-A
|
|
inc l
|
|
jr msg_lp
|
|
|
|
|
|
loading_msg: defm "pacemuzx v1.3"
|
|
defb 0
|
|
|
|
specnet_msg: defm "Disable Spectranet traps now..."
|
|
defb 0
|
|
|
|
plus2a3_msg: defm "This program requires a +2A/+3"
|
|
defb 0
|
|
|
|
|
|
defs (-$)%256 ; align to next 256-byte boundary
|
|
|
|
; Scan a 32-byte block for changes, used for fast scanning of the Pac-Man display
|
|
; Aligned on a 256-byte boundary for easy resuming of the scanning
|
|
;
|
|
find_change: ld a,(de) ; 0
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 1
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 2
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 3
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 4
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 5
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 6
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 7
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 8
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 9
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 10
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 11
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 12
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 13
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 14
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 15
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 16
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 17
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 18
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 19
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 20
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 21
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 22
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 23
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 24
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 25
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 26
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 27
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 28
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 29
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 30
|
|
cp (hl)
|
|
ret nz
|
|
inc e
|
|
inc l
|
|
|
|
ld a,(de) ; 31
|
|
cp (hl)
|
|
ret nz
|
|
inc de ; 16-bit increment as we may be at 256-byte boundary
|
|
inc hl
|
|
|
|
dec b
|
|
jp nz,find_change ; jump too big for DJNZ
|
|
|
|
pop hl ; junk return to update
|
|
ret
|
|
|
|
IF colour
|
|
set_mono: ld a,&c3 ; JP
|
|
ld (patch_jp1),a
|
|
ld (patch_jp2),a
|
|
|
|
ld a,16 ; offset to ghost with mouth
|
|
ld (patch_sub+1),a
|
|
ld (patch_add+1),a
|
|
xor a
|
|
ld (patch_add2+1),a
|
|
ld a,2 ; offset between blue ghosts
|
|
ld (patch_add3+1),a
|
|
|
|
ld a,&0e ; LD C,n
|
|
ld (patch_jr),a
|
|
|
|
xor a ; dummy colour mask
|
|
ld (patch_and+1),a
|
|
|
|
call page_screen
|
|
ld hl,&5afd ; current attr from first (bottom) fruit
|
|
ld a,(attr_colour) ; maze colour
|
|
cp (hl)
|
|
jp z,page_rom ; nothing to do if already mono
|
|
|
|
ld hl,&59a2 ; attr column above lives
|
|
call mono_strip
|
|
ld hl,&59bd ; attr column above fruits
|
|
call mono_strip
|
|
jp page_rom
|
|
|
|
set_colour: ld a,&01 ; LD BC,nn
|
|
ld (patch_jp1),a
|
|
ld (patch_jp2),a
|
|
|
|
xor a ; no offset for Blinky
|
|
ld (patch_sub+1),a
|
|
ld (patch_add+1),a
|
|
ld (patch_add3+1),a
|
|
ld a,2 ; offset between blue ghosts
|
|
ld (patch_add2+1),a
|
|
|
|
ld a,&18 ; JR
|
|
ld (patch_jr),a
|
|
|
|
ld a,7 ; colour mask
|
|
ld (patch_and+1),a
|
|
|
|
call page_screen
|
|
ld hl,&5afd ; first fruit attr
|
|
ld a,(attr_colour) ; maze colour
|
|
cp (hl)
|
|
jp nz,page_rom ; nothing to do if already colour
|
|
|
|
ld hl,bak_chars1
|
|
ld de,bak_chars2
|
|
ld b,&40 ; 2 lines of 32 background tiles
|
|
ld a,b ; &40 = space
|
|
redraw_lp: ld (hl),a ; write spaces over our cached values
|
|
ld (de),a ; to force a redraw of any fruits, in colour
|
|
inc l
|
|
inc e
|
|
djnz redraw_lp
|
|
jp page_rom
|
|
|
|
|
|
mono_strip: ld a,(attr_colour) ; maze colour
|
|
strip_alt: ld de,32-1 ; offset to next attr line, adjusted for width=2
|
|
ld b,11
|
|
strip_lp: ld (hl),a ; paint back to mono
|
|
inc l
|
|
ld (hl),a
|
|
add hl,de
|
|
djnz strip_lp
|
|
bit 7,h ; already painting the alt screen?
|
|
ret nz ; return if so
|
|
ld de,&8000-(11*32) ; offset between normal and alt screens
|
|
add hl,de
|
|
jr strip_alt
|
|
ENDIF
|
|
|
|
end_a000: equ $
|
|
|
|
new_stack: equ &b000 ; hangs back into &Axxx
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
org &b000
|
|
; Tables are generated here at run time
|
|
conv_8_6: defs &100
|
|
conv_x: defs &100
|
|
conv_y: defs &100
|
|
scradtab: defs &200
|
|
bak_chars1: defs &400 ; copy of Pac-Man display for normal screen
|
|
bak_chars2: defs &400 ; copy of Pac-Man display for alt screen
|
|
|
|
IF colour
|
|
; Map sprite number to suitable Spectrum attribute colour
|
|
attr_map: defb &42,&42,&06,&46,&42,&44,&05,&45, &42,&42,&07,&07,&42,&42,&00,&00
|
|
defb &46,&46,&46,&46,&46,&46,&46,&46, &46,&46,&46,&46,&41,&41,&00,&00
|
|
defb &42,&42,&42,&42,&42,&42,&42,&42, &45,&45,&45,&45,&46,&46,&46,&46
|
|
defb &46,&07,&42,&42,&46,&46,&46,&46, &46,&46,&46,&46,&46,&46,&46,&46
|
|
ENDIF
|
|
|
|
end_b000: equ $
|
|
|
|
org &b000
|
|
; Graphics are here at load time
|
|
load_tiles: incbin "tiles.bin" ; 192 tiles * 6 lines * 1 byte per line = 1152 bytes
|
|
load_sprites: incbin "sprites.bin" ; 76 sprites * 12 lines * 2 byte per line = 1824 bytes
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
org &c000
|
|
; 16K of Pac-Man ROMs
|
|
incbin "pacman.6e"
|
|
incbin "pacman.6f"
|
|
incbin "pacman.6h"
|
|
incbin "pacman.6j"
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
end start ; auto-run address
|