match3 / index.html
offerpk3's picture
Update index.html
fecfabc verified
<!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 animation for matched tiles */
.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; // 5 minutes in seconds
let gameTimer = null;
let matchesNeeded = 20;
let matchesMade = 0;
// Sound effects using Web Audio API
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() {
// Happy match sound - ascending notes
playSound(523, 0.1); // C
setTimeout(() => playSound(659, 0.1), 50); // E
setTimeout(() => playSound(784, 0.2), 100); // G
}
function playNoMatchSound() {
// Sad "no" sound - descending notes
playSound(400, 0.3, 'sawtooth');
setTimeout(() => playSound(300, 0.3, 'sawtooth'), 100);
}
function playLevelCompleteSound() {
// Victory fanfare
const notes = [523, 659, 784, 1047];
notes.forEach((note, i) => {
setTimeout(() => playSound(note, 0.3), i * 100);
});
}
function playGameOverSound() {
// Sad game over sound
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 = [];
// Find horizontal 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 });
}
}
}
// Find vertical matches
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();
// Create floating animations for matched tiles
matches.forEach(match => {
const tile = document.querySelector(`[data-row="${match.row}"][data-col="${match.col}"]`);
tile.classList.add('matching');
// Get tile position for floating animation
const rect = tile.getBoundingClientRect();
createFloatingTile(board[match.row][match.col], rect.left, rect.top);
});
// Update match counter
matchesMade += Math.floor(matches.length / 3);
const remaining = Math.max(0, matchesNeeded - matchesMade);
document.getElementById('matchesLeft').textContent = remaining;
// Update progress bar
const progress = (matchesMade / matchesNeeded) * 100;
document.getElementById('progressFill').style.width = Math.min(100, progress) + '%';
// Add score
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; // Time bonus
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); // Less time each level
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');
// Remove any existing popups
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);
}
// Initialize the game
newGame();
</script>
</body>
</html>