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
|
;; Clear the carry and add ADDR to the indexed digit on 'Score::m_players'. If
;; the result is larger or equal than 10, then 0 is stored and the carry flag is
;; set. Otherwise the carry flag is cleared.
.macro BCD_ADD ADDR
lda Score::m_players, x
clc
adc ADDR
cmp #10
bcc :+
sec
lda #0
:
sta Score::m_players, x
.endmacro
;; Add ADDR to the indexed digit on 'Score::m_players' _with_ carry. If the
;; result is larger or equal than 10, then 0 is stored and the carry flag is
;; set. Otherwise the carry flag is cleared.
.macro BCD_ADDC ADDR
lda Score::m_players, x
adc ADDR
clc
cmp #10
bcc :+
sec
lda #0
:
sta Score::m_players, x
.endmacro
;; Only add the carry to the indexed digit on 'Score::m_players' _with_
;; carry. If the result is larger or equal than 10, then 0 is stored and the
;; carry flag is set. Otherwise the carry flag is cleared.
.macro BCD_JUST_CARRY
lda Score::m_players, x
adc #0
clc
cmp #10
bne :+
sec
lda #0
:
sta Score::m_players, x
.endmacro
;; A score is a 6-digit number where each digit is stored on a byte of its
;; own. This makes adding numbers and representing them into the screen dead
;; easy.
;;
;; This is indeed rather wasteful, but we have a lot of RAM to spare and the
;; ease of use is quite convenient.
.scope Score
;; Scores for both players are stored in a single buffer. Even indeces
;; contain digits for the first player, and odd indeces contain digits for
;; the second player. Digits are stored in little-endian format.
;;
;; Interweaving digits this way might seem weird, but it actually makes
;; indexing things super easy: the 'active' bit from
;; 'Globals::zp_multiplayer' can be used to index the first item, and then
;; it's a matter of advancing the 'x' register twice in order to get the
;; next digit.
PLAYERS_BUFF_SIZE = $0C
m_players = $300 ; asan:reserve PLAYERS_BUFF_SIZE
;; The high score for this session.
m_hi = $30C ; asan:reserve $06
;; Initialize the scores for both players.
.proc init_players_scores
lda #0
ldx #0
@loop:
sta m_players, x
inx
cpx #PLAYERS_BUFF_SIZE
bne @loop
rts
.endproc
;; Add to the current player's score the number stored in
;; 'Globals::zp_tmp{0,1,2}', where 'Globals::zp_tmp0' has the least
;; significant number.
.proc add_to_player
;; See 'Score::m_players' on why this is the way to select the current
;; player's score.
lda Globals::zp_multiplayer
and #$01
tax
;;;
;; The first three digits are the product of adding the contents of
;; 'Globals::zp_tmp{0,1,2}'.
BCD_ADD Globals::zp_tmp0
inx
inx
BCD_ADDC Globals::zp_tmp1
inx
inx
BCD_ADDC Globals::zp_tmp2
inx
inx
;;;
;; The rest are just a matter of adding the carry.
BCD_JUST_CARRY
inx
inx
BCD_JUST_CARRY
inx
inx
BCD_JUST_CARRY
;;;
;; And set the 'score' flag, signaling a need of updating the score.
lda Globals::zp_extra_flags
ora #$80
sta Globals::zp_extra_flags
rts
.endproc
;; Save the score of either of the two players if any of them are higher
;; than the high score we have right now.
.proc save_hi_score
;;;
;; Check player 1.
ldx #(PLAYERS_BUFF_SIZE - 2)
ldy #5
@player1_loop:
lda Score::m_hi, y
cmp Score::m_players, x
bcc @save_player1
dex
dex
dey
cpy #$FF
bne @player1_loop
;; No dice! If we are in multiplayer mode, then check player 2,
;; otherwise just quit.
bit Globals::zp_multiplayer
bpl @end
;;;
;; Check player 2.
ldx #(PLAYERS_BUFF_SIZE - 1)
ldy #5
@player2_loop:
lda Score::m_hi, y
cmp Score::m_players, x
bcc @save_player2
dex
dex
dey
cpy #$FF
bne @player2_loop
;; Not player 2 either. Just quit.
rts
;;;
;; One of the players actually achieved a high score. Let's save it.
@save_player1:
ldx #0
beq @save
@save_player2:
ldx #1
@save:
ldy #0
@save_loop:
lda Score::m_players, x
sta Score::m_hi, y
inx
inx
iny
cpy #6
bne @save_loop
;; And set the 'high' bit so this change is reflected on screen.
lda Globals::zp_extra_flags
ora #$40
sta Globals::zp_extra_flags
@end:
rts
.endproc
;; Update the score for both players. This might be a bit too much on single
;; player mode but we have to show both scores on the title screen
;; anyways. Hence, since we have all the time in the world anyways, we
;; update both and avoid branching and stuff.
.proc nmi_update_scores
;; The 'y' register will contain the right high byte for the PPU
;; address. This is needed because scores are to be displayed on both
;; the title and game screens.
lda PPU::zp_control
and #$02
tax
ldy hi_ppu_address, x
;; Now we just put the numbers. The 'x' index has to go "backwards", and
;; taking into account that both players live on the same buffer. The
;; tile ID is basically the integer value + $10, which is the position
;; of the '0' character on our tile set.
bit PPU::m_status
sty PPU::m_address
lda #$62
sta PPU::m_address
clc
ldx #(PLAYERS_BUFF_SIZE - 2)
@player1_loop:
lda Score::m_players, x
adc #$10
sta PPU::m_data
dex
dex
cpx #$FE
bne @player1_loop
;; And the same for the second player.
bit PPU::m_status
sty PPU::m_address
lda #$78
sta PPU::m_address
clc
ldx #(PLAYERS_BUFF_SIZE - 1)
@player2_loop:
lda Score::m_players, x
adc #$10
sta PPU::m_data
dex
dex
cpx #$FF
bne @player2_loop
;; Then do the high score if requested.
lda Globals::zp_extra_flags
and #$40
beq @unset
bit PPU::m_status
sty PPU::m_address
lda #$6D
sta PPU::m_address
clc
ldx #5
@hi_loop:
lda Score::m_hi, x
adc #$10
sta PPU::m_data
dex
cpx #$FF
bne @hi_loop
@unset:
;; Disable the 'score' flag.
lda Globals::zp_extra_flags
and #$3F
sta Globals::zp_extra_flags
rts
hi_ppu_address:
.byte $20, $00, $28, $00
.endproc
.endscope
;; Add the score for a dead enemy to the current player's score.
.macro ADD_ENEMY_SCORE
lda #5
sta Globals::zp_tmp0
lda #2
sta Globals::zp_tmp1
lda #0
sta Globals::zp_tmp2
jsr Score::add_to_player
.endmacro
;; Add the score for grabbing a shuttle part / fuel tank to the current player's
;; score.
.macro ADD_PART_FUEL_SCORE
lda #0
sta Globals::zp_tmp0
lda #0
sta Globals::zp_tmp1
lda #1
sta Globals::zp_tmp2
jsr Score::add_to_player
.endmacro
;; Add the score for grabbing an item to the current player's score.
.macro ADD_ITEM_SCORE
lda #0
sta Globals::zp_tmp0
lda #5
sta Globals::zp_tmp1
lda #2
sta Globals::zp_tmp2
jsr Score::add_to_player
.endmacro
|