03-06-2010, 12:05 PM
I like this code. These are like PRINT routines, but optimized for a specialized task :!:
Here you are some optimizations:
Since BC is later discarded, this can be replaced by
This sequence occurs twice in the code. If you could use subversion, we could insert this into the library code in 1.2.6 so we can work as a team, submitting patches, and so on. :roll: (If you don't know, I could show you svn basic usages)
This can be replaced by
Since this is an xpos-- ANSI C output code (Z88DK, I guess) :-)
I think all commented code should be also removed out and comments translated into English (I could also do this).
Also JR is slower than JP. Since this routine is speed-critical, I replaced the jr here:
And finally, function encapsulation optimization:
In order to call some asm functions from BASIC you do
Using FASTCALL with no parameters is like having an inline asm function. So you could return with a simple RET. But instead of returning from XXXX, you can return directly from asm_func:
Even better, you can do your asm functions directly callable both from ASM and BASIC contexts. Doing all of these changes, this is the resulting code:
Look also at the MultiKeys checking at the end of the above code (click on the Expand button to see it in a greater window). The following are equivalent:
The 1st IF should be transformed into the second one automatically, but I haven't implemented such optimization yet.
Here you are some optimizations:
Code:
;; A
LD BC,6 ; 10
EX DE,HL ; 4
ADD HL,BC ; 11
; EX DE,HL ; 4
;inc de
;inc de
;inc de
...
Code:
;; A
LD HL, 6 ; 10
ADD HL, DE ; 11
;inc de
;inc de
;inc de
...
Code:
; xpos--
ld hl, xpos
ld a, (hl)
dec a
ld (hl), a
Code:
; xpos--
ld hl, xpos
dec (hl)
Since this is an xpos-- ANSI C output code (Z88DK, I guess) :-)
I think all commented code should be also removed out and comments translated into English (I could also do this).
Also JR is slower than JP. Since this routine is speed-critical, I replaced the jr here:
Code:
ld a, (de)
cp 99
jp z, nxt3 ; <== JP is faster
And finally, function encapsulation optimization:
In order to call some asm functions from BASIC you do
Code:
SUB xxxx
asm
call asm_func
end asm
END SUB
Code:
SUB FASTCALL xxxx
asm
JP asm_func ; <== JUMP to asm_func and return from there
end asm
END SUB
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
;; Sprite 2
udgs2: defb 99,0,0,0
x_pos2: defb 0
y_pos2: defb 0
cx_pos2: defb 0
cy_pos2: defb 0
buffer2: 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
attrs2: defb 7,7,7,7
buffatrs2: defb 0,0,0,0
;; Sprite 3
udgs3: defb 99,0,0,0
x_pos3: defb 0
y_pos3: defb 0
cx_pos3: defb 0
cy_pos3: defb 0
buffer3: 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
attrs3: defb 7,7,7,7
buffatrs3: defb 0,0,0,0
;; Sprite 4
udgs4: defb 99,0,0,0
x_pos4: defb 0
y_pos4: defb 0
cx_pos4: defb 0
cy_pos4: defb 0
buffer4: 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
attrs4: defb 7,7,7,7
buffatrs4: defb 0,0,0,0
;; 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
spare: defb 85,85,85,85,85,85,85,85
;; Routine to save the background to the buffer
;borra_sprites:
; pointer = datap
; for (i = 0; i < 4; i ++) {
; CX = *(pointer + 6)
; CY = *(pointer + 7)
; scr_address = get_scr_address (CX, CY);
; for (j = 0; j < 8; j ++) {
; *scr_address = *(pointer + 8 + j);
; scr_address += 256;
; }
; scr_address = get_scr_address (CX+1, CY);
; for (j = 0; j < 8; j ++) {
; *scr_address = *(pointer + 16 + j);
; scr_address += 256;
; }
; scr_address = get_scr_address (CX, CY+1);
; for (j = 0; j < 8; j ++) {
; *scr_address = *(pointer + 24 + j);
; scr_address += 256;
; }
; scr_address = get_scr_address (CX+1, CY+1);
; for (j = 0; j < 8; j ++) {
; *scr_address = *(pointer + 32 + j);
; scr_address += 256;
; }
; pointer += 40;
; }
fspEraseAsm:
ld de, datap ;
ld b, 4 ;
i4chars2: push bc
;;
ld a, (de)
cp 99
jr z, nxt2
;; 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
ret
fspBufferAndDrawAsm:
init_sprites:
; pointer = datap
; for (i = 0; i < 4; i ++) {
; X = *(pointer + 4); // A
; Y = *(pointer + 5);
; scr_address = get_scr_address (X, Y); // B
; for (j = 0; j < 8; j ++) {
; *(pointer + 8 + j) = *scraddress;
; scraddress += 256;
; }
; scr_address = get_scr_address (X+1, Y); // C
; for (j = 0; j < 8; j ++) {
; *(pointer + 16 + j) = *scraddress;
; scraddress += 256;
; }
; scr_address = get_scr_address (X, Y+1); // D
; for (j = 0; j < 8; j ++) {
; *(pointer + 24 + j) = *scraddress;
; scraddress += 256;
; }
; scr_address = get_scr_address (X+1, Y+1); // E
; for (j = 0; j < 8; j ++) {
; *(pointer + 32 + j) = *scraddress;
; scraddress += 256;
; }
; pointer = pointer + 40;
; }
ld de, datap ;
ld b, 4 ;
i4chars: push bc
;;
ld a, (de)
cp 99
jr z, nxt1
;; A
LD HL, 4
ADD HL, DE
ld DE, xpos
;ld a, (de)
;ld (hl), a
;inc de
LDI
ld DE, ypos
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
; 1st character
LDI ; bc was loaded with 32 instead of 31
;to account for the BC-- this instruction has.
; 2nd character
LDI
add hl, bc ; we can get away with this
; because BC++ as well as hl--
; 3rd character
LDI
; 4th character
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
jp z, nxt3
;; copiar aquÎáÎõÎá "de" a un buffer
call charsabuff
; Obtenemos xpos
ld hl, xpos
EX DE,HL
LDI
; Obtenemos ypos
ld DE, ypos
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
dec (hl)
; ypos ++
ld hl, ypos
inc (hl)
; Graphic address
ld hl, bufchars+2
call getde
; Pintamos el tercer char
call docopy
; xpos ++
ld hl, xpos
inc (hl)
; Graphic address
ld hl, bufchars+3
call getde
; Pintamos el cuarto char
call docopy
pop de
; de = de + 32
ld hl, 32
add hl, de
ex de, hl
;; attr
; xpos --
ld hl, xpos
dec (hl)
; ypos --
ld hl, ypos
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 HL,(23675)
;; HL = *(23675) + 256 * *(23676)
rlca
rlca
rlca ; a = N * 8
ld d, 0
ld e, a
add hl, de
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
END ASM
END SUB
REM FUNCTION ENCAPSULATION
REM This function is callable both from BASIC and ASM
SUB fastcall fspUpdate() : REM <= This name is used for CALL from BASIC
asm
fspUpdateAsm: ; <= This name is used for CALL from ASM
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 fspRedraw()
asm
halt
;REM Erase the sprites
call fspEraseAsm
;REM Save background and
;REM print sprites
call fspBufferAndDrawAsm
;REM update coordinates
jp fspUpdateAsm
end asm
END SUB
REM below this is Apenao's Demo Program:
#include <sinclair.bas>
#include <memcopy.bas>
#include <keys.bas>
100 DIM gentle (0 to 3,0 to 7) AS uByte => { { 15, 15, 15, 15, 15, 15, 13, 15} , _
{ 240, 144, 208, 208, 240, 240, 176, 240} , _
{ 15, 14, 63, 0, 0, 12, 26, 30} , _
{ 176, 112, 252, 0, 0, 48, 104, 120}}
110 POKE Uinteger 23675, @gentle(0,0)
memcopy (0,16384,4092)
LET gx=10:LET gy=10:let dx=3:let dy=3:let tx=6:let ty=6:let cx=18:let cy=18
let dmx=1:let dmy=1:let tmx=-1:let tmy=-1:let cmx=1:let cmy=-1
fspInitialize (0,0,1,2,3,gx,gy) : REM (sprite number, udgA, udgB,udgC,udgD, initial X co-ordinate, initial y-coordinate)
fspInitialize (1,0,1,2,3,dx,dy)
fspInitialize (2,0,1,2,3,tx,ty)
fspInitialize (3,0,1,2,3,cx,cy)
fspAttr(0,WHITE,MAGENTA,FALSE,FALSE) : REM (sprite number, ink, paper, bright, flash)
fspAttr(1,YELLOW,BLACK,FALSE,FALSE)
fspAttr(2,GREEN,BLUE,TRUE,FALSE)
fspAttr(3,RED,WHITE,FALSE,FALSE)
DO
PAUSE 2
REM IF MultiKeys(X) <> X can be replaced by MultiKeys(X)
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=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
fspCoord (0,gx,gy)
fspCoord (1,dx,dy)
fspCoord (2,tx,ty)
fspCoord (3,cx,cy)
fspRedraw()
loop
Code:
IF Multikeys(KEYO) <> 0 THEN
...
END IF
IF MultiKeys(KEYO) THEN : REM This comparison is faster and take less memory
...
END IF