From c13a54c266d81241724c8cee53ccc53b2bd445d5 Mon Sep 17 00:00:00 2001 From: giomba Date: Thu, 21 Dec 2017 15:14:42 +0100 Subject: [PATCH] First commit --- .gitignore | 1 + BUGS | 2 + Makefile | 2 + snake.asm | 518 +++++++++++++++++++++++++++++++++++++++++++++++++++++ snake.sid | Bin 0 -> 4236 bytes 5 files changed, 523 insertions(+) create mode 100644 .gitignore create mode 100644 BUGS create mode 100644 Makefile create mode 100644 snake.asm create mode 100644 snake.sid diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ff3d80 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +snake.prg diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..448467b --- /dev/null +++ b/BUGS @@ -0,0 +1,2 @@ +- Fix random-eaten food that prevents from playng +- Search for proper SID song diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fa68991 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: + dasm snake.asm -osnake.prg diff --git a/snake.asm b/snake.asm new file mode 100644 index 0000000..7d5c5bc --- /dev/null +++ b/snake.asm @@ -0,0 +1,518 @@ + processor 6502 + +; Zero page utilities +; ---------------------------------------------------------------------- + +; Where is the snake head in video memory? Do math to calculate address +; using $a0-$a1 +tileMem = $a0 +printStatusString = $a3 + + org $1000 + +; SID tune (previously properly cleaned, see HVSC) +; ---------------------------------------------------------------------- +. = $1000 +sidtune: + INCBIN "snake.sid" + +; Data section +; ---------------------------------------------------------------------- +. = $2100 + +; Number of interrupt +; Used as counter to be decremented to do some things less frequently +irqn: + BYTE #4 + +; Direction of the snake (2,4,6,8 as down,left,right,up) +direction: + BYTE #6 + +; Snake head coordinates in video memory +snakeX: + BYTE #19 +snakeY: + BYTE #12 + +; Parameters for calcTileMem +calcTileX: + BYTE +calcTileY: + BYTE + +; List start and length +listStart: + BYTE #5 +length: + BYTE #5 + +; Random value +random: + BYTE + +; Costants +; ---------------------------------------------------------------------- + +; Screen width and height +screenW: + BYTE #40 +screenH: + BYTE #24 +snakeTile: + BYTE #81 +snakeColor: + BYTE #5 +foodTile: + BYTE #90 +foodColor: + BYTE #15 + +gameoverString: + BYTE "GAMEOVER" + BYTE #0 + +; List +; ---------------------------------------------------------------------- +. = $2200 +listX: + BYTE #19 + +. = $2300 +listY: + BYTE #12 + +; ENTRY OF PROGRAM +; ---------------------------------------------------------------------- +. = $2400 +start: + ; Clear screen, initialize keyboard, restore interrupts + jsr $ff81 + + ; Set screen colors + ; - - - - - - - - - + lda #12 + sta $d020 ; overscan + lda #9 + sta $d021 ; center + ; Upper bar + ldx #39 +upperbarLoop: + lda #6 + sta $d800,x + lda #160 + sta $400,x + dex + bne upperbarLoop + + ; Scoreboard + lda #7 + sta $d810 + sta $d811 + + ; 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 + + ; Put first piece of food + lda foodTile + sta $500 + + ; Enable interrupts + cli + + ; Endless loop, show that there is enogh time after the interrupt +endless: + ldx $400 + inx + stx $400 + jmp endless + +; Interrupt Handler +; ---------------------------------------------------------------------- +irq: + ; Acknoweledge IRQ + dec $d019 + + ; Check counter + ldx irqn + dex + stx irqn + beq irqsometime ; if counter is 0, then do these "rare" things + jmp irqalways ; else skip this section, and go to always-do interrupt routine + +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 keybEndCheck + lda direction + cmp #2 + beq keybEndCheck + lda #8 + sta direction + jmp keybEndCheck +keybEndCheck: + + ; 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 dirEndCheck + ldy snakeY + dey + sty snakeY +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 (actually doing food check only) + 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 foodTile + bne checkSelfEat ; if memory does not contain food, then skip to next test... + ldx length ; else, increment snake length, + inx + stx length +genFood: + ldx random ; increment random value + inx + stx random + txa + sta calcTileX ; use it as tile X-coordinate + lsr ; divide it by 8; maximum expected is 32 (256/8) + lsr + lsr + cmp #18 ; test if it is still > 18 + bcc foodNoMod ; value must be < 18, + ; because it is the highest value v + ; for which v * 40 + 256 < 1000 + ; this ensures that piece if food will be placed + ; within visible screen + sec ; if value is > 18, then subtract 18; now it + sbc #18 ; surely is less than 18 +foodNoMod: + bne foodNoLow ; if it is 0, then set 1 (avoid first line) + lda #1 +foodNoLow: + sta calcTileY ; use this new value as tile Y-coordinate + jsr calcTileMem ; calc its 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 foodTile ; else, just put that fucking piece of food there + sta (tileMem),y + lda #$d4 + clc + adc tileMem + 1 + sta tileMem + 1 + lda foodColor + sta (tileMem),y + + jsr printScore ; print score + jmp checkEndSelfEat +checkEndFood: + +checkSelfEat: + cmp snakeTile + bne checkEndSelfEat + jmp gameover + +checkEndSelfEat: + + ; Draw snake head + lda snakeX ; calc char address in video memory, and put snakeTile + sta calcTileX + lda snakeY + sta calcTileY + jsr calcTileMem + lda snakeTile + sta (tileMem),y + + lda #$d4 ; add #$d400 to previous address (obtain color memory + clc ; correspondent), and put snakeColor + adc tileMem + 1 + sta tileMem + 1 + lda snakeColor + 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 + +irqalways: + ; Things that must be done every interrupt (50Hz) + ; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + ; Play music + jsr sidtune + 3 + + ; Increase random value + ldx random + inx + stx random + + ; Go to original system routine + jmp $ea31 + + brk + +; Game is over +; ---------------------------------------------------------------------- +gameover: + lda #gameoverString + sta printStatusString + 1 + jsr printStatus + jsr sidtune +gameoverLoop: + sei + jmp gameoverLoop + +; Subroutines +; ---------------------------------------------------------------------- +; Do some math to calculate tile address in video memory +; using x,y coordinates +; Formula: addr = $400 + y * screenW + 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 screenW ; 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 score +printScore: + ; Push registers on stack (save) + pha + txa + pha + tya + pha + + ; Take length in A and copy also in X + lda length + tax + + lsr ; Take most significant nibble + lsr + lsr + lsr + jsr printDigit + sta $410 ; print msb char at $410 + + txa ; Take least significant nibble (use previous copy) + and #$0f + jsr printDigit + sta $411 ; print lsb char at $411 + + ; Pull registers from stack (restore) + pla + tay + pla + tax + pla + + 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 + 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] + 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 + sec + sbc #$40 ; convert from standard ASCII to Commodore screen code + sta $413,y + iny + jmp printStatusLoop +printStatusEnd: + rts diff --git a/snake.sid b/snake.sid new file mode 100644 index 0000000000000000000000000000000000000000..8df7eeb6f1bfb1077223c7662cfdd28c5be16d0b GIT binary patch literal 4236 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 P>%3kdXJU>|(F*?tsqCb+ literal 0 HcmV?d00001