From 9206086b8a65e0d25da8509e84e23bb3efcb7911 Mon Sep 17 00:00:00 2001 From: John Lorentzson Date: Wed, 26 Mar 2025 10:28:23 +0100 Subject: [PATCH] Import from tarball created on 2024-03-14 --- .gitignore | 3 + build.sh | 3 + c64.inc | 84 +++++++++++++++++ enum.asm | 8 ++ gameloop.asm | 183 +++++++++++++++++++++++++++++++++++++ graphics.asm | 73 +++++++++++++++ init.asm | 215 +++++++++++++++++++++++++++++++++++++++++++ main.asm | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++ utilities.asm | 88 ++++++++++++++++++ variables.asm | 14 +++ zeropage.asm | 7 ++ 11 files changed, 924 insertions(+) create mode 100644 .gitignore create mode 100755 build.sh create mode 100644 c64.inc create mode 100644 enum.asm create mode 100644 gameloop.asm create mode 100644 graphics.asm create mode 100644 init.asm create mode 100644 main.asm create mode 100644 utilities.asm create mode 100644 variables.asm create mode 100644 zeropage.asm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..02a6980 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.prg +*.lst diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..a3b2050 --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cl65 -o pong.prg -l pong.lst -t c64 -C c64-asm.cfg -u__EXEHDR__ main.asm diff --git a/c64.inc b/c64.inc new file mode 100644 index 0000000..b0e6b0e --- /dev/null +++ b/c64.inc @@ -0,0 +1,84 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + + PROGSTART = $080D ; just after BASIC header + + ;; Misc + CINV = $0314 ; Hardware IRQ vector + + ;; CIA registers + PRA = $DC00 + PRB = $DC01 + DDRA = $DC02 + DDRB = $DC03 + CIA_ICR = $DC0D + + PRA2 = $DD00 + + ;; VIC-II registers + VICBASE = $D000 + SPRITE_0X = VICBASE+0 + SPRITE_0Y = VICBASE+1 + SPRITE_1X = VICBASE+2 + SPRITE_1Y = VICBASE+3 + SPRITE_2X = VICBASE+4 + SPRITE_2Y = VICBASE+5 + SPRITE_3X = VICBASE+6 + SPRITE_3Y = VICBASE+7 + SPRITE_4X = VICBASE+8 + SPRITE_4Y = VICBASE+9 + SPRITE_5X = VICBASE+10 + SPRITE_5Y = VICBASE+11 + SPRITE_6X = VICBASE+12 + SPRITE_6Y = VICBASE+13 + SPRITE_7X = VICBASE+14 + SPRITE_7Y = VICBASE+15 + SPRITE_X_MSB = VICBASE+16 + YSCROLL_MODE = VICBASE+17 + RASTER = VICBASE+18 + ;; ... + SPRITE_ENABLE = VICBASE+21 + XSCROLL_MODE = VICBASE+22 + SPRITE_XPAND_Y = VICBASE+23 + ;; ... + MEMPTR = VICBASE+24 + VICINT = VICBASE+25 + VICINTMASK = VICBASE+26 + SPRITE_BG_PRIO = VICBASE+27 + ;; ... + SPRITE_XPAND_X = VICBASE+29 + SSCOL = VICBASE+30 + ;; ... + BORDER = VICBASE+32 + BGCOL0 = VICBASE+33 + BGCOL1 = VICBASE+34 + BGCOL2 = VICBASE+35 + BGCOL3 = VICBASE+36 + ;; ... + SPRITE_0C = VICBASE+39 + SPRITE_1C = VICBASE+40 + SPRITE_2C = VICBASE+41 + SPRITE_3C = VICBASE+42 + SPRITE_4C = VICBASE+43 + SPRITE_5C = VICBASE+44 + SPRITE_6C = VICBASE+45 + SPRITE_7C = VICBASE+46 + + COLORMEM = $D800 + + ;; SID registers + SIDBASE = $D400 + SIDF1H = SIDBASE+1 + SIDF1L = SIDBASE+2 + SIDCR1 = SIDBASE+4 + SIDAD1 = SIDBASE+5 + SIDSR1 = SIDBASE+6 + + SIDFMV = SIDBASE+24 + + ;; Macros + + .macro padto addr + .repeat (addr - *) + .byte 0 + .endrep + .endmacro diff --git a/enum.asm b/enum.asm new file mode 100644 index 0000000..97c2a87 --- /dev/null +++ b/enum.asm @@ -0,0 +1,8 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + + .enum mode + StartScreen = 0 + MainGame + ScoreHighlight + Win + .endenum diff --git a/gameloop.asm b/gameloop.asm new file mode 100644 index 0000000..bc63503 --- /dev/null +++ b/gameloop.asm @@ -0,0 +1,183 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + +;;; Main gameloop routine. +game: + lda p1score + ldx #4 + jsr printbyte + + lda p2score + ldx #(40 - 6) + jsr printbyte + + lda col + beq @nocol + jsr handle_collision +@nocol: + ;; Player input: + ;; F1 (restart) + ldx #%11111110 + ldy #%00010000 + jsr keydown + bne @norestart + jmp PROGSTART +@norestart: + + ;; Q (player 1 up) + ldx #%01111111 + ldy #%01000000 + jsr keydown + bne @skip1 + dec p1y + dec p1y + + ;; A (player 1 down) +@skip1: + ldx #%11111101 + ldy #%00000100 + jsr keydown + bne @skip2 + inc p1y + inc p1y +@skip2: + + ;; O (player 2 up) + ldx #%11101111 + ldy #%01000000 + jsr keydown + bne @skip3 + dec p2y + dec p2y +@skip3: + + ;; L (player 2 down) + ldx #%11011111 + ldy #%00000100 + jsr keydown + bne @skip4 + inc p2y + inc p2y +@skip4: + + ;; Clamp paddles to stay on-screen. + lda #$32 ;player 1 top + cmp p1y + bcc @dontclamp1 + sta p1y +@dontclamp1: + lda #($fa - 42) ;player 1 bottom + cmp p1y + bcs @dontclamp2 + sta p1y +@dontclamp2: + lda #$32 ;player 2 top + cmp p2y + bcc @dontclamp3 + sta p2y +@dontclamp3: + lda #($fa - 42) ;player 2 bottom + cmp p2y + bcs @dontclamp4 + sta p2y +@dontclamp4: + +update_paddles: + lda p1y + sta SPRITE_0Y + lda p2y + sta SPRITE_1Y + +ball_physics: + lda #BALLVERT + and balldata + beq @down ; 0 = down, 1 = up + txa + clc + dec ball_y + lda #BALLVSPEED + bit balldata + beq @slowballup + dec ball_y ;else we have a fast down ball +@slowballup: + ;; Top bounce check + lda ball_y + cmp #$32 + beq @topbouncestill + bcs @horizontal +@topbouncestill: + ;; If it's <= 32 then we bounce off the top + jsr ball_bounce_vert + jmp @horizontal + +@down: + inc ball_y + lda #BALLVSPEED + bit balldata + beq @slowballdown + inc ball_y +@slowballdown: + ;; Bottom bounce check + lda ball_y + cmp #($fa - 8) ;$fa is bottom of the screen, - 8 for ball height + bcc @horizontal + jsr ball_bounce_vert + ;; fallthrough to horizontal + +@horizontal: + lda #BALLHORI + and balldata + beq @left ; 0 = left, 1 = right + ;; Move ball right + jsr ball_x_inc ;first increment + lda #BALLHSPEED + bit balldata + beq @done + ;; if bit BALLHSPEED is set, we have a horizontally fast ball + jsr ball_x_inc ;so we increment again + jmp @done +@left: + ;; Move ball left + jsr ball_x_dec ;first decrement + lda #BALLHSPEED + bit balldata + beq @done + ;; if bit BALLHSPEED is set, we have a horizontally fast ball + jsr ball_x_dec ;so we dec again +@done: + +check_ball_goal: + ;; Check to see if it's time to change score and reset the ball + lda #%00000100 + bit SPRITE_X_MSB ;check if it's on the left or right side + bne @right + + lda ball_x + cmp #$10 + bcs @nope + inc p2score + lda p2score + jsr clampscore + sta p2score + jsr reset_ball + jsr highlightscore + jmp @nope +@right: + lda ball_x + cmp #$50 + bcc @nope + inc p1score + lda p1score + jsr clampscore + sta p1score + jsr reset_ball + jsr highlightscore +@nope: + +ball_sprite: + lda ball_x + sta SPRITE_2X + lda ball_y + sta SPRITE_2Y + + ;; MAIN GAME ROUTINE ENDS HERE, PUT SUBROUTINES AFTER + rts diff --git a/graphics.asm b/graphics.asm new file mode 100644 index 0000000..236a260 --- /dev/null +++ b/graphics.asm @@ -0,0 +1,73 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + + SPRITEBASE = $3400 + +sprite_data: + .repeat 21 + .byte $ff, 0, 0 ;defines 8x21 paddle sprite + .endrep + .byte 0 + + .repeat 8 + .byte $ff, 0, 0 + .endrep + .repeat 13 + .byte 0, 0, 0 + .endrep + .byte 0 + +sprite_data_end: + +scrlinelo: + .byte $00 + .byte $28 + .byte $50 + .byte $78 + .byte $A0 + .byte $C8 + .byte $F0 + .byte $18 + .byte $40 + .byte $68 + .byte $90 + .byte $B8 + .byte $E0 + .byte $08 + .byte $30 + .byte $58 + .byte $80 + .byte $A8 + .byte $D0 + .byte $F8 + .byte $20 + .byte $48 + .byte $70 + .byte $98 + .byte $C0 + +scrlinehi: + .byte $04 + .byte $04 + .byte $04 + .byte $04 + .byte $04 + .byte $04 + .byte $04 + .byte $05 + .byte $05 + .byte $05 + .byte $05 + .byte $05 + .byte $05 + .byte $06 + .byte $06 + .byte $06 + .byte $06 + .byte $06 + .byte $06 + .byte $06 + .byte $07 + .byte $07 + .byte $07 + .byte $07 + .byte $07 diff --git a/init.asm b/init.asm new file mode 100644 index 0000000..0c2fcab --- /dev/null +++ b/init.asm @@ -0,0 +1,215 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + +init: + ;; Set our custom NMI vector so that pressing restore quits + lda #nmi + sta $0319 + + ;; Reset gamemode + lda #mode::StartScreen + sta gamemode + + ;; Reset variables + lda #0 + sta p1score + sta p2score + sta p1y + sta p2y + sta exit + lda #64 + sta ball_x + sta ball_y + + + sei ;turn off IRQ while setting up + + lda #$7f + sta CIA_ICR ;disable CIA IRQ + bit CIA_ICR ;acknowledge it + + ;; Set up our graphics + ldx #$00 + stx BGCOL0 ;black background, appropriate for pong + stx BORDER + + jsr loadtitlescreen + + lda #%00000111 + sta SPRITE_ENABLE ;enable sprite 0,1,2 + sta SSCOL + sta SPRITE_BG_PRIO + lda #%00000011 + sta SPRITE_XPAND_Y + lda #$01 + sta SPRITE_0C + sta SPRITE_1C + sta SPRITE_2C + + lda #%00000010 + sta SPRITE_X_MSB + lda #$18 + sta SPRITE_0X + lda #$50 + sta SPRITE_1X + + lda #$32 + sta SPRITE_0Y + sta SPRITE_1Y + + lda #$64 + sta SPRITE_2X + sta SPRITE_2Y + + ;; Load sprites + ;; Note: breaks if we have more than 255 bytes of sprite gfx + ldx #(sprite_data_end - sprite_data) ;DANGER + ldy #$00 +@loadloop: + lda sprite_data,y + sta SPRITEBASE,y + iny + dex + bne @loadloop + ;; done when X is zero + + ;; lda #scorestr + ;; sta z:zscratch1 + ;; ldx #4 + ;; jsr printstr + + ;; Set up our own IRQ routine + lda #irq + sta CINV+1 + + ldx #$0 + stx RASTER ;trigger IRQ on scanline 0 (vblank) + ldx #%00000101 ;raster & sprite-sprite col IRQs + stx VICINTMASK ;enable VIC IRQ + + cli ;turn IRQ back on, we're ready +idle: + jmp idle ;do nothing forever, game is driven by IRQ + +nmi: + brk + +clearscreen: + ;; Let's clear the background + lda #VMBASE + sta z:zscratch1 + lda #COLORMEM + sta z:zscratch3 + ldx #$04 +@loopouter: + ldy #$00 +@loopinner: + lda #$20 + sta (zscratch0),y + lda #$01 ;write the color white + sta (zscratch2),y ;to every part of color RAM + iny + bne @loopinner + inc z:zscratch1 ;increment page + inc z:zscratch3 + dex + bne @loopouter + + ;; Set up sprites + ;; sprite ptr $d0 = memory location $3400 + ldx #$d0 + stx VMBASE+($400 - 8) + stx VMBASE+($400 - 7) + inx + stx VMBASE+($400 - 6) + + rts + +loadtitlescreen: + jsr clearscreen + + lda #title + sta z:zscratch1 + + ldx #$0a + ldy #$0b + jsr printstr + + lda #<credits + sta z:zscratch0 + lda #>credits + sta z:zscratch1 + + ldx #$0c + ldy #$0e + jsr printstr + + lda #<tutorial2 + sta z:zscratch0 + lda #>tutorial2 + sta z:zscratch1 + + ldx #$09 + ldy #$15 + jsr printstr + + lda #<tutorial1 + sta z:zscratch0 + lda #>tutorial1 + sta z:zscratch1 + + ldx #$0a + ldy #$17 + jsr printstr + + rts + +title: + .byte $10, $0F, $0E, $07, $2C, $20, $0D, $0F, $12, $05, $20, $0F, $12, $20, $0C, $05, $13, $13, 0 + +credits: + .byte $0D, $01, $04, $05, $20, $02, $19, $20, $04, $15, $15, $11, $0E, $04, 0 + +tutorial1: + .byte $03, $0F, $0E, $14, $12, $0F, $0C, $20, $17, $09, $14, $08, $20, $11, $01, $20, $0F, $0C, 0 + +tutorial2: + .byte $10, $12, $05, $13, $13, $20, $13, $10, $01, $03, $05, $20, $14, $0F, $20, $13, $14, $01, $12, $14, 0 + +loadgamefield: + jsr clearscreen + + ;; now we'll draw the center column + ldx #$25 +@lineloop: + lda scrlinelo,x + sta z:zscratch0 + lda scrlinehi,x + sta z:zscratch1 + txa + and #%00000001 + bne @skipdot + lda #103 + ldy #20 + sta (zscratch0),y + jmp @next +@skipdot: + lda #116 + ldy #19 + sta (zscratch0),y +@next: + dex + bpl @lineloop + + rts diff --git a/main.asm b/main.asm new file mode 100644 index 0000000..80000ec --- /dev/null +++ b/main.asm @@ -0,0 +1,246 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + .include "c64.inc" + .include "enum.asm" + + VMBASE = $0400 + + .org PROGSTART + + .include "init.asm" ;must come first + .include "utilities.asm" + + ;; Start screen, waits for space key to be pressed. +titlescreen: + ;; More visually interesting sprite positioning for + ;; the title screen. Overrides default from init. + lda #$82 + sta SPRITE_0Y + + ldx #%01111111 + ldy #%00010000 + jsr keydown + bne @dontstart + ;; Start the game here. + jsr loadgamefield ;set the background + lda #mode::MainGame ;don't forget that # + sta gamemode ;set the game mode to MainGame +@dontstart: + rts + + + ;; Wait and highlight the score change for half a second. + ;; Happens every time a player scores or whatever it's called in tennis +scorewait: + ;; While waiting we play a buzz + lda #$0f + sta SIDFMV + lda #$08 + sta SIDAD1 + lda #$68 + sta SIDSR1 + + lda #$45 + sta SIDF1L + lda #$1d + sta SIDF1H + + lda #$11 + sta SIDCR1 + + dec timer + bne @dontresume + ;; Reset gamemode + lda #mode::MainGame + sta gamemode + ;; Reset background color + lda #0 + sta BGCOL0 + ;; Stop sound + lda #$10 + sta SIDCR1 +@dontresume: + rts + + + ;; Call after a player scores to highlight the win +highlightscore: + lda #mode::ScoreHighlight + sta gamemode + lda #25 + sta timer + lda #2 + sta BGCOL0 + rts + + .include "gameloop.asm" + + + ;; "Victory" mode when 100 points has been reached. +win: + dec timer + bne @skip + dec timer2 + bne @continue + ;; After five seconds (10 half-seconds) we reset the game + jmp PROGSTART +@continue: + lda #25 + sta timer + lda BGCOL0 + eor #8 + sta BGCOL0 +@skip: + rts + +winstop: + lda #25 + sta timer + lda #10 + sta timer2 + lda #$0d + sta BGCOL0 + lda #mode::Win ;DO NOT FORGET THE # + sta gamemode + rts + +ball_x_inc: + inc ball_x + bne @done + ;; If ball_x incremented to zero we have either looped around + ;; the screen (shouldn't happen) or crossed the bit 8 boundry. + ;; So we set the MSB to one, since we're going right. + lda #%00000100 + ora SPRITE_X_MSB + sta SPRITE_X_MSB +@done: + rts + +ball_x_dec: + dec ball_x + lda ball_x + cmp #$ff + bne @done + ;; If ball_x incremented to zero we have either looped around + ;; the screen (shouldn't happen) or crossed the bit 8 boundry. + ;; So we set the MSB to one, since we're going right. + lda #%11111011 + and SPRITE_X_MSB + sta SPRITE_X_MSB +@done: + rts + +ball_bounce_vert: + jsr color_change + lda #%10000000 + eor balldata + sta balldata + rts + +ball_bounce_hori: + jsr color_change + lda #%01000000 + eor balldata + sta balldata + rts + +reset_ball: + jsr ball_bounce_hori + lda #$90 + sta ball_y + lda #$c0 + sta ball_x + lda #%11111011 + and SPRITE_X_MSB + sta SPRITE_X_MSB + rts + +color_change: + inc bordercol + lda bordercol + and #$0f + cmp #2 + bcs @fine + lda #2 + sta bordercol +@fine: + lda bordercol + sta BORDER + rts + +handle_collision: + jsr ball_bounce_hori ;obvious, flip the ball's horizontal direction + lda #$00 + sta col ;clear the collision flag + rts + + ;; Cheap trick to make a hexprinted number look decimal. + ;; Score comes in the A register, clamped score returned in A +clampscore: + tax + and #%00001111 + cmp #$0a + bcc @noclamplower + ;; yes clamp, inc upper nybble, ex. $19 -> $20 + txa + and #%11110000 + clc + adc #$10 + tax +@noclamplower: + txa + and #%11110000 + cmp #$a0 + bcc @noclamp + ;; over 99? just stop at that point, we'll flash some colors then reset + jsr winstop + pla + pla +@noclamp: + txa + rts + +modedispatch: + jsr jumpwithtable + .addr titlescreen + .addr game ;normal gameplay + .addr scorewait + .addr win + + + ;; Main IRQ subroutine. CPU gets sent here by the interrupts from the + ;; VIC-II's raster and collision interrupts, and is responsible for + ;; calling everything else. Since the game is interrupt-driven, you + ;; could add a RTS to the init code and have the game run together + ;; with BASIC. Would be pretty stupid but you *can* do it. That does + ;; give me an idea though... +irq: + lda SSCOL ;need to do this for collision checking to work + + lda #%00000100 ;check if irq is for collision or not + and VICINT + beq @nocol + ;; Collision + sta col ;set the collision flag + lda #$7f + and VICINT + sta VICINT + jmp @skipgame +@nocol: + lda gamemode + jsr modedispatch +@skipgame: + lda #$7f + and VICINT + sta VICINT + + jmp $ea31 + + .include "variables.asm" + + BALLVERT = %10000000 + BALLHORI = %01000000 + BALLVSPEED = %00000010 + BALLHSPEED = %00000001 + + .include "graphics.asm" + .include "zeropage.asm" diff --git a/utilities.asm b/utilities.asm new file mode 100644 index 0000000..2057309 --- /dev/null +++ b/utilities.asm @@ -0,0 +1,88 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + + ;; Jumps to a routine via a table. Index goes in A register. +jumpwithtable: + tay + + ;; Set up pointer to jump table + pla + sta z:zscratch0 + pla + sta z:zscratch1 + + tya + asl + tay + iny + + lda (zscratch0),y + sta z:zscratch2 + iny + lda (zscratch0),y + sta z:zscratch3 + + jmp (zscratch2) + +printbyte: + inx + jsr printnyb + .repeat 4 + lsr + .endrep + dex + jsr printnyb + rts + +printnyb: + pha + and #$0f + cmp #$0a + bcc @number ;if nybble<$A then print a number, else letter + sec + sbc #$09 ;letters start at $01, so sub away 9 + jmp @done +@number: + clc + adc #$30 ;print a number, starting at $30 +@done: + sta VMBASE,x + pla + rts + + ;; Put string pointer into zscratch1+2 + ;; X position into X, y pos into Y +printstr: + lda scrlinelo,y + sta z:zscratch2 + lda scrlinehi,y + sta z:zscratch3 + + txa + clc + adc z:zscratch2 + sta z:zscratch2 + lda z:zscratch3 + adc #0 + sta z:zscratch3 + + ldy #$00 +@loop: + lda (zscratch0),y + beq @done + sta (zscratch2),y + iny + inx + jmp @loop +@done: + rts + +keydown: + lda #$ff + sta DDRA + lda #$00 + sta DDRB + stx PRA + tya + and PRB + rts + diff --git a/variables.asm b/variables.asm new file mode 100644 index 0000000..bca1b8c --- /dev/null +++ b/variables.asm @@ -0,0 +1,14 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- +gamemode: .res 1, mode::StartScreen +p1y: .res 1, 0 +p1score: .res 1, $90 +p2score: .res 1, 0 +p2y: .res 1, 0 +col: .res 1, 0 +balldata: .res 1, %01000011 ;ball starts going right, fast vert & hori +ball_x: .res 1, 64 +ball_y: .res 1, 64 +bordercol: .res 1, 0 +timer: .res 1, 0 +timer2: .res 1, 0 +exit: .res 1, 0 diff --git a/zeropage.asm b/zeropage.asm new file mode 100644 index 0000000..fc3a786 --- /dev/null +++ b/zeropage.asm @@ -0,0 +1,7 @@ +;;; -*- Mode: asm; indent-tabs-mode: t; tab-width: 8 -*- + .zeropage + .org $50 +zscratch0: .res 1 +zscratch1: .res 1 +zscratch2: .res 1 +zscratch3: .res 1