Mastering Machine Code on Your ZX81
By Toni Baker

Sinclair ZX Spectrum
GRAPHICS GAMES

SPIRALS

In this fast moving real-time graphics game (intended for use with SLOW) you are placed at the start of a square spiral and must reach the end of it in the minimum possible time. Your score is constantly displayed - it starts of at 99900 and decrements continuously, but you can't cheat by breaking out early with a high score - the program won't allow that. Now and again the score will reach zero before you reach the end of the spiral. If that happens you obviously need more practice!

This fascinating and highly amusing game is unfortunately for NEW ROM users with SLOW only. It will not work in FAST because although the program will still consider itself to be running perfectly smoothly, the average human operator won't know what's going on because of the fact that the screen in front of them is completely black.

This is a fascinating game to watch - witnessing the score decrease before your very eyes is surprisingly effective. You can make the game as difficult as you like by altering the initial value of the "timing" - held in BC. I've given it 0400, but you could use 0800 for a slower game, 0200 for a faster game, and so on.

There is one difficulty built in though - if you hit a wall you don't just bounce off, you actually become embedded in it, and the only way you can get out is to exactly reverse your direction. It can be quite tricky.

Well good luck on your race - keep a record of the high scores (no cheating) and see if you can master it.

The keys will move you as follows: Any key on the bottom row will move you downwards (except for shift, which has no effect), any key on the top row moves you up. The middle two rows move you left and right, with the lefthand ten keys (QWERTASDFG) moving you to the left, and the ten righthand keys (YUIOPHJKLn/l) moving you to the right. This system was adopted instead of using the cursor controls 5, 6, 7, and 8 for two reasons.

  • It is easier for people to understand and become familiar with.
  • It is easier to program, since we only need to test one register after the keyboard scan instead of two.
The program lists as follows, and can be relocated to any desired location by changing just one [two] addresses. The program should be called from the point labelled START.

E1        SPRINT    POP HL              This subroutine prints
7E                  LD A,(HL)           out a picture of the board,
23                  INC HL              along with your initial
E5                  PUSH HL             score. It must however be
FEFF                CP FF               provided with a list of
C8                  RET Z               data terminated by FF.
D7                  RST 10
18F6                JR SPRINT
CD-sprint  START    CALL SPRINT         Calls the subroutine. The
                                        following is data for the
                                        subroutine.
DEFB 80 80 80 80 80 80 80 80 80 80 80 76
     80 15 80 00 00 00 00 00 00 00 00 80 76
     80 00 80 00 80 80 80 80 80 00 80 76
     80 00 80 00 80 00 00 00 80 00 80 76
     80 00 80 00 80 00 80 00 80 00 80 76
     80 00 80 00 80 80 80 00 80 00 80 76
     80 00 80 00 00 00 00 00 80 00 80 76
     80 00 80 80 80 80 80 80 80 00 80 76
     80 00 00 00 00 00 00 00 00 00 00 80 76
     80 80 80 80 80 80 80 80 80 80 80 76
     76
     3E 34 3A 37 00 38 28 34 37 2A 00 33 34 3C 00 25 25 25 1C 1C FF

2A0C40    SETUP     LD HL,(D_FILE)      This section initialises the
110E00              LD DE,000E          two "variables" used in our
19                  ADD HL,DE           program.
227B40              LD (POSITION),HL
210000              LD HL,0000
227940              LD (LASTMOVE),HL
2A0C40    LOOP      LD HL,(D_FILE)      Decrement the score.
118B00              LD DE,008B
19                  ADD HL,DE
7E        DECIMAL   LD A,(HL)
A7                  AND A
2008                JR NZ,POSITIVE      ###########
0605                LD B,05             #+#       #
23        RESET     INC HL              # # ##### #
361C                LD (HL),1C          # # #   # #
10FB                DJNZ RESET          # # # # # #
C9                  RET                 # # ### # #
3D        POSITIVE  DEC A               # #     # #
FE1B                CP 1B               # ####### #
2005                JR NZ,OK            #         #
3625                LD (HL),25          ###########
2B                  DEC HL
18EA                JR DECIMAL
77        OK        LD (HL),A
010004              LD BC,SPEED         A timed delay. Altering the
0B        DELAY     DEC BC              initial value of BC changes
78                  LD A,B              the speed of the game.
B1                  OR C
20FB                JR NZ,DELAY
CDBB02              CALL KSCAN          Scan keyboard. L now contains
7D                  LD A,L              a value corresponding to the
2F                  CPL                 direction required.
6F                  LD L,A
E681                AND 81
2805                JR Z,NOTDOWN        Find direction.
110C00              LD DE,000C
181C                JR CHKMOVE
7D        NOTDOWN   LD A,L
E618                AND 18
2805                JR Z,NOTUP
11F4FF              LD DE,FFF4
1812                JR CHKMOVE
7D        NOTUP     LD A,L
E660                AND 60
2805                JR Z,NOTRIGHT
110100              LD DE,0001
1808                JR CHKMOVE
7D        NOTRIGHT  LD A,L
E606                AND 6
28B2                JR Z,LOOP
11FFFF              LD DE,FFFF
2A7940    CHKMOVE   LD HL,(LASTMOVE)    Is player embedded in wall?
7D                  LD A,L
B4                  OR H
2807                JR Z,MOVE
19                  ADD HL,DE           If so, is player reversing?
7D                  LD A,L
B4                  OR H
2802                JR Z,MOVE
18A1                JR LOOP
2A7B40    MOVE      LD HL,(POSITION)    Reassign square with black
7E                  LD A,(HL)           or white space as required.
E680                AND 80
77                  LD (HL),A
19                  ADD HL,DE           Find new position.
7E                  LD A,(HL)           Draw black or white cross
F615                OR 15               as appropriate.
77                  LD (HL),A
227B40              LD (POSITION),HL
210000              LD HL,0000          Store direction moved if
17                  RLA                 a wall has been hit.
3002                JR NC,NOTHIT
62                  LD H,D
6B                  LD L,E
227940    NOTHIT    LD (LASTMOVE),HL
2A0C40              LD HL,(D_FILE)      Check to see whether the
113600              LD DE,0036          finished square has been
19                  ADD HL,DE           reached.
ED5B7B40            LD DE,(POSITION)
ED52                SBC HL,DE
C8                  RET Z
C3-loop             JP LOOP

Download available for 16K ZX81 -> chapter14-spirals.p.
[As I used HEXLD3D to load this high, the instructions are as follows: Set RAMTOP to 4A00 (18944) with POKE 16389,74/NEW. Type RUN to play the game. Addresses used: 4A82 to 4B77 is occupied by HEXLD3D, SPRINT is 4B82, START is 4B8C (19340), LOOP is 4C2D and the timing value SPEED (default 0400) is held at 4C4C which you'll probably want to double to 0800.]

BREAKOUT

In this version of BREAKOUT, which incidentally may only be run on a NEW ROM in SLOW, you move the bat with any of the keys on the keyboard - those on the left will move you to the left, and those on the right will move you to the right. The game is intended to be played only by those people with 3.25K or more, but it can be persuaded to run in less if the following few lines of machine code are added to the program - these should precede the main program:

FD362200  EXTRA     LD (IY+22),00
210003              LD HL,0300
AF        SPACES    XOR A
D7                  RST 10
2B                  DEC HL
7C                  LD A,H
B5                  OR L
20F9                JR NZ,SPACES

The reason for this is that the main BREAKOUT program assumes that the screen is initially completely full - that is, that it contains twenty-four rows, each consisting of thirty-two spaces followed by a newline. If your machine has less than 3.25K on board then this will not be so, because of the way that the ROM sets up the screen. To rectify this we first LD (IY+22) with 00. IY is always 4000 at the start of any USR routine, so IY+22 is 4022, which is the system variable DF_SZ. This represents the number of rows in the bottom half of the screen (the part we cannot print to) - by telling the machine that this number is zero it follows that the number of rows that we cannot print to is also zero, thus the whole screen is at our disposal. HL counts the number of spaces to be printed to ensure that we do not try to run off the end of the screen.

BREAKOUT is a program in four parts. These parts are:

  • Initialise everything.
  • Restart the game for each new ball.
  • Move the ball.
  • Move the bat.
We will go over each of these steps in scrutinous detail.

Firstly to initialise everything. This involves a) printing the playing board, b) defining the initial ball position, and c) setting the initial speed of the game. To print the board:

20002200 TABLESTART DEFW 0020 0022
E0FFDEFF            DEFW FFE0 FFDE
2A0C40    BREAKOUT  LD HL,(D_FILE)    Load all of the bricks into
118500              LD DE,0085        position.
19                  ADD HL,DE
018080              LD BC,8080        B is the number of bricks, C is a
23        NXBRK     INC HL            constant used quite frequently in
7E                  LD A,(HL)         this section.
FE76                CP 76
28FA                JR Z,NXBRK
3608                LD (HL),08
10F6                DJNZ NXBRK
2A0C40              LD HL,(D_FILE)    Put top wall in position.
061E                LD B,1E           This part puts in the first thirty
23        NXBL      INC HL            blocks.
71                  LD (HL),C
10FC                DJNZ NXBL
23                  INC HL
369C                LD (HL),9C        The current score -zero - is
23                  INC HL            entered.
71                  LD (HL),C         The last block is set in place.
23                  INC HL
23                  INC HL
111F00              LD DE,001F        DE is one more than the number of
0617                LD B,17           spaces between the walls.
71        SIDES     LD (HL),C         Both side walls are loaded into
19                  ADD HL,DE         position.
71                  LD (HL),C
23                  INC HL
23                  INC HL
10F9                DJNZ SIDES
0620                LD B,20           Now the base-line is drawn in.
361B      BASE      LD (HL),1B
23                  INC HL
10FB                DJNZ BASE

You'll notice that in this version of the game I've ensured that a row of full stops is printed below the very bottom of the screen. This provides a convenient test for whether or not the ball has hit the base. Finally, to set the ball position and speed, the procedure is:

11FCFE              LD DE,FEFC          This is the displacement from
                                        the current print position to
                                        the ball's starting point.
19                  ADD HL,DE           Locate this starting point.
223C40              LD (BALLINIT),HL    Store it.
210009              LD HL,0900          This is the initial speed.
224640              LD (SPEED),HL       Store it.

This is actually all the initialisation we need. You'll notice several things missing - for example although the ball is located it is not actually printed. The bat is not mentioned at all! The reason is that the bat is redrawn every time the game is restarted, and so is the ball. Why bother to find the initial position then? Well in this version, the ball starts off in a slightly different position each time. This ensures that it is possible to wipe out all of the bricks.

The variable SPEED has a dual purpose. Firstly it determines the speed of the game - that is, the speed at which the bat and ball will move (the bat moves at precisely twice the ball speed), but secondly it determines when the game is over. When SPEED decrements to zero (the lower the number, the faster the game) we know that the game is over.

Section two of the game does the following tasks:

  • Change the initial ball position, whilst also noting the current ball position and printing the ball.
  • Set the initial direction of movement of the ball to up/right.
  • Change the speed of the game and check for end of game.
  • Print the bat, and at the same time delete any previous bat symbol that may have been there.
  • Give the human player a chance to recover from the last session, since presumably [he/]she won't want one ball to leap into the game immediately [after] the last one vanishes.
The section is this. Look at the manner in which the bat is printed and the previous bat overwritten.

2A3C40    RESTART   LD HL,(BALLINIT)     Change the starting
23                  INC HL               position of the ball.
223C40              LD (BALLINIT),HL
224040              LD (BALLPOS),HL      Start the ball here.
3634                LD (HL),34           Print the ball.
21E0FF              LD HL,FFE0           Set the initial direction.
224440              LD (DIRECTION),HL
3A4740              LD A,(SPEED)high     Increase the speed.
3D                  DEC A
C8                  RET Z                Return to BASIC if lives have
324740              LD (SPEED)high,A     run out.
2A0C40              LD HL,(D_FILE)       Reprint the bat in its
11B702              LD DE,02B7           starting position.
19                  ADD HL,DE
3600                LD (HL),00
3E03                LD A,03              A contains the bat symbol.
23                  INC HL
77                  LD (HL),A
23                  INC HL
77                  LD (HL),A
23                  INC HL
77                  LD (HL),A
224240              LD (BATPOS),HL       Store the initial bat
23                  INC HL               position (this is the
77                  LD (HL),A            position of the centre of the
23                  INC HL               bat).
77                  LD (HL),A
0618                LD B,18              Now erase the rest of the
23        ERASE     INC HL               row, in case a previous bat
3600                LD (HL),00           symbol remains there.
10FB                DJNZ ERASE
210000              LD HL,0000           Set a very long delay, for
1803                JR DELAY             the player to recover for the
                                         next ball.

The last two lines, which cause a short pause between sessions, will become clear when the start of the next section is given.

To move the ball we first of all go through a timed delay loop (controlled by SPEED - the speed of the game) and then unprint the previous position of the ball. The contents of the next square in the direction the ball is travelling are examined, and one of the following will happen:

  • If a full stop has been reached then the ball has gone off the bottom of the screen - the game is restarted.
  • If either a space (i.e. nothing hit) or a brick is located, the ball is reprinted, at this new position.
  • If anything other than a space is reached, the direction of movement of the ball is changed at random.
  • If the ball was notreprinted then find the contents of the next square in this new direction and re-examine the situation.
  • If a brick was hit, the score is increased by 1.
Now, in order that we may choose a new random direction validly we require a table of directions to choose from. These valid directions are 0020, 0022, FFE0, and FFDE. You should store these numbers, low part first, at any address in RAM, and call the start of this table TABLESTART. The program which will then achieve all of this is as follows:

2A4640    LOOP      LD HL,(SPEED)        This is a short delay loop
2B        DELAY     DEC HL               which controls the speed of
7C                  LD A,H               the game.
B5                  OR L
20FB                JR NZ,DELAY
04                  INC B                The ball is only moved every
CB40                BIT 0,B              other time round the loop, so
205A                JR NZ,MOVEBAT        that the bat moves twice as
                                         fast as the ball.
2A4040    MOVEBALL  LD HL,(BALLPOS)      Current ball position is found.
3600                LD (HL),00           Erase the ball.
ED5B4440            LD DE,(DIRECTION)    Find next position of the ball.
19                  ADD HL,DE
7E                  LD A,(HL)            Find the contents of this new
FE1B                CP 1B                position.
28A6                JR Z,RESTART         Has the ball hit the base?
4F                  LD C,A               Start next ball if so.
E6F7                AND F7
2005                JR NZ,DONTMOVE       Only reprint the ball if the
3634                LD (HL),34           new position is either empty
224040              LD (BALLPOS),HL      or contains a brick.
B1        DONTMOVE  OR C                 Retrieve previous contents.
283E                JR Z,MOVEBAT         Change direction if not a
E5                  PUSH HL              space.
2A3240              LD HL,(SEED)         Generate new direction at
54                  LD D,H               random.
5D                  LD E,L
29                  ADD HL,HL
29                  ADD HL,HL
19                  ADD HL,DE
29                  ADD HL,HL
29                  ADD HL,HL
29                  ADD HL,HL
19                  ADD HL,DE
223240              LD (SEED),HL
7C                  LD A,H               Choose this direction from a
E606                AND 06               table.
C6tablestartlow     ADD A,TABLESTARTlow
6F                  LD L,A
26tablestarthigh    LD H,TABLESTARThigh
5E                  LD E,(HL)
23                  INC HL
56                  LD D,(HL)
ED534440            LD (DIRECTION),DE
E1                  POP HL
79                  LD A,C               If the contents of the square
FE08                CP 08                is not a brick, then move
20BF                JR NZ,MOVEBALL       again.
2A0C40              LD HL,(D_FILE)       Having established that a
111F00              LD DE,001F           brick has been hit, the score
19                  ADD HL,DE            is increased by one.
7E                  LD A,(HL)
FE80                CP 80
2002                JR NZ,DIGIT
3E9C                LA A,9C
3C        DIGIT     INC A
FEA6                CP A6
2005                JR NZ,INCREASED
369C                LD (HL),9C
2B                  DEC HL
18EF                JR CARRY
77        INCREASED LD (HL),A

An interesting point to watch for is the way in which the score is increased. Compare the mechanism to that used in SPIRALS to decrease the score. There are one or two differences between this and the last. Firstly of course we are here using INVERSE digits instead of ordinary digits, though this difference is rather trivial. Secondly the BREAKOUT score increases instead of decreases. Thirdly, the SPIRALS score would terminate at zero, whereas the BREAKOUT score can increase indefinitely.

To move the bat, first of all the keyboard is scanned, and if a left-hand key is pressed [then] the bat is moved to the left, provided of course there is not a wall in the way, and if a right-hand key is pressed then the bat is moved to the right, if possible. Note that if a left and right key are pressed simultaneously [then] the bat should not move at all. In our program such a circumstance would cause the bat to move first to the left, and then to the right.

Study this, the final part of the program, and watch the way the bat is actually moved. Remember that the variable BATPOS stores the position of the middle of the bat.

C5        MOVEBAT   PUSH BC           Preserve the value of B.
CDBB02              CALL KSCAN        Scan the keyboard.
C1                  POP BC
7D                  LD A,L
2F                  CPL
F5                  PUSH AF           Stack contains a value
E60F                AND 0F            corresponding to the key pressed.
2817                JR Z,NOTLEFT      If the player moves left....
2A4240              LD HL,(BATPOS)    Locate the bat.
2B                  DEC HL
2B                  DEC HL
2B                  DEC HL
7E                  LD A,(HL)         Is there a wall to our left?
FE80                CP 80             If so, don't move.
2829                JR Z,CYCLE1
3603                LD (HL),03        Extend the bat to the left.
23                  INC HL
23                  INC HL
224240              LD )BATPOS),HL    Store new bat position.
23                  INC HL
23                  INC HL
23                  INC HL
3600                LD (HL),00        Decrease bat to the right.
F1        NOTLEFT   POP AF
E6F0                AND F0
2819                JR Z,CYCLE2       If the player moves right....
2A4240              LD HL,(BATPOS)
23                  INC HL
23                  INC HL
23                  INC HL
7E                  LD A,(HL)         Is there a wall to the right?
FE80                CP 80             If so, don't move.
280E                JR Z,CYCLE2
3603                LD (HL),03        Extend the bat to the right.
2B                  DEC HL
2B                  DEC HL
224240              LD (BATPOS),HL    Store new bat position.
2B                  DEC HL
2B                  DEC HL
2B                  DEC HL
3600                LD (HL),00        Decrease bat to the left.
E5                  PUSH HL           Same for next time round.
E1        CYCLE1    POP HL
C3loop    CYCLE2    JP LOOP

Download available for 16K ZX81 -> chapter14-breakout.p.
[I used HEXLD3D to load this high, but if you set RAMTOP to 4A00 (18944) the game won't run (perhaps HEXLD3D and the game's machine code should have been higher - who knows) so don't set RAMTOP to anything. Type RUN to play the game. Addresses used: 4A82 to 4B77 is occupied by HEXLD3D, TABLESTART is 4B78, BREAKOUT is 4B80 (19328) and LOOP is 4C02. The initial timing value (default 0900) is held at 4BBF which you may want to increase.]

Sinclair ZX Spectrum

  Previous Page Back Next Page