Ziggurat

Occasional ramblings on games, generally retro related

​When my parents brought home a ZX81 one day (complete with wobbly 16K RAM pack, of course) I discovered the joy of programming. But it wasn't until I got my hands on a ZX Spectrum that my obsession with games really began, which continued with the C64, Amiga, right through to this day. The 80s and early 90s were an amazing time for games, not just for the games themselves but for the fascinating people behind them - it was truly a time of pioneers and creativity.

I myself have spent the last (almost) 20 years working in the games industry on all manner of platforms, most recently iOS. Ziggurat Development Ltd is my company here in NZ that provides contract programming services.

Diary of a Game: Part 1 - Wha' Happen?

Man, what happened to the last couple of months?! In the first part of this series I mentioned that my work tended to be rather full on.. at that point I knew I was going to be spending the end of January traveling on business, but what I didn't know was that that would be immediately followed by another, longer business trip. I had a whole 4 days at home during February, and not a single weekend. Work-wise it was a very productive and beneficial time, but it did mean that other things slipped through the cracks.

Despite that I did manage to make a bit of progress on the game. The items/balls/bubbles/whatever now fall down and pile up correctly. I'm still just using characters - I want to get the core game state update functional before worrying about making things look nicer.

In order to use characters and get alternating rows offset correctly I'm using a series of raster interrupts to modify the horizontal scroll value. In the video below, you can see how it looks before these interrupts start running, and once they are the coloured lines show the amount of raster time they take (which is pretty long due to them occurring on "bad lines").

The flashing border colours at the top of the screen show the raster time taken for various bits of the game tick routine. The black border is the routine that updates all the cells of what I call the board - each cell in the board is either empty or contains an item. The white shows the time spent re-drawing the board and its cells.

betterrandcapture2.gif

Yes, this is pretty terrible performance wise right now. My philosophy with coding is to do the easiest, quickest thing first and then step back and evaluate. I'm not smart enough to plan entire systems out ahead of time - in my experience once you have something doing what you originally thought you wanted, you often realise that it sucks or that there are issues that you hadn't even thought of beforehand. Being able to iterate rapidly on the things that matter is important.

Random Problems

Speaking of going with the quickest, easiest implementation first and terrible performance... This was my initial super basic pass at a random number generator:

random_num_in_range: 
    sta rand_hi

!rand_loop: 
    jsr random_num 
    cmp rand_hi 
    bcs !rand_loop- 
    rts

random_num: 
    lda $d012 
    eor $dc04 
    sbc $dc05 
    rts

rand_hi: .byte 0

The random_num routine was found on this lemon64 thread.

random_num_in_range took a value in the A register and generated a random number between 0 and that value - 1. This allowed me to quickly get to work on the game update routines, but every few frames it would take a very, very long time. The problem, of course, is how it makes sure the result is within the range the caller wanted. In this case it just kept on trying until it generated a number within the desired range - and when that range is small (in this case I was looking for a value between 0 and 5 for the column, and 1 and 4 for the colour), chances are it's gonna have to try a lot of times.

The typical approach for this kind of thing is to take the modulus of the random number with the upper value (e.g. for a number between 0 and 5 in C, you would do rand() % 6). I was too lazy to implement a general modulus routine (and I suspect it may be heavier than I would want) so I went with this:

.macro random_high(high) {
    jsr random_num
    .var next = pow(2, ceil(log(high)/log(2))) - 1
    and #next
!shift_loop:
    cmp #high    
    bcc !fine+
    beq !fine+
    sec
    sbc #high
!fine: 
}

Thanks to Kickassembler's very nice language features, I calculate the power of 2 value that's either equal to the passed in parameter or the next highest. Then I can subtract 1 to get a bitmask (e.g if the value passed in was 8, then the bitmask would be 7, i.e. %00000111), use the logical and, and if that result happens to be out of range then I just subtract the high value. Once again that'll give me a number between 0 and high- 1.

The problem with this is that if you want, say, a number between 0 and 5, then that's a fraction of the possible numbers returned by the random_num routine (which is a byte, so 0-255). So the distribution of numbers in the desired range is highly dependent on just how good your random number generator is.

This video shows the result of using the original, super cheap & basic routine.

badcapture2.gif

Not ideal, eh? A lot of repetition, with some values hardly ever getting chosen.

In the end I decided to go with the RNG described here.

random_num:
    lda seed
    beq doEor
    asl
    beq noEor //if the input was $80, skip the EOR
    bcc noEor
doEor:
    eor #$1d
noEor:
    sta seed
    rts
seed: .byte 0

Thanks to that you can see the much better distribution in the first video in this post, and it still manages to do it with a low cycle count.

For a much more in depth look at RNGs in 6502, then this is well worth checking out.

What Next?

Obviously an important part of a "match 3" style game is the matching, so I've been working on that. This has been a somewhat humbling exercise so far, as I started it thinking I would knock it out in no time, but then the reality of only having 1 general purpose register, 2 index registers and a very limited set of addressing modes dawned on me yet again. With that said, I've made some good progress and am hopeful it'll all be working nicely in time for the next post.