<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>终极井字棋 - 弱智AI版</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
background-color: #f0f0f0;
font-family: Arial, sans-serif;
}
.container {
margin: 20px;
}
.settings {
margin: 20px 0;
display: flex;
gap: 20px;
}
select {
padding: 5px;
font-size: 16px;
}
.ultimate-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
background-color: #333;
padding: 10px;
border-radius: 10px;
}
.sub-board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background-color: #999;
padding: 1px;
border: 2px solid transparent;
}
.sub-board.active {
border-color: #ff4444;
}
.cell {
width: 40px;
height: 40px;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
cursor: pointer;
transition: background-color 0.3s;
position: relative;
}
.cell::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
box-sizing: border-box;
border-right: 1px solid #ccc;
border-bottom: 1px solid #ccc;
}
.cell:nth-child(3n)::after {
border-right: none;
}
.cell:nth-child(n+7)::after {
border-bottom: none;
}
.cell:hover {
background-color: #eee;
}
.sub-board.won-X .cell {
background-color: #ffcccc;
}
.sub-board.won-O .cell {
background-color: #ccccff;
}
.status {
margin-top: 20px;
font-size: 24px;
}
button {
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<div class="settings">
<select id="difficulty">
<option value="easy">简单模式</option>
<option value="medium">中等模式</option>
<option value="hard">困难模式</option>
</select>
<select id="playerOrder">
<option value="first">先手</option>
<option value="second">后手</option>
</select>
</div>
<div class="ultimate-board" id="board"></div>
<div class="status" id="status"></div>
<button onclick="newGame()">新游戏</button>
</div>
<script>
class UltimateTicTacToe {
constructor(humanFirst, difficulty) {
this.humanSymbol = humanFirst ? 'X' : 'O';
this.aiSymbol = humanFirst ? 'O' : 'X';
this.difficulty = difficulty;
this.currentPlayer = humanFirst ? this.humanSymbol : this.aiSymbol;
this.activeBoard = null;
this.boards = Array(9).fill().map(() => ({
cells: Array(9).fill(''),
winner: null
}));
this.gameOver = false;
this.initBoard();
if (!humanFirst) this.aiMove();
}
initBoard() {
const boardElement = document.getElementById('board');
boardElement.innerHTML = '';
for (let i = 0; i < 9; i++) {
const subBoard = document.createElement('div');
subBoard.className = 'sub-board';
subBoard.dataset.index = i;
for (let j = 0; j < 9; j++) {
const cell = document.createElement('div');
cell.className = 'cell';
cell.dataset.board = i;
cell.dataset.cell = j;
cell.addEventListener('click', (e) => this.handleClick(e));
subBoard.appendChild(cell);
}
boardElement.appendChild(subBoard);
}
this.updateGame();
}
handleClick(event) {
if (this.gameOver || this.currentPlayer !== this.humanSymbol) return;
const boardIndex = parseInt(event.target.dataset.board);
const cellIndex = parseInt(event.target.dataset.cell);
if (this.isValidMove(boardIndex, cellIndex)) {
this.makeMove(boardIndex, cellIndex);
}
}
isValidMove(boardIndex, cellIndex) {
if (this.activeBoard !== null && boardIndex !== this.activeBoard) return false;
if (this.boards[boardIndex].winner) return false;
return this.boards[boardIndex].cells[cellIndex] === '';
}
makeMove(boardIndex, cellIndex, isAI = false) {
this.boards[boardIndex].cells[cellIndex] = this.currentPlayer;
this.checkSubWinner(boardIndex);
this.activeBoard = this.boards[cellIndex].winner ? null : cellIndex;
this.currentPlayer = isAI ? this.humanSymbol : this.aiSymbol;
this.checkGameOver();
this.updateGame();
if (!this.gameOver && !isAI) {
this.aiMove();
}
}
aiMove() {
const possibleMoves = this.getValidMoves();
if (possibleMoves.length === 0) return;
let bestMove;
switch(this.difficulty) {
case 'hard':
bestMove = this.minimax(3, -Infinity, Infinity, true)[1];
break;
case 'medium':
bestMove = this.getStrategicMove(possibleMoves);
break;
default:
bestMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
}
setTimeout(() => {
this.makeMove(bestMove.boardIndex, bestMove.cellIndex, true);
}, 500);
}
minimax(depth, alpha, beta, maximizingPlayer) {
if (depth === 0 || this.gameOver) {
return [this.evaluateBoard(), null];
}
const possibleMoves = this.getValidMoves();
let bestValue = maximizingPlayer ? -Infinity : Infinity;
let bestMove = null;
for (const move of possibleMoves) {
// 模拟移动
this.boards[move.boardIndex].cells[move.cellIndex] =
maximizingPlayer ? this.aiSymbol : this.humanSymbol;
this.checkSubWinner(move.boardIndex);
const [currentValue] = this.minimax(depth - 1, alpha, beta, !maximizingPlayer);
// 撤销移动
this.boards[move.boardIndex].cells[move.cellIndex] = '';
this.boards[move.boardIndex].winner = null;
if (maximizingPlayer) {
if (currentValue > bestValue) {
bestValue = currentValue;
bestMove = move;
alpha = Math.max(alpha, bestValue);
}
} else {
if (currentValue < bestValue) {
bestValue = currentValue;
bestMove = move;
beta = Math.min(beta, bestValue);
}
}
if (beta <= alpha) break;
}
return [bestValue, bestMove];
}
evaluateBoard() {
let score = 0;
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
const mainBoard = this.boards.map(b => {
if (b.winner === this.aiSymbol) return 1;
if (b.winner === this.humanSymbol) return -1;
return 0;
});
// 检查主棋盘胜利
for (const line of lines) {
const [a, b, c] = line;
const sum = mainBoard[a] + mainBoard[b] + mainBoard[c];
if (Math.abs(sum) === 3) return sum * 1000;
if (sum === 2) score += 500;
if (sum === -2) score -= 500;
}
// 子棋盘权重
this.boards.forEach((board, index) => {
if (board.winner === this.aiSymbol) score += 100;
else if (board.winner === this.humanSymbol) score -= 100;
});
return score;
}
getStrategicMove(moves) {
// 中心优先
const centerMoves = moves.filter(m => [4].includes(m.cellIndex));
if (centerMoves.length > 0) return centerMoves[0];
// 胜利机会
const winningMoves = moves.filter(m => {
const temp = [...this.boards[m.boardIndex].cells];
temp[m.cellIndex] = this.aiSymbol;
return this.checkWinning(temp);
});
if (winningMoves.length > 0) return winningMoves[0];
// 阻止玩家
const blockingMoves = moves.filter(m => {
const temp = [...this.boards[m.boardIndex].cells];
temp[m.cellIndex] = this.humanSymbol;
return this.checkWinning(temp);
});
if (blockingMoves.length > 0) return blockingMoves[0];
return moves[0];
}
getValidMoves() {
const moves = [];
const targetBoards = this.activeBoard !== null ?
[this.activeBoard] :
Array.from({length: 9}, (_, i) => i);
targetBoards.forEach(boardIndex => {
if (this.boards[boardIndex].winner) return;
this.boards[boardIndex].cells.forEach((cell, cellIndex) => {
if (!cell) moves.push({ boardIndex, cellIndex });
});
});
return moves;
}
checkSubWinner(boardIndex) {
const cells = this.boards[boardIndex].cells;
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
for (const line of lines) {
const [a, b, c] = line;
if (cells[a] && cells[a] === cells[b] && cells[a] === cells[c]) {
this.boards[boardIndex].winner = cells[a];
return;
}
}
if (!cells.includes('')) {
this.boards[boardIndex].winner = 'D';
}
}
checkGameOver() {
const winners = this.boards.map(b => b.winner);
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
for (const line of lines) {
const [a, b, c] = line;
if (winners[a] && winners[a] === winners[b] && winners[a] === winners[c]) {
this.gameOver = true;
return;
}
}
if (winners.every(w => w !== null)) {
this.gameOver = true;
}
}
updateGame() {
document.querySelectorAll('.sub-board').forEach((subBoard, index) => {
const boardData = this.boards[index];
subBoard.classList.toggle('active',
!this.gameOver &&
(this.activeBoard === null || this.activeBoard === index) &&
!boardData.winner
);
subBoard.className = 'sub-board';
if (boardData.winner === 'X') subBoard.classList.add('won-X');
if (boardData.winner === 'O') subBoard.classList.add('won-O');
subBoard.querySelectorAll('.cell').forEach((cell, cellIndex) => {
cell.textContent = boardData.cells[cellIndex];
});
});
const statusElement = document.getElementById('status');
statusElement.textContent = this.gameOver ? '游戏结束!'
: `当前玩家:${this.currentPlayer}`;
}
checkWinning(cells) {
const lines = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]];
return lines.some(([a, b, c]) =>
cells[a] && cells[a] === cells[b] && cells[a] === cells[c]
);
}
}
let game;
function newGame() {
const difficulty = document.getElementById('difficulty').value;
const humanFirst = document.getElementById('playerOrder').value === 'first';
game = new UltimateTicTacToe(humanFirst, difficulty);
}
newGame();
</script>
</body>
</html>