| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Ocean Match-3 Adventure</title> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Arial', sans-serif; |
| background: linear-gradient(135deg, #001f3f 0%, #003366 50%, #006699 100%); |
| min-height: 100vh; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| color: white; |
| overflow: hidden; |
| } |
| |
| .game-container { |
| text-align: center; |
| background: rgba(255, 255, 255, 0.1); |
| backdrop-filter: blur(10px); |
| border-radius: 20px; |
| padding: 30px; |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); |
| position: relative; |
| } |
| |
| h1 { |
| font-size: 2.5em; |
| margin-bottom: 10px; |
| text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); |
| background: linear-gradient(45deg, #00d4ff, #90e0ef); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| } |
| |
| .game-stats { |
| display: grid; |
| grid-template-columns: repeat(4, 1fr); |
| gap: 15px; |
| margin-bottom: 20px; |
| font-size: 1em; |
| font-weight: bold; |
| } |
| |
| .stat-box { |
| background: rgba(255, 255, 255, 0.1); |
| border-radius: 10px; |
| padding: 10px; |
| border: 2px solid rgba(0, 212, 255, 0.3); |
| } |
| |
| .stat-label { |
| font-size: 0.8em; |
| opacity: 0.8; |
| margin-bottom: 5px; |
| } |
| |
| .stat-value { |
| font-size: 1.2em; |
| color: #00d4ff; |
| } |
| |
| .timer { |
| color: #ff6b6b !important; |
| } |
| |
| .timer.warning { |
| animation: timerPulse 1s infinite; |
| } |
| |
| @keyframes timerPulse { |
| 0%, 100% { transform: scale(1); } |
| 50% { transform: scale(1.1); color: #ff4757; } |
| } |
| |
| .level-progress { |
| margin-bottom: 15px; |
| } |
| |
| .progress-bar { |
| width: 100%; |
| height: 8px; |
| background: rgba(255, 255, 255, 0.2); |
| border-radius: 4px; |
| overflow: hidden; |
| } |
| |
| .progress-fill { |
| height: 100%; |
| background: linear-gradient(90deg, #00d4ff, #90e0ef); |
| border-radius: 4px; |
| transition: width 0.3s ease; |
| width: 0%; |
| } |
| |
| .game-board { |
| display: grid; |
| grid-template-columns: repeat(8, 60px); |
| grid-template-rows: repeat(8, 60px); |
| gap: 2px; |
| background: rgba(0, 50, 100, 0.5); |
| padding: 10px; |
| border-radius: 15px; |
| margin: 0 auto; |
| border: 3px solid rgba(255, 255, 255, 0.2); |
| position: relative; |
| } |
| |
| .tile { |
| width: 60px; |
| height: 60px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 2em; |
| background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.05)); |
| border-radius: 10px; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| border: 2px solid transparent; |
| position: relative; |
| overflow: hidden; |
| } |
| |
| .tile:hover { |
| transform: scale(1.05); |
| box-shadow: 0 5px 15px rgba(0, 212, 255, 0.4); |
| } |
| |
| .tile.selected { |
| border-color: #00d4ff; |
| box-shadow: 0 0 20px rgba(0, 212, 255, 0.8); |
| transform: scale(1.1); |
| } |
| |
| .tile.matching { |
| animation: matchPulse 0.6s ease-in-out; |
| background: linear-gradient(135deg, rgba(255, 215, 0, 0.9), rgba(255, 165, 0, 0.9)); |
| z-index: 10; |
| } |
| |
| @keyframes matchPulse { |
| 0% { transform: scale(1); } |
| 50% { transform: scale(1.3); box-shadow: 0 0 30px rgba(255, 215, 0, 0.8); } |
| 100% { transform: scale(1); } |
| } |
| |
| @keyframes fall { |
| from { transform: translateY(-60px); opacity: 0; } |
| to { transform: translateY(0); opacity: 1; } |
| } |
| |
| .tile.falling { |
| animation: fall 0.4s ease-out; |
| } |
| |
| |
| .floating-tile { |
| position: absolute; |
| font-size: 2em; |
| pointer-events: none; |
| z-index: 1000; |
| animation: floatDown 2s ease-in forwards; |
| } |
| |
| @keyframes floatDown { |
| 0% { |
| transform: translateY(0) rotate(0deg); |
| opacity: 1; |
| } |
| 100% { |
| transform: translateY(600px) rotate(360deg); |
| opacity: 0; |
| } |
| } |
| |
| .controls { |
| margin-top: 20px; |
| display: flex; |
| gap: 15px; |
| justify-content: center; |
| } |
| |
| button { |
| padding: 12px 24px; |
| font-size: 1em; |
| font-weight: bold; |
| border: none; |
| border-radius: 25px; |
| cursor: pointer; |
| background: linear-gradient(135deg, #00d4ff, #0099cc); |
| color: white; |
| transition: all 0.3s ease; |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); |
| } |
| |
| button:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3); |
| } |
| |
| button:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| transform: none; |
| } |
| |
| .hint-text { |
| margin-top: 15px; |
| font-size: 0.9em; |
| opacity: 0.8; |
| color: #90e0ef; |
| } |
| |
| .level-complete { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: linear-gradient(135deg, #00d4ff, #0099cc); |
| padding: 40px; |
| border-radius: 20px; |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5); |
| z-index: 2000; |
| animation: levelCompleteAnimation 0.5s ease-out; |
| } |
| |
| @keyframes levelCompleteAnimation { |
| from { transform: translate(-50%, -50%) scale(0); } |
| to { transform: translate(-50%, -50%) scale(1); } |
| } |
| |
| .game-over { |
| position: fixed; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: linear-gradient(135deg, #ff6b6b, #ff4757); |
| padding: 40px; |
| border-radius: 20px; |
| box-shadow: 0 20px 40px rgba(0, 0, 0, 0.5); |
| z-index: 2000; |
| animation: gameOverAnimation 0.5s ease-out; |
| } |
| |
| @keyframes gameOverAnimation { |
| from { transform: translate(-50%, -50%) scale(0) rotate(180deg); } |
| to { transform: translate(-50%, -50%) scale(1) rotate(0deg); } |
| } |
| |
| .sound-toggle { |
| position: absolute; |
| top: 10px; |
| right: 10px; |
| background: rgba(255, 255, 255, 0.2); |
| border: none; |
| border-radius: 50%; |
| width: 40px; |
| height: 40px; |
| cursor: pointer; |
| font-size: 1.2em; |
| color: white; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="game-container"> |
| <button class="sound-toggle" onclick="toggleSound()" id="soundToggle">π</button> |
| |
| <h1>π Ocean Match-3 π</h1> |
| |
| <div class="game-stats"> |
| <div class="stat-box"> |
| <div class="stat-label">Level</div> |
| <div class="stat-value" id="level">1</div> |
| </div> |
| <div class="stat-box"> |
| <div class="stat-label">Score</div> |
| <div class="stat-value" id="score">0</div> |
| </div> |
| <div class="stat-box"> |
| <div class="stat-label">Moves</div> |
| <div class="stat-value" id="moves">30</div> |
| </div> |
| <div class="stat-box"> |
| <div class="stat-label">Time</div> |
| <div class="stat-value timer" id="timer">5:00</div> |
| </div> |
| </div> |
|
|
| <div class="level-progress"> |
| <div class="stat-label">Level Progress</div> |
| <div class="progress-bar"> |
| <div class="progress-fill" id="progressFill"></div> |
| </div> |
| <div style="margin-top: 5px; font-size: 0.9em;"> |
| <span id="matchesLeft">20</span> matches needed |
| </div> |
| </div> |
| |
| <div class="game-board" id="gameBoard"></div> |
| |
| <div class="controls"> |
| <button onclick="newGame()">New Game</button> |
| <button onclick="findHint()">Hint</button> |
| <button onclick="pauseGame()" id="pauseBtn">Pause</button> |
| </div> |
| |
| <div class="hint-text"> |
| Swap adjacent tiles to match 3 or more of the same sea creatures! |
| </div> |
| </div> |
|
|
| <script> |
| const BOARD_SIZE = 8; |
| const TILE_TYPES = ['π ', 'π', 'π‘', 'π¦', 'π', 'π¦']; |
| |
| let board = []; |
| let selectedTile = null; |
| let score = 0; |
| let moves = 30; |
| let level = 1; |
| let gameActive = true; |
| let gamePaused = false; |
| let soundEnabled = true; |
| let timeLeft = 300; |
| let gameTimer = null; |
| let matchesNeeded = 20; |
| let matchesMade = 0; |
| |
| |
| const audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| |
| function playSound(frequency, duration, type = 'sine') { |
| if (!soundEnabled) return; |
| |
| const oscillator = audioContext.createOscillator(); |
| const gainNode = audioContext.createGain(); |
| |
| oscillator.connect(gainNode); |
| gainNode.connect(audioContext.destination); |
| |
| oscillator.frequency.value = frequency; |
| oscillator.type = type; |
| |
| gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); |
| gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration); |
| |
| oscillator.start(audioContext.currentTime); |
| oscillator.stop(audioContext.currentTime + duration); |
| } |
| |
| function playMatchSound() { |
| |
| playSound(523, 0.1); |
| setTimeout(() => playSound(659, 0.1), 50); |
| setTimeout(() => playSound(784, 0.2), 100); |
| } |
| |
| function playNoMatchSound() { |
| |
| playSound(400, 0.3, 'sawtooth'); |
| setTimeout(() => playSound(300, 0.3, 'sawtooth'), 100); |
| } |
| |
| function playLevelCompleteSound() { |
| |
| const notes = [523, 659, 784, 1047]; |
| notes.forEach((note, i) => { |
| setTimeout(() => playSound(note, 0.3), i * 100); |
| }); |
| } |
| |
| function playGameOverSound() { |
| |
| playSound(200, 0.5, 'sawtooth'); |
| setTimeout(() => playSound(150, 0.8, 'sawtooth'), 200); |
| } |
| |
| function toggleSound() { |
| soundEnabled = !soundEnabled; |
| document.getElementById('soundToggle').textContent = soundEnabled ? 'π' : 'π'; |
| } |
| |
| function startTimer() { |
| gameTimer = setInterval(() => { |
| if (!gamePaused && gameActive) { |
| timeLeft--; |
| updateTimerDisplay(); |
| |
| if (timeLeft <= 60) { |
| document.getElementById('timer').classList.add('warning'); |
| } |
| |
| if (timeLeft <= 0) { |
| gameOver(); |
| } |
| } |
| }, 1000); |
| } |
| |
| function updateTimerDisplay() { |
| const minutes = Math.floor(timeLeft / 60); |
| const seconds = timeLeft % 60; |
| document.getElementById('timer').textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; |
| } |
| |
| function pauseGame() { |
| gamePaused = !gamePaused; |
| const pauseBtn = document.getElementById('pauseBtn'); |
| pauseBtn.textContent = gamePaused ? 'Resume' : 'Pause'; |
| } |
| |
| function createFloatingTile(tileType, startX, startY) { |
| const floatingTile = document.createElement('div'); |
| floatingTile.className = 'floating-tile'; |
| floatingTile.textContent = tileType; |
| floatingTile.style.left = startX + 'px'; |
| floatingTile.style.top = startY + 'px'; |
| |
| document.body.appendChild(floatingTile); |
| |
| setTimeout(() => { |
| floatingTile.remove(); |
| }, 2000); |
| } |
| |
| function initializeBoard() { |
| const gameBoard = document.getElementById('gameBoard'); |
| gameBoard.innerHTML = ''; |
| board = []; |
| |
| for (let row = 0; row < BOARD_SIZE; row++) { |
| board[row] = []; |
| for (let col = 0; col < BOARD_SIZE; col++) { |
| let tileType; |
| do { |
| tileType = TILE_TYPES[Math.floor(Math.random() * TILE_TYPES.length)]; |
| } while (wouldCreateMatch(row, col, tileType)); |
| |
| board[row][col] = tileType; |
| |
| const tile = document.createElement('div'); |
| tile.className = 'tile'; |
| tile.textContent = tileType; |
| tile.dataset.row = row; |
| tile.dataset.col = col; |
| tile.addEventListener('click', handleTileClick); |
| gameBoard.appendChild(tile); |
| } |
| } |
| } |
| |
| function wouldCreateMatch(row, col, tileType) { |
| let horizontalCount = 1; |
| for (let c = col - 1; c >= 0 && board[row][c] === tileType; c--) { |
| horizontalCount++; |
| } |
| for (let c = col + 1; c < BOARD_SIZE && board[row] && board[row][c] === tileType; c++) { |
| horizontalCount++; |
| } |
| |
| let verticalCount = 1; |
| for (let r = row - 1; r >= 0 && board[r][col] === tileType; r--) { |
| verticalCount++; |
| } |
| for (let r = row + 1; r < BOARD_SIZE && board[r] && board[r][col] === tileType; r++) { |
| verticalCount++; |
| } |
| |
| return horizontalCount >= 3 || verticalCount >= 3; |
| } |
| |
| function handleTileClick(event) { |
| if (!gameActive || gamePaused) return; |
| |
| const clickedTile = event.target; |
| const row = parseInt(clickedTile.dataset.row); |
| const col = parseInt(clickedTile.dataset.col); |
| |
| if (selectedTile === null) { |
| selectedTile = { row, col, element: clickedTile }; |
| clickedTile.classList.add('selected'); |
| } else { |
| if (selectedTile.row === row && selectedTile.col === col) { |
| selectedTile.element.classList.remove('selected'); |
| selectedTile = null; |
| } else if (isAdjacent(selectedTile.row, selectedTile.col, row, col)) { |
| attemptSwap(selectedTile.row, selectedTile.col, row, col); |
| selectedTile.element.classList.remove('selected'); |
| selectedTile = null; |
| } else { |
| selectedTile.element.classList.remove('selected'); |
| selectedTile = { row, col, element: clickedTile }; |
| clickedTile.classList.add('selected'); |
| } |
| } |
| } |
| |
| function isAdjacent(row1, col1, row2, col2) { |
| const rowDiff = Math.abs(row1 - row2); |
| const colDiff = Math.abs(col1 - col2); |
| return (rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1); |
| } |
| |
| function attemptSwap(row1, col1, row2, col2) { |
| const temp = board[row1][col1]; |
| board[row1][col1] = board[row2][col2]; |
| board[row2][col2] = temp; |
| |
| const matches = findAllMatches(); |
| |
| if (matches.length > 0) { |
| updateDisplay(); |
| moves--; |
| document.getElementById('moves').textContent = moves; |
| |
| setTimeout(() => { |
| processMatches(); |
| }, 300); |
| } else { |
| board[row1][col1] = board[row2][col2]; |
| board[row2][col2] = temp; |
| playNoMatchSound(); |
| } |
| } |
| |
| function findAllMatches() { |
| const matches = []; |
| |
| |
| for (let row = 0; row < BOARD_SIZE; row++) { |
| let count = 1; |
| let currentType = board[row][0]; |
| |
| for (let col = 1; col < BOARD_SIZE; col++) { |
| if (board[row][col] === currentType) { |
| count++; |
| } else { |
| if (count >= 3) { |
| for (let i = 0; i < count; i++) { |
| matches.push({ row, col: col - count + i }); |
| } |
| } |
| count = 1; |
| currentType = board[row][col]; |
| } |
| } |
| |
| if (count >= 3) { |
| for (let i = 0; i < count; i++) { |
| matches.push({ row, col: BOARD_SIZE - count + i }); |
| } |
| } |
| } |
| |
| |
| for (let col = 0; col < BOARD_SIZE; col++) { |
| let count = 1; |
| let currentType = board[0][col]; |
| |
| for (let row = 1; row < BOARD_SIZE; row++) { |
| if (board[row][col] === currentType) { |
| count++; |
| } else { |
| if (count >= 3) { |
| for (let i = 0; i < count; i++) { |
| matches.push({ row: row - count + i, col }); |
| } |
| } |
| count = 1; |
| currentType = board[row][col]; |
| } |
| } |
| |
| if (count >= 3) { |
| for (let i = 0; i < count; i++) { |
| matches.push({ row: BOARD_SIZE - count + i, col }); |
| } |
| } |
| } |
| |
| return matches; |
| } |
| |
| function processMatches() { |
| const matches = findAllMatches(); |
| |
| if (matches.length === 0) { |
| checkGameEnd(); |
| return; |
| } |
| |
| playMatchSound(); |
| |
| |
| matches.forEach(match => { |
| const tile = document.querySelector(`[data-row="${match.row}"][data-col="${match.col}"]`); |
| tile.classList.add('matching'); |
| |
| |
| const rect = tile.getBoundingClientRect(); |
| createFloatingTile(board[match.row][match.col], rect.left, rect.top); |
| }); |
| |
| |
| matchesMade += Math.floor(matches.length / 3); |
| const remaining = Math.max(0, matchesNeeded - matchesMade); |
| document.getElementById('matchesLeft').textContent = remaining; |
| |
| |
| const progress = (matchesMade / matchesNeeded) * 100; |
| document.getElementById('progressFill').style.width = Math.min(100, progress) + '%'; |
| |
| |
| score += matches.length * 10 * level; |
| document.getElementById('score').textContent = score; |
| |
| setTimeout(() => { |
| matches.forEach(match => { |
| board[match.row][match.col] = null; |
| }); |
| |
| dropTiles(); |
| fillEmptySpaces(); |
| updateDisplay(); |
| |
| setTimeout(() => { |
| processMatches(); |
| }, 400); |
| }, 600); |
| } |
| |
| function dropTiles() { |
| for (let col = 0; col < BOARD_SIZE; col++) { |
| let writeIndex = BOARD_SIZE - 1; |
| |
| for (let row = BOARD_SIZE - 1; row >= 0; row--) { |
| if (board[row][col] !== null) { |
| board[writeIndex][col] = board[row][col]; |
| if (writeIndex !== row) { |
| board[row][col] = null; |
| } |
| writeIndex--; |
| } |
| } |
| } |
| } |
| |
| function fillEmptySpaces() { |
| for (let row = 0; row < BOARD_SIZE; row++) { |
| for (let col = 0; col < BOARD_SIZE; col++) { |
| if (board[row][col] === null) { |
| board[row][col] = TILE_TYPES[Math.floor(Math.random() * TILE_TYPES.length)]; |
| } |
| } |
| } |
| } |
| |
| function updateDisplay() { |
| const tiles = document.querySelectorAll('.tile'); |
| tiles.forEach(tile => { |
| const row = parseInt(tile.dataset.row); |
| const col = parseInt(tile.dataset.col); |
| tile.textContent = board[row][col]; |
| tile.classList.remove('matching', 'selected'); |
| tile.classList.add('falling'); |
| |
| setTimeout(() => { |
| tile.classList.remove('falling'); |
| }, 400); |
| }); |
| } |
| |
| function checkGameEnd() { |
| if (matchesMade >= matchesNeeded) { |
| levelComplete(); |
| } else if (moves <= 0 || timeLeft <= 0) { |
| gameOver(); |
| } |
| } |
| |
| function levelComplete() { |
| gameActive = false; |
| clearInterval(gameTimer); |
| playLevelCompleteSound(); |
| |
| const popup = document.createElement('div'); |
| popup.className = 'level-complete'; |
| popup.innerHTML = ` |
| <h2>π Level ${level} Complete! π</h2> |
| <p>Score: ${score}</p> |
| <p>Time Bonus: ${timeLeft * 5}</p> |
| <button onclick="nextLevel()" style="margin-top: 20px;">Next Level</button> |
| `; |
| document.body.appendChild(popup); |
| |
| score += timeLeft * 5; |
| document.getElementById('score').textContent = score; |
| } |
| |
| function nextLevel() { |
| document.querySelector('.level-complete').remove(); |
| level++; |
| matchesNeeded = 15 + (level * 5); |
| matchesMade = 0; |
| moves = 30 + (level * 2); |
| timeLeft = 300 - (level * 15); |
| gameActive = true; |
| |
| document.getElementById('level').textContent = level; |
| document.getElementById('moves').textContent = moves; |
| document.getElementById('matchesLeft').textContent = matchesNeeded; |
| document.getElementById('progressFill').style.width = '0%'; |
| document.getElementById('timer').classList.remove('warning'); |
| |
| initializeBoard(); |
| startTimer(); |
| } |
| |
| function gameOver() { |
| gameActive = false; |
| clearInterval(gameTimer); |
| playGameOverSound(); |
| |
| const popup = document.createElement('div'); |
| popup.className = 'game-over'; |
| popup.innerHTML = ` |
| <h2>π Game Over! π</h2> |
| <p>Final Score: ${score}</p> |
| <p>Level Reached: ${level}</p> |
| <button onclick="document.querySelector('.game-over').remove(); newGame();" style="margin-top: 20px;">Play Again</button> |
| `; |
| document.body.appendChild(popup); |
| } |
| |
| function newGame() { |
| score = 0; |
| moves = 30; |
| level = 1; |
| matchesNeeded = 20; |
| matchesMade = 0; |
| timeLeft = 300; |
| gameActive = true; |
| gamePaused = false; |
| selectedTile = null; |
| |
| clearInterval(gameTimer); |
| |
| document.getElementById('score').textContent = score; |
| document.getElementById('moves').textContent = moves; |
| document.getElementById('level').textContent = level; |
| document.getElementById('matchesLeft').textContent = matchesNeeded; |
| document.getElementById('progressFill').style.width = '0%'; |
| document.getElementById('pauseBtn').textContent = 'Pause'; |
| document.getElementById('timer').classList.remove('warning'); |
| |
| |
| const existingPopups = document.querySelectorAll('.level-complete, .game-over'); |
| existingPopups.forEach(popup => popup.remove()); |
| |
| initializeBoard(); |
| startTimer(); |
| updateTimerDisplay(); |
| } |
| |
| function findHint() { |
| if (!gameActive || gamePaused) return; |
| |
| const tiles = document.querySelectorAll('.tile'); |
| const randomTile = tiles[Math.floor(Math.random() * tiles.length)]; |
| |
| randomTile.style.animation = 'matchPulse 1s ease-in-out 2'; |
| setTimeout(() => { |
| randomTile.style.animation = ''; |
| }, 2000); |
| } |
| |
| |
| newGame(); |
| </script> |
| </body> |
| </html> |