0

I am building an AI agent to play this Block Breaker game by Google. My goal is to provide my agents with precise information about the game state, but also the ability for me to manipulate the game and ensure all agents work with the same board.

What I've done so far:

  1. Event Loop & Rendering: I managed to identify the event loop and render function (and their call chain) using Firefox's DevTools. However, I'm having trouble locating where the functions are actually defined in the source code.

    • Breakpoint Setup: I've set a breakpoint in the event loop to trace function calls, but I'm unable to track down where these functions are located in the codebase.

      • Image 1: Setting breakpoints in the event loop

      • Image 2: Breakpoint and tracebacks

  2. Game Board Representation:

    I found a variable that represents the board (A), which is a 7x4 array. Each block has the following attributes:

    • type (c9) - Can be "ACID", "EXPLOSIVE", "GHOST", "GHOST_TANGIBLE", "NORMAL", "MIRROR", "MUSHROOM"etc.
    • time_destroyed (Ptc) - -1 if not destroyed, or any positive value if destroyed.

    I also located the paddle's position (Ua), which has attributes x and y.

    I can mutate these variables when the breakpoint is hit, but they're not editable after I cancel the breakpoint. How can I still view/edit these variable even not in debug mode

    • Image 3: Mutating the board via the console
  3. What I need help with:

    • I am struggling to locate where the game keeps track of other moving objects such as:

      • Acid (drop after hitting acid block)
      • Spores (drop after hitting mushroom block)
      • Rubble (drop after htting brick block)
      • The Ball itself (and all the bonus ball)

Thank you in advance for any guidance or suggestions!

1
  • Please edit your question to re-insert the screenshots by the provided image tool, not some random external service. Apparently the current provision failed. -- Optimally, if the screenshots show text, insert the text as a verbatim copy. Commented Oct 6 at 9:07

1 Answer 1

0

You can't get A outside that function. Once the scope closes, it's gone.

Instead, you can use a script like this to do it:

(function foreverLoop() {
  const canvas = document.querySelector("canvas");
  const ctx = canvas.getContext("2d");
  const { width, height } = canvas;
  const imgData = ctx.getImageData(0, 0, width, height);
  const data = imgData.data;

  const visited = new Uint8Array(width * height);

  // === Colors & Thresholds ===
  const ballColor = [230, 230, 230];
  const ballThreshold = 40;

  const barColor = [230, 230, 230];
  const barThreshold = 40;

  function colorMatch(r, g, b, target, threshold) {
    return (
      Math.abs(r - target[0]) < threshold &&
      Math.abs(g - target[1]) < threshold &&
      Math.abs(b - target[2]) < threshold
    );
  }

  function getPixelIndex(x, y) {
    return (y * width + x) * 4;
  }

  function getVisitedIndex(x, y) {
    return y * width + x;
  }

  function floodFill(x, y, targetColor, threshold) {
    const queue = [[x, y]];
    const pixels = [];
    let minX = x, maxX = x, minY = y, maxY = y;

    while (queue.length) {
      const [cx, cy] = queue.pop();
      const idx = getVisitedIndex(cx, cy);
      if (visited[idx]) continue;
      visited[idx] = 1;

      const pixIdx = getPixelIndex(cx, cy);
      const [r, g, b] = data.slice(pixIdx, pixIdx + 3);
      if (!colorMatch(r, g, b, targetColor, threshold)) continue;

      pixels.push([cx, cy]);
      minX = Math.min(minX, cx);
      maxX = Math.max(maxX, cx);
      minY = Math.min(minY, cy);
      maxY = Math.max(maxY, cy);

      for (let dx = -1; dx <= 1; dx++) {
        for (let dy = -1; dy <= 1; dy++) {
          const nx = cx + dx;
          const ny = cy + dy;
          if (
            nx >= 0 && nx < width &&
            ny >= 0 && ny < height &&
            !visited[getVisitedIndex(nx, ny)]
          ) {
            queue.push([nx, ny]);
          }
        }
      }
    }

    return { pixels, minX, maxX, minY, maxY };
  }

  const balls = [];
  const bars = [];

  // === Scan image for BALLS ===
for (let y = 0; y < height; y++) {
  for (let x = 0; x < width; x++) {
    if (visited[getVisitedIndex(x, y)]) continue;

    const idx = getPixelIndex(x, y);
    const [r, g, b] = data.slice(idx, idx + 3);

    if (colorMatch(r, g, b, ballColor, ballThreshold)) {
      const region = floodFill(x, y, ballColor, ballThreshold);
      const size = region.pixels.length;
      if (size >= 400 && size <= 500) {
        const sum = region.pixels.reduce((acc, [px, py]) => [acc[0] + px, acc[1] + py], [0, 0]);
        const cx = sum[0] / size;
        const cy = sum[1] / size;
        balls.push({ x: cx, y: cy, size });
      }
    }
  }
}

// === Reset visited array ===
visited.fill(0);

// === Scan image for BARS ===
for (let y = 0; y < height; y++) {
  for (let x = 0; x < width; x++) {
    if (visited[getVisitedIndex(x, y)]) continue;

    const idx = getPixelIndex(x, y);
    const [r, g, b] = data.slice(idx, idx + 3);

    if (colorMatch(r, g, b, barColor, barThreshold)) {
      const region = floodFill(x, y, barColor, barThreshold);
      const { pixels, minX, maxX, minY, maxY } = region;
      const widthBox = maxX - minX + 1;
      const heightBox = maxY - minY + 1;
      const area = widthBox * heightBox;
      const aspectRatio = widthBox / heightBox;

      if (widthBox > 100 && aspectRatio > 4) {
        const cx = (minX + maxX) / 2;
        const cy = (minY + maxY) / 2;
        bars.push({ x: cx, y: cy, width: widthBox, height: heightBox });
      }
    }
  }
}



  if (balls.length === 0 || bars.length === 0) {
    console.warn("Could not find bar or ball. Retrying...");
    setTimeout(foreverLoop, 500);
    return;
  }

  // === Get targets ===
  const ball = balls[balls.length - 1];
  const bar = bars[0];

  const direction = bar.x < ball.x ? 'ArrowRight' : 'ArrowLeft';
  const keyCode = direction === 'ArrowRight' ? 39 : 37;
  const moveStep = 3;
  const maxSteps = 200;

  function simulateKeyDown(key, keyCode) {
    const event = new KeyboardEvent("keydown", {
      key, code: key, keyCode, which: keyCode, bubbles: true,
    });
    document.dispatchEvent(event);
  }

  function simulateKeyUp(key, keyCode) {
    const event = new KeyboardEvent("keyup", {
      key, code: key, keyCode, which: keyCode, bubbles: true,
    });
    document.dispatchEvent(event);
  }

  let currentX = bar.x;
  let steps = 0;
  console.log("currentx: " + currentX)
    console.log("ballx: " + ball.x)
  const moveInterval = setInterval(() => {
    const delta = Math.abs(currentX - ball.x);

    if (delta <= moveStep || steps >= maxSteps) {
      simulateKeyUp(direction, keyCode);
      clearInterval(moveInterval);
      setTimeout(foreverLoop, 100); // 🔁 Loop again
      return;
    }

    simulateKeyDown(direction, keyCode);
    currentX += direction === 'ArrowRight' ? moveStep : -moveStep;
    steps++;
  }, 16);
})();

This is a little glitchy, but it gets ~500 points without fail. Paste it in the console after you open the game but before you start playing, then start the game and let the script do it's job. It does not avoid poison and only works with white balls

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.