diff --git a/Makefile b/Makefile index c396cfc..af9ff0c 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/basic.asm b/basic.asm new file mode 100644 index 0000000..9c80788 --- /dev/null +++ b/basic.asm @@ -0,0 +1,2 @@ + ; 10 SYS10240 ($2800) BASIC autostart + BYTE #$0b,#$08,#$0a,#$00,#$9e,#$31,#$30,#$32,#$34,#$30,#$00,#$00,#$00 diff --git a/cost.asm b/const.asm similarity index 100% rename from cost.asm rename to const.asm diff --git a/data.asm b/data.asm new file mode 100644 index 0000000..2a7b0e5 --- /dev/null +++ b/data.asm @@ -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 + + diff --git a/game.asm b/game.asm new file mode 100644 index 0000000..4356325 --- /dev/null +++ b/game.asm @@ -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 + + diff --git a/gameover.asm b/gameover.asm new file mode 100644 index 0000000..a68cf87 --- /dev/null +++ b/gameover.asm @@ -0,0 +1,17 @@ +; Game is over +; ---------------------------------------------------------------------- +gameover: + lda #gameoverString + sta printStatusString + 1 + jsr printStatus + + ; Set gameover and outro status + lda #$ff + sta outroDelay + lda #ST_OUTRO + sta status + rts + + diff --git a/intro1.asm b/intro1.asm new file mode 100644 index 0000000..9fc1c2e --- /dev/null +++ b/intro1.asm @@ -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 + 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 + 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 + + diff --git a/introreset.asm b/introreset.asm new file mode 100644 index 0000000..dfef527 --- /dev/null +++ b/introreset.asm @@ -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 ; 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 ; and line (21th line) + sta printIntroString + 1 + lda #$58 + sta introScreenStart + lda #$07 + sta introScreenStart + 1 + jsr printIntro + + rts + + diff --git a/main.asm b/main.asm new file mode 100644 index 0000000..8cc54e5 --- /dev/null +++ b/main.asm @@ -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: diff --git a/outro.asm b/outro.asm new file mode 100644 index 0000000..5c9df74 --- /dev/null +++ b/outro.asm @@ -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 + diff --git a/program.asm b/program.asm new file mode 100644 index 0000000..847b2a2 --- /dev/null +++ b/program.asm @@ -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 ; 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 + + diff --git a/snake.asm b/snake.asm deleted file mode 100644 index 76f52a9..0000000 --- a/snake.asm +++ /dev/null @@ -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 ; 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 ; 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 ; 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 + 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 + 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 + 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: - lda (printIntroString),y ; get char from string - beq printIntroEnd ; if zero, then end (string must be null-terminated) - cmp #$40 ; is char greater or equal to #$40 = #64 = `@' ? - bcc printIntroEndCheck ; if not, it is less, thus it must be - ; a full stop, comma, colon or something - ; that actually has the same value in both - ; true ASCII and in PET screen codes - ; otherwise, it is greater than `@`, so must - ; subtract 64 because CBM and its encodings - ; are simply a big shit - sec - sbc #$40 - -printIntroEndCheck: - sta (introScreenStart),y ; put screen code to screen - iny ; next char in string - jmp printIntroLoop -printIntroEnd: - rts - -; Include -; ______________________________________________________________________ - INCLUDE "multicolor.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: diff --git a/subroutines.asm b/subroutines.asm new file mode 100644 index 0000000..bcc2926 --- /dev/null +++ b/subroutines.asm @@ -0,0 +1,135 @@ +; 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: + lda (printIntroString),y ; get char from string + beq printIntroEnd ; if zero, then end (string must be null-terminated) + cmp #$40 ; is char greater or equal to #$40 = #64 = `@' ? + bcc printIntroEndCheck ; if not, it is less, thus it must be + ; a full stop, comma, colon or something + ; that actually has the same value in both + ; true ASCII and in PET screen codes + ; otherwise, it is greater than `@`, so must + ; subtract 64 because CBM and its encodings + ; are simply a big shit + sec + sbc #$40 + +printIntroEndCheck: + sta (introScreenStart),y ; put screen code to screen + iny ; next char in string + jmp printIntroLoop +printIntroEnd: + rts + + diff --git a/zeropage.asm b/zeropage.asm new file mode 100644 index 0000000..172d297 --- /dev/null +++ b/zeropage.asm @@ -0,0 +1,20 @@ +; Zero page utilities +; ---------------------------------------------------------------------- +; 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 + +