
Over the next few months I will be adding to this series of code snippets to help people make games on the spectrum next, so make sure you check back every now and then, or you could leave a comment and get notified when I post again!
Snippet 1
This little piece of code will let you check to see which scanline the beam is on as it draws the screen, so rather than using interrupts we can wait for any vertical position on the screen.
In your main loop, after you have done all your game code, you can wait for the beam to be offscreen and then go around the main loop again!
WasteLoop: call ReadRaster // check the current scan line ld a,192 // check for 192 cp l // are we at 192 yet jp nz,WasteLoop // nope so waste cycles jp MainLoop // do the whole thing black and blue again and again
So to the snippet
This code will read the scan line and return the value in HL, its all commented so there is not much more to say.
//-------------------------------------------------------------------------- // // Read the current Raster into HL // Out: hl = raster line on the screen // // Dirty a,bc // //-------------------------------------------------------------------------- ReadRaster: ld a,$1e // select the Active video line (MSB) ld bc,$243b // set bc to $243b select the NEXT register to read the video line out (c),a // tell the hardware we are going to read the video line ld bc,$253b // set bc to $253b so we can access the read value in a,(c) // so now read the port $253b NextREG data/value and 1 // mask off the Active line MSB ld h,a // store the high part in h for returning ld a,$1f // now read LSB of the Active raster line LSB (0-255) ld bc,$243b // as before set bc to $243b select the NEXT register out (c),a // tell the hardware we now want to read the register $1f ld bc,$253b // set bc to $253b so we can access the read value for the last time in a,(c) // read the register ld l,a // and store in the low part of hl ret
Snippet 2
The next snippet will generate pseudo random numbers, that can be used in many places in your game, such as determining baddie behaviour or just making things look more organic with a little random variance. In my shoot em’ up I am using a random number to rotate and flip my explosion sprites, so they don’t all look the same.
call getRandom // get the random number ld a,l // I only need the low part and %00001110 // mask off the rotations and mirrors ld (ix+object.mirrorRotateBits),a // bit 3 = X mirror, bit 2 = Y mirror, bit 1 = Rotate
So to the snippet
This code will generate a pseudo random number return the value in HL, its all commented so again there is not much more to say.
//-------------------------------------------------------------------------- // 16bit pseudo random number using the xor shift method // // out hl pseudorandom number // // dirty none //-------------------------------------------------------------------------- randomSeed: dw %0101101001100101 // seed (number) to start with getRandom: push af ld hl,(randomSeed) // get the last seed ld a,h // add high byte register rra // rotate right accumulator with carry ld a,r // adding in the Memory Refresh Register add a,l // now do the same with the low seed byte rra // rotate right accumulator with carry again xor h // exclusive or high seed byte with the accumulator ld h,a // put the accumulator into the high seed ld a,l // and put the low seed into the accumulator rra // rotate right accumulator with carry again ld a,h // do the same with the high using the carry from the low rra // rotate right accumulator with carry again xor l // exclusive or low seed byte with the accumulator ld l,a // yes store the accumulator in the low seed byte xor h // exclusive or high seed byte with the accumulator ld h,a // now store the accumulator in the high seed byte ld (randomSeed),hl // and store as the next seed pop af ret
In the past I have used a pseudo random number generator like this to make random levels that always come out exactly the same. Simply by setting the seed before I generate the level, I was able generate an identical sequence of random numbers which saved space as the whole level was just a 16 bit seed. Likewise on other games I have called the random number generator in the main loop which produced a more random set of random numbers.
Snippet 3
Now lets talk about the DMA, which is a fast processor to move large amounts of data pretty quickly, the data destination could be a ZX spectrum next system register, or another address in memory.
The DMA functions are quite self explanatory as I have used binary notation and I have added // 76543210 comments above so you can see which bits are being set, enabling you to learn more about the DMA in the docs.
The first function I use to upload sprites to the FPGA.
//---------------------------------------------------------------------- // Function: Upload a set of sprites // In: a = dest sprite shape to start at // de = length of data // hl = shape data //---------------------------------------------------------------------- define PORT_DMA_DATA $6b // labled zxnDMA in the docs dmaSprites: ld bc, $303b // tell the hardware the next value is a sprite slot out (c),a // write the the slot ld (dmaSpriteSource),hl // set the source address ld (dmaSpriteLength),de // now set the length of the data ld hl,dmaSpriteCode // now start the transfer by copying the dma ld b,dmaSpriteCodeLen // code to the DMA processor which will ld c,PORT_DMA_DATA // start as soon as all the data has gone otir // through the port ret //---------------------------------------------------------------------- // actual sprite DMA code //---------------------------------------------------------------------- dmaSpriteCode: db $83 // DMA Disable // 76543210 db %01111101 // WR0-Transfer mode, A -> B, write adress + block length dmaSpriteSource: dw 0 // WR0-Port A, Start address (source address) dmaSpriteLength: dw 0 // WR0-Block length (length in bytes) // 76543210 db %01010100 // WR1-read A time byte, increment, to memory, bitmask db %00000010 // WR1-Cycle length port A // 76543210 db %01101000 // WR2-write v, Port B address is fixed, Port B is IO db %00000010 // WR2-Cycle length port B // 76543210 db %10101101 // WR4-Continuous mode (use this for block tansfer), write dest adress dw PORT_SPRITE // WR4-Dest address (destination address) // 76543210 db %10000010 // WR5-Restart on end of block, RDY active LOW (STOP) // 76543210 db %11001111 // WR6-Load db %10000111 // WR6-Enable DMA endSpriteDmaCode: dmaSpriteCodeLen equ endSpriteDmaCode-dmaSpriteCode // calculate the length of the dma code
The next one is a modification of the above function to transfer blocks of memory from one place to another.
//---------------------------------------------------------------------- // // Function: DMA block of memory from one location to another // In: hl = source address // de = address of destination // bc = length of data // //---------------------------------------------------------------------- dmaBlock: ld (dmaBlockSource),hl // same process as above setting the source ld (dmaBlockLength),bc // and length of the data, ld (dmaBlockDest),de // but this time its not a port so we have a dest address ld hl,dmaBlockCode // now set start the transfer by copying the dma ld b,dmaBlockCodeLen // code to the DMA processor which will ld c,PORT_DMA_DATA // start as soon as all the data has gone otir // through the port ret //---------------------------------------------------------------------- // actual block DMA code //---------------------------------------------------------------------- dmaBlockCode: db $83 // DMA Disable // 76543210 db %01111101 // WR0-Transfer mode, A -> B, write adress + block length dmaBlockSource: dw 0 // WR0-Port A, Start address (source address) dmaBlockLength: dw 0 // WR0-Block length (length in bytes) // 76543210 db %01010100 // WR1-read A time byte, increment, to memory, bitmask db %00000010 // WR1-Cycle length port A // 76543210 db %01010000 // WR2-write v, memory address is increments, Port B is IO db %00000010 // WR2-Cycle length port B // 76543210 db %10101101 // WR4-Continuous mode (use this for block tansfer), write dest adress dmaBlockDest: dw 0 // WR4-Dest address (destination address) // 76543210 db %10000010 // WR5-Restart on end of block, RDY active LOW (STOP) // 76543210 db %11001111 // WR6-Load db %10000111 // WR6-Enable DMA endBlockDmaCode: dmaBlockCodeLen equ endBlockDmaCode-dmaBlockCode // calculate the length of the dma code
It should be said that I got the original DMA code from a demo in cSpect, and used it to learn, then modified to suit my needs.
One interesting thing you can do with the DMA is clear a block of memory like this.
// use the DMA to clear a block of memory ld hl,characterScreen // destination address ld (hl),0 // write zero to the destination ld de,hl // copy the source address to the destination inc de // increment the destination ld bc,$a00-1 // how many bytes to clear call dmaBlock // now use the dma to clear
More to come soon so please come back.
I want to be notified when you update this as I love your snippits 🙂
nice code 🙂
Great site.
I also want to be notified!
I’ve been enjoying reading through your code snippets. I’ve backed KS2 so can’t wait to get my Next. In the meantime trying to understand what I can in terms of coding for it. So looking forward to reading more on this great website.