2018-10-01 01:07:45 +00:00
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
processor 6502
|
|
|
|
include "vcs.h"
|
|
|
|
include "macro.h"
|
|
|
|
include "xmacro.h"
|
|
|
|
|
2018-10-01 01:07:45 +00:00
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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.
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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.
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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.
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; Also, we're going to keep score and have a rudimentary
|
|
|
|
; scoreboard, which makes this sort of an actual game!
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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.
|
2018-10-01 01:07:45 +00:00
|
|
|
;
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
2016-12-16 01:21:51 +00:00
|
|
|
|
|
|
|
seg.u Variables
|
|
|
|
org $80
|
|
|
|
|
|
|
|
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 ; 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
|
|
|
|
PLCOLOR equ #$6c
|
|
|
|
GNDCOLOR equ #$c0
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
; Enable ball if it is on this scanline (in X register)
|
|
|
|
; Modifies A.
|
|
|
|
; Takes 13 cycles if ball is present, 12 if absent.
|
|
|
|
MAC DRAW_BALL
|
|
|
|
lda #%00000000
|
|
|
|
cpx YBall
|
|
|
|
bne .noball
|
|
|
|
lda #%00000010 ; for ENAM0 the 2nd bit is enable
|
|
|
|
.noball
|
|
|
|
sta ENABL ; enable ball
|
|
|
|
ENDM
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
seg Code
|
|
|
|
org $f000
|
|
|
|
|
|
|
|
; Initialize and set initial offsets of objects.
|
|
|
|
Start CLEAN_START
|
|
|
|
; Set player 0 vertical position
|
|
|
|
lda #185-SpriteHeight
|
|
|
|
sta YPlyr ; player Y position, top to bottom
|
|
|
|
; Set player 0 horizontal position
|
|
|
|
lda #70
|
|
|
|
sta XPlyr
|
|
|
|
ldx #0
|
|
|
|
jsr SetHorizPos2
|
|
|
|
; Set ball horizontal position
|
|
|
|
lda #0
|
|
|
|
sta XBall
|
|
|
|
ldx #4
|
|
|
|
jsr SetHorizPos2
|
|
|
|
; Set ball initial velocity
|
|
|
|
lda #1
|
|
|
|
sta YBallVel
|
|
|
|
lda #129
|
|
|
|
sta YBall
|
|
|
|
lda #$40
|
|
|
|
sta XBallVel
|
|
|
|
; Set up initial bricks
|
|
|
|
ldx #0
|
|
|
|
lda #$ff
|
|
|
|
SetupBricks
|
|
|
|
;txa ; uncomment for a sparse brick pattern
|
|
|
|
sta Bricks,x
|
|
|
|
inx
|
|
|
|
cpx #BytesPerRow*NBrickRows
|
|
|
|
bne SetupBricks
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
|
|
|
; Next frame loop
|
|
|
|
NextFrame
|
|
|
|
VERTICAL_SYNC
|
|
|
|
|
|
|
|
; 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
|
|
|
|
lda #PLCOLOR ; set the playfield color
|
|
|
|
sta COLUPF
|
|
|
|
lda #%00010101 ; playfield reflection and ball size/priority
|
|
|
|
sta CTRLPF
|
|
|
|
lda #0 ; blank out the playfield
|
|
|
|
sta PF0
|
|
|
|
sta PF1
|
|
|
|
sta PF2
|
|
|
|
|
|
|
|
; 37 lines of VBLANK
|
|
|
|
TIMER_SETUP 37
|
|
|
|
; Set up sprite pointer depending on button press
|
|
|
|
lda #<Frame0
|
|
|
|
sta SpritePtr
|
|
|
|
lda #>Frame0
|
|
|
|
sta SpritePtr+1 ; normal sprite bitmap
|
|
|
|
lda INPT4 ;read button input
|
|
|
|
bmi ButtonNotPressed2 ;skip if button not pressed
|
|
|
|
lda #<Frame1
|
|
|
|
sta SpritePtr
|
|
|
|
lda #>Frame1
|
|
|
|
sta SpritePtr+1 ; alternate sprite bitmap
|
|
|
|
ButtonNotPressed2
|
|
|
|
TIMER_WAIT
|
|
|
|
|
|
|
|
; 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
|
|
|
|
lda #$48
|
|
|
|
sta COLUP0 ; set color for left
|
|
|
|
lda #$a8
|
|
|
|
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
|
|
|
|
asl
|
|
|
|
adc Temp
|
|
|
|
sta Temp ; tmp = score * 5
|
|
|
|
; Now we divide our current Y coordinate by 2
|
|
|
|
; to get the index into the digit bitmap.
|
|
|
|
txa
|
|
|
|
ror ; A = Ycoord / 2
|
|
|
|
adc Temp ; A += tmp
|
|
|
|
tay
|
|
|
|
lda DigitsBitmap,y ; A = DigitsBitmap[offset]
|
|
|
|
and #$0F ; mask out the rightmost digit
|
|
|
|
sta WSYNC
|
|
|
|
sta PF1 ; store digit to playfield 1 register
|
|
|
|
DRAW_BALL ; draw the ball on this line?
|
|
|
|
; (only for collision purposes)
|
|
|
|
inx
|
|
|
|
cpx #10 ; digits are 5 pixels high * 2 lines per pixel
|
|
|
|
bcc ScanLoop1a
|
|
|
|
; Clear the playfield
|
|
|
|
lda #0
|
|
|
|
sta PF1
|
|
|
|
; 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
|
|
|
|
; Continue until the bricks start on line 32.
|
|
|
|
ScanLoop1b
|
|
|
|
sta WSYNC
|
|
|
|
DRAW_BALL ; draw the ball on this line?
|
|
|
|
; (only for collision purposes)
|
|
|
|
inx
|
|
|
|
cpx #BrickYStart
|
|
|
|
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!
|
2018-08-26 13:19:09 +00:00
|
|
|
bcs DoneBrickDraw ; exit outer loop
|
2016-12-16 01:21:51 +00:00
|
|
|
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
|
2017-05-13 12:08:36 +00:00
|
|
|
stx COLUPF ; change colors for bricks
|
2016-12-16 01:21:51 +00:00
|
|
|
; Load the first byte of bricks
|
|
|
|
; Bricks are stored in six contiguous arrays (row-major)
|
|
|
|
lda Bricks+NBL*0,y
|
|
|
|
sta PF0 ; store first playfield byte
|
|
|
|
; Store the next two bytes
|
|
|
|
lda Bricks+NBL*1,y
|
|
|
|
sta PF1
|
|
|
|
lda Bricks+NBL*2,y
|
|
|
|
sta PF2
|
|
|
|
; 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
|
|
|
|
sta PF0
|
|
|
|
lda Bricks+NBL*4,y
|
|
|
|
sta PF1
|
|
|
|
lda Bricks+NBL*5,y
|
|
|
|
sta PF2
|
|
|
|
dec Temp
|
|
|
|
beq ScanLoop3b ; all lines in current brick row done?
|
|
|
|
bne ScanLoop3a ; branch always taken
|
|
|
|
; Clear playfield from bricks loop
|
|
|
|
DoneBrickDraw
|
2018-08-26 13:19:09 +00:00
|
|
|
sta WSYNC
|
2016-12-16 01:21:51 +00:00
|
|
|
lda #0
|
|
|
|
sta PF0
|
|
|
|
sta PF1
|
|
|
|
sta 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
|
|
|
|
sec
|
|
|
|
sbc #128-SpriteHeight
|
|
|
|
sta YSprOfs
|
|
|
|
ScanLoop4
|
|
|
|
; Is this scanline within sprite bounds?
|
|
|
|
dec YSprOfs
|
|
|
|
lda YSprOfs
|
|
|
|
cmp #SpriteHeight ; sprite is 16 pixels high + padding
|
|
|
|
bcc InSprite
|
|
|
|
lda #0 ; no sprite, draw the padding
|
|
|
|
InSprite
|
|
|
|
tay
|
|
|
|
lda ColorFrame0,y ; load color data
|
|
|
|
pha ; push color data onto stack
|
|
|
|
lda (SpritePtr),y ; load bitmap data
|
|
|
|
sta WSYNC ; wait for next scanline (as late as possible!)
|
|
|
|
sta GRP0 ; set sprite 0 pixels
|
|
|
|
pla ; pull bitmap data from stack
|
|
|
|
sta COLUP0 ; set sprite 0 color
|
|
|
|
DRAW_BALL ; draw the ball on this line?
|
|
|
|
inx
|
|
|
|
cpx #184
|
|
|
|
bne ScanLoop4 ; 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
|
|
|
|
sty COLUPF ; set the playfield color
|
|
|
|
sta PF0
|
|
|
|
sta PF1
|
|
|
|
sta PF2
|
|
|
|
lda #0
|
|
|
|
sta GRP0
|
|
|
|
inx
|
2018-08-26 13:19:09 +00:00
|
|
|
cpx #190
|
2016-12-16 01:21:51 +00:00
|
|
|
bne ScanLoop5
|
|
|
|
|
|
|
|
; Disable ball
|
|
|
|
lda #0
|
|
|
|
sta ENABL
|
|
|
|
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
2018-08-26 13:19:09 +00:00
|
|
|
; 29 lines of overscan needed, but we have lots of logic to do.
|
2016-12-16 01:21:51 +00:00
|
|
|
; So we're going to use the PIA timer to let us know when
|
2018-08-26 13:19:09 +00:00
|
|
|
; almost 29 lines of overscan have passed.
|
2016-12-16 01:21:51 +00:00
|
|
|
; This handy macro does a WSYNC and then sets up the timer.
|
2018-08-26 13:19:09 +00:00
|
|
|
TIMER_SETUP 29
|
2016-12-16 01:21:51 +00:00
|
|
|
|
|
|
|
; Check for collisions
|
|
|
|
lda #%01000000
|
|
|
|
bit CXP0FB ; collision between player 0 and ball?
|
|
|
|
bne PlayerCollision
|
|
|
|
lda #%10000000
|
|
|
|
bit CXBLPF ; collision between playfield and ball?
|
|
|
|
bne PlayfieldCollision
|
|
|
|
beq NoCollision
|
|
|
|
; 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
|
|
|
|
inc Captured ; set capture flag
|
|
|
|
bne NoCollision
|
|
|
|
ButtonNotPressed
|
|
|
|
lda #0
|
|
|
|
sta Captured ; clear capture flag
|
|
|
|
; See if we bounce off of top half or bottom half of player
|
|
|
|
; (yplyr + height/2 - yball)
|
|
|
|
ldx #1
|
|
|
|
lda YPlyr
|
|
|
|
clc
|
|
|
|
adc #SpriteHeight/2
|
|
|
|
sec
|
|
|
|
sbc YBall
|
|
|
|
bmi StoreVel ; bottom half, bounce down
|
|
|
|
ldx #$ff ; top half, bounce up
|
|
|
|
bne StoreVel
|
|
|
|
PlayfieldCollision
|
|
|
|
; Which brick do we break?
|
|
|
|
; try the one nearest to us
|
|
|
|
lda YBall
|
|
|
|
ldx XBall
|
|
|
|
jsr BreakBrick
|
|
|
|
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
|
|
|
|
lda Score
|
|
|
|
clc
|
|
|
|
adc #1
|
|
|
|
sta Score
|
|
|
|
cld
|
|
|
|
jmp StoreVel
|
|
|
|
CollisionNoBrick
|
|
|
|
; If bouncing off top of playfield, bounce down
|
|
|
|
ldx #1
|
|
|
|
lda YBall
|
|
|
|
bpl StoreVel
|
|
|
|
; Otherwise bounce up
|
|
|
|
ldx #$ff
|
|
|
|
StoreVel
|
|
|
|
; Store final velocity
|
|
|
|
stx YBallVel
|
|
|
|
; Make a little sound
|
|
|
|
txa
|
|
|
|
adc #45
|
|
|
|
sta AUDF0 ; frequency
|
|
|
|
lda #6
|
|
|
|
sta AVol0 ; shadow register for volume
|
|
|
|
NoCollision
|
|
|
|
; Clear collision registers for next frame
|
|
|
|
sta CXCLR
|
|
|
|
; Ball captured? if so, no motion
|
|
|
|
lda Captured
|
|
|
|
bne DoneMovement
|
|
|
|
; Move ball vertically
|
|
|
|
lda YBall
|
|
|
|
clc
|
|
|
|
adc YBallVel
|
|
|
|
bne NoBallHitTop
|
|
|
|
ldx #1
|
|
|
|
stx YBallVel
|
|
|
|
NoBallHitTop
|
|
|
|
sta YBall
|
|
|
|
; Move ball horizontally
|
|
|
|
lda XBallVel ; signed X velocity
|
|
|
|
bmi BallMoveLeft ; < 0? move left
|
|
|
|
clc
|
|
|
|
adc XBallErr
|
|
|
|
sta XBallErr ; XBallErr += XBallVel
|
|
|
|
bcc DoneMovement ; no wrap around? done
|
|
|
|
inc XBall ; XBall += 1
|
|
|
|
lda XBall
|
|
|
|
cmp #160 ; moved off right side?
|
|
|
|
bcc DoneMovement ; no, done
|
|
|
|
lda #0
|
|
|
|
sta XBall ; wrap around to left
|
|
|
|
beq DoneMovement ; always taken
|
|
|
|
BallMoveLeft
|
|
|
|
clc
|
|
|
|
adc XBallErr
|
|
|
|
sta XBallErr
|
|
|
|
bcs DoneMovement
|
|
|
|
dec XBall ; decrement xball
|
|
|
|
lda XBall
|
|
|
|
cmp #160
|
|
|
|
bcc DoneMovement
|
|
|
|
lda #159
|
|
|
|
sta XBall
|
|
|
|
DoneMovement
|
|
|
|
; Joystick player movement
|
|
|
|
; For up and down, we INC or DEC the Y Position
|
|
|
|
lda #%00010000 ;Up?
|
|
|
|
bit SWCHA
|
|
|
|
bne SkipMoveUp ; bit set? skip move
|
|
|
|
ldx YPlyr
|
|
|
|
cpx #129
|
|
|
|
bcc SkipMoveUp
|
|
|
|
dex
|
|
|
|
stx YPlyr
|
|
|
|
lda Captured ; captured? move the ball too
|
|
|
|
beq SkipMoveUp
|
|
|
|
dec YPlyr
|
|
|
|
SkipMoveUp
|
|
|
|
lda #%00100000 ;Down?
|
|
|
|
bit SWCHA
|
|
|
|
bne SkipMoveDown ; bit set? skip move
|
|
|
|
ldx YPlyr
|
|
|
|
cpx #185-SpriteHeight
|
|
|
|
bcs SkipMoveDown
|
|
|
|
inx
|
|
|
|
stx YPlyr
|
|
|
|
lda Captured ; captured? move the ball too
|
|
|
|
beq SkipMoveDown
|
|
|
|
inc YBall
|
|
|
|
SkipMoveDown
|
|
|
|
; 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
|
|
|
|
bvs SkipMoveLeft ; V flag set?
|
|
|
|
lda XPlyr
|
|
|
|
beq SkipMoveLeft ; don't allow move left of screen
|
|
|
|
dec XPlyr
|
|
|
|
lda Captured
|
|
|
|
beq SkipMoveLeft
|
|
|
|
dec XBall ; if captured, also move the ball
|
|
|
|
SkipMoveLeft
|
|
|
|
bit SWCHA
|
|
|
|
bmi SkipMoveRight ; N flag set?
|
|
|
|
lda XPlyr
|
|
|
|
cmp #150
|
|
|
|
bcs SkipMoveRight ; don't allow move right of screen
|
|
|
|
inc XPlyr
|
|
|
|
lda Captured
|
|
|
|
beq SkipMoveRight
|
|
|
|
inc XBall ; if captured, also move the ball
|
|
|
|
SkipMoveRight
|
|
|
|
; Set ball position using SetHorizPos
|
|
|
|
lda XBall
|
|
|
|
ldx #4
|
|
|
|
jsr SetHorizPos2
|
|
|
|
; Set player position using SetHorizPos
|
|
|
|
lda XPlyr
|
|
|
|
ldx #0
|
|
|
|
jsr SetHorizPos2
|
|
|
|
|
|
|
|
; Play audio from shadow register?
|
|
|
|
ldx AVol0
|
|
|
|
beq NoAudio
|
|
|
|
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
|
2018-08-26 13:19:09 +00:00
|
|
|
; passed 29 scanlines. This handy macro does this.
|
2016-12-16 01:21:51 +00:00
|
|
|
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
|
|
|
|
sbc #BrickYStart ; subtract top Y of brick field
|
|
|
|
; Divide by brick height
|
|
|
|
DivideRowLoop
|
|
|
|
iny
|
|
|
|
sbc #BrickHeight
|
|
|
|
bcs DivideRowLoop ; loop until < 0
|
|
|
|
cpy #NBrickRows
|
|
|
|
bcs NoBrickFound
|
|
|
|
; Now that we have the line, get byte and bit offset for brick
|
|
|
|
clc
|
|
|
|
adc #BrickHeight
|
|
|
|
pha ; save the remainder to return as result
|
|
|
|
txa
|
|
|
|
clc
|
|
|
|
adc #3 ; adjust because SetHorizPos is off by a few pixels
|
|
|
|
lsr
|
|
|
|
lsr ; divide X coordinate by 4
|
|
|
|
tax ; transfer brick column to X
|
|
|
|
tya ; load brick row # in A
|
|
|
|
clc
|
|
|
|
adc PFOfsTable,x ; add offset
|
|
|
|
tay
|
|
|
|
lda PFMaskTable,x
|
|
|
|
eor #$ff
|
|
|
|
and Bricks,y
|
|
|
|
cmp Bricks,y ; was there a change?
|
|
|
|
beq NoBrickFound2 ; no, so return -1 as result
|
|
|
|
sta Bricks,y
|
|
|
|
pla ; return remainder as result
|
|
|
|
rts
|
|
|
|
NoBrickFound2
|
|
|
|
pla ; pull the remainder, but ignore it
|
|
|
|
NoBrickFound
|
|
|
|
lda #$FF ; return -1 as result
|
|
|
|
rts
|
|
|
|
|
|
|
|
; 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
|
2017-05-13 12:08:36 +00:00
|
|
|
; HMOVE. So it takes two scanlines to complete.
|
2016-12-16 01:21:51 +00:00
|
|
|
SetHorizPos2
|
|
|
|
sec ; set carry flag
|
2017-05-13 12:08:36 +00:00
|
|
|
sta WSYNC ; start a new line
|
|
|
|
sta HMCLR
|
2016-12-16 01:21:51 +00:00
|
|
|
DivideLoop
|
|
|
|
sbc #15 ; subtract 15
|
|
|
|
bcs DivideLoop ; branch until negative
|
|
|
|
eor #7 ; calculate fine offset
|
|
|
|
asl
|
|
|
|
asl
|
|
|
|
asl
|
|
|
|
asl
|
|
|
|
sta HMP0,x ; set fine offset
|
|
|
|
sta RESP0,x ; fix coarse position
|
|
|
|
sta WSYNC
|
|
|
|
sta HMOVE ; apply the previous fine position(s)
|
|
|
|
rts ; return to caller
|
|
|
|
|
|
|
|
; Height of our sprite in lines
|
|
|
|
SpriteHeight equ 17
|
|
|
|
|
2019-03-26 00:39:53 +00:00
|
|
|
;; Bitmap data "standing" position
|
|
|
|
;;{w:8,h:16,brev:1,flip:1};;
|
2016-12-16 01:21:51 +00:00
|
|
|
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
|
2019-03-26 00:39:53 +00:00
|
|
|
;; Bitmap data "throwing" position
|
|
|
|
;;{w:8,h:16,brev:1,flip:1};;
|
2016-12-16 01:21:51 +00:00
|
|
|
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
|
2019-03-26 00:39:53 +00:00
|
|
|
;; Color data for each line of sprite
|
|
|
|
;;{pal:"vcs"};;
|
2016-12-16 01:21:51 +00:00
|
|
|
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;
|
2019-03-26 00:39:53 +00:00
|
|
|
;; Bitmap pattern for digits
|
2017-05-13 12:08:36 +00:00
|
|
|
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
|
2019-03-21 16:13:27 +00:00
|
|
|
;;end
|
2016-12-16 01:21:51 +00:00
|
|
|
|
|
|
|
; 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
|
2017-05-13 12:08:36 +00:00
|
|
|
|
2016-12-16 01:21:51 +00:00
|
|
|
; 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
|
|
|
|
.word Start
|
|
|
|
.word Start
|