Big memory and repository reorganization

This commit is contained in:
giomba 2018-09-22 10:58:10 +02:00
parent 868d1e6253
commit 5d9248e580
14 changed files with 946 additions and 920 deletions

View File

@ -1,8 +1,8 @@
64:
dasm snake.asm -DSYSTEM=64 -DDEBUG=0 -osnake.prg
dasm main.asm -DSYSTEM=64 -DDEBUG=0 -osnake.prg
debug:
dasm snake.asm -DSYSTEM=64 -DDEBUG=1 -ssymbols.txt -osnake.prg
dasm main.asm -DSYSTEM=64 -DDEBUG=1 -ssymbols.txt -osnake.prg
16:
dasm snake.asm -DSYSTEM=16 -osnake.prg
dasm main.asm -DSYSTEM=16 -osnake.prg

2
basic.asm Normal file
View File

@ -0,0 +1,2 @@
; 10 SYS10240 ($2800) BASIC autostart
BYTE #$0b,#$08,#$0a,#$00,#$9e,#$31,#$30,#$32,#$34,#$30,#$00,#$00,#$00

View File

53
data.asm Normal file
View File

@ -0,0 +1,53 @@
; Data section
; ----------------------------------------------------------------------
; Number of interrupt
; Used as counter to be decremented to do some things less frequently
irqn:
BYTE
; Direction of the snake (2,4,6,8 as down,left,right,up)
; 5 means `pause`
direction:
BYTE
; Snake head coordinates in video memory
snakeX:
BYTE
snakeY:
BYTE
; Parameters for calcTileMem
calcTileX:
BYTE
calcTileY:
BYTE
; List start and length
listStart:
BYTE
length:
BYTE
; Random value
random:
BYTE
; Status
status:
BYTE
; Intro counter
introCounter:
BYTE #$4
; Intro string x position, and next increment
introX:
BYTE #$0
introXinc:
BYTE #$1
; Outro delay
outroDelay
BYTE #$ff

314
game.asm Normal file
View File

@ -0,0 +1,314 @@
statusPlay: ; do Game
; Check counter
ldx irqn
dex
stx irqn
beq irqsometime ; if counter is 0, then do these "rare" things
rts ; else do nothing and simply return
; as you can see, game actually runs at 12 Hz
irqsometime:
; Things that must be done only one in four interrupts (12 Hz)
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; If I am here, counter reached 0, so first reset it
ldx #4
stx irqn
; Get pressed key and decide snake direction
jsr $ffe4 ; Kernal routine GETIN
cmp #0
beq keybEndCheck ; if no key pressed, just skip this switch-case
ldx #$7f ; else, if key pressed, reset random variable
stx random
keybCheckA: ; check for press of key `A`
cmp #$41
bne keybCheckS ; if not pressed `A`, just skip to next key to check
lda direction ; else check if current direction is right
cmp #6
beq keybEndCheck ; if yes, you can't turn over yourself, so just skip to next key check
lda #4
sta direction ; else set direction to left and store new value
jmp keybEndCheck ; skip all other key tests
keybCheckS: ; simply re-do for S, D, W and other keys...
cmp #$53
bne keybCheckD
lda direction
cmp #8
beq keybEndCheck
lda #2
sta direction
jmp keybEndCheck
keybCheckD:
cmp #$44
bne keybCheckW
lda direction
cmp #4
beq keybEndCheck
lda #6
sta direction
jmp keybEndCheck
keybCheckW:
cmp #$57
bne keybCheckP
lda direction
cmp #2
beq keybEndCheck
lda #8
sta direction
jmp keybEndCheck
keybCheckP:
cmp #$50
bne keybEndCheck
lda #5
sta direction
jmp keybEndCheck
keybEndCheck:
; Get joystick status and decide snake direction
; Joystick register bits 4:0 => Fire,Right,Left,Down,Up
; 0 = Pressed; 1 = Idle
lda $dc00 ; CIA joystick port 2 register
ror ; rotate bit and put bit#0 in CF
tax ; store byte value for next key check
bcs joyCheckDown ; if CF = 1, then key was not depressed, so skip and check next...
; ... else key was depressed!
lda direction ; check for not overlapping direction (turn over yourself)
cmp #2
beq joyEndCheck
lda #8
sta direction
jmp joyEndCheck
joyCheckDown:
txa
ror
tax
bcs joyCheckLeft
lda direction
cmp #8
beq joyEndCheck
lda #2
sta direction
jmp joyEndCheck
joyCheckLeft:
txa
ror
tax
bcs joyCheckRight
lda direction
cmp #6
beq joyEndCheck
lda #4
sta direction
jmp joyEndCheck
joyCheckRight:
txa
ror
tax
bcs joyCheckFire
lda direction
cmp #4
beq joyEndCheck
lda #6
sta direction
jmp joyEndCheck
joyCheckFire: ; `Fire` joystick key used to pause game
txa
ror
tax
bcs joyEndCheck
lda #5
sta direction
joyEndCheck:
; Get direction and move head accordingly
lda direction
dirCheck2: ; check if direction is down...
cmp #2
bne dirCheck4 ; if not down, then skip and check next direction,
ldy snakeY ; else, direction is down, so get snakeY
iny ; increment snakeY (keep in mind that screen up/down coordinates are reversed)
sty snakeY ; update snakeY
dirCheck4: ; simply re-do for other directions...
cmp #4
bne dirCheck6
ldx snakeX
dex
stx snakeX
dirCheck6:
cmp #6
bne dirCheck8
ldx snakeX
inx
stx snakeX
dirCheck8:
cmp #8
bne dirCheck5
ldy snakeY
dey
sty snakeY
dirCheck5:
cmp #5
bne dirEndCheck
jmp skipPauseTests
dirEndCheck:
; Check screen boundaries overflow
lda snakeX
cmp #40
bne overCheckX0 ; if snakeX is not 40, then all ok, skip to next test
lda #0 ; else, there is an overflow, so trespass screen border
sta snakeX
overCheckX0: ; simply re-do for every side of the screen
lda snakeX
cmp #$ff
bne overCheckY1
lda #39
sta snakeX
overCheckY1:
lda snakeY
cmp #25
bne overCheckY0
lda #1
sta snakeY
overCheckY0
lda snakeY
cmp #0
bne overEndCheck
lda #24
sta snakeY
overEndCheck:
; Put new head coordinates in list
ldy listStart
lda snakeX
sta listX,y
lda snakeY
sta listY,y
iny
sty listStart
ldy #0
; Check for food eat / wall hit / self-eat
; - - - - - - - - - - - - - - - - - - - - - -
; --- Food eat ---
lda snakeX ; calc head location in memory
sta calcTileX
lda snakeY
sta calcTileY
jsr calcTileMem
lda (tileMem),y ; read content of head memory location
cmp #FOOD_TILE
beq foodEaten ; if memory does contain food, then perform foodEaten actions,
jmp checkSelfEat ; else just loooong jump to test if I ate myself
foodEaten:
ldx length ; else, increment snake length,
inx
stx length
genFood:
ldx random
inx
stx random
txa
genFoodX: ; calculate `random` modulo SCREEN_W
sec
sbc #SCREEN_W
cmp #SCREEN_W
bcs genFoodX
sta calcTileX
txa
genFoodY: ; calculate `random` modulo 22 (22 = SCREEN_H - 1)
sec
sbc #22
cmp #22
bcs genFoodY
clc ; add 1 because 1st line can not be used
adc #1
sta calcTileY
; Now I have X and Y coordinate for food stored in calcTileX, calcTileY
; and I must check it is not the location that I am going to overwrite
; with the head in draw snake head...
lda calcTileX
cmp snakeX
bne foodOK
lda calcTileY
cmp snakeY
beq genFood
foodOK:
; debug -- print choosen X,Y for food
; ldy #$18
; lda calcTileX
; jsr printByte
; ldy #$1b
; lda calcTileY
; jsr printByte
ldy #0
jsr calcTileMem ; calc food address in memory
lda (tileMem),y ; check if memory is empty
cmp #$20 ; is there a space?
bne genFood ; if not, must generate another number
lda #FOOD_TILE ; else, just put that fucking piece of food there
sta (tileMem),y
lda #$d4
clc
adc tileMem + 1
sta tileMem + 1
lda #FOOD_COLOR
sta (tileMem),y
; print score at $10th column
ldy #$10
lda length
jsr printByte
jmp checkEndSelfEat
checkEndFood:
; --- Self eat ---
checkSelfEat:
cmp #SNAKE_TILE
bne checkEndSelfEat
jmp gameover
checkEndSelfEat:
; Draw snake head
ldy #0
lda snakeX ; calc char address in video memory, and put SNAKE_TILE
sta calcTileX
lda snakeY
sta calcTileY
jsr calcTileMem
lda #SNAKE_TILE
sta (tileMem),y
lda #$d4 ; add #$d400 to previous address (obtain color memory
clc ; correspondent), and put SNAKE_COLOR
adc tileMem + 1
sta tileMem + 1
lda #SNAKE_COLOR
sta (tileMem),y
; Erase snake tail
lda listStart ; take start of list, and subtract snake length,
sec ; to obtain index of end of list
sbc length
tax ; use previous value as index in list, and calc video memory address
lda listX,x
sta calcTileX
lda listY,x
sta calcTileY
jsr calcTileMem
lda #$20 ; just put a space to erase snake tail tile
sta (tileMem),y
skipPauseTests:
rts

17
gameover.asm Normal file
View File

@ -0,0 +1,17 @@
; Game is over
; ----------------------------------------------------------------------
gameover:
lda #<gameoverString
sta printStatusString
lda #>gameoverString
sta printStatusString + 1
jsr printStatus
; Set gameover and outro status
lda #$ff
sta outroDelay
lda #ST_OUTRO
sta status
rts

80
intro1.asm Normal file
View File

@ -0,0 +1,80 @@
; Currently statusIntro0 is the same as statusIntro1
; statusIntro1 has just been reserved for future use
statusIntro0:
statusIntro1:
; Decrement interrupt divider for the intro
ldx introCounter
dex
stx introCounter
cpx #0
beq status1do ; if divider is 0, then do status1do ...
rts ; ... else just do nothing and return
status1do:
; Reset introCounter
ldx #5
stx introCounter
; I want to print strings at different columns to make them
; bounce across the screen, so take last introX and add introXinc,
; then print string at that point. If introX is too far right, then
; set introXinc as #$ff (equals -1) so next time introX will be
; decremented by 1. And then, if introX is too far left, then
; set introXinc as #$01 so next time will be moved to right again.
lda introX
clc
adc introXinc ; this is #$01 or #$0ff, so actually it is +1 or -1
sta introX
cmp #19 ; am I too far right?
beq status1setSX ; if yes, set SX (left)
cmp #0 ; am I too far left?
beq status1setDX ; if yes, set DX (right)
jmp status1okset ; else do nothing (aka, next time re-use current
; increment value)
status1setDX:
lda #$01 ; set introXinc as +1
sta introXinc
jmp status1okset
status1setSX:
lda #$ff ; set introXinc as -1
sta introXinc
jmp status1okset
status1okset:
; Print "SNAKE BY GIOMBA" (see above for pointer details)
lda #<intro0string
sta printIntroString
lda #>intro0string
sta printIntroString + 1
; $0428 is 2nd line (previously filled with color shades by reset routine)
lda #$28
clc
adc introX ; just add X, to make it look like it has moved
sta introScreenStart
lda #$04
sta introScreenStart + 1
jsr printIntro
; Print "PRESS SPACE TO PLAY"
lda #<intro1string
sta printIntroString
lda #>intro1string
sta printIntroString + 1
; $0478 is 4th line (previously filled with color shades by reset routine)
; add #19, then sub introX will make it move to other way of 2nd line
lda #$78
clc
adc #19 ; add #19
sec
sbc introX ; sub introX
sta introScreenStart
lda #$04
sta introScreenStart + 1
jsr printIntro
; Some considerations on speed:
; yes, maybe I should have put the string chars once in screen text memory
; and then move it left and right. Should re-think about this.
; For now, just return.
rts

57
introreset.asm Normal file
View File

@ -0,0 +1,57 @@
; Intro reset
; ----------------------------------------------------------------------
introreset:
jsr multicolorOff
; Clear screen
ldx #$ff
lda #$20
introresetCLS:
sta $400,x
sta $500,x
sta $600,x
sta $700,x
dex
cpx #$ff
bne introresetCLS
; Copy shade colors from costant table to color RAM for 2nd and 4th line of text
ldx #39
introresetColorShade
lda colorshade,x
sta $d828,x ; 2nd line
sta $d878,x ; 4th line
dex
cpx #$ff
bne introresetColorShade
; Set screen colors
lda #0
sta $d020 ; overscan
sta $d021 ; center
; Print website
lda #<intro2string ; lsb of string address
sta printIntroString ; put into lsb of source pointer
lda #>intro2string ; do the same for msb of string address
sta printIntroString + 1 ; put into msb of source pointer
lda #$26 ; this is lsb of address of 20th line
sta introScreenStart ; put into lsb of dest pointer
lda #$07 ; do the same for msb of adress of 20th line
sta introScreenStart + 1 ; put into msb of dest pointer
jsr printIntro ; print
; Print Copyright
lda #<intro3string ; the assembly is the same as above,
sta printIntroString ; just change string to be printed
lda #>intro3string ; and line (21th line)
sta printIntroString + 1
lda #$58
sta introScreenStart
lda #$07
sta introScreenStart + 1
jsr printIntro
rts

79
main.asm Normal file
View File

@ -0,0 +1,79 @@
processor 6502
; Platform specific code
; Code yet to be developed, example to use:
; ----------------------------------------------------------------------
#if SYSTEM = 64
; Commodore64 specific code
#else
; Commodore16 specific code
#endif
; Uninitialized zeropage segment
SEG.U zeropageSegment
org $02
INCLUDE "zeropage.asm"
; Initialized segments
SEG basicSegment
org $801
INCLUDE "basic.asm"
SEG dataSegment
org $900
INCLUDE "data.asm"
INCLUDE "const.asm"
; List
; ----------------------------------------------------------------------
. = $e00
listX:
. = $f00
listY:
; SID tune (previously properly cleaned, see HVSC)
; ----------------------------------------------------------------------
SEG sidSegment
org $1000
sidtune:
INCBIN "amour.sid"
#if DEBUG = 1
ECHO "End of SIDtune. Space left: ",($2000 - .)
#endif
; Font Data
; ----------------------------------------------------------------------
SEG tggsSegment
org $2000
; This binary data that defines the font is exactly 2kB long ($800)
tggsFont:
INCBIN "tggs.font"
; Include program
; ----------------------------------------------------------------------
SEG programSegment
org $2800
jmp start
INCLUDE "game.asm"
INCLUDE "gameover.asm"
INCLUDE "gamereset.asm"
INCLUDE "introreset.asm"
INCLUDE "intro1.asm"
INCLUDE "multicolor.asm"
INCLUDE "outro.asm"
INCLUDE "program.asm"
INCLUDE "subroutines.asm"
#if DEBUG = 1
ECHO "Program ends at: ",.
#endif
;
; coded during december 2017
; by giomba -- giomba at glgprograms.it
; this software is free software and is distributed
; under the terms of GNU GPL v3 license
;
; vim: set expandtab tabstop=4 shiftwidth=4:

16
outro.asm Normal file
View File

@ -0,0 +1,16 @@
; Decrement outroDelay, just to let player see her/his end screen
; with score
statusOutro:
ldy outroDelay ; load outroDelay and decrement
dey
sty outroDelay
cpy #0
beq statusOutroEnd
rts
statusOutroEnd:
; Set status as ST_END: this way, the loop out of this interrupt,
; will know that we finished, and will play the intro again
lda #ST_END
sta status
rts

170
program.asm Normal file
View File

@ -0,0 +1,170 @@
; ENTRY OF PROGRAM
; ----------------------------------------------------------------------
start:
; Clear screen, initialize keyboard, restore interrupts
jsr $ff81
; Disable all interrupts
sei
; Turn off CIA interrupts and (eventually) flush the pending queue
ldy #$7f
sty $dc0d
sty $dd0d
lda $dc0d
lda $dd0d
; Set Interrupt Request Mask as we want IRQ by raster beam
lda #$1
sta $d01a
; Store in $314 address of our custom interrupt handler
ldx #<irq ; least significant byte
ldy #>irq ; most significant byte
stx $314
sty $315
; Set raster beam to trigger interrupt at row zero
lda #$00
sta $d012
; Bit#0 of $d011 is used as bit#9 of $d012, and must be zero
lda $d011
and #$7f
sta $d011
; Initialize player for first song
lda #0
jsr sidtune
; Initialize MultiColor mode
jsr multicolorInit
; Zero-fill zeropage variables
lda #$0
ldx #$90
zeroFillZeroPage:
dex
sta $0,x
cpx #$2
bne zeroFillZeroPage
; Set status as first-time intro playing
lda #ST_INTRO0
sta status
; Enable interrupts
cli
; Reset screen (and other parameters) to play intro
jsr introreset
intro0running: ; Cycle here until SPACE or `Q` is pressed
jsr $ffe4 ; GETIN
cmp #$20 ; Is it SPACE?
beq intro0end ; if yes, go to intro0end and start game (see)
cmp #$51 ; Is it Q?
bne intro0running ; If not, keep looping here,
jmp $fce2 ; else, reset the computer
; Intro is finished, now it's time to start the proper game
intro0end:
; Pause everything in interrupt
lda #ST_PAUSE
sta status
; Set init variables of the game
jsr gamereset
; Set status as game playing
lda #ST_PLAY
sta status
endless:
; Loop waiting for gameover
lda status
cmp #ST_END ; is status equal to end ?
bne endless ; if not, just wait looping here, else...
jsr introreset ; reset variables for intro
lda #ST_INTRO0
sta status ; put machine into play intro status
jmp intro0running ; and go there waiting for keypress
; Interrupt Handler
; ----------------------------------------------------------------------
irq:
; Things that must be done every interrupt (50Hz)
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Acknoweledge IRQ
dec $d019
; Save registers in stack
pha
txa
pha
tya
pha
#if DEBUG = 1
; Change background to visually see the ISR timing
lda #2
sta $d020
#endif
; Check status and call appropriate sub-routine
; Sort of switch-case
lda status
cmp #ST_INTRO0
bne checkStatus1
jsr statusIntro0
jmp checkEndStatus
checkStatus1:
cmp #ST_INTRO1
bne checkStatus2
jsr statusIntro1
jmp checkEndStatus
checkStatus2
cmp #ST_PLAY
bne checkStatus3
jsr statusPlay
jmp checkEndStatus
checkStatus3
cmp #ST_OUTRO
bne checkEndStatus
jsr statusOutro
jmp checkEndStatus
checkEndStatus:
#if DEBUG = 1
; Change background to show how much time does music take for each interrupt
lda #1
sta $d020
#endif
; Play music
jsr sidtune + 3
jsr sidtune + 3
jsr sidtune + 3
jsr sidtune + 3
jsr sidtune + 3
; Increase random value
inc random
#if DEBUG = 1
; Change background back again to visally see ISR timing
lda #11
sta $d020
#endif
; Restore registers from stack
pla
tay
pla
tax
pla
; Go to original system routine
jmp $ea31

917
snake.asm
View File

@ -1,917 +0,0 @@
processor 6502
; Platform specific code
; Code yet to be developed, example to use:
; ----------------------------------------------------------------------
#if SYSTEM = 64
; Commodore64 specific code
#else
; Commodore16 specific code
#endif
; Zero page utilities
; ----------------------------------------------------------------------
SEG.U zeropage
org $02
; Where is the snake head in video memory? Do math to calculate address
; using pointer at tileMem,tileMem+1
tileMem DS 2
; Pointer to status string
printStatusString DS 2
; Pointer to intro string
printIntroString DS 2
; Pointer to screen position where to print intro string ($fb-$fc)
introScreenStart DS 2
#if DEBUG = 1
; Locations $90-$FF in zeropage are used by kernal
ECHO "End of zeropage variables. Space left: ",($90 - .)
#endif
SEG program
org $801
. = $801 ; 10 SYS10240 ($2800) BASIC autostart
BYTE #$0b,#$08,#$0a,#$00,#$9e,#$31,#$30,#$32,#$34,#$30,#$00,#$00,#$00
; Data section
; ----------------------------------------------------------------------
; Number of interrupt
; Used as counter to be decremented to do some things less frequently
irqn:
BYTE
; Direction of the snake (2,4,6,8 as down,left,right,up)
; 5 means `pause`
direction:
BYTE
; Snake head coordinates in video memory
snakeX:
BYTE
snakeY:
BYTE
; Parameters for calcTileMem
calcTileX:
BYTE
calcTileY:
BYTE
; List start and length
listStart:
BYTE
length:
BYTE
; Random value
random:
BYTE
; Status
status:
BYTE
; Intro counter
introCounter:
BYTE #$4
; Intro string x position, and next increment
introX:
BYTE #$0
introXinc:
BYTE #$1
; Outro delay
outroDelay
BYTE #$ff
INCLUDE "cost.asm"
#if DEBUG = 1
ECHO "End of Data. Space left: ",($e00 - .)
#endif
; List
; ----------------------------------------------------------------------
. = $e00
listX:
. = $f00
listY:
; SID tune (previously properly cleaned, see HVSC)
; ----------------------------------------------------------------------
. = $1000
sidtune:
INCBIN "amour.sid"
#if DEBUG = 1
ECHO "End of SIDtune. Space left: ",($2000 - .)
#endif
. = $2000
; This binary data that defines the font is exactly 2kB long ($800)
tggsFont:
INCBIN "tggs.font"
; ENTRY OF PROGRAM
; ----------------------------------------------------------------------
. = $2800
start:
; Clear screen, initialize keyboard, restore interrupts
jsr $ff81
; Disable all interrupts
sei
; Turn off CIA interrupts and (eventually) flush the pending queue
ldy #$7f
sty $dc0d
sty $dd0d
lda $dc0d
lda $dd0d
; Set Interrupt Request Mask as we want IRQ by raster beam
lda #$1
sta $d01a
; Store in $314 address of our custom interrupt handler
ldx #<irq ; least significant byte
ldy #>irq ; most significant byte
stx $314
sty $315
; Set raster beam to trigger interrupt at row zero
lda #$00
sta $d012
; Bit#0 of $d011 is used as bit#9 of $d012, and must be zero
lda $d011
and #$7f
sta $d011
; Initialize player for first song
lda #0
jsr sidtune
; Initialize MultiColor mode
jsr multicolorInit
; Zero-fill zeropage variables
lda #$0
ldx #$90
zeroFillZeroPage:
dex
sta $0,x
cpx #$2
bne zeroFillZeroPage
; Set status as first-time intro playing
lda #ST_INTRO0
sta status
; Enable interrupts
cli
; Reset screen (and other parameters) to play intro
jsr introreset
intro0running: ; Cycle here until SPACE or `Q` is pressed
jsr $ffe4 ; GETIN
cmp #$20 ; Is it SPACE?
beq intro0end ; if yes, go to intro0end and start game (see)
cmp #$51 ; Is it Q?
bne intro0running ; If not, keep looping here,
jmp $fce2 ; else, reset the computer
; Intro is finished, now it's time to start the proper game
intro0end:
; Pause everything in interrupt
lda #ST_PAUSE
sta status
; Set init variables of the game
jsr gamereset
; Set status as game playing
lda #ST_PLAY
sta status
endless:
; Loop waiting for gameover
lda status
cmp #ST_END ; is status equal to end ?
bne endless ; if not, just wait looping here, else...
jsr introreset ; reset variables for intro
lda #ST_INTRO0
sta status ; put machine into play intro status
jmp intro0running ; and go there waiting for keypress
INCLUDE "gamereset.asm"
; Intro reset
; ----------------------------------------------------------------------
introreset:
jsr multicolorOff
; Clear screen
ldx #$ff
lda #$20
introresetCLS:
sta $400,x
sta $500,x
sta $600,x
sta $700,x
dex
cpx #$ff
bne introresetCLS
; Copy shade colors from costant table to color RAM for 2nd and 4th line of text
ldx #39
introresetColorShade
lda colorshade,x
sta $d828,x ; 2nd line
sta $d878,x ; 4th line
dex
cpx #$ff
bne introresetColorShade
; Set screen colors
lda #0
sta $d020 ; overscan
sta $d021 ; center
; Print website
lda #<intro2string ; lsb of string address
sta printIntroString ; put into lsb of source pointer
lda #>intro2string ; do the same for msb of string address
sta printIntroString + 1 ; put into msb of source pointer
lda #$26 ; this is lsb of address of 20th line
sta introScreenStart ; put into lsb of dest pointer
lda #$07 ; do the same for msb of adress of 20th line
sta introScreenStart + 1 ; put into msb of dest pointer
jsr printIntro ; print
; Print Copyright
lda #<intro3string ; the assembly is the same as above,
sta printIntroString ; just change string to be printed
lda #>intro3string ; and line (21th line)
sta printIntroString + 1
lda #$58
sta introScreenStart
lda #$07
sta introScreenStart + 1
jsr printIntro
rts
; Interrupt Handler
; ----------------------------------------------------------------------
irq:
; Things that must be done every interrupt (50Hz)
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; Acknoweledge IRQ
dec $d019
; Save registers in stack
pha
txa
pha
tya
pha
#if DEBUG = 1
; Change background to visually see the ISR timing
lda #2
sta $d020
#endif
; Check status and call appropriate sub-routine
; Sort of switch-case
lda status
cmp #ST_INTRO0
bne checkStatus1
jsr statusIntro0
jmp checkEndStatus
checkStatus1:
cmp #ST_INTRO1
bne checkStatus2
jsr statusIntro1
jmp checkEndStatus
checkStatus2
cmp #ST_PLAY
bne checkStatus3
jsr statusPlay
jmp checkEndStatus
checkStatus3
cmp #ST_OUTRO
bne checkEndStatus
jsr statusOutro
jmp checkEndStatus
checkEndStatus:
#if DEBUG = 1
; Change background to show how much time does music take for each interrupt
lda #1
sta $d020
#endif
; Play music
jsr sidtune + 3
jsr sidtune + 3
jsr sidtune + 3
jsr sidtune + 3
jsr sidtune + 3
; Increase random value
inc random
#if DEBUG = 1
; Change background back again to visally see ISR timing
lda #11
sta $d020
#endif
; Restore registers from stack
pla
tay
pla
tax
pla
; Go to original system routine
jmp $ea31
; Currently statusIntro0 is the same as statusIntro1
; statusIntro1 has just been reserved for future use
statusIntro0:
statusIntro1:
; Decrement interrupt divider for the intro
ldx introCounter
dex
stx introCounter
cpx #0
beq status1do ; if divider is 0, then do status1do ...
rts ; ... else just do nothing and return
status1do:
; Reset introCounter
ldx #5
stx introCounter
; I want to print strings at different columns to make them
; bounce across the screen, so take last introX and add introXinc,
; then print string at that point. If introX is too far right, then
; set introXinc as #$ff (equals -1) so next time introX will be
; decremented by 1. And then, if introX is too far left, then
; set introXinc as #$01 so next time will be moved to right again.
lda introX
clc
adc introXinc ; this is #$01 or #$0ff, so actually it is +1 or -1
sta introX
cmp #19 ; am I too far right?
beq status1setSX ; if yes, set SX (left)
cmp #0 ; am I too far left?
beq status1setDX ; if yes, set DX (right)
jmp status1okset ; else do nothing (aka, next time re-use current
; increment value)
status1setDX:
lda #$01 ; set introXinc as +1
sta introXinc
jmp status1okset
status1setSX:
lda #$ff ; set introXinc as -1
sta introXinc
jmp status1okset
status1okset:
; Print "SNAKE BY GIOMBA" (see above for pointer details)
lda #<intro0string
sta printIntroString
lda #>intro0string
sta printIntroString + 1
; $0428 is 2nd line (previously filled with color shades by reset routine)
lda #$28
clc
adc introX ; just add X, to make it look like it has moved
sta introScreenStart
lda #$04
sta introScreenStart + 1
jsr printIntro
; Print "PRESS SPACE TO PLAY"
lda #<intro1string
sta printIntroString
lda #>intro1string
sta printIntroString + 1
; $0478 is 4th line (previously filled with color shades by reset routine)
; add #19, then sub introX will make it move to other way of 2nd line
lda #$78
clc
adc #19 ; add #19
sec
sbc introX ; sub introX
sta introScreenStart
lda #$04
sta introScreenStart + 1
jsr printIntro
; Some considerations on speed:
; yes, maybe I should have put the string chars once in screen text memory
; and then move it left and right. Should re-think about this.
; For now, just return.
rts
statusPlay: ; do Game
; Check counter
ldx irqn
dex
stx irqn
beq irqsometime ; if counter is 0, then do these "rare" things
rts ; else do nothing and simply return
; as you can see, game actually runs at 12 Hz
irqsometime:
; Things that must be done only one in four interrupts (12 Hz)
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
; If I am here, counter reached 0, so first reset it
ldx #4
stx irqn
; Get pressed key and decide snake direction
jsr $ffe4 ; Kernal routine GETIN
cmp #0
beq keybEndCheck ; if no key pressed, just skip this switch-case
ldx #$7f ; else, if key pressed, reset random variable
stx random
keybCheckA: ; check for press of key `A`
cmp #$41
bne keybCheckS ; if not pressed `A`, just skip to next key to check
lda direction ; else check if current direction is right
cmp #6
beq keybEndCheck ; if yes, you can't turn over yourself, so just skip to next key check
lda #4
sta direction ; else set direction to left and store new value
jmp keybEndCheck ; skip all other key tests
keybCheckS: ; simply re-do for S, D, W and other keys...
cmp #$53
bne keybCheckD
lda direction
cmp #8
beq keybEndCheck
lda #2
sta direction
jmp keybEndCheck
keybCheckD:
cmp #$44
bne keybCheckW
lda direction
cmp #4
beq keybEndCheck
lda #6
sta direction
jmp keybEndCheck
keybCheckW:
cmp #$57
bne keybCheckP
lda direction
cmp #2
beq keybEndCheck
lda #8
sta direction
jmp keybEndCheck
keybCheckP:
cmp #$50
bne keybEndCheck
lda #5
sta direction
jmp keybEndCheck
keybEndCheck:
; Get joystick status and decide snake direction
; Joystick register bits 4:0 => Fire,Right,Left,Down,Up
; 0 = Pressed; 1 = Idle
lda $dc00 ; CIA joystick port 2 register
ror ; rotate bit and put bit#0 in CF
tax ; store byte value for next key check
bcs joyCheckDown ; if CF = 1, then key was not depressed, so skip and check next...
; ... else key was depressed!
lda direction ; check for not overlapping direction (turn over yourself)
cmp #2
beq joyEndCheck
lda #8
sta direction
jmp joyEndCheck
joyCheckDown:
txa
ror
tax
bcs joyCheckLeft
lda direction
cmp #8
beq joyEndCheck
lda #2
sta direction
jmp joyEndCheck
joyCheckLeft:
txa
ror
tax
bcs joyCheckRight
lda direction
cmp #6
beq joyEndCheck
lda #4
sta direction
jmp joyEndCheck
joyCheckRight:
txa
ror
tax
bcs joyCheckFire
lda direction
cmp #4
beq joyEndCheck
lda #6
sta direction
jmp joyEndCheck
joyCheckFire: ; `Fire` joystick key used to pause game
txa
ror
tax
bcs joyEndCheck
lda #5
sta direction
joyEndCheck:
; Get direction and move head accordingly
lda direction
dirCheck2: ; check if direction is down...
cmp #2
bne dirCheck4 ; if not down, then skip and check next direction,
ldy snakeY ; else, direction is down, so get snakeY
iny ; increment snakeY (keep in mind that screen up/down coordinates are reversed)
sty snakeY ; update snakeY
dirCheck4: ; simply re-do for other directions...
cmp #4
bne dirCheck6
ldx snakeX
dex
stx snakeX
dirCheck6:
cmp #6
bne dirCheck8
ldx snakeX
inx
stx snakeX
dirCheck8:
cmp #8
bne dirCheck5
ldy snakeY
dey
sty snakeY
dirCheck5:
cmp #5
bne dirEndCheck
jmp skipPauseTests
dirEndCheck:
; Check screen boundaries overflow
lda snakeX
cmp #40
bne overCheckX0 ; if snakeX is not 40, then all ok, skip to next test
lda #0 ; else, there is an overflow, so trespass screen border
sta snakeX
overCheckX0: ; simply re-do for every side of the screen
lda snakeX
cmp #$ff
bne overCheckY1
lda #39
sta snakeX
overCheckY1:
lda snakeY
cmp #25
bne overCheckY0
lda #1
sta snakeY
overCheckY0
lda snakeY
cmp #0
bne overEndCheck
lda #24
sta snakeY
overEndCheck:
; Put new head coordinates in list
ldy listStart
lda snakeX
sta listX,y
lda snakeY
sta listY,y
iny
sty listStart
ldy #0
; Check for food eat / wall hit / self-eat
; - - - - - - - - - - - - - - - - - - - - - -
; --- Food eat ---
lda snakeX ; calc head location in memory
sta calcTileX
lda snakeY
sta calcTileY
jsr calcTileMem
lda (tileMem),y ; read content of head memory location
cmp #FOOD_TILE
beq foodEaten ; if memory does contain food, then perform foodEaten actions,
jmp checkSelfEat ; else just loooong jump to test if I ate myself
foodEaten:
ldx length ; else, increment snake length,
inx
stx length
genFood:
ldx random
inx
stx random
txa
genFoodX: ; calculate `random` modulo SCREEN_W
sec
sbc #SCREEN_W
cmp #SCREEN_W
bcs genFoodX
sta calcTileX
txa
genFoodY: ; calculate `random` modulo 22 (22 = SCREEN_H - 1)
sec
sbc #22
cmp #22
bcs genFoodY
clc ; add 1 because 1st line can not be used
adc #1
sta calcTileY
; Now I have X and Y coordinate for food stored in calcTileX, calcTileY
; and I must check it is not the location that I am going to overwrite
; with the head in draw snake head...
lda calcTileX
cmp snakeX
bne foodOK
lda calcTileY
cmp snakeY
beq genFood
foodOK:
; debug -- print choosen X,Y for food
; ldy #$18
; lda calcTileX
; jsr printByte
; ldy #$1b
; lda calcTileY
; jsr printByte
ldy #0
jsr calcTileMem ; calc food address in memory
lda (tileMem),y ; check if memory is empty
cmp #$20 ; is there a space?
bne genFood ; if not, must generate another number
lda #FOOD_TILE ; else, just put that fucking piece of food there
sta (tileMem),y
lda #$d4
clc
adc tileMem + 1
sta tileMem + 1
lda #FOOD_COLOR
sta (tileMem),y
; print score at $10th column
ldy #$10
lda length
jsr printByte
jmp checkEndSelfEat
checkEndFood:
; --- Self eat ---
checkSelfEat:
cmp #SNAKE_TILE
bne checkEndSelfEat
jmp gameover
checkEndSelfEat:
; Draw snake head
ldy #0
lda snakeX ; calc char address in video memory, and put SNAKE_TILE
sta calcTileX
lda snakeY
sta calcTileY
jsr calcTileMem
lda #SNAKE_TILE
sta (tileMem),y
lda #$d4 ; add #$d400 to previous address (obtain color memory
clc ; correspondent), and put SNAKE_COLOR
adc tileMem + 1
sta tileMem + 1
lda #SNAKE_COLOR
sta (tileMem),y
; Erase snake tail
lda listStart ; take start of list, and subtract snake length,
sec ; to obtain index of end of list
sbc length
tax ; use previous value as index in list, and calc video memory address
lda listX,x
sta calcTileX
lda listY,x
sta calcTileY
jsr calcTileMem
lda #$20 ; just put a space to erase snake tail tile
sta (tileMem),y
skipPauseTests:
rts
; Game is over
; ----------------------------------------------------------------------
gameover:
lda #<gameoverString
sta printStatusString
lda #>gameoverString
sta printStatusString + 1
jsr printStatus
; Set gameover and outro status
lda #$ff
sta outroDelay
lda #ST_OUTRO
sta status
rts
; Decrement outroDelay, just to let player see her/his end screen
; with score
statusOutro:
ldy outroDelay ; load outroDelay and decrement
dey
sty outroDelay
cpy #0
beq statusOutroEnd
rts
statusOutroEnd:
; Set status as ST_END: this way, the loop out of this interrupt,
; will know that we finished, and will play the intro again
lda #ST_END
sta status
rts
; Subroutines
; ----------------------------------------------------------------------
; Do some math to calculate tile address in video memory
; using x,y coordinates
; Formula: addr = $400 + y * SCREEN_W + x
calcTileMem:
; Save registers
pha
txa
pha
tya
pha
; Set tileMem to $400
lda #$00
sta tileMem
lda #$04
sta tileMem + 1
ldy calcTileY ; Get head Y coordinate
calcTileMult:
tya
beq calcTileEnd ; if Y is equal to zero, nothing to do, just skip moltiplication, else...
dey ; decrement Y
clc
lda #SCREEN_W ; A = screen width = 40
adc tileMem ; A = screen width + tileMem (low)
sta tileMem ; update tileMem (low)
lda #0 ; do the same with higher byte: A = 0
adc tileMem + 1 ; add (eventual) carry
sta tileMem + 1 ; update tileMem (high)
jmp calcTileMult ; do again until Y == 0
calcTileEnd: ; now multiplication is ended, so add X
lda calcTileX
adc tileMem
sta tileMem
lda #0
adc tileMem + 1
sta tileMem + 1
; Restore old registers
pla
tay
pla
tax
pla
rts
; Print a byte in hexadecimal
; A input register for byte to print
; Y input register for printing colum (on first line)
printByte:
; Copy parameter also in X
tax
lsr ; Take most significant nibble
lsr
lsr
lsr
jsr printDigit
sta $400,y ; print msb char
txa ; Take least significant nibble (use previous copy)
and #$0f
jsr printDigit
sta $401,y ; print lsb char
rts
; Transform a base-36 digit into a Commodore screen code
; Leave input digit in accumulator; returns output screen code in accumulator
printDigit:
cmp #10
bcs printDigitL ; if it is not a decimal digit, then go to printDigitL
clc ; it is a decimal digit! Just add `0` (48)
adc #48
ora #$80 ; reverse color
rts
printDigitL: ; it is not a decimal digit, then...
sec
sbc #10 ; take away 10
clc
adc #1 ; add 1, so you obtain something in [A-F]
ora #$80 ; reverse color
rts
; Print null-terminated string on status bar
; address of string is given in input using memory location printStatusString
printStatus:
ldy #0
printStatusLoop:
lda (printStatusString),y
beq printStatusEnd
cmp #$20
bne printStatusSkipSpace
lda #$60
printStatusSkipSpace:
sec
sbc #$40 ; convert from standard ASCII to Commodore screen code
ora #$80 ; reverse color
sta $413,y
iny
jmp printStatusLoop
printStatusEnd:
rts
; Print string for intro
; Input parameters:
; printIntroString pointer to string to be printed (source)
; introScreenStart pointer to text video memory on screen where to print (dest)
printIntro:
ldy #0
printIntroLoop: