Welcome, Guest
You have to register before you can post on our site.

Username
  

Password
  





Search Forums

(Advanced Search)

Forum Statistics
» Members: 258
» Latest member: manuelzo75
» Forum threads: 1,074
» Forum posts: 6,434

Full Statistics

Online Users
There are currently 332 online users.
» 0 Member(s) | 330 Guest(s)
Bing, Google

Latest Threads
.tap file code not execut...
Forum: Help & Support
Last Post: Zoran
04-28-2025, 10:59 AM
» Replies: 4
» Views: 194
Exit from more than one l...
Forum: Wishlist
Last Post: Duefectu
04-23-2025, 10:06 PM
» Replies: 3
» Views: 262
put small ASM programs li...
Forum: How-To & Tutorials
Last Post: Zoran
04-18-2025, 02:02 PM
» Replies: 6
» Views: 1,526
Creating +3 Menus - Loadi...
Forum: Help & Support
Last Post: merlinkv
04-16-2025, 02:08 PM
» Replies: 6
» Views: 517
Randomize not very random...
Forum: Help & Support
Last Post: Zoran
04-08-2025, 10:40 AM
» Replies: 4
» Views: 419
Scope rules
Forum: Bug Reports
Last Post: Zoran
04-04-2025, 09:46 AM
» Replies: 2
» Views: 290
Using constants not allow...
Forum: Bug Reports
Last Post: baltasarq
03-19-2025, 10:00 PM
» Replies: 8
» Views: 1,016
404 page not found
Forum: Documentation
Last Post: boriel
03-08-2025, 07:16 PM
» Replies: 5
» Views: 2,855
Spectrum keywords codes
Forum: Bug Reports
Last Post: boriel
03-08-2025, 11:00 AM
» Replies: 1
» Views: 398
ZXodus][Engine
Forum: ZX Basic Compiler
Last Post: boriel
02-19-2025, 11:43 PM
» Replies: 69
» Views: 213,491

 
  Inherent IM2 Functions
Posted by: britlion - 03-14-2010, 05:40 PM - Forum: Wishlist - No Replies

SUB <name> as interrupt
stuff
end sub

would be pretty awesome.

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.

Print this item

  New bug-fix release 1.2.5-r1513
Posted by: boriel - 03-12-2010, 10:36 PM - Forum: Bug Reports - Replies (12)

Hi,

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 Confusedhock: :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 -->

Print this item

  FourSpriter Final (Slower) Version
Posted by: britlion - 03-11-2010, 01:51 AM - Forum: How-To & Tutorials - Replies (11)

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

;; 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

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
        
         ; Draw the second Character
         call   fspCopyToScreen
        
         ; xpos--
         dec b
        
         ; ypos ++
         inc c
        
         ; Address of Graphics
         EXX
         LD A,(HL)
         INC HL
         EXX
         call   fspGetUdgAddr
        
         ; Draw the third character
         call   fspCopyToScreen
        
         ; xpos ++
         inc b
        
         ; 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.

Code:
#include <sinclair.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)

    CLS
    for n = 0 to 15
    print paper (n mod 7); ink ((n+3) mod 8); "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"
    next n
    
    let cx=1:let cy=1: let cmx=1:let cmy=1
    let dx=7:let dy=5: let dmx=1:let dmy=-1
    let tx=10:let ty=7: let tmx=-1:let tmy=1
    LET gx=15:LET gy=11
    
    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 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

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

    fspRedraw()

loop

Print this item

  Interrupt Mode 2
Posted by: britlion - 03-11-2010, 12:22 AM - Forum: ZX Basic Compiler - Replies (9)

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?

Print this item

  FourSpriter MK III
Posted by: britlion - 03-10-2010, 06:08 PM - Forum: How-To & Tutorials - Replies (3)

Okay, reduced the flicker quite a bit more.

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

;; 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

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
        
         ; Draw the second Character
         call   fspCopyToScreen
        
         ; xpos--
         dec b
        
         ; ypos ++
         inc c
        
         ; Address of Graphics
         EXX
         LD A,(HL)
         INC HL
         EXX
         call   fspGetUdgAddr
        
         ; Draw the third character
         call   fspCopyToScreen
        
         ; xpos ++
         inc b
        
         ; 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
  
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)

    CLS
    for n = 0 to 15
    print paper (n mod 7); ink ((n+3) mod 8); "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"
    next n
    
    let cx=1:let cy=1: let cmx=1:let cmy=1
    let dx=7:let dy=5: let dmx=1:let dmy=-1
    let tx=10:let ty=7: let tmx=-1:let tmy=1
    LET gx=15:LET gy=11
    
    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) 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

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

    fspRedraw()

loop

Print this item

  Incorrect Line numbers (*solved*)
Posted by: britlion - 03-07-2010, 07:16 AM - Forum: Bug Reports - Replies (6)

I'm getting an error in the code "illegal character _ at line xxx"

And there isn't one in line xxx.

I suspect that this line number comes from the assembler, and it's a line number based on the code stripped of comments and white space perhaps?

I've noticed that it's sometimes hard to pin down the correct line number for errors before.

Print this item

  SVN
Posted by: britlion - 03-06-2010, 06:55 PM - Forum: ZX Basic Compiler - Replies (6)

I've never used it at all; but I hear that it's a nice code control system.

How does one get going then?

Do you use windows, or linux?

Print this item

  Fourspriter: Alternate Version
Posted by: britlion - 03-06-2010, 05:29 AM - Forum: How-To & Tutorials - Replies (47)

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

Print this item

  Stack Bug in ZXB 1.2.5-r1489 ?
Posted by: britlion - 03-05-2010, 04:07 PM - Forum: Bug Reports - Replies (19)

I was updating fourspriter to use SUBs as opposed to gotos. Probably slower, but a little cleaner on the interface to the user.

Anyway, as it stands the main program:

Calls initialize for the first sprite
which calls a dummy "data" routine (which is really just for asm. The first line is return. It only calls it to make sure it's not peephole optimized out)
Comes back to main
calls coords for the first sprite
comes back to main
calls initialize for the second sprite
which calls the dummy data routine again......

and then it goes wrong. It never comes back from this data routine this second time, even though it should return immediately. In fact, it restarts the main program again. Over and over - and then after a few cycles, crashes.


I confirmed the code does the same thing with 1.23 and 1.22; and I didn't think they had the stack bugs, so it could be something I'm doing wrong, I suppose. I can't really see what could go wrong with "return" being the only statement, though.

The code is a bit complex, and is unfinished; but hopefully you can see where I'm going. It has testing PRINTs in it. Just copy and paste and see if it behaves the same way for you.

Code:
SUB fspInitialize (n as uByte, a as uByte, b as uByte, c as uByte, d 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)
PRINT AT 0,0;"Initialize" : PAUSE 1: PAUSE 0
fspData() : REM call the data sub to make sure it isn't optimized out of the compile. That call just returns.
PRINT AT 2,0;"FSPData came back to Initialize" : PAUSE 1: PAUSE 0
IF n>3 OR a>20 or b>20 or c>20 or d>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)
POKE targetAddr,a
targetAddr=targetAddr+1
POKE targetAddr,b
targetAddr=targetAddr+1
POKE targetAddr,c
targetAddr=targetAddr+1
POKE targetAddr,d
PRINT AT 3,0;"We're at the end of Initialize"
PAUSE 1:PAUSE 0
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,fspAttrByte,fspAttrByte,fspAttrByte,fspAttrByte)
END SUB    

SUB fspData()
fspDataStart:
RETURN : REM if called we should return immediately. This sub is for data and ASM only.
REM This routine contains Fourspriter data and code that is called by other routines. In effect, it's a dummy subroutine.
REM It is called by fspInitialize in order to ensure it is included in the build.

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   0,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   0,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   0,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   0,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:      
         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
END ASM
END SUB


SUB fspErase()
asm
;; 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;
; }

         ld       de, datap      ;
         ld      b,   4         ;
i4chars2:   push   bc
        
         ;;
         ld      a,   (de)
         cp      99
         jr      z,   nxt2

         ;; A

         inc      de
         inc      de
         inc      de
         inc      de
         inc      de
         inc      de
        
         ; Obtenemos xpos = CX
         ld      a,   (de)
         ld      hl,   xpos
         ld      (hl), a
        
         inc    de
        
         ; Obtenemos ypos = CY
         ld      a,   (de)
         ld      hl, ypos
         ld      (hl), a
        
         inc    de
        
         ;; 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
        
         ; 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
         ld      a, (hl)
         dec      a
         ld      (hl), a
        
         ; ypos --
         ld      hl, ypos
         ld      a, (hl)
         dec      a
         ld      (hl), a
              
         ;
         call   get_attr_address
        
         ;
         call   copyattrs
        
         ;
        
nxt2:      pop      bc
         djnz    i4chars2        
      
jp fspErase_end


borra_char:            
         call   get_scr_address   ;
         ld      l,    c
         ld      h,    b         ;
        
         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
        



fspErase_end:

end asm
END SUB


SUB fspBufferBackground()
asm
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

         inc      de
         inc      de
         inc      de
         inc      de

         ;
         ld      a,   (de)
         ld      hl,   xpos
         ld      (hl), a
        
         inc    de
        
         ;
         ld      a,   (de)
         ld      hl, ypos
         ld      (hl), a
        
         inc    de
         inc    de
         inc    de
        
         ;; B
        
         ;
         call   copia_char
        
         ; xpos++
         ld      hl, xpos
         inc      (hl)
        
         ;; C
        
         ;
         call    copia_char

         ; xpos --
         ld      hl, xpos
         ld      a, (hl)
         dec      a
         ld      (hl), a
        
         ; 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
         ld      a, (hl)
         dec      a
         ld      (hl), a
        
         ; ypos --
         ld      hl, ypos
         ld      a, (hl)
         dec      a
         ld      (hl), a
              
         ;
         call   get_attr_address
        
         ;
         ld      l,    c
         ld      h,    b
         ld      bc, 31
         ld      a,   (hl)
         ld      (de), a         ; Primer carácter
         inc      hl
         inc      de
         ld      a,    (hl)  
         ld      (de), a         ; Segundo carácter
         add      hl,   bc
         inc      de
         ld      a,   (hl)
         ld      (de), a         ; Tercer carácter
         inc      hl
         inc      de
         ld      a,    (hl)
         ld      (de), a         ; Cuarto carácter
         inc      de            

         ; Fin del bucle
        
nxt1:      pop      bc
         djnz    i4chars
        
         ;ret
jp fspBufferBackground_end
        
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  ; Subroutines don't need to return themselves.

fspBufferBackground_end:        
;; Routine to move the sprites:

end asm
END SUB

SUB fspRedraw()
asm
   halt
end asm

   ';call borra_sprites            ; Erase the sprites
   fspErase()
   ';call init_sprites               ; Save background
   fspBufferBackground()
   ';call draw_sprites            ; print sprites
   fspDraw()
   ';call update_coordinates         ; update coordinates :)
   fspUpdate()
   ';ret

END SUB

SUB fspDraw()
asm
;; Print sprites routine
;; UDGs are labeled from  0 to 21. The addres of the UDG is:
;; *(23675) + 256 * *(23676) + 8 * N
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      a,   (de)
         ld      hl,   xpos
         ld      (hl), a
         inc    de
         ; Obtenemos ypos
         ld      a,   (de)
         ld      hl, ypos
         ld      (hl), a
         inc    de
         inc    de
         inc    de
         push    de
        
         ld      hl, bufchars
         call   getde
         call   docopy
         ; xpos++
         ld      hl, xpos
         inc      (hl)
         ; Dirección del gráfico
         ld      hl, bufchars
         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
         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
         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
        
         ; ypos --
         ld      hl, ypos
         ld      a, (hl)
         dec      a
         ld      (hl), a
              
         ;
         call   get_attr_address
        
         ;
         call   copyattrs
        
         inc      de
         inc      de
         inc      de
         inc      de            ;
        
nxt3:      pop      bc
         djnz    i4chars3
        
         ;ret
jp fspDraw_end

;; 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
         ;;      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
         ret


bufchars:   defb   0,0,0, 0      ;

charsabuff: ld      hl,   bufchars
         ld      a, (de)
         ld      (hl), a
         inc      de
         inc      hl
         ld      a, (de)
         ld      (hl), a
         inc      de
         inc      hl
         ld      a, (de)
         ld      (hl), a
         inc      de
         inc      hl
         ld      a, (de)
         ld      (hl), a
         inc      de
         inc      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
fspDraw_end:
END ASM
END SUB

SUB fspUpdate()
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


#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)
    PRINT AT 5,0;"We came back from Initialize the first time" : PAUSE 1: PAUSE 0
    fspCoord (0,gy,gx)
    PRINT AT 6,0;"We came back from fspCoord the first time": PAUSE 1: PAUSE 0
    CLS
    fspInitialize (1,0,1,2,3)
    PRINT AT 7,0;"We came back from Initialize the second time" : PAUSE 1: PAUSE 0    
    fspCoord (1,dy,dx)
    fspInitialize (2,0,1,2,3)
    fspCoord (2,ty,tx)
    fspInitialize (3,0,1,2,3)
    fspCoord (3,cy,cx)
    fspAttrs (0,31,31,31,31)
    fspAttrs (1,12,12,12,12)
    fspAttrs (2,26,26,26,26)
    fspAttrs (3,6,6,6,6)
    

DO

    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
pause 2
    fspCoord (0,gx,gy)
    fspCoord (1,dx,dy)
    fspCoord (2,tx,ty)
    fspCoord (3,cx,cy)
    fspRedraw()
loop

Print this item

  ZX Assembler Inline Basic
Posted by: britlion - 03-04-2010, 04:51 AM - Forum: How-To & Tutorials - No Replies

ZXB allows you to do stuff that's pretty crazy.

I had a fancy cls routine - that didn't actually clear the screen, just ran a attribute bar over the screen, and then of course you do a CLS from basic afterwards. The effect is the same, but it looks cool.

It wouldn't compile right - the call to the ZX ROM sound routine kept corrupting things. Probably messing with IX and IY registers in naughty ways.

Then it occurred to me.... just compile a beep into the middle. :-)

Look:

Code:
sub clearscreen()
asm
    ld hl, 22528         ; 28000 33     0     88     10ts
    ld de, 32         ; 28003 17     32     0     10ts
l_6d66: push de             ; 28006 213 11ts
    push hl             ; 28007 229 11ts
    ld de, 32         ; 28008 17     32     0     10ts
    ld b, 24         ; 28011 6     22     7ts
l_6d6d: ld (hl), 24     ; Pick your leading bar attribute colour here.
    add hl, de         ; 28015 25     11ts
    djnz l_6d6d         ; 28016 16     251 8/13ts
    push de          ; save our registers before dropping to compiled code.
end asm
beep .05,-20
asm
    pop de           ; recover our register
    pop hl             ; (we didn't need to save HL, because we're resetting it)
    push hl             ; 28032 229 11ts
    ld b, 24         ; 28033 6     22     7ts
l_6d83: ld (hl), 63   ; Pick you final attribute here
    add hl, de         ; 28037 25     11ts
    djnz l_6d83         ; 28038 16     251 8/13ts
    pop hl             ; 28040 225 10ts
    pop de             ; 28041 209 10ts
    dec de             ; 28042 27     6ts
    inc hl             ; 28043 35     6ts
    ld a, d             ; 28044 122 4ts
    or e             ; 28045 179 4ts
    jr nz, l_6d66         ; 28046 32     214 7/12ts
    end asm
    cls : rem actually wipe the bits off the screen - we just ran an attribute bar before!
end sub

CLS
Print "hello world"
pause 1
pause 0

clearscreen()

print "Hello!"

How beautiful is that?
So long as I stack the registers I need (in this case DE), I can find them again when I come back. In a way, it's inline basic in the middle of assembler :-)

Anyway, that's my silliness for the day. Also: This makes for a nice clear screen subroutine. Change the bar colour and the final colour where indicated (and remember, ink and paper should be the same, so it does 'clear' it by hiding any characters on the screen until the CLS.

It's also pretty easy to change the sound (or remove it altogether - but you need something there to slow it down, or you don't see the effect. A busy loop or a pause would work too).

Print this item