-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpong.asm
313 lines (263 loc) · 9.52 KB
/
pong.asm
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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; TODO:
;;; -- change assembler to NESASM -- DONE
;;; -- jump table for gamestates -- DONE
;;; -- separate parts to files -- DONE
;;; -- separate game engine and NMI -- DONE
;;; -- use same code for P1 and P2, with indexing
;;; -- re-write joypad checking, delete pause and select counters -- DONE
;;; -- use graphics buffer, use this format to store "map" data (RLE) -- DONE
;;; -- add sound engine and effects
;;; -- let player choose paddle color -- DONE
;;; -- animate the ball
;;; -- add a kinda AI for CPU opponent
;;; -- properly handle ppu $2000 and $2001 changes
;;; -- find out ways to make gameplay exciting and implement them
;;; --- items to pick up:
;;; ---- longer paddle
;;; ---- shorter paddle
;;; ---- shoot at opponents paddle
;;; ---- freeze opponent for a moment
;;; ---- ball multiplier
;;; ---- ball goes faster towards opponent
;;; ---- temporary wall in the middle
;;; ---- ball random repositioning
;;;;;;;;;;;;;;;;; iNES HEADER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.inesprg 2 ;1x 16kb PRG code
.ineschr 1 ;1x 8kb CHR data
.inesmap 0 ; mapper 0 = NROM, no bank swapping
.inesmir 1 ;background mirroring (vertical mirroring = horizontal scrolling)
;;;;;;;;;;;;;;;; VARIABLES ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.rsset $0000
gamestate: .rs 1 ; We store state of the game here for game engine
jump_pointer: .rs 2 ; Pointer used to jump to correct game engine subroutine
sleeping: .rs 1 ; We wake up game engine once per frame using this variable
ballx: .rs 1 ; ball horizontal position
bally: .rs 1 ; ball vertical position
ballup: .rs 1 ; 1 = ball moving up
balldown: .rs 1 ; 1 = ball moving down
ballleft: .rs 1 ; 1 = ball moving left
ballright: .rs 1 ; 1 = ball moving right
ballspeedx: .rs 1 ; ball horizontal speed per frame
ballspeedy: .rs 1 ; ball vertical speed per frame
paddle1ytop: .rs 1 ; player 1 paddle top vertical position
paddle2ytop: .rs 1 ; player 2 paddle bottom vertical position - well...I use it as if it was paddle2ytop... no point to handle 1 paddle different than other
joypad1_current: .rs 1 ; player 1 joypad buttons read this frame
joypad2_current: .rs 1 ; player 2 joypad buttons read this frame
joypad1_old: .rs 1 ; player 1 joypad buttons read previous frame
joypad2_old: .rs 1 ; player 2 joypad buttons read previous frame
joypad1_pressed: .rs 1 ; player 1 joypad buttons pressed this frame
joypad2_pressed: .rs 1 ; player 2 joypad buttons pressed this frame
score1: .rs 1 ; player 1 score, 0-15
score2: .rs 1 ; player 2 score, 0-15
score_option_selected: .rs 1 ; the selected winning score option
current_option: .rs 1 ; on title screen: 0 if winning score is being set, 1 if colors
player1_color: .rs 1 ; the selected color for player 1 paddle
player2_color: .rs 1 ; the selected color for player 1 paddle
ball_released: .rs 1 ; 1 if the ball was released
lastscorer: .rs 1 ; 0 in beginning, 1 if P1, 2 if P2
winningscore: .rs 1 ; when a player reaches this score, game is over
selected_option: .rs 1 ; the option selected on title screen
number_of_players: .rs 1 ; 1 if 2 player mode selected, 0 if not
pointerLo: .rs 1 ; Low byte of a pointer
pointerHi: .rs 1 ; High byte of a pointer
need_draw: .rs 1 ; 1 if drawing from buffer needed
need_palette_update: .rs 1 ; pallettes shall be updated only during vblank so singal to NMI if needed
ppu_pointer: .rs 2 ; Using this to write to PPU (HI byte first)
ppu_data_address: .rs 2 ; Address of data to write to PPU
ppu_control_soft: .rs 1 ; Buffer for required PPU control ($2000)
ppu_mask_soft: .rs 1 ; Buffer for required PPU mask ($2001)
ppu_mask_prev: .rs 1 ; Keep the previous value if we turn the screen off
ppu_temp1: .rs 1 ; Temporary variable for ppu
ppu_temp2: .rs 1 ; Temporary variable for ppu
ppu_rle_byte_counter: .rs 1 ; This counter stores the num of bytes read from memory while loading RLE encoded data
random_0_to_5: .rs 1 ; This helps choosing random paddle color for computer
need_update_random: .rs 1 ; This indicates to NMI to update this number
palette_buffer: .rs 32 ; We keep our palette data in this buffer and when updates are made, NMI will tell the PPU
h_scroll: .rs 1 ; Horizontal scroll value. usually 0, except when title screen scrolls in
timer_counter: .rs 1 ; This variable is used for timing purposes
;temp1: .rs 1 ; General puprose temporary variable
;;;;;;;;;;;;;;;;
;----- first 8k bank of PRG-ROM
; We will stick our sound engine in the first one, which starts at $8000.
.bank 0
.org $8000
; .include "sound_engine.asm" ; LATER WILL INCLUDE
;;;;;;;;;;;;;;;; GAME CODE ;;;;;;;;;;;;;;;;;;;;;;;;
;----- second 8k bank of PRG-ROM
.bank 1
.org $A000
;----- third 8k bank of PRG-ROM
.bank 2
.org $C000
;;;;;;;;;;;;;;; RESET ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
RESET:
sei ; disable IRQs
cld ; disable decimal mode
ldx #$40
stx $4017 ; disable APU frame IRQ
ldx #$FF
txs ; Set up stack
inx ; now X = 0
stx $2000 ; disable NMI
stx $2001 ; disable rendering
stx $4010 ; disable DMC IRQs
jsr waitForVblank
;; Clear the RAM
.clrmem:
lda #$00
sta $0000, x
sta $0100, x
sta $0300, x
sta $0400, x
sta $0500, x
sta $0600, x
sta $0700, x
lda #$FE
sta $0200, x
inx
bne .clrmem
jsr waitForVblank
;; Load the palettes
lda $2002
lda #$3F
sta $2006
lda #$00
sta $2006
ldy #$00
.palette_loop:
lda palette_data, y
sta $2007
sta palette_buffer, y
iny
cpy #$20
bne .palette_loop
jsr clear_nametable0
jsr clear_nametable1
jsr draw_title_screen
;; No scrolling
lda #$00
sta $2005
sta $2005
;;;Set some initial ball stats
lda #INITIAL_BALL_POS_Y
sta bally
lda #INITIAL_BALL_POS_P1_X
sta ballx
;;;Set up paddles
lda #PADDLE_TOP_LIMIT
sta paddle1ytop
sta paddle2ytop
;; Initial game state:
lda #STATETITLEANIMATION
sta gamestate
lda #%10010001 ; enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
sta ppu_control_soft
sta $2000
lda #%00011110 ; enable sprites, enable background, no clipping on left side
sta ppu_mask_soft
sta $2001
Forever:
;;; Run the game engine here, if we are woken up!!
;;; We will use variable "sleeping", and NMI will set it to 0, so the game engine will run once per frame
inc sleeping
.loop:
lda sleeping
bne .loop
;; The code below this line will execute when NMI wakes us up
jsr safe_read_joypads
jsr game_engine_run
jmp Forever ;; Back to sleep
NMI:
;; Store the registers in the stack (game engine might need the data stored there)
pha
txa
pha
tya
pha
;; Update sprites
lda #$00
sta $2003
lda #$02
sta $4014
;; Update palettes if needed
lda need_palette_update
beq .draw_bg
lda $2002
lda #$3F
sta $2006
lda #$00
sta $2006
tay
sta need_palette_update
.palette_loop:
lda palette_buffer, y
sta $2007
iny
cpy #$20
bne .palette_loop
;; Resetting PPU address.
;; Dunno why but without it the game dies after palette update
;; e.g. when clicking start fort the first time on options screen
;; in 1 player mode
lda $2002
lda #$20
sta $2006
lda #$00
sta $2006
.draw_bg:
;; Draw background if needed
lda need_draw
beq .drawing_done
jsr draw_from_buffer
lda #$00
sta need_draw
.drawing_done:
;; No scrolling
;; -- Need to set scroll after EVERY write to $2006 (PPUADDR) becuase
;; -- they share the same internal register -> write to $2006 overwrites $2005 (PPUSCROLL)
lda h_scroll
sta $2005
lda #$00
sta $2005
.wake_up_game_engine:
;; Wake up the the game engine
lda #$00
sta sleeping
;; Restore the registers
pla
tay
pla
tax
pla
rti
irq:
rti
;;;;;;;;;;;;;;;; WAIT FOR VBLANK ;;;;;;;;;;;;;;;;;;;;;;;;;;;
waitForVblank:
lda $2002
bpl waitForVblank ;;; BIT 7 os $2002 is 1 when we are in a vblank, 0 if not
rts
;;;;;;;;;;;;;;;;;;;;;; INCLUDE SUBROUTINES ;;;;;;;;;;;;;;;;;;;;;;;
.include "ppu_subroutines.asm"
.include "drawing_subroutines.asm"
.include "joypad_subroutines.asm"
.include "game_engine.asm"
.include "random_numbers.asm"
;----- fourth 8k bank of PRG-ROM
.bank 3
.org $E000
;;;;;;;;;;;;;;;;;;;;;; INCLUDE DATA ;;;;;;;;;;;;;;;;;;;;;;;
.include "graphicData.i"
.include "game_constants.i"
;;;;;;;;;;;;;;;;;;;;;; VECTORS ;;;;;;;;;;;;;;;;;;;
.org $FFFA ;first of the three vectors starts here
.dw NMI ;when an NMI happens (once per frame if enabled) the
;processor will jump to the label NMI:
.dw RESET ;when the processor first turns on or is reset, it will jump
;to the label RESET:
.dw irq ;external interrupt IRQ is not used in this tutorial
;;;;;;;;;;;;;;;;;; CHR ROM ;;;;;;;;;;;;;;;;;;;;;;;
.bank 4
.org $0000
.incbin "pong.chr"