[stella] The Reflex!

Subject: [stella] The Reflex!
From: "Lee Fastenau" <stella@xxxxxxxxxxxxxxx>
Date: Tue, 1 Mar 2005 00:08:01 -0500
> Past
> "level creation" contests on AtariAge have been very successful, and
> such a design contest for Crazy Balloon would be the first for a new
> 2600 homebrew game (the others have been for hacks of existing Atari
> games).

Well, this wasn't hosted by AtariAge... and the game has yet to be
completed, but does this count?
http://www.ioyu.com/io/atari/reflex/level_index.asp

(And the super-secret, never-before-seen "view all" page)
http://www.ioyu.com/io/atari/reflex/level_index_big.asp

I haven't built the encoder for this particular data yet, but the decoder in
the ROM itself is done... or at least the hugely bloated version of the
decoder is done.  Not the Jentzsch-ified 12-byte decoder. ;)

And here's the latest (totally unplayable) source for Reflex.  Feel free to
poke around and criticize... oh, and definitely feel free to fix the paddle
English bug that's driving me insane.

Although I'm sure there aren't enough comments, there should be more there
than last time.  (After long periods away from the code, it helps to go back
and step through functions, adding comments along the way.)

Enjoy, (or sneer in disgust, take your pick)
-Lee


Archives (includes files) at http://www.biglist.com/lists/stella/archives/
Unsub & more at http://stella.biglist.com
; Codename: Reflex
; by Lee W. Fastenau
; 03/26/2004

; Assemble with DASM using:
; dasm.exe reflex.asm -f3 -oreflex.bin
; (Ensure vcs.h and macro.h are in the same folder or specify the include path using the -I option)

; TO-DO
;    -- HIGH PRIORITY --
; OK Fix var allocation bug
; OK Driving control support
;  W Improve state handling
; OK Add sound
;  W Add ball velocity table
;  W Add ball velocity multiplier
; OK Two-player controller support
; OK Two-player paddle update
; OK Two-player score kernel
; OK Two-player game kernel
; OK Better paddle reflection
;  W Paddle "english"
;  * Add "New game" state
;  * Board clear (win) state
;  * Improve pregame state
; OK Improve ball out detection
;  * Improve ball out state
; OK Add "lives" counter
; OK Add PAL50/60 support
; OK "Bootup" video config (NTSC/PAL50/PAL60)
;  * Fire button starts game
;  * Build online level editor
;  W Add level decompressor
;  * Level selection using Game Select while holding Game Reset
;  W Optimize program structure
;	- Moved game kernel to beginning of ROM
;	- Removed all ALIGNs
;	- Changed game kernel to us skipdraw

;    -- LOW PRIORITY --
;  * Add unique board designs
;  * Re-code ball effects
;  * Implement difficulty switches
;  W Intro music
;  * Fun scrolly bottom message
; OK Improve ALT_CHAR_SET data
; OK Better SECAM support (When Assembly Option set)

;    -- FOR OKGE04 PREVIEW --
; OK One-player paddle
; OK Two-player paddles
; OK Board clear detect and reset board
; OK Initial ball speed/direction
; OK Ball speed increase during game
; OK Two-player scoring
; OK Two-player win detect


; CLEANUP REMINDERS
;  * Bring PF priority to front
;  * Check default modes
;  * Check debug constants
;  * Check state delay values
;  * Check for debug "ALIGN 256" or "ds.b" (in RAM is okay)

		processor 6502
		include "vcs.h"
		include "macro.h"

; ------------------------------------
; Global Macros
; ------------------------------------
		MAC WARN_BOUNDARY
.start			SET {1}
.end			SET *
			IF >.start != >.end
				echo "Page boundary crossed (",{2},.start,"-",.end,")"
			ENDIF
		ENDM

		MAC REQUIRED_BOUNDARY
.start			SET {1}
.end			SET *
			IF >.start == >.end
				echo "Expected page boundary not crossed (",{2},.start,"-",.end,")"
			ENDIF
		ENDM

; ------------------------------------
; Assembly Options
; ------------------------------------
SECAM_PALETTE		equ 0		; 0 = PAL palette, 1 = SECAM palette
DEFAULT_GAME_MODE	equ 0		; See gameModes tab (currently modes 0-5 available)
CHAR_SET		equ 0		; 0 = default, 1 = original
VU_METERS		equ 1		; 0 = No VU meters, 1 = VU meters on title screen
TITLE_SONG		equ 2		; 0 = Devil's Workshop
					; 1 = Lost in Legoland

; ------------------------------------
; DEBUG_CONSTANTS
; ------------------------------------
NO_MUSIC		equ 0		; Disable music (used for remaining sane during development)
TEST_PADDLE		equ 0		; Temporary test paddle
JOYSTICK_BALL		equ 0		; Control ball with joystick
STATIC_BALL		equ 0		; Ball doesn't move
NO_COLLISION		equ 0		; Turn collision/reflection off (always off when JOYSTICK_BALL is 1)
NO_REMOVE		equ 0		; Turn brick removal off
NO_PADDLE		equ 0		; Solid border, no paddle
FAST_BALL		equ 0		; Make ball move really fast
DEBUG_COLLISION		equ 0		; Use leftmost digit of score to show last collision
INFINITE_LIVES		equ 0		; Hmm... what's this do?

; ------------------------------------
; Constant declarations
; ------------------------------------
white			equ $0E		; The NTSC color palette
lightgreen		equ $CC
green			equ $CC
yellow			equ $2E
orange			equ $2A
darkgray		equ $02	
black			equ $00
lightblue		equ $8A
lightred		equ $3A

scoreColor		equ yellow
scoreBgColor		equ black
bgColor			equ darkgray
paddleColor		equ yellow
livesColor		equ orange
ballHighlight		equ white
ballShadow		equ green
flashColor1		equ white
flashColor2		equ white
paddle0Color		equ lightblue
paddle1Color		equ lightred

		IF !SECAM_PALETTE
PAL_white		equ $0E		; Und die PAL Farbe Palette
PAL_lightgreen		equ $5C
PAL_green		equ $58
PAL_yellow		equ $2E
PAL_orange		equ $2A
PAL_darkgray		equ $02	
PAL_black		equ $00
PAL_lightblue		equ $BA
PAL_lightred		equ $6A

PAL_scoreColor		equ PAL_yellow
PAL_scoreBgColor	equ PAL_black
PAL_bgColor		equ PAL_darkgray
PAL_paddleColor		equ PAL_yellow
PAL_livesColor		equ PAL_orange
PAL_ballHighlight	equ PAL_white
PAL_ballShadow		equ PAL_green
PAL_flashColor1		equ PAL_white
PAL_flashColor2		equ PAL_white
PAL_paddle0Color	equ PAL_lightblue
PAL_paddle1Color	equ PAL_lightred
		ELSE
PAL_white		equ $0E		; La palette de couleur de SECAM
PAL_lightgreen		equ $0E
PAL_green		equ $08
PAL_yellow		equ $0C
PAL_orange		equ $0C
PAL_darkgray		equ $00	
PAL_black		equ $00
PAL_lightblue		equ $02
PAL_lightred		equ $04

PAL_scoreColor		equ PAL_yellow
PAL_scoreBgColor	equ PAL_black
PAL_bgColor		equ PAL_darkgray
PAL_paddleColor		equ PAL_white
PAL_livesColor		equ PAL_white
PAL_ballHighlight	equ PAL_white
PAL_ballShadow		equ PAL_green
PAL_flashColor1		equ PAL_white
PAL_flashColor2		equ PAL_white
PAL_paddle0Color	equ PAL_lightblue
PAL_paddle1Color	equ PAL_lightred
		ENDIF

deathDelay	equ 50
pregameDelay	equ 50
clearDelay	equ 180

playfieldWidth	equ 32
playfieldHeight	equ 16
paddleBound	equ 46
paddle1Width	equ 16
paddle2Width	equ 6
rowHeight	equ 8
flashDuration	equ 5
ballStartX	equ 83
ballStartY	equ 22
startingLives	equ 3

state_options	equ 0
state_newgame	equ 1
state_newlevel	equ 2
state_pregame	equ 3
state_ingame	equ 4
state_ballout	equ 5
state_gameover	equ 6
state_clear	equ 7

sfxBrickFreq	equ 4
sfxPaddleFreq	equ 9
sfxEchoDelay	equ 4
		IF !NO_MUSIC
musicVolume	equ 15
		ELSE
musicVolume	equ 0
		ENDIF

brickScore	equ $0020		; Score is BCD, so use hex... (weird, huh?)
levelScore	equ $1000		

p1score		equ score
p1scoreDigit	equ digit5
p2score		equ score+1
p2scoreDigit	equ digit3

ball_minSpeed	equ 1			; Base multiplier for speed
ball_maxSpeed	equ 10			; Max multiplier for speed

mode_preset	equ %00011111		; Inverted
mode_flags	equ %11101100		; Inverted
mode_switched	equ %00000100		; 1=switched last frame, 0=not switched
mode_twoplayers	equ %00000001		; 1=two players, 0=one player
mode_twoscores	equ %00000010		; 1=competition, 0=cooperative/one player
mode_driving	equ %00010000		; 1=driving controller, 0=joystick (ALWAYS d4)
mode_oneplayer	equ 0			; This is here to make the gameModes tab more readable
mode_onescore	equ 0			; This is here to make the gameModes tab more readable
mode_joystick	equ 0			; This is here to make the gameModes tab more readable

flag_lives	equ %00000111		; Life counter
flag_lastcbsw	equ %00001000		; Last color/bw switch state
flag_palColors	equ %10000000		; PAL color palette flag
flag_pal50	equ %01000000		; PAL 50Hz flag (else 60Hz)

		seg.u vars
variables	org $80
; ------------------------------------
; Variable declarations
; ------------------------------------
blockPtr	dc.w			; Pointer for removing blocks
blockData
block1l		ds.b playfieldHeight	; Columns 0-7
block2l		ds.b playfieldHeight	; Columns 8-15
block2r		ds.b playfieldHeight	; Columns 16-23
block1r		ds.b playfieldHeight	; Columns 24-31

temp		dc.b			; Temp storage
gameMode	dc.b			; See mode bits above
gameState	dc.b			; See state values above
gameFlags	dc.b			; Other game flags
stateTimer	dc.b			; General timer

levelByte 	dc.b			; Compressed level data byte index
levelBit 	dc.b			; Compressed level data bit index

ballx		dc.w			; Ball x pos
bally		dc.w			; Ball y pos
ballmx		dc.w			; Ball x move
ballmy		dc.w			; Ball y move
ballDirection	dc.b			; Ball direction
ballSpeed	dc.b			; Ball speed

lineCount				; Shares space with blockX
blockX		dc.b			; Ball block x location
blockY		dc.b			; Ball block y location
offsetX		dc.b			; Collide x offset
offsetY		dc.b			; Collide y offset
tryX		dc.b			; Ball collision PF bit pattern
tryY		dc.b			; Y offset for ball collision
tryX_store	dc.b

oldSWCHA	dc.b			; Controller nibbles (Player 1 & 2)
paddle0		dc.b			; Paddle movement (Player 1)
paddle1		dc.b			; Paddle movement (Player 2)
paddlePos	dc.w			; Paddle position (player 1 & 2)
paddleSize	dc.w			; Paddle size -4 (player 1 & 2)

blockSection	dc.b			; Section 0-3

pcol		dc.b			; Paddle color for 1-player mode / wall color
p0col		dc.b			; Paddle colors for 2-player mode
p1col		dc.b

digit0		dc.w			; Score digit pointers
digit1		dc.w
digit2		dc.w
digit3		dc.w
digit4		dc.w
digit5		dc.w

score		ds.b 3			; BCD score (1 nibble per digit)
blockCount	dc.b			; Number of bricks remaining

sfxTimer0	dc.b
sfxEcho0	dc.b
sfxVolume0	dc.b

noteTimer	equ score
noteCounter	equ score+2
noteVolume	equ sfxEcho0

stack		ds.w 3			; Reserved for 3 levels of JSR's

		echo "------------------------------------"
		echo "RAM Bytes Used:",[*-variables]d
		echo "RAM Bytes Free:",[$100-*]d

		seg prog
program		org $F000

;
;	gameArea (130 scanlines)
;
gameArea subroutine
		ldy #playfieldHeight-1
		
		ldx bally
		inx

		lda #rowHeight-1
		sta lineCount

		lda p0col
		sta COLUPF

		lda #>rowColor
		sta tryY
		
		jmp setColors
		
skipdraw0a	lda #0			;+2	69	9
		beq skipreturn0a	;+3	72	12
		
skipdraw0b	lda #0			;+2	72	13
		beq skipreturn0b	;+3	75	16
		
startGameKernel
		sta WSYNC
		SLEEP 60		;+60	60	Running start, so to speak

row0
.loop1
		dex			;+2	62	2	Skipdraw in all its wonderful glory!
		cpx #ballHeight		;+2	64	4	http://www.biglist.com/lists/stella/archives/200309/msg00056.html
		bcs skipdraw0a		;+2/+3	66/67	6/7
		lda ballShape,x		;+4	70	10
		SLEEP 2			;+2	72	12
skipreturn0a	sta GRP1		;+3	75	15
		SLEEP 13		;+13	12
		lda block1l,y		;+4	16
		sta PF1			;+3	*19*	Update left half of playfield
		lda block2l,y		;+4	23
		sta PF2			;+3	26
		SLEEP 8			;+8	34
		lda block1r,y		;+4	38
		sta PF1			;+3	41
		lda block2r,y		;+4	45
		sta PF2			;+3	*48*	Update right half of playfield
		SLEEP 4			;+4	52
		dec lineCount		;+5	57
		bne .loop1		;+3/2	*60*/59
		WARN_BOUNDARY row0,"row0 INSIDE"
					
		dex			;+2	61
		cpx #ballHeight		;+2	63
		bcs skipdraw0b		;+2/+3	65/66
		lda ballShape,x		;+4	69
		SLEEP 2			;+2	71
skipreturn0b	sta GRP1		;+3	74
		SLEEP 12		;+12	10
		lda block1l,y		;+4	14
		sta PF1			;+3	*17*	Update left half of playfield
		lda block2l,y		;+4	21
		sta PF2			;+3	24
		lda #rowHeight-1	;+2	26
		sta lineCount		;+3	29
		SLEEP 5			;+5	34
		lda block1r,y		;+4	38
		sta PF1			;+3	41
		lda block2r,y		;+4	45
		sta PF2			;+3	*48*	Update right half of playfield
		SLEEP 13		;+13	61
		dey			;+2	63
		bne .enterHere		;+3	66	Unconditional branch
		WARN_BOUNDARY row0,"row0 OUTSIDE"

skipdraw1a	lda #0			;+2	75
		beq skipreturn1a	;+3	2
		
skipdraw1b	lda #0			;+2	68
		jmp skipreturn1b	;+3	71
		
rows1thru14
.loop2
		lda pcol		;+3	63
		sta COLUPF		;+3	66	Set paddle color
.enterHere	dex			;+2	68
		cpx #ballHeight		;+2	70
		bcs skipdraw1a		;+2/+3	72/73
		WARN_BOUNDARY skipdraw1a,"skipdraw1a"
		lda ballShape,x		;+4	76
		SLEEP 2			;+2	2
skipreturn1a	sta.w GRP1		;+4	6
		lda pcol		;+3	9
		sta COLUPF		;+3	12
		lda block1l,y		;+4	16
		sta PF1			;+3	*19*	Update left half of playfield
		lda block2l,y		;+4	23
		sta PF2			;+3	26
		lda (tryX),y		;+5	31
		sta COLUPF		;+3	*34*	Set board color
		lda block1r,y		;+4	38
		sta PF1			;+3	41
		lda block2r,y		;+4	45
		sta PF2			;+3	*48*	Update right half of playfield
		SLEEP 4			;+4	52
		dec lineCount		;+5	57
		bne .loop2		;+3/2	*60*/59
		WARN_BOUNDARY rows1thru14,"rows1thru14 INSIDE"
					
		lda pcol		;+3	62
		sta COLUPF		;+3	65	Set paddle color
		dex			;+2	61
		cpx #ballHeight		;+2	63
		bcs skipdraw1b		;+2/+3	65/66
		WARN_BOUNDARY skipdraw1b,"skipdraw1b"
		lda ballShape,x		;+4	69
		SLEEP 2			;+2	71
skipreturn1b	sta GRP1		;+3	74
		lda pcol		;+3	7
		sta COLUPF		;+3	10
		lda block1l,y		;+4	14
		sta PF1			;+3	*17*	Update left half of playfield
		lda block2l,y		;+4	21
		sta PF2			;+3	24
		lda #rowHeight-1	;+2	26
		sta lineCount		;+3	29
		lda #0			;+2	31
		sta COLUPF		;+3	*34*	Set board color
		lda block1r,y		;+4	38
		sta PF1			;+3	41
		lda block2r,y		;+4	45
		sta PF2			;+3	*48*	Update right half of playfield
		SLEEP 3			;+3	51
		lda pcol		;+3	54
		dey			;+2	56
		bne .loop2		;+4/+2	60/58
		REQUIRED_BOUNDARY rows1thru14,"rows1thru14 OUTSIDE"

		sta COLUPF		;+3	61
		jmp .enterHere1		;+3	64
row15
.loop3
		SLEEP 4			;+4	64
.enterHere1	dex			;+2	66
		cpx #ballHeight		;+2	68
		bcs skipdraw2a		;+2/+3	70/71
		lda ballShape,x		;+4	74
		SLEEP 2			;+2	76
skipreturn2a	sta GRP1		;+3	3
		SLEEP 3			;+3	6
		lda p1col		;+3	9
		sta COLUPF		;+3	12
		lda block1l,y		;+4	16
		sta PF1			;+3	*19*	Update left half of playfield
		lda block2l,y		;+4	23
		sta PF2			;+3	26
		SLEEP 8			;+8	34
		lda block1r,y		;+4	38
		sta PF1			;+3	41
		lda block2r,y		;+4	45
		sta PF2			;+3	*48*	Update right half of playfield
		SLEEP 4			;+4	52
		dec lineCount		;+5	57
		bne .loop3		;+3/2	*60*/59
		WARN_BOUNDARY row15,"row15 INSIDE"
					
		dex			;+2	61
		cpx #ballHeight		;+2	63
		bcs skipdraw2b		;+2/+3	65/66
		lda ballShape,x		;+4	69
		SLEEP 2			;+2	71
skipreturn2b	sta GRP1		;+3	74
		SLEEP 12		;+12	10
		lda block1l,y		;+4	14
		sta PF1			;+3	*17*	Update left half of playfield
		lda block2l,y		;+4	21
		sta PF2			;+3	24
		lda #rowHeight-1	;+2	26
		sta lineCount		;+3	29
		SLEEP 5			;+5	34
		lda block1r,y		;+4	38
		sta PF1			;+3	41
		lda block2r,y		;+4	45
		sta PF2			;+3	*48*	Update right half of playfield
		dey			;+2	50
		WARN_BOUNDARY row15,"row15 OUTSIDE"

		SLEEP 26		;+26	76	WHEW!
		
		lda #$00		;Clear playfield (1 scanline)
		sta PF0
		sta PF1
		sta PF2
		sta GRP1
		rts

skipdraw2a	lda #0			;+2	75
		jmp skipreturn2a	;+3	2
		
skipdraw2b	lda #0			;+2	68
		jmp skipreturn2b	;+3	71


setColors	bit gameFlags
		bmi .palColors
		lda #<rowColor
		sta tryX
		lda #ballHighlight
		sta COLUP0
		lda #ballShadow
		sta COLUP1
		jmp startGameKernel
.palColors	lda #<PAL_rowColor
		sta tryX
		lda #PAL_ballHighlight
		sta COLUP0
		lda #PAL_ballShadow
		sta COLUP1
		jmp startGameKernel

;
;	scoreArea (22 scanlines)
;
scoreArea subroutine
		lda #0
		sta GRP0
		sta GRP1

		lda gameMode
		and #mode_twoscores
		beq .oneScore
.twoScores
		sta WSYNC		; Sync up so we can do some rough positioning
		sleep 29
		sta RESP0		; Position left score
		sleep 26
		sta RESP1		; Position right score
		sta WSYNC		; Sync again and
		lda #$0E		; Set color
		sta COLUBK		; Store in register
		sta WSYNC
		lda #$00		; Set color
		sta COLUBK		; Store in register
		lda p0col
		sta COLUP0
		lda p1col
		sta COLUP1
		ldy #15
		
.loop		sta WSYNC
		sty lineCount
		tya
		lsr
		tay
		lda (p1scoreDigit),y
		sta GRP0
		lda (p2scoreDigit),y
		sta GRP1
		ldy lineCount
		dey
		bne .loop
		
		sta WSYNC
		sta WSYNC
		lda #0
		sta GRP0
		sta GRP1
		
		jmp .end

.oneScore	sta WSYNC		; Score positioning begins at hblank
					; Thus, the counting begins...
					;cycles	total	pixel position
		SLEEP 37		;37	37	43px	(43px = 37 cycles * 3 - 68 hblank)
		sta RESP0		;+3	40	52px	Since TIA moves at 3 color clocks [or pixels]
		sta RESP1		;+3	43	61px	per cycle, we have to multiply cycles by 3.
		lda #$10		; Nudge P0 1px left (P0 is at 51px, theoretically)
		sta HMP0
		lda #$20		; Nudge P1 2px left (p1 is at 59px, theoretically)
		sta HMP1
		sta WSYNC		; Sync again and
		lda #$0E		; Set color
		sta COLUBK		; Store in register

		bit gameFlags
		bmi .pal0
		lda #scoreColor
		jmp .endPal0
.pal0		lda #PAL_scoreColor
.endPal0	sta COLUP0		; Set the color registers for P0
		sta COLUP1		; And P1
		lda #1
		sta VDELP0		; Set the delay registers for P0
		sta VDELP1		; And P1 ... Why?  Visit this link:
					; http://www.biglist.com/lists/stella/archives/199704/msg00137.html
		lda #3
		sta NUSIZ0		; Set the number size registers to show three clones for P0
		sta NUSIZ1		; And (you guessed it) P1

		ldy #15			; This will be our row counter for the score
					; Counts down 15 to 0

scoreKernel
		sta WSYNC		; And let's count again...
		sta HMOVE		;+3	3 Move
		lda #scoreBgColor	;+2	5	Set background color for score
		sta COLUBK		;+3	8	Store in register
		SLEEP 54		;+54	62	The score loop doesn't use WSYNC,
					;		so we have to burn cycles to kind
					;		of "hit the ground running"
.score
					;cycles	total	pixel position
		sty lineCount		;+3	65	Y is multi-use, so we have to store it
		tya			;+2	70	Move Y to A
		lsr			;+2	72	and divide by 2
		tay			;+2	74	to give us double height Y offset
		lda (digit0),y		;+5	76
					; This is where HBLANK begins!
					; Don't need WSYNC when exactly 76 cycles are used
		sta GRP0		;+3	3
		lda (digit1),y		;+5	8
		sta GRP1		;+3	11	VDELP0/1 are integral to how this works
		lda (digit2),y		;+5	16	See an EXCELLENT description of exactly
		sta GRP0		;+3	19	what's happening here:
		lda (digit5),y		;+5	24
		sta temp		;+3	27	http://www.biglist.com/lists/stella/archives/199704/msg00137.html
		lda (digit4),y		;+5	32
		tax			;+2	34
		lda (digit3),y		;+5	39
		ldy temp		;+3	42	1st digit begins displaying here
		sta GRP1		;+3	45	(see score positioning above to see why)
		stx GRP0		;+3	48
		sty GRP1		;+3	51
		sta GRP0		;+3	54
		ldy lineCount		;+3	57
		dey			;+2	59
		bpl .score		;+3	62
		
		sta WSYNC		; Blank row
		WARN_BOUNDARY scoreKernel,"scoreKernel"
		lda #0			; Here, we reset the graphics registers
		sta GRP0
		sta GRP1
		sta GRP0
		sta GRP1
		sta VDELP0
		sta VDELP1
		sta NUSIZ0
		sta NUSIZ1
		
.end		sta WSYNC		; White line below score
		lda #$0E
		sta COLUBK
		sta WSYNC		; Black row

		lda #bgColor
		bit gameFlags
		bpl .skipPalColors
		lda #PAL_bgColor
.skipPalColors	sta COLUBK
		rts
		
;
;	positionBall	(2 scanlines)
;
positionBall subroutine
		ldx #1

; Positioning code by: RMundschau on Stella list (THANKS!)
; Inputs:  A = Desired position.
;          X = Desired object to be positioned (0-5).
; Outputs: X = unchanged
;          A = Fine Adjustment value.
;          Y = the "remainder" of the division by 15 minus an additional 15.

		lda ballx
		sta WSYNC		; 00  Sync to start of scanline.
		sec			; 02  Set the carry flag so no borrow will be applied during the division.
positionBallLoop1
.loop1
		sbc #15			; 04 ; Waste the necessary amount of time dividing X-pos by 15!
		bcs .loop1		; 06/07 - 11/16/21/26/31/36/41/46/51/56/61/66
		WARN_BOUNDARY positionBallLoop1,"positionBallLoop1"
		
		tay			; 08 ;  At this point the value in A is -1 to -15.   In this code I use a table
					;       to quickly convert that value to the fine adjust value needed.
		lda fineAdjustTable,Y	; 13 -> Consume 5 cycles by guaranteeing we cross a page boundary
		sta HMP0,X		; 17 	Store the fine adjustment value.
		sta RESP0,X		; 21/ 26/31/36/41/46/51/56/61/66/71 - Set the rough position.

		sta WSYNC		
		sta HMOVE
		rts
	
; ------------------------------------
; Data
; ------------------------------------
ballShape	dc.b %00001000		; Ball
		dc.b %00010100
		dc.b %00011000
		dc.b %00011100
		dc.b %00011100
		dc.b %00001000
ballHeight	equ *-ballShape

titleData	
titlePF1l	dc.b %10000101
		dc.b %10000101
		dc.b %10000101
		dc.b %10000101
		dc.b %10000110
		dc.b %10000101
		dc.b %10000101
		dc.b %10000111

titlePF2l	dc.b %00101110
		dc.b %00100010
		dc.b %00100010
		dc.b %00100010
		dc.b %01100110
		dc.b %00100010
		dc.b %00100010
		dc.b %11101110

titlePF2r	dc.b %01101110
		dc.b %01001000
		dc.b %01001000
		dc.b %01001000
		dc.b %01001100
		dc.b %01001000
		dc.b %01001000
		dc.b %01001110

titlePF1r	dc.b %10000101
		dc.b %10000101
		dc.b %10000101
		dc.b %10000101
		dc.b %10000010
		dc.b %10000101
		dc.b %10000101
		dc.b %10000101
		;WARN_BOUNDARY titleData,"titleData"		; (Page crossing OK here)
		
rowColorData
rowColor	dc.b paddleColor	; Paddle and brick color (NTSC)
		dc.b paddleColor
		dc.b paddleColor
		dc.b paddleColor
		dc.b lightred+4
		dc.b lightred+2
		dc.b lightred
		dc.b lightred-2
		dc.b lightblue+4
		dc.b lightblue+2
		dc.b lightblue
		dc.b lightblue-2
		dc.b paddleColor
		dc.b paddleColor
		dc.b paddleColor
		dc.b paddleColor
		
		IF !SECAM_PALETTE
PAL_rowColor	dc.b PAL_paddleColor	; Paddle and brick color (PAL)
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_lightred+4
		dc.b PAL_lightred+2
		dc.b PAL_lightred
		dc.b PAL_lightred-2
		dc.b PAL_lightblue+4
		dc.b PAL_lightblue+2
		dc.b PAL_lightblue
		dc.b PAL_lightblue-2
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor

		ELSE

PAL_rowColor	dc.b PAL_paddleColor	; Paddle and brick color (SECAM)
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_lightred
		dc.b PAL_lightred
		dc.b PAL_lightred
		dc.b PAL_lightred
		dc.b PAL_lightblue
		dc.b PAL_lightblue
		dc.b PAL_lightblue
		dc.b PAL_lightblue
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		dc.b PAL_paddleColor
		
		ENDIF
		WARN_BOUNDARY rowColorData,"rowColorData"

livesTab	dc.b %00000000		; Also used for VU meters
		dc.b %01000000
		dc.b %01010000
		dc.b %01010100
		dc.b %01010101
		WARN_BOUNDARY livesTab,"livesTab"

singleBrickTab	dc.b %10000000		; Thanks Manuel! :)
		dc.b %01000000
		dc.b %00100000
		dc.b %00010000
		dc.b %00001000
		dc.b %00000100
		dc.b %00000010
		dc.b %00000001
		WARN_BOUNDARY singleBrickTab,"singleBrickTab"

doubleBrickTab	dc.b %11000000
		dc.b %11000000
		dc.b %00110000
		dc.b %00110000
		dc.b %00001100
		dc.b %00001100
		dc.b %00000011
		dc.b %00000011
		WARN_BOUNDARY doubleBrickTab,"doubleBrickTab"

dx1		equ 8			; Ball velocity lookup 
dx2		equ 22
dx3		equ 33
dx4		equ 39

dy1		equ (dx4*2)
dy2		equ (dx3*2)
dy3		equ (dx2*2)
dy4		equ (dx1*2)

deltaXTab	dc.b dx1,dx2,dx3,dx4
deltaYTab	dc.b dy1,dy2,dy3,dy4

englishTab	dc.b -2			; "English" as in paddle english,
		dc.b -1			; not language... okay?
		dc.b  0
		dc.b  0
		dc.b  0
		dc.b  0
		dc.b  1
		dc.b  2
		
gameModes	dc.b mode_joystick | mode_oneplayer  | mode_onescore	; Game mode 0
		dc.b mode_joystick | mode_twoplayers | mode_twoscores	; Game mode 1
		dc.b mode_joystick | mode_twoplayers | mode_onescore	; Game mode 2
		dc.b mode_driving  | mode_oneplayer  | mode_onescore	; Game mode 3
		dc.b mode_driving  | mode_twoplayers | mode_twoscores	; Game mode 4
		dc.b mode_driving  | mode_twoplayers | mode_onescore	; Game mode 5
		;WARN_BOUNDARY gameModes,"gameModes"		; (Page crossing OK here)
gameModesAvailable	dc.b *-gameModes
	
spinRightTab	dc.b $DD		; 1100 -> 1101
		dc.b $FF		; 1101 -> 1111
		dc.b $CC		; 1110 -> 1100
		dc.b $EE		; 1111 -> 1110
		WARN_BOUNDARY spinRightTab,"spinRightTab"

spinLeftTab	dc.b $EE		; 1100 -> 1110
		dc.b $CC		; 1101 -> 1100
		dc.b $FF		; 1110 -> 1111
		dc.b $DD		; 1111 -> 1101
		WARN_BOUNDARY spinLeftTab,"spinLeftTab"

playerMask	dc.b $F0
		dc.b $0F
		WARN_BOUNDARY playerMask,"playerMask"

		IF CHAR_SET==0
digitData	hex 7C FE C6 C6 C6 FE 7C 00	;0
		hex 18 18 18 18 18 18 38 38	;1
		hex FE FE C0 FC 7E 06 FE FC	;2
		hex FC FE 06 FE FE 06 FE FC	;3
		hex 06 06 06 7E FE C6 C6 C6	;4
		hex FC FE 06 FE FC C0 FE FE	;5
		hex 7C FE C6 FE FC C0 FC 7C	;6
		hex 06 06 06 06 06 06 FE FC	;7
		hex 7C FE C6 FE FE C6 FE 7C	;8
		hex 7C 7E 06 7E FE C6 FE 7C	;9
		hex 00 FE 86 AA AA AA FE 00	;WIN
		hex 00 FE C6 DE DE DE FE 00	;LOSE
		WARN_BOUNDARY digitData,"digitData"
		ELSE
digitData	hex 7C C6 C6 C6 C6 C6 C6 7C	;0
		hex FE 18 18 18 18 78 38 18	;1
		hex FE C0 60 3C 06 06 C6 7C	;2
		hex 7C C6 06 06 3C 06 C6 7C	;3
		hex 0C 0C FE CC CC CC 6C 0C	;4
		hex 7C C6 06 06 FC C0 C0 FE	;5
		hex 7C C6 C6 C6 FC C0 60 38	;6
		hex 30 30 30 30 18 0C 06 FE	;7
	 	hex 7C C6 C6 C6 7C C6 C6 7C	;8
		hex 38 0C 06 7E C6 C6 C6 7C	;9
		hex 00 FE 86 AA AA AA FE 00	;WIN
		hex 00 FE C6 DE DE DE FE 00	;LOSE
		WARN_BOUNDARY digitData,"digitData"
		ENDIF

fineAdjustBegin	dc.b	%01110000	; Left 7	
		dc.b	%01100000	; Left 6
		dc.b	%01010000	; Left 5
		dc.b	%01000000	; Left 4
		dc.b	%00110000	; Left 3
		dc.b	%00100000	; Left 2
		dc.b	%00010000	; Left 1
		dc.b	%00000000	; No movement.
		dc.b	%11110000	; Right 1
		dc.b	%11100000	; Right 2
		dc.b	%11010000	; Right 3
		dc.b	%11000000	; Right 4
		dc.b	%10110000	; Right 5
		dc.b	%10100000	; Right 6
		dc.b	%10010000	; Right 7
fineAdjustTable EQU fineAdjustBegin - %11110001  ; NOTE: %11110001 = -15
		WARN_BOUNDARY fineAdjustBegin,"fineAdjustBegin"

optionData
optionJS	dc.b %01111110
		dc.b %10100001
		dc.b %11010001
		dc.b %10100001
		dc.b %11111111
		dc.b %11100111
		dc.b %11011011
		dc.b %11111111

		dc.b %01010000
		dc.b %00011000
		dc.b %00010000
		dc.b %00011000
		dc.b %00010000
		dc.b %00011000
		dc.b %00010000
		dc.b %00011000
		
optionDC	dc.b %00011000
		dc.b %00111100
		dc.b %01111110
		dc.b %01011010
		dc.b %11011011
		dc.b %10011001
		dc.b %10011001
		dc.b %10111101

		dc.b %11111111
		dc.b %11000011
		dc.b %10000001
		dc.b %11000011
		dc.b %01000010
		dc.b %01100110
		dc.b %00111100
		dc.b %00011000
		WARN_BOUNDARY optionData,"optionData"
		
levelData	dc.b %00001100		; One entire compressed level!
		
levelDataTable	dc.b %00010101		; Huffman table used for decompression
		dc.b $80 | 2
		dc.b %00010000
		dc.b %01000000

levelBrickTable	dc.b %00000000		; Brick layout lookup table
		dc.b %11000000
		dc.b %00110000
		dc.b %11110000
		dc.b %00001100
		dc.b %11001100
		dc.b %00111100
		dc.b %11111100
		dc.b %00000011
		dc.b %11000011
		dc.b %00110011
		dc.b %11110011
		dc.b %00001111
		dc.b %11001111
		dc.b %00111111
		dc.b %11111111
		
		echo "Level Data Length:",[*-levelData]d


		IF TITLE_SONG==0	; Devil's Workshop
		echo "Title Song: Devil's Workshop"
melodyChannel	dc.b 4			; High notes 
		dc.b 10			; Low notes
beatChannel	equ 8			; White noise
beatHi		equ $80 | 8		; Snare drum
beatLo		equ $80 | 31		; Bass drum

noteData0	dc.b -96
		dc.b 30,96
		dc.b 27,48
		dc.b 25,48
		dc.b 30,72
		dc.b 27,24
		dc.b -96
		dc.b 30,96
		dc.b 25,24
		dc.b 25,24
		dc.b 25,48
		dc.b 27,72
		dc.b 27,24
		dc.b -96
		dc.b 30,96
		dc.b 27,96
		dc.b 25,96
		dc.b -96
		dc.b 30,96
		dc.b 27,96
		dc.b 25,96
		dc.b -96
		dc.b -96
measureLength0	equ *-noteData0

noteData1	dc.b 17,23
		dc.b beatLo,1
		dc.b beatHi,24
		dc.b 15,24
		dc.b 15,23
		dc.b beatLo,1
measureLength1	equ *-noteData1

noteOffset	dc.b 0
measureLength	dc.b measureLength0
		dc.b measureLength1
		echo "Music Data Length:",[*-noteData0]d
		ENDIF
		
		IF TITLE_SONG==1	; Lost in Legoland
		echo "Title Song: Lost in Legoland"
melodyChannel	dc.b 7			; High notes 
		dc.b 6			; Low notes
beatChannel	equ 8			; White noise
beatHi		equ $80 | 8		; Snare drum
beatLo		equ $80 | 31		; Bass drum

noteData0
		IF 0			; 0 = Music, 1 = Silence
		dc.b -96
		ELSE
		dc.b 16,80
		dc.b 21,20
		dc.b 17,40
		dc.b 17,40
		dc.b 16,60
		dc.b 19,20
		dc.b 14,40
		dc.b 16,60
		dc.b 14,40
		dc.b 21,20
		dc.b 17,40
		dc.b 17,40
		dc.b 16,60
		dc.b 19,20
		dc.b 14,40
		dc.b -20

		dc.b 16,80
		dc.b 21,20
		dc.b 17,40
		dc.b 17,40
		dc.b 16,60
		dc.b 19,20
		dc.b 14,40
		dc.b 16,60
		dc.b 14,40
		dc.b 21,20
		dc.b 17,40
		dc.b 17,40
		dc.b 16,60
		dc.b 19,20
		dc.b 14,40
		dc.b -20

		dc.b -80
		dc.b -80
		dc.b -80
		dc.b -80
		dc.b -80
		dc.b -80
		dc.b -80
		dc.b -80
		ENDIF
measureLength0	equ *-noteData0

noteData1
bassLo		equ 19
bassHi		equ 9
		IF 0			; 0 = Music, 1 = Silence
		dc.b -96
		ELSE
		dc.b bassHi,17
		dc.b beatHi,3
		dc.b -7
		dc.b beatLo,3
		dc.b bassHi,10
		dc.b bassHi,9
		dc.b beatLo,1
		dc.b -9
		dc.b beatLo,1
		dc.b bassLo,9
		dc.b beatHi,1
		dc.b bassLo,10
		dc.b -10
		dc.b bassLo,18
		dc.b beatLo,2
		dc.b -9
		dc.b beatHi,1
		
		dc.b -9
		dc.b beatLo,1
		dc.b bassLo,8
		dc.b beatHi,2
		dc.b bassHi,19
		dc.b beatLo,1
		ENDIF
measureLength1	equ *-noteData1

noteOffset	dc.b 0
measureLength	dc.b measureLength0
		dc.b measureLength1
		echo "Music Data Length:",[*-noteData0]d
		ENDIF

		IF TITLE_SONG==2	; new
		echo "Title Song: new"
melodyChannel	dc.b 4			; High notes 
		dc.b 10			; Low notes
beatChannel	equ 8			; White noise
beatHi		equ $80 | 8		; Snare drum
beatLo		equ $80 | 31		; Bass drum

noteData0
		dc.b 28,12
		dc.b 28,12
		dc.b 28,12
		dc.b 28,12

		dc.b 27,12
		dc.b 27,12
		dc.b 27,12
		dc.b 27,12

		dc.b 25,12
		dc.b 25,12
		dc.b 25,12
		dc.b 25,12

		dc.b 30,12
		dc.b 30,12
		dc.b 30,12
		dc.b 30,12
measureLength0	equ *-noteData0

noteData1
		dc.b beatHi,24
		dc.b -23
		dc.b beatLo,1
		dc.b beatHi,24
		dc.b -23
		dc.b beatLo,1
measureLength1	equ *-noteData1

noteOffset	dc.b 0
measureLength	dc.b measureLength0
		dc.b measureLength1
		echo "Music Data Length:",[*-noteData0]d
		ENDIF
		
;
;	startCart
;
startCart subroutine
		CLEAN_START		; Clear mem/setup registers
resetGame
		ldx #$ff		; Reset stack pointer
		txs
		jsr initGame
		jsr startOptions
		jmp mainLoop

;
;	initGame
;
initGame subroutine
		lda #%00000101		; Reflect playfield and bring to front
		sta CTRLPF

		lda #$00		; PF0 will always be empty
		sta PF0
		
		lda #bgColor		; Initialize background color
		sta COLUBK
		
;		sta collideFlag		; Clear collision flag
		sta CXCLR		; Clear collision registers

		lda #>digitData		; Set the MSB for each of our digit pointers
		sta digit0+1		; They are all the same since they don't 
		sta digit1+1		; cross any page boundaries
		sta digit2+1
		sta digit3+1
		sta digit4+1
		sta digit5+1
		
		lda #0
		sta SWACNT
		sta SWBCNT
		
		lda SWCHB		; Check color/bw switch for ntsc/pal switching
		and #flag_lastcbsw	; Isolate color/bw switch
		ora gameFlags		; Set color/bw flag in gameFlags based on switch
		sta gameFlags		; Basically initialize the flag to prevent unwanted mode switch
		
		lda SWCHB		; Now check for power-on video mode
		lsr			; Check for Reset button (PAL50)
		bcs .checkSelect	; If carry set, then button not held
		lda gameFlags		; Else, set gameFlags to PAL50
		ora #%11000000
		sta gameFlags
		bne .setDefaults
		
.checkSelect	lsr			; Check for Select button (PAL60)
		bcs .setDefaults	; If carry set, then button not held
		lda gameFlags		; Else, set gameFlags to PAL60
		and #%00111111
		ora #%10000000
		sta gameFlags

		
.setDefaults	ldy #DEFAULT_GAME_MODE	; Set default game mode flags
		lda gameModes,y
		sta gameMode
		lda #DEFAULT_GAME_MODE
		asl
		asl
		asl
		asl
		asl
		ora gameMode
		ora #mode_switched
		sta gameMode

		jsr setPaddleColors
				

.continue
		jsr updateScore		; Initialize score pointers	

		;lda gameFlags		; Force 50Hz
		;ora #flag_pal50
		;sta gameFlags

		rts

;
;	mainLoop
;
mainLoop subroutine
		lda gameState		; gameState determines flow of each frame
		and #$0F

;
;	optionsFrame
;		
optionsFrame subroutine
		cmp #state_options
		bne .checkNext
		jsr startVBlank
		jsr calcBallSpeed
		jsr moveBall
		jsr playMusic
		jsr endVBlank
		jsr optionsKernel
		jsr startOverscan
		jsr reflectAndRemove
		jsr readConsole
		jsr endOverscan
		jmp mainLoop
.checkNext

;
;	pregameFrame
;
pregameFrame subroutine
		cmp #state_pregame
		bne .checkNext
		jsr startVBlank
		jsr updateSounds
		jsr pregameTimer
		jsr readControllers
		jsr updatePaddles
		jsr endVBlank
		jsr gameKernel
		jsr startOverscan
		jsr readConsole
		jsr endOverscan
		jmp mainLoop
.checkNext

;
;	ingameFrame
;
ingameFrame subroutine
		cmp #state_ingame
		bne .checkNext
		jsr startVBlank
		jsr updateSounds
		jsr readControllers
		jsr updatePaddles
		IF JOYSTICK_BALL
			jsr joystickBall
		ENDIF
		IF !STATIC_BALL
			jsr calcBallSpeed
			jsr moveBall
		ENDIF
		jsr endVBlank
		jsr gameKernel
		jsr startOverscan
		jsr reflectAndRemove
		jsr readConsole
		jsr endOverscan
		jmp mainLoop
.checkNext

;
;	balloutFrame
;
balloutFrame subroutine
		cmp #state_ballout
		bne .checkNext
		jsr startVBlank
		jsr updateSounds
		lda #0
		jsr updatePaddles
		jsr endVBlank
		jsr gameKernel
		jsr startOverscan
		jsr readConsole
		jsr deathTimer
		jsr endOverscan
		jmp mainLoop
.checkNext

;
;	clearFrame
;
clearFrame subroutine
		cmp #state_clear
		bne .checkNext
		jsr startVBlank
		jsr updateSounds
		lda #0
		jsr updatePaddles
		jsr clearTimer
		jsr endVBlank
		jsr gameKernel
		jsr startOverscan
		jsr readConsole
		jsr endOverscan
		jmp mainLoop
.checkNext

;
;	invalid state...
;
		brk			; If this gets executed, then I screwed up somewhere!
					; BRK will reset everything.

;
;	startVBlank
;
startVBlank subroutine
		VERTICAL_SYNC
		bit gameFlags		; Let's see if the 50Hz flag is set
		bvs .pal50		; It's stored in the overflow bit, so check that
		lda  #43		; 60Hz mode
		sta  TIM64T
		rts
.pal50		lda  #73		; 50Hz mode
		sta  TIM64T
		rts
		
;
;	endVBlank
;
endVBlank subroutine
.wait		lda INTIM		; Wait for timer to expire
		bne .wait
		sta WSYNC	
		sta VBLANK 
		rts

;
;	startOptions
;
startOptions subroutine
		lda #11
		sta ballSpeed
		lda #13
		sta ballDirection

		lda #ballStartX		; Set ball starting position
		sta ballx
		lda #ballStartY
		sta bally
		lda #0
		sta ballx+1
		sta bally+1
		lda #$FF

.topborder	sta block1l
		sta block2l
		sta block2r
		sta block1r
		
.bottomborder	sta block1l+15
		sta block2l+15
		sta block2r+15
		sta block1r+15

		lda #$80
		ldx #14
.sideborders	sta block1l,x
		sta block1r,x
		dex
		bne .sideborders
		
		ldy #7			; Draw REFLEX title screen
.loop		lda titlePF1l,y
		sta block1l+4,y
		lda titlePF2l,y
		sta block2l+4,y
		lda titlePF2r,y
		sta block2r+4,y
		lda titlePF1r,y
		sta block1r+4,y
		dey
		bpl .loop
		lda #state_options
		sta gameState
		lda #0			; Silence!
		sta AUDV0
		sta AUDV1
		sta noteCounter
		sta noteCounter+1
		lda #1
		sta noteTimer
		sta noteTimer+1
		rts

;
;	playMusic
;
playMusic subroutine
					; This fun "little" music routine was inspired by the
					; one Kirk Israel wrote in his excellent game JoustPong.
					; http://www.alienbill.com/joustpong/

					; It's a bit heavier than Kirk's, I think, but it
					; does volume decay and allows notes and percussion
					; in both channels.  The data's also a little compressed since
					; rests are stored as a single negative byte while notes
					; and percussion are stored as a word (freq,duration).
					; For a note to be percussion, set freq to a negative value.
					; As usual, music data is stored backwards.
					
		ldx #1			; Loop through audio channels 1 and 0
.loop		dec noteTimer,x		; Decrement the note timer
		bne .noteContinue	; If not 0, then note continues to play
		lda noteCounter,x	; Else examine the note counter
		bne .noteChange		; If positive, then get next note
		lda measureLength,x	; Else reset the counter
		sta noteCounter,x	; ...and save it
.noteChange	dec noteCounter,x	; Decrease the note counter
		lda noteCounter,x	; ...and load it
		clc			; Get ready for addition
		adc noteOffset,x	; Add the note data offset value for channel X
		tay			; Transfer to Y for indexing
		lda noteData0,y		; Get new note length
		bmi .rest		; If negative, then it's a rest
		sta noteTimer,x		; Else, store it in the note timer
		dec noteCounter,x	; Prepare to get note pitch
		dey			; Decrease the note data index
		lda noteData0,y 	; Get new note pitch
		bmi .percussion		; If negative, then it's percussion
		sta AUDF0,x		; Else, set pitch
		lda melodyChannel,x
		sta AUDC0,x		; ...and channel (note waveform)
		bne .setVolume		; Unconditional branch
.percussion	and #%01111111		; Mask out negative bit
		sta AUDF0,x		; Set frequency
		lda #beatChannel
		sta AUDC0,x		; Set channel (percussion waveform)
.setVolume	lda #musicVolume	; Read in constant (basically max volume)
		sta noteVolume,x	; Save it
		sta AUDV0,x		; ...and set volume
		bne .skipVolume		; Unconditional branch
.rest		eor #$FF		; If rest, then get 2's complement
		clc
		adc #1
		sta noteTimer,x		; Save rest duration
		lda #0
		sta noteVolume,x
		sta AUDV0,x 		; Set the volume to 0 for rest
		beq .skipVolume
.noteContinue	ldy noteVolume,x	; See if we should decrease the note volume...
		beq .skipVolume		; If volume at 0, then do nothing
		dey			; Else decrease volume
		sty noteVolume,x	; Save it
		sty AUDV0,x		; Set volume
.skipVolume	dex			; Decrease channel counter
		bpl .loop		; Loop if positive
		rts

;
;	decompressLevel
;
decompressLevel subroutine

block		equ blockX		; Base block pointer
blockExtra	equ tryX		; Base "extra" block pointer
blockIndex	equ offsetX		; Board offset index
treeIndex	equ offsetY		; Huffman tree index

		lda #80			; OKGE04
		sta blockCount		; Solid board for gaming expo demo
		
		lda #7
		sta blockIndex		; Start at the top of the board
		lda #0			; Init vars...
		sta treeIndex		; Start at the root of the huffman tree
		sta block+1		; Initialize high bytes of block pointers
		sta blockExtra+1
		lda #block2l+4		; Initialize low bytes of block pointers
		sta block
		lda #block1l+4
		sta blockExtra
		ldx levelByte		; Prepare to grab a byte of compressed data
		lda levelData,x		; Load A with compressed byte
		ldx levelBit		; Prepare to locate bit...
.bitLoop	asl			; Shift bits into carry flag
		dex			; Bits are numbered 0-7 from left to right
		bpl .bitLoop		; Keep looping until x<0
		tay			; Save (shifted) compressed byte for later
		
.lookupLoop	lda #0			; Prep to add carry to treeIndex
		adc treeIndex		; Add carry bit to treeIndex
		inc levelBit		; Keep track of bit location for next call to decompressLevel
		ldx levelBit
		cpx #8			; Compare levelBit to 8
		bne .skipAdjustment	; If level bit < 8, then don't adjust pointers
		ldx #0			; Else, reset levelBit
		stx levelBit
		inc levelByte		; And increase levelByte
		ldx levelByte		; Load levelByte for indexing
		ldy levelData,x		; Load a new compressed level data byte
		
.skipAdjustment	tax			; Prep treeIndex for indexing
		lda levelDataTable,x	; Grab byte from lookup table
		bpl .endNode		; If positive, then it's an end node
		and #%01111111		; Else, mask out negative bit
		sta treeIndex		; Use positive value (0-127) for new treeIndex
		tya			; Load up our level data byte
		asl			; Shift a bit into the carry flag
		tay			; Save the level data byte
		jmp .lookupLoop		; And rotate a new bit into "our byte"		
.endNode	asl			; Move overflow bit (D6) into sign (D7) for easy checking
		bmi .checkForEnd	; If negative, then finished getting data
		lsr			; Shift it back
		sty temp		; Else, save level data byte
		tax			; Save levelDataTable value
		and #%00001111		; Isolate 4-block lookup index
		tay			; Prepare for table indexing
		lda levelBrickTable,y	; Get the brick layout
		ldy blockIndex		; Get destination offset index
		sta (block),y		; Save brick layout to board!
		txa			; Restore levelDataTable value
		and #%00010000		; Isolate "extra" brick bit
		beq .storeBrick		; If zero, then save it
		lda #%00000011		; Else, turn on "extra" brick
.storeBrick	sta (blockExtra),y	; Save "extra" brick to board!

		dec blockIndex		; Prepare to fill next block
		bpl .resetIndex		; If >= 0, then continue to reset index
		lda #block2l+4		; Else, check to see if still drawing on left side
		cmp block
		beq .setRight		; If on left side of screen, jump to right
		rts			; Else, finished drawing board
.setRight	lda #block2r+4		; Set low bytes of block pointers
		sta block
		lda #block1r+4
		sta blockExtra
		lda #7
		sta blockIndex		; Start at the top of the board
.resetIndex	lda #0
		sta treeIndex		; Reset tree index to root
		lda temp		; Retrieve level data byte
		asl			; Shift a bit into the carry flag
		tay			; Save the level data byte
		jmp .lookupLoop		; Get the next byte of compressed data!
		
.checkForEnd	lda #block2l+4		; See if block and blockIndex have changed
		cmp block		; First, check block
		bne .copy		; If changed, then start mirroring
		lda #7			; Else, check blockIndex
		cmp blockIndex
		bne .copy		; If changed, then start mirroring
		lda #0			; Else, reset the compressed data pointers
		sta levelByte
		sta levelBit
		jmp decompressLevel	; ...and try again
		
		echo "Level Decompressor Length:",(*-decompressLevel)d
		
.mirror		ldx #3			; X is used to index copy source on left side
.copy		ldy blockIndex		; Y is used to index copy destination on both sides
		lda #block2l+4		; Check to see which copying function to use
		cmp block
		bne .copyRight		; If block ptr on the right, then branch
.copyLeft	inx			; Copy left side... easy stuff
		lda block2l+4,x		; Now that I'm done with the decompression routine
		sta (block),y		; I need to build the compression routine...
		lda block1l+4,x		; Which will live in an external app... probably online
		sta (blockExtra),y	; I've done it before, but right now, I need to sleep.  Zzzzzz...
		jmp .moveCopyPtr
.copyRight	lda block2l+4,y		; Copy right side... easy stuff
		sta (block),y
		lda block1l+4,y
		sta (blockExtra),y

.moveCopyPtr	dec blockIndex		; Prepare to fill next block
		bpl .copy		; If >= 0, then continue to reset index
		lda #block2l+4		; Else, check to see if still drawing on left side
		cmp block
		beq .setRight2		; If on left side of screen, jump to right
		rts			; Else, finished drawing board
.setRight2	lda #block2r+4		; Set low bytes of block pointers
		sta block
		lda #block1r+4
		sta blockExtra
		lda #7
		sta blockIndex		; Start at the top of the board
		bne .copy

;
;	startPregame
;
startPregame subroutine
		lda #ball_minSpeed
		sta ballSpeed
		lda #2
		sta ballDirection

		ELSE
		lda #3
		sta ballmx
		lda #3
		sta ballmy
		ENDIF

		lda #ballStartX		; Set ball starting position
		sta ballx
		lda #ballStartY
		sta bally
		lda #state_pregame
		sta gameState
		lda #pregameDelay
		sta stateTimer

		lda #0			; Silence!
		sta AUDV0
		sta AUDV1
		lda #12
		sta AUDC0
		rts

;
;	calcBallSpeed
;
calcBallSpeed subroutine
		lda ballDirection	; Convert the 4-bit direction into a 2-bit lookup value
		and #%00000100		; Check for odd/even quadrant
		beq .noInvert		; If even, then just isolate 2-bit lookup value
		lda ballDirection	; Else, invert lookup value
		eor #%00000011		; Invert lookup value
		bpl .getIndex		; Uncondition jump
.noInvert	lda ballDirection	; Reload direction since it was demolished with the AND
.getIndex	and #%00000011		; Isolate the lookup value
		tay			; And transfer it to Y for indexing

		lda deltaXTab,y		; Get X delta value
		sta ballmx+1		; Save it in the ball X movement register (low order)
		sta blockX		; And also in blockX for temporary storage

		lda deltaYTab,y		; Get Y delta value
		sta ballmy+1		; Save it in the ball Y movement register (low order)
		sta blockX+2		; And also in blockX+2 for temporary storage

		lda #0			; Zeroize the high order movement bytes
		sta ballmx		; Update ball X movement register (high order)
		sta ballmy		; Update ball Y movement register (high order)

		ldy #2			; Prepare to loop through X and Y movement registers
.loop		ldx ballSpeed		; Load up the ball speed multiplier
.loopx		clc			; Prepare for addition (multiple times!)
		lda ballmx+1,y		; Grab low order byte for X or Y movement register
		adc blockX,y		; Add that looked-up value
		sta ballmx+1,y		; And save the result
		lda #0			; Reset A to prep for adding the carry bit
		adc ballmx,y		; Add carry bit to the high order byte of the register
		sta ballmx,y		; And save the result
		dex			; Decrease multiplier counter
		bne .loopx		; Loop if > 0
		dey			; Decrease Y counter
		dey			; ... twice, because the movement registers are word length
		bpl .loop		; And loop if it's positive (in this case, zero)
		
		lda ballDirection	
		and #%00001000
		beq .skipNegateX
		sec
		lda #0
		sbc ballmx+1
		sta ballmx+1
		lda #0
		sbc ballmx
		sta ballmx
.skipNegateX		
		lda ballDirection
		clc
		adc #4
		and #%00001000
		beq .skipNegateY
		sec
		lda #0
		sbc ballmy+1
		sta ballmy+1
		lda #0
		sbc ballmy
		sta ballmy
.skipNegateY		
		rts

;
;	pregameTimer
;
pregameTimer subroutine
		dec stateTimer
		bne .end
		lda #state_ingame
		sta gameState
.end		rts

;
;	deathTimer
;
deathTimer subroutine
		jsr updateScore
		dec stateTimer
		bne .end
		lda gameFlags
		and #flag_lives
		bne .takeLife
		jsr startOptions
		rts
.takeLife
		lda gameMode
		and #mode_twoscores
		bne .pregame
		IF !INFINITE_LIVES
		dec gameFlags
		ENDIF
.pregame	jsr startPregame
.end		rts

;
;	clearTimer
;
clearTimer subroutine
		dec stateTimer
		bne .end
		jsr clearBlocks
		lda #0
		sta levelByte
		sta levelBit
		jsr decompressLevel
		jsr resetPaddles
		jsr startPregame
.end		rts

;
;	updateSounds
;
updateSounds subroutine
		lda sfxEcho0		; Load echo value
		beq .end		; If 0, then no sound
		bpl .doTimer		; If positive, then continue sound
		lda #$0F		; Else, initialize new sound
		sta sfxEcho0		; Echo and volume begin at max (15)
		sta sfxVolume0
		lda #sfxEchoDelay	; Echo timer is reset
		sta sfxTimer0
		
.doTimer	dec sfxTimer0		; Decrease echo timer
		bpl .doVolume		; If positive, then continue sound
		lda #sfxEchoDelay	; Else, echo timer is reset
		sta sfxTimer0	
		dec sfxEcho0		; Reduce echo value by 3
		dec sfxEcho0
		dec sfxEcho0
		lda sfxEcho0
		sta sfxVolume0		; Set volume to new echo value
		
.doVolume	lda sfxVolume0		; Load volume
		beq .end		; If zero, then end
		dec sfxVolume0		; Reduce volume by 3
		dec sfxVolume0
		dec sfxVolume0
		lda sfxVolume0
		sta AUDV0		; Actually set volume register
		
.end		rts

;
;	addScore
;
addScore subroutine
		sed			; Decimal mode
		clc			; Clear carry, we're getting ready to add
		adc score+2		; A + LSB (right 2 digits)
		sta score+2
		lda score+1		; Load middle 2 digits
		adc temp		; Add carry and high order byte
		sta score+1
		lda score		; Load first 2 digits
		adc #0			; Add carry
		sta score
		cld			; We're not in decimal mode anymore, Toto		
.end		rts

;
;	updateScore
;
updateScore subroutine
		ldy #5			; Set up score pointers (counts down 5 to 0)
.scoreloop	tya			; 
		lsr			; Divide by 2 to get score byte offset (save carry)
		tax			; Move A to X for use as offset
		lda score,x		; Get 2 digits
		bcc .highnibble		; If carry clear, then use high nibble
		and #$0F		; else isolate low nibble
		asl			; Multiply by 8 (the height of our graphics)
		asl			; A will be used as the LSB in our pointer
		asl
		bcc .endnibble
.highnibble	and #$F0		; Isolate high nibble
		lsr			; Divide by two to get our pointer's LSB
.endnibble	sta temp		; Store LSB
		tya			; Calculate offset for the pointer itself
		asl			; Y*2 because each pointer is a word
		tax			; Move A to X for use as an offset
		lda temp		; Get our pointer's LSB
		clc
		adc #<digitData
		sta digit0,x		; And store it
		dey			; Decrease our counter
		bpl .scoreloop		; Repeat for the next digit
		rts

;
;	setPaddleColors
;
setPaddleColors subroutine
		bit gameFlags
		bmi .pal0
		lda #paddleColor	; Initialize paddle colors
		bne .continue0
.pal0		lda #PAL_paddleColor
.continue0	sta p0col
		sta p1col
		sta pcol

		lda gameMode
		lsr			; Shift the twoplayer bit out
		bcc .end		; If carry clear, then skip two players

.twoPlayer	bit gameFlags
		bmi .pal1
		lda #paddle0Color
		ldx #paddle1Color
		bne .continue1
.pal1		lda #PAL_paddle0Color
		ldx #PAL_paddle1Color
.continue1	sta p0col
		stx p1col
.end		rts
		
;
;	readConsole
;
readConsole subroutine
		lda SWCHB		; Check color/bw switch for ntsc/pal switching
		and #flag_lastcbsw	; Isolate color/bw switch
		sta temp		
		lda gameFlags
		and #flag_lastcbsw
		cmp temp
		beq .checkReset
		lda gameFlags
		bit gameFlags
		bvs .modeNTSC
		bmi .modePAL60
.modePAL50	ora #%10000000
		bne .flipSwitch
.modePAL60	ora #%01000000
		bne .flipSwitch
.modeNTSC	and #%00111111
.flipSwitch	eor #flag_lastcbsw
		sta gameFlags
		jsr setPaddleColors	

.checkReset
		lda SWCHB
		lsr
		bcs .checkSelect
		lda gameMode
		and #mode_switched
		beq .startGame
		rts
.startGame	lda #0			; Reset the BCD score
		sta score
		sta score+1
		sta score+2

		jsr updateScore
		jsr clearBlocks
		lda #0
		sta levelByte
		sta levelBit
		jsr decompressLevel
		jsr resetPaddles
		jsr startPregame
		lda #$ff
		eor #flag_lives
		and gameFlags
		ora #startingLives
		sta gameFlags
		
		jmp .setSwitch
		
.checkSelect	lsr
		bcs .noSelect
		lda gameMode
		and #mode_switched
		bne .end
		lda gameState
		cmp #state_options
		beq .notInGame
		jsr startOptions
		jmp .setSwitch
		
.notInGame	lda #<digitData		; Set the score to display 000000
		ldy #10			; Count down from 10 by 2's (10,8,6,4,2,0)
.scrLoop	sta digit0,y		; Store the LSB of '0' char into digit pointer
		dey			; Decrease Y by 2
		dey
		bpl .scrLoop		; Loop if positive
		lda gameMode
		lsr
		lsr
		lsr
		lsr
		lsr
		clc
		adc #1
		cmp gameModesAvailable
		bne .switch
		lda #0
.switch		tay
		asl
		asl
		asl
		asl
		asl
		sta temp
		lda gameMode
		and #mode_preset
		and #mode_flags
		ora temp
		ora gameModes,y
		ora #mode_switched
		sta gameMode
		jsr setPaddleColors	
		rts

.setSwitch	lda #mode_switched
		ora gameMode
		sta gameMode
		rts
		
.noSelect	lda #mode_switched
		eor #$ff
		and gameMode
		sta gameMode

.end		rts

;
;	readControllers
;
readControllers subroutine
		lda gameMode		; Check for driving controller
		and #mode_driving	; Isolate controller bit
		bne .driving		; If set, then driving
		
.joystick	ldy #1			; Loop through both joysticks
.jsLoop		lda SWCHA		; Check joystick RIGHT bit
		cpy #0			; Check for joystick 0 or 1
		beq .skipJsShift	; If 0, then don't shift SWCHA register
		asl			; Else, shift it left 4 bits
		asl
		asl
		asl
.skipJsShift	sta temp		; Save it because BIT has no immediate mode...
		bit temp		; Copies negative and overflow bits into processor status
.jsRight	bmi .jsLeft		; If joystick RIGHT (negative) bit is set, then check left
		lda #1			; Set movement offset (clockwise)
		jmp .jsEndLoop
.jsLeft		bvs .jsNomove		; If joystick LEFT (overflow) bit is set, then no movement
		lda #-1			; Set movement offset (counterclockwise)
		jmp .jsEndLoop
.jsNomove	lda #0			;
.jsEndLoop	sta paddle0,y
		dey
		bpl .jsLoop
		rts
		
.driving	ldx #1			; Loop through both driving controllers
.dcLoop		lda oldSWCHA		; Load last controller state
		and playerMask,x	; Mask out left or right nibble
		sta temp		; Save nibble
		lda SWCHA		; Load driving controller (joystick) register
		and playerMask,x	; Isolate appropriate nibble
		cmp temp		; Compare with last joystick register
		beq .dcNomove		; If equal, then no movement
		and #%00110011		; Else, isolate controller bits
		cpx #0			; Check for joystick 0 or 1
		bne .skipDcShift	; If 1, then don't shift controller bits
		lsr			; Else, shift them right 4 bits
		lsr
		lsr
		lsr
.skipDcShift	tay			; Transfer controller bits to Y to use as index
		lda spinRightTab,y	; Load A with bit pattern matching a move RIGHT
		and playerMask,x	; Isolate appropriate nibble (again)
		cmp temp		; Compare with last controller state
		beq .dcRight		; If equal, then move right
		lda spinLeftTab,y	; Load A with bit pattern matching a move LEFT
		and playerMask,x	; Isolate appropriate nibble (yet again)
		cmp temp		; Compare with last controller state
		beq .dcLeft		; If equal, then move left
		jmp .dcNoAction		; Else just take the last move value (overspin!)
		
		
		
		
.dcLeft		lda #-1			; Move left (counterclockwise)
		jmp .dcEndLoop
.dcRight	lda #1			; Else, move right (clockwise)
		jmp .dcEndLoop
.dcNomove	lda #0
.dcEndLoop	ldy paddle0,x		; Double the movement if last movement was non-zero
		beq .dcNoDouble		; If zero, then no double
		asl			; Else, double it (make driving controller more nimble)
.dcNoDouble	sta paddle0,x
.dcNoAction	dex
		bpl .dcLoop
		lda SWCHA
		sta oldSWCHA
		rts

;
;	clearBlocks
;
clearBlocks subroutine
		lda #0			; Prepare to clear playfield
		ldx #4*playfieldHeight	; Set up clearing loop
.loop		sta blockData,x		; Clear playfield
		dex			; Decrease loop counter
		bpl .loop		; Branch for loop

;
;	resetPaddles
;
resetPaddles subroutine
		lda #10
		sta paddlePos
		lda #12-4
		sta paddleSize
		ldx #$FC		; One-player paddle
		lda gameMode		; Check for two player mode
		and #mode_twoplayers	; Examine the mode_twoplayers bit...
		beq .setPaddles		; If one player, then set the paddle size
		ldx #14			; Else set up side borders
.sideborders	lda #$80		; Loop to create borders
		ora block1l,x
		sta block1l,x
		lda #$80
		ora block1r,x
		sta block1r,x
		dex
		bne .sideborders
		ldx #$E0		; Two-player paddles
.setPaddles	stx block2l
		stx block2r
		stx block2l+playfieldHeight-1
		stx block2r+playfieldHeight-1
.end		rts

;
;	updatePaddles
;
updatePaddles subroutine
		lda gameMode		; Check for two player mode
		and #mode_twoplayers	; Examine the mode_twoplayers bit...
		beq .onePlayer		; If clear, then onePlayer
		jmp .twoPlayers		; If set, then twoPlayers
		
.onePlayer	lda paddle0		; Examine paddle0 (player 1)
		beq .mirror		; If 0, then just mirror the paddle, don't move it
		bpl .right1p		; If positive, then move right (clockwise)
					; Else, move left (counter clockwise)
.left1p		eor #$FF		; Convert paddle0 to positive integer
		tay			; Transfer to Y
		iny			; And increment to complete the two's complement math
.left1pLoop	rol block1r+14		; Slide bottom of paddle to the right
		ror block1l		; The ROLs and RORs compensate for PF graphics flip-floppiness
		rol block2l
		ror block2r
		rol block1r
		php			; Save processor flags (only need Carry bit)
		ldx #13			; Loop through right side of paddle
.loop1		rol block1r,x		; Rotate bits out...
		ror block1r+1,x		; ... and in to achieve upward slide
		dex
		bne .loop1
		plp			; Get that Carry bit we had put into cold storage with PHP
		ror block1r+1,x		; And rotate it into the bottom bit of the right side of paddle
		inc paddlePos		; Bottom/right paddle position increase
		lda paddlePos
		cmp #46
		bmi .left1pSkip		; If paddlePos is positive, then we're good...
		lda #0
		sta paddlePos
.left1pSkip	dey
		bne .left1pLoop		; Do this as many times as paddle0 dictates
		jmp .mirror

.right1p	tay			; Transfer paddle0 to Y
.right1pLoop	rol block1r+1		; Slide bottom of paddle to the left
		ror block1r
		rol block2r
		ror block2l
		rol block1l
		php			; Save processor flags (only need Carry bit)
		ldx #1			; Loop through right side of paddle
.loop2		rol block1r+1,x		; Rotate bits out...
		ror block1r,x		; ... and in to achieve downward slide
		inx
		cpx #14			; Have to compare before branch because this is an incrementing loop
		bne .loop2
		plp			; Get Carry flag
		ror block1r,x		; Rotate it into the rightmost bit of the bottom of paddle
		dec paddlePos		; Bottom/right paddle position decrease
		bpl .right1pSkip	; If paddlePos is positive, then we're good...
		lda #45
		sta paddlePos
.right1pSkip	dey
		bne .right1pLoop	; Do this as many times as paddle0 dictates
		jmp .mirror

.mirror
		;lda paddlePos		; Paddle debug
		;sta block2r+1		; Paddle debug

		lda block1l		; Now that we've moved the bottom-right, copy it to the top-left
		sta block1r+playfieldHeight-1
		lda block2l
		sta block2r+playfieldHeight-1
		lda block2r
		sta block2l+playfieldHeight-1
		lda block1r
		sta block1l+playfieldHeight-1
		ldy #14			; We need two counters in this loop... Y goes down
		ldx #1			; And X goes up
.mirrorLoop	lda block1r,y		; Load a byte from the right side
		and #%10000000		; Isolate the paddle bit
		sta temp		; Save it
		lda #%01111111		; Load the mask for the left side
		and block1l,x		; Get everything in the corresponding left byte EXCEPT paddle bit
		ora temp		; Apply the paddle bit from the right to the left byte
		sta block1l,x		; And save it (Code's probably easier to read than these comments!)
		inx
		dey
		bne .mirrorLoop
		
		rts

.twoPlayers	lda paddle0		; Examine paddle0 (player 1)
		beq .player2
		bpl .p1Right

.p1Left		eor #$FF		; Convert paddle0 to positive integer
		tay			; Transfer to Y
		iny			; And increment to complete the two's complement math
.p1LeftLoop	lda #%10000000
		and block1l+playfieldHeight-1
		bne .p1LeftEnd
		lsr block1r+playfieldHeight-1	; The ROLs and RORs compensate for PF graphics flip-floppiness
		rol block2r+playfieldHeight-1
		ror block2l+playfieldHeight-1
		rol block1l+playfieldHeight-1
.p1LeftEnd	dey
		bne .p1LeftLoop		; Do this as many times as paddle0 dictates
		jmp .player2
		
.p1Right	tay
.p1RightLoop	lda #%10000000
		and block1r+playfieldHeight-1
		bne .p1RightEnd
		lsr block1l+playfieldHeight-1	; The ROLs and RORs compensate for PF graphics flip-floppiness
		rol block2l+playfieldHeight-1
		ror block2r+playfieldHeight-1
		rol block1r+playfieldHeight-1
.p1RightEnd	dey
		bne .p1RightLoop	; Do this as many times as paddle0 dictates

.player2	lda paddle1		; Examine paddle1 (player 2)
		beq .end
		bpl .p2Right

.p2Left		eor #$FF		; Convert paddle1 to positive integer
		tay			; Transfer to Y
		iny			; And increment to complete the two's complement math
.p2LeftLoop	lda #%10000000
		and block1l
		bne .p2LeftEnd
		lsr block1r		; The ROLs and RORs compensate for PF graphics flip-floppiness
		rol block2r
		ror block2l
		rol block1l
.p2LeftEnd	dey
		bne .p2LeftLoop		; Do this as many times as paddle1 dictates
		rts
		
.p2Right	tay
.p2RightLoop	lda #%10000000
		and block1r
		bne .p2RightEnd
		lsr block1l		; The ROLs and RORs compensate for PF graphics flip-floppiness
		rol block2l
		ror block2r
		rol block1r
.p2RightEnd	dey
		bne .p2RightLoop	; Do this as many times as paddle1 dictates
		rts

.end		rts

;
;	joystickBall (DEBUG)
;
		IF JOYSTICK_BALL
joystickBall subroutine			; For debugging purposes
		lda #0
		sta ballmx+1
		sta ballmy+1
		sta ballmx
		sta ballmy
		bit SWCHA
		bmi .left
		lda #80
		sta ballmx+1
		lda #00
		sta ballmx
.left		bit SWCHA
		bvs .down
		lda #-80
		sta ballmx+1
		lda #-1
		sta ballmx
.down		lda SWCHA
		asl
		asl
		sta temp
		bit temp
		bmi .up
		lda #160
		sta ballmy+1
		lda #0
		sta ballmy
.up		bit temp
		bvs .end
		lda #-160
		sta ballmy+1
		lda #-1
		sta ballmy
.end		rts
		ENDIF

;
;	moveBall
;
moveBall subroutine
		lda bally+1		; 16-bit addition (bally=bally+ballmy)
		clc
		adc ballmy+1
		sta bally+1
		lda bally
		adc ballmy
		cmp #133		; Check Y > ## (Ball wraps around if no obstructions)
		bmi .checkYunder
		jsr ballOut
		jmp .doneWithBally
.checkYunder	cmp #0			; Check Y <= ##
		bpl .doneWithBally
		jsr ballOut
.doneWithBally	sta bally

		lda ballx+1		; 16-bit addition (ballx=ballx+ballmx)
		clc
		adc ballmx+1
		sta ballx+1
		lda ballx
		adc ballmx
		cmp #147		; Check X > ##
		bmi .checkXunder
		jsr ballOut
		jmp .doneWithBallx
.checkXunder	cmp #21			; Check X <= ##
		bpl .doneWithBallx
		jsr ballOut
.doneWithBallx	sta ballx
		rts

;
;	ballOut
;
ballOut subroutine
		ldx #state_ballout
		stx gameState
		ldx #deathDelay
		stx stateTimer
		ldx #0
		stx paddle0
		stx paddle1
		sta temp
		lda gameMode
		and #mode_twoscores
		beq .end
		ldx #1
		lda #133
		cmp temp
		bmi .skip
		inx
.skip		inc score,x
		lda score,x
		cmp #3
		bne .end
		lda gameFlags
		and #(flag_lives^$FF)
		sta gameFlags
.end		lda temp
		rts

;
;	emptyKernel
;
emptyKernel subroutine
		ldy #192
		jsr skipLines
		rts

;
;	optionsKernel
;
optionsKernel subroutine
		jsr scoreArea		;+22	22
		jsr optionsArea		;+20	42
		jsr positionBall	;+2	44
		jsr gameArea		;+130	174
		IF !VU_METERS
		ldy #18
		jsr skipLines		;+18	192
		ELSE
		ldy #10
		jsr skipLines		;+10	184
		jsr vuMetersArea	;+8	192
		ENDIF
		rts

;
;	gameKernel
;
gameKernel subroutine
		jsr scoreArea		;+22	22
		ldy #6
		jsr skipLines		;+6	28
		jsr positionBall	;+2	30
		jsr gameArea		;+130	160
		ldy #11
		jsr skipLines		;+11	161
		jsr livesArea		;+5	166
		ldy #16
		jsr skipLines		;+16	192
		rts

;
;	optionsArea (20 scanlines)
;
optionsArea subroutine
		sta WSYNC
		lda #0
		sta GRP0
		lda #$0c
		sta COLUP0
		sleep 15
		sta RESP0
		;lda #1
		;sta HMP0

		sta WSYNC
		;sta HMOVE

		lda gameMode		; One- or two-player controller options
		and #mode_twoplayers
		sta NUSIZ0		; Double sprite accordingly

		lda gameMode		; Joystick or driving control icon
		and #mode_driving	; Isolate driving control bit
		clc
		adc <#optionJS		; Clean math, takes advantage of driving control bit position
		sta blockPtr		; Set LSB of sprite pointer
		lda >#optionJS
		sta blockPtr+1		; Set MSB of sprite pointer

		ldy #15
		ldx #$0F

.loop		sta WSYNC
		lda (blockPtr),y
		sta GRP0
		dex
		dex
		stx COLUP0
		inx
		dey
		sta WSYNC
		lda (blockPtr),y
		sta GRP0
		stx COLUP0
		dey
		bpl .loop
		sta WSYNC		; Blank row
		lda #0			; Here, we reset the graphics registers
		sta GRP0
		sta NUSIZ0
		sta WSYNC
		rts

;
;	vuMetersArea (8 scanlines)
;
vuMetersArea subroutine
		IF VU_METERS

		SLEEP 7
		sta RESP1
		ldx #-1

.loop		lda noteVolume+1,x
		lsr
		lsr
		tay
		iny
		lda livesTab,y
		sta WSYNC
		sta GRP1
		sta WSYNC
		sta WSYNC

		lda #0
		sta WSYNC
		sta GRP1

		inx
		beq .loop

		rts
		ENDIF
		
;
;	livesArea (5 scanlines)
;
livesArea subroutine
		ldy #5			; Init scanline counter
		lda gameMode		; Check game mode
		and #mode_twoscores	; If a competitive game, then don't show lives
		bne .skipLives
		bit gameFlags
		bmi .palColors
		lda #livesColor		; Set the PF color for lives
		bne .continue
.palColors	lda #PAL_livesColor	; Set the PF color for lives
.continue	sta COLUPF
		lda gameFlags		
		and #flag_lives		; Isolate the life counter from gameFlags
		tax			; Transfer to X for indexing
		lda livesTab,x		; Lookup the bit pattern for x # of lives
		ldx #0			; Set x to 0 for use in left half of non-mirrored PF
.loop0		sta WSYNC		; Wait for next scanline
		stx PF1			; Zeroize left half of PF1
		sleep 40		; Wait until left PF1 drawn before setting right PF1
		sta PF1			; Store lives bit pattern
		dey			; Decrease scanline counter
		bne .loop0		; And loop
		stx PF1			; After loop ends, zeroize PF1 again
		rts
.skipLives	sta WSYNC
		dey
		bne .skipLives
		rts

;
;	skipLines (Y scanlines)
;
skipLines subroutine
.loop		sta WSYNC
		dey
		bne .loop
		rts
		
;
;	startOverscan
;
startOverscan subroutine
		lda #2
		bit gameFlags		; Let's see if the 50Hz flag is set
		bvs .pal50		; It's stored in the overflow bit, so check that
		ldx #35			; 60Hz
		sta WSYNC
		stx TIM64T
		sta VBLANK
		rts
.pal50		ldx #65			; 50Hz
		sta WSYNC
		stx TIM64T
		sta VBLANK
		rts

;
;	endOverscan
;
endOverscan subroutine
.wait		lda INTIM
		bne .wait
		rts
		
;
;	getBallLocation
;
getBallLocation subroutine
		lda ballx		; Calculate ball X position in blocks
		sec			; blockX = (ballx-19)/4
		sbc #19
		sta temp	
		and #%00000011		; offsetX = (ballx-19) mod 4
		sta offsetX
		lda temp
		lsr
		lsr
		sta blockX
		sec			; Calculate ball Y position in blocks
		lda #[rowHeight * 16]+1 ; blockY = ((rowHeight*16+1)-bally)/8
		sbc bally
		sta temp
		and #%00000111		; offsetY = ((rowHeight*16+1)-bally) mod 8
		sta offsetY
		lda temp
		lsr
		lsr
		lsr
		sta blockY
		rts

;
;	getBrickValue
;	inputs: a,y (block deltas)
;	outputs: a (bit position of collision; 0 if none)
;
getBrickValue subroutine
		tya			; Add Y to blockY
		clc		
		adc blockY
		sta tryY		; Store result in tryY
		txa			; Add X to blockX
		clc
		adc blockX
		sta tryX		; Store result in tryX
		sta tryX_store
		beq .isPaddle		; Determine if tryX,tryY is a paddle
		cmp #31
		beq .isPaddle
		lda tryY
		beq .isPaddle
		cmp #15
;		beq .isPaddle
		bne .notPaddle
.isPaddle	lda #-1
		bne .continue
.notPaddle	lda #0
.continue	sta temp
		lda tryX
		lsr			; Divide tryX by 8 to get PF byte 0,1,2, or 3
		lsr
		lsr
		sta blockSection	; Store result in blockSection
		lda <#blockData		; Load LSB of blockData
		sta blockPtr		; Store it as LSB of blockPtr
		ldy blockSection	; Load X for counting
		beq .skipOffset		; If blockSection is 0, then no offset calculation
.loop		clc			; Else loop to find blockPtr offset
		adc #playfieldHeight	; Add playfieldHeight (16) to blockPtr
		sta blockPtr
		lda tryX		; And subtract 8 from tryX
		adc #-8			; adc vs. sbc because I know carry is clear
		sta tryX		; Store result
		lda blockPtr		; Reload blockPtr and loop
		dey
		bne .loop
.skipOffset	lda blockSection
		lsr			; Determine if blockSection is even or odd
		bcc .dontFlip		; If even, then don't flip tryX
		lda tryX		; Else, flip tryX
		eor #%00000111		; 0=7,1=6,2=5,etc...
		jmp .testBrick
.dontFlip
		lda tryX	
.testBrick
		sta tryX
		tay
		lda singleBrickTab,y
		ldy tryY
		and (blockPtr),y
		ora temp
		sta temp
		rts

;
;	reflect
;
reflect subroutine
					; Overview of inputs
					; A non-zero value in temp indicates a collision
					; x,y contain values between -1 and 3
					; -1 = Reflect ball movement if positive
					;  0 = Reflect ball movement (+ to -, or - to +)
					;  1 = Reflect ball movement if negative
					;  2 = Do not reflect
					;  3 = Special ball handling because ball is "buried"
					;      in a brick, not just touching
					
					; Note: I tried very hard to keep the ball from behaving
					; stupidly during "ambiguous" collisions usually caused
					; by very high ballmx or ballmy values.  This is my excuse
					; for the following bit of spaghetti...
		lda temp		; Check temp
		bne .tryX		; If non-zero, then process horizontal movement
		rts			; Else return from sub
		
.tryX		txa			; Prep x (-1 to 3) for processing
		beq .reflectX		; If x=0, then reflect horizontal movement
		cmp #2
		beq .tryY		; If x=2, then don't reflect horizontal movement (try vertical)
		cmp #3
		bne .checkNegative	; If -1 or 1, then conditionally reflect ball in specified direction
		lda tryX_store		; If x=3, prepare for "buried" ball processing
		bne .checkRight		; If not buried in left side paddle, then check the right side
		ldx #1			; Else force ball to move right (positive)
		bne .checkBottom	; Unconditional jump to check bottom side paddle
.checkRight	cmp #31			; Check if ball buried in right side paddle
		bne .noHorizontal	; If not, then no horizontal reflection
		ldx #-1			; Else force ball to move left (negative)
		bne .checkBottom	; Unconditional jump to check bottom side paddle
.noHorizontal	ldx #2			; No horizontal reflection

.checkBottom	lda tryY		; Check top/bottom bounds
		bne .checkTop		; If not buried in bottom side paddle, then check the top side
		ldy #-1			; Else force ball to move up (negative)
		bne .tryX		; Unconditional jump to reprocess reflection with new x,y values
.checkTop	cmp #15			; Check if ball buried in top side paddle
		bne .noVertical		; If not, then no vertical reflection
		ldy #1			; Else force ball to move down (positive)
		bne .tryX		; Unconditional jump to reprocess reflection with new x,y values
.noVertical	ldy #2			; No vertical reflection
		cpx #2			; See if there's horizontal reflection
		bne .tryX		; If so, then reprocess reflection with new x,y values
		
		ldy #2			; Else, reflect X only... (This assumption is not 100% right)
		bne .reflectX		; Unconditional jump to reflect horizontal movement

.checkNegative	eor ballmx		; EOR with ballmx
		and #%10000000		; Isolate Negative bit
		beq .tryY
.reflectX	lda #15
		sec
		sbc ballDirection
		sta ballDirection
.tryY		tya			; Load signed value in a
		beq .reflectY		; If 0, then reflect
		cmp #2
		beq .english		; If 2, then skip reflect
		eor ballmy		; EOR with ballmy
		and #%10000000		; Isolate Negative bit
		beq .english
.reflectY	lda ballDirection
		and #%00001000
		ldy temp
		sta temp
		lda #7
		sec
		sbc ballDirection
		and #%00000111
		ora temp
		sty temp
		sta ballDirection
		
.english
		
		IF 1			; Collect some debugging info...
.startDebug	
		
.endDebug	rts
		ENDIF

		lda gameState
		cmp #state_options	; Check for "options" state
		beq .done		; If so, exit

		ldy #0			; paddlePos and paddleSize offset
		
.bottom		lda tryY		; Check paddle english for bottom
		bne .top		; If ball didn't hit bottom, then check top
		lda gameMode		; Load gameMode flags
		and #mode_twoplayers	; Isolate two-player bit (bit 0)
		tay			; Update paddlePos and paddleSize offset
		lda tryX_store		; Else, get paddle index
		ldx #12			; And set a direction offset (I'm confused by my own notes...)
		bne .getDelta		; Unconditional branch
		
.top		cmp #15			; Check paddle english for top
		bne .left		; If ball didn't hit top, then check left
		lda #31			; Else, get paddle index
		sec
		sbc tryX_store		; Carry was set by CMP
		bpl .topContinue	; If not negative, then continue
		clc
		adc #45
.topContinue	ldx #4			; And set a direction offset
		bne .getDelta		; Unconditional branch

.left		lda tryX_store		; Check paddle english for left
		bne .right		; If ball didn't hit left, then check right
		lda #47			; Else, get paddle index
		sbc tryY		; Carry was cleared by CMP in .top
		ldx #8			; And set a direction offset
		bne .getDelta		; Unconditional branch

.right		cmp #31			; Check paddle english for right
		bne .done		; If ball didn't hit right, then no english
		lda #30			; Else, get paddle index
		adc tryY		; Carry was set by CMP
		ldx #8			; And set a direction offset

.getDelta	sec
		sbc paddlePos,y
		sbc #2
		bmi .addDelta
		sbc paddleSize,y
		bmi .done
.addDelta	stx offsetX
		clc
		adc offsetX
		clc
		adc ballDirection
		and #%00000111
		sec
		sbc offsetX
		and #%00001111
		sta ballDirection
.done		rts
		

;
;	removeBrick
;		
removeBrick subroutine
		lda temp
		bne .zapBlock
		rts
.zapBlock	cmp #-1			; Check for paddle
		bne .continue		; If not, don't exit
		lda gameState
		cmp #state_options	; Check for "options" state
		beq .end
		lda #-1			; Ping!
		sta sfxEcho0		
		lda #sfxPaddleFreq
		sta AUDF0
		jmp .end
		
.continue	lda gameState
		cmp #state_options	; Check for "options" state
		beq .end		; If so, exit

		ldy tryX		; Remove brick...
		lda doubleBrickTab,y
		ldy tryY
		and (blockPtr),y
		eor #%11111111
		and (blockPtr),y
		sta (blockPtr),y
		lda #flashDuration
		sta stateTimer
		lda #-1			; Ping!
		sta sfxEcho0		
		lda #sfxBrickFreq
		sta AUDF0

		dec blockCount		; Decrease the block counter
		bne .addScore		; If not zero, then add score
		lda #state_clear
		sta gameState
		lda #clearDelay
		sta stateTimer
		lda #0
		sta paddle0
		sta paddle1


		

.addScore	lda gameMode		; Check for scoring type
		and #mode_twoscores	; If it's two scores...
		bne .end		; Then don't update the score here
		lda #>brickScore	; Else add points to score
		sta temp
		lda #<brickScore
		jsr addScore		; THIS IS A THIRD LEVEL JSR
		jsr updateScore
.end		pla			; Pull the jsr return address off stack
		pla			; (Yep, it's two bytes)
		;jsr updateScore		; THIS IS A THIRD LEVEL JSR
		sta CXCLR		; Clear collision register
		rts

;
;	DEBUG_COLLISION stuff
;
		IF DEBUG_COLLISION	; Only assemble this subroutine if flag set
displayCollision subroutine
		tax			; Transfer the updated left 2 score digits to X
		lda temp		; Was there a collision?
		beq .endDebug		; If not, then end
		stx score		; If so, update the left 2 score digits
.endDebug	rts
		ENDIF

		MAC DISPLAY_COLLISION
		IF DEBUG_COLLISION
.collide	SET {1}<<4		; Shift the input value left 4 bits
		lda score		; Load left 2 score digits
		and #%00001111		; Zero out leftmost score digit
		ora #.collide		; Overlay the input value 
		jsr displayCollision	; Execute above routine
		ENDIF
		ENDM

;
;	reflectAndRemove
;		
reflectAndRemove subroutine		
		IF !NO_REMOVE	
		lda #0
		sta blockPtr+1		; Set MSB of blockPtr to zero page

		jsr getBallLocation	; Retrieve block and offset positions
		
		lda CXP1FB		; Load player1/playfield collision register
		asl			; Shift the important bit out to alter carry flag
		bcs .removeScript	; If carry set (collision), then remove a brick
		rts			; Otherwise, we're done

					; This remove algorithm refers to block locations
					; as such, with 5 being the ball location:
					; 1 2 3		1
					; 4(5)6		0
					; 7 8 9		-1
.removeScript
		ldx #0			; X - Check for block underneath ball (position 5)
		ldy #0			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 5
		ldx #3
		ldy #3
		jsr reflect
		jsr removeBrick
		
		
.try4		lda offsetX		; Try to remove block 4 (see comment above) and so on...
		bne .try6
		ldx #-1			; X
		ldy #0			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 4
		ldx #1			; Reflection
		ldy #2
		jsr reflect
		jsr removeBrick
		beq .try2

.try6		lda offsetX
		cmp #3
		bne .try2
		ldx #1			; X
		ldy #0			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 6
		ldx #-1			; Reflection
		ldy #2
		jsr reflect
		jsr removeBrick

.try2		lda offsetY
		cmp #5
		bmi .try8
		ldx #0			; X
		ldy #1			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 2
		ldx #2
		ldy #1			; Reflection
		jsr reflect
		jsr removeBrick

.try1		lda offsetX
		bne .try3
		ldx #-1			; X
		ldy #1			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 1
		ldx #1			; Reflection
		ldy #1
		jsr reflect
		jsr removeBrick

.try3		lda offsetX
		cmp #3
		bne .dammit
		ldx #1			; X
		ldy #1			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 3
		ldx #-1			; Reflection
		ldy #1
		jsr reflect
		jsr removeBrick

.try8		lda offsetY
		cmp #2
		bpl .dammit
		ldx #0			; X
		ldy #-1			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 8
		ldx #2
		ldy #-1			; Reflection
		jsr reflect
		jsr removeBrick

.try7		lda offsetX
		bne .try9
		ldx #-1			; X
		ldy #-1			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 7
		ldx #1			; Reflection
		ldy #-1
		jsr reflect
		jsr removeBrick

.try9		lda offsetX
		cmp #3
		bne .dammit
		ldx #1			; X
		ldy #-1			; Y
		jsr getBrickValue
		DISPLAY_COLLISION 9
		ldx #-1			; Reflection
		ldy #-1
		jsr reflect
		jsr removeBrick
		
.dammit					; This should theoretically never get called
		
.end
		ENDIF
		sta CXCLR		; Clear collision register
		rts

;
;	ballEffects
;
;ballEffects subroutine
;		lda stateTimer
;		beq .noFlash
;		dec stateTimer
;		lda #%00000010
;		and stateTimer
;		bne .flash2
;		lda #flashColor1
;		jmp .setColors
;.flash2		lda #flashColor2
;.setColors	sta COLUP0
;		sta COLUP1
;		rts
;.noFlash
;		lda #ballHighlight
;		sta COLUP0
;		lda #ballShadow
;		sta COLUP1
;.end		rts


; ------------------------------------
; Reset vector
; ------------------------------------
		echo "ROM Bytes Used:",[*-program+4]d
		echo "ROM Bytes Free:",[$10000-*-4]d

		seg res
reset		org $FFFC
		dc.w startCart
		dc.w startCart

Current Thread