--------------------------------------------------------------------------- -- Boulder Dash - Displaying Amoeba Pointer Movements -- by AnS, 2012 -- requested by CtrlAltDestroy --------------------------------------------------------------------------- -- Showcases following functions: -- * memory.registerexec() --------------------------------------------------------------------------- -- Usage: -- Start playing any level of the BoulderDash that has the Amoeba enemy, -- for example Level 2-3, Level 4-1 or Level 6-3. -- Come close to the Amoeba enemy and see if you can find any regularities -- in its movement. When you are convinced that its behavior is completely -- erratic, run the script and unpause emulation. Now the script will draw -- various arrows that show you the actual rules of the enemy AI. -- -- You can control the "playback slider" in the bottom of the screen (click -- or drag it with mouse). Also you can pause and unpause this mini-playback -- while the emulator is still paused. This is necessary because the Amoeba -- makes a new set of moves every 8 frames, and this time span is not enough -- for you to replay all the logged events. --------------------------------------------------------------------------- -- Explanations: -- The Amoeba AI is defined by UpdateAmoeba() function (see the disassembly -- code at the end of this script). -- The UpdateAmoeba() makes many steps with the Amoeba Pointer (so the arrow -- may move from tile to tile 100-200 times within the span of one frame! -- This complexity makes it look unpredictable for player, even though the -- rules of movement are very simple (move clockwise, rotate anticlockwise). -- -- This script allows you to see all steps of this Pointer, so you can grasp -- the logic of Amoeba movement/multiplication. The script registers hooks -- to all essential points of the UpdateAmoeba() code, and logs the whole -- process of the function execution. Then it pauses emulation and replays -- the log of events by drawing arrows over the game sprites. -- The log auto-resets at the beginning of every new call of UpdateAmoeba(). -- -- Similar approach can be used for displaying complex AI in other games. --------------------------------------------------------------------------- -- constants CELL_SIZE = 16; CELL_HALFSIZE = CELL_SIZE / 2; UPPER_BORDER_SIZE = 64; LEFT_BORDER_SIZE = 32; INTRO_DELAY = 1; OUTRO_DELAY = 1; MAX_ARROW_SIZE = 9; PAUSE_BUTTON_X = 2; PAUSE_BUTTON_Y = 172; PAUSE_BUTTON_WIDTH = 16; PAUSE_BUTTON_HEIGHT = 16; TIMELINE_X = 20; TIMELINE_LENGTH = 232; TIMELINE_HEIGHT = 8; TIMELINE_Y = 180; ColorStatuses = { "#00A000FF", "#00FF00FF", "#A0A0A0FF", "#00FFFFFF", "#FFFFFFFF", "#C0C000FF" }; STATUS_NORMAL = 1; -- Green: Normal movements STATUS_ROTATED = 2; -- Light-green: just rotated 90 degrees (anticlockwise) relative to current movement direction STATUS_BUMPED = 3; -- Grey: bumped into obstacle STATUS_SET = 4; -- Blue: Set amoeba STATUS_REFUSED = 5; -- White: Refused to set amoeba STATUS_END = 6; -- Yellow: RTS from UpdateAmoeba -- variables animation_timer = 0; current_step = 0; animation_speed = 0.5; animation_paused = false; mouse_held = false; dragging_timeline = false; log_size = 0; creation_frame = -1; -- arrays for storing the log data Amoeba_X = {}; Amoeba_Y = {}; AmoebaDirection = {}; AmoebaPhase = {}; AmoebaTimer = {}; ColorStatus = {}; ------------------------------------------------------------------------ function reset_log() log_size = 0; animation_timer = 0; creation_frame = movie.framecount(); emu.pause(); end function append_log_normal() log_size = log_size + 1; Amoeba_X[log_size] = memory.readbyte(0x0050); -- Temp_X Amoeba_Y[log_size] = memory.readbyte(0x004F); -- Temp_Y AmoebaDirection[log_size] = memory.readbyte(0x004E); -- Temp_AmoebaDirection AmoebaPhase[log_size] = memory.readbyte(0x00B7); AmoebaTimer[log_size] = memory.readbyte(0x00BD); ColorStatus[log_size] = STATUS_NORMAL; -- Green: Normal movements end function append_log_rotated() log_size = log_size + 1; Amoeba_X[log_size] = memory.readbyte(0x0050); -- Temp_X Amoeba_Y[log_size] = memory.readbyte(0x004F); -- Temp_Y AmoebaDirection[log_size] = memory.readbyte(0x004E); -- Temp_AmoebaDirection AmoebaPhase[log_size] = memory.readbyte(0x00B7); AmoebaTimer[log_size] = memory.readbyte(0x00BD); ColorStatus[log_size] = STATUS_ROTATED; -- Light-green: just rotated 90 degrees (anticlockwise) relative to current movement direction end function append_log_bumped() log_size = log_size + 1; Amoeba_X[log_size] = memory.readbyte(0x0050); -- Temp_X Amoeba_Y[log_size] = memory.readbyte(0x004F); -- Temp_Y AmoebaDirection[log_size] = memory.readbyte(0x004E); -- Temp_AmoebaDirection AmoebaPhase[log_size] = memory.readbyte(0x00B7); AmoebaTimer[log_size] = memory.readbyte(0x00BD); ColorStatus[log_size] = STATUS_BUMPED; -- Grey: bumped into obstacle end function append_log_set() log_size = log_size + 1; Amoeba_X[log_size] = memory.readbyte(0x0050); -- Temp_X Amoeba_Y[log_size] = memory.readbyte(0x004F); -- Temp_Y AmoebaDirection[log_size] = memory.readbyte(0x004E); -- Temp_AmoebaDirection AmoebaPhase[log_size] = memory.readbyte(0x00B7); AmoebaTimer[log_size] = memory.readbyte(0x00BD); ColorStatus[log_size] = STATUS_SET; -- Blue: Set amoeba end function append_log_refused() log_size = log_size + 1; Amoeba_X[log_size] = memory.readbyte(0x0050); -- Temp_X Amoeba_Y[log_size] = memory.readbyte(0x004F); -- Temp_Y AmoebaDirection[log_size] = memory.readbyte(0x004E); -- Temp_AmoebaDirection AmoebaPhase[log_size] = memory.readbyte(0x00B7); AmoebaTimer[log_size] = memory.readbyte(0x00BD); ColorStatus[log_size] = STATUS_REFUSED; -- White: Refused to set amoeba end function append_log_rts() log_size = log_size + 1; Amoeba_X[log_size] = memory.readbyte(0x00BC); AmoebaDirectionAnd_Y = memory.readbyte(0x00BB); Amoeba_Y[log_size] = AND(AmoebaDirectionAnd_Y, 0x3F); AmoebaDirection[log_size] = AND(AmoebaDirectionAnd_Y, 0xC0); AmoebaPhase[log_size] = memory.readbyte(0x00B7); AmoebaTimer[log_size] = memory.readbyte(0x00BD); ColorStatus[log_size] = STATUS_END; -- Yellow: RTS from UpdateAmoeba end ------------------------------------------------------------------------ function DrawArrow(x, y, direction, size, color) x = x + CELL_HALFSIZE; y = y + CELL_HALFSIZE; if (direction == 00) then -- Left gui.line(x, y - size, x - size, y, color); gui.line(x - size, y, x, y + size, color); gui.line(x, y + size, x, y - size, color); end if (direction == 0x40) then -- up gui.line(x - size, y, x, y - size, color); gui.line(x, y - size, x + size, y, color); gui.line(x + size, y, x - size, y, color); end if (direction == 0x80) then -- Right gui.line(x, y - size, x + size, y, color); gui.line(x + size, y, x, y + size, color); gui.line(x, y + size, x, y - size, color); end if (direction == 0xC0) then -- Down gui.line(x - size, y, x, y + size, color); gui.line(x, y + size, x + size, y, color); gui.line(x + size, y, x - size, y, color); end end ------------------------------------------------------------------------ -- This function is called every frame when the emulator is unpaused, -- and 20 times per second when the emulator is paused. function update() AmoebaOrigin_X = memory.readbyte(0x00B9); AmoebaOriginDirectionAnd_Y = memory.readbyte(0x00B8); AmoebaOrigin_Y = AND(AmoebaOriginDirectionAnd_Y, 0x3F); AmoebaOriginDirection = AND(AmoebaOriginDirectionAnd_Y, 0xC0); Camera_X_Lo = memory.readbyte(0x00BE); Camera_X_Hi = memory.readbyte(0x00BF); Camera_Y_Lo = memory.readbyte(0x00C0); Camera_Y_Hi = memory.readbyte(0x00C1); Camera_X = (256 * Camera_X_Hi + Camera_X_Lo) - UPPER_BORDER_SIZE; Camera_Y = (256 * Camera_Y_Hi + Camera_Y_Lo) - LEFT_BORDER_SIZE; if (log_size > 0) then inp = input.get() if (inp.leftclick) then xm = inp.xmouse ym = inp.ymouse -- check click on the Pause/Resume button if (not mouse_held) then if (xm >= PAUSE_BUTTON_X and ym >= PAUSE_BUTTON_Y and xm < PAUSE_BUTTON_X + PAUSE_BUTTON_WIDTH and ym < PAUSE_BUTTON_Y + PAUSE_BUTTON_HEIGHT) then animation_paused = not animation_paused; end end -- check click on the timeline if (dragging_timeline or (xm >= TIMELINE_X and ym >= TIMELINE_Y - TIMELINE_HEIGHT and xm < TIMELINE_X + TIMELINE_LENGTH and ym < TIMELINE_Y + TIMELINE_HEIGHT)) then shift = xm - TIMELINE_X; if (shift < 0) then shift = 0; else if (shift > TIMELINE_LENGTH) then shift = TIMELINE_LENGTH; end end animation_timer = (INTRO_DELAY + log_size + OUTRO_DELAY) * shift / TIMELINE_LENGTH; dragging_timeline = true; end mouse_held = true; else mouse_held = false; dragging_timeline = false; end if (not animation_paused and not mouse_held) then -- animate animation_timer = animation_timer + animation_speed; if (animation_timer > INTRO_DELAY + log_size + OUTRO_DELAY) then animation_timer = 0; end end current_step = math.floor(animation_timer - INTRO_DELAY); if (current_step > log_size) then current_step = log_size; else if (current_step < 1) then current_step = 1; end end -- show info if (ColorStatus[current_step] == STATUS_END) then -- emphasize the phase change by yellow gui.text(3, 11, "AmoebaPhase = " .. AND(AmoebaPhase[current_step], 3), "yellow"); else gui.text(3, 11, "AmoebaPhase = " .. AND(AmoebaPhase[current_step], 3)); end if ((ColorStatus[current_step] == STATUS_SET) or (ColorStatus[current_step] == STATUS_REFUSED)) then -- emphasize the decrement by red gui.text(3, 20, "AmoebaTimer = " .. AmoebaTimer[current_step], "red"); else gui.text(3, 20, "AmoebaTimer = " .. AmoebaTimer[current_step]); end -- draw origin DrawArrow(AmoebaOrigin_X * CELL_SIZE - Camera_X, AmoebaOrigin_Y * CELL_SIZE - Camera_Y, AmoebaOriginDirection, 5, "#00FFFFFF"); -- draw tail of pointers for i = (MAX_ARROW_SIZE - 1), 0, -1 do step = current_step - i; if (step > 0) then for s = 1, (MAX_ARROW_SIZE - i) do -- draw previous position of pointer DrawArrow(Amoeba_X[step] * CELL_SIZE - Camera_X, Amoeba_Y[step] * CELL_SIZE - Camera_Y, AmoebaDirection[step], s, ColorStatuses[ColorStatus[step]]); end end end -- draw black border around the last (current) pointer DrawArrow(Amoeba_X[current_step] * CELL_SIZE - Camera_X, Amoeba_Y[current_step] * CELL_SIZE - Camera_Y, AmoebaDirection[current_step], MAX_ARROW_SIZE + 1, "black"); -- draw GUI -- Pause/Resume button gui.box(PAUSE_BUTTON_X, PAUSE_BUTTON_Y, PAUSE_BUTTON_X + PAUSE_BUTTON_WIDTH, PAUSE_BUTTON_Y + PAUSE_BUTTON_HEIGHT, "grey", "black"); if (animation_paused) then for s = 1, 6 do DrawArrow(PAUSE_BUTTON_X - 2, PAUSE_BUTTON_Y, 0x80, s, "black"); end else gui.box(PAUSE_BUTTON_X + (PAUSE_BUTTON_WIDTH / 2) - 3, PAUSE_BUTTON_Y + 3, PAUSE_BUTTON_X + (PAUSE_BUTTON_WIDTH / 2) - 1, PAUSE_BUTTON_Y + PAUSE_BUTTON_HEIGHT - 3, "black", "black"); gui.box(PAUSE_BUTTON_X + (PAUSE_BUTTON_WIDTH / 2) + 3, PAUSE_BUTTON_Y + 3, PAUSE_BUTTON_X + (PAUSE_BUTTON_WIDTH / 2) + 1, PAUSE_BUTTON_Y + PAUSE_BUTTON_HEIGHT - 3, "black", "black"); end -- line gui.box(TIMELINE_X, TIMELINE_Y - TIMELINE_HEIGHT, TIMELINE_X + TIMELINE_LENGTH, TIMELINE_Y + TIMELINE_HEIGHT, "#00000040", "black"); gui.box(TIMELINE_X, TIMELINE_Y - 1, TIMELINE_X + TIMELINE_LENGTH, TIMELINE_Y + 1, "white", "black"); -- slider shift = TIMELINE_LENGTH * animation_timer / (INTRO_DELAY + log_size + OUTRO_DELAY); gui.box(TIMELINE_X + shift - 1, TIMELINE_Y - TIMELINE_HEIGHT, TIMELINE_X + shift + 1, TIMELINE_Y + TIMELINE_HEIGHT, "grey", "black"); -- info gui.text(TIMELINE_X + 80 + creation_frame % 3, TIMELINE_Y + TIMELINE_HEIGHT + 2, "Displaying " .. current_step .. " / " .. log_size); gui.text(TIMELINE_X + 80 + creation_frame % 3, TIMELINE_Y + TIMELINE_HEIGHT + 11, "Created at frame " .. creation_frame); end end ------------------------------------------------------------------------ gui.register(update); memory.registerexec(0xCE60, reset_log); -- ReallyUpdateAmoeba --memory.registerexec(0xCE66, reset_log); -- RestartFromOrigin memory.registerexec(0xCE95, append_log_normal); -- TryMultiplying_Left memory.registerexec(0xCEAE, append_log_normal); -- TryMultiplying_Up memory.registerexec(0xCEC7, append_log_normal); -- TryMultiplying_Right memory.registerexec(0xCEE0, append_log_normal); -- TryMultiplying_Down memory.registerexec(0xCEF9, append_log_normal); -- TryMultiplying_Left2 memory.registerexec(0xCF12, append_log_normal); -- TryMultiplying_Up2 memory.registerexec(0xCF2B, append_log_normal); -- TryMultiplying_Right2 memory.registerexec(0xCEA3, append_log_rotated); -- Rotated from left to down memory.registerexec(0xCEBC, append_log_rotated); -- Rotated from up to left memory.registerexec(0xCED5, append_log_rotated); -- Rotated from right to up memory.registerexec(0xCEEE, append_log_rotated); -- Rotated from down to right memory.registerexec(0xCF07, append_log_rotated); -- Rotated from left to down memory.registerexec(0xCF20, append_log_rotated); -- Rotated from up to left memory.registerexec(0xCF39, append_log_rotated); -- Rotated from right to up memory.registerexec(0xCE9C, append_log_bumped); -- Bumped into obstacle memory.registerexec(0xCEB5, append_log_bumped); -- Bumped into obstacle memory.registerexec(0xCECE, append_log_bumped); -- Bumped into obstacle memory.registerexec(0xCEE7, append_log_bumped); -- Bumped into obstacle memory.registerexec(0xCF00, append_log_bumped); -- Bumped into obstacle memory.registerexec(0xCF19, append_log_bumped); -- Bumped into obstacle memory.registerexec(0xCF32, append_log_bumped); -- Bumped into obstacle memory.registerexec(0xCF97, append_log_normal); -- Jumped to another amoeba memory.registerexec(0xCFA5, append_log_set); -- Set amoeba memory.registerexec(0xCFA3, append_log_refused); -- Refused to set amoeba memory.registerexec(0xCF5D, append_log_rts); -- RTS from UpdateAmoeba ------------------------------------------------------------------------ -- Disassembly of the BoulderDash code in question: -- -- UpdateAmoeba: -- ; Called every frame, but works only once per 8 frames -- >01:CE51:A5 B7 LDA AmoebaPhase = #$82 -- 01:CE53:F0 0A BEQ ExitWithoutUpdate -- 01:CE55:C9 FF CMP #$FF -- 01:CE57:F0 06 BEQ ExitWithoutUpdate -- 01:CE59:A5 FE LDA FrameCounter = #$6A -- 01:CE5B:29 07 AND #$07 -- 01:CE5D:F0 01 BEQ ReallyUpdateAmoeba -- ExitWithoutUpdate: -- 01:CE5F:60 RTS ----------------------------------------------------------- -- ReallyUpdateAmoeba: -- 01:CE60:A5 B7 LDA AmoebaPhase = #$82 -- 01:CE62:29 03 AND #$03 -- 01:CE64:D0 0C BNE PrepareForLoop25 -- RestartFromOrigin: -- ; Every time the amoeba multiplies or fails to multiply within 32 frames, it restarts to the original point -- 01:CE66:A9 00 LDA #$00 -- 01:CE68:85 B7 STA AmoebaPhase = #$82 -- 01:CE6A:A5 B8 LDA AmoebaOriginDirectionAnd_Y = #$41 -- 01:CE6C:85 BB STA AmoebaDirectionAnd_Y = #$41 -- 01:CE6E:A5 B9 LDA AmoebaOrigin_X = #$14 -- 01:CE70:85 BC STA Amoeba_X = #$18 -- PrepareForLoop25: -- 01:CE72:A9 19 LDA #$19 -- 01:CE74:85 4D STA 25Tries = #$0A -- ; Temp_X = Amoeba_X; Temp_Y = AmoebaDirectionAnd_Y & 00111111b; Temp_AmoebaDirection = AmoebaDirectionAnd_Y & 11000000b -- 01:CE76:A5 BB LDA AmoebaDirectionAnd_Y = #$41 -- 01:CE78:29 3F AND #$3F -- 01:CE7A:A8 TAY -- 01:CE7B:A5 BB LDA AmoebaDirectionAnd_Y = #$41 -- 01:CE7D:29 C0 AND #$C0 -- 01:CE7F:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CE81:A6 BC LDX Amoeba_X = #$18 -- 01:CE83:84 4F STY Temp_Y = #$38 -- 01:CE85:86 50 STX Temp_X = #$0A -- Loop25Tries: -- 01:CE87:A5 4E LDA Temp_AmoebaDirection = #$00 -- 01:CE89:C9 40 CMP #$40 -- 01:CE8B:F0 21 BEQ TryMultiplying_Up -- 01:CE8D:C9 80 CMP #$80 -- 01:CE8F:F0 36 BEQ TryMultiplying_Right -- 01:CE91:C9 C0 CMP #$C0 -- 01:CE93:F0 4B BEQ TryMultiplying_Down -- TryMultiplying_Left: -- 01:CE95:20 5E CF JSR CheckMultiplying_Left -- 01:CE98:B0 05 BCS NextTimeTryDown -- 01:CE9A:F0 0A BEQ OkSetAmoeba -- 01:CE9C:4C AC CE JMP Restore_X -- NextTimeTryDown: -- ; Since we just moved the pointer to another amoeba, rotate direction anticlockwise -- 01:CE9F:A9 C0 LDA #$C0 -- 01:CEA1:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CEA3:4C 44 CF JMP DoUntilAll25TriesExpired -- OkSetAmoeba: -- 01:CEA6:20 99 CF JSR IntendToSetAmoebaToMap -- 01:CEA9:90 01 BCC Restore_X -- 01:CEAB:60 RTS ----------------------------------------------------------- -- Restore_X: -- 01:CEAC:E6 50 INC Temp_X = #$0A -- TryMultiplying_Up: -- 01:CEAE:20 6A CF JSR CheckMultiplying_Up -- 01:CEB1:B0 05 BCS NextTimeTryLeft -- 01:CEB3:F0 0A BEQ OkSetAmoeba -- 01:CEB5:4C C5 CE JMP Restore_Y -- NextTimeTryLeft: -- ; Since we just moved the pointer to another amoeba, rotate direction anticlockwise -- 01:CEB8:A9 00 LDA #$00 -- 01:CEBA:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CEBC:4C 44 CF JMP DoUntilAll25TriesExpired -- OkSetAmoeba: -- 01:CEBF:20 99 CF JSR IntendToSetAmoebaToMap -- 01:CEC2:90 01 BCC Restore_Y -- 01:CEC4:60 RTS ----------------------------------------------------------- -- Restore_Y: -- 01:CEC5:E6 4F INC Temp_Y = #$38 -- TryMultiplying_Right: -- 01:CEC7:20 76 CF JSR CheckMultiplying_Right -- 01:CECA:B0 05 BCS NextTimeTryUp -- 01:CECC:F0 0A BEQ OkSetAmoeba -- 01:CECE:4C DE CE JMP Restore_X -- NextTimeTryUp: -- ; Since we just moved the pointer to another amoeba, rotate direction anticlockwise -- 01:CED1:A9 40 LDA #$40 -- 01:CED3:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CED5:4C 44 CF JMP DoUntilAll25TriesExpired -- OkSetAmoeba: -- 01:CED8:20 99 CF JSR IntendToSetAmoebaToMap -- 01:CEDB:90 01 BCC Restore_X -- 01:CEDD:60 RTS ----------------------------------------------------------- -- Restore_X: -- 01:CEDE:C6 50 DEC Temp_X = #$0A -- TryMultiplying_Down: -- 01:CEE3:B0 05 BCS NextTimeTryRight -- 01:CEE5:F0 0A BEQ OkSetAmoeba -- 01:CEE7:4C F7 CE JMP Restore_X -- NextTimeTryRight: -- ; Since we just moved the pointer to another amoeba, rotate direction anticlockwise -- 01:CEEA:A9 80 LDA #$80 -- 01:CEEC:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CEEE:4C 44 CF JMP DoUntilAll25TriesExpired -- OkSetAmoeba: -- 01:CEF1:20 99 CF JSR IntendToSetAmoebaToMap -- 01:CEF4:90 01 BCC Restore_X -- 01:CEF6:60 RTS ----------------------------------------------------------- -- Restore_X: -- 01:CEF7:C6 4F DEC Temp_Y = #$38 -- TryMultiplying_Left2: -- 01:CEF9:20 5E CF JSR CheckMultiplying_Left -- 01:CEFC:B0 05 BCS NextTimeTryDown2 -- 01:CEFE:F0 0A BEQ OkSetAmoeba -- 01:CF00:4C 10 CF JMP Restore_X -- NextTimeTryDown2: -- ; Since we just moved the pointer to another amoeba, rotate direction anticlockwise -- 01:CF03:A9 C0 LDA #$C0 -- 01:CF05:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CF07:4C 44 CF JMP DoUntilAll25TriesExpired -- OkSetAmoeba: -- 01:CF0A:20 99 CF JSR IntendToSetAmoebaToMap -- 01:CF0D:90 01 BCC Restore_X -- 01:CF0F:60 RTS ----------------------------------------------------------- -- Restore_X: -- 01:CF10:E6 50 INC Temp_X = #$0A -- TryMultiplying_Up2: -- 01:CF12:20 6A CF JSR CheckMultiplying_Up -- 01:CF15:B0 05 BCS NextTimeTryLeft2 -- 01:CF17:F0 0A BEQ OkSetAmoeba -- 01:CF19:4C 29 CF JMP Restore_Y -- NextTimeTryLeft2: -- ; Since we just moved the pointer to another amoeba, rotate direction anticlockwise -- 01:CF1C:A9 00 LDA #$00 -- 01:CF1E:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CF20:4C 44 CF JMP DoUntilAll25TriesExpired -- OkSetAmoeba: -- 01:CF23:20 99 CF JSR IntendToSetAmoebaToMap -- 01:CF26:90 01 BCC Restore_Y -- 01:CF28:60 RTS ----------------------------------------------------------- -- Restore_Y: -- 01:CF29:E6 4F INC Temp_Y = #$38 -- TryMultiplying_Right2: -- 01:CF2B:20 76 CF JSR CheckMultiplying_Right -- 01:CF2E:B0 05 BCS NextTimeTryUp2 -- 01:CF30:F0 0A BEQ OkSetAmoeba -- 01:CF32:4C 42 CF JMP Restore_X -- NextTimeTryUp2: -- ; Since we just moved the pointer to another amoeba, rotate direction anticlockwise -- 01:CF35:A9 40 LDA #$40 -- 01:CF37:85 4E STA Temp_AmoebaDirection = #$00 -- 01:CF39:4C 44 CF JMP DoUntilAll25TriesExpired -- OkSetAmoeba: -- 01:CF3C:20 99 CF JSR IntendToSetAmoebaToMap -- 01:CF3F:90 01 BCC Restore_X -- 01:CF41:60 RTS ----------------------------------------------------------- -- Restore_X: -- 01:CF42:C6 50 DEC Temp_X = #$0A -- DoUntilAll25TriesExpired: -- 01:CF44:C6 4D DEC 25Tries = #$0A -- 01:CF46:F0 03 BEQ All25TriesExpired -- 01:CF48:4C 87 CE JMP Loop25Tries -- All25TriesExpired: -- ; Exits from UpdateAmoeba -- 01:CF4B:A5 4F LDA Temp_Y = #$38 -- 01:CF4D:05 4E ORA Temp_AmoebaDirection = #$00 -- 01:CF4F:85 BB STA AmoebaDirectionAnd_Y = #$41 -- 01:CF51:A5 50 LDA Temp_X = #$0A -- 01:CF53:85 BC STA Amoeba_X = #$18 -- 01:CF55:E6 B7 INC AmoebaPhase = #$82 -- 01:CF57:A5 B7 LDA AmoebaPhase = #$82 -- 01:CF59:29 83 AND #$83 -- 01:CF5B:85 B7 STA AmoebaPhase = #$82 -- 01:CF5D:60 RTS ----------------------------------------------------------- -- -- CheckMultiplying_Left: -- ; Returns C=1 when the pointer is on another amoeba, Z=1 when can multiply -- 01:CF5E:A4 4F LDY Temp_Y = #$38 -- 01:CF60:C6 50 DEC Temp_X = #$0A -- 01:CF62:A6 50 LDX Temp_X = #$0A -- 01:CF64:20 7D CC JSR GetObjectFromMap -- 01:CF67:4C 8B CF JMP CheckMultiplying -- CheckMultiplying_Up: -- ; Returns C=1 when the pointer is on another amoeba, Z=1 when can multiply -- 01:CF6A:C6 4F DEC Temp_Y = #$38 -- 01:CF6C:A4 4F LDY Temp_Y = #$38 -- 01:CF6E:A6 50 LDX Temp_X = #$0A -- 01:CF70:20 7D CC JSR GetObjectFromMap -- 01:CF73:4C 8B CF JMP CheckMultiplying -- CheckMultiplying_Right: -- ; Returns C=1 when the pointer is on another amoeba, Z=1 when can multiply -- 01:CF76:E6 50 INC Temp_X = #$0A -- 01:CF78:A4 4F LDY Temp_Y = #$38 -- 01:CF7A:A6 50 LDX Temp_X = #$0A -- 01:CF7C:20 7D CC JSR GetObjectFromMap -- 01:CF7F:4C 8B CF JMP CheckMultiplying -- CheckMultiplying_Down: -- ; Returns C=1 when the pointer is on another amoeba, Z=1 when can multiply -- 01:CF82:E6 4F INC Temp_Y = #$38 -- 01:CF84:A4 4F LDY Temp_Y = #$38 -- 01:CF86:A6 50 LDX Temp_X = #$0A -- 01:CF88:20 7D CC JSR GetObjectFromMap -- CheckMultiplying: -- ; Returns C=1 when the pointer is on another amoeba, Z=1 when can multiply -- 01:CF8B:C9 D0 CMP #$D0 -- 01:CF8D:F0 08 BEQ OccupiedByAmoeba -- 01:CF8F:C9 00 CMP #$00 -- 01:CF91:F0 02 BEQ $CF95 -- 01:CF93:C9 20 CMP #$20 -- ; Return result in Z flag: Z=1 when can multiply -- 01:CF95:18 CLC -- 01:CF96:60 RTS ----------------------------------------------------------- -- OccupiedByAmoeba: -- ; The place is occupied by another amoeba, so we can't multiply there -- ; but instead we will move the pointer there! -- 01:CF97:38 SEC -- 01:CF98:60 RTS ----------------------------------------------------------- -- -- IntendToSetAmoebaToMap: -- ; Uses Temp_X/Temp_Y, returns C=1 on success, and C=0 when --Countdown_BD != 0 -- 01:CF99:A5 B7 LDA AmoebaPhase = #$82 -- 01:CF9B:09 80 ORA #$80 -- 01:CF9D:85 B7 STA AmoebaPhase = #$82 -- 01:CF9F:C6 BD DEC Amoeba_Timer = #$1D -- 01:CFA1:F0 02 BEQ ReallySetAmoebaToMap -- DontWantToMultiply: -- ; The IntendToSetAmoebaToMap() must be called many times, only then the multiplication will be made -- 01:CFA3:18 CLC -- 01:CFA4:60 RTS ----------------------------------------------------------- -- ReallySetAmoebaToMap: -- ; Uses Temp_X/Temp_Y, returns C=1 on success -- 01:CFA5:A4 4F LDY Temp_Y = #$38 -- 01:CFA7:A6 50 LDX Temp_X = #$0A -- 01:CFA9:A9 D0 LDA #$D0 -- 01:CFAB:20 77 D0 JSR SetObjectToMap -- 01:CFAE:A4 4F LDY Temp_Y = #$38 -- 01:CFB0:A6 50 LDX Temp_X = #$0A -- 01:CFB2:A9 D0 LDA #$D0 -- 01:CFB4:20 B7 D0 JSR AddObjectToBuffer -- SheduleNextTimer: -- ; Every time the waiting period decreases by 4 -- 01:CFB7:A5 BA LDA AmoebaTimerResetValue = #$3F -- 01:CFB9:38 SEC -- 01:CFBA:E9 04 SBC #$04 -- 01:CFBC:85 BA STA AmoebaTimerResetValue = #$3F -- 01:CFBE:85 BD STA Amoeba_Timer = #$1D -- ; Next update should reset the pointer to the origin -- 01:CFC0:A9 80 LDA #$80 -- 01:CFC2:85 B7 STA AmoebaPhase = #$82 -- 01:CFC4:38 SEC -- 01:CFC5:60 RTS ----------------------------------------------------------- ------------------------------------------------------------------------