-
Notifications
You must be signed in to change notification settings - Fork 0
/
Chapter 21.a
758 lines (700 loc) · 29.4 KB
/
Chapter 21.a
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
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
;-------------------------------------------------------------
; Chapter 21 - Asynchronous Playfields: Bricks - Online
;
; We've got collisions working, but now we'd like some more
; interaction. We can make a little "breakout" style game
; where the ball knocks out rows of bricks. We'll need to
; draw several rows of bricks, any or all of which might be missing.
;
; We'll use a technique called "asychronous playfields".
; Remember that the playfield is either symmetric (20 pixels
; followed by the same 20 pixels reversed) or repeated (20 pixels
; repeated twice). But if we change the playfield registers
; *during* the scanline, we can make the second half a
; different bitmap than the first half.
;
; We're going to move away from the HMPx/HMOVE method of
; setting object position and use the SetHorizPos method, since
; we really need to know the X position of both player and ball
; at all times. The way the subroutine is written, this takes
; two scanlines per object. But we do it during the overscan
; period at the end of the frame, and we've got the cycles
; to spare.
;
; Also, we're going to keep score and have a rudimentary
; scoreboard, which makes this sort of an actual game!
;
; Fun fact: Messing with the HMOVE register causes a "comb"
; effect on the left 8 pixels of the screen, which can be seen
; at the bottom of the screen in the overscan region.
;
;-------------------------------------------------------------
processor 6502
include "vcs.h"
include "macro.h"
include "xmacro.h" ; timer macros
;-----------------------------------------------------------------
; Variables SEGment
seg.u Variables ; set Variables SEGment (uninitialized)
org $80 ; start Variables at $80
;-----------------------------------------------------------------
; Declare variables
XPlyr byte ; player x pos
YPlyr byte ; player y pos
XBall byte ; ball x pos
YBall byte ; ball y pos
SpritePtr word ; sprite pointer
YSprOfs byte ; temp sprite offset
YBallVel byte ; ball Y velocity
XBallVel byte ; ball X velocity
XBallErr byte ; ball X fractional error
Captured byte ; ball capture flag
AVol0 byte ; shadow register for AVOL0
Score byte ; current BCD-encoded score
Temp byte ; temporary storage
Bricks ds 36 ; Define Storage 36 bytes - brick bitmap (6x6 bytes)
ScoreHeight equ 20 ; height of top scoreboard
BrickYStart equ 32 ; starting Y coordinate of bricks
BrickHeight equ 16 ; height of each brick in pixels
NBrickRows equ 6 ; number of lines of bricks
NBL equ NBrickRows ; abbreviation for number of brick rows
BytesPerRow equ 6 ; number of bytes for each row of bricks
BricksPerRow equ 40 ; number of bricks in each row
; (two bytes have only 4 active pixels)
;-----------------------------------------------------------------
; Color constants
BGCOLOR equ #$80 ; blue
PLCOLOR equ #$6c ; light pink
GNDCOLOR equ #$c0 ; green
;-----------------------------------------------------------------
; Enable ball if it is on this scanline (in X register)
; Modifies A.
MAC DRAW_BALL ; create MACro DRAW_BALL
lda #%00000000 ; LoaD byte -> A
cpx YBall ; ComPare X <-> yball - sets zero flag
bne .noball ; Branch Not Equal - branch if Z flag !- 1
lda #%00000010 ; for ENAM0 (ENAble Missile 0) the 2nd bit is enabled
.noball
sta ENABL ; STore A -> ENABL - enable ball
ENDM ; END Macro
;-----------------------------------------------------------------
; Code SEGment
seg Code ; set Code SEGment
org $f000 ; start Code at $f000
;-----------------------------------------------------------------
; Initialize and set initial offsets of objects.
Start
CLEAN_START ; macro to safely clear memroy and TIA
lda #185-SpriteHeight ; LoaD (185 - SpriteHeight) -> A
sta YPlyr ; STore A -> yplyr - player Y position, top to bottom
;-----------------------------------------------------------------
; Set player 0 horizontal position
lda #79
sta XPlyr ; STore A -> Player X Position
ldx #0
jsr SetHorizPos2 ; Jump to SubRoutine
;-----------------------------------------------------------------
; Set ball horizontal position
lda #0
sta XBall ; STore A -> Ball X Position
ldx #4
jsr SetHorizPos2 ; Jump to SubRoutine
;-----------------------------------------------------------------
; Set ball initial velocity
lda #1 ; LoaD #1 -> A
sta YBallVel ; STore A -> ball's Y Velocity
lda #129
sta YBall ; STore A -> Ball Y Position
lda #$40
sta XBallVel ; STore A -> ball's X Velocity
; Set up initial bricks
ldx #0
lda #$ff
SetupBricks
;txa ; uncomment for a sparse brick pattern
sta Bricks,x ; STore A -> Bricks at index X
inx ; INcrement X
cpx #BytesPerRow*NBrickRows ; ComPare X <-> num bytes per row * num bricks per row
bne SetupBricks ; Branch Not Equal - branch if Z flag != 1
;-----------------------------------------------------------------
; Next frame loop
NextFrame
;-----------------------------------------------------------------
; Vertical Sync - 3 lines
VERTICAL_SYNC ; macro gives us 3 lines of VSYNC
;-----------------------------------------------------------------
; in case the ball is on screen
lda ColorFrame0 ; load 1st entry of color data
sta COLUP0 ; set sprite 0 color
;-----------------------------------------------------------------
; Set up playfield
lda #BGCOLOR ; set the background color
sta COLUBK ; STore A -> COLUBK
lda #PLCOLOR ; set the playfield color
sta COLUPF ; STore A -> COLUPF
lda #%00010101 ; playfield reflection and ball size/priority
sta CTRLPF ; STore A -> CTRLPF
lda #0 ; blank out the playfield
sta PF0 ; STore A -> PF0
sta PF1 ; STore A -> PF1
sta PF2 ; STore A -> PF2
;-----------------------------------------------------------------
; 37 lines of VBLANK
TIMER_SETUP 37
;-----------------------------------------------------------------
; Set up sprite pointer depending on button press
lda #<Frame0 ; LoaD A with lo byte address
sta SpritePtr ; STore A -> SpritePtr
lda #>Frame0 ; LoaD A with hi byte address
sta SpritePtr+1 ; normal sprite bitmap
lda INPT4 ; read button input
bmi ButtonNotPressed2 ; Branch if MInum - skip if button not pressed
lda #<Frame1 ; LoaD A with lo byte address
sta SpritePtr ; STore A -> SpritePtr
lda #>Frame1 ; LoaD A with hi byte address
sta SpritePtr+1 ; alternate sprite bitmap
ButtonNotPressed2
TIMER_WAIT ; Wait timer macro
;-----------------------------------------------------------------
; Draw 192 scanlines
ldx #0 ; X will contain the frame Y coordinate
; First, we'll draw the scoreboard.
; Put the playfield into score mode (bit 2) which gives
; two different colors for the left/right side of
; the playfield (given by COLUP0 and COLUP1).
lda #%00010010 ; score mode + 2 pixel ball
sta CTRLPF ; STore A -> ConTRoL Play Field
lda #$48 ; salmon color
sta COLUP0 ; set color for left
lda #$a8 ; sky blue color
sta COLUP1 ; set color for right
;-----------------------------------------------------------------
; We need to extract each digit of the BCD-coded score
; (there are two digits, each 4 bits) and find the appropriate
; entry in the DigitsBitmap bitmap table.
; We'll just draw one digit to keep it simple.
ScanLoop1a
clc ; clear carry flag
; Digits are 5 pixels high, so we need to multiply each
; digit by 5 to find our digit in the bitmap table.
lda Score ; grab the BCD score
and #$0F ; mask out the least significant digit
sta Temp
asl ; Arithmetic Shift Left
asl ; Arithmetic Shift Left
adc Temp ; ADd with Carry
sta Temp ; tmp = score * 5
; Now we divide our current Y coordinate by 2
; to get the index into the digit bitmap.
txa ; Transfer X -> A
ror ; ROtate Right - A = Ycoord / 2
adc Temp ; ADd with Carry - A += tmp
tay ; Transfer A -> Y
lda DigitsBitmap,y ; LoaD DigitsBitmap[offset] -> A
and #$0F ; logical AND - mask out the rightmost digit
sta WSYNC ; strobe WSYNC
sta PF1 ; store digit to playfield 1 register
DRAW_BALL ; draw the ball on this line?
; (only for collision purposes)
inx ; INcrement X
cpx #10 ; digits are 5 pixels high * 2 lines per pixel
bcc ScanLoop1a ; Branch if Carry Clear
; Clear the playfield
lda #0
sta PF1 ; STore A -> Play Field 1
; Turn playfield reflection off, since our brick field
; will be drawn asymetrically (and turn score mode off)
lda #%00010100 ; no reflection + ball priority + 2 pixel ball
sta CTRLPF ; STore A -> ConTRoL Play Field
; Continue until the bricks start on line 32.
ScanLoop1b
sta WSYNC ; wait for horizontal sync - next scanline
DRAW_BALL ; draw the ball on this line? - macro defined above
; (only for collision purposes)
inx ; INcrement X
cpx #BrickYStart ; ComPare X <-> Y Starting position of brick
bne ScanLoop1b
; Next we'll draw the brick field, which is asymmetrical.
; We use two loops: the inner loop draws a row of bricks
; and the outer loop sets up for the next row.
; Timing is very important here! Note that we skip
; drawing the ball if it falls on a line after we start a
; new row. This will cause a little flicker but it is not
; very noticable.
SLEEP 44 ; make sure we start near the end of scanline
ldy #$ff ; start with row = -1
ScanLoop3b
iny ; go to next brick row
lda #BrickHeight ; for the outer loop, we count
sta Temp ; 'brickheight' scan lines for each row
cpy #NBrickRows ; done drawing all brick rows?
bcc ScanSkipSync ; no -- but don't have time to draw ball!
jmp DoneBrickDraw ; exit outer loop
ScanLoop3a
; These instructions are skipped on lines after the brick row changes.
; We need the extra cycles.
DRAW_BALL ; draw the ball on this line?
ScanSkipSync
sta WSYNC ; wait for next scanline
stx COLUPF ; change colors for bricks - STore X -> playfield color and luminance
; Load the first byte of bricks
; Bricks are stored in six contiguous arrays (row-major)
lda Bricks+NBL*0,y ; LoaD entry at Bricks array, index offset
sta PF0 ; store first playfield byte
lda Bricks+NBL*1,y ; LoaD entry at Bricks array, index offset
sta PF1 ; store second playfield byte
lda Bricks+NBL*2,y ; LoaD entry at Bricks array, index offset
sta PF2 ; store third playfield byte
; Here's the asymmetric part -- by this time the TIA clock
; is far enough that we can rewrite the same PF registers
; and display new data on the right side of the screen
inx ; good place for INX b/c of timing
nop ; yet more timing
lda Bricks+NBL*3,y ; LoaD entry at Bricks array, index offset
sta PF0 ; store first playfield byte
lda Bricks+NBL*4,y ; LoaD entry at Bricks array, index offset
sta PF1 ; store second playfield byte
lda Bricks+NBL*5,y ; LoaD entry at Bricks array, index offset
sta PF2 ; store third playfield byte
dec Temp ; DECrement Temp
beq ScanLoop3b ; all lines in current brick row done?
bne ScanLoop3a ; branch always taken
; Clear playfield from bricks loop
DoneBrickDraw
SLEEP 6 ; SLEEP macro for 6 cycles
lda #0 ; LoaD 0 -> A
sta PF0 ; STore A -> PF0
sta PF1 ; STore A -> PF1
sta PF2 ; STore A -> PF2
;-----------------------------------------------------------------
; Draw bottom half of screen with player sprite.
; Setup 'ysprofs' which is the calculated offset into
; sprite lookup tables (it can exceed bounds, we'll test)
; Since the sprite table is reversed, the starting offset is
; YPlyr - YStart - SpriteHeight
lda YPlyr ; LoaD player y position -> A
sec ; SEt Carry flag to 1
sbc #128-SpriteHeight ; SuBtract with Carry A-(128-SpriteHeight)
sta YSprOfs ; STore A -> temp sprite offset
ScanLoop4
;-----------------------------------------------------------------
; Is this scanline within sprite bounds?
dec YSprOfs ; DECrement temp sprite offset
lda YSprOfs ; LoaD A -> temp sprite offset
cmp #SpriteHeight ; CoMPare A <-> memory loc - if A==M set Z
; (sprite is 16 pixels high + padding)
bcc InSprite ; Branch if Carry Clear (if Z==0)
lda #0 ; no sprite, draw the padding
InSprite
tay ; Transfer A -> Y
lda ColorFrame0,y ; LoaD color data (memoryaddress + y) -> A
pha ; PusH A (color data) onto stack
lda (SpritePtr),y ; LoaD bitmap data (memoryaddress + y) -> A
sta WSYNC ; STore A -> Wait for next scanline (as late as possible!)
sta GRP0 ; STore A -> sprite 0 pixels (GRaphics bitmap Player 0)
pla ; PuLl to Accumulator - bitmap data from stack
sta COLUP0 ; STore A -> (COlor/LUminence Player 0) set sprite 0 color
DRAW_BALL ; draw the ball on this line?
inx ; INcrement X
cpx #184 ; ComPare X <-> 184 - set Z if equal
bne ScanLoop4 ; Branch if Z != 1 - repeat next scanline until finished
;-----------------------------------------------------------------
; 8 more pixels for bottom border, and then we'll just leave it
; on for the overscan region.
ldy #$c8 ; set the playfield color
ScanLoop5
dey ; make a nice gradient
lda #$ff
sta WSYNC ; wait for horizontal sync
sty COLUPF ; set the playfield color
sta PF0 ; STore A -> PF0
sta PF1 ; STore A -> PF1
sta PF2 ; STore A -> PF2
lda #0 ; LoaD 0 -> A
sta GRP0 ; STore GRP0 (GRaphics butmap Player 0) -> A
inx ; INcrement X
cpx #192 ; ComPare X <-> 192 - set Z if equal
bne ScanLoop5 ; Branch if Z != 1 - repeat next scanline until finished
;-----------------------------------------------------------------
; Disable ball
lda #0 ; LoaD 0 -> A
sta ENABL ; STore A -> ENABL - set ENAble BaLl to 0
;-----------------------------------------------------------------
; 30 lines of overscan needed, but we have lots of logic to do.
; So we're going to use the PIA timer to let us know when
; almost 30 lines of overscan have passed.
; This handy macro does a WSYNC and then sets up the timer.
TIMER_SETUP 30 ; Timer for 30 lines
;-----------------------------------------------------------------
; Check for collisions
lda #%01000000 ; Load the bitmask -> A
bit CXP0FB ; test if BITs are set in target memory location
; collision between player 0 and ball?
bne PlayerCollision ; Branch if Z != 1 (meaning logical AND - the bit was set)
lda #%10000000 ; Load the bitmask -> A
bit CXBLPF ; test if BITs are set in target memory location
; collision between playfield and ball?
bne PlayfieldCollision ; Branch if Z != 1 (meaning logical AND - the bit was set)
beq NoCollision ; Branch if Z = 1 (result of logical AND - bit was not set)
;-----------------------------------------------------------------
; Now we bounce the ball depending on where it is
PlayerCollision
;-----------------------------------------------------------------
; Is the button pressed? if so, just capture the ball
lda INPT4 ; read button input
bmi ButtonNotPressed ; skip if button not pressed - Branch if Minus
inc Captured ; set capture flag
bne NoCollision ; Branch Not Equal (if Z != 1)
ButtonNotPressed
lda #0
sta Captured ; clear capture flag - STore A -> Captured flag
;-----------------------------------------------------------------
; See if we bounce off of top half or bottom half of player
; (YPlyr + height/2 - YBall)
ldx #1 ; LoaD 1 into X
lda YPlyr ; LoaD player y position into A
clc ; CLear Carry flag
adc #SpriteHeight/2 ; ADd with Carry half of the sprite height with A
sec ; SEt Carry flag to 1
sbc YBall ; SuBtract wity Carry A - y position of ball
bmi StoreVel ; Branch if MInus - bottom half, bounce down
ldx #$ff ; top half, bounce up
bne StoreVel ; Branch Not Equal (if Z != 1)
PlayfieldCollision
;-----------------------------------------------------------------
; Which brick do we break?
; try the one nearest to us
lda YBall ; LoaD ball Y position -> A
ldx XBall ; LoaD ball X position -> X
jsr BreakBrick ; Jump to SubRoutine
bmi CollisionNoBrick ; return -1 = no brick found
; Did we hit the top or the bottom of a brick?
; If top, bounce up, otherwise down
ldx #$ff ; ball velocity = up
cmp #BrickHeight/2 ; top half of brick?
bcc BounceBallUp ; yofs < brickheight/2
ldx #1 ; ball velocity = down
BounceBallUp
; Go to BCD mode and increment the score.
; This treats 'score' as two decimal digits,
; one in each nibble, for ADC and SBC operations.
sed ; SEt Decimal flag = 1
lda Score ; LoaD Score -> A
clc ; Clear Carry Flag
adc #1 ; ADd with Carry A + 1
sta Score ; STore A -> Score
cld ; CLear Decimal flag
jmp StoreVel ; JuMP to StoreVel
CollisionNoBrick
; If bouncing off top of playfield, bounce down
ldx #1 ; Load 1 -> X
lda YBall ; LoaD ball y position -> A
bpl StoreVel ; Branch if PLus (if minus flag not set)
;-----------------------------------------------------------------
; Otherwise bounce up
ldx #$ff ; LoaD $FF -> X
StoreVel
;-----------------------------------------------------------------
; Store final velocity
stx YBallVel ; STore y ball velocity -> X
;-----------------------------------------------------------------
; Make a little sound
txa ; Transfer X -> A
adc #45 ; ADd with Carry A + 45
sta AUDF0 ; frequency - STore A -> AUDF0 (Audio Frequency Channel 0)
lda #6 ; Load 6 -> A
sta AVol0 ; shadow register for volume - STore A -> AVOL0 (Audio Volume Channel 0)
NoCollision
;-----------------------------------------------------------------
; Clear collision registers for next frame
sta CXCLR ; STore A -> CXCLR (Clear collision latches - strobe)
;-----------------------------------------------------------------
; Ball Captured? if so, no motion
lda Captured ; LoaD Captured flag -> A
bne DoneMovement ; Branch if Z flag Not Equal 1
;-----------------------------------------------------------------
; Move ball vertically
lda YBall ; LoaD ball y position -> A
clc ; CLear Carry flag
adc YBallVel ; ADd with Carry ball y velocity + A
bne NoBallHitTop ; Branch if Z != 1
ldx #1 ; LoaD 1 -> X
stx YBallVel ; STore X -> YBallVel - Ball moves up
NoBallHitTop
sta YBall ; STore A -> ball Y position
;-----------------------------------------------------------------
; Move ball horizontally
; We use an fractional counter for the ball, and we have to
; set a different HMOVE value depending on if it's left or right
lda XBallVel ; LoaD the ball X velocity -> A
bmi BallMoveLeft ; Branch if Minus flag set - < 0? move left
clc ; CLear Carry flag
adc XBallErr ; ADd with Carry A + ball X fractional error
sta XBallErr ; STore A -> ball x fractional error
bcc DoneMovement ; no wrap around? done
inc XBall ; XBall += 1
lda XBall ; LoaD X position of ball -> A
cmp #160 ; moved off right side?
bcc DoneMovement ; no, done
lda #0 ; LoaD 0 -> A
sta XBall ; wrap around to left
beq DoneMovement ; always taken
BallMoveLeft
clc ; CLear Carry flag
adc XBallErr ; ADd with Carry A = A + XBallErr + C
sta XBallErr ; STore A -> XBallErr
bcs DoneMovement ; Branch if Carry Set
dec XBall ; DECrement xball
lda XBall ; LoaD XBall -> A
cmp #160 ; CoMPare A <-> 160
bcc DoneMovement ; Branch if Carry Clear
lda #159 ; LoaD 159 -> A
sta XBall ; STore A -> Ball X Position
DoneMovement
;-----------------------------------------------------------------
; Joystick player movement
; For up and down, we INC or DEC the Y Position
lda #%00010000 ; LoaD bitmask -> A (***Up?)
bit SWCHA ; BIt Test Target <-> A (logical AND between target and Accum)
; and sets Z flag to 1 if the bits are equal
bne SkipMoveUp0 ; Branch if Z flag is Not Equal to 1
ldx YPlyr ; LoaD player 0 y pos -> X
cpx #129 ; ComPare X -> #129 ("bottom" extent)
bcc SkipMoveUp0 ; Branch if Carry Clear
dex ; DEcrement X
stx YPlyr ; STore player 0 Y position -> X
lda Captured ; Captured? move the ball too - LoaD Captured -> A
beq SkipMoveUp0 ; Branch if EQual (if Z == 1) - set at bit above
dec YPlyr ; DECrement YPlyr
SkipMoveUp0
lda #%00100000 ; LoaD bitmask -> A (***Down?)
bit SWCHA ; BIt Test Target <-> A (logical AND between target and Accum)
bne SkipMoveDown0 ; Branch if Z flag is Not Equal to 1
ldx YPlyr ; LoaD player 0 y position -> X
cpx #185-SpriteHeight ; ComPare X -> #185 ("top" extent)
bcs SkipMoveDown0 ; Branch if Carry Set
inx ; INcrement X
stx YPlyr ; STore X -> player 0 Y positoin
lda Captured ; Captured? move the ball too - LoaD Captured -> A
beq SkipMoveDown0 ; Branch if EQual (if Z == 1) - set at bit above
inc YBall
SkipMoveDown0
;-----------------------------------------------------------------
; Note that the horizontal position is not contained in RAM,
; but inaccessibly inside the TIA's registers! Some games can
; get away with this if they use the collision registers.
ldx #0 ; assume speed is 0 if no movement
; We'll test the left/right flags using a special feature of
; the BIT instruction, which sets the N and V flags to the
; 7th and 6th bit of the target.
bit SWCHA ; BIT compare A with SWCHA
bvs SkipMoveLeft0 ; Branch if oVerflow Set - V flag set?
lda XPlyr ; LoaD player X position -> A
beq SkipMoveLeft0 ; don't allow move left of screen
dec XPlyr ; DECrement player X position
lda Captured ; LoaD Captured flag -> A
beq SkipMoveLeft0 ; Branch if Z == 1
dec XBall ; if captured, also move the ball
SkipMoveLeft0
bit SWCHA ; BIT compare A with SWCHA
bmi SkipMoveRight0 ; N flag set?
lda XPlyr ; LoaD player X position -> A
cmp #150 ; CoMPare A <-> 150
bcs SkipMoveRight0 ; don't allow move right of screen
inc XPlyr ; INCrement player X Position
lda Captured ; LoaD Captured flag -> A
beq SkipMoveRight0 ; Branch if Z == 1
inc XBall ; if captured, also move the ball
SkipMoveRight0
; Set ball position using SetHorizPos
lda XBall
ldx #4 ; index identifying ball
jsr SetHorizPos2
; Set player position using SetHorizPos
lda XPlyr
ldx #0 ; index identifying player 1
jsr SetHorizPos2
;-----------------------------------------------------------------
; Play audio from shadow register?
ldx AVol0 ; Load audio volume channel 0 shadow register -> X
beq NoAudio ; Branch if Z == 1 (set by beq NoCaptureMove0)
dex ; decrement volume every frame
stx AUDV0 ; store in volume hardware register
stx AVol0 ; store in shadow register
lda #3 ;
sta AUDC0 ; shift counter mode 3 for weird bounce sound
NoAudio
;-----------------------------------------------------------------
; Wait until our timer expires and then WSYNC, so then we'll have
; passed 30 scanlines. This handy macro does this.
TIMER_WAIT
;-----------------------------------------------------------------
; Goto next frame
jmp NextFrame
;-----------------------------------------------------------------
; Subroutine to try to break a brick at a given X-Y coordinate.
; X contains the X coordinate.
; A contains the Y coordinate.
; On return, A = -1 if no brick was present,
; otherwise A = Y offset (0-brickheight-1) of brick hit.
BreakBrick
ldy #$ff
sec ; SEt Carry flag to 1
sbc #BrickYStart ; subtract top Y of brick field
; Divide by brick height
DivideRowLoop
iny ; INcrement Y
sbc #BrickHeight ; SuBtract with Carry A-BrickHeight-(1-C)
bcs DivideRowLoop ; loop until < 0
cpy #NBrickRows ; ComPare Y <-> NBrickRows
bcs NoBrickFound ; Branch if Carry Set
; Now that we have the line, get byte and bit offset for brick
clc ; CLear Carry
adc #BrickHeight ; ADd with Carry A + BrickHeight + Carry
pha ; PusH A - save the remainder to return as result
txa ; Transfer X -> A
clc ; CLear Carry
adc #3 ; adjust because SetHorizPos is off by a few pixels
lsr ; Logical Shift A Right
lsr ; Logical Shift A Right - divide X coordinate by 4
tax ; Transfer A -> X - transfer brick column to X
tya ; Transfer Y -> A - load brick row # in A
clc ; CLear Carry
adc PFOfsTable,x ; ADd with Carry PlayField Offset Table at index X
tay ; Transfer A -> Y
lda PFMaskTable,x ; LoaD PlayField MasK Table at Index X -> A
eor #$ff ; Exclusive OR A with $FF
and Bricks,y ; AND A with Bricks at Y Index
cmp Bricks,y ; was there a change?
beq NoBrickFound2 ; no, so return -1 as result
sta Bricks,y ; STore Bricks at index Y
pla ; PuLl from stack -> A - return remainder as result
rts ; ReTurn from Subroutine
NoBrickFound2
pla ; PuLl from stack -> A - pull the remainder, but ignore it
NoBrickFound
lda #$FF ; return -1 as result
rts ; ReTurn from Subroutine
;-----------------------------------------------------------------
; SetHorizPos2 - Sets the horizontal position of an object.
; The X register contains the index of the desired object:
; X=0: player 0
; X=1: player 1
; X=2: missile 0
; X=3: missile 1
; X=4: ball
; This routine does a WSYNC both before and after, followed by
; an HMOVE and HMCLR. So it takes two scanlines to complete.
SetHorizPos2
sec ; set carry flag
sta WSYNC ; start a new line
sta HMCLR ; clear horizontal motion registers
DivideLoop
sbc #15 ; subtract 15
bcs DivideLoop ; branch until negative
eor #7 ; calculate fine offset
asl ; shift left
asl ; shift left
asl ; shift left
asl ; shift left
sta HMP0,x ; set fine offset
sta RESP0,x ; fix coarse position
sta WSYNC ; Wait for Sync
sta HMOVE ; apply the previous fine position(s)
rts ; return to caller
;-----------------------------------------------------------------
; Height of our sprite in lines
SpriteHeight equ 17
;-----------------------------------------------------------------
; Bitmap data "standing" position
Frame0
.byte #0
.byte #%01101100;$F6
.byte #%00101000;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111010;$C2
.byte #%01111100;$C2
.byte #%00111000;$C2
.byte #%00111000;$16
.byte #%01000100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
;-----------------------------------------------------------------
; Bitmap data "throwing" position
Frame1
.byte #0
.byte #%01101100;$F6
.byte #%01000100;$86
.byte #%00101000;$86
.byte #%00111000;$86
.byte #%10111010;$C2
.byte #%10111101;$C2
.byte #%01111101;$C2
.byte #%00111001;$C2
.byte #%00111000;$16
.byte #%01101100;$16
.byte #%01111100;$16
.byte #%01111100;$18
.byte #%01010100;$18
.byte #%01111100;$18
.byte #%11111110;$F2
.byte #%00111000;$F4
;-----------------------------------------------------------------
; Color data for each line of sprite
ColorFrame0
.byte #$FF ; ball color if not sharing line with player sprite
.byte #$F6;
.byte #$86;
.byte #$86;
.byte #$86;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$C2;
.byte #$16;
.byte #$16;
.byte #$16;
.byte #$18;
.byte #$18;
.byte #$18;
.byte #$F2;
.byte #$F4;
;-----------------------------------------------------------------
; Bitmap pattern for digits
DigitsBitmap ;;{w:8,h:5,count:10,brev:1};;
.byte $EE,$AA,$AA,$AA,$EE
.byte $22,$22,$22,$22,$22
.byte $EE,$22,$EE,$88,$EE
.byte $EE,$22,$66,$22,$EE
.byte $AA,$AA,$EE,$22,$22
.byte $EE,$88,$EE,$22,$EE
.byte $EE,$88,$EE,$AA,$EE
.byte $EE,$22,$22,$22,$22
.byte $EE,$AA,$EE,$AA,$EE
.byte $EE,$AA,$EE,$22,$EE
; Playfield bitmasks for all 40 brick columns
PFMaskTable
REPEAT 2
.byte #$10,#$20,#$40,#$80
.byte #$80,#$40,#$20,#$10,#$08,#$04,#$02,#$01
.byte #$01,#$02,#$04,#$08,#$10,#$20,#$40,#$80
REPEND
; Brick array byte offsets for all 40 brick columns
PFOfsTable
.byte NBL*0,NBL*0,NBL*0,NBL*0
.byte NBL*1,NBL*1,NBL*1,NBL*1, NBL*1,NBL*1,NBL*1,NBL*1
.byte NBL*2,NBL*2,NBL*2,NBL*2, NBL*2,NBL*2,NBL*2,NBL*2
.byte NBL*3,NBL*3,NBL*3,NBL*3
.byte NBL*4,NBL*4,NBL*4,NBL*4, NBL*4,NBL*4,NBL*4,NBL*4
.byte NBL*5,NBL*5,NBL*5,NBL*5, NBL*5,NBL*5,NBL*5,NBL*5
;-----------------------------------------------------------------
; Epilogue
org $fffc ; start at $fffc
.word Start ; reset vector at $fffc
.word Start ; interrupt vector at $fffe (unused in VCS)