/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // NumberButton: A tappable number on the board var NumberButton = Container.expand(function () { var self = Container.call(this); // Attach circle background, always use btnSize for size and scaling var btnSize = 240; // unified button/box size (increased for bigger buttons) var circle = self.attachAsset('numberCircle', { anchorX: 0.5, anchorY: 0.5, width: btnSize, height: btnSize, scaleX: btnSize / 220, scaleY: btnSize / 220 }); // Number text var numberText = new Text2('1', { size: 120, // increased for bigger buttons // This will be scaled with the button fill: 0xFFFFFF }); numberText.anchor.set(0.5, 0.5); self.addChild(numberText); self.value = 1; // The number this button represents self.isActive = true; // If false, ignore taps self.setNumber = function (n) { self.value = n; numberText.setText(n); }; // flash method removed to prevent color changes // Touch/click event self.down = function (x, y, obj) { if (!self.isActive) return; // Find empty box and check adjacency if (typeof numbersContainer !== "undefined" && numbersContainer) { var emptyBox = null; for (var i = 0; i < numbersContainer.children.length; i++) { var child = numbersContainer.children[i]; if (child.alpha && child.alpha < 1) { emptyBox = child; break; } } if (emptyBox) { // Get grid positions var btnPos = getGridPos(self); var emptyPos = getGridPos(emptyBox); var isAdjacent = false; if (btnPos.row === emptyPos.row && Math.abs(btnPos.col - emptyPos.col) === 1) { isAdjacent = true; } else if (btnPos.col === emptyPos.col && Math.abs(btnPos.row - emptyPos.row) === 1) { isAdjacent = true; } if (isAdjacent) { // Animate swap var oldX = self.x, oldY = self.y; tween(self, { x: emptyBox.x, y: emptyBox.y }, { duration: 120, easing: tween.easeOut }); tween(emptyBox, { x: oldX, y: oldY }, { duration: 120, easing: tween.easeOut }); // Swap positions in-place var tmpX = self.x, tmpY = self.y; self.x = emptyBox.x; self.y = emptyBox.y; emptyBox.x = oldX; emptyBox.y = oldY; return; // Do not trigger number tap logic } } } if (typeof onNumberButtonPressed === "function") { onNumberButtonPressed(self); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c20 }); /**** * Game Code ****/ // We'll use a simple circle as a background for each number for better touch targets. // Numbers will be rendered as Text2, so no need for custom shapes or images for numbers. // --- Game parameters --- var boardPadding = 80; var minNumbers = 15; var maxNumbers = 15; var level = 1; var numbersOnBoard = minNumbers; var maxLevel = 20; // --- State --- var numberButtons = []; var nextNumber = 1; var isPlaying = false; // --- UI --- // Timer and number UI removed as per requirements // --- Functions --- // Define btnSize globally so it is accessible in all functions var btnSize = 240; // unified button/box size, used everywhere for NumberButton (increased for bigger buttons) function shuffleArray(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } return arr; } function layoutNumbers(n) { // Remove old buttons for (var i = 0; i < numberButtons.length; i++) { numberButtons[i].destroy(); } numberButtons = []; // Remove old background if exists if (typeof numberRowBgBorder !== "undefined" && numberRowBgBorder && numberRowBgBorder.parent) { numberRowBgBorder.parent.removeChild(numberRowBgBorder); numberRowBgBorder.destroy(); } numberRowBgBorder = null; if (typeof numberRowBg !== "undefined" && numberRowBg && numberRowBg.parent) { numberRowBg.parent.removeChild(numberRowBg); numberRowBg.destroy(); } numberRowBg = null; // Make a smaller square background and border, centered var squareSize = Math.floor((Math.min(2048, 2732) - boardPadding * 2) * 0.7); // 70% of previous size var bgX = 2048 / 2; var bgY = 2732 / 2; // Add border: create a slightly larger square behind the main background var borderThickness = 14; numberRowBgBorder = LK.getAsset('numberCircle', { width: squareSize + borderThickness * 2, height: squareSize + borderThickness * 2, color: 0xffffff, // White border for visibility anchorX: 0.5, anchorY: 0.5, scaleX: (squareSize + borderThickness * 2) / 220, scaleY: (squareSize + borderThickness * 2) / 220, x: bgX, y: bgY }); game.addChild(numberRowBgBorder); numberRowBg = LK.getAsset('numberCircle', { width: squareSize, height: squareSize, color: 0x222b38, anchorX: 0.5, anchorY: 0.5, scaleX: squareSize / 220, scaleY: squareSize / 220, x: bgX, y: bgY }); game.addChild(numberRowBg); // Add a second box at the bottom of the board var bottomBoxY = bgY + squareSize / 2 + 80 + squareSize * 0.15; // 80px below main box, adjust as needed var bottomBox = LK.getAsset('numberCircle', { width: squareSize * 0.7, height: squareSize * 0.7, color: 0x222b38, anchorX: 0.5, anchorY: 0.5, scaleX: squareSize * 0.7 / 220, scaleY: squareSize * 0.7 / 220, x: bgX, y: bottomBoxY }); game.addChild(bottomBox); // Add a third box below the second box var thirdBoxY = bottomBoxY + squareSize * 0.7 / 2 + 60 + squareSize * 0.7 / 2; // 60px below second box var thirdBox = LK.getAsset('numberCircle', { width: squareSize * 0.7, height: squareSize * 0.7, color: 0x222b38, anchorX: 0.5, anchorY: 0.5, scaleX: squareSize * 0.7 / 220, scaleY: squareSize * 0.7 / 220, x: bgX, y: thirdBoxY }); game.addChild(thirdBox); // Create a container to hold all numbers, centered in the square if (typeof numbersContainer !== "undefined" && numbersContainer && numbersContainer.parent) { numbersContainer.parent.removeChild(numbersContainer); numbersContainer.destroy(); } numbersContainer = new Container(); numbersContainer.x = bgX; numbersContainer.y = bgY; game.addChild(numbersContainer); // Layout numbers in a 5x3 table grid, all boxes the same size, with a visual empty box var cols = 5; var rows = 3; var btnSize = 240; // unified button/box size, used everywhere for NumberButton (increased for bigger buttons) var btnSpacing = 70; // space between all boxes var gridWidth = cols * btnSize + (cols - 1) * btnSpacing; var gridHeight = rows * btnSize + (rows - 1) * btnSpacing; var startX = -gridWidth / 2 + btnSize / 2; var startY = -gridHeight / 2 + btnSize / 2; var positions = []; for (var row = 0; row < rows; row++) { for (var col = 0; col < cols; col++) { positions.push({ x: startX + col * (btnSize + btnSpacing), y: startY + row * (btnSize + btnSpacing) }); } } // Decide which position will be empty (last one for now) var emptyIndex = positions.length - 1; // Always use 1-15 for the board var nums = []; for (var i = 1; i <= 15; i++) nums.push(i); shuffleArray(nums); // Place all numbers inside the single numbersContainer, no per-number backgrounds // Ensure each number is assigned a unique position and no two numbers overlap // To avoid clustering, use a grid and assign each number to a unique cell, but shuffle the number assignments, not the positions. // This ensures all numbers are evenly spread and never overlap or cluster. var numIdx = 0; for (var i = 0; i < positions.length; i++) { if (i === emptyIndex) { // Add a visual empty box var emptyBox = LK.getAsset('numberCircle', { anchorX: 0.5, anchorY: 0.5, width: btnSize, height: btnSize, scaleX: btnSize / 220, scaleY: btnSize / 220, x: positions[i].x, y: positions[i].y, color: 0x222b38, alpha: 0.25 }); numbersContainer.addChild(emptyBox); continue; } // Assign each number to a unique, evenly distributed position (no clustering) var btn = new NumberButton(); btn.setNumber(nums[numIdx]); btn.x = positions[i].x; btn.y = positions[i].y; btn.isActive = true; btn.scaleX = 1; btn.scaleY = 1; numberButtons.push(btn); numbersContainer.addChild(btn); numIdx++; } // All numbers are now inside a single container (numbersContainer) which is inside the large square background. } function startLevel(lvl) { level = lvl; numbersOnBoard = 15; nextNumber = 1; layoutNumbers(numbersOnBoard); isPlaying = true; } // updateScoreText and updateTimerText removed function endGame(win) { isPlaying = false; // Remove all number buttons for (var i = 0; i < numberButtons.length; i++) { numberButtons[i].destroy(); } numberButtons = []; // Show win/lose if (win) { LK.showYouWin(); } else { LK.showGameOver(); } } function onNumberButtonPressed(btn) { if (!isPlaying) return; if (!btn.isActive) return; // --- No swap logic needed, just process number tap --- if (btn.value === nextNumber) { // Correct btn.isActive = false; // No color change, no disappearance, just mark as inactive // Do not remove or hide the number, keep it visible nextNumber++; if (nextNumber > numbersOnBoard) { // Level complete isPlaying = false; LK.setTimeout(function () { if (level >= maxLevel) { endGame(true); } else { startLevel(level + 1); } }, 600); } } else { // Wrong // No color change, no penalty // Do nothing, do not trigger game over } } // --- Game event handlers --- game.update = function () { if (!isPlaying) return; // Timer and time-based game over removed }; // --- Start game --- function startGame() { level = 1; startLevel(level); } startGame(); // --- Touch handling for drag-and-slide of rows/columns (sliding puzzle style) --- var dragButton = null; var dragStartX = 0; var dragStartY = 0; var dragBtnStartX = 0; var dragBtnStartY = 0; var dragRowOrCol = null; // {type: 'row'|'col', idx: number} var dragGroup = []; // buttons being dragged var dragEmptyBox = null; var dragEmptyStartX = 0; var dragEmptyStartY = 0; // Helper: find the NumberButton under a given (x, y) in numbersContainer local space function getButtonAtPoint(x, y) { for (var i = 0; i < numberButtons.length; i++) { var btn = numberButtons[i]; // Convert global (x, y) to numbersContainer local var local = numbersContainer.toLocal({ x: x, y: y }); var bx = btn.x, by = btn.y; var half = btnSize / 2; if (local.x >= bx - half && local.x <= bx + half && local.y >= by - half && local.y <= by + half) { return btn; } } return null; } // Helper: get grid position (col, row) for a button or empty box function getGridPos(obj) { // Use layoutNumbers grid logic var cols = 5, rows = 3; var btnSpacing = 70; var gridWidth = cols * btnSize + (cols - 1) * btnSpacing; var gridHeight = rows * btnSize + (rows - 1) * btnSpacing; var startX = -gridWidth / 2 + btnSize / 2; var startY = -gridHeight / 2 + btnSize / 2; var col = Math.round((obj.x - startX) / (btnSize + btnSpacing)); var row = Math.round((obj.y - startY) / (btnSize + btnSpacing)); return { col: col, row: row }; } // Helper: get the empty box object function getEmptyBox() { for (var i = 0; i < numbersContainer.children.length; i++) { var child = numbersContainer.children[i]; if (child.alpha && child.alpha < 1) { return child; } } return null; } // Helper: get all buttons in a row or column (excluding empty box) function getButtonsInLine(type, idx) { var result = []; for (var i = 0; i < numberButtons.length; i++) { var btn = numberButtons[i]; var pos = getGridPos(btn); if (type === 'row' && pos.row === idx || type === 'col' && pos.col === idx) { result.push(btn); } } return result; } // Only allow sliding if game is playing game.down = function (x, y, obj) { if (!isPlaying) return; // Convert to numbersContainer local var local = numbersContainer.toLocal({ x: x, y: y }); var btn = getButtonAtPoint(x, y); if (btn && btn.isActive) { dragButton = btn; dragStartX = x; dragStartY = y; dragBtnStartX = btn.x; dragBtnStartY = btn.y; // Determine if we can slide row or column (must contain empty box) var emptyBox = getEmptyBox(); var btnPos = getGridPos(btn); var emptyPos = getGridPos(emptyBox); if (btnPos.row === emptyPos.row) { dragRowOrCol = { type: 'row', idx: btnPos.row }; dragGroup = getButtonsInLine('row', btnPos.row); } else if (btnPos.col === emptyPos.col) { dragRowOrCol = { type: 'col', idx: btnPos.col }; dragGroup = getButtonsInLine('col', btnPos.col); } else { dragRowOrCol = null; dragGroup = []; dragButton = null; return; } dragEmptyBox = emptyBox; dragEmptyStartX = emptyBox.x; dragEmptyStartY = emptyBox.y; } }; game.move = function (x, y, obj) { if (!isPlaying) return; if (!dragButton || !dragRowOrCol) return; var dx = x - dragStartX; var dy = y - dragStartY; // Only allow drag if movement is significant (avoid accidental tap) if (Math.abs(dx) > 10 || Math.abs(dy) > 10) { // Move the group visually along the allowed axis if (dragRowOrCol.type === 'row') { for (var i = 0; i < dragGroup.length; i++) { dragGroup[i].x += dx - (dragGroup[i].lastDragDX || 0); } dragEmptyBox.x += dx - (dragEmptyBox.lastDragDX || 0); // Store last drag for (var i = 0; i < dragGroup.length; i++) { dragGroup[i].lastDragDX = dx; } dragEmptyBox.lastDragDX = dx; } else if (dragRowOrCol.type === 'col') { for (var i = 0; i < dragGroup.length; i++) { dragGroup[i].y += dy - (dragGroup[i].lastDragDY || 0); } dragEmptyBox.y += dy - (dragEmptyBox.lastDragDY || 0); // Store last drag for (var i = 0; i < dragGroup.length; i++) { dragGroup[i].lastDragDY = dy; } dragEmptyBox.lastDragDY = dy; } } }; game.up = function (x, y, obj) { if (!dragButton || !dragRowOrCol) { dragButton = null; dragRowOrCol = null; dragGroup = []; dragEmptyBox = null; return; } // On release, check if drag is enough to swap with empty box var threshold = btnSize * 0.6; var moved = false; if (dragRowOrCol.type === 'row') { var dx = dragGroup[0].lastDragDX || 0; if (Math.abs(dx) > threshold) { // Determine direction: right or left var dir = dx > 0 ? 1 : -1; var emptyPos = getGridPos(dragEmptyBox); // Only allow if empty is adjacent in row var canMove = false; for (var i = 0; i < dragGroup.length; i++) { var pos = getGridPos(dragGroup[i]); if (Math.abs(pos.col - emptyPos.col) === 1) { canMove = true; break; } } if (canMove) { // Prevent merging: only allow swap if no other button is at the empty box's position var mergeDetected = false; for (var j = 0; j < numberButtons.length; j++) { if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === dragEmptyBox.x && numberButtons[j].y === dragEmptyBox.y) { mergeDetected = true; break; } } if (!mergeDetected) { // Before swapping, ensure no two buttons will end up at the same position var targetX = dragEmptyBox.x; var targetY = dragEmptyBox.y; var collision = false; for (var j = 0; j < numberButtons.length; j++) { if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === targetX && numberButtons[j].y === targetY) { collision = true; break; } } if (!collision) { // Swap positions: move all buttons in row toward empty, move empty to button's old pos for (var i = 0; i < dragGroup.length; i++) { var btn = dragGroup[i]; var pos = getGridPos(btn); if (dir === 1 && pos.col < emptyPos.col || dir === -1 && pos.col > emptyPos.col) { // Move this button to empty tween(btn, { x: dragEmptyBox.x, y: dragEmptyBox.y }, { duration: 120, easing: tween.easeOut }); dragEmptyBox.x = btn.x; dragEmptyBox.y = btn.y; moved = true; break; } } } } } } } else if (dragRowOrCol.type === 'col') { var dy = dragGroup[0].lastDragDY || 0; if (Math.abs(dy) > threshold) { // Determine direction: down or up var dir = dy > 0 ? 1 : -1; var emptyPos = getGridPos(dragEmptyBox); // Only allow if empty is adjacent in col var canMove = false; for (var i = 0; i < dragGroup.length; i++) { var pos = getGridPos(dragGroup[i]); if (Math.abs(pos.row - emptyPos.row) === 1) { canMove = true; break; } } if (canMove) { // Prevent merging: only allow swap if no other button is at the empty box's position var mergeDetected = false; for (var j = 0; j < numberButtons.length; j++) { if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === dragEmptyBox.x && numberButtons[j].y === dragEmptyBox.y) { mergeDetected = true; break; } } if (!mergeDetected) { // Before swapping, ensure no two buttons will end up at the same position var targetX = dragEmptyBox.x; var targetY = dragEmptyBox.y; var collision = false; for (var j = 0; j < numberButtons.length; j++) { if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === targetX && numberButtons[j].y === targetY) { collision = true; break; } } if (!collision) { // Swap positions: move all buttons in col toward empty, move empty to button's old pos for (var i = 0; i < dragGroup.length; i++) { var btn = dragGroup[i]; var pos = getGridPos(btn); if (dir === 1 && pos.row < emptyPos.row || dir === -1 && pos.row > emptyPos.row) { // Move this button to empty tween(btn, { x: dragEmptyBox.x, y: dragEmptyBox.y }, { duration: 120, easing: tween.easeOut }); dragEmptyBox.x = btn.x; dragEmptyBox.y = btn.y; moved = true; break; } } } } } } } // Snap all to grid if not moved if (!moved) { // Snap all buttons in group and empty box back to original for (var i = 0; i < dragGroup.length; i++) { var btn = dragGroup[i]; tween(btn, { x: dragBtnStartX, y: dragBtnStartY }, { duration: 120, easing: tween.easeOut }); btn.lastDragDX = 0; btn.lastDragDY = 0; } if (dragEmptyBox) { tween(dragEmptyBox, { x: dragEmptyStartX, y: dragEmptyStartY }, { duration: 120, easing: tween.easeOut }); dragEmptyBox.lastDragDX = 0; dragEmptyBox.lastDragDY = 0; } } // Reset drag state dragButton = null; dragRowOrCol = null; dragGroup = []; dragEmptyBox = null; }; // --- Storage (for future highscore etc.) --- // Not implemented in MVP // --- Music/Sound --- // Not implemented in MVP // --- End of file ---
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// NumberButton: A tappable number on the board
var NumberButton = Container.expand(function () {
var self = Container.call(this);
// Attach circle background, always use btnSize for size and scaling
var btnSize = 240; // unified button/box size (increased for bigger buttons)
var circle = self.attachAsset('numberCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: btnSize,
height: btnSize,
scaleX: btnSize / 220,
scaleY: btnSize / 220
});
// Number text
var numberText = new Text2('1', {
size: 120,
// increased for bigger buttons
// This will be scaled with the button
fill: 0xFFFFFF
});
numberText.anchor.set(0.5, 0.5);
self.addChild(numberText);
self.value = 1; // The number this button represents
self.isActive = true; // If false, ignore taps
self.setNumber = function (n) {
self.value = n;
numberText.setText(n);
};
// flash method removed to prevent color changes
// Touch/click event
self.down = function (x, y, obj) {
if (!self.isActive) return;
// Find empty box and check adjacency
if (typeof numbersContainer !== "undefined" && numbersContainer) {
var emptyBox = null;
for (var i = 0; i < numbersContainer.children.length; i++) {
var child = numbersContainer.children[i];
if (child.alpha && child.alpha < 1) {
emptyBox = child;
break;
}
}
if (emptyBox) {
// Get grid positions
var btnPos = getGridPos(self);
var emptyPos = getGridPos(emptyBox);
var isAdjacent = false;
if (btnPos.row === emptyPos.row && Math.abs(btnPos.col - emptyPos.col) === 1) {
isAdjacent = true;
} else if (btnPos.col === emptyPos.col && Math.abs(btnPos.row - emptyPos.row) === 1) {
isAdjacent = true;
}
if (isAdjacent) {
// Animate swap
var oldX = self.x,
oldY = self.y;
tween(self, {
x: emptyBox.x,
y: emptyBox.y
}, {
duration: 120,
easing: tween.easeOut
});
tween(emptyBox, {
x: oldX,
y: oldY
}, {
duration: 120,
easing: tween.easeOut
});
// Swap positions in-place
var tmpX = self.x,
tmpY = self.y;
self.x = emptyBox.x;
self.y = emptyBox.y;
emptyBox.x = oldX;
emptyBox.y = oldY;
return; // Do not trigger number tap logic
}
}
}
if (typeof onNumberButtonPressed === "function") {
onNumberButtonPressed(self);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c20
});
/****
* Game Code
****/
// We'll use a simple circle as a background for each number for better touch targets.
// Numbers will be rendered as Text2, so no need for custom shapes or images for numbers.
// --- Game parameters ---
var boardPadding = 80;
var minNumbers = 15;
var maxNumbers = 15;
var level = 1;
var numbersOnBoard = minNumbers;
var maxLevel = 20;
// --- State ---
var numberButtons = [];
var nextNumber = 1;
var isPlaying = false;
// --- UI ---
// Timer and number UI removed as per requirements
// --- Functions ---
// Define btnSize globally so it is accessible in all functions
var btnSize = 240; // unified button/box size, used everywhere for NumberButton (increased for bigger buttons)
function shuffleArray(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
return arr;
}
function layoutNumbers(n) {
// Remove old buttons
for (var i = 0; i < numberButtons.length; i++) {
numberButtons[i].destroy();
}
numberButtons = [];
// Remove old background if exists
if (typeof numberRowBgBorder !== "undefined" && numberRowBgBorder && numberRowBgBorder.parent) {
numberRowBgBorder.parent.removeChild(numberRowBgBorder);
numberRowBgBorder.destroy();
}
numberRowBgBorder = null;
if (typeof numberRowBg !== "undefined" && numberRowBg && numberRowBg.parent) {
numberRowBg.parent.removeChild(numberRowBg);
numberRowBg.destroy();
}
numberRowBg = null;
// Make a smaller square background and border, centered
var squareSize = Math.floor((Math.min(2048, 2732) - boardPadding * 2) * 0.7); // 70% of previous size
var bgX = 2048 / 2;
var bgY = 2732 / 2;
// Add border: create a slightly larger square behind the main background
var borderThickness = 14;
numberRowBgBorder = LK.getAsset('numberCircle', {
width: squareSize + borderThickness * 2,
height: squareSize + borderThickness * 2,
color: 0xffffff,
// White border for visibility
anchorX: 0.5,
anchorY: 0.5,
scaleX: (squareSize + borderThickness * 2) / 220,
scaleY: (squareSize + borderThickness * 2) / 220,
x: bgX,
y: bgY
});
game.addChild(numberRowBgBorder);
numberRowBg = LK.getAsset('numberCircle', {
width: squareSize,
height: squareSize,
color: 0x222b38,
anchorX: 0.5,
anchorY: 0.5,
scaleX: squareSize / 220,
scaleY: squareSize / 220,
x: bgX,
y: bgY
});
game.addChild(numberRowBg);
// Add a second box at the bottom of the board
var bottomBoxY = bgY + squareSize / 2 + 80 + squareSize * 0.15; // 80px below main box, adjust as needed
var bottomBox = LK.getAsset('numberCircle', {
width: squareSize * 0.7,
height: squareSize * 0.7,
color: 0x222b38,
anchorX: 0.5,
anchorY: 0.5,
scaleX: squareSize * 0.7 / 220,
scaleY: squareSize * 0.7 / 220,
x: bgX,
y: bottomBoxY
});
game.addChild(bottomBox);
// Add a third box below the second box
var thirdBoxY = bottomBoxY + squareSize * 0.7 / 2 + 60 + squareSize * 0.7 / 2; // 60px below second box
var thirdBox = LK.getAsset('numberCircle', {
width: squareSize * 0.7,
height: squareSize * 0.7,
color: 0x222b38,
anchorX: 0.5,
anchorY: 0.5,
scaleX: squareSize * 0.7 / 220,
scaleY: squareSize * 0.7 / 220,
x: bgX,
y: thirdBoxY
});
game.addChild(thirdBox);
// Create a container to hold all numbers, centered in the square
if (typeof numbersContainer !== "undefined" && numbersContainer && numbersContainer.parent) {
numbersContainer.parent.removeChild(numbersContainer);
numbersContainer.destroy();
}
numbersContainer = new Container();
numbersContainer.x = bgX;
numbersContainer.y = bgY;
game.addChild(numbersContainer);
// Layout numbers in a 5x3 table grid, all boxes the same size, with a visual empty box
var cols = 5;
var rows = 3;
var btnSize = 240; // unified button/box size, used everywhere for NumberButton (increased for bigger buttons)
var btnSpacing = 70; // space between all boxes
var gridWidth = cols * btnSize + (cols - 1) * btnSpacing;
var gridHeight = rows * btnSize + (rows - 1) * btnSpacing;
var startX = -gridWidth / 2 + btnSize / 2;
var startY = -gridHeight / 2 + btnSize / 2;
var positions = [];
for (var row = 0; row < rows; row++) {
for (var col = 0; col < cols; col++) {
positions.push({
x: startX + col * (btnSize + btnSpacing),
y: startY + row * (btnSize + btnSpacing)
});
}
}
// Decide which position will be empty (last one for now)
var emptyIndex = positions.length - 1;
// Always use 1-15 for the board
var nums = [];
for (var i = 1; i <= 15; i++) nums.push(i);
shuffleArray(nums);
// Place all numbers inside the single numbersContainer, no per-number backgrounds
// Ensure each number is assigned a unique position and no two numbers overlap
// To avoid clustering, use a grid and assign each number to a unique cell, but shuffle the number assignments, not the positions.
// This ensures all numbers are evenly spread and never overlap or cluster.
var numIdx = 0;
for (var i = 0; i < positions.length; i++) {
if (i === emptyIndex) {
// Add a visual empty box
var emptyBox = LK.getAsset('numberCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: btnSize,
height: btnSize,
scaleX: btnSize / 220,
scaleY: btnSize / 220,
x: positions[i].x,
y: positions[i].y,
color: 0x222b38,
alpha: 0.25
});
numbersContainer.addChild(emptyBox);
continue;
}
// Assign each number to a unique, evenly distributed position (no clustering)
var btn = new NumberButton();
btn.setNumber(nums[numIdx]);
btn.x = positions[i].x;
btn.y = positions[i].y;
btn.isActive = true;
btn.scaleX = 1;
btn.scaleY = 1;
numberButtons.push(btn);
numbersContainer.addChild(btn);
numIdx++;
}
// All numbers are now inside a single container (numbersContainer) which is inside the large square background.
}
function startLevel(lvl) {
level = lvl;
numbersOnBoard = 15;
nextNumber = 1;
layoutNumbers(numbersOnBoard);
isPlaying = true;
}
// updateScoreText and updateTimerText removed
function endGame(win) {
isPlaying = false;
// Remove all number buttons
for (var i = 0; i < numberButtons.length; i++) {
numberButtons[i].destroy();
}
numberButtons = [];
// Show win/lose
if (win) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}
function onNumberButtonPressed(btn) {
if (!isPlaying) return;
if (!btn.isActive) return;
// --- No swap logic needed, just process number tap ---
if (btn.value === nextNumber) {
// Correct
btn.isActive = false;
// No color change, no disappearance, just mark as inactive
// Do not remove or hide the number, keep it visible
nextNumber++;
if (nextNumber > numbersOnBoard) {
// Level complete
isPlaying = false;
LK.setTimeout(function () {
if (level >= maxLevel) {
endGame(true);
} else {
startLevel(level + 1);
}
}, 600);
}
} else {
// Wrong
// No color change, no penalty
// Do nothing, do not trigger game over
}
}
// --- Game event handlers ---
game.update = function () {
if (!isPlaying) return;
// Timer and time-based game over removed
};
// --- Start game ---
function startGame() {
level = 1;
startLevel(level);
}
startGame();
// --- Touch handling for drag-and-slide of rows/columns (sliding puzzle style) ---
var dragButton = null;
var dragStartX = 0;
var dragStartY = 0;
var dragBtnStartX = 0;
var dragBtnStartY = 0;
var dragRowOrCol = null; // {type: 'row'|'col', idx: number}
var dragGroup = []; // buttons being dragged
var dragEmptyBox = null;
var dragEmptyStartX = 0;
var dragEmptyStartY = 0;
// Helper: find the NumberButton under a given (x, y) in numbersContainer local space
function getButtonAtPoint(x, y) {
for (var i = 0; i < numberButtons.length; i++) {
var btn = numberButtons[i];
// Convert global (x, y) to numbersContainer local
var local = numbersContainer.toLocal({
x: x,
y: y
});
var bx = btn.x,
by = btn.y;
var half = btnSize / 2;
if (local.x >= bx - half && local.x <= bx + half && local.y >= by - half && local.y <= by + half) {
return btn;
}
}
return null;
}
// Helper: get grid position (col, row) for a button or empty box
function getGridPos(obj) {
// Use layoutNumbers grid logic
var cols = 5,
rows = 3;
var btnSpacing = 70;
var gridWidth = cols * btnSize + (cols - 1) * btnSpacing;
var gridHeight = rows * btnSize + (rows - 1) * btnSpacing;
var startX = -gridWidth / 2 + btnSize / 2;
var startY = -gridHeight / 2 + btnSize / 2;
var col = Math.round((obj.x - startX) / (btnSize + btnSpacing));
var row = Math.round((obj.y - startY) / (btnSize + btnSpacing));
return {
col: col,
row: row
};
}
// Helper: get the empty box object
function getEmptyBox() {
for (var i = 0; i < numbersContainer.children.length; i++) {
var child = numbersContainer.children[i];
if (child.alpha && child.alpha < 1) {
return child;
}
}
return null;
}
// Helper: get all buttons in a row or column (excluding empty box)
function getButtonsInLine(type, idx) {
var result = [];
for (var i = 0; i < numberButtons.length; i++) {
var btn = numberButtons[i];
var pos = getGridPos(btn);
if (type === 'row' && pos.row === idx || type === 'col' && pos.col === idx) {
result.push(btn);
}
}
return result;
}
// Only allow sliding if game is playing
game.down = function (x, y, obj) {
if (!isPlaying) return;
// Convert to numbersContainer local
var local = numbersContainer.toLocal({
x: x,
y: y
});
var btn = getButtonAtPoint(x, y);
if (btn && btn.isActive) {
dragButton = btn;
dragStartX = x;
dragStartY = y;
dragBtnStartX = btn.x;
dragBtnStartY = btn.y;
// Determine if we can slide row or column (must contain empty box)
var emptyBox = getEmptyBox();
var btnPos = getGridPos(btn);
var emptyPos = getGridPos(emptyBox);
if (btnPos.row === emptyPos.row) {
dragRowOrCol = {
type: 'row',
idx: btnPos.row
};
dragGroup = getButtonsInLine('row', btnPos.row);
} else if (btnPos.col === emptyPos.col) {
dragRowOrCol = {
type: 'col',
idx: btnPos.col
};
dragGroup = getButtonsInLine('col', btnPos.col);
} else {
dragRowOrCol = null;
dragGroup = [];
dragButton = null;
return;
}
dragEmptyBox = emptyBox;
dragEmptyStartX = emptyBox.x;
dragEmptyStartY = emptyBox.y;
}
};
game.move = function (x, y, obj) {
if (!isPlaying) return;
if (!dragButton || !dragRowOrCol) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
// Only allow drag if movement is significant (avoid accidental tap)
if (Math.abs(dx) > 10 || Math.abs(dy) > 10) {
// Move the group visually along the allowed axis
if (dragRowOrCol.type === 'row') {
for (var i = 0; i < dragGroup.length; i++) {
dragGroup[i].x += dx - (dragGroup[i].lastDragDX || 0);
}
dragEmptyBox.x += dx - (dragEmptyBox.lastDragDX || 0);
// Store last drag
for (var i = 0; i < dragGroup.length; i++) {
dragGroup[i].lastDragDX = dx;
}
dragEmptyBox.lastDragDX = dx;
} else if (dragRowOrCol.type === 'col') {
for (var i = 0; i < dragGroup.length; i++) {
dragGroup[i].y += dy - (dragGroup[i].lastDragDY || 0);
}
dragEmptyBox.y += dy - (dragEmptyBox.lastDragDY || 0);
// Store last drag
for (var i = 0; i < dragGroup.length; i++) {
dragGroup[i].lastDragDY = dy;
}
dragEmptyBox.lastDragDY = dy;
}
}
};
game.up = function (x, y, obj) {
if (!dragButton || !dragRowOrCol) {
dragButton = null;
dragRowOrCol = null;
dragGroup = [];
dragEmptyBox = null;
return;
}
// On release, check if drag is enough to swap with empty box
var threshold = btnSize * 0.6;
var moved = false;
if (dragRowOrCol.type === 'row') {
var dx = dragGroup[0].lastDragDX || 0;
if (Math.abs(dx) > threshold) {
// Determine direction: right or left
var dir = dx > 0 ? 1 : -1;
var emptyPos = getGridPos(dragEmptyBox);
// Only allow if empty is adjacent in row
var canMove = false;
for (var i = 0; i < dragGroup.length; i++) {
var pos = getGridPos(dragGroup[i]);
if (Math.abs(pos.col - emptyPos.col) === 1) {
canMove = true;
break;
}
}
if (canMove) {
// Prevent merging: only allow swap if no other button is at the empty box's position
var mergeDetected = false;
for (var j = 0; j < numberButtons.length; j++) {
if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === dragEmptyBox.x && numberButtons[j].y === dragEmptyBox.y) {
mergeDetected = true;
break;
}
}
if (!mergeDetected) {
// Before swapping, ensure no two buttons will end up at the same position
var targetX = dragEmptyBox.x;
var targetY = dragEmptyBox.y;
var collision = false;
for (var j = 0; j < numberButtons.length; j++) {
if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === targetX && numberButtons[j].y === targetY) {
collision = true;
break;
}
}
if (!collision) {
// Swap positions: move all buttons in row toward empty, move empty to button's old pos
for (var i = 0; i < dragGroup.length; i++) {
var btn = dragGroup[i];
var pos = getGridPos(btn);
if (dir === 1 && pos.col < emptyPos.col || dir === -1 && pos.col > emptyPos.col) {
// Move this button to empty
tween(btn, {
x: dragEmptyBox.x,
y: dragEmptyBox.y
}, {
duration: 120,
easing: tween.easeOut
});
dragEmptyBox.x = btn.x;
dragEmptyBox.y = btn.y;
moved = true;
break;
}
}
}
}
}
}
} else if (dragRowOrCol.type === 'col') {
var dy = dragGroup[0].lastDragDY || 0;
if (Math.abs(dy) > threshold) {
// Determine direction: down or up
var dir = dy > 0 ? 1 : -1;
var emptyPos = getGridPos(dragEmptyBox);
// Only allow if empty is adjacent in col
var canMove = false;
for (var i = 0; i < dragGroup.length; i++) {
var pos = getGridPos(dragGroup[i]);
if (Math.abs(pos.row - emptyPos.row) === 1) {
canMove = true;
break;
}
}
if (canMove) {
// Prevent merging: only allow swap if no other button is at the empty box's position
var mergeDetected = false;
for (var j = 0; j < numberButtons.length; j++) {
if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === dragEmptyBox.x && numberButtons[j].y === dragEmptyBox.y) {
mergeDetected = true;
break;
}
}
if (!mergeDetected) {
// Before swapping, ensure no two buttons will end up at the same position
var targetX = dragEmptyBox.x;
var targetY = dragEmptyBox.y;
var collision = false;
for (var j = 0; j < numberButtons.length; j++) {
if (numberButtons[j] !== dragGroup[0] && numberButtons[j].x === targetX && numberButtons[j].y === targetY) {
collision = true;
break;
}
}
if (!collision) {
// Swap positions: move all buttons in col toward empty, move empty to button's old pos
for (var i = 0; i < dragGroup.length; i++) {
var btn = dragGroup[i];
var pos = getGridPos(btn);
if (dir === 1 && pos.row < emptyPos.row || dir === -1 && pos.row > emptyPos.row) {
// Move this button to empty
tween(btn, {
x: dragEmptyBox.x,
y: dragEmptyBox.y
}, {
duration: 120,
easing: tween.easeOut
});
dragEmptyBox.x = btn.x;
dragEmptyBox.y = btn.y;
moved = true;
break;
}
}
}
}
}
}
}
// Snap all to grid if not moved
if (!moved) {
// Snap all buttons in group and empty box back to original
for (var i = 0; i < dragGroup.length; i++) {
var btn = dragGroup[i];
tween(btn, {
x: dragBtnStartX,
y: dragBtnStartY
}, {
duration: 120,
easing: tween.easeOut
});
btn.lastDragDX = 0;
btn.lastDragDY = 0;
}
if (dragEmptyBox) {
tween(dragEmptyBox, {
x: dragEmptyStartX,
y: dragEmptyStartY
}, {
duration: 120,
easing: tween.easeOut
});
dragEmptyBox.lastDragDX = 0;
dragEmptyBox.lastDragDY = 0;
}
}
// Reset drag state
dragButton = null;
dragRowOrCol = null;
dragGroup = [];
dragEmptyBox = null;
};
// --- Storage (for future highscore etc.) ---
// Not implemented in MVP
// --- Music/Sound ---
// Not implemented in MVP
// --- End of file ---