/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Gem = Container.expand(function (type) { var self = Container.call(this); self.gemType = type; self.gridX = 0; self.gridY = 0; self.isAnimating = false; self.isScaling = false; var gemAssets = ['gem_red', 'gem_blue', 'gem_green', 'gem_yellow', 'gem_purple', 'gem_orange', 'gem_cyan', 'gem_pink']; var gemGraphics = self.attachAsset(gemAssets[type], { anchorX: 0.5, anchorY: 0.5, scaleX: CELL_SIZE / 256, scaleY: CELL_SIZE / 256 }); self.setGridPosition = function (gridX, gridY) { self.gridX = gridX; self.gridY = gridY; self.x = boardStartX + gridX * CELL_SIZE + CELL_SIZE / 2; self.y = boardStartY + gridY * CELL_SIZE + CELL_SIZE / 2; }; self.animateToPosition = function (targetX, targetY, duration, onComplete) { if (duration === undefined) duration = 300; self.isAnimating = true; tween(self, { x: targetX, y: targetY }, { duration: duration, easing: tween.easeOut, onFinish: function onFinish() { self.isAnimating = false; if (onComplete) onComplete(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x8B4513 // Brown background }); /**** * Game Code ****/ // Gold yellow // Deep pink // Medium slate blue var GAME_STATE_HOME = 0; var GAME_STATE_PLAYING = 1; var GAME_STATE_PAUSED = 2; var currentGameState = GAME_STATE_HOME; // Pause menu elements var pauseMenuContainer = null; var pauseMenuBg = null; var volumeButton = null; var exitButton = null; var resumeButton = null; var volumeEnabled = storage.volumeEnabled !== undefined ? storage.volumeEnabled : true; var BOARD_SIZE = 8; // Smaller board for kids - easier to see patterns var CELL_SIZE = Math.floor(Math.min(2048, 2732 - 500) / BOARD_SIZE * 0.95); // Bigger gems, leave more space for UI var BOARD_TOTAL_SIZE = BOARD_SIZE * CELL_SIZE; var boardStartX = (2048 - BOARD_TOTAL_SIZE) / 2; var boardStartY = (2732 - BOARD_TOTAL_SIZE) / 2 + 100; // Offset for top UI var board = []; var gems = []; var selectedGem = null; var isProcessingMatches = false; var isDragging = false; var dragStartX = 0; var dragStartY = 0; var score = 0; // Home screen elements var homeContainer = null; var playButton = null; var titleText = null; var textInputField = null; var textInputBorder = null; var textInputText = null; var inputText = ''; var passwordInputField = null; var passwordInputBorder = null; var passwordInputText = null; var passwordText = ''; var createAccountButton = null; var activeInputField = 'name'; // Track which field is active: 'name' or 'password' var keyboardContainer = null; var keyboardVisible = false; var keyboardKeys = []; var keyboardLayout = [['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'], ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'], ['Z', 'X', 'C', 'V', 'B', 'N', 'M']]; function createPauseMenu() { pauseMenuContainer = new Container(); game.addChild(pauseMenuContainer); // Create semi-transparent background pauseMenuBg = pauseMenuContainer.attachAsset('pauseMenuBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); // Pause menu title var pauseTitle = new Text2('GAME PAUSED', { size: 80, fill: 0xFFFFFF }); pauseTitle.anchor.set(0.5, 0.5); pauseTitle.x = 2048 / 2; pauseTitle.y = 2732 / 2 - 200; pauseMenuContainer.addChild(pauseTitle); // Resume button resumeButton = pauseMenuContainer.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 - 100 }); var resumeText = new Text2('RESUME GAME', { size: 50, fill: 0xFFFFFF }); resumeText.anchor.set(0.5, 0.5); resumeText.x = 2048 / 2; resumeText.y = 2732 / 2 - 100; pauseMenuContainer.addChild(resumeText); // Volume toggle button volumeButton = pauseMenuContainer.attachAsset('volumeToggle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); var volumeText = new Text2(volumeEnabled ? 'VOLUME: ON' : 'VOLUME: OFF', { size: 40, fill: 0xFFFFFF }); volumeText.anchor.set(0.5, 0.5); volumeText.x = 2048 / 2; volumeText.y = 2732 / 2; pauseMenuContainer.addChild(volumeText); // Exit button exitButton = pauseMenuContainer.attachAsset('menuButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 100 }); var exitText = new Text2('EXIT TO MENU', { size: 50, fill: 0xFFFFFF }); exitText.anchor.set(0.5, 0.5); exitText.x = 2048 / 2; exitText.y = 2732 / 2 + 100; pauseMenuContainer.addChild(exitText); // Store reference to volume text for updates pauseMenuContainer.volumeText = volumeText; } function createCustomKeyboard() { if (keyboardContainer) return; // Already exists keyboardContainer = new Container(); game.addChild(keyboardContainer); // Create keyboard background var keyboardBg = keyboardContainer.attachAsset('keyboardBg', { anchorX: 0.5, anchorY: 1.0, x: 2048 / 2, y: 2732 }); // Clear previous keys keyboardKeys = []; var startY = 2732 - 750; // Start position for keys var keySpacing = 150; var rowSpacing = 110; // Create letter keys for (var row = 0; row < keyboardLayout.length; row++) { var rowKeys = keyboardLayout[row]; var rowWidth = rowKeys.length * keySpacing; var startX = (2048 - rowWidth) / 2 + keySpacing / 2; for (var col = 0; col < rowKeys.length; col++) { var letter = rowKeys[col]; var keyX = startX + col * keySpacing; var keyY = startY + row * rowSpacing; var key = keyboardContainer.attachAsset('keyboardKey', { anchorX: 0.5, anchorY: 0.5, x: keyX, y: keyY }); var keyText = new Text2(letter, { size: 45, fill: 0xFFFFFF }); keyText.anchor.set(0.5, 0.5); keyText.x = keyX; keyText.y = keyY; keyboardContainer.addChild(keyText); keyboardKeys.push({ key: key, text: keyText, letter: letter, x: keyX, y: keyY, width: 140, height: 100 }); } } // Create space key var spaceKey = keyboardContainer.attachAsset('keyboardSpaceKey', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: startY + 3 * rowSpacing }); var spaceText = new Text2('SPACE', { size: 40, fill: 0xFFFFFF }); spaceText.anchor.set(0.5, 0.5); spaceText.x = 2048 / 2; spaceText.y = startY + 3 * rowSpacing; keyboardContainer.addChild(spaceText); keyboardKeys.push({ key: spaceKey, text: spaceText, letter: ' ', x: 2048 / 2, y: startY + 3 * rowSpacing, width: 400, height: 100 }); // Create backspace key var backspaceKey = keyboardContainer.attachAsset('keyboardBackspaceKey', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 300, y: startY + 3 * rowSpacing }); var backspaceText = new Text2('←', { size: 50, fill: 0xFFFFFF }); backspaceText.anchor.set(0.5, 0.5); backspaceText.x = 2048 / 2 - 300; backspaceText.y = startY + 3 * rowSpacing; keyboardContainer.addChild(backspaceText); keyboardKeys.push({ key: backspaceKey, text: backspaceText, letter: 'BACKSPACE', x: 2048 / 2 - 300, y: startY + 3 * rowSpacing, width: 200, height: 100 }); // Create done key var doneKey = keyboardContainer.attachAsset('keyboardDoneKey', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 + 300, y: startY + 3 * rowSpacing }); var doneText = new Text2('DONE', { size: 40, fill: 0xFFFFFF }); doneText.anchor.set(0.5, 0.5); doneText.x = 2048 / 2 + 300; doneText.y = startY + 3 * rowSpacing; keyboardContainer.addChild(doneText); keyboardKeys.push({ key: doneKey, text: doneText, letter: 'DONE', x: 2048 / 2 + 300, y: startY + 3 * rowSpacing, width: 200, height: 100 }); // Animate keyboard sliding up keyboardContainer.y = 800; tween(keyboardContainer, { y: 0 }, { duration: 300, easing: tween.easeOut }); keyboardVisible = true; } function hideCustomKeyboard() { if (!keyboardContainer) return; tween(keyboardContainer, { y: 800 }, { duration: 300, easing: tween.easeIn, onFinish: function onFinish() { if (keyboardContainer) { keyboardContainer.destroy(); keyboardContainer = null; keyboardKeys = []; keyboardVisible = false; } } }); } function handleKeyboardInput(letter) { if (letter === 'BACKSPACE') { if (activeInputField === 'name') { if (inputText.length > 0) { inputText = inputText.slice(0, -1); } } else if (activeInputField === 'password') { if (passwordText.length > 0) { passwordText = passwordText.slice(0, -1); } } } else if (letter === 'DONE') { hideCustomKeyboard(); return; } else if (letter === ' ') { if (activeInputField === 'name') { if (inputText.length < 20) { inputText += ' '; } } else if (activeInputField === 'password') { if (passwordText.length < 20) { passwordText += ' '; } } } else { if (activeInputField === 'name') { if (inputText.length < 20) { inputText += letter; } } else if (activeInputField === 'password') { if (passwordText.length < 20) { passwordText += letter; } } } // Update text display based on active field if (activeInputField === 'name' && textInputText) { if (inputText.length === 0) { textInputText.setText('Enter your name...'); textInputText.fill = 0x888888; } else { textInputText.setText(inputText); textInputText.fill = 0x000000; } } else if (activeInputField === 'password' && passwordInputText) { if (passwordText.length === 0) { passwordInputText.setText('Enter password...'); passwordInputText.fill = 0x888888; } else { // Show asterisks for password var maskedPassword = '*'.repeat(passwordText.length); passwordInputText.setText(maskedPassword); passwordInputText.fill = 0x000000; } } } function createHomeScreen() { homeContainer = new Container(); game.addChild(homeContainer); // Create title using gemMatchTitle asset titleText = homeContainer.attachAsset('gemMatchTitle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 800 }); // Create play button using playButton asset playButton = homeContainer.attachAsset('playButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1400 }); // Play button text removed per request // Create text input field under play button textInputBorder = homeContainer.attachAsset('textInputBorder', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1600 }); textInputField = homeContainer.attachAsset('textInputField', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1600 }); // Create placeholder text for input field textInputText = new Text2('Enter your name...', { size: 50, fill: 0x888888 }); textInputText.anchor.set(0.5, 0.5); textInputText.x = 2048 / 2; textInputText.y = 1600; homeContainer.addChild(textInputText); // Create password input field under name field passwordInputBorder = homeContainer.attachAsset('textInputBorder', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1750 }); passwordInputField = homeContainer.attachAsset('textInputField', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1750 }); // Create placeholder text for password field passwordInputText = new Text2('Enter password...', { size: 50, fill: 0x888888 }); passwordInputText.anchor.set(0.5, 0.5); passwordInputText.x = 2048 / 2; passwordInputText.y = 1750; homeContainer.addChild(passwordInputText); // Create account button under password field createAccountButton = homeContainer.attachAsset('createAccountButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1900 }); // Add bouncy animation to play button tween(playButton, { scaleX: 1.05, scaleY: 1.05 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(playButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { // Restart the bounce animation if (currentGameState === GAME_STATE_HOME && playButton) { tween(playButton, { scaleX: 1.05, scaleY: 1.05 }, { duration: 1000, easing: tween.easeInOut }); } } }); } }); // Add super vibrant rainbow color animation with bounce effect var rainbowColors = [0xFF0080, 0x00FF80, 0x8000FF, 0xFF8000, 0x0080FF, 0xFF4080, 0x80FF00, 0xFF0040]; var colorIndex = 0; function animateTitle() { if (currentGameState === GAME_STATE_HOME && titleText && titleText.fill !== undefined) { // Color transition tween(titleText, { fill: rainbowColors[colorIndex] }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { colorIndex = (colorIndex + 1) % rainbowColors.length; LK.setTimeout(animateTitle, 100); } }); // Add bouncy scale animation if (titleText.scaleX !== undefined && titleText.scaleY !== undefined) { tween(titleText, { scaleX: 1.1, scaleY: 1.1 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (titleText && titleText.scaleX !== undefined && titleText.scaleY !== undefined) { tween(titleText, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeIn }); } } }); } } } animateTitle(); } function startGame() { currentGameState = GAME_STATE_PLAYING; // Reset moves for new game movesRemaining = maxMoves; // Reset hint system lastMoveTime = LK.ticks; clearHintAnimations(); // Remove home screen if (homeContainer) { homeContainer.destroy(); homeContainer = null; playButton = null; titleText = null; passwordInputField = null; passwordInputBorder = null; passwordInputText = null; createAccountButton = null; } // Create game elements createGameElements(); initializeBoard(); } function createGameElements() { // Create board background var boardBackground = game.addChild(LK.getAsset('boardBg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: boardStartY + BOARD_TOTAL_SIZE / 2, scaleX: BOARD_TOTAL_SIZE / 960, scaleY: BOARD_TOTAL_SIZE / 960 })); // Initialize score display with bright colors scoreTxt = new Text2('Score: 0', { size: 90, fill: 0xFF1493 // Bright pink text }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); scoreTxt.y = 80; // Add target score display targetTxt = new Text2('Target: ' + targetScore, { size: 70, fill: 0x32CD32 // Bright green text }); targetTxt.anchor.set(0.5, 0); LK.gui.top.addChild(targetTxt); targetTxt.y = 180; // Add encouraging message display messageTxt = new Text2('Match the colorful gems!', { size: 60, fill: 0xFFD700 // Golden yellow text }); messageTxt.anchor.set(0.5, 0); LK.gui.top.addChild(messageTxt); messageTxt.y = 250; // Add moves display next to target movesTxt = new Text2('Moves: ' + movesRemaining, { size: 70, fill: 0xFF4500 // Orange red text }); movesTxt.anchor.set(0, 0); LK.gui.top.addChild(movesTxt); movesTxt.x = targetTxt.x + targetTxt.width / 2 + 50; movesTxt.y = 180; // Create pause button below the game board pauseButton = game.addChild(LK.getAsset('pauseButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: boardStartY + BOARD_TOTAL_SIZE + 150 })); // Pause button text removed per request } // UI elements - declare globally but create in game var scoreTxt = null; var targetTxt = null; var messageTxt = null; var movesTxt = null; var pauseButton = null; var targetScore = 500; // Kid-friendly target var maxMoves = 20; // Limited moves for the game var movesRemaining = maxMoves; // Hint system variables var lastMoveTime = 0; var hintTimeout = null; var hintAnimations = []; function initializeBoard() { // Create 2D array for board for (var y = 0; y < BOARD_SIZE; y++) { board[y] = []; gems[y] = []; for (var x = 0; x < BOARD_SIZE; x++) { var gemType = Math.floor(Math.random() * 8); var gem = new Gem(gemType); gem.setGridPosition(x, y); board[y][x] = gemType; gems[y][x] = gem; game.addChild(gem); } } // Remove initial matches removeInitialMatches(); } function clearHintAnimations() { // Stop all hint animations for (var i = 0; i < hintAnimations.length; i++) { var hintData = hintAnimations[i]; if (hintData.gem && hintData.gem.scaleX !== undefined) { tween.stop(hintData.gem, { scaleX: true, scaleY: true }); // Reset scale to normal var originalScale = CELL_SIZE / 256; hintData.gem.scaleX = originalScale; hintData.gem.scaleY = originalScale; } } hintAnimations = []; // Clear hint timeout if (hintTimeout) { LK.clearTimeout(hintTimeout); hintTimeout = null; } } function removeInitialMatches() { var foundMatches = true; var iterations = 0; while (foundMatches && iterations < 10) { foundMatches = false; iterations++; for (var y = 0; y < BOARD_SIZE; y++) { for (var x = 0; x < BOARD_SIZE; x++) { if (hasMatchAt(x, y)) { var newType = Math.floor(Math.random() * 8); board[y][x] = newType; gems[y][x].gemType = newType; // Update gem graphics by recreating gems[y][x].destroy(); var newGem = new Gem(newType); newGem.setGridPosition(x, y); gems[y][x] = newGem; game.addChild(newGem); foundMatches = true; } } } } } function findPossibleMoves() { var possibleMoves = []; for (var y = 0; y < BOARD_SIZE; y++) { for (var x = 0; x < BOARD_SIZE; x++) { if (board[y][x] === -1) continue; // Check adjacent positions for potential swaps var directions = [{ dx: 1, dy: 0 }, { dx: -1, dy: 0 }, { dx: 0, dy: 1 }, { dx: 0, dy: -1 }]; for (var d = 0; d < directions.length; d++) { var newX = x + directions[d].dx; var newY = y + directions[d].dy; if (newX >= 0 && newX < BOARD_SIZE && newY >= 0 && newY < BOARD_SIZE && board[newY][newX] !== -1) { // Simulate swap var originalType1 = board[y][x]; var originalType2 = board[newY][newX]; board[y][x] = originalType2; board[newY][newX] = originalType1; // Check if this creates matches if (hasMatchAt(x, y) || hasMatchAt(newX, newY)) { possibleMoves.push({ gem1: { x: x, y: y }, gem2: { x: newX, y: newY } }); } // Revert swap board[y][x] = originalType1; board[newY][newX] = originalType2; } } } } return possibleMoves; } function hasMatchAt(x, y) { if (board[y][x] === undefined || board[y][x] === -1) return false; var gemType = board[y][x]; // Check horizontal match var horizontalCount = 1; // Check left for (var i = x - 1; i >= 0 && board[y] && board[y][i] === gemType; i--) { horizontalCount++; } // Check right for (var i = x + 1; i < BOARD_SIZE && board[y] && board[y][i] === gemType; i++) { horizontalCount++; } // Check vertical match var verticalCount = 1; // Check up for (var i = y - 1; i >= 0 && board[i] && board[i][x] === gemType; i--) { verticalCount++; } // Check down for (var i = y + 1; i < BOARD_SIZE && board[i] && board[i][x] === gemType; i++) { verticalCount++; } return horizontalCount >= 3 || verticalCount >= 3; } function showHint() { // Clear any existing hint animations clearHintAnimations(); // Find possible moves var possibleMoves = findPossibleMoves(); if (possibleMoves.length === 0) return; // Pick a random possible move var randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)]; var gem1 = gems[randomMove.gem1.y][randomMove.gem1.x]; var gem2 = gems[randomMove.gem2.y][randomMove.gem2.x]; if (!gem1 || !gem2) return; // Create bouncing animation for both gems var originalScale = CELL_SIZE / 256; var hintGems = [gem1, gem2]; var hintColors = [0xFFFF00, 0x00FFFF]; // Yellow and cyan for visibility var _loop = function _loop() { gem = hintGems[i]; color = hintColors[i]; // Flash the gem with hint color LK.effects.flashObject(gem, color, 800); // Add bouncing animation function createBounceAnimation(targetGem) { function bounce() { if (hintAnimations.length === 0) return; // Stop if hints were cleared tween(targetGem, { scaleX: originalScale * 1.3, scaleY: originalScale * 1.3 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { if (hintAnimations.length === 0) return; // Stop if hints were cleared tween(targetGem, { scaleX: originalScale, scaleY: originalScale }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { // Continue bouncing LK.setTimeout(bounce, 200); } }); } }); } bounce(); } createBounceAnimation(gem); hintAnimations.push({ gem: gem }); }, gem, color; for (var i = 0; i < hintGems.length; i++) { _loop(); } } function getGemAt(gameX, gameY) { var gridX = Math.floor((gameX - boardStartX) / CELL_SIZE); var gridY = Math.floor((gameY - boardStartY) / CELL_SIZE); if (gridX >= 0 && gridX < BOARD_SIZE && gridY >= 0 && gridY < BOARD_SIZE) { return gems[gridY][gridX]; } return null; } function isAdjacent(gem1, gem2) { var dx = Math.abs(gem1.gridX - gem2.gridX); var dy = Math.abs(gem1.gridY - gem2.gridY); return dx === 1 && dy === 0 || dx === 0 && dy === 1; } function swapGems(gem1, gem2) { if (!gem1 || !gem2 || gem1.isAnimating || gem2.isAnimating) return; // Store original positions and types var originalGem1X = gem1.gridX; var originalGem1Y = gem1.gridY; var originalGem2X = gem2.gridX; var originalGem2Y = gem2.gridY; var originalGem1Type = gem1.gemType; var originalGem2Type = gem2.gemType; // Calculate original visual positions var originalGem1PosX = boardStartX + originalGem1X * CELL_SIZE + CELL_SIZE / 2; var originalGem1PosY = boardStartY + originalGem1Y * CELL_SIZE + CELL_SIZE / 2; var originalGem2PosX = boardStartX + originalGem2X * CELL_SIZE + CELL_SIZE / 2; var originalGem2PosY = boardStartY + originalGem2Y * CELL_SIZE + CELL_SIZE / 2; // Temporarily update board and gem properties to test for matches board[originalGem1Y][originalGem1X] = originalGem2Type; board[originalGem2Y][originalGem2X] = originalGem1Type; gem1.gemType = originalGem2Type; gem2.gemType = originalGem1Type; // Check if this swap creates any matches var hasMatch = hasMatchAt(originalGem1X, originalGem1Y) || hasMatchAt(originalGem2X, originalGem2Y); if (hasMatch) { // Valid swap - complete the swap gem1.setGridPosition(originalGem2X, originalGem2Y); gem2.setGridPosition(originalGem1X, originalGem1Y); // Update gems array gems[originalGem1Y][originalGem1X] = gem2; gems[originalGem2Y][originalGem2X] = gem1; // Animate to swapped positions gem1.animateToPosition(originalGem2PosX, originalGem2PosY, 200); gem2.animateToPosition(originalGem1PosX, originalGem1PosY, 200, function () { if (volumeEnabled) { var swapSound = LK.getSound('swap'); if (swapSound) swapSound.play(); } // Decrement moves on successful swap movesRemaining--; if (movesTxt) { movesTxt.setText('Moves: ' + movesRemaining); // Color code the moves display if (movesRemaining <= 3) { movesTxt.fill = 0xFF0000; // Red - critical } else if (movesRemaining <= 5) { movesTxt.fill = 0xFF8C00; // Orange - warning } else { movesTxt.fill = 0xFF4500; // Orange red - normal } } // Update hint system - player made a move lastMoveTime = LK.ticks; clearHintAnimations(); checkForMatches(); }); } else { // Invalid swap - revert changes board[originalGem1Y][originalGem1X] = originalGem1Type; board[originalGem2Y][originalGem2X] = originalGem2Type; gem1.gemType = originalGem1Type; gem2.gemType = originalGem2Type; // Animate gems briefly toward each other then back var swapDistance = 30; var gem1SwipePosX = originalGem1PosX + (originalGem2PosX - originalGem1PosX) * 0.3; var gem1SwipePosY = originalGem1PosY + (originalGem2PosY - originalGem1PosY) * 0.3; var gem2SwipePosX = originalGem2PosX + (originalGem1PosX - originalGem2PosX) * 0.3; var gem2SwipePosY = originalGem2PosY + (originalGem1PosY - originalGem2PosY) * 0.3; // First animate toward each other gem1.animateToPosition(gem1SwipePosX, gem1SwipePosY, 150, function () { // Then animate back to original positions gem1.animateToPosition(originalGem1PosX, originalGem1PosY, 150); }); gem2.animateToPosition(gem2SwipePosX, gem2SwipePosY, 150, function () { // Then animate back to original positions gem2.animateToPosition(originalGem2PosX, originalGem2PosY, 150); if (volumeEnabled) { var invalidSound = LK.getSound('invalid_move'); if (invalidSound) invalidSound.play(); } }); } } function checkForMatches() { if (isProcessingMatches) return; var matchesFound = []; var processedPositions = {}; // Track which positions we've already processed // Find all matches by checking every position on the board for (var y = 0; y < BOARD_SIZE; y++) { for (var x = 0; x < BOARD_SIZE; x++) { if (board[y][x] === -1) continue; // Skip empty cells var positionKey = x + ',' + y; if (processedPositions[positionKey]) continue; // Skip already processed positions var gemType = board[y][x]; var foundMatches = []; // Check horizontal matches starting from this position var horizontalCount = 1; var horizontalPositions = [{ x: x, y: y }]; // Check right for (var i = x + 1; i < BOARD_SIZE && board[y][i] === gemType; i++) { horizontalCount++; horizontalPositions.push({ x: i, y: y }); } // Check left - this was missing in original logic for (var i = x - 1; i >= 0 && board[y][i] === gemType; i--) { horizontalCount++; horizontalPositions.unshift({ x: i, y: y }); // Add to beginning } if (horizontalCount >= 3) { foundMatches = foundMatches.concat(horizontalPositions); } // Check vertical matches starting from this position var verticalCount = 1; var verticalPositions = [{ x: x, y: y }]; // Check down for (var i = y + 1; i < BOARD_SIZE && board[i][x] === gemType; i++) { verticalCount++; verticalPositions.push({ x: x, y: i }); } // Check up - this was missing in original logic for (var i = y - 1; i >= 0 && board[i][x] === gemType; i--) { verticalCount++; verticalPositions.unshift({ x: x, y: i }); // Add to beginning } if (verticalCount >= 3) { // Merge with horizontal matches if they overlap, otherwise add separately if (foundMatches.length > 0) { // Check if vertical matches overlap with horizontal var hasOverlap = false; for (var v = 0; v < verticalPositions.length; v++) { for (var h = 0; h < foundMatches.length; h++) { if (verticalPositions[v].x === foundMatches[h].x && verticalPositions[v].y === foundMatches[h].y) { hasOverlap = true; break; } } if (hasOverlap) break; } if (hasOverlap) { // Merge unique vertical positions for (var v = 0; v < verticalPositions.length; v++) { var vPos = verticalPositions[v]; var alreadyExists = false; for (var h = 0; h < foundMatches.length; h++) { if (foundMatches[h].x === vPos.x && foundMatches[h].y === vPos.y) { alreadyExists = true; break; } } if (!alreadyExists) { foundMatches.push(vPos); } } } else { foundMatches = foundMatches.concat(verticalPositions); } } else { foundMatches = verticalPositions; } } // Add all found matches to the main list and mark positions as processed for (var i = 0; i < foundMatches.length; i++) { var match = foundMatches[i]; var alreadyFound = false; for (var j = 0; j < matchesFound.length; j++) { if (matchesFound[j].x === match.x && matchesFound[j].y === match.y) { alreadyFound = true; break; } } if (!alreadyFound) { matchesFound.push(match); } // Mark this position as processed processedPositions[match.x + ',' + match.y] = true; } } } // Check for 2x2 square matches and add rows if found for (var y = 0; y < BOARD_SIZE - 1; y++) { for (var x = 0; x < BOARD_SIZE - 1; x++) { var squareMatches = checkSquareMatch(x, y); if (squareMatches.length === 4) { // Check if any of these squares are already in our matches var hasSquareMatch = false; for (var i = 0; i < squareMatches.length; i++) { for (var j = 0; j < matchesFound.length; j++) { if (squareMatches[i].x === matchesFound[j].x && squareMatches[i].y === matchesFound[j].y) { hasSquareMatch = true; break; } } if (hasSquareMatch) break; } if (hasSquareMatch) { // Add entire rows for square matches for (var rowY = y; rowY <= y + 1; rowY++) { for (var rowX = 0; rowX < BOARD_SIZE; rowX++) { var alreadyFound = false; for (var j = 0; j < matchesFound.length; j++) { if (matchesFound[j].x === rowX && matchesFound[j].y === rowY) { alreadyFound = true; break; } } if (!alreadyFound) { matchesFound.push({ x: rowX, y: rowY }); } } } } } } } if (matchesFound.length > 0) { isProcessingMatches = true; removeMatches(matchesFound); } else { // No matches found, check if moves are depleted if (movesRemaining <= 0) { messageTxt.setText('NO MORE MOVES!'); LK.effects.flashScreen(0xFF0000, 1000); // Red flash for game over LK.setTimeout(function () { LK.showGameOver(); }, 1500); } } } function checkSquareMatch(x, y) { if (x >= BOARD_SIZE - 1 || y >= BOARD_SIZE - 1) return []; var gemType = board[y][x]; // Check if all 4 positions in 2x2 square have same gem type if (board[y][x] === gemType && board[y][x + 1] === gemType && board[y + 1][x] === gemType && board[y + 1][x + 1] === gemType) { return [{ x: x, y: y }, { x: x + 1, y: y }, { x: x, y: y + 1 }, { x: x + 1, y: y + 1 }]; } return []; } function getRowsToRemove(matches) { var rowsToRemove = []; // Check if any 2x2 square matches exist for (var y = 0; y < BOARD_SIZE - 1; y++) { for (var x = 0; x < BOARD_SIZE - 1; x++) { var squareMatches = checkSquareMatch(x, y); if (squareMatches.length === 4) { // Check if this square is part of our current matches var isPartOfMatches = false; for (var i = 0; i < squareMatches.length; i++) { for (var j = 0; j < matches.length; j++) { if (squareMatches[i].x === matches[j].x && squareMatches[i].y === matches[j].y) { isPartOfMatches = true; break; } } if (isPartOfMatches) break; } if (isPartOfMatches) { // Add both rows to removal list if (rowsToRemove.indexOf(y) === -1) rowsToRemove.push(y); if (rowsToRemove.indexOf(y + 1) === -1) rowsToRemove.push(y + 1); } } } } return rowsToRemove; } function getMatchesAt(x, y) { if (board[y][x] === -1) return []; // Empty cell var gemType = board[y][x]; var allMatches = []; // Check horizontal matches var horizontalMatches = [{ x: x, y: y }]; // Check left for (var i = x - 1; i >= 0 && board[y][i] === gemType; i--) { horizontalMatches.push({ x: i, y: y }); } // Check right for (var i = x + 1; i < BOARD_SIZE && board[y][i] === gemType; i++) { horizontalMatches.push({ x: i, y: y }); } if (horizontalMatches.length >= 3) { for (var i = 0; i < horizontalMatches.length; i++) { allMatches.push(horizontalMatches[i]); } } // Check vertical matches var verticalMatches = [{ x: x, y: y }]; // Check up for (var i = y - 1; i >= 0 && board[i][x] === gemType; i--) { verticalMatches.push({ x: x, y: i }); } // Check down for (var i = y + 1; i < BOARD_SIZE && board[i][x] === gemType; i++) { verticalMatches.push({ x: x, y: i }); } if (verticalMatches.length >= 3) { // Add vertical matches, avoiding duplicates from horizontal for (var i = 0; i < verticalMatches.length; i++) { var vMatch = verticalMatches[i]; var alreadyExists = false; for (var j = 0; j < allMatches.length; j++) { if (allMatches[j].x === vMatch.x && allMatches[j].y === vMatch.y) { alreadyExists = true; break; } } if (!alreadyExists) { allMatches.push(vMatch); } } } return allMatches; } function removeMatches(matches) { // Check for 2x2 square matches and get rows to clear var rowsToRemove = getRowsToRemove(matches); var totalGemsToRemove = matches.length; // If we have a square match, add entire rows to removal if (rowsToRemove.length > 0) { var rowMatches = []; for (var i = 0; i < rowsToRemove.length; i++) { var rowY = rowsToRemove[i]; for (var x = 0; x < BOARD_SIZE; x++) { rowMatches.push({ x: x, y: rowY }); } } // Combine with original matches, avoiding duplicates for (var i = 0; i < rowMatches.length; i++) { var rowMatch = rowMatches[i]; var alreadyExists = false; for (var j = 0; j < matches.length; j++) { if (matches[j].x === rowMatch.x && matches[j].y === rowMatch.y) { alreadyExists = true; break; } } if (!alreadyExists) { matches.push(rowMatch); } } totalGemsToRemove = matches.length; } // Add score with celebration for big matches score += matches.length * 10; scoreTxt.setText('Score: ' + score); // Kid-friendly encouraging messages based on match size and square detection var encouragingMessages = ['Great job!', 'Awesome!', 'Fantastic!', 'Amazing!', 'Super cool!', 'You\'re doing great!', 'Keep going!', 'Brilliant!', 'Wonderful!']; if (rowsToRemove.length > 0) { messageTxt.setText('SQUARE POWER! ROW CLEARED!'); LK.effects.flashScreen(0x00FF00, 1000); // Bright green flash for square matches if (volumeEnabled) { var squareSound = LK.getSound('square_match'); if (squareSound) squareSound.play(); } } else if (matches.length >= 6) { messageTxt.setText('WOW! INCREDIBLE!'); LK.effects.flashScreen(0xFFD700, 800); // Extra long gold flash if (volumeEnabled) { var bigSound = LK.getSound('big_match'); if (bigSound) bigSound.play(); } } else if (matches.length >= 5) { messageTxt.setText('SUPER COMBO!'); LK.effects.flashScreen(0xFFD700, 500); // Gold flash for 5+ matches if (volumeEnabled) { var bigSound = LK.getSound('big_match'); if (bigSound) bigSound.play(); } } else if (matches.length >= 4) { messageTxt.setText('GREAT MATCH!'); LK.effects.flashScreen(0xFF69B4, 300); // Pink flash for 4+ matches if (volumeEnabled) { var bigSound = LK.getSound('big_match'); if (bigSound) bigSound.play(); } } else { var randomMessage = encouragingMessages[Math.floor(Math.random() * encouragingMessages.length)]; messageTxt.setText(randomMessage); if (volumeEnabled) { var matchSound = LK.getSound('match'); if (matchSound) matchSound.play(); } } // Update target display with progress var progress = Math.min(score / targetScore, 1.0); var progressPercent = Math.floor(progress * 100); targetTxt.setText('Target: ' + targetScore + ' (' + progressPercent + '%)'); // Encouraging messages based on progress if (progress >= 0.9) { targetTxt.fill = 0xFF6347; // Orange - almost there! } else if (progress >= 0.5) { targetTxt.fill = 0xFFD700; // Gold - halfway there! } else { targetTxt.fill = 0x32CD32; // Green - keep going! } // Check if target is reached if (score >= targetScore) { messageTxt.setText('🎉 YOU DID IT! 🎉'); LK.effects.flashScreen(0x00FF00, 1000); // Bright green celebration LK.showYouWin(); } // Flash and remove matched gems with rainbow colors var rainbowColors = [0xFF69B4, 0x00BFFF, 0x32CD32, 0xFFD700, 0xFF6347, 0x9370DB]; for (var i = 0; i < matches.length; i++) { var match = matches[i]; var gem = gems[match.y][match.x]; if (gem) { var flashColor = rainbowColors[i % rainbowColors.length]; LK.effects.flashObject(gem, flashColor, 300); tween(gem, { alpha: 0, scaleX: 1.5, // Make gems grow bigger before disappearing scaleY: 1.5 }, { duration: 300, easing: tween.easeOut, // Bouncy effect onFinish: function onFinish() { // Properly destroy the gem after animation if (gem && gem.destroy) { gem.destroy(); } } }); } board[match.y][match.x] = -1; // Mark as empty gems[match.y][match.x] = null; // Clear gem reference } // Wait for animation then drop gems LK.setTimeout(function () { dropGems(); }, 350); } function dropGems() { var moved = false; // Drop existing gems for (var x = 0; x < BOARD_SIZE; x++) { var writePos = BOARD_SIZE - 1; for (var y = BOARD_SIZE - 1; y >= 0; y--) { if (board[y][x] !== -1) { if (y !== writePos) { // Move gem down board[writePos][x] = board[y][x]; gems[writePos][x] = gems[y][x]; gems[writePos][x].setGridPosition(x, writePos); gems[writePos][x].animateToPosition(boardStartX + x * CELL_SIZE + CELL_SIZE / 2, boardStartY + writePos * CELL_SIZE + CELL_SIZE / 2, 200); board[y][x] = -1; gems[y][x] = null; moved = true; } writePos--; } } } // Fill empty spaces with new gems - ensure we fill from top to bottom for (var x = 0; x < BOARD_SIZE; x++) { for (var y = 0; y < BOARD_SIZE; y++) { if (board[y][x] === -1) { var newType = Math.floor(Math.random() * 8); var newGem = new Gem(newType); newGem.setGridPosition(x, y); newGem.y = boardStartY - (BOARD_SIZE - y) * CELL_SIZE + CELL_SIZE / 2; // Start above board newGem.animateToPosition(boardStartX + x * CELL_SIZE + CELL_SIZE / 2, boardStartY + y * CELL_SIZE + CELL_SIZE / 2, 400); board[y][x] = newType; gems[y][x] = newGem; game.addChild(newGem); moved = true; } } } // Double check - ensure no empty spaces remain by creating additional gems if needed for (var x = 0; x < BOARD_SIZE; x++) { for (var y = 0; y < BOARD_SIZE; y++) { if (!gems[y][x] || board[y][x] === -1) { var newType = Math.floor(Math.random() * 8); var newGem = new Gem(newType); newGem.setGridPosition(x, y); board[y][x] = newType; gems[y][x] = newGem; game.addChild(newGem); moved = true; } } } if (moved) { LK.setTimeout(function () { isProcessingMatches = false; checkForMatches(); // Check for cascade matches }, 450); } else { isProcessingMatches = false; } } game.down = function (x, y, obj) { if (currentGameState === GAME_STATE_HOME) { // Check if play button was clicked using coordinate bounds if (playButton) { var buttonLeft = playButton.x - playButton.width * playButton.scaleX / 2; var buttonRight = playButton.x + playButton.width * playButton.scaleX / 2; var buttonTop = playButton.y - playButton.height * playButton.scaleY / 2; var buttonBottom = playButton.y + playButton.height * playButton.scaleY / 2; if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) { // Add click effect to play button tween(playButton, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(playButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { if (volumeEnabled) LK.getSound('button_click').play(); startGame(); } }); } }); } // Check if text input field was clicked if (textInputField) { var fieldLeft = textInputField.x - textInputField.width / 2; var fieldRight = textInputField.x + textInputField.width / 2; var fieldTop = textInputField.y - textInputField.height / 2; var fieldBottom = textInputField.y + textInputField.height / 2; if (x >= fieldLeft && x <= fieldRight && y >= fieldTop && y <= fieldBottom) { // Set active field to name activeInputField = 'name'; // Show custom keyboard if (volumeEnabled) LK.getSound('button_click').play(); // Add visual feedback tween(textInputField, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(textInputField, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { createCustomKeyboard(); } }); } }); return; } // Check if password input field was clicked if (passwordInputField) { var fieldLeft = passwordInputField.x - passwordInputField.width / 2; var fieldRight = passwordInputField.x + passwordInputField.width / 2; var fieldTop = passwordInputField.y - passwordInputField.height / 2; var fieldBottom = passwordInputField.y + passwordInputField.height / 2; if (x >= fieldLeft && x <= fieldRight && y >= fieldTop && y <= fieldBottom) { // Set active field to password activeInputField = 'password'; // Show custom keyboard if (volumeEnabled) LK.getSound('button_click').play(); // Add visual feedback tween(passwordInputField, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(passwordInputField, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { createCustomKeyboard(); } }); } }); return; } } // Check if create account button was clicked if (createAccountButton) { var buttonLeft = createAccountButton.x - createAccountButton.width / 2; var buttonRight = createAccountButton.x + createAccountButton.width / 2; var buttonTop = createAccountButton.y - createAccountButton.height / 2; var buttonBottom = createAccountButton.y + createAccountButton.height / 2; if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) { // Add click effect to create account button tween(createAccountButton, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(createAccountButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { if (volumeEnabled) LK.getSound('button_click').play(); // Handle account creation logic if (inputText.trim().length === 0 || passwordText.trim().length === 0) { // Show error message for empty fields console.log('Please enter both name and password'); return; } // Save account data to storage var accountData = { username: inputText.trim(), password: passwordText.trim(), dateCreated: new Date().toISOString(), highScore: 0 }; // Save to storage with username as key storage['account_' + inputText.trim()] = accountData; storage.currentUser = inputText.trim(); console.log('Account created successfully for:', inputText); // Clear input fields after successful creation inputText = ''; passwordText = ''; // Update display if (textInputText) { textInputText.setText('Enter your name...'); textInputText.fill = 0x888888; } if (passwordInputText) { passwordInputText.setText('Enter password...'); passwordInputText.fill = 0x888888; } // Start the game automatically after account creation startGame(); } }); } }); } } // Handle keyboard clicks if (keyboardVisible && keyboardContainer) { for (var i = 0; i < keyboardKeys.length; i++) { var keyData = keyboardKeys[i]; var keyLeft = keyData.x - keyData.width / 2; var keyRight = keyData.x + keyData.width / 2; var keyTop = keyData.y - keyData.height / 2; var keyBottom = keyData.y + keyData.height / 2; if (x >= keyLeft && x <= keyRight && y >= keyTop && y <= keyBottom) { // Key pressed - add visual feedback if (volumeEnabled) LK.getSound('button_click').play(); var originalKey = keyData.key; tween(originalKey, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(originalKey, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); } }); handleKeyboardInput(keyData.letter); return; } } } } } return; } if (currentGameState === GAME_STATE_PAUSED) { // Handle pause menu clicks if (resumeButton) { var buttonLeft = resumeButton.x - resumeButton.width / 2; var buttonRight = resumeButton.x + resumeButton.width / 2; var buttonTop = resumeButton.y - resumeButton.height / 2; var buttonBottom = resumeButton.y + resumeButton.height / 2; if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) { // Resume game if (volumeEnabled) LK.getSound('button_click').play(); currentGameState = GAME_STATE_PLAYING; pauseMenuContainer.destroy(); pauseMenuContainer = null; return; } } if (volumeButton) { var buttonLeft = volumeButton.x - volumeButton.width / 2; var buttonRight = volumeButton.x + volumeButton.width / 2; var buttonTop = volumeButton.y - volumeButton.height / 2; var buttonBottom = volumeButton.y + volumeButton.height / 2; if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) { // Toggle volume if (volumeEnabled) LK.getSound('button_click').play(); volumeEnabled = !volumeEnabled; storage.volumeEnabled = volumeEnabled; pauseMenuContainer.volumeText.setText(volumeEnabled ? 'VOLUME: ON' : 'VOLUME: OFF'); return; } } if (exitButton) { var buttonLeft = exitButton.x - exitButton.width / 2; var buttonRight = exitButton.x + exitButton.width / 2; var buttonTop = exitButton.y - exitButton.height / 2; var buttonBottom = exitButton.y + exitButton.height / 2; if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) { // Exit to home screen if (volumeEnabled) LK.getSound('button_click').play(); currentGameState = GAME_STATE_HOME; // Clear all game elements game.removeChildren(); // Reset UI elements LK.gui.top.removeChildren(); scoreTxt = null; targetTxt = null; messageTxt = null; movesTxt = null; pauseButton = null; pauseMenuContainer = null; // Reset game variables board = []; gems = []; selectedGem = null; isProcessingMatches = false; isDragging = false; score = 0; // Reset input fields inputText = ''; passwordText = ''; // Show home screen createHomeScreen(); return; } } return; } if (currentGameState !== GAME_STATE_PLAYING || isProcessingMatches) return; // Check if pause button was clicked if (pauseButton) { var buttonLeft = pauseButton.x - pauseButton.width / 2; var buttonRight = pauseButton.x + pauseButton.width / 2; var buttonTop = pauseButton.y - pauseButton.height / 2; var buttonBottom = pauseButton.y + pauseButton.height / 2; if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) { // Add click effect to pause button tween(pauseButton, { scaleX: 0.9, scaleY: 0.9 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(pauseButton, { scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { // Show pause menu if (volumeEnabled) LK.getSound('button_click').play(); currentGameState = GAME_STATE_PAUSED; createPauseMenu(); } }); } }); return; } } selectedGem = getGemAt(x, y); if (selectedGem) { if (volumeEnabled) { var selectSound = LK.getSound('select'); if (selectSound) selectSound.play(); } isDragging = true; dragStartX = x; dragStartY = y; // Fun rainbow flash and bigger bounce effect for kids var rainbowColors = [0xFF69B4, 0x00BFFF, 0x32CD32, 0xFFD700, 0xFF6347, 0x9370DB]; var randomColor = rainbowColors[Math.floor(Math.random() * rainbowColors.length)]; LK.effects.flashObject(selectedGem, randomColor, 300); // Store reference to selected gem for use in callbacks var gemToAnimate = selectedGem; // Check if gem exists and has valid properties before starting tween, and prevent multiple scale animations if (gemToAnimate && gemToAnimate.scaleX !== undefined && gemToAnimate.scaleY !== undefined && !gemToAnimate.isScaling) { // Stop any existing scale tweens on this gem tween.stop(gemToAnimate, { scaleX: true, scaleY: true }); // Mark gem as scaling to prevent multiple simultaneous animations gemToAnimate.isScaling = true; // Store original scale values var originalScaleX = CELL_SIZE / 256; var originalScaleY = CELL_SIZE / 256; tween(gemToAnimate, { scaleX: originalScaleX * 1.4, // Bigger bounce for kids scaleY: originalScaleY * 1.4 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { // Check if gem still exists and has valid properties before animating back if (gemToAnimate && gemToAnimate.scaleX !== undefined && gemToAnimate.scaleY !== undefined) { tween(gemToAnimate, { scaleX: originalScaleX, scaleY: originalScaleY }, { duration: 150, easing: tween.easeIn, onFinish: function onFinish() { // Reset scaling flag when animation is complete if (gemToAnimate) { gemToAnimate.isScaling = false; } } }); } else { // Reset scaling flag even if gem no longer exists if (gemToAnimate) { gemToAnimate.isScaling = false; } } } }); } } }; game.move = function (x, y, obj) { if (currentGameState !== GAME_STATE_PLAYING || !isDragging || !selectedGem || isProcessingMatches) return; var deltaX = x - dragStartX; var deltaY = y - dragStartY; var threshold = CELL_SIZE / 2; if (Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold) { var targetGem = null; if (Math.abs(deltaX) > Math.abs(deltaY)) { // Horizontal swipe if (deltaX > 0 && selectedGem.gridX < BOARD_SIZE - 1) { targetGem = gems[selectedGem.gridY][selectedGem.gridX + 1]; } else if (deltaX < 0 && selectedGem.gridX > 0) { targetGem = gems[selectedGem.gridY][selectedGem.gridX - 1]; } } else { // Vertical swipe if (deltaY > 0 && selectedGem.gridY < BOARD_SIZE - 1) { targetGem = gems[selectedGem.gridY + 1][selectedGem.gridX]; } else if (deltaY < 0 && selectedGem.gridY > 0) { targetGem = gems[selectedGem.gridY - 1][selectedGem.gridX]; } } if (targetGem) { swapGems(selectedGem, targetGem); } isDragging = false; selectedGem = null; } }; game.up = function (x, y, obj) { if (currentGameState !== GAME_STATE_PLAYING) return; isDragging = false; selectedGem = null; }; // Game update loop for hint system game.update = function () { if (currentGameState === GAME_STATE_PLAYING && !isProcessingMatches) { // Check if 5 seconds (300 ticks at 60fps) have passed since last move var timeSinceLastMove = LK.ticks - lastMoveTime; if (timeSinceLastMove >= 300 && hintAnimations.length === 0) { // Show hint after 5 seconds of inactivity showHint(); } } }; // Initialize home screen createHomeScreen();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Gem = Container.expand(function (type) {
var self = Container.call(this);
self.gemType = type;
self.gridX = 0;
self.gridY = 0;
self.isAnimating = false;
self.isScaling = false;
var gemAssets = ['gem_red', 'gem_blue', 'gem_green', 'gem_yellow', 'gem_purple', 'gem_orange', 'gem_cyan', 'gem_pink'];
var gemGraphics = self.attachAsset(gemAssets[type], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: CELL_SIZE / 256,
scaleY: CELL_SIZE / 256
});
self.setGridPosition = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = boardStartX + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = boardStartY + gridY * CELL_SIZE + CELL_SIZE / 2;
};
self.animateToPosition = function (targetX, targetY, duration, onComplete) {
if (duration === undefined) duration = 300;
self.isAnimating = true;
tween(self, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isAnimating = false;
if (onComplete) onComplete();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x8B4513 // Brown background
});
/****
* Game Code
****/
// Gold yellow
// Deep pink
// Medium slate blue
var GAME_STATE_HOME = 0;
var GAME_STATE_PLAYING = 1;
var GAME_STATE_PAUSED = 2;
var currentGameState = GAME_STATE_HOME;
// Pause menu elements
var pauseMenuContainer = null;
var pauseMenuBg = null;
var volumeButton = null;
var exitButton = null;
var resumeButton = null;
var volumeEnabled = storage.volumeEnabled !== undefined ? storage.volumeEnabled : true;
var BOARD_SIZE = 8; // Smaller board for kids - easier to see patterns
var CELL_SIZE = Math.floor(Math.min(2048, 2732 - 500) / BOARD_SIZE * 0.95); // Bigger gems, leave more space for UI
var BOARD_TOTAL_SIZE = BOARD_SIZE * CELL_SIZE;
var boardStartX = (2048 - BOARD_TOTAL_SIZE) / 2;
var boardStartY = (2732 - BOARD_TOTAL_SIZE) / 2 + 100; // Offset for top UI
var board = [];
var gems = [];
var selectedGem = null;
var isProcessingMatches = false;
var isDragging = false;
var dragStartX = 0;
var dragStartY = 0;
var score = 0;
// Home screen elements
var homeContainer = null;
var playButton = null;
var titleText = null;
var textInputField = null;
var textInputBorder = null;
var textInputText = null;
var inputText = '';
var passwordInputField = null;
var passwordInputBorder = null;
var passwordInputText = null;
var passwordText = '';
var createAccountButton = null;
var activeInputField = 'name'; // Track which field is active: 'name' or 'password'
var keyboardContainer = null;
var keyboardVisible = false;
var keyboardKeys = [];
var keyboardLayout = [['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P'], ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L'], ['Z', 'X', 'C', 'V', 'B', 'N', 'M']];
function createPauseMenu() {
pauseMenuContainer = new Container();
game.addChild(pauseMenuContainer);
// Create semi-transparent background
pauseMenuBg = pauseMenuContainer.attachAsset('pauseMenuBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
// Pause menu title
var pauseTitle = new Text2('GAME PAUSED', {
size: 80,
fill: 0xFFFFFF
});
pauseTitle.anchor.set(0.5, 0.5);
pauseTitle.x = 2048 / 2;
pauseTitle.y = 2732 / 2 - 200;
pauseMenuContainer.addChild(pauseTitle);
// Resume button
resumeButton = pauseMenuContainer.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 100
});
var resumeText = new Text2('RESUME GAME', {
size: 50,
fill: 0xFFFFFF
});
resumeText.anchor.set(0.5, 0.5);
resumeText.x = 2048 / 2;
resumeText.y = 2732 / 2 - 100;
pauseMenuContainer.addChild(resumeText);
// Volume toggle button
volumeButton = pauseMenuContainer.attachAsset('volumeToggle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
var volumeText = new Text2(volumeEnabled ? 'VOLUME: ON' : 'VOLUME: OFF', {
size: 40,
fill: 0xFFFFFF
});
volumeText.anchor.set(0.5, 0.5);
volumeText.x = 2048 / 2;
volumeText.y = 2732 / 2;
pauseMenuContainer.addChild(volumeText);
// Exit button
exitButton = pauseMenuContainer.attachAsset('menuButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 100
});
var exitText = new Text2('EXIT TO MENU', {
size: 50,
fill: 0xFFFFFF
});
exitText.anchor.set(0.5, 0.5);
exitText.x = 2048 / 2;
exitText.y = 2732 / 2 + 100;
pauseMenuContainer.addChild(exitText);
// Store reference to volume text for updates
pauseMenuContainer.volumeText = volumeText;
}
function createCustomKeyboard() {
if (keyboardContainer) return; // Already exists
keyboardContainer = new Container();
game.addChild(keyboardContainer);
// Create keyboard background
var keyboardBg = keyboardContainer.attachAsset('keyboardBg', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048 / 2,
y: 2732
});
// Clear previous keys
keyboardKeys = [];
var startY = 2732 - 750; // Start position for keys
var keySpacing = 150;
var rowSpacing = 110;
// Create letter keys
for (var row = 0; row < keyboardLayout.length; row++) {
var rowKeys = keyboardLayout[row];
var rowWidth = rowKeys.length * keySpacing;
var startX = (2048 - rowWidth) / 2 + keySpacing / 2;
for (var col = 0; col < rowKeys.length; col++) {
var letter = rowKeys[col];
var keyX = startX + col * keySpacing;
var keyY = startY + row * rowSpacing;
var key = keyboardContainer.attachAsset('keyboardKey', {
anchorX: 0.5,
anchorY: 0.5,
x: keyX,
y: keyY
});
var keyText = new Text2(letter, {
size: 45,
fill: 0xFFFFFF
});
keyText.anchor.set(0.5, 0.5);
keyText.x = keyX;
keyText.y = keyY;
keyboardContainer.addChild(keyText);
keyboardKeys.push({
key: key,
text: keyText,
letter: letter,
x: keyX,
y: keyY,
width: 140,
height: 100
});
}
}
// Create space key
var spaceKey = keyboardContainer.attachAsset('keyboardSpaceKey', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: startY + 3 * rowSpacing
});
var spaceText = new Text2('SPACE', {
size: 40,
fill: 0xFFFFFF
});
spaceText.anchor.set(0.5, 0.5);
spaceText.x = 2048 / 2;
spaceText.y = startY + 3 * rowSpacing;
keyboardContainer.addChild(spaceText);
keyboardKeys.push({
key: spaceKey,
text: spaceText,
letter: ' ',
x: 2048 / 2,
y: startY + 3 * rowSpacing,
width: 400,
height: 100
});
// Create backspace key
var backspaceKey = keyboardContainer.attachAsset('keyboardBackspaceKey', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 300,
y: startY + 3 * rowSpacing
});
var backspaceText = new Text2('←', {
size: 50,
fill: 0xFFFFFF
});
backspaceText.anchor.set(0.5, 0.5);
backspaceText.x = 2048 / 2 - 300;
backspaceText.y = startY + 3 * rowSpacing;
keyboardContainer.addChild(backspaceText);
keyboardKeys.push({
key: backspaceKey,
text: backspaceText,
letter: 'BACKSPACE',
x: 2048 / 2 - 300,
y: startY + 3 * rowSpacing,
width: 200,
height: 100
});
// Create done key
var doneKey = keyboardContainer.attachAsset('keyboardDoneKey', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 300,
y: startY + 3 * rowSpacing
});
var doneText = new Text2('DONE', {
size: 40,
fill: 0xFFFFFF
});
doneText.anchor.set(0.5, 0.5);
doneText.x = 2048 / 2 + 300;
doneText.y = startY + 3 * rowSpacing;
keyboardContainer.addChild(doneText);
keyboardKeys.push({
key: doneKey,
text: doneText,
letter: 'DONE',
x: 2048 / 2 + 300,
y: startY + 3 * rowSpacing,
width: 200,
height: 100
});
// Animate keyboard sliding up
keyboardContainer.y = 800;
tween(keyboardContainer, {
y: 0
}, {
duration: 300,
easing: tween.easeOut
});
keyboardVisible = true;
}
function hideCustomKeyboard() {
if (!keyboardContainer) return;
tween(keyboardContainer, {
y: 800
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (keyboardContainer) {
keyboardContainer.destroy();
keyboardContainer = null;
keyboardKeys = [];
keyboardVisible = false;
}
}
});
}
function handleKeyboardInput(letter) {
if (letter === 'BACKSPACE') {
if (activeInputField === 'name') {
if (inputText.length > 0) {
inputText = inputText.slice(0, -1);
}
} else if (activeInputField === 'password') {
if (passwordText.length > 0) {
passwordText = passwordText.slice(0, -1);
}
}
} else if (letter === 'DONE') {
hideCustomKeyboard();
return;
} else if (letter === ' ') {
if (activeInputField === 'name') {
if (inputText.length < 20) {
inputText += ' ';
}
} else if (activeInputField === 'password') {
if (passwordText.length < 20) {
passwordText += ' ';
}
}
} else {
if (activeInputField === 'name') {
if (inputText.length < 20) {
inputText += letter;
}
} else if (activeInputField === 'password') {
if (passwordText.length < 20) {
passwordText += letter;
}
}
}
// Update text display based on active field
if (activeInputField === 'name' && textInputText) {
if (inputText.length === 0) {
textInputText.setText('Enter your name...');
textInputText.fill = 0x888888;
} else {
textInputText.setText(inputText);
textInputText.fill = 0x000000;
}
} else if (activeInputField === 'password' && passwordInputText) {
if (passwordText.length === 0) {
passwordInputText.setText('Enter password...');
passwordInputText.fill = 0x888888;
} else {
// Show asterisks for password
var maskedPassword = '*'.repeat(passwordText.length);
passwordInputText.setText(maskedPassword);
passwordInputText.fill = 0x000000;
}
}
}
function createHomeScreen() {
homeContainer = new Container();
game.addChild(homeContainer);
// Create title using gemMatchTitle asset
titleText = homeContainer.attachAsset('gemMatchTitle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 800
});
// Create play button using playButton asset
playButton = homeContainer.attachAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1400
});
// Play button text removed per request
// Create text input field under play button
textInputBorder = homeContainer.attachAsset('textInputBorder', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1600
});
textInputField = homeContainer.attachAsset('textInputField', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1600
});
// Create placeholder text for input field
textInputText = new Text2('Enter your name...', {
size: 50,
fill: 0x888888
});
textInputText.anchor.set(0.5, 0.5);
textInputText.x = 2048 / 2;
textInputText.y = 1600;
homeContainer.addChild(textInputText);
// Create password input field under name field
passwordInputBorder = homeContainer.attachAsset('textInputBorder', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1750
});
passwordInputField = homeContainer.attachAsset('textInputField', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1750
});
// Create placeholder text for password field
passwordInputText = new Text2('Enter password...', {
size: 50,
fill: 0x888888
});
passwordInputText.anchor.set(0.5, 0.5);
passwordInputText.x = 2048 / 2;
passwordInputText.y = 1750;
homeContainer.addChild(passwordInputText);
// Create account button under password field
createAccountButton = homeContainer.attachAsset('createAccountButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1900
});
// Add bouncy animation to play button
tween(playButton, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Restart the bounce animation
if (currentGameState === GAME_STATE_HOME && playButton) {
tween(playButton, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
});
}
});
// Add super vibrant rainbow color animation with bounce effect
var rainbowColors = [0xFF0080, 0x00FF80, 0x8000FF, 0xFF8000, 0x0080FF, 0xFF4080, 0x80FF00, 0xFF0040];
var colorIndex = 0;
function animateTitle() {
if (currentGameState === GAME_STATE_HOME && titleText && titleText.fill !== undefined) {
// Color transition
tween(titleText, {
fill: rainbowColors[colorIndex]
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
colorIndex = (colorIndex + 1) % rainbowColors.length;
LK.setTimeout(animateTitle, 100);
}
});
// Add bouncy scale animation
if (titleText.scaleX !== undefined && titleText.scaleY !== undefined) {
tween(titleText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (titleText && titleText.scaleX !== undefined && titleText.scaleY !== undefined) {
tween(titleText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
}
}
});
}
}
}
animateTitle();
}
function startGame() {
currentGameState = GAME_STATE_PLAYING;
// Reset moves for new game
movesRemaining = maxMoves;
// Reset hint system
lastMoveTime = LK.ticks;
clearHintAnimations();
// Remove home screen
if (homeContainer) {
homeContainer.destroy();
homeContainer = null;
playButton = null;
titleText = null;
passwordInputField = null;
passwordInputBorder = null;
passwordInputText = null;
createAccountButton = null;
}
// Create game elements
createGameElements();
initializeBoard();
}
function createGameElements() {
// Create board background
var boardBackground = game.addChild(LK.getAsset('boardBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: boardStartY + BOARD_TOTAL_SIZE / 2,
scaleX: BOARD_TOTAL_SIZE / 960,
scaleY: BOARD_TOTAL_SIZE / 960
}));
// Initialize score display with bright colors
scoreTxt = new Text2('Score: 0', {
size: 90,
fill: 0xFF1493 // Bright pink text
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 80;
// Add target score display
targetTxt = new Text2('Target: ' + targetScore, {
size: 70,
fill: 0x32CD32 // Bright green text
});
targetTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(targetTxt);
targetTxt.y = 180;
// Add encouraging message display
messageTxt = new Text2('Match the colorful gems!', {
size: 60,
fill: 0xFFD700 // Golden yellow text
});
messageTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(messageTxt);
messageTxt.y = 250;
// Add moves display next to target
movesTxt = new Text2('Moves: ' + movesRemaining, {
size: 70,
fill: 0xFF4500 // Orange red text
});
movesTxt.anchor.set(0, 0);
LK.gui.top.addChild(movesTxt);
movesTxt.x = targetTxt.x + targetTxt.width / 2 + 50;
movesTxt.y = 180;
// Create pause button below the game board
pauseButton = game.addChild(LK.getAsset('pauseButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: boardStartY + BOARD_TOTAL_SIZE + 150
}));
// Pause button text removed per request
}
// UI elements - declare globally but create in game
var scoreTxt = null;
var targetTxt = null;
var messageTxt = null;
var movesTxt = null;
var pauseButton = null;
var targetScore = 500; // Kid-friendly target
var maxMoves = 20; // Limited moves for the game
var movesRemaining = maxMoves;
// Hint system variables
var lastMoveTime = 0;
var hintTimeout = null;
var hintAnimations = [];
function initializeBoard() {
// Create 2D array for board
for (var y = 0; y < BOARD_SIZE; y++) {
board[y] = [];
gems[y] = [];
for (var x = 0; x < BOARD_SIZE; x++) {
var gemType = Math.floor(Math.random() * 8);
var gem = new Gem(gemType);
gem.setGridPosition(x, y);
board[y][x] = gemType;
gems[y][x] = gem;
game.addChild(gem);
}
}
// Remove initial matches
removeInitialMatches();
}
function clearHintAnimations() {
// Stop all hint animations
for (var i = 0; i < hintAnimations.length; i++) {
var hintData = hintAnimations[i];
if (hintData.gem && hintData.gem.scaleX !== undefined) {
tween.stop(hintData.gem, {
scaleX: true,
scaleY: true
});
// Reset scale to normal
var originalScale = CELL_SIZE / 256;
hintData.gem.scaleX = originalScale;
hintData.gem.scaleY = originalScale;
}
}
hintAnimations = [];
// Clear hint timeout
if (hintTimeout) {
LK.clearTimeout(hintTimeout);
hintTimeout = null;
}
}
function removeInitialMatches() {
var foundMatches = true;
var iterations = 0;
while (foundMatches && iterations < 10) {
foundMatches = false;
iterations++;
for (var y = 0; y < BOARD_SIZE; y++) {
for (var x = 0; x < BOARD_SIZE; x++) {
if (hasMatchAt(x, y)) {
var newType = Math.floor(Math.random() * 8);
board[y][x] = newType;
gems[y][x].gemType = newType;
// Update gem graphics by recreating
gems[y][x].destroy();
var newGem = new Gem(newType);
newGem.setGridPosition(x, y);
gems[y][x] = newGem;
game.addChild(newGem);
foundMatches = true;
}
}
}
}
}
function findPossibleMoves() {
var possibleMoves = [];
for (var y = 0; y < BOARD_SIZE; y++) {
for (var x = 0; x < BOARD_SIZE; x++) {
if (board[y][x] === -1) continue;
// Check adjacent positions for potential swaps
var directions = [{
dx: 1,
dy: 0
}, {
dx: -1,
dy: 0
}, {
dx: 0,
dy: 1
}, {
dx: 0,
dy: -1
}];
for (var d = 0; d < directions.length; d++) {
var newX = x + directions[d].dx;
var newY = y + directions[d].dy;
if (newX >= 0 && newX < BOARD_SIZE && newY >= 0 && newY < BOARD_SIZE && board[newY][newX] !== -1) {
// Simulate swap
var originalType1 = board[y][x];
var originalType2 = board[newY][newX];
board[y][x] = originalType2;
board[newY][newX] = originalType1;
// Check if this creates matches
if (hasMatchAt(x, y) || hasMatchAt(newX, newY)) {
possibleMoves.push({
gem1: {
x: x,
y: y
},
gem2: {
x: newX,
y: newY
}
});
}
// Revert swap
board[y][x] = originalType1;
board[newY][newX] = originalType2;
}
}
}
}
return possibleMoves;
}
function hasMatchAt(x, y) {
if (board[y][x] === undefined || board[y][x] === -1) return false;
var gemType = board[y][x];
// Check horizontal match
var horizontalCount = 1;
// Check left
for (var i = x - 1; i >= 0 && board[y] && board[y][i] === gemType; i--) {
horizontalCount++;
}
// Check right
for (var i = x + 1; i < BOARD_SIZE && board[y] && board[y][i] === gemType; i++) {
horizontalCount++;
}
// Check vertical match
var verticalCount = 1;
// Check up
for (var i = y - 1; i >= 0 && board[i] && board[i][x] === gemType; i--) {
verticalCount++;
}
// Check down
for (var i = y + 1; i < BOARD_SIZE && board[i] && board[i][x] === gemType; i++) {
verticalCount++;
}
return horizontalCount >= 3 || verticalCount >= 3;
}
function showHint() {
// Clear any existing hint animations
clearHintAnimations();
// Find possible moves
var possibleMoves = findPossibleMoves();
if (possibleMoves.length === 0) return;
// Pick a random possible move
var randomMove = possibleMoves[Math.floor(Math.random() * possibleMoves.length)];
var gem1 = gems[randomMove.gem1.y][randomMove.gem1.x];
var gem2 = gems[randomMove.gem2.y][randomMove.gem2.x];
if (!gem1 || !gem2) return;
// Create bouncing animation for both gems
var originalScale = CELL_SIZE / 256;
var hintGems = [gem1, gem2];
var hintColors = [0xFFFF00, 0x00FFFF]; // Yellow and cyan for visibility
var _loop = function _loop() {
gem = hintGems[i];
color = hintColors[i]; // Flash the gem with hint color
LK.effects.flashObject(gem, color, 800);
// Add bouncing animation
function createBounceAnimation(targetGem) {
function bounce() {
if (hintAnimations.length === 0) return; // Stop if hints were cleared
tween(targetGem, {
scaleX: originalScale * 1.3,
scaleY: originalScale * 1.3
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (hintAnimations.length === 0) return; // Stop if hints were cleared
tween(targetGem, {
scaleX: originalScale,
scaleY: originalScale
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
// Continue bouncing
LK.setTimeout(bounce, 200);
}
});
}
});
}
bounce();
}
createBounceAnimation(gem);
hintAnimations.push({
gem: gem
});
},
gem,
color;
for (var i = 0; i < hintGems.length; i++) {
_loop();
}
}
function getGemAt(gameX, gameY) {
var gridX = Math.floor((gameX - boardStartX) / CELL_SIZE);
var gridY = Math.floor((gameY - boardStartY) / CELL_SIZE);
if (gridX >= 0 && gridX < BOARD_SIZE && gridY >= 0 && gridY < BOARD_SIZE) {
return gems[gridY][gridX];
}
return null;
}
function isAdjacent(gem1, gem2) {
var dx = Math.abs(gem1.gridX - gem2.gridX);
var dy = Math.abs(gem1.gridY - gem2.gridY);
return dx === 1 && dy === 0 || dx === 0 && dy === 1;
}
function swapGems(gem1, gem2) {
if (!gem1 || !gem2 || gem1.isAnimating || gem2.isAnimating) return;
// Store original positions and types
var originalGem1X = gem1.gridX;
var originalGem1Y = gem1.gridY;
var originalGem2X = gem2.gridX;
var originalGem2Y = gem2.gridY;
var originalGem1Type = gem1.gemType;
var originalGem2Type = gem2.gemType;
// Calculate original visual positions
var originalGem1PosX = boardStartX + originalGem1X * CELL_SIZE + CELL_SIZE / 2;
var originalGem1PosY = boardStartY + originalGem1Y * CELL_SIZE + CELL_SIZE / 2;
var originalGem2PosX = boardStartX + originalGem2X * CELL_SIZE + CELL_SIZE / 2;
var originalGem2PosY = boardStartY + originalGem2Y * CELL_SIZE + CELL_SIZE / 2;
// Temporarily update board and gem properties to test for matches
board[originalGem1Y][originalGem1X] = originalGem2Type;
board[originalGem2Y][originalGem2X] = originalGem1Type;
gem1.gemType = originalGem2Type;
gem2.gemType = originalGem1Type;
// Check if this swap creates any matches
var hasMatch = hasMatchAt(originalGem1X, originalGem1Y) || hasMatchAt(originalGem2X, originalGem2Y);
if (hasMatch) {
// Valid swap - complete the swap
gem1.setGridPosition(originalGem2X, originalGem2Y);
gem2.setGridPosition(originalGem1X, originalGem1Y);
// Update gems array
gems[originalGem1Y][originalGem1X] = gem2;
gems[originalGem2Y][originalGem2X] = gem1;
// Animate to swapped positions
gem1.animateToPosition(originalGem2PosX, originalGem2PosY, 200);
gem2.animateToPosition(originalGem1PosX, originalGem1PosY, 200, function () {
if (volumeEnabled) {
var swapSound = LK.getSound('swap');
if (swapSound) swapSound.play();
}
// Decrement moves on successful swap
movesRemaining--;
if (movesTxt) {
movesTxt.setText('Moves: ' + movesRemaining);
// Color code the moves display
if (movesRemaining <= 3) {
movesTxt.fill = 0xFF0000; // Red - critical
} else if (movesRemaining <= 5) {
movesTxt.fill = 0xFF8C00; // Orange - warning
} else {
movesTxt.fill = 0xFF4500; // Orange red - normal
}
}
// Update hint system - player made a move
lastMoveTime = LK.ticks;
clearHintAnimations();
checkForMatches();
});
} else {
// Invalid swap - revert changes
board[originalGem1Y][originalGem1X] = originalGem1Type;
board[originalGem2Y][originalGem2X] = originalGem2Type;
gem1.gemType = originalGem1Type;
gem2.gemType = originalGem2Type;
// Animate gems briefly toward each other then back
var swapDistance = 30;
var gem1SwipePosX = originalGem1PosX + (originalGem2PosX - originalGem1PosX) * 0.3;
var gem1SwipePosY = originalGem1PosY + (originalGem2PosY - originalGem1PosY) * 0.3;
var gem2SwipePosX = originalGem2PosX + (originalGem1PosX - originalGem2PosX) * 0.3;
var gem2SwipePosY = originalGem2PosY + (originalGem1PosY - originalGem2PosY) * 0.3;
// First animate toward each other
gem1.animateToPosition(gem1SwipePosX, gem1SwipePosY, 150, function () {
// Then animate back to original positions
gem1.animateToPosition(originalGem1PosX, originalGem1PosY, 150);
});
gem2.animateToPosition(gem2SwipePosX, gem2SwipePosY, 150, function () {
// Then animate back to original positions
gem2.animateToPosition(originalGem2PosX, originalGem2PosY, 150);
if (volumeEnabled) {
var invalidSound = LK.getSound('invalid_move');
if (invalidSound) invalidSound.play();
}
});
}
}
function checkForMatches() {
if (isProcessingMatches) return;
var matchesFound = [];
var processedPositions = {}; // Track which positions we've already processed
// Find all matches by checking every position on the board
for (var y = 0; y < BOARD_SIZE; y++) {
for (var x = 0; x < BOARD_SIZE; x++) {
if (board[y][x] === -1) continue; // Skip empty cells
var positionKey = x + ',' + y;
if (processedPositions[positionKey]) continue; // Skip already processed positions
var gemType = board[y][x];
var foundMatches = [];
// Check horizontal matches starting from this position
var horizontalCount = 1;
var horizontalPositions = [{
x: x,
y: y
}];
// Check right
for (var i = x + 1; i < BOARD_SIZE && board[y][i] === gemType; i++) {
horizontalCount++;
horizontalPositions.push({
x: i,
y: y
});
}
// Check left - this was missing in original logic
for (var i = x - 1; i >= 0 && board[y][i] === gemType; i--) {
horizontalCount++;
horizontalPositions.unshift({
x: i,
y: y
}); // Add to beginning
}
if (horizontalCount >= 3) {
foundMatches = foundMatches.concat(horizontalPositions);
}
// Check vertical matches starting from this position
var verticalCount = 1;
var verticalPositions = [{
x: x,
y: y
}];
// Check down
for (var i = y + 1; i < BOARD_SIZE && board[i][x] === gemType; i++) {
verticalCount++;
verticalPositions.push({
x: x,
y: i
});
}
// Check up - this was missing in original logic
for (var i = y - 1; i >= 0 && board[i][x] === gemType; i--) {
verticalCount++;
verticalPositions.unshift({
x: x,
y: i
}); // Add to beginning
}
if (verticalCount >= 3) {
// Merge with horizontal matches if they overlap, otherwise add separately
if (foundMatches.length > 0) {
// Check if vertical matches overlap with horizontal
var hasOverlap = false;
for (var v = 0; v < verticalPositions.length; v++) {
for (var h = 0; h < foundMatches.length; h++) {
if (verticalPositions[v].x === foundMatches[h].x && verticalPositions[v].y === foundMatches[h].y) {
hasOverlap = true;
break;
}
}
if (hasOverlap) break;
}
if (hasOverlap) {
// Merge unique vertical positions
for (var v = 0; v < verticalPositions.length; v++) {
var vPos = verticalPositions[v];
var alreadyExists = false;
for (var h = 0; h < foundMatches.length; h++) {
if (foundMatches[h].x === vPos.x && foundMatches[h].y === vPos.y) {
alreadyExists = true;
break;
}
}
if (!alreadyExists) {
foundMatches.push(vPos);
}
}
} else {
foundMatches = foundMatches.concat(verticalPositions);
}
} else {
foundMatches = verticalPositions;
}
}
// Add all found matches to the main list and mark positions as processed
for (var i = 0; i < foundMatches.length; i++) {
var match = foundMatches[i];
var alreadyFound = false;
for (var j = 0; j < matchesFound.length; j++) {
if (matchesFound[j].x === match.x && matchesFound[j].y === match.y) {
alreadyFound = true;
break;
}
}
if (!alreadyFound) {
matchesFound.push(match);
}
// Mark this position as processed
processedPositions[match.x + ',' + match.y] = true;
}
}
}
// Check for 2x2 square matches and add rows if found
for (var y = 0; y < BOARD_SIZE - 1; y++) {
for (var x = 0; x < BOARD_SIZE - 1; x++) {
var squareMatches = checkSquareMatch(x, y);
if (squareMatches.length === 4) {
// Check if any of these squares are already in our matches
var hasSquareMatch = false;
for (var i = 0; i < squareMatches.length; i++) {
for (var j = 0; j < matchesFound.length; j++) {
if (squareMatches[i].x === matchesFound[j].x && squareMatches[i].y === matchesFound[j].y) {
hasSquareMatch = true;
break;
}
}
if (hasSquareMatch) break;
}
if (hasSquareMatch) {
// Add entire rows for square matches
for (var rowY = y; rowY <= y + 1; rowY++) {
for (var rowX = 0; rowX < BOARD_SIZE; rowX++) {
var alreadyFound = false;
for (var j = 0; j < matchesFound.length; j++) {
if (matchesFound[j].x === rowX && matchesFound[j].y === rowY) {
alreadyFound = true;
break;
}
}
if (!alreadyFound) {
matchesFound.push({
x: rowX,
y: rowY
});
}
}
}
}
}
}
}
if (matchesFound.length > 0) {
isProcessingMatches = true;
removeMatches(matchesFound);
} else {
// No matches found, check if moves are depleted
if (movesRemaining <= 0) {
messageTxt.setText('NO MORE MOVES!');
LK.effects.flashScreen(0xFF0000, 1000); // Red flash for game over
LK.setTimeout(function () {
LK.showGameOver();
}, 1500);
}
}
}
function checkSquareMatch(x, y) {
if (x >= BOARD_SIZE - 1 || y >= BOARD_SIZE - 1) return [];
var gemType = board[y][x];
// Check if all 4 positions in 2x2 square have same gem type
if (board[y][x] === gemType && board[y][x + 1] === gemType && board[y + 1][x] === gemType && board[y + 1][x + 1] === gemType) {
return [{
x: x,
y: y
}, {
x: x + 1,
y: y
}, {
x: x,
y: y + 1
}, {
x: x + 1,
y: y + 1
}];
}
return [];
}
function getRowsToRemove(matches) {
var rowsToRemove = [];
// Check if any 2x2 square matches exist
for (var y = 0; y < BOARD_SIZE - 1; y++) {
for (var x = 0; x < BOARD_SIZE - 1; x++) {
var squareMatches = checkSquareMatch(x, y);
if (squareMatches.length === 4) {
// Check if this square is part of our current matches
var isPartOfMatches = false;
for (var i = 0; i < squareMatches.length; i++) {
for (var j = 0; j < matches.length; j++) {
if (squareMatches[i].x === matches[j].x && squareMatches[i].y === matches[j].y) {
isPartOfMatches = true;
break;
}
}
if (isPartOfMatches) break;
}
if (isPartOfMatches) {
// Add both rows to removal list
if (rowsToRemove.indexOf(y) === -1) rowsToRemove.push(y);
if (rowsToRemove.indexOf(y + 1) === -1) rowsToRemove.push(y + 1);
}
}
}
}
return rowsToRemove;
}
function getMatchesAt(x, y) {
if (board[y][x] === -1) return []; // Empty cell
var gemType = board[y][x];
var allMatches = [];
// Check horizontal matches
var horizontalMatches = [{
x: x,
y: y
}];
// Check left
for (var i = x - 1; i >= 0 && board[y][i] === gemType; i--) {
horizontalMatches.push({
x: i,
y: y
});
}
// Check right
for (var i = x + 1; i < BOARD_SIZE && board[y][i] === gemType; i++) {
horizontalMatches.push({
x: i,
y: y
});
}
if (horizontalMatches.length >= 3) {
for (var i = 0; i < horizontalMatches.length; i++) {
allMatches.push(horizontalMatches[i]);
}
}
// Check vertical matches
var verticalMatches = [{
x: x,
y: y
}];
// Check up
for (var i = y - 1; i >= 0 && board[i][x] === gemType; i--) {
verticalMatches.push({
x: x,
y: i
});
}
// Check down
for (var i = y + 1; i < BOARD_SIZE && board[i][x] === gemType; i++) {
verticalMatches.push({
x: x,
y: i
});
}
if (verticalMatches.length >= 3) {
// Add vertical matches, avoiding duplicates from horizontal
for (var i = 0; i < verticalMatches.length; i++) {
var vMatch = verticalMatches[i];
var alreadyExists = false;
for (var j = 0; j < allMatches.length; j++) {
if (allMatches[j].x === vMatch.x && allMatches[j].y === vMatch.y) {
alreadyExists = true;
break;
}
}
if (!alreadyExists) {
allMatches.push(vMatch);
}
}
}
return allMatches;
}
function removeMatches(matches) {
// Check for 2x2 square matches and get rows to clear
var rowsToRemove = getRowsToRemove(matches);
var totalGemsToRemove = matches.length;
// If we have a square match, add entire rows to removal
if (rowsToRemove.length > 0) {
var rowMatches = [];
for (var i = 0; i < rowsToRemove.length; i++) {
var rowY = rowsToRemove[i];
for (var x = 0; x < BOARD_SIZE; x++) {
rowMatches.push({
x: x,
y: rowY
});
}
}
// Combine with original matches, avoiding duplicates
for (var i = 0; i < rowMatches.length; i++) {
var rowMatch = rowMatches[i];
var alreadyExists = false;
for (var j = 0; j < matches.length; j++) {
if (matches[j].x === rowMatch.x && matches[j].y === rowMatch.y) {
alreadyExists = true;
break;
}
}
if (!alreadyExists) {
matches.push(rowMatch);
}
}
totalGemsToRemove = matches.length;
}
// Add score with celebration for big matches
score += matches.length * 10;
scoreTxt.setText('Score: ' + score);
// Kid-friendly encouraging messages based on match size and square detection
var encouragingMessages = ['Great job!', 'Awesome!', 'Fantastic!', 'Amazing!', 'Super cool!', 'You\'re doing great!', 'Keep going!', 'Brilliant!', 'Wonderful!'];
if (rowsToRemove.length > 0) {
messageTxt.setText('SQUARE POWER! ROW CLEARED!');
LK.effects.flashScreen(0x00FF00, 1000); // Bright green flash for square matches
if (volumeEnabled) {
var squareSound = LK.getSound('square_match');
if (squareSound) squareSound.play();
}
} else if (matches.length >= 6) {
messageTxt.setText('WOW! INCREDIBLE!');
LK.effects.flashScreen(0xFFD700, 800); // Extra long gold flash
if (volumeEnabled) {
var bigSound = LK.getSound('big_match');
if (bigSound) bigSound.play();
}
} else if (matches.length >= 5) {
messageTxt.setText('SUPER COMBO!');
LK.effects.flashScreen(0xFFD700, 500); // Gold flash for 5+ matches
if (volumeEnabled) {
var bigSound = LK.getSound('big_match');
if (bigSound) bigSound.play();
}
} else if (matches.length >= 4) {
messageTxt.setText('GREAT MATCH!');
LK.effects.flashScreen(0xFF69B4, 300); // Pink flash for 4+ matches
if (volumeEnabled) {
var bigSound = LK.getSound('big_match');
if (bigSound) bigSound.play();
}
} else {
var randomMessage = encouragingMessages[Math.floor(Math.random() * encouragingMessages.length)];
messageTxt.setText(randomMessage);
if (volumeEnabled) {
var matchSound = LK.getSound('match');
if (matchSound) matchSound.play();
}
}
// Update target display with progress
var progress = Math.min(score / targetScore, 1.0);
var progressPercent = Math.floor(progress * 100);
targetTxt.setText('Target: ' + targetScore + ' (' + progressPercent + '%)');
// Encouraging messages based on progress
if (progress >= 0.9) {
targetTxt.fill = 0xFF6347; // Orange - almost there!
} else if (progress >= 0.5) {
targetTxt.fill = 0xFFD700; // Gold - halfway there!
} else {
targetTxt.fill = 0x32CD32; // Green - keep going!
}
// Check if target is reached
if (score >= targetScore) {
messageTxt.setText('🎉 YOU DID IT! 🎉');
LK.effects.flashScreen(0x00FF00, 1000); // Bright green celebration
LK.showYouWin();
}
// Flash and remove matched gems with rainbow colors
var rainbowColors = [0xFF69B4, 0x00BFFF, 0x32CD32, 0xFFD700, 0xFF6347, 0x9370DB];
for (var i = 0; i < matches.length; i++) {
var match = matches[i];
var gem = gems[match.y][match.x];
if (gem) {
var flashColor = rainbowColors[i % rainbowColors.length];
LK.effects.flashObject(gem, flashColor, 300);
tween(gem, {
alpha: 0,
scaleX: 1.5,
// Make gems grow bigger before disappearing
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut,
// Bouncy effect
onFinish: function onFinish() {
// Properly destroy the gem after animation
if (gem && gem.destroy) {
gem.destroy();
}
}
});
}
board[match.y][match.x] = -1; // Mark as empty
gems[match.y][match.x] = null; // Clear gem reference
}
// Wait for animation then drop gems
LK.setTimeout(function () {
dropGems();
}, 350);
}
function dropGems() {
var moved = false;
// Drop existing gems
for (var x = 0; x < BOARD_SIZE; x++) {
var writePos = BOARD_SIZE - 1;
for (var y = BOARD_SIZE - 1; y >= 0; y--) {
if (board[y][x] !== -1) {
if (y !== writePos) {
// Move gem down
board[writePos][x] = board[y][x];
gems[writePos][x] = gems[y][x];
gems[writePos][x].setGridPosition(x, writePos);
gems[writePos][x].animateToPosition(boardStartX + x * CELL_SIZE + CELL_SIZE / 2, boardStartY + writePos * CELL_SIZE + CELL_SIZE / 2, 200);
board[y][x] = -1;
gems[y][x] = null;
moved = true;
}
writePos--;
}
}
}
// Fill empty spaces with new gems - ensure we fill from top to bottom
for (var x = 0; x < BOARD_SIZE; x++) {
for (var y = 0; y < BOARD_SIZE; y++) {
if (board[y][x] === -1) {
var newType = Math.floor(Math.random() * 8);
var newGem = new Gem(newType);
newGem.setGridPosition(x, y);
newGem.y = boardStartY - (BOARD_SIZE - y) * CELL_SIZE + CELL_SIZE / 2; // Start above board
newGem.animateToPosition(boardStartX + x * CELL_SIZE + CELL_SIZE / 2, boardStartY + y * CELL_SIZE + CELL_SIZE / 2, 400);
board[y][x] = newType;
gems[y][x] = newGem;
game.addChild(newGem);
moved = true;
}
}
}
// Double check - ensure no empty spaces remain by creating additional gems if needed
for (var x = 0; x < BOARD_SIZE; x++) {
for (var y = 0; y < BOARD_SIZE; y++) {
if (!gems[y][x] || board[y][x] === -1) {
var newType = Math.floor(Math.random() * 8);
var newGem = new Gem(newType);
newGem.setGridPosition(x, y);
board[y][x] = newType;
gems[y][x] = newGem;
game.addChild(newGem);
moved = true;
}
}
}
if (moved) {
LK.setTimeout(function () {
isProcessingMatches = false;
checkForMatches(); // Check for cascade matches
}, 450);
} else {
isProcessingMatches = false;
}
}
game.down = function (x, y, obj) {
if (currentGameState === GAME_STATE_HOME) {
// Check if play button was clicked using coordinate bounds
if (playButton) {
var buttonLeft = playButton.x - playButton.width * playButton.scaleX / 2;
var buttonRight = playButton.x + playButton.width * playButton.scaleX / 2;
var buttonTop = playButton.y - playButton.height * playButton.scaleY / 2;
var buttonBottom = playButton.y + playButton.height * playButton.scaleY / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
// Add click effect to play button
tween(playButton, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(playButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
if (volumeEnabled) LK.getSound('button_click').play();
startGame();
}
});
}
});
}
// Check if text input field was clicked
if (textInputField) {
var fieldLeft = textInputField.x - textInputField.width / 2;
var fieldRight = textInputField.x + textInputField.width / 2;
var fieldTop = textInputField.y - textInputField.height / 2;
var fieldBottom = textInputField.y + textInputField.height / 2;
if (x >= fieldLeft && x <= fieldRight && y >= fieldTop && y <= fieldBottom) {
// Set active field to name
activeInputField = 'name';
// Show custom keyboard
if (volumeEnabled) LK.getSound('button_click').play();
// Add visual feedback
tween(textInputField, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(textInputField, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
createCustomKeyboard();
}
});
}
});
return;
}
// Check if password input field was clicked
if (passwordInputField) {
var fieldLeft = passwordInputField.x - passwordInputField.width / 2;
var fieldRight = passwordInputField.x + passwordInputField.width / 2;
var fieldTop = passwordInputField.y - passwordInputField.height / 2;
var fieldBottom = passwordInputField.y + passwordInputField.height / 2;
if (x >= fieldLeft && x <= fieldRight && y >= fieldTop && y <= fieldBottom) {
// Set active field to password
activeInputField = 'password';
// Show custom keyboard
if (volumeEnabled) LK.getSound('button_click').play();
// Add visual feedback
tween(passwordInputField, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(passwordInputField, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
createCustomKeyboard();
}
});
}
});
return;
}
}
// Check if create account button was clicked
if (createAccountButton) {
var buttonLeft = createAccountButton.x - createAccountButton.width / 2;
var buttonRight = createAccountButton.x + createAccountButton.width / 2;
var buttonTop = createAccountButton.y - createAccountButton.height / 2;
var buttonBottom = createAccountButton.y + createAccountButton.height / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
// Add click effect to create account button
tween(createAccountButton, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(createAccountButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
if (volumeEnabled) LK.getSound('button_click').play();
// Handle account creation logic
if (inputText.trim().length === 0 || passwordText.trim().length === 0) {
// Show error message for empty fields
console.log('Please enter both name and password');
return;
}
// Save account data to storage
var accountData = {
username: inputText.trim(),
password: passwordText.trim(),
dateCreated: new Date().toISOString(),
highScore: 0
};
// Save to storage with username as key
storage['account_' + inputText.trim()] = accountData;
storage.currentUser = inputText.trim();
console.log('Account created successfully for:', inputText);
// Clear input fields after successful creation
inputText = '';
passwordText = '';
// Update display
if (textInputText) {
textInputText.setText('Enter your name...');
textInputText.fill = 0x888888;
}
if (passwordInputText) {
passwordInputText.setText('Enter password...');
passwordInputText.fill = 0x888888;
}
// Start the game automatically after account creation
startGame();
}
});
}
});
}
}
// Handle keyboard clicks
if (keyboardVisible && keyboardContainer) {
for (var i = 0; i < keyboardKeys.length; i++) {
var keyData = keyboardKeys[i];
var keyLeft = keyData.x - keyData.width / 2;
var keyRight = keyData.x + keyData.width / 2;
var keyTop = keyData.y - keyData.height / 2;
var keyBottom = keyData.y + keyData.height / 2;
if (x >= keyLeft && x <= keyRight && y >= keyTop && y <= keyBottom) {
// Key pressed - add visual feedback
if (volumeEnabled) LK.getSound('button_click').play();
var originalKey = keyData.key;
tween(originalKey, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(originalKey, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
});
handleKeyboardInput(keyData.letter);
return;
}
}
}
}
}
return;
}
if (currentGameState === GAME_STATE_PAUSED) {
// Handle pause menu clicks
if (resumeButton) {
var buttonLeft = resumeButton.x - resumeButton.width / 2;
var buttonRight = resumeButton.x + resumeButton.width / 2;
var buttonTop = resumeButton.y - resumeButton.height / 2;
var buttonBottom = resumeButton.y + resumeButton.height / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
// Resume game
if (volumeEnabled) LK.getSound('button_click').play();
currentGameState = GAME_STATE_PLAYING;
pauseMenuContainer.destroy();
pauseMenuContainer = null;
return;
}
}
if (volumeButton) {
var buttonLeft = volumeButton.x - volumeButton.width / 2;
var buttonRight = volumeButton.x + volumeButton.width / 2;
var buttonTop = volumeButton.y - volumeButton.height / 2;
var buttonBottom = volumeButton.y + volumeButton.height / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
// Toggle volume
if (volumeEnabled) LK.getSound('button_click').play();
volumeEnabled = !volumeEnabled;
storage.volumeEnabled = volumeEnabled;
pauseMenuContainer.volumeText.setText(volumeEnabled ? 'VOLUME: ON' : 'VOLUME: OFF');
return;
}
}
if (exitButton) {
var buttonLeft = exitButton.x - exitButton.width / 2;
var buttonRight = exitButton.x + exitButton.width / 2;
var buttonTop = exitButton.y - exitButton.height / 2;
var buttonBottom = exitButton.y + exitButton.height / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
// Exit to home screen
if (volumeEnabled) LK.getSound('button_click').play();
currentGameState = GAME_STATE_HOME;
// Clear all game elements
game.removeChildren();
// Reset UI elements
LK.gui.top.removeChildren();
scoreTxt = null;
targetTxt = null;
messageTxt = null;
movesTxt = null;
pauseButton = null;
pauseMenuContainer = null;
// Reset game variables
board = [];
gems = [];
selectedGem = null;
isProcessingMatches = false;
isDragging = false;
score = 0;
// Reset input fields
inputText = '';
passwordText = '';
// Show home screen
createHomeScreen();
return;
}
}
return;
}
if (currentGameState !== GAME_STATE_PLAYING || isProcessingMatches) return;
// Check if pause button was clicked
if (pauseButton) {
var buttonLeft = pauseButton.x - pauseButton.width / 2;
var buttonRight = pauseButton.x + pauseButton.width / 2;
var buttonTop = pauseButton.y - pauseButton.height / 2;
var buttonBottom = pauseButton.y + pauseButton.height / 2;
if (x >= buttonLeft && x <= buttonRight && y >= buttonTop && y <= buttonBottom) {
// Add click effect to pause button
tween(pauseButton, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(pauseButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Show pause menu
if (volumeEnabled) LK.getSound('button_click').play();
currentGameState = GAME_STATE_PAUSED;
createPauseMenu();
}
});
}
});
return;
}
}
selectedGem = getGemAt(x, y);
if (selectedGem) {
if (volumeEnabled) {
var selectSound = LK.getSound('select');
if (selectSound) selectSound.play();
}
isDragging = true;
dragStartX = x;
dragStartY = y;
// Fun rainbow flash and bigger bounce effect for kids
var rainbowColors = [0xFF69B4, 0x00BFFF, 0x32CD32, 0xFFD700, 0xFF6347, 0x9370DB];
var randomColor = rainbowColors[Math.floor(Math.random() * rainbowColors.length)];
LK.effects.flashObject(selectedGem, randomColor, 300);
// Store reference to selected gem for use in callbacks
var gemToAnimate = selectedGem;
// Check if gem exists and has valid properties before starting tween, and prevent multiple scale animations
if (gemToAnimate && gemToAnimate.scaleX !== undefined && gemToAnimate.scaleY !== undefined && !gemToAnimate.isScaling) {
// Stop any existing scale tweens on this gem
tween.stop(gemToAnimate, {
scaleX: true,
scaleY: true
});
// Mark gem as scaling to prevent multiple simultaneous animations
gemToAnimate.isScaling = true;
// Store original scale values
var originalScaleX = CELL_SIZE / 256;
var originalScaleY = CELL_SIZE / 256;
tween(gemToAnimate, {
scaleX: originalScaleX * 1.4,
// Bigger bounce for kids
scaleY: originalScaleY * 1.4
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Check if gem still exists and has valid properties before animating back
if (gemToAnimate && gemToAnimate.scaleX !== undefined && gemToAnimate.scaleY !== undefined) {
tween(gemToAnimate, {
scaleX: originalScaleX,
scaleY: originalScaleY
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
// Reset scaling flag when animation is complete
if (gemToAnimate) {
gemToAnimate.isScaling = false;
}
}
});
} else {
// Reset scaling flag even if gem no longer exists
if (gemToAnimate) {
gemToAnimate.isScaling = false;
}
}
}
});
}
}
};
game.move = function (x, y, obj) {
if (currentGameState !== GAME_STATE_PLAYING || !isDragging || !selectedGem || isProcessingMatches) return;
var deltaX = x - dragStartX;
var deltaY = y - dragStartY;
var threshold = CELL_SIZE / 2;
if (Math.abs(deltaX) > threshold || Math.abs(deltaY) > threshold) {
var targetGem = null;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 0 && selectedGem.gridX < BOARD_SIZE - 1) {
targetGem = gems[selectedGem.gridY][selectedGem.gridX + 1];
} else if (deltaX < 0 && selectedGem.gridX > 0) {
targetGem = gems[selectedGem.gridY][selectedGem.gridX - 1];
}
} else {
// Vertical swipe
if (deltaY > 0 && selectedGem.gridY < BOARD_SIZE - 1) {
targetGem = gems[selectedGem.gridY + 1][selectedGem.gridX];
} else if (deltaY < 0 && selectedGem.gridY > 0) {
targetGem = gems[selectedGem.gridY - 1][selectedGem.gridX];
}
}
if (targetGem) {
swapGems(selectedGem, targetGem);
}
isDragging = false;
selectedGem = null;
}
};
game.up = function (x, y, obj) {
if (currentGameState !== GAME_STATE_PLAYING) return;
isDragging = false;
selectedGem = null;
};
// Game update loop for hint system
game.update = function () {
if (currentGameState === GAME_STATE_PLAYING && !isProcessingMatches) {
// Check if 5 seconds (300 ticks at 60fps) have passed since last move
var timeSinceLastMove = LK.ticks - lastMoveTime;
if (timeSinceLastMove >= 300 && hintAnimations.length === 0) {
// Show hint after 5 seconds of inactivity
showHint();
}
}
};
// Initialize home screen
createHomeScreen();
make a orange gem realistic. In-Game asset. 2d. High contrast. No shadows
a purple gem not a diamond realistic. In-Game asset. 2d. High contrast. No shadows
a red gem realistic. In-Game asset. 2d. High contrast. No shadows
a yellow gem found from the caves of colorado make it realistic. In-Game asset. 2d. High contrast. No shadows
a cyan realistic gem. In-Game asset. 2d. High contrast. No shadows
a pink gem realistic. In-Game asset. 2d. High contrast. No shadows
a very modern pause button. In-Game asset. 2d. High contrast. No shadows
a fun play button that says play on it super modern and kid friendly. In-Game asset. 2d. High contrast. No shadows
a create account button for signing up in a game supa kid friendly and modern. In-Game asset. 2d. High contrast. No shadows