Gamecoding in machinecode on a ZX81.

Any discussions related to the creation of new hardware or software for the ZX80 or ZX81
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: Gamecoding in machinecode on a ZX81.

Post by David G »

OK, I see that I missed the "copy 20x" instruction:
Copy this part 20x into the deleted part.

Code: Select all

	block 20,128		; BLOCK places 20x character 128,inverted space
	db 118
I only had one block instead of 20 blocks

Up/Down working
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: Gamecoding in machinecode on a ZX81.

Post by David G »

  • movement is very smooth
  • using BC to track up/down and left/right position is slick
basic at this point, but exciting to see how it all works
Attachments
Game1-005.jpg
Game1-005.jpg (1.63 KiB) Viewed 8819 times
dr beep
Posts: 2060
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Gamecoding in machinecode on a ZX81.

Post by dr beep »

6 scoring

It is time to add some gameplay.

I was thinking to make it a game where within some time you need to hit a character that randomly
appears on the screen. If not in time a block will form so the next time you need to go around
to get to the next character.

The first step would a a random position on the screen where a character would appear.
Although BASIC has a RND-function we will make our own routine for it.
Random on the ZX81 is not random at all. It is a function that uses a startposition, does a
calculation and set a new start. The start can be altered with the timer. We will do something
similar but a lot easier. Some games need a good random generator, most games however can do with
a less perfect randomizer. We will do to.
Our base for a random number will be the ROM-routine.
In reverse order we will read values from the ROM as a random number.
I choose reverse order because I will use the framecounter as well to keep the pointer in the ROM
changing as well. Frames decrease so an increase could be unaltered when frames decrease.
A double decrease will always alter the pointer.

Edit GAME1-006.asm and save to GAME1-007.asm.
If your latest version is 005 then make it 007 as well to synchronize.
I am at 006, but don't see a change to 006 in this topic.

Now add this routine somewhere in the source apart from the mainprogram.
Youo could insert it between the field-routine and the display file (after the RET).

Code: Select all

rnd	ld de,0			; get the pointer in ROM
	ld hl,(frames)		; for randomness get framecounter
	add hl,de		; add framecounter
	dec hl			; when framecounter is unchanged make sure a change is done
	ld a,h			; H can have any value, but ROM is #0000-#1FFF
	and #0f			; only #0000-#0f00 is what we use
	ld h,a			; set pointer back within ROM
	ld (rnd+1),hl		; save current pointer (selfmodifying code!!!)
	ld a,(hl)		; get value in ROM
range	sub 20			; we need 0-19 only
	jr nc,range
	adc a,20		; undo last subtraction, range 1-20
	ret			; back to mainprogram
The explanation of the code is added, perhaps I need to explain selfmodifying code a bit.
The first time this routine is called DE will have the value 0.
The second time the value has changed. This saves 3(!) bytes elsewhere where we would store it
elsewhere in memory. 2 bytes for the savelocation and 1 byte for the opcode LD DE,(RAMPLACE).


Now alter the start of the game a bit.
We need it likes this when we are playing the game later.

Code: Select all

; free codeable memory
gamecode ld bc,#0101	; B=1, C=1, first position on the screen
newdot	push bc		; save xy player 
	call rnd	; get a random number	
	ld b,a		; set as Y
	call rnd	; get a next random number
	ld c,a		; st as X
	call field
	ld (hl),27+128	; place an inverted dot
	pop bc		; get xy player

moveloop call field
	ld (hl),"O"-27+128	; we write a "O" INVERTED on the screen
Save, compile and run.
See what happens when you go to the dot.
dr beep
Posts: 2060
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Gamecoding in machinecode on a ZX81.

Post by dr beep »

6 scoring (2)

So when you go over the dot it disappears and the game is like before.
We need to add a test if we hit the dot. And if we do we need a new dot.

Let us add that part.

Where can we test if we hit the dot (and later if we hit a block)?
Just before we display our cursor we can read the screenvalue.
If it hits the dot we need to erase it and get a new one.

Code: Select all

gamecode ld bc,#0101	; B=1, C=1, first position on the screen
newdot	push bc		; save xy player 
	call rnd	; get a random number	
	ld b,a		; set as Y
	call rnd	; get a next random number
	ld c,a		; set as X
	call field
	ld (hl),27+128	; place an inverted dot
	pop bc		; get xy player

moveloop call field
	ld a,(hl)		; get current screen value
	cp 27+128		; is it a dot 
	jr nz,test2
	ld (hl),128		; erase the dot
	jr newdot		;
test2	nop			; now NOP, added for later.
	ld (hl),"O"-27+128	; we write a "O" on the screen
Save, compile, run to see what it is now...
dr beep
Posts: 2060
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Gamecoding in machinecode on a ZX81.

Post by dr beep »

6 scoring (3)

When you play the game 2 things come clear.....
The game is too fast with just 2 frames delay, we will set it to 4.

Code: Select all

	push hl	
	ld hl,frames		; make hl point to the timecounter
	ld a,(hl)		; get the timecounter in A
	sub 4			; take of 4, the number we want to test
wfr	cp (hl)			; test if the value is the same
	jr nz,wfr		; NOW WE WAIT UNTIL FRAMES MATCHES A!
	pop hl
The controls stop when we press 2 keys, we will fix that later.

Besides that it plays like a game but we need an incentive for scoring
or gameover.

Let us add the name of the game and screeninfo about scores:

Code: Select all

; the display file, Code the lines needed.
dfile 	db 118

; each line has text and a Newline
	db 0,0,0,0,0,"D"+101,"O"+101,"T"+101,"B"+101,"U"+101,"S"+101,"T"+101,"E"+101,"R"+101,"S"+101,118
score   db 28,28,28,28,28,0,0,0,0
timer   db 28,28,0,0,0,0
hiscore db 28,28,28,28,28,118
screen	block 20,128		; BLOCK places 20x character 128,inverted space
We add a (re)start option wit the following code

Code: Select all

; free codeable memory
gamecode ld a,(lastk)		; get inport from lastkey
	sub 191			; is it HJKL-NL?
	jr nz,gamecode		; if not we wait until pressed

[/ccode]

Now we start with a keypress.... next to do, reset score and add timer
David G
Posts: 387
Joined: Thu Jul 17, 2014 7:58 am
Location: 21 North, 156 West

Re: Gamecoding in machinecode on a ZX81.

Post by David G »

all working, step by step
dr beep
Posts: 2060
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Gamecoding in machinecode on a ZX81.

Post by dr beep »

OK,

I was away for a few weeks to code something for 40th anniversary, but I am back.

--------------------------------------


6 scoring (4)


Ok, so we can play but we need an incentive to get a score.

Let us add a timer with a minimal setting.
We already have a random-routine, which we can re-use to set a random time.


Get our progam and save it as Game1-010.asm (we well skip a few numbers).

The right place to add a random timing is between NEWDOT and MOVELOOP.
After we found a new position for the dot we can add a timer

Code: Select all

newdot	push bc		; save xy player 
	call rnd	; get a random number	
	ld b,a		; set as Y
	call rnd	; get a next random number
	ld c,a		; set as X
	call field
	ld (hl),27+128	; place an inverted dot

	call rnd	; a number between 1 and 20
	add a,10	; at least 10 moves to get on the place
	ld (timecnt+1),a ; we use SELFMODIFYINGCODE, explained at timecnt-label 

	pop bc		; get xy player

moveloop call field

The second part we need is the decreaseloop.

Code: Select all

test2	nop			; now NOP, added for later.
	ld (hl),"O"-27+128	; we write a "O" on the screen

timecnt ld a,0			; value changes in every move. shorter than LD A,(TIMECNT)
	dec a			; take off a loop
	ld (timecnt+1),a	; save counter
	push af			; save counter for test after display
	call dispnr		; show the number time left
	pop af			; get counter
	or a			; test for zero, shorter than CP 0 !!!
	jr nz,playon		; we still can play
	jp gamecode		; temporary end of game 
playon	push hl	
	ld hl,frames		; make hl point to the timecounter
	ld a,(hl)		; get the timecounter in A
	sub 4			; take of 2, the number we want to test
wfr	cp (hl)			; test if the value is the same

And finally we need a routine for the number display.
Score is an increasing number which is easier to disply (next thing we will do).
Decreasing is harder, but a 2 digit number can be displayed simple with this routine.
After the label OKMOVE and before FIELD-routine we add this routine

Code: Select all

okmove  jr moveloop		; stay in playloop

; Show remaining time on screen, value in A
dispnr	push hl			; keep originl HL needed later
	ld hl,timer		; the 10-digit on the screen
	ld (hl),27		; we preset this with "-10"
timset	inc (hl)		; add 10 points
	sub 10			; take of 10 from the value we have
	jr nc,timset		; keep doing the 10-counter		
	inc hl			; now go to units
	add a,38		; we are 10 under 0 and we need range ascii "0"="9"
	ld (hl),a		; display units
	pop hl			; retrieve HL
	ret

; BC = XY, return HL=field on screen

Save and compile, see what goes right and what goes wrong.....
dr beep
Posts: 2060
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Gamecoding in machinecode on a ZX81.

Post by dr beep »

6 scoring (5)


So version 10 showed us a few things.

1) timer only went down when we pressed a key
2) after missing a dot within time gameover did not clear the screen
3) we still don't get a score.
4) time to get to a dot is (too) short
5) we are allowed more missed items than just 1.

Save as Game1-011.asm to keep track of the changes.
we will do the first 3 changes.


1)
The first change is easy.

Code: Select all

wkey	ld bc,(lastk)		; get key information
	ld a,c			; copy C in A to keep C
	inc a			; Test if NOKEY pressed
;	jr z,wkey		; NO KEY, wait for key

	ld (hl),128		; we pressed a key, so we erase the old position

	call nz,#7bd		; BC holds info about key, ROM can translated this here
First we skip the wait loop, we continue after reading LASTK even if we don't press a key.
However if we enter the ROM-routine without a key pressed then the routine will never return.
So the call to translate portreading into a key value must only be called when a key is pressed.
This is possible with the CALL under condition. We change the CALL #7BD into CALL NZ,#7BD.

If a key is pressed the routine will return the keyvalue. If no key is pressed A will hold zero.
This value is not a value of a key we need to control so this will work.

BTW... The TEST2 NOP remains. This is needed when we have missed a dot but we are allowed to miss a few.


2)
We add a clearscreen routine after starting a new game

Code: Select all

gamecode ld a,(lastk)
	sub 191
	jr nz,gamecode

cls	ld hl,screen		; start of screen
cl1	ld (hl),128		; set inverted space
	inc hl			; go to next field
	ld a,(hl)		; test end of line
	cp #76
	jr nz,cl1		; do full line
	inc hl			; go to next field
	ld a,(hl)		; test end of screen
	cp #e9
	jr nz,cl1		; if not, do next field
Note that this clearscreen is not a CLS like in BASIC.
In BASIC the screen would be filled with spaces where we use inverted spaces.


3)
Time for some scoring:
Just above TEST2 we caught a dot and made a jump to NEWDOT-routine to get a new dot position.
Before the jump we add a score routine.

Code: Select all


	ld (hl),128		; erase the dot
	
	ld a,(timecnt+1)	; we get the remaining time
	push bc			; we save coordinates
	ld b,a			; loop counter in B
addtime	ld hl,score+4		; start at units of score
	call addpoint		; add 1 point
	djnz addtime		; add all remaining time
	pop bc 			; get coordinates back

	jr newdot		; make a new dot

ten     ld (hl),28
	dec hl
addpoint inc (hl)		; add a point to the score
	ld a,(hl)
	cp 38			; test if "9" goes to "A"
	jr z,ten		; if so, alter score to add points to 10/100/1000
	ret			; 1 point added



Compile and see how far we are now.
Spinnetti
Posts: 253
Joined: Sat Sep 12, 2020 11:29 pm

Re: Gamecoding in machinecode on a ZX81.

Post by Spinnetti »

I feel really dumb when I see what you guys can do! Me and my best buddy are just finishing up our rendition of Donkey Kong. We started it in 1983, so we aren't the fastest coders lol. While "done", some sections are really inelegant and I want to rewrite those. If its ok to ask here (just delete or move if not) How do I manage an array of addresses? In Donkey Kong (not an exact copy, but our flavor), I want to use a couple 1D arrays to manage the data.

Like this

Barrels: $nnnn, $nn, $nnnn, $nn.... where the two bytes are the location and the next byte is a direction variable (or they could be two arrays_

What I'm trying to do conceptually is:

LD B,Number of barrels (fixed)
Loop:
LD Barrels ;first barrel address in "Barrels"
LD direction ;first direction byte
<do some stuff>
LD (current barrel),$1C ;print out a barrel
INC pointer to the next barrel/direction triad
DJNZ loop

I'm sure this is simple, but I'm stumped!

Thanks!
Zeddy: ZX80, ZX81/ZXpand, TS1000/ZXpand, TS1500/Zxpand+
Speccy: 48k, +, +2, +3, TS2068, "Bare Metal" Pi, Next KS2, IF1/Microdrives/Vdrive/Light Gun/VGA-Joy
QL: Minerva/QL-VGA/Custom PSU
C5: 24v, LiFE battery, Disc brakes
dr beep
Posts: 2060
Joined: Thu Jun 16, 2011 8:35 am
Location: Boxmeer

Re: Gamecoding in machinecode on a ZX81.

Post by dr beep »

Spinnetti wrote: Fri Apr 16, 2021 3:28 am I feel really dumb when I see what you guys can do! Me and my best buddy are just finishing up our rendition of Donkey Kong. We started it in 1983, so we aren't the fastest coders lol. While "done", some sections are really inelegant and I want to rewrite those. If its ok to ask here (just delete or move if not) How do I manage an array of addresses? In Donkey Kong (not an exact copy, but our flavor), I want to use a couple 1D arrays to manage the data.

Like this

Barrels: $nnnn, $nn, $nnnn, $nn.... where the two bytes are the location and the next byte is a direction variable (or they could be two arrays_

What I'm trying to do conceptually is:

LD B,Number of barrels (fixed)
Loop:
LD Barrels ;first barrel address in "Barrels"
LD direction ;first direction byte
<do some stuff>
LD (current barrel),$1C ;print out a barrel
INC pointer to the next barrel/direction triad
DJNZ loop

I'm sure this is simple, but I'm stumped!

Thanks!
the source of digg it has a routine to move and drop boulders, similar to your question.

https://sinclairzxworld.com/viewtopic.p ... 728#p41728
Post Reply