Cellular automaton Simulation of Conway’s Game of Life.
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Conway's Game of Life ; Steven Reid (c) 2024 ; A large screen version of life for the ZX81. ; v1 12/30/2024 - initial build ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Header and startup ; ; start up stuff ORG 16514 ; stored in REM at top (ZX81) jr start ; needed for z80asm ; title and copyright (will show when LISTed) copy: DB _as,_as,_as,_l_,_i_,_f_,_e_,_as,_as,_as DB _s_,_l_,_r_,_sl,_2_,_0_,_2_,_4_,_as,_as DB _as,$76 ; ***LIFE***SLR/2024*** ; starting routines (if any) start: ; call slow ; SLOW is required. ; call cls ; clear/expand screen ; ; end header and startup ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Main program ; main_loop: call init_life ; Initilize Game of Life ; run simulation life_loop: call live_or_die ; run life simulation call copy_to_screen ; copy new life to screen call delay_and_test ; tests for break, key, gen>100 ; was a key pressed or generations hit 100? or a jp z,life_loop ; no, display next generation jp main_loop ; otherwise start again! ; ; end main ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Routines ; ; +++ ; Initilize Life ; init_life: xor a ; a=0 ld (generation),a ; clear generations ; initilize screen call cls ; clear/expand screen call clear_buffer ; loop through screen, creating random life ld hl,(d_file) ; get screen start ld bc,1+33 add hl,bc ; skip a row+1 ; loop through rows ld c,22 l_xlp: inc hl ; skip first char ; loop through cols ld b,30 l_ylp: ld (hl),$80 ; there is life! call rnd ; is there life? jr c,l_skip ; skip on carry ld (hl),$00 ; there is life! l_skip: inc hl ; skip to next col djnz l_ylp ; next y inc hl ; skip last char inc hl ; skip CR dec c jr nz,l_xlp ; next x ret ; end initilize ; --- ; +++ ; Live or die live_or_die: ; increment generation ld a,(generation) inc a ld (generation),a ; loop through screen, creating random life ld hl,buffer ; get buffer location ld bc,33 add hl,bc ; skip a row ex de,hl ; put buffer location into de ld hl,(d_file) ; get screen start ld bc,34 add hl,bc ; skip a row+1 ; loop through rows ld c,21 ; 1-22 lod_xlp: inc hl ; skip first char inc de ; skip first char ; loop through cols (2-31) ld b,30 lod_ylp: push bc ; save vars push de push hl ; count life around location ld c,0 ; reset counter dec hl ; move to x,y-1 call count_life inc hl inc hl ; move to x,y+1 call count_life ld de,33+2 and a ; clear carry sbc hl,de ; move to x-1,y-1 call count_life inc hl ; move to x-1,y call count_life inc hl ; move to x+1,y call count_life ld de,33+31 add hl,de ; move to x+1,y-1 call count_life inc hl ; move to x+1,y call count_life inc hl ; move to x+1,y+1 call count_life pop hl ; restore hl & de pop de ld a,(hl) or a ; check if cell alive or dead ld a,c ; move count to a jr z,lod_empty ; is empty, skip ahead ; check if dies (c<>2 and c<>3) cp 2 ; check if 2 jr z,lod_alive ; is 2, stays alive! cp 3 ; check if 3 jr z,lod_alive ; is 3, stays alive! xor a ; cell dies jr lod_skip ; done lod_empty: ; check if lives cp 3 ; check if 3 ld a,0 ; assume empty cell jr nz,lod_skip ; not 3, skip ahead lod_alive: ld a,$80 ; yes, cell is born lod_skip: ld (de),a ; load new cell in buffer pop bc inc de ; next char inc hl ; next char djnz lod_ylp ; next y inc de ; skip last char inc de ; skip CR inc hl ; skip last char inc hl ; skip CR dec c jr nz,lod_xlp ; next x ret count_life: ld a,(hl) ; get cell or a ; is it alive? ret z ; no, return inc c ; yes, then increment count ret ; end life or die ; --- ; +++ ; Copy to screen copy_to_screen: ; copy buffer to screen ld hl,(d_file) ; get display inc hl ex de,hl ; put display (to) into de ld hl,buffer ; get buffer location ld bc,33*buffer_size ldir ; load buffer on screen! ret ; end copy_to_screen ; --- ; +++ ; Clear buffer from display ; clear_buffer: ld de,buffer ; load buffer (to) into de ld hl,(d_file) ; load display (from) into hl inc hl ; skip to first character ld bc,33*buffer_size ldir ; clear buffer from screen ret ; end clear buffer ; --- ; +++ ; ; Binary Random ; ; out: a = a number between 0 and 255 ; On return: C = 0, NC = 1 ; preserves: bc,de,hl ; destroys: af ; rnd: ; random routine with refresh push hl ; preserve hl rnd_val: ld hl,0 ; seed value ld a,r ; grab the refresh register add a,l ; add msb of seed (l) ld l,a ; and save it as the new value ld a,r ; grab the refresh register again add a,h ; add lsb of seed (h) and $1f ; mask it to stay in ZX81 ROM (lower 8K) ld h,a ; set pointer back within ROM ld (rnd_val+1),hl ; save current pointer (self modifying code!!!) ld a,(hl) ; get value in ROM pop hl ; restore hl cp 33 ; will set C if > 33 ret ; back to mainprogram ; end random ; ; --- ; +++ ; Get Key ; ; in: none ; out: a = character code of pressed key ; destroys: af, bc, hl, de ; get_key: ; did the player make a move? xor a ; clear a ld bc,(last_k) ; get key information inc c ; Test if NOKEY pressed ret z ; nope, return dec c ; restore c call findchar ; yep, grab character pressed ld a,(hl) ret ; return ; end get move ; --- ; +++ ; Break ; ; preserves state, but will exit if SPACE is pushed check_break: exx ; save register states ; did the player press break key (space)? call $0f46 ; was break pressed? (break-1 ROM routine) jr nc,break ; no, then return exx ; restore registers ret ; and return ; yes, exit the program as normal break: rst $0008 ; call ERROR-1 reset DB $ff ; with error code 0 (normal exit) ; end break ; --- ; +++ ; Delay and test ; ; Will break out of program if SPACE is pressed ; Will end early if a key is pressed ; Will end if generation hits 100 delay_count: DW $0000 delay_and_test: ld a,(generation) ; get generation cp 100 ; is it 100? ret nc ; yes, return ld hl,100 ; time to delay delay: ld (delay_count),hl ; save delay call check_break ; pressed break? call get_key ; pressed a key? or a ret nz ; yes, return early ; check if done ld hl,(delay_count) ; grab what to test dec hl ; subtract 1 ld a,h ; check if done or l jr nz,delay ; not zero, keep going! ret ; pause is done! ; end delay and test ; --- ; ; end routines ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Variables ; ; life information generation: DB _0_ buffer_size: EQU 24 buffer: DEFS 33*buffer_size ; ; end variables ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; Defines ; ; ZX81 system vars d_file: EQU $400c df_cc: EQU 16398 last_k: EQU 16421 margin: EQU 16424 s_posn: EQU 16441 frames: EQU 16436 ; ZX81 ROM functions kscan: EQU $02bb findchar: EQU $07bd stop: EQU $0cdc slow: EQU $0f2b fast: EQU $02e7 save: EQU $02f9 printat: EQU $08f5 pause: EQU $0f35 cls: EQU $0a2a ; ZX81 Characters (not ASCII) _sp: EQU $00 _qu: EQU $0b _lb: EQU $0c _dl: EQU $0d _cl: EQU $0e _lp: EQU $10 _rp: EQU $11 _gt: EQU $12 _lt: EQU $13 _eq: EQU $14 _pl: EQU $15 _mi: EQU $16 _as: EQU $17 _sl: EQU $18 _sc: EQU $19 _cm: EQU $1a _pr: EQU $1b _0_: EQU $1c _1_: EQU $1d _2_: EQU $1e _3_: EQU $1f _4_: EQU $20 _5_: EQU $21 _6_: EQU $22 _7_: EQU $23 _8_: EQU $24 _9_: EQU $25 _a_: EQU $26 _b_: EQU $27 _c_: EQU $28 _d_: EQU $29 _e_: EQU $2a _f_: EQU $2b _g_: EQU $2c _h_: EQU $2d _i_: EQU $2e _j_: EQU $2f _k_: EQU $30 _l_: EQU $31 _m_: EQU $32 _n_: EQU $33 _o_: EQU $34 _p_: EQU $35 _q_: EQU $36 _r_: EQU $37 _s_: EQU $38 _t_: EQU $39 _u_: EQU $3a _v_: EQU $3b _w_: EQU $3c _x_: EQU $3d _y_: EQU $3e _z_: EQU $3f ; ; end defines ; ; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *