| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>AvaRush - Adventure Match Game</title> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Arial', sans-serif; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| min-height: 100vh; |
| overflow-x: hidden; |
| } |
| |
| .game-container { |
| max-width: 1200px; |
| margin: 0 auto; |
| padding: 10px; |
| } |
| |
| .header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| background: rgba(255, 255, 255, 0.1); |
| backdrop-filter: blur(10px); |
| padding: 15px 20px; |
| border-radius: 15px; |
| margin-bottom: 10px; |
| } |
| |
| .logo { |
| font-size: 2.5em; |
| font-weight: bold; |
| background: linear-gradient(45deg, #ff6b6b, #4ecdc4); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| text-shadow: 2px 2px 4px rgba(0,0,0,0.3); |
| } |
| |
| .nav-buttons { |
| display: flex; |
| gap: 10px; |
| } |
| |
| .nav-btn { |
| padding: 10px 20px; |
| background: linear-gradient(45deg, #ff6b6b, #ee5a52); |
| color: white; |
| border: none; |
| border-radius: 25px; |
| cursor: pointer; |
| font-weight: bold; |
| transition: all 0.3s ease; |
| } |
| |
| .nav-btn:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 5px 15px rgba(0,0,0,0.3); |
| } |
| |
| .game-area { |
| display: flex; |
| gap: 20px; |
| height: 600px; |
| } |
| |
| .left-panel, .right-panel { |
| width: 200px; |
| background: rgba(255, 255, 255, 0.1); |
| backdrop-filter: blur(10px); |
| border-radius: 15px; |
| padding: 20px; |
| color: white; |
| } |
| |
| .game-board { |
| flex: 1; |
| background: rgba(255, 255, 255, 0.1); |
| backdrop-filter: blur(10px); |
| border-radius: 15px; |
| padding: 20px; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| } |
| |
| .score-info { |
| text-align: right; |
| margin-bottom: 20px; |
| } |
| |
| .score-item { |
| background: rgba(255, 255, 255, 0.2); |
| padding: 10px; |
| border-radius: 10px; |
| margin-bottom: 10px; |
| } |
| |
| .moves-info { |
| text-align: left; |
| margin-bottom: 20px; |
| } |
| |
| .canvas-container { |
| position: relative; |
| border-radius: 15px; |
| overflow: hidden; |
| box-shadow: 0 10px 30px rgba(0,0,0,0.3); |
| } |
| |
| canvas { |
| display: block; |
| background: #2c3e50; |
| } |
| |
| .game-controls { |
| display: flex; |
| gap: 10px; |
| margin-top: 20px; |
| } |
| |
| .control-btn { |
| padding: 15px 30px; |
| font-size: 18px; |
| border: none; |
| border-radius: 25px; |
| cursor: pointer; |
| font-weight: bold; |
| transition: all 0.3s ease; |
| } |
| |
| .play-btn { |
| background: linear-gradient(45deg, #4ecdc4, #44a08d); |
| color: white; |
| } |
| |
| .pause-btn { |
| background: linear-gradient(45deg, #ffa726, #ff7043); |
| color: white; |
| } |
| |
| .control-btn:hover { |
| transform: scale(1.05); |
| } |
| |
| .objective-info { |
| background: rgba(255, 255, 255, 0.2); |
| padding: 15px; |
| border-radius: 10px; |
| text-align: center; |
| color: white; |
| font-weight: bold; |
| margin-bottom: 20px; |
| } |
| |
| .level-info { |
| text-align: center; |
| margin: 20px 0; |
| } |
| |
| .modal { |
| display: none; |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background: rgba(0,0,0,0.8); |
| z-index: 1000; |
| } |
| |
| .modal-content { |
| position: absolute; |
| top: 50%; |
| left: 50%; |
| transform: translate(-50%, -50%); |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| padding: 30px; |
| border-radius: 20px; |
| color: white; |
| text-align: center; |
| min-width: 300px; |
| } |
| |
| .toast { |
| position: fixed; |
| top: 20px; |
| right: 20px; |
| background: linear-gradient(45deg, #4ecdc4, #44a08d); |
| color: white; |
| padding: 15px 25px; |
| border-radius: 10px; |
| z-index: 1001; |
| transform: translateX(400px); |
| transition: transform 0.3s ease; |
| } |
| |
| .toast.show { |
| transform: translateX(0); |
| } |
| |
| .gem { |
| width: 40px; |
| height: 40px; |
| border-radius: 8px; |
| display: inline-block; |
| margin: 2px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| |
| .gem:hover { |
| transform: scale(1.1); |
| } |
| |
| .gem-red { background: linear-gradient(45deg, #ff6b6b, #ee5a52); } |
| .gem-blue { background: linear-gradient(45deg, #4ecdc4, #44a08d); } |
| .gem-green { background: linear-gradient(45deg, #a8e6cf, #88d8a3); } |
| .gem-yellow { background: linear-gradient(45deg, #ffd93d, #ffb74d); } |
| .gem-purple { background: linear-gradient(45deg, #b19cd9, #9b59b6); } |
| .gem-ice { background: linear-gradient(45deg, #e3f2fd, #bbdefb); border: 2px solid #2196f3; } |
| |
| .modal-buttons { |
| display: flex; |
| gap: 15px; |
| justify-content: center; |
| margin-top: 20px; |
| } |
| |
| .modal-btn { |
| padding: 10px 20px; |
| border: none; |
| border-radius: 20px; |
| cursor: pointer; |
| font-weight: bold; |
| transition: all 0.3s ease; |
| } |
| |
| .close-btn { |
| background: #e74c3c; |
| color: white; |
| } |
| |
| .continue-btn { |
| background: #27ae60; |
| color: white; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="game-container"> |
| <div class="header"> |
| <div class="logo">AvaRush</div> |
| <div class="nav-buttons"> |
| <button class="nav-btn" onclick="showModal('shop')">Shop</button> |
| <button class="nav-btn" onclick="showModal('equip')">Equip</button> |
| <button class="nav-btn" onclick="showModal('friends')">Friends</button> |
| <button class="nav-btn" onclick="showModal('tasks')">Tasks</button> |
| <button class="nav-btn" onclick="showModal('rating')">Rating</button> |
| <button class="nav-btn" onclick="showModal('boosters')">Boosters</button> |
| <button class="nav-btn" onclick="showModal('skins')">Skins</button> |
| </div> |
| </div> |
|
|
| <div class="game-area"> |
| <div class="left-panel"> |
| <div class="moves-info"> |
| <h3>Moves</h3> |
| <div class="score-item"> |
| <div style="font-size: 2em; color: #4ecdc4;" id="moves-count">25</div> |
| </div> |
| </div> |
| <div class="objective-info"> |
| <div>Crush Ice</div> |
| <div style="font-size: 1.5em; color: #4ecdc4;" id="ice-count">0/28</div> |
| </div> |
| <div class="level-info"> |
| <h3>Level</h3> |
| <div style="font-size: 2em; color: #ffd93d;" id="player-level">1</div> |
| <div style="font-size: 0.9em; margin-top: 5px;" id="map-name">Forest Valley</div> |
| </div> |
| </div> |
|
|
| <div class="game-board"> |
| <div class="canvas-container"> |
| <canvas id="gameCanvas" width="480" height="480"></canvas> |
| </div> |
| <div class="game-controls"> |
| <button class="control-btn pause-btn" onclick="pauseGame()" id="pauseBtn">Pause</button> |
| <button class="control-btn play-btn" onclick="playGame()" id="playBtn" style="display: none;">Play</button> |
| </div> |
| </div> |
|
|
| <div class="right-panel"> |
| <div class="score-info"> |
| <h3>Score</h3> |
| <div class="score-item"> |
| <div style="font-size: 2em; color: #ff6b6b;" id="score">0</div> |
| </div> |
| <div class="score-item"> |
| <div>High Score</div> |
| <div style="color: #ffd93d;" id="high-score">1250</div> |
| </div> |
| <div class="score-item"> |
| <div>Level Progress</div> |
| <div style="color: #4ecdc4;" id="level-progress">Level 1/30</div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="modal" class="modal"> |
| <div class="modal-content"> |
| <div id="modal-title"></div> |
| <div id="modal-body"></div> |
| <div class="modal-buttons"> |
| <button class="modal-btn close-btn" onclick="closeModal()">Close</button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="toast" class="toast"></div> |
|
|
| <script> |
| |
| let gameState = { |
| score: 0, |
| moves: 25, |
| level: 1, |
| iceCount: 0, |
| iceTarget: 28, |
| isPaused: false, |
| currentMap: 'forest', |
| gems: [], |
| selectedGem: null, |
| animations: [], |
| fallingGems: [], |
| isAnimating: false |
| }; |
| |
| |
| let audioContext; |
| |
| function initAudio() { |
| try { |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); |
| } catch (e) { |
| console.log('Audio not supported'); |
| } |
| } |
| |
| function playSound(frequency, duration, type = 'sine') { |
| if (!audioContext) return; |
| |
| const oscillator = audioContext.createOscillator(); |
| const gainNode = audioContext.createGain(); |
| |
| oscillator.connect(gainNode); |
| gainNode.connect(audioContext.destination); |
| |
| oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime); |
| oscillator.type = type; |
| |
| gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); |
| gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + duration); |
| |
| oscillator.start(audioContext.currentTime); |
| oscillator.stop(audioContext.currentTime + duration); |
| } |
| |
| function playMatchSound() { |
| |
| playSound(523.25, 0.2); |
| setTimeout(() => playSound(659.25, 0.2), 100); |
| setTimeout(() => playSound(783.99, 0.3), 200); |
| } |
| |
| function playDropSound() { |
| |
| playSound(220, 0.15, 'triangle'); |
| } |
| |
| function playIceBreakSound() { |
| |
| playSound(1000, 0.1, 'square'); |
| setTimeout(() => playSound(800, 0.1, 'square'), 50); |
| setTimeout(() => playSound(600, 0.1, 'square'), 100); |
| } |
| |
| |
| const maps = { |
| forest: { name: 'Forest Valley', bg: '#2d5016', levels: [1,2,3,4,5,6,7,8,9,10] }, |
| desert: { name: 'Desert Oasis', bg: '#8b4513', levels: [11,12,13,14,15,16,17,18,19,20] }, |
| skylands: { name: 'Sky Realm', bg: '#4169e1', levels: [21,22,23,24,25,26,27,28,29,30] } |
| }; |
| |
| |
| const canvas = document.getElementById('gameCanvas'); |
| const ctx = canvas.getContext('2d'); |
| const gridSize = 8; |
| const gemSize = 60; |
| |
| |
| const gemTypes = ['wood', 'water', 'snake', 'bee', 'whale', 'fish']; |
| const gemColors = { |
| wood: '#8B4513', |
| water: '#4682B4', |
| snake: '#228B22', |
| bee: '#FFD700', |
| whale: '#4169E1', |
| fish: '#FF6347' |
| }; |
| |
| |
| function initGame() { |
| createGemGrid(); |
| updateUI(); |
| updateMapTheme(); |
| gameLoop(); |
| } |
| |
| function createGemGrid() { |
| gameState.gems = []; |
| for (let row = 0; row < gridSize; row++) { |
| gameState.gems[row] = []; |
| for (let col = 0; col < gridSize; col++) { |
| gameState.gems[row][col] = { |
| type: gemTypes[Math.floor(Math.random() * gemTypes.length)], |
| x: col * gemSize, |
| y: row * gemSize, |
| isIce: Math.random() < 0.1 |
| }; |
| } |
| } |
| |
| addIceGems(); |
| } |
| |
| function addIceGems() { |
| let iceAdded = 0; |
| while (iceAdded < gameState.iceTarget && iceAdded < 15) { |
| const row = Math.floor(Math.random() * gridSize); |
| const col = Math.floor(Math.random() * gridSize); |
| if (!gameState.gems[row][col].isIce) { |
| gameState.gems[row][col].isIce = true; |
| iceAdded++; |
| } |
| } |
| } |
| |
| function drawGame() { |
| |
| const currentMapData = Object.values(maps).find(map => |
| map.levels.includes(gameState.level) |
| ) || maps.forest; |
| |
| ctx.fillStyle = currentMapData.bg; |
| ctx.fillRect(0, 0, canvas.width, canvas.height); |
| |
| |
| ctx.strokeStyle = 'rgba(255,255,255,0.1)'; |
| ctx.lineWidth = 1; |
| for (let i = 0; i <= gridSize; i++) { |
| ctx.beginPath(); |
| ctx.moveTo(i * gemSize, 0); |
| ctx.lineTo(i * gemSize, canvas.height); |
| ctx.stroke(); |
| |
| ctx.beginPath(); |
| ctx.moveTo(0, i * gemSize); |
| ctx.lineTo(canvas.width, i * gemSize); |
| ctx.stroke(); |
| } |
| |
| |
| for (let row = 0; row < gridSize; row++) { |
| for (let col = 0; col < gridSize; col++) { |
| const gem = gameState.gems[row][col]; |
| if (gem && !gem.isMatched) { |
| drawGem(gem, col * gemSize, row * gemSize); |
| } |
| } |
| } |
| |
| |
| gameState.fallingGems.forEach(fallingGem => { |
| drawGem(fallingGem.gem, fallingGem.x, fallingGem.y); |
| }); |
| |
| |
| gameState.animations.forEach(animation => { |
| drawMatchAnimation(animation); |
| }); |
| |
| |
| if (gameState.selectedGem && !gameState.isAnimating) { |
| const { row, col } = gameState.selectedGem; |
| ctx.strokeStyle = '#fff'; |
| ctx.lineWidth = 3; |
| ctx.strokeRect(col * gemSize + 2, row * gemSize + 2, gemSize - 4, gemSize - 4); |
| } |
| } |
| |
| function drawMatchAnimation(animation) { |
| const progress = (Date.now() - animation.startTime) / animation.duration; |
| if (progress >= 1) return; |
| |
| const alpha = 1 - progress; |
| const scale = 1 + progress * 0.5; |
| |
| ctx.save(); |
| ctx.globalAlpha = alpha; |
| ctx.translate(animation.x + gemSize/2, animation.y + gemSize/2); |
| ctx.scale(scale, scale); |
| ctx.translate(-gemSize/2, -gemSize/2); |
| |
| |
| ctx.fillStyle = '#FFD700'; |
| for (let i = 0; i < 8; i++) { |
| const angle = (i / 8) * Math.PI * 2; |
| const x = Math.cos(angle) * 20 * progress; |
| const y = Math.sin(angle) * 20 * progress; |
| ctx.beginPath(); |
| ctx.arc(gemSize/2 + x, gemSize/2 + y, 3, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| |
| ctx.restore(); |
| } |
| |
| function drawGem(gem, x, y) { |
| const padding = 4; |
| const size = gemSize - padding * 2; |
| const centerX = x + padding + size/2; |
| const centerY = y + padding + size/2; |
| |
| ctx.save(); |
| |
| |
| switch(gem.type) { |
| case 'wood': |
| drawWood(centerX, centerY, size); |
| break; |
| case 'water': |
| drawWaterDrop(centerX, centerY, size); |
| break; |
| case 'snake': |
| drawSnake(centerX, centerY, size); |
| break; |
| case 'bee': |
| drawBee(centerX, centerY, size); |
| break; |
| case 'whale': |
| drawWhale(centerX, centerY, size); |
| break; |
| case 'fish': |
| drawFish(centerX, centerY, size); |
| break; |
| } |
| |
| |
| if (gem.isIce) { |
| ctx.strokeStyle = '#87CEEB'; |
| ctx.lineWidth = 3; |
| ctx.strokeRect(x + padding, y + padding, size, size); |
| |
| |
| ctx.strokeStyle = 'rgba(135, 206, 235, 0.7)'; |
| ctx.lineWidth = 2; |
| ctx.beginPath(); |
| ctx.moveTo(centerX, y + padding); |
| ctx.lineTo(centerX, y + padding + size); |
| ctx.moveTo(x + padding, centerY); |
| ctx.lineTo(x + padding + size, centerY); |
| ctx.moveTo(x + padding + size/4, y + padding + size/4); |
| ctx.lineTo(x + padding + 3*size/4, y + padding + 3*size/4); |
| ctx.moveTo(x + padding + 3*size/4, y + padding + size/4); |
| ctx.lineTo(x + padding + size/4, y + padding + 3*size/4); |
| ctx.stroke(); |
| } |
| |
| ctx.restore(); |
| } |
| |
| function drawWood(centerX, centerY, size) { |
| const radius = size * 0.4; |
| |
| |
| const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius); |
| gradient.addColorStop(0, '#D2691E'); |
| gradient.addColorStop(1, '#8B4513'); |
| ctx.fillStyle = gradient; |
| ctx.beginPath(); |
| ctx.arc(centerX, centerY, radius, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.strokeStyle = '#654321'; |
| ctx.lineWidth = 2; |
| for(let i = 1; i <= 3; i++) { |
| ctx.beginPath(); |
| ctx.arc(centerX, centerY, radius * 0.3 * i, 0, Math.PI * 2); |
| ctx.stroke(); |
| } |
| } |
| |
| function drawWaterDrop(centerX, centerY, size) { |
| const radius = size * 0.4; |
| |
| |
| const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius); |
| gradient.addColorStop(0, '#87CEEB'); |
| gradient.addColorStop(1, '#4682B4'); |
| ctx.fillStyle = gradient; |
| |
| ctx.beginPath(); |
| ctx.arc(centerX, centerY + radius * 0.2, radius * 0.8, 0, Math.PI * 2); |
| ctx.moveTo(centerX, centerY - radius); |
| ctx.quadraticCurveTo(centerX - radius * 0.5, centerY, centerX, centerY + radius * 0.2); |
| ctx.quadraticCurveTo(centerX + radius * 0.5, centerY, centerX, centerY - radius); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; |
| ctx.beginPath(); |
| ctx.arc(centerX - radius * 0.3, centerY - radius * 0.3, radius * 0.2, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| |
| function drawSnake(centerX, centerY, size) { |
| const radius = size * 0.15; |
| |
| |
| ctx.strokeStyle = '#228B22'; |
| ctx.lineWidth = radius; |
| ctx.lineCap = 'round'; |
| ctx.beginPath(); |
| ctx.moveTo(centerX - size * 0.3, centerY - size * 0.2); |
| ctx.quadraticCurveTo(centerX + size * 0.2, centerY - size * 0.1, centerX, centerY); |
| ctx.quadraticCurveTo(centerX - size * 0.2, centerY + size * 0.1, centerX + size * 0.3, centerY + size * 0.2); |
| ctx.stroke(); |
| |
| |
| ctx.fillStyle = '#32CD32'; |
| ctx.beginPath(); |
| ctx.arc(centerX + size * 0.3, centerY + size * 0.2, radius * 1.2, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#000'; |
| ctx.beginPath(); |
| ctx.arc(centerX + size * 0.25, centerY + size * 0.15, 2, 0, Math.PI * 2); |
| ctx.arc(centerX + size * 0.35, centerY + size * 0.15, 2, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| |
| function drawBee(centerX, centerY, size) { |
| const bodyWidth = size * 0.6; |
| const bodyHeight = size * 0.3; |
| |
| |
| ctx.fillStyle = '#FFD700'; |
| ctx.beginPath(); |
| ctx.ellipse(centerX, centerY, bodyWidth/2, bodyHeight/2, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#000'; |
| for(let i = 0; i < 3; i++) { |
| ctx.fillRect(centerX - bodyWidth/2 + i * bodyWidth/3, centerY - bodyHeight/2, bodyWidth/6, bodyHeight); |
| } |
| |
| |
| ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; |
| ctx.beginPath(); |
| ctx.ellipse(centerX - bodyWidth/4, centerY - bodyHeight/2, bodyWidth/3, bodyHeight/3, -Math.PI/6, 0, Math.PI * 2); |
| ctx.ellipse(centerX + bodyWidth/4, centerY - bodyHeight/2, bodyWidth/3, bodyHeight/3, Math.PI/6, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#FFD700'; |
| ctx.beginPath(); |
| ctx.arc(centerX, centerY - bodyHeight/2, bodyHeight/3, 0, Math.PI * 2); |
| ctx.fill(); |
| } |
| |
| function drawWhale(centerX, centerY, size) { |
| const bodyWidth = size * 0.7; |
| const bodyHeight = size * 0.4; |
| |
| |
| const gradient = ctx.createLinearGradient(centerX - bodyWidth/2, centerY, centerX + bodyWidth/2, centerY); |
| gradient.addColorStop(0, '#4169E1'); |
| gradient.addColorStop(1, '#191970'); |
| ctx.fillStyle = gradient; |
| |
| ctx.beginPath(); |
| ctx.ellipse(centerX, centerY, bodyWidth/2, bodyHeight/2, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(centerX + bodyWidth/2, centerY); |
| ctx.lineTo(centerX + bodyWidth/2 + size * 0.2, centerY - size * 0.15); |
| ctx.lineTo(centerX + bodyWidth/2 + size * 0.2, centerY + size * 0.15); |
| ctx.closePath(); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#FFF'; |
| ctx.beginPath(); |
| ctx.arc(centerX - bodyWidth/4, centerY - bodyHeight/4, 4, 0, Math.PI * 2); |
| ctx.fill(); |
| ctx.fillStyle = '#000'; |
| ctx.beginPath(); |
| ctx.arc(centerX - bodyWidth/4, centerY - bodyHeight/4, 2, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.strokeStyle = '#87CEEB'; |
| ctx.lineWidth = 3; |
| ctx.beginPath(); |
| ctx.moveTo(centerX, centerY - bodyHeight/2); |
| ctx.lineTo(centerX, centerY - bodyHeight/2 - size * 0.2); |
| ctx.stroke(); |
| } |
| |
| function drawFish(centerX, centerY, size) { |
| const bodyWidth = size * 0.6; |
| const bodyHeight = size * 0.4; |
| |
| |
| const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, bodyWidth/2); |
| gradient.addColorStop(0, '#FFA500'); |
| gradient.addColorStop(1, '#FF6347'); |
| ctx.fillStyle = gradient; |
| |
| ctx.beginPath(); |
| ctx.ellipse(centerX, centerY, bodyWidth/2, bodyHeight/2, 0, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(centerX + bodyWidth/2, centerY); |
| ctx.lineTo(centerX + bodyWidth/2 + size * 0.25, centerY - size * 0.2); |
| ctx.lineTo(centerX + bodyWidth/2 + size * 0.15, centerY); |
| ctx.lineTo(centerX + bodyWidth/2 + size * 0.25, centerY + size * 0.2); |
| ctx.closePath(); |
| ctx.fill(); |
| |
| |
| ctx.beginPath(); |
| ctx.moveTo(centerX - bodyWidth/4, centerY + bodyHeight/2); |
| ctx.lineTo(centerX - bodyWidth/4 - size * 0.1, centerY + bodyHeight/2 + size * 0.15); |
| ctx.lineTo(centerX, centerY + bodyHeight/2); |
| ctx.closePath(); |
| ctx.fill(); |
| |
| |
| ctx.fillStyle = '#FFF'; |
| ctx.beginPath(); |
| ctx.arc(centerX - bodyWidth/4, centerY - bodyHeight/6, 5, 0, Math.PI * 2); |
| ctx.fill(); |
| ctx.fillStyle = '#000'; |
| ctx.beginPath(); |
| ctx.arc(centerX - bodyWidth/4, centerY - bodyHeight/6, 3, 0, Math.PI * 2); |
| ctx.fill(); |
| |
| |
| ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; |
| ctx.lineWidth = 1; |
| for(let i = 0; i < 3; i++) { |
| ctx.beginPath(); |
| ctx.arc(centerX - bodyWidth/4 + i * bodyWidth/6, centerY, bodyWidth/8, 0, Math.PI * 2); |
| ctx.stroke(); |
| } |
| } |
| |
| function darkenColor(color, amount) { |
| const num = parseInt(color.replace("#", ""), 16); |
| const amt = Math.round(2.55 * amount * 100); |
| const R = (num >> 16) - amt; |
| const G = (num >> 8 & 0x00FF) - amt; |
| const B = (num & 0x0000FF) - amt; |
| return "#" + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 + |
| (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 + |
| (B < 255 ? B < 1 ? 0 : B : 255)).toString(16).slice(1); |
| } |
| |
| |
| function gameLoop() { |
| if (!gameState.isPaused) { |
| updateAnimations(); |
| drawGame(); |
| } |
| requestAnimationFrame(gameLoop); |
| } |
| |
| function updateAnimations() { |
| |
| gameState.fallingGems = gameState.fallingGems.filter(fallingGem => { |
| fallingGem.y += fallingGem.speed; |
| fallingGem.speed += 0.5; |
| |
| const targetY = fallingGem.targetRow * gemSize; |
| if (fallingGem.y >= targetY) { |
| fallingGem.y = targetY; |
| gameState.gems[fallingGem.targetRow][fallingGem.col] = fallingGem.gem; |
| playDropSound(); |
| return false; |
| } |
| return true; |
| }); |
| |
| |
| gameState.animations = gameState.animations.filter(animation => { |
| return (Date.now() - animation.startTime) < animation.duration; |
| }); |
| |
| |
| if (gameState.fallingGems.length === 0 && gameState.animations.length === 0 && gameState.isAnimating) { |
| gameState.isAnimating = false; |
| |
| setTimeout(() => { |
| const newMatches = findMatches(); |
| if (newMatches.length > 0) { |
| processMatches(newMatches); |
| } |
| }, 200); |
| } |
| } |
| |
| |
| canvas.addEventListener('click', function(e) { |
| if (gameState.isPaused) return; |
| |
| const rect = canvas.getBoundingClientRect(); |
| const x = e.clientX - rect.left; |
| const y = e.clientY - rect.top; |
| |
| const col = Math.floor(x / gemSize); |
| const row = Math.floor(y / gemSize); |
| |
| if (row >= 0 && row < gridSize && col >= 0 && col < gridSize) { |
| handleGemClick(row, col); |
| } |
| }); |
| |
| function handleGemClick(row, col) { |
| if (gameState.isAnimating) return; |
| |
| if (!gameState.selectedGem) { |
| gameState.selectedGem = { row, col }; |
| } else { |
| const selected = gameState.selectedGem; |
| if (selected.row === row && selected.col === col) { |
| gameState.selectedGem = null; |
| } else if (isAdjacent(selected, { row, col })) { |
| swapGems(selected, { row, col }); |
| gameState.selectedGem = null; |
| } else { |
| gameState.selectedGem = { row, col }; |
| } |
| } |
| } |
| |
| function isAdjacent(gem1, gem2) { |
| const rowDiff = Math.abs(gem1.row - gem2.row); |
| const colDiff = Math.abs(gem1.col - gem2.col); |
| return (rowDiff === 1 && colDiff === 0) || (rowDiff === 0 && colDiff === 1); |
| } |
| |
| function swapGems(gem1, gem2) { |
| const temp = gameState.gems[gem1.row][gem1.col]; |
| gameState.gems[gem1.row][gem1.col] = gameState.gems[gem2.row][gem2.col]; |
| gameState.gems[gem2.row][gem2.col] = temp; |
| |
| const matches = findMatches(); |
| if (matches.length > 0) { |
| gameState.moves--; |
| processMatches(matches); |
| updateUI(); |
| showToast('Great match!'); |
| } else { |
| |
| const temp = gameState.gems[gem1.row][gem1.col]; |
| gameState.gems[gem1.row][gem1.col] = gameState.gems[gem2.row][gem2.col]; |
| gameState.gems[gem2.row][gem2.col] = temp; |
| showToast('No matches found!'); |
| } |
| } |
| |
| function findMatches() { |
| const matches = []; |
| |
| |
| for (let row = 0; row < gridSize; row++) { |
| let count = 1; |
| let currentType = gameState.gems[row][0].type; |
| for (let col = 1; col < gridSize; col++) { |
| if (gameState.gems[row][col].type === currentType) { |
| count++; |
| } else { |
| if (count >= 3) { |
| for (let i = col - count; i < col; i++) { |
| matches.push({ row, col: i }); |
| } |
| } |
| count = 1; |
| currentType = gameState.gems[row][col].type; |
| } |
| } |
| if (count >= 3) { |
| for (let i = gridSize - count; i < gridSize; i++) { |
| matches.push({ row, col: i }); |
| } |
| } |
| } |
| |
| |
| for (let col = 0; col < gridSize; col++) { |
| let count = 1; |
| let currentType = gameState.gems[0][col].type; |
| for (let row = 1; row < gridSize; row++) { |
| if (gameState.gems[row][col].type === currentType) { |
| count++; |
| } else { |
| if (count >= 3) { |
| for (let i = row - count; i < row; i++) { |
| matches.push({ row: i, col }); |
| } |
| } |
| count = 1; |
| currentType = gameState.gems[row][col].type; |
| } |
| } |
| if (count >= 3) { |
| for (let i = gridSize - count; i < gridSize; i++) { |
| matches.push({ row: i, col }); |
| } |
| } |
| } |
| |
| return matches; |
| } |
| |
| function processMatches(matches) { |
| let iceDestroyed = 0; |
| gameState.isAnimating = true; |
| |
| |
| matches.forEach(match => { |
| const gem = gameState.gems[match.row][match.col]; |
| if (gem.isIce) { |
| iceDestroyed++; |
| playIceBreakSound(); |
| } |
| |
| |
| gameState.animations.push({ |
| x: match.col * gemSize, |
| y: match.row * gemSize, |
| startTime: Date.now(), |
| duration: 500 |
| }); |
| |
| |
| gameState.gems[match.row][match.col].isMatched = true; |
| }); |
| |
| |
| playMatchSound(); |
| |
| |
| gameState.iceCount += iceDestroyed; |
| gameState.score += matches.length * 100 + iceDestroyed * 200; |
| |
| |
| setTimeout(() => { |
| applyGravity(); |
| fillEmptySpaces(); |
| }, 300); |
| |
| if (gameState.iceCount >= gameState.iceTarget) { |
| setTimeout(() => { |
| levelComplete(); |
| }, 1000); |
| } |
| } |
| |
| function applyGravity() { |
| |
| for (let col = 0; col < gridSize; col++) { |
| const column = []; |
| |
| |
| for (let row = gridSize - 1; row >= 0; row--) { |
| if (!gameState.gems[row][col].isMatched) { |
| column.push(gameState.gems[row][col]); |
| } |
| } |
| |
| |
| for (let row = 0; row < gridSize; row++) { |
| gameState.gems[row][col] = null; |
| } |
| |
| |
| column.forEach((gem, index) => { |
| const newRow = gridSize - 1 - index; |
| const currentRow = findGemCurrentRow(gem, col); |
| |
| if (currentRow !== newRow && currentRow !== -1) { |
| gameState.fallingGems.push({ |
| gem: gem, |
| x: col * gemSize, |
| y: currentRow * gemSize, |
| targetRow: newRow, |
| col: col, |
| speed: 2 |
| }); |
| } else { |
| gameState.gems[newRow][col] = gem; |
| } |
| }); |
| } |
| } |
| |
| function findGemCurrentRow(targetGem, col) { |
| for (let row = 0; row < gridSize; row++) { |
| if (gameState.gems[row][col] === targetGem) { |
| return row; |
| } |
| } |
| return -1; |
| } |
| |
| function fillEmptySpaces() { |
| |
| for (let col = 0; col < gridSize; col++) { |
| for (let row = 0; row < gridSize; row++) { |
| if (!gameState.gems[row][col]) { |
| const newGem = { |
| type: gemTypes[Math.floor(Math.random() * gemTypes.length)], |
| isIce: false, |
| isMatched: false |
| }; |
| |
| |
| gameState.fallingGems.push({ |
| gem: newGem, |
| x: col * gemSize, |
| y: -gemSize * (gridSize - row), |
| targetRow: row, |
| col: col, |
| speed: 2 |
| }); |
| } |
| } |
| } |
| } |
| |
| function levelComplete() { |
| showToast('Level Complete!'); |
| gameState.level++; |
| if (gameState.level > 30) { |
| showToast('Congratulations! Game Complete!'); |
| gameState.level = 30; |
| } else { |
| setTimeout(() => { |
| initializeNewLevel(); |
| }, 2000); |
| } |
| } |
| |
| function initializeNewLevel() { |
| gameState.moves = 25; |
| gameState.iceCount = 0; |
| gameState.iceTarget = Math.min(28 + gameState.level * 2, 50); |
| updateMapTheme(); |
| createGemGrid(); |
| updateUI(); |
| } |
| |
| function updateMapTheme() { |
| let currentMap = 'forest'; |
| if (gameState.level >= 11 && gameState.level <= 20) { |
| currentMap = 'desert'; |
| } else if (gameState.level >= 21) { |
| currentMap = 'skylands'; |
| } |
| |
| gameState.currentMap = currentMap; |
| document.getElementById('map-name').textContent = maps[currentMap].name; |
| } |
| |
| |
| function updateUI() { |
| document.getElementById('score').textContent = gameState.score; |
| document.getElementById('moves-count').textContent = gameState.moves; |
| document.getElementById('ice-count').textContent = `${gameState.iceCount}/${gameState.iceTarget}`; |
| document.getElementById('player-level').textContent = gameState.level; |
| document.getElementById('level-progress').textContent = `Level ${gameState.level}/30`; |
| } |
| |
| |
| function pauseGame() { |
| gameState.isPaused = true; |
| document.getElementById('pauseBtn').style.display = 'none'; |
| document.getElementById('playBtn').style.display = 'block'; |
| showToast('Game Paused'); |
| } |
| |
| function playGame() { |
| gameState.isPaused = false; |
| document.getElementById('pauseBtn').style.display = 'block'; |
| document.getElementById('playBtn').style.display = 'none'; |
| showToast('Game Resumed'); |
| } |
| |
| |
| function showModal(type) { |
| const modal = document.getElementById('modal'); |
| const title = document.getElementById('modal-title'); |
| const body = document.getElementById('modal-body'); |
| |
| const modalContent = { |
| shop: { |
| title: 'ποΈ Shop', |
| body: '<p>Buy power-ups and boosters!</p><div style="margin: 20px 0;"><div>π Gems: 150</div><div>β‘ Energy Boost - 50 gems</div><div>π₯ Fire Boost - 75 gems</div><div>βοΈ Ice Breaker - 100 gems</div></div>' |
| }, |
| equip: { |
| title: 'βοΈ Equipment', |
| body: '<p>Manage your power-ups and equipment!</p><div style="margin: 20px 0;"><div>π‘οΈ Equipped: Basic Hammer</div><div>β‘ Energy Boost: Ready</div><div>π₯ Fire Power: Cooldown 2 moves</div></div>' |
| }, |
| friends: { |
| title: 'π₯ Friends', |
| body: '<p>Connect with friends and send lives!</p><div style="margin: 20px 0;"><div>π’ Alex_Gamer - Online</div><div>π‘ Sarah_123 - 5 min ago</div><div>π΄ Mike_Pro - 1 hour ago</div><button style="margin-top: 10px; padding: 5px 15px; background: #4ecdc4; border: none; border-radius: 15px; color: white;">Invite Friends</button></div>' |
| }, |
| tasks: { |
| title: 'π Daily Tasks', |
| body: '<p>Complete tasks for rewards!</p><div style="margin: 20px 0;"><div>β
Play 3 levels - Completed</div><div>β³ Crush 50 ice blocks - 28/50</div><div>β³ Score 5000 points - 0/5000</div><div>β³ Use 5 power-ups - 0/5</div></div>' |
| }, |
| rating: { |
| title: 'π Leaderboard', |
| body: '<p>Global Rankings</p><div style="margin: 20px 0;"><div>π₯ ProGamer99 - 45,230</div><div>π₯ MatchMaster - 42,100</div><div>π₯ GemCrusher - 38,950</div><div style="color: #4ecdc4;">π
You - 0 (Rank: 15,432)</div></div>' |
| }, |
| boosters: { |
| title: 'π Boosters', |
| body: '<p>Activate powerful boosters!</p><div style="margin: 20px 0;"><div>β‘ Color Bomb - 3 left</div><div>πͺοΈ Line Blast - 2 left</div><div>βοΈ Ice Breaker - 1 left</div><div>π₯ Fire Storm - 0 left</div></div>' |
| }, |
| skins: { |
| title: 'π¨ Skins & Themes', |
| body: '<p>Customize your game appearance!</p><div style="margin: 20px 0;"><div>π² Forest Theme - Active</div><div>ποΈ Desert Theme - Unlocked</div><div>βοΈ Sky Theme - Locked</div><div>π Galaxy Theme - Locked</div></div>' |
| } |
| }; |
| |
| const content = modalContent[type]; |
| title.innerHTML = `<h2>${content.title}</h2>`; |
| body.innerHTML = content.body; |
| modal.style.display = 'block'; |
| } |
| |
| function closeModal() { |
| document.getElementById('modal').style.display = 'none'; |
| } |
| |
| |
| function showToast(message) { |
| const toast = document.getElementById('toast'); |
| toast.textContent = message; |
| toast.classList.add('show'); |
| |
| setTimeout(() => { |
| toast.classList.remove('show'); |
| }, 3000); |
| } |
| |
| |
| window.onclick = function(event) { |
| const modal = document.getElementById('modal'); |
| if (event.target === modal) { |
| closeModal(); |
| } |
| } |
| |
| |
| initGame(); |
| initAudio(); |
| showToast('Welcome to AvaRush!'); |
| </script> |
| </body> |
| </html> |