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