User prompt
Please fix the bug: 'Timeout.tick error: isUnlocked is not defined' in or related to this line: 'card.isUnlocked = isUnlocked;' Line Number: 178
User prompt
Please fix the bug: 'Timeout.tick error: priceText is not defined' in or related to this line: 'card.addChild(priceText);' Line Number: 174
User prompt
elimina la funcionalidad de monedas y habilita los cosmeticos ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
haz que se pueda seleccionar cosmeticos
User prompt
haz que se pueda seleccionar cosmeticos ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.selectedSkinSet = index;' Line Number: 151
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.selectedSkinSet = index;' Line Number: 151 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
haz que se pueda seleccionar cosmeticos
User prompt
mejora la animación del titulo para que se parezca al movimiento de los personajes de aniamción de los años 20
User prompt
mejora la animación del titulo
User prompt
mejora la animación del titulo
User prompt
mejora la animación del titulo
User prompt
agrega un efecto de pulso altitulo en bucle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
agrega un efecto de pulso altitulo en bucle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
haz que el contador no disminuya hasta que finalice la gravedad y la roptura de los memes
User prompt
Agrega música al juego. Haz que sea aleatoria. cada un minuto y 20 que cambie con la otra
User prompt
Please fix the bug: 'Timeout.tick error: musicSwitchTimer is not defined' in or related to this line: 'if (musicSwitchTimer) {' Line Number: 2052
User prompt
haz que la musica sea aleatoria entre Backgroundmusic 1 y 2, y que cada 1 minuto y 20 cambie
User prompt
agrega una animación de pulso en bucle al logo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Agrega Backgroundmusic al menu en bucle
User prompt
Haz que la habilidad especial verde no se cree cuando se use otra habilidad
User prompt
haz que la habilidad cambie de asset y no se pueda romper con ningun meme
User prompt
haz que la habilidad en vez de cambiar de tinte cambie de asset y no se pueda romper con ningun meme
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of undefined (reading 'call')' in or related to this line: 'GridCell.prototype.setValue.call(this, newValue);' Line Number: 289
User prompt
Please fix the bug: 'Timeout.tick error: Cannot read properties of undefined (reading 'call')' in or related to this line: 'GridCell.prototype.init.call(this);' Line Number: 274
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScores: { classic: 0, fast: 0 } }); /**** * Classes ****/ var CosmeticsScreen = Container.expand(function () { var self = Container.call(this); // Create title var title = new Text2("Cosmetics", { size: 120, fill: 0x000000 }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 300; self.addChild(title); // Create coming soon text var comingSoonText = new Text2("Coming Soon!", { size: 80, fill: 0x888888 }); comingSoonText.anchor.set(0.5, 0.5); comingSoonText.x = 2048 / 2; comingSoonText.y = 2732 / 2; self.addChild(comingSoonText); // Create back button var backButton = new Container(); var backButtonBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5 }); backButtonBg.tint = 0xFF5500; var backText = new Text2("BACK TO MENU", { size: 70, fill: 0xFFFFFF }); backText.anchor.set(0.5, 0.5); backButton.addChild(backButtonBg); backButton.addChild(backText); backButton.x = 2048 / 2; backButton.y = 2732 / 2 + 400; self.addChild(backButton); // Add button interaction backButton.interactive = true; backButton.down = function (x, y, obj) { LK.effects.flashObject(backButtonBg, 0xFFFFFF, 200); LK.setTimeout(function () { game.removeChild(self); initializeMenu(); }, 300); }; return self; }); var GridCell = Container.expand(function () { var self = Container.call(this); self.init = function () { if (!self.background) { self.background = self.attachAsset('cuadricula', { anchorX: 0.5, anchorY: 0.5 }); } self.value = 0; self.sprite = null; self.row = -1; self.col = -1; self.isSpecial = false; self.specialType = null; return self; }; self.setValue = function (newValue) { if (self.value === newValue) { return; } self.value = newValue; if (self.sprite) { self.removeChild(self.sprite); } var spriteId = 'meme' + newValue; self.sprite = LK.getAsset(spriteId, { anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.sprite); // Apply special effects if this is a special meme if (self.isSpecial) { // Remove any previous special icon if it exists if (self.specialIcon) { self.removeChild(self.specialIcon); self.specialIcon = null; } if (self.specialType === 'bomb') { // Add bomb icon on top of the meme self.specialIcon = LK.getAsset('habilidadBomba', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.specialIcon); } else { // Add clear icon on top of the meme for horizontal/vertical specials self.specialIcon = LK.getAsset('HabilidadClear', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.specialIcon); } } }; self.activateSpecialPower = function () { if (!self.isSpecial) { return; } var cellsToDestroy = []; var bonusPoints = 0; if (self.specialType === 'horizontal') { // Destroy all cells in the same column for (var row = extraRows; row < gridSize + extraRows; row++) { if (gridCells[row][self.col] && gridCells[row][self.col] !== self) { cellsToDestroy.push(gridCells[row][self.col]); } } // Award bonus points for horizontal special activation bonusPoints = 100; } else if (self.specialType === 'vertical') { // Destroy all cells in the same row for (var col = 0; col < gridSize; col++) { if (gridCells[self.row][col] && gridCells[self.row][col] !== self) { cellsToDestroy.push(gridCells[self.row][col]); } } // Award bonus points for vertical special activation bonusPoints = 100; } else if (self.specialType === 'bomb') { // Destroy adjacent cells (bomb effect) for (var row = Math.max(extraRows, self.row - 1); row <= Math.min(gridSize + extraRows - 1, self.row + 1); row++) { for (var col = Math.max(0, self.col - 1); col <= Math.min(gridSize - 1, self.col + 1); col++) { if (gridCells[row][col] && gridCells[row][col] !== self) { cellsToDestroy.push(gridCells[row][col]); } } } // Award bonus points for bomb special activation bonusPoints = 150; } else if (self.specialType === 'green') { // Green special meme power is activated when swapped, not when destroyed // The power is implemented in handleCellTap function // Award bonus points for green special activation (though it should be handled elsewhere) bonusPoints = 200; } // Award special activation bonus points if (bonusPoints > 0) { updateScore(bonusPoints); // Show floating score text for special activation var scorePopup = new Text2("+" + bonusPoints, { size: 60, fill: 0xFFFF00 }); scorePopup.anchor.set(0.5, 0.5); scorePopup.x = self.x; scorePopup.y = self.y; game.addChild(scorePopup); // Animate score popup tween(scorePopup, { y: scorePopup.y - 80, alpha: 0 }, { duration: 800, onComplete: function onComplete() { game.removeChild(scorePopup); scorePopup.destroy(); } }); } // Destroy collected cells if (cellsToDestroy.length > 0) { destroyCells(cellsToDestroy); // Also destroy this special meme self.beingDestroyed = true; LK.getSound('Explosion').play(); LK.effects.flashObject(self, 0xFFFFFF, 200); gridContainer.removeChild(self); self.destroy(); gridCells[self.row][self.col] = null; } }; self.showSelection = function () { if (self.selectionHighlight) { return; } self.selectionHighlight = new Container(); // Create pulsating highlight effect var highlight = LK.getAsset('cuadricula', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.15, scaleY: 1.15, alpha: 0.6 }); highlight.tint = 0x00FFFF; // Cyan highlight self.selectionHighlight.addChild(highlight); self.addChildAt(self.selectionHighlight, 0); // Create pulsating animation function pulseAnimation() { tween(highlight, { alpha: 0.3, scaleX: 1.2, scaleY: 1.2 }, { duration: 500, easing: tween.easeInOutQuad, onComplete: function onComplete() { tween(highlight, { alpha: 0.6, scaleX: 1.15, scaleY: 1.15 }, { duration: 500, easing: tween.easeInOutQuad, onComplete: pulseAnimation }); } }); } pulseAnimation(); }; self.hideSelection = function () { if (self.selectionHighlight) { self.removeChild(self.selectionHighlight); self.selectionHighlight = null; } }; self.down = function (x, y, obj) { handleCellTap(self); }; self.init(); return self; }); var GreenSpecialMeme = GridCell.expand(function () { var self = GridCell.call(this); self.init = function () { self.background = self.attachAsset('cuadricula', { anchorX: 0.5, anchorY: 0.5 }); self.value = 0; self.sprite = null; self.row = -1; self.col = -1; self.isSpecial = true; self.specialType = 'green'; return self; }; // Override the setValue method to apply the green special visual self.setValue = function (newValue) { // Call the parent setValue method first GridCell.prototype.setValue.call(this, newValue); // Add the green special icon if (self.specialIcon) { self.removeChild(self.specialIcon); } self.specialIcon = self.attachAsset('habilidadVerde', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); }; // Override activateSpecialPower to handle green special ability self.activateSpecialPower = function () { if (!self.isSpecial || self.specialType !== 'green') { return; } // Implementation continues in the original method in the game code // This is just a placeholder as the actual implementation // is handled in the handleCellTap function var bonusPoints = 200; updateScore(bonusPoints); }; return self; }); var MemeParticle = Container.expand(function () { var self = Container.call(this); self.init = function (textureId) { self.textureId = textureId; if (self.sprite) { self.removeChild(self.sprite); } self.sprite = self.attachAsset(self.textureId, { anchorX: 0.5, anchorY: 0.5 }); self.alpha = 0.8; self.scale.set(0.5); // Increased initial scale self.speedX = (Math.random() - 0.5) * 1; // Reduced horizontal speed self.speedY = 0.5 + Math.random() * 0.5; // Reduced downward speed self.rotationSpeed = (Math.random() - 0.5) * 0.05; // Reduced rotation speed return self; }; self.update = function () { self.x += self.speedX; self.y += self.speedY; self.rotation += self.rotationSpeed; // Remove particle when it goes off screen if (self.y > 2732 + 100 || self.x < -100 || self.x > 2048 + 100) { // Increased threshold self.destroy(); } }; return self; }); var MenuScreen = Container.expand(function () { var self = Container.call(this); // Create title var title = self.attachAsset('titulo', { anchorX: 0.5, anchorY: 0, scaleX: 3, scaleY: 3 }); title.x = 2048 / 2; title.y = 300; // Create Zen mode button var classicButton = new Container(); var classicButtonBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5 }); classicButtonBg.tint = 0x00AA00; var classicText = new Text2("ZEN MODE", { size: 70, fill: 0xFFFFFF }); classicText.anchor.set(0.5, 0.5); classicButton.addChild(classicButtonBg); classicButton.addChild(classicText); classicButton.x = 2048 / 2; classicButton.y = 2732 / 2; self.addChild(classicButton); // Create Fast mode button var fastButton = new Container(); var fastButtonBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5 }); fastButtonBg.tint = 0x0088FF; var fastText = new Text2("FAST MODE", { size: 70, fill: 0xFFFFFF }); fastText.anchor.set(0.5, 0.5); fastButton.addChild(fastButtonBg); fastButton.addChild(fastText); fastButton.x = 2048 / 2; fastButton.y = 2732 / 2 + 300; self.addChild(fastButton); // Create Records button var recordsButton = new Container(); var recordsButtonBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5 }); recordsButtonBg.tint = 0xFFA500; var recordsText = new Text2("HIGH SCORES", { size: 70, fill: 0xFFFFFF }); recordsText.anchor.set(0.5, 0.5); recordsButton.addChild(recordsButtonBg); recordsButton.addChild(recordsText); recordsButton.x = 2048 / 2; recordsButton.y = 2732 / 2 + 900; self.addChild(recordsButton); // Create Cosmetics button var cosmeticsButton = new Container(); var cosmeticsButtonBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5 }); cosmeticsButtonBg.tint = 0x9932CC; // Purple tint var cosmeticsText = new Text2("COSMETICS", { size: 70, fill: 0xFFFFFF }); cosmeticsText.anchor.set(0.5, 0.5); cosmeticsButton.addChild(cosmeticsButtonBg); cosmeticsButton.addChild(cosmeticsText); cosmeticsButton.x = 2048 / 2; cosmeticsButton.y = 2732 / 2 + 600; self.addChild(cosmeticsButton); // Add button interactions classicButton.interactive = true; classicButton.down = function (x, y, obj) { LK.effects.flashObject(classicButtonBg, 0xFFFFFF, 200); LK.setTimeout(function () { self.startGame('classic'); }, 300); }; fastButton.interactive = true; fastButton.down = function (x, y, obj) { LK.effects.flashObject(fastButtonBg, 0xFFFFFF, 200); LK.setTimeout(function () { self.startGame('fast'); }, 300); }; // Add records button interaction recordsButton.interactive = true; recordsButton.down = function (x, y, obj) { LK.effects.flashObject(recordsButtonBg, 0xFFFFFF, 200); LK.setTimeout(function () { game.removeChild(self); showRecordsScreen(); }, 300); }; // Add cosmetics button interaction cosmeticsButton.interactive = true; cosmeticsButton.down = function (x, y, obj) { LK.effects.flashObject(cosmeticsButtonBg, 0xFFFFFF, 200); LK.setTimeout(function () { game.removeChild(self); showCosmeticsScreen(); }, 300); }; // Function to start the game self.startGame = function (mode) { game.gameMode = mode || 'classic'; game.removeChild(self); game.initializeGame(); }; // Create container for background particles self.particleContainer = new Container(); self.addChildAt(self.particleContainer, 0); // Add particles behind other elements // Meme particle types self.memeParticles = ['meme1', 'meme2', 'meme3', 'meme4', 'meme5']; // Particle emission timer self.particleTimer = LK.setInterval(function () { self.emitParticle(); }, 500); // Emit a particle every 500ms // Function to emit a particle self.emitParticle = function () { var randomMemeId = self.memeParticles[Math.floor(Math.random() * self.memeParticles.length)]; var particle = new MemeParticle().init(randomMemeId); particle.x = Math.random() * 2048; // Random horizontal position at the top particle.y = -50; // Start above the screen self.particleContainer.addChild(particle); }; // Add update method to handle particle movement self.update = function () { // Update all particles self.particleContainer.children.forEach(function (particle) { if (particle && particle.update) { particle.update(); } }); }; // Override destroy to clear interval self.destroy = function () { LK.clearInterval(self.particleTimer); self.particleTimer = null; self.particleContainer.destroy({ children: true }); self.particleContainer = null; Container.prototype.destroy.call(self); // Call parent destroy method }; return self; }); var PauseButton = Container.expand(function () { var self = Container.call(this); // Create pause button icon var pauseIcon = self.attachAsset('Pause', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); // Create background circle for the button var background = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); background.tint = 0x444444; background.alpha = 0.7; // Add the background first so it's behind the icon self.addChildAt(background, 0); // Handle button press self.down = function (x, y, obj) { LK.effects.flashObject(self, 0xFFFFFF, 200); showPausePopup(); }; return self; }); var PausePopup = Container.expand(function () { var self = Container.call(this); // Create popup background var popupBg = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.5, scaleY: 4.5 }); popupBg.tint = 0x222222; popupBg.alpha = 0.9; // Create popup title var popupTitle = new Text2("PAUSED", { size: 80, fill: 0xFFFFFF }); popupTitle.anchor.set(0.5, 0); popupTitle.y = -250; self.addChild(popupTitle); // Create continue button var continueButton = new Container(); var continueBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.2 }); continueBg.tint = 0x00AA00; var continueText = new Text2("CONTINUE", { size: 60, fill: 0xFFFFFF }); continueText.anchor.set(0.5, 0.5); continueButton.addChild(continueBg); continueButton.addChild(continueText); continueButton.y = -20; self.addChild(continueButton); // Create surrender button var surrenderButton = new Container(); var surrenderBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.2 }); surrenderBg.tint = 0xFF0000; var surrenderText = new Text2("SURRENDER", { size: 60, fill: 0xFFFFFF }); surrenderText.anchor.set(0.5, 0.5); surrenderButton.addChild(surrenderBg); surrenderButton.addChild(surrenderText); surrenderButton.y = 200; self.addChild(surrenderButton); // Add button interactions continueButton.interactive = true; continueButton.down = function (x, y, obj) { LK.effects.flashObject(continueBg, 0xFFFFFF, 200); LK.setTimeout(function () { game.removeChild(self); // Fast Mode doesn't have pause functionality }, 200); }; surrenderButton.interactive = true; surrenderButton.down = function (x, y, obj) { LK.effects.flashObject(surrenderBg, 0xFFFFFF, 200); LK.setTimeout(function () { game.removeChild(self); game.onGameOver(); }, 200); }; return self; }); var RecordsScreen = Container.expand(function () { var self = Container.call(this); // Create title var title = new Text2("High Scores", { size: 120, fill: 0x000000 }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 300; self.addChild(title); // Create zen mode score display var classicScoreBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.8 }); classicScoreBg.tint = 0x00AA00; // Format the score to have line breaks for better display var zenModeText = "ZEN MODE:\n" + storage.highScores.classic; var classicScoreText = new Text2(zenModeText, { size: 60, fill: 0xFFFFFF }); classicScoreText.anchor.set(0.5, 0.5); var classicScoreContainer = new Container(); classicScoreContainer.addChild(classicScoreBg); classicScoreContainer.addChild(classicScoreText); classicScoreContainer.x = 2048 / 2; classicScoreContainer.y = 2732 / 2 - 200; self.addChild(classicScoreContainer); // Create fast mode score display var fastScoreBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.8 }); fastScoreBg.tint = 0x0088FF; // Format the score to have line breaks for better display var fastModeText = "FAST MODE:\n" + storage.highScores.fast; var fastScoreText = new Text2(fastModeText, { size: 60, fill: 0xFFFFFF }); fastScoreText.anchor.set(0.5, 0.5); var fastScoreContainer = new Container(); fastScoreContainer.addChild(fastScoreBg); fastScoreContainer.addChild(fastScoreText); fastScoreContainer.x = 2048 / 2; fastScoreContainer.y = 2732 / 2 + 100; self.addChild(fastScoreContainer); // Create back button var backButton = new Container(); var backButtonBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5 }); backButtonBg.tint = 0xFF5500; var backText = new Text2("BACK TO MENU", { size: 70, fill: 0xFFFFFF }); backText.anchor.set(0.5, 0.5); backButton.addChild(backButtonBg); backButton.addChild(backText); backButton.x = 2048 / 2; backButton.y = 2732 / 2 + 400; self.addChild(backButton); // Add button interaction backButton.interactive = true; backButton.down = function (x, y, obj) { LK.effects.flashObject(backButtonBg, 0xFFFFFF, 200); LK.setTimeout(function () { game.removeChild(self); initializeMenu(); }, 300); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xF4FFFF }); /**** * Game Code ****/ // Game state and initialization var menuScreen; var gameInitialized = false; var score = 0; var scoreText; var gridContainer; var gameMode = 'classic'; // Track current game mode var timerText; var timerSeconds = 10; // 10 seconds timer for Fast Mode var timerInterval; var saveScoreInterval; // Interval for saving classic mode high score // Centralized initialization function for adding elements to the game function initializeGameElements() { // Create and add grid container if it doesn't exist if (!gridContainer) { gridContainer = new Container(); game.addChild(gridContainer); } // Create score text if it doesn't exist if (!scoreText) { scoreText = new Text2("Score: 0", { size: 80, fill: 0x000000 }); scoreText.anchor.set(0.5, 0); scoreText.x = 2048 / 2; scoreText.y = 100; game.addChild(scoreText); } // Create timer text for fast mode if needed if (game.gameMode === 'fast' && !timerText) { timerText = new Text2("Time: " + timerSeconds.toFixed(1), { size: 120, fill: 0xFF0000 }); timerText.anchor.set(0.5, 1); timerText.x = 2048 / 2; timerText.y = 2732 - 100; game.addChild(timerText); } // Add mode indicator text game.modeText = new Text2(game.gameMode === 'classic' ? "Zen Mode" : "Fast Mode", { size: 60, fill: game.gameMode === 'classic' ? 0x00AA00 : 0x0088FF }); game.modeText.anchor.set(0.5, 0); game.modeText.x = 2048 / 2; game.modeText.y = 180; game.addChild(game.modeText); // Add pause button to the top right corner only in zen mode if (game.gameMode === 'classic') { game.pauseButton = new PauseButton(); game.pauseButton.x = 2048 - 100; game.pauseButton.y = 100; game.pauseButton.interactive = true; game.addChild(game.pauseButton); } } // Function to show pause popup function showPausePopup() { var pausePopup = new PausePopup(); pausePopup.x = 2048 / 2; pausePopup.y = 2732 / 2; game.addChild(pausePopup); // Fast Mode doesn't have pause functionality, so no need to pause the timer } // Show records screen function showRecordsScreen() { var recordsScreen = new RecordsScreen(); game.addChild(recordsScreen); } // Show cosmetics screen function showCosmeticsScreen() { var cosmeticsScreen = new CosmeticsScreen(); game.addChild(cosmeticsScreen); } // Initialize the menu function initializeMenu() { menuScreen = new MenuScreen(); game.addChild(menuScreen); } // Function to periodically save high score function startPeriodicSave() { // Clear any existing save interval if (saveScoreInterval) { LK.clearInterval(saveScoreInterval); saveScoreInterval = null; } // Only start save interval in classic mode if (game.gameMode === 'classic') { saveScoreInterval = LK.setInterval(function () { // Check if current score is higher than stored high score if (score > storage.highScores.classic) { storage.highScores.classic = score; console.log("Classic mode high score saved: " + score); } }, 60000); // Save every minute (60000ms) } } // Initialize the game game.initializeGame = function () { // Initialize score system score = 0; // Clear existing grid container if it exists if (gridContainer) { while (gridContainer.children.length > 0) { var child = gridContainer.children[0]; gridContainer.removeChild(child); child.destroy(); } } // Initialize timer for Fast Mode if (game.gameMode === 'fast') { // Clear any existing timer if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } // Calculate initial max timer value based on score var maxTimer = 10; // Use exponential thresholds for slower decrease if (score >= 100) { maxTimer = 9; } if (score >= 500) { maxTimer = 8; } if (score >= 1000) { maxTimer = 7; } if (score >= 3500) { maxTimer = 6; } if (score >= 10000) { maxTimer = 5; } if (score >= 25000) { maxTimer = 4; } if (score >= 50000) { maxTimer = 3; } // Reset timer value timerSeconds = maxTimer; if (timerText) { timerText.setText("Time: " + maxTimer.toFixed(1)); } // Start timer countdown startTimer(); } // Centralize initialization of all game elements initializeGameElements(); // Initialize score initializeScore(); // Initialize the game grid initializeGrid(); ensureNoInitialMatches(); gameInitialized = true; // Start periodic save of high score for classic mode startPeriodicSave(); }; // Create and position score text function initializeScore() { // Now handled in initializeGameElements if (scoreText) { scoreText.setText("Score: 0"); } } // Update score display function updateScore(points) { score += points; if (scoreText) { scoreText.setText("Score: " + score); } } var cellPool = []; function getGridCell() { if (cellPool.length > 0) { return cellPool.pop().init(); } return new GridCell(); } function recycleGridCell(cell) { if (cell) { cellPool.push(cell); } } var selectedCell = null; var isAnimating = false; window.gravityInProgress = false; window.fillInProgress = false; // Track combo counts for sound effects var comboCounter = 0; var comboInProgress = false; function handleCellTap(tappedCell) { if (isAnimating || window.gravityInProgress || window.fillInProgress) { return; } if (!tappedCell || !gridCells[tappedCell.row] || gridCells[tappedCell.row][tappedCell.col] !== tappedCell || tappedCell.beingDestroyed) { return; } // Special memes need to be matched with other memes to be activated, so we don't check for special activation here // If we already have a selected cell if (selectedCell !== null) { // If it's valid if (selectedCell && gridCells[selectedCell.row] && gridCells[selectedCell.row][selectedCell.col] === selectedCell) { // If we tap the same cell, deselect it if (selectedCell === tappedCell) { selectedCell.hideSelection(); selectedCell = null; return; } // Check if they're adjacent var isAdjacent = Math.abs(selectedCell.row - tappedCell.row) === 1 && selectedCell.col === tappedCell.col || Math.abs(selectedCell.col - tappedCell.col) === 1 && selectedCell.row === tappedCell.row; if (!isAdjacent) { // Not adjacent, switch selection selectedCell.hideSelection(); selectedCell = tappedCell; selectedCell.showSelection(); return; } // They are adjacent, continue with swap var cell1 = selectedCell; var cell2 = tappedCell; // Hide selection before swap cell1.hideSelection(); selectedCell = null; var isAdjacent = Math.abs(cell1.row - cell2.row) === 1 && cell1.col === cell2.col || Math.abs(cell1.col - cell2.col) === 1 && cell1.row === cell2.row; if (!isAdjacent) { return; } } else { // Invalid selected cell, select the new one if (selectedCell) { selectedCell.hideSelection(); } selectedCell = tappedCell; selectedCell.showSelection(); return; } } else { // No selection yet, select this cell selectedCell = tappedCell; selectedCell.showSelection(); return; } isAnimating = true; var pos1_x = cell1.x; var pos1_y = cell1.y; var pos2_x = cell2.x; var pos2_y = cell2.y; var row1 = cell1.row; var col1 = cell1.col; var row2 = cell2.row; var col2 = cell2.col; // Check if cells are special memes var greenSpecialCell = null; var regularCell = null; var lineSpecialCells = []; var twoGreenSpecials = false; // Check for two green special memes if (cell1 instanceof GreenSpecialMeme && cell2 instanceof GreenSpecialMeme) { twoGreenSpecials = true; } // Check for green special meme else if (cell1 instanceof GreenSpecialMeme) { greenSpecialCell = cell1; regularCell = cell2; } else if (cell2 instanceof GreenSpecialMeme) { greenSpecialCell = cell2; regularCell = cell1; } // Check for line-clearing special memes (horizontal/vertical) if (cell1.isSpecial && (cell1.specialType === 'horizontal' || cell1.specialType === 'vertical')) { lineSpecialCells.push(cell1); } if (cell2.isSpecial && (cell2.specialType === 'horizontal' || cell2.specialType === 'vertical')) { lineSpecialCells.push(cell2); } gridCells[row1][col1] = cell2; gridCells[row2][col2] = cell1; cell1.row = row2; cell1.col = col2; cell2.row = row1; cell2.col = col1; // Make sure any selection highlights are removed if (cell1.selectionHighlight) { cell1.hideSelection(); } if (cell2.selectionHighlight) { cell2.hideSelection(); } // Check for special combinations: two green special memes, two bombs, line-clearing + bomb, or two line special memes if (twoGreenSpecials) { // Two green special memes combination - destroys all visible memes var cellsToDestroy = []; // Collect all visible cells on the board for (var row = extraRows; row < gridSize + extraRows; row++) { for (var col = 0; col < gridSize; col++) { if (gridCells[row][col] && gridCells[row][col] !== cell1 && gridCells[row][col] !== cell2) { cellsToDestroy.push(gridCells[row][col]); } } } // Award massive bonus for two green special meme combination updateScore(1000); // Show mega score popup var megaScorePopup = new Text2("+1000", { size: 100, fill: 0x00FF00 }); megaScorePopup.anchor.set(0.5, 0.5); megaScorePopup.x = 2048 / 2; megaScorePopup.y = 2732 / 2; game.addChild(megaScorePopup); // Animate mega score popup tween(megaScorePopup, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 1000, onComplete: function onComplete() { game.removeChild(megaScorePopup); megaScorePopup.destroy(); } }); // Mark special memes for destruction cell1.beingDestroyed = true; cell2.beingDestroyed = true; // Schedule destruction after the swap animation completes LK.setTimeout(function () { LK.getSound('Explosion').play(); // Flash screen effect for this powerful combination LK.effects.flashScreen(0x00FF00, 500); // Destroy the collected cells if (cellsToDestroy.length > 0) { destroyCells(cellsToDestroy); } // Destroy the special memes LK.effects.flashObject(cell1, 0xFFFFFF, 200); LK.effects.flashObject(cell2, 0xFFFFFF, 200); gridContainer.removeChild(cell1); gridContainer.removeChild(cell2); cell1.destroy(); cell2.destroy(); gridCells[cell1.row][cell1.col] = null; gridCells[cell2.row][cell2.col] = null; }, 250); } else if (cell1.isSpecial && cell1.specialType === 'bomb' && cell2.isSpecial && cell2.specialType === 'bomb') { // Two bomb special memes combination - creates a 5x5 explosion var cellsToDestroy = []; // Determine which cell was moved by the player var activatedCell = cell1; // Create a 5x5 area effect centered on the first bomb for (var r = Math.max(extraRows, cell1.row - 2); r <= Math.min(gridSize + extraRows - 1, cell1.row + 2); r++) { for (var c = Math.max(0, cell1.col - 2); c <= Math.min(gridSize - 1, cell1.col + 2); c++) { if (gridCells[r][c] && gridCells[r][c] !== cell1 && gridCells[r][c] !== cell2) { cellsToDestroy.push(gridCells[r][c]); } } } // Mark special memes for destruction cell1.beingDestroyed = true; cell2.beingDestroyed = true; // Schedule destruction after the swap animation completes LK.setTimeout(function () { LK.getSound('Explosion').play(); // Destroy the collected cells if (cellsToDestroy.length > 0) { destroyCells(cellsToDestroy); } // Destroy the special memes LK.effects.flashObject(cell1, 0xFFFFFF, 200); LK.effects.flashObject(cell2, 0xFFFFFF, 200); gridContainer.removeChild(cell1); gridContainer.removeChild(cell2); cell1.destroy(); cell2.destroy(); gridCells[cell1.row][cell1.col] = null; gridCells[cell2.row][cell2.col] = null; }, 250); } else if (cell1.isSpecial && cell1.specialType === 'bomb' && cell2.isSpecial && (cell2.specialType === 'horizontal' || cell2.specialType === 'vertical') || cell2.isSpecial && cell2.specialType === 'bomb' && cell1.isSpecial && (cell1.specialType === 'horizontal' || cell1.specialType === 'vertical')) { // Line-clearing + bomb special combination var bombCell = cell1.specialType === 'bomb' ? cell1 : cell2; var lineCell = cell1.specialType === 'horizontal' || cell1.specialType === 'vertical' ? cell1 : cell2; var cellsToDestroy = []; var affectedRows = []; // Determine which cell was moved by the player var activatedCell = cell1; // Enhanced effect: destroy 3 rows or columns based on the line special type if (lineCell.specialType === 'horizontal') { // Destroy 3 rows centered on the bomb's row for (var r = Math.max(extraRows, bombCell.row - 1); r <= Math.min(gridSize + extraRows - 1, bombCell.row + 1); r++) { affectedRows.push(r); for (var c = 0; c < gridSize; c++) { if (gridCells[r][c] && gridCells[r][c] !== bombCell && gridCells[r][c] !== lineCell) { cellsToDestroy.push(gridCells[r][c]); } } } } else { // vertical // Destroy 3 columns centered on the bomb's column for (var r = extraRows; r < gridSize + extraRows; r++) { for (var c = Math.max(0, bombCell.col - 1); c <= Math.min(gridSize - 1, bombCell.col + 1); c++) { if (gridCells[r][c] && gridCells[r][c] !== bombCell && gridCells[r][c] !== lineCell) { cellsToDestroy.push(gridCells[r][c]); } } } } // Mark special memes for destruction bombCell.beingDestroyed = true; lineCell.beingDestroyed = true; // Schedule destruction after the swap animation completes LK.setTimeout(function () { LK.getSound('Explosion').play(); // Destroy the collected cells if (cellsToDestroy.length > 0) { destroyCells(cellsToDestroy); } // Destroy the special memes LK.effects.flashObject(bombCell, 0xFFFFFF, 200); LK.effects.flashObject(lineCell, 0xFFFFFF, 200); gridContainer.removeChild(bombCell); gridContainer.removeChild(lineCell); bombCell.destroy(); lineCell.destroy(); gridCells[bombCell.row][bombCell.col] = null; gridCells[lineCell.row][lineCell.col] = null; }, 250); } // Check if two line special memes were swapped (horizontal and vertical) else if (lineSpecialCells.length === 2) { var cellsToDestroy = []; // Collect cells in both row and column var row1, col1, row2, col2; var activatedSpecial; // Determine which special meme was moved by the player (based on original positions) if (lineSpecialCells[0] === cell1) { activatedSpecial = lineSpecialCells[0]; row1 = lineSpecialCells[0].row; col1 = lineSpecialCells[0].col; } else { activatedSpecial = lineSpecialCells[1]; row1 = lineSpecialCells[1].row; col1 = lineSpecialCells[1].col; } // Add cells from the activated special's row or column for (var row = extraRows; row < gridSize + extraRows; row++) { // Add cells from column if (gridCells[row][col1] && gridCells[row][col1] !== lineSpecialCells[0] && gridCells[row][col1] !== lineSpecialCells[1]) { cellsToDestroy.push(gridCells[row][col1]); } } // Add cells from the activated special's row for (var col = 0; col < gridSize; col++) { // Add cells from row if (col !== col1 && gridCells[row1][col] && gridCells[row1][col] !== lineSpecialCells[0] && gridCells[row1][col] !== lineSpecialCells[1]) { cellsToDestroy.push(gridCells[row1][col]); } } // Mark special memes for destruction lineSpecialCells.forEach(function (specialMeme) { specialMeme.beingDestroyed = true; }); // Schedule destruction after the swap animation completes LK.setTimeout(function () { LK.getSound('Explosion').play(); // Destroy the collected cells if (cellsToDestroy.length > 0) { destroyCells(cellsToDestroy); } // Destroy the special memes lineSpecialCells.forEach(function (specialMeme) { LK.effects.flashObject(specialMeme, 0xFFFFFF, 200); gridContainer.removeChild(specialMeme); specialMeme.destroy(); gridCells[specialMeme.row][specialMeme.col] = null; }); }, 250); } // Check if greenSpecialCell was swapped with a line-clearing special meme or bomb else if (greenSpecialCell && regularCell) { // Check if regularCell is a line-clearing special meme if (regularCell.isSpecial && (regularCell.specialType === 'horizontal' || regularCell.specialType === 'vertical')) { var targetType = regularCell.value; var cellsToTransform = []; // Find all cells with the same type as the swapped line-clearing meme for (var row = extraRows; row < gridSize + extraRows; row++) { for (var col = 0; col < gridSize; col++) { var cell = gridCells[row][col]; if (cell && cell.value === targetType && cell !== greenSpecialCell && cell !== regularCell && !cell.isSpecial) { cellsToTransform.push(cell); } } } // Mark special memes for destruction greenSpecialCell.beingDestroyed = true; // Schedule transformation and activation after the swap animation completes LK.setTimeout(function () { LK.getSound('Explosion').play(); LK.effects.flashObject(greenSpecialCell, 0xFFFFFF, 200); // Transform all cells of the same type into line-clearing memes cellsToTransform.forEach(function (cell) { cell.isSpecial = true; // Randomly assign horizontal or vertical cell.specialType = Math.random() < 0.5 ? 'horizontal' : 'vertical'; // Apply special icon if (cell.specialIcon) { cell.removeChild(cell.specialIcon); cell.specialIcon = null; } cell.specialIcon = LK.getAsset('HabilidadClear', { anchorX: 0.5, anchorY: 0.5 }); cell.addChild(cell.specialIcon); LK.effects.flashObject(cell, 0xFFFFFF, 200); // Activate each special meme with a slight delay LK.setTimeout(function () { cell.activateSpecialPower(); }, 100 + Math.random() * 300); }); // Remove the green special meme gridContainer.removeChild(greenSpecialCell); greenSpecialCell.destroy(); gridCells[greenSpecialCell.row][greenSpecialCell.col] = null; // Activate the original line-clearing meme last LK.setTimeout(function () { regularCell.activateSpecialPower(); }, 500); }, 250); } else if (regularCell.isSpecial && regularCell.specialType === 'bomb') { // Green special + bomb special interaction var targetType = regularCell.value; var cellsToTransform = []; // Find all cells with the same type as the bomb meme for (var row = extraRows; row < gridSize + extraRows; row++) { for (var col = 0; col < gridSize; col++) { var cell = gridCells[row][col]; if (cell && cell.value === targetType && cell !== greenSpecialCell && cell !== regularCell && !cell.isSpecial) { cellsToTransform.push(cell); } } } // Mark green special meme for destruction greenSpecialCell.beingDestroyed = true; // Schedule transformation and activation after swap animation LK.setTimeout(function () { LK.getSound('Explosion').play(); LK.effects.flashObject(greenSpecialCell, 0xFFFFFF, 200); // Transform all cells of same type into bomb memes cellsToTransform.forEach(function (cell) { cell.isSpecial = true; cell.specialType = 'bomb'; // Apply bomb icon if (cell.specialIcon) { cell.removeChild(cell.specialIcon); cell.specialIcon = null; } cell.specialIcon = LK.getAsset('habilidadBomba', { anchorX: 0.5, anchorY: 0.5 }); cell.addChild(cell.specialIcon); LK.effects.flashObject(cell, 0xFFFFFF, 200); // Activate each bomb with slight delay LK.setTimeout(function () { cell.activateSpecialPower(); }, 100 + Math.random() * 300); }); // Remove the green special meme gridContainer.removeChild(greenSpecialCell); greenSpecialCell.destroy(); gridCells[greenSpecialCell.row][greenSpecialCell.col] = null; // Activate the original bomb last LK.setTimeout(function () { regularCell.activateSpecialPower(); }, 500); }, 250); } else { var targetType = regularCell.value; var cellsToDestroy = []; // Find all cells with the same type as the swapped meme for (var row = extraRows; row < gridSize + extraRows; row++) { for (var col = 0; col < gridSize; col++) { var cell = gridCells[row][col]; if (cell && cell.value === targetType && cell !== greenSpecialCell) { cellsToDestroy.push(cell); } } } // Also destroy the green special meme itself greenSpecialCell.beingDestroyed = true; // Schedule destruction after the swap animation completes LK.setTimeout(function () { if (cellsToDestroy.length > 0) { LK.getSound('Explosion').play(); LK.effects.flashObject(greenSpecialCell, 0xFFFFFF, 200); destroyCells(cellsToDestroy); gridContainer.removeChild(greenSpecialCell); greenSpecialCell.destroy(); gridCells[greenSpecialCell.row][greenSpecialCell.col] = null; } }, 250); } } tween(cell1, { x: pos2_x, y: pos2_y }, { duration: 200 }); tween(cell2, { x: pos1_x, y: pos1_y }, { duration: 200 }); selectedCell = null; LK.setTimeout(function () { checkForAndDestroyMatches(cell1, cell2); if (!window.destructionInProgress && !window.gravityInProgress && !window.fillInProgress) { isAnimating = false; } }, 250); } function getMatches() { var matches = []; function findDirectionalMatches(isHorizontal) { var primary, secondary; var primaryMax = isHorizontal ? gridSize + extraRows : gridSize; var secondaryMax = isHorizontal ? gridSize : gridSize + extraRows; for (primary = isHorizontal ? extraRows : 0; primary < primaryMax; primary++) { if (!gridCells[primary]) { continue; } for (secondary = 0; secondary < secondaryMax;) { if (secondary > secondaryMax - 3) { secondary++; continue; } var cell1 = isHorizontal ? gridCells[primary] ? gridCells[primary][secondary] : null : gridCells[secondary] ? gridCells[secondary][primary] : null; if (!cell1 || !cell1.value) { secondary++; continue; } var currentMatchValue = cell1.value; var currentMatchCells = [cell1]; for (var k = secondary + 1; k < secondaryMax; k++) { var nextCell = isHorizontal ? gridCells[primary] ? gridCells[primary][k] : null : gridCells[k] ? gridCells[k][primary] : null; if (nextCell && nextCell.value === currentMatchValue) { currentMatchCells.push(nextCell); } else { // If no new matches found, reset combo counter LK.setTimeout(function () { if (!window.gravityInProgress && !window.fillInProgress) { comboInProgress = false; comboCounter = 0; } }, 500); break; } } if (currentMatchCells.length >= 3) { var validCells = currentMatchCells.filter(function (cell) { return cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell; }); if (validCells.length >= 3) { var visibleCells = validCells.filter(function (cell) { return cell.row >= extraRows; }); var invisibleCells = validCells.filter(function (cell) { return cell.row < extraRows; }); // Either all cells are visible, or all cells are invisible, or there are at least 3 visible cells if (visibleCells.length === 0 || invisibleCells.length === 0 || visibleCells.length >= 3) { // Add isHorizontal and matchType to the match data validCells.isHorizontal = isHorizontal; validCells.matchType = currentMatchValue; matches.push(validCells); } } } secondary += currentMatchCells.length > 0 ? currentMatchCells.length : 1; } } } findDirectionalMatches(true); findDirectionalMatches(false); return matches; } function destroyCells(cellsToDestroy) { isAnimating = true; // Create a unique ID for this destruction process var destructionId = Date.now() + Math.random(); // Check if match crosses visible/invisible boundary var visibleCells = cellsToDestroy.filter(function (cell) { return cell.row >= extraRows; }); var invisibleCells = cellsToDestroy.filter(function (cell) { return cell.row < extraRows; }); // If mixed visibility and only one cell is visible, don't destroy any if (visibleCells.length > 0 && invisibleCells.length > 0 && (visibleCells.length === 1 || invisibleCells.length === 1)) { isAnimating = false; return; } // Reset timer if we're in Fast Mode and destroying visible cells if (game.gameMode === 'fast' && visibleCells.length > 0) { resetTimer(); } // Calculate points for destroyed cells - only count visible cells var basePoints = visibleCells.length * 10; var visibleCellsToDestroy = cellsToDestroy.filter(function (cell) { return cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell; }); // Find the center cell (the one that triggered the match) var centerCell = null; // If we have the cells from a swap, use one of those as the center if (arguments.length > 1 && arguments[1]) { centerCell = arguments[1]; } else if (cellsToDestroy.length > 0) { // Otherwise use the middle cell of the match centerCell = cellsToDestroy[Math.floor(cellsToDestroy.length / 2)]; } // Group cells by meme type var cellsByType = {}; visibleCellsToDestroy.forEach(function (cell) { if (!cellsByType[cell.value]) { cellsByType[cell.value] = []; } cellsByType[cell.value].push(cell); }); // Count unique groups for combo sound var uniqueGroupCount = Object.keys(cellsByType).length; if (uniqueGroupCount > 0) { // Increment combo counter only if this is part of a continuous combo if (!comboInProgress) { comboCounter = uniqueGroupCount; comboInProgress = true; } else { comboCounter += uniqueGroupCount; } // Calculate score multiplier based on combo count var comboMultiplier = 1; if (comboCounter >= 9) { comboMultiplier = 5; } else if (comboCounter >= 6) { comboMultiplier = 4; } else if (comboCounter >= 4) { comboMultiplier = 3; } else if (comboCounter >= 3) { comboMultiplier = 2; } else if (comboCounter >= 2) { comboMultiplier = 1.5; } // Award points with combo multiplier var pointsToAdd = Math.floor(basePoints * comboMultiplier); updateScore(pointsToAdd); // Show floating score text at center cell if available if (centerCell && centerCell.row >= extraRows) { var scorePopup = new Text2("+" + pointsToAdd, { size: 60, fill: 0xFF8800 }); scorePopup.anchor.set(0.5, 0.5); scorePopup.x = centerCell.x; scorePopup.y = centerCell.y; game.addChild(scorePopup); // Animate score popup tween(scorePopup, { y: scorePopup.y - 80, alpha: 0 }, { duration: 800, onComplete: function onComplete() { game.removeChild(scorePopup); scorePopup.destroy(); } }); } // Stop any currently playing combo sounds before playing a new one LK.getSound('Combo1').stop(); LK.getSound('Combo2').stop(); LK.getSound('Combo3').stop(); LK.getSound('Combo4').stop(); LK.getSound('Combo5').stop(); if (comboCounter >= 9) { LK.getSound('Combo5').play(); } else if (comboCounter >= 6) { LK.getSound('Combo4').play(); } else if (comboCounter >= 4) { LK.getSound('Combo3').play(); } else if (comboCounter >= 3) { LK.getSound('Combo2').play(); } else if (comboCounter >= 2) { LK.getSound('Combo1').play(); } } var specialCell = null; var isHorizontalMatch = false; var isVerticalMatch = false; for (var type in cellsByType) { var typeCells = cellsByType[type]; if (typeCells.length >= 6) { // Create green special meme for 6 or more matches if (centerCell && centerCell.value === parseInt(type)) { // Create a new green special meme var greenMeme = new GreenSpecialMeme().init(); greenMeme.setValue(parseInt(type)); greenMeme.x = centerCell.x; greenMeme.y = centerCell.y; greenMeme.row = centerCell.row; greenMeme.col = centerCell.col; greenMeme.beingDestroyed = false; // Replace the center cell with the green special meme gridCells[centerCell.row][centerCell.col] = greenMeme; gridContainer.removeChild(centerCell); centerCell.destroy(); gridContainer.addChild(greenMeme); specialCell = greenMeme; } else { // Create a new green special meme var greenMeme = new GreenSpecialMeme().init(); greenMeme.setValue(parseInt(type)); greenMeme.x = typeCells[0].x; greenMeme.y = typeCells[0].y; greenMeme.row = typeCells[0].row; greenMeme.col = typeCells[0].col; greenMeme.beingDestroyed = false; // Replace the first cell with the green special meme gridCells[typeCells[0].row][typeCells[0].col] = greenMeme; gridContainer.removeChild(typeCells[0]); typeCells[0].destroy(); gridContainer.addChild(greenMeme); specialCell = greenMeme; } // Award bonus points for creating a green special meme updateScore(100); // Remove special cell from cells to destroy visibleCellsToDestroy = visibleCellsToDestroy.filter(function (cell) { return cell !== typeCells[0] && cell !== centerCell; }); break; // Only create one special meme } else if (typeCells.length === 5) { // Create bomb special meme (blue) if (centerCell && centerCell.value === parseInt(type)) { specialCell = centerCell; } else { specialCell = typeCells[0]; } specialCell.isSpecial = true; specialCell.specialType = 'bomb'; specialCell.beingDestroyed = false; // Award bonus points for creating a bomb special meme updateScore(75); // Remove special cell from cells to destroy visibleCellsToDestroy = visibleCellsToDestroy.filter(function (cell) { return cell !== specialCell; }); break; // Only create one special meme } else if (typeCells.length === 4) { // Determine if match is horizontal or vertical var allInSameRow = true; var allInSameCol = true; var firstCell = typeCells[0]; for (var i = 1; i < typeCells.length; i++) { if (typeCells[i].row !== firstCell.row) { allInSameRow = false; } if (typeCells[i].col !== firstCell.col) { allInSameCol = false; } } isHorizontalMatch = allInSameRow; isVerticalMatch = allInSameCol; // Create special meme only if it's a clean horizontal or vertical match if (isHorizontalMatch || isVerticalMatch) { // Use center cell if it matches the type, otherwise use the first cell of this type if (centerCell && centerCell.value === parseInt(type)) { specialCell = centerCell; } else { specialCell = typeCells[0]; } specialCell.isSpecial = true; specialCell.specialType = isHorizontalMatch ? 'horizontal' : 'vertical'; specialCell.beingDestroyed = false; // Award bonus points for creating a line-clearing special meme updateScore(50); // Remove special cell from cells to destroy visibleCellsToDestroy = visibleCellsToDestroy.filter(function (cell) { return cell !== specialCell; }); break; // Only create one special meme } } } // Sort cells by distance from center cell for ripple effect if (centerCell && visibleCellsToDestroy.length > 0) { visibleCellsToDestroy.sort(function (a, b) { var distA = Math.abs(a.row - centerCell.row) + Math.abs(a.col - centerCell.col); var distB = Math.abs(b.row - centerCell.row) + Math.abs(b.col - centerCell.col); return distA - distB; }); } var delay = 0; var delayIncrement = 50; var totalDestructionTime = visibleCellsToDestroy.length * delayIncrement; // Mark cells as being destroyed to prevent them from being part of new matches visibleCellsToDestroy.forEach(function (cell) { if (cell) { cell.beingDestroyed = true; // Check if any of these cells are matched with a special meme if (cell.isSpecial) { // Special meme is being destroyed, activate its power LK.setTimeout(function () { cell.activateSpecialPower(); }, 100); } } }); // If we have a special meme, apply the appropriate special icon if (specialCell && specialCell.isSpecial) { // Remove any previous special icon if it exists if (specialCell.specialIcon) { specialCell.removeChild(specialCell.specialIcon); specialCell.specialIcon = null; } if (specialCell.specialType === 'bomb') { // Add bomb icon on top of the meme specialCell.specialIcon = LK.getAsset('habilidadBomba', { anchorX: 0.5, anchorY: 0.5 }); specialCell.addChild(specialCell.specialIcon); } else if (specialCell.specialType === 'green' && !(specialCell instanceof GreenSpecialMeme)) { // Create a new green special meme to replace the current cell var greenMeme = new GreenSpecialMeme().init(); greenMeme.setValue(specialCell.value); greenMeme.x = specialCell.x; greenMeme.y = specialCell.y; greenMeme.row = specialCell.row; greenMeme.col = specialCell.col; greenMeme.beingDestroyed = false; // Replace the cell with the green special meme gridCells[specialCell.row][specialCell.col] = greenMeme; gridContainer.removeChild(specialCell); specialCell.destroy(); gridContainer.addChild(greenMeme); specialCell = greenMeme; } else { // Add clear icon on top of the meme for horizontal/vertical specials specialCell.specialIcon = LK.getAsset('HabilidadClear', { anchorX: 0.5, anchorY: 0.5 }); specialCell.addChild(specialCell.specialIcon); } LK.effects.flashObject(specialCell, 0xFFFFFF, 300); } visibleCellsToDestroy.forEach(function (cell) { LK.setTimeout(function () { if (cell && cell.beingDestroyed && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) { LK.getSound('Explosion').play(); LK.effects.flashObject(cell, 0xFFFFFF, 200); gridContainer.removeChild(cell); cell.destroy(); gridCells[cell.row][cell.col] = null; } }, delay); delay += delayIncrement; }); // After all cells in this group are destroyed, check if we need to apply gravity LK.setTimeout(function () { // Check if all destructions are complete before applying gravity var allCellsDestroyed = true; for (var r = 0; r < gridSize + extraRows; r++) { for (var c = 0; c < gridSize; c++) { if (gridCells[r] && gridCells[r][c] && gridCells[r][c].beingDestroyed) { allCellsDestroyed = false; } } } // Only the last destruction process should trigger gravity if (allCellsDestroyed && !window.gravityInProgress && !window.fillInProgress) { applyGravity(); } }, totalDestructionTime + 50); } function applyGravity() { isAnimating = true; var cellsToFall = []; if (window.gravityInProgress) { return; } window.gravityInProgress = true; for (var col = 0; col < gridSize; col++) { for (var row = gridSize + extraRows - 1; row >= extraRows; row--) { if (!gridCells[row][col]) { var sourceRow = row - 1; while (sourceRow >= 0 && !gridCells[sourceRow][col]) { sourceRow--; } if (sourceRow >= 0) { var cellToMove = gridCells[sourceRow][col]; if (cellToMove) { cellsToFall.push({ cell: cellToMove, fromRow: sourceRow, toRow: row, col: col }); gridCells[row][col] = cellToMove; gridCells[sourceRow][col] = null; cellToMove.row = row; if (sourceRow < extraRows) { gridContainer.addChild(cellToMove); } } } } } } var longestDelay = 0; var baseDelay = 15; var constantDuration = 250; cellsToFall.sort(function (a, b) { if (a.col !== b.col) { return a.col - b.col; } return b.toRow - a.toRow; }); cellsToFall.forEach(function (fallInfo, index) { var delay = index * baseDelay; var newPos = getCellPosition(fallInfo.toRow, fallInfo.col); var totalTime = delay + constantDuration; if (totalTime > longestDelay) { longestDelay = totalTime; } LK.setTimeout(function () { if (fallInfo.cell && gridCells[fallInfo.cell.row] && gridCells[fallInfo.cell.row][fallInfo.cell.col] === fallInfo.cell) { tween(fallInfo.cell, { y: newPos.y }, { duration: constantDuration, easing: tween.linear }); } }, delay); }); LK.setTimeout(function () { window.gravityInProgress = false; if (!window.destructionInProgress) { fillEmptySpacesWithNewMemes(); } }, longestDelay + 50); } function fillEmptySpacesWithNewMemes() { if (window.fillInProgress || window.gravityInProgress) { return; } window.fillInProgress = true; var anyNewMemeAdded = false; var newCellsToAdd = []; for (var col = 0; col < gridSize; col++) { for (var row = 0; row < gridSize + extraRows; row++) { if (!gridCells[row][col]) { anyNewMemeAdded = true; var newCell = getGridCell(); var pos = getCellPosition(row, col); newCell.x = pos.x; newCell.y = pos.y - 400; var randomValue = Math.floor(Math.random() * 5) + 1; newCell.setValue(randomValue); newCell.row = row; newCell.col = col; gridCells[row][col] = newCell; newCellsToAdd.push({ cell: newCell, destY: pos.y, row: row, col: col }); if (row >= extraRows) { gridContainer.addChild(newCell); } } } } var longestDelay = 0; var baseDelay = 15; var constantDuration = 250; newCellsToAdd.sort(function (a, b) { if (a.col !== b.col) { return a.col - b.col; } return b.row - a.row; }); newCellsToAdd.forEach(function (cellData, index) { var delay = index * baseDelay; var totalTime = delay + constantDuration; if (totalTime > longestDelay) { longestDelay = totalTime; } LK.setTimeout(function () { if (cellData.cell && gridCells[cellData.row] && gridCells[cellData.row][cellData.col] === cellData.cell) { tween(cellData.cell, { y: cellData.destY }, { duration: constantDuration, easing: tween.linear }); } }, delay); }); if (anyNewMemeAdded) { LK.setTimeout(function () { window.fillInProgress = false; if (window.destructionInProgress || window.gravityInProgress) { isAnimating = false; return; } var visibleMatchGroups = getMatches().filter(function (group) { // Check if the match has cells in both visible and invisible areas var visibleCells = group.filter(function (cell) { return cell.row >= extraRows; }); var invisibleCells = group.filter(function (cell) { return cell.row < extraRows; }); // Don't count groups with both visible and invisible cells unless there are at least 3 visible cells if (visibleCells.length > 0 && invisibleCells.length > 0 && visibleCells.length < 3) { return false; } return visibleCells.length >= 3; // At least 3 visible cells make a valid visible match }); var newMatches = visibleMatchGroups; if (newMatches.length > 0) { // Group by meme type var matchesByType = {}; newMatches.forEach(function (group) { if (group.length > 0) { var type = group[0].value; if (!matchesByType[type]) { matchesByType[type] = []; } matchesByType[type].push(group); } }); var newCellsToDestroy = []; var newCellTracker = {}; // Process each type separately for (var type in matchesByType) { var typeGroups = matchesByType[type]; // Find a central cell for this type var centerCell = null; if (typeGroups.length > 0 && typeGroups[0].length > 0) { // Pick the center of the first match group of this type var matchGroup = typeGroups[0]; centerCell = matchGroup[Math.floor(matchGroup.length / 2)]; } // Collect all cells of this type var typeCells = []; typeGroups.forEach(function (group) { group.forEach(function (cell) { var cellKey = cell.row + "_" + cell.col; if (!newCellTracker[cellKey] && cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) { typeCells.push(cell); newCellTracker[cellKey] = true; } }); }); // Destroy cells of this type if (typeCells.length) { destroyCells(typeCells, centerCell); } } if (Object.keys(matchesByType).length === 0) { isAnimating = false; } } else { isAnimating = false; } }, longestDelay + 50); } else { window.fillInProgress = false; isAnimating = false; // Reset combo tracking when no more matches are being created comboInProgress = false; comboCounter = 0; } } // Timer management functions function startTimer() { // Clear any existing timer if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } // Calculate initial max timer value based on score // This ensures proper timer initialization when starting a game with existing score var maxTimer = 10; // Use exponential thresholds for slower decrease if (score >= 100) { maxTimer = 9; } if (score >= 500) { maxTimer = 8; } if (score >= 1000) { maxTimer = 7; } if (score >= 3500) { maxTimer = 6; } if (score >= 10000) { maxTimer = 5; } if (score >= 25000) { maxTimer = 4; } if (score >= 50000) { maxTimer = 3; } // Only set timer to max when initializing new game, not when resuming if (!game.timerInitialized) { timerSeconds = maxTimer; game.timerInitialized = true; } // Update timer display initially if (timerText) { timerText.setText("Time: " + timerSeconds.toFixed(1)); } // Start countdown timer with decimals (100ms interval) timerInterval = LK.setInterval(function () { timerSeconds -= 0.1; timerSeconds = Math.max(0, parseFloat(timerSeconds.toFixed(1))); // Fix floating point issues // Update timer display if (timerText) { timerText.setText("Time: " + timerSeconds.toFixed(1)); } // Check for game over if (timerSeconds <= 0) { // Stop timer LK.clearInterval(timerInterval); timerInterval = null; // Instead of showing game over screen, trigger the custom game over game.onGameOver(); } }, 100); } function resetTimer() { // Only reset timer in Fast Mode if (game.gameMode === 'fast') { // Calculate max timer value based on score // Start with 10 seconds, decrease as score increases, minimum of 3 seconds var maxTimer = 10; // Use exponential thresholds for slower decrease if (score >= 100) { maxTimer = 9; } if (score >= 500) { maxTimer = 8; } if (score >= 1000) { maxTimer = 7; } if (score >= 3500) { maxTimer = 6; } if (score >= 10000) { maxTimer = 5; } if (score >= 25000) { maxTimer = 4; } if (score >= 50000) { maxTimer = 3; } // Set timer to calculated max timerSeconds = maxTimer; if (timerText) { timerText.setText("Time: " + timerSeconds.toFixed(1)); } } } // Handle game over events to return to menu game.onGameOver = function () { // Save high score if current score is higher if (game.gameMode === 'classic' && score > storage.highScores.classic) { storage.highScores.classic = score; } else if (game.gameMode === 'fast' && score > storage.highScores.fast) { storage.highScores.fast = score; } // Stop timer if running if (timerInterval) { LK.clearInterval(timerInterval); timerInterval = null; } // Clear periodic save interval if (saveScoreInterval) { LK.clearInterval(saveScoreInterval); saveScoreInterval = null; } // For Fast Mode, show a custom high score popup instead of the game over screen if (game.gameMode === 'fast') { // Create popup container var popupContainer = new Container(); // Create popup background var popupBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 4, scaleY: 4.5 }); popupBg.tint = 0x0088FF; // Blue tint for Fast Mode // Create popup title var popupTitle = new Text2("GAME OVER", { size: 100, fill: 0xFFFFFF }); popupTitle.anchor.set(0.5, 0); popupTitle.y = -350; // Create high score text with line break var highScoreText = new Text2("YOUR HIGH SCORE:\n" + storage.highScores.fast, { size: 60, fill: 0xFFFFFF }); highScoreText.anchor.set(0.5, 0); highScoreText.y = -200; // Create current score text with line break var currentScoreText = new Text2("THIS GAME:\n" + score, { size: 60, fill: 0xFFFFFF }); currentScoreText.anchor.set(0.5, 0); currentScoreText.y = -50; // Create button to return to menu var menuButton = new Container(); var menuButtonBg = LK.getAsset('button', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 1.5 }); menuButtonBg.tint = 0xFF5500; var menuButtonText = new Text2("BACK TO MENU", { size: 70, fill: 0xFFFFFF }); menuButtonText.anchor.set(0.5, 0.5); menuButton.addChild(menuButtonBg); menuButton.addChild(menuButtonText); menuButton.y = 250; // Add all elements to popup popupContainer.addChild(popupBg); popupContainer.addChild(popupTitle); popupContainer.addChild(highScoreText); popupContainer.addChild(currentScoreText); popupContainer.addChild(menuButton); // Position popup in center of screen popupContainer.x = 2048 / 2; popupContainer.y = 2732 / 2; game.addChild(popupContainer); // Add button interaction menuButton.interactive = true; menuButton.down = function (x, y, obj) { LK.effects.flashObject(menuButtonBg, 0xFFFFFF, 200); // Clear the game board while (gridContainer.children.length > 0) { var child = gridContainer.children[0]; gridContainer.removeChild(child); child.destroy(); } if (scoreText) { game.removeChild(scoreText); scoreText = null; } if (timerText) { game.removeChild(timerText); timerText = null; } // Remove popup game.removeChild(popupContainer); // Clean up any game UI elements if (game.modeText) { game.removeChild(game.modeText); game.modeText = null; } if (game.pauseButton) { game.removeChild(game.pauseButton); game.pauseButton = null; } game.timerInitialized = false; // Show menu initializeMenu(); }; } else { // For Classic Mode, use the standard game over screen LK.setTimeout(function () { // Clear the game board while (gridContainer.children.length > 0) { var child = gridContainer.children[0]; gridContainer.removeChild(child); child.destroy(); } if (scoreText) { game.removeChild(scoreText); scoreText = null; } if (timerText) { game.removeChild(timerText); timerText = null; } // Clean up any game UI elements if (game.modeText) { game.removeChild(game.modeText); game.modeText = null; } if (game.pauseButton) { game.removeChild(game.pauseButton); game.pauseButton = null; } game.timerInitialized = false; // Show menu initializeMenu(); }, 500); } }; function checkForAndDestroyMatches(swappedCellA, swappedCellB) { if (window.gravityInProgress || window.fillInProgress) { return; } var allMatchGroupsOnBoard = getMatches(); if (!allMatchGroupsOnBoard.length) { return; } var relevantMatchGroups = allMatchGroupsOnBoard.filter(function (group) { // Check if the match has cells in both visible and invisible areas var visibleCells = group.filter(function (cell) { return cell.row >= extraRows; }); var invisibleCells = group.filter(function (cell) { return cell.row < extraRows; }); // Don't count groups with both visible and invisible cells unless there are at least 3 visible cells if (visibleCells.length > 0 && invisibleCells.length > 0 && visibleCells.length < 3) { return false; } return group.some(function (cellInGroup) { return (cellInGroup === swappedCellA || cellInGroup === swappedCellB) && cellInGroup.row >= extraRows; }); }); if (!relevantMatchGroups.length) { return; } // Group by meme type var matchesByType = {}; relevantMatchGroups.forEach(function (group) { if (group.length > 0) { var type = group[0].value; if (!matchesByType[type]) { matchesByType[type] = []; } matchesByType[type].push(group); } }); var uniqueCellTracker = {}; var cellsToDestroy = []; // Process each type separately for (var type in matchesByType) { var typeGroups = matchesByType[type]; // Flatten all groups of this type var typeCells = []; typeGroups.forEach(function (group) { group.forEach(function (cell) { if (cell.row >= extraRows) { typeCells.push(cell); } }); }); // Add each cell of this type to cellsToDestroy typeCells.forEach(function (cell) { var cellKey = cell.row + "_" + cell.col; if (!uniqueCellTracker[cellKey] && cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) { cellsToDestroy.push(cell); uniqueCellTracker[cellKey] = true; } }); } ; if (cellsToDestroy.length) { // Pass the swapped cell that created the match as the center for the ripple effect var centerCell = swappedCellA && relevantMatchGroups.some(function (group) { return group.includes(swappedCellA); }) ? swappedCellA : swappedCellB; destroyCells(cellsToDestroy, centerCell); LK.setTimeout(function () { if (window.gravityInProgress || window.fillInProgress) { return; } var visibleMatchGroups = getMatches().filter(function (group) { // Check if the match has cells in both visible and invisible areas var visibleCells = group.filter(function (cell) { return cell.row >= extraRows; }); var invisibleCells = group.filter(function (cell) { return cell.row < extraRows; }); // Don't count groups with both visible and invisible cells unless there are at least 3 visible cells if (visibleCells.length > 0 && invisibleCells.length > 0 && visibleCells.length < 3) { return false; } return visibleCells.length >= 3; // At least 3 visible cells make a valid visible match }); var newMatches = visibleMatchGroups; if (newMatches.length > 0) { // Group by meme type var matchesByType = {}; newMatches.forEach(function (group) { if (group.length > 0) { var type = group[0].value; if (!matchesByType[type]) { matchesByType[type] = []; } matchesByType[type].push(group); } }); // Process each type separately for (var type in matchesByType) { var typeGroups = matchesByType[type]; var newCellsToDestroy = []; var newCellTracker = {}; // Collect all cells of this type typeGroups.forEach(function (group) { group.forEach(function (cell) { var cellKey = cell.row + "_" + cell.col; if (!newCellTracker[cellKey] && cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) { newCellsToDestroy.push(cell); newCellTracker[cellKey] = true; } }); }); if (newCellsToDestroy.length) { // Find center cell of this type var centerCell = newCellsToDestroy[Math.floor(newCellsToDestroy.length / 2)]; destroyCells(newCellsToDestroy, centerCell); } } } }, 400); } } var gridSize = 8; var extraRows = 9; var cellSpacing = 10; var cellSize = 208; var gridCells = []; var totalGridWidth = gridSize * cellSize + (gridSize - 1) * cellSpacing; var totalVisibleGridHeight = totalGridWidth; var totalGridHeight = totalVisibleGridHeight + extraRows * (cellSize + cellSpacing); var startX = (2048 - totalGridWidth) / 2 + cellSize / 2; var startY = (-1300 - totalVisibleGridHeight) / 2 + cellSize / 2 + extraRows * (cellSize + cellSpacing); function getCellPosition(row, col) { return { x: startX + col * (cellSize + cellSpacing), y: startY + row * (cellSize + cellSpacing) - extraRows * (cellSize + cellSpacing) }; } function initializeGrid() { gridCells = []; for (var row = 0; row < gridSize + extraRows; row++) { gridCells[row] = []; for (var col = 0; col < gridSize; col++) { var pos = getCellPosition(row, col); var cell = getGridCell ? getGridCell() : new GridCell(); cell.x = pos.x; cell.y = pos.y; cell.row = row; cell.col = col; var randomValue; var attempts = 0; do { randomValue = Math.floor(Math.random() * 5) + 1; attempts++; var leftMatchCount = 0; var aboveMatchCount = 0; if (col >= 2) { if (gridCells[row][col - 1].value === randomValue && gridCells[row][col - 2].value === randomValue) { leftMatchCount = 2; } } if (row >= 2) { if (gridCells[row - 1][col].value === randomValue && gridCells[row - 2][col].value === randomValue) { aboveMatchCount = 2; } } } while ((leftMatchCount >= 2 || aboveMatchCount >= 2) && attempts < 10); cell.setValue(randomValue); if (row >= extraRows) { gridContainer.addChild(cell); } gridCells[row][col] = cell; } } } // Initialize menu instead of directly starting the game initializeMenu(); function ensureNoInitialMatches() { var matches = getMatches(); if (matches.length > 0) { matches.forEach(function (group) { group.forEach(function (cell) { var currentValue = cell.value; var newValue; do { newValue = Math.floor(Math.random() * 5) + 1; } while (newValue === currentValue); cell.setValue(newValue); }); }); ensureNoInitialMatches(); } } ensureNoInitialMatches();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScores: {
classic: 0,
fast: 0
}
});
/****
* Classes
****/
var CosmeticsScreen = Container.expand(function () {
var self = Container.call(this);
// Create title
var title = new Text2("Cosmetics", {
size: 120,
fill: 0x000000
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 300;
self.addChild(title);
// Create coming soon text
var comingSoonText = new Text2("Coming Soon!", {
size: 80,
fill: 0x888888
});
comingSoonText.anchor.set(0.5, 0.5);
comingSoonText.x = 2048 / 2;
comingSoonText.y = 2732 / 2;
self.addChild(comingSoonText);
// Create back button
var backButton = new Container();
var backButtonBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
backButtonBg.tint = 0xFF5500;
var backText = new Text2("BACK TO MENU", {
size: 70,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backButtonBg);
backButton.addChild(backText);
backButton.x = 2048 / 2;
backButton.y = 2732 / 2 + 400;
self.addChild(backButton);
// Add button interaction
backButton.interactive = true;
backButton.down = function (x, y, obj) {
LK.effects.flashObject(backButtonBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
game.removeChild(self);
initializeMenu();
}, 300);
};
return self;
});
var GridCell = Container.expand(function () {
var self = Container.call(this);
self.init = function () {
if (!self.background) {
self.background = self.attachAsset('cuadricula', {
anchorX: 0.5,
anchorY: 0.5
});
}
self.value = 0;
self.sprite = null;
self.row = -1;
self.col = -1;
self.isSpecial = false;
self.specialType = null;
return self;
};
self.setValue = function (newValue) {
if (self.value === newValue) {
return;
}
self.value = newValue;
if (self.sprite) {
self.removeChild(self.sprite);
}
var spriteId = 'meme' + newValue;
self.sprite = LK.getAsset(spriteId, {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.sprite);
// Apply special effects if this is a special meme
if (self.isSpecial) {
// Remove any previous special icon if it exists
if (self.specialIcon) {
self.removeChild(self.specialIcon);
self.specialIcon = null;
}
if (self.specialType === 'bomb') {
// Add bomb icon on top of the meme
self.specialIcon = LK.getAsset('habilidadBomba', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.specialIcon);
} else {
// Add clear icon on top of the meme for horizontal/vertical specials
self.specialIcon = LK.getAsset('HabilidadClear', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.specialIcon);
}
}
};
self.activateSpecialPower = function () {
if (!self.isSpecial) {
return;
}
var cellsToDestroy = [];
var bonusPoints = 0;
if (self.specialType === 'horizontal') {
// Destroy all cells in the same column
for (var row = extraRows; row < gridSize + extraRows; row++) {
if (gridCells[row][self.col] && gridCells[row][self.col] !== self) {
cellsToDestroy.push(gridCells[row][self.col]);
}
}
// Award bonus points for horizontal special activation
bonusPoints = 100;
} else if (self.specialType === 'vertical') {
// Destroy all cells in the same row
for (var col = 0; col < gridSize; col++) {
if (gridCells[self.row][col] && gridCells[self.row][col] !== self) {
cellsToDestroy.push(gridCells[self.row][col]);
}
}
// Award bonus points for vertical special activation
bonusPoints = 100;
} else if (self.specialType === 'bomb') {
// Destroy adjacent cells (bomb effect)
for (var row = Math.max(extraRows, self.row - 1); row <= Math.min(gridSize + extraRows - 1, self.row + 1); row++) {
for (var col = Math.max(0, self.col - 1); col <= Math.min(gridSize - 1, self.col + 1); col++) {
if (gridCells[row][col] && gridCells[row][col] !== self) {
cellsToDestroy.push(gridCells[row][col]);
}
}
}
// Award bonus points for bomb special activation
bonusPoints = 150;
} else if (self.specialType === 'green') {
// Green special meme power is activated when swapped, not when destroyed
// The power is implemented in handleCellTap function
// Award bonus points for green special activation (though it should be handled elsewhere)
bonusPoints = 200;
}
// Award special activation bonus points
if (bonusPoints > 0) {
updateScore(bonusPoints);
// Show floating score text for special activation
var scorePopup = new Text2("+" + bonusPoints, {
size: 60,
fill: 0xFFFF00
});
scorePopup.anchor.set(0.5, 0.5);
scorePopup.x = self.x;
scorePopup.y = self.y;
game.addChild(scorePopup);
// Animate score popup
tween(scorePopup, {
y: scorePopup.y - 80,
alpha: 0
}, {
duration: 800,
onComplete: function onComplete() {
game.removeChild(scorePopup);
scorePopup.destroy();
}
});
}
// Destroy collected cells
if (cellsToDestroy.length > 0) {
destroyCells(cellsToDestroy);
// Also destroy this special meme
self.beingDestroyed = true;
LK.getSound('Explosion').play();
LK.effects.flashObject(self, 0xFFFFFF, 200);
gridContainer.removeChild(self);
self.destroy();
gridCells[self.row][self.col] = null;
}
};
self.showSelection = function () {
if (self.selectionHighlight) {
return;
}
self.selectionHighlight = new Container();
// Create pulsating highlight effect
var highlight = LK.getAsset('cuadricula', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.15,
scaleY: 1.15,
alpha: 0.6
});
highlight.tint = 0x00FFFF; // Cyan highlight
self.selectionHighlight.addChild(highlight);
self.addChildAt(self.selectionHighlight, 0);
// Create pulsating animation
function pulseAnimation() {
tween(highlight, {
alpha: 0.3,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeInOutQuad,
onComplete: function onComplete() {
tween(highlight, {
alpha: 0.6,
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 500,
easing: tween.easeInOutQuad,
onComplete: pulseAnimation
});
}
});
}
pulseAnimation();
};
self.hideSelection = function () {
if (self.selectionHighlight) {
self.removeChild(self.selectionHighlight);
self.selectionHighlight = null;
}
};
self.down = function (x, y, obj) {
handleCellTap(self);
};
self.init();
return self;
});
var GreenSpecialMeme = GridCell.expand(function () {
var self = GridCell.call(this);
self.init = function () {
self.background = self.attachAsset('cuadricula', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 0;
self.sprite = null;
self.row = -1;
self.col = -1;
self.isSpecial = true;
self.specialType = 'green';
return self;
};
// Override the setValue method to apply the green special visual
self.setValue = function (newValue) {
// Call the parent setValue method first
GridCell.prototype.setValue.call(this, newValue);
// Add the green special icon
if (self.specialIcon) {
self.removeChild(self.specialIcon);
}
self.specialIcon = self.attachAsset('habilidadVerde', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
};
// Override activateSpecialPower to handle green special ability
self.activateSpecialPower = function () {
if (!self.isSpecial || self.specialType !== 'green') {
return;
}
// Implementation continues in the original method in the game code
// This is just a placeholder as the actual implementation
// is handled in the handleCellTap function
var bonusPoints = 200;
updateScore(bonusPoints);
};
return self;
});
var MemeParticle = Container.expand(function () {
var self = Container.call(this);
self.init = function (textureId) {
self.textureId = textureId;
if (self.sprite) {
self.removeChild(self.sprite);
}
self.sprite = self.attachAsset(self.textureId, {
anchorX: 0.5,
anchorY: 0.5
});
self.alpha = 0.8;
self.scale.set(0.5); // Increased initial scale
self.speedX = (Math.random() - 0.5) * 1; // Reduced horizontal speed
self.speedY = 0.5 + Math.random() * 0.5; // Reduced downward speed
self.rotationSpeed = (Math.random() - 0.5) * 0.05; // Reduced rotation speed
return self;
};
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
self.rotation += self.rotationSpeed;
// Remove particle when it goes off screen
if (self.y > 2732 + 100 || self.x < -100 || self.x > 2048 + 100) {
// Increased threshold
self.destroy();
}
};
return self;
});
var MenuScreen = Container.expand(function () {
var self = Container.call(this);
// Create title
var title = self.attachAsset('titulo', {
anchorX: 0.5,
anchorY: 0,
scaleX: 3,
scaleY: 3
});
title.x = 2048 / 2;
title.y = 300;
// Create Zen mode button
var classicButton = new Container();
var classicButtonBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
classicButtonBg.tint = 0x00AA00;
var classicText = new Text2("ZEN MODE", {
size: 70,
fill: 0xFFFFFF
});
classicText.anchor.set(0.5, 0.5);
classicButton.addChild(classicButtonBg);
classicButton.addChild(classicText);
classicButton.x = 2048 / 2;
classicButton.y = 2732 / 2;
self.addChild(classicButton);
// Create Fast mode button
var fastButton = new Container();
var fastButtonBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
fastButtonBg.tint = 0x0088FF;
var fastText = new Text2("FAST MODE", {
size: 70,
fill: 0xFFFFFF
});
fastText.anchor.set(0.5, 0.5);
fastButton.addChild(fastButtonBg);
fastButton.addChild(fastText);
fastButton.x = 2048 / 2;
fastButton.y = 2732 / 2 + 300;
self.addChild(fastButton);
// Create Records button
var recordsButton = new Container();
var recordsButtonBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
recordsButtonBg.tint = 0xFFA500;
var recordsText = new Text2("HIGH SCORES", {
size: 70,
fill: 0xFFFFFF
});
recordsText.anchor.set(0.5, 0.5);
recordsButton.addChild(recordsButtonBg);
recordsButton.addChild(recordsText);
recordsButton.x = 2048 / 2;
recordsButton.y = 2732 / 2 + 900;
self.addChild(recordsButton);
// Create Cosmetics button
var cosmeticsButton = new Container();
var cosmeticsButtonBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
cosmeticsButtonBg.tint = 0x9932CC; // Purple tint
var cosmeticsText = new Text2("COSMETICS", {
size: 70,
fill: 0xFFFFFF
});
cosmeticsText.anchor.set(0.5, 0.5);
cosmeticsButton.addChild(cosmeticsButtonBg);
cosmeticsButton.addChild(cosmeticsText);
cosmeticsButton.x = 2048 / 2;
cosmeticsButton.y = 2732 / 2 + 600;
self.addChild(cosmeticsButton);
// Add button interactions
classicButton.interactive = true;
classicButton.down = function (x, y, obj) {
LK.effects.flashObject(classicButtonBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
self.startGame('classic');
}, 300);
};
fastButton.interactive = true;
fastButton.down = function (x, y, obj) {
LK.effects.flashObject(fastButtonBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
self.startGame('fast');
}, 300);
};
// Add records button interaction
recordsButton.interactive = true;
recordsButton.down = function (x, y, obj) {
LK.effects.flashObject(recordsButtonBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
game.removeChild(self);
showRecordsScreen();
}, 300);
};
// Add cosmetics button interaction
cosmeticsButton.interactive = true;
cosmeticsButton.down = function (x, y, obj) {
LK.effects.flashObject(cosmeticsButtonBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
game.removeChild(self);
showCosmeticsScreen();
}, 300);
};
// Function to start the game
self.startGame = function (mode) {
game.gameMode = mode || 'classic';
game.removeChild(self);
game.initializeGame();
};
// Create container for background particles
self.particleContainer = new Container();
self.addChildAt(self.particleContainer, 0); // Add particles behind other elements
// Meme particle types
self.memeParticles = ['meme1', 'meme2', 'meme3', 'meme4', 'meme5'];
// Particle emission timer
self.particleTimer = LK.setInterval(function () {
self.emitParticle();
}, 500); // Emit a particle every 500ms
// Function to emit a particle
self.emitParticle = function () {
var randomMemeId = self.memeParticles[Math.floor(Math.random() * self.memeParticles.length)];
var particle = new MemeParticle().init(randomMemeId);
particle.x = Math.random() * 2048; // Random horizontal position at the top
particle.y = -50; // Start above the screen
self.particleContainer.addChild(particle);
};
// Add update method to handle particle movement
self.update = function () {
// Update all particles
self.particleContainer.children.forEach(function (particle) {
if (particle && particle.update) {
particle.update();
}
});
};
// Override destroy to clear interval
self.destroy = function () {
LK.clearInterval(self.particleTimer);
self.particleTimer = null;
self.particleContainer.destroy({
children: true
});
self.particleContainer = null;
Container.prototype.destroy.call(self); // Call parent destroy method
};
return self;
});
var PauseButton = Container.expand(function () {
var self = Container.call(this);
// Create pause button icon
var pauseIcon = self.attachAsset('Pause', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Create background circle for the button
var background = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
background.tint = 0x444444;
background.alpha = 0.7;
// Add the background first so it's behind the icon
self.addChildAt(background, 0);
// Handle button press
self.down = function (x, y, obj) {
LK.effects.flashObject(self, 0xFFFFFF, 200);
showPausePopup();
};
return self;
});
var PausePopup = Container.expand(function () {
var self = Container.call(this);
// Create popup background
var popupBg = self.attachAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 4.5
});
popupBg.tint = 0x222222;
popupBg.alpha = 0.9;
// Create popup title
var popupTitle = new Text2("PAUSED", {
size: 80,
fill: 0xFFFFFF
});
popupTitle.anchor.set(0.5, 0);
popupTitle.y = -250;
self.addChild(popupTitle);
// Create continue button
var continueButton = new Container();
var continueBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2
});
continueBg.tint = 0x00AA00;
var continueText = new Text2("CONTINUE", {
size: 60,
fill: 0xFFFFFF
});
continueText.anchor.set(0.5, 0.5);
continueButton.addChild(continueBg);
continueButton.addChild(continueText);
continueButton.y = -20;
self.addChild(continueButton);
// Create surrender button
var surrenderButton = new Container();
var surrenderBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2
});
surrenderBg.tint = 0xFF0000;
var surrenderText = new Text2("SURRENDER", {
size: 60,
fill: 0xFFFFFF
});
surrenderText.anchor.set(0.5, 0.5);
surrenderButton.addChild(surrenderBg);
surrenderButton.addChild(surrenderText);
surrenderButton.y = 200;
self.addChild(surrenderButton);
// Add button interactions
continueButton.interactive = true;
continueButton.down = function (x, y, obj) {
LK.effects.flashObject(continueBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
game.removeChild(self);
// Fast Mode doesn't have pause functionality
}, 200);
};
surrenderButton.interactive = true;
surrenderButton.down = function (x, y, obj) {
LK.effects.flashObject(surrenderBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
game.removeChild(self);
game.onGameOver();
}, 200);
};
return self;
});
var RecordsScreen = Container.expand(function () {
var self = Container.call(this);
// Create title
var title = new Text2("High Scores", {
size: 120,
fill: 0x000000
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 300;
self.addChild(title);
// Create zen mode score display
var classicScoreBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.8
});
classicScoreBg.tint = 0x00AA00;
// Format the score to have line breaks for better display
var zenModeText = "ZEN MODE:\n" + storage.highScores.classic;
var classicScoreText = new Text2(zenModeText, {
size: 60,
fill: 0xFFFFFF
});
classicScoreText.anchor.set(0.5, 0.5);
var classicScoreContainer = new Container();
classicScoreContainer.addChild(classicScoreBg);
classicScoreContainer.addChild(classicScoreText);
classicScoreContainer.x = 2048 / 2;
classicScoreContainer.y = 2732 / 2 - 200;
self.addChild(classicScoreContainer);
// Create fast mode score display
var fastScoreBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.8
});
fastScoreBg.tint = 0x0088FF;
// Format the score to have line breaks for better display
var fastModeText = "FAST MODE:\n" + storage.highScores.fast;
var fastScoreText = new Text2(fastModeText, {
size: 60,
fill: 0xFFFFFF
});
fastScoreText.anchor.set(0.5, 0.5);
var fastScoreContainer = new Container();
fastScoreContainer.addChild(fastScoreBg);
fastScoreContainer.addChild(fastScoreText);
fastScoreContainer.x = 2048 / 2;
fastScoreContainer.y = 2732 / 2 + 100;
self.addChild(fastScoreContainer);
// Create back button
var backButton = new Container();
var backButtonBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
backButtonBg.tint = 0xFF5500;
var backText = new Text2("BACK TO MENU", {
size: 70,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
backButton.addChild(backButtonBg);
backButton.addChild(backText);
backButton.x = 2048 / 2;
backButton.y = 2732 / 2 + 400;
self.addChild(backButton);
// Add button interaction
backButton.interactive = true;
backButton.down = function (x, y, obj) {
LK.effects.flashObject(backButtonBg, 0xFFFFFF, 200);
LK.setTimeout(function () {
game.removeChild(self);
initializeMenu();
}, 300);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xF4FFFF
});
/****
* Game Code
****/
// Game state and initialization
var menuScreen;
var gameInitialized = false;
var score = 0;
var scoreText;
var gridContainer;
var gameMode = 'classic'; // Track current game mode
var timerText;
var timerSeconds = 10; // 10 seconds timer for Fast Mode
var timerInterval;
var saveScoreInterval; // Interval for saving classic mode high score
// Centralized initialization function for adding elements to the game
function initializeGameElements() {
// Create and add grid container if it doesn't exist
if (!gridContainer) {
gridContainer = new Container();
game.addChild(gridContainer);
}
// Create score text if it doesn't exist
if (!scoreText) {
scoreText = new Text2("Score: 0", {
size: 80,
fill: 0x000000
});
scoreText.anchor.set(0.5, 0);
scoreText.x = 2048 / 2;
scoreText.y = 100;
game.addChild(scoreText);
}
// Create timer text for fast mode if needed
if (game.gameMode === 'fast' && !timerText) {
timerText = new Text2("Time: " + timerSeconds.toFixed(1), {
size: 120,
fill: 0xFF0000
});
timerText.anchor.set(0.5, 1);
timerText.x = 2048 / 2;
timerText.y = 2732 - 100;
game.addChild(timerText);
}
// Add mode indicator text
game.modeText = new Text2(game.gameMode === 'classic' ? "Zen Mode" : "Fast Mode", {
size: 60,
fill: game.gameMode === 'classic' ? 0x00AA00 : 0x0088FF
});
game.modeText.anchor.set(0.5, 0);
game.modeText.x = 2048 / 2;
game.modeText.y = 180;
game.addChild(game.modeText);
// Add pause button to the top right corner only in zen mode
if (game.gameMode === 'classic') {
game.pauseButton = new PauseButton();
game.pauseButton.x = 2048 - 100;
game.pauseButton.y = 100;
game.pauseButton.interactive = true;
game.addChild(game.pauseButton);
}
}
// Function to show pause popup
function showPausePopup() {
var pausePopup = new PausePopup();
pausePopup.x = 2048 / 2;
pausePopup.y = 2732 / 2;
game.addChild(pausePopup);
// Fast Mode doesn't have pause functionality, so no need to pause the timer
}
// Show records screen
function showRecordsScreen() {
var recordsScreen = new RecordsScreen();
game.addChild(recordsScreen);
}
// Show cosmetics screen
function showCosmeticsScreen() {
var cosmeticsScreen = new CosmeticsScreen();
game.addChild(cosmeticsScreen);
}
// Initialize the menu
function initializeMenu() {
menuScreen = new MenuScreen();
game.addChild(menuScreen);
}
// Function to periodically save high score
function startPeriodicSave() {
// Clear any existing save interval
if (saveScoreInterval) {
LK.clearInterval(saveScoreInterval);
saveScoreInterval = null;
}
// Only start save interval in classic mode
if (game.gameMode === 'classic') {
saveScoreInterval = LK.setInterval(function () {
// Check if current score is higher than stored high score
if (score > storage.highScores.classic) {
storage.highScores.classic = score;
console.log("Classic mode high score saved: " + score);
}
}, 60000); // Save every minute (60000ms)
}
}
// Initialize the game
game.initializeGame = function () {
// Initialize score system
score = 0;
// Clear existing grid container if it exists
if (gridContainer) {
while (gridContainer.children.length > 0) {
var child = gridContainer.children[0];
gridContainer.removeChild(child);
child.destroy();
}
}
// Initialize timer for Fast Mode
if (game.gameMode === 'fast') {
// Clear any existing timer
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
// Calculate initial max timer value based on score
var maxTimer = 10;
// Use exponential thresholds for slower decrease
if (score >= 100) {
maxTimer = 9;
}
if (score >= 500) {
maxTimer = 8;
}
if (score >= 1000) {
maxTimer = 7;
}
if (score >= 3500) {
maxTimer = 6;
}
if (score >= 10000) {
maxTimer = 5;
}
if (score >= 25000) {
maxTimer = 4;
}
if (score >= 50000) {
maxTimer = 3;
}
// Reset timer value
timerSeconds = maxTimer;
if (timerText) {
timerText.setText("Time: " + maxTimer.toFixed(1));
}
// Start timer countdown
startTimer();
}
// Centralize initialization of all game elements
initializeGameElements();
// Initialize score
initializeScore();
// Initialize the game grid
initializeGrid();
ensureNoInitialMatches();
gameInitialized = true;
// Start periodic save of high score for classic mode
startPeriodicSave();
};
// Create and position score text
function initializeScore() {
// Now handled in initializeGameElements
if (scoreText) {
scoreText.setText("Score: 0");
}
}
// Update score display
function updateScore(points) {
score += points;
if (scoreText) {
scoreText.setText("Score: " + score);
}
}
var cellPool = [];
function getGridCell() {
if (cellPool.length > 0) {
return cellPool.pop().init();
}
return new GridCell();
}
function recycleGridCell(cell) {
if (cell) {
cellPool.push(cell);
}
}
var selectedCell = null;
var isAnimating = false;
window.gravityInProgress = false;
window.fillInProgress = false;
// Track combo counts for sound effects
var comboCounter = 0;
var comboInProgress = false;
function handleCellTap(tappedCell) {
if (isAnimating || window.gravityInProgress || window.fillInProgress) {
return;
}
if (!tappedCell || !gridCells[tappedCell.row] || gridCells[tappedCell.row][tappedCell.col] !== tappedCell || tappedCell.beingDestroyed) {
return;
}
// Special memes need to be matched with other memes to be activated, so we don't check for special activation here
// If we already have a selected cell
if (selectedCell !== null) {
// If it's valid
if (selectedCell && gridCells[selectedCell.row] && gridCells[selectedCell.row][selectedCell.col] === selectedCell) {
// If we tap the same cell, deselect it
if (selectedCell === tappedCell) {
selectedCell.hideSelection();
selectedCell = null;
return;
}
// Check if they're adjacent
var isAdjacent = Math.abs(selectedCell.row - tappedCell.row) === 1 && selectedCell.col === tappedCell.col || Math.abs(selectedCell.col - tappedCell.col) === 1 && selectedCell.row === tappedCell.row;
if (!isAdjacent) {
// Not adjacent, switch selection
selectedCell.hideSelection();
selectedCell = tappedCell;
selectedCell.showSelection();
return;
}
// They are adjacent, continue with swap
var cell1 = selectedCell;
var cell2 = tappedCell;
// Hide selection before swap
cell1.hideSelection();
selectedCell = null;
var isAdjacent = Math.abs(cell1.row - cell2.row) === 1 && cell1.col === cell2.col || Math.abs(cell1.col - cell2.col) === 1 && cell1.row === cell2.row;
if (!isAdjacent) {
return;
}
} else {
// Invalid selected cell, select the new one
if (selectedCell) {
selectedCell.hideSelection();
}
selectedCell = tappedCell;
selectedCell.showSelection();
return;
}
} else {
// No selection yet, select this cell
selectedCell = tappedCell;
selectedCell.showSelection();
return;
}
isAnimating = true;
var pos1_x = cell1.x;
var pos1_y = cell1.y;
var pos2_x = cell2.x;
var pos2_y = cell2.y;
var row1 = cell1.row;
var col1 = cell1.col;
var row2 = cell2.row;
var col2 = cell2.col;
// Check if cells are special memes
var greenSpecialCell = null;
var regularCell = null;
var lineSpecialCells = [];
var twoGreenSpecials = false;
// Check for two green special memes
if (cell1 instanceof GreenSpecialMeme && cell2 instanceof GreenSpecialMeme) {
twoGreenSpecials = true;
}
// Check for green special meme
else if (cell1 instanceof GreenSpecialMeme) {
greenSpecialCell = cell1;
regularCell = cell2;
} else if (cell2 instanceof GreenSpecialMeme) {
greenSpecialCell = cell2;
regularCell = cell1;
}
// Check for line-clearing special memes (horizontal/vertical)
if (cell1.isSpecial && (cell1.specialType === 'horizontal' || cell1.specialType === 'vertical')) {
lineSpecialCells.push(cell1);
}
if (cell2.isSpecial && (cell2.specialType === 'horizontal' || cell2.specialType === 'vertical')) {
lineSpecialCells.push(cell2);
}
gridCells[row1][col1] = cell2;
gridCells[row2][col2] = cell1;
cell1.row = row2;
cell1.col = col2;
cell2.row = row1;
cell2.col = col1;
// Make sure any selection highlights are removed
if (cell1.selectionHighlight) {
cell1.hideSelection();
}
if (cell2.selectionHighlight) {
cell2.hideSelection();
}
// Check for special combinations: two green special memes, two bombs, line-clearing + bomb, or two line special memes
if (twoGreenSpecials) {
// Two green special memes combination - destroys all visible memes
var cellsToDestroy = [];
// Collect all visible cells on the board
for (var row = extraRows; row < gridSize + extraRows; row++) {
for (var col = 0; col < gridSize; col++) {
if (gridCells[row][col] && gridCells[row][col] !== cell1 && gridCells[row][col] !== cell2) {
cellsToDestroy.push(gridCells[row][col]);
}
}
}
// Award massive bonus for two green special meme combination
updateScore(1000);
// Show mega score popup
var megaScorePopup = new Text2("+1000", {
size: 100,
fill: 0x00FF00
});
megaScorePopup.anchor.set(0.5, 0.5);
megaScorePopup.x = 2048 / 2;
megaScorePopup.y = 2732 / 2;
game.addChild(megaScorePopup);
// Animate mega score popup
tween(megaScorePopup, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 1000,
onComplete: function onComplete() {
game.removeChild(megaScorePopup);
megaScorePopup.destroy();
}
});
// Mark special memes for destruction
cell1.beingDestroyed = true;
cell2.beingDestroyed = true;
// Schedule destruction after the swap animation completes
LK.setTimeout(function () {
LK.getSound('Explosion').play();
// Flash screen effect for this powerful combination
LK.effects.flashScreen(0x00FF00, 500);
// Destroy the collected cells
if (cellsToDestroy.length > 0) {
destroyCells(cellsToDestroy);
}
// Destroy the special memes
LK.effects.flashObject(cell1, 0xFFFFFF, 200);
LK.effects.flashObject(cell2, 0xFFFFFF, 200);
gridContainer.removeChild(cell1);
gridContainer.removeChild(cell2);
cell1.destroy();
cell2.destroy();
gridCells[cell1.row][cell1.col] = null;
gridCells[cell2.row][cell2.col] = null;
}, 250);
} else if (cell1.isSpecial && cell1.specialType === 'bomb' && cell2.isSpecial && cell2.specialType === 'bomb') {
// Two bomb special memes combination - creates a 5x5 explosion
var cellsToDestroy = [];
// Determine which cell was moved by the player
var activatedCell = cell1;
// Create a 5x5 area effect centered on the first bomb
for (var r = Math.max(extraRows, cell1.row - 2); r <= Math.min(gridSize + extraRows - 1, cell1.row + 2); r++) {
for (var c = Math.max(0, cell1.col - 2); c <= Math.min(gridSize - 1, cell1.col + 2); c++) {
if (gridCells[r][c] && gridCells[r][c] !== cell1 && gridCells[r][c] !== cell2) {
cellsToDestroy.push(gridCells[r][c]);
}
}
}
// Mark special memes for destruction
cell1.beingDestroyed = true;
cell2.beingDestroyed = true;
// Schedule destruction after the swap animation completes
LK.setTimeout(function () {
LK.getSound('Explosion').play();
// Destroy the collected cells
if (cellsToDestroy.length > 0) {
destroyCells(cellsToDestroy);
}
// Destroy the special memes
LK.effects.flashObject(cell1, 0xFFFFFF, 200);
LK.effects.flashObject(cell2, 0xFFFFFF, 200);
gridContainer.removeChild(cell1);
gridContainer.removeChild(cell2);
cell1.destroy();
cell2.destroy();
gridCells[cell1.row][cell1.col] = null;
gridCells[cell2.row][cell2.col] = null;
}, 250);
} else if (cell1.isSpecial && cell1.specialType === 'bomb' && cell2.isSpecial && (cell2.specialType === 'horizontal' || cell2.specialType === 'vertical') || cell2.isSpecial && cell2.specialType === 'bomb' && cell1.isSpecial && (cell1.specialType === 'horizontal' || cell1.specialType === 'vertical')) {
// Line-clearing + bomb special combination
var bombCell = cell1.specialType === 'bomb' ? cell1 : cell2;
var lineCell = cell1.specialType === 'horizontal' || cell1.specialType === 'vertical' ? cell1 : cell2;
var cellsToDestroy = [];
var affectedRows = [];
// Determine which cell was moved by the player
var activatedCell = cell1;
// Enhanced effect: destroy 3 rows or columns based on the line special type
if (lineCell.specialType === 'horizontal') {
// Destroy 3 rows centered on the bomb's row
for (var r = Math.max(extraRows, bombCell.row - 1); r <= Math.min(gridSize + extraRows - 1, bombCell.row + 1); r++) {
affectedRows.push(r);
for (var c = 0; c < gridSize; c++) {
if (gridCells[r][c] && gridCells[r][c] !== bombCell && gridCells[r][c] !== lineCell) {
cellsToDestroy.push(gridCells[r][c]);
}
}
}
} else {
// vertical
// Destroy 3 columns centered on the bomb's column
for (var r = extraRows; r < gridSize + extraRows; r++) {
for (var c = Math.max(0, bombCell.col - 1); c <= Math.min(gridSize - 1, bombCell.col + 1); c++) {
if (gridCells[r][c] && gridCells[r][c] !== bombCell && gridCells[r][c] !== lineCell) {
cellsToDestroy.push(gridCells[r][c]);
}
}
}
}
// Mark special memes for destruction
bombCell.beingDestroyed = true;
lineCell.beingDestroyed = true;
// Schedule destruction after the swap animation completes
LK.setTimeout(function () {
LK.getSound('Explosion').play();
// Destroy the collected cells
if (cellsToDestroy.length > 0) {
destroyCells(cellsToDestroy);
}
// Destroy the special memes
LK.effects.flashObject(bombCell, 0xFFFFFF, 200);
LK.effects.flashObject(lineCell, 0xFFFFFF, 200);
gridContainer.removeChild(bombCell);
gridContainer.removeChild(lineCell);
bombCell.destroy();
lineCell.destroy();
gridCells[bombCell.row][bombCell.col] = null;
gridCells[lineCell.row][lineCell.col] = null;
}, 250);
}
// Check if two line special memes were swapped (horizontal and vertical)
else if (lineSpecialCells.length === 2) {
var cellsToDestroy = [];
// Collect cells in both row and column
var row1, col1, row2, col2;
var activatedSpecial;
// Determine which special meme was moved by the player (based on original positions)
if (lineSpecialCells[0] === cell1) {
activatedSpecial = lineSpecialCells[0];
row1 = lineSpecialCells[0].row;
col1 = lineSpecialCells[0].col;
} else {
activatedSpecial = lineSpecialCells[1];
row1 = lineSpecialCells[1].row;
col1 = lineSpecialCells[1].col;
}
// Add cells from the activated special's row or column
for (var row = extraRows; row < gridSize + extraRows; row++) {
// Add cells from column
if (gridCells[row][col1] && gridCells[row][col1] !== lineSpecialCells[0] && gridCells[row][col1] !== lineSpecialCells[1]) {
cellsToDestroy.push(gridCells[row][col1]);
}
}
// Add cells from the activated special's row
for (var col = 0; col < gridSize; col++) {
// Add cells from row
if (col !== col1 && gridCells[row1][col] && gridCells[row1][col] !== lineSpecialCells[0] && gridCells[row1][col] !== lineSpecialCells[1]) {
cellsToDestroy.push(gridCells[row1][col]);
}
}
// Mark special memes for destruction
lineSpecialCells.forEach(function (specialMeme) {
specialMeme.beingDestroyed = true;
});
// Schedule destruction after the swap animation completes
LK.setTimeout(function () {
LK.getSound('Explosion').play();
// Destroy the collected cells
if (cellsToDestroy.length > 0) {
destroyCells(cellsToDestroy);
}
// Destroy the special memes
lineSpecialCells.forEach(function (specialMeme) {
LK.effects.flashObject(specialMeme, 0xFFFFFF, 200);
gridContainer.removeChild(specialMeme);
specialMeme.destroy();
gridCells[specialMeme.row][specialMeme.col] = null;
});
}, 250);
}
// Check if greenSpecialCell was swapped with a line-clearing special meme or bomb
else if (greenSpecialCell && regularCell) {
// Check if regularCell is a line-clearing special meme
if (regularCell.isSpecial && (regularCell.specialType === 'horizontal' || regularCell.specialType === 'vertical')) {
var targetType = regularCell.value;
var cellsToTransform = [];
// Find all cells with the same type as the swapped line-clearing meme
for (var row = extraRows; row < gridSize + extraRows; row++) {
for (var col = 0; col < gridSize; col++) {
var cell = gridCells[row][col];
if (cell && cell.value === targetType && cell !== greenSpecialCell && cell !== regularCell && !cell.isSpecial) {
cellsToTransform.push(cell);
}
}
}
// Mark special memes for destruction
greenSpecialCell.beingDestroyed = true;
// Schedule transformation and activation after the swap animation completes
LK.setTimeout(function () {
LK.getSound('Explosion').play();
LK.effects.flashObject(greenSpecialCell, 0xFFFFFF, 200);
// Transform all cells of the same type into line-clearing memes
cellsToTransform.forEach(function (cell) {
cell.isSpecial = true;
// Randomly assign horizontal or vertical
cell.specialType = Math.random() < 0.5 ? 'horizontal' : 'vertical';
// Apply special icon
if (cell.specialIcon) {
cell.removeChild(cell.specialIcon);
cell.specialIcon = null;
}
cell.specialIcon = LK.getAsset('HabilidadClear', {
anchorX: 0.5,
anchorY: 0.5
});
cell.addChild(cell.specialIcon);
LK.effects.flashObject(cell, 0xFFFFFF, 200);
// Activate each special meme with a slight delay
LK.setTimeout(function () {
cell.activateSpecialPower();
}, 100 + Math.random() * 300);
});
// Remove the green special meme
gridContainer.removeChild(greenSpecialCell);
greenSpecialCell.destroy();
gridCells[greenSpecialCell.row][greenSpecialCell.col] = null;
// Activate the original line-clearing meme last
LK.setTimeout(function () {
regularCell.activateSpecialPower();
}, 500);
}, 250);
} else if (regularCell.isSpecial && regularCell.specialType === 'bomb') {
// Green special + bomb special interaction
var targetType = regularCell.value;
var cellsToTransform = [];
// Find all cells with the same type as the bomb meme
for (var row = extraRows; row < gridSize + extraRows; row++) {
for (var col = 0; col < gridSize; col++) {
var cell = gridCells[row][col];
if (cell && cell.value === targetType && cell !== greenSpecialCell && cell !== regularCell && !cell.isSpecial) {
cellsToTransform.push(cell);
}
}
}
// Mark green special meme for destruction
greenSpecialCell.beingDestroyed = true;
// Schedule transformation and activation after swap animation
LK.setTimeout(function () {
LK.getSound('Explosion').play();
LK.effects.flashObject(greenSpecialCell, 0xFFFFFF, 200);
// Transform all cells of same type into bomb memes
cellsToTransform.forEach(function (cell) {
cell.isSpecial = true;
cell.specialType = 'bomb';
// Apply bomb icon
if (cell.specialIcon) {
cell.removeChild(cell.specialIcon);
cell.specialIcon = null;
}
cell.specialIcon = LK.getAsset('habilidadBomba', {
anchorX: 0.5,
anchorY: 0.5
});
cell.addChild(cell.specialIcon);
LK.effects.flashObject(cell, 0xFFFFFF, 200);
// Activate each bomb with slight delay
LK.setTimeout(function () {
cell.activateSpecialPower();
}, 100 + Math.random() * 300);
});
// Remove the green special meme
gridContainer.removeChild(greenSpecialCell);
greenSpecialCell.destroy();
gridCells[greenSpecialCell.row][greenSpecialCell.col] = null;
// Activate the original bomb last
LK.setTimeout(function () {
regularCell.activateSpecialPower();
}, 500);
}, 250);
} else {
var targetType = regularCell.value;
var cellsToDestroy = [];
// Find all cells with the same type as the swapped meme
for (var row = extraRows; row < gridSize + extraRows; row++) {
for (var col = 0; col < gridSize; col++) {
var cell = gridCells[row][col];
if (cell && cell.value === targetType && cell !== greenSpecialCell) {
cellsToDestroy.push(cell);
}
}
}
// Also destroy the green special meme itself
greenSpecialCell.beingDestroyed = true;
// Schedule destruction after the swap animation completes
LK.setTimeout(function () {
if (cellsToDestroy.length > 0) {
LK.getSound('Explosion').play();
LK.effects.flashObject(greenSpecialCell, 0xFFFFFF, 200);
destroyCells(cellsToDestroy);
gridContainer.removeChild(greenSpecialCell);
greenSpecialCell.destroy();
gridCells[greenSpecialCell.row][greenSpecialCell.col] = null;
}
}, 250);
}
}
tween(cell1, {
x: pos2_x,
y: pos2_y
}, {
duration: 200
});
tween(cell2, {
x: pos1_x,
y: pos1_y
}, {
duration: 200
});
selectedCell = null;
LK.setTimeout(function () {
checkForAndDestroyMatches(cell1, cell2);
if (!window.destructionInProgress && !window.gravityInProgress && !window.fillInProgress) {
isAnimating = false;
}
}, 250);
}
function getMatches() {
var matches = [];
function findDirectionalMatches(isHorizontal) {
var primary, secondary;
var primaryMax = isHorizontal ? gridSize + extraRows : gridSize;
var secondaryMax = isHorizontal ? gridSize : gridSize + extraRows;
for (primary = isHorizontal ? extraRows : 0; primary < primaryMax; primary++) {
if (!gridCells[primary]) {
continue;
}
for (secondary = 0; secondary < secondaryMax;) {
if (secondary > secondaryMax - 3) {
secondary++;
continue;
}
var cell1 = isHorizontal ? gridCells[primary] ? gridCells[primary][secondary] : null : gridCells[secondary] ? gridCells[secondary][primary] : null;
if (!cell1 || !cell1.value) {
secondary++;
continue;
}
var currentMatchValue = cell1.value;
var currentMatchCells = [cell1];
for (var k = secondary + 1; k < secondaryMax; k++) {
var nextCell = isHorizontal ? gridCells[primary] ? gridCells[primary][k] : null : gridCells[k] ? gridCells[k][primary] : null;
if (nextCell && nextCell.value === currentMatchValue) {
currentMatchCells.push(nextCell);
} else {
// If no new matches found, reset combo counter
LK.setTimeout(function () {
if (!window.gravityInProgress && !window.fillInProgress) {
comboInProgress = false;
comboCounter = 0;
}
}, 500);
break;
}
}
if (currentMatchCells.length >= 3) {
var validCells = currentMatchCells.filter(function (cell) {
return cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell;
});
if (validCells.length >= 3) {
var visibleCells = validCells.filter(function (cell) {
return cell.row >= extraRows;
});
var invisibleCells = validCells.filter(function (cell) {
return cell.row < extraRows;
});
// Either all cells are visible, or all cells are invisible, or there are at least 3 visible cells
if (visibleCells.length === 0 || invisibleCells.length === 0 || visibleCells.length >= 3) {
// Add isHorizontal and matchType to the match data
validCells.isHorizontal = isHorizontal;
validCells.matchType = currentMatchValue;
matches.push(validCells);
}
}
}
secondary += currentMatchCells.length > 0 ? currentMatchCells.length : 1;
}
}
}
findDirectionalMatches(true);
findDirectionalMatches(false);
return matches;
}
function destroyCells(cellsToDestroy) {
isAnimating = true;
// Create a unique ID for this destruction process
var destructionId = Date.now() + Math.random();
// Check if match crosses visible/invisible boundary
var visibleCells = cellsToDestroy.filter(function (cell) {
return cell.row >= extraRows;
});
var invisibleCells = cellsToDestroy.filter(function (cell) {
return cell.row < extraRows;
});
// If mixed visibility and only one cell is visible, don't destroy any
if (visibleCells.length > 0 && invisibleCells.length > 0 && (visibleCells.length === 1 || invisibleCells.length === 1)) {
isAnimating = false;
return;
}
// Reset timer if we're in Fast Mode and destroying visible cells
if (game.gameMode === 'fast' && visibleCells.length > 0) {
resetTimer();
}
// Calculate points for destroyed cells - only count visible cells
var basePoints = visibleCells.length * 10;
var visibleCellsToDestroy = cellsToDestroy.filter(function (cell) {
return cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell;
});
// Find the center cell (the one that triggered the match)
var centerCell = null;
// If we have the cells from a swap, use one of those as the center
if (arguments.length > 1 && arguments[1]) {
centerCell = arguments[1];
} else if (cellsToDestroy.length > 0) {
// Otherwise use the middle cell of the match
centerCell = cellsToDestroy[Math.floor(cellsToDestroy.length / 2)];
}
// Group cells by meme type
var cellsByType = {};
visibleCellsToDestroy.forEach(function (cell) {
if (!cellsByType[cell.value]) {
cellsByType[cell.value] = [];
}
cellsByType[cell.value].push(cell);
});
// Count unique groups for combo sound
var uniqueGroupCount = Object.keys(cellsByType).length;
if (uniqueGroupCount > 0) {
// Increment combo counter only if this is part of a continuous combo
if (!comboInProgress) {
comboCounter = uniqueGroupCount;
comboInProgress = true;
} else {
comboCounter += uniqueGroupCount;
}
// Calculate score multiplier based on combo count
var comboMultiplier = 1;
if (comboCounter >= 9) {
comboMultiplier = 5;
} else if (comboCounter >= 6) {
comboMultiplier = 4;
} else if (comboCounter >= 4) {
comboMultiplier = 3;
} else if (comboCounter >= 3) {
comboMultiplier = 2;
} else if (comboCounter >= 2) {
comboMultiplier = 1.5;
}
// Award points with combo multiplier
var pointsToAdd = Math.floor(basePoints * comboMultiplier);
updateScore(pointsToAdd);
// Show floating score text at center cell if available
if (centerCell && centerCell.row >= extraRows) {
var scorePopup = new Text2("+" + pointsToAdd, {
size: 60,
fill: 0xFF8800
});
scorePopup.anchor.set(0.5, 0.5);
scorePopup.x = centerCell.x;
scorePopup.y = centerCell.y;
game.addChild(scorePopup);
// Animate score popup
tween(scorePopup, {
y: scorePopup.y - 80,
alpha: 0
}, {
duration: 800,
onComplete: function onComplete() {
game.removeChild(scorePopup);
scorePopup.destroy();
}
});
}
// Stop any currently playing combo sounds before playing a new one
LK.getSound('Combo1').stop();
LK.getSound('Combo2').stop();
LK.getSound('Combo3').stop();
LK.getSound('Combo4').stop();
LK.getSound('Combo5').stop();
if (comboCounter >= 9) {
LK.getSound('Combo5').play();
} else if (comboCounter >= 6) {
LK.getSound('Combo4').play();
} else if (comboCounter >= 4) {
LK.getSound('Combo3').play();
} else if (comboCounter >= 3) {
LK.getSound('Combo2').play();
} else if (comboCounter >= 2) {
LK.getSound('Combo1').play();
}
}
var specialCell = null;
var isHorizontalMatch = false;
var isVerticalMatch = false;
for (var type in cellsByType) {
var typeCells = cellsByType[type];
if (typeCells.length >= 6) {
// Create green special meme for 6 or more matches
if (centerCell && centerCell.value === parseInt(type)) {
// Create a new green special meme
var greenMeme = new GreenSpecialMeme().init();
greenMeme.setValue(parseInt(type));
greenMeme.x = centerCell.x;
greenMeme.y = centerCell.y;
greenMeme.row = centerCell.row;
greenMeme.col = centerCell.col;
greenMeme.beingDestroyed = false;
// Replace the center cell with the green special meme
gridCells[centerCell.row][centerCell.col] = greenMeme;
gridContainer.removeChild(centerCell);
centerCell.destroy();
gridContainer.addChild(greenMeme);
specialCell = greenMeme;
} else {
// Create a new green special meme
var greenMeme = new GreenSpecialMeme().init();
greenMeme.setValue(parseInt(type));
greenMeme.x = typeCells[0].x;
greenMeme.y = typeCells[0].y;
greenMeme.row = typeCells[0].row;
greenMeme.col = typeCells[0].col;
greenMeme.beingDestroyed = false;
// Replace the first cell with the green special meme
gridCells[typeCells[0].row][typeCells[0].col] = greenMeme;
gridContainer.removeChild(typeCells[0]);
typeCells[0].destroy();
gridContainer.addChild(greenMeme);
specialCell = greenMeme;
}
// Award bonus points for creating a green special meme
updateScore(100);
// Remove special cell from cells to destroy
visibleCellsToDestroy = visibleCellsToDestroy.filter(function (cell) {
return cell !== typeCells[0] && cell !== centerCell;
});
break; // Only create one special meme
} else if (typeCells.length === 5) {
// Create bomb special meme (blue)
if (centerCell && centerCell.value === parseInt(type)) {
specialCell = centerCell;
} else {
specialCell = typeCells[0];
}
specialCell.isSpecial = true;
specialCell.specialType = 'bomb';
specialCell.beingDestroyed = false;
// Award bonus points for creating a bomb special meme
updateScore(75);
// Remove special cell from cells to destroy
visibleCellsToDestroy = visibleCellsToDestroy.filter(function (cell) {
return cell !== specialCell;
});
break; // Only create one special meme
} else if (typeCells.length === 4) {
// Determine if match is horizontal or vertical
var allInSameRow = true;
var allInSameCol = true;
var firstCell = typeCells[0];
for (var i = 1; i < typeCells.length; i++) {
if (typeCells[i].row !== firstCell.row) {
allInSameRow = false;
}
if (typeCells[i].col !== firstCell.col) {
allInSameCol = false;
}
}
isHorizontalMatch = allInSameRow;
isVerticalMatch = allInSameCol;
// Create special meme only if it's a clean horizontal or vertical match
if (isHorizontalMatch || isVerticalMatch) {
// Use center cell if it matches the type, otherwise use the first cell of this type
if (centerCell && centerCell.value === parseInt(type)) {
specialCell = centerCell;
} else {
specialCell = typeCells[0];
}
specialCell.isSpecial = true;
specialCell.specialType = isHorizontalMatch ? 'horizontal' : 'vertical';
specialCell.beingDestroyed = false;
// Award bonus points for creating a line-clearing special meme
updateScore(50);
// Remove special cell from cells to destroy
visibleCellsToDestroy = visibleCellsToDestroy.filter(function (cell) {
return cell !== specialCell;
});
break; // Only create one special meme
}
}
}
// Sort cells by distance from center cell for ripple effect
if (centerCell && visibleCellsToDestroy.length > 0) {
visibleCellsToDestroy.sort(function (a, b) {
var distA = Math.abs(a.row - centerCell.row) + Math.abs(a.col - centerCell.col);
var distB = Math.abs(b.row - centerCell.row) + Math.abs(b.col - centerCell.col);
return distA - distB;
});
}
var delay = 0;
var delayIncrement = 50;
var totalDestructionTime = visibleCellsToDestroy.length * delayIncrement;
// Mark cells as being destroyed to prevent them from being part of new matches
visibleCellsToDestroy.forEach(function (cell) {
if (cell) {
cell.beingDestroyed = true;
// Check if any of these cells are matched with a special meme
if (cell.isSpecial) {
// Special meme is being destroyed, activate its power
LK.setTimeout(function () {
cell.activateSpecialPower();
}, 100);
}
}
});
// If we have a special meme, apply the appropriate special icon
if (specialCell && specialCell.isSpecial) {
// Remove any previous special icon if it exists
if (specialCell.specialIcon) {
specialCell.removeChild(specialCell.specialIcon);
specialCell.specialIcon = null;
}
if (specialCell.specialType === 'bomb') {
// Add bomb icon on top of the meme
specialCell.specialIcon = LK.getAsset('habilidadBomba', {
anchorX: 0.5,
anchorY: 0.5
});
specialCell.addChild(specialCell.specialIcon);
} else if (specialCell.specialType === 'green' && !(specialCell instanceof GreenSpecialMeme)) {
// Create a new green special meme to replace the current cell
var greenMeme = new GreenSpecialMeme().init();
greenMeme.setValue(specialCell.value);
greenMeme.x = specialCell.x;
greenMeme.y = specialCell.y;
greenMeme.row = specialCell.row;
greenMeme.col = specialCell.col;
greenMeme.beingDestroyed = false;
// Replace the cell with the green special meme
gridCells[specialCell.row][specialCell.col] = greenMeme;
gridContainer.removeChild(specialCell);
specialCell.destroy();
gridContainer.addChild(greenMeme);
specialCell = greenMeme;
} else {
// Add clear icon on top of the meme for horizontal/vertical specials
specialCell.specialIcon = LK.getAsset('HabilidadClear', {
anchorX: 0.5,
anchorY: 0.5
});
specialCell.addChild(specialCell.specialIcon);
}
LK.effects.flashObject(specialCell, 0xFFFFFF, 300);
}
visibleCellsToDestroy.forEach(function (cell) {
LK.setTimeout(function () {
if (cell && cell.beingDestroyed && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) {
LK.getSound('Explosion').play();
LK.effects.flashObject(cell, 0xFFFFFF, 200);
gridContainer.removeChild(cell);
cell.destroy();
gridCells[cell.row][cell.col] = null;
}
}, delay);
delay += delayIncrement;
});
// After all cells in this group are destroyed, check if we need to apply gravity
LK.setTimeout(function () {
// Check if all destructions are complete before applying gravity
var allCellsDestroyed = true;
for (var r = 0; r < gridSize + extraRows; r++) {
for (var c = 0; c < gridSize; c++) {
if (gridCells[r] && gridCells[r][c] && gridCells[r][c].beingDestroyed) {
allCellsDestroyed = false;
}
}
}
// Only the last destruction process should trigger gravity
if (allCellsDestroyed && !window.gravityInProgress && !window.fillInProgress) {
applyGravity();
}
}, totalDestructionTime + 50);
}
function applyGravity() {
isAnimating = true;
var cellsToFall = [];
if (window.gravityInProgress) {
return;
}
window.gravityInProgress = true;
for (var col = 0; col < gridSize; col++) {
for (var row = gridSize + extraRows - 1; row >= extraRows; row--) {
if (!gridCells[row][col]) {
var sourceRow = row - 1;
while (sourceRow >= 0 && !gridCells[sourceRow][col]) {
sourceRow--;
}
if (sourceRow >= 0) {
var cellToMove = gridCells[sourceRow][col];
if (cellToMove) {
cellsToFall.push({
cell: cellToMove,
fromRow: sourceRow,
toRow: row,
col: col
});
gridCells[row][col] = cellToMove;
gridCells[sourceRow][col] = null;
cellToMove.row = row;
if (sourceRow < extraRows) {
gridContainer.addChild(cellToMove);
}
}
}
}
}
}
var longestDelay = 0;
var baseDelay = 15;
var constantDuration = 250;
cellsToFall.sort(function (a, b) {
if (a.col !== b.col) {
return a.col - b.col;
}
return b.toRow - a.toRow;
});
cellsToFall.forEach(function (fallInfo, index) {
var delay = index * baseDelay;
var newPos = getCellPosition(fallInfo.toRow, fallInfo.col);
var totalTime = delay + constantDuration;
if (totalTime > longestDelay) {
longestDelay = totalTime;
}
LK.setTimeout(function () {
if (fallInfo.cell && gridCells[fallInfo.cell.row] && gridCells[fallInfo.cell.row][fallInfo.cell.col] === fallInfo.cell) {
tween(fallInfo.cell, {
y: newPos.y
}, {
duration: constantDuration,
easing: tween.linear
});
}
}, delay);
});
LK.setTimeout(function () {
window.gravityInProgress = false;
if (!window.destructionInProgress) {
fillEmptySpacesWithNewMemes();
}
}, longestDelay + 50);
}
function fillEmptySpacesWithNewMemes() {
if (window.fillInProgress || window.gravityInProgress) {
return;
}
window.fillInProgress = true;
var anyNewMemeAdded = false;
var newCellsToAdd = [];
for (var col = 0; col < gridSize; col++) {
for (var row = 0; row < gridSize + extraRows; row++) {
if (!gridCells[row][col]) {
anyNewMemeAdded = true;
var newCell = getGridCell();
var pos = getCellPosition(row, col);
newCell.x = pos.x;
newCell.y = pos.y - 400;
var randomValue = Math.floor(Math.random() * 5) + 1;
newCell.setValue(randomValue);
newCell.row = row;
newCell.col = col;
gridCells[row][col] = newCell;
newCellsToAdd.push({
cell: newCell,
destY: pos.y,
row: row,
col: col
});
if (row >= extraRows) {
gridContainer.addChild(newCell);
}
}
}
}
var longestDelay = 0;
var baseDelay = 15;
var constantDuration = 250;
newCellsToAdd.sort(function (a, b) {
if (a.col !== b.col) {
return a.col - b.col;
}
return b.row - a.row;
});
newCellsToAdd.forEach(function (cellData, index) {
var delay = index * baseDelay;
var totalTime = delay + constantDuration;
if (totalTime > longestDelay) {
longestDelay = totalTime;
}
LK.setTimeout(function () {
if (cellData.cell && gridCells[cellData.row] && gridCells[cellData.row][cellData.col] === cellData.cell) {
tween(cellData.cell, {
y: cellData.destY
}, {
duration: constantDuration,
easing: tween.linear
});
}
}, delay);
});
if (anyNewMemeAdded) {
LK.setTimeout(function () {
window.fillInProgress = false;
if (window.destructionInProgress || window.gravityInProgress) {
isAnimating = false;
return;
}
var visibleMatchGroups = getMatches().filter(function (group) {
// Check if the match has cells in both visible and invisible areas
var visibleCells = group.filter(function (cell) {
return cell.row >= extraRows;
});
var invisibleCells = group.filter(function (cell) {
return cell.row < extraRows;
});
// Don't count groups with both visible and invisible cells unless there are at least 3 visible cells
if (visibleCells.length > 0 && invisibleCells.length > 0 && visibleCells.length < 3) {
return false;
}
return visibleCells.length >= 3; // At least 3 visible cells make a valid visible match
});
var newMatches = visibleMatchGroups;
if (newMatches.length > 0) {
// Group by meme type
var matchesByType = {};
newMatches.forEach(function (group) {
if (group.length > 0) {
var type = group[0].value;
if (!matchesByType[type]) {
matchesByType[type] = [];
}
matchesByType[type].push(group);
}
});
var newCellsToDestroy = [];
var newCellTracker = {};
// Process each type separately
for (var type in matchesByType) {
var typeGroups = matchesByType[type];
// Find a central cell for this type
var centerCell = null;
if (typeGroups.length > 0 && typeGroups[0].length > 0) {
// Pick the center of the first match group of this type
var matchGroup = typeGroups[0];
centerCell = matchGroup[Math.floor(matchGroup.length / 2)];
}
// Collect all cells of this type
var typeCells = [];
typeGroups.forEach(function (group) {
group.forEach(function (cell) {
var cellKey = cell.row + "_" + cell.col;
if (!newCellTracker[cellKey] && cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) {
typeCells.push(cell);
newCellTracker[cellKey] = true;
}
});
});
// Destroy cells of this type
if (typeCells.length) {
destroyCells(typeCells, centerCell);
}
}
if (Object.keys(matchesByType).length === 0) {
isAnimating = false;
}
} else {
isAnimating = false;
}
}, longestDelay + 50);
} else {
window.fillInProgress = false;
isAnimating = false;
// Reset combo tracking when no more matches are being created
comboInProgress = false;
comboCounter = 0;
}
}
// Timer management functions
function startTimer() {
// Clear any existing timer
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
// Calculate initial max timer value based on score
// This ensures proper timer initialization when starting a game with existing score
var maxTimer = 10;
// Use exponential thresholds for slower decrease
if (score >= 100) {
maxTimer = 9;
}
if (score >= 500) {
maxTimer = 8;
}
if (score >= 1000) {
maxTimer = 7;
}
if (score >= 3500) {
maxTimer = 6;
}
if (score >= 10000) {
maxTimer = 5;
}
if (score >= 25000) {
maxTimer = 4;
}
if (score >= 50000) {
maxTimer = 3;
}
// Only set timer to max when initializing new game, not when resuming
if (!game.timerInitialized) {
timerSeconds = maxTimer;
game.timerInitialized = true;
}
// Update timer display initially
if (timerText) {
timerText.setText("Time: " + timerSeconds.toFixed(1));
}
// Start countdown timer with decimals (100ms interval)
timerInterval = LK.setInterval(function () {
timerSeconds -= 0.1;
timerSeconds = Math.max(0, parseFloat(timerSeconds.toFixed(1))); // Fix floating point issues
// Update timer display
if (timerText) {
timerText.setText("Time: " + timerSeconds.toFixed(1));
}
// Check for game over
if (timerSeconds <= 0) {
// Stop timer
LK.clearInterval(timerInterval);
timerInterval = null;
// Instead of showing game over screen, trigger the custom game over
game.onGameOver();
}
}, 100);
}
function resetTimer() {
// Only reset timer in Fast Mode
if (game.gameMode === 'fast') {
// Calculate max timer value based on score
// Start with 10 seconds, decrease as score increases, minimum of 3 seconds
var maxTimer = 10;
// Use exponential thresholds for slower decrease
if (score >= 100) {
maxTimer = 9;
}
if (score >= 500) {
maxTimer = 8;
}
if (score >= 1000) {
maxTimer = 7;
}
if (score >= 3500) {
maxTimer = 6;
}
if (score >= 10000) {
maxTimer = 5;
}
if (score >= 25000) {
maxTimer = 4;
}
if (score >= 50000) {
maxTimer = 3;
}
// Set timer to calculated max
timerSeconds = maxTimer;
if (timerText) {
timerText.setText("Time: " + timerSeconds.toFixed(1));
}
}
}
// Handle game over events to return to menu
game.onGameOver = function () {
// Save high score if current score is higher
if (game.gameMode === 'classic' && score > storage.highScores.classic) {
storage.highScores.classic = score;
} else if (game.gameMode === 'fast' && score > storage.highScores.fast) {
storage.highScores.fast = score;
}
// Stop timer if running
if (timerInterval) {
LK.clearInterval(timerInterval);
timerInterval = null;
}
// Clear periodic save interval
if (saveScoreInterval) {
LK.clearInterval(saveScoreInterval);
saveScoreInterval = null;
}
// For Fast Mode, show a custom high score popup instead of the game over screen
if (game.gameMode === 'fast') {
// Create popup container
var popupContainer = new Container();
// Create popup background
var popupBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 4.5
});
popupBg.tint = 0x0088FF; // Blue tint for Fast Mode
// Create popup title
var popupTitle = new Text2("GAME OVER", {
size: 100,
fill: 0xFFFFFF
});
popupTitle.anchor.set(0.5, 0);
popupTitle.y = -350;
// Create high score text with line break
var highScoreText = new Text2("YOUR HIGH SCORE:\n" + storage.highScores.fast, {
size: 60,
fill: 0xFFFFFF
});
highScoreText.anchor.set(0.5, 0);
highScoreText.y = -200;
// Create current score text with line break
var currentScoreText = new Text2("THIS GAME:\n" + score, {
size: 60,
fill: 0xFFFFFF
});
currentScoreText.anchor.set(0.5, 0);
currentScoreText.y = -50;
// Create button to return to menu
var menuButton = new Container();
var menuButtonBg = LK.getAsset('button', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
menuButtonBg.tint = 0xFF5500;
var menuButtonText = new Text2("BACK TO MENU", {
size: 70,
fill: 0xFFFFFF
});
menuButtonText.anchor.set(0.5, 0.5);
menuButton.addChild(menuButtonBg);
menuButton.addChild(menuButtonText);
menuButton.y = 250;
// Add all elements to popup
popupContainer.addChild(popupBg);
popupContainer.addChild(popupTitle);
popupContainer.addChild(highScoreText);
popupContainer.addChild(currentScoreText);
popupContainer.addChild(menuButton);
// Position popup in center of screen
popupContainer.x = 2048 / 2;
popupContainer.y = 2732 / 2;
game.addChild(popupContainer);
// Add button interaction
menuButton.interactive = true;
menuButton.down = function (x, y, obj) {
LK.effects.flashObject(menuButtonBg, 0xFFFFFF, 200);
// Clear the game board
while (gridContainer.children.length > 0) {
var child = gridContainer.children[0];
gridContainer.removeChild(child);
child.destroy();
}
if (scoreText) {
game.removeChild(scoreText);
scoreText = null;
}
if (timerText) {
game.removeChild(timerText);
timerText = null;
}
// Remove popup
game.removeChild(popupContainer);
// Clean up any game UI elements
if (game.modeText) {
game.removeChild(game.modeText);
game.modeText = null;
}
if (game.pauseButton) {
game.removeChild(game.pauseButton);
game.pauseButton = null;
}
game.timerInitialized = false;
// Show menu
initializeMenu();
};
} else {
// For Classic Mode, use the standard game over screen
LK.setTimeout(function () {
// Clear the game board
while (gridContainer.children.length > 0) {
var child = gridContainer.children[0];
gridContainer.removeChild(child);
child.destroy();
}
if (scoreText) {
game.removeChild(scoreText);
scoreText = null;
}
if (timerText) {
game.removeChild(timerText);
timerText = null;
}
// Clean up any game UI elements
if (game.modeText) {
game.removeChild(game.modeText);
game.modeText = null;
}
if (game.pauseButton) {
game.removeChild(game.pauseButton);
game.pauseButton = null;
}
game.timerInitialized = false;
// Show menu
initializeMenu();
}, 500);
}
};
function checkForAndDestroyMatches(swappedCellA, swappedCellB) {
if (window.gravityInProgress || window.fillInProgress) {
return;
}
var allMatchGroupsOnBoard = getMatches();
if (!allMatchGroupsOnBoard.length) {
return;
}
var relevantMatchGroups = allMatchGroupsOnBoard.filter(function (group) {
// Check if the match has cells in both visible and invisible areas
var visibleCells = group.filter(function (cell) {
return cell.row >= extraRows;
});
var invisibleCells = group.filter(function (cell) {
return cell.row < extraRows;
});
// Don't count groups with both visible and invisible cells unless there are at least 3 visible cells
if (visibleCells.length > 0 && invisibleCells.length > 0 && visibleCells.length < 3) {
return false;
}
return group.some(function (cellInGroup) {
return (cellInGroup === swappedCellA || cellInGroup === swappedCellB) && cellInGroup.row >= extraRows;
});
});
if (!relevantMatchGroups.length) {
return;
}
// Group by meme type
var matchesByType = {};
relevantMatchGroups.forEach(function (group) {
if (group.length > 0) {
var type = group[0].value;
if (!matchesByType[type]) {
matchesByType[type] = [];
}
matchesByType[type].push(group);
}
});
var uniqueCellTracker = {};
var cellsToDestroy = [];
// Process each type separately
for (var type in matchesByType) {
var typeGroups = matchesByType[type];
// Flatten all groups of this type
var typeCells = [];
typeGroups.forEach(function (group) {
group.forEach(function (cell) {
if (cell.row >= extraRows) {
typeCells.push(cell);
}
});
});
// Add each cell of this type to cellsToDestroy
typeCells.forEach(function (cell) {
var cellKey = cell.row + "_" + cell.col;
if (!uniqueCellTracker[cellKey] && cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) {
cellsToDestroy.push(cell);
uniqueCellTracker[cellKey] = true;
}
});
}
;
if (cellsToDestroy.length) {
// Pass the swapped cell that created the match as the center for the ripple effect
var centerCell = swappedCellA && relevantMatchGroups.some(function (group) {
return group.includes(swappedCellA);
}) ? swappedCellA : swappedCellB;
destroyCells(cellsToDestroy, centerCell);
LK.setTimeout(function () {
if (window.gravityInProgress || window.fillInProgress) {
return;
}
var visibleMatchGroups = getMatches().filter(function (group) {
// Check if the match has cells in both visible and invisible areas
var visibleCells = group.filter(function (cell) {
return cell.row >= extraRows;
});
var invisibleCells = group.filter(function (cell) {
return cell.row < extraRows;
});
// Don't count groups with both visible and invisible cells unless there are at least 3 visible cells
if (visibleCells.length > 0 && invisibleCells.length > 0 && visibleCells.length < 3) {
return false;
}
return visibleCells.length >= 3; // At least 3 visible cells make a valid visible match
});
var newMatches = visibleMatchGroups;
if (newMatches.length > 0) {
// Group by meme type
var matchesByType = {};
newMatches.forEach(function (group) {
if (group.length > 0) {
var type = group[0].value;
if (!matchesByType[type]) {
matchesByType[type] = [];
}
matchesByType[type].push(group);
}
});
// Process each type separately
for (var type in matchesByType) {
var typeGroups = matchesByType[type];
var newCellsToDestroy = [];
var newCellTracker = {};
// Collect all cells of this type
typeGroups.forEach(function (group) {
group.forEach(function (cell) {
var cellKey = cell.row + "_" + cell.col;
if (!newCellTracker[cellKey] && cell.row >= extraRows && cell && gridCells[cell.row] && gridCells[cell.row][cell.col] === cell) {
newCellsToDestroy.push(cell);
newCellTracker[cellKey] = true;
}
});
});
if (newCellsToDestroy.length) {
// Find center cell of this type
var centerCell = newCellsToDestroy[Math.floor(newCellsToDestroy.length / 2)];
destroyCells(newCellsToDestroy, centerCell);
}
}
}
}, 400);
}
}
var gridSize = 8;
var extraRows = 9;
var cellSpacing = 10;
var cellSize = 208;
var gridCells = [];
var totalGridWidth = gridSize * cellSize + (gridSize - 1) * cellSpacing;
var totalVisibleGridHeight = totalGridWidth;
var totalGridHeight = totalVisibleGridHeight + extraRows * (cellSize + cellSpacing);
var startX = (2048 - totalGridWidth) / 2 + cellSize / 2;
var startY = (-1300 - totalVisibleGridHeight) / 2 + cellSize / 2 + extraRows * (cellSize + cellSpacing);
function getCellPosition(row, col) {
return {
x: startX + col * (cellSize + cellSpacing),
y: startY + row * (cellSize + cellSpacing) - extraRows * (cellSize + cellSpacing)
};
}
function initializeGrid() {
gridCells = [];
for (var row = 0; row < gridSize + extraRows; row++) {
gridCells[row] = [];
for (var col = 0; col < gridSize; col++) {
var pos = getCellPosition(row, col);
var cell = getGridCell ? getGridCell() : new GridCell();
cell.x = pos.x;
cell.y = pos.y;
cell.row = row;
cell.col = col;
var randomValue;
var attempts = 0;
do {
randomValue = Math.floor(Math.random() * 5) + 1;
attempts++;
var leftMatchCount = 0;
var aboveMatchCount = 0;
if (col >= 2) {
if (gridCells[row][col - 1].value === randomValue && gridCells[row][col - 2].value === randomValue) {
leftMatchCount = 2;
}
}
if (row >= 2) {
if (gridCells[row - 1][col].value === randomValue && gridCells[row - 2][col].value === randomValue) {
aboveMatchCount = 2;
}
}
} while ((leftMatchCount >= 2 || aboveMatchCount >= 2) && attempts < 10);
cell.setValue(randomValue);
if (row >= extraRows) {
gridContainer.addChild(cell);
}
gridCells[row][col] = cell;
}
}
}
// Initialize menu instead of directly starting the game
initializeMenu();
function ensureNoInitialMatches() {
var matches = getMatches();
if (matches.length > 0) {
matches.forEach(function (group) {
group.forEach(function (cell) {
var currentValue = cell.value;
var newValue;
do {
newValue = Math.floor(Math.random() * 5) + 1;
} while (newValue === currentValue);
cell.setValue(newValue);
});
});
ensureNoInitialMatches();
}
}
ensureNoInitialMatches();
la figura de una casa color blanca simple para una interfaz. In-Game asset. 2d. High contrast. No shadows
haz el fondo color morado
circular check logo. In-Game asset. 2d. High contrast. No shadows
Cuadrado con los bordes redondeado negro. In-Game asset. 2d. High contrast. No shadows
hazlo un gris claro
Que sea blanco
Que sea blanco