| <!DOCTYPE html> |
| <html lang="zh-CN"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>五子棋双人游戏</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .board { |
| background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" viewBox="0 0 30 30"><line x1="15" y1="0" x2="15" y2="30" stroke="%23a78bfa" stroke-width="1"/><line x1="0" y1="15" x2="30" y2="15" stroke="%23a78bfa" stroke-width="1"/></svg>'); |
| background-size: 30px 30px; |
| position: relative; |
| } |
| |
| .piece { |
| position: absolute; |
| width: 26px; |
| height: 26px; |
| border-radius: 50%; |
| transform: translate(-50%, -50%); |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); |
| transition: all 0.3s ease; |
| } |
| |
| .piece.black { |
| background: radial-gradient(circle at 30% 30%, #555, #000); |
| } |
| |
| .piece.white { |
| background: radial-gradient(circle at 30% 30%, #fff, #ddd); |
| } |
| |
| .piece.last-move { |
| box-shadow: 0 0 0 3px rgba(255, 215, 0, 0.7); |
| } |
| |
| .win-line { |
| position: absolute; |
| background-color: rgba(255, 0, 0, 0.7); |
| transform-origin: 0 0; |
| z-index: 10; |
| } |
| |
| @keyframes pulse { |
| 0% { transform: scale(1); } |
| 50% { transform: scale(1.1); } |
| 100% { transform: scale(1); } |
| } |
| |
| .current-player { |
| animation: pulse 1.5s infinite; |
| } |
| </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 bg-white rounded-xl shadow-xl overflow-hidden"> |
| <div class="bg-purple-600 text-white p-4"> |
| <h1 class="text-2xl font-bold text-center">五子棋双人游戏</h1> |
| </div> |
| |
| <div class="p-6 flex flex-col md:flex-row gap-6"> |
| <div class="flex-1"> |
| <div class="flex justify-between items-center mb-4"> |
| <div id="black-player" class="flex items-center gap-2 p-3 rounded-lg bg-gray-100"> |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-gray-500 to-black shadow"></div> |
| <span class="font-medium">黑方</span> |
| </div> |
| |
| <div id="white-player" class="flex items-center gap-2 p-3 rounded-lg bg-gray-100"> |
| <div class="w-8 h-8 rounded-full bg-gradient-to-br from-white to-gray-300 shadow"></div> |
| <span class="font-medium">白方</span> |
| </div> |
| </div> |
| |
| <div class="relative mx-auto"> |
| <div id="board" class="board w-full max-w-lg h-auto border-2 border-purple-400 bg-amber-50"></div> |
| </div> |
| |
| <div class="mt-4 text-center"> |
| <p id="status" class="text-lg font-semibold text-purple-700">游戏开始,黑方先行</p> |
| </div> |
| </div> |
| |
| <div class="md:w-64 flex flex-col gap-4"> |
| <div class="bg-gray-50 p-4 rounded-lg shadow-inner"> |
| <h3 class="font-bold text-purple-700 border-b pb-2 mb-2">游戏规则</h3> |
| <ul class="text-sm space-y-2 text-gray-700"> |
| <li class="flex items-start gap-2"> |
| <i class="fas fa-circle text-xs mt-1 text-purple-500"></i> |
| <span>黑方先手,白方后手</span> |
| </li> |
| <li class="flex items-start gap-2"> |
| <i class="fas fa-circle text-xs mt-1 text-purple-500"></i> |
| <span>横、竖、斜连成五子获胜</span> |
| </li> |
| <li class="flex items-start gap-2"> |
| <i class="fas fa-circle text-xs mt-1 text-purple-500"></i> |
| <span>棋盘大小:15×15</span> |
| </li> |
| </ul> |
| </div> |
| |
| <div class="bg-gray-50 p-4 rounded-lg shadow-inner"> |
| <h3 class="font-bold text-purple-700 border-b pb-2 mb-2">游戏控制</h3> |
| <div class="flex flex-col gap-2"> |
| <button id="restart-btn" class="bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center gap-2"> |
| <i class="fas fa-redo"></i> |
| <span>重新开始</span> |
| </button> |
| <button id="undo-btn" class="bg-gray-200 hover:bg-gray-300 text-gray-800 py-2 px-4 rounded-lg transition flex items-center justify-center gap-2"> |
| <i class="fas fa-undo"></i> |
| <span>悔棋</span> |
| </button> |
| </div> |
| </div> |
| |
| <div class="bg-gray-50 p-4 rounded-lg shadow-inner"> |
| <h3 class="font-bold text-purple-700 border-b pb-2 mb-2">游戏记录</h3> |
| <div id="move-history" class="text-sm space-y-1 max-h-40 overflow-y-auto"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="win-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> |
| <div class="bg-white rounded-xl p-6 max-w-sm w-full mx-4 shadow-2xl"> |
| <div class="text-center"> |
| <div id="win-icon" class="text-6xl mb-4"> |
| <i class="fas fa-trophy text-yellow-500"></i> |
| </div> |
| <h2 id="win-message" class="text-2xl font-bold mb-2">黑方获胜!</h2> |
| <p class="text-gray-600 mb-6">五子连珠,精彩对局!</p> |
| <button id="close-win-modal" class="bg-purple-600 hover:bg-purple-700 text-white py-2 px-6 rounded-lg transition"> |
| 再来一局 |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const BOARD_SIZE = 15; |
| const CELL_SIZE = 30; |
| const PIECE_RADIUS = 13; |
| |
| |
| let board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(null)); |
| let currentPlayer = 'black'; |
| let gameOver = false; |
| let moveHistory = []; |
| let lastMove = null; |
| |
| |
| const boardElement = document.getElementById('board'); |
| const statusElement = document.getElementById('status'); |
| const blackPlayerElement = document.getElementById('black-player'); |
| const whitePlayerElement = document.getElementById('white-player'); |
| const restartBtn = document.getElementById('restart-btn'); |
| const undoBtn = document.getElementById('undo-btn'); |
| const moveHistoryElement = document.getElementById('move-history'); |
| const winModal = document.getElementById('win-modal'); |
| const winMessage = document.getElementById('win-message'); |
| const closeWinModal = document.getElementById('close-win-modal'); |
| |
| |
| function initBoard() { |
| boardElement.innerHTML = ''; |
| boardElement.style.width = `${BOARD_SIZE * CELL_SIZE}px`; |
| boardElement.style.height = `${BOARD_SIZE * CELL_SIZE}px`; |
| |
| |
| const starPoints = [ |
| [3, 3], [3, 11], [7, 7], [11, 3], [11, 11] |
| ]; |
| |
| starPoints.forEach(([x, y]) => { |
| const star = document.createElement('div'); |
| star.className = 'absolute w-2 h-2 bg-black rounded-full'; |
| star.style.left = `${x * CELL_SIZE}px`; |
| star.style.top = `${y * CELL_SIZE}px`; |
| star.style.transform = 'translate(-50%, -50%)'; |
| boardElement.appendChild(star); |
| }); |
| } |
| |
| |
| function resetGame() { |
| board = Array(BOARD_SIZE).fill().map(() => Array(BOARD_SIZE).fill(null)); |
| currentPlayer = 'black'; |
| gameOver = false; |
| moveHistory = []; |
| lastMove = null; |
| |
| initBoard(); |
| updateStatus(); |
| updatePlayerIndicators(); |
| clearWinLine(); |
| moveHistoryElement.innerHTML = ''; |
| |
| |
| winModal.classList.add('hidden'); |
| } |
| |
| |
| function updateStatus() { |
| if (gameOver) { |
| return; |
| } |
| |
| statusElement.textContent = currentPlayer === 'black' |
| ? '轮到黑方落子' |
| : '轮到白方落子'; |
| } |
| |
| |
| function updatePlayerIndicators() { |
| if (currentPlayer === 'black') { |
| blackPlayerElement.classList.add('current-player', 'ring-2', 'ring-purple-500'); |
| whitePlayerElement.classList.remove('current-player', 'ring-2', 'ring-purple-500'); |
| } else { |
| whitePlayerElement.classList.add('current-player', 'ring-2', 'ring-purple-500'); |
| blackPlayerElement.classList.remove('current-player', 'ring-2', 'ring-purple-500'); |
| } |
| } |
| |
| |
| function placePiece(x, y) { |
| if (gameOver || board[x][y] !== null) { |
| return false; |
| } |
| |
| |
| moveHistory.push({ |
| x, y, |
| player: currentPlayer, |
| board: JSON.parse(JSON.stringify(board)) |
| }); |
| |
| |
| board[x][y] = currentPlayer; |
| lastMove = { x, y }; |
| |
| |
| const piece = document.createElement('div'); |
| piece.className = `piece ${currentPlayer}`; |
| piece.style.left = `${(x + 0.5) * CELL_SIZE}px`; |
| piece.style.top = `${(y + 0.5) * CELL_SIZE}px`; |
| |
| |
| piece.classList.add('last-move'); |
| |
| boardElement.appendChild(piece); |
| |
| |
| addToMoveHistory(x, y); |
| |
| |
| if (checkWin(x, y)) { |
| gameOver = true; |
| showWin(currentPlayer); |
| return true; |
| } |
| |
| |
| currentPlayer = currentPlayer === 'black' ? 'white' : 'black'; |
| updateStatus(); |
| updatePlayerIndicators(); |
| |
| return true; |
| } |
| |
| |
| function checkWin(x, y) { |
| const directions = [ |
| [1, 0], |
| [0, 1], |
| [1, 1], |
| [1, -1] |
| ]; |
| |
| for (const [dx, dy] of directions) { |
| let count = 1; |
| |
| |
| for (let i = 1; i < 5; i++) { |
| const nx = x + i * dx; |
| const ny = y + i * dy; |
| if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] === currentPlayer) { |
| count++; |
| } else { |
| break; |
| } |
| } |
| |
| |
| for (let i = 1; i < 5; i++) { |
| const nx = x - i * dx; |
| const ny = y - i * dy; |
| if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] === currentPlayer) { |
| count++; |
| } else { |
| break; |
| } |
| } |
| |
| if (count >= 5) { |
| drawWinLine(x, y, dx, dy); |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| |
| function drawWinLine(x, y, dx, dy) { |
| |
| let startX = x, startY = y; |
| let endX = x, endY = y; |
| |
| |
| for (let i = 1; i < 5; i++) { |
| const nx = x + i * dx; |
| const ny = y + i * dy; |
| if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] === currentPlayer) { |
| endX = nx; |
| endY = ny; |
| } else { |
| break; |
| } |
| } |
| |
| |
| for (let i = 1; i < 5; i++) { |
| const nx = x - i * dx; |
| const ny = y - i * dy; |
| if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] === currentPlayer) { |
| startX = nx; |
| startY = ny; |
| } else { |
| break; |
| } |
| } |
| |
| |
| const line = document.createElement('div'); |
| line.className = 'win-line'; |
| |
| |
| const startPixelX = (startX + 0.5) * CELL_SIZE; |
| const startPixelY = (startY + 0.5) * CELL_SIZE; |
| const endPixelX = (endX + 0.5) * CELL_SIZE; |
| const endPixelY = (endY + 0.5) * CELL_SIZE; |
| |
| const length = Math.sqrt(Math.pow(endPixelX - startPixelX, 2) + Math.pow(endPixelY - startPixelY, 2)); |
| const angle = Math.atan2(endPixelY - startPixelY, endPixelX - startPixelX); |
| |
| line.style.width = `${length}px`; |
| line.style.height = '4px'; |
| line.style.left = `${startPixelX}px`; |
| line.style.top = `${startPixelY}px`; |
| line.style.transform = `rotate(${angle}rad)`; |
| |
| boardElement.appendChild(line); |
| } |
| |
| |
| function clearWinLine() { |
| const lines = document.querySelectorAll('.win-line'); |
| lines.forEach(line => line.remove()); |
| } |
| |
| |
| function showWin(winner) { |
| winMessage.textContent = winner === 'black' ? '黑方获胜!' : '白方获胜!'; |
| statusElement.textContent = winner === 'black' ? '游戏结束,黑方获胜!' : '游戏结束,白方获胜!'; |
| winModal.classList.remove('hidden'); |
| } |
| |
| |
| function addToMoveHistory(x, y) { |
| const moveNumber = moveHistory.length; |
| const moveItem = document.createElement('div'); |
| moveItem.className = 'flex items-center justify-between py-1 px-2 hover:bg-gray-100 rounded'; |
| |
| const moveText = document.createElement('span'); |
| moveText.className = 'text-gray-700'; |
| moveText.textContent = `${moveNumber}. ${currentPlayer === 'black' ? '黑' : '白'} (${x+1}, ${y+1})`; |
| |
| moveItem.appendChild(moveText); |
| moveHistoryElement.appendChild(moveItem); |
| |
| |
| moveHistoryElement.scrollTop = moveHistoryElement.scrollHeight; |
| } |
| |
| |
| function undoMove() { |
| if (gameOver || moveHistory.length === 0) { |
| return; |
| } |
| |
| const lastMove = moveHistory.pop(); |
| board = lastMove.board; |
| currentPlayer = lastMove.player; |
| |
| |
| initBoard(); |
| for (const move of moveHistory) { |
| const piece = document.createElement('div'); |
| piece.className = `piece ${move.player}`; |
| piece.style.left = `${(move.x + 0.5) * CELL_SIZE}px`; |
| piece.style.top = `${(move.y + 0.5) * CELL_SIZE}px`; |
| boardElement.appendChild(piece); |
| } |
| |
| |
| updateStatus(); |
| updatePlayerIndicators(); |
| clearWinLine(); |
| |
| |
| moveHistoryElement.removeChild(moveHistoryElement.lastChild); |
| } |
| |
| |
| boardElement.addEventListener('click', function(e) { |
| const rect = boardElement.getBoundingClientRect(); |
| const x = Math.floor((e.clientX - rect.left) / CELL_SIZE); |
| const y = Math.floor((e.clientY - rect.top) / CELL_SIZE); |
| |
| if (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE) { |
| placePiece(x, y); |
| } |
| }); |
| |
| restartBtn.addEventListener('click', resetGame); |
| undoBtn.addEventListener('click', undoMove); |
| closeWinModal.addEventListener('click', resetGame); |
| |
| |
| resetGame(); |
| }); |
| </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=Daddy-one/a" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |