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
;
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *