I don't know how to call this, but heretic might be close. Let me explain what happens:
I'm making my crap game and I'm following strictly the programmers manual of good practics, you know, first I do the loading screen, then I do some graphics, sprite first and then the backgrounds and at last I try to do some code to move the whole thing with QAOPM (it could be I'm reading the manual upside down, yes).
Now I'm at the stage where I have the sprites done and I'm making the rooms' background. At this stage I just draw it all on the screen and when I'm happy with an item I add a ' before the line to make it a coment and move on to the next item. When I have everything, I will sort it all out.
Well, I had the first room layout. I did a carpet to decorate it. It was ok so I greyed it out (with the ' ) and started to desing a "David Star". When I print it, the first two graphics of the sprite data (which happen to be the mask for the sprite looking down) are corrupted. Somehow it seems that the background data (made with plots and draws) is overwritting the beginning of the sprite data. The strange thing is that if I print the carpet first and over it I print the star, nothing happens. And if I print the star making it double lined, everything is OK too.
So I'll send you a package with the code and attachements so you can check it if you want. No need to hurry up, I can workaround it and go on with the game.
It would be nice to be able to map the program produced in memory, work out where it is - and optionally, where gaps are (very useful for IM2 stuff) so that the gaps could be used. Even if only as buffer memory for other functions.
A clever compiler could follow this as a code entry point separate from the main thread and assume any variables or functions called would actually be used.
A really clever compiler would explain that PAUSE was a bad idea. As is HALT in any ASM chained - because it would have disabled interrupts and re-enabled them at the end.
I've been working very hard in fixing almost 10 obscure bugs in the -O2 and -O3 options. And they finally seems to be fixed. Apenao's game compiles with -O3 flawlessly and plays well (obviously, -O3 includes -O2 optimizations); but I need more testing. So anyone who has programs to test both -O2 and -O3, and check if it compiles and behaves as it does with no optimization at all.
If everything works ok, I expect the compiler will behave more intelligently than ever before. It's been ultra-hard hock: :roll: but I hope it's worth the hassle.
You can download .zip or .tag.gz 1.2.5 files, or the MSI windows install: <!-- m --><a class="postlink" href="http://www.boriel.com/files/zxb/zxbasic-1.2.5r1513.msi">http://www.boriel.com/files/zxb/zxbasic-1.2.5r1513.msi</a><!-- m -->
This is the final version, unless I /really/ have to mess with it more.
MK III might be better for people that want more time between sprite updates to do things - but they might get flickery sprites. This version is quite a bit slower, because after a halt to sync to the screen it waits for the screen to be drawn, and then plays with the sprites. This is a hold of about 1.5 frames per update doing nothing at all.
A clever programmer could probably steal some of the "wait for the screen to draw" time for their own purposes, if they had code that ran in fixed time.
On the plus side, this version is rock solid flicker free. Which was the point of the exercise, after all.
NOTE: There's an addition to the original useage. It has testing code built into the functions so you shouldn't be able to break them. When you are happy that the program runs fine, put #DEFINE FINAL_CODE into the program early on, and it will remove the boundary checking code. (and thus be a little bit smaller).
Code:
REM Most of this code is (c) Paul Fisher, 2010.
REM Thankyou to Apenao and the Mojon twins for producing what I here altered beyond recognition!
REM Free use is hereby given to anyone to do what they please with it, on the proviso they say thankyou to me on anything produced using it.
#DEFINE BLACK 0
#DEFINE BLUE 1
#DEFINE RED 2
#DEFINE MAGENTA 3
#DEFINE GREEN 4
#DEFINE CYAN 5
#DEFINE YELLOW 6
#DEFINE WHITE 7
#DEFINE TRANSPARENT 8
#DEFINE CONTRAST 9
#DEFINE TRUE 1
#DEFINE FALSE 0
SUB fspInitialize (n as uByte, udgA as uByte, udgB as uByte, udgC as uByte, udgD as uByte,x as uByte,y as uByte) 'Initialize Sprite: n (sprite number 0-3);a,b,c,d (UDG number 0-20,99 means the sprite won't be printed)
#ifndef FINAL_CODE
IF n>3 OR (udgA>20 AND udgA<>99) or udgB>20 or udgC>20 or udgD>20 then Print "Out Of Bounds Call in ";"fspInitialize" : stop : END IF : REM Bounds checking - so a bad function call doesn't poke all over memory.
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+(48*n)
fspErase()
POKE targetAddr,udgA
targetAddr=targetAddr+1
POKE targetAddr,udgB
targetAddr=targetAddr+1
POKE targetAddr,udgC
targetAddr=targetAddr+1
POKE targetAddr,udgD
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
fspBufferAndDraw()
RETURN
fspDataStart:
REM This Section contains Fourspriter data and code that is called by other routines.
ASM
;; 16x16 Sprites moving a character each frame
;; Copyleft 2009 The Mojon Twins.
;; Updated in 2010 by Paul Fisher
;; Uses UDGs (it reads the address from the system variables).
;; It moves up to 4 sprites, preserving the backgrounds.
;; Sprite control is done writing a certain number
;; in the Sprite's first UDG
;; If the first UDG is 99, it won't be printed
; Note that the sprites are initialized to disabled.
fspDataStartAsm: ; each block is 48 bytes long. [This comment originally said 40 bytes, and it's clearly 48]
;; Sprite 1
udgs1: defb 99,0,0,0 ; Four UDGs of the first sprite.
x_pos1: defb 0 ; X position in chars.
y_pos1: defb 0 ; Y position in chars.
cx_pos1: defb 0 ; Previous X position in chars.
cy_pos1: defb 0 ; Previous Y position in chars.
buffer1: defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; Background Gfx buffer.
attrs1: defb 6,7,7,7 ; Sprite ATTRs
buffatrs1: defb 0,0,0,0 ; Background Attr's buffer
fspCollisionDetect: defb 0,0,0 ; Sprite 0 with 1,2,3
defb 0,0 ; Sprite 1 with 2,3
defb 0 ; sprite 2 with 3
fspCopyToScreen:
call get_scr_address ; Returns (xpos, ypos) in HL
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ret
fspCopyFromScreen:
call get_scr_address
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ret
; This function returns the address into HL of the screen address
; b,c in character grid notation.
; Original code was extracted by BloodBaz
get_scr_address:
push de
ld a,b ; b=x
and 31
ld l,a
ld a,c ; c=y
ld d,a
and 24
add a,64
ld h,a
ld a,d
and 7
rrca
rrca
rrca
or l
ld l,a
pop de
ret
;; This function returns the memory address of the Character Position
;; b,c in the attribute screen memory.
;; Adapted from code by Jonathan Cauldwell.
get_attr_address:
ld a,c ;ypos
rrca
rrca
rrca ; Multiply by 32
ld l,a ; Pass to L
and 3 ; Mask with 00000011
add a,88 ; 88 * 256 = 22528 - start of attributes.
ld h,a ; Put it in the High Byte
ld a,l ; We get y value *32
and 224 ; Mask with 11100000
ld l,a ; Put it in L
ld a,b ; xpos
add a,l ; Add it to the Low byte
ld l,a ; Put it back in L, and we're done. HL=Address.
ret
;; Routine to save the background to the buffer
fspEraseAsm:
ld hl, fspDataStartAsm
EXX
ld b, 4
i4chars2:
EXX
;;
ld a, (hl)
cp 99
jr z, nxt2
;; A
LD BC,6
;EX DE,HL ; HL now points at the start sprite data instead
ADD HL,BC ; HL moved on to point to old x-y positions.
LD b,(HL) ; b=x
INC HL
LD c,(HL) ;c=y
INC HL
EX DE,HL ; Now it's DE that points into the data buffer.
; Draw first background character
call fspCopyToScreen
;; B
; xpos++
inc b
; Draw second background Character
call fspCopyToScreen
;; C
; xpos --
dec b
; ypos ++
inc c
; draw Third background Character
call fspCopyToScreen
;; D
; xpos++
inc b
; Draw Fourth background Character
call fspCopyToScreen
;; move to attr
inc de
inc de
inc de
inc de
;Set our position to top left corner again
; xpos --
dec b
; ypos --
dec c
call get_attr_address
EX DE,HL
;copyattrs
ld bc, 32
;First
LDI
;Second
LDI
; DE=DE+BC
EX DE,HL
add hl,bc
EX DE,HL
;Third
LDI
;Fourth
LDI
nxt2: EXX
djnz i4chars2
ret
fspBufferAndDrawAsm:
ld hl, fspDataStartAsm
EXX
ld b, 4
i4chars:
;push bc
EXX
;Active Sprite?
ld a, (hl)
cp 99
jr z, nxt1
;; A
LD BC,4
ADD HL,BC ; move past the udg part
;xpos
ld b,(hl)
inc hl
;ypos
ld c,(hl)
inc hl
inc hl ; move past the old co-ordinates
inc hl
EX de,hl
call fspCopyFromScreen
;; B
; xpos++
;ld hl, xpos
;inc (hl)
inc b
call fspCopyFromScreen
;; C
; xpos --
;ld hl, xpos
;dec (hl)
dec b
; ypos ++
;ld hl, ypos
;inc (hl)
inc c
call fspCopyFromScreen
;; D
; xpos++
;ld hl, xpos
;inc (hl)
inc b
call fspCopyFromScreen
;; Now we point to the ATTR buffer,adding 4:
inc de
inc de
inc de
inc de
;Put ourselves back at the top left corner.
; xpos --
dec b
; ypos --
dec c
call get_attr_address
ld bc, 32
;First Character
LDI ; bc was loaded with 32 instead of 31 to account for the BC-- this instruction has.
;Second Character
LDI
add hl, bc ; we can get away with this because BC++ as well as hl--
;Third Character
LDI
;Fourth Character
LDI
EX DE,HL ; back to an HL pointer.
nxt1: EXX
djnz i4chars
; Go straight into Painting new sprites for speed.
;; Print sprites routine
;; UDGs are labeled from 0 to 21. The addres of the UDG is:
;; *(23675) + 256 * *(23676) + 8 * N
fspDrawAsm:
ld hl, fspDataStartAsm ;
EXX
ld b, 4 ;
i4chars3:
;push bc
EXX
ld a, (hl)
cp 99
jr z, nxt3
;; Copy pointer "HL" to alt reg
push HL
exx
pop HL
exx
ld bc,4
add hl,bc
; Get xpos
LD b,(hl) ;x
inc hl
; Get ypos
ld c,(hl) ;y
inc hl
push HL ; de Save pointer.
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
;Draw the first character
call fspCopyToScreen
; xpos++
inc b
; Address of the graphics
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
; Address of next Graphic
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
; Draw the fourth Character
call fspCopyToScreen
pop hl ; Recover hl to point at the start of the pixel buffer
; hl = hl + 32
ld a,l
add a,34
ld l,a
jp nc, fspNoIncH
inc h ; hl now points to the attr data.
fspNoIncH:
EX DE,HL
;Reset position to top left.
; xpos --
dec b
; ypos --
dec c
;; attr
call get_attr_address
EX DE,HL
;copyattrs
ld bc, 32
;First
LDI
;Second
LDI
; DE=DE+BC
EX DE,HL
add hl,bc
EX DE,HL
;Third
LDI
;Fourth
LDI
LD BC,4
ADD HL,BC ; Move pointer to start of next block
nxt3: EXX
djnz i4chars3
ret
fspGetUdgAddr:
; finds the address of the UDG # in A
ld HL,(23675) ;HL points to the UDG space
rlca
rlca
rlca ; a = N * 8
ld d, 0
ld e, a
add hl, de
EX DE,HL
ret
fspUpdateAsm: ; This name will be used from ASM
; For each Sprite:
;; *(data + 6) = *(data + 4)
;; *(data + 7) = *(data + 5)
ld hl, fspDataStartAsm+4 ; Points to sprite 1
ld de, fspDataStartAsm+6
ldi
ldi
ld hl, fspDataStartAsm+4+48 ; Points to sprite 2
ld de, fspDataStartAsm+6+48
ldi
ldi
ld hl, fspDataStartAsm+4+48+48 ; Points to sprite 3
ld de, fspDataStartAsm+6+48+48
ldi
ldi
ld hl, fspDataStartAsm+4+48+48+48 ; Points to sprite 4
ld de, fspDataStartAsm+6+48+48+48
ldi
ldi
ret
END ASM
END SUB
SUB fspDisable (n as UByte)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspDisable" : stop: END IF
#endif
fspErase()
POKE @fspDataStart+48*n,99
end SUB
SUB fspCoord (n as uByte, x as uByte, y as uByte) 'Set sprite coords: n (sprite number);x,y (vertical,horizontal coords)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspCoord" : stop: END IF
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+4+48*n
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
rem targetAddr=targetAddr+1
rem POKE targetAddr,x
rem targetAddr=targetAddr+1
rem POKE targetAddr,y
END SUB
SUB fspAttrs (n as uByte, attra as uByte, attrb as uByte, attrc as uByte, attrd as uByte) 'Set sprite attrs: n (sprite number);a,b,c,d (UDG attrs)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspAttrs" : stop: END IF
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+40+48*n
POKE targetAddr,attra
targetAddr=targetAddr+1
POKE targetAddr,attrb
targetAddr=targetAddr+1
POKE targetAddr,attrc
targetAddr=targetAddr+1
POKE targetAddr,attrd
END SUB
FUNCTION fspAttrByte(fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte) as uByte
#ifndef FINAL_CODE
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then Print "Out Of Bounds Call in ";"fspAttrByte" : stop: END IF
#endif
return (fspFlash shl 7) + (fspBright shl 6) + (fspPaper shl 3) + fspInk
END FUNCTION
SUB fspAttr(n as uByte,fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte)
#ifndef FINAL_CODE
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then Print "Out Of Bounds Call in ";"fspAttr" : stop: END IF
#endif
DIM attrByte as uByte
attrByte=fspAttrByte(fspInk,fspPaper,fspBright,fspFlash)
fspAttrs(n,attrByte,attrByte,attrByte,attrByte)
END SUB
SUB FASTCALL fspErase()
asm
call fspEraseAsm
end asm
END SUB
SUB FASTCALL fspBufferAndDraw()
asm
call fspBufferAndDrawAsm
end asm
END SUB
SUB FASTCALL fspUpdate()
asm
call fspUpdateAsm
END ASM
END SUB
SUB fspRedraw()
asm
halt
; Wait for scan line
LD BC,2120
fspRedrawloop:
DEC BC
LD A,B
OR C
JR NZ,fspRedrawloop
;REM Erase the sprites
call fspEraseAsm
;REM Save background and
;REM print sprites
call fspBufferAndDrawAsm
;REM update coordinates
call fspUpdateAsm
end asm
END SUB
Also, here is Apenao's Demonstration program, tweaked to be a little shinier.
DO
pause 1
IF MULTIKEYS(KEYO) and gx>0 THEN LET gx=gx-1: END IF
IF MULTIKEYS(KEYP) and gx<30 THEN LET gx=gx+1:END IF
IF MULTIKEYS(KEYQ) and gy>0 THEN LET gy=gy-1 :END IF
IF MULTIKEYS(KEYA) and gy<22 THEN LET gy=gy+1: END IF
let dx=dx+dmx:if dx=1 or dx=30 then let dmx=-dmx : END IF
let dy=dy+dmy:if dy=0 or dy=22 then let dmy=-dmy: END IF
let tx=tx+tmx:if tx=0 or tx=30 then let tmx=-tmx: END IF
let ty=ty+tmy:if ty=0 or ty=22 then let tmy=-tmy: END IF
let cx=cx+cmx:if cx=0 or cx=30 then let cmx=-cmx: END IF
let cy=cy+cmy:if cy=0 or cy=22 then let cmy=-cmy: END IF
So... if I was crazy and wanted to implement an IM2 routine.... how do you think I should do it?
Most of my attempts to put the 257 byte data block in memory have failed spectacularly. I think using ORG in an asm statement tends to upset the compiler; but the thing about setting up interrupts is we NEED to put a 257 byte vector table into a very very specific location in memory, and then need to ALSO have the actual interrupt service routine in the specific place that the vector table points to.
*ponder*
I suppose we could use DIM variable @address to force the issue with an array?
On the whole though, this fixed location code isn't really supported by the compiler at this time. How could we work around it?
Sadly, because it's erase all four sprites and then redraws all sprites, different sprites start to flicker at different points.
If using 4 sprites:
Sprite 0 starts to flicker at above (pixel) line 10
And every other sprite at about 8 lines further down, per sprite.
If using 3 sprites
Sprites 0 is stable
Sprite 1 flickers above row 6
Sprite 3 starts to flicker at above line 16.
If using 2 sprites
Everything is stable.
I assume this is why the mojon's demonstrations only use 2 sprites...
I can't see any more speed optimizations (and right now it probably completes a redraw cycle in less than half the time of the original). There are probably some left, and no doubt Boriel will spot them immediately because he's a genius. However, right now I'm looking at changing /how/ it works. Things like not redrawing a sprite if it hasn't moved, and rather than erase all sprites and then draw all sprites, perhaps go for an erase-buffer-draw routine that does it on a per-sprite basis; limiting the time in which flicker can hit to areas of the screen.
The only alternative to that is to use a back-buffer screen; and that's a LOT of memory.
So right now, it's perfectly useable, if you think about where your sprites are going to go on the screen. The more sprites you have, the more careful you have to be - and keep the later numbered sprites away from the top of the screen!
So, here's how it stands right now (with a tweaked version of Apenao's demo program):
Code:
#DEFINE BLACK 0
#DEFINE BLUE 1
#DEFINE RED 2
#DEFINE MAGENTA 3
#DEFINE GREEN 4
#DEFINE CYAN 5
#DEFINE YELLOW 6
#DEFINE WHITE 7
#DEFINE TRANSPARENT 8
#DEFINE CONTRAST 9
#DEFINE TRUE 1
#DEFINE FALSE 0
SUB fspInitialize (n as uByte, udgA as uByte, udgB as uByte, udgC as uByte, udgD as uByte,x as uByte,y as uByte) 'Initialize Sprite: n (sprite number 0-3);a,b,c,d (UDG number 0-20,99 means the sprite won't be printed)
#ifndef FINAL_CODE
IF n>3 OR (udgA>20 AND udgA<>99) or udgB>20 or udgC>20 or udgD>20 then Print "Out Of Bounds Call in ";"fspInitialize" : stop : END IF : REM Bounds checking - so a bad function call doesn't poke all over memory.
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+(48*n)
fspErase()
POKE targetAddr,udgA
targetAddr=targetAddr+1
POKE targetAddr,udgB
targetAddr=targetAddr+1
POKE targetAddr,udgC
targetAddr=targetAddr+1
POKE targetAddr,udgD
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
fspBufferAndDraw()
RETURN
fspDataStart:
REM This Section contains Fourspriter data and code that is called by other routines.
ASM
;; 16x16 Sprites moving a character each frame
;; Copyleft 2009 The Mojon Twins.
;; Updated Copyleft 2010 Paul Fisher
;; Uses UDGs (it reads the address from the system variables).
;; It moves up to 4 sprites, preserving the backgrounds.
;; Sprite control is done writing a certain number
;; in the Sprite's first UDG
;; If the first UDG is 99, it won't be printed
; Note that the sprites are initialized to disabled.
fspDataStartAsm: ; each block is 48 bytes long. [This comment originally said 40 bytes, and it's clearly 48]
;; Sprite 1
udgs1: defb 99,0,0,0 ; Four UDGs of the first sprite.
x_pos1: defb 0 ; X position in chars.
y_pos1: defb 0 ; Y position in chars.
cx_pos1: defb 0 ; Previous X position in chars.
cy_pos1: defb 0 ; Previous Y position in chars.
buffer1: defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; Background Gfx buffer.
attrs1: defb 6,7,7,7 ; Sprite ATTRs
buffatrs1: defb 0,0,0,0 ; Background Attr's buffer
fspCollisionDetect: defb 0,0,0 ; Sprite 0 with 1,2,3
defb 0,0 ; Sprite 1 with 2,3
defb 0 ; sprite 2 with 3
fspCopyToScreen:
call get_scr_address ; Returns (xpos, ypos) in HL
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ret
fspCopyFromScreen:
call get_scr_address
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ret
; This function returns the address into HL of the screen address
; b,c in character grid notation.
; Original code was extracted by BloodBaz
get_scr_address:
push de
ld a,b ; b=x
and 31
ld l,a
ld a,c ; c=y
ld d,a
and 24
add a,64
ld h,a
ld a,d
and 7
rrca
rrca
rrca
or l
ld l,a
pop de
ret
;; This function returns the memory address of the Character Position
;; b,c in the attribute screen memory.
;; Adapted from code by Jonathan Cauldwell.
get_attr_address:
ld a,c ;ypos
rrca
rrca
rrca ; Multiply by 32
ld l,a ; Pass to L
and 3 ; Mask with 00000011
add a,88 ; 88 * 256 = 22528 - start of attributes.
ld h,a ; Put it in the High Byte
ld a,l ; We get y value *32
and 224 ; Mask with 11100000
ld l,a ; Put it in L
ld a,b ; xpos
add a,l ; Add it to the Low byte
ld l,a ; Put it back in L, and we're done. HL=Address.
ret
;; Routine to save the background to the buffer
fspEraseAsm:
ld hl, fspDataStartAsm
EXX
ld b, 4
i4chars2:
EXX
;;
ld a, (hl)
cp 99
jr z, nxt2
;; A
LD BC,6
;EX DE,HL ; HL now points at the start sprite data instead
ADD HL,BC ; HL moved on to point to old x-y positions.
LD b,(HL) ; b=x
INC HL
LD c,(HL) ;c=y
INC HL
EX DE,HL ; Now it's DE that points into the data buffer.
; Draw first background character
call fspCopyToScreen
;; B
; xpos++
inc b
; Draw second background Character
call fspCopyToScreen
;; C
; xpos --
dec b
; ypos ++
inc c
; draw Third background Character
call fspCopyToScreen
;; D
; xpos++
inc b
; Draw Fourth background Character
call fspCopyToScreen
;; move to attr
inc de
inc de
inc de
inc de
;Set our position to top left corner again
; xpos --
dec b
; ypos --
dec c
call get_attr_address
EX DE,HL
;copyattrs
ld bc, 32
;First
LDI
;Second
LDI
; DE=DE+BC
EX DE,HL
add hl,bc
EX DE,HL
;Third
LDI
;Fourth
LDI
nxt2: EXX
djnz i4chars2
ret
fspBufferAndDrawAsm:
ld hl, fspDataStartAsm
EXX
ld b, 4
i4chars:
;push bc
EXX
;Active Sprite?
ld a, (hl)
cp 99
jr z, nxt1
;; A
LD BC,4
ADD HL,BC ; move past the udg part
;xpos
ld b,(hl)
inc hl
;ypos
ld c,(hl)
inc hl
inc hl ; move past the old co-ordinates
inc hl
EX de,hl
call fspCopyFromScreen
;; B
; xpos++
;ld hl, xpos
;inc (hl)
inc b
call fspCopyFromScreen
;; C
; xpos --
;ld hl, xpos
;dec (hl)
dec b
; ypos ++
;ld hl, ypos
;inc (hl)
inc c
call fspCopyFromScreen
;; D
; xpos++
;ld hl, xpos
;inc (hl)
inc b
call fspCopyFromScreen
;; Now we point to the ATTR buffer,adding 4:
inc de
inc de
inc de
inc de
;Put ourselves back at the top left corner.
; xpos --
dec b
; ypos --
dec c
call get_attr_address
ld bc, 32
;First Character
LDI ; bc was loaded with 32 instead of 31 to account for the BC-- this instruction has.
;Second Character
LDI
add hl, bc ; we can get away with this because BC++ as well as hl--
;Third Character
LDI
;Fourth Character
LDI
EX DE,HL ; back to an HL pointer.
nxt1: EXX
djnz i4chars
; Go straight into Painting new sprites for speed.
;; Print sprites routine
;; UDGs are labeled from 0 to 21. The addres of the UDG is:
;; *(23675) + 256 * *(23676) + 8 * N
fspDrawAsm:
ld hl, fspDataStartAsm ;
EXX
ld b, 4 ;
i4chars3:
;push bc
EXX
ld a, (hl)
cp 99
jr z, nxt3
;; Copy pointer "HL" to alt reg
push HL
exx
pop HL
exx
ld bc,4
add hl,bc
; Get xpos
LD b,(hl) ;x
inc hl
; Get ypos
ld c,(hl) ;y
inc hl
push HL ; de Save pointer.
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
;Draw the first character
call fspCopyToScreen
; xpos++
inc b
; Address of the graphics
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
; Address of next Graphic
EXX
LD A,(HL)
INC HL
EXX
call fspGetUdgAddr
; Draw the fourth Character
call fspCopyToScreen
pop hl ; Recover hl to point at the start of the pixel buffer
; hl = hl + 32
ld a,l
add a,34
ld l,a
jp nc, fspNoIncH
inc h ; hl now points to the attr data.
fspNoIncH:
EX DE,HL
;Reset position to top left.
; xpos --
dec b
; ypos --
dec c
;; attr
call get_attr_address
EX DE,HL
;copyattrs
ld bc, 32
;First
LDI
;Second
LDI
; DE=DE+BC
EX DE,HL
add hl,bc
EX DE,HL
;Third
LDI
;Fourth
LDI
LD BC,4
ADD HL,BC ; Move pointer to start of next block
nxt3: EXX
djnz i4chars3
ret
fspGetUdgAddr:
; finds the address of the UDG # in A
ld HL,(23675) ;HL points to the UDG space
rlca
rlca
rlca ; a = N * 8
ld d, 0
ld e, a
add hl, de
EX DE,HL
ret
fspUpdateAsm: ; This name will be used from ASM
; For each Sprite:
;; *(data + 6) = *(data + 4)
;; *(data + 7) = *(data + 5)
ld hl, fspDataStartAsm+4 ; Points to sprite 1
ld de, fspDataStartAsm+6
ldi
ldi
ld hl, fspDataStartAsm+4+48 ; Points to sprite 2
ld de, fspDataStartAsm+6+48
ldi
ldi
ld hl, fspDataStartAsm+4+48+48 ; Points to sprite 3
ld de, fspDataStartAsm+6+48+48
ldi
ldi
ld hl, fspDataStartAsm+4+48+48+48 ; Points to sprite 4
ld de, fspDataStartAsm+6+48+48+48
ldi
ldi
ret
END ASM
END SUB
SUB fspDisable (n as UByte)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspDisable" : stop: END IF
#endif
fspErase()
POKE @fspDataStart+48*n,99
end SUB
SUB fspCoord (n as uByte, x as uByte, y as uByte) 'Set sprite coords: n (sprite number);x,y (vertical,horizontal coords)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspCoord" : stop: END IF
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+4+48*n
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
rem targetAddr=targetAddr+1
rem POKE targetAddr,x
rem targetAddr=targetAddr+1
rem POKE targetAddr,y
END SUB
SUB fspAttrs (n as uByte, attra as uByte, attrb as uByte, attrc as uByte, attrd as uByte) 'Set sprite attrs: n (sprite number);a,b,c,d (UDG attrs)
#ifndef FINAL_CODE
IF n>3 then Print "Out Of Bounds Call in ";"fspAttrs" : stop: END IF
#endif
DIM targetAddr as uInteger
targetAddr=@fspDataStart+40+48*n
POKE targetAddr,attra
targetAddr=targetAddr+1
POKE targetAddr,attrb
targetAddr=targetAddr+1
POKE targetAddr,attrc
targetAddr=targetAddr+1
POKE targetAddr,attrd
END SUB
FUNCTION fspAttrByte(fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte) as uByte
#ifndef FINAL_CODE
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then Print "Out Of Bounds Call in ";"fspAttrByte" : stop: END IF
#endif
return (fspFlash shl 7) + (fspBright shl 6) + (fspPaper shl 3) + fspInk
END FUNCTION
SUB fspAttr(n as uByte,fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte)
#ifndef FINAL_CODE
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then Print "Out Of Bounds Call in ";"fspAttr" : stop: END IF
#endif
DIM attrByte as uByte
attrByte=fspAttrByte(fspInk,fspPaper,fspBright,fspFlash)
fspAttrs(n,attrByte,attrByte,attrByte,attrByte)
END SUB
SUB FASTCALL fspErase()
asm
call fspEraseAsm
end asm
END SUB
SUB FASTCALL fspBufferAndDraw()
asm
call fspBufferAndDrawAsm
end asm
END SUB
SUB FASTCALL fspUpdate()
asm
call fspUpdateAsm
END ASM
END SUB
SUB fspRedraw()
asm
halt
;REM Erase the sprites
call fspEraseAsm
;REM Save background and
;REM print sprites
call fspBufferAndDrawAsm
;REM update coordinates
call fspUpdateAsm
DO
pause 2
IF MULTIKEYS(KEYO) and gx>0 THEN LET gx=gx-1: END IF
IF MULTIKEYS(KEYP) and gx<30 THEN LET gx=gx+1:END IF
IF MULTIKEYS(KEYQ) and gy>0 THEN LET gy=gy-1 :END IF
IF MULTIKEYS(KEYA) and gy<22 THEN LET gy=gy+1: END IF
let dx=dx+dmx:if dx=1 or dx=30 then let dmx=-dmx : END IF
let dy=dy+dmy:if dy=0 or dy=22 then let dmy=-dmy: END IF
let tx=tx+tmx:if tx=0 or tx=30 then let tmx=-tmx: END IF
let ty=ty+tmy:if ty=0 or ty=22 then let tmy=-tmy: END IF
let cx=cx+cmx:if cx=0 or cx=30 then let cmx=-cmx: END IF
let cy=cy+cmy:if cy=0 or cy=22 then let cmy=-cmy: END IF
I've made quite a few changes to the actual FourSpriter code. I have made it significantly shorter, and not a small amount faster in execution, using tricks like LDI to copy directly from (HL) to (DE) as opposed to copying into and out of the A register. I removed at least one redundant section of code that was identical with another one - redirecting calls took care of that. There are also places that use bafflingly long code to do something simple. For example:
Code:
ld hl, 23676 ;10
ld a, (hl) ; 7
ld e, a ; 4
ld hl, 23675 ;10
ld a, (hl) ; 7
ld l, a ; 4
ld a, e ; 4
ld h, a ; 4=50
Is used to get the address of the UDG into the HL register. It takes 50 T states to do this, whereas:
Code:
ld HL,(23675)
Does it in one instruction, and 16 T states.
I know I'm not really that good with Z80 code....but stuff like that leaves me scratching my head.
Anyway, It still seems to catch the ray trace at the top of the screen between erase and draw (which means that for the first frame after updating sprites, you can't see them on the top 24 lines (or 3 characters). It was 26, but I managed to speed it up enough T states to creep back 2 lines
My advice, unless I (or someone else) can work a way around this is to use this area for status bars - or at least avoid running sprites into it.
I tried to make it more user friendly than the original - now all Sprite actions can be controlled fairly easily with calls to routines and functions. Most of these are used internally, and the chances are all a user will need are the routines to initialize, color, move and redraw moved sprites.
Usage:
Code:
fspInit (sprite#, graphicsA,graphicsB,graphicsC,graphicsD,x,y)
Sets sprite sprite # to use the four UDG's listed from 0-20. This also turns on an inactive sprite.
fspDisable(Sprite#)
Shuts down a sprite (internally by setting its first graphic to 99).
fspCoord(sprite#,x,y)
Sets the new co-ordinates for a sprite.
fspAttrs(sprite#, AttrA,AttrB,AttrC,AttrD)
Sets a sprite to have attributes A,B,C,D.
Note: This is the advanced use, that allows different bits of the sprite to be different colours.
fspAttrByte(ink,paper,bright,flash) as uByte
returns an attribute byte equivalent to the input ink,paper,bright and flash.
This function is used by the following subroutine:
fspAttr (sprite#,ink,paper,bright,flash)
Sets ALL four attributes of a sprite to the input values for ink,paper,bright and flash.
This is expected to be the common usage.
Note: Color words are #defined, so you can use fspAttr(0,WHITE,BLACK,FALSE,FALSE) to set a sprite to white on black, no bright, no flash.
fspRedraw()
Back buffer -> screen (erasing sprites)
screen -> back buffer (save background)
sprites -> screen (srawing sprites)
current location -> last location (update location)
In essence it moves the sprites to their new coordinates
It effectively, internally uses the following routines, that the user may not need to call.
They are made available, however, in case the programmer needs more direct control of sprite activity.
fspErase()
Erases all the sprites and replaces them with their background data.
fspBufferBackgroundAndDraw()
Copies screen to buffer and redraws the sprites onto the screen.
fspUpdate()
Updates co-ordinates
The actual code. Note that Apenao's example program is already attached at the end.
Code:
#DEFINE BLACK 0
#DEFINE BLUE 1
#DEFINE RED 2
#DEFINE MAGENTA 3
#DEFINE GREEN 4
#DEFINE CYAN 5
#DEFINE YELLOW 6
#DEFINE WHITE 7
#DEFINE TRANSPARENT 8
#DEFINE CONTRAST 9
#DEFINE TRUE 1
#DEFINE FALSE 0
SUB fspInitialize (n as uByte, udgA as uByte, udgB as uByte, udgC as uByte, udgD as uByte,x as uByte,y as uByte) 'Initialize Sprite: n (sprite number 0-3);a,b,c,d (UDG number 0-20,99 means the sprite won't be printed)
IF n>3 OR (udgA>20 AND udgA<>99) or udgB>20 or udgC>20 or udgD>20 then return : END IF : REM Bounds checking - so a bad function call doesn't poke all over memory.
DIM targetAddr as uInteger
targetAddr=@fspDataStart+(48*n)
fspErase()
POKE targetAddr,udgA
targetAddr=targetAddr+1
POKE targetAddr,udgB
targetAddr=targetAddr+1
POKE targetAddr,udgC
targetAddr=targetAddr+1
POKE targetAddr,udgD
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
targetAddr=targetAddr+1
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
fspBufferAndDraw()
RETURN
fspDataStart:
REM This Section contains Fourspriter data and code that is called by other routines.
ASM
;; 16x16 Sprites moving a character each frame
;; Copyleft 2009 The Mojon Twins.
;; Pergreñado por na_th_an
;; Uses UDGs (it reads the address from the system variables).
;; It moves up to 4 sprites, preserving the backgrounds.
datap: ; each block is 48 bytes long. [This comment originally said 40 bytes, and it's clearly 48]
;; Sprite 1
udgs1: defb 99,0,0,0 ; Four UDGs of the first sprite.
x_pos1: defb 0 ; X position in chars.
y_pos1: defb 0 ; Y position in chars.
cx_pos1: defb 0 ; Previous X position in chars.
cy_pos1: defb 0 ; Previous Y position in chars.
buffer1: defb 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; Background Gfx buffer.
attrs1: defb 7,7,7,7 ; Sprite ATTRs
buffatrs1: defb 0,0,0,0 ; Background Attr's buffer
;; General use coordinates
xpos: defb 0
ypos: defb 0
cxpos: defb 0
cypos: defb 0
;; Sprite control is done writing a certain number
;; in the Sprite's first UDG
;; If the first UDG is 99, it won't be printed
docopy:
borra_char:
call get_scr_address ; Dirección (xpos, ypos) en BC
ld l, c
ld h, b ; Dirección (xpos, ypos) en HL
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ld a, (de)
ld (hl), a
inc h
inc de
ret
;; Esta función devuelve en bc la dirección de memoria
;; de la posición (xpos, ypos) en carácteres. Extraído de código
;; por BloodBaz.
get_scr_address:
push de
ld a, (xpos)
and 31
ld c, a
ld a, (ypos)
ld d, a
and 24
add a, 64
ld b, a
ld a, d
and 7
rrca
rrca
rrca
or c
ld c, a
pop de
ret
;; Esta función devuelve en bc la dirección de memoria
;; de la posición (xpos, ypos) dentro del archivo de atributos.
;; Adaptado de código por Jonathan Cauldwell.
;; 010110YY YYYXXXXX
get_attr_address:
ld a, (ypos) ; Cogemos y
rrca
rrca
rrca ; La multiplicamos por 32
ld c, a ; nos lo guardamos en c
and 3 ; ponemos una mascarita 00000011
add a, 88 ; 88 * 256 = 22528, aquí empieza el tema
ld b, a ; Hecho el bite superior.
ld a, c ; Nos volvemos a traer y * 32
and 224 ; Mascarita 11100000
ld c, a ; Lo volvemos a poner en c
ld a, (xpos) ; Cogemos x
add a, c ; Le sumamos lo que teníamos antes.
ld c, a ; Listo. Ya tenemos en BC la dirección.
ret
;; A
LD BC,6 ; 10
EX DE,HL ; 4
ADD HL,BC ; 11
; EX DE,HL ; 4
;inc de
;inc de
;inc de
;inc de
;inc de
;inc de
; Obtenemos xpos = CX
ld DE, xpos
;ld a, (de)
;ld (hl), a
LDI
;inc de
; Obtenemos ypos = CY
ld DE, ypos
;ld a, (de)
;ld (hl), a
;inc de
LDI
EX DE,HL
;; B
; Pintamos el primer char
call borra_char
; xpos++
ld hl, xpos
inc (hl)
;; C
; Pintamos el segundo char
call borra_char
; xpos --
ld hl, xpos
;ld a, (hl)
;dec a
;ld (hl), a
dec (hl)
; ypos ++
ld hl, ypos
inc (hl)
;; D
; Pintamos el tercer char
call borra_char
; xpos++
ld hl, xpos
inc (hl)
;; E
; Pintamos el cuarto char
call borra_char
;; attr
inc de
inc de
inc de
inc de
;;
; xpos --
ld hl, xpos
dec (hl)
; ypos --
ld hl, ypos
dec (hl)
;
call get_attr_address
;
call copyattrs
;
nxt2: pop bc
djnz i4chars2
ret
copia_char:
call get_scr_address ;
ld l, c
ld h, b ;
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
ld a, (hl)
ld (de), a
inc h
inc de
;
ld DE, ypos
;ld a, (de)
;ld (hl), a
;inc de
LDI
EX DE,HL
inc de
inc de
;; B
;
call copia_char
; xpos++
ld hl, xpos
inc (hl)
;; C
;
call copia_char
; xpos --
ld hl, xpos
dec (hl)
; ypos ++
ld hl, ypos
inc (hl)
;; D
;
call copia_char
; xpos++
ld hl, xpos
inc (hl)
;; E
;
call copia_char
;; Now we point to the ATTR buffer,adding 4:
inc de
inc de
inc de
inc de
;;
; xpos --
ld hl, xpos
dec (hl)
; ypos --
ld hl, ypos
dec (hl)
;
call get_attr_address
;
ld l, c
ld h, b
ld bc, 32
;ld a, (hl)
;ld (de), a ; Primer carácter
;inc hl
;inc de
LDI ; bc was loaded with 32 instead of 31 to account for the BC-- this instruction has.
;ld a, (hl)
;ld (de), a ; Segundo carácter
;inc de
LDI
add hl, bc ; we can get away with this because BC++ as well as hl--
;ld a, (hl)
;ld (de), a ; Tercer carácter
;inc hl
;inc de
LDI
;ld a, (hl)
;ld (de), a ; Cuarto carácter
;inc de
LDI
; Fin del bucle
nxt1: pop bc
djnz i4chars
; ret - No! Go straight into Painting new sprites for speed.
;; Print sprites routine
;; UDGs are labeled from 0 to 21. The addres of the UDG is:
;; *(23675) + 256 * *(23676) + 8 * N
fspDrawAsm:
draw_sprites:
ld de, datap ;
ld b, 4 ;
i4chars3: push bc
ld a, (de)
cp 99
jr z, nxt3
;; copiar aquí "de" a un buffer
call charsabuff
; Obtenemos xpos
ld hl, xpos
EX DE,HL
;ld a, (de)
;ld (hl), a
;inc de
LDI
; Obtenemos ypos
;ld a, (de)
ld DE, ypos
;ld (hl), a
;inc de
LDI
inc HL ; de
inc HL ; de
push HL ; de
ld hl, bufchars
call getde
call docopy
; xpos++
ld hl, xpos
inc (hl)
; Dirección del gráfico
ld hl, bufchars+1
;inc hl
call getde
; Pintamos el segundo char
call docopy
; xpos--
ld hl, xpos
ld a, (hl)
dec a
ld (hl), a
; de = de + 32
ld bc, 32
ld h, d
ld l, e
add hl, bc
ld d, h
ld e, l
;; attr
; xpos --
ld hl, xpos
;ld a, (hl)
;dec a
;ld (hl), a
dec (hl)
; ypos --
ld hl, ypos
;ld a, (hl)
;dec a
;ld (hl), a
dec(hl)
;
call get_attr_address
;
call copyattrs
inc de
inc de
inc de
inc de ;
nxt3: pop bc
djnz i4chars3
ret
;; Esta función calcula la posición en memoria del UDG apuntado por
;; el número que está en la dirección hl.
getde: ld a, (hl)
;ld d, a
;ld hl, 23676
;ld a, (hl)
;ld e, a
;ld hl, 23675
;ld a, (hl)
;ld l, a
;ld a, e
;ld h, a
ld HL,(23675)
;; HL = *(23675) + 256 * *(23676)
;ld a, d
rlca
rlca
rlca ; a = N * 8
ld d, 0
ld e, a
add hl, de
;ld d, h
;ld e, l
EX DE,HL
ret
bufchars: defb 0,0,0, 0 ;
charsabuff: ld hl, bufchars
EX DE,HL
LDI
LDI
LDI
LDI
EX DE,HL
ret
copyattrs: ld l, c
ld h, b
ld bc, 31
ld a, (de)
ld (hl), a ; Primer carácter
inc hl
inc de
ld a, (de)
ld (hl), a ; Segundo carácter
add hl, bc
inc de
ld a, (de)
ld (hl), a ; Tercer carácter
inc hl
inc de
ld a, (de)
ld (hl), a ; Cuarto carácter
inc de ; Ahora de apunta a datap+48, o sea, al siguiente sprite.
ret
fspUpdateAsm:
update_coordinates:
ld de, datap ; Apuntamos a la zona de datos
ld hl, datap ; idem
ld b, 4 ; 4 iteraciones
i4chars4: push bc
;; Para cada sprite hay que hacer:
;; *(datap + 6) = *(datap + 4)
;; *(datap + 7) = *(datap + 5)
inc de
inc de
inc de
inc de
ld a, (de) ; a = X
inc hl
inc hl
inc hl
inc hl
inc hl
inc hl
ld (hl), a ; CX = a
inc de
ld a, (de) ; a = Y
inc hl
ld (hl), a ; CY = a
inc hl
;; hl = hl + 40
ld bc, 40
add hl, bc
;; de = hl
ld d, h
ld e, l
pop bc
djnz i4chars4
ret
END ASM
END SUB
SUB fspDisable (n as UByte)
IF n>3 then return : END IF
POKE @fspDataStart+48*n,99
end SUB
SUB fspCoord (n as uByte, x as uByte, y as uByte) 'Set sprite coords: n (sprite number);x,y (vertical,horizontal coords)
IF n>3 then return : END IF
DIM targetAddr as uInteger
targetAddr=@fspDataStart+4+48*n
POKE targetAddr,x
targetAddr=targetAddr+1
POKE targetAddr,y
rem targetAddr=targetAddr+1
rem POKE targetAddr,x
rem targetAddr=targetAddr+1
rem POKE targetAddr,y
END SUB
SUB fspAttrs (n as uByte, attra as uByte, attrb as uByte, attrc as uByte, attrd as uByte) 'Set sprite attrs: n (sprite number);a,b,c,d (UDG attrs)
IF n>3 then return : END IF
DIM targetAddr as uInteger
targetAddr=@fspDataStart+40+48*n
POKE targetAddr,attra
targetAddr=targetAddr+1
POKE targetAddr,attrb
targetAddr=targetAddr+1
POKE targetAddr,attrc
targetAddr=targetAddr+1
POKE targetAddr,attrd
END SUB
FUNCTION fspAttrByte(fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte) as uByte
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then return 0: END IF: Rem bounds check
return (fspFlash shl 7) + (fspBright shl 6) + (fspPaper shl 3) + fspInk
END FUNCTION
SUB fspAttr(n as uByte,fspInk as uByte,fspPaper as uByte,fspBright as uByte, fspFlash as uByte)
if fspInk > 7 OR fspPaper > 7 OR fspBright > 1 or fspFlash >1 then return : END IF: Rem bounds check
DIM attrByte as uByte
attrByte=fspAttrByte(fspInk,fspPaper,fspBright,fspFlash)
fspAttrs(n,attrByte,attrByte,attrByte,attrByte)
END SUB
SUB FASTCALL fspErase()
asm
call fspEraseAsm
end asm
END SUB
SUB FASTCALL fspBufferAndDraw()
asm
call fspBufferAndDrawAsm
end asm
END SUB
SUB FASTCALL fspUpdate()
asm
call fspUpdateAsm
END ASM
END SUB
SUB fspRedraw()
asm
halt
;REM Erase the sprites
call fspEraseAsm
;REM Save background and
;REM print sprites
call fspBufferAndDrawAsm
;REM update coordinates
call fspUpdateAsm
DO
pause 2
IF MULTIKEYS(KEYO)<>0 and gx>0 THEN LET gx=gx-1: END IF
IF MULTIKEYS(KEYP)<>0 and gx<30 THEN LET gx=gx+1:END IF
IF MULTIKEYS(KEYQ)<>0 and gy>0 THEN LET gy=gy-1 :END IF
IF MULTIKEYS(KEYA)<>0 and gy<22 THEN LET gy=gy+1: END IF
let dx=dx+dmx:if dx=0 or dx=30 then let dmx=-dmx : END IF
let dy=dy+dmy:if dy=0 or dy=22 then let dmy=-dmy: END IF
let tx=tx+tmx:if tx=0 or tx=30 then let tmx=-tmx: END IF
let ty=ty+tmy:if ty=0 or ty=22 then let tmy=-tmy: END IF
let cx=cx+cmx:if cx=0 or cx=30 then let cmx=-cmx: END IF
let cy=cy+cmy:if cy=0 or cy=22 then let cmy=-cmy: END IF