matty #20 AI

HTML

<!DOCTYPE html>
<html lang="nl">
<head>
    <meta charset="UTF-8">
    <title>matty #20 - easy as 1-2-3</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="matty: play math games in your browser">
    <meta name="keywords" content="Math, Math games, math games in browser, HTML, CSS, JavaScript">
    <meta name="format-detection" content="telephone=no">
    <style>
        body {
            font-family: Tahoma, sans-serif;
            text-align: center;
            background-color: white;
            color: black;
            margin: 0;
            min-height: 100vh;
            display: flex;
            flex-direction: column;
        }

        h1 {
            text-transform: lowercase;
            margin: 5px 0;
        }

        .container {
            flex: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
        }

        .grid {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 5px;
            width: 90%;
            max-width: 450px;
            margin: 10px auto;
        }

        .ball {
            width: 100%;
            padding-top: 100%;
            position: relative;
            background-color: black;
            border-radius: 50%;
            font-size: 2vw;
            color: white;
            cursor: pointer;
            user-select: none;
            transition: transform 0.5s ease;
        }

        .ball::after {
            content: attr(data-number);
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
        }

        .ball.selected {
            background-color: lightgray;
        }

        .ball.empty {
            background-color: transparent;
        }

        .ball.empty::after {
            content: '';
        }

        @keyframes fall {
      ...

JavaScript

function enumerateMoves(grid) {
  let moves = [];

  function addMove(r1, c1, r2, c2, r3, c3) {
    moves.push([[r1, c1], [r2, c2], [r3, c3]]);
  }

  function find3(r1, c1, r2, c2) {
    if (r2 > 0 && grid[r2 - 1][c2] === 3) addMove(r1, c1, r2, c2, r2 - 1, c2);
    if (c2 > 0 && grid[r2][c2 - 1] === 3) addMove(r1, c1, r2, c2, r2, c2 - 1);
    if (r2 < 6 && grid[r2 + 1][c2] === 3) addMove(r1, c1, r2, c2, r2 + 1, c2);
    if (c2 < 6 && grid[r2][c2 + 1] === 3) addMove(r1, c1, r2, c2, r2, c2 + 1);
  }

  function find2(r1, c1) {
    if (r1 > 0 && grid[r1 - 1][c1] === 2) find3(r1, c1, r1 - 1, c1);
    if (c1 > 0 && grid[r1][c1 - 1] === 2) find3(r1, c1, r1, c1 - 1);
    if (r1 < 6 && grid[r1 + 1][c1] === 2) find3(r1, c1, r1 + 1, c1);
    if (c1 < 6 && grid[r1][c1 + 1] === 2) find3(r1, c1, r1, c1 + 1);
  }

  for (let r = 0; r < 7; ++r) {
    for (let c = 0; c < 7; ++c) {
      if (grid[r][c] === 1) find2(r, c);
    }
  }
  return moves;
}

function selectRandomMove(moves) {
  return moves[Math.floor(Math.random() * moves.length)];
}

function executeMove(grid, move) {
  grid = structuredClone(grid);
  for (const [r, c] of move) {
    grid[r][c] = 0;
  }
  for (let c = 0; c < 7; ++c) {
    var r2 = 6;
    for (let r = 6; r >= 0; --r) {
      if (grid[r][c] > 0) grid[r2--][c] = grid[r][c];
    }
    while (r2 >= 0) grid[r2--][c] = 0;
  }
  return grid;
}

function selectBestMove(grid, moves) {
  function search(grid, depthLeft) {
    if (depthLeft === 0) {
      //return enumerateMoves(grid).length;
      //return calculateFitness(grid);
      return calculateFitness2(grid);
    }
    var bestScore = -Infinity;
    for (let move of enumerateMoves(grid)) {
      bestScore = Math.max(bestScore, search(executeMove(grid, move), depthLeft - 1));
    }
    return bestScore;
  }

  var bestMoves = [];
  var bestScore = -Infinity;
  for (let move of moves) {
    var newGrid = executeMove(grid, move);
    var score = search(newGrid, 3);
    if (score > bestScore) {
     ...