Tutorial - learn machine code in 30mins

edited October 2012 in Development
Message from the moderators - bigjon has requested that readers' attention be drawn to a better version of this tutorial, one which is easier to read: it can be found at

http://chuntey.wordpress.com/2010/01/12/tutorial-zx-spectrum-machine-code-game-30-minutes/

Original message of this post follows:

I've written a short tutorial to get you started writing machine code.
Anyone fancy giving it a go and reporting back on your progress here?
ZX SPECTRUM MACHINE CODE GAME 30 MINUTE TUTORIAL
By bigjon (Jon Kingsman)
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
org 33000
;org 33000 = arrange to put our machine code at free, fast-running (over 32768) and memorable address in RAM
;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 256. 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 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 65355.
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 65355 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 65355.
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 8x8 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 tesco.net - atsign - jon.kingsman (reversed)
Post edited by bigjon on
«1

Comments

  • edited November 2009
    ...interesting... Having all the coding talent of a rubber fish in the middle of the Sahara desert, myself, Im intrigued... Ill certainly be looking this code over to see what I can learn, and will be interested to see where this thread goes... Although Ill say upfront, that cuppa will have to be pint sized to have me enlightened by the time Im through with it... ;)
  • edited November 2009
    I'm not a novice so I can't comment from that angle but I'm sure many will appreciate your efforts :-) M/C really is that easy to understand once it clicks.

    Just a couple of comments:

    All the z80 registers are 8 bits: A, F (flags), B, C, D, E, H, L and, if we delve into the (sort of) undocumented, IXL, IXH, IYL, IYH, The z80 architecture allows these 8 bit registers to be paired up to act as 16 bit registers: AF, BC, DE, HL, IX, IY. This list excludes the specialized PC (program counter, the 16 bit register that holds the address of the next instruction), SP (the 16 bit register pointing at the address of the stack) and the second set of registers AFBCDEHL' available after an EXX or EX AF,AF'.

    Secondly, the keyboard cannot be locked. It's a simple hardware peripheral that is always accessible via an i/o port. DI disables maskable interrupts and EI enables them. What happens in the spectrum is that the ULA generates an interrupt at the start of each video frame (ie 50 times per second). When maskable interrupts are enabled (EI), the z80 will stop what it is doing and respond by calling an interrupt service routine. Afterward it will resume where it left off. By disabling interrupts with DI you are preventing the ULA interrrupt from doing anything. ZX Basic arranges to run BASIC's interrupt routine so by default that is what will happen when your m/c runs with interrupts enabled. BASIC's ISR does things like scan the keyboard and update frames which is more or less useless from an m/c program's point of view.
  • edited November 2009
    Th first thing which comes to my mind when I see this tutorial is not its content but presentation.

    It would be far most readable and easy to understand (at least for me) if it had some colors, bold fonts, spaces between paragraphs, maybe some pictures. So I would suggest putting in into document that allows such formatting (e.g. into Word).

    Then I could evaluate the content :)
  • edited November 2009
    Have ta agree with ralf - the presentation needs improving E.G comments on code should be in a different colour to the code.

    Also I couldn't follow what was going on in chapter 1 - perhaps chapter 1 would be better setting out the aim of the tutorial and how your going to acheive the aim by describing the game and it's coding sections, chapter 2 would then set out the development environment ( spin, basin, whatever ) and subsequent chapters leading through the code sections defined in chapter 1.

    Still, nice to see someone putting their experiences on the record for others to learn from. :)
  • edited November 2009
    Secondly, the keyboard cannot be locked. It's a simple hardware peripheral that is always accessible via an i/o port. DI disables maskable interrupts and EI enables them. What happens in the spectrum is that the ULA generates an interrupt at the start of each video frame (ie 50 times per second). When maskable interrupts are enabled (EI), the z80 will stop what it is doing and respond by calling an interrupt service routine. Afterward it will resume where it left off. By disabling interrupts with DI you are preventing the ULA interrrupt from doing anything. ZX Basic arranges to run BASIC's interrupt routine so by default that is what will happen when your m/c runs with interrupts enabled. BASIC's ISR does things like scan the keyboard and update frames which is more or less useless from an m/c program's point of view.
    So do I need to EI in order to read the keyboard in the middle of the principal loop? Does the port number in bc only work if the ISR is running?
  • edited November 2009
    bigjon wrote: »
    So do I need to EI in order to read the keyboard in the middle of the principal loop? Does the port number in bc only work if the ISR is running?
    Rhetorical questions, I believe.... If the answers should differ from no & no then your program would'nt work....
    Nice try this tutorial, BTW.
  • edited November 2009
    roko wrote: »
    Rhetorical questions, I believe.... If the answers should differ from no & no then your program would'nt work....
    No, genuine questions. I've used EI before I read the keyboard, but I wasn't sure whether AA was implying that I didn't need to.
    You're answer suggests that I can ditch the EI before the keyboard read and the DI after it in my listing. I haven't got time to try it now but I will later.
  • edited November 2009
    Thanks for the tutorial! It shall be very interesting to read.

    However, I must agree that it quite hard to read at the moment. I will have to save it out to some other documant and re-format it, perhaps just by inserting some more line-breaks.
  • edited November 2009
    You do not need to enable interrupts in order to read the keyboard via its i/o address. The keyboard can *always* be read from its i/o address.

    You can think of the z80 as having two 64k address spaces -- one where memory lies and one where peripherals (typically) lie. The only difference external to the z80 is that the mreq pin physically goes low when an address is in the memory space and the iorq pin physically goes low when an address is in i/o space. Memory watches the mreq pin to know it should respond and peripherals watch the iorq pin. z80 instructions that refer to memory cause the mreq pin to go low (eg, "LD HL,(16384)" -- read 16 bit value from addresses 16485/16385 ; "LD A,(HL)" -- read 8 bits from the memory address held in the HL register pair). z80 instructions that refer to i/o cause the iorq pin to go low (eg, "IN A,(C)" -- read 8 bits from the 16-bit i/o address held in BC). In the first sentence I said i/o space is "where peripherals typically lie". This is because peripherals can be wired to respond to memory addresses. Then they are known as memory-mapped peripherals. The reason one may want to do this is because the z80 instruction set is much, much richer in accessing memory than i/o. It is also faster to access memoiy than i/o. In the spectruim the ULA's display file can be thought of as a memory mapped display device. THe ULA actually owns the 16k RAM beginning at address 16384 and blocks the z80 from accessing it while it needs to access it (known as contention). Having the display file memory mapped allows the z80's full range of memory instructions to be used to manipulate display data. Consider an alternative found in the MSX machines -- they have a dedicated TMS99xx (?) VDU to generate the display that comes with sprite hardware. The drawback is that the full video memory owned by the VDU is not mapped into the z80's address space and writes to the VDU's display memory involves contorted and slow i/o instructions. This made the MSX fairly crap at scrolling, an operation involving a full update of the display data.

    Some cpus such as the 65xx and 68xx have no separate i/o space. This means peripherals are always memory-mapped, the downside of which is that you never have access to a full 64k of memory since some of it must contain peripheral registers.

    Sorry for getting a bit distracted there. I think your confusion comes from the fact that the BASIC system reads the keyboard in the interrupt service routine. If you disbale interrupts, the interrupt routine doesn't run and BASIC will not see any key presses. This does not mean you cannot read the state of the keyboard hardware directly. You can do that at any time.
  • edited November 2009
    Rickard wrote: »
    However, I must agree that it quite hard to read at the moment. I will have to save it out to some other documant and re-format it, perhaps just by inserting some more line-breaks.
    OK, I'll convert it to html and post a link to it. I'll ask in another thread for free web-hosting recommendations.
  • fogfog
    edited November 2009
    it's good that your doing it :) but it's too much too soon IMHO.

    I code 6502, not z80 so get an idea of some of it, just well thru most of the code you use decimal, I'd get people used to using Hex and also some small bits (address's) relate to a hex value.

    Most people fall into one of the following camps >

    * complete novice , don't know about hex, interupts, the op's / syntax etc. but maybe the next "mr smith" in the making..

    * know basic , wanna progress further and gain the obvious speed advantage

    * learned on another machine and want to see how it is different.

    I do think showing people interupts is a bit too soon, getting them used to some of the op codes etc would be good.. and things like the stack.

    even things like , when folks were in wh smith.. and they used to do "<insert name> .. is cool" print / goto program.. using that as a starting point and doing an asm version also..

    although I'm sure there are books here folks would recommend to people starting out...

    I only can comment on the c64 one, the definative programmers guide by Rae West if anyone ever wants to learn c64 stuff (ok.. don't send the lynch mob around :D)

    things like the memory map etc help also, just a few thoughts anyway. I just think it's a bit well much for a noob to it. Everyone had to start of with the straight forward things first. yer it's boring ,but it's a good foundation.
  • edited November 2009
    fog wrote: »
    Most people fall into one of the following camps >

    * complete novice , don't know about hex, interupts, the op's / syntax etc. but maybe the next "mr smith" in the making..

    * know basic , wanna progress further and gain the obvious speed advantage

    * learned on another machine and want to see how it is different.
    Thanks for the feedback. I guess I wrote it for people in the second category since that's where I'm coming from and found it frustrating trawling through the theory stuff about hex etc before I could program even the simplest of games. (I used 'Introducing Spectrum Machine Code' by Ian Sinclair which is great but stops when things are just getting interesting, and was written when folks would have had to assembled into mc op-codes by hand.)
  • fogfog
    edited November 2009
    Also with regard to monitors / assemblers, what did most people start with using?

    I mean I intially started with the expert / action replay monitor on the c64...where there wasn't any helpers....e.g. you couldn't use labels etc you had to use specifics.. in a way that helps you with things though. It forces you to use precise things.. and even the fact that you can put in hex numbers instead of / also a command..e.g. 8d 20 d0 (would STA $D020) or is it 9d.. i forget. People might get into bad habits.


    you yourself said you had read a boring book , BUT it was the foundations really. I have to think of the mindset of whats the equivelent c64 command I think,might make things easier

    How does it work with looking at others code on the speccy though, with the c64 you'd just hit the action replay/expert button and you could get a good idea of the mechanics of how to do effects or what they were manipulating to do stuff.

    the screen thing "io" on action replay was handy.. it'd show you the screen mode.. if sprites were turned on/off etc..Is there anything like that on speccy?

    I'll have a look at that book though, funny his surname is Sinclair :) thanks
  • edited November 2011
    Just realised that nowhere on this thread does it say that Arjun did a properly-formatted version on his blog - thanks Arjun! It's at
    http://chuntey.wordpress.com/, third tutorial down at the time of writing.
  • edited November 2011
    IMHO if the attention span of potential readers doesn't exceed 30 mins, assembly coding (Z80 or otherwise) is not for them.

    Tutorials should really be written by people who have 2 qualities:
    1. Have a thorough understanding of the subject matter (=not in the case of a newbie).
    2. Know how to write in a manner that's accessible to newbies. This is a quality that doesn't depend on knowledge... a writer has it, or not.
    bigjon wrote: »
    OK, I'll convert it to html and post a link to it. I'll ask in another thread for free web-hosting recommendations.
    That would help, but again: if above doesn't apply then there's probably X number of books that are better @ explaining things to newbies than your tutorial. Not to discourage your efforts, but really: your time might be better spent deepening your understanding of Z80 assembly (practice!), than trying to explain your limited knowledge to 'also-newbies'. Z80 assembly isn't hard to learn, and there's already plenty of good books out there. All it takes is time/perseverance, and perhaps a certain mindset.
  • edited November 2011
    Ta bigjon, just wanted to say earlier this year your 30 min tutorial in conjunction with a few other things really helped me to get cracking on Z80 asm. I found the guidance on the actual act of assembly using spin most useful, without that I could imagine giving up early on.
  • edited November 2011
    R-Tape wrote: »
    Ta bigjon, just wanted to say earlier this year your 30 min tutorial in conjunction with a few other things really helped me to get cracking on Z80 asm. I found the guidance on the actual act of assembly using spin most useful, without that I could imagine giving up early on.

    ...If that's true then thanks from me too, as I've already [strike]nicked[/strike] borrowed R-Tape's redefine key routine as I've been learning... :D

    I only started learning it this year, and from a (personal) learner's point of view, I reckon the key motivation comes from having a clear game in mind that you want to create. Then taking it step by step - designing a sprite, printing it on screen, getting it moving etc.

    I've found a lot of guides very useful (e.g. the JC document), but as soon as I see a game listing, or a routine I don't instantly want to use, I'm not particularly interested.
  • edited November 2011
    Tutorials should really be written by people who have 2 qualities:
    1. Have a thorough understanding of the subject matter (=not in the case of a newbie).
    2. Know how to write in a manner that's accessible to newbies. This is a quality that doesn't depend on knowledge... a writer has it, or not.
    set.
    I think a more important third criterion is to empathise with the newbie and write from their point of view. My breakthrough moment in guitar-playing came when someone who had been playing only just longer than me showed me how to bend a string. I've already said that I wrote the tutorial I would have wished to use when I was starting - other approaches are of course available :)
  • edited September 2012
    bigjon wrote: »
    I've written a short tutorial to get you started writing machine code.
    Anyone fancy giving it a go and reporting back on your progress here?
    ZX SPECTRUM MACHINE CODE GAME 30 MINUTE TUTORIAL
    By bigjon (Jon Kingsman)
    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
    org 33000
    ;org 33000 = arrange to put our machine code at free, fast-running (over 32768) and memorable address in RAM
    ;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 256. 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 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 65355.
    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 65355 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 65355.
    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 8x8 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 tesco.net - atsign - jon.kingsman (reversed)
    
    i think im going to need more than a cup of tea to tackle this, and a prescription
  • edited September 2012
    buzzy wrote: »
    i think im going to need more than a cup of tea to tackle this, and a prescription
    How about a brandy and soda? I'd recommend you use the version at the link in my sig, which Arjun has very kindly reformatted in paragraphs etc so at least you'll get less of a headache reading it. Best wishes for your journey, and if you get stuck, post your questions on here and dozens of people who know an awful lot more than me will be only too pleased to answer them.
  • edited September 2012
    Is it possible for a mod to insert a message into post #1 on this thread redirecting casual browsers to the properly formatted (and, indeed, edited for accuracy) version on Arjun's chuntey blog? Something along the lines of

    *********************************************************************************************
    Message from the moderators - bigjon has requested that readers' attention be drawn to a better version of this tutorial, one which is easier to read: it can be found at
    http://chuntey.wordpress.com/2010/01/12/tutorial-zx-spectrum-machine-code-game-30-minutes/
    *********************************************************************************************

    Many thanks!
  • edited September 2012
    bigjon wrote: »
    How about a brandy and soda? I'd recommend you use the version at the link in my sig, which Arjun has very kindly reformatted in paragraphs etc so at least you'll get less of a headache reading it. Best wishes for your journey, and if you get stuck, post your questions on here and dozens of people who know an awful lot more than me will be only too pleased to answer them.

    thanks, much appreciated!
  • edited October 2012
    Hey, I'm slowly learning z80 assembly and your tutorial was a great help, thanks :)

    I was wondering, though, if you would consider expanding it to explain how certain things work exactly, like the collision detection, and what do the IN and CP instructions do.

    Thanks in advance :)
  • edited October 2012
    IN is used for reading the keyboard so you can detect keypresses. I wrote an article for ZX Shed explaining this and you can download the original magazine here.

    Unfortunately it was edited out from the copy on WoS (the editor was a fool, eh Lee? :lol: )
    I wanna tell you a story 'bout a woman I know...
  • edited October 2012
    Hey, thanks for that :) I actually read through the whole issue. Perhaps I should just scour the entire magazine archive for as many tutorials as I can find.

    Actually, if anyone could just point me to the magazines which ran an assembly tutorial, that'd be great. I really don't want to pour through the entire archive, checking each title individually.
  • edited October 2012
    Since this thread has been severely resurrected..
    fog wrote: »

    Most people fall into one of the following camps >

    * complete novice , don't know about hex, interupts, the op's / syntax etc. but maybe the next "mr smith" in the making..

    * know basic , wanna progress further and gain the obvious speed advantage

    * learned on another machine and want to see how it is different.


    For people in the first to second categories (though preferably with a decent knowledge of Sinclair Basic) - I did a ZX Basic Compiler tutorial some while ago when writing up a lil pac man game.

    Several tutorials at http://www.boriel.com/wiki/en/index.php/ZX_BASIC:Tutorials - and in particular, the one I wrote at http://goo.gl/4jPd5

    Since the whole game was completed (even if the tutorial wasn't quite) - you have a finished game and source code to look at [ http://www.worldofspectrum.org/infoseekid.cgi?id=0027713 ] after working through the whole tutorial. Hopefully I documented it enough :)

    Why is this relevant? I maintain that moving from Sinclair basic to compiled basic - with a compiler that allows you to inline machine code (which I demonstrate in that tutorial) is a great way to step to M/C - start with routines, and replace speed critical code with an optimized routine. Then, if you want to, you can go machine code all the way if you want! But while learning, it lets you wrap the code in some basic much more easily to run it and get it started...
  • edited October 2012
    Actually, I'm honestly and seriously impressed by the demoscene on the Spectrum and I wanted to put together a small demo in my spare time, just to learn how it's done.
  • Hi bigjon... if you are still around....

    I am using the Zeus Assembler to build this example on a real speccy and I am getting an error 0 - "illegal character or incomplete statement" at the line

    ld bc,$1fff; max waiting time

    I am new to MC and also the Zeus assembler but thought I would ask to see if there was an incompatibility issue.

    Thanks for the great effort applied here, I am definitely someone who learns by example.
  • I suspect you'll have to translate the machine code to something that Zeus understands. To this end, you may want to take a look at the Zeus Manual. For starters, Zeus expects all lines to have a line number. You can get Zeus to auto-generate them for you, or you can enter your own. Secondly, hex numbers should be preceded by the # sign instead of $, I believe. There may be other gotchas in the manual, so you may want to read it first!
  • edited April 2018
    Zeus? Wow...
    Post edited by Gedlion on
Sign In or Register to comment.