NITETIME GOPHER 1.0 - Lake Koshkonong Node
Server: gopher.nitetime.net:70    Selector: /commodore/battler.asm
!cpu 6502
!to "gol_battler.prg", cbm

; =======================================================================
; CONWAY'S GAME OF LIFE - AUTO-BATTLER SCREENSAVER
; 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

; --- Zero Page Variables ---
; Using safe workspace ranges for Native ML execution
PTR_TOP     = $50
PTR_MID     = $52
PTR_BOT     = $54
PTR_NXT     = $56
PTR_PRV     = $58
DRAW_PTR    = $5A
SCR_PTR     = $5C

NEIGH_CNT   = $5E   ; [FIX 1] Holds neighbor sum to safely load current cell
PG_CUR      = $5F   ; [IMPROVEMENT] Rotating high-byte for Current Board ($20, $24, $28)
PG_NXT      = $60   ; [IMPROVEMENT] Rotating high-byte for Next Board
PG_PRV      = $61   ; [IMPROVEMENT] Rotating high-byte for Previous Board

SCORE_L     = $62
SCORE_H     = $63
HISCORE_L   = $64
HISCORE_H   = $65

FLAG_CHANGE = $66
FLAG_PRV_CH = $67
ROW_CNT     = $68

; =======================================================================
; C128 BASIC Header (10 SYS 7181)
; [FIX 2] Properly structured BASIC loader with dynamic Next-Line link.
; =======================================================================
* = $1C01
!word BasicEnd          ; Link to next BASIC line
!word 10                ; Line Number (10)
!byte $9E               ; SYS token
!byte $37,$31,$38,$31   ; ASCII "7181" (Address $1C0D)
!byte 0                 ; End of Line 10
BasicEnd:
!word 0                 ; End of BASIC program markers

; Engine entry point starts at exactly $1C0D
* = $1C0D

Start:
    ; Init SID Voice 3 for Hardware RNG
    lda #$FF
    sta SID_FREQ3_L
    sta SID_FREQ3_H
    lda #$80        ; Noise Waveform
    sta SID_CTRL3

    ; Init Variables
    lda #0
    sta HISCORE_L
    sta HISCORE_H
    sta BORDER
    sta BGCOLOR

    jsr ClearScreen
    jsr InitColors

NewRun:
    ; Reset Run Variables
    lda #0
    sta SCORE_L
    sta SCORE_H
    sta BORDER

    ; Set initial memory banks for the 3 rolling buffers
    lda #$20        ; $2000
    sta PG_CUR
    lda #$24        ; $2400
    sta PG_NXT
    lda #$28        ; $2800
    sta PG_PRV

    ; [FIX 5] Completely wipe all three buffers to 0 before starting
    ; This ensures no leftover data triggers a false period-2 loop on Gen 1.
    jsr ClearAllBoards
    jsr SeedBoard
    
MainLoop:
    jsr DrawBoard
    jsr UpdateScore ; [FIX 3] Update scores here so the HUD remains visible during CalcNext
    jsr CalcNext

    ; Loop Detection: 
    ; FLAG_CHANGE = 0 -> NXT == CUR (Fixed Point)
    ; FLAG_PRV_CH = 0 -> NXT == PRV (Period-2 Oscillator)
    lda FLAG_CHANGE
    beq .endOfRun
    lda FLAG_PRV_CH
    beq .endOfRun

    jsr SwapBoards  ; [IMPROVEMENT] Swaps high-byte ZP pointers instead of 1000-byte copies
    jmp MainLoop

.endOfRun:
    ; Delay briefly so the viewer sees the final resting state
    ldx #0
    ldy #0
.delay:
    iny
    bne .delay
    inx
    bne .delay
    jmp NewRun

; =======================================================================
; Subroutines
; =======================================================================

ClearAllBoards:
    ; Wipes $2000 through $2BFF (all 3 buffers + padding)
    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
    iny
    bne .clrLp
    rts

SeedBoard:
    ; [FIX 6] Only seeds rows 1-23 and cols 1-38 to preserve a dead outer border.
    ; [IMPROVEMENT] Adjusted density to 25% for better chaotic battles.
    lda #40
    sta DRAW_PTR
    lda PG_NXT
    sta DRAW_PTR+1
    
    ldx #23         ; 23 inner rows
.sRow:
    ldy #1          ; Skip col 0
.sCol:
    lda SID_NOISE
    and #$03        ; Mask to 0, 1, 2, or 3
    cmp #$01        ; Is it 0?
    bcc .sAlive     ; 25% chance of spawning alive
    lda #0
    beq .sStore
.sAlive:
    lda #1
.sStore:
    sta (DRAW_PTR),y
    iny
    cpy #39         ; Skip col 39
    bne .sCol
    
    ; Advance line
    lda DRAW_PTR
    clc
    adc #40
    sta DRAW_PTR
    bcc +
    inc DRAW_PTR+1
+   dex
    bne .sRow
    
    jsr SwapBoards
    rts

ClearScreen:
    ; Clear entire screen to spaces ($20)
    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:
    ; Paint the screen memory with Green ($05)
    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:
    ; [FIX 3] Only draws rows 1-23. The HUD on Row 0 is never overwritten.
    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         ; 23 Rows
.rLoop:
    ldy #1          ; Skip col 0
.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         ; Skip col 39
    bne .cLoop

    ; Advance Pointers by 40
    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:
    ; [IMPROVEMENT] Pointer rotation instead of 1000-byte RAM copy
    ; PRV = CUR, CUR = NXT, NXT = PRV (Recycle old PRV as new blank NXT)
    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
+
    ; [FIX 4] Fixed high-score tracking. Tying previous high-score does NOT break it.
    lda SCORE_H
    cmp HISCORE_H
    bcc .checkProx  ; Less than high score
    bne .isNewHi    ; Greater than high score (New Record!)
    lda SCORE_L
    cmp HISCORE_L
    bcc .checkProx  ; Less than high score
    beq .checkProx  ; Exact Tie. Do not trigger New Record yet.

.isNewHi:
    lda SCORE_L
    sta HISCORE_L
    lda SCORE_H
    sta HISCORE_H
    inc BORDER      ; Strobe the border on record break!
    jmp .doneScore

.checkProx:
    ; Proximity Hype: if High Bytes match, we are within 255 ticks
    lda HISCORE_H
    cmp SCORE_H
    bne .cold
    lda #$07        ; Yellow border warning
    sta BORDER
    jmp .doneScore
.cold:
    lda #$00        ; Black border
    sta BORDER

.doneScore:
    jsr DrawScores
    rts

DrawScores:
    ; Current Score (Top Left - Row 0)
    lda SCORE_H
    jsr PrintByte
    stx VIDRAM+0
    sty VIDRAM+1
    lda SCORE_L
    jsr PrintByte
    stx VIDRAM+2
    sty VIDRAM+3

    ; High Score (Top Right - Row 0)
    lda HISCORE_H
    jsr PrintByte
    stx VIDRAM+35
    sty VIDRAM+36
    lda HISCORE_L
    jsr PrintByte
    stx VIDRAM+37
    sty VIDRAM+38
    rts

PrintByte:
    ; Convert A to Hex Screen Codes (High nibble X, Low nibble Y)
    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:
    ; Reset loop detection flags for this frame
    lda #0
    sta FLAG_CHANGE
    sta FLAG_PRV_CH

    ; Setup 3-row sliding window pointers using dynamic Page Pointers
    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 #40
    sta PTR_PRV
    lda PG_PRV
    sta PTR_PRV+1

    lda #23         ; Process 23 inner rows
    sta ROW_CNT

.rowLoop:
    ldy #1          ; Start at inner column 1 (skip left edge)

.colLoop:
    ; Neighbor math (Top Row)
    dey             ; Col - 1
    lda (PTR_TOP),Y
    clc
    adc (PTR_BOT),Y
    iny             ; Col 0
    clc
    adc (PTR_TOP),Y
    adc (PTR_BOT),Y
    iny             ; Col + 1
    clc
    adc (PTR_TOP),Y
    adc (PTR_BOT),Y
    dey             ; Back to Col 0
    
    ; Neighbor math (Mid Row)
    dey             ; Col - 1
    clc
    adc (PTR_MID),Y
    iny
    iny             ; Col + 1
    clc
    adc (PTR_MID),Y
    dey             ; Back to Col 0

    ; [FIX 1] Replaced illegal `LDX (PTR_MID),Y` with valid safe storage
    sta NEIGH_CNT   ; Save summed neighbor count (0-8)
    lda (PTR_MID),y ; Load center cell state
    tax             ; Transfer cell state to X
    lda NEIGH_CNT   ; Restore neighbor count to Accumulator

    ; State Rules execution
    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
    
    ; Fixed Point Check
    cmp (PTR_MID),Y
    beq .chkPrv
    pha
    lda #1
    sta FLAG_CHANGE
    pla
.chkPrv:
    ; Period-2 Oscillator Check
    cmp (PTR_PRV),Y
    beq .nextCell
    pha
    lda #1
    sta FLAG_PRV_CH
    pla

.nextCell:
    iny
    cpy #39         ; Stop at inner column 38 (skip right edge)
    bne .colLoop

    jsr AdvPtrs
    dec ROW_CNT
    bne .rowLoop
    rts

AdvPtrs:
    ; Steps all sliding windows down one 40-byte row
    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
+
    lda PTR_PRV
    clc
    adc #40
    sta PTR_PRV
    bcc +
    inc PTR_PRV+1
+
    rts
SIGNAL ACTIVE   //   WISCO 608   //   Gateway locked to gopher.nitetime.net