|
<!DOCTYPE html> |
|
<html> |
|
<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 { |
|
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.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) { |
|
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 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() { |
|
this.currentPlayer = this.currentPlayer === 'black' ? 'white' : 'black'; |
|
this.updateDisplay(); |
|
|
|
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.captures = { black: 0, white: 0 }; |
|
this.lastMove = null; |
|
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() { |
|
this.makeRandomMove(); |
|
} |
|
} |
|
|
|
const game = new GoGame(); |
|
</script> |
|
</body> |
|
</html> |