-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.s
384 lines (322 loc) · 7.21 KB
/
main.s
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
.include "global.inc"
.segment "ZEROPAGE"
; variables
turn: .res 1
gamestate: .res 1
controller1: .res 1
controller1release: .res 1
nmis: .res 1 ; how many nmis have passed
need_draw: .res 1 ; do we need to draw draw buffer?
need_scroll: .res 1
a1: .res 1 ; temp var
a2: .res 1 ; temp var
a3: .res 1 ; temp var
.segment "HEADER"
; iNES header
; see http://wiki.nesdev.com/w/index.php/INES
.byte $4e, $45, $53, $1a ; "NES" followed by MS-DOS EOF
.byte $01 ; size of PRG ROM in 16 KiB units
.byte $01 ; size of CHR ROM in 8 KiB units
.byte $00 ; horizontal mirroring, mapper 000 (NROM)
.byte $00 ; mapper 000 (NROM)
.byte $00 ; size of PRG RAM in 8 KiB units
.byte $00 ; NTSC
.byte $00 ; unused
.res 5, $00 ; zero-filled
.segment "STARTUP"
start:
; wait for PPU, see: https://wiki.nesdev.com/w/index.php/PPU_power_up_state
bit $2002 ; clear the VBL flag if it was set at reset time
waitforppu:
bit $2002
bpl waitforppu ; at this point, about 27384 cycles have passed
init:
jsr init_memory
jmp clear_oam
init_memory:
; initialize vars to zero
lda #GameState::start
sta gamestate
lda #0
sta controller1
sta controller1release
sta dlevel
sta need_draw
sta turn
inc turn ; set turn to 1
jsr initialize_player
lda Messages::none
jsr push_msg
lda Messages::none
jsr push_msg
lda Messages::none
jsr push_msg
jsr init_buffer
jsr init_status
rts
clear_oam:
lda #$FF
ldx #$00
oamloop:
; reset all sprites via loop
sta $0200, x
inx
cpx #$00
bne oamloop
init_palettes:
; initialize background palettes
lda #$3F
sta $2006
lda #$00
sta $2006 ; these two instructions tell the PPU to read/write data to $3F00 (BG color)
ldx #$00
paletteloop:
lda PALETTE, x
sta $2007 ; this tells PPU to write color palette to the previous address in PPU
inx
cpx #32
bne paletteloop
ldx #$FF ; reset stack to $FF
txs
init_ppu:
; wait for ppu to be ready one last time
bit $2002
bpl init_ppu
; at this point, about 57165 cycles have passed
; initialize ppu vblank NMI
lda #%10000000
sta $2000
; initialize game!
jmp main
nmi:
pha
txa
pha
tya
pha
lda a1
pha
lda need_draw
beq continue_nmi
render_draw_buffer:
; render draw queue
jsr render_buffer
; done drawing
lda #$00
sta need_draw
continue_nmi:
lda #$00
sta $2003
; draw OAM data via DMA
lda #$02
sta $4014
lda has_status
bne continue_status
jmp finish_nmi
continue_status:
; update base ppu addr for statusbar
lda base_nt
ora #%10000010
sta $2000
; update scroll for statusbar
bit $2002
lda #0
sta $2005
lda #240 - 8*4
sta $2005
; detect sprite-zero hit
sprite_zero_clear_wait:
bit $2002
bvs sprite_zero_clear_wait
sprite_zero_wait:
bit $2002
bvc sprite_zero_wait
; split scroll
;Write nametable bits to t.
lda base_nt
asl
asl
sta $2006
;Write y bits to t.
lda scroll + 1
sta $2005
;The last write needs to occur during horizontal blanking
;to avoid visual glitches.
;HBlank is very short, so calculate the value to write now, before HBlank.
and #$F8
asl
asl
sta a1
lda scroll
;Write the X bits to t and x.
sta $2005
;Finish calculating the fourth write.
lsr
lsr
lsr
ora a1
;Wait for HBlank
ldx #13 ;How long to wait. Play around with this value
;until you don't have a visual glitch.
loop:
dex
bne loop
;Write to t and copy t to v.
sta $2006
finish_nmi:
pla
sta a1
pla
tay
pla
tax
pla
inc nmis
rti
irq:
rti
.segment "CODE"
main:
jsr clear_messages
; check if we're still in batch buffer mode
lda buffer_index
beq check_scroll
; we're still in batch buffer mode, update tiles buffer
jmp update
check_scroll:
; check if we need to scroll screen
lda need_scroll
beq check_state
; scroll twice after buffer update
jsr update_scroll
jsr update_scroll
; turn off scrolling
lda #0
sta need_scroll
; done, update
jmp update
check_state:
jsr readcontroller
lda gamestate
cmp #GameState::playing
beq playgame
; wait for start press if we're not playing
wait_for_start:
lda controller1release
and #%00010000 ; start
beq done
jmp init_game
playgame:
; update turn when input is made
lda controller1release
beq done
inc turn
jsr handle_input
; check input result & update game's state
cmp #InputResult::new_dlevel
beq regenerate
cmp #InputResult::escape
beq escape_dungeon
cmp #InputResult::win
beq win_dungeon
cmp #InputResult::move
beq player_moved
ai:
jsr mob_ai
; ensure player alive, otherwise display Game Over screen
lda mobs + Mob::hp
bne continue_ai
jmp game_over
continue_ai:
jsr mob_spawner
jsr player_regen
update_buffer:
; skip buffering status if buffer full (scrolling)
lda need_scroll
bne update
jsr buffer_status
; todo figure out better way to buffer messages, probably want to
; todo buffer them using draw loop, clearing on next action
;jsr buffer_messages
update:
; notify nmi to draw the buffer
lda #1
sta need_draw
; update sprite OAM data, only if not scrolling
lda need_scroll
bne done
jsr update_sprites
done:
lda nmis
wait_nmi:
cmp nmis
beq wait_nmi
; endless game loop
jmp main
; state updates
init_game:
jsr init_memory
lda #GameState::playing
sta gamestate
; initialize *both* seed values
lda nmis
sta seed
sta seed + 1
; re-generate next dungeon level
regenerate:
; temporarily turn off statusbar
lda #0
sta has_status
; initialize & flip seed bits
lda nmis
eor #$32
sta seed
; generate dungeon
jsr generate
jsr render
; trigger drawing & rendering statusbar
lda #1
sta need_draw
sta has_status
jmp done
; update state to end & render escape
escape_dungeon:
lda #GameState::end
sta gamestate
jsr render_escape
jmp update
; update state to win
win_dungeon:
lda #GameState::win
sta gamestate
jsr render_win
jmp update
; update state to game over
game_over:
lda #GameState::end
sta gamestate
jsr render_death
jmp update
; ensure buffer is updated when new tiles seen
player_moved:
jsr update_screen_offsets
jsr buffer_tiles
lda #1
sta need_scroll
jmp ai
.segment "RODATA"
PALETTE:
; tiles
.byte $0f, $09, $2d, $3a, $0f, $21, $2c, $3a
.byte $0f, $2d, $08, $18, $0f, $21, $2c, $3a
; sprites
.byte $0f, $08, $2d, $30, $0f, $1d, $1d, $0a
.byte $0f, $1d, $1d, $2d, $0f, $1d, $1d, $06
.segment "VECTORS"
; set interrupt vectors to point to handlers
.word nmi ;$fffa NMI
.word start ;$fffc Reset
.word irq ;$fffe IRQ
.segment "CHARS"
; include CHR ROM data
.incbin "tiles.chr"