!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!" }