/****
* 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