Difference between revisions of "Disassembly:Videocart 16"

```;-------------------------------------------------------------------------------
; Dodge It - Videocart 16
;  for the Fairchild Video Entertainment System
;
; Disassembly Generated using Peter Trauner's f8tool
;
;  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

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)

; 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()

; Set in shuffleGame()
main.gameSettings = \$A

; -- 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

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

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

; 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

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

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

org \$0800

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
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
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

; == 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

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
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()
;-------------------------------------------------------------------------------

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

; == Return ==

; == Entry Point ==

; == 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

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

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

; Return after 10 seconds or a choice is made
.exit:
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
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
;  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 ==
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
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; 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
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
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
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]
LR   A, delay.index      ; 0996 40
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 ==

; 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

; 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
; else
LIS  \$1                  ; 09b3 71
NS   main.curBall        ; 09b4 fb
BNZ  .setSpeedMask       ; 09b6 94 02
COM                      ; 09b8 18

; 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

; 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

DCI  .jumpTable          ; 09df 2a 09 e6
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
LR   A, drawTimer.xpos   ; 0a21 40
LR   draw.xpos, A        ; 0a22 51
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

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

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

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

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

; 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)
; else
LIS  \$1                  ; 0a75 71
NS   main.curBall        ; 0a76 fb
BNZ  .setSpeedMask       ; 0a78 94 02
COM                      ; 0a7a 18

; 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
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

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
; 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

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
; 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   .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
BNC  .checkBottomWall    ; 0ad3 92 0b

; Clamp position to left wall and set direction to the right

; Play sound for hitting wall
LI   SOUND_1kHz          ; 0ad7 20 40
PI   playSound           ; 0ada 28 0c c8

BR   .setXSpeed          ; 0add 90 e5

; -- Check collision with top and bottom walls --
.checkBottomWall:
; If ball is moving upwards, branch ahead
AS   .ypos               ; 0ae0 c2
BM   .checkTopWall       ; 0ae1 91 19
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
; Variables pertaining to the ball that shares curBall's speed byte
.otherSpeed   = \$4

.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
BNZ  .setSpeedMaskAgain  ; 0b1a 94 02
COM                      ; 0b1c 18
LR   .thisBitmask, A     ; 0b1d 57

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

; Apply the bitmask to get our speed from memory
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   .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
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   .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
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:
LR   A, draw.ypos        ; 0b56 42

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
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
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
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)

; 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
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
.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 -----------------------------------------------------
.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
BNZ  .getThisBitmask     ; 0c31 94 02
COM                      ; 0c33 18
; 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
NS   (IS)                ; 0c39 fc
AS   .speedThing         ; 0c3a c8
; 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

;  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
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
NS   main.gameSettings   ; 0d40 fa
SL   1                   ; 0d41 13
; 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

; 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
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
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

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

.spiralLoop:
PI   drawSpiral          ; 0e4c 28 0f 0a
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
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
NS   (IS)                ; 0e8c fc
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

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
; 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

; hiScore.hi += tempTimer.hi
LR   A,(IS)              ; 0ef1 4c
AS   .tempTimerHi        ; 0ef2 c6
LR   (IS),A              ; 0ef3 5c
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!

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 ==
;.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

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

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

```