Disassembly:Videocart 16

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

processor f8

include "ves.h"

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

main.curBall = $B PLAYER_1 = 0 PLAYER_2 = 1

Ball arrays

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

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

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

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

MAX_BALLS = 11 MAX_PLAYERS = 2 MAX_ENEMIES = 9

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

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

Constants used to generate the positions of the walls

WALL_MIN = $10 ; Most upper-left point possible to generate a field

Note
These two constants, contrary to the values they help set, are just
simply based off the distance from the origin.

WALL_XMAX = $58 WALL_YMAX = $38

WALL_X_OFFSET_MAX = $12 WALL_Y_OFFSET_MAX = $0B

Constants to render border at the start of the game

FIELD_CORNER = WALL_MIN ; Top left corner of box, as rendered FIELD_WIDTH = WALL_XMAX - WALL_MIN + 1 ; $49 FIELD_HEIGHT = WALL_YMAX - WALL_MIN + 1 ; $29

Constants to spawn the balls at the edges of the field

SPAWN_XMIN = WALL_MIN SPAWN_XMAX = WALL_XMAX-1 SPAWN_YMIN = WALL_MIN SPAWN_YMAX = WALL_YMAX-1

-- Timers --------------------------------------------------------------------
Each of these values is 2 bytes of binary-coded decimal.

timer.hi = 066 timer.lo = 067 hiScore.p1.hi = 054 hiScore.p1.lo = 055 hiScore.p2.hi = 073 hiScore.p2.lo = 074

Used to get the lower nybble of a timer byte ("SR 4" can get the upper one)

DIGIT_MASK = $0F

Parameters for drawing the timers' positions

TIMER_X_LEFT = $1F TIMER_X_CENTER = $39 TIMER_X_RIGHT = $54 TIMER_Y_OFFSET = $A

-- Game mode -----------------------------------------------------------------
The lower two bits represent what button was pressed on the console. The upper
six bits of this are randomized in shuffleGame(), but of those bits only the
top one matters.

gameMode = 075 MODE_2P_MASK = %00000001 ; Unset: 1 player, set: 2 players MODE_SPEED_MASK = %00000010 ; Set: fast, unset: slow MODE_CHOICE_MASK = %00000011 ; Used to check if the above two bits are set

Set
fiddle with speed upon collision, unset: don't fiddle with speed
Note
It appears that this can only be set in shuffleGame(), and that it is
cleared in explode()

MODE_BOUNCE_MASK = %10000000

Set in shuffleGame()

main.gameSettings = $A MASK_PLAYER_SIZE = %11000000 MASK_ENEMY_SIZE = %00110000 MASK_PLAYER_SPEED = %00001100 MASK_ENEMY_SPEED = %00000011

-- Misc Registers ------------------------------------------------------------
Yes, 070 and 071 have different semantics for different functions called from
the main loop.
Controller inputs. Written in readInput, and read in doPlayers and gameOver

input.p1 = 070 ; Left controller input.p2 = 071 ; Right controller

Arguments for doBall

doBall.size = 070 doBall.speed = 071

Local register for collision()

testBall = 071

If set, execute explode() at the end of the main loop

explosionFlag = 072 MASK_EXPLODE = %10000000

Randomly generated number

RNG.seedHi = 076 RNG.seedLo = 077

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

BCD_ADJUST = $66

Sounds

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

Graphics

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

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

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

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

org $0800

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

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

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

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

-------------------------------------------------------------------------------
Data Tables
Delay table A (easy)

delayTableEasy: ; 082d db $19, $16, $13, $11, $0e, $0c, $0a, $08, $06, $03, $01

Delay table B (pro)

delayTableHard: ; 0838 db $0b, $0a, $09, $08, $07, $06, $05, $04, $03, $02, $01

Bitmasks used while randomizing the game mode

gameModeMasks:  ; 0843 db $C0, $30, $0C, $03, $FC

This table is referenced but never read. Based on the code that references
this table, it likely pertained to the enemy speeds. (Also, there is a chance
that the endian-ness is wrong on these.)

unusedSpeedTable: ; 0848 00 00 12 0b 0b 06 02 01 dw $0000, $120B, $0B06, $0201

Colors of P1, P2, and enemies

ballColors: ; 0850 40 c0 80 db BLUE, GREEN, RED

Table helps translate the button press to the game mode number

menuChoices: ; 0853 db $00, $01, $02, $03, $03

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

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

Valid values for draw.param

DRAW_RECT = %10000000 ; Draw a rectangle DRAW_ATTRIBUTE = %11000000 ; Draw the attribute column

== Entry Point A == (for drawing a character)

drawChar: subroutine

== Local Variables ==

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

Get the starting address of the desired character

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

== Entry point B == (for drawing a box)

drawBox: ; (xcount,ycount) = (width,height) LR A, draw.width  ; 0862 44 LR .xcount, A  ; 0863 56

   LR   A, draw.height      ; 0864 45
   LR   .ycount, A      ; 0865 57

.doRowLoop:

I/O write the ypos

; Extract color bits from ypos LR A, draw.ypos  ; 0866 42 NI MASK_COLOR  ; 0867 21 c0 LR .color, A  ; 0869 58

; Mask out sound, put the ypos in .data LR A, draw.ypos  ; 086a 42 COM  ; 086b 18 NI MASK_NO_SOUND  ; 086c 21 3f LR .data, A  ; 086e 53

; Write row to port 5, making sure to preserve the sound INS 5  ; 086f a5 NI MASK_SOUND  ; 0870 21 c0 AS .data  ; 0872 c3 OUTS 5  ; 0873 b5

Load the pixel data into .data

; If either DRAW_RECT or DRAW_ATTRIBUTE is ; then set all of the pixels and jump ahead CLR  ; 0874 70 AS draw.param  ; 0875 c0 LI  %11111111  ; 0876 20 ff BM .setPixelData  ; 0878 91 09

; Load .data from memory LM  ; 087a 16 LR .data, A  ; 087b 53

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

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

I/O write the xpos

.doPixelLoop: LR A, draw.xpos  ; 0883 41 COM  ; 0884 18 OUTS 4  ; 0885 b4

I/O write the color

; if MSB of .data is 1, draw that color ; if MSB of .data is 0, draw the BG color CLR  ; 0886 70 AS .data  ; 0887 c3 LR A, .color  ; 0888 48 BM .setColor  ; 0889 91 02 LIS 0  ; 088b 70 .setColor: COM  ; 088c 18 NI MASK_COLOR  ; 088d 21 c0 OUTS 1  ; 088f b1

Iterate on to the next data bit, making sure to pad with 1

; .data = (.data << 1) + 1 LR A, .data  ; 0890 43 SL 1  ; 0891 13 INC  ; 0892 1f LR .data, A  ; 0893 53

If DRAW_ATTRIBUTE is set, iterate to the color of the next column

; Check if DRAW_ATTRIBUTE is set LR A, draw.param  ; 0894 40 SL 1  ; 0895 13 BP .activateWrite  ; 0896 81 04 ; If so, .color = .color << 1 LR A, .color  ; 0898 48 SL 1  ; 0899 13 LR .color, A  ; 089a 58

I/O write to push our color through

.activateWrite: LI $60  ; 089b 20 60 OUTS 0  ; 089d b0 LI $50  ; 089e 20 50 OUTS 0  ; 08a0 b0

; xpos++ LR A, draw.xpos  ; 08a1 41 INC  ; 08a2 1f LR draw.xpos, A  ; 08a3 51

Spin in place to make sure the write goes through

LIS 4  ; 08a4 74 .delay: AI $ff  ; 08a5 24 ff BNZ .delay  ; 08a7 94 fd

; xcount--, loop on to next pixel if not zero DS .xcount  ; 08a9 36 BNZ .doPixelLoop  ; 08aa 94 d8

; ypos++ LR A, draw.ypos  ; 08ac 42 INC  ; 08ad 1f LR draw.ypos, A  ; 08ae 52

Reset xcount and xpos

; xcount = width LR A, draw.width  ; 08af 44 LR .xcount,A  ; 08b0 56 ; xpos = xpos - width COM  ; 08b1 18 INC  ; 08b2 1f AS draw.xpos  ; 08b3 c1 LR draw.xpos, A  ; 08b4 51

; ycount--, loop on to next row if not zero DS .ycount  ; 08b5 37 BNZ .doRowLoop  ; 08b6 94 af

Reset ypos

; ypos = ypos - height LR A, draw.height  ; 08b8 45 COM  ; 08b9 18 INC  ; 08ba 1f AS draw.ypos  ; 08bb c2 LR draw.ypos, A  ; 08bc 52

Clear I/O ports

CLR  ; 08bd 70 OUTS 1  ; 08be b1 OUTS 0  ; 08bf b0

POP  ; 08c0 1c

end draw()
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
rand()
Leaf Function
Random number generator. I am uncertain how random this is, or what the
mathematical basis is behind it.
== Arguments ==
None
== Returns ==

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

== Entry Point ==

rand: subroutine

== Local Variable ==

.tempISAR = 8

save ISAR to a temp register

LR A,IS  ; 08c1 0a LR .tempISAR, A; 08c2 58

r6 = o77*2 + o76

SETISAR RNG.seedLo  ; 08c3 67 6f LR A,(IS)-  ; 08c5 4e SL 1  ; 08c6 13 AS (IS)+  ; 08c7 cd LR RNG.regHi, A  ; 08c8 56

r6,7 = (r6,77)*2

; do the lo byte LR A,(IS)  ; 08c9 4c AS (IS)  ; 08ca cc LR RNG.regLo, A  ; 08cb 57

; do the hi byte LR J,W  ; 08cc 1e ; save status reg LR A, RNG.regHi  ; 08cd 46 SL 1  ; 08ce 13 LR W,J  ; 08cf 1d ; reload status reg LNK  ; 08d0 19 LR RNG.regHi, A  ; 08d1 56

r6,7 = (r6,7)*2

; do the lo byte LR A, RNG.regLo  ; 08d2 47 AS RNG.regLo  ; 08d3 c7 LR RNG.regLo, A  ; 08d4 57

; do the hi byte LR J,W  ; 08d5 1e LR A, RNG.regHi  ; 08d6 46 SL 1  ; 08d7 13 LR W,J  ; 08d8 1d LNK  ; 08d9 19 LR RNG.regHi, A  ; 08da 56

r6,7 += r66,67

; do the lo byte LR A, RNG.regLo  ; 08db 47 AS (IS)-  ; 08dc ce LR RNG.regLo, A  ; 08dd 57

; do the hi byte LR A, RNG.regHi  ; 08de 46 LNK  ; 08df 19 AS (IS)+  ; 08e0 cd LR RNG.regHi, A  ; 08e1 56

r6,r7 += 0x3619
o76,77 = r6,r7

; do the lo byte LR A, RNG.regLo  ; 08e2 47 AI $19  ; 08e3 24 19 LR RNG.regLo, A  ; 08e5 57 LR (IS)-,A  ; 08e6 5e

; do the hi byte LR A, RNG.regHi  ; 08e7 46 LNK  ; 08e8 19 AI $36  ; 08e9 24 36 LR RNG.regHi, A  ; 08eb 56 LR (IS)+,A  ; 08ec 5d

; Restore ISAR LR A, .tempISAR  ; 08ed 48 LR IS,A  ; 08ee 0b

; Return POP  ; 08ef 1c

end of rand()
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
menu()
Mid-Level Function
Returns the menu button you pressed.
Note that drawing "G?" is handled by main()
== Return ==

menu.buttons = 0

== Entry Point ==

menu: subroutine

== Locals ==

.waitTimerHi = 2 .waitTimerLo = 1

Wait time is 10 seconds, according to the manual.

.WAIT_TIME = $af00 .DEFAULT_MODE = $1

LR K,P  ; 08f0 08

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

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

; set upper byte of .waitTimer LI [>.WAIT_TIME]  ; 08f6 20 af LR .waitTimerHi, A  ; 08f8 52

.pollInputLoop: PI rand  ; 08f9 28 08 c1

; Set DC (to be used after this function in main) DCI menuChoices  ; 08fc 2a 08 53

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

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

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

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

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

; Return BR .exit  ; 090e 90 f6

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

readInput: subroutine SETISAR input.p1  ; 0910 67 68

; Clear I/O ports CLR  ; 0912 70 OUTS 1  ; 0913 b1 OUTS 4  ; 0914 b4

; Read left controller from I/O port 1 INS 1  ; 0915 a1 LR (IS)+,A  ; 0916 5d

; Read right controller from I/O port 2 INS 4  ; 0917 a4 LR (IS)-,A  ; 0918 5e

; if(-(input.p1 + input.p2) == 0) then exit AS (IS)  ; 0919 cc INC  ; 091a 1f COM  ; 091b 18 BZ .exit  ; 091c 84 06

; else, twiddle with the RNG SETISARL RNG.seedLo  ; 091e 6f ; RNG.lo = RNG.lo + 1 LIS $1  ; 091f 71 AS (IS)  ; 0920 cc LR (IS)-,A  ; 0921 5e ; RNG.hi-- DS (IS)  ; 0922 3c

.exit: POP  ; 0923 1c

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

doPlayers: subroutine

== Locals ==

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

LR K,P  ; 0924 08

; Read input from hand controllers PI readInput  ; 0925 28 09 10

Randomize which player is processed first

; if LSB of RNG is set ; curBall = player 1 ; else ; curBall = player 2 SETISAR RNG.seedLo  ; 0928 67 6f LIS PLAYER_2  ; 092a 71 NS (IS)  ; 092b fc LIS PLAYER_1  ; 092c 70 BNZ .setPlayer  ; 092d 94 02 LIS PLAYER_2  ; 092f 71 .setPlayer: LR main.curBall,A  ; 0930 5b

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

start loop

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

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

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

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

Check if right is pressed

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

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

Check if left is pressed

.checkLeft: LIS CONTROL_LEFT  ; 0951 72 NS (IS)  ; 0952 fc BNZ .checkDown  ; 0953 94 08

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

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

Check if down is pressed

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

; If so, set y direction to down LR A,.ypos  ; 0960 42 NI MASK_YPOSITION  ; 0961 21 3f BR .setYspeed  ; 0963 90 08

Check if up is pressed

.checkUp: LIS CONTROL_FORWARD  ; 0965 78 NS (IS)  ; 0966 fc BNZ .prepSaveBall  ; 0967 94 0b

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

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

Copy the speed to the other nybble

.prepSaveBall: LR A,.speed  ; 0973 40 SL 4  ; 0974 15 AS .speed  ; 0975 c0 LR .speed,A  ; 0976 50 ; saveBall will figure out which nybble to save

; Save the ball to the scratchpad arrays PI saveBall  ; 0977 28 09 a2

Set curBall to the other player's ball

; (why not xor the register with a constant 1?) LIS PLAYER_2  ; 097a 71 NS main.curBall  ; 097b fb LIS PLAYER_1  ; 097c 70 BNZ .setNextPlayer  ; 097d 94 02 LIS PLAYER_2  ; 097f 71 .setNextPlayer: LR main.curBall,A  ; 0980 5b

; .loopCount-- DS .loopCount  ; 0981 38 BNZ .playerLoop  ; 0982 94 b0

; Return LR P,K  ; 0984 09 POP  ; 0985 1c

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

delay.index = 0 ; when entering through delayByTable delay.count = 0 ; when entering through delayVariable

== Entry Point A ==

delayByTable: subroutine

== Locals ==

.tempISAR = 3

; if(gameMode & speedMask == 0) ; count = delayTableEasy[index] ; else ; count = delayTableHard[index] ; Set DCI delayTableEasy  ; 0986 2a 08 2d

Save the ISAR

LR A,IS  ; 0989 0a LR .tempISAR,A  ; 098a 53

Test to check the game speed

SETISAR gameMode  ; 098b 67 6d LIS MODE_SPEED_MASK  ; 098d 72 NS (IS)  ; 098e fc

Restore the ISAR

LR A,.tempISAR  ; 098f 43 LR IS,A  ; 0990 0b

; Branch ahead if playing easy BZ .loadData  ; 0991 84 04

; Else, set the table to hard DCI delayTableHard  ; 0993 2a 08 38

delay.count = delayTable[index]

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

== Entry Point B ==

delayVariable:

A = 0

.outerLoop: LIS 0  ; 099a 70

A++

.innerLoop: INC  ; 099b 1f BNZ .innerLoop  ; 099c 94 fe

count--

DS delay.count  ; 099e 30 BNZ .outerLoop  ; 099f 94 fa

; Return POP  ; 09a1 1c

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

saveBall.speed = $0 saveBall.xpos = $1 saveBall.ypos = $2

main.curBall = $B

saveBall: subroutine

== Local ==

.speedMask = $3

xpos[curBall] = saveBall.xpos

LI balls.xpos  ; 09a2 20 10 AS main.curBall  ; 09a4 cb LR IS,A  ; 09a5 0b LR A,saveBall.xpos  ; 09a6 41 LR (IS),A  ; 09a7 5c

ypos[curBall] = saveBall.xpos

LR A,IS  ; 09a8 0a AI MAX_BALLS  ; 09a9 24 0b LR IS,A  ; 09ab 0b LR A,saveBall.ypos  ; 09ac 42 LR (IS),A  ; 09ad 5c

Calculate index and bitmask for the bitpacked velocity array

; ISAR = balls.speed + curBall/2 LR A, main.curBall  ; 09ae 4b SR 1  ; 09af 12 AI balls.speed  ; 09b0 24 26 LR IS,A  ; 09b2 0b

; if curBall is even ; bitmask = %00001111 ; else ; bitmask = %11110000 LIS $1  ; 09b3 71 NS main.curBall  ; 09b4 fb LIS MASK_SPEED  ; 09b5 7f BNZ .setSpeedMask  ; 09b6 94 02 COM  ; 09b8 18 .setSpeedMask: LR .speedMask,A  ; 09b9 53

Set curBall speed bitfield

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

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

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

; Return POP  ; 09c1 1c

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

spawnBall: subroutine LR K,P  ; 09c2 08

== Local Variables ==

.speed = $0 .xpos = $1 .ypos = $2

== Local Constants ==

.SPEED = %01010101 .PLAYER_Y = $23 .PLAYER1_X = $33 .PLAYER2_X = $3A

keep rerolling RNG until it gets an inbounds x and y position

.reroll: PI rand  ; 09c3 28 08 c1

xpos = rng.hi

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

LR .xpos,A  ; 09cf 51

ypos = rng.lo

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

LR .ypos,A  ; 09d9 52

speed = 0x55

LI .SPEED  ; 09da 20 55 LR .speed,A  ; 09dc 50

Spawn the ball against one of the walls

; use lower 2 bits of rng.hi as index to jump table ; This is essentially a case statement LIS  %00000011  ; 09dd 73 NS RNG.regHi  ; 09de f6

; jump to (jump_table + 2*A) DCI .jumpTable  ; 09df 2a 09 e6 ADC  ; 09e2 8e ADC  ; 09e3 8e LR Q,DC  ; 09e4 0e ; Jump! LR P0,Q  ; 09e5 0d

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

.north: ; ypos = 0x11 ; ydir = sount LI SPAWN_YMIN+1  ; 09ee 20 11 LR .ypos,A  ; 09f0 52 BR .spawnPlayers  ; 09f1 90 1a

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

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

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

.spawnPlayers: ; exit if current ball is not a player LR A, main.curBall  ; 0a0c 4b CI [MAX_PLAYERS-1]  ; 0a0d 25 01 BNC .exit  ; 0a0f 92 0b

Ignore all the above calculations and spawn the players

; ypos = 0x23 LI .PLAYER_Y  ; 0a11 20 23 LR .ypos,A  ; 0a13 52 ; if (curBall == Player 1) ; xpos = 0x33 ; else xpos = 0x33 + 0x07 LI .PLAYER1_X  ; 0a14 20 33 BNZ .setPlayerXPos  ; 0a16 94 03 AI .PLAYER2_X-.PLAYER1_X ; 0a18 24 07 .setPlayerXPos: LR .xpos,A  ; 0a1a 51

Save xpos, ypos, and speed

.exit: PI saveBall  ; 0a1b 28 09 a2

LR P,K  ; 0a1e 09 POP  ; 0a1f 1c

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

drawTimer.xpos = 0 drawTimer.ypos = 2 ; and color

drawTimer: LR K,P  ; 0a20 08

== Local Constant ==

.X_DELTA = <[-5]

Draw ones digit

; Load xpos LR A, drawTimer.xpos  ; 0a21 40 LR draw.xpos, A  ; 0a22 51 ; Adjust ypos LI TIMER_Y_OFFSET  ; 0a23 20 0a AS drawTimer.ypos  ; 0a25 c2 LR draw.ypos, A  ; 0a26 52 ; Set character LI DIGIT_MASK  ; 0a27 20 0f NS (IS)  ; 0a29 fc LR draw.param, A  ; 0a2a 50 ; Width LIS CHAR_WIDTH  ; 0a2b 74 LR draw.width, A  ; 0a2c 54 ; Height LIS CHAR_HEIGHT  ; 0a2d 75 LR draw.height, A  ; 0a2e 55

PI drawChar  ; 0a2f 28 08 58

Draw tens digit

; Set character LR A,(IS)-  ; 0a32 4e SR 4  ; 0a33 14 LR draw.param, A  ; 0a34 50 ; xpos -= xdelta LI .X_DELTA  ; 0a35 20 fb AS draw.xpos  ; 0a37 c1 LR draw.xpos, A  ; 0a38 51

PI drawChar  ; 0a39 28 08 58

Draw hundreds digit

; Set character LR A,(IS)  ; 0a3c 4c NI DIGIT_MASK  ; 0a3d 21 0f LR draw.param, A  ; 0a3f 50 ; xpos -= xdelta LI .X_DELTA  ; 0a40 20 fb AS draw.xpos  ; 0a42 c1 LR draw.xpos, A  ; 0a43 51

PI drawChar  ; 0a44 28 08 58

Draw thousands digit

; Set character LR A,(IS)  ; 0a47 4c SR 4  ; 0a48 14 LR draw.param, A  ; 0a49 50 ; xpos -= xdelta LI .X_DELTA  ; 0a4a 20 fb AS draw.xpos  ; 0a4c c1 LR draw.xpos, A  ; 0a4d 51

PI drawChar  ; 0a4e 28 08 58

; Exit LR P,K  ; 0a51 09 POP  ; 0a52 1c

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

doBall: subroutine LR K,P  ; 0a53 08

-- Undraw the ball -----------------------------------------------------------

.tempYpos = $9

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

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

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

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

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

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

; Undraw ball PI drawBox  ; 0a6b 28 08 62

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

-- Apply x and y velocities to the ball --------------------------------------

.xpos = $1 .ypos = $2

.tempSpeed = $3 .speedMask = $6

Get bitpacked velocity

; ISAR = balls.speed[curBall/2] LR A, main.curBall  ; 0a70 4b SR 1  ; 0a71 12 AI balls.speed  ; 0a72 24 26 LR IS,A  ; 0a74 0b

; if (index is odd) ; speedMask = $0F ; else ; speedMask = $F0 LIS $1  ; 0a75 71 NS main.curBall  ; 0a76 fb LIS MASK_SPEED  ; 0a77 7f BNZ .setSpeedMask  ; 0a78 94 02 COM  ; 0a7a 18 .setSpeedMask: LR .speedMask,A  ; 0a7b 56

; Load the other ball's speed nybble ; Note: This is never read. COM  ; 0a7c 18 NS (IS)  ; 0a7d fc LR $0,A  ; 0a7e 50

; Load this ball's speed nybble LR A,.speedMask  ; 0a7f 46 NS (IS)  ; 0a80 fc LR .tempSpeed,A  ; 0a81 53 ; Shift right by 4 and save the result if non-zero SR 4  ; 0a82 14 BZ .applyVelocity  ; 0a83 84 02 LR .tempSpeed,A  ; 0a85 53

Apply x velocity

.applyVelocity: ; Test if bit 7 of xpos is set CLR  ; 0a86 70 AS .xpos  ; 0a87 c1 ; Save result of test LR J,W  ; 0a88 1e

; Load xspeed to A LR A,.tempSpeed  ; 0a89 43 SR 1  ; 0a8a 12 SR 1  ; 0a8b 12

; If bit 7 of xpos wasn't set, branch ahead LR W,J  ; 0a8c 1d BP .addXVelocity  ; 0a8d 81 03

; Else, negate the xspeed COM  ; 0a8f 18 INC  ; 0a90 1f .addXVelocity: ; xpos = xpos +/- xspeed AS .xpos  ; 0a91 c1 LR .xpos,A  ; 0a92 51

Apply y velocity

; Test if bit 7 of ypos is set CLR  ; 0a93 70 AS .ypos  ; 0a94 c2 ; Save result of test LR J,W  ; 0a95 1e

; Load yspeed to A LIS  %00000011  ; 0a96 73 NS .tempSpeed  ; 0a97 f3

; If bit 7 of ypos wasn't set, branch ahead LR W,J  ; 0a98 1d BP .addYVelocity  ; 0a99 81 03

; Else, negate yspeed COM  ; 0a9b 18 INC  ; 0a9c 1f .addYVelocity: ; ypos = ypos +/- yspeed AS .ypos  ; 0a9d c2 LR .ypos,A  ; 0a9e 52

-- Ball/Wall collision detection ---------------------------------------------

.bounceSpeed = $0 ; Speed imparted by bouncing off the walls .rightBound = $4 .lowerBound = $5

Get player or enemy right bound, depending on curBall

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

Likewise, get lower bound

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

-- Check collision with left and right walls --

; Clear .bounceSpeed CLR  ; 0aaf 70 LR .bounceSpeed,A  ; 0ab0 50

Check collision with right wall

; If ball is going leftward, branch ahead AS .xpos  ; 0ab1 c1 BM .checkLeftWall  ; 0ab2 91 18 ; Branch if (xpos + rightBound < 256) AS .rightBound  ; 0ab4 c4 BNC .checkBottomWall  ; 0ab5 92 29

We have collided with the right wall

; Clamp position to right wall and set direction to left LR A,.rightBound  ; 0ab7 44 COM  ; 0ab8 18 INC  ; 0ab9 1f AI MASK_DIRECTION  ; 0aba 24 80 LR .xpos,A  ; 0abc 51

; Play sound for hitting wall LI SOUND_1kHz  ; 0abd 20 40 LR playSound.sound,A  ; 0abf 53 PI playSound  ; 0ac0 28 0c c8

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

Check if colliding with left wall

.checkLeftWall: ; Mask out the directional bit LR A,.xpos  ; 0acb 41 NI MASK_POSITION  ; 0acc 21 7f

; branch ahead if(leftBound < xpos) COM  ; 0ace 18 INC  ; 0acf 1f SETISAR wall.left  ; 0ad0 66 6a AS (IS)  ; 0ad2 cc BNC .checkBottomWall  ; 0ad3 92 0b

; Clamp position to left wall and set direction to the right LR A,(IS)  ; 0ad5 4c LR .xpos,A  ; 0ad6 51

; Play sound for hitting wall LI SOUND_1kHz  ; 0ad7 20 40 LR playSound.sound,A  ; 0ad9 53 PI playSound  ; 0ada 28 0c c8

BR .setXSpeed  ; 0add 90 e5

-- Check collision with top and bottom walls --

.checkBottomWall: CLR  ; 0adf 70 ; If ball is moving upwards, branch ahead AS .ypos  ; 0ae0 c2 BM .checkTopWall  ; 0ae1 91 19 ; Apply bitmask NI MASK_YPOSITION  ; 0ae3 21 3f ; Branch if ypos + lowerBound < 256 AS .lowerBound  ; 0ae5 c5 BNC .applySpeedChanges  ; 0ae6 92 27

We have collided with the lower wall

; Clamp position to the lower wall and set the direction to up LR A,.lowerBound  ; 0ae8 45 COM  ; 0ae9 18 INC  ; 0aea 1f AI MASK_DIRECTION  ; 0aeb 24 80 LR draw.ypos,A  ; 0aed 52

; Play sound for hitting wall LI SOUND_1kHz  ; 0aee 20 40 LR playSound.sound,A  ; 0af0 53 PI playSound  ; 0af1 28 0c c8

Set y speed

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

Check if colliding with top wall

.checkTopWall: SETISARU wall.upper  ; 0afb 66 NI MASK_YPOSITION  ; 0afc 21 3f ; branch ahead if(topBound < ypos) COM  ; 0afe 18 INC  ; 0aff 1f SETISARL wall.upper  ; 0b00 6d AS (IS)  ; 0b01 cc BNC .applySpeedChanges ; 0b02 92 0b

We have collided with the top wall

; Clamp position to top wall and set direction downwards LR A,(IS)  ; 0b04 4c LR draw.ypos,A  ; 0b05 52

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

BR .setYSpeed  ; 0b0c 90 e7

-- Apply velocity changes from wall bounces ----------------------------------
Variables pertaining to curBall

.thisSpeed = $5 .thisBitmask = $7

Variables pertaining to the ball that shares curBall's speed byte

.otherSpeed = $4 .otherBitmask = $6

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

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

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

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

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

Apply y axis bounce

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

; Mask out yspeed from thisSpeed LI MASK_XSPEED  ; 0b2a 20 cc NS .thisBitmask  ; 0b2c f7 NS .thisSpeed  ; 0b2d f5 LR .thisSpeed,A  ; 0b2e 55

; .thisSpeed.y = .bounceSpeed.y LI MASK_YSPEED  ; 0b2f 20 33 NS .bounceSpeed  ; 0b31 f0 AS .thisSpeed  ; 0b32 c5 NS .thisBitmask  ; 0b33 f7 LR .thisSpeed,A  ; 0b34 55

Apply x axis bounce

.saveXAxisBounce: ; Branch ahead if .bounceSpeed.x == 0 LI MASK_XSPEED  ; 0b35 20 cc NS .bounceSpeed  ; 0b37 f0 BZ .prepSaveBall  ; 0b38 84 0c

; Mask out xspeed from thisSpeed

	LI   MASK_YSPEED         ; 0b3a 20 33

NS .thisBitmask  ; 0b3c f7 NS .thisSpeed  ; 0b3d f5 LR .thisSpeed,A  ; 0b3e 55

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

Prepare to save ball to array

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

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

; It is finished... we can save the results PI saveBall  ; 0b48 28 09 a2

-- Redraw the ball -----------------------------------------------------------

; if(curball <=1) ; color = ballColors[curBall] ; else ; color = ballColors[2] DCI ballColors  ; 0b4b 2a 08 50 LR A,main.curBall  ; 0b4e 4b CI [MAX_PLAYERS-1]  ; 0b4f 25 01 LIS 2  ; 0b51 72 BNC .setColor  ; 0b52 92 02 LR A,main.curBall  ; 0b54 4b .setColor: ADC  ; 0b55 8e LR A, draw.ypos  ; 0b56 42

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

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

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

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

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

; Redraw ball PI drawBox  ; 0b69 28 08 62

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

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

mainBall.xpos = $1 mainBall.ypos = $2

== Entry Point ==

collision: subroutine LR K,P  ; 0b6e 08

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

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

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

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

; Check if we're in 2-player mode SETISARL gameMode  ; 0b81 6d LIS $1  ; 0b82 71 NS (IS)  ; 0b83 fc ; If so, skip ahead BNZ .getBallPosition  ; 0b84 94 07

; If not, check if the loop counter is a player's ball SETISARL testBall  ; 0b86 69 LR A,(IS)  ; 0b87 4c CI [MAX_PLAYERS-1]  ; 0b88 25 01 ; If so, skip the current ball BZ .testBallLoop ; 0b8a 84 ed

.getBallPosition: ; r1 = xpos[curBall] LI balls.xpos  ; 0b8c 20 10 AS main.curBall  ; 0b8e cb LR IS,A  ; 0b8f 0b LR A,(IS)  ; 0b90 4c ; Mask out the direction NI MASK_POSITION  ; 0b91 21 7f LR mainBall.xpos,A  ; 0b93 51

; r2 = ypos[curBall] LR A,IS  ; 0b94 0a AI MAX_BALLS  ; 0b95 24 0b LR IS,A  ; 0b97 0b LR A,(IS)  ; 0b98 4c ; Mask out the direction NI MASK_YPOSITION  ; 0b99 21 3f LR mainBall.ypos,A  ; 0b9b 52

-- Test collision along x axis -----------------------------------------------

.xDelta = $1

; mainBall.xpos-testBall.xpos SETISAR testBall  ; 0b9c 67 69 LI balls.xpos  ; 0b9e 20 10 AS (IS)  ; 0ba0 cc LR IS,A  ; 0ba1 0b LR A,(IS)  ; 0ba2 4c NI MASK_POSITION  ; 0ba3 21 7f COM  ; 0ba5 18 INC  ; 0ba6 1f AS mainBall.xpos  ; 0ba7 c1

; Save flags LR J,W  ; 0ba8 1e ; Keep results if (mainBall.xpos >= testBall.xpos) BP .saveXdelta  ; 0ba9 81 03 ; Otherwise negate the results COM  ; 0bab 18 INC  ; 0bac 1f

.saveXdelta: ; abs(mainBall.x - testBall.x) LR .xDelta,A  ; 0bad 51

; branch ahead if testBall is not a player ball LR A,IS  ; 0bae 0a CI [balls.xpos+MAX_PLAYERS-1] ; 0baf 25 11 BNC .useEnemySize  ; 0bb1 92 0b

; branch ahead if mainBall.xpos < testBall.xpos ; or: if mainBall is left of testBall LR W,J  ; 0bb3 1d  ; Reuse flags from earlier BM .useEnemySize  ; 0bb4 91 08

; Get player ball width LI MASK_PLAYER_SIZE  ; 0bb6 20 c0 NS main.gameSettings  ; 0bb8 fa SR 1  ; 0bb9 12 SR 1  ; 0bba 12 BR .testXaxis  ; 0bbb 90 04

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

.testXaxis: SR 4  ; 0bc0 14

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

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

-- Test collision on the y axis ----------------------------------------------

.yDelta = $2

; mainBall.ypos-testBall.ypos LR A,IS  ; 0bc6 0a AI MAX_BALLS  ; 0bc7 24 0b LR IS,A  ; 0bc9 0b LR A,(IS)  ; 0bca 4c NI MASK_YPOSITION  ; 0bcb 21 3f COM  ; 0bcd 18 INC  ; 0bce 1f AS mainBall.ypos  ; 0bcf c2

; Save flags LR J,W  ; 0bd0 1e ; Keep results if (mainBall.ypos >= testBall.ypos) BP .saveYdelta  ; 0bd1 81 03 ; Otherwise negate the results COM  ; 0bd3 18 INC  ; 0bd4 1f .saveYdelta: ; abs(mainBall.ypos-testBall.ypos) LR .yDelta,A  ; 0bd5 52

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

; branch ahead if mainBall.ypos < testBall.ypos ; or: if mainBall is north of testBall LR W,J  ; 0bdb 1d  ; Reuse flags from earlier BM .useEnemySize2  ; 0bdc 91 08

; Get player ball width LI MASK_PLAYER_SIZE  ; 0bde 20 c0 NS main.gameSettings  ; 0be0 fa SR 1  ; 0be1 12 SR 1  ; 0be2 12 BR .testYaxis  ; 0be3 90 04 ; or get enemy ball width .useEnemySize2: LI MASK_ENEMY_SIZE  ; 0be5 20 30 NS main.gameSettings  ; 0be7 fa .testYaxis: SR 4  ; 0be8 14

; yDelta - tempWidth COM  ; 0be9 18 INC  ; 0bea 1f AS .yDelta  ; 0beb c2

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

-- If we got to this point, a collision has happened -------------------------

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

.makeNoise: ; Play sound LI SOUND_500Hz  ; 0bf8 20 80 LR playSound.sound,A  ; 0bfa 53 PI playSound  ; 0bfb 28 0c c8

; RNG for random bounce trajectory PI rand  ; 0bfe 28 08 c1

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

-- Fiddle with the x direction -----------------------------------------------

.speedThing = $8 .SPEED_ADJUST = $44 .randBall = $0 ; TODO: Give this variable a better name (it's not random)

Randomize x direction of mainBall

; Set ISAR to xpos[curBall] LI balls.xpos  ; 0c06 20 10 AS main.curBall  ; 0c08 cb LR IS,A  ; 0c09 0b ; XOR the direction with the RNG LI MASK_DIRECTION  ; 0c0a 20 80 NS RNG.regHi  ; 0c0c f6 XS (IS)  ; 0c0d ec LR (IS),A  ; 0c0e 5c ; Save flags from (MASK_DIRECTION xor RNG) ; Note: These flags do not appear to be used LR J,W  ; 0c0f 1e

Randomize x direction of testBall

; ISAR = balls.xpos + testBall SETISAR testBall  ; 0c10 67 69 LI balls.xpos  ; 0c12 20 10 AS (IS)  ; 0c14 cc LR IS,A  ; 0c15 0b ; Add RNG.lo to the direction LI MASK_DIRECTION  ; 0c16 20 80 NS RNG.regLo  ; 0c18 f7 AS (IS)  ; 0c19 cc LR (IS),A  ; 0c1a 5c

; We'll be using this later to adjust the speed LI .SPEED_ADJUST  ; 0c1b 20 44 LR .speedThing,A  ; 0c1d 58

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

-- Fiddle with the speed -----------------------------------------------------

.thisBitmask = $3 .otherSpeed = $4

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

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

; Conjure up the bitmask to extract randBall's speed LIS $1  ; 0c2e 71 NS .randBall  ; 0c2f f0 LIS MASK_SPEED  ; 0c30 7f BNZ .getThisBitmask  ; 0c31 94 02 COM  ; 0c33 18 .getThisBitmask: ; Save randBall's speed bitmask LR .thisBitmask,A  ; 0c34 53 ; Temp storage for the other speed bitfield COM  ; 0c35 18 NS (IS)  ; 0c36 fc LR .otherSpeed,A  ; 0c37 54

; Get the speed bitfield for randBall LR A,.thisBitmask  ; 0c38 43 NS (IS)  ; 0c39 fc ; Add .speedThing to it, and clean up with the bitmask AS .speedThing  ; 0c3a c8 NS .thisBitmask  ; 0c3b f3 ; Merge the two speed bitfields and save the result AS .otherSpeed  ; 0c3c c4 LR (IS),A  ; 0c3d 5c

; Return (don't process any more collisions for mainBall) JMP collision.return  ; 0c3e 29 0b 6c

-- Fiddle with y direction ---------------------------------------------------

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

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

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

; Set mainBall's direction to down LR A,(IS)  ; 0c51 4c OI MASK_DIRECTION  ; 0c52 22 80

; Load flags from earlier ; if testBall went down, mainBall goes up ; if testBall went up, mainBall goes down LR W,J  ; 0c54 1d BP .setYdirection  ; 0c55 81 03 NI MASK_YPOSITION  ; 0c57 21 3f .setYdirection: LR (IS),A  ; 0c59 5c

; We'll be using this later to adjust the velocity LI .SPEED_ADJUST  ; 0c5a 20 44 LR .speedThing,A  ; 0c5c 58

; Go to the "fiddle with speed" section of this function BR .checkMode  ; 0c5d 90 c2

end of collision()
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
setWalls()
Sets the positions of the walls along one axis given an input range.
== Arguments ==
*walls = ISAR

walls.max = $1 walls.min = $2

== Constants ==


setWalls: subroutine LR K,P  ; 0c5f 08

== Local ==

.tempWall = $4

.reroll: ; Reroll RNG until r6 is non-zero PI rand  ; 0c60 28 08 c1 CLR  ; 0c63 70 AS RNG.regHi  ; 0c64 c6 BZ .reroll  ; 0c65 84 fa

Make sure the RNG is in range, depending on the axis being set

; if(r1 == 0x58) ; x axis case ; if(RNG > 0x12) ; go back and reroll ; else if(RNG > 0x0B) ; y axis case ; go back and reroll LR A,walls.max  ; 0c67 41 CI WALL_XMAX  ; 0c68 25 58

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

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

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

Get the base value for the right/lower wall

; Note: the greater this number is, the more to the left (or top) this wall ; is. (Unintuitive. Works opposite of how the upper and left walls work.) ; .tempWall = -(max-rng+1) COM  ; 0c75 18 INC  ; 0c76 1f INC  ; 0c77 1f AS walls.max  ; 0c78 c1 COM  ; 0c79 18 INC  ; 0c7a 1f LR .tempWall,A  ; 0c7b 54

Adjust the right/lower wall according to the enemy's size

; wall.right(or lower)Enemy = playerSize + .tempWall LI MASK_ENEMY_SIZE  ; 0c7c 20 30 NS main.gameSettings  ; 0c7e fa SR 4  ; 0c7f 14 AS .tempWall  ; 0c80 c4 LR (IS)+,A  ; 0c81 5d

Adjust the right/lower wall according to the player's size

; wall.right(or lower)Player = playerSize + .tempWall LI MASK_PLAYER_SIZE  ; 0c82 20 c0 NS main.gameSettings  ; 0c84 fa SR 4  ; 0c85 14 SR 1  ; 0c86 12 SR 1  ; 0c87 12 AS .tempWall  ; 0c88 c4 LR (IS)+,A  ; 0c89 5d

Set the left or top boundary

; ISAR++ = walls.min + RNG LR A,RNG.regHi  ; 0c8a 46 AS walls.min  ; 0c8b c2 LR (IS)+,A  ; 0c8c 5d

; Exit LR P,K  ; 0c8d 09 POP  ; 0c8e 1c

end of setWalls()
-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
flash()
Mid-Level Function
UNUSED
Makes the screen flash -- possibly an old form of the death animation. Working
off of that assumption, we will assume that this function would have been
called after a player collision in the ball-ball collision function.
== Arguments ==
testBall = 071
No Returns

flash: subroutine LR K,P  ; 0c8f 08

== Locals ==

.loopCount = $9 .NUM_LOOPS = $25

LI .NUM_LOOPS  ; 0c90 20 25 LR .loopCount, A  ; 0c92 59

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

Loop back here to reset the sound and row attribute color to the above value

.loopResetColor: LR A,(IS)  ; 0c9f 4c

Loop back here to keep the sound and row attribute color cleared

.loopClearColor: ; Set ypos/color LR draw.ypos, A  ; 0ca0 52

; Make sound ; NOTE: sound is not played if curBall is one of the player balls LR A,(IS)  ; 0ca1 4c LR playSound.sound,A  ; 0ca2 53 PI playSound  ; 0ca3 28 0c c8

LISL 0  ; 0ca6 68 ; ISAR = 070 ; Temp? ; Set xpos to attribute column LI DRAW_ATTR_X  ; 0ca7 20 7d LR draw.xpos, A  ; 0ca9 51 ; Set width LIS DRAW_ATTR_W  ; 0caa 72 LR draw.width, A  ; 0cab 54 ; Set height LI DRAW_SCREEN_H  ; 0cac 20 40 LR draw.height, A  ; 0cae 55 ; Set rendering parameter LI DRAW_ATTRIBUTE  ; 0caf 20 c0 LR draw.param, A  ; 0cb1 50 PI drawBox  ; 0cb2 28 08 62

; Clear sound CLR  ; 0cb5 70 OUTS 5  ; 0cb6 b5

; Delay LIS $b  ; 0cb7 7b LR delay.count, A  ; 0cb8 50 PI delayVariable  ; 0cb9 28 09 9a

; loopCount-- ; exit it less than zero DS .loopCount  ; 0cbc 39 BM .exit  ; 0cbd 91 08

; if (timer is even) ; ypos/color = (ISAR) LIS $1  ; 0cbf 71 NS .loopCount  ; 0cc0 f9 CLR  ; 0cc1 70 BZ .loopResetColor  ; 0cc2 84 dc ; else ; ypos/color = 0 BR .loopClearColor  ; 0cc4 90 db

.exit: LR P,K  ; 0cc6 09 POP  ; 0cc7 1c

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

playSound.sound = 3

main.curBall = $b
== Entry Point ==

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

playSound.exit: POP  ; 0ccf 1c

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

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

; Enable data from controllers LI $40  ; 0cd2 20 40 OUTS 0  ; 0cd4 b0

; Seed RNG from uninitialized ports INS 4  ; 0cd5 a4 LR (IS)-,A  ; 0cd6 5e INS 5  ; 0cd7 a5 LR (IS)-,A  ; 0cd8 5e

; Clear BIOS stack pointer at 073 ; This game does not use the BIOS's stack functions LISL 3  ; 0cd9 6b CLR  ; 0cda 70 LR (IS),A  ; 0cdb 5c ; The BIOS already intialized the rest of the scratchpad to zero

; Clear port OUTS 0  ; 0cdc b0

Clear screen

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

PI drawBox  ; 0ce9 28 08 62

Set row attributes

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

Draw the "G?" screen

.G_X = $30 .G_Y = $1B .Q_X = $35

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

PI drawChar  ; 0d04 28 08 58

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

PI drawChar  ; 0d0c 28 08 58

Wait 10 seconds for input

PI menu  ; 0d0f 28 08 f0 ; The button press is returned in A (default is 1)

Use a table to put the number of the button pressed into gameMode

SETISAR gameMode  ; 0d12 67 6d SR 1  ; 0d14 12 ; DC was set in the menu ADC  ; 0d15 8e LM  ; 0d16 16 LR (IS),A  ; 0d17 5c

; Continue on to next procedure

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

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

.reroll: ; Array of bitmaks to be used in the following series of tests DCI gameModeMasks  ; 0d1e 2a 08 43

; Get a random number PI rand  ; 0d21 28 08 c1

Test to see if the number is a valid game setting

.temp = $8

; Put bits 6 and 7 of RNG into .temp (for player ball size) LM  ; 0d24 16 NS RNG.regHi  ; 0d25 f6 LR .temp,A  ; 0d26 58

; Add bits 4 and 5 of RNG to the previous result (for enemy ball size) LM  ; 0d27 16 NS RNG.regHi  ; 0d28 f6 SL 1  ; 0d29 13 SL 1  ; 0d2a 13 AS .temp  ; 0d2b c8 ; if(playerSize + enemySize < 4), then reroll BNC .reroll  ; 0d2c 92 f1

; Test if at least one of bits 2 and 3 of RNG are set LM  ; 0d2e 16 NS RNG.regHi  ; 0d2f f6 ; if(playerSpeed == 0), then reroll BZ .reroll  ; 0d30 84 ed

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

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

Put the upper six bits of the RNG into gameMode

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

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

Set playfield walls

; Set playfield walls for x axis LI WALL_XMAX  ; 0d43 20 58 LR walls.max,A  ; 0d45 51 LI WALL_MIN  ; 0d46 20 10 LR walls.min,A  ; 0d48 52 SETISAR wall.rightEnemy  ; 0d49 66 68 PI setWalls  ; 0d4b 28 0c 5f

; Set playfield walls for y axis LI WALL_YMAX  ; 0d4e 20 38 LR walls.max,A  ; 0d50 51 PI setWalls  ; 0d51 28 0c 5f

; Continue on to next procedure

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

restartGame: subroutine

Draw playfield walls

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

Draw inner box of playfield

.tempSize = $3

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

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

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

AS draw.width  ; 0d74 c4 LR draw.width, A  ; 0d75 54

; Set ypos (color is blank) SETISARL wall.upper  ; 0d76 6d LR A,(IS)  ; 0d77 4c LR draw.ypos, A  ; 0d78 52

; height = -(wall.top - wall.lowerEnemy) + enemySize SETISARL wall.lowerEnemy ; 0d79 6b AS (IS)  ; 0d7a cc COM  ; 0d7b 18 INC  ; 0d7c 1f AS .tempSize  ; 0d7d c3 LR draw.height, A  ; 0d7e 55

; Set rendering properties LI DRAW_RECT  ; 0d7f 20 80 LR draw.param, A  ; 0d81 50

; Draw PI drawBox  ; 0d82 28 08 62

Clear timer

SETISAR timer.hi  ; 0d85 66 6e CLR  ; 0d87 70 LR (IS)+,A  ; 0d88 5d LR (IS)+,A  ; 0d89 5d

Spawn the balls

; Spawn the players CLR  ; 0d8a 70 .spawnLoop: LR main.curBall, A  ; 0d8b 5b PI spawnBall  ; 0d8c 28 09 c2

LR A, main.curBall  ; 0d8f 4b INC  ; 0d90 1f CI [MAX_PLAYERS-1]  ; 0d91 25 01 BC .spawnLoop  ; 0d93 82 f7

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

Clear the the explosion flag

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

; Continue on to next procedure

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

mainLoop: subroutine ; Clear sound CLR  ; 0da0 70 OUTS 5  ; 0da1 b5

Change delay index according to the timer

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

Increment 16-bit BCD timer

; timer.lo++ LI $01 + BCD_ADJUST  ; 0dae 20 67 ASD (IS)  ; 0db0 dc LR (IS)-,A  ; 0db1 5e BNC .setTimerPos  ; 0db2 92 12 ; if carry, timer.hi++ LI $01 + BCD_ADJUST  ; 0db4 20 67 ASD (IS)  ; 0db6 dc LR (IS)+,A  ; 0db7 5d

Check if the explosion flag should be set

; Check if hundreds digit is zero NI DIGIT_MASK  ; 0db8 21 0f BNZ .setTimerPos  ; 0dba 94 0a ; If so, check if tens and ones digits are zero CLR  ; 0dbc 70 AS (IS)  ; 0dbd cc BNZ .setTimerPos  ; 0dbe 94 06 ; If so, set the explosion flag SETISAR explosionFlag  ; 0dc0 67 6a LI MASK_EXPLODE  ; 0dc2 20 80 LR (IS),A  ; 0dc4 5c

Handle Drawing of the timer

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

Perform the delay (to keep the game speed consistent)

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

Check if a new ball needs to be spawned

; curBall = balls.count SETISAR balls.count  ; 0ddf 65 6e LI  %00001111  ; 0de1 20 0f NS (IS)+  ; 0de3 fd LR main.curBall, A  ; 0de4 5b

; ISAR is delayIndex here ; Check if curBall >= delayIndex LR A,(IS)  ; 0de5 4c COM  ; 0de6 18 INC  ; 0de7 1f AS main.curBall  ; 0de8 cb ; if so, branch ahead BP .ballLoopInit  ; 0de9 81 0d ; if not, spawn a new ball

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

; Spawn new ball PI spawnBall  ; 0ded 28 09 c2

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

Handle enemy balls

.ballLoopInit: SETISAR balls.count  ; 0df7 65 6e LI  %00001111  ; 0df9 20 0f NS (IS)  ; 0dfb fc LR main.curBall, A  ; 0dfc 5b

.ballLoop: ; doBall.size = enemy ball size SETISAR doBall.size  ; 0dfd 67 68 LI MASK_ENEMY_SIZE  ; 0dff 20 30 NS main.gameSettings  ; 0e01 fa SR 4  ; 0e02 14 LR (IS)+,A  ; 0e03 5d

; doBall.speed = enemy speed LI MASK_ENEMY_SPEED  ; 0e04 20 03 NS main.gameSettings  ; 0e06 fa LR (IS),A  ; 0e07 5c

PI doBall  ; 0e08 28 0a 53 PI collision  ; 0e0b 28 0b 6e

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

Handle player balls

PI doPlayers  ; 0e14 28 09 24

; doBall.size = player ball size SETISAR doBall.size  ; 0e17 67 68 LI MASK_PLAYER_SIZE  ; 0e19 20 c0 NS main.gameSettings  ; 0e1b fa SR 4  ; 0e1c 14 SR 1  ; 0e1d 12 SR 1  ; 0e1e 12 LR (IS)+,A  ; 0e1f 5d

; doBall.size = player speed LI MASK_PLAYER_SPEED  ; 0e20 20 0c NS main.gameSettings  ; 0e22 fa SR 1  ; 0e23 12 SR 1  ; 0e24 12 LR (IS),A  ; 0e25 5c

; Handle player 1 LI 0  ; 0e26 20 00 LR main.curBall,A  ; 0e28 5b PI doBall  ; 0e29 28 0a 53

; Check if were doing 2 player mode SETISAR gameMode  ; 0e2c 67 6d LIS 1  ; 0e2e 71 NS (IS)  ; 0e2f fc BZ .checkExplosion  ; 0e30 84 05 ; If so handle player 2 LR main.curBall,A  ; 0e32 5b PI doBall  ; 0e33 28 0a 53

Deal with the explosion

.checkExplosion: ; Loop back to beginning if explosion flag isn't set SETISAR explosionFlag  ; 0e36 67 6a CLR  ; 0e38 70 AS (IS)  ; 0e39 cc BP .endMain  ; 0e3a 81 06

; Clear explosion flag, and then explode CLR  ; 0e3c 70 LR (IS),A  ; 0e3d 5c JMP explode  ; 0e3e 29 0f 6b

.endMain: JMP mainLoop  ; 0e41 29 0d a0

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

gameOver: subroutine

Make the multicolored spiral death effect

.Y_CENTER = $24 .MAX_RADIUS = $14

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

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

Wait a bit before clearing the spiral effect

; delayVariable($0) CLR  ; 0e62 70 LR delay.count, A  ; 0e63 50 PI delayVariable  ; 0e64 28 09 9a

Clear the spiral

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

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

Delay a bit to allow the players time before input is polled later

; Delay LI $28  ; 0e83 20 28 LR delay.count,A  ; 0e85 50 PI delayVariable  ; 0e86 28 09 9a

Branch depending on whether this is 1 or 2 player mode

; Check if two players SETISAR gameMode  ; 0e89 67 6d LIS MODE_2P_MASK  ; 0e8b 71 NS (IS)  ; 0e8c fc ; If so, jump ahead BNZ .2Pcleanup  ; 0e8d 94 38

-- Game over cleanup - 1 player mode -----------------------------------------

.tempTimerHi = $6 .tempTimerLo = $7

Check if a new high score was set

; tempTimer = timer SETISAR timer.hi  ; 0e8f 66 6e LR A,(IS)+  ; 0e91 4d LR .tempTimerHi,A  ; 0e92 56 LR A,(IS)  ; 0e93 4c LR .tempTimerLo,A  ; 0e94 57

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

; tempTimer.lo - hiScore.lo LR A,(IS)  ; 0e9f 4c COM  ; 0ea0 18 INC  ; 0ea1 1f AS .tempTimerLo  ; 0ea2 c7 ; if(tempTimer.lo < hiScore.p1.lo), we do not have a new high score BM .delayP1  ; 0ea3 91 0e ; else, we have a new high score

Draw the new high score

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

PI drawTimer  ; 0eaf 28 0a 20

Delay to give player time to push the controller

.delayP1: LI $40  ; 0eb2 20 40 LR delay.count, A  ; 0eb4 50 PI delayVariable  ; 0eb5 28 09 9a

; Read controllers PI readInput  ; 0eb8 28 09 10

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

; Else, keep the gametype and restart the game JMP restartGame  ; 0ec0 29 0d 54

-- End of 1 player case ------------------------------------------------------

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

-- Game over cleanup - 2 player mode -----------------------------------------

.2Pcleanup:

Check who gets the timer added to their score

; tempTimer = timer SETISAR timer.hi  ; 0ec6 66 6e LR A,(IS)+  ; 0ec8 4d LR .tempTimerHi,A  ; 0ec9 56 LR A,(IS)  ; 0eca 4c LR .tempTimerLo,A  ; 0ecb 57

; Check who died SETISAR testBall  ; 0ecc 67 69 LIS $1  ; 0ece 71 NS (IS)  ; 0ecf fc BNZ .P1survived  ; 0ed0 94 0b

Set drawTimer parameters for player 2

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

Set drawTimer parameters for player 1

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

Add the current timer to the winning player's high score

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

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

PI drawTimer  ; 0ef8 28 0a 20

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

; Read controllers PI readInput  ; 0efb 28 09 10

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

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

; Else, just restart the current game JMP restartGame  ; 0f07 29 0d 54

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

spiral.radius = 046

.ypos = 2

drawSpiral: subroutine LR K,P  ; 0f0a 08

== Locals ==
Note
These take the place of variables used while the game is being played!
Note 2
The reason these registers don't use the dot notation like other
locals (eg. ".temp") is because that doesn't work with the SETISAR macros
because of how DASM handles namespaces.

spiral.hdiameter = 024 ; o24 - horizontal diameter spiral.hcount = 025  ; o25 - horizontal counter spiral.vcount = 026  ; o26 - vertical counter spiral.vdiameter = 027 ; o27 - vertical diameter spiral.lapCount = 036  ; o36 - spiral lap counter

.X_CENTER = $34

Initialize things before the big loop

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

SETISAR spiral.hdiameter ; 0f11 62 6c

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

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

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

; Set ISAR SETISARU spiral.vcount  ; 0f1e 62

Dummy arithmetic operation

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

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

Start of the big loop, which contains 4 small loops for each direction

.startLap: .plotUp: ; ypos-- DS draw.ypos  ; 0f25 32 PI drawBox  ; 0f26 28 08 62 ; .vcount-- DS (IS)  ; 0f29 3c ; loop until .vcount reaches 0 BNZ .plotUp  ; 0f2a 94 fa

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

Prep for .plotDown

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

.plotRight: ; xpos++ LR A, draw.xpos  ; 0f34 41 INC  ; 0f35 1f LR draw.xpos, A  ; 0f36 51

PI drawBox  ; 0f37 28 08 62 ; .hcount-- DS (IS)  ; 0f3a 3c ; loop until hcount reaches 0 BNZ .plotRight  ; 0f3b 94 f8

Clear sound

CLR  ; 0f3d 70 OUTS 5  ; 0f3e b5

Prep for .plotLeft

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

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

Prep for .plotUp

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

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

Prep for .plotRight

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

Prep for next loop

; .lapCount-- SETISARU spiral.lapCount ; 0f5e 63 DS (IS)  ; 0f5f 3c ; Reset ISAR SETISARU spiral.vcount  ; 0f60 62 ; save flags (to be used above shortly after .plotUp) LR J,W  ; 0f61 1e

Play sound

LR A,$2  ; 0f62 42 OUTS 5  ; 0f63 b5

; Start new lap if(.lapCount != 0) BNZ .startLap  ; 0f64 94 c0

Adjust .vcount for the last .plotUp so we make a clean sqaure

; .vcount-- DS (IS)  ; 0f66 3c ; Note: This lap will only do .plotUp before exiting BR .startLap  ; 0f67 90 bd

.exit: LR P,K  ; 0f69 09 POP  ; 0f6a 1c

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

explode: subroutine

== Local Regs ==

.loopCount = $0

== Local Constants ==

.NUM_LOOPS = MAX_ENEMIES .X_CENTER = $30 .Y_CENTER = $22

== Start ==
Prepare for loop to set x positions

; ISAR = balls.xpos + MAX_PLAYERS LIS MAX_PLAYERS  ; 0f6b 72 AI balls.xpos  ; 0f6c 24 10 LR IS,A  ; 0f6e 0b ; .loopCount = .NUM_LOOPS LIS .NUM_LOOPS  ; 0f6f 79 LR .loopCount,A  ; 0f70 50

Set xpos of all enemy balls

.xLoop: ; Set xpos while preserving the x direction LI MASK_DIRECTION  ; 0f71 20 80 NS (IS)  ; 0f73 fc AI .X_CENTER  ; 0f74 24 30 LR (IS),A  ; 0f76 5c ; ISAR++ (NOTE: ISAR post-increment would only affect the lower octal digit) LR A,IS  ; 0f77 0a INC  ; 0f78 1f LR IS,A  ; 0f79 0b ; .loopCount--, loop back if not zero DS .loopCount  ; 0f7a 30 BNZ .xLoop  ; 0f7b 94 f5

Prepare for loop to set y positions

; ISAR = balls.ypos + MAX_PLAYERS LR A,IS  ; 0f7d 0a AI MAX_PLAYERS  ; 0f7e 24 02 LR IS,A  ; 0f80 0b ; .loopCount = .NUM_LOOPS LIS .NUM_LOOPS  ; 0f81 79 LR .loopCount,A  ; 0f82 50

Set ypos of all enemy balls

.yLoop: ; Set ypos while preserving the y direction LI MASK_DIRECTION  ; 0f83 20 80 NS (IS)  ; 0f85 fc AI .Y_CENTER  ; 0f86 24 22 LR (IS),A  ; 0f88 5c ; ISAR++ LR A,IS  ; 0f89 0a INC  ; 0f8a 1f LR IS,A  ; 0f8b 0b ; .loopCount, loop back if not DS .loopCount  ; 0f8c 30 BNZ .yLoop  ; 0f8d 94 f5

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

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

; Exit JMP mainLoop  ; 0f98 29 0d a0

end explode()
-------------------------------------------------------------------------------

; Unused byte db $b2 ; This byte mirrors the $2b (NOP) in this cart's header. ; Coincidence or creative whimsy?

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

EoF