This tutorial by Jon Kingsman (bigjon) originally appeared in a thread on WoSF. Reproduced with permission.
Roadrace game by bigjon, incorporating suggestions from Dr Beep, skoolkid and Matt B
Hi folks, I’m a machine code novice who coded a very small roadrace game to help me learn.
I reckon you can learn the basics of machine code in half an hour by coding this game step by step.
This tutorial assumes you have a working knowledge of ZX Spectrum basic, and the ZX Spin emulator.
Make yourself a large cup of tea – by the time you’ve drunk it, you be able to program in machine code!
CHAPTER 1 – Create a machine code function that returns the score to BASIC
Machine code programs are a series of bytes in the Spectrum’s memory.
In this chapter we will
- – Use the Spin assembler to write a few bytes into the memory generating a score for our game.
- – Write a BASIC program to run the machine code and print the score we have obtained from it.
Open ZX Spin. Select Tools -> Z80 Assembler.
To run our roadrace game, we need to execute the following steps:
MAIN ;label for main section of program as opposed to graphics data etc ;arrange to put our machine code at free, fast-running (over 32768) and memorable address in RAM org 33000 ;initialise score ;initialise road, car PRINCIPALLOOP ;label for the loop in the game that will execute over and over ;read keyboard ;set new carposition ;crash? if so, go to GAMEOVER. ;print car ;scroll road ;random road left or right ;jump back to PRINCIPALLOOP GAMEOVER ;label for the cleaning up that needs to be done before returning to BASIC ;return score to BASIC
Copy and paste the paragraph above into the Spin Assembler.It will appear as 15 lines of mainly grey text.
The text is grey because text after a ; is a comment. The assembler ignores it but it’s there for our benefit.
You can TAB comments over towards the right-hand side of the assembler page to make your code more readable.
The labels are in pink.The assembler won’t put anything in RAM for them but will use them as entry points to jump to.
In the assembler, do File -> Save as and type something like mc30mintut.asm into the save box.
We’ll do the first and last of these steps in this chapter, starting with the last one.
The assembly language instruction for ‘return to calling program’ (in our case a BASIC routine) is ‘ret’.
Click on the end of line 15, press enter to create line 16 and type ret
The word ret‘ appears in blue. This is Spin’s colour code for an instruction.
When the Spin assembler gets to the instruction ret it writes the byte 201 into the memory at an address we choose.
The computer knows that the first byte it meets will be an instruction byte.
It does something different for each byte from 0 to 255. There’s a list in Appendix A of the Spectrum Manual.
Some instruction bytes, like 201 for ret, are complete as is – no further info is needed to complete the action.
Some instruction bytes need one or two bytes of data afterwards for the computer to know what to do.
Some other instruction bytes need a further instruction byte to clarify the action required.
Now we’ll initialise the score, ready for the mc program to report it back to BASIC at the end of the game.
The computer does most of its work using 7 temporary byte-sized addresses called registers.
The first register deals with single bytes only (numbers 0 to 255), the other six are in pairs to deal with 0 to 65535.
The first (single-byte) register is called the A register, sometimes also referred to as the accumulator.
The other three register pairs are called BC, DE, and HL (H is for High byte, L is for Low byte)
Any machine code function called from basic will return the value from 0 to 65535 in the BC register.
We will write the value 0 into the BC register, ready to increase it by 1 each time the game goes round its principal loop.
At the beginning of line 4, type ld bc,0. ld is the instruction for load a value into a register or register pair.
The instruction byte for ld is different for each register or register pair that is loaded.
The instruction byte for ld bc is 1. The computer then expects 2 data bytes to give it a number from 0 to 65535.
In our case the two data bytes will be 0,0. So the assembler will write 1,0,0,201 at address 33000 in RAM.
We’ll assemble this code now. Do File -> Save, then File -> Assemble. Type 33000 into the Start Address box and click OK.
At the bottom window of the assembler you should see a report that says “No errors in 16 lines. 4 bytes generated”.
You can see the four bytes are now at memory address 33000 by clicking in the main Spin display window on Tools -> Debugger.
To run these four bytes of machine code, enter this one-line program in the main Spin display window:
10 PRINT AT 0,0; “Your score was “; USR 33000
Now RUN the program. Did you get “Your score was 0”? Congratulations – you have coded your first machine code program!
Do File -> Save in the main Spin display window and save as something like mc30mintut.sna. Here ends Chapter 1!
CHAPTER 2 – Display material on the screen.
There are two areas of the Spectrum’s memory which have a direct effect on the screen display.
The complicated way is the display file, from addresses 16384 to 22527, which stores a dash of 8 pixels per byte.
Try POKE-ing 255 into bytes within this range to see the funny order in which this memory area is mapped onto the screen.
The simple way is the attribute file, from 22528 to 23296, which affects an 8×8 pixel block per byte, in logical order.
In this chapter we will
- Draw our ‘car’ by changing the paper colour of one character square to blue.
- Draw our ‘road’ by using a loop to create two vertical stripes of black paper colour down the screen.
In the spin assembler line 5, delete the word ‘road’ in the comments.
At the beginning of line 5, type ld hl,23278. This points HL to the middle of the bottom row in the display file.
Insert line 6, ld a,8. This puts a blue PAPER colour into the A register. Why 8? See the BASIC manual chapter 16.
Insert line 7, ld (hl),a. The brackets round hl mean the load will be to the address in RAM that hl is pointing to.
Insert line 8, ld (32900),hl ;save car posn. We’ll store the attribute file address of the ‘car’ in some free bytes in RAM.
Now for the road. Insert line 4, ld hl,22537 ;initialise road. This points to a third of the way along the top line.
To save the road position, which we’ll need frequently, we’ll let the computer choose where to store it, on its ‘stack’.
Chapter 24 of the manual has a diagram showing where the machine stack is in the RAM.
To write to the stack we use push. To write from the stack we use pop. What goes on the stack first will come off last.
Insert line 5, push hl ;save road posn. Insert line 21, pop hl ;empty stack
To print a black road we need 0 in the accumulator (ch16 of the BASIC manual).
We could do ld a, 0 but this takes 2 bytes whereas xor a takes only one. Insert line 6, xor a.
xor compares the chosen register to the A register and puts a 1 in the A register for each bit that is different.
We’ll print the top line of the road. Two double squares of black with a 9-square gap between them.
Insert line 7, then copy and paste the following code:
ld (hl),a inc hl ;inc increases the register by one, dec decreases it by one. ld (hl),a ld de,9 ;for a 9-space gap in the road. add hl,de ;add adds the registers together, so hl points to the right hand side of the road. ld (hl),a inc hl ld (hl),a
To get hl to point to the left hand verge on the next line, we need to move 21 bytes further in the attribute file.
Insert line 15, ld de, 21 ;point to left verge on next line
Insert line 16, add hl,de
To fill the screen with the road we will use machine code’s equivalent of a FOR-NEXT loop, djnz.
djnz stands for Decrement then Jump if Not Zero. We load the b register with the number of times we want to loop.
Insert line 7, ld b,24 ;print road verge on 24 lines.
Insert line 8, fillscreen – this is the label for our loop to jump back to.
Insert line 19, djnz fillscreen.
Because our routine will continue from the loop when b=0, we no longer need to initialise b as well as c to 0 in the next line.
Change line 20 ld bc, 0 to ld c,b. This is one byte shorter.
Assemble and save. If you want to see the blue ‘car’, you’ll need to add something like 20 PAUSE 0 to your basic program.
CHAPTER 3 – move the car, test for collision.
Time to start playing the game! First we need to erase the car ready to move it if the player wants to.
Insert line 26, then copy and paste the following code:
ld hl,(32900) ;retrieve car posn ld a,56 ;erase car ld (hl),a
Before we read the keyboard we will lock the keyboard for most of the game, and unlock it only when we want to read the keys.
The instruction to lock the keyboard is di = ‘disable interrupts. Its opposite is ei = ‘enable interrupts’.
Replace line 3 with di. Insert line 29, ei. Insert line 31, di. Insert line 41, ei
To read the keys we use the IN ports – see ch23 of the BASIC manual – to read the left and right half of the bottom row.
We load bc with the port number and use the instruction cp (compare) to see if the number has dropped to show a keypress.
Delete line30 and replace with the following code:
ld bc,65278 ;read keyboard caps to v in a,(c) cp 191 jr nz, moveright inc l moveright ld bc,32766 ;read keyboard space to b in a,(c) cp 191 jr nz, dontmove dec l dontmove
jr nz stands for jump relative if not zero. It skips over the instruction to increment / decrement the car position.
Replace line 43 with the following to see if we bump into the oncoming road 32 bytes (1 screen) down the attribute file:
ld (32900),hl ;store car posn ld de, 32 ;new carposn xor a ;set carry flag to 0 sbc hl,de ld a,(hl) ;crash? or a jr z,gameover ld a,8 ;print car ld (hl),a
We’d like to sub hl,de but there’s no such instruction so we use sbc, subtract with carry, and set the carry flag to zero.
or compares the register to the a register bit by bit and leaves a 1 in the a register for each bit that is 1 in either.
If all the digits are zero, then the zero flag will be set, so we can use or a to test for a black paper colour.
Delete line 53. Delete line 53 again!
To clean up the score at GAMEOVER insert line21, push bc; save score. Replace line 57 with pop bc;retrieve score
To cycle round the game before GAMEOVER change line 55 to jp PRINCIPALLOOP.
Assemble, save, and run. You’ll need to deliberately crash to get out!
CHAPTER 4 – scroll and move the road, keep score, adjust speed.
To scroll the road down the screen we copy the screen attribute bytes to the line beneath 736 times.
We use the instruction lddr, which stand for LoaD ((hl) to (de)),Decrement (hl and de) and Repeat (until bc is zero).
Replace line 53 with the following:
ld hl,23263 ;scroll road ld de,23295 ld bc,736 lddr pop bc ;retrieve score
To add 1 to the score and save it ready for GAMEOVER, insert the following into line 59:
inc bc ;add 1 to score
push bc ;save score
To move the road randomly left or right on the top line we use the following algorithm –
Choose a location in ROM where the are 256 random looking bytes and add the low byte of the score in bc to it.
If it is odd, lower the road position in hl by one. If it is even, increase by one.
(To test the last bit for odd and even we use ‘and 1’ which “masks” the last bit and sets the zero flag if it is 0).
Check to see if the road has reached the edge of the screen and bump it away if it has.
Print the new road top line like we did in chapter 2.
Replace line 58 with the following hefty chunk of code:
pop hl ;retrieve road posn push hl ;save road posn ld a,56 ;delete old road ld (hl),a inc hl ld (hl),a ld de,9 add hl,de ld (hl),a inc hl ld (hl),a ;random road left or right ld hl,14000 ;source of random bytes in ROM ld d,0 ld e,c add hl, de ld a,(hl) pop hl ;retrieve road posn dec hl ;move road posn 1 left and 1 jr z, roadleft inc hl inc hl roadleft ld a,l ;check left cp 255 jr nz, checkright inc hl inc hl checkright ld a,l cp 21 jr nz, newroadposn dec hl dec hl newroadposn push hl ;save road posn xor a ;print new road ld (hl),a inc hl ld (hl),a ld de,9 add hl,de ld (hl),a inc hl ld (hl),a
The last thing we need to do to have a playable game is slow down our blindingly fast machine code.
Insert the following into line 106 (as extension material, you could adjust the figure in bc with a keypress to ‘brake’):
;wait routine ld bc,$1fff ;max waiting time wait dec bc ld a,b or c jr nz, wait
Save, assemble, and run – and that’s it! Has your tea gone cold yet?
A full listing follows, with my email address at the end for your comments and suggestions:
main org 33000 di ld hl, 22537 ;initialise road push hl ;save road posn xor a ld b,24 fillscreen ld (hl),a inc hl ld (hl),a ld de,9 add hl,de ld (hl),a inc hl ld (hl),a ld de,21 add hl,de djnz fillscreen ld c,b ;initialise score push bc ;save score ld hl,23278 ;initialise car ld a,8 ld (hl),a ld (32900),hl ;save car posn principalloop ld hl,(32900) ;retrieve car posn ld a,56 ;erase car ld (hl),a ei ld bc,65278 ;read keyboard caps to v in a,(c) cp 191 jr nz, moveright inc l moveright ld bc,32766 ;read keyboard space to b in a,(c) cp 191 jr nz, dontmove dec l dontmove di ld (32900),hl ;store car posn ld de, 32 ;new carposn xor a ;set carry flag to 0 sbc hl,de ld a,(hl) ;crash? or a jr z,gameover ld a,8 ;print car ld (hl),a ld hl,23263 ;scroll road ld de,23295 ld bc,736 lddr pop bc ;retrieve score pop hl ;retrieve road posn push hl ;save road posn ld a,56 ;delete old road ld (hl),a inc hl ld (hl),a ld de,9 add hl,de ld (hl),a inc hl ld (hl),a ;random road left or right ld hl,14000 ;source of random bytes in ROM ld d,0 ld e,c add hl, de ld a,(hl) pop hl ;retrieve road posn dec hl ;move road posn 1 left and 1 jr z, roadleft inc hl inc hl roadleft ld a,l ;check left cp 255 jr nz, checkright inc hl inc hl checkright ld a,l cp 21 jr nz, newroadposn dec hl dec hl newroadposn push hl ;save road posn xor a ;print new road ld (hl),a inc hl ld (hl),a ld de,9 add hl,de ld (hl),a inc hl ld (hl),a inc bc ;add 1 to score push bc ;save score ;wait routine ld bc,$1fff ;max waiting time wait dec bc ld a,b or c jr nz, wait jp principalloop gameover pop bc ;retrieve score pop hl ;empty stack ei ret; game and tutorial written by Jon Kingsman ('bigjon', 'bj'). electronic mail gmail.com - atsign - jon.kingsman (reversed)
Welcome to the third and final edition of this guide to BASIC!
Now that we’ve created the graphics for our game and have the key handling routines in place, we can now finally move on to the fun part of animating stuff and watch the screen come alive with millions of tiny coloured pixels (okay, maybe not millions but what the heck, who’s counting?)!
Firstly, if you remember the plot of our game, our intrepid hero of the game Krapz has to survive as long as he can by collecting little quanta particles until he gains enough power to jump out the warp into… yet another more difficult screen. He also has to avoid touching the “ST fluctuation trails” he leaves behind in his wake plus he must stay clear of the deadly boundaries of the warp. In order to pull this fancy (oh sure! – Ed) stuff off, we’ll break it down to a set of tasks.
Task #1: Set up the playing area. This involves drawing the boundaries of the warp and populating the playing field with randomly distributed set of quanta particles. Since every new screen that Krapz jumps into involves the above steps, it makes sense to package it as a sub-routine. Which is what we’ll do:
115 LET quanta=5*level: LET time=seedtime+(level*30): CLS 120 PRINT AT 1,0; INK 3;"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";AT 20,0; INK 3;"\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" 130 FOR f=1 TO 19 140 PRINT AT f,0; INK 3;"\b";AT f,31; INK 3;"\b" 150 NEXT f 160 LET x=10: LET y=10 250 FOR f=1 TO 5*level 260 LET p=INT (RND*17)+2 270 LET q=INT (RND*28)+2 280 IF ((p=y AND q=x) OR (ATTR (p,q)=5)) THEN GO TO 260: REM don't overwrite player or another quanta! 290 PRINT AT p,q; INK 5;"\g" 300 NEXT f 999 RETURN
The first line looks a bit complex but is actually a rather simple way of increasing the difficulty level as Krapz progresses through the game. The first LET statement simply increases the number of quanta particle in a screen by a factor of 5 depending on which level (screen) Krapz is playing on. For the first screen (level = 1), there will be only 5 quanta particles to collect. On the 2nd screen (level = 2) there will be 10, and so on and so forth. The second LET statement controls how much time Krapz has to collect all the quanta particles. This is again a factor of the level Krapz is on – 30 times the level as a matter of fact, which compensates for the fact that there are more quantas on each screen as we progress. You’ll notice that a seedtime has been tacked on in the equation to ensure that the player has a basic minimum time to start with on each level.
After having cleared the screen, it’s time to draw the boundaries of the warp – in our case it’s simply a rectangular arena to delineate the playing field. First we draw the top and bottom edges, which is simply a matter of printing 32 characters of the UDG “b” (incorrectly reproduced as \b in the listing above) that we created earlier on. Line 120 prints the top edge and the bottom edge in lurid purple ink.
To draw the vertical left and right edges we employ a FOR-NEXT loop from line 130 to 150 that print UDG “b” 19 times vertically.
It’s now time to randomly populate the playing area with quanta particles. Line 160 first sets the initial co-ordinates of Krapz on each screen – he always starts on row 10, column 10 on every screen.
We then employ a FOR-NEXT loop to print the actual quanta. The number of particles is simply a factor of the level the player is on, and is in fact exactly the same as the variable quanta we calculated on line 115. In fact we can substitute the 5*level with quanta and the loop will work just as well. Line 260 and 270 calculate the row and column (p,q) to print the quanta on. INT (RND* 17) gives us a random number from 0 to 16. We add a factor of 2 to ensure we don’t print anything on row 0 and 1 – the top edge of the playing arena. Note that if got 16 and added 2 we get 18, which still within the bottom edge (column 20) of the arena. Similarl, we keep the quanta within the left and right edges of the playing arena in line 270.
Line 280 introduces a necessary check that ensures that we aren’t printing a quanta on top of another already existing quanta on screen (we do want all our quantas visible individually on the screen!). Plus we don’t want to print at the position Krapz is already in (10,10). So, what we are checking for is “whether the quanta co-ordinates are the same as Krapz’s co-ordinates OR whether the quanta particle is being printed on top of another quanta” state. This is achieved by the IF statement, where the two expressions are separated by an OR (as in the above statement) condition which returns true if any one of the expression is true (either printing on Krapz or printing on a quanta).
To check for whether the quanta co-ordinates are same as Krapz’s co-ordinates, we simply have to check if the column and row co-ordinates for both match up. If they are the same, then we are at the same location on screen. This is calculated using the expression in the IF statement p=y AND q=x. The AND condition returns true if and only if both the sub-expressions are true.
The next thing we have to check for is the case of a quanta overwrite condition. This is done by using the ATTR command, which given a x & y co-ordinate returns the colour attribute at that co-ordinate. The BASIC manual explains how to interpret this value but suffice to say that for our purpose if ATTR returns a value of 5 it means that a cyan coloured character (INK 5) is present at that position. As you will see on line 290, where we print our quanta particles, they are printed with INK 5. In essence, what we are saying is that if ATTR returns 5, we assume that we have a quanta particle there regardless of what actually might be there. In order to not trip up our assumptions we ensure that we do not print any other stuff in INK 5 anywhere the screen. In our program only the quanta particles can have the cyan colour when playing the game.Period.
If we are overwriting Krapz or another quanta, we simply go back to line 160 and re-calculate a new quanta position until we are satisfied we aren’t overwriting anything we shouldn’t be overwriting! If all is well, we proceed to actually print a quanta particle on the screen at the position we calculated (line 290). Once we’ve printed all the quanta particles required for a level we RETURN from the sub-routine via line 999.
There! We’re all set to actually do some gameplay and stuff! Lets move on to the task of moving Krapz around on the playing area. Remember we set up our control keys in Code Shed Guide 2, so it’s time to use those keys to move Krapz around.The control scheme we’ll follow is a simple one. Pressing one of the four direction keys changes Krapz’s direction instantly (inertia? What’s that?). The key needn’t be held down if one wishes Krapz to continue in that direction though- his inertia (that word again!) will keep him going in that direction until a different direction key is pressed.
Have a look at this code which begins our main game loop (so called because we’ll be executing it repeatedly till something happens to break proceedings) proper:
1010 PRINT AT 21,0;"Lives: ";lives;TAB 20;"Score: ";score 1020 PRINT AT 0,0;"Time: ";time;" " 1030 PRINT AT 0,20;"Level: ";level 2000 LET a$=INKEY$ 2010 IF a$=k$(1) THEN LET dir=1: REM right 2020 IF a$=k$(2) THEN LET dir=2: REM left 2030 IF a$=k$(3) THEN LET dir=3: REM up 2040 IF a$=k$(4) THEN LET dir=4: REM down 2045 IF dir<>0 THEN PRINT AT y,x; INK 1;CHR$ (143) 2050 IF dir=1 THEN LET x=x+1 2060 IF dir=2 THEN LET x=x-1 2070 IF dir=3 THEN LET y=y-1 2080 IF dir=4 THEN LET y=y+1
Lines 1010 to 1030 give information regarding the status of the number of lives left, the current score, the time left and the current level being played. It’s our HUD if you will. Since it’s within the main game loop, it will be updated continously.
Lines 2000-2080 are responsible for our inertial control mechanism. The logic is quite simple. We sample the keyboard for any keypress. If it’s a direction key we change Krapz’s direction of movement. If no directional key is pressed, Krapz continues to move along in the same direction. Here’s how. Line 2000 reads in a key from the keyboard (null if no key is pressed). Lines 20101 to 2040 compare the value in a$ with the value in k$ (our desired control keys). Depending on which key is pressed, a variable called dir is set to a particular value that signifies a direction.
Line 2045 prints the trail behind Krapz. Basically, all it does is see if Krapz is moving (Krapz doesn’t start moving until you press a key initially), we don’t bother printing a trail. If he is moving (dir will have some non-zero value then), we will print a solid block of blue at the current Krapz co-ordinates. CHR$ (143) will print the solid graphic block you see on the numeric 8 key on the Speccy keyboard (Graphic mode + shift + 8). What’s this CHR$ you ask? Well it’s one way of printing a character on the screen. For example,if you do PRINT CHR$(65) it will print the letter “A” on the screen. This is because CHR$ converts a number to its string equivalent from the character set. If you take a look at the character set table in the Speccy manual you’ll see that numbers from 32 to 127 represent characters from the ASCII standard. Which is why 65 corresponds to letter “A”. From 128 to 255, the character set is unique to the Spectrum with some special characters like the Graphic Blocks taking up positions 129 to 143. You can print any of these characters by passing the code number to CHR$. Some unprintable characters (no, not the sort you’re thinking of) can do some fancy print formatting tricks – refer to the manual for more on that.
Coming back to the code, so we print the blue block at the current Krapz co-ordinates to signify a trail. “Hang on!” you say. “Won’t we be overwriting Krapz in the process?”. But of course! But since we’ll be repositioning Krapz at new co-ordinates anyway, it doesn’t matter. In fact, the code to calculate Krapz’s new co-ordinates follows:
2050 IF dir=1 THEN LET x=x+1 2060 IF dir=2 THEN LET x=x-1 2070 IF dir=3 THEN LET y=y-1 2080 IF dir=4 THEN LET y=y+1 2090 IF (ATTR (y,x)=3 OR ATTR (y,x)=1) THEN FOR f=0 TO 4: PRINT AT y,x; PAPER 2; INK 6; FLASH 1;CHR$ (145+f); FLASH 0: BEEP 0.4,RND*f: NEXT f: PAUSE 10: GO TO 6000
Lines 2050 to 2080 recalculate the new x or y position depending on the direction of travel. Simple, eh?
In line 2090 we check whether Krapz has crashed into the boundary walls or walked into his own trail (ST fluctuations are bad remember?). This is handled by a single IF statement that checks whether the attribute colour at the newly calculated Krapz co-ordinate matches INK 3 (the magenta colour with which the walls are drawn) or INK 1 (blue colour of Krapz’s trail). If it is, it’s time to play a sad ditty expressing our condolences and create a nifty looking explosion. Since I’m not musically inclined I’m just using a simple BEEP statement that randomly plays some stuff in a low pitch. Creating the explosion is simplicity in itself – I’ve created 4 explosion UDG’s using BASin’s UDG creator tool. To simulate the explosion effect, I just print the four characters (with full FLASH and stuff for added effect) one atop the other with a small time gap inbetween to slow things down a bit. It’s not the greatest explosion effect you’ll ever see but it’s good enough for our purpose. After the dust has settled down, we send the program off to line 6000 where the last recitals are performed. More on that later.
2095 IF ATTR (y,x)=5 THEN LET score=score+(level*10): LET quanta=quanta-1: BEEP 0.01,0.1: IF quanta=0 THEN PRINT AT 10,3; PAPER 1; INK 5; FLASH 1;"Space-Time Jump! Get Ready!"; FLASH 0: FOR f=1 TO 10: BEEP 0.03,RND*f: NEXT f: PAUSE 50: LET level=level+1: GO TO 6020 3000 PRINT AT y,x; INK 4;"\a"
Line 2095 determines what happens when Krapz picks up a quanta particle (we simply perform an attribute check for that). What happens is this: first the score is increased by 10 times the level we are on (as a measure of difficulty).The number of active quantas on the screen is decreased by one. If there are no more quantas left, it’s time to trigger a Space-Time jump and warp to the next level which is easily done with a simple message, a few random beeps and a jump to Line 6020 that sets a few variables back to initial values and then re-draws the level screen by jumping to line 1010.
If a space-time warp hasn’t been triggered (because there are quantas still left on the screen), we go on to print Krapz at the new locatio in line 3000.
We come to a final bit of code that deals with the player running out of time.
3005 LET time=time-1 3010 IF time=0 THEN PRINT AT 10,5; PAPER 2; INK 6; FLASH 1;"S-T Field Collapsing!"; FLASH 0: FOR f=10 TO 1 STEP -1: BEEP 0.1,f/2: NEXT f: PAUSE 50: FOR f=2 TO 19: PRINT AT f,1; PAPER 1; INK 2;"\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::\::": BEEP 0.05,RND*f/2: NEXT f: GO TO 6000 4000 GO TO 1010 6000 LET lives=lives-1 6010 IF lives=0 THEN PRINT AT 10,10; PAPER 4; INK 1;" GAME OVER! ": PAUSE 100 6012 IF lives=0 THEN IF score>hiscore THEN LET hiscore=score: PRINT AT 12,10; PAPER 5; INK 1; FLASH 1;"New High Score!"; FLASH 0: PAUSE 100 6015 IF lives=0 THEN RETURN
Line 3005 simply decreases the available time by 1 unit.
Line 3010 determines what happens next. If we’ve run out of time it’s time for another dramatic message with annoying random beeps and appropriately dangerous looking flashing text. We up the ante by printing a line of solid block (the odd ::\ is the same GRAPHIC MODE + SHIFT + 8 friend we met a while back) that slowly fills up the entire screen to signify a field collapse. We then jump to line 6000 to finish off the formalities.
If we haven’t run out of time we simply loop back to line 1010 via Line 4000 to continue playing.
Line 6000 is where the case of death is handled. First the number of available lives is decreased by 1. Line 6010 checks if we’ve used up all our lives, in which case it’s really Game Over! Line 6012 next checks if we created a new high score by simply comparing the current score with the high score. If our current score is higher, we save it as the high score and congratulate the player on achieving the same.
Line 6015 proceeds to return from the main game loop routine and dumps us back at the main menu screen from where we’d first come from thus bringing us back full circle to square one.
And that my friends brings us to an end to this guide! Hopefully, you’ll go on to write better and bigger stuff than the Dash-it game described here. If not, don’t forget to send your entry to the annual Crap Games Competition! Cheers!
The source code for 64 column printing was originally provided by Andrew Owen in a thread on WoSF. Reproduced with permission.
BASIC normally provides only 32 columns for printing text. This machine code routine, which can be called from BASIC, effectively doubles the number of columns horizontally available for printing.
In BASIC, do a CLEAR 49999 so that BASIC won’t overrun the machine code in memory.
Assemble or load the following routine at address 50000. Do a RANDOMIZE USR 50000, to run the routine and perform the magic for 64 column printing.
To print in 64 columns, use stream 4, for eg: PRINT #4;AT 0,53;”Hello World”
; --------------- ; 4x8 font driver, (c) 2007 Andrew Owen ; --------------- org 50000 ; ; -------------------------------- ; CREATE CHANNEL AND ATTACH STREAM ; -------------------------------- ; ; Based on code by Ian Beardsmore from Your Spectrum issue 7, September 1984. c_chan: ld hl,($5c53) ; a channel must be created below basic ; so look at the system variable PROG dec hl ; move hl down one address ld bc,$0005 ; the new channel takes 5 bytes call $1655 ; call the MAKE_ROOM routine inc hl ; move HL up one address ld bc,chan_4 ; could write the bytes directly but ; then code would be non-relocatable ld (hl),c ; low byte of the output routine inc hl ; move HL up one address push hl ; save this address for later ld (hl),b ; high byte of the output routine inc hl ; move HL up one address ld bc,$15c4 ; address of input routine ld (hl),c ; low byte of the input routine inc hl ; move HL up one address ld (hl),b ; high byte of the input routine inc hl ; move HL up one address ld (hl),'P' ; channel type; 'K', 'S', 'R' or 'P' ; attach stream pop hl ; the first address plus one of the ; extra space stored earlier ld de,($5c4f) ; store the contents of CHANS in DE and a ; clear the carry flag before ; calculation sbc hl,de ; the difference between the start of ; the channels area and the start of the ; extra space becomes the offset, stored ; in HL ex de,hl ; store the offset in DE ld hl,$5c10 ; store the contents of STRMS in HL ld a,$04 ; stream number 4 add a,$03 ; take account of streams -3 to -1 add a,a ; each of the seven default streams has ; two bytes of offset data ; the total number of bytes occupied, ; held in a, forms the offset for the ; new stream ld b,$00 ; set b to hold $00 ld c,a ; set the low byte of the offset add hl,bc ; the offset is added to the base ; address to give the correct location ; in the streams table to store the ; offset ld (hl),e ; the low byte of the offset inc hl ; move HL up one address ld (hl),d ; the high byte of the offset ret ; all done ; ----------------- ; CHANNEL #4 OUTPUT ; ----------------- ; ; Based on code by Tony Samuels from Your Spectrum issue 13, April 1985. ; A channel wrapper for the 64-column display driver. chan_4: ld b,a ; save character ld a,(atflg) ; value of AT flag and a ; test against zero jr nz,getrow ; jump if not ld a,b ; restore character atchk: cp $16 ; test for AT jr nz,crchk ; if not test for CR ld a,$ff ; set the AT flag ld (atflg),a ; next character will be row ret ; return getrow: cp $fe ; test AT flag jr z,getcol ; jump if setting col ld a,b ; restore character cp $18 ; greater than 23? jr nc,err_b ; error if so ld (row),a ; store it in row ld hl,atflg ; AT flag dec (hl) ; indicates next character is col ret ; return getcol: ld a,b ; restore character cp $40 ; greater than 63? jr nc,err_b ; error if so ld (col),a ; store it in col xor a ; set a to zero ld (atflg),a ; store in AT flag ret ; return err_b: xor a ; set a to zero ld (atflg),a ; clear AT flag rst 08h ; defb $0a ; crchk: cp $0d ; check for return jr z,do_cr ; to carriage return if so call pr_64 ; print it ld hl,col ; increment inc (hl) ; the column ld a,(hl) ; cp $40 ; column 64? ret nz ; do_cr: xor a ; set A to zero ld (col),a ; reset column ld a,(row) ; get the row inc a ; increment it cp $18 ; row 24? jr z,wrap ; zend: ld (row),a ; write it back ret wrap: xor a ; jr zend ; ; ------------------------ ; 64 COLUMN DISPLAY DRIVER ; ------------------------ pr_64: rra ; divide by two with remainder in carry flag ld h,$00 ; clear H ld l,a ; CHAR to low byte of HL ex af,af' ; save the carry flag add hl,hl ; multiply add hl,hl ; by add hl,hl ; eight ld de,font-$80 ; offset to FONT add hl,de ; HL holds address of first byte of ; character map in FONT push hl ; save font address ; convert the row to the base screen address ld a,(row) ; get the row ld b,a ; save it and $18 ; mask off bit 3-4 ld d,a ; store high byte of offset in D ld a,b ; retrieve it and $07 ; mask off bit 0-2 rlca ; shift rlca ; five rlca ; bits rlca ; to the rlca ; left ld e,a ; store low byte of offset in E ; add the column ld a,(col) ; get the column rra ; divide by two with remainder in carry flag push af ; store the carry flag ld h,$40 ; base location ld l,a ; plus column offset add hl,de ; add the offset ex de,hl ; put the result back in DE ; HL now points to the location of the first byte of char data in FONT_1 ; DE points to the first screen byte in SCREEN_1 ; C holds the offset to the routine pop af ; restore column carry flag pop hl ; restore the font address jr nc,odd_col ; jump if odd column even_col: ex af,af' ; restore char position carry flag jr c,l_on_l ; left char on left col jr r_on_l ; right char on left col odd_col: ex af,af' ; restore char position carry flag jr nc,r_on_r ; right char on right col jr l_on_r ; left char on right col ; ------------------------------- ; WRITE A CHARACTER TO THE SCREEN ; ------------------------------- ; ; There are four separate routines ; HL points to the first byte of a character in FONT ; DE points to the first byte of the screen address ; left nibble on left hand side l_on_l: ld c,$08 ; 8 bytes to write ll_lp: ld a,(de) ; read byte at destination and $f0 ; mask area used by new character ld b,a ; store in b ld a,(hl) ; get byte of font and $0f ; mask off unused half or b ; combine with background ld (de),a ; write it back inc d ; point to next screen location inc hl ; point to next font data dec c ; adjust counter jr nz,ll_lp ; loop 8 times ret ; done ; right nibble on right hand side r_on_r: ld c,$08 ; 8 bytes to write rr_lp: ld a,(de) ; read byte at destination and $0f ; mask area used by new character ld b,a ; store in b ld a,(hl) ; get byte of font and $f0 ; mask off unused half or b ; combine with background ld (de),a ; write it back inc d ; point to next screen location inc hl ; point to next font data dec c ; adjust counter jr nz,rr_lp ; loop 8 times ret ; done ; left nibble on right hand side l_on_r: ld c,$08 ; 8 bytes to write lr_lp: ld a,(de) ; read byte at destination and $0f ; mask area used by new character ld b,a ; store in b ld a,(hl) ; get byte of font rrca ; shift right rrca ; four bits rrca ; leaving 7-4 rrca ; empty and $f0 ; or b ; combine with background ld (de),a ; write it back inc d ; point to next screen location inc hl ; point to next font data dec c ; adjust counter jr nz,lr_lp ; loop 8 times ret ; done ; right nibble on left hand side r_on_l: ld c,$08 ; 8 bytes to write rl_lp: ld a,(de) ; read byte at destination and $f0 ; mask area used by new character ld b,a ; store in b ld a,(hl) ; get byte of font rlca ; shift left rlca ; four bits rlca ; leaving 3-0 rlca ; empty and $0f ; or b ; combine with background ld (de),a ; write it back inc d ; point to next screen location inc hl ; point to next font data dec c ; adjust counter jr nz,rl_lp ; loop 8 times ret ; done ; -------------- ; TEXT VARIABLES ; -------------- ; ; Used by the 64 column driver atflg: defb $00 ; AT flag row: defb $00 ; row col: defb $00 ; col ; ------------------- ; half width 4x8 font ; ------------------- ; ; 384 bytes font: defb $00,$02,$02,$02,$02,$00,$02,$00,$00,$52,$57,$02,$02,$07,$02,$00; defb $00,$25,$71,$62,$32,$74,$25,$00,$00,$22,$42,$30,$50,$50,$30,$00; defb $00,$14,$22,$41,$41,$41,$22,$14,$00,$20,$70,$22,$57,$02,$00,$00; defb $00,$00,$00,$00,$07,$00,$20,$20,$00,$01,$01,$02,$02,$04,$14,$00; defb $00,$22,$56,$52,$52,$52,$27,$00,$00,$27,$51,$12,$21,$45,$72,$00; defb $00,$57,$54,$56,$71,$15,$12,$00,$00,$17,$21,$61,$52,$52,$22,$00; defb $00,$22,$55,$25,$53,$52,$24,$00,$00,$00,$00,$22,$00,$00,$22,$02; defb $00,$00,$10,$27,$40,$27,$10,$00,$00,$02,$45,$21,$12,$20,$42,$00; defb $00,$23,$55,$75,$77,$45,$35,$00,$00,$63,$54,$64,$54,$54,$63,$00; defb $00,$67,$54,$56,$54,$54,$67,$00,$00,$73,$44,$64,$45,$45,$43,$00; defb $00,$57,$52,$72,$52,$52,$57,$00,$00,$35,$15,$16,$55,$55,$25,$00; defb $00,$45,$47,$45,$45,$45,$75,$00,$00,$62,$55,$55,$55,$55,$52,$00; defb $00,$62,$55,$55,$65,$45,$43,$00,$00,$63,$54,$52,$61,$55,$52,$00; defb $00,$75,$25,$25,$25,$25,$22,$00,$00,$55,$55,$55,$55,$27,$25,$00; defb $00,$55,$55,$25,$22,$52,$52,$00,$00,$73,$12,$22,$22,$42,$72,$03; defb $00,$46,$42,$22,$22,$12,$12,$06,$00,$20,$50,$00,$00,$00,$00,$0F; defb $00,$20,$10,$03,$05,$05,$03,$00,$00,$40,$40,$63,$54,$54,$63,$00; defb $00,$10,$10,$32,$55,$56,$33,$00,$00,$10,$20,$73,$25,$25,$43,$06; defb $00,$42,$40,$66,$52,$52,$57,$00,$00,$14,$04,$35,$16,$15,$55,$20; defb $00,$60,$20,$25,$27,$25,$75,$00,$00,$00,$00,$62,$55,$55,$52,$00; defb $00,$00,$00,$63,$55,$55,$63,$41,$00,$00,$00,$53,$66,$43,$46,$00; defb $00,$00,$20,$75,$25,$25,$12,$00,$00,$00,$00,$55,$55,$27,$25,$00; defb $00,$00,$00,$55,$25,$25,$53,$06,$00,$01,$02,$72,$34,$62,$72,$01; defb $00,$24,$22,$22,$21,$22,$22,$04,$00,$56,$A9,$06,$04,$06,$09,$06;
By default Sinclair BASIC provides very limited string manipulation techniques. However, you can easily add more functionality via the following methods.
The following methods are taken from an original World of Spectrum Forum thread, started by Andrew Owen. However, I’ve added some details for better explanation of each method.
The earlier versions of UPPER$ and LOWER$ didn’t work as expected and Alvin Albrecht was kind enough to provide the corrected versions.
DEF FN L$(s$, x) = s$( to x)
Given a string, the L$ function will return a string starting from the leftmost character (hence called LEFT$ in some BASIC implementations) and up to x characters to the right. Therefore, if you have a string, say “Hello” and wish to extract just “Hell” from it, you can do FN L$(a$, 4), where a$ is the string holding “Hello”. A typical example in BASIC would be:
05 DEF FN L$(s$, x) = s$( to x) 10 LET a$ = "Hello" 20 LET b$ = FN L$(a$, 4) 30 PRINT b$
The above BASIC example is easily modified for the rest of the functions that follow.
DEF FN R$(s$, x) = s$(LEN s$ + 1 – x TO LEN s$)
Given a string, the R$ function will return a string starting from the rightmost character (hence called RIGHT$ in some BASIC implementations) and up to x characters to the left. Therefore, if you have a string, say “Hello” and wish to extract just “ello” from it, you can do FN R$(a$, 4), where a$ is the string holding “Hello”.
MID$(string, number1, number2)
DEF FN M$(s$, x,y) = s$(x TO x – 1 + y)
Given a string, the M$ function will return a string starting from anywhere in the string x and up to y characters to the right (hence called MID$ in some BASIC implementations). Therefore, if you have a string, say “Hello” and wish to extract just “ell” from it, you can do FN M$(a$, 2,4), where a$ is the string holding “Hello”.
DEF FN D$(s$) = VAL$ “CHR$ ((CODE s$)+(32 AND CODE s$ > 64 AND CODE s$ < 92))+FN D$(s$(2 TO ))”( TO 27+12*(LEN s> 1))
Given a string, the D$ function will return a string with all characters converted to lower case (hence called LOWER$ in some BASIC implementations). Therefore, if you have a string, say “Hello” and wish to convert all of it to lower case (“hello”), you can do FN D$(a$), where a$ is the string holding “Hello”.
DEF FN U$(s$) = VAL$ “CHR$ ((CODE s$)-(32 AND CODE s$ > 96 AND CODE s$ < 124))+FN u$(s$(2 TO ))”( TO 28+12*(LEN s$> 1))
Given a string, the D$ function will return a string with all characters converted to upper case (hence called UPPER$ in some BASIC implementations). Therefore, if you have a string, say “Hello” and wish to convert all of it to upper case (“HELLO”), you can do FN U$(a$), where a$ is the string holding “Hello”.
Traditionally, games in BASIC use the keyboard for player input and act accordingly. For eg, the keys Q, A, O and P may be used to move the player character around and M may be used to fire or other action. However, as anyone who has played ‘professional’ games (i.e written using machine code) on the speccy would know, using a joystick is a far better alternative especially in fast action games.
This article will tell you how to use the popular Kempston Joystick in your games for that ‘professional’ touch. 😉
To begin with, inputs from the Kempston Joystick are read via port 31. The Spectrum manual has more information on what a ‘port’ is and so I won’t be covering it here. Suffice to say, any external device connected to the speccy reads through a specific port, numbered 1 to 255. The Kempston Joystick uses port 31.
To read the ‘value’ in port 31 you simply do: LET kj = IN 31. Depending on what state the joystick is in, kj will hold a very specific value that can be interpreted as desired. Now then, the Kempston joystick has only 5 states to deal with, namely ‘Fire’ (any of the fire buttons pressed), ‘Up’ (joystick pulled back), ‘Down’ (joystick pushed forward), ‘Left’ (joystick to the left) and ‘Right’ (joystick to the right).
This information is packed into a single byte in the following format: 000FUDLR. As you can see only the lower 5 bits are of interest to us, which means we can ignore any value of kj above 31. Depending on which bit is set (value of 1) we can assume the joystick is in that state. Multiple states are also possible – firing and moving for example, or moving diagonally for example. Obviously you can’t have states of ‘Up’ & ‘Down’ or ‘Left’ & ‘Right’ occurring simultaneously!
The table of basic values that we need to test is given below:
|kj (IN 31)||Bit pattern (lower 5 bits only)||State|
For the purpose of this tutorial I’m going to ignore multiple directional states because BASIC doesn’t support the kind of bit level manipulation we would like to achieve detection of that. However, it will be useful to detect if we’re firing and moving so that will be on the agenda.
Let’s get the simple detection out of the way first!
10 LET x=10: LET y=10 20 LET kj=IN 31: REM get the state of joystick 30 PRINT AT y,x;"*" 40 IF (kj>31) OR (kj=0) THEN GO TO 20: REM ignore spurious inputs 45 BORDER 1: PRINT AT y,x;" ": REM overwrite at old position 50 IF kj=1 THEN LET x=x+1: REM left 60 IF kj=2 THEN LET x=x-1: REM right 70 IF kj=4 THEN LET y=y+1: REM Down 80 IF kj=8 THEN LET y=y-1: REM Up 90 IF kj=16 THEN BORDER 2: REM Fire 100 GO TO 20
The code is pretty simple. To see it in action we will print an asterisk on the screen that can be moved about by manipulating the joystick. Screen boundaries aren’t checked though to keep the code simple. We’ll also set the border to Red if the fire button is pressed. That’s it really.
To check for the event of fire and moving we’ll have to perform a simple trick. When firing and moving, kj will have bit 5 (for fire) and the bits for movement (any of them from 1 to 4) to be set, leading to a number that is greater than 16 (since fire by alone is itself 16). So all we do then is check for this fact (kj > 16) and if so, note that the fire is being pressed and then subtract 16 from kj so that we can continue to check for the movement keys as normal.
The above code can be modified so (changed lines in blue):
10 LET x=10: LET y=10 20 LET fire=0: LET kj=IN 31: REM note: reset fire event every time! 30 PRINT AT y,x;"*" 40 IF (kj>31) OR (kj=0) THEN GO TO 20 45 BORDER 1: PRINT AT y,x;" " 46 IF kj>16 THEN LET kj=kj-16: LET fire=1: REM Check if firing AND moving 50 IF kj=1 THEN LET x=x+1 60 IF kj=2 THEN LET x=x-1 70 IF kj=4 THEN LET y=y+1 80 IF kj=8 THEN LET y=y-1 90 IF kj=16 OR fire THEN BORDER 2: REM fire by itself or firing and moving? If so red border 100 GO TO 20
That’s about it really. Doing the above in machine code is, in fact, easier because it has all the necessary operators to perform low level bit manipulation which you really need if you want to check for multiple states of the joystick. However, I believe the above will do very nicely for those BASIC games that do not need diagonal movement!
Experiment and enjoy!
Publisher: Hiive Books
Author: Andrew Rollings
Some wise person once said, “Never judge a book by its cover.” Sane advice when it comes to books, no doubt, but when it comes to “The ZX Spectrum Book” the cover pretty much tells you what you can expect from the book – polish, style, quality and staggering attention to detail. Why, even the envelope that the book ships in has a customised Spectrum labeling on it! Beat that!
But a bit of background on the book before I give out more details on it. It was in mid-2005 (I think, well it was some time ago at any rate!) that Andrew Rollings announced he was doing a memorabilia book that covered a fair bit of Spectrum gaming history. He figured that he would finish the book by September 2005 (yes, that’s not a spelling mistake). *snigger*
Of course, he didn’t! In fact, it took more than a year to finish the book. And now that we have the book, we know why!
The book chronicles more than 230 games covering the lifespan of the Speccy gaming industry from 1982 to 1992. Each year is colour coded to help you quickly find a relevant game. Apart from a Contents section that lists the games by their year of release, there is a handy Index section that lists the game in alphabetical order.
The entire meaty 250 odd paged book is printed in full-colour (Spectrum colours in fact) and the paper, colour reproduction and print quality is outstanding.
To cap it the foreword is written by Sir Clive Sinclair himself.
If that doesn’t impress you the contents of the book certainly will.
Effectively, there are 9 chapters in the book each representing the year of interest from 1982 to 199x. Nick Humphries (of YSRNR fame) introduces each chapter with an excellent summation of the important milestones in that year. This sets a nice background and tone for the games being presented in the chapter.
Each write-up of a game is broken up into three sections that give you a brief background about the game, some trivia and a succinct overview of the game itself. The write-ups are accompanied by two in-game screenshots, the loading screen and the inlay of the game. In addition, where possible, the ratings given by the Big Three (Crash, Your Sinclair, and Sinclair User) are reproduced to give you a fair idea of the overall perception of the game (by the magazines that is) when it came out.
If you’re wondering what kind of games are chronicled, it’s interesting to note that Andrew has been rather eclectic in his selection. He writes about the good games, the bad games and even the downright ugly ones. It’s a fair representation of the gaming scene that existed during that particular period. More importantly, the chosen games either have some sort of historical significance or interesting trivia that helped set them apart from the others. That’s not to say he’s managed to write about every interesting game that existed – that would have been well near impossible considering the number of games that were released – but lets say every game included in the book has a story to tell and make a very interesting read.
There’s nothing much else to say really. This book deserves to be on every specchums coffee table – it’s a piece of Spectrum computing history (the book that is, not the coffee table). If you haven’t got it already, go on spoil yourself – you know you want to!
Game: Exolon DX
Authors: Graham Goring, John Blythe & Infamous
When I first played Exolon by Raffaele Cecco on the speccy I was blown away by the colourful graphics and the super smooth animation of the main character – Vitorc. To date it remains as a reminder as to what can be done on the speccy when a competent coder and graphic artist design a game with the limitations and abilities of the speccy in mind.
So when retrospec announced a Exolon remake in the offing way back in 2005, I was hugely interested in knowing how the talented folks at retrospec would remake this classic. The answer is ExolonDX, the second ever remake done by Graham Goring for retrospec.
The starting intro sets the mood for the remake nicely. Colourful, well drawn graphics with all sorts of embellishments greet you before whisking you away to the main menu. The first thing you should do is go to the options screen and switch the display to 16 or 32 bit full screen mode (unless you don’t want to of course!) to get the maximum pleasure out of the game. You will need to quit and restart the game for the setting to take effect though, which is a bit annoying but easily done.
The first screen in the game shows off the graphics nicely. Cool looking spaceship, nifty status panel, smooth parallax-scrolling starfield and ,whey-hey, the Cybernoid ship zooming off screen in the background! Okay enough sight-seeing. Time to lay into that gun emplacement with a quick rocket from the rocket launcher. Woohoo! Watch that sucker explode! Well then, not a bad start eh? Wonder what lies beyond that crazy looking alien artifact…
And with that you’re on your way. The first thing to note in the remake is that the number of lives has reduced from the gracious (but much needed) nine lives to a mere two. Yep, that’s right – two. That’s probably because the remake doesn’t require you to be pixel perfect as in the original speccy version so things are indeed a bit easier. Plus the remake is pretty much faithful to the original version so there aren’t any nasty surprises in store to upset your march through the game.
Amidst the graphical extravaganza the only thing I found a bit amiss was the graphic for Vitorc himself. He looks like a spaceman in a spacesuit – as he should – but lacks the character that Vitorc had in the original version. Plus the upgrade suit doesn’t alter Vitorc’s appearance as dramatically. I’m probably nit-picking but it’s these two points that caught my eye early on in the game.
Apart from that Exolon is a visual feast with extremely colourful well defined graphics and really cool visual effects that bring the game to life. In the sound department, Exolon boasts of a thumpingly good soundtrack that plays in the background and competent but varied sound effects that accompany most of the actions in the game.
I must say I thoroughly enjoyed playing the remake for the simple reason that it’s a well crafted remake that stays to the original but significantly improves on all the aspects of the original game. Highly recommended!
Interestingly, ExolonDX credits Hugh Binns as the creator of the original graphics. However, I can’t find any reference to Hugh Binn as the author of the graphics for the speccy version (on which ExolonDX is based). Indeed, he isn’t mentioned anywhere in the credits for the speccy version of Exolon. Hugh Binns, however, did do the graphics for the Amstrad CPC version, so there’s probably been a mix-up somewhere. Either that or Raffaele cheekily took all the credit!