forked from z80playground/cpm-fat
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathGOFL.asm
374 lines (332 loc) · 14.5 KB
/
GOFL.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
; Conway's Game Of Life for the Z80 Playground.
; This was written by Albert Pauw in February 2021, originally for CP/M,
; and adapted for the Z80 playground Monitor by john Squires.
; After boiling it down A LOT, it turns out that Game-Of-Life is very simple:
; Arrange a large grid of cells, where each can be either alive = 1 or dead = 0.
; Make sure there is an empty row all around the grid with a dead cell in it, like this:
; 0000000000
; 0XXXXXXXX0
; 0XXXXXXXX0
; 0000000000 Where 0 = dead cell, and X = active area which can be 0s or 1s.
;
; Iterate over all the cells in the active area. For each:
; Make a note of whether the cell is alive or dead.
; Start counting neighbours for the cell, starting at 0.
; Add to this value the alive/dead value of all 8 neighbouring cells around it.
; Store the neighbour count in the top 4 bits, and the alive/dead state of the cell
; in bit 0. You will then end up with each cell having a binary value something like these examples:
; 0010 0001 - This is 2 neighbours and the cell is currently alive.
; 0011 0000 - This is a dead cell with 3 neighbours.
; 1000 0001 - This is an alive cell with 8 neighbours.
;
; Now, it turns out that all possible combinations result in the death of a cell, or
; a dead cell staying dead, except these three:
; 0010 0001 - Alive cell with 2 neighbours stays alive.
; 0011 0001 - Alive cell with 3 neighbours stays alive.
; 0011 0000 - Dead cell with 3 neighbours comes to life.
;
; So all we need to do is iterate over the cells again.
; If the cell contains one of these 3 values, set the cell to 1.
; Otherwise set it to 0.
;
; Then show all cells on screen, and start again!
Width: EQU 80
Height: EQU 25
PatternWidth: equ 80
Size: EQU Width*Height
DOT: EQU '.' ; ASCII 46, so EVEN. This is important later!
HASH: EQU '#' ; ASCII 25, so ODD. This is important later!
ESC: EQU 27
GOFL_Begin:
call show_intro_screen
call long_pause
call wait_for_key
ld a, b
cp 200
jp nc, copy_pattern3
cp 100
jp nc, copy_pattern2
copy_pattern1:
ld hl, initial_pattern1
call copy_initial_pattern
jr GOFL_Begin1
copy_pattern2:
ld hl, initial_pattern2
call copy_initial_pattern
jr GOFL_Begin1
copy_pattern3:
ld hl, initial_pattern3
call copy_initial_pattern
jr GOFL_Begin1
GOFL_Begin1:
CALL GOFL_HCursor ; Hide cursor
CALL GOFL_Cls ; Clear screen
main_gofl_loop:
; First, iterate over the cells, counting the neighbours
ld c, Height
ld h, BufferPage+1 ; h = y coord, l = x coord
iterate_outer:
ld l, 1 ; Start at coord 1,1
ld b, Width
iterate_loop:
ld a, (hl) ; Get original cell content
and %00000001
ld d, a ; Store in d
xor a ; Clear a
dec l ; West neighbour
add a, (hl)
dec h ; North-West neighbour
add a, (hl)
inc l ; North neighbour
add a, (hl)
inc l ; North-East neighbour
add a, (hl)
inc h ; East neighbour
add a, (hl)
inc h ; South-East neighbour
add a, (hl)
dec l ; South neighbour
add a, (hl)
dec l ; South-West neighbour
add a, (hl)
inc l ; Get back to center cell
dec h
sla a ; rotate left
sla a ; rotate left
sla a ; rotate left
sla a ; rotate left
or d ; Put back the original cell content
ld (hl), a ; Store final result
inc l
djnz iterate_loop
inc h
ld l, 1
dec c
jr nz, iterate_outer
; Now iterate over the cells again, applying the rules
apply_rules:
ld c, Height
ld h, BufferPage+1
apply_rules_outer:
ld l, 1 ; Start at 1,1
ld b, Width
apply_rules_loop:
; 0010 0001 - Alive cell with 2 neighbours stays alive.
; 0011 0001 - Alive cell with 3 neighbours stays alive.
; 0011 0000 - Dead cell with 3 neighbours comes to life.
ld a, (hl) ; Get the content into a
cp %00100001
jr z, cell_alive
cp %00110001
jr z, cell_alive
cp %00110000
jr z, cell_alive
ld (hl), 0 ; Cell dies
jp apply_rules_continue
cell_alive:
ld (hl), 1 ; Cell lives
apply_rules_continue:
inc l
djnz apply_rules_loop
inc h
ld l, 1
dec c
jr nz, apply_rules_outer
; Now print the cells to the screen
GOFL_Print:
call GOFL_Home
ld h, BufferPage+1
ld l, 1 ; Start at 1,1
LD c, Height ; Set size for loops, height...
Pr0:
ld b, Width ; ...and width
Pr1:
LD A, (HL) ; Get cell value in buffer
and 1 ; Is it ODD?
jp z, print_empty_cell ; If not, it is an empty cell
ld d, HASH
jp print_got_character
print_empty_cell:
ld d, DOT
print_got_character:
in a, (uart_LSR) ; check UART is ready to send.
bit 5, a ; zero flag set to true if bit 5 is 0
jp z, print_got_character ; non-zero = ready for next char.
ld a, d
out (uart_tx_rx), a ; AND SEND IT OUT
INC L ; Next character in buffer
djnz Pr1 ; Count down and loop
dec c ; decrease row counter
jp z, skip_newline_on_last_row
call newline
skip_newline_on_last_row:
ld l, 1 ; Back to start of row
inc h ; Move down a row
ld a, c
cp 0
jp nz, Pr0 ; Loop over rows
; Now check for key press to end
CALL char_in ; Check for keypress
AND A
Jp Z, main_gofl_loop ; Loop around again if no key
call GOFL_SCursor ; Turn cursor back ok
ret
; Helper routines
GOFL_Home:
call message
DB ESC, '[H', 0
ret
GOFL_Cls:
call message
DB ESC, '[2J', ESC, '[H', 0
ret
GOFL_HCursor:
; ANSI hide cursor
call message
DB ESC, '[?25l', 0
ret
GOFL_SCursor:
; ANSI show cursor
call message
DB ESC, '[?25h', 0
ret
initial_pattern1:
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '...................................................#............................'
DB '.....................................................#..........................'
DB '..................................................##..###.......................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
initial_pattern2:
DB '................................................................................'
DB '..##............................................................................'
DB '.........................#......................................................'
DB '.......................#.#......................................................'
DB '.............##......##............##...........................................'
DB '............#...#....##............##...........................................'
DB '.##........#.....#...##.........................................................'
DB '.##........#...#.##....#.#......................................................'
DB '...........#.....#.......#......................................................'
DB '............#...#...............................................................'
DB '.............##.................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
initial_pattern3:
DB '................................................................................'
DB '................................................................................'
DB '....................................................................##..........'
DB '....................................................................##..........'
DB '................................................................................'
DB '.....................#..#.......................................................'
DB '.........................#.......................####...........................'
DB '.....................#...#......................................................'
DB '......................####.........................####.........................'
DB '................................................................................'
DB '....#...........................................................................'
DB '.....#..........................................................................'
DB '...###..........................................................................'
DB '...................................................................#..#.........'
DB '..................................................................#.............'
DB '..................................................................#...#.........'
DB '...........................................###....................####..........'
DB '..........................................###...................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................................................................................'
DB '................##..............................................................'
DB '................##..............................................................'
show_intro_screen:
call GOFL_Cls
call message
db 'This is the "Game Of Life", originally devised by John Conway in the 1970s.', 13, 10
db 'This implementation was written by Albert Pauw on a Z80 Playground using CP/M.', 13, 10
db 'It has been adapted to run in the Monitor as a demo.', 13, 10
db 'Make sure you have a screen of at least 80 x 25 characters.', 13, 10
db 'Press any key to start...', 13, 10, 0
ret
wait_for_key:
; Waits for a key, and generates a random number in b, which it returns!
ld b, 0
wait_for_key1:
inc b
call char_in
cp 0
jp z, wait_for_key1
ret
copy_initial_pattern:
; Copy the starting pattern into the buffer.
; The pointer to the pattern is passed in HL.
; The pattern is made of "." and "#" but we store it in the buffer as
; 1s and 0s. We do this by ANDing the char with %00000001, which is
; why the '#' char needs to be ODD and the '.' char needs to be EVEN.
push hl
; But first, totally zero out the entire buffer
ld hl, Buffer
ld (hl), 0
ld de, Buffer+1
ld b, Height+3
ld c, 0
ldir
pop hl
; Now copy the pattern to the buffer
ld d, BufferPage+1 ; Initialise at location 1,1
ld e, 1 ; in the buffer (top left is 0,0)
ld c, Height
copy_initial_pattern_rows:
ld b, Width
push hl ; Store pattern pointer
copy_initial_pattern_cols:
ld a, (hl) ; Copy from pattern to buffer
and %00000001 ; Isolate bit 0 only
ld (de), a
inc hl ; Move to next location in pattern
inc e ; next column
djnz copy_initial_pattern_cols ; loop columns
pop hl ; Back to start of current row in pattern
push de
ld de, PatternWidth
add hl, de ; Move to next row in pattern
pop de
ld e, 1 ; Back to start of buffer row
inc d ; But move down a row
dec c ; loop rows
jr nz, copy_initial_pattern_rows
ret
;
; The buffer needs to be in RAM... ;
;
Buffer equ $8000
BufferPage equ $80