/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Gem = Container.expand(function (gemType, gridX, gridY) { var self = Container.call(this); self.gemType = gemType; self.gridX = gridX; self.gridY = gridY; self.isAnimating = false; self.hasMoved = false; // Track if bo1 gem has been moved var gemAssets = ['gemRed', 'gemBlue', 'gemGreen', 'gemYellow', 'gemPurple', 'gemOrange', 'bo1']; var gemGraphics = self.attachAsset(gemAssets[gemType], { anchorX: 0.5, anchorY: 0.5 }); self.setGridPosition = function (x, y) { self.gridX = x; self.gridY = y; }; self.animateToPosition = function (targetX, targetY, duration, onComplete) { if (!duration) duration = 200; self.isAnimating = true; tween(self, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut, onFinish: function onFinish() { self.isAnimating = false; if (onComplete) onComplete(); } }); }; self.startBo1Animation = function () { if (self.gemType !== 6) return; function createPulseEffect() { if (!self.parent) return; // Pulsing scale effect tween(self, { scaleX: 1.3, scaleY: 1.3 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent && self.gemType === 6) { createPulseEffect(); } } }); } }); } function createGlowEffect() { if (!self.parent) return; // Glowing tint effect tween(self, { tint: 0xFFD700 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (!self.parent) return; tween(self, { tint: 0xFFFFFF }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent && self.gemType === 6) { createGlowEffect(); } } }); } }); } function createRotationEffect() { if (!self.parent) return; // Gentle rotating effect var currentRotation = self.rotation || 0; tween(self, { rotation: currentRotation + Math.PI * 2 }, { duration: 3000, easing: tween.linear, onFinish: function onFinish() { if (self.parent && self.gemType === 6) { createRotationEffect(); } } }); } // Start all bo1 effects with slight delays LK.setTimeout(function () { if (self.parent && self.gemType === 6) { createPulseEffect(); } }, 100); LK.setTimeout(function () { if (self.parent && self.gemType === 6) { createGlowEffect(); } }, 200); LK.setTimeout(function () { if (self.parent && self.gemType === 6) { createRotationEffect(); } }, 300); }; // Special animation for bo1 gems - call after function is defined if (self.gemType === 6) { self.startBo1Animation(); } self.destroy = function () { // Play gem-specific destruction sound var gemSounds = ['gemRedDestroy', 'gemBlueDestroy', 'gemGreenDestroy', 'gemYellowDestroy', 'gemPurpleDestroy', 'gemOrangeDestroy', 'gemPinkDestroy']; if (self.gemType >= 0 && self.gemType < gemSounds.length) { try { LK.getSound(gemSounds[self.gemType]).play(); } catch (e) { console.log('Sound not found:', gemSounds[self.gemType]); } } // Special animation for red gems (gemType 0) if (self.gemType === 0) { self.createFlameExplosion(); } // Special animation for blue gems (gemType 1) if (self.gemType === 1) { self.createWaterExplosion(); } // Special animation for green gems (gemType 2) if (self.gemType === 2) { self.createLeafSpiral(); } // Special animation for yellow gems (gemType 3) if (self.gemType === 3) { self.createStarExplosion(); } // Special animation for purple gems (gemType 4) if (self.gemType === 4) { self.createPurpleImplosion(); } // Special animation for orange gems (gemType 5) if (self.gemType === 5) { self.createLavaMelting(); } // Special animation for pink gems (gemType 6) if (self.gemType === 6) { self.createHeartExplosion(); } // Create tween effect before actually destroying tween(self, { alpha: 0, scaleX: 0.2, scaleY: 0.2 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { if (self.parent) { self.parent.removeChild(self); } } }); }; self.createFlameExplosion = function () { var flameCount = 6; // Reduced from 8 var radius = 100; for (var i = 0; i < flameCount; i++) { var angle = i / flameCount * Math.PI * 2; var flameParticle = LK.getAsset('flameParticle', { anchorX: 0.5, anchorY: 0.5 }); // Set initial position at gem center flameParticle.x = self.x; flameParticle.y = self.y; flameParticle.zIndex = 5; // Add to game if (self.parent) { self.parent.addChild(flameParticle); } // Calculate spiral target position var targetX = self.x + Math.cos(angle) * radius; var targetY = self.y + Math.sin(angle) * radius; // Create spiral rotation and movement animation tween(flameParticle, { x: targetX, y: targetY, rotation: Math.PI * 4, // 2 full rotations scaleX: 3.0, scaleY: 3.0 }, { duration: 400, easing: tween.easeOut }); // Fade out and disappear tween(flameParticle, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (flameParticle.parent) { flameParticle.parent.removeChild(flameParticle); } } }); // Add flame color transition from orange to red tween(flameParticle, { tint: 0xff0000 }, { duration: 300, easing: tween.easeInOut }); } }; self.createWaterExplosion = function () { var dropletCount = 8; // Reduced from 12 var maxRadius = 120; for (var i = 0; i < dropletCount; i++) { var angle = i / dropletCount * Math.PI * 2; var waterDroplet = LK.getAsset('waterDroplet', { anchorX: 0.5, anchorY: 0.5 }); // Set initial position at gem center waterDroplet.x = self.x; waterDroplet.y = self.y; waterDroplet.zIndex = 5; // Random radius for scattered effect var radius = 60 + Math.random() * 60; // Add to game if (self.parent) { self.parent.addChild(waterDroplet); } // Calculate target position for water droplet var targetX = self.x + Math.cos(angle) * radius; var targetY = self.y + Math.sin(angle) * radius; // Create bouncing water droplet movement tween(waterDroplet, { x: targetX, y: targetY, scaleX: 2.5, scaleY: 2.5 }, { duration: 350, easing: tween.bounceOut }); // Create ripple effect with scaling tween(waterDroplet, { scaleX: 0.8, scaleY: 1.2 }, { duration: 200, easing: tween.easeInOut }); // Fade out like evaporating water tween(waterDroplet, { alpha: 0, scaleY: 0.2 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { if (waterDroplet.parent) { waterDroplet.parent.removeChild(waterDroplet); } } }); // Add water color transition from light blue to transparent tween(waterDroplet, { tint: 0x00aaff }, { duration: 250, easing: tween.easeInOut }); } }; self.createPurpleImplosion = function () { var vortexCount = 10; // Reduced from 16 var initialRadius = 150; for (var i = 0; i < vortexCount; i++) { var angle = i / vortexCount * Math.PI * 2; var purpleVortex = LK.getAsset('purpleVortex', { anchorX: 0.5, anchorY: 0.5 }); // Set initial position in a circle around the gem var startX = self.x + Math.cos(angle) * initialRadius; var startY = self.y + Math.sin(angle) * initialRadius; purpleVortex.x = startX; purpleVortex.y = startY; purpleVortex.zIndex = 5; // Add purple tint and initial scale purpleVortex.tint = 0x8B00FF; purpleVortex.scaleX = 0.5; purpleVortex.scaleY = 0.5; // Add to game if (self.parent) { self.parent.addChild(purpleVortex); } // Create spiral inward movement animation tween(purpleVortex, { x: self.x, y: self.y, rotation: Math.PI * 6, // 3 full rotations inward scaleX: 3.5, scaleY: 3.5 }, { duration: 600, easing: tween.easeIn }); // Fade in then fade out with implosion effect tween(purpleVortex, { alpha: 1.0 }, { duration: 200, easing: tween.easeOut }); // Final implosion - scale down and disappear tween(purpleVortex, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { if (purpleVortex.parent) { purpleVortex.parent.removeChild(purpleVortex); } } }); // Color transition from purple to dark purple tween(purpleVortex, { tint: 0x4B0082 }, { duration: 300, easing: tween.easeInOut }); } }; self.createLeafSpiral = function () { var leafCount = 6; // Reduced from 10 var spiralRadius = 140; for (var i = 0; i < leafCount; i++) { var angle = i / leafCount * Math.PI * 2; var leafParticle = LK.getAsset('leafParticle', { anchorX: 0.5, anchorY: 0.5 }); // Set initial position at gem center leafParticle.x = self.x; leafParticle.y = self.y; leafParticle.zIndex = 5; // Add natural green tint and initial scale leafParticle.tint = 0x228B22; leafParticle.scaleX = 0.8; leafParticle.scaleY = 0.8; leafParticle.rotation = angle; // Initial rotation based on position // Add to game if (self.parent) { self.parent.addChild(leafParticle); } // Create spiral outward movement with natural floating motion var targetX = self.x + Math.cos(angle) * spiralRadius; var targetY = self.y + Math.sin(angle) * spiralRadius; // Add some randomness for natural leaf movement targetX += (Math.random() - 0.5) * 60; targetY += (Math.random() - 0.5) * 60; // Create floating leaf movement animation tween(leafParticle, { x: targetX, y: targetY, rotation: angle + Math.PI * 3, // 1.5 full rotations scaleX: 2.8, scaleY: 2.8 }, { duration: 500, easing: tween.easeOut }); // Add gentle swaying motion like leaves in wind tween(leafParticle, { x: targetX + Math.sin(angle * 2) * 30, y: targetY + Math.cos(angle * 2) * 20 }, { duration: 800, easing: tween.easeInOut }); // Fade out like autumn leaves tween(leafParticle, { alpha: 0, scaleX: 0.3, scaleY: 0.3, rotation: angle + Math.PI * 5 // Continue rotating as it fades }, { duration: 700, easing: tween.easeInOut, onFinish: function onFinish() { if (leafParticle.parent) { leafParticle.parent.removeChild(leafParticle); } } }); // Add leaf color transition from green to brown tween(leafParticle, { tint: 0x8B4513 // Brown color }, { duration: 400, easing: tween.easeInOut }); } }; self.createStarExplosion = function () { var starCount = 8; // Reduced from 14 var maxRadius = 160; for (var i = 0; i < starCount; i++) { var angle = i / starCount * Math.PI * 2; var starParticle = LK.getAsset('starParticle', { anchorX: 0.5, anchorY: 0.5 }); // Set initial position at gem center starParticle.x = self.x; starParticle.y = self.y; starParticle.zIndex = 5; // Add golden yellow tint and initial scale starParticle.tint = 0xFFD700; starParticle.scaleX = 0.3; starParticle.scaleY = 0.3; starParticle.rotation = angle; // Add to game if (self.parent) { self.parent.addChild(starParticle); } // Create spiral outward sun burst movement var radius = 80 + Math.random() * 80; var targetX = self.x + Math.cos(angle) * radius; var targetY = self.y + Math.sin(angle) * radius; // Add sparkle randomness for star effect targetX += (Math.random() - 0.5) * 40; targetY += (Math.random() - 0.5) * 40; // Create explosive star movement animation tween(starParticle, { x: targetX, y: targetY, rotation: angle + Math.PI * 4, // 2 full rotations scaleX: 5.0, scaleY: 5.0 }, { duration: 450, easing: tween.easeOut }); // Add pulsing sparkle effect tween(starParticle, { scaleX: 6.5, scaleY: 6.5 }, { duration: 150, easing: tween.easeInOut }); // Create secondary sparkle pulse tween(starParticle, { scaleX: 4.5, scaleY: 4.5, rotation: angle + Math.PI * 6 // Continue rotating }, { duration: 300, easing: tween.easeInOut }); // Fade out like fading starlight tween(starParticle, { alpha: 0, scaleX: 0.2, scaleY: 0.2, rotation: angle + Math.PI * 8 // Final rotation burst }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (starParticle.parent) { starParticle.parent.removeChild(starParticle); } } }); // Add star color transition from gold to bright white tween(starParticle, { tint: 0xFFFFFF // Bright white }, { duration: 350, easing: tween.easeInOut }); } }; self.createLavaMelting = function () { var lavaCount = 8; // Reduced from 12 var meltRadius = 130; for (var i = 0; i < lavaCount; i++) { var angle = i / lavaCount * Math.PI * 2; var lavaParticle = LK.getAsset('lavaParticle', { anchorX: 0.5, anchorY: 0.5 }); // Set initial position at gem center lavaParticle.x = self.x; lavaParticle.y = self.y; lavaParticle.zIndex = 5; // Add molten orange-red tint and initial scale lavaParticle.tint = 0xFF4500; lavaParticle.scaleX = 0.6; lavaParticle.scaleY = 0.6; lavaParticle.rotation = Math.random() * Math.PI * 2; // Add to game if (self.parent) { self.parent.addChild(lavaParticle); } // Create melting downward movement with spiral motion var radius = 70 + Math.random() * 60; var targetX = self.x + Math.cos(angle) * radius; var targetY = self.y + Math.sin(angle) * radius; // Add downward melting bias targetY += Math.random() * 80 + 40; // Add some randomness for natural lava flow targetX += (Math.random() - 0.5) * 50; // Create lava melting movement animation tween(lavaParticle, { x: targetX, y: targetY, rotation: angle + Math.PI * 3, // 1.5 full rotations scaleX: 3.2, scaleY: 3.2 }, { duration: 550, easing: tween.easeOut }); // Add viscous lava dripping effect tween(lavaParticle, { y: targetY + 60, scaleY: 4.0 // Stretch vertically like dripping lava }, { duration: 400, easing: tween.easeIn }); // Create molten glow pulsing effect tween(lavaParticle, { scaleX: 4.0, tint: 0xFF6600 // Brighter orange }, { duration: 300, easing: tween.easeInOut }); // Cool down and solidify effect tween(lavaParticle, { alpha: 0, scaleX: 0.4, scaleY: 0.2, tint: 0x8B0000, // Dark red when cooling rotation: angle + Math.PI * 5 // Continue rotating as it cools }, { duration: 650, easing: tween.easeInOut, onFinish: function onFinish() { if (lavaParticle.parent) { lavaParticle.parent.removeChild(lavaParticle); } } }); // Add secondary heat shimmer effect tween(lavaParticle, { scaleX: 2.8, scaleY: 3.5 }, { duration: 200, easing: tween.easeInOut }); } }; self.createHeartExplosion = function () { var heartCount = 8; // Heart-shaped particles var maxRadius = 140; for (var i = 0; i < heartCount; i++) { var angle = i / heartCount * Math.PI * 2; var heartParticle = LK.getAsset('starParticle', { anchorX: 0.5, anchorY: 0.5 }); // Set initial position at gem center heartParticle.x = self.x; heartParticle.y = self.y; heartParticle.zIndex = 5; // Add pink tint and initial scale heartParticle.tint = 0xFF69B4; // Hot pink color heartParticle.scaleX = 0.4; heartParticle.scaleY = 0.4; heartParticle.rotation = angle; // Add to game if (self.parent) { self.parent.addChild(heartParticle); } // Create heart-shaped movement pattern var radius = 80 + Math.random() * 60; var targetX = self.x + Math.cos(angle) * radius; var targetY = self.y + Math.sin(angle) * radius; // Add heart-like floating effect targetX += Math.sin(angle * 2) * 30; targetY += Math.cos(angle * 2) * 20; // Create romantic floating movement animation tween(heartParticle, { x: targetX, y: targetY, rotation: angle + Math.PI * 3, // 1.5 full rotations scaleX: 4.0, scaleY: 4.0 }, { duration: 500, easing: tween.easeOut }); // Add gentle floating motion like floating hearts tween(heartParticle, { y: targetY - 40, scaleX: 5.0, scaleY: 5.0 }, { duration: 400, easing: tween.easeInOut }); // Fade out like disappearing love tween(heartParticle, { alpha: 0, scaleX: 0.3, scaleY: 0.3, rotation: angle + Math.PI * 5 // Continue romantic spinning }, { duration: 700, easing: tween.easeInOut, onFinish: function onFinish() { if (heartParticle.parent) { heartParticle.parent.removeChild(heartParticle); } } }); // Add color transition from hot pink to light pink tween(heartParticle, { tint: 0xFFB6C1 // Light pink color }, { duration: 350, easing: tween.easeInOut }); } }; return self; }); var Particle = Container.expand(function (x, y, color) { var self = Container.call(this); var particleGraphics = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5 }); self.reset = function (x, y, color) { // Set particle color particleGraphics.tint = color; // Set initial position self.x = x; self.y = y; // Random velocity - increased for more dramatic effect self.velocityX = (Math.random() - 0.5) * 300; self.velocityY = (Math.random() - 0.5) * 300; self.gravity = 600; self.life = 1.0; self.fadeSpeed = 1.0; self.scale = 1.0; self.scaleSpeed = 0.5; self.alpha = 1.0; self.scaleX = 1.0; self.scaleY = 1.0; }; // Initialize with provided values self.reset(x, y, color); self.update = function () { // Update position based on velocity self.x += self.velocityX * (1 / 60); self.y += self.velocityY * (1 / 60); // Apply gravity (positive for flipped board - particles fall down visually) self.velocityY += self.gravity * (1 / 60); // Fade out over time self.life -= self.fadeSpeed * (1 / 60); self.alpha = Math.max(0, self.life); // Scale down over time for dramatic effect self.scale -= self.scaleSpeed * (1 / 60); if (self.scale > 0) { self.scaleX = self.scale; self.scaleY = self.scale; } // Remove when fully faded if (self.life <= 0) { self.destroy(); } }; self.destroy = function () { if (self.parent) { self.parent.removeChild(self); // Remove from active particles array for (var i = 0; i < activeParticles.length; i++) { if (activeParticles[i] === self) { activeParticles.splice(i, 1); break; } } // Return to pool for reuse if (particlePool.length < 50) { particlePool.push(self); } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ var particlePool = []; var activeParticles = []; function createParticle(x, y, color) { var particle; if (particlePool.length > 0) { particle = particlePool.pop(); particle.reset(x, y, color); } else { particle = new Particle(x, y, color); } activeParticles.push(particle); return particle; } var GRID_SIZE_X = 6; var GRID_SIZE_Y = 9; var CELL_SIZE = 250; var BOARD_OFFSET_X = (2048 - GRID_SIZE_X * CELL_SIZE) / 2; var BOARD_OFFSET_Y = (2732 - GRID_SIZE_Y * CELL_SIZE) / 2; var gameBoard = []; var selectedGem = null; var draggedGem = null; var dragStartPos = null; var isDragging = false; var isSwapping = false; var score = 0; var comboMultiplier = 1; var consecutiveGemsDestroyed = 0; // Timer system - 7.5 minutes in seconds var gameTimeLimit = 7.5 * 60; // 7.5 minutes = 450 seconds var remainingTime = gameTimeLimit; var gameTimer = null; // Background removed - using plain black background // Update timer display function updateTimer() { var minutes = Math.floor(remainingTime / 60); var seconds = remainingTime % 60; var timeText = minutes + ':' + (seconds < 10 ? '0' : '') + seconds; timerTxt.setText(timeText); } // Update health bar display function updateHealthBar() { // Calculate how many segments should be visible (10 segments total) var visibleSegments = Math.ceil(playerHealth / 10); // Update health text healthTxt.setText('HEALTH: ' + playerHealth); // Remove segments that should no longer be visible for (var i = 0; i < healthSegments.length; i++) { var segment = healthSegments[i]; if (i >= visibleSegments && segment.parent) { // Animate segment removal with cutting effect tween(segment, { scaleX: 0, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function (segmentToRemove) { return function () { if (segmentToRemove.parent) { segmentToRemove.parent.removeChild(segmentToRemove); } }; }(segment) }); } } // Add back segments that should be visible but are missing for (var i = 0; i < visibleSegments; i++) { var segment = healthSegments[i]; if (segment && !segment.parent) { // Re-add the segment to the GUI segment.scaleX = 1; segment.alpha = 1; segment.tint = 0x00ff00; lolo1.addChild(segment); // Animate segment restoration with growing effect tween(segment, { scaleX: 1, alpha: 1 }, { duration: 300, easing: tween.easeOut }); } } // Flash remaining segments red when taking damage if (playerHealth < maxHealth) { for (var j = 0; j < visibleSegments; j++) { if (healthSegments[j] && healthSegments[j].parent) { tween(healthSegments[j], { tint: 0xff6666 }, { duration: 200, easing: tween.easeInOut, onFinish: function (segmentToFlash) { return function () { tween(segmentToFlash, { tint: 0x00ff00 }, { duration: 200, easing: tween.easeInOut }); }; }(healthSegments[j]) }); } } } } // Reduce player health function reduceHealth(amount) { playerHealth = Math.max(0, playerHealth - amount); updateHealthBar(); // Flash screen red when taking damage LK.effects.flashScreen(0xff0000, 300); // Check for game over if (playerHealth <= 0) { LK.showGameOver(); } } // Create explosion effect at gem position function createExplosion(x, y, gemColor) { var particleCount = 8; // Reduced from 15 var gemColors = [0xff2222, 0x2222ff, 0x22ff22, 0xffff22, 0xff22ff, 0xff6622, 0xFF69B4]; var color = gemColors[gemColor] || 0xffffff; for (var i = 0; i < particleCount; i++) { var particle = createParticle(x, y, color); game.addChild(particle); } } // Board frame removed - using plain black background // Create background layer var lolo0 = new Container(); game.addChild(lolo0); lolo0.zIndex = 1; // Behind all other elements // Add black background to background layer var blackBg = lolo0.attachAsset('blackBackground', { anchorX: 0, anchorY: 0 }); blackBg.x = 0; blackBg.y = 0; // Create UI container for all game elements var lolo1 = new Container(); game.addChild(lolo1); lolo1.zIndex = 2; // In front of lolo0 layer // Create score display var scoreTxt = new Text2('SCORE: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); // Add purple shadow effect and purple border stroke if (scoreTxt.style) { scoreTxt.style.dropShadow = true; scoreTxt.style.dropShadowColor = "#8B00FF"; scoreTxt.style.dropShadowBlur = 3; scoreTxt.style.dropShadowDistance = 3; scoreTxt.style.stroke = "#8B00FF"; scoreTxt.style.strokeThickness = 4; } lolo1.addChild(scoreTxt); scoreTxt.x = BOARD_OFFSET_X + GRID_SIZE_X * CELL_SIZE / 2; scoreTxt.y = BOARD_OFFSET_Y + GRID_SIZE_Y * CELL_SIZE + 100; // Create health system var playerHealth = 100; var maxHealth = 100; // Create health bar background (red) var healthBarBg = LK.getAsset('healthBarBg', { anchorX: 0, anchorY: 0.5 }); healthBarBg.x = BOARD_OFFSET_X; healthBarBg.y = BOARD_OFFSET_Y - 150; lolo1.addChild(healthBarBg); // Create segmented health bar with 10 individual pieces var healthSegments = []; var segmentWidth = 40; // Each segment is 40px wide (400px total / 10 segments) for (var i = 0; i < 10; i++) { var healthSegment = LK.getAsset('healthBarFg', { anchorX: 0, anchorY: 0.5 }); healthSegment.width = segmentWidth; healthSegment.x = BOARD_OFFSET_X + i * segmentWidth; healthSegment.y = BOARD_OFFSET_Y - 150; healthSegment.segmentIndex = i; healthSegments.push(healthSegment); lolo1.addChild(healthSegment); } // Create health text var healthTxt = new Text2('HEALTH: 100', { size: 50, fill: 0x8B00FF }); // Make text bold and add styling for better visibility if (healthTxt.style) { healthTxt.style.fontWeight = "bold"; healthTxt.style.stroke = "#ffffff"; healthTxt.style.strokeThickness = 2; } healthTxt.anchor.set(0, 0.5); healthTxt.x = BOARD_OFFSET_X; healthTxt.y = BOARD_OFFSET_Y - 100; lolo1.addChild(healthTxt); // Create timer display var timerTxt = new Text2('7:30', { size: 60, fill: 0xFFFF00 }); // Make timer text bold and add styling for better visibility if (timerTxt.style) { timerTxt.style.fontWeight = "bold"; timerTxt.style.stroke = "#ffffff"; timerTxt.style.strokeThickness = 2; timerTxt.style.dropShadow = true; timerTxt.style.dropShadowColor = "#8B00FF"; timerTxt.style.dropShadowBlur = 3; timerTxt.style.dropShadowDistance = 3; } timerTxt.anchor.set(1, 0); // Anchor to top-right for positioning lolo1.addChild(timerTxt); timerTxt.x = BOARD_OFFSET_X + GRID_SIZE_X * CELL_SIZE; timerTxt.y = BOARD_OFFSET_Y - 250; // Initialize empty board function initializeBoard() { gameBoard = []; for (var x = 0; x < GRID_SIZE_X; x++) { gameBoard[x] = []; for (var y = 0; y < GRID_SIZE_Y; y++) { gameBoard[x][y] = null; } } } // Fill board with random gems function fillBoard() { for (var x = 0; x < GRID_SIZE_X; x++) { for (var y = 0; y < GRID_SIZE_Y; y++) { if (!gameBoard[x][y]) { var gemType = Math.floor(Math.random() * 6); // Only generate gems 0-5, excluding bo1 var gem = new Gem(gemType, x, y); var worldPos = gridToWorld(x, y); gem.x = worldPos.x; gem.y = worldPos.y; gem.zIndex = 4; gameBoard[x][y] = gem; lolo1.addChild(gem); } } } } // Convert grid coordinates to world coordinates function gridToWorld(gridX, gridY) { return { x: BOARD_OFFSET_X + gridX * CELL_SIZE + CELL_SIZE / 2, y: BOARD_OFFSET_Y + (GRID_SIZE_Y - 1 - gridY) * CELL_SIZE + CELL_SIZE / 2 }; } // Convert world coordinates to grid coordinates function worldToGrid(worldX, worldY) { var gridX = Math.floor((worldX - BOARD_OFFSET_X) / CELL_SIZE); var gridY = GRID_SIZE_Y - 1 - Math.floor((worldY - BOARD_OFFSET_Y) / CELL_SIZE); if (gridX < 0 || gridX >= GRID_SIZE_X || gridY < 0 || gridY >= GRID_SIZE_Y) { return null; } return { x: gridX, y: gridY }; } // Check if two grid positions are adjacent function areAdjacent(pos1, pos2) { var dx = Math.abs(pos1.x - pos2.x); var dy = Math.abs(pos1.y - pos2.y); return dx === 1 && dy === 0 || dx === 0 && dy === 1; } // Check for matches starting at position (2x2 squares and 3-in-a-row) function checkMatches(startX, startY) { var gem = gameBoard[startX][startY]; if (!gem) return []; var matches = []; var gemType = gem.gemType; // Check horizontal 3-in-a-row matches var horizontalCount = 1; var horizontalGems = [gem]; // Check right direction for (var x = startX + 1; x < GRID_SIZE_X; x++) { var rightGem = gameBoard[x][startY]; if (rightGem && rightGem.gemType === gemType) { horizontalCount++; horizontalGems.push(rightGem); } else { break; } } // Check left direction for (var x = startX - 1; x >= 0; x--) { var leftGem = gameBoard[x][startY]; if (leftGem && leftGem.gemType === gemType) { horizontalCount++; horizontalGems.unshift(leftGem); } else { break; } } // Add horizontal matches if 3 or more if (horizontalCount >= 3) { for (var h = 0; h < horizontalGems.length; h++) { matches.push(horizontalGems[h]); } } // Check vertical 3-in-a-row matches var verticalCount = 1; var verticalGems = [gem]; // Check up direction for (var y = startY + 1; y < GRID_SIZE_Y; y++) { var upGem = gameBoard[startX][y]; if (upGem && upGem.gemType === gemType) { verticalCount++; verticalGems.push(upGem); } else { break; } } // Check down direction for (var y = startY - 1; y >= 0; y--) { var downGem = gameBoard[startX][y]; if (downGem && downGem.gemType === gemType) { verticalCount++; verticalGems.unshift(downGem); } else { break; } } // Add vertical matches if 3 or more if (verticalCount >= 3) { for (var v = 0; v < verticalGems.length; v++) { var found = false; for (var m = 0; m < matches.length; m++) { if (matches[m] === verticalGems[v]) { found = true; break; } } if (!found) { matches.push(verticalGems[v]); } } } // Check if this gem is part of a 2x2 square // Check all possible 2x2 squares this gem could be part of var squarePositions = [ // Top-left corner [{ x: startX, y: startY }, { x: startX + 1, y: startY }, { x: startX, y: startY + 1 }, { x: startX + 1, y: startY + 1 }], // Top-right corner [{ x: startX - 1, y: startY }, { x: startX, y: startY }, { x: startX - 1, y: startY + 1 }, { x: startX, y: startY + 1 }], // Bottom-left corner [{ x: startX, y: startY - 1 }, { x: startX + 1, y: startY - 1 }, { x: startX, y: startY }, { x: startX + 1, y: startY }], // Bottom-right corner [{ x: startX - 1, y: startY - 1 }, { x: startX, y: startY - 1 }, { x: startX - 1, y: startY }, { x: startX, y: startY }]]; for (var s = 0; s < squarePositions.length; s++) { var square = squarePositions[s]; var validSquare = true; var squareGems = []; // Check if all positions in this square are valid and contain same gem type for (var p = 0; p < square.length; p++) { var pos = square[p]; if (pos.x < 0 || pos.x >= GRID_SIZE_X || pos.y < 0 || pos.y >= GRID_SIZE_Y) { validSquare = false; break; } var squareGem = gameBoard[pos.x][pos.y]; if (!squareGem || squareGem.gemType !== gemType) { validSquare = false; break; } squareGems.push(squareGem); } // If we found a valid 2x2 square, add all gems to matches if (validSquare) { for (var g = 0; g < squareGems.length; g++) { var found = false; for (var m = 0; m < matches.length; m++) { if (matches[m] === squareGems[g]) { found = true; break; } } if (!found) { matches.push(squareGems[g]); } } } } return matches; } // Check if any valid moves exist on the board function hasValidMoves() { for (var x = 0; x < GRID_SIZE_X; x++) { for (var y = 0; y < GRID_SIZE_Y; y++) { var currentGem = gameBoard[x][y]; if (!currentGem) continue; // Check all adjacent positions for potential swaps var adjacentPositions = [{ x: x + 1, y: y }, // Right { x: x - 1, y: y }, // Left { x: x, y: y + 1 }, // Up { x: x, y: y - 1 } // Down ]; for (var i = 0; i < adjacentPositions.length; i++) { var adjPos = adjacentPositions[i]; if (adjPos.x >= 0 && adjPos.x < GRID_SIZE_X && adjPos.y >= 0 && adjPos.y < GRID_SIZE_Y) { var adjacentGem = gameBoard[adjPos.x][adjPos.y]; if (adjacentGem) { // Temporarily swap gems to check if it creates matches var tempGem1Type = currentGem.gemType; var tempGem2Type = adjacentGem.gemType; currentGem.gemType = tempGem2Type; adjacentGem.gemType = tempGem1Type; // Check if this swap creates any matches var matches1 = checkMatches(x, y); var matches2 = checkMatches(adjPos.x, adjPos.y); // Restore original gem types currentGem.gemType = tempGem1Type; adjacentGem.gemType = tempGem2Type; // If either position has matches after swap, it's a valid move if (matches1.length > 0 || matches2.length > 0) { return true; } } } } } } return false; } // Shuffle gems on the board when no valid moves exist function shuffleBoard() { var gemTypes = []; // Collect all gem types currently on the board for (var x = 0; x < GRID_SIZE_X; x++) { for (var y = 0; y < GRID_SIZE_Y; y++) { if (gameBoard[x][y]) { gemTypes.push(gameBoard[x][y].gemType); } } } // Shuffle the gem types array for (var i = gemTypes.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = gemTypes[i]; gemTypes[i] = gemTypes[j]; gemTypes[j] = temp; } // Assign shuffled gem types back to the board var typeIndex = 0; for (var x = 0; x < GRID_SIZE_X; x++) { for (var y = 0; y < GRID_SIZE_Y; y++) { if (gameBoard[x][y]) { var gem = gameBoard[x][y]; var newGemType = gemTypes[typeIndex]; typeIndex++; // Create new gem with shuffled type var newGem = new Gem(newGemType, x, y); var worldPos = gridToWorld(x, y); newGem.x = worldPos.x; newGem.y = worldPos.y; newGem.zIndex = 4; // Remove old gem and add new one gem.destroy(); gameBoard[x][y] = newGem; lolo1.addChild(newGem); } } } } // Find all matches on the board function findAllMatches() { var allMatches = []; var processedGems = []; for (var x = 0; x < GRID_SIZE_X; x++) { for (var y = 0; y < GRID_SIZE_Y; y++) { if (gameBoard[x][y]) { var matches = checkMatches(x, y); for (var i = 0; i < matches.length; i++) { var gem = matches[i]; var found = false; for (var j = 0; j < processedGems.length; j++) { if (processedGems[j] === gem) { found = true; break; } } if (!found) { allMatches.push(gem); processedGems.push(gem); } } } } } return allMatches; } // Clear matched gems function clearMatches(matches) { if (matches.length === 0) return; LK.getSound('match').play(); var points = matches.length * 10 * comboMultiplier; var oldScore = score; score += points; scoreTxt.setText('SCORE: ' + score); // Health gain system - check if player should gain health every 1500 points var oldHealthGainLevel = Math.floor(oldScore / 1500); var newHealthGainLevel = Math.floor(score / 1500); if (newHealthGainLevel > oldHealthGainLevel && playerHealth < maxHealth) { // Player gains health, but don't exceed max health var healthToGain = Math.min(10, maxHealth - playerHealth); playerHealth += healthToGain; updateHealthBar(); // Flash screen green to show health gain LK.effects.flashScreen(0x00ff00, 500); } // Check for win condition - player reaches 10000 points if (score >= 10000) { LK.clearInterval(gameTimer); LK.showYouWin(); return; } // Store destroyed gems for bo1 placement detection var destroyedGems = []; for (var i = 0; i < matches.length; i++) { var gem = matches[i]; // Store gem info before destroying destroyedGems.push({ gridX: gem.gridX, gridY: gem.gridY, gemType: gem.gemType }); // Create explosion effect at gem position createExplosion(gem.x, gem.y, gem.gemType); gameBoard[gem.gridX][gem.gridY] = null; gem.destroy(); // Change background color when gem is destroyed var backgroundColors = [0x1a1a2e, 0x16213e, 0x0f3460, 0x533483, 0x7209b7, 0x2d1b69]; var newColor = backgroundColors[Math.floor(Math.random() * backgroundColors.length)]; tween(blackBg, { tint: newColor }, { duration: 500, easing: tween.easeInOut }); } // After clearing gems, apply gravity and fill empty spaces LK.setTimeout(function () { // Apply gravity repeatedly until no more gems fall function applyGravityRepeatedly() { var moved = applyGravity(); if (moved) { LK.setTimeout(applyGravityRepeatedly, 100); } else { fillEmptySpaces(destroyedGems); // Check for new matches after falling gems settle LK.setTimeout(function () { var newMatches = findAllMatches(); if (newMatches.length > 0) { comboMultiplier++; clearMatches(newMatches); } else { comboMultiplier = 1; consecutiveGemsDestroyed = 0; // Reset counter when no more matches isSwapping = false; // Check if any valid moves exist, if not shuffle the board if (!hasValidMoves()) { shuffleBoard(); } } }, 500); } } applyGravityRepeatedly(); }, 200); } // Apply gravity to make gems fall function applyGravity() { var moved = false; for (var x = 0; x < GRID_SIZE_X; x++) { // Collect all non-null gems in this column from bottom to top var gemsInColumn = []; for (var y = 0; y < GRID_SIZE_Y; y++) { if (gameBoard[x][y]) { gemsInColumn.push(gameBoard[x][y]); } gameBoard[x][y] = null; // Clear the position } // Place gems back from bottom (index 0) upward, filling gaps for (var i = 0; i < gemsInColumn.length; i++) { var gem = gemsInColumn[i]; var newY = i; // Place at bottom-most available position if (gem.gridY !== newY) { gem.setGridPosition(x, newY); var worldPos = gridToWorld(x, newY); gem.animateToPosition(worldPos.x, worldPos.y, 300); moved = true; } gameBoard[x][newY] = gem; } } return moved; } // Check if 5 consecutive gems of any type were just destroyed and return position for 1 bo1 gem function has5ConsecutivePattern(destroyedGems) { if (!destroyedGems || destroyedGems.length < 5) return null; // Check for horizontal 5-in-a-row pattern of any gem type in destroyed gems for (var y = 0; y < GRID_SIZE_Y; y++) { for (var startX = 0; startX <= GRID_SIZE_X - 5; startX++) { // Try each gem type (0-5, excluding bo1) for (var gemType = 0; gemType < 6; gemType++) { var gemCount = 0; var consecutivePositions = []; // Check if 5 consecutive horizontal positions had gems of this type destroyed for (var x = startX; x < startX + 5; x++) { var wasGemDestroyed = false; for (var i = 0; i < destroyedGems.length; i++) { var destroyedGem = destroyedGems[i]; if (destroyedGem.gridX === x && destroyedGem.gridY === y && destroyedGem.gemType === gemType) { wasGemDestroyed = true; break; } } if (wasGemDestroyed) { gemCount++; consecutivePositions.push({ x: x, y: y }); } else { break; // Not consecutive } } if (gemCount === 5) { // Return 1 position for bo1 gem at center of the 5-gem pattern return [{ x: startX + 2, y: y }]; } } } } // Check for vertical 5-in-a-row pattern of any gem type in destroyed gems for (var x = 0; x < GRID_SIZE_X; x++) { for (var startY = 0; startY <= GRID_SIZE_Y - 5; startY++) { // Try each gem type (0-5, excluding bo1) for (var gemType = 0; gemType < 6; gemType++) { var gemCount = 0; var consecutivePositions = []; // Check if 5 consecutive vertical positions had gems of this type destroyed for (var y = startY; y < startY + 5; y++) { var wasGemDestroyed = false; for (var i = 0; i < destroyedGems.length; i++) { var destroyedGem = destroyedGems[i]; if (destroyedGem.gridX === x && destroyedGem.gridY === y && destroyedGem.gemType === gemType) { wasGemDestroyed = true; break; } } if (wasGemDestroyed) { gemCount++; consecutivePositions.push({ x: x, y: y }); } else { break; // Not consecutive } } if (gemCount === 5) { // Return 1 position for bo1 gem at center of the 5-gem pattern return [{ x: x, y: startY + 2 }]; } } } } return null; } // Fill empty spaces with new gems function fillEmptySpaces(lastDestroyedGems) { var bo1Positions = has5ConsecutivePattern(lastDestroyedGems); // First, place bo1 gems if any 5-consecutive pattern was detected if (bo1Positions && bo1Positions.length > 0) { for (var i = 0; i < bo1Positions.length; i++) { var bo1Pos = bo1Positions[i]; // Remove existing gem at bo1 position if there is one if (gameBoard[bo1Pos.x][bo1Pos.y]) { gameBoard[bo1Pos.x][bo1Pos.y].destroy(); gameBoard[bo1Pos.x][bo1Pos.y] = null; } var bo1Gem = new Gem(6, bo1Pos.x, bo1Pos.y); // gemType 6 is bo1 var worldPos = gridToWorld(bo1Pos.x, bo1Pos.y); bo1Gem.x = worldPos.x; bo1Gem.y = worldPos.y; bo1Gem.zIndex = 4; gameBoard[bo1Pos.x][bo1Pos.y] = bo1Gem; lolo1.addChild(bo1Gem); // Add visual effect to highlight bo1 appearance tween(bo1Gem, { scaleX: 1.5, scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, onFinish: function (gem) { return function () { tween(gem, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200, easing: tween.easeOut }); }; }(bo1Gem) }); } } for (var x = 0; x < GRID_SIZE_X; x++) { // Count how many empty spaces exist in this column var emptyCount = 0; for (var y = 0; y < GRID_SIZE_Y; y++) { if (!gameBoard[x][y]) { emptyCount++; } } // Fill empty spaces from top of column downward var fillIndex = 0; for (var y = 0; y < GRID_SIZE_Y; y++) { if (!gameBoard[x][y]) { var gemType = Math.floor(Math.random() * 6); // Only generate gems 0-5, excluding bo1 var gem = new Gem(gemType, x, y); var worldPos = gridToWorld(x, y); gem.x = worldPos.x; // Start new gems from above the visible area gem.y = BOARD_OFFSET_Y - CELL_SIZE * (emptyCount - fillIndex + 1); gem.zIndex = 4; gameBoard[x][y] = gem; lolo1.addChild(gem); gem.animateToPosition(worldPos.x, worldPos.y, 200 + fillIndex * 50); // Staggered timing fillIndex++; } } } } // Swap two gems // Function to destroy gems in 5x6 area around bo1 gem function destroyGemsInDirections(bo1X, bo1Y) { var gemsToDestroy = []; // Define 5x6 area around bo1 gem (2 left, 2 right, 2 up, 3 down from bo1) var startX = Math.max(0, bo1X - 2); var endX = Math.min(GRID_SIZE_X - 1, bo1X + 2); var startY = Math.max(0, bo1Y - 2); var endY = Math.min(GRID_SIZE_Y - 1, bo1Y + 3); // Collect all gems in the 5x6 area for (var x = startX; x <= endX; x++) { for (var y = startY; y <= endY; y++) { // Skip the bo1 gem itself if (x === bo1X && y === bo1Y) continue; var gem = gameBoard[x][y]; if (gem && gem.gemType !== 6) { // Don't destroy other bo1 gems gemsToDestroy.push(gem); } } } // Destroy all collected gems with animation for (var i = 0; i < gemsToDestroy.length; i++) { var gem = gemsToDestroy[i]; // Create explosion effect createExplosion(gem.x, gem.y, gem.gemType); // Clear from board gameBoard[gem.gridX][gem.gridY] = null; // Destroy gem with tween effect gem.destroy(); } // Add screen shake effect when bo1 explodes if (gemsToDestroy.length > 0) { // Create shake effect on game board and gems tween(lolo1, { x: lolo1.x + 15 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(lolo1, { x: lolo1.x - 30 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(lolo1, { x: lolo1.x + 30 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(lolo1, { x: lolo1.x - 15 }, { duration: 100, easing: tween.easeOut }); } }); } }); } }); } // Add points for destroyed gems if (gemsToDestroy.length > 0) { var points = gemsToDestroy.length * 50; // Higher points for bo1 destruction score += points; scoreTxt.setText('SCORE: ' + score); } return gemsToDestroy.length; } function swapGems(gem1, gem2) { if (isSwapping) return; // Check if either gem is bo1 and has already been moved if (gem1.gemType === 6 && gem1.hasMoved || gem2.gemType === 6 && gem2.hasMoved) { return; // Don't allow swap if bo1 has already been moved } isSwapping = true; // Store original positions var originalGem1X = gem1.gridX; var originalGem1Y = gem1.gridY; var originalGem2X = gem2.gridX; var originalGem2Y = gem2.gridY; // Mark bo1 gems as moved if (gem1.gemType === 6) { gem1.hasMoved = true; } if (gem2.gemType === 6) { gem2.hasMoved = true; } // Update grid positions gem1.setGridPosition(gem2.gridX, gem2.gridY); gem2.setGridPosition(originalGem1X, originalGem1Y); gameBoard[gem1.gridX][gem1.gridY] = gem1; gameBoard[gem2.gridX][gem2.gridY] = gem2; var pos1 = gridToWorld(gem1.gridX, gem1.gridY); var pos2 = gridToWorld(gem2.gridX, gem2.gridY); // Animate the swap gem1.animateToPosition(pos1.x, pos1.y, 200); gem2.animateToPosition(pos2.x, pos2.y, 200); LK.setTimeout(function () { // Check if either gem is bo1 and trigger directional destruction var bo1Activated = false; var bo1GemsToDestroy = []; if (gem1.gemType === 6) { // bo1 gem type var destroyedCount = destroyGemsInDirections(gem1.gridX, gem1.gridY); if (destroyedCount > 0) bo1Activated = true; // Mark bo1 for destruction after it has moved if (gem1.hasMoved) { bo1GemsToDestroy.push(gem1); } } if (gem2.gemType === 6) { // bo1 gem type var destroyedCount = destroyGemsInDirections(gem2.gridX, gem2.gridY); if (destroyedCount > 0) bo1Activated = true; // Mark bo1 for destruction after it has moved if (gem2.hasMoved) { bo1GemsToDestroy.push(gem2); } } // Destroy bo1 gems that have been moved for (var i = 0; i < bo1GemsToDestroy.length; i++) { var bo1Gem = bo1GemsToDestroy[i]; createExplosion(bo1Gem.x, bo1Gem.y, bo1Gem.gemType); gameBoard[bo1Gem.gridX][bo1Gem.gridY] = null; bo1Gem.destroy(); } var matches = findAllMatches(); if (matches.length > 0 || bo1Activated) { // Valid swap - process matches if (matches.length > 0) { clearMatches(matches); } else if (bo1Activated) { // Only bo1 was activated, apply gravity and fill spaces LK.setTimeout(function () { function applyGravityRepeatedly() { var moved = applyGravity(); if (moved) { LK.setTimeout(applyGravityRepeatedly, 100); } else { fillEmptySpaces(); LK.setTimeout(function () { var newMatches = findAllMatches(); if (newMatches.length > 0) { comboMultiplier++; clearMatches(newMatches); } else { comboMultiplier = 1; isSwapping = false; if (!hasValidMoves()) { shuffleBoard(); } } }, 500); } } applyGravityRepeatedly(); }, 200); } } else { // Invalid move - reduce health by 10 and animate back to original positions reduceHealth(10); gem1.setGridPosition(originalGem1X, originalGem1Y); gem2.setGridPosition(originalGem2X, originalGem2Y); gameBoard[gem1.gridX][gem1.gridY] = gem1; gameBoard[gem2.gridX][gem2.gridY] = gem2; var pos1 = gridToWorld(gem1.gridX, gem1.gridY); var pos2 = gridToWorld(gem2.gridX, gem2.gridY); gem1.animateToPosition(pos1.x, pos1.y, 200); gem2.animateToPosition(pos2.x, pos2.y, 200); LK.setTimeout(function () { isSwapping = false; // Check if any valid moves exist after invalid swap if (!hasValidMoves()) { shuffleBoard(); } }, 200); } }, 200); } // Game input handling game.down = function (x, y, obj) { if (isSwapping) return; var gridPos = worldToGrid(x, y); if (!gridPos) return; var clickedGem = gameBoard[gridPos.x][gridPos.y]; if (!clickedGem) return; draggedGem = clickedGem; dragStartPos = { x: x, y: y }; isDragging = true; draggedGem.alpha = 0.7; }; game.move = function (x, y, obj) { if (!isDragging || !draggedGem || isSwapping) return; // Calculate drag distance var dragDeltaX = x - dragStartPos.x; var dragDeltaY = y - dragStartPos.y; var dragDistance = Math.sqrt(dragDeltaX * dragDeltaX + dragDeltaY * dragDeltaY); // Only process if drag distance is significant enough if (dragDistance > CELL_SIZE * 0.3) { // Determine drag direction var targetGridX = draggedGem.gridX; var targetGridY = draggedGem.gridY; if (Math.abs(dragDeltaX) > Math.abs(dragDeltaY)) { // Horizontal drag if (dragDeltaX > 0) { targetGridX = Math.min(GRID_SIZE_X - 1, draggedGem.gridX + 1); } else { targetGridX = Math.max(0, draggedGem.gridX - 1); } } else { // Vertical drag (flipped coordinate system) if (dragDeltaY > 0) { targetGridY = Math.max(0, draggedGem.gridY - 1); } else { targetGridY = Math.min(GRID_SIZE_Y - 1, draggedGem.gridY + 1); } } // Update visual position to show intended swap var targetWorldPos = gridToWorld(targetGridX, targetGridY); draggedGem.x = targetWorldPos.x; draggedGem.y = targetWorldPos.y; // Store target position for later use draggedGem.targetGridX = targetGridX; draggedGem.targetGridY = targetGridY; } }; game.up = function (x, y, obj) { if (!isDragging || !draggedGem || isSwapping) return; var startGridPos = { x: draggedGem.gridX, y: draggedGem.gridY }; // Check if we have a target position from dragging if (draggedGem.targetGridX !== undefined && draggedGem.targetGridY !== undefined) { var targetGridPos = { x: draggedGem.targetGridX, y: draggedGem.targetGridY }; // Check if target position is valid and adjacent if (areAdjacent(startGridPos, targetGridPos) && gameBoard[targetGridPos.x][targetGridPos.y]) { var targetGem = gameBoard[targetGridPos.x][targetGridPos.y]; // Attempt swap swapGems(draggedGem, targetGem); } else { // Invalid swap - animate back to original position var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY); draggedGem.animateToPosition(worldPos.x, worldPos.y, 200); } // Clean up target position draggedGem.targetGridX = undefined; draggedGem.targetGridY = undefined; } else { // No significant drag - just return to original position var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY); draggedGem.animateToPosition(worldPos.x, worldPos.y, 200); } draggedGem.alpha = 1.0; draggedGem = null; dragStartPos = null; isDragging = false; }; // Create grid lines around gems function createGridLines() { // Create vertical lines for (var x = 0; x <= GRID_SIZE_X; x++) { for (var lineY = 0; lineY < GRID_SIZE_Y * CELL_SIZE; lineY += 20) { var verticalLine = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.05, scaleY: 0.5 }); verticalLine.tint = 0x8B4513; verticalLine.x = BOARD_OFFSET_X + x * CELL_SIZE; verticalLine.y = BOARD_OFFSET_Y + lineY + 10; verticalLine.zIndex = 3; game.addChild(verticalLine); } } // Create horizontal lines for (var y = 0; y <= GRID_SIZE_Y; y++) { for (var lineX = 0; lineX < GRID_SIZE_X * CELL_SIZE; lineX += 20) { var horizontalLine = LK.getAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.05 }); horizontalLine.tint = 0x8B4513; horizontalLine.x = BOARD_OFFSET_X + lineX + 10; horizontalLine.y = BOARD_OFFSET_Y + y * CELL_SIZE; horizontalLine.zIndex = 3; game.addChild(horizontalLine); } } } // Initialize the game initializeBoard(); fillBoard(); createGridLines(); // Add game update loop for continuous animations game.update = function () { // Clean up particles that are off-screen or dead for (var i = activeParticles.length - 1; i >= 0; i--) { var particle = activeParticles[i]; if (!particle.parent || particle.y > 3000 || particle.y < -500) { if (particle.parent) { particle.destroy(); } } } // Add smooth swaying rotation animation to all gems for (var x = 0; x < GRID_SIZE_X; x++) { for (var y = 0; y < GRID_SIZE_Y; y++) { var gem = gameBoard[x][y]; if (gem && !gem.isAnimating && !gem.rotationStarted) { // Create smooth swaying rotation effect gem.rotationStarted = true; var delay = (x + y) * 50; // Stagger the rotation animation var maxRotation = Math.PI / 6 + Math.random() * Math.PI / 12; // Random sway amount between 30-45 degrees LK.setTimeout(function (currentGem, maxRot) { return function () { function createSwayRotation(direction) { if (currentGem.parent && !currentGem.isAnimating) { var targetRotation = direction * maxRot; tween(currentGem, { rotation: targetRotation }, { duration: 2000 + Math.random() * 1000, // Random duration between 2-3 seconds easing: tween.easeInOut, onFinish: function onFinish() { if (currentGem.parent && !currentGem.isAnimating) { createSwayRotation(-direction); // Reverse direction for back-and-forth motion } } }); } } createSwayRotation(Math.random() > 0.5 ? 1 : -1); // Start with random direction }; }(gem, maxRotation), delay); } } } }; // Start countdown timer gameTimer = LK.setInterval(function () { remainingTime--; updateTimer(); // Game over when time runs out if (remainingTime <= 0) { LK.clearInterval(gameTimer); LK.showGameOver(); } }, 1000); // Update every 1000ms (1 second) // Start background music LK.playMusic('background'); // Initialize timer display updateTimer(); // Initial match clearing LK.setTimeout(function () { var matches = findAllMatches(); if (matches.length > 0) { clearMatches(matches); } }, 100);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Gem = Container.expand(function (gemType, gridX, gridY) {
var self = Container.call(this);
self.gemType = gemType;
self.gridX = gridX;
self.gridY = gridY;
self.isAnimating = false;
self.hasMoved = false; // Track if bo1 gem has been moved
var gemAssets = ['gemRed', 'gemBlue', 'gemGreen', 'gemYellow', 'gemPurple', 'gemOrange', 'bo1'];
var gemGraphics = self.attachAsset(gemAssets[gemType], {
anchorX: 0.5,
anchorY: 0.5
});
self.setGridPosition = function (x, y) {
self.gridX = x;
self.gridY = y;
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
if (!duration) duration = 200;
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
self.startBo1Animation = function () {
if (self.gemType !== 6) return;
function createPulseEffect() {
if (!self.parent) return;
// Pulsing scale effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.parent) return;
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && self.gemType === 6) {
createPulseEffect();
}
}
});
}
});
}
function createGlowEffect() {
if (!self.parent) return;
// Glowing tint effect
tween(self, {
tint: 0xFFD700
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.parent) return;
tween(self, {
tint: 0xFFFFFF
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent && self.gemType === 6) {
createGlowEffect();
}
}
});
}
});
}
function createRotationEffect() {
if (!self.parent) return;
// Gentle rotating effect
var currentRotation = self.rotation || 0;
tween(self, {
rotation: currentRotation + Math.PI * 2
}, {
duration: 3000,
easing: tween.linear,
onFinish: function onFinish() {
if (self.parent && self.gemType === 6) {
createRotationEffect();
}
}
});
}
// Start all bo1 effects with slight delays
LK.setTimeout(function () {
if (self.parent && self.gemType === 6) {
createPulseEffect();
}
}, 100);
LK.setTimeout(function () {
if (self.parent && self.gemType === 6) {
createGlowEffect();
}
}, 200);
LK.setTimeout(function () {
if (self.parent && self.gemType === 6) {
createRotationEffect();
}
}, 300);
};
// Special animation for bo1 gems - call after function is defined
if (self.gemType === 6) {
self.startBo1Animation();
}
self.destroy = function () {
// Play gem-specific destruction sound
var gemSounds = ['gemRedDestroy', 'gemBlueDestroy', 'gemGreenDestroy', 'gemYellowDestroy', 'gemPurpleDestroy', 'gemOrangeDestroy', 'gemPinkDestroy'];
if (self.gemType >= 0 && self.gemType < gemSounds.length) {
try {
LK.getSound(gemSounds[self.gemType]).play();
} catch (e) {
console.log('Sound not found:', gemSounds[self.gemType]);
}
}
// Special animation for red gems (gemType 0)
if (self.gemType === 0) {
self.createFlameExplosion();
}
// Special animation for blue gems (gemType 1)
if (self.gemType === 1) {
self.createWaterExplosion();
}
// Special animation for green gems (gemType 2)
if (self.gemType === 2) {
self.createLeafSpiral();
}
// Special animation for yellow gems (gemType 3)
if (self.gemType === 3) {
self.createStarExplosion();
}
// Special animation for purple gems (gemType 4)
if (self.gemType === 4) {
self.createPurpleImplosion();
}
// Special animation for orange gems (gemType 5)
if (self.gemType === 5) {
self.createLavaMelting();
}
// Special animation for pink gems (gemType 6)
if (self.gemType === 6) {
self.createHeartExplosion();
}
// Create tween effect before actually destroying
tween(self, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
};
self.createFlameExplosion = function () {
var flameCount = 6; // Reduced from 8
var radius = 100;
for (var i = 0; i < flameCount; i++) {
var angle = i / flameCount * Math.PI * 2;
var flameParticle = LK.getAsset('flameParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
flameParticle.x = self.x;
flameParticle.y = self.y;
flameParticle.zIndex = 5;
// Add to game
if (self.parent) {
self.parent.addChild(flameParticle);
}
// Calculate spiral target position
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Create spiral rotation and movement animation
tween(flameParticle, {
x: targetX,
y: targetY,
rotation: Math.PI * 4,
// 2 full rotations
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 400,
easing: tween.easeOut
});
// Fade out and disappear
tween(flameParticle, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (flameParticle.parent) {
flameParticle.parent.removeChild(flameParticle);
}
}
});
// Add flame color transition from orange to red
tween(flameParticle, {
tint: 0xff0000
}, {
duration: 300,
easing: tween.easeInOut
});
}
};
self.createWaterExplosion = function () {
var dropletCount = 8; // Reduced from 12
var maxRadius = 120;
for (var i = 0; i < dropletCount; i++) {
var angle = i / dropletCount * Math.PI * 2;
var waterDroplet = LK.getAsset('waterDroplet', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
waterDroplet.x = self.x;
waterDroplet.y = self.y;
waterDroplet.zIndex = 5;
// Random radius for scattered effect
var radius = 60 + Math.random() * 60;
// Add to game
if (self.parent) {
self.parent.addChild(waterDroplet);
}
// Calculate target position for water droplet
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Create bouncing water droplet movement
tween(waterDroplet, {
x: targetX,
y: targetY,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 350,
easing: tween.bounceOut
});
// Create ripple effect with scaling
tween(waterDroplet, {
scaleX: 0.8,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeInOut
});
// Fade out like evaporating water
tween(waterDroplet, {
alpha: 0,
scaleY: 0.2
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (waterDroplet.parent) {
waterDroplet.parent.removeChild(waterDroplet);
}
}
});
// Add water color transition from light blue to transparent
tween(waterDroplet, {
tint: 0x00aaff
}, {
duration: 250,
easing: tween.easeInOut
});
}
};
self.createPurpleImplosion = function () {
var vortexCount = 10; // Reduced from 16
var initialRadius = 150;
for (var i = 0; i < vortexCount; i++) {
var angle = i / vortexCount * Math.PI * 2;
var purpleVortex = LK.getAsset('purpleVortex', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position in a circle around the gem
var startX = self.x + Math.cos(angle) * initialRadius;
var startY = self.y + Math.sin(angle) * initialRadius;
purpleVortex.x = startX;
purpleVortex.y = startY;
purpleVortex.zIndex = 5;
// Add purple tint and initial scale
purpleVortex.tint = 0x8B00FF;
purpleVortex.scaleX = 0.5;
purpleVortex.scaleY = 0.5;
// Add to game
if (self.parent) {
self.parent.addChild(purpleVortex);
}
// Create spiral inward movement animation
tween(purpleVortex, {
x: self.x,
y: self.y,
rotation: Math.PI * 6,
// 3 full rotations inward
scaleX: 3.5,
scaleY: 3.5
}, {
duration: 600,
easing: tween.easeIn
});
// Fade in then fade out with implosion effect
tween(purpleVortex, {
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
// Final implosion - scale down and disappear
tween(purpleVortex, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (purpleVortex.parent) {
purpleVortex.parent.removeChild(purpleVortex);
}
}
});
// Color transition from purple to dark purple
tween(purpleVortex, {
tint: 0x4B0082
}, {
duration: 300,
easing: tween.easeInOut
});
}
};
self.createLeafSpiral = function () {
var leafCount = 6; // Reduced from 10
var spiralRadius = 140;
for (var i = 0; i < leafCount; i++) {
var angle = i / leafCount * Math.PI * 2;
var leafParticle = LK.getAsset('leafParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
leafParticle.x = self.x;
leafParticle.y = self.y;
leafParticle.zIndex = 5;
// Add natural green tint and initial scale
leafParticle.tint = 0x228B22;
leafParticle.scaleX = 0.8;
leafParticle.scaleY = 0.8;
leafParticle.rotation = angle; // Initial rotation based on position
// Add to game
if (self.parent) {
self.parent.addChild(leafParticle);
}
// Create spiral outward movement with natural floating motion
var targetX = self.x + Math.cos(angle) * spiralRadius;
var targetY = self.y + Math.sin(angle) * spiralRadius;
// Add some randomness for natural leaf movement
targetX += (Math.random() - 0.5) * 60;
targetY += (Math.random() - 0.5) * 60;
// Create floating leaf movement animation
tween(leafParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 3,
// 1.5 full rotations
scaleX: 2.8,
scaleY: 2.8
}, {
duration: 500,
easing: tween.easeOut
});
// Add gentle swaying motion like leaves in wind
tween(leafParticle, {
x: targetX + Math.sin(angle * 2) * 30,
y: targetY + Math.cos(angle * 2) * 20
}, {
duration: 800,
easing: tween.easeInOut
});
// Fade out like autumn leaves
tween(leafParticle, {
alpha: 0,
scaleX: 0.3,
scaleY: 0.3,
rotation: angle + Math.PI * 5 // Continue rotating as it fades
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (leafParticle.parent) {
leafParticle.parent.removeChild(leafParticle);
}
}
});
// Add leaf color transition from green to brown
tween(leafParticle, {
tint: 0x8B4513 // Brown color
}, {
duration: 400,
easing: tween.easeInOut
});
}
};
self.createStarExplosion = function () {
var starCount = 8; // Reduced from 14
var maxRadius = 160;
for (var i = 0; i < starCount; i++) {
var angle = i / starCount * Math.PI * 2;
var starParticle = LK.getAsset('starParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
starParticle.x = self.x;
starParticle.y = self.y;
starParticle.zIndex = 5;
// Add golden yellow tint and initial scale
starParticle.tint = 0xFFD700;
starParticle.scaleX = 0.3;
starParticle.scaleY = 0.3;
starParticle.rotation = angle;
// Add to game
if (self.parent) {
self.parent.addChild(starParticle);
}
// Create spiral outward sun burst movement
var radius = 80 + Math.random() * 80;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Add sparkle randomness for star effect
targetX += (Math.random() - 0.5) * 40;
targetY += (Math.random() - 0.5) * 40;
// Create explosive star movement animation
tween(starParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 4,
// 2 full rotations
scaleX: 5.0,
scaleY: 5.0
}, {
duration: 450,
easing: tween.easeOut
});
// Add pulsing sparkle effect
tween(starParticle, {
scaleX: 6.5,
scaleY: 6.5
}, {
duration: 150,
easing: tween.easeInOut
});
// Create secondary sparkle pulse
tween(starParticle, {
scaleX: 4.5,
scaleY: 4.5,
rotation: angle + Math.PI * 6 // Continue rotating
}, {
duration: 300,
easing: tween.easeInOut
});
// Fade out like fading starlight
tween(starParticle, {
alpha: 0,
scaleX: 0.2,
scaleY: 0.2,
rotation: angle + Math.PI * 8 // Final rotation burst
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (starParticle.parent) {
starParticle.parent.removeChild(starParticle);
}
}
});
// Add star color transition from gold to bright white
tween(starParticle, {
tint: 0xFFFFFF // Bright white
}, {
duration: 350,
easing: tween.easeInOut
});
}
};
self.createLavaMelting = function () {
var lavaCount = 8; // Reduced from 12
var meltRadius = 130;
for (var i = 0; i < lavaCount; i++) {
var angle = i / lavaCount * Math.PI * 2;
var lavaParticle = LK.getAsset('lavaParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
lavaParticle.x = self.x;
lavaParticle.y = self.y;
lavaParticle.zIndex = 5;
// Add molten orange-red tint and initial scale
lavaParticle.tint = 0xFF4500;
lavaParticle.scaleX = 0.6;
lavaParticle.scaleY = 0.6;
lavaParticle.rotation = Math.random() * Math.PI * 2;
// Add to game
if (self.parent) {
self.parent.addChild(lavaParticle);
}
// Create melting downward movement with spiral motion
var radius = 70 + Math.random() * 60;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Add downward melting bias
targetY += Math.random() * 80 + 40;
// Add some randomness for natural lava flow
targetX += (Math.random() - 0.5) * 50;
// Create lava melting movement animation
tween(lavaParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 3,
// 1.5 full rotations
scaleX: 3.2,
scaleY: 3.2
}, {
duration: 550,
easing: tween.easeOut
});
// Add viscous lava dripping effect
tween(lavaParticle, {
y: targetY + 60,
scaleY: 4.0 // Stretch vertically like dripping lava
}, {
duration: 400,
easing: tween.easeIn
});
// Create molten glow pulsing effect
tween(lavaParticle, {
scaleX: 4.0,
tint: 0xFF6600 // Brighter orange
}, {
duration: 300,
easing: tween.easeInOut
});
// Cool down and solidify effect
tween(lavaParticle, {
alpha: 0,
scaleX: 0.4,
scaleY: 0.2,
tint: 0x8B0000,
// Dark red when cooling
rotation: angle + Math.PI * 5 // Continue rotating as it cools
}, {
duration: 650,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (lavaParticle.parent) {
lavaParticle.parent.removeChild(lavaParticle);
}
}
});
// Add secondary heat shimmer effect
tween(lavaParticle, {
scaleX: 2.8,
scaleY: 3.5
}, {
duration: 200,
easing: tween.easeInOut
});
}
};
self.createHeartExplosion = function () {
var heartCount = 8; // Heart-shaped particles
var maxRadius = 140;
for (var i = 0; i < heartCount; i++) {
var angle = i / heartCount * Math.PI * 2;
var heartParticle = LK.getAsset('starParticle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set initial position at gem center
heartParticle.x = self.x;
heartParticle.y = self.y;
heartParticle.zIndex = 5;
// Add pink tint and initial scale
heartParticle.tint = 0xFF69B4; // Hot pink color
heartParticle.scaleX = 0.4;
heartParticle.scaleY = 0.4;
heartParticle.rotation = angle;
// Add to game
if (self.parent) {
self.parent.addChild(heartParticle);
}
// Create heart-shaped movement pattern
var radius = 80 + Math.random() * 60;
var targetX = self.x + Math.cos(angle) * radius;
var targetY = self.y + Math.sin(angle) * radius;
// Add heart-like floating effect
targetX += Math.sin(angle * 2) * 30;
targetY += Math.cos(angle * 2) * 20;
// Create romantic floating movement animation
tween(heartParticle, {
x: targetX,
y: targetY,
rotation: angle + Math.PI * 3,
// 1.5 full rotations
scaleX: 4.0,
scaleY: 4.0
}, {
duration: 500,
easing: tween.easeOut
});
// Add gentle floating motion like floating hearts
tween(heartParticle, {
y: targetY - 40,
scaleX: 5.0,
scaleY: 5.0
}, {
duration: 400,
easing: tween.easeInOut
});
// Fade out like disappearing love
tween(heartParticle, {
alpha: 0,
scaleX: 0.3,
scaleY: 0.3,
rotation: angle + Math.PI * 5 // Continue romantic spinning
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (heartParticle.parent) {
heartParticle.parent.removeChild(heartParticle);
}
}
});
// Add color transition from hot pink to light pink
tween(heartParticle, {
tint: 0xFFB6C1 // Light pink color
}, {
duration: 350,
easing: tween.easeInOut
});
}
};
return self;
});
var Particle = Container.expand(function (x, y, color) {
var self = Container.call(this);
var particleGraphics = self.attachAsset('particle', {
anchorX: 0.5,
anchorY: 0.5
});
self.reset = function (x, y, color) {
// Set particle color
particleGraphics.tint = color;
// Set initial position
self.x = x;
self.y = y;
// Random velocity - increased for more dramatic effect
self.velocityX = (Math.random() - 0.5) * 300;
self.velocityY = (Math.random() - 0.5) * 300;
self.gravity = 600;
self.life = 1.0;
self.fadeSpeed = 1.0;
self.scale = 1.0;
self.scaleSpeed = 0.5;
self.alpha = 1.0;
self.scaleX = 1.0;
self.scaleY = 1.0;
};
// Initialize with provided values
self.reset(x, y, color);
self.update = function () {
// Update position based on velocity
self.x += self.velocityX * (1 / 60);
self.y += self.velocityY * (1 / 60);
// Apply gravity (positive for flipped board - particles fall down visually)
self.velocityY += self.gravity * (1 / 60);
// Fade out over time
self.life -= self.fadeSpeed * (1 / 60);
self.alpha = Math.max(0, self.life);
// Scale down over time for dramatic effect
self.scale -= self.scaleSpeed * (1 / 60);
if (self.scale > 0) {
self.scaleX = self.scale;
self.scaleY = self.scale;
}
// Remove when fully faded
if (self.life <= 0) {
self.destroy();
}
};
self.destroy = function () {
if (self.parent) {
self.parent.removeChild(self);
// Remove from active particles array
for (var i = 0; i < activeParticles.length; i++) {
if (activeParticles[i] === self) {
activeParticles.splice(i, 1);
break;
}
}
// Return to pool for reuse
if (particlePool.length < 50) {
particlePool.push(self);
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
var particlePool = [];
var activeParticles = [];
function createParticle(x, y, color) {
var particle;
if (particlePool.length > 0) {
particle = particlePool.pop();
particle.reset(x, y, color);
} else {
particle = new Particle(x, y, color);
}
activeParticles.push(particle);
return particle;
}
var GRID_SIZE_X = 6;
var GRID_SIZE_Y = 9;
var CELL_SIZE = 250;
var BOARD_OFFSET_X = (2048 - GRID_SIZE_X * CELL_SIZE) / 2;
var BOARD_OFFSET_Y = (2732 - GRID_SIZE_Y * CELL_SIZE) / 2;
var gameBoard = [];
var selectedGem = null;
var draggedGem = null;
var dragStartPos = null;
var isDragging = false;
var isSwapping = false;
var score = 0;
var comboMultiplier = 1;
var consecutiveGemsDestroyed = 0;
// Timer system - 7.5 minutes in seconds
var gameTimeLimit = 7.5 * 60; // 7.5 minutes = 450 seconds
var remainingTime = gameTimeLimit;
var gameTimer = null;
// Background removed - using plain black background
// Update timer display
function updateTimer() {
var minutes = Math.floor(remainingTime / 60);
var seconds = remainingTime % 60;
var timeText = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText(timeText);
}
// Update health bar display
function updateHealthBar() {
// Calculate how many segments should be visible (10 segments total)
var visibleSegments = Math.ceil(playerHealth / 10);
// Update health text
healthTxt.setText('HEALTH: ' + playerHealth);
// Remove segments that should no longer be visible
for (var i = 0; i < healthSegments.length; i++) {
var segment = healthSegments[i];
if (i >= visibleSegments && segment.parent) {
// Animate segment removal with cutting effect
tween(segment, {
scaleX: 0,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function (segmentToRemove) {
return function () {
if (segmentToRemove.parent) {
segmentToRemove.parent.removeChild(segmentToRemove);
}
};
}(segment)
});
}
}
// Add back segments that should be visible but are missing
for (var i = 0; i < visibleSegments; i++) {
var segment = healthSegments[i];
if (segment && !segment.parent) {
// Re-add the segment to the GUI
segment.scaleX = 1;
segment.alpha = 1;
segment.tint = 0x00ff00;
lolo1.addChild(segment);
// Animate segment restoration with growing effect
tween(segment, {
scaleX: 1,
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Flash remaining segments red when taking damage
if (playerHealth < maxHealth) {
for (var j = 0; j < visibleSegments; j++) {
if (healthSegments[j] && healthSegments[j].parent) {
tween(healthSegments[j], {
tint: 0xff6666
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function (segmentToFlash) {
return function () {
tween(segmentToFlash, {
tint: 0x00ff00
}, {
duration: 200,
easing: tween.easeInOut
});
};
}(healthSegments[j])
});
}
}
}
}
// Reduce player health
function reduceHealth(amount) {
playerHealth = Math.max(0, playerHealth - amount);
updateHealthBar();
// Flash screen red when taking damage
LK.effects.flashScreen(0xff0000, 300);
// Check for game over
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Create explosion effect at gem position
function createExplosion(x, y, gemColor) {
var particleCount = 8; // Reduced from 15
var gemColors = [0xff2222, 0x2222ff, 0x22ff22, 0xffff22, 0xff22ff, 0xff6622, 0xFF69B4];
var color = gemColors[gemColor] || 0xffffff;
for (var i = 0; i < particleCount; i++) {
var particle = createParticle(x, y, color);
game.addChild(particle);
}
}
// Board frame removed - using plain black background
// Create background layer
var lolo0 = new Container();
game.addChild(lolo0);
lolo0.zIndex = 1; // Behind all other elements
// Add black background to background layer
var blackBg = lolo0.attachAsset('blackBackground', {
anchorX: 0,
anchorY: 0
});
blackBg.x = 0;
blackBg.y = 0;
// Create UI container for all game elements
var lolo1 = new Container();
game.addChild(lolo1);
lolo1.zIndex = 2; // In front of lolo0 layer
// Create score display
var scoreTxt = new Text2('SCORE: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
// Add purple shadow effect and purple border stroke
if (scoreTxt.style) {
scoreTxt.style.dropShadow = true;
scoreTxt.style.dropShadowColor = "#8B00FF";
scoreTxt.style.dropShadowBlur = 3;
scoreTxt.style.dropShadowDistance = 3;
scoreTxt.style.stroke = "#8B00FF";
scoreTxt.style.strokeThickness = 4;
}
lolo1.addChild(scoreTxt);
scoreTxt.x = BOARD_OFFSET_X + GRID_SIZE_X * CELL_SIZE / 2;
scoreTxt.y = BOARD_OFFSET_Y + GRID_SIZE_Y * CELL_SIZE + 100;
// Create health system
var playerHealth = 100;
var maxHealth = 100;
// Create health bar background (red)
var healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0,
anchorY: 0.5
});
healthBarBg.x = BOARD_OFFSET_X;
healthBarBg.y = BOARD_OFFSET_Y - 150;
lolo1.addChild(healthBarBg);
// Create segmented health bar with 10 individual pieces
var healthSegments = [];
var segmentWidth = 40; // Each segment is 40px wide (400px total / 10 segments)
for (var i = 0; i < 10; i++) {
var healthSegment = LK.getAsset('healthBarFg', {
anchorX: 0,
anchorY: 0.5
});
healthSegment.width = segmentWidth;
healthSegment.x = BOARD_OFFSET_X + i * segmentWidth;
healthSegment.y = BOARD_OFFSET_Y - 150;
healthSegment.segmentIndex = i;
healthSegments.push(healthSegment);
lolo1.addChild(healthSegment);
}
// Create health text
var healthTxt = new Text2('HEALTH: 100', {
size: 50,
fill: 0x8B00FF
});
// Make text bold and add styling for better visibility
if (healthTxt.style) {
healthTxt.style.fontWeight = "bold";
healthTxt.style.stroke = "#ffffff";
healthTxt.style.strokeThickness = 2;
}
healthTxt.anchor.set(0, 0.5);
healthTxt.x = BOARD_OFFSET_X;
healthTxt.y = BOARD_OFFSET_Y - 100;
lolo1.addChild(healthTxt);
// Create timer display
var timerTxt = new Text2('7:30', {
size: 60,
fill: 0xFFFF00
});
// Make timer text bold and add styling for better visibility
if (timerTxt.style) {
timerTxt.style.fontWeight = "bold";
timerTxt.style.stroke = "#ffffff";
timerTxt.style.strokeThickness = 2;
timerTxt.style.dropShadow = true;
timerTxt.style.dropShadowColor = "#8B00FF";
timerTxt.style.dropShadowBlur = 3;
timerTxt.style.dropShadowDistance = 3;
}
timerTxt.anchor.set(1, 0); // Anchor to top-right for positioning
lolo1.addChild(timerTxt);
timerTxt.x = BOARD_OFFSET_X + GRID_SIZE_X * CELL_SIZE;
timerTxt.y = BOARD_OFFSET_Y - 250;
// Initialize empty board
function initializeBoard() {
gameBoard = [];
for (var x = 0; x < GRID_SIZE_X; x++) {
gameBoard[x] = [];
for (var y = 0; y < GRID_SIZE_Y; y++) {
gameBoard[x][y] = null;
}
}
}
// Fill board with random gems
function fillBoard() {
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (!gameBoard[x][y]) {
var gemType = Math.floor(Math.random() * 6); // Only generate gems 0-5, excluding bo1
var gem = new Gem(gemType, x, y);
var worldPos = gridToWorld(x, y);
gem.x = worldPos.x;
gem.y = worldPos.y;
gem.zIndex = 4;
gameBoard[x][y] = gem;
lolo1.addChild(gem);
}
}
}
}
// Convert grid coordinates to world coordinates
function gridToWorld(gridX, gridY) {
return {
x: BOARD_OFFSET_X + gridX * CELL_SIZE + CELL_SIZE / 2,
y: BOARD_OFFSET_Y + (GRID_SIZE_Y - 1 - gridY) * CELL_SIZE + CELL_SIZE / 2
};
}
// Convert world coordinates to grid coordinates
function worldToGrid(worldX, worldY) {
var gridX = Math.floor((worldX - BOARD_OFFSET_X) / CELL_SIZE);
var gridY = GRID_SIZE_Y - 1 - Math.floor((worldY - BOARD_OFFSET_Y) / CELL_SIZE);
if (gridX < 0 || gridX >= GRID_SIZE_X || gridY < 0 || gridY >= GRID_SIZE_Y) {
return null;
}
return {
x: gridX,
y: gridY
};
}
// Check if two grid positions are adjacent
function areAdjacent(pos1, pos2) {
var dx = Math.abs(pos1.x - pos2.x);
var dy = Math.abs(pos1.y - pos2.y);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
// Check for matches starting at position (2x2 squares and 3-in-a-row)
function checkMatches(startX, startY) {
var gem = gameBoard[startX][startY];
if (!gem) return [];
var matches = [];
var gemType = gem.gemType;
// Check horizontal 3-in-a-row matches
var horizontalCount = 1;
var horizontalGems = [gem];
// Check right direction
for (var x = startX + 1; x < GRID_SIZE_X; x++) {
var rightGem = gameBoard[x][startY];
if (rightGem && rightGem.gemType === gemType) {
horizontalCount++;
horizontalGems.push(rightGem);
} else {
break;
}
}
// Check left direction
for (var x = startX - 1; x >= 0; x--) {
var leftGem = gameBoard[x][startY];
if (leftGem && leftGem.gemType === gemType) {
horizontalCount++;
horizontalGems.unshift(leftGem);
} else {
break;
}
}
// Add horizontal matches if 3 or more
if (horizontalCount >= 3) {
for (var h = 0; h < horizontalGems.length; h++) {
matches.push(horizontalGems[h]);
}
}
// Check vertical 3-in-a-row matches
var verticalCount = 1;
var verticalGems = [gem];
// Check up direction
for (var y = startY + 1; y < GRID_SIZE_Y; y++) {
var upGem = gameBoard[startX][y];
if (upGem && upGem.gemType === gemType) {
verticalCount++;
verticalGems.push(upGem);
} else {
break;
}
}
// Check down direction
for (var y = startY - 1; y >= 0; y--) {
var downGem = gameBoard[startX][y];
if (downGem && downGem.gemType === gemType) {
verticalCount++;
verticalGems.unshift(downGem);
} else {
break;
}
}
// Add vertical matches if 3 or more
if (verticalCount >= 3) {
for (var v = 0; v < verticalGems.length; v++) {
var found = false;
for (var m = 0; m < matches.length; m++) {
if (matches[m] === verticalGems[v]) {
found = true;
break;
}
}
if (!found) {
matches.push(verticalGems[v]);
}
}
}
// Check if this gem is part of a 2x2 square
// Check all possible 2x2 squares this gem could be part of
var squarePositions = [
// Top-left corner
[{
x: startX,
y: startY
}, {
x: startX + 1,
y: startY
}, {
x: startX,
y: startY + 1
}, {
x: startX + 1,
y: startY + 1
}],
// Top-right corner
[{
x: startX - 1,
y: startY
}, {
x: startX,
y: startY
}, {
x: startX - 1,
y: startY + 1
}, {
x: startX,
y: startY + 1
}],
// Bottom-left corner
[{
x: startX,
y: startY - 1
}, {
x: startX + 1,
y: startY - 1
}, {
x: startX,
y: startY
}, {
x: startX + 1,
y: startY
}],
// Bottom-right corner
[{
x: startX - 1,
y: startY - 1
}, {
x: startX,
y: startY - 1
}, {
x: startX - 1,
y: startY
}, {
x: startX,
y: startY
}]];
for (var s = 0; s < squarePositions.length; s++) {
var square = squarePositions[s];
var validSquare = true;
var squareGems = [];
// Check if all positions in this square are valid and contain same gem type
for (var p = 0; p < square.length; p++) {
var pos = square[p];
if (pos.x < 0 || pos.x >= GRID_SIZE_X || pos.y < 0 || pos.y >= GRID_SIZE_Y) {
validSquare = false;
break;
}
var squareGem = gameBoard[pos.x][pos.y];
if (!squareGem || squareGem.gemType !== gemType) {
validSquare = false;
break;
}
squareGems.push(squareGem);
}
// If we found a valid 2x2 square, add all gems to matches
if (validSquare) {
for (var g = 0; g < squareGems.length; g++) {
var found = false;
for (var m = 0; m < matches.length; m++) {
if (matches[m] === squareGems[g]) {
found = true;
break;
}
}
if (!found) {
matches.push(squareGems[g]);
}
}
}
}
return matches;
}
// Check if any valid moves exist on the board
function hasValidMoves() {
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
var currentGem = gameBoard[x][y];
if (!currentGem) continue;
// Check all adjacent positions for potential swaps
var adjacentPositions = [{
x: x + 1,
y: y
},
// Right
{
x: x - 1,
y: y
},
// Left
{
x: x,
y: y + 1
},
// Up
{
x: x,
y: y - 1
} // Down
];
for (var i = 0; i < adjacentPositions.length; i++) {
var adjPos = adjacentPositions[i];
if (adjPos.x >= 0 && adjPos.x < GRID_SIZE_X && adjPos.y >= 0 && adjPos.y < GRID_SIZE_Y) {
var adjacentGem = gameBoard[adjPos.x][adjPos.y];
if (adjacentGem) {
// Temporarily swap gems to check if it creates matches
var tempGem1Type = currentGem.gemType;
var tempGem2Type = adjacentGem.gemType;
currentGem.gemType = tempGem2Type;
adjacentGem.gemType = tempGem1Type;
// Check if this swap creates any matches
var matches1 = checkMatches(x, y);
var matches2 = checkMatches(adjPos.x, adjPos.y);
// Restore original gem types
currentGem.gemType = tempGem1Type;
adjacentGem.gemType = tempGem2Type;
// If either position has matches after swap, it's a valid move
if (matches1.length > 0 || matches2.length > 0) {
return true;
}
}
}
}
}
}
return false;
}
// Shuffle gems on the board when no valid moves exist
function shuffleBoard() {
var gemTypes = [];
// Collect all gem types currently on the board
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
gemTypes.push(gameBoard[x][y].gemType);
}
}
}
// Shuffle the gem types array
for (var i = gemTypes.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = gemTypes[i];
gemTypes[i] = gemTypes[j];
gemTypes[j] = temp;
}
// Assign shuffled gem types back to the board
var typeIndex = 0;
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
var gem = gameBoard[x][y];
var newGemType = gemTypes[typeIndex];
typeIndex++;
// Create new gem with shuffled type
var newGem = new Gem(newGemType, x, y);
var worldPos = gridToWorld(x, y);
newGem.x = worldPos.x;
newGem.y = worldPos.y;
newGem.zIndex = 4;
// Remove old gem and add new one
gem.destroy();
gameBoard[x][y] = newGem;
lolo1.addChild(newGem);
}
}
}
}
// Find all matches on the board
function findAllMatches() {
var allMatches = [];
var processedGems = [];
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
var matches = checkMatches(x, y);
for (var i = 0; i < matches.length; i++) {
var gem = matches[i];
var found = false;
for (var j = 0; j < processedGems.length; j++) {
if (processedGems[j] === gem) {
found = true;
break;
}
}
if (!found) {
allMatches.push(gem);
processedGems.push(gem);
}
}
}
}
}
return allMatches;
}
// Clear matched gems
function clearMatches(matches) {
if (matches.length === 0) return;
LK.getSound('match').play();
var points = matches.length * 10 * comboMultiplier;
var oldScore = score;
score += points;
scoreTxt.setText('SCORE: ' + score);
// Health gain system - check if player should gain health every 1500 points
var oldHealthGainLevel = Math.floor(oldScore / 1500);
var newHealthGainLevel = Math.floor(score / 1500);
if (newHealthGainLevel > oldHealthGainLevel && playerHealth < maxHealth) {
// Player gains health, but don't exceed max health
var healthToGain = Math.min(10, maxHealth - playerHealth);
playerHealth += healthToGain;
updateHealthBar();
// Flash screen green to show health gain
LK.effects.flashScreen(0x00ff00, 500);
}
// Check for win condition - player reaches 10000 points
if (score >= 10000) {
LK.clearInterval(gameTimer);
LK.showYouWin();
return;
}
// Store destroyed gems for bo1 placement detection
var destroyedGems = [];
for (var i = 0; i < matches.length; i++) {
var gem = matches[i];
// Store gem info before destroying
destroyedGems.push({
gridX: gem.gridX,
gridY: gem.gridY,
gemType: gem.gemType
});
// Create explosion effect at gem position
createExplosion(gem.x, gem.y, gem.gemType);
gameBoard[gem.gridX][gem.gridY] = null;
gem.destroy();
// Change background color when gem is destroyed
var backgroundColors = [0x1a1a2e, 0x16213e, 0x0f3460, 0x533483, 0x7209b7, 0x2d1b69];
var newColor = backgroundColors[Math.floor(Math.random() * backgroundColors.length)];
tween(blackBg, {
tint: newColor
}, {
duration: 500,
easing: tween.easeInOut
});
}
// After clearing gems, apply gravity and fill empty spaces
LK.setTimeout(function () {
// Apply gravity repeatedly until no more gems fall
function applyGravityRepeatedly() {
var moved = applyGravity();
if (moved) {
LK.setTimeout(applyGravityRepeatedly, 100);
} else {
fillEmptySpaces(destroyedGems);
// Check for new matches after falling gems settle
LK.setTimeout(function () {
var newMatches = findAllMatches();
if (newMatches.length > 0) {
comboMultiplier++;
clearMatches(newMatches);
} else {
comboMultiplier = 1;
consecutiveGemsDestroyed = 0; // Reset counter when no more matches
isSwapping = false;
// Check if any valid moves exist, if not shuffle the board
if (!hasValidMoves()) {
shuffleBoard();
}
}
}, 500);
}
}
applyGravityRepeatedly();
}, 200);
}
// Apply gravity to make gems fall
function applyGravity() {
var moved = false;
for (var x = 0; x < GRID_SIZE_X; x++) {
// Collect all non-null gems in this column from bottom to top
var gemsInColumn = [];
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (gameBoard[x][y]) {
gemsInColumn.push(gameBoard[x][y]);
}
gameBoard[x][y] = null; // Clear the position
}
// Place gems back from bottom (index 0) upward, filling gaps
for (var i = 0; i < gemsInColumn.length; i++) {
var gem = gemsInColumn[i];
var newY = i; // Place at bottom-most available position
if (gem.gridY !== newY) {
gem.setGridPosition(x, newY);
var worldPos = gridToWorld(x, newY);
gem.animateToPosition(worldPos.x, worldPos.y, 300);
moved = true;
}
gameBoard[x][newY] = gem;
}
}
return moved;
}
// Check if 5 consecutive gems of any type were just destroyed and return position for 1 bo1 gem
function has5ConsecutivePattern(destroyedGems) {
if (!destroyedGems || destroyedGems.length < 5) return null;
// Check for horizontal 5-in-a-row pattern of any gem type in destroyed gems
for (var y = 0; y < GRID_SIZE_Y; y++) {
for (var startX = 0; startX <= GRID_SIZE_X - 5; startX++) {
// Try each gem type (0-5, excluding bo1)
for (var gemType = 0; gemType < 6; gemType++) {
var gemCount = 0;
var consecutivePositions = [];
// Check if 5 consecutive horizontal positions had gems of this type destroyed
for (var x = startX; x < startX + 5; x++) {
var wasGemDestroyed = false;
for (var i = 0; i < destroyedGems.length; i++) {
var destroyedGem = destroyedGems[i];
if (destroyedGem.gridX === x && destroyedGem.gridY === y && destroyedGem.gemType === gemType) {
wasGemDestroyed = true;
break;
}
}
if (wasGemDestroyed) {
gemCount++;
consecutivePositions.push({
x: x,
y: y
});
} else {
break; // Not consecutive
}
}
if (gemCount === 5) {
// Return 1 position for bo1 gem at center of the 5-gem pattern
return [{
x: startX + 2,
y: y
}];
}
}
}
}
// Check for vertical 5-in-a-row pattern of any gem type in destroyed gems
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var startY = 0; startY <= GRID_SIZE_Y - 5; startY++) {
// Try each gem type (0-5, excluding bo1)
for (var gemType = 0; gemType < 6; gemType++) {
var gemCount = 0;
var consecutivePositions = [];
// Check if 5 consecutive vertical positions had gems of this type destroyed
for (var y = startY; y < startY + 5; y++) {
var wasGemDestroyed = false;
for (var i = 0; i < destroyedGems.length; i++) {
var destroyedGem = destroyedGems[i];
if (destroyedGem.gridX === x && destroyedGem.gridY === y && destroyedGem.gemType === gemType) {
wasGemDestroyed = true;
break;
}
}
if (wasGemDestroyed) {
gemCount++;
consecutivePositions.push({
x: x,
y: y
});
} else {
break; // Not consecutive
}
}
if (gemCount === 5) {
// Return 1 position for bo1 gem at center of the 5-gem pattern
return [{
x: x,
y: startY + 2
}];
}
}
}
}
return null;
}
// Fill empty spaces with new gems
function fillEmptySpaces(lastDestroyedGems) {
var bo1Positions = has5ConsecutivePattern(lastDestroyedGems);
// First, place bo1 gems if any 5-consecutive pattern was detected
if (bo1Positions && bo1Positions.length > 0) {
for (var i = 0; i < bo1Positions.length; i++) {
var bo1Pos = bo1Positions[i];
// Remove existing gem at bo1 position if there is one
if (gameBoard[bo1Pos.x][bo1Pos.y]) {
gameBoard[bo1Pos.x][bo1Pos.y].destroy();
gameBoard[bo1Pos.x][bo1Pos.y] = null;
}
var bo1Gem = new Gem(6, bo1Pos.x, bo1Pos.y); // gemType 6 is bo1
var worldPos = gridToWorld(bo1Pos.x, bo1Pos.y);
bo1Gem.x = worldPos.x;
bo1Gem.y = worldPos.y;
bo1Gem.zIndex = 4;
gameBoard[bo1Pos.x][bo1Pos.y] = bo1Gem;
lolo1.addChild(bo1Gem);
// Add visual effect to highlight bo1 appearance
tween(bo1Gem, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function (gem) {
return function () {
tween(gem, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
};
}(bo1Gem)
});
}
}
for (var x = 0; x < GRID_SIZE_X; x++) {
// Count how many empty spaces exist in this column
var emptyCount = 0;
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (!gameBoard[x][y]) {
emptyCount++;
}
}
// Fill empty spaces from top of column downward
var fillIndex = 0;
for (var y = 0; y < GRID_SIZE_Y; y++) {
if (!gameBoard[x][y]) {
var gemType = Math.floor(Math.random() * 6); // Only generate gems 0-5, excluding bo1
var gem = new Gem(gemType, x, y);
var worldPos = gridToWorld(x, y);
gem.x = worldPos.x;
// Start new gems from above the visible area
gem.y = BOARD_OFFSET_Y - CELL_SIZE * (emptyCount - fillIndex + 1);
gem.zIndex = 4;
gameBoard[x][y] = gem;
lolo1.addChild(gem);
gem.animateToPosition(worldPos.x, worldPos.y, 200 + fillIndex * 50); // Staggered timing
fillIndex++;
}
}
}
}
// Swap two gems
// Function to destroy gems in 5x6 area around bo1 gem
function destroyGemsInDirections(bo1X, bo1Y) {
var gemsToDestroy = [];
// Define 5x6 area around bo1 gem (2 left, 2 right, 2 up, 3 down from bo1)
var startX = Math.max(0, bo1X - 2);
var endX = Math.min(GRID_SIZE_X - 1, bo1X + 2);
var startY = Math.max(0, bo1Y - 2);
var endY = Math.min(GRID_SIZE_Y - 1, bo1Y + 3);
// Collect all gems in the 5x6 area
for (var x = startX; x <= endX; x++) {
for (var y = startY; y <= endY; y++) {
// Skip the bo1 gem itself
if (x === bo1X && y === bo1Y) continue;
var gem = gameBoard[x][y];
if (gem && gem.gemType !== 6) {
// Don't destroy other bo1 gems
gemsToDestroy.push(gem);
}
}
}
// Destroy all collected gems with animation
for (var i = 0; i < gemsToDestroy.length; i++) {
var gem = gemsToDestroy[i];
// Create explosion effect
createExplosion(gem.x, gem.y, gem.gemType);
// Clear from board
gameBoard[gem.gridX][gem.gridY] = null;
// Destroy gem with tween effect
gem.destroy();
}
// Add screen shake effect when bo1 explodes
if (gemsToDestroy.length > 0) {
// Create shake effect on game board and gems
tween(lolo1, {
x: lolo1.x + 15
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(lolo1, {
x: lolo1.x - 30
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(lolo1, {
x: lolo1.x + 30
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(lolo1, {
x: lolo1.x - 15
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
});
}
});
}
// Add points for destroyed gems
if (gemsToDestroy.length > 0) {
var points = gemsToDestroy.length * 50; // Higher points for bo1 destruction
score += points;
scoreTxt.setText('SCORE: ' + score);
}
return gemsToDestroy.length;
}
function swapGems(gem1, gem2) {
if (isSwapping) return;
// Check if either gem is bo1 and has already been moved
if (gem1.gemType === 6 && gem1.hasMoved || gem2.gemType === 6 && gem2.hasMoved) {
return; // Don't allow swap if bo1 has already been moved
}
isSwapping = true;
// Store original positions
var originalGem1X = gem1.gridX;
var originalGem1Y = gem1.gridY;
var originalGem2X = gem2.gridX;
var originalGem2Y = gem2.gridY;
// Mark bo1 gems as moved
if (gem1.gemType === 6) {
gem1.hasMoved = true;
}
if (gem2.gemType === 6) {
gem2.hasMoved = true;
}
// Update grid positions
gem1.setGridPosition(gem2.gridX, gem2.gridY);
gem2.setGridPosition(originalGem1X, originalGem1Y);
gameBoard[gem1.gridX][gem1.gridY] = gem1;
gameBoard[gem2.gridX][gem2.gridY] = gem2;
var pos1 = gridToWorld(gem1.gridX, gem1.gridY);
var pos2 = gridToWorld(gem2.gridX, gem2.gridY);
// Animate the swap
gem1.animateToPosition(pos1.x, pos1.y, 200);
gem2.animateToPosition(pos2.x, pos2.y, 200);
LK.setTimeout(function () {
// Check if either gem is bo1 and trigger directional destruction
var bo1Activated = false;
var bo1GemsToDestroy = [];
if (gem1.gemType === 6) {
// bo1 gem type
var destroyedCount = destroyGemsInDirections(gem1.gridX, gem1.gridY);
if (destroyedCount > 0) bo1Activated = true;
// Mark bo1 for destruction after it has moved
if (gem1.hasMoved) {
bo1GemsToDestroy.push(gem1);
}
}
if (gem2.gemType === 6) {
// bo1 gem type
var destroyedCount = destroyGemsInDirections(gem2.gridX, gem2.gridY);
if (destroyedCount > 0) bo1Activated = true;
// Mark bo1 for destruction after it has moved
if (gem2.hasMoved) {
bo1GemsToDestroy.push(gem2);
}
}
// Destroy bo1 gems that have been moved
for (var i = 0; i < bo1GemsToDestroy.length; i++) {
var bo1Gem = bo1GemsToDestroy[i];
createExplosion(bo1Gem.x, bo1Gem.y, bo1Gem.gemType);
gameBoard[bo1Gem.gridX][bo1Gem.gridY] = null;
bo1Gem.destroy();
}
var matches = findAllMatches();
if (matches.length > 0 || bo1Activated) {
// Valid swap - process matches
if (matches.length > 0) {
clearMatches(matches);
} else if (bo1Activated) {
// Only bo1 was activated, apply gravity and fill spaces
LK.setTimeout(function () {
function applyGravityRepeatedly() {
var moved = applyGravity();
if (moved) {
LK.setTimeout(applyGravityRepeatedly, 100);
} else {
fillEmptySpaces();
LK.setTimeout(function () {
var newMatches = findAllMatches();
if (newMatches.length > 0) {
comboMultiplier++;
clearMatches(newMatches);
} else {
comboMultiplier = 1;
isSwapping = false;
if (!hasValidMoves()) {
shuffleBoard();
}
}
}, 500);
}
}
applyGravityRepeatedly();
}, 200);
}
} else {
// Invalid move - reduce health by 10 and animate back to original positions
reduceHealth(10);
gem1.setGridPosition(originalGem1X, originalGem1Y);
gem2.setGridPosition(originalGem2X, originalGem2Y);
gameBoard[gem1.gridX][gem1.gridY] = gem1;
gameBoard[gem2.gridX][gem2.gridY] = gem2;
var pos1 = gridToWorld(gem1.gridX, gem1.gridY);
var pos2 = gridToWorld(gem2.gridX, gem2.gridY);
gem1.animateToPosition(pos1.x, pos1.y, 200);
gem2.animateToPosition(pos2.x, pos2.y, 200);
LK.setTimeout(function () {
isSwapping = false;
// Check if any valid moves exist after invalid swap
if (!hasValidMoves()) {
shuffleBoard();
}
}, 200);
}
}, 200);
}
// Game input handling
game.down = function (x, y, obj) {
if (isSwapping) return;
var gridPos = worldToGrid(x, y);
if (!gridPos) return;
var clickedGem = gameBoard[gridPos.x][gridPos.y];
if (!clickedGem) return;
draggedGem = clickedGem;
dragStartPos = {
x: x,
y: y
};
isDragging = true;
draggedGem.alpha = 0.7;
};
game.move = function (x, y, obj) {
if (!isDragging || !draggedGem || isSwapping) return;
// Calculate drag distance
var dragDeltaX = x - dragStartPos.x;
var dragDeltaY = y - dragStartPos.y;
var dragDistance = Math.sqrt(dragDeltaX * dragDeltaX + dragDeltaY * dragDeltaY);
// Only process if drag distance is significant enough
if (dragDistance > CELL_SIZE * 0.3) {
// Determine drag direction
var targetGridX = draggedGem.gridX;
var targetGridY = draggedGem.gridY;
if (Math.abs(dragDeltaX) > Math.abs(dragDeltaY)) {
// Horizontal drag
if (dragDeltaX > 0) {
targetGridX = Math.min(GRID_SIZE_X - 1, draggedGem.gridX + 1);
} else {
targetGridX = Math.max(0, draggedGem.gridX - 1);
}
} else {
// Vertical drag (flipped coordinate system)
if (dragDeltaY > 0) {
targetGridY = Math.max(0, draggedGem.gridY - 1);
} else {
targetGridY = Math.min(GRID_SIZE_Y - 1, draggedGem.gridY + 1);
}
}
// Update visual position to show intended swap
var targetWorldPos = gridToWorld(targetGridX, targetGridY);
draggedGem.x = targetWorldPos.x;
draggedGem.y = targetWorldPos.y;
// Store target position for later use
draggedGem.targetGridX = targetGridX;
draggedGem.targetGridY = targetGridY;
}
};
game.up = function (x, y, obj) {
if (!isDragging || !draggedGem || isSwapping) return;
var startGridPos = {
x: draggedGem.gridX,
y: draggedGem.gridY
};
// Check if we have a target position from dragging
if (draggedGem.targetGridX !== undefined && draggedGem.targetGridY !== undefined) {
var targetGridPos = {
x: draggedGem.targetGridX,
y: draggedGem.targetGridY
};
// Check if target position is valid and adjacent
if (areAdjacent(startGridPos, targetGridPos) && gameBoard[targetGridPos.x][targetGridPos.y]) {
var targetGem = gameBoard[targetGridPos.x][targetGridPos.y];
// Attempt swap
swapGems(draggedGem, targetGem);
} else {
// Invalid swap - animate back to original position
var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY);
draggedGem.animateToPosition(worldPos.x, worldPos.y, 200);
}
// Clean up target position
draggedGem.targetGridX = undefined;
draggedGem.targetGridY = undefined;
} else {
// No significant drag - just return to original position
var worldPos = gridToWorld(draggedGem.gridX, draggedGem.gridY);
draggedGem.animateToPosition(worldPos.x, worldPos.y, 200);
}
draggedGem.alpha = 1.0;
draggedGem = null;
dragStartPos = null;
isDragging = false;
};
// Create grid lines around gems
function createGridLines() {
// Create vertical lines
for (var x = 0; x <= GRID_SIZE_X; x++) {
for (var lineY = 0; lineY < GRID_SIZE_Y * CELL_SIZE; lineY += 20) {
var verticalLine = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.05,
scaleY: 0.5
});
verticalLine.tint = 0x8B4513;
verticalLine.x = BOARD_OFFSET_X + x * CELL_SIZE;
verticalLine.y = BOARD_OFFSET_Y + lineY + 10;
verticalLine.zIndex = 3;
game.addChild(verticalLine);
}
}
// Create horizontal lines
for (var y = 0; y <= GRID_SIZE_Y; y++) {
for (var lineX = 0; lineX < GRID_SIZE_X * CELL_SIZE; lineX += 20) {
var horizontalLine = LK.getAsset('particle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.05
});
horizontalLine.tint = 0x8B4513;
horizontalLine.x = BOARD_OFFSET_X + lineX + 10;
horizontalLine.y = BOARD_OFFSET_Y + y * CELL_SIZE;
horizontalLine.zIndex = 3;
game.addChild(horizontalLine);
}
}
}
// Initialize the game
initializeBoard();
fillBoard();
createGridLines();
// Add game update loop for continuous animations
game.update = function () {
// Clean up particles that are off-screen or dead
for (var i = activeParticles.length - 1; i >= 0; i--) {
var particle = activeParticles[i];
if (!particle.parent || particle.y > 3000 || particle.y < -500) {
if (particle.parent) {
particle.destroy();
}
}
}
// Add smooth swaying rotation animation to all gems
for (var x = 0; x < GRID_SIZE_X; x++) {
for (var y = 0; y < GRID_SIZE_Y; y++) {
var gem = gameBoard[x][y];
if (gem && !gem.isAnimating && !gem.rotationStarted) {
// Create smooth swaying rotation effect
gem.rotationStarted = true;
var delay = (x + y) * 50; // Stagger the rotation animation
var maxRotation = Math.PI / 6 + Math.random() * Math.PI / 12; // Random sway amount between 30-45 degrees
LK.setTimeout(function (currentGem, maxRot) {
return function () {
function createSwayRotation(direction) {
if (currentGem.parent && !currentGem.isAnimating) {
var targetRotation = direction * maxRot;
tween(currentGem, {
rotation: targetRotation
}, {
duration: 2000 + Math.random() * 1000,
// Random duration between 2-3 seconds
easing: tween.easeInOut,
onFinish: function onFinish() {
if (currentGem.parent && !currentGem.isAnimating) {
createSwayRotation(-direction); // Reverse direction for back-and-forth motion
}
}
});
}
}
createSwayRotation(Math.random() > 0.5 ? 1 : -1); // Start with random direction
};
}(gem, maxRotation), delay);
}
}
}
};
// Start countdown timer
gameTimer = LK.setInterval(function () {
remainingTime--;
updateTimer();
// Game over when time runs out
if (remainingTime <= 0) {
LK.clearInterval(gameTimer);
LK.showGameOver();
}
}, 1000); // Update every 1000ms (1 second)
// Start background music
LK.playMusic('background');
// Initialize timer display
updateTimer();
// Initial match clearing
LK.setTimeout(function () {
var matches = findAllMatches();
if (matches.length > 0) {
clearMatches(matches);
}
}, 100);
Alev spiral parçacık. In-Game asset. 2d. High contrast. No shadows
Su damlası patlama parçacık. In-Game asset. 2d. High contrast. No shadows
Karadelik vortex parçacık spiral. In-Game asset. 2d. High contrast. No shadows
Yeşil yaprak süzülen. In-Game asset. 2d. High contrast. No shadows
Yıldız patlama parlak. In-Game asset. 2d. High contrast. No shadows
Lav topu erimiş. In-Game asset. 2d. High contrast. No shadows
Health bar, grandyan, green,. In-Game asset. 2d. High contrast. No shadows
Hiç birşeye dokunma sadece yeşil olan kısımları kırmızı ile yer değiştir
Bomba şeklinide grandyan ama çizgili rengarenk olsun