aboutsummaryrefslogtreecommitdiff
path: root/rand/rand.s
blob: abf944f91651fc6b7fd39e3a9b532cfafae86b11 (plain)
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