|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>Go Game</title> |
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
body { |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
min-height: 100vh; |
|
background: #f0f0f0; |
|
font-family: Arial, sans-serif; |
|
padding: 20px; |
|
} |
|
.game-container { |
|
background: #DEB887; |
|
padding: 20px; |
|
border-radius: 10px; |
|
box-shadow: 0 0 20px rgba(0,0,0,0.1); |
|
} |
|
.board { |
|
display: grid; |
|
grid-template-columns: repeat(19, 30px); |
|
grid-template-rows: repeat(19, 30px); |
|
gap: 0px; |
|
background: #DEB887; |
|
border: 2px solid #000; |
|
position: relative; |
|
} |
|
.intersection { |
|
width: 30px; |
|
height: 30px; |
|
position: relative; |
|
cursor: pointer; |
|
} |
|
.intersection::before { |
|
content: ''; |
|
position: absolute; |
|
top: 50%; |
|
left: 0; |
|
width: 100%; |
|
height: 1px; |
|
background: #000; |
|
} |
|
.intersection::after { |
|
content: ''; |
|
position: absolute; |
|
left: 50%; |
|
top: 0; |
|
height: 100%; |
|
width: 1px; |
|
background: #000; |
|
} |
|
.stone { |
|
position: absolute; |
|
width: 26px; |
|
height: 26px; |
|
border-radius: 50%; |
|
top: 2px; |
|
left: 2px; |
|
z-index: 1; |
|
transition: all 0.2s ease; |
|
} |
|
.black { |
|
background: #000; |
|
box-shadow: 2px 2px 2px rgba(0,0,0,0.2); |
|
} |
|
.white { |
|
background: #fff; |
|
box-shadow: 2px 2px 2px rgba(0,0,0,0.2); |
|
} |
|
.controls { |
|
margin-top: 20px; |
|
display: flex; |
|
gap: 10px; |
|
} |
|
button { |
|
padding: 10px 20px; |
|
font-size: 16px; |
|
border: none; |
|
border-radius: 5px; |
|
cursor: pointer; |
|
background: #4CAF50; |
|
color: white; |
|
transition: background 0.3s; |
|
} |
|
button:hover { |
|
background: #45a049; |
|
} |
|
.score { |
|
margin-top: 20px; |
|
font-size: 18px; |
|
display: flex; |
|
gap: 20px; |
|
} |
|
.mode-select { |
|
margin-bottom: 20px; |
|
} |
|
select { |
|
padding: 8px; |
|
font-size: 16px; |
|
border-radius: 5px; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="mode-select"> |
|
<select id="gameMode"> |
|
<option value="pvp">Player vs Player</option> |
|
<option value="ai-easy">Player vs AI (Easy)</option> |
|
<option value="ai-middle">Player vs AI (Middle)</option> |
|
<option value="ai-hard">Player vs AI (Hard)</option> |
|
</select> |
|
</div> |
|
|
|
<div class="game-container"> |
|
<div class="board" id="board"></div> |
|
</div> |
|
|
|
<div class="score"> |
|
<div>Black Score: <span id="blackScore">0</span></div> |
|
<div>White Score: <span id="whiteScore">0</span></div> |
|
</div> |
|
|
|
<div class="controls"> |
|
<button id="passBtn">Pass</button> |
|
<button id="resetBtn">Reset</button> |
|
</div> |
|
|
|
<script> |
|
class GoGame { |
|
constructor() { |
|
this.size = 19; |
|
this.board = Array(this.size).fill().map(() => Array(this.size).fill(null)); |
|
this.currentPlayer = 'black'; |
|
this.lastMove = null; |
|
this.passes = 0; |
|
this.gameMode = 'pvp'; |
|
this.aiDifficulty = 'easy'; |
|
this.score = { black: 0, white: 0 }; |
|
this.capturedStones = { black: 0, white: 0 }; |
|
|
|
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; |
|
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) => { |
|
const mode = e.target.value; |
|
this.gameMode = mode.startsWith('ai') ? 'ai' : 'pvp'; |
|
this.aiDifficulty = mode.split('-')[1] || 'easy'; |
|
this.reset(); |
|
}); |
|
} |
|
handleMove(e) { |
|
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') { |
|
setTimeout(() => this.makeAIMove(), 500); |
|
} |
|
} |
|
} |
|
isValidMove(row, col) { |
|
if(this.board[row][col] !== null) return false; |
|
|
|
this.board[row][col] = this.currentPlayer; |
|
const group = this.getGroup(row, col); |
|
const liberties = this.countLiberties(group); |
|
|
|
this.board[row][col] = null; |
|
|
|
return liberties > 0; |
|
} |
|
placeStone(row, col) { |
|
if(this.board[row][col] === null) { |
|
this.board[row][col] = this.currentPlayer; |
|
this.renderStone(row, col); |
|
this.captures(); |
|
this.passes = 0; |
|
this.currentPlayer = this.currentPlayer === 'black' ? 'white' : 'black'; |
|
this.updateScore(); |
|
} |
|
} |
|
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); |
|
} |
|
getGroup(row, col) { |
|
const color = this.board[row][col]; |
|
const group = new Set(); |
|
const stack = [[row, col]]; |
|
|
|
while(stack.length > 0) { |
|
const [r, c] = stack.pop(); |
|
const key = `${r},${c}`; |
|
|
|
if(!group.has(key)) { |
|
group.add(key); |
|
const neighbors = this.getNeighbors(r, c); |
|
|
|
for(const [nr, nc] of neighbors) { |
|
if(this.board[nr][nc] === color) { |
|
stack.push([nr, nc]); |
|
} |
|
} |
|
} |
|
} |
|
|
|
return group; |
|
} |
|
countLiberties(group) { |
|
const liberties = new Set(); |
|
|
|
for(const pos of group) { |
|
const [row, col] = pos.split(',').map(Number); |
|
const neighbors = this.getNeighbors(row, col); |
|
|
|
for(const [nr, nc] of neighbors) { |
|
if(this.board[nr][nc] === null) { |
|
liberties.add(`${nr},${nc}`); |
|
} |
|
} |
|
} |
|
|
|
return liberties.size; |
|
} |
|
getNeighbors(row, col) { |
|
const neighbors = []; |
|
const directions = [[-1,0], [1,0], [0,-1], [0,1]]; |
|
|
|
for(const [dr, dc] of directions) { |
|
const newRow = row + dr; |
|
const newCol = col + dc; |
|
|
|
if(newRow >= 0 && newRow < this.size && newCol >= 0 && newCol < this.size) { |
|
neighbors.push([newRow, newCol]); |
|
} |
|
} |
|
|
|
return neighbors; |
|
} |
|
evaluatePosition(row, col) { |
|
let score = 0; |
|
|
|
|
|
const centerDistance = Math.abs(row - 9) + Math.abs(col - 9); |
|
score += (18 - centerDistance) * 2; |
|
|
|
const neighbors = this.getNeighbors(row, col); |
|
let friendlyStones = 0; |
|
let enemyStones = 0; |
|
let liberties = 0; |
|
for (const [nr, nc] of neighbors) { |
|
if (this.board[nr][nc] === this.currentPlayer) friendlyStones++; |
|
else if (this.board[nr][nc] === null) liberties++; |
|
else enemyStones++; |
|
} |
|
score += friendlyStones * 10; |
|
score += liberties * 5; |
|
score += enemyStones * 3; |
|
return score; |
|
} |
|
makeAIMove() { |
|
const validMoves = []; |
|
const scoredMoves = []; |
|
for (let i = 0; i < this.size; i++) { |
|
for (let j = 0; j < this.size; j++) { |
|
if (this.isValidMove(i, j)) { |
|
const score = this.evaluatePosition(i, j); |
|
scoredMoves.push({ row: i, col: j, score: score }); |
|
validMoves.push([i, j]); |
|
} |
|
} |
|
} |
|
if (validMoves.length === 0) { |
|
this.pass(); |
|
return; |
|
} |
|
let selectedMove; |
|
switch (this.aiDifficulty) { |
|
case 'easy': |
|
selectedMove = validMoves[Math.floor(Math.random() * validMoves.length)]; |
|
break; |
|
case 'middle': |
|
scoredMoves.sort((a, b) => b.score - a.score); |
|
const middlePool = scoredMoves.slice(0, Math.floor(scoredMoves.length / 2)); |
|
const middleChoice = middlePool[Math.floor(Math.random() * middlePool.length)]; |
|
selectedMove = [middleChoice.row, middleChoice.col]; |
|
break; |
|
case 'hard': |
|
scoredMoves.sort((a, b) => b.score - a.score); |
|
const bestMove = scoredMoves[0]; |
|
selectedMove = [bestMove.row, bestMove.col]; |
|
break; |
|
} |
|
this.placeStone(selectedMove[0], selectedMove[1]); |
|
} |
|
captures() { |
|
for(let i = 0; i < this.size; i++) { |
|
for(let j = 0; j < this.size; j++) { |
|
if(this.board[i][j] !== null) { |
|
const group = this.getGroup(i, j); |
|
if(this.countLiberties(group) === 0) { |
|
for(const pos of group) { |
|
const [row, col] = pos.split(',').map(Number); |
|
const capturedColor = this.board[row][col]; |
|
this.board[row][col] = null; |
|
const intersection = document.querySelector(`[data-row="${row}"][data-col="${col}"]`); |
|
intersection.innerHTML = ''; |
|
this.capturedStones[this.currentPlayer]++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
pass() { |
|
this.passes++; |
|
if(this.passes === 2) { |
|
alert('Game Over!'); |
|
return; |
|
} |
|
this.currentPlayer = this.currentPlayer === 'black' ? 'white' : 'black'; |
|
if(this.gameMode === 'ai' && this.currentPlayer === 'white') { |
|
setTimeout(() => this.makeAIMove(), 500); |
|
} |
|
} |
|
reset() { |
|
this.board = Array(this.size).fill().map(() => Array(this.size).fill(null)); |
|
this.currentPlayer = 'black'; |
|
this.passes = 0; |
|
this.score = { black: 0, white: 0 }; |
|
this.capturedStones = { black: 0, white: 0 }; |
|
const intersections = document.querySelectorAll('.intersection'); |
|
intersections.forEach(intersection => intersection.innerHTML = ''); |
|
this.updateScore(); |
|
} |
|
updateScore() { |
|
document.getElementById('blackScore').textContent = |
|
this.score.black + this.capturedStones.black; |
|
document.getElementById('whiteScore').textContent = |
|
this.score.white + this.capturedStones.white; |
|
} |
|
} |
|
const game = new GoGame(); |
|
</script> |
|
</body> |
|
</html> |