Jump to content
IGNORED

Adventures in ZX Spectrum Dev


Alexlotl
 Share

Recommended Posts

On holiday this week in a badly sound-insulated holiday house, so I haven't had a chance to watch that vid - I'll give it a look next week. The comments section yielded this very useful link, though - http://www.speccyvirgins.com/

 

I was going to do a quick-and-dirty post about colour attribute data (which seems to be a lot more straightforward than the actual pixel memory layout), but I thought I'd come up with a nice way of drawing my willy (hnuk) at any block coordinate. So the other night, I wrote a nice little routine to do just that. Then, doing some Googling afterwards, I struck upon this:

 

eureka.png.ef3ab55120f7e1826043315b85832191.png

 

(From this site)

 

This is crazy - you can turn your X and Y values into memory addresses just through (fast!) bit-twiddling. This has opened my eyes a bit - clearly anything that splits on any power of two is open to this kind of solution using masking, shifting, etc.

 

Anyway, inspired by the above, I re-wrote my coordinate routine. It's not the same as the one at the link, as it's not down to pixel level, only to block level. I also added a little routine to increment the y value by 1, moving to the next band if  necessary; this was partially to ensure I understood all the bit shifting, and partially as I'm not sure of the best approach. I could store the original X and Y values on the stack, recover them after drawing the first block then call calcoords again, but I'd need to sit down and work out the T-state difference between the two approaches. Also, can use of the stack be measured solely in T-states, or does memory latency become a factor once you start using RAM? But isn't each call to CALL/RET using the stack anyway? More research needed.

 

	DEVICE ZXSPECTRUM48	; compiler directive - target platform

	ORG $8000		; go to $8000 - this is the start of the uncontended (fast!) RAM

main:
	ld a, $00		; load the colour black into the accumulator
	out ($FE), a		; rom call - set border colour
	ld b,2			; x block co-ordinate (0-31)
	ld c,7			; y block co-ordinate (0-23)
	call drawwilly
	ld b,24
	ld c,15
	call drawwilly
	ret

drawwilly:
	ld hl, minerwilly	; load the willy sprite data
	call calcoords		; using coords in b and c, put target memory address in de
	call drawblock		; draw first block
	call incy		; shift destination address in de to next line vertically
	call drawblock		; draw second block
	ret

calcoords:
	ld a,c 			; copy y-coord to accumulator
	and %00011000		; Wipes out all but the bits relating to band
	or %01000000		; adds the 010 prefix
	ld d,a			; copy to MSB of destination
	ld a,c			; re-copy y-coord to accumulator
	and %00000111		; Wipes out all but the bits relating to row within band
	rra			; rotate right 4 times: 0000 00YY (Y)
	rra			; Y000 000Y (Y)
	rra			; YY00 0000 (Y)
	rra			; YYY0 0000 (0)
	add a,b			; simply add x-coord
	ld e,a			; copy accumulator to LSB of destination
	ret
	
incy:
	ld a,e			; copy LSB into accumulator
	cp %11100000		; compare with value of last-line-of-band y coords
	jp nc, nextband		; if equal to or larger, we need a new band calculating
	add a,$20		; otherwise, just add 32 for the next row
	ld e,a			; copy the updated LSB back into the destination
	ld a,d			; get the MSB of the destination
	and %01011000		; Resets Y pixel offset.
	ld d,a			; copy back into destination MSB	 
	ret

nextband:
	and %00011111		; Blanks out y coords of LSB
	ld e,a			; copy back to destination LSB
	ld a,d			; get destination MSB
	add a,$08		; increment band bit - this will push us into attribute space if we're already in the last band
	and %01011000		; 0101 1000 - ensures 010 prefix is maintained, wiping out any carry. Also wipes Y pixel offset.
	ld d,a			; copy back into destination MSB	
	ret

drawblock:
	ld b, 8			; line counter - 8 lines in block
	jp lineloop

nextline:
	inc d			; advance to next line in block - moved this to ensure it's not run on first loop
lineloop:
	ld a, (hl)		; load the line of sprite data into accumulator
	ld (de),a		; copy this into the current target memory location
	inc hl			; increment sprite pointer - we want to do this even if we've finished drawing this block
	djnz nextline		; decrements b and jumps to nextline if not zero
	ret

; Data
minerwilly:
	DEFB	$06,$3E,$7C,$34,$3E,$3C,$18,$3C
	DEFB	$7E,$7E,$F7,$FB,$3C,$76,$6E,$77

	savesna "willcoordinated.sna", main

Notes

  • Discovered I can use % prefixed binary numbers, which is useful for bit-twiddling
  • djnz is a neat instruction that decrements B, then jumps to a label if it's not zero. Under the bonnet this is a relative jump, which is limited to +/-128 bytes, but that seems like acres here, and it's a whisper faster than separate dec and jp nz commands.
  • Nicked a bit from one of Jonathan Cauldwell's samples to set the border to black - this made is easier to confirm my willies were drawing correctly.
  • Annoyingly, there are only three bands, not four (not a power of two!), so there's no easy bitmask solution to stop the nextband routine potentially shifting sprite data into attribute space (I haven't crunched the figures yet, but I'm guessing the entirety of attribute space sits the memory that would be used by that fourth band, if it existed). I could add some extra logic here to defend against that, but I think a caveat emptor approach will suffice.

And here's the not-very-thrilling output. Both of these willies are straddling screen bands, the one on the left straddling the first and second band, and the one on the right straddling the second and third.

 

twowillies.png.8e6feb3c0985ce0ec50f497ef7229818.png

 

Next time - colour, finally.

Link to comment
Share on other sites

In other news, I've been reading ZX Spectrum Machine Language for the Absolute Beginner, and I think it's a pretty damn good book. The registers-as-hands-and-feet metaphor seemed a bit contrived at first, but makes for nice explanations about some of the Z80's behaviour (I liked the concept of A/HL as "preferred hands", like my right hand). It also does a good job of communicating a lot of Z80 conventions and neat tricks (like using XOR A as a quick way of setting the accumulator to 0). There's some skippable stuff about interfacing with BASIC and hand-coding the machine code, but other than that it's pretty tightly written.

 

Recommended, thus far - check out the link in my first post.

 

Link to comment
Share on other sites

Busy week, but will hopefully get some more time on this soon.

 

Got this in the post today - it's pristine, spine never cracked! Smells weird though, what I'm guessing was once New Textbook Smell, but is now 35 years old.

 

43cdb9d4ddfd7c30989438b4aa4e4b1d.jpg

 

As mentioned briefly in my first post, The Oliver Twins confirmed to me via Twitter that they named Wizard Zaks after the author of this book, such was its bible status back in the day.

 

Also spent some of the evening watching this while doing the dishes:

 

 

Uncle Clive doesn't seem to be the loveable character I'd imagined as a child. Compelling stuff though, with a great soundtrack.

 

Link to comment
Share on other sites

  • 2 years later...
20 minutes ago, deKay said:

Can anyone recommend a plugin and assembler for use with Visual Studio Code?

 

I used this turorial:

 

https://www.specnext.com/forum/viewtopic.php?f=16&t=304

 

Basically you use pasmo as an assembler, configured through the tasks.json. For syntax colouring, I just grabbed the one that VS Code recommended to me.

Link to comment
Share on other sites

Mentioned this before, but the Jonathon Cauldwell book "How To Write Spectrum Games" is a great introduction:

 

https://spectrumcomputing.co.uk/zxdb/sinclair/entries/2001501/HowToWriteSpectrumGames(v1.0).pdf

 

It's a little inscrutible in places, but it forces you to think about what's happening, rather than just regurgitating the listings.

Link to comment
Share on other sites

It always seems a bit of a shame that the original developers didn't seem great at keeping "how to" documentation, things like how Dave Perry managed to get so much colour out of the Spectrum or how Dominic Cummings managed to get such nice smooth scrolling (with colour) for Zynaps. I guess people just didn't keep much documentation in those days, but it does mean that anyone coming to it now has to relearn all that knowledge that the original developers knew (but with very minimal documentation!) 

 

I've often wondered if the Commodore 64 had better documentation, which might explain why there seems to be more home-brew coded from scratch, whereas a lot of Spectrum home-brew uses things like AGD.

Link to comment
Share on other sites

15 minutes ago, gone fishin' said:

It always seems a bit of a shame that the original developers didn't seem great at keeping "how to" documentation, things like how Dave Perry managed to get so much colour out of the Spectrum or how Dominic Cummings managed to get such nice smooth scrolling (with colour) for Zynaps. I guess people just didn't keep much documentation in those days, but it does mean that anyone coming to it now has to relearn all that knowledge that the original developers knew (but with very minimal documentation!) 

 

I've often wondered if the Commodore 64 had better documentation, which might explain why there seems to be more home-brew coded from scratch, whereas a lot of Spectrum home-brew uses things like AGD.

 

Just on the subject of documentation, I've been digging into the commented disassembly of the Jet Set Willy code:

 

https://skoolkid.github.io/jetsetwilly/dec/maps/all.html

 

It's absolutely fascinating to see how it was built, and even having just a little knowledge of assembly language you can glean a lot from it. 

 

This game seemed so big and complex when I was a kid, but most of it is build around a couple of hundred lines of code and whole bunch of data.

Link to comment
Share on other sites

  • 2 weeks later...

I had the day off today following the Super Bowl and particular plans, so I locked myself in the back bedroom and tried to figure out a bit more assembler.

 

I started with modest goals. All I wanted to achieve was the rendering of a couple of enemy sprites in a way that I could easily extend. If I could make them walk patrols, so much the better. It all seemed simple, until I got lost in a world of register management and memory locations, that is!

 

 

I think I'm at a point where I need to step back and understand how you do this at scale. Even this tiny, simplistic demo is taxing me. Where I'm falling over is keeping track of registers and their usage, and ensuring that I don't use one with an unexpected value. I spent a lot of time this afternoon wondering just why one of the enemy sprites was rendering a corrupted garbage, only to find that I'd failed to reset one of the halves of a register pair. I'm also having difficulty remembering to differentiate between 8 bit and 16 bit numbers.

 

That said, I am making progress. I'm trying to build this in a way that I can extend - so the enemy placement and platforms are part of a level structure that I can replicate with new layouts and just point the engine at that part of memory to run it. So far, unless I'm missing a trick, this involes lots of offseting from particular areas of memory to store details like which direction the enemies move, what the extent of their patrol is... But, the upshot is that I can add in extra enemies now just by adding a row of 10 or so numbers.

 

Next step is probably to revisit this memory structure and tidy it up so that it allows for a range of level complexities. Oh, and maybe look at some kind of collision detection and a life counter.

Link to comment
Share on other sites

So I thought to myself, I've learned the absolute basics, why not convert an arcade game? Okay, it's a lofty ambition, but I think I've picked one that is at least possible.

The game is 1982's The Pit. A single screen proto-Boulderdash game by Zileg/Centuri/Taito in which you have to descend into a mine and collect diamonds, avoiding enemy robots, falling rocks and a tank that wants to stop you so much it is prepared to level a mountain. The Pit in the title refers to an acid filled monster room that must be traversed before you can escape with the loot. Here's what it looks like in all it's glory:
 



The game received a C64 port (https://www.youtube.com/watch?v=VNDOdggdORo), but not a Spectrum one, which I think is a shame. The palette almost looks like a Spectrum game, and the 8x8 tiles seem suited to the Spectrum screen.

So I've made a start on drawing the screen and figuring out how I'm going to fit everything in. I kind of want to make it as faithful to the arcade as possible, with compromises only where I need to. And here's where I run into my first problem:

20200206.png.4f3a4ff6fc1d283b2a21eeef2f6419a7.png

The arcade has about 29 character lines of playspace, and another 3 for the score at the top and high score at the bottom. I have 24 to play around with. The screenshot above is my attempt to modify the playspace to fit on a single screen, losing some lines here and there. It would probably work, but a lot of the rock layouts have very specific requirements for layout, so it would be a big compromise.

I'm currently thinking about making it a push-scroll instead, only displaying the half+ of the screen the player is in at any one time. Allowing 3 lines for scores, this would leave 21 lines of the 29 visible, which might be a worthwhile compromise to preserve the layout. It's not a particularly fast-moving game, so there's not a lot of need to see all of the screen. The C64 game, for the record, cut it down in a similar way as above. I guess I'll just have to see how it feels.

So, early days. Really, really early days... Other issues I can anticipate right now:

1) Colour - while it looks well constrained in 8x8 blocks, the movement is all pixel based, and dirt can be mined across character cell boundaries. Quite how I'll overcome this with the different coloured rocks/player/dirt, I'm not sure.
2) Movement - the arcade has a very precise movement feel to it, which requires accuracy and can be infuriating at times. Replicating this will be important for retaining the feel of the game.

Of course, I'll probably get distracted by another project and not get that far... but maybe the world will get the Spectrum port of The Pit it has craved so long! 

Link to comment
Share on other sites

A nice stormy weekend, so the perfect time to catch up on 35 years of missed Spectrum coding time.

Here's where I'm currently at:
 



I decided to go with the push screen scrolling, which I think works quite well. Of course, this needed me to figure out about screen buffers and what not, and fast copying between the buffer and the screen. Honestly, I thought the layout of the Spectrum screen would melt my brain, but I figured it out eventually. Spent most of yesterday afternoon trying to figure out why a little line of corruption was sneaking onto my restored screen, only to remember I was using a stack manipulation technique to do the copy and I'd forgotten to disable interupts. Doh.

I spent today making the man move around and doing a first pass at collision detection, which is a little complicated because he can move into two cells at any one move. I've got it working and it seems to work well, but I feel as though I wrote a LOT of code to do it. Definitely a place I'll revisit for efficiency later.

I also added the title screen, a reasonably faithful version of the arcade. It's so dumb, I couldn't resist.

Link to comment
Share on other sites

More progress. A spaceship and a "zonker" appear.
 



I've also added in all the rocks from the first level. Which now means I'll need to implement the digging mechanism to be able to get around the whole level. It'll be enouragement to face it, I suppose.

Had an interesting bug over the last few days, where a single line of my sprite's graphic would render one character space above his head, but only when stradling two particular columns. This seemed to just appear as I added some unrelated code (actually, it was always there, just hidden by the background. For some reason, it moved to a different column and became far more visible). I spent a lot of time debugging that, until I realised I was inc'ing l, but not h. And in a particular circumstance where l was FF, this was causing that the memory pointer to write one line up.

Thank goodness for the Z80 Debugger is all I can say!

Got a list of tasks I need to tackle now. It's nice to be able to treat this like a project and plan out what I'm going to work on next. I like how it's all breaking down into manageable modules - implement the ship, implement the tank, implement digging, etc. It makes it feel achievable.

Link to comment
Share on other sites

This time, I ran into my first head scratcher - how to reproduce the digging mechanic. Initially, I toyed with the idea of doing the movement and the digging as character-based. This would have made things so much easier, especially with colour clash, with figuring out what you can dig, what you can't, where you can move etc. etc.

However, part of the uniqueness of the arcade game is the pixel movement, which allows you to end up with rocks perched on single pixels, or get hung up on a narrow entrance while a robot chases you. So sticking to character movement would have lost a lot of the charm of the game... and I decided to go with pixel movement.

First problem was that I was using attrs to do collision detection. If I wanted to avoid too much colour clash, I needed to make the player yellow-on-black, like the dirt, instead of white-on-black. Which meant that collision detection no longer worked. So first thing was to re-write collision detection with a routine that looked at the pixels the player is moving into. This allowed me to coopt this to detect a case where the destination space had dirt in it and whether the player could "dig it". At this point, my brain melted, as I realised how many different permutations there could possibly be in a well dug board.

Then I stumbled on something useful - in the arcade, pixel movement only applies to up/down movement. For left right movement, the game moves 8 pixels at a time. Which means that dirt can't be halved across columns, and it cuts the number of possibilities right down.

After that, it became a lot clearer. A couple of edge cases needed for digging up or down into dirt on top of or under rocks, a bit of animation and a flash effect (embiggen the video to see), and here we are...
 



I'm happy with it! Needs some tweaking and bug fixing here and there, but its the first real game mechanic done.

Link to comment
Share on other sites

Enjoying this thread.  I too have been (for a long while) dallying with z80 on a Spectrum, and - whilst I appreciate the new games that are created - there's a lot of identikit feeling AGD type games, and the z88dk C stuff can create amazing results but the "from scratch" asm stuff and approach is what interests me too.  I'm happy enough to ROM call bits and bobs, but I think the feeling of having reinvented the wheel is reward in itself.  It's not like development time/release schedules mean off-the-shelf code is the way to go.

 

I've currently been messing with a Proportional Printing routine, that I think has a couple of cool features and should be efficient for the storage of data, plus a (now very well featured) font designer that comes in two flavours - Fixed and Proportional Width.  (And in utterly reinventing the wheel *again* I've not reused any code from my fixed width 6 pixel font/42 Column stream printing routine I previously did).

That has all come about because I wanted a rotated font stored in memory to use as a scroller in my attribute only double buffered demo.

 

I'm also at the high level planning stage of converting two games I made for BASIC competitions into asm.  I *think* they're both within grasp.

 

I will, at some point, look at posting w.i.p. either here or spectrumcomputing.  But I get a bit "it needs to be finished to some arbitrary degree" before I post.  And once I've got there, I just carry on coding and set a new goal.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue. Use of this website is subject to our Privacy Policy, Terms of Use, and Guidelines.