JSnakeDevDiary180212 - simondotm/stardot-wiki GitHub Wiki
<----Back to JSnake Dev Diary Index
I started from scratch a couple of weeks back. This first entry is a review of where I've got to thus far. I daresay subsequent entries may be more brief as the changes will likely be smaller, incremental steps.
It seems to me that both the pleasure and the pain of coding in assembler is that everything is done in tiny baby steps, i.e. it often takes a long series of (short) 6502 assembler instructions to accomplish something, whereas in a high level language, only one or two lines of code may be needed to do the same thing. I found myself returning to this thought many times as I worked out my first goal: plotting a series of pixels to the screen in a horizontal line. Yes, it's a very humble goal, but I've found that taking many small steps has resulted in achievable results, which gives me incentive to continue.
I learned that MODE 2 would likely be the easiest mode to use to move pixels around the screen. Why? Because in MODE 2, a byte in screen memory represents two pixels.. so you ought to be able to move two pixels at a time by manipulating bytes. It's genereally easier (and quicker) to add and subtract bytes than it is to manipulate bits in a byte.
Kees van Oss succintly describes MODE 2 and 5 pixels in his dev diary for Atom Galaforce. I've borrowed his diagram to illustrate:
His full dev diary for Atom Galaforce can be found at: http://www.retrosoftware.co.uk/wiki/index.php/GalaforceAtomDevDiary
Before even writing any assembler, to test the idea that simply storing a value in memory would result in pixel/s being drawn on the screen, I typed this:
>MODE 2
> ?&4000=&0C
This will plot two green pixels (horizontally) to the screen.
I then read about Beeb screen addressing with horror. Knowing that MODE 2 is a 20K screen mode starting at &3000, ending at &7FFF - I foolishly assumed that addressing would simply be consecutive bytes starting from top left of screen until bottom right is reached.. ie top line of screen would be &3000 to &304F, 2nd line &3050 to &30A0 etc. Sadly, it's more complicated. Instead, BBC screen modes (apart from 7) are based on a series of 8x8 pixel blocks running horizontally, left to right (and then on to the next row) from the top left of the screen down to the bottom right. This is because the 6845 CRTC video chip used by the BBC is primarily designed to display 8x8 pixel characters. This is a pain if I want to move a set of pixels across the screen as I can't simply add one to an address each time I want to write to a pixel to the right, for example.
This is illustrated below, where I want to write 8 pixels to the screen. Each box represents 8x8 bytes in MODE2, where each byte stores 2 pixels. Note that screen addressing in MODE2 starts at &3000, top left, but I've start at 0 for clarity. See how in this case, to plot 8 pixels I'd have to write to addresses 26, 27, 60 and 61. Not nice.

To make life easier, a handy routine could be written to return a screen address derived from X and Y coordinate input parameters (similar to the PLOT or DRAW BASIC instructions). Being such a newbie to 6502 I wasn't thrilled at the prospect of trying to work out such a routine at this stage. Fortunately, both assembler books I have referenced have such routines. Further, Steve O'Leary has written some liberally commented assembler routines that handle this issue and published it in the Sample Code section of this site: http://www.retrosoftware.co.uk/wiki/index.php/Calculate_Screen_Address
I chose to use Steve's code as it was more generic and easier to use than the examples in the assembler books - but the actual text in chapters 7 and 10 of "Mastering Assembler" and chapter 10 of "Creative assembler" are well worth the read for a very full explanation of screen addressing which helped me better understand Steve's routine.
Using Steve's Screen Address routine.. I was soon able to progress to these giddy heights:
As mentioned earlier, I'm progressing in small steps.. so I'm not using sprites just yet. All I'm doing is plotting a 2x3 blob of green pixels to screen, as per this code snippet:
jsr ScreenStartAddress
lda #&0C ; &0C = 2 green pixels
eor (XYScreenAddress,X) : sta (XYScreenAddress,X); EOR &0C with contents of (screen)address. Store (i.e plot) result to screen address
sta snakehead_scrn_addr ; store EORed result of plotted pixel - ie it's colour. If it is green (&0C), no collision, otherwise, we have a collision.
inc YOrd
jsr ScreenStartAddress
lda #&0C
ldx #0
eor (XYScreenAddress,X) :sta (XYScreenAddress,X)
sta snakehead_scrn_addr+1
inc YOrd
jsr ScreenStartAddress
lda #&0C
ldx #0
eor (XYScreenAddress,X) : sta (XYScreenAddress,X)
sta snakehead_scrn_addr+2
dec YOrd
dec YOrd
So to move the snake, I have a routine called Move, which contains the above snippet. Move is called repeatedly from a loop. The snake is just a series of 2x3 pixel blobs which are plotted using above code.
The snake moves in increments of 2 pixels. The easiest way to animate the snake movement is to append a "new" head (pixel blob) to the existing one, and erase the tail pixel blob. I've reserved 256 bytes in memory (I may yet need more, but for now 256 bytes is easy to work with) to store X and Y ords for each pixel blob that forms part of the snake. This means I can store up to 128 X,Y coord pairs, each representing the top left pixel of a 2x3 blob.
The 256 bytes are used as a circular list. I have a snake tail pointer and the snake head pointer, which point to the addresses in the list that record the X and Y coords of the tail pixel blob and head pixel blob. This way, it easy for the Move routine to look up and update tail and head locations.
Once I had the snake moving around the screen, I also created (and cribbed) some more routines. I nabbed some of the keyboard routine from HyperViper and plotted some food to the screen. The food is currently a yellow square :)
Next, I need collision detection to check for:
-
collision with self
-
collision with food
-
collision with screen boundaries
The routine probably needs updating. I've checked collision in two ways -
-
I check the resultant colour of the head pixels following the EORing of head pixels to screen. If green, no collision (the snake is green), if red then collision with food, otherwise collision with self. If none of the above, then on to next collision check
-
Check to see if snake head touches screen boundaries by comparing x,y ord of head with ords of screen edges
Routine is shown below. Would welcome critique from the experts amongst us (be gentle though :) )
ldx #3
.CollisionLoop
dex
lda snakehead_scrn_addr, X
cmp #&0C ; head is green - no collision
beq decCLcounter
cmp #&03 ; check if head has hit yellow - collision with food
bne CheckSelf
lda #HitFood
sta CollisionState
rts
.CheckSelf
lda #HitSelf
sta CollisionState
rts
.decCLcounter
txa
bne CollisionLoop
jmp therest ; no collisions to food or self so jmp to more checks
.therest ; check for collision with scrn boundary
dec snakehead_ptr
lda (snakehead_ptr,X)
cmp #LBound
beq CDEnd
cmp #RBound
beq CDEnd
inc snakehead_ptr
lda (snakehead_ptr,X)
cmp #UBound ; problem here - each snake seg is 3 pixels deep.. so when snake moves up, y Ord is decreased by 3.. which means when we
beq CDEnd
cmp #BBound-3
beq CDEnd
lda #HitNothing
sta CollisionState
rts
.CDEnd
lda #HitScrnBoundary
sta CollisionState
rts
At this point, I thought I'd better plan the rest of the routines I'd be needing. Here it is for better or for worse.
First draft pseudocode with approximate structure and routines. Routines with * are those that I haven't started on yet
.GameState
{
jsr.StartScreen*
jsr .InGame
jsr .HiScore*
.Ingame
{
jsr Init
jsr SnakeLive
.Init
{
jsr SetupGameScreen
jsr Draw Boundary *
jsr Draw Banner *
jsr Init Snake
jsr Init Score *
jsr Plot Food
}
.SnakeLive
{
.SnakeMoving
{
jsr.Keypress
jsr .Move
jsr .Collision Detect
If Food,
jsr .EatFood.
jsr .GrowSnake
jsr .UpdateScore*
jsr .UpdateBanner*
(loop if no collision
}
jsr .Crash
dec Lives
jsr Update banner*
}
(loop around Ingame until Lives = 0)
}
}
jmp Gamestate (ie after Hiscore table, go back to opening "welcome" screen of game)
.Draw Boundary*
.Draw Banner*
.EatFood*
.UpdateScore*
.UpdateBanner*
.RandomNumber
.PlotFood
.Crash *
<do a check to make sure food not colliding with anything before it is plotted>
.HiScore *