|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Battleship Game</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<style> |
|
|
.cell { |
|
|
width: 40px; |
|
|
height: 40px; |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
.cell:hover:not(.hit):not(.miss) { |
|
|
transform: scale(1.1); |
|
|
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); |
|
|
} |
|
|
.ship { |
|
|
background-color: #4b5563; |
|
|
} |
|
|
.hit { |
|
|
background-color: #ef4444; |
|
|
position: relative; |
|
|
} |
|
|
.hit::after { |
|
|
content: "✖"; |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
color: white; |
|
|
font-size: 20px; |
|
|
} |
|
|
.miss { |
|
|
background-color: #60a5fa; |
|
|
position: relative; |
|
|
} |
|
|
.miss::after { |
|
|
content: "•"; |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
color: white; |
|
|
font-size: 20px; |
|
|
} |
|
|
@media (max-width: 768px) { |
|
|
.cell { |
|
|
width: 30px; |
|
|
height: 30px; |
|
|
} |
|
|
} |
|
|
@media (max-width: 480px) { |
|
|
.cell { |
|
|
width: 25px; |
|
|
height: 25px; |
|
|
} |
|
|
} |
|
|
.ship-preview { |
|
|
opacity: 0.7; |
|
|
} |
|
|
.ship-placing { |
|
|
background-color: #10b981; |
|
|
} |
|
|
.ship-invalid { |
|
|
background-color: #f59e0b; |
|
|
} |
|
|
.ship-sunk { |
|
|
background-color: #991b1b; |
|
|
} |
|
|
.ship-sunk::after { |
|
|
content: "☠"; |
|
|
position: absolute; |
|
|
top: 50%; |
|
|
left: 50%; |
|
|
transform: translate(-50%, -50%); |
|
|
color: white; |
|
|
font-size: 20px; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4"> |
|
|
<div class="container mx-auto max-w-6xl"> |
|
|
<h1 class="text-4xl font-bold text-center mb-8 text-blue-400">Battleship Game</h1> |
|
|
|
|
|
<div id="setup-screen" class="text-center mb-8"> |
|
|
<h2 class="text-2xl font-semibold mb-4">Place Your Ships</h2> |
|
|
<div class="flex flex-wrap justify-center gap-4 mb-6"> |
|
|
<div id="carrier" class="flex items-center bg-gray-800 p-2 rounded cursor-move" draggable="true" data-size="5"> |
|
|
<div class="flex"> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
</div> |
|
|
<span class="ml-2">Carrier (5)</span> |
|
|
</div> |
|
|
<div id="battleship" class="flex items-center bg-gray-800 p-2 rounded cursor-move" draggable="true" data-size="4"> |
|
|
<div class="flex"> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
</div> |
|
|
<span class="ml-2">Battleship (4)</span> |
|
|
</div> |
|
|
<div id="cruiser" class="flex items-center bg-gray-800 p-2 rounded cursor-move" draggable="true" data-size="3"> |
|
|
<div class="flex"> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
</div> |
|
|
<span class="ml-2">Cruiser (3)</span> |
|
|
</div> |
|
|
<div id="submarine" class="flex items-center bg-gray-800 p-2 rounded cursor-move" draggable="true" data-size="3"> |
|
|
<div class="flex"> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
</div> |
|
|
<span class="ml-2">Submarine (3)</span> |
|
|
</div> |
|
|
<div id="destroyer" class="flex items-center bg-gray-800 p-2 rounded cursor-move" draggable="true" data-size="2"> |
|
|
<div class="flex"> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
<div class="cell ship-preview bg-gray-600 border border-gray-400"></div> |
|
|
</div> |
|
|
<span class="ml-2">Destroyer (2)</span> |
|
|
</div> |
|
|
</div> |
|
|
<div class="flex flex-col md:flex-row justify-center gap-8 mb-6"> |
|
|
<div> |
|
|
<h3 class="text-xl font-medium mb-2">Your Board</h3> |
|
|
<div id="player-board" class="grid grid-cols-10 gap-0 border-2 border-blue-400 bg-blue-900 bg-opacity-20"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<div class="mt-8 md:mt-0"> |
|
|
<div class="flex items-center justify-between mb-2"> |
|
|
<h3 class="text-xl font-medium">Orientation</h3> |
|
|
<button id="rotate-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded"> |
|
|
Horizontal ↔ |
|
|
</button> |
|
|
</div> |
|
|
<div id="instructions" class="bg-gray-800 p-4 rounded-lg mb-4"> |
|
|
<p class="mb-2">Drag and drop ships onto your board.</p> |
|
|
<p>Click the button above to change ship orientation.</p> |
|
|
</div> |
|
|
<button id="start-game-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg text-lg font-semibold hidden"> |
|
|
Start Game |
|
|
</button> |
|
|
<button id="randomize-btn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg mr-2"> |
|
|
Randomize |
|
|
</button> |
|
|
<button id="reset-btn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg"> |
|
|
Reset |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="game-screen" class="hidden"> |
|
|
<div class="flex flex-col md:flex-row justify-center gap-8 mb-8"> |
|
|
<div> |
|
|
<h3 class="text-xl font-medium mb-2">Your Board</h3> |
|
|
<div id="player-game-board" class="grid grid-cols-10 gap-0 border-2 border-blue-400 bg-blue-900 bg-opacity-20"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
<h3 class="text-xl font-medium mb-2">Enemy Board</h3> |
|
|
<div id="enemy-board" class="grid grid-cols-10 gap-0 border-2 border-red-400 bg-red-900 bg-opacity-20"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="text-center mt-6"> |
|
|
<div id="game-status" class="text-2xl font-semibold mb-4 text-yellow-400"></div> |
|
|
<div class="flex justify-center gap-4"> |
|
|
<button id="new-game-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg"> |
|
|
New Game |
|
|
</button> |
|
|
<button id="show-ships-btn" class="bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded-lg"> |
|
|
Show Enemy Ships |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="game-over-screen" class="hidden text-center mt-8"> |
|
|
<h2 id="game-result" class="text-3xl font-bold mb-6"></h2> |
|
|
<div class="flex justify-center gap-4"> |
|
|
<button id="play-again-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-3 rounded-lg text-lg font-semibold"> |
|
|
Play Again |
|
|
</button> |
|
|
<button id="main-menu-btn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg text-lg font-semibold"> |
|
|
Main Menu |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
|
|
const gameState = { |
|
|
playerBoard: [], |
|
|
enemyBoard: [], |
|
|
playerShips: [], |
|
|
enemyShips: [], |
|
|
currentShip: null, |
|
|
currentShipSize: 0, |
|
|
currentOrientation: 'horizontal', |
|
|
gamePhase: 'setup', |
|
|
playerTurn: true, |
|
|
enemyBoardAttacks: [], |
|
|
shipsToPlace: [ |
|
|
{ id: 'carrier', name: 'Carrier', size: 5 }, |
|
|
{ id: 'battleship', name: 'Battleship', size: 4 }, |
|
|
{ id: 'cruiser', name: 'Cruiser', size: 3 }, |
|
|
{ id: 'submarine', name: 'Submarine', size: 3 }, |
|
|
{ id: 'destroyer', name: 'Destroyer', size: 2 } |
|
|
], |
|
|
placedShips: 0 |
|
|
}; |
|
|
|
|
|
|
|
|
const setupScreen = document.getElementById('setup-screen'); |
|
|
const gameScreen = document.getElementById('game-screen'); |
|
|
const gameOverScreen = document.getElementById('game-over-screen'); |
|
|
const playerBoard = document.getElementById('player-board'); |
|
|
const playerGameBoard = document.getElementById('player-game-board'); |
|
|
const enemyBoard = document.getElementById('enemy-board'); |
|
|
const rotateBtn = document.getElementById('rotate-btn'); |
|
|
const startGameBtn = document.getElementById('start-game-btn'); |
|
|
const randomizeBtn = document.getElementById('randomize-btn'); |
|
|
const resetBtn = document.getElementById('reset-btn'); |
|
|
const newGameBtn = document.getElementById('new-game-btn'); |
|
|
const showShipsBtn = document.getElementById('show-ships-btn'); |
|
|
const playAgainBtn = document.getElementById('play-again-btn'); |
|
|
const mainMenuBtn = document.getElementById('main-menu-btn'); |
|
|
const gameStatus = document.getElementById('game-status'); |
|
|
const gameResult = document.getElementById('game-result'); |
|
|
|
|
|
|
|
|
function initializeBoards() { |
|
|
|
|
|
playerBoard.innerHTML = ''; |
|
|
playerGameBoard.innerHTML = ''; |
|
|
enemyBoard.innerHTML = ''; |
|
|
|
|
|
|
|
|
gameState.playerBoard = Array(10).fill().map(() => Array(10).fill(0)); |
|
|
gameState.enemyBoard = Array(10).fill().map(() => Array(10).fill(0)); |
|
|
gameState.playerShips = []; |
|
|
gameState.enemyShips = []; |
|
|
gameState.placedShips = 0; |
|
|
|
|
|
|
|
|
for (let row = 0; row < 10; row++) { |
|
|
for (let col = 0; col < 10; col++) { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'cell border border-gray-400 bg-blue-900 bg-opacity-30'; |
|
|
cell.dataset.row = row; |
|
|
cell.dataset.col = col; |
|
|
cell.addEventListener('dragover', handleDragOver); |
|
|
cell.addEventListener('drop', handleDrop); |
|
|
cell.addEventListener('dragenter', handleDragEnter); |
|
|
cell.addEventListener('dragleave', handleDragLeave); |
|
|
playerBoard.appendChild(cell); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (let row = 0; row < 10; row++) { |
|
|
for (let col = 0; col < 10; col++) { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'cell border border-gray-400 bg-blue-900 bg-opacity-30'; |
|
|
cell.dataset.row = row; |
|
|
cell.dataset.col = col; |
|
|
playerGameBoard.appendChild(cell); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (let row = 0; row < 10; row++) { |
|
|
for (let col = 0; col < 10; col++) { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'cell border border-gray-400 bg-red-900 bg-opacity-30'; |
|
|
cell.dataset.row = row; |
|
|
cell.dataset.col = col; |
|
|
cell.addEventListener('click', handleEnemyCellClick); |
|
|
enemyBoard.appendChild(cell); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setupDragAndDrop() { |
|
|
const ships = document.querySelectorAll('[draggable="true"]'); |
|
|
|
|
|
ships.forEach(ship => { |
|
|
ship.addEventListener('dragstart', handleDragStart); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function handleDragStart(e) { |
|
|
gameState.currentShip = e.target.id; |
|
|
gameState.currentShipSize = parseInt(e.target.dataset.size); |
|
|
e.dataTransfer.setData('text/plain', e.target.id); |
|
|
e.dataTransfer.effectAllowed = 'copy'; |
|
|
} |
|
|
|
|
|
|
|
|
function handleDragOver(e) { |
|
|
e.preventDefault(); |
|
|
e.dataTransfer.dropEffect = 'copy'; |
|
|
} |
|
|
|
|
|
|
|
|
function handleDragEnter(e) { |
|
|
if (gameState.currentShip) { |
|
|
const row = parseInt(e.target.dataset.row); |
|
|
const col = parseInt(e.target.dataset.col); |
|
|
|
|
|
|
|
|
highlightShipPlacement(row, col, gameState.currentShipSize, gameState.currentOrientation); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleDragLeave(e) { |
|
|
|
|
|
const cells = playerBoard.querySelectorAll('.cell'); |
|
|
cells.forEach(cell => { |
|
|
cell.classList.remove('ship-placing', 'ship-invalid'); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function handleDrop(e) { |
|
|
e.preventDefault(); |
|
|
|
|
|
const row = parseInt(e.target.dataset.row); |
|
|
const col = parseInt(e.target.dataset.col); |
|
|
const shipId = e.dataTransfer.getData('text/plain'); |
|
|
const shipSize = gameState.currentShipSize; |
|
|
|
|
|
|
|
|
if (canPlaceShip(row, col, shipSize, gameState.currentOrientation)) { |
|
|
|
|
|
placeShip(row, col, shipSize, gameState.currentOrientation, shipId); |
|
|
|
|
|
|
|
|
const shipElement = document.getElementById(shipId); |
|
|
shipElement.style.display = 'none'; |
|
|
|
|
|
|
|
|
gameState.placedShips++; |
|
|
|
|
|
|
|
|
if (gameState.placedShips === gameState.shipsToPlace.length) { |
|
|
startGameBtn.classList.remove('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const cells = playerBoard.querySelectorAll('.cell'); |
|
|
cells.forEach(cell => { |
|
|
cell.classList.remove('ship-placing', 'ship-invalid'); |
|
|
}); |
|
|
|
|
|
gameState.currentShip = null; |
|
|
} |
|
|
|
|
|
|
|
|
function highlightShipPlacement(row, col, size, orientation) { |
|
|
|
|
|
const cells = playerBoard.querySelectorAll('.cell'); |
|
|
cells.forEach(cell => { |
|
|
cell.classList.remove('ship-placing', 'ship-invalid'); |
|
|
}); |
|
|
|
|
|
|
|
|
const cellsToHighlight = []; |
|
|
let isValid = true; |
|
|
|
|
|
if (orientation === 'horizontal') { |
|
|
for (let i = 0; i < size; i++) { |
|
|
const newCol = col + i; |
|
|
if (newCol >= 10) { |
|
|
isValid = false; |
|
|
break; |
|
|
} |
|
|
|
|
|
|
|
|
if (gameState.playerBoard[row][newCol] !== 0) { |
|
|
isValid = false; |
|
|
} |
|
|
|
|
|
cellsToHighlight.push({row, col: newCol}); |
|
|
} |
|
|
} else { |
|
|
for (let i = 0; i < size; i++) { |
|
|
const newRow = row + i; |
|
|
if (newRow >= 10) { |
|
|
isValid = false; |
|
|
break; |
|
|
} |
|
|
|
|
|
|
|
|
if (gameState.playerBoard[newRow][col] !== 0) { |
|
|
isValid = false; |
|
|
} |
|
|
|
|
|
cellsToHighlight.push({row: newRow, col}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
cellsToHighlight.forEach(({row, col}) => { |
|
|
const index = row * 10 + col; |
|
|
const cell = playerBoard.children[index]; |
|
|
|
|
|
if (isValid) { |
|
|
cell.classList.add('ship-placing'); |
|
|
} else { |
|
|
cell.classList.add('ship-invalid'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function canPlaceShip(row, col, size, orientation) { |
|
|
if (orientation === 'horizontal') { |
|
|
|
|
|
if (col + size > 10) return false; |
|
|
|
|
|
|
|
|
for (let i = 0; i < size; i++) { |
|
|
if (gameState.playerBoard[row][col + i] !== 0) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} else { |
|
|
|
|
|
if (row + size > 10) return false; |
|
|
|
|
|
|
|
|
for (let i = 0; i < size; i++) { |
|
|
if (gameState.playerBoard[row + i][col] !== 0) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
function placeShip(row, col, size, orientation, shipId) { |
|
|
const shipCells = []; |
|
|
|
|
|
if (orientation === 'horizontal') { |
|
|
for (let i = 0; i < size; i++) { |
|
|
const newCol = col + i; |
|
|
if (newCol < 10) { |
|
|
gameState.playerBoard[row][newCol] = shipId; |
|
|
shipCells.push({row, col: newCol}); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
for (let i = 0; i < size; i++) { |
|
|
const newRow = row + i; |
|
|
if (newRow < 10) { |
|
|
gameState.playerBoard[newRow][col] = shipId; |
|
|
shipCells.push({row: newRow, col}); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
gameState.playerShips.push({ |
|
|
id: shipId, |
|
|
size: size, |
|
|
hits: 0, |
|
|
cells: shipCells |
|
|
}); |
|
|
|
|
|
|
|
|
updatePlayerBoard(); |
|
|
} |
|
|
|
|
|
|
|
|
function updatePlayerBoard() { |
|
|
for (let row = 0; row < 10; row++) { |
|
|
for (let col = 0; col < 10; col++) { |
|
|
const index = row * 10 + col; |
|
|
const cell = playerBoard.children[index]; |
|
|
const gameCell = playerGameBoard.children[index]; |
|
|
|
|
|
|
|
|
cell.classList.remove('ship'); |
|
|
gameCell.classList.remove('ship'); |
|
|
|
|
|
|
|
|
if (gameState.playerBoard[row][col] !== 0) { |
|
|
cell.classList.add('ship'); |
|
|
gameCell.classList.add('ship'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function randomizeShips() { |
|
|
|
|
|
gameState.playerBoard = Array(10).fill().map(() => Array(10).fill(0)); |
|
|
gameState.playerShips = []; |
|
|
gameState.placedShips = 0; |
|
|
|
|
|
|
|
|
gameState.shipsToPlace.forEach(ship => { |
|
|
document.getElementById(ship.id).style.display = 'flex'; |
|
|
}); |
|
|
|
|
|
|
|
|
gameState.shipsToPlace.forEach(ship => { |
|
|
let placed = false; |
|
|
let attempts = 0; |
|
|
const maxAttempts = 100; |
|
|
|
|
|
while (!placed && attempts < maxAttempts) { |
|
|
const orientation = Math.random() < 0.5 ? 'horizontal' : 'vertical'; |
|
|
const row = Math.floor(Math.random() * 10); |
|
|
const col = Math.floor(Math.random() * 10); |
|
|
|
|
|
if (canPlaceShip(row, col, ship.size, orientation)) { |
|
|
placeShip(row, col, ship.size, orientation, ship.id); |
|
|
placed = true; |
|
|
gameState.placedShips++; |
|
|
} |
|
|
|
|
|
attempts++; |
|
|
} |
|
|
|
|
|
if (!placed) { |
|
|
console.error(`Could not place ${ship.name} after ${maxAttempts} attempts`); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
updatePlayerBoard(); |
|
|
|
|
|
|
|
|
if (gameState.placedShips === gameState.shipsToPlace.length) { |
|
|
startGameBtn.classList.remove('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function resetGame() { |
|
|
|
|
|
gameState.playerBoard = Array(10).fill().map(() => Array(10).fill(0)); |
|
|
gameState.playerShips = []; |
|
|
gameState.placedShips = 0; |
|
|
gameState.currentShip = null; |
|
|
gameState.currentShipSize = 0; |
|
|
gameState.currentOrientation = 'horizontal'; |
|
|
|
|
|
|
|
|
gameState.shipsToPlace.forEach(ship => { |
|
|
document.getElementById(ship.id).style.display = 'flex'; |
|
|
}); |
|
|
|
|
|
|
|
|
startGameBtn.classList.add('hidden'); |
|
|
|
|
|
|
|
|
updatePlayerBoard(); |
|
|
|
|
|
|
|
|
rotateBtn.textContent = 'Horizontal ↔'; |
|
|
} |
|
|
|
|
|
|
|
|
function startGame() { |
|
|
|
|
|
setupEnemyShips(); |
|
|
|
|
|
|
|
|
setupScreen.classList.add('hidden'); |
|
|
gameScreen.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
gameStatus.textContent = "Your turn - Attack the enemy!"; |
|
|
gameState.playerTurn = true; |
|
|
gameState.gamePhase = 'playing'; |
|
|
|
|
|
|
|
|
updatePlayerGameBoard(); |
|
|
|
|
|
|
|
|
gameState.enemyBoardAttacks = Array(10).fill().map(() => Array(10).fill(false)); |
|
|
} |
|
|
|
|
|
|
|
|
function setupEnemyShips() { |
|
|
gameState.enemyBoard = Array(10).fill().map(() => Array(10).fill(0)); |
|
|
gameState.enemyShips = []; |
|
|
|
|
|
|
|
|
gameState.shipsToPlace.forEach(ship => { |
|
|
let placed = false; |
|
|
let attempts = 0; |
|
|
const maxAttempts = 500; |
|
|
|
|
|
while (!placed && attempts < maxAttempts) { |
|
|
const orientation = Math.random() < 0.5 ? 'horizontal' : 'vertical'; |
|
|
|
|
|
const row = orientation === 'vertical' ? |
|
|
Math.floor(Math.random() * (10 - ship.size)) : |
|
|
Math.floor(Math.random() * 10); |
|
|
const col = orientation === 'horizontal' ? |
|
|
Math.floor(Math.random() * (10 - ship.size)) : |
|
|
Math.floor(Math.random() * 10); |
|
|
|
|
|
if (canPlaceEnemyShip(row, col, ship.size, orientation)) { |
|
|
placeEnemyShip(row, col, ship.size, orientation, ship.id); |
|
|
placed = true; |
|
|
} |
|
|
|
|
|
attempts++; |
|
|
} |
|
|
|
|
|
if (!placed) { |
|
|
console.error(`Could not place enemy ${ship.name} after ${maxAttempts} attempts`); |
|
|
|
|
|
gameState.enemyBoard = Array(10).fill().map(() => Array(10).fill(0)); |
|
|
gameState.enemyShips = []; |
|
|
setupEnemyShips(); |
|
|
return; |
|
|
} |
|
|
}); |
|
|
|
|
|
console.log("Enemy ships placed:", gameState.enemyShips); |
|
|
} |
|
|
|
|
|
|
|
|
function canPlaceEnemyShip(row, col, size, orientation) { |
|
|
if (orientation === 'horizontal') { |
|
|
|
|
|
if (col + size > 10) return false; |
|
|
|
|
|
|
|
|
for (let i = 0; i < size; i++) { |
|
|
if (gameState.enemyBoard[row][col + i] !== 0) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} else { |
|
|
|
|
|
if (row + size > 10) return false; |
|
|
|
|
|
|
|
|
for (let i = 0; i < size; i++) { |
|
|
if (gameState.enemyBoard[row + i][col] !== 0) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
|
|
|
function placeEnemyShip(row, col, size, orientation, shipId) { |
|
|
const shipCells = []; |
|
|
|
|
|
if (orientation === 'horizontal') { |
|
|
for (let i = 0; i < size; i++) { |
|
|
const newCol = col + i; |
|
|
if (newCol < 10) { |
|
|
gameState.enemyBoard[row][newCol] = shipId; |
|
|
shipCells.push({row, col: newCol}); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
for (let i = 0; i < size; i++) { |
|
|
const newRow = row + i; |
|
|
if (newRow < 10) { |
|
|
gameState.enemyBoard[newRow][col] = shipId; |
|
|
shipCells.push({row: newRow, col}); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (shipCells.length === size) { |
|
|
|
|
|
gameState.enemyShips.push({ |
|
|
id: shipId, |
|
|
name: gameState.shipsToPlace.find(s => s.id === shipId).name, |
|
|
size: size, |
|
|
hits: 0, |
|
|
cells: shipCells |
|
|
}); |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
function updatePlayerGameBoard() { |
|
|
for (let row = 0; row < 10; row++) { |
|
|
for (let col = 0; col < 10; col++) { |
|
|
const index = row * 10 + col; |
|
|
const cell = playerGameBoard.children[index]; |
|
|
|
|
|
|
|
|
cell.className = 'cell border border-gray-400 bg-blue-900 bg-opacity-30'; |
|
|
|
|
|
|
|
|
if (gameState.playerBoard[row][col] !== 0) { |
|
|
|
|
|
const cellHit = cell.classList.contains('hit'); |
|
|
if (cellHit) { |
|
|
cell.classList.add('ship'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleEnemyCellClick(e) { |
|
|
if (!gameState.playerTurn || gameState.gamePhase !== 'playing') return; |
|
|
|
|
|
const row = parseInt(e.target.dataset.row); |
|
|
const col = parseInt(e.target.dataset.col); |
|
|
|
|
|
|
|
|
if (e.target.classList.contains('hit') || e.target.classList.contains('miss')) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const hit = gameState.enemyBoard[row][col] !== 0; |
|
|
gameState.enemyBoardAttacks[row][col] = true; |
|
|
|
|
|
if (hit) { |
|
|
|
|
|
e.target.classList.add('hit'); |
|
|
|
|
|
|
|
|
const shipId = gameState.enemyBoard[row][col]; |
|
|
const ship = gameState.enemyShips.find(s => s.id === shipId); |
|
|
|
|
|
|
|
|
ship.hits++; |
|
|
|
|
|
|
|
|
if (ship.hits === ship.size) { |
|
|
|
|
|
ship.cells.forEach(({row, col}) => { |
|
|
const index = row * 10 + col; |
|
|
const cell = enemyBoard.children[index]; |
|
|
cell.classList.add('ship-sunk'); |
|
|
}); |
|
|
|
|
|
gameStatus.textContent = `You sunk the enemy ${shipId}!`; |
|
|
|
|
|
|
|
|
if (gameState.enemyShips.every(ship => ship.hits === ship.size)) { |
|
|
endGame(true); |
|
|
return; |
|
|
} |
|
|
} else { |
|
|
gameStatus.textContent = "Hit! Attack again!"; |
|
|
} |
|
|
} else { |
|
|
|
|
|
e.target.classList.add('miss'); |
|
|
gameStatus.textContent = "Miss! Enemy's turn."; |
|
|
gameState.playerTurn = false; |
|
|
|
|
|
|
|
|
setTimeout(enemyTurn, 1000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function enemyTurn() { |
|
|
|
|
|
let row, col, index, cell; |
|
|
let validAttack = false; |
|
|
let attempts = 0; |
|
|
const maxAttempts = 100; |
|
|
|
|
|
|
|
|
while (!validAttack && attempts < maxAttempts) { |
|
|
row = Math.floor(Math.random() * 10); |
|
|
col = Math.floor(Math.random() * 10); |
|
|
index = row * 10 + col; |
|
|
cell = playerGameBoard.children[index]; |
|
|
|
|
|
if (!cell.classList.contains('hit') && !cell.classList.contains('miss')) { |
|
|
validAttack = true; |
|
|
} |
|
|
|
|
|
attempts++; |
|
|
} |
|
|
|
|
|
|
|
|
if (!validAttack) { |
|
|
console.error("Couldn't find valid attack cell"); |
|
|
gameState.playerTurn = true; |
|
|
gameStatus.textContent = "Your turn - Attack the enemy!"; |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const hit = gameState.playerBoard[row][col] !== 0; |
|
|
|
|
|
if (hit) { |
|
|
|
|
|
cell.classList.add('hit'); |
|
|
|
|
|
|
|
|
const shipId = gameState.playerBoard[row][col]; |
|
|
const ship = gameState.playerShips.find(s => s.id === shipId); |
|
|
|
|
|
|
|
|
ship.hits++; |
|
|
|
|
|
|
|
|
if (ship.hits === ship.size) { |
|
|
|
|
|
ship.cells.forEach(({row, col}) => { |
|
|
const index = row * 10 + col; |
|
|
const cell = playerGameBoard.children[index]; |
|
|
cell.classList.add('ship-sunk'); |
|
|
}); |
|
|
|
|
|
gameStatus.textContent = `Enemy sunk your ${shipId}!`; |
|
|
|
|
|
|
|
|
if (gameState.playerShips.every(ship => ship.hits === ship.size)) { |
|
|
endGame(false); |
|
|
return; |
|
|
} |
|
|
} else { |
|
|
gameStatus.textContent = "Enemy hit your ship! Their turn continues."; |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(enemyTurn, 1000); |
|
|
} else { |
|
|
|
|
|
cell.classList.add('miss'); |
|
|
gameStatus.textContent = "Enemy missed! Your turn."; |
|
|
gameState.playerTurn = true; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function endGame(playerWon) { |
|
|
gameState.gamePhase = 'gameOver'; |
|
|
gameScreen.classList.add('hidden'); |
|
|
gameOverScreen.classList.remove('hidden'); |
|
|
|
|
|
if (playerWon) { |
|
|
gameResult.textContent = "Congratulations! You won!"; |
|
|
gameResult.className = "text-3xl font-bold mb-6 text-green-400"; |
|
|
} else { |
|
|
gameResult.textContent = "Game Over! You lost!"; |
|
|
gameResult.className = "text-3xl font-bold mb-6 text-red-400"; |
|
|
} |
|
|
|
|
|
|
|
|
showEnemyShips(); |
|
|
} |
|
|
|
|
|
|
|
|
function showEnemyShips() { |
|
|
for (let row = 0; row < 10; row++) { |
|
|
for (let col = 0; col < 10; col++) { |
|
|
const index = row * 10 + col; |
|
|
const cell = enemyBoard.children[index]; |
|
|
|
|
|
if (gameState.enemyBoard[row][col] !== 0) { |
|
|
cell.classList.add('ship'); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function toggleShowEnemyShips() { |
|
|
const cells = enemyBoard.querySelectorAll('.cell'); |
|
|
let showing = false; |
|
|
|
|
|
|
|
|
cells.forEach(cell => { |
|
|
if (cell.classList.contains('ship')) { |
|
|
showing = true; |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
cells.forEach(cell => { |
|
|
if (gameState.enemyBoard[cell.dataset.row][cell.dataset.col] !== 0) { |
|
|
if (showing) { |
|
|
cell.classList.remove('ship'); |
|
|
showShipsBtn.textContent = 'Show Enemy Ships'; |
|
|
} else { |
|
|
cell.classList.add('ship'); |
|
|
showShipsBtn.textContent = 'Hide Enemy Ships'; |
|
|
} |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function newGame() { |
|
|
|
|
|
gameState.gamePhase = 'setup'; |
|
|
gameState.playerTurn = true; |
|
|
|
|
|
|
|
|
gameScreen.classList.add('hidden'); |
|
|
gameOverScreen.classList.add('hidden'); |
|
|
setupScreen.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
initializeBoards(); |
|
|
resetGame(); |
|
|
} |
|
|
|
|
|
|
|
|
function playAgain() { |
|
|
|
|
|
gameState.gamePhase = 'setup'; |
|
|
gameState.playerTurn = true; |
|
|
|
|
|
|
|
|
gameOverScreen.classList.add('hidden'); |
|
|
setupScreen.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
initializeBoards(); |
|
|
resetGame(); |
|
|
} |
|
|
|
|
|
|
|
|
function mainMenu() { |
|
|
|
|
|
gameState.gamePhase = 'setup'; |
|
|
gameState.playerTurn = true; |
|
|
|
|
|
|
|
|
gameOverScreen.classList.add('hidden'); |
|
|
setupScreen.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
initializeBoards(); |
|
|
resetGame(); |
|
|
} |
|
|
|
|
|
|
|
|
rotateBtn.addEventListener('click', () => { |
|
|
gameState.currentOrientation = gameState.currentOrientation === 'horizontal' ? 'vertical' : 'horizontal'; |
|
|
rotateBtn.textContent = gameState.currentOrientation === 'horizontal' ? 'Horizontal ↔' : 'Vertical ↕'; |
|
|
}); |
|
|
|
|
|
startGameBtn.addEventListener('click', startGame); |
|
|
randomizeBtn.addEventListener('click', randomizeShips); |
|
|
resetBtn.addEventListener('click', resetGame); |
|
|
newGameBtn.addEventListener('click', newGame); |
|
|
showShipsBtn.addEventListener('click', toggleShowEnemyShips); |
|
|
playAgainBtn.addEventListener('click', playAgain); |
|
|
mainMenuBtn.addEventListener('click', mainMenu); |
|
|
|
|
|
|
|
|
initializeBoards(); |
|
|
setupDragAndDrop(); |
|
|
}); |
|
|
</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://deepsite.hf.co/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://deepsite.hf.co" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://deepsite.hf.co?remix=MikePfunk28/battleship-oldschool" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |