!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