Snowman, programmo un gioco per il Commodore 64
Un gioco scritto totalmente in assembler per il C64
Provo a realizzare un gioco per il Commodore 64 programmando direttamente tutto in linguaggio Assembler.
Se vuoi provarlo sul tuo emulatore C64 i programmi sono disponibili nei formati .d64 e per CBM .prg studio
Scarica qui il software in formato .d64.
Se ti interessa vedere i codici sorgenti in assembly, il progetto è liberamente scaricabile dal link in questa pagina nel formato per “CBM .prg Studio“
Scarica qui i sorgenti per CBM .prg studio.
Per farlo funzionare servirà installare che un emulatore C64 per PC come ad esempio il VICE-the Versatile Commodore Emulator.
Questo progetto contiene numerosi spunti utili per imparare le tecniche di programmazione dell’epoca per realizzare videogiochi sul C64, tecniche che andrebbero spiegate singolarmente dedicando un articolo specifico per ogni singolo argomento.
Per questioni di tempo per ora posto i codici. Appena avrò la possibilità dedicherò la spiegazione ai singoli concetti.
Segue il codice in assembler.
; Programming by Alessandro Scola www.alessandroscola.com info@alessandroscola.com
; Music by Wham
; SID Programming by Magnus Djurberg
; 10 SYS (34577)
*=$0801
BYTE $0E, $08, $0A, $00, $9E, $20, $28, $33, $34, $35, $37, $37, $29, $00, $00, $00
*=$8711
LDA #$FF
LDY #$15
STA $D019 ; azzera memoria interrup
JSR $E544 ;CANCELLA SCHERMO
LDA #1
STA $D020 ; colore parte esterna schermo bianca
LDA #6
STA $D021 ; COLORE sfondo
; stampa le scritte di testata
LDY #1 ; con colore bianco
LDX #0
LOOP_TESTATA_1
LDA TESTATA_1,X
STA $0400,X
TYA
STA $D800,X
INX
CPX #40
BNE LOOP_TESTATA_1
LDX #0
LOOP_TESTATA_2
LDA TESTATA_2,X
STA $0450,X
TYA
STA $D850,X
INX
CPX #40
BNE LOOP_TESTATA_2
LDX #0
LOOP_TESTATA_3
LDA TESTATA_3,X
STA $04A0,X
TYA
STA $D8A0,X
INX
CPX #40
BNE LOOP_TESTATA_3
; init music
lda #0
tax
tay
jsr music_player
;IMPOSTA VELOCITA DIFFERENTI PER I FIOCCHI
;IN PAGINA 0 DA $03 a step di 2 butes MEMORIZZO per ogni fiocco OGNI QUANTI FRAMES FAR SCENDERE IL FIOCCO DI 1 PX
LDA #$03 ; 3 = ogni 3 frame
STA $4001 ; REGISTRO SET VELOCITA FIOCCO 1 (SPRITE 2)
STA $4002 ; CONTATORE DECREMENTATO AD OGNI FRAME
LDA #$02
STA $4003 ; REGISTRO SET VELOCITA FIOCCO 2 (SPRITE 3)
STA $4004 ; CONTATORE DECREMENTATO AD OGNI FRAME
LDA #$03 ; 3 = ogni 3 frames
STA $4005 ; REGISTRO SET VELOCITA FIOCCO 3 (SPRITE 4)
STA $4006 ; CONTATORE DECREMENTATO AD OGNI FRAME
LDA #$02 ; 2 = ogni 2 frames
STA $4007 ; REGISTRO SET VELOCITA FIOCCO 4 (SPRITE 5)
STA $4008 ; CONTATORE DECREMENTATO AD OGNI FRAME
LDA #$03 ; 3 = ogni 3 frames
STA $4009 ; REGISTRO SET VELOCITA FIOCCO 5 (SPRITE 6)
STA $400A ; CONTATORE DECREMENTATO AD OGNI FRAME
LDA #$02 ; 2 = ogni 2 frames
STA $400B ; REGISTRO SET VELOCITA FIOCCO 6 (SPRITE 7)
STA $400C ; CONTATORE DECREMENTATO AD OGNI FRAME
; CONTATORE FRAME PER MOVIMENTO GAMBE
LDA #5 ; SPRITE GAMBE CHE CAMMINANO CAMBIANO OGNI 9+1=10 FRAMES
STA $02 ; OGNI QUANTI FRAMES AGGIORNO GAMBE CHE CORORNO
LDA #0
STA $03 ; QUALE DEI 2 SPRITE GAMBE DX STO VISUALIZZANDO ? 0 OPPURE 1
LDA #1 ;FLAG=1 POSIZIONE FIOCCO IN PRIMA META SCHERMO; FLAG=0 POSIZIONO IN SECONDA META
STA $04
LDA #0
STA PUNTEGGIO ; PUNTEGGIO
JSR VISUALIZZA_PUNTEGGIO
Init
lda #$03
sta $D01C ; sprite multicolor
lda #$00
sta $D01D ; Sprite 0 x-expand OFF
sta $D017 ; Sprite 0 y-expand OFF
lda #$FF
sta $D015 ; abilita tutti e 8 gli sprites
lda #09 ; marron
sta $D025 ; ALL SPRITES MULTICOLOR 1
lda #$00 ;nero
sta $D026 ; ALL SPRITES MULTICOLOR 2
; SPRITE 0 corpo snowman
lda #$D3 ; 211
sta $07F8 ; pointer sprite 0
lda #$01 ; bianco
sta $D027 ; sprite 0 color
lda #$A2 ; X
sta $D000 ; sprite 0 X position
lda #$DC
sta $D001 ; sprite 0 Y position
; SPRITE 1 gambe snowman
lda #$D5 ; D5 = 213 e D6 = 214
sta $07F9 ; pointer sprite 1
lda #$01
sta $D028 ; sprite 1 color
lda #$A2
sta $D002 ; sprite 1 X position
lda #$F1
sta $D003 ; sprite 1 Y position
; SPRITE 2-3-4-5-6-7 fiocco di neve 1-2-3-4-5-6
lda #$D9 ; 217
sta $07FA ; pointer sprite 2
STA $07FB ; pointer sprite 3
STA $07FC ; pointer sprite 4
STA $07FD ; pointer sprite 5
STA $07FE ; pointer sprite 6
STA $07FF ; pointer sprite 7
; imposta colore bianco per tutti i fiocchi
lda #$01 ; colore bianco
STA $D029 ; sprite 2 color
STA $D02A ; sprite 3 color
STA $D02B ; sprite 4 color
STA $D02C ; sprite 5 color
STA $D02D ; sprite 6 color
STA $D02E ; sprite 7 color
; imposta posizione X per tutti i fiocchi (SOLO PER DEBUG)
LDA #$20
STA $D004 ; sprite 2 X position
LDA #$30
STA $D006 ; sprite 3 X position
LDA #$40
STA $D008 ; sprite 4 X position
LDA #$50
STA $D00A ; sprite 5 X position
LDA #$60
STA $D00C ; sprite 6 X position
LDA #$70
STA $D00E ; sprite 7 X position
; imposta posizione Y=0 per tutti i fiocchi
LDA #244
STA $D005 ; sprite 2 Y position
STA $D007 ; sprite 3 Y position
STA $D009 ; sprite 4 Y position
STA $D00B ; sprite 5 Y position
STA $D00D ; sprite 6 Y position
STA $D00F ; sprite 7 Y position
lda #0
STA $D01B ; priorità SPRITES rispetto allo sfondo
;-------------------------------------------------
LDX #12
RIPOSIZ_FIOCCHI
JSR RIPOSIZ_FIOCCO_X
DEX
DEX
BNE RIPOSIZ_FIOCCHI
sei ; turn off interrupts
lda #$7f
ldx #$01
sta $dc0d ; Turn off CIA 1 interrupts
sta $dd0d ; Turn off CIA 2 interrupts
stx $d01a ; Turn on raster interrupts
lda #$1b
ldx #$08
ldy #$14
sta $d011 ; Clear high bit of $d012, set text mode
stx $d016 ; single-colour
sty $d018 ; screen at $0400, charset at $2000
lda #<int ; low part of address of interrupt handler code
ldx #>int ; high part of address of interrupt handler code
ldy #$80 ; line to trigger interrupt
sta $0314 ; store in interrupt vector
stx $0315
sty $d012
lda $dc0d ; ACK CIA 1 interrupts
lda $dd0d ; ACK CIA 2 interrupts
asl $d019 ; ACK VIC interrupts
cli
;---------- INIZIO MAIN LOOP ---------------------------------------------------
loop
jmp loop ; LOOP INFINITO
;---------- FINE MAIN LOOP -----------------------------------------------------
;---------- INIZIO ROUTINE RASTER, ESEGUITA AD OGNI REFRESH SCHERMO-------------
int
;LDA #$7
;STA $D020 ; change border colour to yellow
JSR AGGIORNA_FIOCCHI
JSR CHECK_JOYSTICK
JSR CHECK_SPRITES_COLLISIONS
; FACCIO AVANZARE LA MUSICA
JSR music_player + 3
asl $d019 ; ACK interrupt (to re-enable it)
pla
tay
pla
tax
pla
RTI ; return from interrupt
;---------- FINE ROUTINE RASTER, ESEGUITA AD OGNI REFRESH SCHERMO---------------
VISUALIZZA_PUNTEGGIO
; VISUALIZZA PUNTEGGIO SU SCHERMO:
DISPLAY
LDY #5 ; SCREEN OFFSET
LDX #0 ; SCORE BYTE INDEX
SLOOP
LDA PUNTEGGIO,X
PHA
AND #$0F
JSR PLOTDIGIT
PLA
LSR A
LSR A
LSR A
LSR A
JSR PLOTDIGIT
INX
CPX #3
BNE SLOOP
RTS
PLOTDIGIT
CLC
ADC #48 ; 0 = CODICE ASCII 48
STA $04AC,Y
DEY
RTS
; --------------------------------------------------------------------------
AGGIORNA_FIOCCHI
; AGGIORNA LA POSIZIONE Y solo dei fiocchi che devono essere aggiornati in base a velocità
LDX #12 ; OFFSET DA SOMMARE A LOC 4000 PER OTTENERE CONTATORE VELOCITA SPRITE
; DA QUI DECREMENTO FINO A OFFSET=0 CHE CORRISPONDE A LOC 4001 = VELOCITA SPRITE
@LOOP_FIOCCHI
LDA $4000,X ; QUANTI FRAMES SONO PASSATI? SE LOC=0 ALLORA AGGIORNO POSIZIONE Y FIOCCO
SEC
SBC #$01
STA $4000,X ; RIMEMORIZZO CONTATORE DECREMENTATO PER LO SPRITE IN ESAME SBC #$01
BNE @NON_AGGIORNO_FIOCCO_SINGOLO
; SE VELOCITA = 0 ALLORA AGGIORNO POSIZXIONE FIOCCO SINGOLO
LDA $4000-1,X ; riprIStino settaggio velocità
STA $4000,X
LDA $D003,X ; LEGGO COORDINATA Y
CLC
ADC #$01 ; INCREMENTO POSIZIONE Y DEL FIOCCO
STA $D003,X ; SETTO COORDINATA Y
LDA $D003,X
CMP #245 ; SPRITE APPENA FUORI DAL BORDO INFERIORE
BNE @NON_AGGIORNO_FIOCCO_SINGOLO
JSR RIPOSIZ_FIOCCO_X ; SE POSIZIONE Y=0 (FIOCCO USCITO DA SCHERMO IN BASSO) ALLORA RIPOSIZIONO IN X LO SPRITE CON UN NUMERO RANDOM
@NON_AGGIORNO_FIOCCO_SINGOLO
DEX
DEX
BNE @LOOP_FIOCCHI
RTS
; --------------------------------------------------------------------------
RIPOSIZ_FIOCCO_X
LDA #35 ; POSIZIONEO SPRITE CON Y=35 APPENA FUORI DAL BORDO IN ALTO
STA $D003,X ; AGGIORNO POSIZ Y
LDA $04
BNE PRIMAMETASCHERMO
; RESET $04
LDA #1
STA $04
SECONDAMETASCHERMO
; CASO MSB=1
; SETTO MSB=1 PER LO SPRITE GIUSTO
CPX #12 ; SPRITE E' IL NUMERO 7 ?
BNE 2_SP_6
LDA $D010
ORA #%10000000 ; SETTO A 1 MSB SPRITE 7
STA $D010
JMP 2_FINITO
2_SP_6
CPX #10 ; SPRITE E' IL NUMERO 6 ?
BNE 2_SP_5
LDA $D010
ORA #%01000000 ; SETTO A 1 M MSB SPRITE 6
STA $D010
JMP 2_FINITO
2_SP_5
CPX #8 ; SPRITE E' IL NUMERO 5 ?
BNE 2_SP_4
LDA $D010
ORA #%00100000 ; SETTO A 1 M MSB SPRITE 5
STA $D010
JMP 2_FINITO
2_SP_4
CPX #6 ; SPRITE E' IL NUMERO 4 ?
BNE 2_SP_3
LDA $D010
ORA #%00010000 ; SETTO A 1 M MSB SPRITE 4
STA $D010
JMP 2_FINITO
2_SP_3
CPX #4 ; SPRITE E' IL NUMERO 3 ?
BNE 2_SP_2
LDA $D010
ORA #%00001000 ; SETTO A 1 M MSB SPRITE 3
STA $D010
JMP 2_FINITO
2_SP_2
CPX #2 ; SPRITE E' IL NUMERO 2 ?
BNE 2_FINITO
LDA $D010
ORA #%00000100 ; SETTO A 1 M MSB SPRITE 2
STA $D010
2_FINITO
JMP LSB
PRIMAMETASCHERMO
; CASO MSB=0
; SETTO MSB=0 PER LO SPRITE GIUSTO
CPX #12 ; SPRITE E' IL NUMERO 7 ?
BNE 1_SP_6
LDA $D010
AND #%01111111 ; AZZERO MSB SPRITE 7
STA $D010
JMP 1_FINITO
1_SP_6
CPX #10 ; SPRITE E' IL NUMERO 6 ?
BNE 1_SP_5
LDA $D010
AND #%10111111 ; AZZERO MSB SPRITE 6
STA $D010
JMP 1_FINITO
1_SP_5
CPX #8 ; SPRITE E' IL NUMERO 5 ?
BNE 1_SP_4
LDA $D010
AND #%11011111 ; AZZERO MSB SPRITE 5
STA $D010
JMP 1_FINITO
1_SP_4
CPX #6 ; SPRITE E' IL NUMERO 4 ?
BNE 1_SP_3
LDA $D010
AND #%11101111 ; AZZERO MSB SPRITE 4
STA $D010
JMP 1_FINITO
1_SP_3
CPX #4 ; SPRITE E' IL NUMERO 3 ?
BNE 1_SP_2
LDA $D010
AND #%11110111 ; AZZERO MSB SPRITE 3
STA $D010
JMP 1_FINITO
1_SP_2
CPX #2 ; SPRITE E' IL NUMERO 2 ?
BNE 1_FINITO
LDA $D010
AND #%11111011 ; AZZERO MSB SPRITE 2
STA $D010
1_FINITO
; DECREMENTO FLAG X ALTERNARE PRIMA E SECONDA META SCHERMO
LDA $04
SEI
SBC #1
STA $04
LSB
; GENERAZIONE RANDOM COODINATA X
txa
pha
LDA #0
JSR $E09A
LDA $64 ; valore restituito qui
STA $D002,X ; AGGIORNO POSIZ X
pla
tax
RTS
; --------------------------------------------------------------------------
CHECK_JOYSTICK
LDX $DC00 ; legge stato JOYSTICK
CPX #$77 ; JOYSTICK a destra ?
BNE NODESTRA
; ----------------------------------
; JOYSTICK A DESTRA
; ----------------------------------
LDA #$D3 ; 211
STA $07F8 ; cambia immagine sprite (snowman a destra)
; ANIMAZIONE GAMBE:
LDA $02
BNE NON_AGGIORNA_GAMBE_DX ; SE $02<>0 E' NON ORA DI AGGIORNARE LO SPRITE GAMBE DX
; RISPRISTINA 02
LDA #05
STA $02
; CHE PRITE GAMBE DEVO VISUALIZZARE ?
LDA $03
BNE CAMBIO_GAMBE_DX_1 ; SE = 1 ANIMAZIONE GAMBE 1
LDA #1 ; CAMBIO $03
STA $03
CAMBIO_GAMBE_DX_0
LDA #$D5
JMP AGGIORNA_PUNTAT_GAMBE_DX
CAMBIO_GAMBE_DX_1
LDA #0 ; CAMBIO $03
STA $03
; ANIMAZIONE GAMBE 1:
LDA #$D6
AGGIORNA_PUNTAT_GAMBE_DX
STA $07F9 ; cambia imamgine sprite (gambe snowman a destra)
JMP AGGIORNA_POSIZ_PUPAZZO_DX
NON_AGGIORNA_GAMBE_DX
SEI
SBC #01
STA $02 ; DECREMENTO E AGGIORNO $02
AGGIORNA_POSIZ_PUPAZZO_DX
LDA $D010
AND #%00000001
CMP #%00000001 ; flag X > 255 ?
BEQ DESTRA_SOPRA255
INC $D000 ; COORDINATA X PUPAZZO
INC $D002 ; COORDINATA X GAMBE PUPAZZO
BEQ OLTRE255PRIMAVOLTA
JMP FINEJOYSTICK
DESTRA_SOPRA255
; verifica se arrivato a bordo destro
LDX $D000
CPX #$41 ; massimo posizione destra
beq FINEJOYSTICK_DX
inc $D000 ; incrementa posizione X sprite 0
inc $D002 ; incrementa posizione X sprite 1
FINEJOYSTICK_DX
RTS
OLTRE255PRIMAVOLTA
inc $D000
inc $D002
LDA $D010
ORA #%00000011
STA $D010 ; ACCENDO I 2 BIT PIU A DESTRA DI MSB POSIZIONE X
jmp FINEJOYSTICK
INCREMENTAX
inc $D000
inc $D002
NODESTRA
ldx $DC00
cpx #$7B ; JOYSTICK a sinistra ?
bne FINEJOYSTICK
; ----------------------------------
; JOYSTICK A SINISTRA
; ----------------------------------
lda #$D4 ; 212
sta $07F8 ; cambia imamgine sprite (snowman a sinistra)
; ANIMAZIONE GAMBE:
LDA $02
BNE NON_AGGIORNA_GAMBE_SX ; SE $02<>0 E' NON ORA DI AGGIORNARE LO SPRITE GAMBE SX
; RISPRISTINA 02
LDA #05
STA $02
; CHE PRITE GAMBE DEVO VISUALIZZARE ?
LDA $03
BNE CAMBIO_GAMBE_SX_1 ; SE = 1 ANIMAZIONE GAMBE 1
LDA #1 ; CAMBIO $03
STA $03
CAMBIO_GAMBE_SX_0
LDA #$D7
JMP AGGIORNA_PUNTAT_GAMBE_SX
CAMBIO_GAMBE_SX_1
LDA #0 ; CAMBIO $03
STA $03
; ANIMAZIONE GAMBE 1:
LDA #$D8
AGGIORNA_PUNTAT_GAMBE_SX
STA $07F9 ; cambia imamgine sprite (gambe snowman a destra)
JMP AGGIORNA_POSIZ_PUPAZZO_SX
NON_AGGIORNA_GAMBE_SX
SEI
SBC #01
STA $02 ; DECREMENTO E AGGIORNO $02
AGGIORNA_POSIZ_PUPAZZO_SX
LDA $D010
AND #%00000011
CMP #%00000011 ; X > 255 ?
beq SINISTRA_SOPRA255
; verifica se arrivato a bordo sinistro
ldx $D000
cpx #$18 ; minima posizione a sinistra
beq FINEJOYSTICK ; non vado più a sinistra di così
jmp DECREMENTAX
SINISTRA_SOPRA255
; verifica se ero già arrivato a X = 255
ldx $D000 ; POSIZIONE X PUPAZZO
cpx #$00
bne DECREMENTAX
LDA $D010
AND #%11111100 ; AZZERO ULTIMI 2 BIT DI MSB POSIZ X
STA $D010
lda #$FF
sta $D000
sta $D002
jmp FINEJOYSTICK
DECREMENTAX
dec $D000 ; decrementa posizione X sprite 0
dec $D002 ; decrementa posizione X sprite 1
FINEJOYSTICK
RTS
; --------------------------------------------------------------------------
CHECK_SPRITES_COLLISIONS
LDA $D01E
TAY ; LO MEMORIZZO PER DOPO PERCHE DOPO AVER LETTO $D01E ESSO SI RESETTA!
BNE YES_COLLISIONS
RTS
YES_COLLISIONS
; collisione con IL CORPO DEL PUPAZZO ?
AND #%00000001
CLC
LSR A
BCC NO_COLLISIONE_PUPAZZO
INC $D020 ; cambia colore sfondoschermo (solo c debug)
; CON QUALE FIOCCO ?
TYA
AND #%11111100
LSR A ; Shift Right One Bit
LSR A ; Shift Right One Bit
; FIOCCO SPRITE 2 ?
CLC
LSR A
BCC FIOCCO_SP2_NO
JSR INCREMENTA_PUNTEGGIO
; RIPOSIZIONO APPENA FUORI DAL BORDO INFERIORE
LDX #244
STX $D005
FIOCCO_SP2_NO
; FIOCCO SPRITE 3 ?
CLC
LSR A
BCC FIOCCO_SP3_NO
JSR INCREMENTA_PUNTEGGIO
; RIPOSIZIONO APPENA FUORI DAL BORDO INFERIORE
LDX #244
STX $D007
FIOCCO_SP3_NO
; FIOCCO SPRITE 4 ?
CLC
LSR A
BCC FIOCCO_SP4_NO
JSR INCREMENTA_PUNTEGGIO
; RIPOSIZIONO APPENA FUORI DAL BORDO INFERIORE
LDX #244
STX $D009
FIOCCO_SP4_NO
; FIOCCO SPRITE 5 ?
CLC
LSR A
BCC FIOCCO_SP5_NO
JSR INCREMENTA_PUNTEGGIO
; RIPOSIZIONO APPENA FUORI DAL BORDO INFERIORE
LDX #244
STX $D00B
FIOCCO_SP5_NO
; FIOCCO SPRITE 6 ?
CLC
LSR A
BCC FIOCCO_SP6_NO
JSR INCREMENTA_PUNTEGGIO
; RIPOSIZIONO APPENA FUORI DAL BORDO INFERIORE
LDX #244
STX $D00D
FIOCCO_SP6_NO
; FIOCCO SPRITE 7 ?
CLC
LSR A
BCC FIOCCO_SP7_NO
JSR INCREMENTA_PUNTEGGIO
; RIPOSIZIONO APPENA FUORI DAL BORDO INFERIORE
LDX #244
STX $D00F
FIOCCO_SP7_NO
JSR VISUALIZZA_PUNTEGGIO
NO_COLLISIONE_PUPAZZO
RTS
INCREMENTA_PUNTEGGIO
; INCREMENTO PUNTEGGIO E LO STAMPO
; incrementa punteggio
SED ;DECIMAL MODE ON
CLC
LDA PUNTEGGIO
ADC #1
STA PUNTEGGIO
LDA PUNTEGGIO+1
ADC #0
STA PUNTEGGIO+2
CLD ; DECIMAL MODE OFF
RTS
*=$1000
music_player
incbin "Last_Christmas.sid", $7e ; music code and data. Load range: $1000-$34AD
*=$34C0 ; 64 x 211 = $34C0
incbin "snowman.spt",1,2,true
*=$3540 ; 64 x 213 = $3504
incbin "snowman-legs.spt",1,4,true
*=$3640 ; 64 x 217 = $3640
incbin "snowflake.spt",1,1,true
TESTATA_1 text ' snowman by alessandro scola 2022 '
TESTATA_2 text ' music: last christmas by wham '
TESTATA_3 text 'punteggio: '
PUNTEGGIO
BYTE 0,0,0