From 5d9248e5805222e46adf3064d123394a30d04d15 Mon Sep 17 00:00:00 2001 From: giomba Date: Sat, 22 Sep 2018 10:58:10 +0200 Subject: [PATCH] Big memory and repository reorganization --- Makefile | 6 +- basic.asm | 2 + cost.asm => const.asm | 0 data.asm | 53 +++ game.asm | 314 +++++++++++++++ gameover.asm | 17 + intro1.asm | 80 ++++ introreset.asm | 57 +++ main.asm | 79 ++++ outro.asm | 16 + program.asm | 170 ++++++++ snake.asm | 917 ------------------------------------------ subroutines.asm | 135 +++++++ zeropage.asm | 20 + 14 files changed, 946 insertions(+), 920 deletions(-) create mode 100644 basic.asm rename cost.asm => const.asm (100%) create mode 100644 data.asm create mode 100644 game.asm create mode 100644 gameover.asm create mode 100644 intro1.asm create mode 100644 introreset.asm create mode 100644 main.asm create mode 100644 outro.asm create mode 100644 program.asm delete mode 100644 snake.asm create mode 100644 subroutines.asm create mode 100644 zeropage.asm 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 + +