<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>ValueAIV2</title>
    <style>
        body { font-family: sans-serif; text-align: center; }
        #boardCanvas { background-color: #f0d9b5; margin-top: 10px; cursor: pointer; }
        #controls { margin-bottom: 10px; }
    </style>
</head>

<body>
    <h1>ValueAIV2</h1>
    <div id="controls">
        难度(最大搜索深度):<input type="range" id="difficulty" min="1" max="5" value="3">
        <span id="difficultyValue">3</span>
        <br>
        先手:
        <label><input type="radio" name="first" id="firstHuman" value="human" checked> 人类</label>
        <label><input type="radio" name="first" id="firstAI" value="ai"> AI</label>
        <br>
        <button id="restartBtn">重新开始</button>
    </div>
    <canvas id="boardCanvas" width="600" height="600"></canvas>

    <script>
        const BOARD_SIZE = 15;
        const CELL_SIZE = 40;
        const EMPTY = 0;
        const BLACK = 1;
        const WHITE = 2;
        
        var humanColor, aiColor, currentPlayer, gameOver = false, maxDepth = 4;
        var board = [];
        var canvas = document.getElementById("boardCanvas");
        var ctx = canvas.getContext("2d");
        
        function initBoard() {
          board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(EMPTY));
          gameOver = false;
          maxDepth = parseInt(document.getElementById("difficulty").value) + 1;
        
          if (document.getElementById("firstHuman").checked) {
            humanColor = BLACK;
            aiColor = WHITE;
            currentPlayer = humanColor;
          } else {
            humanColor = WHITE;
            aiColor = BLACK;
            currentPlayer = aiColor;
          }
          drawBoard();
        
          if (currentPlayer === aiColor) {
            setTimeout(aiMove, 100);
          }
        }
        
        document.getElementById("difficulty").addEventListener("input", function() {
          document.getElementById("difficultyValue").innerText = this.value;
        });
        document.getElementById("restartBtn").addEventListener("click", initBoard);
        canvas.addEventListener("click", handleClick);
        
        function drawBoard() {
          ctx.clearRect(0, 0, canvas.width, canvas.height);
          ctx.strokeStyle = "#333";
          ctx.lineWidth = 1;
        
          for (let i = 0; i < BOARD_SIZE; i++) {
            ctx.beginPath();
            ctx.moveTo(CELL_SIZE / 2, CELL_SIZE / 2 + i * CELL_SIZE);
            ctx.lineTo(canvas.width - CELL_SIZE / 2, CELL_SIZE / 2 + i * CELL_SIZE);
            ctx.stroke();
        
            ctx.beginPath();
            ctx.moveTo(CELL_SIZE / 2 + i * CELL_SIZE, CELL_SIZE / 2);
            ctx.lineTo(CELL_SIZE / 2 + i * CELL_SIZE, canvas.height - CELL_SIZE / 2);
            ctx.stroke();
          }
        
          for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
              if (board[i][j] !== EMPTY) {
                let cx = CELL_SIZE / 2 + j * CELL_SIZE;
                let cy = CELL_SIZE / 2 + i * CELL_SIZE;
                ctx.beginPath();
                ctx.arc(cx, cy, CELL_SIZE * 0.4, 0, 2 * Math.PI);
                ctx.fillStyle = board[i][j] === BLACK ? "black" : "white";
                ctx.fill();
                ctx.stroke();
              }
            }
          }
        }
        
        function handleClick(event) {
          if (gameOver || currentPlayer !== humanColor) return;
          let rect = canvas.getBoundingClientRect();
          let x = event.clientX - rect.left;
          let y = event.clientY - rect.top;
          let j = Math.floor(x / CELL_SIZE);
          let i = Math.floor(y / CELL_SIZE);
          if (i < 0 || i >= BOARD_SIZE || j < 0 || j >= BOARD_SIZE) return;
          if (board[i][j] !== EMPTY) return;
        
          board[i][j] = humanColor;
          drawBoard();
          if (isWinningMove(i, j, humanColor)) {
            gameOver = true;
            setTimeout(() => alert("你赢了!"), 10);
            return;
          }
          currentPlayer = aiColor;
          setTimeout(aiMove, 100);
        }
        
        function isWinningMove(x, y, player) {
          const directions = [[1, 0], [0, 1], [1, 1], [1, -1]];
          for (let d = 0; d < directions.length; d++) {
            let dx = directions[d][0], dy = directions[d][1];
            let count = 1;
            let i = 1;
            while (x + i * dx >= 0 && x + i * dx < BOARD_SIZE && y + i * dy >= 0 && y + i * dy < BOARD_SIZE && board[x + i * dx][y + i * dy] === player) {
              count++;
              i++;
            }
            i = 1;
            while (x - i * dx >= 0 && x - i * dx < BOARD_SIZE && y - i * dy >= 0 && y - i * dy < BOARD_SIZE && board[x - i * dx][y - i * dy] === player) {
              count++;
              i++;
            }
            if (count >= 5) return true;
          }
          return false;
        }
        
        function aiMove() {
          if (gameOver) return;
          let candidateMoves = getCandidateMoves();
        
          for (let move of candidateMoves) {
            board[move.x][move.y] = aiColor;
            if (isWinningMove(move.x, move.y, aiColor)) {
              drawBoard();
              gameOver = true;
              setTimeout(() => alert("AI 赢了!"), 10);
              return;
            }
            board[move.x][move.y] = EMPTY;
          }
        
          for (let move of candidateMoves) {
            board[move.x][move.y] = humanColor;
            if (isWinningMove(move.x, move.y, humanColor)) {
              board[move.x][move.y] = aiColor;
              drawBoard();
              currentPlayer = humanColor;
              return;
            }
            board[move.x][move.y] = EMPTY;
          }
        
          let bestMove = iterativeDeepening(maxDepth);
          if (bestMove) {
            board[bestMove.x][bestMove.y] = aiColor;
            drawBoard();
            if (isWinningMove(bestMove.x, bestMove.y, aiColor)) {
              gameOver = true;
              setTimeout(() => alert("AI 赢了!"), 10);
              return;
            }
          }
          currentPlayer = humanColor;
        }
        
        function getCandidateMoves() {
          let moves = [];
          let minX = BOARD_SIZE, minY = BOARD_SIZE, maxX = 0, maxY = 0;
          let found = false;
          for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
              if (board[i][j] !== EMPTY) {
                found = true;
                if (i < minX) minX = i;
                if (i > maxX) maxX = i;
                if (j < minY) minY = j;
                if (j > maxY) maxY = j;
              }
            }
          }
          if (!found) {
            moves.push({ x: Math.floor(BOARD_SIZE / 2), y: Math.floor(BOARD_SIZE / 2) });
            return moves;
          }
          let margin = 2;
          minX = Math.max(minX - margin, 0);
          minY = Math.max(minY - margin, 0);
          maxX = Math.min(maxX + margin, BOARD_SIZE - 1);
          maxY = Math.min(maxY + margin, BOARD_SIZE - 1);
          for (let i = minX; i <= maxX; i++) {
            for (let j = minY; j <= maxY; j++) {
              if (board[i][j] === EMPTY) {
                moves.push({ x: i, y: j });
              }
            }
          }
          return moves;
        }
        
        function iterativeDeepening(maxDepth) {
          let bestMove = null;
          for (let depth = 1; depth <= maxDepth; depth++) {
            let result = negamax(depth, -Infinity, Infinity, aiColor);
            if (result.move) {
              bestMove = result.move;
            }
          }
          return bestMove;
        }
        
        function negamax(depth, alpha, beta, player) {
          if (depth === 0) {
            return { score: (player === aiColor) ? staticEvaluation() : -staticEvaluation(), move: null };
          }
        
          let best = { score: -Infinity, move: null };
          let moves = getCandidateMoves();
          if (moves.length === 0) {
            return { score: (player === aiColor) ? staticEvaluation() : -staticEvaluation(), move: null };
          }
        
          for (let move of moves) {
            board[move.x][move.y] = player;
            if (isWinningMove(move.x, move.y, player)) {
              board[move.x][move.y] = EMPTY;
              return { score: 1000000 + depth, move: move };
            }
            let nextPlayer = (player === aiColor) ? humanColor : aiColor;
            let result = negamax(depth - 1, -beta, -alpha, nextPlayer);
            let score = -result.score;
            board[move.x][move.y] = EMPTY;
            if (score > best.score) {
              best.score = score;
              best.move = move;
            }
            alpha = Math.max(alpha, score);
            if (alpha >= beta) break;
          }
          return best;
        }
        
        function staticEvaluation() {
          let defenseFactor = 0.82;
          let aiScore = evaluateFor(aiColor);
          let oppScore = evaluateFor(humanColor);
          return aiScore - defenseFactor * oppScore;
        }
        
        function evaluateFor(color) {
          let score = 0;
          for (let i = 0; i < BOARD_SIZE; i++) {
            for (let j = 0; j < BOARD_SIZE; j++) {
              if (board[i][j] === color) {
                for (let d = 0; d < 4; d++) {
                  let dx = [1, 0, 1, 1][d];
                  let dy = [0, 1, 1, -1][d];
                  let prevX = i - dx, prevY = j - dy;
                  if (prevX >= 0 && prevX < BOARD_SIZE && prevY >= 0 && prevY < BOARD_SIZE && board[prevX][prevY] === color) continue;
                  let count = 0;
                  let openEnds = 0;
                  let x = i, y = j;
                  while (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] === color) {
                    count++;
                    x += dx;
                    y += dy;
                  }
                  if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] === EMPTY) openEnds++;
                  x = i - dx;
                  y = j - dy;
                  if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board[x][y] === EMPTY) openEnds++;
                  score += scoreLine(count, openEnds);
                }
              }
            }
          }
          return score;
        }
        
        function scoreLine(count, openEnds) {
          if (count >= 5) return 100000;
          if (count === 4) {
            if (openEnds === 2) return 10000;
            else if (openEnds === 1) return 1000;
          }
          if (count === 3) {
            if (openEnds === 2) return 1000;
            else if (openEnds === 1) return 100;
          }
          if (count === 2) {
            if (openEnds === 2) return 100;
            else if (openEnds === 1) return 10;
          }
          if (count === 1) return 1;
          return 0;
        }
        
        initBoard();
    </script>
</body>

</html>

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.