| <!DOCTYPE html> |
| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Go Game with Strategic AI</title> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Segoe UI', system-ui, sans-serif; |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); |
| min-height: 100vh; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| padding: 20px; |
| } |
| |
| .game-container { |
| max-width: 800px; |
| background: rgba(255, 255, 255, 0.95); |
| border-radius: 20px; |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); |
| padding: 30px; |
| } |
| |
| .board { |
| display: grid; |
| grid-template-columns: repeat(19, 30px); |
| grid-template-rows: repeat(19, 30px); |
| gap: 1px; |
| background: #dcb35c; |
| border: 2px solid #2c3e50; |
| border-radius: 4px; |
| margin: 20px auto; |
| padding: 10px; |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15); |
| } |
| |
| .intersection { |
| width: 30px; |
| height: 30px; |
| position: relative; |
| cursor: pointer; |
| transition: background 0.2s; |
| } |
| |
| .intersection:hover::before { |
| content: ''; |
| position: absolute; |
| width: 28px; |
| height: 28px; |
| border-radius: 50%; |
| background: rgba(0, 0, 0, 0.1); |
| top: 1px; |
| left: 1px; |
| z-index: 2; |
| } |
| |
| .intersection::before { |
| content: ''; |
| position: absolute; |
| left: 0; |
| top: 50%; |
| width: 100%; |
| height: 1px; |
| background: rgba(0, 0, 0, 0.7); |
| z-index: 1; |
| } |
| |
| .intersection::after { |
| content: ''; |
| position: absolute; |
| top: 0; |
| left: 50%; |
| height: 100%; |
| width: 1px; |
| background: rgba(0, 0, 0, 0.7); |
| z-index: 1; |
| } |
| |
| .star-point::after { |
| content: ''; |
| position: absolute; |
| width: 8px; |
| height: 8px; |
| background: #2c3e50; |
| border-radius: 50%; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| z-index: 2; |
| } |
| |
| .stone { |
| width: 28px; |
| height: 28px; |
| border-radius: 50%; |
| position: absolute; |
| top: 1px; |
| left: 1px; |
| z-index: 3; |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| animation: placeStone 0.3s ease-out; |
| } |
| |
| @keyframes placeStone { |
| from { |
| transform: scale(1.2); |
| opacity: 0; |
| } |
| to { |
| transform: scale(1); |
| opacity: 1; |
| } |
| } |
| |
| .black { |
| background: radial-gradient(circle at 35% 35%, #4a4a4a 0%, #000000 100%); |
| box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); |
| } |
| |
| .white { |
| background: radial-gradient(circle at 35% 35%, #ffffff 0%, #e0e0e0 100%); |
| box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1); |
| } |
| |
| .controls { |
| display: flex; |
| justify-content: center; |
| gap: 15px; |
| margin: 20px 0; |
| } |
| |
| button { |
| padding: 10px 20px; |
| font-size: 16px; |
| border: none; |
| border-radius: 8px; |
| background: #3498db; |
| color: white; |
| cursor: pointer; |
| transition: all 0.3s; |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); |
| } |
| |
| button:hover { |
| background: #2980b9; |
| transform: translateY(-2px); |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); |
| } |
| |
| select { |
| padding: 8px 16px; |
| font-size: 16px; |
| border: 1px solid #ddd; |
| border-radius: 8px; |
| background: white; |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| select:hover { |
| border-color: #3498db; |
| } |
| |
| .game-info { |
| text-align: center; |
| margin: 20px 0; |
| padding: 15px; |
| background: #f8f9fa; |
| border-radius: 10px; |
| box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05); |
| } |
| |
| .game-info div { |
| margin: 10px 0; |
| font-size: 18px; |
| color: #2c3e50; |
| } |
| |
| #currentPlayer { |
| font-weight: bold; |
| color: #3498db; |
| } |
| |
| .mode-select { |
| display: flex; |
| justify-content: center; |
| gap: 15px; |
| margin-bottom: 20px; |
| } |
| |
| @media (max-width: 768px) { |
| .game-container { |
| padding: 15px; |
| } |
| |
| .board { |
| transform: scale(0.8); |
| transform-origin: center; |
| } |
| |
| .controls { |
| flex-direction: column; |
| align-items: center; |
| } |
| |
| button { |
| width: 100%; |
| max-width: 200px; |
| } |
| } |
| </style> |
| </head> |
| <body> |
| <div class="game-container"> |
| <div class="mode-select"> |
| <select id="gameMode"> |
| <option value="pvp">Player vs Player</option> |
| <option value="ai">Player vs AI</option> |
| </select> |
| <select id="difficulty"> |
| <option value="easy">Easy</option> |
| <option value="hard">Hard</option> |
| </select> |
| </div> |
|
|
| <div id="board" class="board"></div> |
|
|
| <div class="game-info"> |
| <div>Current Player: <span id="currentPlayer">Black</span></div> |
| <div>Black Captures: <span id="blackCaptures">0</span></div> |
| <div>White Captures: <span id="whiteCaptures">0</span></div> |
| </div> |
|
|
| <div class="controls"> |
| <button id="passBtn">Pass</button> |
| <button id="resetBtn">Reset</button> |
| </div> |
| </div> |
|
|
| <script> |
| class GoGame { |
| constructor() { |
| this.size = 19; |
| this.board = Array(this.size).fill().map(() => Array(this.size).fill(null)); |
| this.currentPlayer = 'black'; |
| this.captures = { black: 0, white: 0 }; |
| this.lastMove = null; |
| this.gameMode = 'pvp'; |
| this.difficulty = 'easy'; |
| this.isProcessing = false; |
| this.initialize(); |
| } |
| |
| initialize() { |
| const boardElement = document.getElementById('board'); |
| boardElement.innerHTML = ''; |
| |
| for (let i = 0; i < this.size; i++) { |
| for (let j = 0; j < this.size; j++) { |
| const intersection = document.createElement('div'); |
| intersection.className = 'intersection'; |
| intersection.dataset.row = i; |
| intersection.dataset.col = j; |
| |
| if (this.isStarPoint(i, j)) { |
| intersection.classList.add('star-point'); |
| } |
| |
| intersection.addEventListener('click', (e) => this.handleMove(e)); |
| boardElement.appendChild(intersection); |
| } |
| } |
| |
| document.getElementById('passBtn').addEventListener('click', () => this.pass()); |
| document.getElementById('resetBtn').addEventListener('click', () => this.reset()); |
| document.getElementById('gameMode').addEventListener('change', (e) => { |
| this.gameMode = e.target.value; |
| this.reset(); |
| }); |
| document.getElementById('difficulty').addEventListener('change', (e) => { |
| this.difficulty = e.target.value; |
| }); |
| |
| this.updateDisplay(); |
| } |
| |
| isStarPoint(row, col) { |
| const starPoints = [ |
| [3, 3], [3, 9], [3, 15], |
| [9, 3], [9, 9], [9, 15], |
| [15, 3], [15, 9], [15, 15] |
| ]; |
| return starPoints.some(point => point[0] === row && point[1] === col); |
| } |
| |
| handleMove(e) { |
| if (this.isProcessing) return; |
| const row = parseInt(e.target.dataset.row); |
| const col = parseInt(e.target.dataset.col); |
| |
| if (this.isValidMove(row, col)) { |
| this.placeStone(row, col); |
| |
| if (this.gameMode === 'ai' && this.currentPlayer === 'white') { |
| this.isProcessing = true; |
| setTimeout(() => { |
| this.makeAIMove(); |
| this.isProcessing = false; |
| }, 500); |
| } |
| } |
| } |
| |
| isValidMove(row, col) { |
| if (this.board[row][col] !== null) return false; |
| |
| this.board[row][col] = this.currentPlayer; |
| const captures = this.checkCaptures(row, col); |
| const suicide = this.isSuicideMove(row, col); |
| |
| this.board[row][col] = null; |
| |
| return !suicide || captures.length > 0; |
| } |
| |
| placeStone(row, col) { |
| this.board[row][col] = this.currentPlayer; |
| this.renderStone(row, col); |
| |
| const captures = this.checkCaptures(row, col); |
| this.removeCaptures(captures); |
| |
| this.lastMove = [row, col]; |
| this.currentPlayer = this.currentPlayer === 'black' ? 'white' : 'black'; |
| this.updateDisplay(); |
| } |
| |
| renderStone(row, col) { |
| const intersection = document.querySelector(`[data-row="${row}"][data-col="${col}"]`); |
| const stone = document.createElement('div'); |
| stone.className = `stone ${this.currentPlayer}`; |
| intersection.appendChild(stone); |
| } |
| |
| checkCaptures(row, col) { |
| const captures = []; |
| const opponent = this.currentPlayer === 'black' ? 'white' : 'black'; |
| const neighbors = this.getNeighbors(row, col); |
| |
| for (const [nRow, nCol] of neighbors) { |
| if (this.board[nRow][nCol] === opponent) { |
| const group = this.getGroup(nRow, nCol); |
| if (this.isGroupCaptured(group)) { |
| captures.push(...group); |
| } |
| } |
| } |
| |
| return captures; |
| } |
| |
| removeCaptures(captures) { |
| for (const [row, col] of captures) { |
| this.board[row][col] = null; |
| const intersection = document.querySelector(`[data-row="${row}"][data-col="${col}"]`); |
| intersection.innerHTML = ''; |
| this.captures[this.currentPlayer]++; |
| } |
| } |
| |
| getGroup(row, col) { |
| const color = this.board[row][col]; |
| const group = []; |
| const visited = new Set(); |
| const stack = [[row, col]]; |
| |
| while (stack.length > 0) { |
| const [r, c] = stack.pop(); |
| const key = `${r},${c}`; |
| |
| if (!visited.has(key)) { |
| visited.add(key); |
| group.push([r, c]); |
| |
| const neighbors = this.getNeighbors(r, c); |
| for (const [nr, nc] of neighbors) { |
| if (this.board[nr][nc] === color) { |
| stack.push([nr, nc]); |
| } |
| } |
| } |
| } |
| |
| return group; |
| } |
| |
| isGroupCaptured(group) { |
| for (const [row, col] of group) { |
| const neighbors = this.getNeighbors(row, col); |
| for (const [nRow, nCol] of neighbors) { |
| if (this.board[nRow][nCol] === null) return false; |
| } |
| } |
| return true; |
| } |
| |
| getNeighbors(row, col) { |
| const neighbors = []; |
| const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; |
| |
| for (const [dRow, dCol] of directions) { |
| const newRow = row + dRow; |
| const newCol = col + dCol; |
| |
| if (newRow >= 0 && newRow < this.size && |
| newCol >= 0 && newCol < this.size) { |
| neighbors.push([newRow, newCol]); |
| } |
| } |
| |
| return neighbors; |
| } |
| |
| isSuicideMove(row, col) { |
| const group = this.getGroup(row, col); |
| return this.isGroupCaptured(group); |
| } |
| |
| pass() { |
| if (this.isProcessing) return; |
| this.currentPlayer = this.currentPlayer === 'black' ? 'white' : 'black'; |
| this.updateDisplay(); |
| |
| if (this.gameMode === 'ai' && this.currentPlayer === 'white') { |
| this.isProcessing = true; |
| setTimeout(() => { |
| this.makeAIMove(); |
| this.isProcessing = false; |
| }, 500); |
| } |
| } |
| |
| reset() { |
| this.board = Array(this.size).fill().map(() => Array(this.size).fill(null)); |
| this.currentPlayer = 'black'; |
| this.captures = { black: 0, white: 0 }; |
| this.lastMove = null; |
| this.isProcessing = false; |
| this.initialize(); |
| } |
| |
| updateDisplay() { |
| document.getElementById('currentPlayer').textContent = |
| this.currentPlayer.charAt(0).toUpperCase() + this.currentPlayer.slice(1); |
| document.getElementById('blackCaptures').textContent = this.captures.black; |
| document.getElementById('whiteCaptures').textContent = this.captures.white; |
| } |
| |
| makeAIMove() { |
| if (this.difficulty === 'easy') { |
| this.makeRandomMove(); |
| } else { |
| this.makeStrategicMove(); |
| } |
| } |
| |
| makeRandomMove() { |
| const validMoves = []; |
| for (let i = 0; i < this.size; i++) { |
| for (let j = 0; j < this.size; j++) { |
| if (this.isValidMove(i, j)) { |
| validMoves.push([i, j]); |
| } |
| } |
| } |
| |
| if (validMoves.length > 0) { |
| const [row, col] = validMoves[Math.floor(Math.random() * validMoves.length)]; |
| this.placeStone(row, col); |
| } else { |
| this.pass(); |
| } |
| } |
| |
| makeStrategicMove() { |
| const opponent = this.currentPlayer === 'black' ? 'white' : 'black'; |
| const captureMoves = this.findCaptureMoves(opponent); |
| if (captureMoves.length > 0) { |
| const [row, col] = captureMoves[0]; |
| this.placeStone(row, col); |
| return; |
| } |
| |
| const territoryMove = this.findTerritoryMove(); |
| if (territoryMove) { |
| const [row, col] = territoryMove; |
| this.placeStone(row, col); |
| return; |
| } |
| |
| const connectionMove = this.findConnectionMove(); |
| if (connectionMove) { |
| const [row, col] = connectionMove; |
| this.placeStone(row, col); |
| return; |
| } |
| |
| const separationMove = this.findSeparationMove(opponent); |
| if (separationMove) { |
| const [row, col] = separationMove; |
| this.placeStone(row, col); |
| return; |
| } |
| |
| const eyeMove = this.findEyeMakingMove(); |
| if (eyeMove) { |
| const [row, col] = eyeMove; |
| this.placeStone(row, col); |
| return; |
| } |
| |
| this.makeRandomMove(); |
| } |
| |
| findCaptureMoves(opponent) { |
| const captureMoves = []; |
| |
| for (let row = 0; row < this.size; row++) { |
| for (let col = 0; col < this.size; col++) { |
| if (this.board[row][col] === opponent) { |
| const group = this.getGroup(row, col); |
| if (this.isGroupCaptured(group)) { |
| const liberties = this.getLiberties(group); |
| captureMoves.push(...liberties); |
| } |
| } |
| } |
| } |
| |
| return captureMoves.filter(([r, c]) => this.isValidMove(r, c)); |
| } |
| |
| findTerritoryMove() { |
| const importantPoints = [ |
| [9, 9], |
| [3, 3], [3, 15], [15, 3], [15, 15], |
| [9, 3], [9, 15], [3, 9], [15, 9] |
| ]; |
| |
| for (const [row, col] of importantPoints) { |
| if (this.isValidMove(row, col)) { |
| return [row, col]; |
| } |
| } |
| |
| return null; |
| } |
| |
| findConnectionMove() { |
| for (let row = 0; row < this.size; row++) { |
| for (let col = 0; col < this.size; col++) { |
| if (this.isValidMove(row, col)) { |
| const neighbors = this.getNeighbors(row, col); |
| for (const [nRow, nCol] of neighbors) { |
| if (this.board[nRow][nCol] === this.currentPlayer) { |
| return [row, col]; |
| } |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| findSeparationMove(opponent) { |
| for (let row = 0; row < this.size; row++) { |
| for (let col = 0; col < this.size; col++) { |
| if (this.isValidMove(row, col)) { |
| const neighbors = this.getNeighbors(row, col); |
| const opponentNeighbors = neighbors.filter(([nRow, nCol]) => this.board[nRow][nCol] === opponent); |
| if (opponentNeighbors.length >= 2) { |
| return [row, col]; |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| findEyeMakingMove() { |
| for (let row = 0; row < this.size; row++) { |
| for (let col = 0; col < this.size; col++) { |
| if (this.isValidMove(row, col)) { |
| const group = this.getGroup(row, col); |
| const liberties = this.getLiberties(group); |
| if (liberties.length >= 2) { |
| return [row, col]; |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| getLiberties(group) { |
| const liberties = new Set(); |
| |
| for (const [row, col] of group) { |
| const neighbors = this.getNeighbors(row, col); |
| for (const [nRow, nCol] of neighbors) { |
| if (this.board[nRow][nCol] === null) { |
| liberties.add(`${nRow},${nCol}`); |
| } |
| } |
| } |
| |
| return Array.from(liberties).map((key) => key.split(',').map(Number)); |
| } |
| } |
| |
| const game = new GoGame(); |
| </script> |
| </body> |
| </html> |
|
|