|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Gomoku - Five in a Row</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<style> |
|
|
.board { |
|
|
display: grid; |
|
|
grid-template-columns: repeat(15, 1fr); |
|
|
grid-template-rows: repeat(15, 1fr); |
|
|
gap: 1px; |
|
|
background-color: #d4a76a; |
|
|
padding: 10px; |
|
|
border-radius: 5px; |
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
|
} |
|
|
|
|
|
.cell { |
|
|
aspect-ratio: 1; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
background-color: rgba(255, 255, 255, 0.8); |
|
|
position: relative; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.cell::before, .cell::after { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
background-color: #333; |
|
|
} |
|
|
|
|
|
.cell::before { |
|
|
width: 100%; |
|
|
height: 1px; |
|
|
} |
|
|
|
|
|
.cell::after { |
|
|
width: 1px; |
|
|
height: 100%; |
|
|
} |
|
|
|
|
|
.stone { |
|
|
width: 80%; |
|
|
height: 80%; |
|
|
border-radius: 50%; |
|
|
z-index: 10; |
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); |
|
|
} |
|
|
|
|
|
.black { |
|
|
background: radial-gradient(circle at 30% 30%, #666, #000); |
|
|
} |
|
|
|
|
|
.white { |
|
|
background: radial-gradient(circle at 30% 30%, #fff, #ccc); |
|
|
} |
|
|
|
|
|
.last-move { |
|
|
box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.7); |
|
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; transform: scale(0.8); } |
|
|
to { opacity: 1; transform: scale(1); } |
|
|
} |
|
|
|
|
|
.animate-stone { |
|
|
animation: fadeIn 0.3s ease-out; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-100 min-h-screen flex flex-col items-center justify-center p-4"> |
|
|
<div class="max-w-4xl w-full"> |
|
|
<h1 class="text-3xl font-bold text-center mb-6 text-gray-800">Gomoku - Five in a Row</h1> |
|
|
|
|
|
<div class="flex flex-col md:flex-row gap-6 items-center md:items-start"> |
|
|
|
|
|
<div class="board w-full max-w-xl mx-auto"> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="w-full md:w-64 bg-white p-4 rounded-lg shadow-md"> |
|
|
<div class="flex items-center justify-between mb-4"> |
|
|
<div class="flex items-center"> |
|
|
<div class="w-6 h-6 rounded-full bg-black mr-2"></div> |
|
|
<span class="font-medium">Black</span> |
|
|
</div> |
|
|
<span id="black-score" class="font-bold">0</span> |
|
|
</div> |
|
|
|
|
|
<div class="flex items-center justify-between mb-6"> |
|
|
<div class="flex items-center"> |
|
|
<div class="w-6 h-6 rounded-full bg-white border border-gray-300 mr-2"></div> |
|
|
<span class="font-medium">White</span> |
|
|
</div> |
|
|
<span id="white-score" class="font-bold">0</span> |
|
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<div class="text-center py-2 px-4 rounded-md bg-gray-100 font-medium"> |
|
|
Current turn: <span id="current-turn" class="font-bold">Black</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mb-6"> |
|
|
<div class="text-center py-2 px-4 rounded-md bg-blue-100 text-blue-800 font-medium hidden" id="game-status"> |
|
|
Game in progress |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex flex-col gap-2"> |
|
|
<button id="new-game" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md transition"> |
|
|
New Game |
|
|
</button> |
|
|
<button id="undo-move" class="w-full bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-md transition"> |
|
|
Undo Move |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
const boardSize = 15; |
|
|
const board = document.querySelector('.board'); |
|
|
const currentTurnDisplay = document.getElementById('current-turn'); |
|
|
const blackScoreDisplay = document.getElementById('black-score'); |
|
|
const whiteScoreDisplay = document.getElementById('white-score'); |
|
|
const gameStatusDisplay = document.getElementById('game-status'); |
|
|
const newGameBtn = document.getElementById('new-game'); |
|
|
const undoMoveBtn = document.getElementById('undo-move'); |
|
|
|
|
|
let gameState = { |
|
|
board: Array(boardSize).fill().map(() => Array(boardSize).fill(null)), |
|
|
currentPlayer: 'black', |
|
|
gameOver: false, |
|
|
moveHistory: [], |
|
|
scores: { black: 0, white: 0 }, |
|
|
lastMove: null |
|
|
}; |
|
|
|
|
|
|
|
|
function initBoard() { |
|
|
board.innerHTML = ''; |
|
|
board.style.gridTemplateColumns = `repeat(${boardSize}, 1fr)`; |
|
|
board.style.gridTemplateRows = `repeat(${boardSize}, 1fr)`; |
|
|
|
|
|
for (let row = 0; row < boardSize; row++) { |
|
|
for (let col = 0; col < boardSize; col++) { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'cell'; |
|
|
cell.dataset.row = row; |
|
|
cell.dataset.col = col; |
|
|
cell.addEventListener('click', () => makeMove(row, col)); |
|
|
board.appendChild(cell); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function makeMove(row, col) { |
|
|
if (gameState.gameOver || gameState.board[row][col] !== null) return; |
|
|
|
|
|
gameState.board[row][col] = gameState.currentPlayer; |
|
|
gameState.moveHistory.push({ row, col, player: gameState.currentPlayer }); |
|
|
gameState.lastMove = { row, col }; |
|
|
|
|
|
renderBoard(); |
|
|
|
|
|
if (checkWin(row, col)) { |
|
|
gameState.gameOver = true; |
|
|
gameState.scores[gameState.currentPlayer]++; |
|
|
updateScores(); |
|
|
gameStatusDisplay.textContent = `${gameState.currentPlayer === 'black' ? 'Black' : 'White'} wins!`; |
|
|
gameStatusDisplay.classList.remove('hidden'); |
|
|
gameStatusDisplay.classList.remove('bg-blue-100', 'text-blue-800'); |
|
|
gameStatusDisplay.classList.add('bg-green-100', 'text-green-800'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (checkDraw()) { |
|
|
gameState.gameOver = true; |
|
|
gameStatusDisplay.textContent = "It's a draw!"; |
|
|
gameStatusDisplay.classList.remove('hidden'); |
|
|
gameStatusDisplay.classList.remove('bg-blue-100', 'text-blue-800'); |
|
|
gameStatusDisplay.classList.add('bg-yellow-100', 'text-yellow-800'); |
|
|
return; |
|
|
} |
|
|
|
|
|
gameState.currentPlayer = gameState.currentPlayer === 'black' ? 'white' : 'black'; |
|
|
currentTurnDisplay.textContent = gameState.currentPlayer === 'black' ? 'Black' : 'White'; |
|
|
} |
|
|
|
|
|
|
|
|
function renderBoard() { |
|
|
for (let row = 0; row < boardSize; row++) { |
|
|
for (let col = 0; col < boardSize; col++) { |
|
|
const cell = document.querySelector(`.cell[data-row="${row}"][data-col="${col}"]`); |
|
|
cell.innerHTML = ''; |
|
|
|
|
|
if (gameState.board[row][col]) { |
|
|
const stone = document.createElement('div'); |
|
|
stone.className = `stone ${gameState.board[row][col]} animate-stone`; |
|
|
|
|
|
|
|
|
if (gameState.lastMove && gameState.lastMove.row === row && gameState.lastMove.col === col) { |
|
|
stone.classList.add('last-move'); |
|
|
} |
|
|
|
|
|
cell.appendChild(stone); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function checkWin(row, col) { |
|
|
const directions = [ |
|
|
[0, 1], |
|
|
[1, 0], |
|
|
[1, 1], |
|
|
[1, -1] |
|
|
]; |
|
|
|
|
|
const player = gameState.board[row][col]; |
|
|
|
|
|
for (const [dx, dy] of directions) { |
|
|
let count = 1; |
|
|
|
|
|
|
|
|
for (let i = 1; i <= 4; i++) { |
|
|
const newRow = row + i * dx; |
|
|
const newCol = col + i * dy; |
|
|
|
|
|
if ( |
|
|
newRow >= 0 && newRow < boardSize && |
|
|
newCol >= 0 && newCol < boardSize && |
|
|
gameState.board[newRow][newCol] === player |
|
|
) { |
|
|
count++; |
|
|
} else { |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (let i = 1; i <= 4; i++) { |
|
|
const newRow = row - i * dx; |
|
|
const newCol = col - i * dy; |
|
|
|
|
|
if ( |
|
|
newRow >= 0 && newRow < boardSize && |
|
|
newCol >= 0 && newCol < boardSize && |
|
|
gameState.board[newRow][newCol] === player |
|
|
) { |
|
|
count++; |
|
|
} else { |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
if (count >= 5) { |
|
|
return true; |
|
|
} |
|
|
} |
|
|
|
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
function checkDraw() { |
|
|
for (let row = 0; row < boardSize; row++) { |
|
|
for (let col = 0; col < boardSize; col++) { |
|
|
if (gameState.board[row][col] === null) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
function undoMove() { |
|
|
if (gameState.moveHistory.length === 0 || gameState.gameOver) return; |
|
|
|
|
|
const lastMove = gameState.moveHistory.pop(); |
|
|
gameState.board[lastMove.row][lastMove.col] = null; |
|
|
gameState.currentPlayer = lastMove.player; |
|
|
gameState.gameOver = false; |
|
|
gameState.lastMove = gameState.moveHistory.length > 0 ? |
|
|
{ row: gameState.moveHistory[gameState.moveHistory.length - 1].row, |
|
|
col: gameState.moveHistory[gameState.moveHistory.length - 1].col } : null; |
|
|
|
|
|
currentTurnDisplay.textContent = gameState.currentPlayer === 'black' ? 'Black' : 'White'; |
|
|
gameStatusDisplay.classList.add('hidden'); |
|
|
renderBoard(); |
|
|
} |
|
|
|
|
|
|
|
|
function newGame() { |
|
|
gameState = { |
|
|
board: Array(boardSize).fill().map(() => Array(boardSize).fill(null)), |
|
|
currentPlayer: 'black', |
|
|
gameOver: false, |
|
|
moveHistory: [], |
|
|
scores: gameState.scores, |
|
|
lastMove: null |
|
|
}; |
|
|
|
|
|
currentTurnDisplay.textContent = 'Black'; |
|
|
gameStatusDisplay.classList.add('hidden'); |
|
|
renderBoard(); |
|
|
} |
|
|
|
|
|
|
|
|
function updateScores() { |
|
|
blackScoreDisplay.textContent = gameState.scores.black; |
|
|
whiteScoreDisplay.textContent = gameState.scores.white; |
|
|
} |
|
|
|
|
|
|
|
|
newGameBtn.addEventListener('click', newGame); |
|
|
undoMoveBtn.addEventListener('click', undoMove); |
|
|
|
|
|
|
|
|
initBoard(); |
|
|
updateScores(); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=gkiwi/fiveinrow" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |