
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>五子棋大师(修复版)</title><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <style>:root {--primary-gradient: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);--panel-bg: rgba(25, 25, 35, 0.85);--accent-color: #ffd700;--board-bg: #dcb35c;--board-border: #5d4037;--board-line: #8b4513;--black-stone: radial-gradient(circle at 30% 30%, #555, #000);--white-stone: radial-gradient(circle at 30% 30%, #fff, #ddd);--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.4);--shadow-md: 0 5px 15px rgba(0, 0, 0, 0.3);--transition-default: all 0.3s ease;}* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;display: flex;flex-direction: column;align-items: center;min-height: 100vh;background: var(--primary-gradient);background-attachment: fixed;color: #fff;padding: 20px;line-height: 1.6;}.header {text-align: center;margin: 20px 0 30px;width: 100%;max-width: 800px;}.header h1 {font-size: clamp(2.4rem, 5vw, 3.2rem);margin-bottom: 8px;text-shadow: 0 2px 8px rgba(0, 0, 0, 0.6);letter-spacing: 1.5px;background: linear-gradient(to right, var(--accent-color), #ffffff);-webkit-background-clip: text;-webkit-text-fill-color: transparent;}.header p {font-size: 1.1rem;opacity: 0.9;max-width: 600px;margin: 0 auto;}.game-container {display: flex;flex-wrap: wrap;justify-content: center;gap: 30px;width: 100%;max-width: 1200px;margin-bottom: 30px;}.board-section {display: flex;flex-direction: column;align-items: center;background: var(--panel-bg);border-radius: 16px;padding: 25px;box-shadow: var(--shadow-lg);backdrop-filter: blur(10px);transition: var(--transition-default);}.info-section {flex: 1;min-width: 300px;display: flex;flex-direction: column;gap: 20px;}.info-card {background: var(--panel-bg);border-radius: 16px;padding: 25px;box-shadow: var(--shadow-md);backdrop-filter: blur(10px);transition: var(--transition-default);}.info-card:hover {transform: translateY(-5px);box-shadow: var(--shadow-lg);}.info-card h2 {display: flex;align-items: center;gap: 10px;margin-bottom: 18px;color: var(--accent-color);font-size: 1.8rem;}.board {display: grid;grid-template-columns: repeat(15, minmax(24px, 36px));grid-template-rows: repeat(15, minmax(24px, 36px));background-color: var(--board-bg);border: 3px solid var(--board-border);box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.3), 0 0 20px rgba(0, 0, 0, 0.5);position: relative;background-image: linear-gradient(to right, var(--board-line) 1px, transparent 1px),linear-gradient(to bottom, var(--board-line) 1px, transparent 1px);background-size: 100% 100%;}.board::before {content: '';position: absolute;width: 8px;height: 8px;border-radius: 50%;background-color: #000;left: calc(3 * 100% / 14);top: calc(3 * 100% / 14);}.board::after {content: '';position: absolute;width: 8px;height: 8px;border-radius: 50%;background-color: #000;left: calc(11 * 100% / 14);top: calc(3 * 100% / 14);}.board .center-dot-1 {position: absolute;width: 8px;height: 8px;border-radius: 50%;background-color: #000;left: 50%;top: 50%;transform: translate(-50%, -50%);}.board .center-dot-2 {position: absolute;width: 8px;height: 8px;border-radius: 50%;background-color: #000;left: calc(3 * 100% / 14);top: calc(11 * 100% / 14);}.board .center-dot-3 {position: absolute;width: 8px;height: 8px;border-radius: 50%;background-color: #000;left: calc(11 * 100% / 14);top: calc(11 * 100% / 14);}.cell {width: 100%;height: 100%;display: flex;justify-content: center;align-items: center;cursor: pointer;position: relative;z-index: 2;transition: var(--transition-default);pointer-events: auto;}.cell:hover::after {content: '';position: absolute;width: 80%;height: 80%;border-radius: 50%;background-color: rgba(255, 255, 255, 0.2);z-index: 1;}.stone {width: 85%;height: 85%;border-radius: 50%;position: relative;z-index: 3;box-shadow: 0 3px 6px rgba(0, 0, 0, 0.5);transition: transform 0.2s ease;}.stone.animate {animation: stoneDrop 0.4s ease;}@keyframes stoneDrop {0% { transform: scale(0.1); opacity: 0; }70% { transform: scale(1.1); opacity: 1; }100% { transform: scale(1); }}.stone.black {background: var(--black-stone);border: 1px solid #333;}.stone.white {background: var(--white-stone);border: 1px solid #ccc;}.stone.winning {animation: winPulse 1.5s infinite;}.last-move::before {content: '';position: absolute;width: 100%;height: 100%;border-radius: 50%;box-shadow: 0 0 0 3px #ff3333;z-index: 1;animation: pulse 1.5s infinite;}@keyframes pulse {0% { box-shadow: 0 0 0 3px rgba(255, 51, 51, 0.7); }50% { box-shadow: 0 0 0 6px rgba(255, 51, 51, 0.3); }100% { box-shadow: 0 0 0 3px rgba(255, 51, 51, 0.7); }}.hint {animation: hintPulse 0.5s infinite alternate;}@keyframes hintPulse {from { box-shadow: 0 0 5px #00ff00; }to { box-shadow: 0 0 15px #00ff00, 0 0 20px #00ff00; }}.status-area {margin: 20px 0;text-align: center;width: 100%;}.status {font-size: 1.8rem;font-weight: bold;padding: 12px 25px;border-radius: 50px;display: inline-block;background: rgba(0, 0, 0, 0.4);text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);margin-bottom: 15px;transition: var(--transition-default);}.status.black-turn {color: var(--accent-color);background: rgba(0, 0, 0, 0.6);}.status.white-turn {color: var(--accent-color);background: rgba(255, 255, 255, 0.2);}.status.win {color: #4cff00;animation: winPulse 2s infinite;}@keyframes winPulse {0% { box-shadow: 0 0 10px rgba(76, 255, 0, 0.5); }50% { box-shadow: 0 0 25px rgba(76, 255, 0, 0.8); }100% { box-shadow: 0 0 10px rgba(76, 255, 0, 0.5); }}.controls {display: flex;flex-wrap: wrap;justify-content: center;gap: 15px;width: 100%;margin-top: 10px;}button {padding: 14px 26px;font-size: 1.05rem;font-weight: 600;background: linear-gradient(to right, #3498db, #1a5f9e);color: white;border: none;border-radius: 50px;cursor: pointer;display: flex;align-items: center;gap: 8px;transition: var(--transition-default);box-shadow: var(--shadow-md);min-width: 130px;justify-content: center;}button:hover {transform: translateY(-3px);box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4);background: linear-gradient(to right, #3cb0fd, #1a75ca);}button:active {transform: translateY(1px);box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);}button#restart {background: linear-gradient(to right, #e74c3c, #c0392b);}button#restart:hover {background: linear-gradient(to right, #ff6b6b, #e74c3c);}button#undo {background: linear-gradient(to right, #9b59b6, #8e44ad);}button#undo:hover {background: linear-gradient(to right, #a66bbe, #9b59b6);}button#save {background: linear-gradient(to right, #2ecc71, #27ae60);}button#save:hover {background: linear-gradient(to right, #2ecc71, #27ae60);}.stats {display: grid;grid-template-columns: repeat(2, 1fr);gap: 15px;margin-top: 10px;}.stat-item {background: rgba(255, 255, 255, 0.1);padding: 15px;border-radius: 10px;text-align: center;transition: var(--transition-default);}.stat-item:hover {transform: translateY(-3px);background: rgba(255, 255, 255, 0.15);}.stat-value {font-size: 2.2rem;font-weight: bold;color: var(--accent-color);margin: 8px 0;}.stat-label {font-size: 0.95rem;opacity: 0.8;}.footer {text-align: center;padding: 20px;font-size: 1rem;opacity: 0.7;width: 100%;max-width: 800px;margin-top: auto;}.settings-panel {position: fixed;top: 20px;right: 20px;background: var(--panel-bg);border-radius: 10px;padding: 15px;box-shadow: var(--shadow-md);z-index: 100;transition: var(--transition-default);}.settings-toggle {background: none;border: none;color: white;font-size: 1.5rem;cursor: pointer;padding: 5px;min-width: auto;}.settings-content {display: none;margin-top: 10px;animation: fadeIn 0.3s ease;}@keyframes fadeIn {from { opacity: 0; transform: translateY(-10px); }to { opacity: 1; transform: translateY(0); }}.settings-content.show {display: block;}.settings-item {margin-bottom: 10px;display: flex;align-items: center;justify-content: space-between;padding: 5px 0;}.settings-item label {margin-right: 10px;cursor: pointer;}.toggle-switch {position: relative;display: inline-block;width: 46px;height: 24px;}.toggle-switch input {opacity: 0;width: 0;height: 0;}.toggle-slider {position: absolute;cursor: pointer;top: 0;left: 0;right: 0;bottom: 0;background-color: rgba(255, 255, 255, 0.2);transition: .4s;border-radius: 24px;}.toggle-slider:before {position: absolute;content: "";height: 18px;width: 18px;left: 3px;bottom: 3px;background-color: white;transition: .4s;border-radius: 50%;}input:checked + .toggle-slider {background-color: var(--accent-color);}input:checked + .toggle-slider:before {transform: translateX(22px);}.difficulty-selector {display: flex;justify-content: space-between;margin: 15px 0;}.difficulty-btn {padding: 8px 15px;font-size: 0.9rem;background: rgba(255, 255, 255, 0.1);border: 2px solid transparent;}.difficulty-btn.active {background: rgba(255, 215, 0, 0.3);border-color: var(--accent-color);}.game-mode {display: flex;gap: 10px;margin-bottom: 15px;}.mode-btn {flex: 1;padding: 10px;background: rgba(255, 255, 255, 0.1);}.mode-btn.active {background: rgba(52, 152, 219, 0.5);}.saved-games {max-height: 200px;overflow-y: auto;margin-top: 10px;}.saved-game-item {background: rgba(255, 255, 255, 0.1);padding: 10px;border-radius: 8px;margin-bottom: 8px;display: flex;justify-content: space-between;align-items: center;cursor: pointer;transition: var(--transition-default);}.saved-game-item:hover {background: rgba(255, 255, 255, 0.2);}.saved-game-info {flex: 1;}.saved-game-name {font-weight: bold;margin-bottom: 3px;}.saved-game-date {font-size: 0.8rem;opacity: 0.7;}.saved-game-controls button {padding: 5px;margin-left: 5px;min-width: auto;background: none;box-shadow: none;}.modal {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.7);display: flex;justify-content: center;align-items: center;z-index: 2000;opacity: 0;pointer-events: none;transition: opacity 0.3s ease;}.modal.show {opacity: 1;pointer-events: all;}.modal-content {background: var(--panel-bg);padding: 25px;border-radius: 16px;width: 90%;max-width: 400px;box-shadow: var(--shadow-lg);transform: translateY(-20px);transition: transform 0.3s ease;}.modal.show .modal-content {transform: translateY(0);}.modal h3 {margin-bottom: 15px;color: var(--accent-color);font-size: 1.5rem;}.modal input {width: 100%;padding: 12px 15px;border: 2px solid rgba(255, 255, 255, 0.2);background: rgba(255, 255, 255, 0.1);color: white;border-radius: 8px;margin-bottom: 20px;font-size: 1rem;}.modal-buttons {display: flex;gap: 10px;}.modal-buttons button {flex: 1;}.move-history {max-height: 150px;overflow-y: auto;margin-top: 10px;padding-right: 5px;}.move-item {display: flex;justify-content: space-between;padding: 5px 0;border-bottom: 1px solid rgba(255, 255, 255, 0.1);}.move-number {color: var(--accent-color);font-weight: bold;min-width: 30px;}.move-position {flex: 1;text-align: center;}.win-animation {position: fixed;top: 0;left: 0;width: 100%;height: 100%;background: rgba(0, 0, 0, 0.85);display: flex;justify-content: center;align-items: center;z-index: 3000;opacity: 0;pointer-events: none;transition: opacity 0.5s ease;}.win-animation.show {opacity: 1;pointer-events: all;}.win-content {text-align: center;padding: 40px;background: var(--panel-bg);border-radius: 20px;box-shadow: 0 0 30px rgba(255, 215, 0, 0.3);animation: popIn 0.6s ease;}@keyframes popIn {0% { transform: scale(0.5); opacity: 0; }70% { transform: scale(1.1); }100% { transform: scale(1); opacity: 1; }}.win-content h2 {color: var(--accent-color);font-size: 2.5rem;margin-bottom: 20px;}.win-content p {font-size: 1.5rem;margin-bottom: 30px;}@media (max-width: 768px) {.header {margin: 10px 0 20px;}.game-container {gap: 20px;}.board-section {padding: 15px;width: 100%;}.info-card {padding: 20px;}.controls {gap: 10px;}button {padding: 12px 18px;font-size: 0.9rem;min-width: auto;flex: 1;}}@media (max-width: 480px) {.controls {flex-direction: column;}button {width: 100%;}.stats {grid-template-columns: 1fr;}}</style>
</head>
<body><div class="header"><h1><i class="fas fa-chess-board"></i> 五子棋大师</h1><p>策略与智慧的终极对决!在古老棋盘上展现您的实力,成为五子连珠的冠军</p></div><div class="settings-panel"><button class="settings-toggle" id="settings-toggle" aria-label="打开设置"><i class="fas fa-cog"></i></button><div class="settings-content" id="settings-content"><div class="settings-item"><label for="sound-toggle">声音</label><label class="toggle-switch"><input type="checkbox" id="sound-toggle" checked><span class="toggle-slider"></span></label></div><div class="settings-item"><label for="animations-toggle">动画</label><label class="toggle-switch"><input type="checkbox" id="animations-toggle" checked><span class="toggle-slider"></span></label></div><div class="settings-item"><label for="hints-toggle">提示</label><label class="toggle-switch"><input type="checkbox" id="hints-toggle" checked><span class="toggle-slider"></span></label></div><div class="settings-item"><label for="history-toggle">显示走棋历史</label><label class="toggle-switch"><input type="checkbox" id="history-toggle" checked><span class="toggle-slider"></span></label></div></div></div><div class="game-container"><div class="board-section"><div class="board" id="board" role="grid" aria-label="五子棋棋盘"><div class="center-dot-1"></div><div class="center-dot-2"></div><div class="center-dot-3"></div></div><div class="status-area"><div class="status black-turn" id="status" role="status">黑方回合</div><div class="controls"><button id="restart" aria-label="重新开始游戏"><i class="fas fa-redo"></i> 重新开始</button><button id="undo" aria-label="悔棋"><i class="fas fa-undo"></i> 悔棋</button><button id="hint" aria-label="显示提示"><i class="fas fa-lightbulb"></i> 提示</button><button id="save" aria-label="保存游戏"><i class="fas fa-save"></i> 保存</button></div></div></div><div class="info-section"><div class="info-card"><h2><i class="fas fa-trophy"></i> 游戏统计</h2><div class="stats"><div class="stat-item"><div class="stat-value" id="black-wins">0</div><div class="stat-label">黑方胜利</div></div><div class="stat-item"><div class="stat-value" id="white-wins">0</div><div class="stat-label">白方胜利</div></div><div class="stat-item"><div class="stat-value" id="black-score">0</div><div class="stat-label">黑方得分</div></div><div class="stat-item"><div class="stat-value" id="white-score">0</div><div class="stat-label">白方得分</div></div></div></div><div class="info-card"><h2><i class="fas fa-history"></i> 走棋历史</h2><div class="move-history" id="move-history"><div class="move-item"><div class="move-number">-</div><div class="move-position">游戏尚未开始</div></div></div></div><div class="info-card"><h2><i class="fas fa-cog"></i> 游戏设置</h2><div class="game-mode"><button class="mode-btn active" data-mode="player-vs-ai">人机对战</button><button class="mode-btn" data-mode="player-vs-player">人人对战</button></div><h3 style="margin: 15px 0 5px; color: var(--accent-color);">难度级别</h3><div class="difficulty-selector"><button class="difficulty-btn active" data-depth="1">简单</button><button class="difficulty-btn" data-depth="2">中等</button><button class="difficulty-btn" data-depth="3">困难</button></div></div><div class="info-card"><h2><i class="fas fa-save"></i> 保存的游戏</h2><div class="saved-games" id="saved-games"><div class="no-saved-games">暂无保存的游戏</div></div></div></div></div><div class="modal" id="save-modal"><div class="modal-content"><h3><i class="fas fa-save"></i> 保存当前游戏</h3><input type="text" id="game-name" placeholder="输入游戏名称..." value="未命名游戏"><div class="modal-buttons"><button id="cancel-save"><i class="fas fa-times"></i> 取消</button><button id="confirm-save"><i class="fas fa-check"></i> 保存</button></div></div></div><div class="win-animation" id="win-animation"><div class="win-content"><h2><i class="fas fa-crown"></i> 胜利!</h2><p id="winner-text">黑方获胜</p><button id="new-game-btn"><i class="fas fa-play-circle"></i> 开始新游戏</button></div></div><div class="footer"><p>© 2025 五子棋大师 | 今日:2025年8月24日 农历七月初二 | 最后更新:14:30</p></div><script>class GomokuGame {constructor() {this.settings = {sound: true,animations: true,showHints: true,showHistory: true,gameMode: 'player-vs-ai',difficulty: 1};this.BOARD_SIZE = 15;this.PLAYERS = {BLACK: 'black',WHITE: 'white',AI: 'white' };this.GAME_MODES = {PLAYER_VS_AI: 'player-vs-ai',PLAYER_VS_PLAYER: 'player-vs-player'};this.DIFFICULTY = {EASY: 1,MEDIUM: 2,HARD: 3};this.PATTERNS = {FIVE: 100000,OPEN_FOUR: 5000,HALF_FOUR: 1000,OPEN_THREE: 500,HALF_THREE: 100,OPEN_TWO: 50,HALF_TWO: 10,OPEN_ONE: 5};try {this.blackWins = localStorage.getItem('gomoku_blackWins') ? parseInt(localStorage.getItem('gomoku_blackWins')) : 0;this.whiteWins = localStorage.getItem('gomoku_whiteWins') ? parseInt(localStorage.getItem('gomoku_whiteWins')) : 0;} catch (e) {console.error('加载统计数据失败:', e);this.blackWins = 0;this.whiteWins = 0;}this.currentMode = this.GAME_MODES.PLAYER_VS_AI;this.aiDepth = this.DIFFICULTY.EASY;this.showHistory = true;this.initElements(); this.bindEvents(); this.resetGame(); this.initSettings(); this.initSounds(); this.loadSavedGames();this.updateStatsDisplay();window.gomokuGame = this;}initElements() {this.boardEl = document.getElementById('board'); this.statusDisplay = document.getElementById('status'); this.blackWinsEl = document.getElementById('black-wins'); this.whiteWinsEl = document.getElementById('white-wins'); this.blackScoreEl = document.getElementById('black-score'); this.whiteScoreEl = document.getElementById('white-score'); this.winAnimation = document.getElementById('win-animation'); this.winnerText = document.getElementById('winner-text'); this.settingsToggle = document.getElementById('settings-toggle'); this.settingsContent = document.getElementById('settings-content'); this.soundToggle = document.getElementById('sound-toggle'); this.animationsToggle = document.getElementById('animations-toggle'); this.hintsToggle = document.getElementById('hints-toggle'); this.historyToggle = document.getElementById('history-toggle');this.modeButtons = document.querySelectorAll('.mode-btn');this.difficultyButtons = document.querySelectorAll('.difficulty-btn');this.moveHistoryEl = document.getElementById('move-history');this.savedGamesEl = document.getElementById('saved-games');this.noSavedGamesEl = document.querySelector('.no-saved-games');this.saveButton = document.getElementById('save');this.saveModal = document.getElementById('save-modal');this.gameNameInput = document.getElementById('game-name');this.confirmSaveButton = document.getElementById('confirm-save');this.cancelSaveButton = document.getElementById('cancel-save');this.lastMoveCell = null;this.winningCells = [];this.aiThinking = false;}bindEvents() {document.getElementById('restart').addEventListener('click', () => this.resetGame()); document.getElementById('undo').addEventListener('click', () => this.undoMove()); document.getElementById('hint').addEventListener('click', () => this.showHint()); document.getElementById('new-game-btn').addEventListener('click', () => {this.winAnimation.classList.remove('show'); });this.settingsToggle.addEventListener('click', () => this.toggleSettings()); this.saveButton.addEventListener('click', () => this.openSaveModal());this.confirmSaveButton.addEventListener('click', () => this.saveCurrentGame());this.cancelSaveButton.addEventListener('click', () => this.closeSaveModal());this.modeButtons.forEach(btn => {btn.addEventListener('click', () => {this.modeButtons.forEach(b => b.classList.remove('active'));btn.classList.add('active');this.currentMode = btn.dataset.mode;this.saveSettings();this.resetGame();});});this.difficultyButtons.forEach(btn => {btn.addEventListener('click', () => {this.difficultyButtons.forEach(b => b.classList.remove('active'));btn.classList.add('active');this.aiDepth = parseInt(btn.dataset.depth);this.saveSettings();});});this.historyToggle.addEventListener('change', (e) => {this.showHistory = e.target.checked;this.saveSettings();if (this.showHistory) {this.moveHistoryEl.style.display = 'block';this.updateMoveHistory();} else {this.moveHistoryEl.style.display = 'none';}});document.addEventListener('keydown', (e) => {if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;if (e.key === 'r' && !e.ctrlKey) this.resetGame();if (e.key === 'u' && !e.ctrlKey) this.undoMove();if (e.key === 'h' && !e.ctrlKey) this.showHint();if (e.key === 's' && !e.ctrlKey) this.openSaveModal();});}initSounds() {this.audioContext = null;this.sounds = {place: { frequency: 330, duration: 0.15, volume: 0.3 },win: { frequency: 660, duration: 0.8, volume: 0.5 },click: { frequency: 440, duration: 0.1, volume: 0.2 },error: { frequency: 220, duration: 0.2, volume: 0.3 }};}createSound(frequency, duration, volume) {if (!this.audioContext) {try {this.audioContext = new (window.AudioContext || window.webkitAudioContext)();} catch (e) {console.log('Web Audio API 不受支持');return;}}const oscillator = this.audioContext.createOscillator();const gainNode = this.audioContext.createGain();oscillator.type = 'sine';oscillator.frequency.setValueAtTime(frequency, this.audioContext.currentTime);gainNode.gain.setValueAtTime(volume, this.audioContext.currentTime);oscillator.connect(gainNode);gainNode.connect(this.audioContext.destination);oscillator.start();oscillator.stop(this.audioContext.currentTime + duration);gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + duration);}playSound(type) {if (!this.settings || !this.settings.sound) return;const sound = this.sounds[type];if (sound) {this.createSound(sound.frequency, sound.duration, sound.volume);}}initSettings() {try {const savedSettings = localStorage.getItem('gomoku_settings');if (savedSettings) {this.settings = { ...this.settings, ...JSON.parse(savedSettings) };}} catch (e) {console.error('加载设置失败,使用默认设置:', e);}this.soundToggle.checked = this.settings.sound;this.animationsToggle.checked = this.settings.animations;this.hintsToggle.checked = this.settings.showHints;this.historyToggle.checked = this.settings.showHistory;this.showHistory = this.settings.showHistory;this.currentMode = this.settings.gameMode || this.GAME_MODES.PLAYER_VS_AI;this.aiDepth = this.settings.difficulty || this.DIFFICULTY.EASY;this.modeButtons.forEach(btn => {btn.classList.toggle('active', btn.dataset.mode === this.currentMode);});this.difficultyButtons.forEach(btn => {btn.classList.toggle('active', parseInt(btn.dataset.depth) === this.aiDepth);});this.soundToggle.addEventListener('change', (e) => {this.settings.sound = e.target.checked;this.saveSettings();});this.animationsToggle.addEventListener('change', (e) => {this.settings.animations = e.target.checked;this.saveSettings();});this.hintsToggle.addEventListener('change', (e) => {this.settings.showHints = e.target.checked;this.saveSettings();});}saveSettings() {this.settings.gameMode = this.currentMode;this.settings.difficulty = this.aiDepth;this.settings.showHistory = this.showHistory;try {localStorage.setItem('gomoku_settings', JSON.stringify(this.settings));} catch (e) {console.error('保存设置失败:', e);}}toggleSettings() {this.settingsContent.classList.toggle('show'); this.playSound('click'); }resetGame() {this.gameBoard = Array(this.BOARD_SIZE).fill().map(() => Array(this.BOARD_SIZE).fill(null));this.currentPlayer = this.PLAYERS.BLACK;this.gameActive = true;this.lastMove = null;this.moveHistory = [];this.cellElements = {};this.winningCells = [];this.aiThinking = false;if (this.lastMoveCell) {this.lastMoveCell.classList.remove('last-move');this.lastMoveCell = null;}this.renderBoard(); this.updateStatus(); this.updateMoveHistory();}renderBoard() {const fragment = document.createDocumentFragment();this.boardEl.innerHTML = '';const dot1 = document.createElement('div');dot1.className = 'center-dot-1';fragment.appendChild(dot1);const dot2 = document.createElement('div');dot2.className = 'center-dot-2';fragment.appendChild(dot2);const dot3 = document.createElement('div');dot3.className = 'center-dot-3';fragment.appendChild(dot3);for (let i = 0; i < this.BOARD_SIZE; i++) {for (let j = 0; j < this.BOARD_SIZE; j++) {const cell = document.createElement('div'); cell.className = 'cell';cell.dataset.row = i;cell.dataset.col = j;cell.setAttribute('role', 'gridcell');cell.setAttribute('aria-label', `行 ${i+1} 列 ${j+1}`);const handleClick = () => this.handleCellClick(i, j);cell.addEventListener('click', handleClick);cell.clickHandler = handleClick;fragment.appendChild(cell); this.cellElements[`${i}-${j}`] = cell;}}this.boardEl.appendChild(fragment);}handleCellClick(row, col) {if (!this.gameActive || this.aiThinking) {this.playSound('error');return;}if (this.currentPlayer === this.PLAYERS.AI && this.currentMode === this.GAME_MODES.PLAYER_VS_AI) {this.playSound('error');return;}if (this.gameBoard[row][col] !== null) {this.playSound('error');return;}this.playSound('place'); this.placeStone(row, col, this.currentPlayer); if (this.currentMode === this.GAME_MODES.PLAYER_VS_AI && this.gameActive) {this.aiMove();}}placeStone(row, col, player) {this.gameBoard[row][col] = player;this.moveHistory.push({row, col, player});this.lastMove = {row, col};this.updateBoard(); this.updateMoveHistory();if (this.checkWin(row, col)) {this.showWinningStones();this.showWinAnimation(player); if (player === this.PLAYERS.BLACK) {this.blackWins++; } else {this.whiteWins++; }this.saveStats();this.updateStatsDisplay();this.updateScore(); this.gameActive = false;this.playSound('win'); return;}if (this.checkDraw()) {this.statusDisplay.textContent = '平局!';this.statusDisplay.className = 'status';this.gameActive = false;return;}this.switchPlayer(); }updateBoard() {this.winningCells.forEach(({row, col}) => {const stone = this.cellElements[`${row}-${col}`].querySelector('.stone');if (stone) stone.classList.remove('winning');});this.winningCells = [];if (this.lastMove) {const {row, col} = this.lastMove; const cell = this.cellElements[`${row}-${col}`]; cell.innerHTML = '';if (this.gameBoard[row][col]) {const stone = document.createElement('div'); stone.className = `stone ${this.gameBoard[row][col]}`; if (this.settings.animations) {stone.classList.add('animate'); }cell.appendChild(stone); if (this.settings.animations) {setTimeout(() => stone.classList.remove('animate'), 400);}}if (this.lastMoveCell) this.lastMoveCell.classList.remove('last-move'); cell.classList.add('last-move'); this.lastMoveCell = cell;}this.updateScore(); }showWinningStones() {if (!this.lastMove) return;const {row, col} = this.lastMove;const player = this.gameBoard[row][col];const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];for (const [dx, dy] of directions) {const winningSequence = [{row, col}];for (let i = 1; i <= 4; i++) {const r = row + i * dx;const c = col + i * dy;if (r < 0 || r >= this.BOARD_SIZE || c < 0 || c >= this.BOARD_SIZE || this.gameBoard[r][c] !== player) break;winningSequence.push({row: r, col: c});}for (let i = 1; i <= 4; i++) {const r = row - i * dx;const c = col - i * dy;if (r < 0 || r >= this.BOARD_SIZE || c < 0 || c >= this.BOARD_SIZE || this.gameBoard[r][c] !== player) break;winningSequence.push({row: r, col: c});}if (winningSequence.length >= 5) {this.winningCells = winningSequence;winningSequence.forEach(({row, col}) => {const stone = this.cellElements[`${row}-${col}`].querySelector('.stone');if (stone) stone.classList.add('winning');});break;}}}checkWin(row, col) {const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];const player = this.gameBoard[row][col]; return directions.some(([dx, dy]) => {const count = 1 + this.countDirection(row, col, dx, dy, player) + this.countDirection(row, col, -dx, -dy, player);return count >= 5;});}countDirection(row, col, dx, dy, player) {let count = 0;for (let i = 1; i <= 4; i++) {const r = row + i * dx;const c = col + i * dy;if (r < 0 || r >= this.BOARD_SIZE || c < 0 || c >= this.BOARD_SIZE || this.gameBoard[r][c] !== player) break;count++;}return count;}checkDraw() {return this.moveHistory.length >= this.BOARD_SIZE * this.BOARD_SIZE;}showWinAnimation(player) {const playerName = player === this.PLAYERS.BLACK ? '黑方' : (player === this.PLAYERS.WHITE && this.currentMode === this.GAME_MODES.PLAYER_VS_AI ? 'AI' : '白方');this.winnerText.textContent = `${playerName}获胜!`;this.winAnimation.classList.add('show'); }switchPlayer() {this.currentPlayer = this.currentPlayer === this.PLAYERS.BLACK ? this.PLAYERS.WHITE : this.PLAYERS.BLACK;this.updateStatus();}updateStatus() {let playerName = this.currentPlayer === this.PLAYERS.BLACK ? '黑方' : (this.currentPlayer === this.PLAYERS.WHITE && this.currentMode === this.GAME_MODES.PLAYER_VS_AI ? 'AI' : '白方');this.statusDisplay.textContent = `${playerName}回合`;this.statusDisplay.className = `status ${this.currentPlayer}-turn`; this.statusDisplay.setAttribute('aria-live', 'polite');}undoMove() {if (this.moveHistory.length <= 0 || !this.gameActive || this.aiThinking) return;this.winningCells.forEach(({row, col}) => {const stone = this.cellElements[`${row}-${col}`].querySelector('.stone');if (stone) stone.classList.remove('winning');});this.winningCells = [];const stepsToUndo = this.currentMode === this.GAME_MODES.PLAYER_VS_AI ? 2 : 1;let undone = 0;while (this.moveHistory.length > 0 && undone < stepsToUndo) {const lastMove = this.moveHistory.pop(); this.gameBoard[lastMove.row][lastMove.col] = null;undone++;}if (this.moveHistory.length > 0) {this.lastMove = this.moveHistory[this.moveHistory.length - 1];this.currentPlayer = this.lastMove.player;} else {this.lastMove = null;this.currentPlayer = this.PLAYERS.BLACK;}this.updateBoard(); this.updateStatus(); this.updateMoveHistory();this.playSound('click'); }showHint() {if (!this.settings.showHints || !this.gameActive || this.aiThinking) return;const bestMove = this.findBestMove(); if (bestMove) {this.playSound('click'); const cell = this.cellElements[`${bestMove.row}-${bestMove.col}`]; cell.classList.add('hint'); setTimeout(() => cell.classList.remove('hint'), 2000);}}aiMove() {if (!this.gameActive || this.currentPlayer !== this.PLAYERS.WHITE) return;this.aiThinking = true;this.statusDisplay.textContent = 'AI思考中...';setTimeout(() => {if (!this.gameActive) {this.aiThinking = false;return;}const bestMove = this.minimax(this.aiDepth, -Infinity, Infinity, true);if (bestMove && this.gameActive) {this.playSound('place');this.placeStone(bestMove.row, bestMove.col, this.PLAYERS.AI);}this.aiThinking = false;}, this.aiDepth * 300);}minimax(depth, alpha, beta, isMaximizingPlayer) {if (depth === 0) {return { score: this.evaluateBoard() };}const possibleMoves = this.getPossibleMoves();if (possibleMoves.length === 0) {return { score: 0 };}let bestMove = null;if (isMaximizingPlayer) {let maxScore = -Infinity;for (const move of possibleMoves) {this.gameBoard[move.row][move.col] = this.PLAYERS.AI;if (this.checkWin(move.row, move.col)) {const score = this.PATTERNS.FIVE * (depth + 1);this.gameBoard[move.row][move.col] = null;return { ...move, score };}const result = this.minimax(depth - 1, alpha, beta, false);const currentScore = result.score;this.gameBoard[move.row][move.col] = null;if (currentScore > maxScore) {maxScore = currentScore;bestMove = move;}alpha = Math.max(alpha, currentScore);if (beta <= alpha) break;}return { ...bestMove, score: maxScore };} else {let minScore = Infinity;for (const move of possibleMoves) {this.gameBoard[move.row][move.col] = this.PLAYERS.BLACK;if (this.checkWin(move.row, move.col)) {const score = -this.PATTERNS.FIVE * (depth + 1);this.gameBoard[move.row][move.col] = null;return { ...move, score };}const result = this.minimax(depth - 1, alpha, beta, true);const currentScore = result.score;this.gameBoard[move.row][move.col] = null;if (currentScore < minScore) {minScore = currentScore;bestMove = move;}beta = Math.min(beta, currentScore);if (beta <= alpha) break;}return { ...bestMove, score: minScore };}}getPossibleMoves() {const moves = [];for (let i = 0; i < this.BOARD_SIZE; i++) {for (let j = 0; j < this.BOARD_SIZE; j++) {if (this.gameBoard[i][j] === null && this.hasNearbyStones(i, j)) {const score = this.evaluatePosition(i, j, this.PLAYERS.AI);moves.push({ row: i, col: j, score });}}}if (moves.length === 0) {const center = Math.floor(this.BOARD_SIZE / 2);for (let i = center - 2; i <= center + 2; i++) {for (let j = center - 2; j <= center + 2; j++) {if (i >= 0 && i < this.BOARD_SIZE && j >= 0 && j < this.BOARD_SIZE && this.gameBoard[i][j] === null) {moves.push({ row: i, col: j, score: 1 });}}}}return moves.sort((a, b) => b.score - a.score);}hasNearbyStones(row, col, distance = 2) {for (let i = Math.max(0, row - distance); i <= Math.min(this.BOARD_SIZE - 1, row + distance); i++) {for (let j = Math.max(0, col - distance); j <= Math.min(this.BOARD_SIZE - 1, col + distance); j++) {if (this.gameBoard[i][j] !== null) {return true;}}}return false;}evaluateBoard() {let score = 0;for (let i = 0; i < this.BOARD_SIZE; i++) {for (let j = 0; j < this.BOARD_SIZE; j++) {if (this.gameBoard[i][j] === this.PLAYERS.AI) {score += this.evaluatePosition(i, j, this.PLAYERS.AI);} else if (this.gameBoard[i][j] === this.PLAYERS.BLACK) {score -= this.evaluatePosition(i, j, this.PLAYERS.BLACK) * 0.9;}}}return score;}findBestMove() {const possibleMoves = this.getPossibleMoves();if (possibleMoves.length === 0) return null;let bestScore = -Infinity;let bestMove = null;for (const move of possibleMoves) {this.gameBoard[move.row][move.col] = this.currentPlayer;const isWinningMove = this.checkWin(move.row, move.col);this.gameBoard[move.row][move.col] = null;if (isWinningMove) {return move;}}for (const move of possibleMoves) {const score = this.evaluatePosition(move.row, move.col, this.currentPlayer); if (score > bestScore) {bestScore = score;bestMove = move;}}return bestMove;}evaluatePosition(row, col, player) {const opponent = player === this.PLAYERS.BLACK ? this.PLAYERS.WHITE : this.PLAYERS.BLACK;let score = 0;score += this.evaluatePattern(row, col, player);score += this.evaluatePattern(row, col, opponent) * 0.8;const center = (this.BOARD_SIZE - 1) / 2;const distanceToCenter = Math.sqrt(Math.pow(row - center, 2) + Math.pow(col - center, 2));score += (this.BOARD_SIZE - distanceToCenter) * 2;return score;}evaluatePattern(row, col, player) {let totalScore = 0;const directions = [[0, 1], [1, 0], [1, 1], [1, -1]];directions.forEach(([dx, dy]) => {const {type} = this.checkPattern(row, col, dx, dy, player);totalScore += this.PATTERNS[type] || 0;});return totalScore;}checkPattern(row, col, dx, dy, player) {let count = 1;let openEnds = 0;for (const [multiplier, isReverse] of [[1, false], [-1, true]]) {let blocked = false;for (let i = 1; i <= 4; i++) {const r = row + i * dx * multiplier;const c = col + i * dy * multiplier;if (r < 0 || r >= this.BOARD_SIZE || c < 0 || c >= this.BOARD_SIZE) {blocked = true;break;}if (this.gameBoard[r][c] === player) {count++;} else if (this.gameBoard[r][c] === null) {if (!isReverse) openEnds++;break;} else {blocked = true;break;}}if (!blocked && isReverse) openEnds++;}let type = '';if (count >= 5) type = 'FIVE';else if (count === 4) {type = openEnds >= 1 ? (openEnds === 2 ? 'OPEN_FOUR' : 'HALF_FOUR') : '';} else if (count === 3) {type = openEnds >= 1 ? (openEnds === 2 ? 'OPEN_THREE' : 'HALF_THREE') : '';} else if (count === 2) {type = openEnds >= 1 ? (openEnds === 2 ? 'OPEN_TWO' : 'HALF_TWO') : '';} else if (count === 1) {type = openEnds === 2 ? 'OPEN_ONE' : '';}return {type, count, openEnds};}updateScore() {let blackCount = 0;let whiteCount = 0;for (let i = 0; i < this.BOARD_SIZE; i++) {for (let j = 0; j < this.BOARD_SIZE; j++) {if (this.gameBoard[i][j] === this.PLAYERS.BLACK) blackCount++;else if (this.gameBoard[i][j] === this.PLAYERS.WHITE || this.gameBoard[i][j] === this.PLAYERS.AI) whiteCount++;}}this.blackScore = blackCount;this.whiteScore = whiteCount;this.blackScoreEl.textContent = this.blackScore; this.whiteScoreEl.textContent = this.whiteScore; }updateStatsDisplay() {this.blackWinsEl.textContent = this.blackWins;this.whiteWinsEl.textContent = this.whiteWins;}saveStats() {try {localStorage.setItem('gomoku_blackWins', this.blackWins.toString());localStorage.setItem('gomoku_whiteWins', this.whiteWins.toString());} catch (e) {console.error('保存统计数据失败:', e);}}updateMoveHistory() {if (!this.showHistory) return;this.moveHistoryEl.innerHTML = '';if (this.moveHistory.length === 0) {const emptyItem = document.createElement('div');emptyItem.className = 'move-item';emptyItem.innerHTML = `<div class="move-number">-</div><div class="move-position">游戏尚未开始</div>`;this.moveHistoryEl.appendChild(emptyItem);return;}this.moveHistory.forEach((move, index) => {const moveNumber = Math.floor(index / 2) + 1;const isBlackMove = move.player === this.PLAYERS.BLACK;let moveItem;if (index % 2 === 0) {moveItem = document.createElement('div');moveItem.className = 'move-item';moveItem.innerHTML = `<div class="move-number">${moveNumber}</div><div class="move-position ${isBlackMove ? 'black' : 'white'}">${String.fromCharCode(65 + move.col)}${move.row + 1}</div><div class="move-position white"></div>`;this.moveHistoryEl.appendChild(moveItem);} else {const lastItem = this.moveHistoryEl.lastChild;if (lastItem) {const whitePos = lastItem.querySelector('.move-position.white');whitePos.textContent = `${String.fromCharCode(65 + move.col)}${move.row + 1}`;}}});this.moveHistoryEl.scrollTop = this.moveHistoryEl.scrollHeight;}openSaveModal() {if (!this.gameActive) {this.playSound('error');return;}this.saveModal.classList.add('show');this.gameNameInput.focus();this.playSound('click');}closeSaveModal() {this.saveModal.classList.remove('show');this.playSound('click');}saveCurrentGame() {const gameName = this.gameNameInput.value.trim() || '未命名游戏';const timestamp = new Date().toISOString();const formattedDate = new Date().toLocaleString();const gameData = {id: Date.now().toString(),name: gameName,date: formattedDate,timestamp: timestamp,board: JSON.parse(JSON.stringify(this.gameBoard)),currentPlayer: this.currentPlayer,moveHistory: JSON.parse(JSON.stringify(this.moveHistory)),gameMode: this.currentMode,difficulty: this.aiDepth};try {let savedGames = JSON.parse(localStorage.getItem('gomoku_saved_games') || '[]');savedGames.push(gameData);localStorage.setItem('gomoku_saved_games', JSON.stringify(savedGames));} catch (e) {console.error('保存游戏失败:', e);alert('保存游戏失败,请重试');return;}this.closeSaveModal();this.loadSavedGames();this.playSound('click');}loadSavedGames() {try {const savedGames = JSON.parse(localStorage.getItem('gomoku_saved_games') || '[]');this.savedGamesEl.innerHTML = '';if (savedGames.length === 0) {const noGamesEl = document.createElement('div');noGamesEl.className = 'no-saved-games';noGamesEl.textContent = '暂无保存的游戏';this.savedGamesEl.appendChild(noGamesEl);return;}savedGames.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));savedGames.forEach(game => {const gameItem = document.createElement('div');gameItem.className = 'saved-game-item';gameItem.innerHTML = `<div class="saved-game-info"><div class="saved-game-name">${game.name}</div><div class="saved-game-date">${game.date}</div></div><div class="saved-game-controls"><button class="load-game" data-id="${game.id}" title="加载游戏"><i class="fas fa-play"></i></button><button class="delete-game" data-id="${game.id}" title="删除游戏"><i class="fas fa-trash"></i></button></div>`;this.savedGamesEl.appendChild(gameItem);});document.querySelectorAll('.load-game').forEach(btn => {btn.addEventListener('click', (e) => {e.stopPropagation();this.loadGame(e.target.closest('.load-game').dataset.id);});});document.querySelectorAll('.delete-game').forEach(btn => {btn.addEventListener('click', (e) => {e.stopPropagation();this.deleteGame(e.target.closest('.delete-game').dataset.id);});});} catch (e) {console.error('加载保存的游戏失败:', e);const errorEl = document.createElement('div');errorEl.className = 'error-message';errorEl.textContent = '加载保存的游戏失败';this.savedGamesEl.appendChild(errorEl);}}loadGame(gameId) {try {const savedGames = JSON.parse(localStorage.getItem('gomoku_saved_games') || '[]');const gameData = savedGames.find(game => game.id === gameId);if (!gameData) return;this.gameBoard = JSON.parse(JSON.stringify(gameData.board));this.currentPlayer = gameData.currentPlayer;this.moveHistory = JSON.parse(JSON.stringify(gameData.moveHistory));this.currentMode = gameData.gameMode;this.aiDepth = gameData.difficulty;this.gameActive = true;this.aiThinking = false;this.renderBoard();this.updateBoard();this.updateStatus();this.updateMoveHistory();this.modeButtons.forEach(btn => {if (btn.dataset.mode === this.currentMode) {btn.classList.add('active');} else {btn.classList.remove('active');}});this.difficultyButtons.forEach(btn => {if (parseInt(btn.dataset.depth) === this.aiDepth) {btn.classList.add('active');} else {btn.classList.remove('active');}});this.playSound('click');} catch (e) {console.error('加载游戏失败:', e);alert('加载游戏失败,请重试');}}deleteGame(gameId) {if (!confirm('确定要删除这个保存的游戏吗?')) return;try {let savedGames = JSON.parse(localStorage.getItem('gomoku_saved_games') || '[]');savedGames = savedGames.filter(game => game.id !== gameId);localStorage.setItem('gomoku_saved_games', JSON.stringify(savedGames));this.loadSavedGames();this.playSound('click');} catch (e) {console.error('删除游戏失败:', e);alert('删除游戏失败,请重试');}}}document.addEventListener('DOMContentLoaded', () => {try {new GomokuGame();} catch (e) {console.error('初始化游戏失败:', e);alert('游戏初始化失败,请刷新页面重试');}});</script>
</body>
</html>