.........1.........2.........3.........4.........5.........6.........7.........8

LIGHT SCREEN DESIGNER
ZX Computing, October/November 1984
Part 03 of 13

Toni Baker completes the structure of our Spectrum graphics package,
and describes the cursors generated by the program.


In this part of the series we tie up all the loose ends (and leave a
few more in the process). Once you have part three, the program will
be unified and even partially usable - no longer a fragmented
assortment of meaningless subroutines. The building blocks we
constructed in the first two parts will be drawn together (to become a
foundation in whose structures should be evident the organisation of
the, as yet, unfinished whole). We start off part three with two
rather weird subroutines, but we quickly progress to the
initialisation and main loop of the program.


Cursor positions

As we explained earlier, cursor positions are held in four registers:
B and C, which store the row and column numbers respectively and
register pair HL, which stores the address of the byte within the
display file which contains the given pixel. These four bytes may be
stored in memory in the order LHCB, so bytes one and two between them
contain an address, byte three contains the column number, and byte
four contains the row number. We also have the additional convention
that byte four may contain the value FF - this is just a signal
meaning "this cursor is not in use". The following subroutine is used
to give initial values to some of the cursors. It performs the
following task: if the cursor is not in use, then reset the cursor to
coordinates 0,0. It assumes that HL is already pointing to byte four.

-----------------------------------------------------------------------------
;Cursor Initialisation subroutine
DDA3 34       RESCURSOR INC  (HL)          ;Is byte four FF?
DDA4 200E               JR   NZ,RC_EXIT    ;Jump if not, restoring value of byte
DDA6 2B                 DEC  HL
DDA7 2B                 DEC  HL
DDA8 2B                 DEC  HL            ;Point to byte one
DDA9 3600               LD   (HL),#00
DDAB 23                 INC  HL
DDAC 3640               LD   (HL),#40      ;Specify cursor address 4000h
                                           ;(top left-hand corner of screen)
DDAE 23                 INC  HL
DDAF 3600               LD   (HL),#00      ;Specify column number zero
DDB1 23                 INC  HL
DDB2 3601               LD   (HL),#01      ;Specify row number zero
                                           ;(because of next instruction)
DDB4 35       RC_EXIT   DEC  (HL)
DDB5 C9                 RET
-----------------------------------------------------------------------------


Origin, marker and cursor

I'd like to introduce you to three cursors now. They are called
ORIGIN, MARKER and CURSOR. The second one - MARKER - is not always
used, but ORIGIN and CURSOR always are. Basically, ORIGIN marks the
start of the line, and CURSOR marks the end. Each of these cursors is
stored amongst the program variables between DB00 and DB41. Each
occupies four bytes - ORIGIN from DB0C to DB0F, MARKER from DB10 to
DB13, and CURSOR from DB14 to DB17. This next subroutine draws each of
the three cursors onto the screen (if they are in use). It relies upon
the subroutine DR_CURSOR which was listed in Part two.

-----------------------------------------------------------------------------
;Routine to draw each of the three cursors: ORIGIN, MARKER and CURSOR
DDB6 D5       DRCURSORS PUSH DE
DDB7 210CDB             LD   HL,ORIGIN     ;HL contains address of program
                                           ;variable named ORIGIN
DDBA 0603               LD   B,#03
DDBC C5       DCS_LOOP  PUSH BC
DDBD 5E                 LD   E,(HL)
DDBE 23                 INC  HL
DDBF 56                 LD   D,(HL)        ;DE= address of cursor within
                                           ;display file
DDC0 23                 INC  HL
DDC1 4E                 LD   C,(HL)        ;C= column number of cursor position
DDC2 23                 INC  HL
DDC3 46                 LD   B,(HL)        ;B= row number of cursor position
DDC4 23                 INC  HL            ;Point HL to next variable
DDC5 78                 LD   A,B           ;A= row number, or FF if cursor not
                                           ;in use
DDC6 FEFF               CP   #FF
DDC8 2806               JR   Z,DCS_CONT    ;Jump if cursor not in use
DDCA E5                 PUSH HL
DDCB EB                 EX   DE,HL         ;HL= address of cursor
DDCC CD6CDD             CALL DR_CURSOR     ;Draw specified cursor
DDCF E1                 POP  HL            ;HL points to next variable
DDD0 C1       DCS_CONT  POP  BC            ;B = loop count
DDD1 10E9               DJNZ DCS_LOOP
DDD3 D1                 POP  DE            ;Leave original DE unaltered
DDD4 C9                 RET
-----------------------------------------------------------------------------


Calling the program

Now we come to the START of the program. The whole program may be run
simply by calling the machine code from the START address. In hex,
this address is DDD5, but by an absolutely astounding coincidence
(what?) the START address in decimal is the highly memorable 56789.
The BASIC instruction RANDOMIZE USR 56789 will call upon "Light Screen
Designer" to do its work. To use this program you have nothing to
remember except 56789. Got it?

In fact, USR 56789 can be used for two entirely different reasons - to
begin a picture, or to CONTINUE with a picture, since with this
program you are free to hop back and forth between BASIC and machine
code as much as you wish. Note that a BASIC CLS instruction is needed
before a new picture is started.

The program distinguishes between the two different forms of start by
quite an ingenious method. To BEGIN with, none of the cursors will be
self-consistent (that is - if HLK says one thing, BC will say
another). Whereas, if a drawing is being continued then each cursor
will be consistent.

The program uses a total of sixteen different cursors. Each of them
occupies four bytes, and each of them lives between DB00 and DB3F.
Watch how the initialisation sequence works. You may find it helpful
to look at at the top part of Figure 1 [PART03.GIF] in order to follow
the program through.

-----------------------------------------------------------------------------
;Complete program initialisation subroutine
DDD5 CDCCDC   START     CALL MESSAGE
DDD8 01                 DEFB 01            ;Print messg "Light Screen Designer"
DDD9 DD2140DB           LD   IX,#DB40      ;Initialise IX as required

;[In part 9 everything from #DDDD-#DE1A was moved down by 4 bytes,   ]
;[and the "LD IX,#DB40" above moved to the end, becoming the new     ]
;[start point for MAIN_LOOP at #DE17. The "LD HL,MAIN_LOOP" (which by]
;[Part 9 was at #DE52) was changed accordingly.                  JimG]

DDDD CDBF16             CALL SET_WORK      ;Maximise spare RAM space
DDE0 2100DB             LD   HL,ORIGIN2    ;Point HL to first program variable
DDE3 0610               LD   B,#10
DDE5 C5       LSD_LOOP  PUSH BC
DDE6 5E                 LD   E,(HL)
DDE7 23                 INC  HL
DDE8 56                 LD   D,(HL)        ;DE= address of cursor, if assigned
DDE9 23                 INC  HL
DDEA 4E                 LD   C,(HL)        ;C= col number of cursor, if assigned
DDEB 23                 INC  HL
DDEC 46                 LD   B,(HL)        ;B= row number of cursor, if assigned
DDED 78                 LD   A,B
DDEE FEB0               CP   #B0
DDF0 300A               JR   NC,LSD_RESET  ;Jump if row number out of range
DDF2 E5                 PUSH HL            ;HL= address corresponding to row
DDF3 CD41DD             CALL PIX_ADDR      ;and column numbers
DDF6 A7                 AND  A
DDF7 ED52               SBC  HL,DE         ;Set zero flag if this address
                                           ;matches to the one given
DDF9 E1                 POP  HL            ;HL points to byte four of program
                                           ;variable
DDFA 2802               JR   Z,LSD_OK      ;Jump if cursor position is already
                                           ;assigned
DDFC 36FF     LSD_RESET LD   (HL),#FF      ;Signal "cursor not in use"
DDFE 23       LSD_OK    INC  HL            ;Point to byte one of next program
                                           ;variable
DDFF C1                 POP  BC
DE00 10E3               DJNZ LSD_LOOP      ;Repeat for all cursors
DE02 70                 LD   (HL),B
DE03 23                 INC  HL
DE04 70                 LD   (HL),B        ;Reset (JFLAGS)
DE05 210FDB             LD   HL,ORIGIIN+3  ;HL points to byte four of (ORIGIN)
DE08 CDA3DD             CALL RESCURSOR     ;Reset origin-cursor to 0,0
                                           ;if not in use
DE0B 2E17               LD   L,#17         ;HL points to byte four of (CURSOR)
DE0D CDA3DD             CALL RESCURSOR     ;Reset main-cursor to 0,0
                                           ;if not in use
DE10 CD8E02   LSD_READY CALL KEY_SCAN      ;DE= immediate keyboard scan
DE13 7B                 LD   A,E           ;A= key code ignoring shift
DE14 FE20               CP   #20
DE16 20F8               JR   NZ,LSD_READY  ;Wait until "ESCAPE" key pressed
DE18 CD6E0D             CALL CLS_LOWER     ;Clear lower part of screen
                                           ;to erase message
-----------------------------------------------------------------------------


Main loop program

Now we come to the main loop program. If you take a look at Figure 1
you'll see I've drawn a flow diagram to show how it works. I don't
often do flow diagrams but this was one of those rare exceptions where
I did. If you follow the workings of the flow diagram you'll see that
the cursors are only on the screen whilst waiting for a key to be
pressed. Once a key is detected the cursors are erased before any
further action is taken. One thing to watch out for though, is the
fact that the keys do not repeat automatically. However, the "shift"
key activates a repeat facility for the cursor keys only. Can you see
how this is achieved?

"Shift" with any other key will result in the possibility of returning
to BASIC. Incidentally, don't worry about the copying the screen part
- all will be revealed later on.

-----------------------------------------------------------------------------
;Main program loop [This was all rewritten in Part 6. JimG]
DE1B CDB6DD   MAIN_LOOP CALL DRCURSORS     ;Draw cursors on screen
DE1E CDB5DC             CALL GET_CHR       ;DE= keyboard scan
DE21 CDB6DD   MAINLOOP2 CALL DRCURSORS     ;Undraw cursors
DE24 2A14DB             LD   HL,(CURSOR)   ;HL= address of main-cursor
DE27 ED4B16DB           LD   BC,(CURSOR+2) ;BC= coordinates of cursor
DE2B D5                 PUSH DE
DE2C 7B                 LD   A,E           ;A= key code ignoring shift
DE2D FE03               CP   #03
DE2F 2853               JR   Z,CSR_DOWN    ;Jump if "cursor down" pressed
DE31 FE04               CP   #04
DE33 2840               JR   Z,CSR_LEFT    ;Jump if "cursor left" pressed
DE35 FE0B               CP   #0B
DE37 2846               JR   Z,CSR_UP      ;Jump if "cursor up" pressed
DE39 FE13               CP   #13
DE3B 283D               JR   Z,CSR_RIGHT   ;Jump if "cursor right" pressed
;[------- This next part was replaced in Part 4 and corrected in Part 5  ]
;[------- (and then the whole of MAIN_LOOP was rewritten in Part 6). JimG]
DE3D DDCB0166           BIT  4,(JFLAGS) hi
DE41 2014               JR   NZ,ML_ACTION  ;Jump unless screen requires copying
DE43 210040             LD   HL,#4000      ;D_FILE
DE46 1100C0             LD   DE,#C000      ;D_FILE_2
DE49 01001B             LD   BC,#1B00
DE4C EDB0               LDIR               ;Copy screen
DE4E 210CDB             LD   HL,#DB0C      ;CURSOR
DE51 1100DB             LD   DE,#DB00      ;CURSOR_2
DE54 4D                 LD   C,L           ;Note: BC= 000C
DE55 EDB0               LDIR               ;Copy cursors
;[-------
DE57 D1       ML_ACTION POP  DE            ;DE= keyboard scan
DE58 211BDE             LD   HL,#DE1B      ;MAIN_LOOP
DE5B E5                 PUSH HL            ;Force subroutine return address
                                           ;to be MAIN_LOOP
DE5C 2142DB             LD   HL,#DB42      ;CMD_ADDRS
DE5F 14                 INC  D
DE60 2803               JR   Z,ML_CASE     ;Jump unless "shift" pressed
DE62 E1                 POP  HL            ;Balance stack
DE63 1839               JR   RET_BASIC     ;Prepare to return to BASIC
DE65 7B       ML_CASE   LD   A,E           ;A= key code
DE66 87                 ADD  A,A
DE67 85                 ADD  A,L
DE68 6F                 LD   L,A           ;HL points to subroutine address
DE69 4E                 LD   C,(HL)
DE6A 23                 INC  HL
DE6B 46                 LD   B,(HL)        ;BC= subroutine address
DE6C C5                 PUSH BC            ;Stack subroutine address
DE6D 2A14DB             LD   HL,(CURSOR)   ;HL= main-cursor address
DE70 ED4B16DB           LD   BC,(CURSOR+2) ;BC= main-cursor coordinates
DE74 C9                 RET                ;Call required subroutine
DE75 CD13DD   CSR_LEFT  CALL LEFT_PIX      ;Move cursor left
DE78 180D               JR   CSR_STORE
DE7A CD1FDD   CSR_RIGHT CALL RIGHT_PIX     ;Move cursor right
DE7D 1808               JR   CSR_STORE
DE7F CD29DD   CSR_UP    CALL UP_PIX        ;Move cursor up
DE82 1803               JR   CSR_STORE
DE84 CD36DD   CSR_DOWN  CALL DOWN_PIX      ;Move cursor down
DE87 3807     CSR_STORE JR   C,CSR_EXIT    ;Jump if cursor cannot move
DE89 2214DB             LD   (CURSOR),HL   ;Store new cursor position
DE8C ED4316DB           LD   (CURSOR+2),BC ;Store new cursor coordinates
DE90 D1       CSR_EXIT  POP  DE            ;DE= keyboard scan
DE91 14                 INC  D
DE92 2887               JR   Z,MAIN_LOOP   ;Loop back unless "shift" pressed
DE94 CDB6DD             CALL DRCURSORS     ;Draw cursors on screen
DE97 76                 HALT
DE98 76                 HALT               ;Wait for 1/25 of a second
DE99 CDC0DC             CALL GET_CHR_2     ;DE= keyboard scan
DE9C 1883               JR   MAINLOOP2     ;Loop back
-----------------------------------------------------------------------------


Returning to BASIC

And now for the final piece of code for this issue ... This is the
RETURN TO BASIC part. It will ask you whether or not you wish to
return to BASIC and will do so only if you reply "Y". (You may
alternatively reply "N" or "escape"). Incidentally the RETURN TO BASIC
option should in fact be available directly (without shift), just by
pressing the "escape" button. To set this up, you should POKE address
DB82 with A4, and address DB83 with DE. This effectively stores the
address of an appropriate RETURN TO BASIC subroutine amongst the
COMMAND ADDRESS table which runs between DB42 and DB8F.

The skeleton of the program is now complete. In the next part I shall
start filling in some of the functions available.

-----------------------------------------------------------------------------
;Subroutine to detect and comply with a RETURN TO BASIC request
DE9E CDA4DE   RET_BASIC CALL ESCAPE        ;Ask whether BASIC wanted
DEA1 C31BDE             JP   MAIN_LOOP     ;Jump back to main loop if not
DEA4 CDCCDC   ESCAPE    CALL MESSAGE       ;Print message and await reply
DEA7 12                 DEFB #12
DEA8 FE59               CP   "Y"
DEAA C0                 RET  NZ            ;Return unless reply was "Y"
DEAB F1                 POP  AF            ;Empty the stack
DEAC C9                 RET                ;Return to BASIC
-----------------------------------------------------------------------------
