Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Carrom Board Game</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --primary-red: #8B0000; | |
| --light-brown: #D2B48C; | |
| --orange: #FFA500; | |
| --black: #000000; | |
| --purple: #800080; | |
| --green: #008000; | |
| --white: #FFFFFF; | |
| --gold: #FFD700; | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Arial', sans-serif; | |
| } | |
| body { | |
| background: linear-gradient(to bottom, var(--primary-red), #A0522D); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 10px; | |
| color: white; | |
| overflow-x: hidden; | |
| } | |
| .header { | |
| width: 100%; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 15px 0; | |
| margin-bottom: 10px; | |
| } | |
| .player-info { | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| } | |
| .player-avatar { | |
| width: 50px; | |
| height: 50px; | |
| border-radius: 8px; | |
| object-fit: cover; | |
| border: 2px solid var(--gold); | |
| } | |
| .player-name { | |
| font-size: 18px; | |
| font-weight: bold; | |
| } | |
| .player-score { | |
| font-size: 24px; | |
| font-weight: bold; | |
| } | |
| .disc-icon { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| margin-left: 5px; | |
| } | |
| .pot-container { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| .pot-icon { | |
| font-size: 30px; | |
| color: var(--gold); | |
| text-shadow: 0 0 5px rgba(255, 215, 0, 0.7); | |
| } | |
| .pot-amount { | |
| font-size: 18px; | |
| font-weight: bold; | |
| } | |
| .game-container { | |
| width: 100%; | |
| max-width: 500px; | |
| aspect-ratio: 1/1; | |
| position: relative; | |
| background-color: var(--light-brown); | |
| border-radius: 10px; | |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); | |
| overflow: hidden; | |
| background-image: | |
| radial-gradient(circle at center, rgba(0,0,0,0.1) 1px, transparent 1px), | |
| linear-gradient(45deg, rgba(0,0,0,0.05) 1px, transparent 1px), | |
| linear-gradient(-45deg, rgba(0,0,0,0.05) 1px, transparent 1px); | |
| background-size: 100px 100px, 20px 20px, 20px 20px; | |
| } | |
| .board-border { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| border: 15px solid var(--orange); | |
| border-radius: 10px; | |
| pointer-events: none; | |
| } | |
| .pocket { | |
| position: absolute; | |
| width: 40px; | |
| height: 40px; | |
| background-color: var(--black); | |
| border-radius: 50%; | |
| z-index: 10; | |
| } | |
| .pocket::before { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 30px; | |
| height: 30px; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| border-radius: 50%; | |
| } | |
| .bumper { | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| background-color: var(--orange); | |
| border-radius: 50%; | |
| z-index: 5; | |
| } | |
| .center-circle { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 80px; | |
| height: 80px; | |
| border: 2px dashed rgba(0, 0, 0, 0.3); | |
| border-radius: 50%; | |
| z-index: 2; | |
| } | |
| .striker-zone { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 20px; | |
| width: 80px; | |
| height: 40px; | |
| border: 2px dashed rgba(255, 255, 255, 0.7); | |
| border-radius: 40px 40px 0 0; | |
| background-color: rgba(255, 255, 255, 0.1); | |
| z-index: 3; | |
| } | |
| .striker { | |
| position: absolute; | |
| width: 25px; | |
| height: 25px; | |
| background-color: var(--white); | |
| border-radius: 50%; | |
| background-image: radial-gradient(circle, rgba(255,255,255,0.8) 2px, transparent 2px); | |
| background-size: 5px 5px; | |
| z-index: 4; | |
| cursor: grab; | |
| touch-action: none; | |
| box-shadow: 0 0 5px rgba(255, 255, 255, 0.7); | |
| } | |
| .disc { | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| z-index: 2; | |
| transition: transform 0.1s linear; | |
| } | |
| .purple-disc { | |
| background-color: var(--purple); | |
| box-shadow: 0 0 5px rgba(128, 0, 128, 0.7); | |
| } | |
| .green-disc { | |
| background-color: var(--green); | |
| box-shadow: 0 0 5px rgba(0, 128, 0, 0.7); | |
| } | |
| .white-disc { | |
| background-color: var(--white); | |
| box-shadow: 0 0 5px rgba(255, 255, 255, 0.7); | |
| } | |
| .queen-disc { | |
| background-color: #FF0000; | |
| box-shadow: 0 0 10px rgba(255, 0, 0, 0.9); | |
| } | |
| .trajectory-line { | |
| position: absolute; | |
| height: 2px; | |
| background-color: rgba(255, 255, 255, 0.7); | |
| z-index: 1; | |
| pointer-events: none; | |
| } | |
| .trajectory-arrow { | |
| position: absolute; | |
| width: 0; | |
| height: 0; | |
| border-left: 8px solid transparent; | |
| border-right: 8px solid transparent; | |
| border-top: 12px solid var(--orange); | |
| z-index: 1; | |
| pointer-events: none; | |
| } | |
| .controls { | |
| margin-top: 20px; | |
| display: flex; | |
| gap: 15px; | |
| flex-wrap: wrap; | |
| justify-content: center; | |
| } | |
| .btn { | |
| padding: 10px 20px; | |
| border: none; | |
| border-radius: 5px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| background-color: var(--orange); | |
| color: white; | |
| } | |
| .btn:hover { | |
| background-color: #FF8C00; | |
| transform: scale(1.05); | |
| } | |
| .btn:active { | |
| transform: scale(0.95); | |
| } | |
| .game-status { | |
| margin-top: 15px; | |
| padding: 10px; | |
| background-color: rgba(0, 0, 0, 0.3); | |
| border-radius: 5px; | |
| text-align: center; | |
| font-size: 16px; | |
| } | |
| .anycoder-link { | |
| position: fixed; | |
| bottom: 10px; | |
| right: 10px; | |
| color: white; | |
| text-decoration: none; | |
| font-size: 12px; | |
| background-color: rgba(0, 0, 0, 0.3); | |
| padding: 5px 10px; | |
| border-radius: 5px; | |
| z-index: 100; | |
| } | |
| @media (max-width: 400px) { | |
| .player-info { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| .player-avatar { | |
| width: 40px; | |
| height: 40px; | |
| } | |
| .player-name { | |
| font-size: 16px; | |
| } | |
| .player-score { | |
| font-size: 20px; | |
| } | |
| .pot-icon { | |
| font-size: 25px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <div class="player-info"> | |
| <img src="https://via.placeholder.com/50x50/808080/FFFFFF?text=A" alt="Ash Avatar" class="player-avatar"> | |
| <div> | |
| <div class="player-name">Ash</div> | |
| <div class="player-score">0 <div class="disc-icon" style="background-color: var(--purple);"></div></div> | |
| </div> | |
| </div> | |
| <div class="pot-container"> | |
| <i class="fas fa-coins pot-icon"></i> | |
| <div class="pot-amount">1000</div> | |
| </div> | |
| <div class="player-info"> | |
| <div> | |
| <div class="player-name">MD</div> | |
| <div class="player-score">1 <div class="disc-icon" style="background-color: var(--green);"></div></div> | |
| </div> | |
| <img src="https://via.placeholder.com/50x50/D2B48C/000000?text=MD" alt="MD Avatar" class="player-avatar"> | |
| </div> | |
| </div> | |
| <div class="game-container"> | |
| <div class="board-border"></div> | |
| <!-- Pockets --> | |
| <div class="pocket" style="top: 10px; left: 10px;"></div> | |
| <div class="pocket" style="top: 10px; right: 10px;"></div> | |
| <div class="pocket" style="bottom: 10px; left: 10px;"></div> | |
| <div class="pocket" style="bottom: 10px; right: 10px;"></div> | |
| <!-- Bumpers --> | |
| <div class="bumper" style="top: 30px; left: 50%; transform: translateX(-50%);"></div> | |
| <div class="bumper" style="top: 50%; left: 30px; transform: translateY(-50%);"></div> | |
| <div class="bumper" style="bottom: 30px; left: 50%; transform: translateX(-50%);"></div> | |
| <div class="bumper" style="top: 50%; right: 30px; transform: translateY(-50%);"></div> | |
| <!-- Center Circle --> | |
| <div class="center-circle"></div> | |
| <!-- Striker Zone --> | |
| <div class="striker-zone"></div> | |
| <!-- Striker --> | |
| <div class="striker" id="striker"></div> | |
| <!-- Trajectory Line and Arrow --> | |
| <div class="trajectory-line" id="trajectoryLine"></div> | |
| <div class="trajectory-arrow" id="trajectoryArrow"></div> | |
| <!-- Discs will be added by JavaScript --> | |
| </div> | |
| <div class="controls"> | |
| <button class="btn" id="resetBtn">Reset Game</button> | |
| <button class="btn" id="aiBtn">AI Opponent</button> | |
| <button class="btn" id="shootBtn">Shoot</button> | |
| </div> | |
| <div class="game-status" id="gameStatus"> | |
| Player Ash's turn. Drag the striker to aim and shoot! | |
| </div> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" class="anycoder-link" target="_blank">Built with anycoder</a> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Game state | |
| const gameState = { | |
| currentPlayer: 'Ash', | |
| scores: { | |
| Ash: 0, | |
| MD: 0 | |
| }, | |
| discs: [], | |
| striker: { | |
| x: 30, | |
| y: 85, | |
| angle: 0, | |
| power: 0, | |
| isDragging: false | |
| }, | |
| queenPocketed: false, | |
| gameOver: false | |
| }; | |
| // DOM elements | |
| const gameContainer = document.querySelector('.game-container'); | |
| const striker = document.getElementById('striker'); | |
| const trajectoryLine = document.getElementById('trajectoryLine'); | |
| const trajectoryArrow = document.getElementById('trajectoryArrow'); | |
| const gameStatus = document.getElementById('gameStatus'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const aiBtn = document.getElementById('aiBtn'); | |
| const shootBtn = document.getElementById('shootBtn'); | |
| const ashScore = document.querySelector('.player-info:first-child .player-score'); | |
| const mdScore = document.querySelector('.player-info:last-child .player-score'); | |
| // Initialize the game | |
| function initGame() { | |
| // Clear existing discs | |
| document.querySelectorAll('.disc').forEach(disc => disc.remove()); | |
| gameState.discs = []; | |
| // Create discs | |
| createDiscs(); | |
| // Reset striker position | |
| striker.style.left = `${gameState.striker.x}%`; | |
| striker.style.top = `${gameState.striker.y}%`; | |
| // Reset game state | |
| gameState.currentPlayer = 'Ash'; | |
| gameState.queenPocketed = false; | |
| gameState.gameOver = false; | |
| // Update UI | |
| updateGameStatus(); | |
| updateScores(); | |
| } | |
| // Create discs | |
| function createDiscs() { | |
| const centerX = 50; | |
| const centerY = 50; | |
| const radius = 15; | |
| const discSize = 20; | |
| // Create queen disc (red) | |
| const queen = document.createElement('div'); | |
| queen.className = 'disc queen-disc'; | |
| queen.style.left = `${centerX}%`; | |
| queen.style.top = `${centerY}%`; | |
| queen.style.width = `${discSize}px`; | |
| queen.style.height = `${discSize}px`; | |
| queen.style.transform = 'translate(-50%, -50%)'; | |
| queen.dataset.type = 'queen'; | |
| gameContainer.appendChild(queen); | |
| gameState.discs.push({ | |
| element: queen, | |
| x: centerX, | |
| y: centerY, | |
| type: 'queen', | |
| pocketed: false | |
| }); | |
| // Create player discs in a circle around the queen | |
| const discCount = 9; | |
| const angleStep = (2 * Math.PI) / discCount; | |
| for (let i = 0; i < discCount; i++) { | |
| const angle = i * angleStep; | |
| const x = centerX + radius * Math.cos(angle); | |
| const y = centerY + radius * Math.sin(angle); | |
| // Alternate between purple and green/white discs | |
| const discType = i % 2 === 0 ? 'purple' : (i % 3 === 0 ? 'green' : 'white'); | |
| const disc = document.createElement('div'); | |
| disc.className = `disc ${discType}-disc`; | |
| disc.style.left = `${x}%`; | |
| disc.style.top = `${y}%`; | |
| disc.style.width = `${discSize}px`; | |
| disc.style.height = `${discSize}px`; | |
| disc.style.transform = 'translate(-50%, -50%)'; | |
| disc.dataset.type = discType; | |
| gameContainer.appendChild(disc); | |
| gameState.discs.push({ | |
| element: disc, | |
| x: x, | |
| y: y, | |
| type: discType, | |
| pocketed: false | |
| }); | |
| } | |
| } | |
| // Update game status display | |
| function updateGameStatus() { | |
| if (gameState.gameOver) { | |
| const winner = gameState.scores.Ash > gameState.scores.MD ? 'Ash' : 'MD'; | |
| gameStatus.textContent = `Game Over! ${winner} wins!`; | |
| return; | |
| } | |
| gameStatus.textContent = `Player ${gameState.currentPlayer}'s turn. Drag the striker to aim and shoot!`; | |
| } | |
| // Update scores display | |
| function updateScores() { | |
| ashScore.innerHTML = `${gameState.scores.Ash} <div class="disc-icon" style="background-color: var(--purple);"></div>`; | |
| mdScore.innerHTML = `${gameState.scores.MD} <div class="disc-icon" style="background-color: var(--green);"></div>`; | |
| } | |
| // Check if a disc is pocketed | |
| function checkPocketed(disc) { | |
| const pockets = [ | |
| { x: 5, y: 5 }, // Top-left | |
| { x: 95, y: 5 }, // Top-right | |
| { x: 5, y: 95 }, // Bottom-left | |
| { x: 95, y: 95 } // Bottom-right | |
| ]; | |
| for (const pocket of pockets) { | |
| const distance = Math.sqrt( | |
| Math.pow(disc.x - pocket.x, 2) + | |
| Math.pow(disc.y - pocket.y, 2) | |
| ); | |
| if (distance < 3) { // Pocket radius is about 3% of container | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| // Handle disc movement physics | |
| function moveDiscs() { | |
| const friction = 0.98; | |
| const gravity = 0.1; | |
| let allStopped = true; | |
| gameState.discs.forEach(disc => { | |
| if (disc.pocketed) return; | |
| const element = disc.element; | |
| // Get current velocity from data attributes | |
| let vx = parseFloat(element.dataset.vx) || 0; | |
| let vy = parseFloat(element.dataset.vy) || 0; | |
| // Apply friction | |
| vx *= friction; | |
| vy *= friction; | |
| // Apply gravity (slight downward pull) | |
| vy += gravity; | |
| // Update position | |
| disc.x += vx; | |
| disc.y += vy; | |
| // Check boundaries | |
| const containerWidth = gameContainer.offsetWidth; | |
| const containerHeight = gameContainer.offsetHeight; | |
| const discSize = parseInt(element.style.width); | |
| // Bounce off walls | |
| if (disc.x - discSize/2 < 5 || disc.x + discSize/2 > 95) { | |
| vx *= -0.8; // Bounce with some energy loss | |
| } | |
| if (disc.y - discSize/2 < 5 || disc.y + discSize/2 > 95) { | |
| vy *= -0.8; // Bounce with some energy loss | |
| } | |
| // Check if pocketed | |
| if (checkPocketed(disc)) { | |
| disc.pocketed = true; | |
| element.style.display = 'none'; | |
| // Update score | |
| if (disc.type === 'queen') { | |
| gameState.queenPocketed = true; | |
| } else if (disc.type === 'purple' && gameState.currentPlayer === 'Ash') { | |
| gameState.scores.Ash++; | |
| } else if ((disc.type === 'green' || disc.type === 'white') && gameState.currentPlayer === 'MD') { | |
| gameState.scores.MD++; | |
| } | |
| updateScores(); | |
| } | |
| // Update element position | |
| element.style.left = `${disc.x}%`; | |
| element.style.top = `${disc.y}%`; | |
| // Store velocity for next frame | |
| element.dataset.vx = vx; | |
| element.dataset.vy = vy; | |
| // Check if any disc is still moving | |
| if (Math.abs(vx) > 0.01 || Math.abs(vy) > 0.01) { | |
| allStopped = false; | |
| } | |
| }); | |
| // If all discs stopped, switch player | |
| if (allStopped && !gameState.gameOver) { | |
| gameState.currentPlayer = gameState.currentPlayer === 'Ash' ? 'MD' : 'Ash'; | |
| updateGameStatus(); | |
| // Check for game over conditions | |
| if (gameState.queenPocketed) { | |
| // Simple game over condition: queen pocketed and player has at least one disc | |
| const currentPlayerDiscs = gameState.discs.filter(d => | |
| !d.pocketed && | |
| ((d.type === 'purple' && gameState.currentPlayer === 'Ash') || | |
| ((d.type === 'green' || d.type === 'white') && gameState.currentPlayer === 'MD')) | |
| ); | |
| if (currentPlayerDiscs.length === 0) { | |
| gameState.gameOver = true; | |
| updateGameStatus(); | |
| } | |
| } | |
| } else if (!allStopped) { | |
| // Continue animation if discs are still moving | |
| requestAnimationFrame(moveDiscs); | |
| } | |
| } | |
| // Handle striker drag | |
| function handleStrikerDrag(e) { | |
| if (gameState.gameOver) return; | |
| const rect = gameContainer.getBoundingClientRect(); | |
| const x = ((e.clientX || e.touches[0].clientX) - rect.left) / rect.width * 100; | |
| const y = ((e.clientY || e.touches[0].clientY) - rect.top) / rect.height * 100; | |
| // Calculate angle and distance from striker zone center | |
| const zoneCenterX = 30; | |
| const zoneCenterY = 85; | |
| const dx = x - zoneCenterX; | |
| const dy = y - zoneCenterY; | |
| const distance = Math.sqrt(dx * dx + dy * dy); | |
| // Limit striker movement to within the striker zone | |
| if (distance <= 15) { // 15% radius for striker zone | |
| striker.style.left = `${x}%`; | |
| striker.style.top = `${y}%`; | |
| // Update striker position in game state | |
| gameState.striker.x = x; | |
| gameState.striker.y = y; | |
| // Calculate angle for trajectory | |
| const angle = Math.atan2(dy, dx); | |
| gameState.striker.angle = angle; | |
| // Calculate power based on distance from center | |
| const power = Math.min(distance / 15 * 100, 100); | |
| gameState.striker.power = power; | |
| // Update trajectory line | |
| updateTrajectory(); | |
| } | |
| } | |
| // Update trajectory line | |
| function updateTrajectory() { | |
| const angle = gameState.striker.angle; | |
| const power = gameState.striker.power; | |
| // Calculate end point of trajectory | |
| const length = 50 + power * 0.5; // Base length + power factor | |
| const endX = gameState.striker.x + Math.cos(angle) * length; | |
| const endY = gameState.striker.y + Math.sin(angle) * length; | |
| // Update trajectory line | |
| trajectoryLine.style.left = `${gameState.striker.x}%`; | |
| trajectoryLine.style.top = `${gameState.striker.y}%`; | |
| trajectoryLine.style.width = `${length}%`; | |
| trajectoryLine.style.transform = `rotate(${angle}rad)`; | |
| trajectoryLine.style.transformOrigin = '0 50%'; | |
| // Update trajectory arrow | |
| trajectoryArrow.style.left = `${endX}%`; | |
| trajectoryArrow.style.top = `${endY}%`; | |
| trajectoryArrow.style.transform = `rotate(${angle + Math.PI/2}rad) translateY(-50%)`; | |
| } | |
| // Handle shoot | |
| function handleShoot() { | |
| if (gameState.gameOver) return; | |
| // Calculate velocity based on angle and power | |
| const angle = gameState.striker.angle; | |
| const power = gameState.striker.power / 20; // Scale down power | |
| const vx = Math.cos(angle) * power; | |
| const vy = Math.sin(angle) * power; | |
| // Apply velocity to striker | |
| striker.dataset.vx = vx; | |
| striker.dataset.vy = vy; | |
| // Add striker to discs for physics | |
| gameState.discs.push({ | |
| element: striker, | |
| x: gameState.striker.x, | |
| y: gameState.striker.y, | |
| type: 'striker', | |
| pocketed: false | |
| }); | |
| // Start disc movement | |
| moveDiscs(); | |
| // Hide trajectory | |
| trajectoryLine.style.display = 'none'; | |
| trajectoryArrow.style.display = 'none'; | |
| } | |
| // AI opponent move | |
| function aiMove() { | |
| if (gameState.currentPlayer !== 'MD' || gameState.gameOver) return; | |
| gameStatus.textContent = "AI is thinking..."; | |
| // Simple AI: target a random disc | |
| setTimeout(() => { | |
| const targetDisc = gameState.discs.find(d => | |
| !d.pocketed && | |
| (d.type === 'green' || d.type === 'white') && | |
| d.element.style.display !== 'none' | |
| ); | |
| if (targetDisc) { | |
| // Calculate angle to target disc | |
| const dx = targetDisc.x - 30; // Striker zone center X | |
| const dy = targetDisc.y - 85; // Striker zone center Y | |
| const angle = Math.atan2(dy, dx); | |
| // Set striker position (slightly offset from center) | |
| const offsetX = 30 + Math.cos(angle) * 5; | |
| const offsetY = 85 + Math.sin(angle) * 5; | |
| striker.style.left = `${offsetX}%`; | |
| striker.style.top = `${offsetY}%`; | |
| gameState.striker.x = offsetX; | |
| gameState.striker.y = offsetY; | |
| gameState.striker.angle = angle; | |
| gameState.striker.power = 50 + Math.random() * 50; // Random power | |
| updateTrajectory(); | |
| // Shoot after a short delay | |
| setTimeout(handleShoot, 1000); | |
| } else { | |
| // No discs to target, end turn | |
| gameState.currentPlayer = 'Ash'; | |
| updateGameStatus(); | |
| } | |
| }, 1500); | |
| } | |
| // Event listeners | |
| striker.addEventListener('mousedown', () => { | |
| gameState.striker.isDragging = true; | |
| trajectoryLine.style.display = 'block'; | |
| trajectoryArrow.style.display = 'block'; | |
| }); | |
| striker.addEventListener('touchstart', (e) => { | |
| e.preventDefault(); | |
| gameState.striker.isDragging = true; | |
| trajectoryLine.style.display = 'block'; | |
| trajectoryArrow.style.display = 'block'; | |
| }); | |
| document.addEventListener('mousemove', (e) => { | |
| if (gameState.striker.isDragging) { | |
| handleStrikerDrag(e); | |
| } | |
| }); | |
| document.addEventListener('touchmove', (e) => { | |
| if (gameState.striker.isDragging) { | |
| e.preventDefault(); | |
| handleStrikerDrag(e); | |
| } | |
| }); | |
| document.addEventListener('mouseup', () => { | |
| gameState.striker.isDragging = false; | |
| }); | |
| document.addEventListener('touchend', () => { | |
| gameState.striker.isDragging = false; | |
| }); | |
| shootBtn.addEventListener('click', handleShoot); | |
| resetBtn.addEventListener('click', initGame); | |
| aiBtn.addEventListener('click', () => { | |
| if (gameState.currentPlayer === 'MD') { | |
| aiMove(); | |
| } else { | |
| gameStatus.textContent = "It's not the AI's turn yet!"; | |
| } | |
| }); | |
| // Initialize the game | |
| initGame(); | |
| }); | |
| </script> | |
| </body> | |
| </html> |