
**********************************************************************
files: HowTo.TXT, HowTo.TAP
       UntilWhile1.BAS, UntilWhile2.BAS, UntilWhile4.BAS
       Dijkstra.C, Dijkstra.BAS, Countdown.BAS
http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yourspec/yr8403pp.zip
       Boxes_Corners.ASM, Boxes_Initialise.ASM, Boxes_CountSquares.ASM
       AexB.ASM, AexB.BAS, AexB.TXT, AinB.ASM
**********************************************************************


** How To Write A Program
   ======================

[list=1]
[*] Have a good idea for a program (difficult).
[*] Prepare a design and write a specification (bwah hah hah!!).
[*] Write a badly structured & inefficient program in BASIC (easy).
[*] Test & correct (repeat many times).
[*] Dismayed by your incompetence, you decide to: a) shoot yourself; or b) enter a monastery; or c) trash your feeble efforts and start all over again.
[*] Learn how to program properly in BASIC.
[*] Write a well structured & efficient program in BASIC (difficult).
[*] Test & correct (repeat many times).
[*] Decide that BASIC is too slow and that compiling it is a good idea.
[*] Try every available compiler and discover that all of them complain about something or other in your code that they can't compile.
[*] Decide that BASIC is still too slow and that compiling it was a bad idea.
[*] Write a badly structured & inefficient program in assembler (easy).
[*] Test & correct (repeat many times).
[*] Dismayed by your incompetence, you decide to: a) shoot yourself; or b) enter a monastery; or c) trash your feeble efforts and start all over again.
[*] Learn how to program properly in assembler.
[*] Write a well structured & efficient program in assembler (difficult).
[*] Test & correct (repeat many times).
[*] Release your masterpiece.
[*] Become rich & famous.
[*] Then you wake up and realise it was all a dream.
[/list]


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


** A Miscellany of Dangerous Tips
   ==============================

Here's a selection of ideas for using some of the more obscure system variables which I've found handy on occasion. They can be very convenient if used wisely, or very disastrous if used without due care & attention. So if you have a problem, if nothing else can help, and if you can understand them, maybe you can use ...

One which I often use is from Toni Baker's book [url=ftp://ftp.worldofspectrum.org/pub/sinclair/books/MasteringMachineCodeOnYourZXSpectrum.pdf]Mastering Machine Code on Your ZX Spectrum[/url] (Chapter 9) which includes a method for hiding machine code (or anything else which takes your fancy, such as data or program variables) between the end of PROG and the beginning of VARS. Simply put, the space required is assigned with one or more LETs after a CLEAR and then VARS moved to point beyond the end of that space. Anything in between is protected and can be used for code or data or for preserving variables from RUN and CLEAR and will be SAVEd along with the BASIC. If using the space for code or data, [b]don't overwrite the first byte[/b] (the initial letter of the first hidden variable) as that's what keeps it hidden. If using it for variables, they can be restored for reference when needed by moving down VARS.

Another method of preserving variables is to assign them in a DEF FN statement. Space for these variables (or pointers to them, in the case of strings) will be automatically created within the DEF FN statement itself by the BASIC editor. They can be referenced by assigning the DEFADD system variable to point to the assigned space, and are not affected by RUN or CLEAR. The aforementioned Chapter 9 describes the structure of the space in the context of passing parameters to machine code, but it has several other uses. Expanding upon this idea, a similar variable space can be assigned anywhere in free RAM and used independently of any DEF FN statement, which allows quick RAM copies to be done with LET rather than the slow POKE.

To alter the size & location of DEF FN strings (which are treated as 1D DIMmed strings), the solution is another handy-dandy system variable DATADD - "address of terminator of last DATA item", which might not seem very useful, as you're not looking for DATA items, but doing a "RESTORE line_number" will set DATADD to the address of the byte preceding that line number, and if that line is the one containing the DEF FN then it's been found, and thus its parameter values can be changed.

An adjunct to this, and of general use when using any DEF FN, is that the parameters used in the last reference to a function remain available for reference in its variable space after the function has been evaluated, so these can be checked from elsewhere in the program. Additionally, DATADD can be used to determine the address of machine code embedded in the program as a hex dump DATA string, so that the decoding loop only needs to be run once, after which the executable machine code is always ready to run from the addressed point.

Putting together some of those ideas provides the further facility of passing parameters to sub-routines without the cumbersome palaver of a bunch of prior LETs; ie. in the form "GO SUB FN a(a$,a,...)" where the FN just returns a line number and the parameters become available for reference within the sub-routine by assigning DEFADD appropriately at some time beforehand. As the DEFADD area is searched before the VARS area, this also provides a means of defining variables which are local to that subroutine "stack", by resetting the hi-byte of DEFADD on return so that the area is no longer referenced.

Glancing off at a slight tangent from that idea arrives at PPC - "line number of statement currently being executed" - which, if evaluated as a DEF FN, can be used as a shorthand method for returning to the start of the current line with, eg. "GO TO FN p()". To go to any line & statement from anywhere then the system variables NEWPPC & NSPPC can be used.

The GO SUB stack lies just beyond "the address of item on machine stack to be used as error return", held in system variable ERR_SP. Evaluating that address and going two bytes beyond arrives at the GO SUB stack which, in blocks of three bytes ending with a $3E (62) records the return line & statement for each GO SUB currently in process. Which can be useful to know if the sub-routine is to do different things depending upon that information. Picking up the top level can be done as s simple DEF FN if required often, and picking up any level can be done as a recursive DEF FN.

Finally, knowing where those signposts are located enables any, or all of them, to be changed, or even removed altogether. This does have a practical use for redirection; and also in error handling, to allow backing out of multiple subroutine levels on an error without having to include an error check in each calling sub-routine.

Examples of all of these methods can be found in the BASIC forum. When used judiciously they can provide features saving time and/or space which are not otherwise possible from BASIC alone.

References (2012):
* storing machine code in BASIC (Jan)
* more on storing mazes (Feb)
* Poking a string to memory... (Mar)
* Keyboard problem... (Apr)
* Screen2String (May)
* BREAK from GOSUB (May)
* enhanced GOSUB/GOTO (in BASIC) (May)
* How much space does a number take? (May)
* General purpose full screen input routine (May)
* saving time populating large arrays (Oct)


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


** UNTIL ... DO and WHILE ... DO loops
   ===================================

UntilWhile1.BAS [code]
   3 LET a$="<<<qwertyuiopasdfghjklzxcvbnm>>>"

  13 REM UNTIL i DO ...
  14 FOR i=0 TO 1: LET i=INKEY$="f" OR INKEY$="F":
       PRINT AT 0,0;:
       PRINT a$(1 TO 32);:
       LET a$=a$(2 TO )+a$(1):
     NEXT i

  23 REM WHILE i DO ...
  24 FOR i=1 TO 0 STEP -1: LET i=INKEY$<>"b" AND INKEY$<>"B":
       PRINT AT 0,0;:
       PRINT a$(1 TO 32);:
       LET a$=a$(32)+a$(TO 31):
     NEXT i
[/code]

These will always execute the loop once even if the terminating condition is true on entry because, although the condition is evaluated by the "LET i=..." at the start, it's not checked until the "NEXT i" at the end. They print in opposite directions just to show which line it's on.

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

Alternatively ... line 3 is UNTIL (condition); line 4 is WHILE (condition). Again, each loop will always be executed at least once.

UntilWhile2.BAS [code]
   3 LET a$=CHR$ 22+CHR$ 0+CHR$ 0+"1234567890qwertyuiopasdfghjklzxcvbnm"
   4 PRINT a$(TO 35): LET a$(4 TO)=a$(5 TO)+a$(4):
     POKE 23618,4:POKE 23620,255 AND (INKEY$="f" OR INKEY$="F")
   5 PRINT a$(TO 35): LET a$(4 TO)=a$(39)+a$(4 TO 38):
     POKE 23618,5:POKE 23620,255 OR (INKEY$<>"b" AND INKEY$<>"B")
[/code]

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

Alternatively ... given that:
	"UNTIL condition DO ..." and "WHILE condition DO ..."
are equivalent to:
	"WHILE condition is false DO ..." and "WHILE condition is true DO ..."
then set:
	variables 'until' = 0 (false) and 'while' = 1 (true)
and call a sub-routine with:
    LET c=until -or- LET c=while
    LET c$="condition": GO SUB nnnn
and the Loop3a defined as:              or Loop3b defined as:
nnnn FOR i=0 TO 0 STEP 0                nnnn FOR i=0 TO 0 STEP 0
       LET i=(c<>VAL c$)                       IF c<>VAL c$ THEN RETURN
       ... statements ...                      ... statements ...
     NEXT i                                  NEXT i
     RETURN
Loop3a will terminate (after at least one pass) when the LET sets i=1, so:
	if c=until (false) then that will be when VAL c$ is true
and:
	if c=while (true ) then that will be when VAL c$ is false

However, Loop3b will terminate as soon as the condition is true, as it is both evaluated & checked at the start. (The previous two methods could be altered to use "IF condition THEN RETURN" as well.)

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

Here's an example of Method 3a which uses FN rather than LET to assign the parameters for the subroutine (see GOSUB+GOTO.TXT for more info). Each of the GO SUB loops will repeat until "e" is pressed.
System variable 23639/40 is DATADD; 23563/4 is DEFADD; CHR$ 101 is "e".

UntilWhile4.BAS [code]
   4 DEF FN l(c,c$)=10: RESTORE 4: LET df=PEEK 23639+256*PEEK 23640+8:
     POKE 23563,df-INT (df/256)*256: POKE 23564,INT (df/256)
   5 LET until=0: LET while=1
   6 GO SUB FN l(until,"CODE i$=101")
   7 IF INKEY$="e" THEN GO TO 7
   8 GO SUB FN l(while,"CODE i$<>101")
   9 STOP
  10 LET s=0: FOR i=0 TO 0 STEP 0
       LET i$=INKEY$: LET i=(c<>VAL c$)
       PRINT AT 0,0;s,i: LET s=s+1:
     NEXT i: RETURN
[/code]


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


** Pathfinding in Networks/Mazes
   =============================

I use the Dijkstra algorithm for calculating paths, as it's simple & quick, although it needs a lot of memory (in Spectrum terms). It's easy to write it in Spectrum BASIC in a couple of dozen lines, but impossible to run it as it needs several meg of memory for any decent sized game. Here's the C version which I use. It calculates the shortest route from the source to every other location; so this can be done for any two locations by making one of them the source and recalculating.

The input file starts with the number of non-zero edges (= the number of following input lines). Each following line consists of three numbers specifying two nodes and the length of the edge between them. A node is a location; an edge is the connection between two adjacent locations. I run it on a Unix system and then import the output back into the Spectrum. These dimensions will handle a game with up to 400 locations.

GRAPHSIZE should be at least the number of non-zero edges; ie. the number of lines in the input file minus one (not counting the header line). In the case of the first game where I used it, there were 400 locations/nodes and 711 non-zero edges. I made it 1024 so that I didn't have to keep changing it for different games.

Dijkstra.C [code]
#include <stdio.h>

#define GRAPHSIZE 1024
#define INFINITY GRAPHSIZE*GRAPHSIZE
#define MAX(a, b) ((a > b) ? (a) : (b))

int e; /* The number of non-zero edges in the graph */
int n; /* The number of nodes in the graph */
long dist[GRAPHSIZE][GRAPHSIZE]; /* dist[i][j] is the distance between nodes i and j; or 0 if there is no direct connection */
long d[GRAPHSIZE]; /* d[i] is the length of the shortest path between the source (s) and node i */

void printD() {
	int i;
    for (i = 1; i <= n; ++i)
		printf("%10d\t%10ld\n",i,d[i]);
}

void dijkstra(int s) {
	int i, k, mini;
	int visited[GRAPHSIZE];

    for (i = 1; i <= n; ++i) {
		d[i] = INFINITY;
		visited[i] = 0; /* the i-th element has not yet been visited */
	}

	d[s] = 0;

    for (k = 1; k <= n; ++k) {
		mini = -1;
        for (i = 1; i <= n; ++i)
            if (!visited[i] && ((mini == -1) || (d[i] < d[mini])))
				mini = i;

		visited[mini] = 1;

        for (i = 1; i <= n; ++i)
			if (dist[mini][i])
                if (d[mini] + dist[mini][i] < d[i])
					d[i] = d[mini] + dist[mini][i];
	}
}

int main(int argc, char *argv[]) {
	int i, j;
	int u, v, w;

	FILE *fin = fopen("ff01dijk.txt", "r");
    fscanf(fin, "%d", &e);
	printf( "edges=%d\n",e );
    for (i = 0; i < e; ++i) {
        for (j = 0; j < e; ++j)
			dist[i][j] = 0;
	}
	printf( "reset %d\n",i );
	n = -1;
    for (i = 0; i < e; ++i) {
        fscanf(fin, "%d%d%d", &u, &v, &w);
		dist[u][v] = w;
		n = MAX(u, MAX(v, n));
	}
	printf( "nodes=%d\n",n );
	fclose(fin);

	dijkstra(1);

	printD();

	return 0;
}
[/code]

Here's a Spectrum BASIC example with five nodes:

Dijkstra.BAS [code]
  10 DEF FN m(a,b)=(a AND a>b)+(b AND a<=b)
 100 LET size=5: LET infy=size*size: DIM x(size,size):
     DIM d(size): DIM v(size): LET n=-1
 110 FOR i=1 TO 10:
       READ u,v,w: LET x(u,v)=w: LET n=FN m(u,FN m(v,n)):
     NEXT i
 120 INPUT source:
     FOR i=1 TO n: LET d(i)=infy: NEXT i:
     LET d(source)=0
1000 DATA 1,2,10,1,4,5,2,3,1,2,4,3,3,5,6,4,2,2,4,3,9,4,5,2,5,1,7,5,3,4
1099 REM -----------------------
2000 FOR k=1 TO n: LET mini=-1
2010   FOR i=1 TO n
2020   IF NOT v(i) AND mini=-1 THEN
         LET mini=i: GO TO 2040
2030   IF NOT v(i) THEN IF d(i)<d(mini) THEN
         LET mini=i
2040   NEXT i
2050   LET v(mini)=1
2060   FOR i=1 TO n
2070     IF x(mini,i) THEN IF d(mini)+x(mini,i)<d(i) THEN
           LET d(i)=d(mini)+x(mini,i)
2090   NEXT i
2100 NEXT k
2199 REM -----------------------
3000 FOR i=1 TO n:
       PRINT "from ";source;" to ";i;" is ";d(i):
     NEXT i
3001 STOP
[/code]

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

On an old PC with a 500mHz processor running SCO OpenServer, calculating the distances from node 1 in a 400 node map takes practically no time at all - I type ./dijkstra, press Enter, and it's finished. That's with printing redirected to a file; printing to the screen takes about half a second.


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


** Learning Z80 assembler - Useful Links
   =====================================

There's these tutorial series for beginners, from the old Spectrum mags, on my [url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/pourri.htm]Spot's Pourri[/url] page.

"[url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/zxmaster.zip]Mastering Machine Code[/url]" (8 parts - 61 kb) by Toni Baker from [i]ZX Computing[/i] 3-11 (Oct/Nov'82-Feb/Mar'84) (no article in issue 9 (Oct/Nov'83)).
"[url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/zx1st_mc.zip]First Steps in Machine Code[/url]" (6 parts - 73 kb) by David Nowotnik from [i]ZX Computing[/i] 16-21 (Dec/Jan'84/85-Oct/Nov'85).
"[url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/sumctut1.zip]Mind over Machine[/url]" (10 parts - 110 kb) by Marcus Jeffery from [i]Sinclair User[/i] 38-47 (May'85-Feb'86).

As well as the article text & diagrams, each archive has a TAP file with the demo programs typed in and tested.

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

Toni Baker's m/code programming articles from ZX Computing are very useful; the one describing the use of the calculator routines is particularly good. They're all typed in and tested on the ZX Computing (Spectrum) page at [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm]TTFn[/url].

The William Tang book "Spectrum Machine Language for the Absolute Beginner" is pretty good as well. Toni Baker's well known book "Mastering Machine Code on Your ZX Spectrum" is full of useful information; excellent for reading through and learning as you go, but it's difficult for finding anything afterwards as it doesn't have an index.

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

Some others are:
Advanced Spectrum Machine Language (David Webb)
Supercharge Your Spectrum (David Webb)

A couple of the better general Spectrum reference books covering both BASIC and machine code are:
Understanding Your Spectrum (Ian Logan)
Spectrum Advanced User Guide (Adrian Dickens)

Then there's:
40 Best Machine Code Routines for the ZX Spectrum (Andrew Hewson)
which has lots of explanatory text for each routine.

and here's also:
Spectrum+2 Machine Language for the Absolute Beginner (Joe Pritchard).

and one which I learned from in the early days was:
Introducing Spectrum Machine Code (Ian Sinclair)

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

ZX Computing was the best magazine for picking up machine code, primarily because of the many articles by Toni Baker (she also did ten programming features in Your Spectrum). Unfortunately ZXC ceased publication before her last programming series "Into the 128" was finished. All those articles and programs are available at [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm]TTFn[/url], including the infamous Light Screen Designer, a program constructed in a series of 13 articles which were published in ZXC over a period of two years. I'd particularly recommend the Machine Code Calculator series, which gives detailed instructions on the workings & use of the ROM's FP calculator.

There were also machine code tutorial in C&VG (Dec'82-Oct'83), Personal Computer News (Apr-Jul'84), Pop.Com.Weekly (Sep'82-Jan'83), and Sinclair Programs (Apr'84-Aug'85, a mixture of BASIC & m/c).


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


** Adding Extra Commands to BASIC
   ==============================

At [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm]TTFn[/url] try these files, which use various methods for adding extra BASIC commands:
In the Your Computer section: Extended BASIC, Multitasking BASIC Extension.
In the Your Spectrum section: BASIC at a Stretch, Extending BASIC (If.1).
In the ZX Computing section: Extended Spectrum BASIC, Spectrum Extended BASIC (If.1).


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


** Renumbering routines
   ====================

There's two reasonable renumbering routines in the ZX Computing section at [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/typehome.htm]TTFn[/url], both of which handle SAVE ... LINE; the ones by Kenneth Baker and Matthew Homer. They don't handle VAL "" though. Even the renumber routine by David Webb in Supercharge Your Spectrum doesn't handle VAL "" numbers, probably because it's a calculated value, not a literal, and a VAL string might hold more than just a simple number to evaluate.


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


** Detecting the ZX Interface 1 ROM Version
   ========================================

Refer to Andrew Pennell's articles in Your Spectrum: [url=http://www.users.globalnet.co.uk/~jg27paw4/yr10/yr10_26.htm]Interface ROM: The Inside Story[/url] and [url=http://www.users.globalnet.co.uk/~jg27paw4/yr18/yr18_27.htm]New ROM Antics[/url].

This snippet seems particularly handy (from the second article):

The first thing you've got to do is find out exactly which ROM you've got. Here's the best way to do this:
SAVE *"m";1;"rom" CODE 237,1: LOAD *"m";1;"rom" CODE 23296: PRINT PEEK 23296
This then copies one byte from the shadow ROM into RAM and should print one of the following numbers:
ROM 1 ...25
ROM 2 ...113
ROM 3 ...115


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


** Paging Memory on the 48k Spectrum
   =================================

Here's a couple of related articles from Your Spectrum magazine:
[url=http://www.users.globalnet.co.uk/~jg27paw4/yr08/yr08_22.htm]Paging on Your Spectrum[/url] by Stephen Adams
[url=http://www.users.globalnet.co.uk/~jg27paw4/yr17/yr17_22.htm]Just A Tick![/url] by Toni Baker


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


** Converting .Z80 to .TAP
   =======================

You could try Gerton Lunter's Z802TAP.EXE which was one of the utilities included with his Z80 emulator.

Z802TAP - Converts .Z80 file to .TAP file - (c) 1999 G.A. Lunter - v4.00
Usage: Z802TAP [options]  inputfile[.Z80]  [outputfile[.TAP]]
Options are:
/u                  Do not compress the blocks
/s screen[.SCR]     Add loading screen 'screen.SCR'
 The .TAP file created contains the snapshot in a loadable format. It can be written to tape using TAP2TAPE and loaded into an ordinary Spectrum (128), but can of course also be loaded into the emulator.
 Z802TAP will not convert SamRam snapshots.

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

Z802TAP has a 1994 version 3.0, and a 1999 version 4.0 (which works OK on WinXP). The uncompressed output TAP is a short loader plus three 16k CODE blocks. I've tried it with The Rats and Lode Runner and after loading there's some initial screen corruption at the bottom, similar to what happens with a Multiface save, but the programs run OK.

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

Martijn van der Heide's Taper program has a Z80-TAP conversion option - but it doesn't work properly! It creates the loader and code blocks OK, but the loader crashes with a "Variable not found" error.

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

I've found the bug in the Taper Z80-TAP conversion loader. The start of the code is like this:

[code]
        CALL $0052               ; 23957 205   82  0
        DEC SP                   ; 23960 59
        DEC SP                   ; 23961 59
        POP HL                   ; 23962 225
        LD BC, 18                ; 23963 1   18  0
        ADD HL, BC               ; 23966 9
        LD DE, 48640             ; 23967 17  0   190
        LD BC, 512               ; 23970 1   0   2
        LDIR                     ; 23973 237 176
        JP 48640                 ; 23975 195 0   190
        NOP                      ; 23978 0
        DI                       ; 23979 243
        LD SP, 49152             ; 23980 49  0   192
        LD IX, 48812             ; 23983 221 33  172 190
        LD IY, 65391             ; 23987 253 33  111 255
        LD HL, 65082             ; 23991 33  58  254
        LD DE, 25464             ; 23994 17  120 99
        LD BC, 36                ; 23997 1   36  0
        EXX                      ; 24000 217
...
[/code]

which is run with RANDOMIZE USR (VARS) set at 23958. Unfortunately the BASIC has a missing leading quote in a PRINT; this causes the "Variable not found" error, as unquoted text is taken to be a variable name. This also makes the BASIC one byte shorter than expected, but the sizes in the tape header block have not taken the missing byte into account, so the VARS pointer is set one byte too high. So typing RANDOMIZE USR 23958 misses the unnecessary (as the USR address will be at the top of the stack anyway) CALL and does "LD D,D : NOP" instead. So instead of popping the expected 23961 it gets 23958 instead and thus the LDIR copies the wrong chunk. Changing the "LD BC,18" operand to 21 fixes it.

Taper converts between TZX & TAP as well (those options work, within the limitations of the TAP format).

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

I got a reply from Mr Lunter and he said that it's now OK to distribute the utility programs which originally came with the registered version of the emulator, so I've put the programs from the Z80 v4.0 Utils directory in [url=http://www.users.globalnet.co.uk/~jg27paw4/pourri/z80-emulator-dos-utils.zip]this ZIP file[/url].

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

Use Z802TAP v4.0 for 128k programs. I tried it with Where Time Stood Still and that worked OK as well. Other converters are available.


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


** Countdown Timer
   ===============

This [url=http://www.users.globalnet.co.uk/~jg27paw4/type-ins/yourspec/yr8403pp.zip]Alarm Clock[/url] routine from Your Spectrum does a countdown in hours & minutes, although it doesn't display anything while it's counting down, but that wouldn't take much to change.

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

Here's an example of how that Alarm Clock routine could be used to count down from a single 'start' number and jump to 'finish' when the counter reaches zero.

Countdown.BAS [code]
   3 REM [b]a: at the start[/b]
   5 CLEAR 65119
  10 LET start=111: LET h=65532: LET m=h+1: LET s=m+1:
     POKE h,INT (start/24):
     POKE m,INT ((start-PEEK h*24)/60):
     POKE s,start-PEEK h*24-PEEK m*60:
     LET finish=9000: RANDOMIZE USR 65120
1000 REM [b]b: somewhere in the main program loop[/b]
1001 LET hr=PEEK h: LET mi=PEEK m: LET se=PEEK s: LET hms=hr+mi+se:
     PRINT AT 0,0;hr;":";mi;":";se;"   ":
     GO TO (PEEK 23621+256*PEEK 23622+1 AND hms)+(finish AND NOT hms)
1002 PAUSE 50: GO TO 1001
9000 REM finish=9000
9001 STOP
[/code]

PEEK 23621+256*PEEK 23622+1 just gives the next line number, so that will work regardless of which line it's on; an explicit line number could also be used instead.


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


** Calculate the BEEP pitch
   ==========================

With the table here:

http://www.worldofspectrum.org/forums/showpost.php?p=429464&postcount=14

and the table here:

http://www.audiology.org/news/interviews/documents/chasinconversionchart.pdf

you should be able to do what you want.


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


** Get the starting address of a game
   ==================================

Set a break point with no address and a condition of "PC>=16384". On 48k models once it starts executing code in RAM then it's at the start of either the game or a custom loader. On 128k models the ROM executes code in the old printer buffer (also in RAMpage 7 from +3 disk), so set the break address above the system variables at 23734.


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


** A painter (enclose & fill) routine
   ==================================

The routine relies on the assumption that moving into a red border square changes it to blue (to avoid duplicate counting). Each box has a counter set to the number of squares in its border; each border square has associated with it a list of box numbers in which it appears as a border (max. 4/square, easier to always have 4, with 0 for "no box"). Each time a border square changes from red to blue, look up the boxes which contain that square and decrement their border counts; whenever a box's border count reaches zero then that box is enclosed. That would need 9 bytes for counters and 4*n bytes for the xref list, where 'n'=the number of distinct border squares.

--

If there were no more than 8 boxes, then it would just need 'n' bytes for the xref list, as the individual bits could be set for the box numbers.

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

It only needs one counter per box. Here's how I did it. Call Initialise once to draw the boxes and set the counters and call CountSquares after each move. A box gets filled when its counter decrements to zero. Calculating the fill area for each box could be moved from InBorder to Initialise and saved in Corners as it's not going to change.


Boxes_Corners.ASM [code]
        org  32768
;corner coordinate table:
;TopL:row,col, TopR:row,col, BotL:row,col, BotR:row,col, counter
        defw Initialise, CountSquares
CORNERS:DEFB 1,1, 1,8, 8,1, 8,8, 0
        defb 2,8, 2,15, 8,8, 8,15, 0
        defb 1,15, 1,22, 8,15, 8,22, 0
        defb 8,2, 8,9, 15,2, 15,9, 0
        defb 8,9, 8,14, 15,9, 15,14, 0
        defb 8,14, 8,21, 15,14, 15,21, 0
        defb 15,1, 15,8, 22,1, 22,8, 0
        defb 15,8, 15,15, 21,8, 21,15, 0
        defb 15,15, 15,22, 22,15, 22,22, 0
        DEFB $ff
[/code]

Boxes_Initialise.ASM [code]
Initialise: ;calculate the size of each border; call once
        ld   ix,CORNERS-9

Loop1:  ld   de,9
        add  ix,de
        ld   a,(ix+0)
        cp   $ff
        ret  z
        ld   a,(ix+3)        ;A =right col
        sub  (ix+1)          ;  -left col
        add  a,a             ;  *2
        ld   b,a
        ld   a,(ix+4)        ;A =bottom row
        sub  (ix+0)          ;  -top row
        add  a,a             ;  *2
        add  a,b
        ld   (ix+8),a        ;A =size of border

        ld   b,(ix+0)
        ld   c,(ix+1)        ;BC=TopL:row,col
        call GetAttrLoc
        ld   a,(ix+4)        ;A =BotL:row
        sub  b
        ld   b,a             ;B =row diff
        push bc
        push hl
        ld   a,(ix+3)        ;A =TopR:col
        inc  a
        sub  c
        ld   c,a             ;C =number of cols

        ld   d,b
        xor  a
        srl  d
        rra
        srl  d
        rra
        srl  d
        rra
        ld   e,a

        add  hl,de
        ex   de,hl           ;DE=bot row pointer
        pop  hl              ;HL=top row pointer
        push hl
        ld   a,16
        ld   b,c             ;B =number of cols
Loop2:  ld   (hl),a          ;draw a red box border
        ld   (de),a
        inc  hl
        inc  de
        djnz Loop2

        dec  hl              ;HL=R col pointer
        pop  de              ;DE=L col pointer
        ld   a,16
        ld   bc,32
        ex   af,af'
        pop  af              ;A =row diff
Loop3:  ex   af,af'
        ld   (hl),a          ;draw a red box border
        ld   (de),a
        add  hl,bc
        ex   de,hl
        add  hl,bc
        ex   de,hl
        ex   af,af'
        dec  a
        jr   nz,Loop3

        jp   Loop1
[/code]

Boxes_CountSquares.ASM [code]
CountSquares: ;check for enclosure; call after each move
        ld   ix,CORNERS-9
        ld   bc,(23680)      ;row,col coords in B,C
        call GetAttrLoc
        ld   a,(hl)
        and  %0011100
        cp   8
        ret  z               ;PAPER already blue, nothing to do
        cp   16
        ret  nz              ;PAPER not red, nothing to do
        ld   a,(hl)
        sub  8
        ld   (hl),a          ;PAPER change from red to blue
NextBox:
        ld   de,9
        add  ix,de
        ld   a,(ix+0)        ;A =top row
        cp   $ff
        ret  z               ;end of table
        cp   b
        jr   z,GotHrow
        ld   a,(ix+4)        ;A =bottom row
        cp   b
        jr   z,GotHrow
        ld   a,(ix+1)        ;A =left col
        cp   c
        jr   z,GotVcol
        ld   a,(ix+3)        ;A =right col
        cp   c
        jr   z,GotVcol
        jr   NextBox

GotHrow:;on either top or bottom row
        ld   a,(ix+1)        ;A =left col
        dec  a
        cp   c
        jr   nc,NextBox      ;too small
        ld   a,(ix+3)        ;A =right col
        cp   c
        jr   c,NextBox       ;too big
        jr   InBorder

GotVcol:;on either left or right col
        ;don't count top & bottom corners, already counted in GotHrow
        ld   a,(ix+0)        ;A =top row
        cp   b
        jr   nc,NextBox      ;too high
        ld   a,(ix+4)        ;A =bottom row
        dec  a
        cp   b
        jr   c,NextBox       ;too low

InBorder:
        dec  (ix+8)
        jp   nz,NextBox
        ;fill box
        push bc
        ld   b,(ix+0)
        ld   c,(ix+1)        ;BC=TopL:row,col
        inc  b
        inc  c
        call GetAttrLoc      ;HL=top left inner corner address
        ld   a,(ix+6)        ;A =BotR:row
        sub  b               ;  -TopL:row
        ld   d,a             ;D =number of rows inside box
        ld   a,(ix+7)        ;A =BotR:col
        sub  c               ;  -TopL:col
        ld   e,a             ;E =number of cols inside box

Loop4:  ld   b,e
        push hl
Loop5:  ld   (hl),24         ;PAPER magenta
        inc  hl
        djnz Loop5
        pop  hl
        ld   bc,32
        add  hl,bc
        dec  d
        jr   nz,Loop4
        pop  bc
        jp   NextBox

GetAttrLoc: ;calculate attribute address for row,col B,C
        ld   h,b
        xor  a
        srl  h
        rra
        srl  h
        rra
        srl  h
        rra
        or   c
        ld   l,a
        ld   a,h
        or   $58
        ld   h,a             ;(HL)=ATTR(B,C)
        ret
[/code]

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

CountSquares does that preliminary check and exits straight away if it doesn't need to do anything. The counter for each box is set to the length of its border. When moving onto a red square the square is changed to blue and the counter for each box which contains that square in its border is decremented. When a box's counter reaches zero it is filled. Nothing else needs to be considered.

It's slightly more complicated if the boxes have more than four corners, but then just divide the meta-box into boxes which do have four corners and fill it when all its counters run down to zero - or have a meta-box counter which holds the number of constituent boxes, decremented each time one of its box counters runs down to zero, and filled when its meta-counter runs down to zero.

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

The general case ... errmmm ..
* The screen is divided into a number of boxes.
* Boxes may be of varying shapes & sizes.
* Each box has a border.
* Boxes and borders are made up of ... errmmm ... blocks.
* Some parts of a box's border may be shared by other boxes.
* Movement is restricted to the borders but not otherwise.
* Setup defines each box's dimensions & each border's empty length.
* All border blocks are initially marked "empty" (eg. by colour).
* Each time an empty border block is entered it is marked "full" (eg. by changing colour).
* Each time a border block is marked "full" the empty length counter for each box which contains that block in their border is decremented.
* Each time a box's border's empty length runs down to zero (ie. it is full) then the box enclosed by this border is also filled. (Thus filling a shared border block might trigger the filling of more than one box.)
* When all the boxes have been filled the screen is complete.
The End


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


** Check if two rectangles intersect
   =================================

Using Einar's formula and a little bit of algebra to simplify it I came up with this routine which returns true if rectangle A is not outside rectangle B. There's a little demo and an explanation afterwards. Origin 0,0 is at bottom left; x1y1 is the rectangle's bottom left coordinate, x2y2 top right; as per Spectrum convention X is the lo-byte and Y the hi-byte.

AexB.ASM [code]
         org  40000
Ax1y1:   defw 0000
Ax2y2:   defw 0000
Bx1y1:   defw 0000
Bx2y2:   defw 0000
AexB:    and  a
         ccf                 ;set Carry
         ld   hl,(Ax1y1)
         ld   de,(Bx2y2)
         call check1
         ld   hl,(Bx1y1)
         ld   de,(Ax2y2)
         call check1
         ld   bc,1
         ret  c              ;A intersected by B
         dec  c
         ret                 ;A not intersected by B
check1:  ld   b,l
         ld   c,e
         call c,check2
         ld   b,h
         ld   c,d
         call c,check2
         ret
check2:  ld   a,b
         sub  c
         ret  c
         sub  1              ;must use SUB as DEC doesn't set Carry
         ret  c
         sub  1
         ret
[/code]

AexB.BAS [code]
  10 CLEAR 39999:
     PLOT 64,44: DRAW 128,0: DRAW 0,88: DRAW -128,0: DRAW 0,-88:
     REM bx1,by1=64,44 ; bx2,by2=192,132
  11 RANDOMIZE  44*256+64:  POKE 40004,PEEK 23670: POKE 40005,PEEK 23671:
     RANDOMIZE 132*256+192: POKE 40006,PEEK 23670: POKE 40007,PEEK 23671
  20 FOR x=48 TO 250 STEP 6: FOR y=30 TO 170 STEP 6:
       PLOT x,y: DRAW 5,0: DRAW 0,5: DRAW -5,0: DRAW 0,-5:
       RANDOMIZE y*256+x:         POKE 40000,PEEK 23670: POKE 40001,PEEK 23671:
       RANDOMIZE (y+5)*256+(x+5): POKE 40002,PEEK 23670: POKE 40003,PEEK 23671
  21   LET bc=USR 40008:
       PRINT AT 0,0;"A is ";"not " AND NOT bc;"intersected by B";TAB 0:
       PAUSE bc*10+NOT bc
  22   OVER 1:
       PLOT x,y: DRAW 5,0: DRAW 0,5: DRAW -5,0: DRAW 0,-5: PLOT x,y:
       OVER 0:
     NEXT y: NEXT x
[/code]

AexB.TXT [code]
widtha = ax2-ax1+1
condition = ax1 + (ax2-ax1+1) < bx1
          = ax2+1 < bx1 ... bx1-ax2 > 1 ... bx1-ax2-1 > 0
         or bx2+1 < ax1                     ax1-bx2-1 > 0
         or ay2+1 < by1                     by1-ay2-1 > 0
         or by2+1 < ay1                     ay1-by2-1 > 0

                  bot L B             bot L A
         if L,H = bx1,by1 and L',H' = ax1,ay1
                  top R A             top R B
            E,D = ax2,ay2     E',D' = bx2,by2
[/code]

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

   That was easier than I'd expected. Returns true if A is inside B.

AinB.ASM [code]
         org  40000
Ax1y1:   defw 0000
Ax2y2:   defw 0000
Bx1y1:   defw 0000
Bx2y2:   defw 0000
AinB:    and  a
         ccf                 ;set Carry
         ld   hl,(Ax1y1)
         ld   de,(Bx1y1)
         call check1
         ld   hl,(Bx2y2)
         ld   de,(Ax2y2)
         call c,check1
         ld   bc,1
         ret  c              ;A inside by B
         dec  c
         ret                 ;A not inside by B
check1:  ld   a,e
         sub  l
         ret  nc
         ld   a,d
         sub  h
         ret
[/code]


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