mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-11-19 09:31:24 +00:00
738 lines
20 KiB
Plaintext
738 lines
20 KiB
Plaintext
|
processor 6502
|
||
|
include "vcs.h"
|
||
|
include "macro.h"
|
||
|
include "xmacro.h"
|
||
|
|
||
|
; 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.
|
||
|
|
||
|
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
|
||
|
sta WSYNC
|
||
|
sta HMOVE
|
||
|
; 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!
|
||
|
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
|
||
|
< stx COLUPF ; change colors for bricks
|
||
|
; 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
|
||
|
SLEEP 6
|
||
|
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
|
||
|
cpx #192
|
||
|
bne ScanLoop5
|
||
|
|
||
|
; Disable ball
|
||
|
lda #0
|
||
|
sta ENABL
|
||
|
|
||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||
|
|
||
|
; 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
|
||
|
|
||
|
; 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
|
||
|
sta WSYNC
|
||
|
sta HMOVE
|
||
|
; Set player position using SetHorizPos
|
||
|
lda XPlyr
|
||
|
ldx #0
|
||
|
jsr SetHorizPos2
|
||
|
sta WSYNC
|
||
|
sta HMOVE
|
||
|
|
||
|
; 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
|
||
|
; 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
|
||
|
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
|
||
|
; a HMOVE and HMCLR. So it takes two scanlines to complete.
|
||
|
SetHorizPos2
|
||
|
sta WSYNC ; start a new line
|
||
|
sec ; set carry flag
|
||
|
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)
|
||
|
sta HMCLR ; reset the old horizontal 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 .byte $0E ; | XXX | $F5C5 Leading zero is not drawn
|
||
|
.byte $0A ; | X X | $F5C6 because it's never used.
|
||
|
.byte $0A ; | X X | $F5C7
|
||
|
.byte $0A ; | X X | $F5C8
|
||
|
.byte $0E ; | XXX | $F5C9
|
||
|
|
||
|
.byte $22 ; | X X | $F5CA
|
||
|
.byte $22 ; | X X | $F5CB
|
||
|
.byte $22 ; | X X | $F5CC
|
||
|
.byte $22 ; | X X | $F5CD
|
||
|
.byte $22 ; | X X | $F5CE
|
||
|
|
||
|
.byte $EE ; |XXX XXX | $F5CF
|
||
|
.byte $22 ; | X X | $F5D0
|
||
|
.byte $EE ; |XXX XXX | $F5D1
|
||
|
.byte $88 ; |X X | $F5D2
|
||
|
.byte $EE ; |XXX XXX | $F5D3
|
||
|
|
||
|
.byte $EE ; |XXX XXX | $F5D4
|
||
|
.byte $22 ; | X X | $F5D5
|
||
|
.byte $66 ; | XX XX | $F5D6
|
||
|
.byte $22 ; | X X | $F5D7
|
||
|
.byte $EE ; |XXX XXX | $F5D8
|
||
|
|
||
|
.byte $AA ; |X X X X | $F5D9
|
||
|
.byte $AA ; |X X X X | $F5DA
|
||
|
.byte $EE ; |XXX XXX | $F5DB
|
||
|
.byte $22 ; | X X | $F5DC
|
||
|
.byte $22 ; | X X | $F5DD
|
||
|
|
||
|
.byte $EE ; |XXX XXX | $F5DE
|
||
|
.byte $88 ; |X X | $F5DF
|
||
|
.byte $EE ; |XXX XXX | $F5E0
|
||
|
.byte $22 ; | X X | $F5E1
|
||
|
.byte $EE ; |XXX XXX | $F5E2
|
||
|
|
||
|
.byte $EE ; |XXX XXX | $F5E3
|
||
|
.byte $88 ; |X X | $F5E4
|
||
|
.byte $EE ; |XXX XXX | $F5E5
|
||
|
.byte $AA ; |X X X X | $F5E6
|
||
|
.byte $EE ; |XXX XXX | $F5E7
|
||
|
|
||
|
.byte $EE ; |XXX XXX | $F5E8
|
||
|
.byte $22 ; | X X | $F5E9
|
||
|
.byte $22 ; | X X | $F5EA
|
||
|
.byte $22 ; | X X | $F5EB
|
||
|
.byte $22 ; | X X | $F5EC
|
||
|
|
||
|
.byte $EE ; |XXX XXX | $F5ED
|
||
|
.byte $AA ; |X X X X | $F5EE
|
||
|
.byte $EE ; |XXX XXX | $F5EF
|
||
|
.byte $AA ; |X X X X | $F5F0
|
||
|
.byte $EE ; |XXX XXX | $F5F1
|
||
|
|
||
|
.byte $EE ; |XXX XXX | $F5F2
|
||
|
.byte $AA ; |X X X X | $F5F3
|
||
|
.byte $EE ; |XXX XXX | $F5F4
|
||
|
.byte $22 ; | X X | $F5F5
|
||
|
.byte $EE ; |XXX XXX | $F5F6
|
||
|
|
||
|
; 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
|
||
|
.word Start
|
||
|
.word Start
|