!cpu 6502
!to "gol_battler.prg", cbm
; =======================================================================
; CONWAY'S GAME OF LIFE - AUTO-BATTLER SCREENSAVER
; Period 1-8 Exact Loop Detection Upgrade
; Copyright (c) nitetime.net
; Generated with the assistance of Gemini AI
; Target: Commodore 128 (Native Mode)
; Compiler: ACME Cross-Assembler
; =======================================================================
; --- System Constants ---
VIDRAM = $0400
COLORRAM = $D800
BORDER = $D020
BGCOLOR = $D021
SID_FREQ3_L = $D40E
SID_FREQ3_H = $D40F
SID_CTRL3 = $D412
SID_NOISE = $D41B
; --- Memory Map Constants ---
; Reverted to $2000s to respect default BASIC MMU memory map.
BASE_CUR = $2000
BASE_NXT = $2400
BASE_PRV = $2800
HIST_BASE = $2C00 ; 8 * 125 bytes = $2C00 - $2FE7
HASH_BASE = $2FE8 ; 8 * 4 bytes = $2FE8 - $3007
TEMP_PACK = $3008 ; 125 bytes = $3008 - $3084
; --- Zero Page Variables ---
PTR_TOP = $50
PTR_MID = $52
PTR_BOT = $54
PTR_NXT = $56
DRAW_PTR = $5A
SCR_PTR = $5C
NEIGH_CNT = $5E
PG_CUR = $5F ; Rotating high-byte for Current Board
PG_NXT = $60 ; Rotating high-byte for Next Board
PG_PRV = $61 ; Rotating high-byte for Previous Board
SCORE_L = $62
SCORE_H = $63
HISCORE_L = $64
HISCORE_H = $65
ROW_CNT = $68
; --- Loop Detection Variables ---
HIST_HEAD = $69 ; (0-7) Next slot to write
HIST_COUNT = $6A ; (0-8) Number of valid history generations
LOOP_PERIOD = $6B ; (0-8) Detected loop period
CUR_HASH = $6C ; $6C-$6F (4 bytes)
PACK_PTR = $70 ; $70-$71
HIST_PTR = $72 ; $72-$73
PACK_BYTE = $76
BIT_COUNT = $77
HIST_DIST = $78
HIST_SLOT = $79
; =======================================================================
; C128 BASIC Header (10 SYS 7181)
; =======================================================================
* = $1C01
!word BasicEnd
!word 10
!byte $9E
!byte $37,$31,$38,$31
!byte 0
BasicEnd:
!word 0
; Engine entry point starts at exactly $1C0D
* = $1C0D
Start:
sei ; Disable interrupts to protect $50-$79 ZP Workspace
lda #$FF
sta SID_FREQ3_L
sta SID_FREQ3_H
lda #$80 ; Noise Waveform
sta SID_CTRL3
lda #0
sta HISCORE_L
sta HISCORE_H
sta BORDER
sta BGCOLOR
jsr ClearScreen
jsr InitColors
NewRun:
lda #0
sta SCORE_L
sta SCORE_H
sta BORDER
; Clear HUD loop indicator
lda #$20
sta VIDRAM+18
sta VIDRAM+19
; Setup initial 3 rotating pages
lda #>BASE_CUR
sta PG_CUR
lda #>BASE_NXT
sta PG_NXT
lda #>BASE_PRV
sta PG_PRV
; Debug Marker 2: Initialization started
lda #2
sta BORDER
jsr ClearAllBoards
; Debug Marker 3: Memory cleared
lda #3
sta BORDER
jsr InitHistory
jsr SeedBoard
; Debug Marker 4: Board seeded
lda #4
sta BORDER
; Seed random board & treat it as Generation 0
lda PG_CUR
jsr PackBoard ; Pack the initial seed
; Debug Marker 5: PackBoard survived!
lda #5
sta BORDER
jsr CommitHistory ; Save to history slot 0
; Debug Marker 6: Entering Main Loop
lda #6
sta BORDER
MainLoop:
jsr DrawBoard
jsr UpdateScore
jsr CalcNext
; Pack the completed candidate generation
lda PG_NXT
jsr PackBoard
; Search for 1-8 exact loop
jsr DetectHistoryLoop
bcs .loopDetected
; No loop found. Commit this new generation to history
jsr CommitHistory
jsr SwapBoards
jmp MainLoop
.loopDetected:
; Draw exact scores, indicate the loop period, draw the frozen state
jsr DrawScores
jsr DrawLoopHUD
jsr SwapBoards ; Move the duplicate state into PG_CUR
jsr DrawBoard ; Draw the board that caused the match
ldx #0
ldy #0
.delay:
iny
bne .delay
inx
bne .delay
jmp NewRun
; =======================================================================
; History & Loop Detection Routines
; =======================================================================
InitHistory:
lda #0
sta HIST_HEAD
sta HIST_COUNT
sta LOOP_PERIOD
rts
PackBoard:
; Packs exactly 1000 cells starting at Page (A) into 125 bytes at TEMP_PACK
; Also computes the 32-bit CUR_HASH.
sta PACK_PTR+1
lda #0
sta PACK_PTR
; Seed the hash deterministically
lda #$AA
sta CUR_HASH
lda #$55
sta CUR_HASH+1
lda #$CC
sta CUR_HASH+2
lda #$33
sta CUR_HASH+3
ldx #0 ; TEMP_PACK output index (0-124)
stx BIT_COUNT ; 0-7 bit accumulator
stx PACK_BYTE
ldy #0 ; Cell pointer (0-255)
.cellLoop:
lda (PACK_PTR),y
beq .zeroBit
sec ; Inject 1
bcs .shift
.zeroBit:
clc ; Inject 0
.shift:
rol PACK_BYTE
inc BIT_COUNT
lda BIT_COUNT
cmp #8
bne .nextCell
; Byte full -> Store it
lda PACK_BYTE
sta TEMP_PACK,x
; Fast Rolling Hash Mix
clc
adc CUR_HASH
sta CUR_HASH
rol CUR_HASH+1
rol CUR_HASH+2
lda CUR_HASH+3
eor PACK_BYTE
sta CUR_HASH+3
; CHECK TERMINATION CONDITION:
; Have we stored exactly 125 bytes? (representing exactly 1000 cells)
inx
cpx #125
beq .packDone
; Not done yet. Reset byte packer
lda #0
sta BIT_COUNT
sta PACK_BYTE
.nextCell:
iny
bne .cellLoop ; If Y hasn't wrapped, continue on this page
; Y wrapped to 0. Advance high byte of source page.
inc PACK_PTR+1
jmp .cellLoop
.packDone:
rts
DetectHistoryLoop:
; Checks up to HIST_COUNT previous generations from newest to oldest
; Returns Carry SET if loop found, CLEAR if no loop.
lda HIST_COUNT
bne +
clc
rts
+
lda #1 ; Start checking Distance = 1
sta HIST_DIST
.loop:
; Slot = (HIST_HEAD - HIST_DIST) AND 7
lda HIST_HEAD
sec
sbc HIST_DIST
and #7
sta HIST_SLOT
tax
; Compare Hash first (Slot * 4 offset)
txa
asl
asl
tay
lda CUR_HASH
cmp HASH_BASE,y
bne .nextDist
lda CUR_HASH+1
cmp HASH_BASE+1,y
bne .nextDist
lda CUR_HASH+2
cmp HASH_BASE+2,y
bne .nextDist
lda CUR_HASH+3
cmp HASH_BASE+3,y
bne .nextDist
; Hash Match! Perform Exact 125-byte Byte-for-Byte comparison
lda HistSlotLo,x
sta HIST_PTR
lda HistSlotHi,x
sta HIST_PTR+1
ldy #0
.cmpLp:
lda TEMP_PACK,y
cmp (HIST_PTR),y
bne .nextDist ; Hash collision, keep searching
iny
cpy #125
bne .cmpLp
; Exact match validated!
lda HIST_DIST
sta LOOP_PERIOD
sec
rts
.nextDist:
inc HIST_DIST
lda HIST_DIST
cmp HIST_COUNT
bcc .loop ; if DIST < COUNT, loop
beq .loop ; if DIST == COUNT, loop
clc ; No matches found
rts
CommitHistory:
; Ensure HIST_COUNT maxes at 8
lda HIST_COUNT
cmp #8
beq +
inc HIST_COUNT
+
; Copy TEMP_PACK to history slot
ldx HIST_HEAD
lda HistSlotLo,x
sta HIST_PTR
lda HistSlotHi,x
sta HIST_PTR+1
ldy #0
.cpyLp:
lda TEMP_PACK,y
sta (HIST_PTR),y
iny
cpy #125
bne .cpyLp
; Copy 32-bit Hash to HASH_BASE
txa
asl
asl
tay
lda CUR_HASH
sta HASH_BASE,y
lda CUR_HASH+1
sta HASH_BASE+1,y
lda CUR_HASH+2
sta HASH_BASE+2,y
lda CUR_HASH+3
sta HASH_BASE+3,y
; Advance Ring Buffer Pointer
inx
txa
and #7
sta HIST_HEAD
rts
; --- Lookups for 125-byte boundaries ---
HistSlotLo:
!byte <(HIST_BASE + 0*125), <(HIST_BASE + 1*125), <(HIST_BASE + 2*125), <(HIST_BASE + 3*125)
!byte <(HIST_BASE + 4*125), <(HIST_BASE + 5*125), <(HIST_BASE + 6*125), <(HIST_BASE + 7*125)
HistSlotHi:
!byte >(HIST_BASE + 0*125), >(HIST_BASE + 1*125), >(HIST_BASE + 2*125), >(HIST_BASE + 3*125)
!byte >(HIST_BASE + 4*125), >(HIST_BASE + 5*125), >(HIST_BASE + 6*125), >(HIST_BASE + 7*125)
; =======================================================================
; Game Engine Subroutines
; =======================================================================
ClearAllBoards:
; Clears 17 Pages: $2000 - $30FF (Boards, History, Hash, Temp Pack)
lda #0
tay
.clrLp:
sta $2000,y
sta $2100,y
sta $2200,y
sta $2300,y
sta $2400,y
sta $2500,y
sta $2600,y
sta $2700,y
sta $2800,y
sta $2900,y
sta $2A00,y
sta $2B00,y
sta $2C00,y
sta $2D00,y
sta $2E00,y
sta $2F00,y
sta $3000,y
iny
bne .clrLp
rts
SeedBoard:
lda #40
sta DRAW_PTR
lda PG_CUR ; Seed directly into CUR
sta DRAW_PTR+1
ldx #23
.sRow:
ldy #1
.sCol:
lda SID_NOISE
and #$03
cmp #$01
bcc .sAlive ; ~25% density
lda #0
beq .sStore
.sAlive:
lda #1
.sStore:
sta (DRAW_PTR),y
iny
cpy #39
bne .sCol
lda DRAW_PTR
clc
adc #40
sta DRAW_PTR
bcc +
inc DRAW_PTR+1
+ dex
bne .sRow
rts
ClearScreen:
lda #$20
ldx #0
.csLoop:
sta VIDRAM,x
sta VIDRAM+250,x
sta VIDRAM+500,x
sta VIDRAM+750,x
inx
cpx #250
bne .csLoop
rts
InitColors:
ldx #0
lda #$05
.colLp:
sta COLORRAM,x
sta COLORRAM+250,x
sta COLORRAM+500,x
sta COLORRAM+750,x
inx
cpx #250
bne .colLp
rts
DrawBoard:
lda #40
sta DRAW_PTR
lda PG_CUR
sta DRAW_PTR+1
lda #<(VIDRAM+40)
sta SCR_PTR
lda #>(VIDRAM+40)
sta SCR_PTR+1
ldx #23
.rLoop:
ldy #1
.cLoop:
lda (DRAW_PTR),y
beq .space
lda #$51 ; Solid Ball
bne .draw
.space:
lda #$20 ; Empty Space
.draw:
sta (SCR_PTR),y
iny
cpy #39
bne .cLoop
lda DRAW_PTR
clc
adc #40
sta DRAW_PTR
bcc +
inc DRAW_PTR+1
+
lda SCR_PTR
clc
adc #40
sta SCR_PTR
bcc +
inc SCR_PTR+1
+
dex
bne .rLoop
rts
SwapBoards:
ldx PG_PRV
lda PG_CUR
sta PG_PRV
ldy PG_NXT
sty PG_CUR
stx PG_NXT
rts
UpdateScore:
inc SCORE_L
bne +
inc SCORE_H
+
lda SCORE_H
cmp HISCORE_H
bcc .checkProx
bne .isNewHi
lda SCORE_L
cmp HISCORE_L
bcc .checkProx
beq .checkProx
.isNewHi:
lda SCORE_L
sta HISCORE_L
lda SCORE_H
sta HISCORE_H
inc BORDER
jmp .doneScore
.checkProx:
lda HISCORE_H
cmp SCORE_H
bne .cold
lda #$07
sta BORDER
jmp .doneScore
.cold:
lda #$00
sta BORDER
.doneScore:
jsr DrawScores
rts
DrawScores:
lda SCORE_H
jsr PrintByte
stx VIDRAM+0
sty VIDRAM+1
lda SCORE_L
jsr PrintByte
stx VIDRAM+2
sty VIDRAM+3
lda HISCORE_H
jsr PrintByte
stx VIDRAM+35
sty VIDRAM+36
lda HISCORE_L
jsr PrintByte
stx VIDRAM+37
sty VIDRAM+38
rts
DrawLoopHUD:
lda #$10 ; "P" screen code
sta VIDRAM+18
lda LOOP_PERIOD
ora #$30 ; Screen code offset for 1-8 ($31-$38)
sta VIDRAM+19
rts
PrintByte:
pha
lsr
lsr
lsr
lsr
tax
lda HexChars,x
tax
pla
and #$0F
tay
lda HexChars,y
tay
rts
HexChars: !byte 48,49,50,51,52,53,54,55,56,57,1,2,3,4,5,6
CalcNext:
lda #0
sta PTR_TOP
lda PG_CUR
sta PTR_TOP+1
lda #40
sta PTR_MID
lda PG_CUR
sta PTR_MID+1
lda #80
sta PTR_BOT
lda PG_CUR
sta PTR_BOT+1
lda #40
sta PTR_NXT
lda PG_NXT
sta PTR_NXT+1
lda #23
sta ROW_CNT
.rowLoop:
ldy #1
.colLoop:
; Top Row Neighbors
dey
lda (PTR_TOP),y
clc
adc (PTR_BOT),y
iny
clc
adc (PTR_TOP),y
adc (PTR_BOT),y
iny
clc
adc (PTR_TOP),y
adc (PTR_BOT),y
dey
; Mid Row Neighbors
dey
clc
adc (PTR_MID),y
iny
iny
clc
adc (PTR_MID),y
dey
sta NEIGH_CNT
lda (PTR_MID),y
tax
lda NEIGH_CNT
; Conway Rules Check
cpx #0
beq .deadCell
.aliveCell:
cmp #2
beq .survive
cmp #3
beq .survive
jmp .die
.deadCell:
cmp #3
beq .survive
jmp .die
.survive:
lda #1
bne .store
.die:
lda #0
.store:
sta (PTR_NXT),y
iny
cpy #39
bne .colLoop
jsr AdvPtrs
dec ROW_CNT
bne .rowLoop
rts
AdvPtrs:
; Only increments the four required 16-bit sliding window pointers
lda PTR_TOP
clc
adc #40
sta PTR_TOP
bcc +
inc PTR_TOP+1
+
lda PTR_MID
clc
adc #40
sta PTR_MID
bcc +
inc PTR_MID+1
+
lda PTR_BOT
clc
adc #40
sta PTR_BOT
bcc +
inc PTR_BOT+1
+
lda PTR_NXT
clc
adc #40
sta PTR_NXT
bcc +
inc PTR_NXT+1
+
rts
; =======================================================================
; Memory Protection Assertion
; =======================================================================
!if * > $2000 { !error "Fatal: Machine code size exceeds boundary and overlaps with working boards at $2000!" }