<!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>
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.