
**********************************************************************
files: Printing_Graphics.TXT, Printing_Graphics.TAP
       grid.bas, BOBette.BAS, GridSq.ASM, Cheque.ASM
       http://www.users.globalnet.co.uk/~jg27paw4/pourri/bob.zip
       UDG256p.ASM, UDG256d.BAS, UDG256d.ASM, GACpic.ASM
       UDGsBig.BAS, UDGs4x4.BAS, UDGs2x2.BAS, MapScroller.BAS
       Scroll1c.ASM, Scroll2b.BAS, Scroll2c.ASM, PlotChar.BAS
       http://www.users.globalnet.co.uk/~jg27paw4/pourri/chrpoint.zip
       ScrollE.ASM, Ellipse.BAS, Bresenham.BAS, CharScroll.ASM
http://www.users.globalnet.co.uk/~jg27paw4/pourri/rotating-unplot-2,2.asm
       S_Wipe.ASM, Eraser.BAS, ScanDown.ASM, MiniScroll.BAS, EnergyBar.ASM
       SCROLL256RR.ASM, SCROLL256PPa.ASM, SCROLL256PPb.ASM
**********************************************************************


** Drawing perspective grids in BASIC
   ==================================

Here's a demo routine drawing a series of grids with from 2x2 to 20x20 lines. I had to change the horizontal lines loop to step by line number rather than cell height (vstep), as a Speccy calculator quirk caused it to come out of the loop one step too soon for some heights.

The reason why the left-hand & right-hand modifiers xlm & xrm aren't the same is that the left-hand modifier is adjusting a PLOT point which varies as per the gradient of the left-hand line, whereas the right-hand modifier is adjusting a DRAW length which is contracting as per the encroaching gradients of both the left- and right-hand lines (going up the screen), so one is double the other.

grid.bas [code]
   3 LET height=88:
     FOR l=2 TO 20:
     PRINT AT 0,0;l;" lines":
     LET vstep=height/(l-1): LET hstep=INT (255/(l-1)*100)/100
   5 DIM c(l,2): LET line=1
  10 LET t=45: LET narrow=(255-t*2)/(l-1):
     FOR b=0 TO 255 STEP hstep:
     PLOT b,0: DRAW t-b,height:
     LET c(line,1)=b: LET c(line,2)=t-b:
     LET line=line+1: LET t=t+narrow:
     NEXT b
  30 LET xl=c(1,1): LET xlm=c(1,2)/(l-1)
  31 LET xr=c(l,1): LET xrm=-(c(l,2)/((l-1)/2))
  32 LET y=0: FOR v=1 TO l:
     PLOT xl,y: DRAW xr,0: LET xl=xl+xlm:
     LET xr=xr-xrm: LET y=y+vstep:
     NEXT v
  40 PAUSE 0: CLS :
     NEXT l
[/code]


**********************************************************************


** More on Storing Mazes (Genesis of BOB)
   =====================

Any maze structure can be defined in a single array variable or RAM data structure. If all of the maze is visible on-screen at the same time then the display file can be the data structure, and using SCREEN$ or ATTR to check for walls or other obstructions / objects / characters is the simplest solution. (ATTR is the only way to check for UDGs on screen from BASIC, as SCREEN$ doesn't recognize them.)

If the maze is not all visible or has more than two dimensions (eg. a house with several floors) then a string array DIMensioned to match the maze, using one character for each location within it, is the next simplest option. Or an arbitrary data structure above RAMTOP or a one-dimensional string could be used if the maze is of an irregular structure.

Whichever method is used to define the maze there should be no need for more than D+1 variables to check it, where D is the number of dimensions, requiring one coordinate variable for each dimension, and +1 is for the maze array/structure itself.

A reverse look-up array could also be useful for finding objects within the maze, defining the current coordinates and status for each object (ie. it tells you "where is this?" rather than "what is here?").

   =====================

I've been pondering on how to use DATA statements directly for displaying background graphics, thus avoiding the duplication of copying the data into an array. Another advantage is that the backgrounds can be different sizes without any wasted space padding. Here's a small demo.

* each background is defined in a single DATA statement;
* characters " " to UDG "U" are entered as normal;
* print controls AT to TAB (172 to 173) and INK to OVER (217 to 222) use their tokens;
* numeric parameters following print controls use characters "" to "" for numbers 0 to 31 (thus "a" to "z" = 1 to 26);
* each DATA statement is encoded once in situ (done in BASIC here, but in practice it needs a short m/code routine), after which it can be printed directly from the DATA line and thus saved within the program code;
* note that the graphics DATA themselves are never READ directly, only the preceding place markers are read in order to locate each graphics string;
* to print a background just set 'm' to the number to get its parameters from m() which are assigned to the m$ header from which reference point it can then be printed;
* the background DATA can be referenced & modified in situ with PEEK & POKE (m$(n) can be referenced in expressions, but (due to the way m$() has been defined) using "LET m$(n)=..." is a bit tricky; there is a way to get that to work, but I've forgotten it just now), thus saving the program could automatically save the current game state without having to save any variables (depending on the DATA structure).

So each background only occupies its own space (rather than doubling up in an array), plus 4 bytes for each place marker and a single 9 byte overhead for the m$ header (which I've put at the start of the UDGs but it could go anywhere, even in a DATA string within the program code).

This is probably slower than a more conventional approach, but for games with many screens could save a lot of space, especially if the game state extended across multiple screens.

BOBette.BAS [code]
   6 DATA "n"
   7 DATA "n","BRIGHT a PAPER g AT ca< TAB > PAPER e AT da< TAB >"
   8 DATA "n","PAPER  AT b TAB  TAB a AT c TAB a AT d TAB a TAB "
   9 DATA "n"

  10 DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN g(v)=v-256*FN h(v): DEF FN h(v)=INT (v/256)
  11 LET udg=FN p(23675): LET defadd=23563: LET datadd=23639
  20 LET num=2: DIM m(num+2,2): LET base=0

  29 REM assign a DEFADD entry for m$
  30 RESTORE 30: FOR a=udg TO udg+8: READ v: POKE a,v: NEXT a:
     DATA CODE "m",CODE "$",14,0,0,0,0,0,CODE ")"

  39 REM locate the DATA statements
  40 FOR m=6 TO num+7:
       RESTORE base+m: READ n$: LET datend=FN p(datadd):
       LET m(m-5,1)=datend-10: LET m(m-5,2)=datend+2
     NEXT m

  45 POKE defadd,FN g(udg): POKE defadd+1,FN h(udg)
  50 FOR m=1 TO num:

  59   REM calculate & store the DATA parameters
  60   LET m(m,1)=m(m+1,2):        REM  'm' start
  61   LET m(m,2)=m(m+2,1)-m(m,1): REM  'm' length
  69   RESTORE base+m+1: READ n$: LET datend=FN p(datadd)
  70   IF n$="y" THEN
         GO TO 78

  71   REM encode the DATA (if not already done)
  72   POKE datend-2,CODE "y":
       FOR a=m(m,1) TO m(m,1)+m(m,2)-1
  73     IF PEEK a>=217 THEN
           POKE a,PEEK a-201: POKE a+1,PEEK (a+1)-96: LET a=a+1
  74     IF PEEK a>=172 THEN
           POKE a,PEEK a-150: POKE a+1,PEEK (a+1)-96: POKE a+2,PEEK (a+2)-96: LET a=a+2
  76   NEXT a

  77   REM print each DATA statement as m$
  78   FOR a=1 TO 2:
         POKE udg+a*2+2,FN g(m(m,a)): POKE udg+a*2+3,FN h(m(m,a))
       NEXT a: PRINT m$
  79 NEXT m
  80 POKE defadd,0: POKE defadd+1,0
[/code]


**********************************************************************


** BOB the Screen Builder
   ======================

Here's my program to use that tokenising idea from "More on Storing Mazes" in a screen designer - "[url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/bob.zip]BOB the Screen Builder[/url]". There's two screens of instructions at the start explaining how to use it. The ZIP file contains TAP & Z80 program files plus BAS & ASM program listings. The machine code routines & workspace are located at the top of RAM to leave as much room as possible for screen definitions (there's about 30kb free). I've checked that all the options work on the Spectrum+ 128k as well.

The central idea is that each screen is encoded as a single DATA string with a mixture of printable characters and attribute tokens. The block of DATA statements can then be saved as a program segment with the OUT command and then MERGEd into their intended game program. BOB includes code which can be used in the game to print the screens directly from the DATA statements rather than having to READ them into separate variables. (It's not necessary to design a full screen each time, as the OUT command prompts for an area to be marked and only that part is encoded.)

At present OUT saves the screens as the encoded version with readable attribute tokens (INK, PAPER, etc.) rather than the decoded version with non-readable control codes (CHR$ 16, CHR$ 17, etc.). I'll change that to be the other way around, as the destination game would only need the decoded version. (It was just easier for testing to be able to read the print controls in the DATA lines.)

The routine at 9990-9998 (which is used to save the DATA lines), together with the DEF FNs in line 5, can be saved separately and MERGEd into other programs, as it's an independent program segment saving routine. It just saves selected ranges of lines from BASIC programs; it can also save the variables separately. DON'T Break out of it, as it creates its own VARS area above the current one and it needs to go through its exit routine to restore things; just enter 0,0 for the range to quit. It uses the display file to store some variables as well, which is the reason for the speckly line which appears at the top of the screen while its running.


**********************************************************************


** Overlaid Square Grid
   ====================

A SCREEN$ can be loaded and overlaid with a BRIGHT on/off grid in a few lines of BASIC, but here's a quicker version. It deals with the problem of black space (ie. normal & BRIGHT black look the same) in a satisfyingly spectacular fashion by flipping the colours on alternate black squares.

GridSq.ASM [code]
SQUARES:ld   c,12
        ld   hl,22528
NEXTROW:ld   b,16
NEXTRES:res  6,(hl)
        inc  hl
        ld   a,(hl)
        cpl
        res  7,a
        ld   (hl),a
        set  6,(hl)
        inc  hl
        djnz NEXTRES
        ld   b,16
NEXTSET:ld   a,(hl)
        cpl
        res  7,a
        ld   (hl),a
        set  6,(hl)
        inc  hl
        res  6,(hl)
        inc  hl
        djnz NEXTSET
NEXTBC: dec  c
        jr   nz,NEXTROW
        ret
[/code]
[center][img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/forestofdoom_bwgrid.gif[/img] [img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/forestofdoom_swgrid.gif[/img][/center]

   ====================

Here's an even quicker method but without the black-on-black handling.

Cheque.ASM [code]
CHEQUE: ld   a,%01111000
        ld   hl,22528
        ld   c,24
        ld   d,%00110000
LOOP0:  ld   b,32
LOOP1:  ld   (hl),a
        xor  d
        inc  hl
        djnz LOOP1
        xor  d
        dec  c
        jr   nz,LOOP0
        ret
[/code]


**********************************************************************


** Increasing the number of UDGs
   =============================
Here's a short routine which will print out 256 UDGs, presuming that there's 2kb of UDGs defined from wherever (UDG) is pointing.

UDG256.ASM [code]
        org  23296
UDG256: call $0d6b           ;CLS
        ld   a,$FE
        call $1601           ;CHAN_OPEN
        ld   a,0
        call $0b03           ;PO_FETCH
UDGLOOP:push af
        call $0b58           ;PO_T&UDG+6
        pop  af
        inc  a
        or   a
        jr   nz,UDGLOOP
        ret
[/code]

Normally PRINT_OUT deducts 144 from UDG codes so they are numbered 0-21 when it comes to print them. The above example avoids that bit of the standard ROM routine so a full 256 could be defined.

For practical use the channel 'S' output address in CHANS would have to point to a precursor routine which recognised the expanded UDG characters, and having 256 wouldn't really be practical as there would be no character codes left for anything else; using the codes from 144-255 would be reasonable, allowing 112 UDGs to be referenced at any one time.

There would be a slight problem if you wanted to type in the expanded UDG characters as part of a BASIC listing, as without altering the input routine as well the Spectrum would still only recognise 21 as being typed in. If UDG 'V' was wanted then the RND token would have to be typed; for UDG 'W' the INKEY$ token, and so on. However, an input convention could be devised, such as preceding expanded UDG codes with a '\' followed by a 3-digit number or something like that, which would be converted by the precursor routine to be the correct CHR$ code for printing.

   =============================

I've written a short routine for handling 256 UDGs, using the "\" syntax which I suggested earlier; expanded UDG codes and regular characters can be mixed, as shown in the example. Only the first 21 UDGs can be typed in via graphics mode; to get the full set the "\" syntax needs to be used.

Note that it checks for control characters and restores the alternative 'Sout' address, as PRINT_OUT resets the channel 'S' output address after printing control characters. Be aware that CLS or CLEAR will also reset it, so it would have to be re-POKEd after either of those commands.

UDG256d.BAS [code]
   3 DEF FN p(a)=PEEK a+256*PEEK (a+1):
     DEF FN g(v)=v-FN h(v)*256: DEF FN h(v)=INT (v/256)
  10 CLEAR VAL "65536-100-256*8-1": LET UDG=VAL "65536-256*8":
     POKE 23675,FN g(UDG): POKE 23676,FN h(UDG)
  11 LET CHANS=FN p(23631): REM Start of CHANS area
  12 LET Sout=CHANS+5     : REM Address of channel 'S' output address
  13 LET UDG256=UDG-100: POKE Sout,FN g(UDG256): POKE Sout+1,FN h(UDG256)
  21 PRINT "a\000 STOP \255z":
     REM prints "a@ STOP #z" where: @=UDG000; STOP=token; and #=UDG255
[/code]

UDG256d.ASM [code]
        org  65536-100-2048
UDG256: cp   "\"
        jr   z,EXPAND
        cp   " "
        jp   nc,$09F4        ;PRINT_OUT printables
        call nz,$09F4        ;PRINT_OUT controls
        ld   de,UDG256       ;printing controls will reset (CURCHL) to $09F4
        call $0A80           ;PO_CHANGE
        ret

EXPAND: call $0B03           ;PO_FETCH
        exx
        xor  a
        cp   b
        inc  a
        inc  a
        jr   c,DOCODE
        cp   c               ;check for at least 3 following characters
        jp   nc,$1E08        ;"E - Out of DATA"

DOCODE: inc  a               ;A =3
        ld   h,0
        ex   af,af'
DOLOOP: ld   a,(de)
        inc  de
        dec  bc
        and  %00001111
        add  a,h
        ld   h,a
        ex   af,af'
        dec  a
        jr   z,NOLOOP

        ex   af,af'
        sla  a
        ld   l,a             ;L =A*2
        sla  a
        sla  a               ;A =A*8
        add  a,l             ;A =A*8 + A*2 = A*10
        ld   h,a
        jr   DOLOOP

NOLOOP: ld   a,h             ;A =UDG code $00-$FF
        exx
        call $0b58           ;PO_T&UDG+6
        jp   $0ADC           ;PO_STORE
[/code]


**********************************************************************


** Extracting Pictures from GAC & PAWS
   ===================================

The pictures from GAC and PAWS adventures can be displayed by using UnGAC or UnPAWS to get the address of each picture definition and then calling the relevant routine with each address in turn. The following routine displays the pictures for GAC's Winter Wonderland.

Load the game, assemble the routine in the printer buffer, and then set a break at BREAKAT. When the break occurs redirect PC to DUMPIC and remove the break at BREAKAT. The routine will stop after each picture has displayed so that it can be saved; press Caps+N to continue.

GACpic.ASM [code]
        org  23296

BREAKAT:equ  $7249           ;break point address in GAC

DUMPIC:
        push bc
        push de
        push hl
        push af
        push ix
        ld   ix,GRAPHICS
NEXTPIC:
        call $0daf           ;CL_ALL
        ld   e,(ix+00)
        ld   d,(ix+01)
        ld   a,$ff
        cp   d
        jp   z,FINISHED
        push ix
        pop  de
        push ix
        call $6c66           ;display location picture
DONEPIC:
        call $028e           ;KEY_SCAN
        or   a
        ld   hl,$2708
        sbc  hl,de           ;E=$08="n", D=$27=Caps
        jr   nz,DONEPIC      ;if not Caps+N then loop
        pop  ix
        inc  ix
        inc  ix
        jr   NEXTPIC
FINISHED:
        pop  ix
        pop  af
        pop  hl
        pop  de
        pop  bc
        jp   BREAKAT
GRAPHICS:
        defw $0003 ; $e96d
        defw $0004 ; $dd30
        defw $000C ; $df78
        defw $000F ; $e35b
        defw $0010 ; $e4aa
        defw $0011 ; $e577
        defw $0012 ; $e60e
        defw $FFFF
[/code]

There's more than one version of GAC and they may not all have the display routine in the same place. For those games which do, it's just necessary to replace the GRAPHICS table at the end of the routine.

Something similar could be done for PAWS if one felt so inclined.

   ===================================

The status panel display in GAC can be stopped by changing:
at $666F: CD F9 66 -to- C3 95 66
at $66A4: 06 0E -to- 18 28
although it still occupies that part of the screen, just now it's blank.


**********************************************************************


** Make 8x UDGs from Attribute Blocks
   ==================================

Here's a BASIC version of the joefish & na_th_an ideas, altered to generate the character pattern in the attributes, side-by-side for comparison. Although na_th_an's version is slower in BASIC, I'd expect a machine code version to be the quicker of the two as it's a simpler procedure and the divisions/2 could be replaced with bit shifts.

Line 10 sets up the array used by the joefish version.
Line 20 is the joefish version.
Line 30 is the na_th_an version.

pat = current attributes
pin = current ink
pap = current paper
pon = reverse attributes ink<=>paper
u$ = "a"-"u"

UDGsBig.BAS [code]
   3 LET udg=FN p(23675):
     DEF FN p(a)=PEEK a+256*PEEK (a+1): DEF FN a(a)=a-INT (a/8)*8
   5 LET pat=PEEK 23693: LET pin=FN a(pat):
     LET pap=FN a(INT (pat/8)): LET pon=pat-7*pap+7*pin
  10 DIM b$(16,4): LET b$(1)=CHR$ pat+CHR$ pat+CHR$ pat+CHR$ pat:
     FOR b=2 TO 16: LET c$=b$(b-1): LET o=NOT PI:
       FOR c=4 TO 1 STEP -1:
         LET d=(c$(c)=CHR$ pon)+(c=4)+o: LET o=(d=2):
         LET c$(c)=CHR$ (pat-((pat-pon) AND d=1)):
       NEXT c: LET b$(b)=c$:
     NEXT b
  20 INPUT u$: CLS : LET u=udg+(CODE u$-97)*8:
     FOR b=0 TO 7:
       LET p=PEEK (u+b): LET h=INT (p/16): LET l=p-h*16:
       LET c$=b$(h+1)+b$(l+1): LET d=22528+b*32:
       FOR c=0 TO 7:
         POKE d+c,CODE c$(c+1):
       NEXT c:
     NEXT b
  30 FOR b=0 TO 7:
       LET p=PEEK (u+b): LET d=22544+b*32:
       FOR c=0 TO 7:
         POKE d+7-c,pat-((pat-pon) AND INT (p/2)<>p/2):
         LET p=INT (p/2):
       NEXT c:
     NEXT b
  99 GO TO 20
[/code]


**********************************************************************


** Drawing UDGs at Different Scales
   ================================

I quite liked that block graphics idea, but I had to draw a little quarter-UDG diagram first to clarify how it would work.
[code]
              01 | 01
       0101 = 01 | 10 = 1001        5 | 9
              -------              -------
              10 | 01               2 | 13
       0010 = 00 | 11 = 1101
[/code]
The numbers were very suggestive, and when adding 128 to them always gave the correct block graphic the problem it was solv-ed.

Here's three ways of evaluating that 4x4 block graphics method. The first two work backwards (right to left), as it seemed that working forwards would involve more calculations - but then I thought of the third option, which didn't.

UDGs4x4.BAS [code]
  10 LET u=USR "g": DIM q(4,4)

  20 FOR b=0 TO 7 STEP 2
  30   LET b1=PEEK (u+b): LET b2=PEEK (u+b+1): LET g$=""
  35   FOR c=1 TO 4
  40     LET p1=INT (b1/4): LET q1=b1-p1*4:
         LET p2=INT (b2/4): LET q2=b2-p2*4
  45     LET g$=CHR$ (128+q2*4+q1)+g$:
         LET b1=p1: LET b2=p2
  55   NEXT c: PRINT g$
  70 NEXT b

 120 FOR b=0 TO 7 STEP 2
 130   LET b1=PEEK (u+b): LET b2=PEEK (u+b+1): PRINT ''TAB 3;
 135   FOR c=1 TO 4
 140     LET p1=INT (b1/4): LET q1=b1-p1*4:
         LET p2=INT (b2/4): LET q2=b2-p2*4
 145     PRINT CHR$ (128+q2*4+q1);CHR$ 8;CHR$ 8;:
         LET b1=p1: LET b2=p2
 155   NEXT c
 170 NEXT b

 220 PRINT '''': FOR b=0 TO 6 STEP 2
 230   LET b1=PEEK (u+b): LET b2=PEEK (u+b+1)
 235   LET d=64: FOR c=1 TO 4
 240     LET p1=INT (b1/d): LET  b1=b1-p1*d:
         LET p2=INT (b2/d): LET  b2=b2-p2*d
 245     PRINT CHR$ (128+p2*4+p1);:
         LET d=d/4
 255   NEXT c: PRINT
 270 NEXT b
[/code]

   ================================

Here's a demo of a much quicker BASIC method for preparing the 2x2 UDGs, by pre-loading the calculations so that they don't have to be done each time. As there are only 16 possible values it's not much of a space overhead. It uses DF_CC for print positioning.

UDGs2x2.BAS [code]
   5 DIM d(16): FOR a=1 TO 16: READ d(a): NEXT a:
     DATA 0,3,12,15,48,51,60,63,192,195,204,207,240,243,252,255
 130 LET r=0: LET c=0
 140 FOR u=USR "a" TO USR "u" STEP 8:
       PRINT AT r,c;: LET db=PEEK 23684+256*PEEK 23685: LET df=db
 150   FOR b=0 TO 7:
         LET p=PEEK (u+b): LET l4=INT (p/16): LET r4=p-l4*16
 155     POKE df,d(l4+1): POKE df+1,d(r4+1)
 156     POKE df+256,d(l4+1): POKE df+257,d(r4+1):
         LET df=df+512: IF b=3 THEN LET df=db+32
 157   NEXT b:
       LET c=c+2 AND c<30: LET r=r+2 AND r<20
 160 NEXT u
[/code]


**********************************************************************


** Scrolling a map
   ===============

If the map is a 2D square array of M*M characters and the displayed area is D*D characters, where D<M; then just keep x,y co-ords for the top left-hand corner, where x,y are each constrained within a minimum of 1,1 and a maximum of M-D+1,M-D+1. Then the area to be displayed can be specified as:

FOR r=y TO y+D-1: PRINT m$(r,x TO x+D-1): NEXT r

If the areas are not square then the x,y constraints would be 1,1 to Mx-Dx+1,My-Dy+1 and the display loop modified accordingly. An AT position would need to be calculated as well if the map window wasn't in the top left-hand corner of the screen.

   ===============

Here's a BASIC character-based map scroller. The cursor moves about within the window and the window scrolls by one row or column when it moves off an open edge. I haven't shown the UDGs DATA statements READ in line 10. Change the six constants in line 10 to suit the required dimensions and position.

The map & UDG data are in the Small Adventure Builder archive on my [url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/pourri.htm]Spot's Pourri[/url] page; files "M" and "AO.UDG".

[code]
yt,xt	= top left display row,col for map window (4,10)
ym,xm	= map dimensions (15,12)
yw,xw	= map window dimensions (11,8)
yd,xd	= top left map co-ords for map window
yp,xp	= player co-ords on map
c		= direction (8,9,10,11)
yk,xk	= player co-ords direction modifiers
ys,xs	= player co-ords on screen
yy,xx	= bottom right map co-ords for map window
yz,xz	= maximum top left map co-ords for map window
[/code]

MapScroller.BAS [code]
  10 LET xm=12: LET ym=15: LET xw=8: LET yw=11: LET xt=10: LET yt=4:
     DIM m$(ym,xm): FOR r=1 TO ym: READ m$(r): NEXT r
  12 LET xd=1: LET yd=1: LET xp=8: LET yp=8: GO SUB 90:
     PRINT AT yp+yt-1,xp+xt-1; INVERSE 1;m$(yp,xp)
  20 PAUSE 0: LET c=CODE INKEY$
  21 LET xk=(c=9)-(c=8): LET xk=xk AND (xp+xk>=1 AND xp+xk<=xm)
  22 LET yk=(c=10)-(c=11): LET yk=yk AND (yp+yk>=1 AND yp+yk<=ym)
  23 LET xp=xp+xk: LET yp=yp+yk:
     LET xx=xd+xw-1: LET yy=yd+yw-1:
     LET xz=xm-xw+1: LET yz=ym-yw+1
  30 IF xp<xd OR xp>xx OR yp<yd OR yp>yy THEN
       LET xd=xd-(xp<xd)+(xp>xx): LET yd=yd-(yp<yd)+(yp>yy):
       LET xd=xd+NOT xd: LET yd=yd+NOT yd:
       LET xd=xd-(xd-xz AND xp>xd+xx):
       LET yd=yd-(yd-yz AND yp>yd+yy): GO SUB 90
  50 LET xs=xp+xt-xd: LET ys=yp+yt-yd:
     PRINT AT ys-yk,xs-xk;m$(yp-yk,xp-xk);AT ys,xs; INVERSE 1;m$(yp,xp):
     GO TO 20
  90 FOR r=yd TO yd+yw-1:
       PRINT AT r-yd+yt,xt;m$(r,xd TO xd+xw-1):
     NEXT r: RETURN
[/code]

   ===============

In SAB.ZIP, "M" is in SAD.TAP and "AO.UDG" is in SAB.TAP, SAR.TAP, SAT.TAP .

   ===============

File "M" is a character array, so load it with:
LOAD "M" DATA m$()

File "AO.UDG" is a UDG character set, so load it with:
LOAD "AO.UDG" CODE udg,168
where 'udg' is the address of the UDG area, calculated by:
PEEK 23675+256*PEEK 23676
which is set to 65368 at startup. It was saved to load at that address by default, so you could just use:
LOAD "AO.UDG" CODE


**********************************************************************


** scroll one column
   =================
Called with "RANDOMIZE option*32+column+USR address". Option=1 to shift attributes and 0 don't. Remove the line marked "<<" if you don't want to erase the old column.

Scroll1c.ASM [code]
SL1:    call $2da2           ;FP-TO-BC
        push bc
        ld   ix,16384
        ld   h,192
        ld   l,0
        call SHIFT           ;shift display column
        call $2d2b           ;STACK-BC
        pop  bc
        bit  5,c             ;check attributes option
        ret  z
        ld   ix,22528
        ld   h,24
        ld   l,1
        call SHIFT           ;shift attributes column
        ret

SHIFT:  res  5,c
        ld   a,c
        add  ix,bc
        ld   de,32
        ld   b,h
LOOP:   ld   c,(ix+00)
        bit  0,l             ;display or attributes?
        jr   nz,KEEP         ;don't reset old attrib column
        ld   (ix+00),0       ;<<
KEEP:   or   a               ;shifting column 00?
        jr   z,COL0
        ld   (ix-01),c       ;no
        jr   NEXT
COL0:   ld   (ix+31),c       ;yes
NEXT:   add  ix,de
        djnz LOOP
        ret
[/code]

   =================

I've expanded upon the initial premise, and written a routine to scroll a character column of a specified height & position with options both to include attributes and preserve the original column.

The simple version copies in fixed steps, so the bytes are not moved in vertical column sequence, due to the way the display file is organised. This doesn't matter if the whole column is being moved but it becomes significant if only part of the column is being moved; only column lengths of 8, 16 and 24 would work properly with the simple version.

Here's a complicated version. As well as being able to specify the column number & attribute copy options it also allows the top & bottom of the column to be specified and whether or not to preserve the old column. Call it with:

[code]
     DEF FN l(c,t,b,a,p)=USR address
     ...
     LET bc=FN l(column,top,bottom,attributes,preserve)
[/code]

The first three parameters are column(0-31) or row(0-23) numbers; the last two are 1/0 for yes/no. It quits with "Q Parameter error" if the print coordinates fall outside the display file. For simplicity it calculates the old & new addresses independently, with separate calls to the LOCATE routines (rather than calculating 'new' relative to 'old'), which is less efficient but easier to follow. The LOCATE routines are from David Webb's "Advanced Spectrum Machine Language" book.

The BASIC demo routine.

Scroll2b.BAS [code]
  11 DEF FN l(c,t,b,a,p)=USR 23296:
     DEF FN r(a,z)=a+INT (RND*(z-a+1))
  12 LET p=0: LET c=97: DIM r$(96): DIM b$(96):
     FOR r=1 TO 94 STEP 3:
       LET r$(r TO r+2)=CHR$ 17+CHR$ (p+1)+CHR$ c:
       LET b$(r TO r+2)=r$(r TO r+1)+" ":
       LET p=p+1 AND p<3: LET c=c+1:
     NEXT r
  13 CLS : LET top=FN r(0,23): LET bot=FN r(top,23):
     FOR r=0 TO 23:
       LET win=(r>=top AND r<=bot): LET row=r-(22 AND r>=22):
       PRINT #(2 AND r<=21);AT row,0;r$ AND win;b$ AND NOT win;
       PAPER 8;AT row,31;CHR$ (r+65):
     NEXT r
  14 LET att=FN r(0,1): LET pre=FN r(0,1):
     PRINT AT 0,0;top'bot'att'pre:
     FOR c=31 TO 0 STEP -1: LET bc=FN l(c,top,bot,att,pre): NEXT c:
     PAUSE 0: GO TO 13
[/code]

Scroll2c.ASM [code]
SL2:    ld   ix,(23563)      ;IX=(DEFADD)

NEXTROW:ld   b,(ix+12)
        ld   c,(ix+04)
        call LOCATED         ;HL=old col address
        call LOCATEA         ;BC=old att address
        push hl
        ld   a,(bc)          ;A =old attributes
        push af
        bit  0,(ix+36)       ;preserve old col?
        jr   nz,NORESET
        ld   a,(23693)       ;ATTR_P
        ld   (bc),a          ;reset attributes
NORESET:ld   b,(ix+12)
        ld   a,(ix+04)
        dec  a
        and  31
        ld   c,a             ;C =new col#
        call LOCATED         ;HL=new col address
        call LOCATEA         ;BC=new att address
        pop  af              ;A =old attributes
        bit  0,(ix+28)       ;copy attributes?
        jr   z,NOATTR
        ld   (bc),a          ;copy attributes
NOATTR: pop  de              ;DE=old col address
        ex   de,hl           ;HL=old col, DE=new col
        ld   b,8
MOVE1C: ld   a,(hl)
        ld   (de),a
        bit  0,(ix+36)       ;preserve old col?
        jr   nz,MOVE2C
        ld   (hl),0          ;reset column
MOVE2C: inc  h
        inc  d
        djnz MOVE1C
        inc  (ix+12)
        ld   a,(ix+20)
        cp   (ix+12)
        jr   nc,NEXTROW
        ret

LOCATED:ld   a,b
        and    %11111000     ;$F8
        add  a,%01000000     ;$40
        ld   h,a
        ld   a,b
        and    %00000111     ;$07
        rrca
        rrca
        rrca
        add  a,c
        ld   l,a
        ld   a,h
        cp   $40
        jr   c,ERRORQ
        cp   $58
        ret  c
ERRORQ: rst  $08             ;ERROR_1
        defb $19             ;Q Parameter error

LOCATEA:ld   a,h
        rrca
        rrca
        rrca
        and    %00000011     ;$03
        or     %01011000     ;$58
        ld   b,a
        ld   c,l
        ret
[/code]


**********************************************************************


** Plot coordinates for Spectrum character set
   ===========================================

This lists the relative PLOT coordinates to construct each character:

PlotChar.BAS [code]
10 DIM c$(8): DIM p(8,8)
15 FOR c=32 TO 127
20   PRINT AT 21,0;CHR$ c
30   FOR y=0 TO 7: FOR x=0 TO 7
40     LET p=POINT (x,y): LET c$(x+1)=CHR$ 143 AND p: LET p(8-y,x+1)=p
50   NEXT x: PRINT AT 7-y,0;c$: NEXT y
55   PRINT AT 21,0;" ";AT 8,0,,,,,,AT 8,0;
60   FOR y=0 TO 7: FOR x=0 TO 7
70     IF p(8-y,x+1) THEN PLOT x,y: PRINT x;",";y;" ";
80   NEXT x: NEXT y
90 INPUT "Press ENTER ";c$: NEXT c
[/code]

It prints each character, calculates the PLOT points, then erases and reconstructs it in both regular & economy sizes using those points (just to show that it's working correctly). To get BIN numbers just use "0" & "1" in c$ instead of space & inverse space.


**********************************************************************


** graphics extractor
   ==================
ChrPoint
The BASIC is 8k and the assembler 1k. I've put the files [url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/chrpoint.zip]here[/url]. The relevant routine in the assembler listing is chkbit. The character defs are put in t$().

The program can also be used as a screen compressor. There's a separate section from 9500 which creates the files for that from those created by the main program, although there's some manual splicing involved after that as I didn't get around to fully automating the procedure.


**********************************************************************


** simple horizontal scroll
   ========================

The string being printed should include its own "clean-up" characters, thus if moving rightwards a string of length l by step s then the string should start with s spaces (or end with s spaces if moving leftwards), then printing the string will automatically clean up behind it. Then it's just necessary to deal with the situation of there being less than (s+l) print positions remaining on the line.

If z=s+l and c=column, then this line of code will move the string rightwards, cleaning up as it goes and wrapping around when it gets to the right-hand edge:

 LET p=32-c: PRINT AT 0,c;a$( TO z-(z-p AND p<z));: LET c=c+s AND c<32-s

and repeat ...


**********************************************************************


multi-scroll m/c subroutine for BASIC
=====================================
ScrollE
Here's a scrolling routine that will manage multiple left/right scrollings for any (sensible) line width or step; (ie. where width is <=32 and step is < width). You can have several scrolling lines whizzing backwards & forwards all over the place at the same time if you're minded. Attributes and positions are controlled with PRINT; speed is controlled with PAUSE.

It's 91 bytes long. Although multiple calls can be embedded within one PRINT, another use would be to have separate calls at points in the main program loop, so each time that section was traversed any scrolly fields within it would scroll by one step. As I've written this to be controlled from BASIC it's not been done as an interrupt routine, so if the program stops at an INPUT for example then all scrolling will stop as well, but it would be easy enough to change it.

It's important to note that ScrollE [b]alters its source string each time it's called[/b], as that records its "scrolly state" for the next time. If, for example, several different calls use the same variable, then each call would inherit the "scrolly state" from the previous one and their "scrolly speed" would be the net sum of the individual speeds. This can have its uses, of course. This is also why using a string expression (as opposed to a variable or literal) doesn't do anything, as it is re-evaluated to "scrolly state 0" every time and nothing happens (unless another scrolle call references one of the variables used in the expression, in which case some quite complicated results can be achieved). A simple slice expression will scroll as the ROM addresses the original variable location rather than creating a copy in its workspace.

ScrollE.ASM [code]
;Syntax: PRINT [...items...;] USR scrolle,string,tstep; [...items...]
;scrolle parameters must be separated by "," and end with ";"
;Multiple scrolle clauses may be included in a PRINT statement,
;  along with any other PRINT items & controls.
;string may be any string variable or literal containing the text to be scrolled;
;  it should be the length of the required scrolling area;
;  where PRINT column+length <=32.
;tstep may be any numeric variable, literal or expression
;tstep = scroll step (must be less than the length of the string)
;      & direction: +=right, -=left
;eg: PRINT PAPER 8; INK 8; AT 10,6;USR scrolle,"scrolling R 20 chars",+1;
;          AT 11,6;USR scrolle,"scrolling L 20 chars",-1;

;NOTE that strings are scrolled in situ, so the value in the string variable
;or literal will have rotated by step bytes after each scrolle call.

scrolle:ld   hl,14
        add  hl,sp
        ld   sp,hl           ;undo USR
getarg: rst  $20
        call $24fb           ;SCANNING (stack parameters)
        rst  $18
        cp   ","
        jr   z,getarg

        call $2dd5           ;FP_TO_A   get step in A
        ex   af,af'          ;save step & flags
        call $2bf1           ;STK_FETCH get string parameters in AEDCB
        ex   af,af'          ;A =step
        jr   nz,SAVEARG      ;FP_TO_A resets Zero if A is -ve (moving left)
        ld   l,a             ;L =step
        ld   a,c             ;A =length
        sub  l               ;A =length - step =split (moving right)
SAVEARG:ld   h,0
        ld   l,a             ;HL=split
        push hl           ;>1
        push de           ;>2;DE=location
        ld   a,c
        sub  l
        push af           ;>3;A = length - split
        push hl           ;>4
        push de           ;>5
        call $203c           ;PR-STRING

        pop  hl           ;<5;HL=location
        ld   de,btext
        pop  bc           ;<4;BC=split
        ldir

        pop  af
        ld   c,a          ;<3;BC= length - split
        pop  de           ;<2;DE=location
        ldir

        ld   hl,btext
        pop  bc           ;<1;BC=split
        ldir

        ret

btext:  defs 32
[/code]


**********************************************************************


** Drawing Ellipses
   ================

Here's a comparison of a modified version of one of my BASIC routines - which uses the conventional square root formula for plotting an ellipse - compared to the SIN/COS approximation alternative. DRAW can't be used to draw an ellipse, as the DRAW curves are comprised of circular arcs, which an ellipse is not - but it can be used to join the dots on the border of an ellipse calculated by some other method. So I changed my old BASIC version to use DRAW anyway. Hah! It's quicker and more accurate than the SIN/COS method, as it's based on the standard formula for drawing a normal (ie. not tilted) ellipse - although still slow, of course.

In the line 53 subroutine below, the PLOT could be dispensed with and the ellipse drawn entirely with DRAW (a single starting PLOT would be needed outside the loop), which would speed it up a bit, but the coordinate calculation would need changing.

Ellipse.BAS [code]
   3 DEF FN t()=(65536*PEEK 23674+256*PEEK 23673+PEEK 23672)/50:
     DEF FN m(a,b)=a-(a-b AND b>a)
  10 LET x=60: REM centre x
  20 LET y=86: REM centre y
  30 LET h=30: REM 1/2 x axis
  40 LET v=60: REM 1/2 y axis
  50 LET h2=h*h: GO SUB 100: LET t1=t:
     LET pn=+1: GO SUB 53:
     LET pn=-1: GO SUB 53:
     GO SUB 100: LET t2=t: PRINT AT 21,0;t2-t1: GO TO 55

  53 LET p=x-h: LET ip=y:
     FOR i=x-h TO x+h:
       LET ix=i-x: LET iy=pn*(v*SQR (1-ix*ix/h2)):
       PLOT i,y+iy: DRAW p-i,ip-(y+iy):
       LET p=i: LET ip=y+iy:
     NEXT i: RETURN

  55 LET x=x+h*3
  60 GO SUB 100: LET t3=t:
     FOR i=0 TO 6.28 STEP .02: PLOT x+h*SIN i,y+v*COS i: NEXT i:
     GO SUB 100: LET t4=t: PRINT AT 21,16;t4-t3
  99 STOP

 100 LET t=FN m(FN t(),FN t()): RETURN
[/code]

[center][img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/sqr_sin+cos_comparison.gif[/img][/center]

   ================

This is the change to line 53 to dispense with the PLOT within the loop.

[code]
  53 LET p=x-h: LET ip=0: PLOT p,y:
     FOR i=x-h+1 TO x+h:
       LET ix=i-x: LET iy=pn*INT ((v*SQR (1-ix*ix/h2))+.5):
       DRAW i-p,iy-ip: LET p=i: LET ip=iy:
     NEXT i: RETURN
[/code]

It turns out that this method is only quicker for prolate ellipses (because line 53 is stepping along the X-axis and there are more steps to do as the ellipse becomes wider); if the ellipse is oblate then the SIN/COS loop is quicker, although slightly scruffier. The latter could be speeded up by doing less PLOTs and using DRAW to "join the dots", as is being done in line 53 (although in that case the dots have become imaginary since I disposed of the PLOTs).

   ================

Another option is the Bresenham approximation algorithm, which is quicker than using the standard square root formula, and is more accurate than the SIN/COS method, but it is rather long and not very quick. Here's the most compact version I could come up with when I was experimenting with ellipses a few months ago. (Well, it would be a lot more compact if I hadn't used such long variable names, but that was so I could easily check my code against the original article.)

Bresenham.BAS [code]
  11 REM BRESENHAM'S ELLIPSE DRAWING ALGORITHM
  12 GO TO 1010
 900 PLOT cx+x,cy+y: PLOT cx-x,cy+y: PLOT cx+x,cy-y: PLOT cx-x,cy-y: RETURN
1010 REM INPUT "Enter centre coordinates for:"'"x: ";cx,"y: ";cy'"Enter 1/2 Axis for:"'"x: ";XRadius,"y: ";YRadius
1011 LET cx=50: LET cy=46: LET XRadius=30: LET YRadius=15

1020 LET TwoXSquare=2*XRadius*XRadius
1030 LET TwoYSquare=2*YRadius*YRadius

1040 LET Y=0: LET X=XRadius
1060 LET XChange=YRadius*YRadius*(1-2*XRadius)
1070 LET YChange=XRadius*XRadius
1080 LET EllipseError=0: LET StoppingY=0
1090 LET StoppingX=TwoYSquare*XRadius
2000 IF StoppingX<StoppingY THEN GO TO 3000
2010 GO SUB 900
2020 LET Y=Y+1
2022 LET StoppingY=StoppingY+TwoXSquare
2024 LET EllipseError=EllipseError+YChange
2026 LET YChange=YChange+TwoXSquare
2030 IF 2*EllipseError+XChange>0 THEN LET X=X-1:
       LET StoppingX=StoppingX-TwoYSquare:
       LET EllipseError=EllipseError+XChange:
       LET XChange=XChange+TwoYSquare
2040 GO TO 2000

3000 LET X=0: LET Y=YRadius
3020 LET YChange=XRadius*XRadius*(1-2*YRadius)
3010 LET XChange=YRadius*YRadius
3030 LET EllipseError=0: LET StoppingX=0
3050 LET StoppingY=TwoXSquare*YRadius
4000 IF StoppingX>StoppingY THEN STOP
4010 GO SUB 900
4020 LET X=X+1
4030 LET StoppingX=StoppingX+TwoYSquare
4040 LET EllipseError=EllipseError+XChange
4050 LET XChange=XChange+TwoYSquare
4060 IF 2*EllipseError+YChange>0 THEN LET Y=Y-1:
       LET StoppingY=StoppingY-TwoXSquare:
       LET EllipseError=EllipseError+YChange:
       LET YChange=YChange+TwoXSquare
4070 GO TO 4000
[/code]

   ================

One centre point (as has everything, I think) but two foci, equidistant from the centre point on the major axis. The sum of the distances from any point on the ellipse to either focus is always the same.

https://www.uwgb.edu/dutchs/MATHALGO/Ellipses.HTM


**********************************************************************


** Vertical Downward Character WrapScroll
   ======================================

CharScroll.ASM [code]
CHARSCROLL:                  ;wrapscroll whole screen by one PRINT row
        di
        LD   hl,$57e0        ;HL=start of last pixline on screen
        ld   a,8
LOOP8:
        push hl
        ex   af,af'
        push hl              ;HL=start of lowest pixline in this LOOP23 pass
        ld   de,SAVE32
        ld   bc,32
        ldir                 ;save the pixline

        ;scroll down corresponding pixrows from previous 23 lines
        ld   a,23
        pop  de              ;DE=start of lowest pixline in this LOOP23 pass
        and  a
LOOP23:
        push af              ;move to corresponding pixline on previous line
        ld   bc,32
        sbc  hl,bc           ;restore value before LDIR
        ld   a,l             ;L =rrrccccc,rrr=charrow 0-7,ccccc=charcol 0-31
        sub  %00100000
        ld   l,a
        jp   nc,SAMESEGMENT
        ld   a,h             ;H =010ssppp,ss=segment 0-2,ppp=pixline 0-7
        sub  %00001000
        ld   h,a
SAMESEGMENT:
        push hl              ;old source ...
        ldir                 ;move down pixline
        pop  de              ;... becomes new destination
        pop  af
        dec  a
        jp   nz,LOOP23

        ld   bc,32
        sbc  hl,bc
        ex   de,hl           ;move DE to corresponding top pixline
        ld   hl,SAVE32       ;wrap from bottom
        ldir

        pop  hl
        dec  h               ;HL=start of previous pixline
        ex   af,af'
        dec  a
        jp   nz,LOOP8

        ei
        ret

SAVE32: equ  23698           ;MEMBOT 30 bytes + unused 2 bytes
[/code]


**********************************************************************


** UDG Collision Detection
   =======================

Download the "Elementary Graphics" article by Toni Baker from the ZX Computing section at TTFn. Part 3 includes an extended SCREEN$ function which works with UDGs and block graphics. See the included TAP file also.

TTFn is at http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm


**********************************************************************


** Sprites in +3 BASIC
   ===================

You're going to have to get to grips with some machine code if you want to do anything useful with sprites, unless you use a tool which provides sprite extensions to Basic, such as Ocean's Laser Basic or HiSoft's Colt - although I'm not sure what's available that's definitely +3 compatible.

Sprite handling was covered in several articles in the Sinclair User "How The Hell!" series. The pages for these can be viewed from the magazine archives section at WoS; here's the pages to look up:

SU#76 (Jul'88) pages 88-89 - a short article at the bottom, although the "Table 1" assembler listing referred to wasn't printed, so I wouldn't bother with that one. I've just mentioned it for completeness.

SU#83 (Feb'89) pages 78-79 - a 2-page article by Steve Marsden of Technician Ted fame; a good intro and a heavily commented assembler listing.

SU#91 (Oct'89) pages 78-79 +
SU#92 (Nov'89) pages 88-90 - a 2-part series by Andrew Hewson, again including a heavily commented assembler listing.

Also, if you go to the Your Sinclair section of the [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm]TTFn[/url] type-ins section at my website and download the Star Tip 7 program, you'll get a sprite handling routine written by Raffaelle Cecco, which can't be bad. There's also several sprite designer programs on that page and in other sections of the site.


**********************************************************************


** GUI in Sinclair BASIC
   =====================

Try these at TTFn, which provide some ideas and example programs:
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/streams.zip]Streams & Channels[/url] (Toni Baker, ZX Computing)
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/sincuser/su8412.zip]Window Scrolls[/url] (Peter Cooke, Sinclair User)
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys910506.zip]WindowX[/url] (Paul Dias, Your Sinclair)
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8811pp.zip]Windas[/url] (Joseph Lynass, Your Sinclair)

There was also the Windos program by Ian Briscoe in Your Computer Dec'84 & Jan'85 (starting on pages 101 and 131 respectively). A couple of commercial GUIs on the Spectrum were Max Desktop (Advanced Memory Systems, available on WoS) and Vision (Individual Software, probably never released). Check the WoS Infoseek magazine references results if you want to read some of the articles about them.

   =====================

Here's another GUI-related program which I've just remembered:
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8912pp.zip]Red Arrow[/url] (Menno van der Star, Your Sinclair)
It moves a pointer around the screen to select things.


**********************************************************************


** 3D Line Drawing
   ===============

Try the [url=http://www.users.globalnet.co.uk/~jg27paw4/yr03/yr03_45.htm]Plotting in 3D[/url] articles from issues 3 & 4 of Your Spectrum at [url=http://www.users.globalnet.co.uk/~jg27paw4/]YRUA?[/url]. There's a link to part 2 from part 1 (and vice versa) and you can also download the programs, typed in and tested.

There's also Toni Baker's articles from ZX Computing at [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm]TTFn[/url], [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/zx8612.zip]128 Animation[/url] and [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/techgrph.zip]Technical Graphics[/url]. There's the complete article text and graphics, plus the programs, typed in and tested.

Then there's [url=ftp://ftp.worldofspectrum.org/pub/sinclair/books/AdvancedGraphicsWithTheSinclairZXSpectrum.pdf]Advanced Graphics with the Sinclair ZX Spectrum[/url] which has chapters such as: "Three-dimensional Coordinate Geometry", "Matrix Representation of Transformations on Three-dimensional Space", "Orthographic Projections", "Simple Hidden Line and Hidden Surface Algorithms"; which sound impressive.


**********************************************************************


** Large Sprites
   =============

The article [url=http://www.users.globalnet.co.uk/~jg27paw4/yr19/yr19_26.zip]Sprite High, No Limit[/url] by Chris Wood from Your Spectrum #19 provides routines for working with sprites up to 56x56 pixels. All the code's typed up and tested in the ZIP file, with a demo Popeye sprite taken from the article.

   =============

Chris Wood's [url=http://www.users.globalnet.co.uk/~jg27paw4/yr19/yr19_26.zip]Sprite High, No Limit[/url] program from Your Spectrum #19 is claimed to handle sprites of up to 31x24 [b]character squares[/b] - ie. almost as big as the screen - although the designer program only caters for sprites up to 56x56 pixels as the editor display is nine times actual size. Read SBREADME.TXT first, as you have to load BROWSER.Z80 first to run the TAP file which includes the article text, demo and various program & data files.

Here's what the designer screen looks like:

[center][img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/yr19_26.gif[/img][/center]

You'll probably need to refer to the [url=ftp://ftp.worldofspectrum.org/pub/sinclair/magazines/YourSpectrum/Issue19/Pages/YourSpectrum1900026.jpg]original article[/url] as well, as the illustrations for the animation sequence referred to in the article are not in the ZIP file.


**********************************************************************


** Machine Code Sprites and Graphics for the ZX Spectrum
   =====================================================

Here's some TAPs (with instructions) of useful magazine type-in sprite routines from [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm]TTFn[/url].

[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8712pp.zip]Star Tip 7 (Sprite Moving)[/url], Raffaelle Cecco, Your Sinclair, Dec'87
[url=http://www.users.globalnet.co.uk/~jg27paw4/yr10/yr10_39.zip]Sprite Designer[/url], Toni Baker, Your Spectrum, Dec'84
[url=http://www.users.globalnet.co.uk/~jg27paw4/yr19/yr19_26.zip]Sprite Designer[/url], Chris Wood, Your Spectrum, Oct'85
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/sincprog/sp8412.zip]Sprite Creator[/url], Roger Allen, Sinclair Programs, Dec'84
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yourcomp/yc8506.zip]Sprite Editor[/url], Robert Newman, Your Computer, Jun'85
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8804pp.zip]Sprite Designer[/url], Dom Early, Your Sinclair, Apr'88
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8808pp.zip]Sprite Editor[/url], Simon Owen, Your Sinclair, Aug'88
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys9211pp.zip]Sprite Grabber[/url], Steve Anderson, Your Sinclair, Nov'92

   =====================================================

There's an extensive handling of the topic in David Webb's book [url=ftp://ftp.worldofspectrum.org/pub/sinclair/books/AdvancedSpectrumMachineLanguage.pdf]Advanced Spectrum Machine Language[/url].

One example of scrolling landscapes is Toni Baker's [url=http://www.users.globalnet.co.uk/~jg27paw4/yr01/yr01_26.zip]Moving Graphics of the Horizontal Kind[/url] in Your Spectrum.


**********************************************************************


** Plotting and Rotating Wireframe Graphics
   ========================================

Try Toni Baker's [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/zx8612.zip]128 Animation[/url] from ZX Computing Dec'86. The frames of the image are drawn in BASIC, each saved into the "vast untapped resources of memory" in RAMdisk and then animated with a machine code routine. It looks pretty good once it gets going.

The "Plotting in 3D" articles by Damir Skrgatic in Your Spectrum might also be of interest.
http://www.users.globalnet.co.uk/~jg27paw4/yr03/yr03_45.htm
http://www.users.globalnet.co.uk/~jg27paw4/yr04/yr04_70.htm

   ========================================

Remember that the Spectrum trig functions use radians not degrees. Here's a little BASIC program to rotate a triangle about a central point, showing the adjustments which need to be made to the coordinates on each turn.Variable l is half the length of a side and t is the turn in degrees.

[code]
   3 DEF FN x(x,y,d)=FN a(x,y,FN r(d)):
     DEF FN a(x,y,r)=x*COS r-y*SIN r
   4 DEF FN y(x,y,d)=FN b(x,y,FN r(d)):
     DEF FN b(x,y,r)=x*SIN r+y*COS r
   5 DEF FN r(d)=d/180*PI
   6 DEF FN d(r)=r/PI*180
   7 DIM a(3,2): LET l=40: LET t=22.5:
     FOR a=1 TO 3: READ p1,p2: LET a(a,1)=128+p1: LET a(a,2)=88+p2: NEXT a:
     DATA -l,-l,+l,-l,0,+l
   8 FOR a=1 TO 3:
      LET p1=a: LET p2=a+1 OR a=3:
      PLOT a(p1,1),a(p1,2):
      DRAW a(p2,1)-a(p1,1),a(p2,2)-a(p1,2):
     NEXT a
  13 FOR a=1 TO 3:
       LET a1=FN x(a(a,1),a(a,2),t): LET a2=FN y(a(a,1),a(a,2),t):
       LET a(a,1)=a1+l: LET a(a,2)=a2-l:
     NEXT a
  15 GO TO 8
[/code]

   ========================================

The general form is:
[code]
x2 = cos  * (x1-x0) - sin  * (y1-y0) + x0
y2 = sin  * (x1-x0) + cos  * (y1-y0) + y0
[/code]
where:
x1,y1 = old coordinates
x0,y0 = centre of rotation coordinates
x2,y2 = new coordinates
and  = angle of rotation ()

To change the shape and orientation of the ellipse at run-time, pre-plan the transformations and hold a table of coordinates for each stage, as calculating each image from scratch at run-time would be too slow.

Here's a little (very slow) BASIC program which demonstrates the function. I generated the table of coordinates using the Bresenham routine with a centre of 128,88 and axes of 45,75 which generates 4*88 points. The loop does nine instances, as the first pass displays the starting position, then 8 more at 22.5 rotate half way around to the upside-down position, just to show that the shape fits exactly over the starting position.

[code]
   3 DEF FN x(x,y,d)=FN a(x,y,FN r(d)):
     DEF FN a(x,y,r)=(x-x0)*COS r-(y-y0)*SIN r+x0
   4 DEF FN y(x,y,d)=FN b(x,y,FN r(d)):
     DEF FN b(x,y,r)=(x-x0)*SIN r+(y-y0)*COS r+y0
   5 DEF FN r(d)=d/180*PI
   6 DEF FN d(r)=r/PI*180
   7 DIM a(3,2): DIM p(88,4,2): LOAD "" DATA p():
     LET t=22.5: LET x0=128: LET y0=88
   8 FOR l=1 TO 9: FOR a=1 TO 88: FOR b=1 TO 4:
       PLOT p(a,b,1),p(a,b,2):
     NEXT b: NEXT a
  13 FOR a=1 TO 88: FOR b=1 TO 4:
       LET a1=FN x(p(a,b,1),p(a,b,2),t): LET a2=FN y(p(a,b,1),p(a,b,2),t):
       LET p(a,b,1)=a1: LET p(a,b,2)=a2
  14 NEXT b: NEXT a
  15 NEXT l
  16 STOP
[/code]

I got the formula from [url=http://homepages.inf.ed.ac.uk/rbf/HIPR2/rotate.htm]this page[/url] at [url=http://homepages.inf.ed.ac.uk/rbf/HIPR2/hipr_top.htm]Image Processing Learning Resources[/url].

   ========================================

With the integer coordinates in a string for all 8 distinct images of a 22.5 step rotation (8*88*4*2=5632 bytes, there are 16 steps, but only 8 distinct images) it takes 0.48 second to display that pattern.

[code]
        org  65368
CH_ADD: equ  23645
COORDS: equ  23677
Rotate: ;requires Bresenham ellipse coordinates to be in array p$()
        ;DIM p$(steps,plots,4,2)
        ;eg. p$(8,88,4,2) for 22.5 angle, x/y axes 45/75
        ld   de,(CH_ADD)
        push de
        ld   de,P_name
        ld   (CH_ADD),de
        call $28b2           ;LOOK_VARS
        pop  de
        ld   (CH_ADD),de
        jp   c,$1c2e         ;"2 - Variable not found"
        push hl
        pop  ix
        ld   b,(ix+4)        ;B =steps
        ld   de,12
        add  hl,de           ;HL=data pointer
NextStep:
        push bc
        ld   b,(ix+6)        ;B =plots
NextPlot:
        push bc
        ld   b,4             ;B =points
NextPoint:
        push bc
        ld   c,(hl)
        inc  hl
        ld   b,(hl)
        inc  hl              ;B,C=y,x
        push hl
        ld   (COORDS),bc
        call $22aa           ;PIXEL_ADD     ;get x,y screen address
        ld   b,a
        inc  b
        ld   a,1
Pixel:  rrca
        djnz Pixel
        ld   b,(hl)
        or   b
        ld   (hl),a
        pop  hl
        pop  bc
        djnz NextPoint
        pop  bc
        djnz NextPlot
        pop  bc
        djnz NextStep
        ret
P_name: defm "p$():"
[/code]

   ========================================

Having compressed the rotation plot sequence into a string array I realised that I could then take advantage of PS's suggestion, as all the coordinates were now in integer values and I need only keep the plots for quadrants 1&2, as 3 mirrors 2 and 4 mirrors 1. I've updated the snapshot piclink above to include a conversion routine from p$(8,88,4,2) to q$(8,88,2,2), which is now 2816 bytes (+ a 10-byte header). This new listing works with q$().

http://www.users.globalnet.co.uk/~jg27paw4/pourri/rotating-unplot-2,2.asm

This includes clean-up code to erase duplicate plots from q$(); the three small sections commented out. The q$() array in the snapshot has already been cleaned up, but the "unclean" version can be restored by re-running the conversion at line 3000, then run Rotate without, then with, the clean-up lines, to see the difference. The clean-up lines can be disabled after that, as it only needs to be cleaned up once.


**********************************************************************


** Revealing a Hidden Picture
   ==========================

I used the embedded array technique for a program I sent in for a Crash competition. That was over 20 years ago and I can't remember how I generated the numbers for the array - using RND, presumably. It's quite quick, but has the disadvantage that the screen is always resolved in the same sequence. It's the "Crash Compo" program on my [url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/pourri.htm]Spot's Pourri[/url] page, if you're curious; the resolving routine starts at line 900.

A much more recent program I did is "Random Plot" - also on that page - which plots all the screen pixels randomly in about 4 minutes. That could be modified to "plot" attribute squares instead. It's in machine code, but the method could be copied in BASIC. The BASIC equivalent is:
[code]  10 LET r=INT (RND*45056): LET y=INT (r/256): LET x=r-y*256:
     PLOT x,y: GO TO 10[/code]

and for the attributes something like this:
[code]  10 LET p=768
  20 LET r=INT (RND*768): LET y=INT (r/24): LET x=r-y*32:
     LET q=22528+y*32+x: LET p=p-(PEEK q<>0): POKE q,0:
     GO TO 20+(NOT p)[/code]

This came about following the "Random plot not so random" thread on c.s.s. back in 2005. An earlier method I devised was:
[code]   5 DEF FN r(c,d,p)=c+(p AND c+p<=d)-(p AND c+p>d AND c-p>=-d)
  10 PLOT FN r(RND*255,255,PEEK 23672),FN r(RND*175,175,PEEK 23672)
  20 GO TO 10[/code]
which does look rather impressive, but I don't now know why (or how) I came up with something so impenetrable.

Either of those BASIC methods could be modified to address the attributes area. The problem with them (and the m/c version) is that they're not "intelligent", so most points are addressed more than once, which extends the completion time substantially. (The DEF FN version takes about 80 minutes.) However, as the attributes area is only 1/8th the size of the display area and the slow PLOT command wouldn't be needed, then it could be speeded up sufficiently to make it useful.

This method solves the duplication problem rather neatly.
Originally Posted by Einar Saukas
[Code]
10 RANDOMIZE: DIM p(768)
30 FOR f=1 TO 768: LET p(f)=f: NEXT f
60 FOR f=768 to 1 STEP -1
70   LET i=INT(RND*f)+1: POKE 22527+p(i),0: LET p(i)=p(f)
100 NEXT f
[/code]

Another routine which might be of interest is Simon Goodwin's random maze generator in [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/oddsends/gamescom.zip]Runaway Robot[/url]. This is in BASIC and rather cleverly constructs a maze which can always be completed. I'd have thought that if the maze area was redefined to be the attributes area then it could be modified to do what you want, by having 768 different types of maze element. (I haven't thought this one through very far, so it may be a mad idea.) The maze construction starts at line 700.

   ==========================

Which reminds me that many years ago I wrote a shuffling routine in COBOL, for generating a random map from a pre-defined set of tiles. The generated maps could also always be completed (it was for a SF treasure hunt game), but I think that was more to do with how I'd constructed the tiles than anything clever in the shuffling routine.

   ==========================

Here's my second attempt at a screen attribute reveal routine, which I've called S_Wipe - for why will be seen if you run it. It's more effective if there are a good spread of colours across the picture. I originally had it toggling FLASH with an "xor 128"where it says "load attribute and copy" but in the end I decided (probably wisely) that it was too ... well ... flashy. I'll enhance it or do a different one if I encounter any inspiration.

Here's a small setup/demo  routine ...
[code]
   5 GO TO 40

  10 CLEAR 29999: BORDER 4
  20 INPUT TAB 7;"load .scr at 30000";TAB 7;"then press 0&ENTER";TAB 15;i
  29 SAVE "attr"CODE 36144,768
  30 SAVE "disp"CODE 30000,6144
  32 SAVE "atdi"CODE 30000,6912
  33 STOP

  40 BORDER 0: INK 0: PAPER 0: CLEAR 29999: POKE 23624,36: INPUT ""
  42 LOAD "attr"CODE 36144,768
  43 LOAD "disp"CODE 16384,6144
  44 RANDOMIZE USR 23296
  45 BORDER 1: PAUSE 0: INK 6: BORDER 4: STOP
[/code]
... and the only slightly less small ...
S_Wipe.ASM [code]
        org  23296
REVEAL:
        di
        ld   b,8             ;8 colours
        ld   de,1            ;direction
        ld   (L2R2L),de
        ld   hl,22528        ;top row, first column
REV0:
        ld   c,b
        push bc
        ld   b,32            ;32 columns
REV1:
        push hl
        push bc
        ld   b,24            ;one column, 24 rows
REV2:
        push bc
        ex   de,hl
        ld   hl,13616
        add  hl,de
        ld   a,(hl)          ;get required attribute
        ex   de,hl
        and  7
        inc  a
        cp   c               ;check INK colour
        jr   nz,NO2          ;if same as REV2 counter
        ld   a,(de)
        ld   (hl),a          ;load attribute
        ld   (de),a          ;and copy
NO2:
        pop  bc
        ld   de,32
        add  hl,de           ;next row in column
        djnz REV2            ;done 24 rows?

        ld   e,16
DELAY2: djnz DELAY2
        dec  e
        jr   nz,DELAY2

        pop  bc
        pop  hl
        ld   de,(L2R2L)
        add  hl,de           ;top of next column
        djnz REV1            ;done 32 columns?
        push hl
        ld   hl,0000
        and  a
        sbc  hl,de
        ex   de,hl           ;DE=-DE, reverse direction
        ld   (L2R2L),de
        pop  hl
        add  hl,de           ;starting column
        pop  bc
        djnz REV0            ;done 8 colours?

        ei
        ret

L2R2L:  defw 0000
[/code]

   ==========================

Here's an alternative version of my previous routine, which does a random block reveal using a 10-bit LFSR, which is a quicker than RND, but as it's essential to include a delay loop in order to see the effect I don't suppose that really matters. Don't use RANDOMIZE to run it as it'll do it the same way every time (because it uses (SEED)).

I got the LFSR tap settings from this [url=http://www.eej.ulst.ac.uk/~ian/modules/EEE515/files/old_files/lfsr/lfsr_table.pdf]Table of Linear Feedback Shift Registers[/url]. There's an example of a 16-bit LFSR loop in the [url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/0-surc-contents.htm]Compendium[/url] file Random.TXT .

[code]
        org  23296

LFSR10:
        di
        ld   de,(23670)      ;(SEED)
        ld   a,d
        and  %00000011
        ld   d,a             ;limit seed to 10 bits, taps at 7 & 10
                             ;                      7  10
                             ;         D        E   v  v
                             ;         000000GH IJKLMNOP
        ld   bc,1023         ;size of 10-bit cycle
LOOP10:
        push bc
        ld   a,e
        rra
        rra
        rra
        xor  e
        and  %00000001
        rla
        rla
        or   d
        ld   d,a
        rr   d
        rr   e

        ld   hl,$57ff
        add  hl,de
        ld   a,h
        cp   $5b
        jr   nc,SKIPDE
        push de
        ex   de,hl
        ld   hl,13616        ;offset to stored attributes
        add  hl,de
        ld   a,(hl)          ;get required attribute
        ld   (de),a
        pop  de
        ld   c,4
DELAY2: djnz DELAY2
        dec  c
        jr   nz,DELAY2

SKIPDE: pop  bc
        dec  bc
        ld   a,b
        or   c
        jr   nz,LOOP10
        ei
        ret
[/code]


**********************************************************************


** Fractal Graphics
   ================

Try these:

[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/popcomwk/pcw8209.zip]Function Display in 3D Graphics[/url]

[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8911pp.zip]Mandelbrot Magic[/url]
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys9001pp.zip]Son of Mandelbrot[/url]
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys9002pp.zip]Mandelbrot - The Story Continues[/url]
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys9009pp.zip]3D Mandelbrot Sets[/url]

[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8910pp.zip]Fractal Triangles[/url]
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys8911pp.zip]Fractal Frolics[/url]
[url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yoursinc/ys9205pp.zip]Fractal Manifestations[/url]


**********************************************************************


** Plotting in the Lower Screen
   ============================

David Webb's book [url=ftp://ftp.worldofspectrum.org/pub/sinclair/books/AdvancedSpectrumMachineLanguage.pdf]Advanced Spectrum Machine Language[/url], chapter 3, has routines for full-screen PLOT and DRAW.

   ============================

Here's what I use in my ellipse routine to plot across the whole screen.
[code]
PLOTXY:
        ld   a,(cx)
        add  a,c
        ld   c,a             ;B =y; C = x
        ld   a,191           ;allow full screen depth
        sub  b
        call $22b0           ;PIXEL_ADD+6 - C (x coord) is preserved
        neg                  ;calculate the SET b,(HL) opcode for bitpos
        add  a,7             ;A =7-A
        sla  a
        sla  a
        sla  a               ;A =A*8
        add  a,198
        ld   (SETPIXL+1),a
        ld   a,h
        cp   $40
        ret  c               ;don't plot if address too low
        cp   $58
        ret  nc              ;don't plot if address too high
SETPIXL:set  0,(hl)          ;bitpos changed by SETPIXL line
        ret
[/code]

It's actually PIXEL_ADD, not PLOT, which checks the vertical coordinate, so if PIXEL_ADD is called at +6 then the check is skipped and a different limit can be used.

PLOT can still be used. On returning from "call $22b0 ;PIXEL_ADD+6" just do "call $22ec ;PLOT_SUB+7" which skips the call to PIXEL_ADD in PLOT.


**********************************************************************


** Colour Clash Comments
   =====================

Each character square can only support two colours - INK & PAPER. So if the bottom right creature is cyan INK on black PAPER, as soon as it moves over a green square there's a conflict as to what can be displayed. Depending upon the currently active INK & PAPER settings for each square only two of the desired three colours will be displayed, so as the creature moves across the green plane the character squares being traversed will switch to green or black PAPER depending upon the attribute settings for each character square being refreshed. This is known as colour clash.

To minimise the effect in BASIC you need to ensure that any two adjacent squares use no more than two colours between them; so in this example the creature backgrounds would be green PAPER to match the plane. Then you only have a problem when the creatures collide, when one is going to inherit the INK colour of the other as they intersect.

The effects of colour clash can be most effectively minimised by machine code sprite routines to control the movement and display of the characters; Gargoyle Games' Light Force and Mastertronic's Star Farce are a couple (of many) examples of animation across multi-coloured backgrounds.

OVER 1 does an XOR between the 8x8 bits of the character on screen and the one to be printed at the same location, so it affects whether pixels will display INK or PAPER colour, but not what those colours are. The colours need to be changed (as mentioned above already) by judicious use of PRINT INK ... PAPER ...

   =====================

You could try using the "transparent" attribute - PAPER 8, INK 8, etc. - to see if that gives a preferable effect. Here's a small demo program to show the difference it makes.

[code]
  10 FOR p=1 TO 5: FOR l=(p-1)*3+p-1 TO (p-1)*3+p+2:
       PRINT AT l,0; PAPER p,,:
     NEXT l: NEXT p
  20 FOR l=0 TO 19:
       LET p=ATTR (l,0)/8:
       PRINT AT l,0; PAPER 7; INK 0;"O";AT l,16; PAPER 8;"O":
       PAUSE 5: PRINT AT l,0; PAPER p;" ";AT l,16;" ":
     NEXT l: GO TO 20
[/code]

You don't get any more colours per square, but it can improve the presentation.


**********************************************************************


** Erasing the Screen
   ==================

If actually clearing the window (ie. resetting all the pixels) isn't essential, and just blanking it (ie. setting paper=ink) is adequate, then it would be quicker just to change the attributes.

The following demo compares printing spaces with copying an attributes segment; as can be seen, the latter is much faster. It copies the prior attributes for rows 4-18, but with the window area set to black on black. Line 2001 sets up the attributes string c$; line 2003 associates a$ with the relevant area of the attributes file; line 2005 does the copy.

As with most performance issues it's a trade-off between space & time; the latter method needs an extra 480 bytes to keep the copy of the attributes segment. Most of the routine is just one-off setup; the "business end" is just the "LET a$=c$" line. Note that a$ becomes reserved; it can't be used for anything else unless DEFADD is reset.

Eraser.BAS [code]
   5 GO SUB 200
  10 FOR r=4 TO 18: PRINT AT r,8; INVERSE 1;b$: NEXT r
  99 GO TO 2001
 100 FOR r=4 TO 18: PRINT AT r,8;"abcdefghijklmno": NEXT r:
     INPUT "ENTER to blank window ";i$: RETURN
 199 REM window 4,8->18,22;               frame 3,7->19,23
 200 FOR r=3 TO 19: PRINT AT r,0;"********************************": NEXT r:
     DIM b$(15): PRINT PAPER 5;AT 3,7;" ";b$;" ":
     FOR r=4 TO 18: PRINT PAPER 5;AT r,7;" ";AT r,23;" ": NEXT r:
     PRINT PAPER 5;AT 19,7;" ";b$;" ": GO SUB 100: RETURN
1999 REM
2001 DIM c$(15*32): LET a=PEEK 23693:
     FOR c=1 TO 32: LET c$(c)=CHR$ a: NEXT c: LET c$(8)=CHR$ 40:
     FOR c=9 TO 23: LET c$(c)=CHR$ 0: NEXT c: LET c$(24)=CHR$ 40:
     FOR c=33 TO 449 STEP 32: LET c$(c TO c+31)=c$( TO 32): NEXT c
2003 RESTORE 2003: FOR a=65527 TO 65535: READ b: POKE a,b: NEXT a:
     POKE 23563,247: POKE 23564,255:
     DATA CODE "A",CODE "$",14,0,128,88,224,1,CODE ")"
2004 GO SUB 100
2005 LET a$=c$
[/code]

   ==================

I did some tests with BASin for five different methods of clearing a 15x15 window to black on black and got these timings:

1. spaces PRINT loop: 13.5 frames
2. tabs PRINT loop: 13.9 frames
3. attributes LET loop: 18 frames
4. attributes LET: 0.6 frame
5. spaces PRINT with controls: 6 frames

So, "don't use a loop" seems sound advice. After that, to reset the window in the display file and blank the attributes, use option 5; if just blanking the attributes is adequate, then use option 4.

   ==================

Line 2003 specifies A$ as being at 22656 (=128+88*256 = 22528+4*32 = 4,0 in attributes) and length 480 (=224+1*256 = 15*32). The two POKEs set DEFADD to point to this definition. The LET therefore copies c$ to that area in the attributes.

The c$ string specifies the required attributes for the 15 rows into which the window fits (excluding the top and bottom frame lines). PEEK 23693 gets ATTR_P for the current "permanent" default attributes; CHR$ 40 is for the cyan frame; CHR$ 0 is to black out the window itself.

   ==================

I tried the attribute erase demo with four compilers: HiSoft BASIC, Softek FP, MCoder 3, Laser Compiler. Ocean's Laser Compiler was the only one which handled it correctly.


**********************************************************************


** Next Pixel Down
   ===============

This [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/elemgrph.zip]Elementary Graphics[/url] article by Toni Baker includes this excellent little diagram illustrating the addressing structure of the display file:

[center][img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/elemg2_4.gif[/img][/center]

From which derives this routine for scanning down a column (in E, col.0 in the example) in pixel steps.

ScanDown.ASM [code]
        ld   hl,$4000
        ld   de,$4000
        ld   bc,0

pixrow: ld   (hl),$ff
        inc  d               ;D =pixrow
        bit  3,d
        jr   z,set_hl
        ld   d,%01000000
        inc  c               ;C = line
        bit  3,c
        jr   z,set_hl
        ld   c,0
        inc  b               ;B =segment
        ld   a,b
        cp   3
        ret  z

set_hl  ld   h,d
        ld   l,e
        push bc
        sla  b
        sla  b
        sla  b
        rrc  c
        rrc  c
        rrc  c
        add  hl,bc
        pop  bc
        jr   pixrow
[/code]

   ===============

The problem with using $22aa PIXEL_ADD is that it calculates the address from scratch every time, whereas in my program I know where the previous coordinate was, so I should be able to calculate the address of the next one with less code; eg. for the eight rows in a line all that's needed is INC D in the example to get the next one down.


**********************************************************************


** Mini scroll routine
   ===================

MiniScroll.BAS [code]
8150 RESTORE 8200: FOR a=1 TO 8: READ a$: FOR b=0 TO 7: READ c: POKE USR a$+b,c: NEXT b: NEXT a
8201 DATA "a",0,0,0,0,0,0,0,0
8202 DATA "b",0,0,0,0,0,0,0,0
8203 DATA "c",0,1,7,12,15,15,7,11,"d",59,63,55,50,3,3,7,15
8205 DATA "e",0,0,0,0,0,0,0,0
8206 DATA "f",0,0,0,0,0,0,0,0
8207 DATA "g",0,224,152,112,248,248,240,144,"h",220,126,252,76,192,96,112,112
9000 FOR a=1 TO 16
9002 PRINT AT 0,0;"\c\g"'"\d\h"
9004 PAUSE 10
9005 POKE 23675,PEEK 23675-1
9008 NEXT a
9010 FOR a=1 TO 16
9011 POKE 23675,PEEK 23675+1
9012 PRINT AT 0,0;"\c\g"'"\d\h"
9014 PAUSE 10
9018 NEXT a: STOP
[/code]
Characters which are vertically adjacent on the screen have to be horizontally adjacent in the UDGs.

   ===================

A 64-byte temporary UDG area could be used for the scrolling, with a small routine to copy across the four characters from the main UDG table and swap them around into their correct positions in the mini-scroll table, so the main UDGs wouldn't need changing at all.


**********************************************************************


** Using SCREEN$ from Assembler
   ============================

[code]
        ld   c,row
        ld   b,col
        call $2538      ;S_SCRN$_S+3
        call $2bf1      ;STK_FETCH from calculator stack: AEDCB
        ld   a,(de)     ;A=SCREEN$(row,col) if BC=1
                        ;no match if BC=0
[/code]

Part 3 of Toni Baker's [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/zx-comp/elemgrph.zip]Elementary Graphics[/url] series includes an enhanced SCREEN$ which recognises all characters. It's a hundred lines long so I haven't posted it here; it can be copied from the ZIP file.


**********************************************************************


** Colour Considerations when Plotting from Assembler
   ==================================================

Remember that PLOT uses ATTR_T (23695) & MASK_T (23696) to set the attributes for the square, not ATTR_P & MASK_P. So ATTR_T needs to be set for Flash,Bright,Paper,Ink and MASK_T bits set for transparent attributes if $22E5 PLOT_SUB is being called.

The corresponding temporary Over and Inverse settings are in P_FLAG (23697, IY+87), bits 0 & 2 respectively (bits 1 & 3 are for the permanent settings). The relevant system variables can be addressed directly, or else using RST $10 to print the colour controls will also work.

To PLOT without affecting the attributes try this:

[CODE]
        ld   b,Ycoord
        ld   c,Xcoord
        ld   (COORDS),bc     ;save coords of last point plotted
        call $22aa           ;PIXEL_ADD
        ld   b,a
        inc  b
        ld   a,1
ROTATE: rrca
        djnz ROTATE
        or   (hl)
        ld   (hl),a
[/CODE]

So if  the current & last colours differ it will display, for example:
[center]this [img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/bresenham-jgc.gif[/img] not this [img]http://www.users.globalnet.co.uk/~jg27paw4/pourri/bresenham-tec.gif[/img][/center]


**********************************************************************


** Plotting an Energy Bar
   ======================

Here's a smaller & faster alternative with no ROM call.
EnergyBar.ASM [code]
        org  23296
        ld   a,27
        ld   (51993),a
;[color=blue]
        ;step energy bar down by one point/pixel
DECONE: ld   a,(51993)       ;A =old energy level
        dec  a
        ld   (51993),a
        ld   hl,$4406        ;right-hand of bar: address + 1
        ld   a,4             ;right-hand of bar: starting value
        jr   LEFT_2
LEFT_1: ld   a,1
LEFT_2: dec  hl
        bit  7,(hl)
        jr   nz,LEFT_1
        sla  (hl)
        or   (hl)
        ld   (hl),a          ;update byte on row Y=171
        dec  h
        ld   (hl),a          ;copy to corresponding byte on row Y=172
        dec  h
        ld   (hl),a          ;and Y=173
;[/color]
        ld   b,10
DELAY:  halt
        djnz DELAY
        ld   a,(51993)
        or   a
        jr   nz,DECONE       ;demo loop
        ret
[/code]


**********************************************************************


** Scrolling by 2 pixels
   =====================

The two routines below scroll a full 32 character line 2 pixels to the right.
The 1st routine is 33 bytes and takes approx. 16957T between the PUSH & POP.
The 2nd routine is 65 bytes and takes approx. 11419T between the PUSH & POP.
The main reason for the difference is that the first one runs the central loop 512 times whereas the second one only does its central loop 64 times.

--

Forgot to mention - #2 works by pointing SP to the line to be scrolled, POPping 4 bytes at a time, shifting them 2 pixels to the right and saving the 2 bits which have dropped off the end in A, plus leaving 2 zero bits at the left. On the next pass, after same, the 2 bits in A are ORed onto the left-hand end of the new chunk in the space vacated by the 2 right shifts. Repeat 8 times for one pixrow; repeat that 8 times for one line. At the end of a pixrow the last 2 bits to drop off are discarded, so the line eventually disappears.

SCROLL256RR.ASM [code]
SCROLL256RR:
        di
        exx
        push hl
        call SCROLL2RR
        call SCROLL2RR
        pop  hl
        exx
        ei
        ret

SCROLL2RR:
        ld   c,8
        ld   hl,16384
        ld   de,256-32
CHAROWRR:
        ld b,32
PIXROWRR:
        rr   (hl)
        inc  l
        djnz PIXROWRR
        add  hl,de
        dec c
        jr nz,CHAROWRR
        ret
[/code]

SCROLL256PPa.ASM [code]
SCROLL256PP:
        di
        exx
        push hl
        ld   (23670),sp
        ld   sp,16384
        ld   c,8
        xor a
CHAROWPP:
        ex af,af'
        xor a
        ld b,8
PIXROWPP:
        pop hl
        pop de
        rr l
        rr h
        rr e
        rr d
        rr a
        rr l
        rr h
        rr e
        rr d
        rr a
        ex af,af'
        or l
        ld l,a
        push de
        push hl
        xor a
        pop hl
        pop de
        djnz PIXROWPP
        ld   hl,256-32
        add  hl,sp
        ld   sp,hl
        dec  c
        jr   nz,CHAROWPP
        ld   sp,(23670)
        pop  hl
        exx
        ei
        ret
[/code]

   =====================

I thought that it might be possible to save space and get a routine that was at least as quick as entering "RR (HL) : INC HL" 256 times. If those 512 lines are put in a sub-routine and called twice to shift by 2 pixels, the space used is:

3 * 256 + 7 = 775 bytes

and the running time with interrupts disabled is:

21 * 512 + 27 * 2 = 10806T

The following routine is just 94 bytes and takes 10800T from PUSH to POP, as the central loop is only executed 32 times.

SCROLL256PPb.ASM [code]
        org  23296
SCROLL256PP:                 ;94 bytes, DI->EI = 10800T
        di
        exx
        push hl
        ld   (23670),sp
          ld   sp,16384
        ld   c,8
        xor a
CHAROWPP:
        ex af,af'
        xor a
          ld b,4
PIXROWPP: ; get 8 bytes from the display & rotate right 2 pixels
          pop hl
          rr l
          rr h
          rra
        exx
        pop hl
        pop de
        pop bc
          rla
        rr l
        rr h
        rr e
        rr d
        rr c
        rr b
        rra
          exx
          rr l
          rr h
          rra
          exx
          rla
        rr l
        rr h
        rr e
        rr d
        rr c
        rr b
        rra
        ex af,af'
        push bc
        push de
        push hl
        	exx
            or   l
            ld   l,a
            push hl
            pop  hl
            ;exx
        xor a
        pop hl
        pop de
          inc sp             ;can't do "pop bc"
          inc sp             ; as it would overwrite counters
        ;exx
        djnz PIXROWPP        ;do 4 times to complete one pixrow
          ld   hl,256-32
        add  hl,sp
        ld   sp,hl
        dec  c
        jr   nz,CHAROWPP     ;do 8 times to complete one charow
        ld   sp,(23670)
        pop  hl
        exx
        ei
        ret
[/code]


**********************************************************************