|
|
<!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> |