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
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
|
;;;
;; Showcase different strategies for Random Number Generation (RNG).
;;
;; Refer to the README.md file for documentation. Here I have left comments
;; whenever there's something "new" if you are coming from the `basics/`
;; directory.
.segment "HEADER"
.byte 'N', 'E', 'S', $1A
.byte $02, $01
.byte $00, $00
.segment "VECTORS"
.addr nmi, reset, irq
.segment "CHARS"
.incbin "../assets/alphanum.chr"
.segment "CODE"
.include "../shared/ppu.s"
.include "../shared/clear.s"
.include "../shared/joypad.s"
;; Different algorithms.
.include "linear.s"
.include "precalc.s"
;; Number of algorithms for this game.
ALGORITHM_SIZE = $02
;; See Vars::zp_button_timer.
KEY_TIMER = 15
;; Variables used by this game.
.scope Vars
;; 0: "Press start" state; 1: "number generation" state.
zp_state = $30
;; Timer for key presses. A button press will only be considered if the
;; button timer is zero. Whenever that's the case, the code will reset the
;; timer to this new value, and decrement it whenever a new press is found.
;; Whenever we reach back to zero, then the button press will be considered
;; again. All of this is because whenever the player presses a button, it
;; actually presses it for more than one frame, and things can go crazy from
;; this fact.
zp_button_timer = $31
;; Algorithm that has been selected.
zp_algorithm = $32
;; The random seed for this game. As described in the README.md file, this
;; is actually a frame counter for the "Press start" state, and whenever the
;; player hits "Start", it will be paused. That is, our random seed is
;; simply the number of frames that the player took to press "Start" at the
;; beginning.
;;
;; NOTE: it's not going to be initialized to get a more random feeling on
;; real hardware from unknown RAM state.
zp_seed = $33
;; The number to be displayed.
zp_number = $34
.endscope
;; Main function, this takes care of reading input, changing the state, and
;; calling the relevant algorithm to get new numbers.
.proc main
;; Initialize all variables (except Vars::zp_seed as explained above).
lda #0
sta Vars::zp_state
sta Vars::zp_button_timer
sta Vars::zp_algorithm
sta Vars::zp_number
;; Clear both screens. Yes, over the top, but it gets the job done.
CLEAR_SCREENS $20, $28
;; Initialize palettes and show the "Press start" message.
jsr init_palettes
jsr show_init_screen
cli
lda #%10001000
sta $2000 ; PPUCTRL
lda #%00011110
sta $2001 ; PPUMASK
@main_game_loop:
;; Should we actually read the joypad? This is handled via the button timer
;; as explained above.
lda Vars::zp_button_timer
beq @check_joypad
dec Vars::zp_button_timer
jmp @end
@check_joypad:
;; Yes! Then read the joypad.
READ_JOYPAD1
;; What's the current game state?
lda Vars::zp_state
bne @check_change_algorithm
;; "Press start" state. If the player is not pressing "Start", ignore
;; everything and go to the end.
lda Joypad::zp_buttons1
and #Joypad::BUTTON_START
beq @end
;; Reset the button timer.
lda #KEY_TIMER
sta Vars::zp_button_timer
;; The random seed has a proper value and we can use that as a first random
;; number.
lda Vars::zp_seed
sta Vars::zp_number
;; The 'linear' algorithm actually disregards any parameters and needs a
;; 16-bit register. Let's initialize this register with the current seed.
sta Linear::zp_register_lo
sta Linear::zp_register_hi
;; Move into the next state.
inc Vars::zp_state
jmp @end
@check_change_algorithm:
;; We are in a running state. Check if the player is asking to change the
;; algorithm.
lda Joypad::zp_buttons1
and #Joypad::BUTTON_SELECT
beq @check_a_button
;; Reset the button timer.
lda #KEY_TIMER
sta Vars::zp_button_timer
;; The player asked to change the algorithm. Do it now and go generate a new
;; number with that.
ldx Vars::zp_algorithm
inx
cpx #ALGORITHM_SIZE
bne @store_algorithm
ldx #0
@store_algorithm:
stx Vars::zp_algorithm
jmp @next_number
@check_a_button:
;; Is the player asking for a new number? If not go to the end.
lda Joypad::zp_buttons1
and #Joypad::BUTTON_A
beq @end
;; Reset the button timer.
lda #KEY_TIMER
sta Vars::zp_button_timer
@next_number:
;; Setup parameters depending on the algorithm and actually call it.
ldx Vars::zp_algorithm
bne @precalc
jsr linear_feedback_shift_register
jmp @store_number
@precalc:
lda Vars::zp_number
jsr precalc
@store_number:
sta Vars::zp_number
@end:
;; And wait for the render to happen as it's done in any other example.
lda #%10000000
ora $20
sta $20
@wait_for_render:
bit $20
bmi @wait_for_render
jmp @main_game_loop
.endproc
;; NMI code is pretty standard. I have added comments for the code which is
;; specific to this game.
.proc nmi
bit $20
bpl @next
pha
txa
pha
tya
pha
lda #$00
sta $2003 ; OAMADDR
lda #$02
sta $4014 ; OAMDMA
;; What's the current game state?
lda Vars::zp_state
beq @seed_inc
;; Running state. Print the current algorithm and number.
jsr print_algorithm
jsr print_value
;; The running state is displayed on the other nametable. Update the PPU
;; control register for this (it's of course stupid to update it every time,
;; but I didn't feel like doing the proper thing of shadowing the PPU
;; control register and update only on changes, etc.).
lda #%10001010
sta $2000 ; PPUCTRL
bne @after_seed
@seed_inc:
;; "Press start" state: just increase the frame counter which is used as a
;; seed.
inc Vars::zp_seed
@after_seed:
bit $2002 ; PPUSTATUS
lda #$00
sta $2005 ; PPUSCROLL
sta $2005 ; PPUSCROLL
lda #%01111111
and $20
sta $20
pla
tay
pla
tax
pla
@next:
rti
.endproc
;; Show the "Alg: <algorithm>" message on screen.
.proc print_algorithm
;; "ALG: "
WRITE_PPU_DATA $298B, $1A
WRITE_PPU_DATA $298C, $25
WRITE_PPU_DATA $298D, $20
WRITE_PPU_DATA $298E, $34
WRITE_PPU_DATA $298F, $00
lda Vars::zp_algorithm
beq @linear
;; "PRECALC"
WRITE_PPU_DATA $2990, $29
WRITE_PPU_DATA $2991, $2B
WRITE_PPU_DATA $2992, $1E
WRITE_PPU_DATA $2993, $1C
WRITE_PPU_DATA $2994, $1A
WRITE_PPU_DATA $2995, $25
WRITE_PPU_DATA $2996, $1C
rts
;; "LINEAR "
@linear:
WRITE_PPU_DATA $2990, $25
WRITE_PPU_DATA $2991, $22
WRITE_PPU_DATA $2992, $27
WRITE_PPU_DATA $2993, $1E
WRITE_PPU_DATA $2994, $1A
WRITE_PPU_DATA $2995, $2B
WRITE_PPU_DATA $2996, $00
rts
.endproc
;; Show the "Val: $<number>" message on screen.
.proc print_value
;; "VAL: $"
WRITE_PPU_DATA $29AB, $2F
WRITE_PPU_DATA $29AC, $1A
WRITE_PPU_DATA $29AD, $25
WRITE_PPU_DATA $29AE, $34
WRITE_PPU_DATA $29AF, $00
WRITE_PPU_DATA $29B0, $35
;; Set the high byte on the 'y' register, and the low byte on the 'x'
;; register.
lda #$F0
and Vars::zp_number
lsr
lsr
lsr
lsr
clc
adc #$10
tay
lda #$0F
and Vars::zp_number
clc
adc #$10
tax
;; Display the actual number.
bit $2002
lda #$29
sta $2006
lda #$B1
sta $2006
sty $2007
stx $2007
rts
.endproc
;; Show the "Press start" message.
.proc show_init_screen
;; "PRESS"
WRITE_PPU_DATA $21AB, $29
WRITE_PPU_DATA $21AC, $2B
WRITE_PPU_DATA $21AD, $1E
WRITE_PPU_DATA $21AE, $2C
WRITE_PPU_DATA $21AF, $2C
;; "START"
WRITE_PPU_DATA $21B1, $2C
WRITE_PPU_DATA $21B2, $2D
WRITE_PPU_DATA $21B3, $1A
WRITE_PPU_DATA $21B4, $2B
WRITE_PPU_DATA $21B5, $2D
rts
.endproc
;; Initialize palettes. A bit over the top since only two colors are used.
.proc init_palettes
lda #$3F
sta $2006 ; PPUADDR
lda #$00
sta $2006 ; PPUADDR
ldx #0
@load_palettes_loop:
lda palettes, x
sta $2007 ; PPUDATA
inx
cpx #$20
bne @load_palettes_loop
rts
palettes:
DEFAULT_COLOR = $0F
;; Background
.byte DEFAULT_COLOR, $20, $FF, $FF
.byte DEFAULT_COLOR, $20, $FF, $FF
.byte DEFAULT_COLOR, $20, $FF, $FF
.byte DEFAULT_COLOR, $20, $FF, $FF
;; Foreground
.byte DEFAULT_COLOR, $20, $FF, $FF
.byte DEFAULT_COLOR, $20, $FF, $FF
.byte DEFAULT_COLOR, $20, $FF, $FF
.byte DEFAULT_COLOR, $20, $FF, $FF
rts
.endproc
;;;
;; NOTE: down below just boilerplate. Nothing special from the `basics/`
;; examples.
.proc reset
sei
cld
ldx #$40
stx $4017 ; APU Frame Counter
ldx #$FF
txs
inx
stx $2000 ; PPUCTRL
stx $2001 ; PPUMASK
stx $4010 ; APU DMC
bit $2002 ; PPUSTATUS
@vblankwait1:
bit $2002 ; PPUSTATUS
bpl @vblankwait1
ldx #0
lda #0
@ram_reset_loop:
sta $000, x
sta $100, x
sta $300, x
sta $400, x
sta $500, x
sta $600, x
sta $700, x
inx
bne @ram_reset_loop ; if x overflows back to #00, then we are done.
lda #$EF
@sprite_reset_loop:
sta $200, x
inx
bne @sprite_reset_loop
lda #$00
sta $2003 ; OAMADDR
lda #$02
sta $4014 ; OAMDMA
@vblankwait2:
bit $2002 ; PPUSTATUS
bpl @vblankwait2
lda #$3F
sta $2006 ; PPUADDR
lda #$00
sta $2006 ; PPUADDR
lda #$0F
ldx #$20
@palettes_reset_loop:
sta $2007 ; PPUDATA
dex
bne @palettes_reset_loop
jmp main
.endproc
.proc irq
rti
.endproc
|