-
Notifications
You must be signed in to change notification settings - Fork 21
/
Copy pathpong.mfk
630 lines (533 loc) · 18.3 KB
/
pong.mfk
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
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
// Taken from Nerdy Nights Week 7's pong example game
// (http://nintendoage.com/forum/messageview.cfm?catid=22&threadid=8747)
//
// Original example made by bunnyboy of nintendoage.com
// Millfork adaptation by Garydos (https://github.com/Garydos)
//
// compile with -t nes_small
import random
import nes_joy
// *STRUCT DEFINTIONS*
struct Ball {
byte x, // ball horizontal position
byte y, // ball vertical position
byte up, // 1 = ball moving up
byte down, // 1 = ball moving down
byte left, // 1 = ball moving left
byte right, // 1 = ball moving right
byte speedx, // ball horizontal speed per frame
byte speedy // ball vertical speed per frame
}
struct Sprite { //NES hardware sprite layout
byte y, // Y Coordinate - 1
byte tile, // tile index #
byte attrs, // attributes
byte x // X Coordinate
}
struct Paddle_Sprs {
Sprite paddletop,
Sprite paddlebody1,
Sprite paddlebody2,
Sprite paddlebottom
}
// *VARIABLES*
byte paddle1ytop // player 1 paddle top vertical position
byte paddle2ytop // player 2 paddle bottom vertical position
byte score1 // player 1 score, 0-15
byte score2 // player 2 score, 0-15
Ball ball // the ball
array oam_buffer [256] @$200 // sprite buffer
Sprite ball_spr @$200 // ball's sprite
Paddle_Sprs left_paddle_sprs @$204 // left paddle's sprites
Paddle_Sprs right_paddle_sprs @$214 // right paddle's sprites
word framecounter // counts the amount of frames that have passed
volatile Gamestate gamestate // the current Gamestate
// *CONSTANTS*
enum Gamestate {
STATETITLE, // displaying title screen
STATEPLAYING, // move paddles/ball, check for collisions
STATEGAMEOVER // displaying game over screen
}
const byte RIGHTWALL = $F4 // when ball reaches one of these, do something
const byte TOPWALL = $18
const byte BOTTOMWALL = $B0
const byte LEFTWALL = $04
const byte PADDLE1X = $08 // horizontal position for paddles, doesnt move
const byte PADDLE2X = $F0
const byte PADDLEHEIGHT = $20 // height of each paddle in pixels
// sprite tiles used by the paddles
const byte PADDLETOPBOTSPR = $00
const byte PADDLEBODYSPR = $01
// sprite attributes used by the paddles
const byte PADDLESPRATTR = $01
const byte PADDLESPRATTRHFLIP = $41
const byte PADDLESPRATTRVFLIP = $81
const byte PADDLESPRATTRHVFLIP = $C1
// sprite tiles and attributes used by the ball
const byte BALLSPRATTR = $01
const byte BALLSPR = $02
// vram locations declared as constants for readability/convenience
// *note that these do not correlate to CPU ram locations*
const word ppu_pallete_ram = $3F00
const word ppu_nametable_ram = $2000
const word ppu_nametable_0_attr_ram = $23C0
void main() {
//Set starting game state
gamestate = STATETITLE
//prepare the title screen gamestate
game_title_init()
while(true){} // all work is done in NMI
}
void nmi() {
//Push sprite information to the PPU through DMA transfer
ppu_oam_dma_write(oam_buffer.addr.hi)
//Run graphics updates + game logic specific to each gamestate
main_game_logic()
}
void irq() {
}
inline void main_game_logic() {
// use a return dispatch here
// to use different logic for each screen/gamestate
return [gamestate] {
STATETITLE @ game_title_logic
STATEPLAYING @ game_playing_logic
STATEGAMEOVER @ game_gameover_logic
}
}
void game_title_init() {
byte i
//for now, turn off the screen and nmi
ppu_ctrl = 0
ppu_mask = 0
//initialize the sprites and palletes
init_graphics()
//write a full screen of background data
load_sky_background()
//write the title screen message
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$018B) // point the PPU to the message's start
for i,0,until,$0B {
ppu_write_data(title_msg[i])
}
//write the border
//top border
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$0020)
for i,0,until,$20 {
ppu_write_data($01)
}
//bottom border
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$0380)
for i,0,until,$20 {
ppu_write_data($01)
}
//set ppu address increment to 32 so we can draw the left and right borders
//(allows us to draw to the nametable in vertical strips rather than horizontal)
ppu_ctrl = %00000100
//left border
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram)
for i,0,until,$20 {
ppu_write_data($01)
}
//right border
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$1F)
for i,0,until,$20 {
ppu_write_data($01)
}
framecounter = 0
ppu_set_scroll(0,0)
ppu_wait_vblank() //wait for next vblank before re-enabling NMI
//so that we don't get messed up scroll registers
//re-enable the screen and nmi
ppu_ctrl = %10010000 // enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
ppu_mask = %00011110 // enable sprites, enable background, no clipping on left side
}
void game_title_logic() {
read_joy1()
if input_start != 0 {
rand_seed = framecounter //seed the random number generator with the amount of frames
//that have passed since the title screen was shown
gamestate = STATEPLAYING
game_playing_init()
return
}
framecounter += 1
}
inline asm void ppu_wait_vblank() {
vblankwait:
BIT $2002
! BPL vblankwait
? RTS
}
void game_playing_init() {
//for now, turn off the screen and nmi
ppu_ctrl = 0
ppu_mask = 0
//write a full screen of data
load_sky_background()
draw_score_text_background()
draw_boundaries_background()
load_play_attribute_table()
//initialize the game
init_game()
reset_ball()
ppu_set_scroll(0,0)
ppu_wait_vblank() //wait for next vblank before re-enabling NMI
//so that we don't get messed up scroll registers
//re-enable the screen and nmi
ppu_ctrl = %10010000 // enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
ppu_mask = %00011110 // enable sprites, enable background, no clipping on left side
}
void game_playing_logic() {
draw_score()
//update scroll last because writes to vram also
//overwrite the scroll register
ppu_set_scroll(0,0) // tell the ppu there is no background scrolling
move_ball()
move_paddles()
check_paddle_collision()
update_sprites()
if score1 >= 15 || score2 >= 15{
//Someone's reached 15 points, the game is over,
//so set the state to game over and reset the
//framecounter
gamestate = STATEGAMEOVER
//move all the sprites off screen
//in preperation for the gameover screen
ball_spr.y = $ef
left_paddle_sprs.paddletop.y = $ef
left_paddle_sprs.paddlebody1.y = $ef
left_paddle_sprs.paddlebody2.y = $ef
left_paddle_sprs.paddlebottom.y = $ef
right_paddle_sprs.paddletop.y = $ef
right_paddle_sprs.paddlebody1.y = $ef
right_paddle_sprs.paddlebody2.y = $ef
right_paddle_sprs.paddlebottom.y = $ef
game_gameover_init()
}
}
void game_gameover_init() {
//for now, turn off nmi and sprites
ppu_ctrl = 0
ppu_mask = 0
draw_score() //draw the final score
draw_game_over_screen() //draw the game over message
framecounter = 0
ppu_set_scroll(0,0) // tell the ppu there is no background scrolling
ppu_wait_vblank() //wait for next vblank before re-enabling NMI
//so that we don't get messed up scroll registers
//re-enable the screen and nmi
ppu_ctrl = %10010000 // enable NMI, sprites from Pattern Table 0, background from Pattern Table 1
ppu_mask = %00011110 // enable sprites, enable background, no clipping on left side
}
void game_gameover_logic() {
if framecounter >= 240{
//3 seconds have passed,
//reset the game
simulate_reset()
}
framecounter += 1
}
void move_ball() {
if ball.up == 1 {
ball.y -= ball.speedy
if ball.y <= TOPWALL {
//bounce, ball now moving down
ball.down = 1
ball.up = 0
}
}
else if ball.down == 1 {
ball.y += ball.speedy
if ball.y + 8 >= BOTTOMWALL {
//bounce, ball now moving up
ball.down = 0
ball.up = 1
}
}
if ball.right == 1 {
ball.x += ball.speedx
if ball.x >= RIGHTWALL {
// ball has gone past a paddle and hit the right wall
// give player1 a point and reset the ball
score1 += 1
reset_ball()
}
}
else if ball.left == 1 {
ball.x -= ball.speedx
if ball.x <= LEFTWALL {
// ball has gone past a paddle and hit the left wall
// give player2 a point and reset the ball
score2 += 1
reset_ball()
}
}
}
void move_paddles() {
// Player 1 controls
read_joy1()
if input_dy < 0 { // Up button
if paddle1ytop > TOPWALL {
paddle1ytop -= 1
}
}
else if input_dy > 0 { // Down button
if (paddle1ytop + PADDLEHEIGHT) < BOTTOMWALL {
paddle1ytop += 1
}
}
// Player 2 controls
read_joy2()
if input_dy < 0 { // Up button
if paddle2ytop > TOPWALL {
paddle2ytop -= 1
}
}
else if input_dy > 0 { // Down button
if (paddle2ytop + PADDLEHEIGHT) < BOTTOMWALL {
paddle2ytop += 1
}
}
}
void check_paddle_collision() {
//Check left paddle collision
if ball.x <= PADDLE1X+8 && ball.y >= paddle1ytop && ball.y <= paddle1ytop + PADDLEHEIGHT {
//bounce the ball back, move it right
ball.left = 0
ball.right = 1
}
//Check right paddle collision
if ball.x >= PADDLE2X-8 && ball.y >= paddle2ytop && ball.y <= paddle2ytop + PADDLEHEIGHT {
//bounce the ball back, move it left
ball.left = 1
ball.right = 0
}
}
inline void init_graphics() {
init_sprites()
load_palletes()
}
macro void load_palletes() {
byte i
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_pallete_ram) // point the PPU to palette ram
for i,0,until,$20 {
ppu_write_data(pallete[i])
}
}
inline void load_sky_background() {
word xx
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram) // point the PPU to palette ram
for xx,0,until,$0400 {
ppu_write_data($00) // $00 = sky
}
}
macro void draw_score_text_background() {
byte i
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$20) // point the PPU to score text's start
for i,0,until,$1C {
ppu_write_data(scorebackground[i])
}
}
macro void draw_boundaries_background() {
byte i
//draw top boundary
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$40) // point the PPU to the top boundary's start
for i,0,until,$20 {
ppu_write_data($81) //write the top boundary tile
}
//draw bottom boundary
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$02C0) // point the PPU to the top boundary's start
for i,0,until,$20 {
ppu_write_data($80) //write the bottom boundary tile
}
}
void draw_game_over_screen() {
byte i
//draw the static game over message
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$0107) // point the PPU to the message's start
for i,0,until,$12 {
ppu_write_data(gameover_msg[i])
}
//draw the win message
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$01AC) // point the PPU to the message's start
if score1 >= score2 {
for i,0,until,$0B {
ppu_write_data(p1_win_msg[i])
}
}
else {
for i,0,until,$0B {
ppu_write_data(p2_win_msg[i])
}
}
}
void draw_p1_win_msg() {
byte i
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$0140) // point the PPU to the message's start
for i,0,until,$20 {
ppu_write_data(p1_win_msg[i])
}
}
void draw_p2_win_msg() {
byte i
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_ram+$0140) // point the PPU to the message's start
for i,0,until,$20 {
ppu_write_data(p2_win_msg[i])
}
}
macro void load_play_attribute_table() {
byte i
read_ppu_status() // read PPU status to reset the high/low latch
ppu_set_addr(ppu_nametable_0_attr_ram) // point the PPU to nametable 0's attribute table
for i,0,until,$10 {
ppu_write_data(attribute[i])
}
}
void init_sprites() {
byte i
for i,0,to,255 {
if (i & %00000011) == 0 {
//each sprite takes up 4 bytes, and we want to edit
//the y position of each sprite (0th byte)
//so we use the %00000011 mask to write every 4th byte (every 0th sprite byte)
oam_buffer[i] = $ef // move the sprite off screen
}
else {
oam_buffer[i] = 0
}
}
}
void init_game() {
//Set some initial ball stats
ball.down = $00
ball.right = $01
ball.up = $00
ball.left = $00
ball.y = $50
ball.x = $80
ball.speedx = $02
ball.speedy = $02
//Set initial paddle states
paddle1ytop = $70
paddle2ytop = $70
}
void update_sprites() {
//Update ball sprite
ball_spr.y = ball.y
ball_spr.tile = BALLSPR
ball_spr.attrs = BALLSPRATTR
ball_spr.x = ball.x
//update paddle sprites
// update top of left paddle
left_paddle_sprs.paddletop.y = paddle1ytop
left_paddle_sprs.paddletop.tile = PADDLETOPBOTSPR
left_paddle_sprs.paddletop.attrs = PADDLESPRATTRVFLIP
left_paddle_sprs.paddletop.x = PADDLE1X
// update body of left paddle
left_paddle_sprs.paddlebody1.y = paddle1ytop + 8
left_paddle_sprs.paddlebody1.tile = PADDLEBODYSPR
left_paddle_sprs.paddlebody1.attrs = PADDLESPRATTRVFLIP
left_paddle_sprs.paddlebody1.x = PADDLE1X
left_paddle_sprs.paddlebody2.y = paddle1ytop + 16
left_paddle_sprs.paddlebody2.tile = PADDLEBODYSPR
left_paddle_sprs.paddlebody2.attrs = PADDLESPRATTRVFLIP
left_paddle_sprs.paddlebody2.x = PADDLE1X
// update bottom of left paddle
left_paddle_sprs.paddlebottom.y = paddle1ytop + 24
left_paddle_sprs.paddlebottom.tile = PADDLETOPBOTSPR
left_paddle_sprs.paddlebottom.attrs = PADDLESPRATTR
left_paddle_sprs.paddlebottom.x = PADDLE1X
// update top of right paddle
right_paddle_sprs.paddletop.y = paddle2ytop
right_paddle_sprs.paddletop.tile = PADDLETOPBOTSPR
right_paddle_sprs.paddletop.attrs = PADDLESPRATTRHVFLIP
right_paddle_sprs.paddletop.x = PADDLE2X
// update body of right paddle
right_paddle_sprs.paddlebody1.y = paddle2ytop + 8
right_paddle_sprs.paddlebody1.tile = PADDLEBODYSPR
right_paddle_sprs.paddlebody1.attrs = PADDLESPRATTRHVFLIP
right_paddle_sprs.paddlebody1.x = PADDLE2X
right_paddle_sprs.paddlebody2.y = paddle2ytop + 16
right_paddle_sprs.paddlebody2.tile = PADDLEBODYSPR
right_paddle_sprs.paddlebody2.attrs = PADDLESPRATTRHVFLIP
right_paddle_sprs.paddlebody2.x = PADDLE2X
// update bottom of right paddle
right_paddle_sprs.paddlebottom.y = paddle2ytop + 24
right_paddle_sprs.paddlebottom.tile = PADDLETOPBOTSPR
right_paddle_sprs.paddlebottom.attrs = PADDLESPRATTRHFLIP
right_paddle_sprs.paddlebottom.x = PADDLE2X
}
inline void draw_score() {
byte digit01
byte digit10
read_ppu_status() // read PPU status to reset the high/low latch
//display player1's score
digit01 = score1 %% 10 //get the ones digit
digit10 = score1 / 10 //get the tens digit
digit10 %%= 10
ppu_set_addr(ppu_nametable_ram+$29) // point the PPU to player1's score number
if digit10 > 0 {
ppu_write_data(digit10 + '0')
}
ppu_write_data(digit01 + '0')
//display player2's score
digit01 = score2 %% 10 //get the ones digit
digit10 = score2 / 10 //get the tens digit
digit10 %%= 10
ppu_set_addr(ppu_nametable_ram+$3C) // point the PPU to player2's score number
if digit10 > 0 {
ppu_write_data(digit10 + '0')
}
ppu_write_data(digit01 + '0')
}
void reset_ball() {
byte dir
//randomize up/down motion
dir = rand()
dir = dir & %00000001 // dir is now either 0 or 1
ball.down = dir
ball.up = dir ^ %00000001 //flip the bit
//randomize left/right motion
dir = rand()
dir = dir & %00000001 // dir is now either 0 or 1
ball.right = dir
ball.left = dir ^ %00000001 //flip the bit
//reset the ball to the center and set its speed
ball.y = $50
ball.x = $80
ball.speedx = $02
ball.speedy = $02
}
// *LEVEL GRAPHICS*
//palletes for entire game (both title and play screens)
const array pallete = [
$22,$29,$1A,$0F, $22,$36,$17,$0F, $22,$30,$21,$0F, $22,$27,$17,$0F, //background palette
$22,$1C,$15,$14, $22,$02,$38,$3C, $22,$1C,$15,$14, $22,$02,$38,$3C //sprite palette
]
const array scorebackground = "P1 Score- P2 Score-" ascii
const array gameover_msg = "G A M E O V E R" ascii
const array p1_win_msg = "P1 Wins!" ascii
const array p2_win_msg = "P2 Wins!" ascii
const array title_msg = "Press Start" ascii
//attribute table for play screen graphics
const array attribute = [
%00000101, %00000101, %00000101, %00000101, %00000101, %00000101, %00000101, %00000101,
%00000101, %00000101, %00000101, %00000101, %00000101, %00000101, %00000101, %00000101,
%00000101, %00000101, %00000101, %00000101, %00000101, %00000101, %00000101, %00000101
]
// *CHARACTER ROM (GRAPHICS)*
segment(chrrom) const array graphics @ $0000 = file("tiles.chr", 0)