Disassembly:Videocart 16

From veswiki
Revision as of 16:48, 1 March 2020 by E5frog (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search
;-------------------------------------------------------------------------------
; Dodge It - Videocart 16
;  for the Fairchild Video Entertainment System
; Original Code Copyright © 1978, Fairchild Semiconductor
;
; Disassembly Generated using Peter Trauner's f8tool
; 
; Comments, Labels, Etc. added by
;  Alex West
;
; Thanks to http://channelf.se/veswiki/ for making this possible
;
; A text file of the instruction manual can be found here:
; http://channelf.se/gallery/txt/videocart16.txt
;
; Build Instructions
;  dasm dodge_it.asm -f3 -ododge_it.bin

	processor f8

	include "ves.h"

;-------------------------------------------------------------------------------
; Common register definitions
;
; Definitions for common registers, bitfields, and constants. Local registers
;  and function arguments are generally not included here.

; -- Ball Properties -----------------------------------------------------------
;
;  The x and y positions are fairly straightforward. The xpos is 7 bits, and 
;   the ypos is 6 bits. Velocity is a bit more complicated.
;
;  The balls' velocity is stored in a sign-magnitude format. The sign, or
;   direction, of the balls' x and y velocities is stored in the upper bits of 
;   the x and y positions, for those respective directions. The magnitudes are
;   stored in a bitpacked array, with the information for two balls being stored
;   in one byte like so:
;
;   /-- Ball 0's x speed
;   |  /-- Ball 0's y speed
;   XX YY xx yy
;         |  \-- Ball 1's y speed
;         \-- Ball 1's x speed
;
;  ...and so on and so forth for balls 2 and 3, 4 and 5, etc.
;
; Astute observers will note that bit 6 the in y position remains unused, and 
;  the last byte of the velocity array has an unused nybble. Such waste...

; Current ball being operated on
main.curBall = $B
PLAYER_1 = 0
PLAYER_2 = 1

; Ball arrays
balls.xpos = 020 ; Array
balls.ypos = 033 ; Array
balls.speed = 046 ; Bitpacked array

balls.count = 056 ; Number of balls currently in play including players
delayIndex  = 057 ; Basically the same as balls.count

MASK_DIRECTION = %10000000
MASK_POSITION  = %01111111
MASK_YPOSITION = %00111111

MASK_SPEED  = %00001111
MASK_XSPEED = %11001100
MASK_YSPEED = %00110011

MAX_BALLS   = 11
MAX_PLAYERS = 2
MAX_ENEMIES = 9

; -- Arena Walls ---------------------------------------------------------------
;
; The left and top walls work how you'd expect. However, the right and bottom
;  walls are weird. Not only do they have different values for player and enemy
;  (to account for their different sizes), but they are also negative. In other
;  words, they give distances for how far the walls are from some point 256
;  pixels away from the origin. It's weird and inexplicable.

wall.rightEnemy = 060
wall.rightPlayer = 061
wall.left = 062
wall.lowerEnemy = 063
wall.lowerPlayer = 064
wall.upper = 065

; Constants used to generate the positions of the walls
WALL_MIN  = $10 ; Most upper-left point possible to generate a field
; Note: These two constants, contrary to the values they help set, are just
;  simply based off the distance from the origin.
WALL_XMAX = $58
WALL_YMAX = $38

WALL_X_OFFSET_MAX = $12
WALL_Y_OFFSET_MAX = $0B

; Constants to render border at the start of the game
FIELD_CORNER = WALL_MIN ; Top left corner of box, as rendered
FIELD_WIDTH  = WALL_XMAX - WALL_MIN + 1 ; $49
FIELD_HEIGHT = WALL_YMAX - WALL_MIN + 1 ; $29

; Constants to spawn the balls at the edges of the field
SPAWN_XMIN = WALL_MIN
SPAWN_XMAX = WALL_XMAX-1
SPAWN_YMIN = WALL_MIN
SPAWN_YMAX = WALL_YMAX-1

; -- Timers --------------------------------------------------------------------

; Each of these values is 2 bytes of binary-coded decimal. 
timer.hi = 066
timer.lo = 067
hiScore.p1.hi = 054
hiScore.p1.lo = 055
hiScore.p2.hi = 073
hiScore.p2.lo = 074

; Used to get the lower nybble of a timer byte ("SR 4" can get the upper one)
DIGIT_MASK = $0F

; Parameters for drawing the timers' positions
TIMER_X_LEFT = $1F
TIMER_X_CENTER = $39
TIMER_X_RIGHT = $54
TIMER_Y_OFFSET = $A

; -- Game mode -----------------------------------------------------------------

; The lower two bits represent what button was pressed on the console. The upper
;  six bits of this are randomized in shuffleGame(), but of those bits only the
;  top one matters.
gameMode = 075
MODE_2P_MASK     = %00000001 ; Unset: 1 player, set: 2 players
MODE_SPEED_MASK  = %00000010 ; Set: fast, unset: slow
MODE_CHOICE_MASK = %00000011 ; Used to check if the above two bits are set
; Set: fiddle with speed upon collision, unset: don't fiddle with speed
; Note: It appears that this can only be set in shuffleGame(), and that it is
;  cleared in explode()
MODE_BOUNCE_MASK = %10000000

; Set in shuffleGame()
main.gameSettings = $A
MASK_PLAYER_SIZE  = %11000000
MASK_ENEMY_SIZE   = %00110000
MASK_PLAYER_SPEED = %00001100
MASK_ENEMY_SPEED  = %00000011

; -- Misc Registers ------------------------------------------------------------

; Yes, 070 and 071 have different semantics for different functions called from 
;  the main loop.

; Controller inputs. Written in readInput, and read in doPlayers and gameOver
input.p1 = 070 ; Left controller
input.p2 = 071 ; Right controller
; Arguments for doBall
doBall.size = 070
doBall.speed = 071
; Local register for collision()
testBall = 071

; If set, execute explode() at the end of the main loop
explosionFlag = 072
MASK_EXPLODE = %10000000

; Randomly generated number
RNG.seedHi = 076
RNG.seedLo = 077

; -- Misc Constants ------------------------------------------------------------
; Most of these should probably be added to ves.h

BCD_ADJUST = $66

; Sounds
SOUND_NONE  = %00000000 ; Silence
SOUND_1kHz  = %01000000 ; 1kHz tone
SOUND_500Hz = %10000000 ; 500Hz tone
SOUND_120Hz = %11000000 ; 120Hz tone

; Graphics
BLUE  = $40
RED   = $80
GREEN = $C0

MASK_COLOR     = %11000000
MASK_SOUND     = %11000000
MASK_NO_SOUND  = %00111111

DRAW_ATTR_X = $7d
DRAW_ATTR_W = 2
DRAW_SCREEN_W = $80
DRAW_SCREEN_H = $40

; end of common register definitions
;-------------------------------------------------------------------------------

	org $0800

CartridgeHeader: db $55, $2b
CartridgeEntry:  JMP init

;-------------------------------------------------------------------------------
; Graphics data
; 
; Each character takes 5 nybbles of data, split across 5 bytes. Even numbered
;  characters take the left nybble while odd numbered characters take the right.

CHAR_WIDTH  = 4
CHAR_HEIGHT = 5
CHAR_G     = $A
CHAR_QMARK = $B

graphicsData:
	; 0805 
	db %01110010 ;  ###  # 
	db %01010110 ;  # # ## 
	db %01010010 ;  # #  # 
	db %01010010 ;  # #  # 
	db %01110111 ;  ### ###
	; 080a
	db %01110111 ;  ### ###
	db %00010001 ;    #   #
	db %01110011 ;  ###  ##
	db %01000001 ;  #     #
	db %01110111 ;  ### ###
	; 080f
	db %01010111 ;  # # ###
	db %01010100 ;  # # #  
	db %01110111 ;  ### ###
	db %00010001 ;    #   #
	db %00010111 ;    # ###
	; 0814
	db %01000111 ;  #   ###
	db %01000001 ;  #     #
	db %01110001 ;  ###   #
	db %01010001 ;  # #   #
	db %01110001 ;  ###   #
	; 0819
	db %01110111 ;  ### ###
	db %01010101 ;  # # # #
	db %01110111 ;  ### ###
	db %01010001 ;  # #   #
	db %01110001 ;  ###   #
	; 081e
	db %11111111 ; ########
	db %10000001 ; #      #
	db %10110010 ; # ##  # 
	db %10010000 ; #  #    
	db %11110010 ; ####  # 
	; 0823
	db %01110111 ;  ### ###
	db %01000101 ;  #   # #
	db %01110111 ;  ### ###
	db %01000101 ;  #   # #
	db %01000101 ;  #   # #
	; 0828
	db %01110111 ;  ### ###
	db %01000010 ;  #    # 
	db %01110010 ;  ###  # 
	db %00010010 ;    #  # 
	db %01110010 ;  ###  # 

;-------------------------------------------------------------------------------
; Data Tables

; Delay table A (easy)
delayTableEasy: ; 082d
	db $19, $16, $13, $11, $0e, $0c, $0a, $08, $06, $03, $01

; Delay table B (pro)
delayTableHard: ; 0838
	db $0b, $0a, $09, $08, $07, $06, $05, $04, $03, $02, $01

; Bitmasks used while randomizing the game mode
gameModeMasks:  ; 0843
	db $C0, $30, $0C, $03, $FC
				
; This table is referenced but never read. Based on the code that references
;  this table, it likely pertained to the enemy speeds. (Also, there is a chance
;  that the endian-ness is wrong on these.)
unusedSpeedTable: ; 0848 00 00 12 0b 0b 06 02 01
	dw $0000, $120B, $0B06, $0201

; Colors of P1, P2, and enemies
ballColors: ; 0850 40 c0 80
	db BLUE, GREEN, RED

; Table helps translate the button press to the game mode number
menuChoices: ; 0853 
	db $00, $01, $02, $03, $03

;-------------------------------------------------------------------------------
; draw(param, xpos, ypos, width, height)
;  Leaf function
;
; This function plots pixels to screen. It has two different entry points, which
;  make it act like two different functions.
;
; When entering via drawChar, draw.param should be set to the index of the
;  character to be drawn. Although the charset only contains 16 characters, it
;  could be expanded up to 64 without changing this function.
;
; When entering via drawBox, draw.param should be set to either DRAW_RECT or
;  DRAW_ATTRIBUTE depending on whether you're drawing a box or the attribute
;  column.
;
; The x and y coordinates are relative to the top-left corner of the screen.
;
; Despite the y position and color being mapped to different I/O ports, this
;  function expects those values to be bitpacked together. The y position takes
;  up the lower 6 bits, and the color takes up the upper 2 bits.
;
; Although this function modifies draw.xpos and draw.ypos, those variables are
;  set back to their original values upon returning from the function.

; == Arguments ==
draw.param  = 0 ; Drawing Parameter or Character Index
draw.xpos   = 1 ; X Position
draw.ypos   = 2 ; Y Position and Color
draw.width  = 4 ; Width
draw.height = 5 ; Width

; Valid values for draw.param
DRAW_RECT      = %10000000 ; Draw a rectangle
DRAW_ATTRIBUTE = %11000000 ; Draw the attribute column

; == Entry Point A == (for drawing a character)
drawChar: subroutine

; == Local Variables ==
.data   = 3 ; pixel data
.xcount = 6 ; horizontal counter
.ycount = 7 ; vertical counter
.temp   = 8 ; helps calculate the data counter
.color  = 8 ; color, as extracted from ypos

; Get the starting address of the desired character
	; DC = graphicsData + param/2 + (param/2)*4
	DCI  graphicsData        ; 0858 2a 08 05
	LR   A, draw.param       ; 085b 40
	SR   1                   ; 085c 12
	LR   .temp, A            ; 085d 58
	SL   1                   ; 085e 13
	SL   1                   ; 085f 13
	AS   .temp               ; 0860 c8
	ADC                      ; 0861 8e

; == Entry point B == (for drawing a box)
drawBox:
	; (xcount,ycount) = (width,height)
	LR   A, draw.width       ; 0862 44
	LR   .xcount, A      ; 0863 56
    LR   A, draw.height      ; 0864 45
    LR   .ycount, A      ; 0865 57

.doRowLoop:
; I/O write the ypos
	; Extract color bits from ypos
	LR   A, draw.ypos        ; 0866 42
	NI   MASK_COLOR          ; 0867 21 c0
	LR   .color, A           ; 0869 58
	
	; Mask out sound, put the ypos in .data
	LR   A, draw.ypos        ; 086a 42
	COM                      ; 086b 18
	NI   MASK_NO_SOUND       ; 086c 21 3f
	LR   .data, A            ; 086e 53
	
	; Write row to port 5, making sure to preserve the sound
	INS  5                   ; 086f a5
	NI   MASK_SOUND          ; 0870 21 c0
	AS   .data               ; 0872 c3
	OUTS 5                   ; 0873 b5

; Load the pixel data into .data
	; If either DRAW_RECT or DRAW_ATTRIBUTE is
	;  then set all of the pixels and jump ahead
	CLR                      ; 0874 70
	AS   draw.param          ; 0875 c0
	LI   %11111111           ; 0876 20 ff
	BM   .setPixelData       ; 0878 91 09
	
	; Load .data from memory
	LM                       ; 087a 16
	LR   .data, A            ; 087b 53

	; If character number is even, just use the left 4 bits
	LIS  $1                  ; 087c 71
	NS   draw.param          ; 087d f0
	BZ   .doPixelLoop        ; 087e 84 04

	; If char is odd, use the right 4 bits by shifting them into place
	LR   A, .data            ; 0880 43
	SL   4                   ; 0881 15	
.setPixelData:
	LR   .data, A            ; 0882 53

; I/O write the xpos
.doPixelLoop:
	LR   A, draw.xpos        ; 0883 41
	COM                      ; 0884 18
	OUTS 4                   ; 0885 b4

; I/O write the color
	; if MSB of .data is 1, draw that color
	; if MSB of .data is 0, draw the BG color
	CLR                      ; 0886 70
	AS   .data               ; 0887 c3
	LR   A, .color           ; 0888 48
	BM   .setColor           ; 0889 91 02
	LIS 0                    ; 088b 70
.setColor:
	COM                      ; 088c 18
	NI   MASK_COLOR          ; 088d 21 c0
	OUTS 1                   ; 088f b1
	
; Iterate on to the next data bit, making sure to pad with 1
	; .data = (.data << 1) + 1
	LR   A, .data            ; 0890 43
	SL   1                   ; 0891 13
	INC                      ; 0892 1f
	LR   .data, A            ; 0893 53
	
; If DRAW_ATTRIBUTE is set, iterate to the color of the next column
	; Check if DRAW_ATTRIBUTE is set
	LR   A, draw.param       ; 0894 40
	SL   1                   ; 0895 13
	BP   .activateWrite      ; 0896 81 04
	; If so, .color = .color << 1
	LR   A, .color           ; 0898 48
	SL   1                   ; 0899 13
	LR   .color, A           ; 089a 58

; I/O write to push our color through
.activateWrite:
	LI   $60                 ; 089b 20 60
	OUTS 0                   ; 089d b0
	LI   $50                 ; 089e 20 50
	OUTS 0                   ; 08a0 b0

	; xpos++
	LR   A, draw.xpos        ; 08a1 41
	INC                      ; 08a2 1f
	LR   draw.xpos, A        ; 08a3 51
	
; Spin in place to make sure the write goes through
	LIS  4                   ; 08a4 74
.delay:
	AI   $ff                 ; 08a5 24 ff
	BNZ  .delay              ; 08a7 94 fd
	
	; xcount--, loop on to next pixel if not zero
	DS   .xcount             ; 08a9 36
	BNZ  .doPixelLoop        ; 08aa 94 d8
	
	; ypos++
	LR   A, draw.ypos        ; 08ac 42
	INC                      ; 08ad 1f
	LR   draw.ypos, A        ; 08ae 52
	
; Reset xcount and xpos
	; xcount = width
	LR   A, draw.width       ; 08af 44
	LR   .xcount,A       ; 08b0 56
	; xpos = xpos - width
	COM                      ; 08b1 18
	INC                      ; 08b2 1f
	AS   draw.xpos           ; 08b3 c1
	LR   draw.xpos, A        ; 08b4 51

	; ycount--, loop on to next row if not zero
	DS   .ycount             ; 08b5 37
	BNZ  .doRowLoop          ; 08b6 94 af
	
; Reset ypos
	; ypos = ypos - height
	LR   A, draw.height      ; 08b8 45
	COM                      ; 08b9 18
	INC                      ; 08ba 1f
	AS   draw.ypos           ; 08bb c2
	LR   draw.ypos, A        ; 08bc 52
	
; Clear I/O ports
	CLR                      ; 08bd 70
	OUTS 1                   ; 08be b1
	OUTS 0                   ; 08bf b0
	
	POP                      ; 08c0 1c

; end draw()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; rand()
;  Leaf Function
;
; Random number generator. I am uncertain how random this is, or what the
;  mathematical basis is behind it.

; == Arguments ==
; None

; == Returns ==
RNG.regHi = $6
RNG.regLo = $7

; == Entry Point ==
rand: subroutine

; == Local Variable ==
.tempISAR = 8

; save ISAR to a temp register
	LR   A,IS                ; 08c1 0a
	LR   .tempISAR, A; 08c2 58
	
; r6 = o77*2 + o76
	SETISAR RNG.seedLo      ; 08c3 67 6f
	LR   A,(IS)-             ; 08c5 4e
	SL   1                   ; 08c6 13
	AS   (IS)+               ; 08c7 cd
	LR   RNG.regHi, A        ; 08c8 56
	
; r6,7 = (r6,77)*2
	;  do the lo byte
	LR   A,(IS)              ; 08c9 4c
	AS   (IS)                ; 08ca cc
	LR   RNG.regLo, A        ; 08cb 57

	;  do the hi byte
	LR   J,W                 ; 08cc 1e ; save status reg
	LR   A, RNG.regHi        ; 08cd 46
	SL   1                   ; 08ce 13
	LR   W,J                 ; 08cf 1d ; reload status reg
	LNK                      ; 08d0 19
	LR   RNG.regHi, A        ; 08d1 56
	
; r6,7 = (r6,7)*2
	;  do the lo byte
	LR   A, RNG.regLo        ; 08d2 47
	AS   RNG.regLo           ; 08d3 c7
	LR   RNG.regLo, A        ; 08d4 57

	;  do the hi byte
	LR   J,W                 ; 08d5 1e
	LR   A, RNG.regHi        ; 08d6 46
	SL   1                   ; 08d7 13
	LR   W,J                 ; 08d8 1d
	LNK                      ; 08d9 19
	LR   RNG.regHi, A        ; 08da 56
	
; r6,7 += r66,67
	;  do the lo byte
	LR   A, RNG.regLo        ; 08db 47
	AS   (IS)-               ; 08dc ce
	LR   RNG.regLo, A        ; 08dd 57

	;  do the hi byte
	LR   A, RNG.regHi        ; 08de 46
	LNK                      ; 08df 19
	AS   (IS)+               ; 08e0 cd
	LR   RNG.regHi, A        ; 08e1 56
	
; r6,r7 += 0x3619	
; o76,77 = r6,r7
	;  do the lo byte
	LR   A, RNG.regLo        ; 08e2 47
	AI   $19                 ; 08e3 24 19
	LR   RNG.regLo, A        ; 08e5 57
	LR   (IS)-,A             ; 08e6 5e

	;  do the hi byte
	LR   A, RNG.regHi        ; 08e7 46
	LNK                      ; 08e8 19
	AI   $36                 ; 08e9 24 36
	LR   RNG.regHi, A        ; 08eb 56
	LR   (IS)+,A             ; 08ec 5d
	
	; Restore ISAR
	LR   A, .tempISAR        ; 08ed 48
	LR   IS,A                ; 08ee 0b

	; Return
	POP                      ; 08ef 1c
; end of rand()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; menu()
;  Mid-Level Function
;
; Returns the menu button you pressed.
;
; Note that drawing "G?" is handled by main()

; == Return ==
menu.buttons = 0

; == Entry Point ==
menu: subroutine

; == Locals ==
.waitTimerHi = 2
.waitTimerLo = 1
; Wait time is 10 seconds, according to the manual.
.WAIT_TIME = $af00
.DEFAULT_MODE = $1

	LR   K,P                 ; 08f0 08

	; set lower byte of .waitTimer
	LIS  [<.WAIT_TIME]       ; 08f1 70
	LR   .waitTimerLo,A      ; 08f2 51

	; clear console buttons, load default state
	OUTS 0                   ; 08f3 b0
	INS  0                   ; 08f4 a0
	LR   menu.buttons, A     ; 08f5 50

	; set upper byte of .waitTimer
	LI   [>.WAIT_TIME]       ; 08f6 20 af
	LR   .waitTimerHi, A     ; 08f8 52
	
.pollInputLoop:
	PI   rand                ; 08f9 28 08 c1
	
	; Set DC (to be used after this function in main)
	DCI  menuChoices         ; 08fc 2a 08 53

	; Read console buttons
	CLR                      ; 08ff 70
	OUTS 0                   ; 0900 b0
	INS  0                   ; 0901 a0

	; Check if different from last time they were read
	XS   menu.buttons        ; 0902 e0
	; if not, decrement .waitTimer
	BZ   .wait               ; 0903 84 03

	; Return after 10 seconds or a choice is made
.exit:
	LR   menu.buttons,A      ; 0905 50
	PK                       ; 0906 0c

	; Wait for a choice for 10 seconds
.wait:
	DS   .waitTimerLo        ; 0907 31
	BNZ  .pollInputLoop      ; 0908 94 f0
	DS   .waitTimerHi        ; 090a 32
	BNZ  .pollInputLoop      ; 090b 94 ed

	; Default to game mode 1 (1 player, easy)
	LIS  .DEFAULT_MODE       ; 090d 71

	; Return
	BR   .exit               ; 090e 90 f6
; end menu()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; readInput()
;  Leaf Function
;
; Reads input from the hand controllers, and twiddles the RNG a bit (if no
;  inputs are detected (?)).
;
; Note: To enable data reads from the controllers, bit 6 of I/O port 0 needs to
;  be set to 1. This is done in draw(), meaning that it doesn't need to be done
;  here (although it might have been better practice to do so).
;
; == Arguments ==
;  None
; == Returns ==
;  input.p1 = 070
;  input.p2 = 071
; == Locals ==
;  None

; == Entry Point ==
readInput: subroutine
	SETISAR input.p1         ; 0910 67 68
	
	; Clear I/O ports
	CLR                      ; 0912 70
	OUTS 1                   ; 0913 b1
	OUTS 4                   ; 0914 b4
		
	; Read left controller from I/O port 1
	INS  1                   ; 0915 a1
	LR   (IS)+,A             ; 0916 5d
	
	; Read right controller from I/O port 2
	INS  4                   ; 0917 a4
	LR   (IS)-,A             ; 0918 5e
	
	; if(-(input.p1 + input.p2) == 0) then exit
	AS   (IS)                ; 0919 cc
	INC                      ; 091a 1f
	COM                      ; 091b 18
	BZ   .exit               ; 091c 84 06
	
	; else, twiddle with the RNG
	SETISARL RNG.seedLo     ; 091e 6f
	; RNG.lo = RNG.lo + 1
	LIS  $1                  ; 091f 71
	AS   (IS)                ; 0920 cc
	LR   (IS)-,A             ; 0921 5e
	; RNG.hi--
	DS   (IS)                ; 0922 3c

.exit:
	POP                      ; 0923 1c
; end of readInput()
;-------------------------------------------------------------------------------
	
;-------------------------------------------------------------------------------
; doPlayers()
;  Mid-Level Function
;
; This function takes the controller inputs sets the speed and direction of each
;  player's ball accordingly. Player speed is taken from main.gameSettings. The 
;  results are then save to the xpos, ypos, and speed arrays in the scratchpad.
;
; The order in which the players are processed is done randomly.
;
; In the case of L/R or U/D conflicts, right takes precedence over left and down
;  over up.
;
; This function does not handle drawing the players.

; == Entry Point ==
doPlayers: subroutine

; == Locals ==
.speed = $0
.xpos = $1
.ypos = $2
.loopCount = $8

	LR   K,P                 ; 0924 08
	
	; Read input from hand controllers
	PI   readInput           ; 0925 28 09 10
	
; Randomize which player is processed first
	; if LSB of RNG is set
	;  curBall = player 1
	; else
	;  curBall = player 2
	SETISAR RNG.seedLo       ; 0928 67 6f
	LIS  PLAYER_2            ; 092a 71
	NS   (IS)                ; 092b fc
	LIS  PLAYER_1            ; 092c 70
	BNZ  .setPlayer          ; 092d 94 02
	LIS  PLAYER_2            ; 092f 71
.setPlayer:
	LR   main.curBall,A      ; 0930 5b

	; .loopCount = 2
	LIS  MAX_PLAYERS         ; 0931 72
	LR   .loopCount,A        ; 0932 58

; start loop
.playerLoop:
	; speed = 0 (so we don't move if nothing is pressed)
	CLR                      ; 0933 70
	LR   .speed,A            ; 0934 50

	; .xpos = xpos[curBall]
	LR   A,main.curBall      ; 0935 4b
	AI   balls.xpos          ; 0936 24 10
	LR   IS,A                ; 0938 0b
	LR   A,(IS)              ; 0939 4c
	LR   .xpos,A             ; 093a 51

	; .ypos = ypos[curBall]
	LR   A,IS                ; 093b 0a
	AI   MAX_BALLS           ; 093c 24 0b
	LR   IS,A                ; 093e 0b
	LR   A,(IS)              ; 093f 4c
	LR   $2,A                ; 0940 52

	; set ISAR to match the current player's controller
	SETISARU RNG.seedLo      ; 0941 67
	LIS  PLAYER_2            ; 0942 71
	NS   main.curBall        ; 0943 fb
	SETISARL input.p2        ; 0944 69
	BNZ  .checkRight         ; 0945 94 02
	SETISARL input.p1        ; 0947 68

; Check if right is pressed
.checkRight:
	LIS  CONTROL_RIGHT       ; 0948 71
	NS   (IS)                ; 0949 fc
	BNZ  .checkLeft          ; 094a 94 06

	; If so, set x direction to right
	LR   A,.xpos             ; 094c 41
	NI   MASK_POSITION      ; 094d 21 7f
	BR   .setXspeed          ; 094f 90 08

; Check if left is pressed
.checkLeft:
	LIS  CONTROL_LEFT        ; 0951 72
	NS   (IS)                ; 0952 fc
	BNZ  .checkDown          ; 0953 94 08

	; If so, set x direction to left
	LR   A,.xpos             ; 0955 41
	OI   MASK_DIRECTION      ; 0956 22 80

.setXspeed:
	; Apply the direction to .xpos
	LR   .xpos,A             ; 0958 51
	; xspeed = gameSettings.playerSpeed
	LIS  MASK_PLAYER_SPEED   ; 0959 7c
	NS   main.gameSettings   ; 095a fa
	LR   .speed,A            ; 095b 50

; Check if down is pressed
.checkDown:
	LIS  CONTROL_BACKWARD    ; 095c 74
	NS   (IS)                ; 095d fc
	BNZ  .checkUp            ; 095e 94 06

	; If so, set y direction to down
	LR   A,.ypos             ; 0960 42
	NI   MASK_YPOSITION      ; 0961 21 3f
	BR   .setYspeed          ; 0963 90 08
	
; Check if up is pressed
.checkUp:
	LIS  CONTROL_FORWARD     ; 0965 78
	NS   (IS)                ; 0966 fc
	BNZ  .prepSaveBall       ; 0967 94 0b

	; If so, set y direction to up
	LR   A,.ypos             ; 0969 42
	OI   MASK_DIRECTION      ; 096a 22 80

.setYspeed:
	; Apply the direction to .ypos
	LR   .ypos,A             ; 096c 52
	; yspeed = gameSettings.playerSpeed
	LIS  MASK_PLAYER_SPEED   ; 096d 7c
	NS   main.gameSettings   ; 096e fa
	SR   1                   ; 096f 12
	SR   1                   ; 0970 12
	AS   .speed              ; 0971 c0
	LR   .speed,A            ; 0972 50

; Copy the speed to the other nybble
.prepSaveBall:
	LR   A,.speed            ; 0973 40
	SL   4                   ; 0974 15
	AS   .speed              ; 0975 c0
	LR   .speed,A            ; 0976 50
	; saveBall will figure out which nybble to save
	
	; Save the ball to the scratchpad arrays
	PI   saveBall            ; 0977 28 09 a2
	
; Set curBall to the other player's ball
	; (why not xor the register with a constant 1?)
	LIS  PLAYER_2            ; 097a 71
	NS   main.curBall        ; 097b fb
	LIS  PLAYER_1            ; 097c 70
	BNZ  .setNextPlayer      ; 097d 94 02
	LIS  PLAYER_2            ; 097f 71
.setNextPlayer:
	LR   main.curBall,A      ; 0980 5b
	
	; .loopCount--
	DS   .loopCount          ; 0981 38
	BNZ  .playerLoop         ; 0982 94 b0
	
	; Return
	LR   P,K                 ; 0984 09
	POP                      ; 0985 1c
; end doPlayers()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; delayByTable(index)
; delayVariable(count)
;  Leaf Functions
;
; This procedure has two different entry points, so we can consider it two
;  different functions. Alternatively, we can think of the first function as
;  calling the second function by having just continuing on to its code.
;  (Alternatively, this is just some spaghetti code.)
;
; The first sets the delay according to the game mode and the current number of
;  balls. This function is necessary to make sure that the game runs at a
;  consistent speed, since the Channel F does not have any means of
;  synchronizing itself to vblank or anything like that.
;
; The second function sets a delay according to an a count provided by the
;  callee. This is useful for providing short pauses, like during a game over.
;
; TODO: Find a rough conversion between delay.count and the amount of time this
;  function actually delays.

; == Arguments ==
; Same register, yes, but this is good syntactic sugar.
delay.index = 0 ; when entering through delayByTable 
delay.count = 0 ; when entering through delayVariable

; == Entry Point A ==
delayByTable: subroutine

; == Locals ==
.tempISAR = 3

	; if(gameMode & speedMask == 0)
	;  count = delayTableEasy[index]
	; else
	;  count = delayTableHard[index]
	; Set 
	DCI  delayTableEasy      ; 0986 2a 08 2d
	
; Save the ISAR
	LR   A,IS                ; 0989 0a
	LR   .tempISAR,A         ; 098a 53
	
; Test to check the game speed
	SETISAR gameMode         ; 098b 67 6d
	LIS  MODE_SPEED_MASK     ; 098d 72
	NS   (IS)                ; 098e fc
	
; Restore the ISAR
	LR   A,.tempISAR         ; 098f 43
	LR   IS,A                ; 0990 0b
	
	; Branch ahead if playing easy
	BZ   .loadData           ; 0991 84 04
	
	; Else, set the table to hard
	DCI  delayTableHard      ; 0993 2a 08 38

; delay.count = delayTable[index]
.loadData:
	LR   A, delay.index      ; 0996 40
	ADC                      ; 0997 8e
	LM                       ; 0998 16
	LR   delay.count, A      ; 0999 50

; == Entry Point B ==
delayVariable:

; A = 0
.outerLoop:
	LIS  0                   ; 099a 70	
; A++
.innerLoop:
	INC                      ; 099b 1f
	BNZ  .innerLoop          ; 099c 94 fe
; count--
	DS   delay.count         ; 099e 30
	BNZ  .outerLoop          ; 099f 94 fa

	; Return
	POP                      ; 09a1 1c
; end of delayByTable() and delayVariable()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; saveBall(ball, speed, xpos, ypos)
;  Leaf Function
;
; Given the ball number, speed, x position, and y position in the input
;  arguements, this function saves those ball parameters into the appropriate
;  arrays in the scratchpad. This function is useful because the speed array is
;  bitpacked.

; == Arguments ==
saveBall.speed = $0
saveBall.xpos = $1
saveBall.ypos = $2
; main.curBall = $B

saveBall: subroutine

; == Local ==
.speedMask = $3

; xpos[curBall] = saveBall.xpos
	LI   balls.xpos          ; 09a2 20 10
	AS   main.curBall        ; 09a4 cb
	LR   IS,A                ; 09a5 0b
	LR   A,saveBall.xpos     ; 09a6 41
	LR   (IS),A              ; 09a7 5c
	
; ypos[curBall] = saveBall.xpos
	LR   A,IS                ; 09a8 0a
	AI   MAX_BALLS           ; 09a9 24 0b
	LR   IS,A                ; 09ab 0b
	LR   A,saveBall.ypos     ; 09ac 42
	LR   (IS),A              ; 09ad 5c
	
; Calculate index and bitmask for the bitpacked velocity array
	; ISAR = balls.speed + curBall/2
	LR   A, main.curBall     ; 09ae 4b
	SR   1                   ; 09af 12
	AI   balls.speed         ; 09b0 24 26
	LR   IS,A                ; 09b2 0b
	
	; if curBall is even
	;  bitmask = %00001111
	; else 
	;  bitmask = %11110000
	LIS  $1                  ; 09b3 71
	NS   main.curBall        ; 09b4 fb
	LIS  MASK_SPEED          ; 09b5 7f
	BNZ  .setSpeedMask       ; 09b6 94 02
	COM                      ; 09b8 18
.setSpeedMask:          
	LR   .speedMask,A        ; 09b9 53

; Set curBall speed bitfield
	; Clear curBall's bitfield from the velocity[curBall/2]
	COM                      ; 09ba 18
	NS   (IS)                ; 09bb fc
	LR   (IS),A              ; 09bc 5c

	; Extract the appropriate speed bitfield from the input argument
	LR   A,saveBall.speed    ; 09bd 40
	NS   .speedMask          ; 09be f3

	; Merge the bitfields and save the result
	AS   (IS)                ; 09bf cc
	LR   (IS),A              ; 09c0 5c

	; Return
	POP                      ; 09c1 1c
; end saveBall()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; spawnBall(curBall)
;  Mid-Level Function
;
; This function spawns a single enemy or player ball.
;
; Enemy balls are given a random position in the playfield and a random 
;  direction, and then clamped to one of the four walls, with their direction
;  being set away from the wall. They are also given a non-random starting speed
;  of 1 and 1 on each axis.
; 
; Player balls are spawned in hardcoded positions in the middle of the court.

; == Arguments ==
; main.curBall = $b

; == Returns ==
; None

; == Entry Point ==
spawnBall: subroutine
	LR   K,P                 ; 09c2 08

; == Local Variables ==
.speed = $0
.xpos = $1
.ypos = $2

; == Local Constants ==
.SPEED = %01010101
.PLAYER_Y = $23
.PLAYER1_X = $33
.PLAYER2_X = $3A
	
; keep rerolling RNG until it gets an inbounds x and y position
.reroll:
	PI   rand                ; 09c3 28 08 c1

; xpos = rng.hi
	LR   A, RNG.regHi        ; 09c6 46
	CI   SPAWN_XMIN          ; 09c7 25 10
	BC   .reroll             ; 09c9 82 f9
	CI   SPAWN_XMAX          ; 09cb 25 57
	BNC  .reroll             ; 09cd 92 f5

	LR   .xpos,A             ; 09cf 51

; ypos = rng.lo
	LR   A, RNG.regLo        ; 09d0 47
	CI   SPAWN_YMIN          ; 09d1 25 10
	BC   .reroll             ; 09d3 82 ef
	CI   SPAWN_YMAX          ; 09d5 25 37
	BNC  .reroll             ; 09d7 92 eb

	LR   .ypos,A             ; 09d9 52
	
; speed = 0x55
	LI   .SPEED              ; 09da 20 55
	LR   .speed,A            ; 09dc 50
	
; Spawn the ball against one of the walls
	; use lower 2 bits of rng.hi as index to jump table
	; This is essentially a case statement
	LIS  %00000011           ; 09dd 73
	NS   RNG.regHi           ; 09de f6
	
	; jump to (jump_table + 2*A)
	DCI  .jumpTable          ; 09df 2a 09 e6
	ADC                      ; 09e2 8e
	ADC                      ; 09e3 8e
	LR   Q,DC                ; 09e4 0e
	; Jump!
	LR   P0,Q                ; 09e5 0d

.jumpTable:
	BR   .north              ; 09e6 90 07
	BR   .east               ; 09e8 90 0a
	BR   .south              ; 09ea 90 13
	BR   .west               ; 09ec 90 1c

.north:
	; ypos = 0x11
	; ydir = sount
	LI   SPAWN_YMIN+1        ; 09ee 20 11
	LR   .ypos,A             ; 09f0 52
	BR   .spawnPlayers       ; 09f1 90 1a
	
.east:
	; xpos = $58 - enemy ball size
	; xdir = west
	LI   MASK_ENEMY_SIZE     ; 09f3 20 30
	NS   main.gameSettings   ; 09f5 fa
	SR   4                   ; 09f6 14
	COM                      ; 09f7 18
	INC                      ; 09f8 1f
	AI   MASK_DIRECTION|(SPAWN_XMAX+1) ; 09f9 24 d8
	LR   .xpos,A             ; 09fb 51
	BR   .spawnPlayers       ; 09fc 90 0f

.south:
	; ypos = $38 - enemy ball size
	; ydir = north
	LI   MASK_ENEMY_SIZE     ; 09fe 20 30
	NS   main.gameSettings   ; 0a00 fa
	SR   4                   ; 0a01 14
	COM                      ; 0a02 18
	INC                      ; 0a03 1f
	AI   MASK_DIRECTION|(SPAWN_YMAX+1) ; 0a04 24 b8
	LR   .ypos,A             ; 0a06 52
	BR   .spawnPlayers       ; 0a07 90 04

.west:
	; xpos = 0x11
	; xdir = east
	LI   SPAWN_XMIN+1        ; 0a09 20 11
	LR   .xpos,A             ; 0a0b 51

.spawnPlayers:
	; exit if current ball is not a player
	LR   A, main.curBall     ; 0a0c 4b
	CI   [MAX_PLAYERS-1]     ; 0a0d 25 01
	BNC   .exit              ; 0a0f 92 0b
	
; Ignore all the above calculations and spawn the players
	; ypos = 0x23
	LI   .PLAYER_Y           ; 0a11 20 23
	LR   .ypos,A             ; 0a13 52
	; if (curBall == Player 1)
	;  xpos = 0x33
	; else xpos = 0x33 + 0x07
	LI   .PLAYER1_X          ; 0a14 20 33
	BNZ  .setPlayerXPos      ; 0a16 94 03
	AI   .PLAYER2_X-.PLAYER1_X ; 0a18 24 07
.setPlayerXPos:
	LR   .xpos,A             ; 0a1a 51

; Save xpos, ypos, and speed
.exit:
	PI   saveBall            ; 0a1b 28 09 a2

	LR   P,K                 ; 0a1e 09
	POP                      ; 0a1f 1c
; end spawnBall()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; drawTimer(int* timer, xpos, ypos)
;  Mid-Level Function
;
; Draws a 4-digit number pointed to by the ISAR. The ISAR should point to the
;  least significant byte of a big-endian word. The x and y positions specify
;  the upper-left corner of the ones digit (not the thousands digit).

; == Arguments ==
; *timer = ISAR
drawTimer.xpos = 0
drawTimer.ypos = 2 ; and color

drawTimer:          
	LR   K,P                 ; 0a20 08
	
; == Local Constant ==
.X_DELTA  = <[-5]
	
; Draw ones digit
	; Load xpos
	LR   A, drawTimer.xpos   ; 0a21 40
	LR   draw.xpos, A        ; 0a22 51
	; Adjust ypos
	LI   TIMER_Y_OFFSET      ; 0a23 20 0a
	AS   drawTimer.ypos      ; 0a25 c2
	LR   draw.ypos, A        ; 0a26 52
	; Set character
	LI   DIGIT_MASK          ; 0a27 20 0f
	NS   (IS)                ; 0a29 fc
	LR   draw.param, A       ; 0a2a 50
	; Width
	LIS  CHAR_WIDTH          ; 0a2b 74
	LR   draw.width, A       ; 0a2c 54
	; Height
	LIS  CHAR_HEIGHT         ; 0a2d 75
	LR   draw.height, A      ; 0a2e 55

	PI   drawChar            ; 0a2f 28 08 58
	
; Draw tens digit
	; Set character
	LR   A,(IS)-             ; 0a32 4e
	SR   4                   ; 0a33 14
	LR   draw.param, A       ; 0a34 50
	; xpos -= xdelta
	LI   .X_DELTA            ; 0a35 20 fb
	AS   draw.xpos           ; 0a37 c1
	LR   draw.xpos, A        ; 0a38 51
	
	PI   drawChar            ; 0a39 28 08 58
	
; Draw hundreds digit
	; Set character
	LR   A,(IS)              ; 0a3c 4c
	NI   DIGIT_MASK          ; 0a3d 21 0f
	LR   draw.param, A       ; 0a3f 50
	; xpos -= xdelta
	LI   .X_DELTA           ; 0a40 20 fb
	AS   draw.xpos           ; 0a42 c1
	LR   draw.xpos, A        ; 0a43 51

	PI   drawChar            ; 0a44 28 08 58
	
; Draw thousands digit
	; Set character
	LR   A,(IS)              ; 0a47 4c
	SR   4                   ; 0a48 14
	LR   draw.param, A       ; 0a49 50
	; xpos -= xdelta
	LI   .X_DELTA            ; 0a4a 20 fb
	AS   draw.xpos           ; 0a4c c1
	LR   draw.xpos, A        ; 0a4d 51

	PI   drawChar            ; 0a4e 28 08 58

	; Exit
	LR   P,K                 ; 0a51 09
	POP                      ; 0a52 1c
; end of drawTimer()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; doBall()
;  Mid-Level Function
;
; This function:
; - Undraws the ball
; - Moves the ball according its velocity
; - Checks if the ball has collided with a wall
; - Saves the changes the ball's velocity
; - Redraws the ball (if the explosion flag is not set)
;
; Since this is such a long function (relative to the rest of the functions in 
;  this game), these parts of the function will be given nice, labeled dividers.
;  Also, the local variables for each part of the function will be declared at
;  the start of each part of the function.

; == Arguments ==
; doBall.size = 070
; doBall.speed = 071
; main.curBall = $b

doBall: subroutine
	LR   K,P                 ; 0a53 08

; -- Undraw the ball -----------------------------------------------------------
.tempYpos = $9

	; Load xpos
	LI   balls.xpos          ; 0a54 20 10
	AS   main.curBall        ; 0a56 cb
	LR   IS,A                ; 0a57 0b
	LR   A,(IS)              ; 0a58 4c
	LR   draw.xpos, A        ; 0a59 51

	; Load ypos
	LR   A,IS                ; 0a5a 0a
	AI   MAX_BALLS           ; 0a5b 24 0b
	LR   IS,A                ; 0a5d 0b
	LR   A,(IS)              ; 0a5e 4c

	; Store temp ypos
	LR   .tempYpos,A         ; 0a5f 59

	; Mask out the color bits from ypos
	NI   MASK_YPOSITION      ; 0a60 21 3f
	LR   draw.ypos, A        ; 0a62 52

	; Load ball size
	SETISAR doBall.size      ; 0a63 67 68
	LR   A,(IS)              ; 0a65 4c
	LR   draw.width, A       ; 0a66 54
	LR   draw.height, A      ; 0a67 55

	; Set parameter
	LI   DRAW_RECT           ; 0a68 20 80
	LR   draw.param, A       ; 0a6a 50

	; Undraw ball
	PI   drawBox             ; 0a6b 28 08 62

	; Reload ypos from temp
	LR   A,.tempYpos         ; 0a6e 49
	LR   draw.ypos, A        ; 0a6f 52

; -- Apply x and y velocities to the ball --------------------------------------
.xpos = $1
.ypos = $2

.tempSpeed = $3
.speedMask = $6

; Get bitpacked velocity
	; ISAR = balls.speed[curBall/2]
	LR   A, main.curBall     ; 0a70 4b
	SR   1                   ; 0a71 12
	AI   balls.speed         ; 0a72 24 26
	LR   IS,A                ; 0a74 0b
				
	; if (index is odd)
	;  speedMask = $0F
	; else
	;  speedMask = $F0
	LIS  $1                  ; 0a75 71
	NS   main.curBall        ; 0a76 fb
	LIS  MASK_SPEED          ; 0a77 7f
	BNZ  .setSpeedMask       ; 0a78 94 02
	COM                      ; 0a7a 18
.setSpeedMask:          
	LR   .speedMask,A        ; 0a7b 56
	
	; Load the other ball's speed nybble
	; Note: This is never read.
	COM                      ; 0a7c 18
	NS   (IS)                ; 0a7d fc
	LR   $0,A                ; 0a7e 50
	
	; Load this ball's speed nybble
	LR   A,.speedMask        ; 0a7f 46
	NS   (IS)                ; 0a80 fc
	LR   .tempSpeed,A        ; 0a81 53
	; Shift right by 4 and save the result if non-zero
	SR   4                   ; 0a82 14
	BZ   .applyVelocity      ; 0a83 84 02
	LR   .tempSpeed,A        ; 0a85 53

; Apply x velocity
.applyVelocity:
	; Test if bit 7 of xpos is set
	CLR                      ; 0a86 70
	AS   .xpos               ; 0a87 c1
	; Save result of test
	LR   J,W                 ; 0a88 1e
	
	; Load xspeed to A
	LR   A,.tempSpeed        ; 0a89 43
	SR   1                   ; 0a8a 12
	SR   1                   ; 0a8b 12
	
	; If bit 7 of xpos wasn't set, branch ahead
	LR   W,J                 ; 0a8c 1d
	BP   .addXVelocity       ; 0a8d 81 03
	
	; Else, negate the xspeed
	COM                      ; 0a8f 18
	INC                      ; 0a90 1f
.addXVelocity:
	; xpos = xpos +/- xspeed
	AS   .xpos               ; 0a91 c1
	LR   .xpos,A             ; 0a92 51

; Apply y velocity
	; Test if bit 7 of ypos is set
	CLR                      ; 0a93 70
	AS   .ypos               ; 0a94 c2
	; Save result of test
	LR   J,W                 ; 0a95 1e
	
	; Load yspeed to A
	LIS  %00000011           ; 0a96 73
	NS   .tempSpeed          ; 0a97 f3
	
	; If bit 7 of ypos wasn't set, branch ahead
	LR   W,J                 ; 0a98 1d
	BP   .addYVelocity       ; 0a99 81 03
	
	; Else, negate yspeed
	COM                      ; 0a9b 18
	INC                      ; 0a9c 1f
.addYVelocity:
	; ypos = ypos +/- yspeed
	AS   .ypos               ; 0a9d c2
	LR   .ypos,A             ; 0a9e 52

; -- Ball/Wall collision detection ---------------------------------------------
.bounceSpeed = $0 ; Speed imparted by bouncing off the walls
.rightBound  = $4
.lowerBound  = $5

; Get player or enemy right bound, depending on curBall
	SETISAR wall.rightEnemy  ; 0a9f 66 68
	LR   A, main.curBall     ; 0aa1 4b
	CI   [MAX_PLAYERS-1]     ; 0aa2 25 01
	BNC   .setRightBound     ; 0aa4 92 02
	SETISARL wall.rightPlayer; 0aa6 69
.setRightBound:          
	LR   A,(IS)              ; 0aa7 4c
	LR   .rightBound,A       ; 0aa8 54

; Likewise, get lower bound
	; .lowerBound = (ISAR+3)
	LR   A,IS                ; 0aa9 0a
	AI   3                   ; 0aaa 24 03
	LR   IS,A                ; 0aac 0b
	LR   A,(IS)              ; 0aad 4c
	LR   .lowerBound,A       ; 0aae 55

; -- Check collision with left and right walls --
	; Clear .bounceSpeed
	CLR                      ; 0aaf 70
	LR   .bounceSpeed,A      ; 0ab0 50

; Check collision with right wall
	; If ball is going leftward, branch ahead
	AS   .xpos               ; 0ab1 c1
	BM   .checkLeftWall      ; 0ab2 91 18
	; Branch if (xpos + rightBound < 256)
	AS   .rightBound         ; 0ab4 c4
	BNC   .checkBottomWall   ; 0ab5 92 29
	
; We have collided with the right wall
	; Clamp position to right wall and set direction to left
	LR   A,.rightBound       ; 0ab7 44
	COM                      ; 0ab8 18
	INC                      ; 0ab9 1f
	AI   MASK_DIRECTION      ; 0aba 24 80
	LR   .xpos,A             ; 0abc 51
	
	; Play sound for hitting wall
	LI   SOUND_1kHz          ; 0abd 20 40
	LR   playSound.sound,A   ; 0abf 53
	PI   playSound           ; 0ac0 28 0c c8

.setXSpeed:
	; .bounceSpeed.x = doBall.speed
	SETISAR doBall.speed     ; 0ac3 67 69
	LR   A,(IS)              ; 0ac5 4c
	SL   1                   ; 0ac6 13
	SL   1                   ; 0ac7 13
	LR   .bounceSpeed,A      ; 0ac8 50
	BR   .checkBottomWall    ; 0ac9 90 15

; Check if colliding with left wall
.checkLeftWall:
	; Mask out the directional bit
	LR   A,.xpos             ; 0acb 41
	NI   MASK_POSITION      ; 0acc 21 7f
	
	; branch ahead if(leftBound < xpos)
	COM                      ; 0ace 18
	INC                      ; 0acf 1f
	SETISAR wall.left        ; 0ad0 66 6a
	AS   (IS)                ; 0ad2 cc
	BNC  .checkBottomWall    ; 0ad3 92 0b
	
	; Clamp position to left wall and set direction to the right
	LR   A,(IS)              ; 0ad5 4c
	LR   .xpos,A             ; 0ad6 51
	
	; Play sound for hitting wall
	LI   SOUND_1kHz          ; 0ad7 20 40
	LR   playSound.sound,A   ; 0ad9 53
	PI   playSound           ; 0ada 28 0c c8
	
	BR   .setXSpeed          ; 0add 90 e5

; -- Check collision with top and bottom walls --
.checkBottomWall:
	CLR                      ; 0adf 70
	; If ball is moving upwards, branch ahead
	AS   .ypos               ; 0ae0 c2
	BM   .checkTopWall       ; 0ae1 91 19
	; Apply bitmask
	NI   MASK_YPOSITION      ; 0ae3 21 3f
	; Branch if ypos + lowerBound < 256
	AS   .lowerBound         ; 0ae5 c5
	BNC  .applySpeedChanges  ; 0ae6 92 27
	
; We have collided with the lower wall
	; Clamp position to the lower wall and set the direction to up
	LR   A,.lowerBound       ; 0ae8 45
	COM                      ; 0ae9 18
	INC                      ; 0aea 1f
	AI   MASK_DIRECTION      ; 0aeb 24 80
	LR   draw.ypos,A         ; 0aed 52
	
	; Play sound for hitting wall
	LI   SOUND_1kHz          ; 0aee 20 40
	LR   playSound.sound,A   ; 0af0 53
	PI   playSound           ; 0af1 28 0c c8

; Set y speed
.setYSpeed:
	; yspeed = doBall.speed
	SETISAR doBall.speed     ; 0af4 67 69
	LR   A,(IS)              ; 0af6 4c
	AS   .bounceSpeed        ; 0af7 c0
	LR   .bounceSpeed,A      ; 0af8 50
	BR   .applySpeedChanges  ; 0af9 90 14

; Check if colliding with top wall
.checkTopWall:
	SETISARU wall.upper      ; 0afb 66
	NI   MASK_YPOSITION      ; 0afc 21 3f
	; branch ahead if(topBound < ypos)
	COM                      ; 0afe 18
	INC                      ; 0aff 1f
	SETISARL wall.upper      ; 0b00 6d
	AS   (IS)                ; 0b01 cc
	BNC   .applySpeedChanges ; 0b02 92 0b
	
; We have collided with the top wall
	; Clamp position to top wall and set direction downwards
	LR   A,(IS)              ; 0b04 4c
	LR   draw.ypos,A         ; 0b05 52

	; Play sound for hitting wall	
	LI   SOUND_1kHz          ; 0b06 20 40
	LR   playSound.sound,A   ; 0b08 53
	PI   playSound           ; 0b09 28 0c c8
	
	BR   .setYSpeed          ; 0b0c 90 e7

; -- Apply velocity changes from wall bounces ----------------------------------
; Variables pertaining to curBall
.thisSpeed    = $5
.thisBitmask  = $7
; Variables pertaining to the ball that shares curBall's speed byte
.otherSpeed   = $4
.otherBitmask = $6

.applySpeedChanges:
	; Copy lower nybble to upper nybble
	LR   A,.bounceSpeed      ; 0b0e 40
	SL   4                   ; 0b0f 15
	AS   .bounceSpeed        ; 0b10 c0
	LR   .bounceSpeed,A      ; 0b11 50

	; ISAR = index of the speed byte
	LR   A,main.curBall      ; 0b12 4b
	SR   1                   ; 0b13 12
	AI   balls.speed         ; 0b14 24 26
	LR   IS,A                ; 0b16 0b

	; Set the bitmask for the appropriate nybble
	LIS  $1                  ; 0b17 71
	NS   main.curBall        ; 0b18 fb
	LIS  MASK_SPEED          ; 0b19 7f
	BNZ  .setSpeedMaskAgain  ; 0b1a 94 02
	COM                      ; 0b1c 18
.setSpeedMaskAgain:
	LR   .thisBitmask, A     ; 0b1d 57

	; Set the bitmask for the other ball's speed nybble 
	COM                      ; 0b1e 18
	LR   .otherBitmask,A     ; 0b1f 56
	; Save other ball's speed nybble
	NS   (IS)                ; 0b20 fc
	LR   .otherSpeed,A       ; 0b21 54

	; Apply the bitmask to get our speed from memory
	LR   A,.thisBitmask      ; 0b22 47
	NS   (IS)                ; 0b23 fc
	LR   .thisSpeed,A        ; 0b24 55

; Apply y axis bounce
	; Branch ahead if .bounceSpeed.y == 0
	LI   MASK_YSPEED         ; 0b25 20 33
	NS   .bounceSpeed        ; 0b27 f0
	BZ   .saveXAxisBounce    ; 0b28 84 0c

	; Mask out yspeed from thisSpeed
	LI   MASK_XSPEED         ; 0b2a 20 cc
	NS   .thisBitmask        ; 0b2c f7
	NS   .thisSpeed          ; 0b2d f5
	LR   .thisSpeed,A        ; 0b2e 55
	
	; .thisSpeed.y = .bounceSpeed.y
	LI   MASK_YSPEED         ; 0b2f 20 33
	NS   .bounceSpeed        ; 0b31 f0
	AS   .thisSpeed          ; 0b32 c5
	NS   .thisBitmask        ; 0b33 f7
	LR   .thisSpeed,A        ; 0b34 55

; Apply x axis bounce
.saveXAxisBounce:
	; Branch ahead if .bounceSpeed.x == 0
	LI   MASK_XSPEED         ; 0b35 20 cc
	NS   .bounceSpeed        ; 0b37 f0
	BZ   .prepSaveBall       ; 0b38 84 0c
				
	; Mask out xspeed from thisSpeed
 	LI   MASK_YSPEED         ; 0b3a 20 33
	NS   .thisBitmask        ; 0b3c f7
	NS   .thisSpeed          ; 0b3d f5
	LR   .thisSpeed,A        ; 0b3e 55

	; .thisSpeed.x = .bounceSpeed.x
	LI   MASK_XSPEED         ; 0b3f 20 cc
	NS   .bounceSpeed        ; 0b41 f0
	AS   .thisSpeed          ; 0b42 c5
	NS   .thisBitmask        ; 0b43 f7
	LR   .thisSpeed,A        ; 0b44 55

; Prepare to save ball to array
.prepSaveBall:
	; Merge the nybbles back together
	LR   A,.thisSpeed        ; 0b45 45
	AS   .otherSpeed         ; 0b46 c4

	; Set speed for saveBall
	LR   saveBall.speed,A    ; 0b47 50

	; It is finished... we can save the results
	PI   saveBall            ; 0b48 28 09 a2
	
; -- Redraw the ball -----------------------------------------------------------
	; if(curball <=1)
	;  color = ballColors[curBall]
	; else
	;  color = ballColors[2]
	DCI  ballColors          ; 0b4b 2a 08 50
	LR   A,main.curBall      ; 0b4e 4b
	CI   [MAX_PLAYERS-1]     ; 0b4f 25 01
	LIS  2                   ; 0b51 72
	BNC   .setColor          ; 0b52 92 02
	LR   A,main.curBall      ; 0b54 4b
.setColor:
	ADC                      ; 0b55 8e
	LR   A, draw.ypos        ; 0b56 42

	; Mask out the direction
	NI   MASK_POSITION      ; 0b57 21 7f

	; OR in the color
	OM                       ; 0b59 8b
	LR   draw.ypos, A        ; 0b5a 52

	; Set drawing parameters
	LI   DRAW_RECT           ; 0b5b 20 80
	LR   draw.param, A       ; 0b5d 50

	; Set ball width/height
	SETISAR doBall.size      ; 0b5e 67 68
	LR   A,(IS)              ; 0b60 4c
	LR   draw.width, A       ; 0b61 54
	LR   draw.height, A      ; 0b62 55

	; Do not redraw if explosion flag is set
	SETISAR explosionFlag    ; 0b63 67 6a
	CLR                      ; 0b65 70
	AS   (IS)                ; 0b66 cc
	BM   .return             ; 0b67 91 04

	; Redraw ball
	PI   drawBox             ; 0b69 28 08 62

collision.return: ; The next function uses this to return as well
.return:
	LR   P,K                 ; 0b6c 09
	POP                      ; 0b6d 1c
; end doBall()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; collision()
;
; Performs ball-ball collision detection against a single ball as specified by
;  the input argument.
;
; The ball specified by the caller is tested against every other active ball to
;  see if they overlap along the x and y axes. If so, and if the ball being
;  tested against is the player's ball, this function jumps directly to
;  gameOver(). Otherwise, the x and y directions and speeds are fiddled with to 
;  make them bounce off each other.

; == Arguments ==
; main.curBall = $b

; == Locals ==
; testBall = 071
mainBall.xpos = $1
mainBall.ypos = $2

; == Entry Point ==
collision: subroutine
	LR   K,P                 ; 0b6e 08

	; setting up the collision loop counter
	; testBall = (delayIndex & 0x0F) + 1
	SETISAR delayIndex       ; 0b6f 65 6f
	LI   %00001111           ; 0b71 20 0f
	NS   (IS)                ; 0b73 fc
	SETISAR testBall         ; 0b74 67 69
	INC                      ; 0b76 1f
	LR   (IS),A              ; 0b77 5c

.testBallLoop:
	; loopCount--
	SETISAR testBall         ; 0b78 67 69
	DS   (IS)                ; 0b7a 3c

	; if(testBall < 0), return
	BM   collision.return; 0b7b 91 f0

	; if(testBall == curBall), skip and go to next ball
	LR   A,(IS)              ; 0b7d 4c
	XS   main.curBall        ; 0b7e eb
	BZ   .testBallLoop       ; 0b7f 84 f8

	; Check if we're in 2-player mode
	SETISARL gameMode        ; 0b81 6d
	LIS  $1                  ; 0b82 71
	NS   (IS)                ; 0b83 fc
	; If so, skip ahead
	BNZ  .getBallPosition    ; 0b84 94 07
	
	; If not, check if the loop counter is a player's ball
	SETISARL testBall        ; 0b86 69
	LR   A,(IS)              ; 0b87 4c
	CI   [MAX_PLAYERS-1]     ; 0b88 25 01
	; If so, skip the current ball
	BZ   .testBallLoop ; 0b8a 84 ed

.getBallPosition:
	; r1 = xpos[curBall]
	LI   balls.xpos          ; 0b8c 20 10
	AS   main.curBall        ; 0b8e cb
	LR   IS,A                ; 0b8f 0b
	LR   A,(IS)              ; 0b90 4c
	; Mask out the direction
	NI   MASK_POSITION       ; 0b91 21 7f
	LR   mainBall.xpos,A     ; 0b93 51
	
	; r2 = ypos[curBall]
	LR   A,IS                ; 0b94 0a
	AI   MAX_BALLS           ; 0b95 24 0b
	LR   IS,A                ; 0b97 0b
	LR   A,(IS)              ; 0b98 4c
	; Mask out the direction
	NI   MASK_YPOSITION      ; 0b99 21 3f
	LR   mainBall.ypos,A     ; 0b9b 52
	
; -- Test collision along x axis -----------------------------------------------
.xDelta = $1

	; mainBall.xpos-testBall.xpos
	SETISAR testBall         ; 0b9c 67 69
	LI   balls.xpos          ; 0b9e 20 10
	AS   (IS)                ; 0ba0 cc
	LR   IS,A                ; 0ba1 0b
	LR   A,(IS)              ; 0ba2 4c
	NI   MASK_POSITION       ; 0ba3 21 7f
	COM                      ; 0ba5 18
	INC                      ; 0ba6 1f
	AS   mainBall.xpos       ; 0ba7 c1
	
	; Save flags
	LR   J,W                 ; 0ba8 1e
	; Keep results if (mainBall.xpos >= testBall.xpos)
	BP   .saveXdelta         ; 0ba9 81 03	
	; Otherwise negate the results
	COM                      ; 0bab 18
	INC                      ; 0bac 1f
	
.saveXdelta:
	; abs(mainBall.x - testBall.x)
	LR   .xDelta,A           ; 0bad 51
	
	; branch ahead if testBall is not a player ball
	LR   A,IS                ; 0bae 0a
	CI   [balls.xpos+MAX_PLAYERS-1] ; 0baf 25 11
	BNC  .useEnemySize       ; 0bb1 92 0b

	; branch ahead if mainBall.xpos < testBall.xpos
	;  or: if mainBall is left of testBall
	LR   W,J                 ; 0bb3 1d    ; Reuse flags from earlier
	BM   .useEnemySize       ; 0bb4 91 08
				
	; Get player ball width
	LI   MASK_PLAYER_SIZE    ; 0bb6 20 c0
	NS   main.gameSettings   ; 0bb8 fa
	SR   1                   ; 0bb9 12
	SR   1                   ; 0bba 12
	BR   .testXaxis          ; 0bbb 90 04

	; or get enemy ball width
.useEnemySize:
	LI   MASK_ENEMY_SIZE     ; 0bbd 20 30
	NS   main.gameSettings   ; 0bbf fa

.testXaxis:
	SR   4                   ; 0bc0 14

	; xDelta - testBall.width
	COM                      ; 0bc1 18
	INC                      ; 0bc2 1f
	AS   .xDelta             ; 0bc3 c1

	; if (xDelta >= testBall.width)
	;  continue on to next ball
	BP   .testBallLoop             ; 0bc4 81 b3
	; else
	;  test the y axis collision

; -- Test collision on the y axis ----------------------------------------------
.yDelta = $2

	; mainBall.ypos-testBall.ypos
	LR   A,IS                ; 0bc6 0a
	AI   MAX_BALLS           ; 0bc7 24 0b
	LR   IS,A                ; 0bc9 0b
	LR   A,(IS)              ; 0bca 4c
	NI   MASK_YPOSITION      ; 0bcb 21 3f
	COM                      ; 0bcd 18
	INC                      ; 0bce 1f
	AS   mainBall.ypos       ; 0bcf c2
	
	; Save flags
	LR   J,W                 ; 0bd0 1e
	; Keep results if (mainBall.ypos >= testBall.ypos)
	BP   .saveYdelta         ; 0bd1 81 03
	; Otherwise negate the results
	COM                      ; 0bd3 18
	INC                      ; 0bd4 1f
.saveYdelta:
	; abs(mainBall.ypos-testBall.ypos)
	LR   .yDelta,A           ; 0bd5 52

	; branch ahead if testBall is not a player ball
	LR   A,IS                ; 0bd6 0a
	CI   [balls.ypos+MAX_PLAYERS-1]; 0bd7 25 1c
	BNC   .useEnemySize2     ; 0bd9 92 0b

	; branch ahead if mainBall.ypos < testBall.ypos
	;  or: if mainBall is north of testBall
	LR   W,J                 ; 0bdb 1d    ; Reuse flags from earlier
	BM   .useEnemySize2      ; 0bdc 91 08
	
	; Get player ball width
	LI   MASK_PLAYER_SIZE    ; 0bde 20 c0
	NS   main.gameSettings   ; 0be0 fa
	SR   1                   ; 0be1 12
	SR   1                   ; 0be2 12
	BR   .testYaxis          ; 0be3 90 04
	; or get enemy ball width
.useEnemySize2:
	LI   MASK_ENEMY_SIZE     ; 0be5 20 30
	NS   main.gameSettings   ; 0be7 fa
.testYaxis:
	SR   4                   ; 0be8 14
	
	; yDelta - tempWidth
	COM                      ; 0be9 18
	INC                      ; 0bea 1f
	AS   .yDelta             ; 0beb c2

	; if (yDelta >= tempWidth)
	;  continue on to next ball
	BP   .testBallLoop       ; 0bec 81 8b
	; else
	;  handle the collision that just happened

; -- If we got to this point, a collision has happened -------------------------
	
	; Check if the collision was with a player
	;  If so, game over
	;  Else, skip ahead
	SETISAR testBall         ; 0bee 67 69
	LR   A,(IS)              ; 0bf0 4c
	CI   [MAX_PLAYERS-1]     ; 0bf1 25 01
	BNC   .makeNoise         ; 0bf3 92 04
	; Game over
	JMP  gameOver            ; 0bf5 29 0e 44

.makeNoise:
	; Play sound
	LI   SOUND_500Hz         ; 0bf8 20 80
	LR   playSound.sound,A   ; 0bfa 53
	PI   playSound           ; 0bfb 28 0c c8
	
	; RNG for random bounce trajectory
	PI   rand                ; 0bfe 28 08 c1

	; branch ahead if(yDelta < 1)
	LR   A,.yDelta           ; 0c01 42
	CI   1                   ; 0c02 25 01
	BC   .randYdirection     ; 0c04 82 3c

; -- Fiddle with the x direction -----------------------------------------------
.speedThing = $8
.SPEED_ADJUST = $44
.randBall = $0 ; TODO: Give this variable a better name (it's not random)
	
; Randomize x direction of mainBall
	; Set ISAR to xpos[curBall]
	LI   balls.xpos          ; 0c06 20 10
	AS   main.curBall        ; 0c08 cb
	LR   IS,A                ; 0c09 0b
	; XOR the direction with the RNG
	LI   MASK_DIRECTION      ; 0c0a 20 80
	NS   RNG.regHi           ; 0c0c f6
	XS   (IS)                ; 0c0d ec
	LR   (IS),A              ; 0c0e 5c
	; Save flags from (MASK_DIRECTION xor RNG)
	; Note: These flags do not appear to be used
	LR   J,W                 ; 0c0f 1e

; Randomize x direction of testBall
	; ISAR = balls.xpos + testBall
	SETISAR testBall         ; 0c10 67 69
	LI   balls.xpos          ; 0c12 20 10
	AS   (IS)                ; 0c14 cc
	LR   IS,A                ; 0c15 0b
	; Add RNG.lo to the direction
	LI   MASK_DIRECTION      ; 0c16 20 80
	NS   RNG.regLo           ; 0c18 f7
	AS   (IS)                ; 0c19 cc
	LR   (IS),A              ; 0c1a 5c
				
	; We'll be using this later to adjust the speed
	LI   .SPEED_ADJUST       ; 0c1b 20 44
	LR   .speedThing,A       ; 0c1d 58

	; randBall = mainBall
	;  The branch that fiddles the y direction sets this to testBall
	LR   A, main.curBall     ; 0c1e 4b
	LR   .randBall,A         ; 0c1f 50

; -- Fiddle with the speed -----------------------------------------------------
.thisBitmask = $3
.otherSpeed = $4

.checkMode:
	; If MODE_BOUNCE_MASK is set, we mess with the speed
	;  Note: This bit is set in shuffleGame(), and is cleared in explode()
	SETISAR gameMode         ; 0c20 67 6d
	CLR                      ; 0c22 70
	AS   (IS)                ; 0c23 cc
	BP   .changeSpeed        ; 0c24 81 04
	; Else, test the next ball
	JMP  .testBallLoop       ; 0c26 29 0b 78

.changeSpeed:
	; ISAR = balls.speed + randBall/2
	LR   A,.randBall         ; 0c29 40
	SR   1                   ; 0c2a 12
	AI   balls.speed         ; 0c2b 24 26
	LR   IS,A                ; 0c2d 0b

	; Conjure up the bitmask to extract randBall's speed
	LIS  $1                  ; 0c2e 71
	NS   .randBall           ; 0c2f f0
	LIS  MASK_SPEED          ; 0c30 7f
	BNZ  .getThisBitmask     ; 0c31 94 02
	COM                      ; 0c33 18
.getThisBitmask:
	; Save randBall's speed bitmask
	LR   .thisBitmask,A      ; 0c34 53
	; Temp storage for the other speed bitfield
	COM                      ; 0c35 18
	NS   (IS)                ; 0c36 fc
	LR   .otherSpeed,A       ; 0c37 54
	
	; Get the speed bitfield for randBall
	LR   A,.thisBitmask      ; 0c38 43
	NS   (IS)                ; 0c39 fc
	; Add .speedThing to it, and clean up with the bitmask
	AS   .speedThing         ; 0c3a c8
	NS   .thisBitmask        ; 0c3b f3
	; Merge the two speed bitfields and save the result
	AS   .otherSpeed         ; 0c3c c4
	LR   (IS),A              ; 0c3d 5c
	
	; Return (don't process any more collisions for mainBall)
	JMP  collision.return            ; 0c3e 29 0b 6c

; -- Fiddle with y direction ---------------------------------------------------
.randYdirection:
	; randBall = testBall
	; ISAR = balls.ypos + randBall
	SETISAR testBall         ; 0c41 67 69
	LR   A,(IS)              ; 0c43 4c
	LR   .randBall,A         ; 0c44 50
	AI   balls.ypos          ; 0c45 24 1b
	LR   IS,A                ; 0c47 0b

	; Flip the y direction of testBall
	LI   MASK_DIRECTION      ; 0c48 20 80
	XS   (IS)                ; 0c4a ec
	LR   (IS),A              ; 0c4b 5c
	; Save flags for later
	LR   J,W                 ; 0c4c 1e

	; ISAR = balls.ypos + mainBall
	LI   balls.ypos          ; 0c4d 20 1b
	AS   main.curBall        ; 0c4f cb
	LR   IS,A                ; 0c50 0b

	; Set mainBall's direction to down
	LR   A,(IS)              ; 0c51 4c
	OI   MASK_DIRECTION      ; 0c52 22 80
	
	; Load flags from earlier
	;  if testBall went down, mainBall goes up
	;  if testBall went up, mainBall goes down
	LR   W,J                 ; 0c54 1d
	BP   .setYdirection      ; 0c55 81 03
	NI   MASK_YPOSITION      ; 0c57 21 3f
.setYdirection:
	LR   (IS),A              ; 0c59 5c
	
	; We'll be using this later to adjust the velocity
	LI   .SPEED_ADJUST       ; 0c5a 20 44
	LR   .speedThing,A       ; 0c5c 58
	
	; Go to the "fiddle with speed" section of this function
	BR   .checkMode          ; 0c5d 90 c2
; end of collision()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; setWalls()
;
; Sets the positions of the walls along one axis given an input range.
  
; == Arguments ==
; *walls = ISAR
walls.max = $1
walls.min = $2

; == Constants ==


setWalls: subroutine 
	LR   K,P                 ; 0c5f 08

; == Local ==
.tempWall = $4

.reroll:
	; Reroll RNG until r6 is non-zero
	PI   rand                ; 0c60 28 08 c1
	CLR                      ; 0c63 70
	AS   RNG.regHi           ; 0c64 c6
	BZ   .reroll             ; 0c65 84 fa
	
; Make sure the RNG is in range, depending on the axis being set
	; if(r1 == 0x58) ; x axis case
	;  if(RNG > 0x12)
	;   go back and reroll
	; else if(RNG > 0x0B) ; y axis case
	;   go back and reroll
	LR   A,walls.max         ; 0c67 41
	CI   WALL_XMAX           ; 0c68 25 58

	LR   A, RNG.regHi        ; 0c6a 46
	BNZ  .clampY             ; 0c6b 94 05

	CI   WALL_X_OFFSET_MAX   ; 0c6d 25 12
	BR   .clampX             ; 0c6f 90 03

.clampY:
	CI   WALL_Y_OFFSET_MAX   ; 0c71 25 0b
.clampX:
	BNC  .reroll             ; 0c73 92 ec

; Get the base value for the right/lower wall
	;  Note: the greater this number is, the more to the left (or top) this wall
	;   is. (Unintuitive. Works opposite of how the upper and left walls work.)
	; .tempWall = -(max-rng+1)
	COM                      ; 0c75 18
	INC                      ; 0c76 1f
	INC                      ; 0c77 1f
	AS   walls.max           ; 0c78 c1
	COM                      ; 0c79 18
	INC                      ; 0c7a 1f
	LR   .tempWall,A         ; 0c7b 54
	
; Adjust the right/lower wall according to the enemy's size
	; wall.right(or lower)Enemy = playerSize + .tempWall
	LI   MASK_ENEMY_SIZE     ; 0c7c 20 30
	NS   main.gameSettings   ; 0c7e fa
	SR   4                   ; 0c7f 14
	AS   .tempWall           ; 0c80 c4
	LR   (IS)+,A             ; 0c81 5d
	
; Adjust the right/lower wall according to the player's size
	; wall.right(or lower)Player = playerSize + .tempWall
	LI   MASK_PLAYER_SIZE    ; 0c82 20 c0
	NS   main.gameSettings   ; 0c84 fa
	SR   4                   ; 0c85 14
	SR   1                   ; 0c86 12
	SR   1                   ; 0c87 12
	AS   .tempWall           ; 0c88 c4
	LR   (IS)+,A             ; 0c89 5d
	
; Set the left or top boundary
	; ISAR++ = walls.min + RNG
	LR   A,RNG.regHi         ; 0c8a 46
	AS   walls.min           ; 0c8b c2
	LR   (IS)+,A             ; 0c8c 5d

	; Exit
	LR   P,K                 ; 0c8d 09
	POP                      ; 0c8e 1c
; end of setWalls()
;-------------------------------------------------------------------------------
	
;-------------------------------------------------------------------------------
; flash()
;  Mid-Level Function
;
; UNUSED
;
; Makes the screen flash -- possibly an old form of the death animation. Working
;  off of that assumption, we will assume that this function would have been
;  called after a player collision in the ball-ball collision function.

; == Arguments ==
; testBall = 071

; No Returns

flash: subroutine
	LR   K,P                 ; 0c8f 08

; == Locals ==
.loopCount = $9
.NUM_LOOPS = $25
	
	LI   .NUM_LOOPS          ; 0c90 20 25
	LR   .loopCount, A       ; 0c92 59

	; Set flash color/sound value depending on value of o71 (who died?)
	SETISAR testBall         ; 0c93 67 69
	LIS  $1                  ; 0c95 71
	NS   (IS)-               ; 0c96 fe
	LI   SOUND_500Hz         ; 0c97 20 80
	BZ   .setSound           ; 0c99 84 03
	LI   SOUND_120Hz         ; 0c9b 20 c0
.setSound:          
	LR   (IS), A             ; 0c9d 5c
	LR   draw.ypos, A        ; 0c9e 52

; Loop back here to reset the sound and row attribute color to the above value
.loopResetColor:          
	LR   A,(IS)              ; 0c9f 4c

; Loop back here to keep the sound and row attribute color cleared
.loopClearColor:          
	; Set ypos/color
	LR   draw.ypos, A        ; 0ca0 52

	; Make sound
	; NOTE: sound is not played if curBall is one of the player balls
	LR   A,(IS)              ; 0ca1 4c
	LR   playSound.sound,A   ; 0ca2 53
	PI   playSound           ; 0ca3 28 0c c8
	
	LISL 0                   ; 0ca6 68 ; ISAR = 070 ; Temp?
	; Set xpos to attribute column
	LI   DRAW_ATTR_X         ; 0ca7 20 7d
	LR   draw.xpos, A        ; 0ca9 51
	; Set width
	LIS  DRAW_ATTR_W         ; 0caa 72
	LR   draw.width, A       ; 0cab 54
	; Set height
	LI   DRAW_SCREEN_H       ; 0cac 20 40
	LR   draw.height, A      ; 0cae 55
	; Set rendering parameter
	LI   DRAW_ATTRIBUTE      ; 0caf 20 c0
	LR   draw.param, A       ; 0cb1 50
	PI   drawBox             ; 0cb2 28 08 62
	
	; Clear sound
	CLR                      ; 0cb5 70
	OUTS 5                   ; 0cb6 b5
	
	; Delay
	LIS  $b                  ; 0cb7 7b
	LR   delay.count, A      ; 0cb8 50
	PI   delayVariable       ; 0cb9 28 09 9a

	; loopCount--
	;  exit it less than zero
	DS   .loopCount          ; 0cbc 39
	BM   .exit               ; 0cbd 91 08
	
	; if (timer is even)
	;  ypos/color = (ISAR)
	LIS  $1                  ; 0cbf 71
	NS   .loopCount          ; 0cc0 f9
	CLR                      ; 0cc1 70
	BZ   .loopResetColor     ; 0cc2 84 dc
	; else
	;  ypos/color = 0
	BR   .loopClearColor     ; 0cc4 90 db

.exit:     
	LR   P,K                 ; 0cc6 09
	POP                      ; 0cc7 1c
; end flash()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; playSound(ball, sound)
;  Leaf Function
;
; Make a ticking noise when the balls collide with something.

; == Arguments ==
playSound.sound = 3
; main.curBall = $b

; == Entry Point ==
playSound: subroutine
	; if(curBall >= MAX_PLAYERS)
	LR   A, main.curBall     ; 0cc8 4b
	CI   [MAX_PLAYERS-1]     ; 0cc9 25 01
	BC   playSound.exit      ; 0ccb 82 03
	; then play the sound
	LR   A, playSound.sound  ; 0ccd 43
	OUTS 5                   ; 0cce b5
	
playSound.exit:          
	POP                      ; 0ccf 1c
; end playSound()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; init()
;  Top-Level Procedure
;
; For simplicity's sake, this disassembly will divide the top-level thread into
;  separate "functions", even though they are not callable and the code just
;  flows and jumps from one block to another.
;
; To initialize the game, this procedure does some initial bookkeeping with the
;  scratchpad, I/O, and display, and then asks the player to select the game
;  mode with the question "G?" The four selectable game types are:
;
; 1 - Slow, 1 player
; 2 - Slow, 2 players
; 3 - Fast, 1 player
; 4 - Fast, 2 players

init: subroutine
	SETISAR RNG.seedLo       ; 0cd0 67 6f

	; Enable data from controllers
	LI   $40                 ; 0cd2 20 40
	OUTS 0                   ; 0cd4 b0
	
	; Seed RNG from uninitialized ports
	INS  4                   ; 0cd5 a4
	LR   (IS)-,A             ; 0cd6 5e
	INS  5                   ; 0cd7 a5
	LR   (IS)-,A             ; 0cd8 5e
	
	; Clear BIOS stack pointer at 073
	;  This game does not use the BIOS's stack functions
	LISL 3                   ; 0cd9 6b
	CLR                      ; 0cda 70
	LR   (IS),A              ; 0cdb 5c
	; The BIOS already intialized the rest of the scratchpad to zero
	
	; Clear port
	OUTS 0                   ; 0cdc b0

; Clear screen
	; Set properties
	LI   DRAW_RECT           ; 0cdd 20 80
	LR   draw.param, A       ; 0cdf 50
	; Set x and y pos
	CLR                      ; 0ce0 70
	LR   draw.xpos, A        ; 0ce1 51
	LR   draw.ypos, A        ; 0ce2 52
	; Set width
	LI   DRAW_SCREEN_W       ; 0ce3 20 80
	LR   draw.width, A       ; 0ce5 54
	; Set height
	LI   DRAW_SCREEN_H       ; 0ce6 20 40
	LR   draw.height, A      ; 0ce8 55

	PI   drawBox             ; 0ce9 28 08 62

; Set row attributes
	; Set rendering properties, ypos, and color
	LI   DRAW_ATTRIBUTE      ; 0cec 20 c0
	LR   draw.param, A       ; 0cee 50
	LR   draw.ypos, A        ; 0cef 52
	; Set width
	LIS  DRAW_ATTR_W         ; 0cf0 72
	LR   draw.width, A       ; 0cf1 54
	; xpos = attribute column
	LI   DRAW_ATTR_X         ; 0cf2 20 7d
	LR   draw.xpos, A        ; 0cf4 51
	; Height and ypos are retained from previous write
	PI   drawBox             ; 0cf5 28 08 62

; Draw the "G?" screen
.G_X = $30
.G_Y = $1B
.Q_X = $35

	; Set char
	LIS  CHAR_G              ; 0cf8 7a
	LR   draw.param, A       ; 0cf9 50
	; Set xpos
	LI   .G_X                ; 0cfa 20 30
	LR   draw.xpos, A        ; 0cfc 51
	; Set ypos and color
	LI   RED | .G_Y          ; 0cfd 20 9b
	LR   draw.ypos, A        ; 0cff 52
	; Set width
	LIS  CHAR_WIDTH          ; 0d00 74
	LR   draw.width, A       ; 0d01 54
	; Set height
	LIS  CHAR_HEIGHT         ; 0d02 75
	LR   draw.height, A      ; 0d03 55

	PI   drawChar            ; 0d04 28 08 58
	
	; Set char
	LIS  CHAR_QMARK          ; 0d07 7b
	LR   draw.param, A       ; 0d08 50
	; Set xpos
	LI   .Q_X                ; 0d09 20 35
	LR   draw.xpos, A        ; 0d0b 51

	PI   drawChar            ; 0d0c 28 08 58
	
; Wait 10 seconds for input
	PI   menu                ; 0d0f 28 08 f0
	; The button press is returned in A (default is 1)

; Use a table to put the number of the button pressed into gameMode
	SETISAR gameMode         ; 0d12 67 6d
	SR   1                   ; 0d14 12
	; DC was set in the menu
	ADC                      ; 0d15 8e
	LM                       ; 0d16 16
	LR   (IS),A              ; 0d17 5c

	; Continue on to next procedure
	
;-------------------------------------------------------------------------------
; shuffleGame()
;  Top-Level Procedure
;
; This function randomizes the game parameters such as player size, enemy size,
;  player speed, enemy speed, the upper six bits of gameMode, and the walls.

shuffleGame: subroutine
	; Preserve the player and game speed bits of gameMode
	SETISAR gameMode         ; 0d18 67 6d
	LR   A,(IS)              ; 0d1a 4c
	NI   MODE_CHOICE_MASK    ; 0d1b 21 03
	LR   (IS),A              ; 0d1d 5c

.reroll:
	; Array of bitmaks to be used in the following series of tests
	DCI  gameModeMasks       ; 0d1e 2a 08 43
	
	; Get a random number
	PI   rand                ; 0d21 28 08 c1
	
; Test to see if the number is a valid game setting
.temp = $8

	; Put bits 6 and 7 of RNG into .temp (for player ball size)
	LM                       ; 0d24 16
	NS   RNG.regHi           ; 0d25 f6
	LR   .temp,A             ; 0d26 58
	
	; Add bits 4 and 5 of RNG to the previous result (for enemy ball size)
	LM                       ; 0d27 16
	NS   RNG.regHi           ; 0d28 f6
	SL   1                   ; 0d29 13
	SL   1                   ; 0d2a 13
	AS   .temp               ; 0d2b c8
	; if(playerSize + enemySize < 4), then reroll
	BNC  .reroll             ; 0d2c 92 f1
	
	; Test if at least one of bits 2 and 3 of RNG are set
	LM                       ; 0d2e 16
	NS   RNG.regHi           ; 0d2f f6
	; if(playerSpeed == 0), then reroll
	BZ   .reroll             ; 0d30 84 ed

	; Test if at least one of bits 0 and 1 of RNG are set
	LM                       ; 0d32 16
	NS   RNG.regHi           ; 0d33 f6
	; if(enemySpeed == 0), then reroll
	BZ   .reroll             ; 0d34 84 e9

	; RNG.regHi contains a valid value, so we can use it
	LR   A, RNG.regHi        ; 0d36 46
	LR   main.gameSettings,A ; 0d37 5a

; Put the upper six bits of the RNG into gameMode
	LM                       ; 0d38 16
	NS   RNG.regLo           ; 0d39 f7
	AS   (IS)                ; 0d3a cc
	LR   (IS)-,A             ; 0d3b 5e
	; Note: This ISAR post-decrement puts the ISAR on player 2's high score.
	;  This is not utilized.

	; DC = (enemySpeed)*2
	; Note: This array is never read from.
	DCI  unusedSpeedTable    ; 0d3c 2a 08 48
	LIS  MASK_ENEMY_SPEED    ; 0d3f 73
	NS   main.gameSettings   ; 0d40 fa
	SL   1                   ; 0d41 13
	ADC                      ; 0d42 8e
	; Note: Perhaps the 2 bytes from this table were meant to be loaded into the
	;  space that is now reserved for player 2's high score.

; Set playfield walls
	; Set playfield walls for x axis
	LI   WALL_XMAX           ; 0d43 20 58
	LR   walls.max,A         ; 0d45 51
	LI   WALL_MIN            ; 0d46 20 10
	LR   walls.min,A         ; 0d48 52
	SETISAR wall.rightEnemy  ; 0d49 66 68
	PI   setWalls            ; 0d4b 28 0c 5f
	
	; Set playfield walls for y axis
	LI   WALL_YMAX           ; 0d4e 20 38
	LR   walls.max,A         ; 0d50 51
	PI   setWalls            ; 0d51 28 0c 5f
	
	; Continue on to next procedure

;-------------------------------------------------------------------------------
; restartGame()
;  Top-Level Procedure
;
; Does prep work necessary to restart (or start the game), such as drawing the
;  playfield, clearing the timer, spawning the players and the first ball, and
;  making sure the explosion flag is clear.

restartGame: subroutine

; Draw playfield walls
	; Set rendering properties
	LI   DRAW_RECT           ; 0d54 20 80
	LR   draw.param, A       ; 0d56 50
	; Set x pos
	LI   FIELD_CORNER        ; 0d57 20 10
	LR   draw.xpos, A        ; 0d59 51
	; Set color (and ypos)
	AI   RED                 ; 0d5a 24 80
	LR   draw.ypos, A        ; 0d5c 52
	; Set width
	LI   FIELD_WIDTH         ; 0d5d 20 49
	LR   draw.width, A       ; 0d5f 54
	; Set height
	LI   FIELD_HEIGHT        ; 0d60 20 29
	LR   draw.height, A      ; 0d62 55
	; Draw box
	PI   drawBox             ; 0d63 28 08 62

; Draw inner box of playfield
.tempSize = $3

	; xpos = wall.left
	SETISAR wall.left        ; 0d66 66 6a
	LR   A,(IS)              ; 0d68 4c
	LR   draw.xpos, A        ; 0d69 51

	; width = -(wall.left + wall.rightEnemy) + enemySize
	SETISARL wall.rightEnemy ; 0d6a 68
	AS   (IS)                ; 0d6b cc
	COM                      ; 0d6c 18
	INC                      ; 0d6d 1f
	LR   draw.width, A       ; 0d6e 54

	LI   MASK_ENEMY_SIZE     ; 0d6f 20 30
	NS   main.gameSettings   ; 0d71 fa
	SR   4                   ; 0d72 14
	LR   .tempSize,A         ; 0d73 53

	AS   draw.width          ; 0d74 c4
	LR   draw.width, A       ; 0d75 54
	
	; Set ypos (color is blank)
	SETISARL wall.upper      ; 0d76 6d
	LR   A,(IS)              ; 0d77 4c
	LR   draw.ypos, A        ; 0d78 52
	
	; height = -(wall.top - wall.lowerEnemy) + enemySize
	SETISARL wall.lowerEnemy ; 0d79 6b
	AS   (IS)                ; 0d7a cc
	COM                      ; 0d7b 18
	INC                      ; 0d7c 1f
	AS   .tempSize           ; 0d7d c3
	LR   draw.height, A      ; 0d7e 55
	
	; Set rendering properties
	LI   DRAW_RECT           ; 0d7f 20 80
	LR   draw.param, A       ; 0d81 50

	; Draw
	PI   drawBox             ; 0d82 28 08 62
	
; Clear timer
	SETISAR timer.hi         ; 0d85 66 6e
	CLR                      ; 0d87 70
	LR   (IS)+,A             ; 0d88 5d
	LR   (IS)+,A             ; 0d89 5d

; Spawn the balls
	; Spawn the players
	CLR                      ; 0d8a 70
.spawnLoop:          
	LR   main.curBall, A     ; 0d8b 5b
	PI   spawnBall           ; 0d8c 28 09 c2
	
	LR   A, main.curBall     ; 0d8f 4b
	INC                      ; 0d90 1f
	CI   [MAX_PLAYERS-1]     ; 0d91 25 01
	BC   .spawnLoop          ; 0d93 82 f7

	; Spawn the first enemy ball
	SETISAR balls.count      ; 0d95 65 6e
	LR   (IS),A              ; 0d97 5c
	LR   main.curBall, A     ; 0d98 5b
	PI   spawnBall           ; 0d99 28 09 c2

; Clear the the explosion flag
	SETISAR explosionFlag    ; 0d9c 67 6a
	CLR                      ; 0d9e 70
	LR   (IS),A              ; 0d9f 5c

	; Continue on to next procedure

;-------------------------------------------------------------------------------
; mainLoop()
;  Top-Level Procedure
;
; Clears the sound, draws the timer, runs a delay function, processes the enemy
;  balls, processes the player balls, and repeats until somebody loses.
;
; Note that since the Channel F lacks vsync or any sort of interval timer, that
;  the game needs to use a delay function to keep the game running at a
;  consistent and reasonable speed.

mainLoop: subroutine
	; Clear sound
	CLR                      ; 0da0 70
	OUTS 5                   ; 0da1 b5
				
; Change delay index according to the timer
	; if (timer.hi > 10)
	;   delay index = 10
	; else
	;	delay index = timer.hi + 1
	SETISAR timer.hi         ; 0da2 66 6e
	LR   A,(IS)+             ; 0da4 4d
	INC                      ; 0da5 1f
	CI   [MAX_BALLS-1]       ; 0da6 25 0a
	BC   .setDelay           ; 0da8 82 02
	LIS  [MAX_BALLS-1]       ; 0daa 7a
.setDelay:
	SETISARU delayIndex      ; 0dab 65
	LR   (IS),A              ; 0dac 5c
	SETISARU timer.lo        ; 0dad 66

; Increment 16-bit BCD timer
	; timer.lo++
	LI   $01 + BCD_ADJUST    ; 0dae 20 67
	ASD  (IS)                ; 0db0 dc
	LR   (IS)-,A             ; 0db1 5e
	BNC   .setTimerPos       ; 0db2 92 12
	; if carry, timer.hi++
	LI   $01 + BCD_ADJUST    ; 0db4 20 67
	ASD  (IS)                ; 0db6 dc
	LR   (IS)+,A             ; 0db7 5d
	
; Check if the explosion flag should be set
	; Check if hundreds digit is zero
	NI   DIGIT_MASK          ; 0db8 21 0f
	BNZ  .setTimerPos        ; 0dba 94 0a
	; If so, check if tens and ones digits are zero
	CLR                      ; 0dbc 70
	AS   (IS)                ; 0dbd cc
	BNZ  .setTimerPos        ; 0dbe 94 06
	; If so, set the explosion flag
	SETISAR explosionFlag    ; 0dc0 67 6a
	LI   MASK_EXPLODE        ; 0dc2 20 80
	LR   (IS),A              ; 0dc4 5c

; Handle Drawing of the timer
.setTimerPos:
	; Check if 1 or 2 player
	SETISAR gameMode         ; 0dc5 67 6d
	LIS  MODE_2P_MASK        ; 0dc7 71
	NS   (IS)                ; 0dc8 fc
	; Display in middle if 2 player mode
	LI   TIMER_X_CENTER      ; 0dc9 20 39
	BNZ  .drawTimer          ; 0dcb 94 03
	; Display to left if 1 player mode
	LI   TIMER_X_LEFT        ; 0dcd 20 1f
.drawTimer:          
	LR   drawTimer.xpos, A   ; 0dcf 50
	; Set color (drawTimer adds the ypos)
	LI   RED                 ; 0dd0 20 80
	LR   drawTimer.ypos, A   ; 0dd2 52
	; Set ISAR to LSB of score
	SETISAR timer.lo         ; 0dd3 66 6f
	PI   drawTimer           ; 0dd5 28 0a 20

; Perform the delay (to keep the game speed consistent)
	; delayByTable(delayIndex)
	SETISAR delayIndex       ; 0dd8 65 6f
	LR   A,(IS)              ; 0dda 4c
	LR   delay.index, A      ; 0ddb 50
	PI   delayByTable        ; 0ddc 28 09 86

; Check if a new ball needs to be spawned
	; curBall = balls.count
	SETISAR balls.count      ; 0ddf 65 6e
	LI   %00001111           ; 0de1 20 0f
	NS   (IS)+               ; 0de3 fd
	LR   main.curBall, A     ; 0de4 5b
	
	; ISAR is delayIndex here
	; Check if curBall >= delayIndex
	LR   A,(IS)              ; 0de5 4c
	COM                      ; 0de6 18
	INC                      ; 0de7 1f
	AS   main.curBall        ; 0de8 cb
	; if so, branch ahead
	BP   .ballLoopInit       ; 0de9 81 0d
	; if not, spawn a new ball

	; curBall = delayIndex
	LR   A,(IS)              ; 0deb 4c
	LR   main.curBall, A     ; 0dec 5b

	; Spawn new ball
	PI   spawnBall           ; 0ded 28 09 c2
	
	; balls.count = delayIndex (preserve upper nybble of ball count)
	SETISAR balls.count      ; 0df0 65 6e
	LI   %11110000           ; 0df2 20 f0
	NS   (IS)+               ; 0df4 fd
	AS   (IS)-               ; 0df5 ce
	LR   (IS),A              ; 0df6 5c

; Handle enemy balls
.ballLoopInit:
	SETISAR balls.count      ; 0df7 65 6e
	LI   %00001111           ; 0df9 20 0f
	NS   (IS)                ; 0dfb fc
	LR   main.curBall, A     ; 0dfc 5b
				
.ballLoop:          
	; doBall.size = enemy ball size
	SETISAR doBall.size      ; 0dfd 67 68
	LI   MASK_ENEMY_SIZE     ; 0dff 20 30
	NS   main.gameSettings   ; 0e01 fa
	SR   4                   ; 0e02 14
	LR   (IS)+,A             ; 0e03 5d
	
	; doBall.speed = enemy speed 
	LI   MASK_ENEMY_SPEED    ; 0e04 20 03
	NS   main.gameSettings   ; 0e06 fa
	LR   (IS),A              ; 0e07 5c

	PI   doBall              ; 0e08 28 0a 53
	PI   collision           ; 0e0b 28 0b 6e
	
	; if we're not dealing with a player ball, then move on to the next ball
	DS   main.curBall        ; 0e0e 3b
	LR   A,main.curBall      ; 0e0f 4b
	CI   [MAX_PLAYERS-1]     ; 0e10 25 01
	BNC   .ballLoop          ; 0e12 92 ea

; Handle player balls
	PI   doPlayers           ; 0e14 28 09 24

	; doBall.size = player ball size
	SETISAR doBall.size      ; 0e17 67 68
	LI   MASK_PLAYER_SIZE    ; 0e19 20 c0
	NS   main.gameSettings   ; 0e1b fa
	SR   4                   ; 0e1c 14
	SR   1                   ; 0e1d 12
	SR   1                   ; 0e1e 12
	LR   (IS)+,A             ; 0e1f 5d
	
	; doBall.size = player speed
	LI   MASK_PLAYER_SPEED   ; 0e20 20 0c
	NS   main.gameSettings   ; 0e22 fa
	SR   1                   ; 0e23 12
	SR   1                   ; 0e24 12
	LR   (IS),A              ; 0e25 5c
	
	; Handle player 1
	LI   0                   ; 0e26 20 00
	LR   main.curBall,A      ; 0e28 5b
	PI   doBall              ; 0e29 28 0a 53
	
	; Check if were doing 2 player mode
	SETISAR gameMode         ; 0e2c 67 6d
	LIS  1                   ; 0e2e 71
	NS   (IS)                ; 0e2f fc
	BZ   .checkExplosion     ; 0e30 84 05	
	; If so handle player 2
	LR   main.curBall,A      ; 0e32 5b
	PI   doBall              ; 0e33 28 0a 53

; Deal with the explosion
.checkExplosion:
	; Loop back to beginning if explosion flag isn't set
	SETISAR explosionFlag    ; 0e36 67 6a
	CLR                      ; 0e38 70
	AS   (IS)                ; 0e39 cc
	BP   .endMain            ; 0e3a 81 06
	
	; Clear explosion flag, and then explode
	CLR                      ; 0e3c 70
	LR   (IS),A              ; 0e3d 5c
	JMP  explode             ; 0e3e 29 0f 6b

.endMain:
	JMP  mainLoop            ; 0e41 29 0d a0
; end of mainLoop()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; gameOver()
;  Top-Level Procedure
;
; collision() jumps to here if the player comes in contact with an enemy ball
;
; This procedure draws the fancy multicolored spiral effect, and then branches
;  to two different paths depending whether we're in 1 or 2 player mode.
;
; In 1 player mode, the game checks the if the timer was better than the
;  player's previous high score, and replaces the score if applicable.
;
; In 2 player mode, the game add's the value of the timer to the surviving
;  player's high score (which makes it more of a running total, really).
;
; This procedure jumps back to either shuffleGame() or restartGame() depending
;  on whether the controller is pushed in.

gameOver: subroutine
; Make the multicolored spiral death effect
.Y_CENTER = $24
.MAX_RADIUS = $14

	; ypos = $24, color = $80
	LI   RED | .Y_CENTER     ; 0e44 20 a4
	LR   draw.ypos, A        ; 0e46 52
	; spiralRadius = $14
	SETISAR spiral.radius    ; 0e47 64 6e
	LI   .MAX_RADIUS         ; 0e49 20 14
	LR   (IS),A              ; 0e4b 5c

.spiralLoop:
	PI   drawSpiral          ; 0e4c 28 0f 0a
	; spiralRadius--
	SETISAR spiral.radius ; 0e4f 64 6e
	DS   (IS)                ; 0e51 3c
	; save flags
	LR   J,W                 ; 0e52 1e
	; color++
	; if(color == 0)
	;  color++
	; ypos = $24
	LR   A, draw.ypos        ; 0e53 42
	AI   $40                 ; 0e54 24 40
	BNC  .setColor           ; 0e56 92 03
	AI   $40                 ; 0e58 24 40
.setColor:
	NI   MASK_COLOR          ; 0e5a 21 c0
	AI   .Y_CENTER           ; 0e5c 24 24
	LR   draw.ypos,A         ; 0e5e 52
	; restore flags
	; loop back if o46 != 0
	LR   W,J                 ; 0e5f 1d
	BNZ  .spiralLoop         ; 0e60 94 eb

; Wait a bit before clearing the spiral effect
	; delayVariable($0)
	CLR                      ; 0e62 70
	LR   delay.count, A      ; 0e63 50
	PI   delayVariable       ; 0e64 28 09 9a

; Clear the spiral
	; Set color depending on who died
	; 1P mode - Red
	SETISAR gameMode         ; 0e67 67 6d
	LIS  MODE_2P_MASK        ; 0e69 71
	NS   (IS)                ; 0e6a fc
	LI   RED                 ; 0e6b 20 80
	BZ   .clearSpiral        ; 0e6d 84 0a
	; 2P mode, P1 - Green
	SETISARL testBall        ; 0e6f 69
	LIS  $1                  ; 0e70 71
	NS   (IS)                ; 0e71 fc
	LI   GREEN               ; 0e72 20 c0
	BZ   .clearSpiral        ; 0e74 84 03
	; 2P mode, P2 - Blue
	LI   BLUE                ; 0e76 20 40
.clearSpiral:
	; Set ypos
	AI   .Y_CENTER           ; 0e78 24 24
	LR   draw.ypos,A         ; 0e7a 52

	; Draw spiral
	SETISAR spiral.radius    ; 0e7b 64 6e
	LI   .MAX_RADIUS         ; 0e7d 20 14
	LR   (IS),A              ; 0e7f 5c
	PI   drawSpiral          ; 0e80 28 0f 0a

; Delay a bit to allow the players time before input is polled later
	; Delay
	LI   $28                 ; 0e83 20 28
	LR   delay.count,A       ; 0e85 50
	PI   delayVariable       ; 0e86 28 09 9a

; Branch depending on whether this is 1 or 2 player mode
	; Check if two players
	SETISAR gameMode         ; 0e89 67 6d
	LIS  MODE_2P_MASK        ; 0e8b 71
	NS   (IS)                ; 0e8c fc
	; If so, jump ahead
	BNZ  .2Pcleanup          ; 0e8d 94 38

; -- Game over cleanup - 1 player mode -----------------------------------------
.tempTimerHi = $6
.tempTimerLo = $7

; Check if a new high score was set
	; tempTimer = timer
	SETISAR timer.hi         ; 0e8f 66 6e
	LR   A,(IS)+             ; 0e91 4d
	LR   .tempTimerHi,A      ; 0e92 56
	LR   A,(IS)              ; 0e93 4c
	LR   .tempTimerLo,A      ; 0e94 57

	; tempTimer.hi - hiScore.p1.hi
	SETISAR hiScore.p1.hi    ; 0e95 65 6c
	LR   A,(IS)+             ; 0e97 4d
	COM                      ; 0e98 18
	INC                      ; 0e99 1f
	AS   .tempTimerHi        ; 0e9a c6
	; if(tempTimer.hi < hiScore.p1.hi), we do not have a new high score
	BM   .delayP1            ; 0e9b 91 16
	; if(tempTimer.hi != hiScore.p1.hi), we have a new high score
	BNZ  .newHighScore       ; 0e9d 94 07
	
	; tempTimer.lo - hiScore.lo
	LR   A,(IS)              ; 0e9f 4c
	COM                      ; 0ea0 18
	INC                      ; 0ea1 1f
	AS   .tempTimerLo        ; 0ea2 c7
	; if(tempTimer.lo < hiScore.p1.lo), we do not have a new high score
	BM   .delayP1            ; 0ea3 91 0e
	; else, we have a new high score

; Draw the new high score
.newHighScore:
	; hiScore = tempTimer
	LR   A,.tempTimerLo      ; 0ea5 47
	LR   (IS)-,A             ; 0ea6 5e
	LR   A,.tempTimerHi      ; 0ea7 46
	LR   (IS)+,A             ; 0ea8 5d
	; Set color
	LI   BLUE                ; 0ea9 20 40
	LR   drawTimer.ypos, A   ; 0eab 52
	; Set xpos
	LI   TIMER_X_RIGHT       ; 0eac 20 54
	LR   drawTimer.xpos, A   ; 0eae 50

	PI   drawTimer           ; 0eaf 28 0a 20

; Delay to give player time to push the controller
.delayP1:
	LI   $40                 ; 0eb2 20 40
	LR   delay.count, A      ; 0eb4 50
	PI   delayVariable       ; 0eb5 28 09 9a

	; Read controllers
	PI   readInput           ; 0eb8 28 09 10

	; If controller is not pushed in, shuffle the gametype
	SETISARL input.p1        ; 0ebb 68
	CLR                      ; 0ebc 70
	AS   (IS)                ; 0ebd cc
	BM   .gotoShuffle        ; 0ebe 91 04

	; Else, keep the gametype and restart the game
	JMP  restartGame         ; 0ec0 29 0d 54
; -- End of 1 player case ------------------------------------------------------

.gotoShuffle:
	JMP  shuffleGame         ; 0ec3 29 0d 18

; -- Game over cleanup - 2 player mode -----------------------------------------
.2Pcleanup:
; Check who gets the timer added to their score
	; tempTimer = timer
	SETISAR timer.hi         ; 0ec6 66 6e
	LR   A,(IS)+             ; 0ec8 4d
	LR   .tempTimerHi,A      ; 0ec9 56
	LR   A,(IS)              ; 0eca 4c
	LR   .tempTimerLo,A      ; 0ecb 57
	
	; Check who died
	SETISAR testBall         ; 0ecc 67 69
	LIS  $1                  ; 0ece 71
	NS   (IS)                ; 0ecf fc
	BNZ  .P1survived         ; 0ed0 94 0b

; Set drawTimer parameters for player 2
	; Set color
	LI   GREEN               ; 0ed2 20 c0
	LR   drawTimer.ypos,A    ; 0ed4 52
	; set xpos
	LI   TIMER_X_RIGHT       ; 0ed5 20 54
	LR   drawTimer.xpos,A    ; 0ed7 50
	; Set ISAR
	SETISAR hiScore.p2.lo    ; 0ed8 67 6c
	BR   .addHiScore         ; 0eda 90 09

; Set drawTimer parameters for player 1
.P1survived:
	; Set ISAR
	SETISAR hiScore.p1.lo    ; 0edc 65 6d
	; Set color
	LI   BLUE                ; 0ede 20 40
	LR   drawTimer.ypos,A    ; 0ee0 52
	; Set xpos
	LI   TIMER_X_LEFT        ; 0ee1 20 1f
	LR   drawTimer.xpos,A    ; 0ee3 50

; Add the current timer to the winning player's high score
.addHiScore:
	; hiScore.lo += tempTimer.lo
	LR   A,.tempTimerLo      ; 0ee4 47
	AS   (IS)                ; 0ee5 cc
	LR   (IS),A              ; 0ee6 5c
	; Add zero in BCD to adjust score and check carry flag
	LI   0 + BCD_ADJUST      ; 0ee7 20 66
	ASD  (IS)                ; 0ee9 dc
	LR   (IS)-,A             ; 0eea 5e
	BNC   .addHiScoreHiByte  ; 0eeb 92 05
	; Carry
	LI   1 + BCD_ADJUST      ; 0eed 20 67
	ASD  (IS)                ; 0eef dc
	LR   (IS),A              ; 0ef0 5c

.addHiScoreHiByte:
	; hiScore.hi += tempTimer.hi
	LR   A,(IS)              ; 0ef1 4c
	AS   .tempTimerHi        ; 0ef2 c6
	LR   (IS),A              ; 0ef3 5c
	; Add zero in BCD to adjust score
	LI   0 + BCD_ADJUST      ; 0ef4 20 66
	ASD  (IS)                ; 0ef6 dc
	LR   (IS)+,A             ; 0ef7 5d

	PI   drawTimer           ; 0ef8 28 0a 20

; There is no delay here, unlike in 1 player mode!

	; Read controllers
	PI   readInput           ; 0efb 28 09 10

	; If neither player is pushing the controller, shuffle gametype
	; Player 1
	SETISARL input.p1        ; 0efe 68
	CLR                      ; 0eff 70
	AS   (IS)+               ; 0f00 cd
	BM   .gotoShuffle        ; 0f01 91 c1

	; Player 2
	CLR                      ; 0f03 70
	AS   (IS)                ; 0f04 cc
	BM   .gotoShuffle        ; 0f05 91 bd

	; Else, just restart the current game
	JMP  restartGame         ; 0f07 29 0d 54
; -- End of 2 player case ------------------------------------------------------

; end of gameOver()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; drawSpiral()
;  Mid-Level Function
; 
; Draws a single-colored square by going in a nice clockwise spiral pattern 
;  starting from the center, like so:
;
;   .-· · ·
;   | .- - - - - -.
;   | | .- - - -. |
;   | | | .- -. | |
;   | | | |.. | | |
;   | | | | | | | |
;   | | | '-' | | |
;   | | '- - -' | |
;   | '- - - - -' |
;   '- - - - - - -'
;
; This function clobbers memory locations otherwise held by the ball arrays,
;  meaning that it cannot be used during the main loop.

; == Arguments ==
spiral.radius = 046
;.ypos = 2

drawSpiral: subroutine
	LR   K,P                 ; 0f0a 08

; == Locals ==
; Note: These take the place of variables used while the game is being played!
; Note 2: The reason these registers don't use the dot notation like other
;  locals (eg. ".temp") is because that doesn't work with the SETISAR macros
;  because of how DASM handles namespaces.
spiral.hdiameter = 024 ; o24 - horizontal diameter
spiral.hcount = 025    ; o25 - horizontal counter
spiral.vcount = 026    ; o26 - vertical counter
spiral.vdiameter = 027 ; o27 - vertical diameter
spiral.lapCount = 036  ; o36 - spiral lap counter

.X_CENTER = $34

; Initialize things before the big loop
	; Set properties to draw a rect
	LI   DRAW_RECT           ; 0f0b 20 80
	LR   draw.param, A       ; 0f0d 50
	; Set xpos
	LI   .X_CENTER           ; 0f0e 20 34
	LR   draw.xpos, A        ; 0f10 51
	; Note: ypos is set before entering this function

	SETISAR spiral.hdiameter ; 0f11 62 6c

	; Set width/height to 1
	LIS  $1                  ; 0f13 71
	LR   draw.width, A       ; 0f14 54
	LR   draw.height, A      ; 0f15 55

	; Set all spiral counters to 1
	; .hdiameter
	LR   (IS)+,A             ; 0f16 5d
	; .hcount
	LR   (IS)+,A             ; 0f17 5d
	; .vcount
	LR   (IS)+,A             ; 0f18 5d
	; .vdiameter
	LR   (IS)-,A             ; 0f19 5e

	; spiral.lapCount = spiral.radius
	SETISARU spiral.radius   ; 0f1a 64
	LR   A,(IS)              ; 0f1b 4c
	SETISARU spiral.lapCount ; 0f1c 63
	LR   (IS),A              ; 0f1d 5c

	; Set ISAR
	SETISARU spiral.vcount   ; 0f1e 62

; Dummy arithmetic operation
	LIS  $1                  ; 0f1f 71
	SL   1                   ; 0f20 13
	; Save the flags from that operation to prevent the "LR W,J" a few lines
	;  down from causing the function to erroneously return early
	LR   J,W                 ; 0f21 1e

	; Draw the center point
	PI   drawBox             ; 0f22 28 08 62

; Start of the big loop, which contains 4 small loops for each direction
.startLap:
.plotUp:
	; ypos--
	DS   draw.ypos           ; 0f25 32
	PI   drawBox             ; 0f26 28 08 62
	; .vcount--
	DS   (IS)                ; 0f29 3c
	; loop until .vcount reaches 0
	BNZ  .plotUp             ; 0f2a 94 fa

	; if (.lapCount == 0) return
	LR   W,J                 ; 0f2c 1d    ; restore flags
	BZ   .exit               ; 0f2d 84 3b

; Prep for .plotDown
	; .vdiameter++
	LR   A,(IS)+             ; 0f2f 4d
	LR   A,(IS)              ; 0f30 4c
	INC                      ; 0f31 1f
	LR   (IS)-,A             ; 0f32 5e
	; .vcount = .vdiameter
	LR   (IS)-,A             ; 0f33 5e

.plotRight:
	; xpos++
	LR   A, draw.xpos        ; 0f34 41
	INC                      ; 0f35 1f
	LR   draw.xpos, A        ; 0f36 51
	
	PI   drawBox             ; 0f37 28 08 62
	; .hcount--
	DS   (IS)                ; 0f3a 3c
	; loop until hcount reaches 0
	BNZ  .plotRight          ; 0f3b 94 f8
	
; Clear sound
	CLR                      ; 0f3d 70
	OUTS 5                   ; 0f3e b5

; Prep for .plotLeft
	; .hdiameter++
	LR   A,(IS)-             ; 0f3f 4e
	LR   A,(IS)              ; 0f40 4c
	INC                      ; 0f41 1f
	LR   (IS)+,A             ; 0f42 5d
	; .hcount = .hdiameter
	LR   (IS)+,A             ; 0f43 5d

.plotDown:
	; ypos++
	LR   A, draw.ypos        ; 0f44 42
	INC                      ; 0f45 1f
	LR   draw.ypos, A        ; 0f46 52
	PI   drawBox             ; 0f47 28 08 62
	; vcount-- (o26)
	DS   (IS)                ; 0f4a 3c
	BNZ  .plotDown           ; 0f4b 94 f8

; Prep for .plotUp
	; .vdiameter++
	LR   A,(IS)+             ; 0f4d 4d
	LR   A,(IS)              ; 0f4e 4c
	INC                      ; 0f4f 1f
	LR   (IS)-,A             ; 0f50 5e
	; .vcount = .vdiameter
	LR   (IS)-,A             ; 0f51 5e

.plotLeft:
	; xpos--
	DS   draw.xpos           ; 0f52 31
	PI   drawBox             ; 0f53 28 08 62
	; .hcount--
	DS   (IS)                ; 0f56 3c
	BNZ  .plotLeft           ; 0f57 94 fa

; Prep for .plotRight
	; .hdiameter++
	LR   A,(IS)-             ; 0f59 4e
	LR   A,(IS)              ; 0f5a 4c
	INC                      ; 0f5b 1f
	LR   (IS)+,A             ; 0f5c 5d
	; .hcount = .hdiameter
	LR   (IS)+,A             ; 0f5d 5d

; Prep for next loop
	; .lapCount--
	SETISARU spiral.lapCount ; 0f5e 63
	DS   (IS)                ; 0f5f 3c
	; Reset ISAR
	SETISARU spiral.vcount   ; 0f60 62
	; save flags (to be used above shortly after .plotUp)
	LR   J,W                 ; 0f61 1e
				
; Play sound
	LR   A,$2                ; 0f62 42
	OUTS 5                   ; 0f63 b5
	
	; Start new lap if(.lapCount != 0)
	BNZ  .startLap           ; 0f64 94 c0
	
; Adjust .vcount for the last .plotUp so we make a clean sqaure
	; .vcount--
	DS   (IS)                ; 0f66 3c
	; Note: This lap will only do .plotUp before exiting
	BR   .startLap           ; 0f67 90 bd

.exit:
	LR   P,K                 ; 0f69 09
	POP                      ; 0f6a 1c
; end drawSpiral()
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; explode()
;  Top-level procedure
;
; Move the balls to the center to "explode". This procedure is executed
;  every 1000 points.
;
; Accessed from the end of the main loop, and returns to the beginning of the
;  main loop.
;
; No input arguments

; == Entry Point ==
explode: subroutine

; == Local Regs ==
.loopCount = $0

; == Local Constants ==
.NUM_LOOPS = MAX_ENEMIES
.X_CENTER = $30
.Y_CENTER = $22

; == Start ==
; Prepare for loop to set x positions
	; ISAR = balls.xpos + MAX_PLAYERS
	LIS  MAX_PLAYERS         ; 0f6b 72
	AI   balls.xpos          ; 0f6c 24 10
	LR   IS,A                ; 0f6e 0b
	; .loopCount = .NUM_LOOPS
	LIS  .NUM_LOOPS          ; 0f6f 79
	LR   .loopCount,A        ; 0f70 50
; Set xpos of all enemy balls
.xLoop:
	; Set xpos while preserving the x direction
	LI   MASK_DIRECTION      ; 0f71 20 80
	NS   (IS)                ; 0f73 fc
	AI   .X_CENTER           ; 0f74 24 30
	LR   (IS),A              ; 0f76 5c
	; ISAR++ (NOTE: ISAR post-increment would only affect the lower octal digit)
	LR   A,IS                ; 0f77 0a
	INC                      ; 0f78 1f
	LR   IS,A                ; 0f79 0b
	; .loopCount--, loop back if not zero
	DS   .loopCount          ; 0f7a 30
	BNZ  .xLoop              ; 0f7b 94 f5

; Prepare for loop to set y positions
	; ISAR = balls.ypos + MAX_PLAYERS
	LR   A,IS                ; 0f7d 0a
	AI   MAX_PLAYERS         ; 0f7e 24 02
	LR   IS,A                ; 0f80 0b
	; .loopCount = .NUM_LOOPS
	LIS  .NUM_LOOPS          ; 0f81 79
	LR   .loopCount,A        ; 0f82 50
; Set ypos of all enemy balls
.yLoop:
	; Set ypos while preserving the y direction
	LI   MASK_DIRECTION      ; 0f83 20 80
	NS   (IS)                ; 0f85 fc
	AI   .Y_CENTER           ; 0f86 24 22
	LR   (IS),A              ; 0f88 5c
	; ISAR++
	LR   A,IS                ; 0f89 0a
	INC                      ; 0f8a 1f
	LR   IS,A                ; 0f8b 0b
	; .loopCount, loop back if not
	DS   .loopCount          ; 0f8c 30
	BNZ  .yLoop              ; 0f8d 94 f5

	; (ISAR) = gameSettings, ISAR++, (ISAR) = gameSettings
	; TODO: Why are we overwriting the speeds of the player balls and the first two enemies?
	LR   A,main.gameSettings ; 0f8f 4a ; is=046
	LR   (IS)+,A             ; 0f90 5d ; is=046
	LR   (IS)+,A             ; 0f91 5d ; is=047

	; Clear MODE_BOUNCE_MASK from gameMode
	SETISAR gameMode         ; 0f92 67 6d
	LR   A,(IS)              ; 0f94 4c
	SL   1                   ; 0f95 13
	SR   1                   ; 0f96 12
	LR   (IS),A              ; 0f97 5c

	; Exit
	JMP  mainLoop               ; 0f98 29 0d a0
; end explode()
;-------------------------------------------------------------------------------
	
	; Unused byte
	db $b2
	; This byte mirrors the $2b (NOP) in this cart's header.
	; Coincidence or creative whimsy?

	; Free space - 94 bytes!
	db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
	db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
	db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
	db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
	db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
	db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff

; EoF