Page 1 of 1

Tutorial: How to create Matrix Rain on Commodore PET Machine

Posted: Fri Dec 26, 2025 12:55 pm
by milasoft64
3.jpg
3.jpg (9.55 KiB) Viewed 49 times
2.jpg
2.jpg (7.48 KiB) Viewed 49 times
1.jpg
1.jpg (13.68 KiB) Viewed 49 times
; ============================================================
; Matrix Rain Effect with Digital Clock (Commodore PET)
; ============================================================
; Matrix-style falling characters + jiffy-clock HH:MM:SS
; Fully self-contained source
; From Petopia demo by Milasoft
; Can be modified for C64 or 128 as well
; ============================================================

* = $0401

; ------------------------------------------------------------
; BASIC stub: 10 SYS 1040
; ------------------------------------------------------------
!byte $19,$08,$0a,$00,$9e,$20,$28,$31,$30,$34,$30,$29
!byte 0,0,0


; ============================================================
; Initialization
; ============================================================

lda #147
jsr $ffd2 ; Clear screen

lda #0
sta TIMER


; ============================================================
; Seed random generator
; ============================================================

lda #21
sta .seedlo
lda #$1c
sta .seedhi ; seed random number generator


; ============================================================
; Initialize rain columns
; ============================================================

ldx #0
InitRain:

InitSpeed:
jsr RANDOM
cmp #9
bcs InitSpeed
sta SPEED,x

txa
sta RAINLO,x

inx
cpx #30 ; stop at 30 drips
bne InitRain


; ------------------------------------------------------------
; Initialize Y positions
; ------------------------------------------------------------

ldx #0
InitY:


lda RAINHIS
sta RAINHI,x

inx
cpx #30
bne InitY


; ============================================================
; Main Loop
; ============================================================
MainLoop:
VWait:
lda $e840
and #32
bne VWait ; wait for vsync to refresh

jsr $ffe4
cmp #32 ; space bar pressed?
bne NoToggle
lda #147
jsr $ffd2


inc .mode ; cycle mode
lda .mode
cmp #3
bne NoToggle
lda #0
sta .mode
jmp NoToggle

NoToggle:
cmp #32
bne +
jmp SkipDraw
+
lda .mode
cmp #1 ; clock only?
beq SkipRain

jsr DRAW ; make it rain

SkipRain:
lda .mode
cmp #2 ; rain only?
beq SkipDraw

jsr clockds ; show clock

SkipDraw:
jmp MainLoop
; ============================================================
; Draw Matrix Rain
; ============================================================

DRAW:
ldx #0

NextColumn:
ldy #0

lda RAINLO,x
sta $fd
lda RAINHI,x
sta $fe ; put pointer into ($fd)

lda $fe
cmp #$80
bcc SkipColumn ; less than $8000 (not ideal but it won't corrupt RAM)
cmp #$84
bcs SkipColumn ; greater than $8400 (off screen?)

jsr RANDOM ; get random character to put
sta ($fd),y

SkipColumn:
lda DEL,x
cmp SPEED,x ; load delay table to compare to speed
beq MoveDown ; do they match?
inc DEL,x ; no so increase delay wait
jmp Advance ; next please


MoveDown:
lda #0
sta DEL,x

lda RAINLO,x
sec
sbc #144
sta $fd
lda RAINHI,x
sbc #1 ; move up 10 lines. this is somewhat sloppy
sta $fe ; but it works. subtract 144 + 256 bytes = 400

lda $fe
cmp #$80
bcc SkipTail ; check boundaries $8000 and $8400
cmp #$84
bcc ClearTail

lda #$80
sta RAINHI,x ; we've reached the bottom

ResetX:
jsr RANDOM ; get random value
cmp #40 ; ensure its on screen
bcs ResetX ; we could AND #$28 but less appealing
sta RAINLO,x

ResetSpeed:
jsr RANDOM
cmp #5
bcs ResetSpeed
sta SPEED,x

inc TIMER
lda TIMER
cmp #60
bne Advance
jmp Advance


ClearTail:
lda #32
sta ($fd),y

SkipTail:
lda RAINLO,x
clc
adc #40
sta RAINLO,x

lda RAINHI,x
adc #0
sta RAINHI,x

Advance:
inx
cpx #30
beq +
jmp NextColumn
+ rts


; ============================================================
; Random Number Generator (LFSR)
; ============================================================

RANDOM:
lda .seedhi
lsr
rol .seedlo
bcc NoEOR
eor #$b4
NoEOR:
sta .seedhi
eor .seedlo
rts


; ============================================================
; Variables
; ============================================================

RAINLO !fill 40,0

RAINHIS !byte $7f,$80,$7f,$80,$7f,$80,$7f,$80,$7f,$80
!byte $7f,$80,$7f,$80,$7f,$80,$7f,$80,$7f,$80
!byte $80,$80,$80,$80,$7f,$80,$80,$80,$80,$80

RAINHI !fill 31,0
SPEED !fill 30,0
DEL !fill 30,0
wPOKE !fill 30,0

.seedlo !byte 0
.seedhi !byte 0

TIMER !byte 0


; ============================================================
; Clock Interface
; ============================================================

TIME = $8d ; Commodore PET Jiffy clock

clockds:
jsr LoadJiffyClock

lda ClkHourDigits
sta num+1
lda ClkHourTens
sta num
lda ClkMinTens
sta num+2
lda ClkMinDigits
sta num+3

jsr show
rts


; ============================================================
; Load Jiffy Clock (FULL ROUTINE)
; ============================================================

SECOND_JIFFIES = 60
MINUTE_JIFFIES = 60 * SECOND_JIFFIES

zptmp = $6e
zptmpB = $6f
remainder = $fc

LoadJiffyClock:

lda #13
sta $89cb
sta $8a1b

sei
lda TIME+2
ldx TIME+1
ldy TIME
cli

sta zptmp
stx remainder
sty remainder+1

lda #0
sta remainder+2

ldx #3
hhrol:
rol zptmp
rol remainder
rol remainder+1
rol remainder+2
dex
bne hhrol

ldx #5
hhdiv:
rol zptmp
rol remainder
rol remainder+1
rol remainder+2

sec
lda remainder
sbc #$c0
tay
lda remainder+1
sbc #$4b
sta zptmpB
lda remainder+2
sbc #$03
bcc hhi

sta remainder+2
lda zptmpB
sta remainder+1
tya
sta remainder
hhi:
dex
bne hhdiv

rol zptmp
ldx zptmp
jsr GetDigitChars
stx ClkHourTens
sty ClkHourDigits

lda remainder
sta zptmp

rol zptmp
rol remainder+1
rol remainder+2
rol zptmp
rol remainder+1
rol remainder+2

ldx #6
mmdiv:
rol zptmp
rol remainder+1
rol remainder+2

sec
lda remainder+1
sbc #<MINUTE_JIFFIES
tay
lda remainder+2
sbc #>MINUTE_JIFFIES
bcc mmi

sta remainder+2
tya
sta remainder+1
mmi:
dex
bne mmdiv

rol zptmp
ldx zptmp
jsr GetDigitChars
stx ClkMinTens
sty ClkMinDigits

lda remainder+1
sta zptmp

rol zptmp
rol remainder+2
rol zptmp
rol remainder+2

ldx #6
ssdiv:
rol zptmp
rol remainder+2

sec
lda remainder+2
sbc #SECOND_JIFFIES
bcc ssi
sta remainder+2
ssi:
dex
bne ssdiv

rol zptmp
ldx zptmp
jsr GetDigitChars
stx ClkSecTens
sty ClkSecDigits
rts


; ============================================================
; Digit conversion
; ============================================================

GetDigitChars:
txa
ldx #0
sec
tenloop:
sbc #10
bcc done10
inx
bcs tenloop
done10:
adc #'0'+10
tay
txa
clc
adc #'0'
tax
rts


; ============================================================
; Clock Display Rendering
; ============================================================

show:
lda ClkSecDigits
and #1
bne nodot
lda #$51
sta $81cb
lda #$20
sta $821b
jmp showdig
nodot:
lda #$20
sta $81cb
lda #$51
sta $821b

showdig:
ldx #0
stx position

nextdig:
lda #<NUMBERS
sta line+1
lda #>NUMBERS
sta line+2

ldx position
lda num,x
sec
sbc #'0'
asl
asl
asl
sta line+1

lda scrlo,x
sta $01
lda scrhi,x
sta $02

jsr drawdigit

inc position
ldx position
cpx #4
bne nextdig
rts


drawdigit:
ldy #0
line: lda $c000
ldx #0
bitloop:
asl
sta $a2
bcc spc
lda #$51
bcs put
spc: lda #$20
put:
sta ($01),y
lda $01
sta $bb
lda $02
clc
adc #8
sta $bc
lda #$0d
sta ($bb),y

lda $a2
iny
inx
cpx #8
bne bitloop

cpy #$f8
beq ddone
tya
adc #$20
tay
inc line+1
bcc line
ddone:
rts


; ============================================================
; Tables & Fonts
; ============================================================

scrlo !byte $6a,$73,$7e,$87
scrhi !byte $81,$81,$81,$81

!align 255,0
NUMBERS
!byte $7c,$c6,$c6,$c6,$c6,$c6,$7c,$00
!byte $18,$38,$18,$18,$18,$18,$7e,$00
!byte $7c,$c6,$06,$7c,$c0,$c6,$fe,$00
!byte $7c,$c6,$06,$1c,$06,$c6,$7c,$00
!byte $cc,$cc,$cc,$fe,$0c,$0c,$0c,$00
!byte $fe,$c6,$c0,$fc,$06,$c6,$7c,$00
!byte $7c,$c6,$c0,$fc,$c6,$c6,$7c,$00
!byte $fe,$c6,$0c,$18,$18,$18,$18,$00
!byte $7c,$c6,$c6,$7c,$c6,$c6,$7c,$00
!byte $7c,$c6,$c6,$7e,$06,$c6,$7c,$00

num !byte 0,0,0,0
position !byte 0
.clock !byte 0
LastSecond !byte 0
.mode
!byte 0

ClkSecTens !byte 0
ClkSecDigits !byte 0
ClkHourTens !byte 0
ClkHourDigits !byte 0
ClkMinTens !byte 0
ClkMinDigits !byte 0