Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Fourspriter: Alternate Version
#1
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 Smile

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

;; 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
         EX DE,HL
         LD BC,4
         ADD HL,BC
        
         ;inc      de
         ;inc      de
         ;inc      de
         ;inc      de
        
         ;
         ld      DE,   xpos
         ;ld      a,   (de)
         ;ld      (hl), a
         ;inc    de
         LDI
        
         ;
         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
        
         ; ypos ++
         ld      hl, ypos
         inc      (hl)
         ; Dirección del gráfico
         ld      hl, bufchars+2
         ;inc      hl
         ;inc      hl
         call   getde
         ; Pintamos el tercer char
         call   docopy
         ; xpos ++
         ld      hl, xpos
         inc      (hl)
         ; Dirección del gráfico
         ld      hl, bufchars+3
         ;inc      hl
         ;inc      hl
         ;inc      hl
         call    getde
        
         ; Pintamos el cuarto char
         call   docopy
                    
         pop      de
        
         ; 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
  
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
    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

    fspCoord (0,gx,gy)
    fspCoord (1,dx,dy)
    fspCoord (2,tx,ty)
    fspCoord (3,cx,cy)

    fspRedraw()

loop
Reply
#2
Awesome work britlion. I think this version is the one that should go to the library. It's faster and over all it's much clearer to the final user than the one I did.
Reply
#3
I like this code. These are like PRINT routines, but optimized for a specialized task :!:
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
            ...
Since BC is later discarded, this can be replaced by
Code:
;; A
             LD HL, 6 ; 10
             ADD HL, DE ; 11
             ;inc      de
             ;inc      de
             ;inc      de
             ...
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)

Code:
; xpos--
             ld      hl, xpos
             ld      a, (hl)
             dec      a
             ld      (hl), a
This can be replaced by
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
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:
Code:
SUB FASTCALL xxxx
    asm
       JP asm_func ; <== JUMP to asm_func and return from there
    end asm
END SUB
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:
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
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:
Code:
IF Multikeys(KEYO) <> 0 THEN
    ...
    END IF
    IF MultiKeys(KEYO) THEN : REM This comparison is faster and take less memory
    ...
    END IF
The 1st IF should be transformed into the second one automatically, but I haven't implemented such optimization yet.
Reply
#4
Even more optimized:

The fspUpdateAsm routine can be optimized to this (please, click on the "Expand View" button on the right side of this message ==>)
Code:
SUB fastcall fspUpdate : REM This name will be used from BASIC
    asm
fspUpdateAsm:          ; This name will be used from ASM
         update_coordinates:
         ld     hl, datap+4     ; Points to data address
         ld     d, h
         ld     e, l            ; idem
         ld     b, 4            ; 4 iterations
i4chars4:
         ;; For each Sprite:
         ;; *(datap + 6) = *(datap + 4)
         ;; *(datap + 7) = *(datap + 5)
         inc    de
         inc    de
         ldi
         inc bc   ; Restores BC
         ldi
         inc bc   ; Restores BC
         ;; hl = hl + 40
         ld     hl, 44
         add    hl, de
         ld      d, h ; DE = HL
         ld      e, l
         djnz    i4chars4
END ASM
END SUB : REM There is an implicit RET here

Compare this routine with the previous one. I think this one is quite optimal (if not already). This should be done in all the code.
Reply
#5
I see you had fun with it, like I did.

Whoever wrote it had some bad habits.

First up they seemed to believe that everything should go through the A register.

And they broke convention that HL is source and DE is destination.

So I patched it with EX DE,HL in several places - it could definitely be rewritten much cleaner without needing those swapped back and forth. It did allow me to use LDI though.

Also the use of dec (hl) and inc (hl) - you spotted one I missed. I swapped out LOTS of those! In several cases I traded JR for JP. Missed some too. Teach me to post code at 2 in the morning :-) I'm glad you found places to shave.

I'm also worried that there are two huge chunks of code that are ALMOST identical - one to copy (de) to (hl) and one to copy (hl) to (de). It probably won't be too hard to make them into one piece of code. I already removed the third copy that was identical to the original.

I completely agree that removing dead comments is worthwhile; I left a lot of them in so people could see at this stage what has been changed!

Now I've got to look at your final upgrades, Boriel. Smile (and nice work, there)
Reply
#6
britlion Wrote:I see you had fun with it, like I did.

Whoever wrote it had some bad habits.
I guess it was a compiler, not a human Wink
See the C source code in the comments. Idea
Reply
#7
Boriel Wrote: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:

Now that I didn't know. Very very nice. I'm glad you like the way I made them user friendly accessible as well as internally accessible though. I was quite pleased with that.
Reply
#8
boriel Wrote:Even more optimized:

The fspUpdateAsm routine can be optimized to this (please, click on the "Expand View" button on the right side of this message ==>)
Code:
SUB fastcall fspUpdate : REM This name will be used from BASIC
    asm
fspUpdateAsm:          ; This name will be used from ASM
         update_coordinates:
         ld     hl, datap+4     ; Points to data address
         ld     d, h
         ld     e, l            ; idem
         ld     b, 4            ; 4 iterations
i4chars4:
         ;; For each Sprite:
         ;; *(datap + 6) = *(datap + 4)
         ;; *(datap + 7) = *(datap + 5)
         inc    de
         inc    de
         ldi
         inc bc   ; Restores BC
         ldi
         inc bc   ; Restores BC
         ;; hl = hl + 40
         ld     hl, 44
         add    hl, de
         ld      d, h ; DE = HL
         ld      e, l
         djnz    i4chars4
END ASM
END SUB : REM There is an implicit RET here
Aha, another place where you find LD <reg>, Number followed by several INC statements. Actually, I didn't look too closely at update, it's true - the speed critical bit is erase-bufferBackground-redraw. Update happens after that, so I was a little lazy at finding ways to clean that part. If you look in the commented out parts, you'll see several places I did the same thing. Nice use of LDI there, though. It's quite a bit faster than the method the original tends to use.

I think the fastest, if you're willing to use 32 bytes in place of 20 would be:
Code:
; For each Sprite:
         ;; *(datap + 6) = *(datap + 4)
         ;; *(datap + 7) = *(datap + 5)

         ld     hl, datap+4     ; Points to sprite 1
         ld     de, datap+6                
         ldi
         ldi

         ld     hl, datap+4+48     ; Points to sprite 2
         ld     de, datap+6+48                      
         ldi
         ldi

         ld     hl, datap+4+48+48     ; Points to sprite 3
         ld     de, datap+6+48+48                    
         ldi
         ldi

         ld     hl, datap+4+48+48+48     ; Points to sprite 4
         ld     de, datap+6+48+48+48                        
         ldi
         ldi

Though since this isn't quite as speed critical, we might think about size. It's worth remembering that unrolled loops and brute force is sometimes quite a bit faster.

This runs in 256 T states. The loop version runs in 535 T states - which is quite a big saving over such a small routine!
Reply
#9
boriel Wrote:
britlion Wrote:I see you had fun with it, like I did.

Whoever wrote it had some bad habits.
I guess it was a compiler, not a human Wink
See the C source code in the comments. Idea

I'm not sure. I saw that and thought about it - but sometimes people use C as a pseudo code, and hand assemble line by line. I think my sqr routine might have similar comments because I did that with the pseudo.

It's possible, though.
Reply
#10
apenao Wrote:Awesome work britlion. I think this version is the one that should go to the library. It's faster and over all it's much clearer to the final user than the one I did.

For the record, Apenao, I probably wouldn't have it, if it weren't for the work you put in making the thing go in the first place!
Reply
#11
Still playing with it.

Got the 24 lines of flicker down to 18 by some speed optimizations.

And the good news, is that I think I can use similar ideas in other areas.
Reply
#12
The author of the original routine, na_th_an told me they have released v2.0 which is faster:
http://www.mojontwins.com/warehouse/fsp2.0.asm
They also gave me permission to include in the compiler, provided they're mentioned (they will be).
We need to wrap this code with sub/end sub and see if it works, and runs faster than previous version.

In this version they use LDI as we did. I see a tentative optimization with the push bc/pop bc sequences, as they can be replaced with inc bc/inc bc.
Reply
#13
Na_th_an was talking about that on worldofspectrum forums. He was asking what I did to improve the original :-)

Now I'm curious as to whether they made changes according to what I did or not.

It was definitely with irony that I noted that I spent about a week improving the routines of fourspriter 1.0 and they were making a newer version, meaning I sort of have the whole make-it-a-library thing to do over! Sad
Reply
#14
britlion Wrote:Na_th_an was talking about that on worldofspectrum forums. He was asking what I did to improve the original :-)

Now I'm curious as to whether they made changes according to what I did or not.

It was definitely with irony that I noted that I spent about a week improving the routines of fourspriter 1.0 and they were making a newer version, meaning I sort of have the whole make-it-a-library thing to do over! Sad
You're right. They told me they used your LDI code idea.

On the other hand, don't feel bad: At first you only have to use SUB... END SUB to enclose subs as you did before, and later try to do even more optimizations, but I expect it's already fast enough. I've seen some optimizations that could be done, anyway... Tongue
Reply
#15
I've optimized the FourSpriter 2.0.
Download the .asm file and try it (I haven't tested it, but it should be even faster).


Attached Files
.zip   4spriter-optimized.zip (Size: 3.45 KB / Downloads: 842)
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)