-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgame_loop.z80
304 lines (257 loc) · 10.8 KB
/
game_loop.z80
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
; actions to carry out per-frame:
; read input and update state of the cursor
; carry out user actions (pushing tiles etc)
; updating positions of sliders and falling tiles etc
; checking for matches, checking for completion, updating score counts etc.
;
;
; More concretely, we split this into three parts
; 1. update game state
; 2. update screen
; 3. sound effects and music
;
; Between 1. and 2. we wait for the end-of-frame signal, so that drawing also starts at the top of the screen
;
; Tasks for 1. update game state:
; read controller input
; modify cursor if necessary, based on input (e.g. letting go of FIRE)
; move tile, if necessary, based on input (e.g. FIRE+LEFT or FIRE+RIGHT) and note dirty region
; update tile status if it is now on slider (or due to be on slider)
; update tile status if it is in freefall including decrement fall position/counter and note dirty region
; (and work out if any new tiles should be in freefall and update them too)
; process sliders and tiles on sliders and note dirty region
; determine matches for stationary tiles (including tiles on sliders)
; update render frames for existing match destruction animation
; update render frames for existing bonus animation
; check if match destruction animation/timer has completed and update remaining blocks count (and set flag)
; and check if remaining blocks == zero and set flag for winning
; update clock time
; determine if clock time < 30 seconds (and set flag to change colour and play new tune)
; determine if clock time <= 0 seconds (and set flag for game over / retry loop)
; sort dirty regions that need redrawing (tiles / background) by y coordinates
; ** wait for frame (e.g. wait for bottom border via HPEN)
;
; Tasks for 2. updates screen
; draw clock
; erase dirty regions
; draw tiles and slider (which will clear dirty flag)
; draw overlays (cursor, bonus, etc)
;
; Tasks for 3. sound effects and music:
; play music
; play sound effects
;
game_loop:
IF DEBUG==1
IF DEBUG_KEYMATRIX==1
call print_debug_keymatrix
ENDIF
IF DEBUG_DIRTY_FLASH
call update_dirty_flasher
ENDIF
ENDIF
IF DEBUG_VSYNC
ld a, 2
out (BORDER), a
ENDIF
call get_input
call check_retry_request ; blocking (effectively) if Retry has been requested; will set level_clear_process on exit
ld a, (level_clear_process)
; 4 = failed (retry selected) , 5 = failed (no more retries, game over)
cp 4
ret z
cp 5
ret z
if DEBUG==1
if DEBUG_FREEZEFRAME_ENABLED
; if key is pressed, halt
; space advances a single frame
ld a, (freeze)
and a
jr z, @+no_freeze
; wait for key up or space
@check_freeze_keyup:
; if space, advance single frame (while halt key is still held
; so we'll still wait here on next frame)
call get_input
ld a, (controller_flags_fire)
and a
jr nz, @+freeze_advance_one_frame
; otherwise wait for halt key up
ld a, (freeze)
and a
jr nz, @-check_freeze_keyup
jp @+no_freeze
@freeze_advance_one_frame:
; space was pressed, so now we need to wait for space keyup
; (otherwise it will just run like normal while space is held)
call get_input
ld a, (controller_flags_fire)
and a
jr nz, @-freeze_advance_one_frame
@no_freeze:
ENDIF
ENDIF
call update_game_state
IF DEBUG_VSYNC
ld a, 1
out (BORDER), a
ENDIF
call wait_frame
call draw_everything
ld a, (level_clear_process)
; 0 = level not clear; 1 = doing the "Clear!" animation; 2 = done (jump to next level); 3 = failed (time over)
; if 0 or 1, still run the game loop (which includes the rendering etc for animations)
cp 2
ret z
cp 3
call z, time_over
IF DEBUG_VSYNC
ld a, 3
out (BORDER), a
ENDIF
call music_tick
IF DEBUG_VSYNC
ld a, 5
out (BORDER), a
ENDIF
call sfx_tick
jp game_loop
update_game_state:
IF DEBUG_VSYNC
ld a, 1
out (BORDER), a
ENDIF
call check_clear
ld a, (level_clear_process)
; 0 = level not clear; 1 = doing the "Clear!" animation; 2 = done (jump to next level); 3 = time over (failed)
cp 2
ret z
cp 1
ret z
cp 3
ret z
; very little to do if the level is clear - just play out animations
; if a match destruction sequence has just finished (so last frame was rendered
; at the end of the PRIOR iteration of game loop draw) then finish up the state changes
; - in particular, things like adding on the final amount of bonus score to the player score
; this will call decrement on remaining blocks upon completion, and trigger a redraw of remaining tiles
call check_final_steps_after_destruct
IF DEBUG_VSYNC
ld a, 33
out (BORDER), a
ENDIF
call update_clock
IF DEBUG_VSYNC
ld a, 38
out (BORDER), a
ENDIF
; check for time_over condition:
ld a, (level_clear_process)
; 3 = failed (time over)
cp 3
ret z
IF DEBUG_VSYNC
ld a, 7
out (BORDER), a
ENDIF
call slider_update
call sanity_check_slider
call check_falling_tiles_queue
call sanity_check_slider
; moves the tiles that were already falling.
; note that update_cursor_position can cause a tile to push into 'midair',
; but it won't be tagged as falling yet, so that it gets one frame
; 'suspended in mid air' (per original game). the 'is it falling'
; logic will catch up with it for the next frame - that's what
; check_falling_tiles_queue does
call update_falling_tiles
call sanity_check_slider
; update cursor position - must do this before redrawing dirty tiles
; (since moving the cursor can make tiles dirty)
IF DEBUG_VSYNC
ld a, 3
out (BORDER), a
ENDIF
call update_cursor_position
call sanity_check_slider
; we kindof need to check this again after update_cursor_position
; but that makes me think I'm doing something wrong
call check_falling_tiles_queue
call sanity_check_slider
IF DEBUG_VSYNC
ld a, 35
out (BORDER), a
ENDIF
call check_match
call sanity_check_slider
call check_controller_unlock
call update_glint
ret
draw_everything:
; ======================================
; EVERYTHING BELOW THIS LINE SHOULD OPERATE ON
; EXISTING DATA AND NOT MODIFY DATA/STATE
; OTHER THAN DRAWING UPDATES TO SCREEN (AND RELATED
; 'DIRTY' TRACKING STATE)
; ======================================
call transform_dirty_to_draw_list
IF DEBUG_VSYNC
ld a, 38
out (BORDER), a
ENDIF
call draw_clock
IF DEBUG_VSYNC
ld a, 32
out (BORDER), a
ENDIF
call erase_old_cursor
IF DEBUG_VSYNC
ld a, 39
out (BORDER), a
ENDIF
; static tiles that have been drawn over in previous frame and need to be redrawn cleanly now
; also, falling tiles, which have already been added to the draw_list (via update_falling_tiles)
call render_draw_list
IF DEBUG_VSYNC
ld a, 1
out (BORDER), a
ENDIF
call draw_sliders ; the sliding block and the stack of blocks on top of it!
IF DEBUG_VSYNC
ld a, 2
out (BORDER), a
ENDIF
call render_destruct
IF DEBUG_VSYNC
ld a, 7
out (BORDER), a
ENDIF
call draw_overlays
IF DEBUG_VSYNC
ld a, 5
out (BORDER), a
ENDIF
call render_score
IF DEBUG_VSYNC
ld a, 6
out (BORDER), a
ENDIF
call render_remaining_tiles ; does nothing if nothing to redraw ; TODO OPTIMIZE conditional call
ret
; We might need this for performance, so that we render tiles from top-to-bottom,
; staying ahead of the raster at all times. (or staying behind the raster - whichever
; works out better, and we'll probably know once we have ticking clock graphics and
; music/sfx routines in place).
; but also it might be fine, and the cost of sorting
; might always outweight the benefit, so we might not need this.
sort_by_y:
ret
draw_overlays:
; stuff like the bonus score that floats over the tiles
; also the cursor
; should probably make some of this macro and inline into game loop
call render_glint
call draw_cursor
call render_bonus
ret