<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>🎮 增强版五子棋 - 双人/电脑对战</title>
<style>
body {
background: #f4f4f4;
font-family: 'Microsoft Yahei', sans-serif;
text-align: center;
margin-top: 30px;
transition: all 0.3s ease;
}
h1 {
color: #333;
transition: color 0.3s ease;
}
canvas {
border: 2px solid #333;
background-color: #fdd;
display: block;
margin: 20px auto;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
}
#status {
font-size: 18px;
margin-top: 10px;
color: green;
min-height: 27px;
transition: color 0.3s ease;
}
button {
padding: 8px 16px;
font-size: 16px;
cursor: pointer;
margin: 5px;
border-radius: 5px;
border: 1px solid #999;
background: #fff;
transition: all 0.2s ease;
}
button:hover:not(:disabled) {
background: #e8e8e8;
transform: translateY(-2px);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#score {
margin-top: 15px;
font-size: 16px;
}
select {
font-size: 16px;
padding: 5px;
border-radius: 5px;
border: 1px solid #999;
}
.controls {
margin: 15px 0;
}
.theme-preview {
display: inline-block;
width: 20px;
height: 20px;
border-radius: 50%;
margin-right: 8px;
vertical-align: middle;
}
.footer-info {
margin-top: 20px;
font-size: 14px;
color: #666;
transition: color 0.3s ease;
}
</style>
</head>
<body>
<h1>🎮 增强版五子棋 - 玩家 vs 玩家 / 玩家 vs AI</h1>
<div class="controls">
<label>游戏模式:
<select id="gameMode">
<option value="local">本地双人对战</option>
<option value="ai">挑战电脑(黑先)</option>
</select>
</label>
<br />
<label style="margin-top:10px; display:inline-block;">主题风格:
<select id="themeSelect">
<option value="classic">🟤 经典木纹</option>
<option value="dark">🌙 暗夜模式</option>
<option value="modern">💎 现代蓝</option>
<option value="nature">🌿 自然绿</option>
<option value="sunset">🌅 夕阳橙</option>
<option value="purple">🌸 梦幻紫</option>
</select>
</label>
</div>
<canvas id="chessBoard" width="600" height="600"></canvas>
<div id="status">轮到黑棋落子</div>
<div class="controls">
<button id="undoBtn" onclick="undoMove()" disabled>⬅️ 撤销上一步</button>
<button onclick="restartGame()">🔄 重新开始</button>
<button onclick="resetScores()">🗑️ 清除战绩</button>
</div>
<audio id="placeSound">
<source src="https://cdn.pixabay.com/audio/2022/03/15/audio_4b7f9a1ba6.mp3" type="audio/mp3">
</audio>
<audio id="winSound">
<source src="https://cdn.pixabay.com/audio/2022/01/17/audio_c46dc4b0ac.mp3" type="audio/mp3">
</audio>
<div id="score">🎮 当前战绩:<br />你(黑棋):0 胜 电脑(白棋):0 胜</div>
<div class="footer-info">
💡 提示:在AI模式下撤销会同时撤回电脑的应对棋步
</div>
<script>
// ========== 主题定义 ==========
const themes = {
classic: {
name: "经典木纹",
background: "#f4f4f4",
canvasBg: "#fdd",
canvasBorder: "#333",
boardLines: "#333",
textColor: "#333",
statusColor: "green",
blackPiece: "#1a1a1a",
blackGradient: ["#333", "#000"],
whitePiece: "#fff",
whiteGradient: ["#fff", "#ddd"],
whiteBorder: "#999",
starColor: "#333",
buttonBg: "#fff",
buttonBorder: "#999",
shadow: "0 0 15px rgba(0, 0, 0, 0.2)"
},
dark: {
name: "暗夜模式",
background: "#1a1a2e",
canvasBg: "#2d2d44",
canvasBorder: "#4a4a6a",
boardLines: "#7a7a9a",
textColor: "#e0e0e0",
statusColor: "#4ade80",
blackPiece: "#0a0a14",
blackGradient: ["#222", "#000"],
whitePiece: "#e8e8e8",
whiteGradient: ["#f0f0f0", "#c0c0c0"],
whiteBorder: "#888",
starColor: "#7a7a9a",
buttonBg: "#2d2d44",
buttonBorder: "#4a4a6a",
shadow: "0 0 20px rgba(100, 100, 255, 0.3)"
},
modern: {
name: "现代蓝",
background: "#e8f4f8",
canvasBg: "#b8d8e8",
canvasBorder: "#2c3e50",
boardLines: "#2c3e50",
textColor: "#2c3e50",
statusColor: "#2980b9",
blackPiece: "#2c3e50",
blackGradient: ["#34495e", "#1a252f"],
whitePiece: "#ecf0f1",
whiteGradient: ["#fff", "#bdc3c7"],
whiteBorder: "#95a5a6",
starColor: "#2c3e50",
buttonBg: "#ecf0f1",
buttonBorder: "#bdc3c7",
shadow: "0 0 15px rgba(44, 62, 80, 0.2)"
},
nature: {
name: "自然绿",
background: "#e8f5e9",
canvasBg: "#c8e6c9",
canvasBorder: "#1b5e20",
boardLines: "#2e7d32",
textColor: "#1b5e20",
statusColor: "#2e7d32",
blackPiece: "#212121",
blackGradient: ["#333", "#111"],
whitePiece: "#f5f5f5",
whiteGradient: ["#fff", "#ddd"],
whiteBorder: "#757575",
starColor: "#1b5e20",
buttonBg: "#e8f5e9",
buttonBorder: "#81c784",
shadow: "0 0 15px rgba(27, 94, 32, 0.2)"
},
sunset: {
name: "夕阳橙",
background: "#fff3e0",
canvasBg: "#ffe0b2",
canvasBorder: "#e65100",
boardLines: "#bf360c",
textColor: "#bf360c",
statusColor: "#e65100",
blackPiece: "#3e2723",
blackGradient: ["#4e342e", "#1b0000"],
whitePiece: "#fff8e1",
whiteGradient: ["#fff", "#ffe082"],
whiteBorder: "#f9a825",
starColor: "#bf360c",
buttonBg: "#fff3e0",
buttonBorder: "#ff8f00",
shadow: "0 0 15px rgba(230, 81, 0, 0.2)"
},
purple: {
name: "梦幻紫",
background: "#f3e5f5",
canvasBg: "#e1bee7",
canvasBorder: "#4a148c",
boardLines: "#6a1b9a",
textColor: "#4a148c",
statusColor: "#7b1fa2",
blackPiece: "#1a0033",
blackGradient: ["#2d0052", "#0a0015"],
whitePiece: "#fce4ec",
whiteGradient: ["#fff", "#f8bbd0"],
whiteBorder: "#ad1457",
starColor: "#6a1b9a",
buttonBg: "#f3e5f5",
buttonBorder: "#ab47bc",
shadow: "0 0 15px rgba(106, 27, 154, 0.2)"
}
};
// ========== 全局变量 ==========
const canvas = document.getElementById("chessBoard");
const ctx = canvas.getContext("2d");
const statusDiv = document.getElementById("status");
const scoreDiv = document.getElementById("score");
const undoBtn = document.getElementById("undoBtn");
const boardSize = 15;
const cellSize = canvas.width / (boardSize + 1);
let chessBoard = [];
let gameOver = false;
let currentPlayer = "black";
let gameMode = "local";
let moveHistory = []; // 记录每一步棋
let currentTheme = themes.classic;
let aiThinking = false; // 防止AI思考时重复操作
let aiTimeout = null; // 记录AI延迟
// ========== 初始化 ==========
function initScores() {
if (!localStorage.getItem("blackWins")) localStorage.setItem("blackWins", "0");
if (!localStorage.getItem("whiteWins")) localStorage.setItem("whiteWins", "0");
updateScoreDisplay();
}
function resetScores() {
if (confirm("确定要清除所有战绩吗?")) {
localStorage.setItem("blackWins", "0");
localStorage.setItem("whiteWins", "0");
updateScoreDisplay();
}
}
function updateScoreDisplay() {
const blackWins = localStorage.getItem("blackWins");
const whiteWins = localStorage.getItem("whiteWins");
scoreDiv.innerHTML = `🎮 当前战绩:<br/>你(黑棋):${blackWins} 胜 电脑(白棋):${whiteWins} 胜`;
}
function playPlaceSound() {
const sound = document.getElementById("placeSound");
sound.currentTime = 0;
sound.play().catch(() => {});
}
function playWinSound() {
const sound = document.getElementById("winSound");
sound.currentTime = 0;
sound.play().catch(() => {});
}
function updateStatus(text) {
statusDiv.textContent = text;
}
function updateUndoButton() {
undoBtn.disabled = gameOver || moveHistory.length === 0 || aiThinking;
}
// ========== 主题切换 ==========
function applyTheme(themeName) {
currentTheme = themes[themeName] || themes.classic;
document.body.style.background = currentTheme.background;
document.body.style.color = currentTheme.textColor;
document.querySelector("h1").style.color = currentTheme.textColor;
document.querySelector(".footer-info").style.color = currentTheme.textColor;
statusDiv.style.color = currentTheme.statusColor;
canvas.style.backgroundColor = currentTheme.canvasBg;
canvas.style.borderColor = currentTheme.canvasBorder;
canvas.style.boxShadow = currentTheme.shadow;
// 按钮样式
const buttons = document.querySelectorAll("button");
buttons.forEach(btn => {
btn.style.background = currentTheme.buttonBg;
btn.style.borderColor = currentTheme.buttonBorder;
btn.style.color = currentTheme.textColor;
});
const selects = document.querySelectorAll("select");
selects.forEach(sel => {
sel.style.background = currentTheme.buttonBg;
sel.style.borderColor = currentTheme.buttonBorder;
sel.style.color = currentTheme.textColor;
});
drawBoard();
}
document.getElementById("themeSelect").addEventListener("change", function () {
applyTheme(this.value);
});
// ========== 游戏核心 ==========
function init() {
for (let i = 0; i < boardSize; i++) {
chessBoard[i] = [];
for (let j = 0; j < boardSize; j++) {
chessBoard[i][j] = "";
}
}
moveHistory = [];
gameOver = false;
aiThinking = false;
if (aiTimeout) clearTimeout(aiTimeout);
currentPlayer = "black";
gameMode = document.getElementById("gameMode").value;
updateStatus(gameMode === "ai" ? "轮到你(黑棋)落子" : "轮到黑棋落子");
drawBoard();
updateUndoButton();
}
function restartGame() {
init();
}
function drawBoard() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制网格线
ctx.strokeStyle = currentTheme.boardLines;
ctx.lineWidth = 1;
for (let i = 1; i <= boardSize; i++) {
ctx.beginPath();
ctx.moveTo(cellSize, i * cellSize);
ctx.lineTo(boardSize * cellSize, i * cellSize);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(i * cellSize, cellSize);
ctx.lineTo(i * cellSize, boardSize * cellSize);
ctx.stroke();
}
// 绘制星位
ctx.fillStyle = currentTheme.starColor;
const stars = [
[3, 3], [11, 3], [3, 11], [11, 11], [7, 7]
];
stars.forEach(([x, y]) => {
ctx.beginPath();
ctx.arc((x + 1) * cellSize, (y + 1) * cellSize, 5, 0, Math.PI * 2);
ctx.fill();
});
// 绘制棋子
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (chessBoard[i][j] === "black") {
drawChess(i, j, "black");
} else if (chessBoard[i][j] === "white") {
drawChess(i, j, "white");
}
}
}
// 标记最后一步棋
if (moveHistory.length > 0) {
const lastMove = moveHistory[moveHistory.length - 1];
const cx = (lastMove.x + 1) * cellSize;
const cy = (lastMove.y + 1) * cellSize;
ctx.strokeStyle = "#ff0000";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(cx, cy, cellSize / 4, 0, Math.PI * 2);
ctx.stroke();
}
}
function drawChess(x, y, color) {
const cx = (x + 1) * cellSize;
const cy = (y + 1) * cellSize;
const radius = cellSize / 2 - 2;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
if (color === "black") {
const gradient = ctx.createRadialGradient(cx - radius / 3, cy - radius / 3, 1, cx, cy, radius);
gradient.addColorStop(0, currentTheme.blackGradient[0]);
gradient.addColorStop(1, currentTheme.blackGradient[1]);
ctx.fillStyle = gradient;
ctx.fill();
} else {
const gradient = ctx.createRadialGradient(cx - radius / 3, cy - radius / 3, 1, cx, cy, radius);
gradient.addColorStop(0, currentTheme.whiteGradient[0]);
gradient.addColorStop(1, currentTheme.whiteGradient[1]);
ctx.fillStyle = gradient;
ctx.fill();
ctx.strokeStyle = currentTheme.whiteBorder;
ctx.lineWidth = 1;
ctx.stroke();
}
}
// ========== 撤销功能 ==========
function undoMove() {
if (gameOver || moveHistory.length === 0 || aiThinking) return;
if (gameMode === "ai") {
// AI模式:撤销AI的棋和玩家的前一步棋
const lastMove = moveHistory[moveHistory.length - 1];
if (lastMove.player === "white" && moveHistory.length >= 2) {
// 最后一步是AI下的,撤回AI和玩家的两步
moveHistory.pop(); // AI的棋
moveHistory.pop(); // 玩家的棋
} else {
// 只有玩家下了一步(游戏刚开始时)
moveHistory.pop();
}
resetBoardFromHistory();
currentPlayer = "black";
updateStatus("轮到你(黑棋)落子");
} else {
// 本地模式:只撤销最后一步
moveHistory.pop();
resetBoardFromHistory();
currentPlayer = currentPlayer === "black" ? "white" : "black";
updateStatus(`轮到${currentPlayer === "black" ? "黑棋" : "白棋"}落子`);
}
drawBoard();
updateUndoButton();
}
function resetBoardFromHistory() {
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
chessBoard[i][j] = "";
}
}
for (const move of moveHistory) {
chessBoard[move.x][move.y] = move.player;
}
}
// ========== 落子事件 ==========
canvas.addEventListener("click", function (e) {
if (gameOver || aiThinking) return;
const rect = canvas.getBoundingClientRect();
const x = Math.round((e.clientX - rect.left) / cellSize) - 1;
const y = Math.round((e.clientY - rect.top) / cellSize) - 1;
if (x >= 0 && x < boardSize && y >= 0 && y < boardSize && chessBoard[x][y] === "") {
chessBoard[x][y] = currentPlayer;
moveHistory.push({ x: x, y: y, player: currentPlayer });
drawBoard();
playPlaceSound();
if (checkWin(x, y)) {
const winner = currentPlayer === "black" ? "黑棋" : "白棋";
updateStatus(`${winner} 获胜!🎉`);
playWinSound();
gameOver = true;
if (gameMode === "ai") {
let key = currentPlayer === "black" ? "blackWins" : "whiteWins";
let count = parseInt(localStorage.getItem(key));
localStorage.setItem(key, count + 1);
updateScoreDisplay();
}
updateUndoButton();
return;
}
currentPlayer = currentPlayer === "black" ? "white" : "black";
if (gameMode === "ai" && currentPlayer === "white") {
updateStatus("🤔 电脑正在思考...");
aiThinking = true;
updateUndoButton();
aiTimeout = setTimeout(aiMove, 600);
} else {
updateStatus(`轮到${currentPlayer === "black" ? "黑棋" : "白棋"}落子`);
}
updateUndoButton();
}
});
// ========== AI逻辑 ==========
function aiMove() {
if (gameOver) {
aiThinking = false;
return;
}
let bestScore = -Infinity;
let bestMove = null;
for (let i = 0; i < boardSize; i++) {
for (let j = 0; j < boardSize; j++) {
if (chessBoard[i][j] === "") {
let score = evaluate(i, j, "white");
if (score > bestScore) {
bestScore = score;
bestMove = { x: i, y: j };
}
}
}
}
if (bestMove) {
chessBoard[bestMove.x][bestMove.y] = "white";
moveHistory.push({ x: bestMove.x, y: bestMove.y, player: "white" });
drawBoard();
playPlaceSound();
if (checkWin(bestMove.x, bestMove.y)) {
updateStatus("电脑(白棋)获胜了 😢");
playWinSound();
gameOver = true;
let key = "whiteWins";
let count = parseInt(localStorage.getItem(key));
localStorage.setItem(key, count + 1);
updateScoreDisplay();
aiThinking = false;
updateUndoButton();
return;
}
currentPlayer = "black";
updateStatus("轮到你(黑棋)落子");
}
aiThinking = false;
updateUndoButton();
}
// ========== 评估函数 ==========
const patternScores = {
live5: 100000,
live4: 10000,
doubleLive3: 5000,
live3: 1000,
live2: 100,
block4: 800,
block3: 50,
dead: 0
};
function evaluate(x, y, player) {
let score = 0;
const directions = [
[[1, 0], [-1, 0]],
[[0, 1], [0, -1]],
[[1, 1], [-1, -1]],
[[1, -1], [-1, 1]]
];
for (const dir of directions) {
let line = "";
for (let step = -4; step <= 4; step++) {
let tx = x + dir[0][0] * step;
let ty = y + dir[0][1] * step;
if (tx >= 0 && tx < boardSize && ty >= 0 && ty < boardSize) {
line += chessBoard[tx][ty] || "_";
} else {
line += "#";
}
}
line = line.replace(/_/i, "o");
if (line.includes("ooooo")) return patternScores.live5;
if (/(_|^)oooo_/.test(line) || /_oooo($|_)/.test(line)) score += patternScores.live4;
else if (/oo_o_o|o_oo_o|o_o_oo|_ooo_/.test(line)) score += patternScores.live3;
else if (/oo_oo/.test(line)) score += patternScores.block4;
else if (/oo_/.test(line)) score += patternScores.live2;
}
// 进攻 + 防守综合评分
let defenseScore = evaluateDefense(x, y);
return score + defenseScore;
}
function evaluateDefense(x, y) {
let score = 0;
const directions = [
[[1, 0], [-1, 0]],
[[0, 1], [0, -1]],
[[1, 1], [-1, -1]],
[[1, -1], [-1, 1]]
];
for (const dir of directions) {
let line = "";
for (let step = -4; step <= 4; step++) {
let tx = x + dir[0][0] * step;
let ty = y + dir[0][1] * step;
if (tx >= 0 && tx < boardSize && ty >= 0 && ty < boardSize) {
line += chessBoard[tx][ty] || "_";
} else {
line += "#";
}
}
line = line.replace(/_/i, "x"); // 假设对手下这里
if (line.includes("xxxxx")) score += patternScores.live5 * 0.9;
else if (/(_|^)xxxx_/.test(line) || /_xxxx($|_)/.test(line)) score += patternScores.live4 * 0.9;
else if (/xx_x_x|x_xx_x|x_x_xx|_xxx_/.test(line)) score += patternScores.live3 * 0.8;
}
return score;
}
// ========== 胜负判断 ==========
function checkWin(x, y) {
const directions = [
[[1, 0], [-1, 0]],
[[0, 1], [0, -1]],
[[1, 1], [-1, -1]],
[[1, -1], [-1, 1]]
];
const player = chessBoard[x][y];
for (const dir of directions) {
let count = 1;
for (const d of dir) {
let tx = x + d[0];
let ty = y + d[1];
while (
tx >= 0 && tx < boardSize &&
ty >= 0 && ty < boardSize &&
chessBoard[tx][ty] === player
) {
count++;
tx += d[0];
ty += d[1];
}
}
if (count >= 5) return true;
}
return false;
}
// ========== 页面初始化 ==========
document.getElementById("gameMode").addEventListener("change", init);
initScores();
applyTheme("classic");
init();
</script>
</body>
</html>
index.html
md
README.md
index.html