/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // PuzzleTile: Represents a single tile in the puzzle grid. var PuzzleTile = Container.expand(function () { var self = Container.call(this); // Properties self.gridX = 0; // grid position x self.gridY = 0; // grid position y self.correctX = 0; // correct grid position x self.correctY = 0; // correct grid position y self.tileIndex = 0; // index in the tile array self.isEmpty = false; // is this the empty tile? // The tile's visual var tileAsset = self.attachAsset('tile', { anchorX: 0, anchorY: 0 }); // For MVP, we color each tile differently for visual distinction self.setColor = function (color) { tileAsset.color = color; // Use tween to smoothly animate the tint change for more vibrant effect tween(tileAsset, { tint: color }, { duration: 300, easing: tween.easeOut }); }; // Set the label (number) for the tile self.setLabel = function (label) { if (self.labelText) { self.labelText.setText(label); } else { var txt = new Text2(label + '', { size: 120, fill: 0x222222 }); txt.anchor.set(0.5, 0.5); txt.x = tileAsset.width / 2; txt.y = tileAsset.height / 2; self.addChild(txt); self.labelText = txt; } }; // Hide label for empty tile self.hideLabel = function () { if (self.labelText) { self.labelText.visible = false; } }; // Show label self.showLabel = function () { if (self.labelText) { self.labelText.visible = true; } }; // Animate to a new position self.moveTo = function (x, y, duration) { tween(self, { x: x, y: y }, { duration: duration || 200, easing: tween.cubicOut }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // In the future, this can be replaced with a real image or a procedurally generated pattern. // For MVP, we use a colored box as a placeholder for each tile, with a random color per game. // We'll use a single image asset for the puzzle, which will be randomly generated each game. // --- Puzzle Settings --- var gridSize = 4; // 4x4 grid (15-puzzle style) var tileSize = 400; // px, will be scaled to fit screen var tileSpacing = 12; // px gap between tiles var puzzleOffsetX = 0; var puzzleOffsetY = 0; var puzzleWidth = gridSize * tileSize + (gridSize - 1) * tileSpacing; var puzzleHeight = gridSize * tileSize + (gridSize - 1) * tileSpacing; // --- State --- var tiles = []; // 2D array [y][x] of PuzzleTile var tileList = []; // flat array of all PuzzleTile var emptyTile = null; // reference to the empty tile var moveCount = 0; var startTime = 0; var timerInterval = null; var isSolved = false; // --- UI --- var moveText = new Text2('Moves: 0', { size: 90, fill: 0xFFFFFF }); moveText.anchor.set(0, 0); // Anchor to top-left for easier positioning LK.gui.topLeft.addChild(moveText); // Add to topLeft GUI container var timeText = new Text2('Time: 0s', { size: 90, fill: 0xFFFFFF }); timeText.anchor.set(0.5, 0); LK.gui.top.addChild(timeText); // Keep timeText in the top-center // Position UI moveText.x = 20; // Small margin from the left edge moveText.y = 120; // Position below the platform menu icon area timeText.x = LK.gui.top.width / 2; // Center timeText timeText.y = 20; // --- Remix Button --- var remixBtn = new Text2('Remix', { size: 90, fill: 0xFFD700 }); remixBtn.anchor.set(0.5, 0); remixBtn.x = LK.gui.top.width / 2; remixBtn.y = 120; remixBtn.interactive = true; remixBtn.buttonMode = true; remixBtn.down = function (x, y, obj) { if (isSolved) return; shufflePuzzle(); moveCount = 0; startTime = Date.now(); updateUI(); }; LK.gui.top.addChild(remixBtn); // --- New Round Button --- var newRoundBtn = new Text2('New Round', { size: 90, fill: 0x4CAF50 // A nice green color for the new round button }); newRoundBtn.anchor.set(0.5, 0); newRoundBtn.x = LK.gui.top.width / 2; newRoundBtn.y = remixBtn.y + remixBtn.height + 30; // Position below the Remix button with some padding newRoundBtn.interactive = true; newRoundBtn.buttonMode = true; newRoundBtn.visible = false; // Initially hidden newRoundBtn.down = function (x, y, obj) { createPuzzle(); // This will re-initialize the game, hide this button, and reset isSolved }; LK.gui.top.addChild(newRoundBtn); // --- Background Menu Button --- var bgMenuBtn = new Text2('BG', { size: 60, fill: 0xFFFFFF }); bgMenuBtn.anchor.set(1, 0); bgMenuBtn.x = LK.gui.topRight.width - 20; bgMenuBtn.y = 20; bgMenuBtn.interactive = true; bgMenuBtn.buttonMode = true; bgMenuBtn.down = function (x, y, obj) { showBackgroundMenu(); }; LK.gui.topRight.addChild(bgMenuBtn); // --- Background Selection Variables --- var backgroundMenu = null; var backgroundColors = [0x222222, // Dark gray (default) 0x00FF00, // Bright green 0x0080FF, // Bright blue 0xFF00FF, // Bright magenta 0xFF0040, // Bright red 0xFF8000, // Bright orange 0x8000FF, // Bright purple 'rainbow' // Rainbow option ]; var currentBgIndex = 0; // --- Rainbow Background Variables --- var rainbowInterval = null; var rainbowHue = 0; // --- Helper Functions --- // Generate a random color palette for the puzzle function generateColorPalette(n) { var colors = []; var baseHue = Math.floor(Math.random() * 360); for (var i = 0; i < n; i++) { // HSL to RGB conversion with more vibrant saturation and lightness var hue = (baseHue + i * (360 / n)) % 360; var rgb = hslToRgb(hue / 360, 0.9, 0.7); // Increased saturation to 0.9 and lightness to 0.7 var color = rgb[0] << 16 | rgb[1] << 8 | rgb[2]; colors.push(color); } return colors; } // HSL to RGB helper function hslToRgb(h, s, l) { var r, g, b; if (s == 0) { r = g = b = l; } else { var hue2rgb = function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; }; var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1 / 3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1 / 3); } return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; } // Shuffle an array in-place function shuffleArray(arr) { for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } } // Check if the puzzle is solved function checkSolved() { for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { var tile = tiles[y][x]; if (tile.gridX !== tile.correctX || tile.gridY !== tile.correctY) { return false; } } } return true; } // Update move and time UI function updateUI() { moveText.setText('Moves: ' + moveCount); var elapsed = Math.floor((Date.now() - startTime) / 1000); timeText.setText('Time: ' + elapsed + 's'); } // --- Puzzle Generation --- function createPuzzle() { if (typeof newRoundBtn !== 'undefined' && newRoundBtn) { newRoundBtn.visible = false; } if (typeof hideTutorial === 'function') { hideTutorial(); } // Remove old tiles for (var i = 0; i < tileList.length; i++) { tileList[i].destroy(); } tiles = []; tileList = []; emptyTile = null; isSolved = false; moveCount = 0; updateUI(); // Generate color palette var numTiles = gridSize * gridSize; var colors = generateColorPalette(numTiles - 1); // Calculate tile size to fit screen var maxWidth = 2048 - 200; var maxHeight = 2732 - 400; tileSize = Math.floor(Math.min((maxWidth - (gridSize - 1) * tileSpacing) / gridSize, (maxHeight - (gridSize - 1) * tileSpacing) / gridSize)); puzzleWidth = gridSize * tileSize + (gridSize - 1) * tileSpacing; puzzleHeight = gridSize * tileSize + (gridSize - 1) * tileSpacing; puzzleOffsetX = Math.floor((2048 - puzzleWidth) / 2); puzzleOffsetY = Math.floor((2732 - puzzleHeight) / 2) + 80; // Create tiles in solved order var idx = 0; for (var y = 0; y < gridSize; y++) { tiles[y] = []; for (var x = 0; x < gridSize; x++) { var tile = new PuzzleTile(); tile.gridX = x; tile.gridY = y; tile.correctX = x; tile.correctY = y; tile.tileIndex = idx; tile.x = puzzleOffsetX + x * (tileSize + tileSpacing); tile.y = puzzleOffsetY + y * (tileSize + tileSpacing); tile.width = tileSize; tile.height = tileSize; tile.setColor(colors[idx] || 0x222222); tile.setLabel(idx + 1); tile.isEmpty = false; tileList.push(tile); tiles[y][x] = tile; game.addChild(tile); idx++; } } // Make last tile the empty tile var lastTile = tiles[gridSize - 1][gridSize - 1]; lastTile.isEmpty = true; lastTile.setColor(0x222222); lastTile.hideLabel(); emptyTile = lastTile; // Shuffle tiles shufflePuzzle(); // Start timer startTime = Date.now(); if (timerInterval) LK.clearInterval(timerInterval); timerInterval = LK.setInterval(updateUI, 500); } // Shuffle the puzzle by simulating random valid moves function shufflePuzzle() { var moves = [[0, 1], [1, 0], [0, -1], [-1, 0]]; var prevX = emptyTile.gridX, prevY = emptyTile.gridY; var shuffleCount = 200 + Math.floor(Math.random() * 100); for (var i = 0; i < shuffleCount; i++) { var possible = []; for (var d = 0; d < moves.length; d++) { var nx = emptyTile.gridX + moves[d][0]; var ny = emptyTile.gridY + moves[d][1]; if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize) { if (!(nx === prevX && ny === prevY)) { possible.push([nx, ny]); } } } if (possible.length === 0) continue; var pick = possible[Math.floor(Math.random() * possible.length)]; var tile = tiles[pick[1]][pick[0]]; swapWithEmpty(tile, false); prevX = emptyTile.gridX; prevY = emptyTile.gridY; } // After shuffling, update all tile positions visually for (var y = 0; y < gridSize; y++) { for (var x = 0; x < gridSize; x++) { var tile = tiles[y][x]; tile.x = puzzleOffsetX + x * (tileSize + tileSpacing); tile.y = puzzleOffsetY + y * (tileSize + tileSpacing); tile.gridX = x; tile.gridY = y; } } } // Swap a tile with the empty tile (if adjacent) function swapWithEmpty(tile, animate) { if (tile.isEmpty) return false; var dx = Math.abs(tile.gridX - emptyTile.gridX); var dy = Math.abs(tile.gridY - emptyTile.gridY); if (dx === 1 && dy === 0 || dx === 0 && dy === 1) { // Swap in tiles array var tx = tile.gridX, ty = tile.gridY; var ex = emptyTile.gridX, ey = emptyTile.gridY; tiles[ty][tx] = emptyTile; tiles[ey][ex] = tile; // Swap grid positions var tmpX = tile.gridX, tmpY = tile.gridY; tile.gridX = emptyTile.gridX; tile.gridY = emptyTile.gridY; emptyTile.gridX = tmpX; emptyTile.gridY = tmpY; // Animate movement var tileTargetX = puzzleOffsetX + tile.gridX * (tileSize + tileSpacing); var tileTargetY = puzzleOffsetY + tile.gridY * (tileSize + tileSpacing); var emptyTargetX = puzzleOffsetX + emptyTile.gridX * (tileSize + tileSpacing); var emptyTargetY = puzzleOffsetY + emptyTile.gridY * (tileSize + tileSpacing); if (animate !== false) { tile.moveTo(tileTargetX, tileTargetY, 120); emptyTile.moveTo(emptyTargetX, emptyTargetY, 120); } else { tile.x = tileTargetX; tile.y = tileTargetY; emptyTile.x = emptyTargetX; emptyTile.y = emptyTargetY; } return true; } return false; } // --- Input Handling --- // Find which tile was pressed function getTileAtPos(x, y) { for (var i = 0; i < tileList.length; i++) { var tile = tileList[i]; if (tile.isEmpty) continue; var tx = tile.x, ty = tile.y; var tw = tile.width, th = tile.height; if (x >= tx && x <= tx + tw && y >= ty && y <= ty + th) { return tile; } } return null; } // Handle tap/click on the puzzle game.down = function (x, y, obj) { if (isSolved) return; var tile = getTileAtPos(x, y); if (!tile) return; var moved = swapWithEmpty(tile, true); if (moved) { moveCount++; updateUI(); if (checkSolved()) { isSolved = true; updateUI(); LK.effects.flashScreen(0x44ff44, 800); // Instead of showing YouWin screen, show the New Round button if (typeof newRoundBtn !== 'undefined' && newRoundBtn) { newRoundBtn.visible = true; } // LK.setTimeout(function () { // Original YouWin call removed // LK.showYouWin(); // }, 900); } } }; // --- Game Update --- game.update = function () { // No per-frame logic needed for MVP }; // --- Tutorial Overlay --- var tutorialOverlay = null; var tutorialStep = 0; var tutorialActive = false; // Show tutorial overlay for the first level function showTutorial() { tutorialActive = true; tutorialStep = 0; if (tutorialOverlay) { tutorialOverlay.destroy(); tutorialOverlay = null; } tutorialOverlay = new Container(); var bg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 20, scaleY: 20, x: 2048 / 2, y: 2732 / 2 }); bg.alpha = 0.7; tutorialOverlay.addChild(bg); var tutText = new Text2("Welcome to Puzzle It!\n\nWatch how to move the tiles...", { size: 100, fill: 0xffffff }); tutText.anchor.set(0.5, 0.5); tutText.x = 2048 / 2; tutText.y = 2732 / 2 - 200; tutorialOverlay.addChild(tutText); var tutBtn = new Text2("Got it!", { size: 80, fill: 0x4CAF50 }); tutBtn.anchor.set(0.5, 0.5); tutBtn.x = 2048 / 2; tutBtn.y = 2732 / 2 + 300; tutBtn.interactive = true; tutBtn.buttonMode = true; tutBtn.visible = false; // Hide button initially tutorialOverlay.addChild(tutBtn); // Add the tutorial overlay to the game directly instead of LK.gui.center game.addChild(tutorialOverlay); // Set up the button's click handler after it's added to the display tree tutBtn.down = function (x, y, obj) { hideTutorial(); }; // Start the step-by-step tutorial startTutorialSteps(tutText, tutBtn); } // Start step-by-step tutorial movements function startTutorialSteps(tutText, tutBtn) { var stepDelay = 2000; // 2 seconds between steps var moveDelay = 1000; // 1 second to show movement var steps = [{ text: "Step 1: Move tile 15 up...", tileToMove: function tileToMove() { // Find tile with number 15 for (var i = 0; i < tileList.length; i++) { if (tileList[i].labelText && tileList[i].labelText.text === "15") { return tileList[i]; } } return null; } }, { text: "Step 2: Move tile 14 right...", tileToMove: function tileToMove() { // Find tile with number 14 for (var i = 0; i < tileList.length; i++) { if (tileList[i].labelText && tileList[i].labelText.text === "14") { return tileList[i]; } } return null; } }, { text: "Step 3: Move tile 11 down...", tileToMove: function tileToMove() { // Find tile with number 11 for (var i = 0; i < tileList.length; i++) { if (tileList[i].labelText && tileList[i].labelText.text === "11") { return tileList[i]; } } return null; } }]; var currentStep = 0; function performNextStep() { if (currentStep >= steps.length) { // Tutorial complete tutText.setText("Great! Now you try.\n\nTap a tile next to the empty space to move it.\n\nPut the numbers in order to win."); tutBtn.visible = true; return; } var step = steps[currentStep]; tutText.setText(step.text); // Wait a moment, then perform the move LK.setTimeout(function () { var tile = step.tileToMove(); if (tile && swapWithEmpty(tile, true)) { // Move was successful, wait for animation then continue LK.setTimeout(function () { currentStep++; performNextStep(); }, moveDelay); } else { // Move failed, skip to next step currentStep++; performNextStep(); } }, stepDelay); } // Start the tutorial steps performNextStep(); } // Hide tutorial overlay function hideTutorial() { tutorialActive = false; if (tutorialOverlay) { game.removeChild(tutorialOverlay); tutorialOverlay.destroy(); tutorialOverlay = null; } } // Show background color selection menu function showBackgroundMenu() { if (backgroundMenu) { hideBackgroundMenu(); return; } backgroundMenu = new Container(); // Semi-transparent background var menuBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 12, scaleY: 8, x: 2048 / 2, y: 2732 / 2 }); menuBg.alpha = 0.9; menuBg.tint = 0x000000; backgroundMenu.addChild(menuBg); // Title var titleText = new Text2("Choose Background", { size: 80, fill: 0xFFFFFF }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 2732 / 2 - 300; backgroundMenu.addChild(titleText); // Color options var colorsPerRow = 4; var colorSize = 120; var colorSpacing = 40; var startX = 2048 / 2 - (colorsPerRow * colorSize + (colorsPerRow - 1) * colorSpacing) / 2 + colorSize / 2; var startY = 2732 / 2 - 100; for (var i = 0; i < backgroundColors.length; i++) { var row = Math.floor(i / colorsPerRow); var col = i % colorsPerRow; var colorBtn = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: colorSize / 100, scaleY: colorSize / 100, x: startX + col * (colorSize + colorSpacing), y: startY + row * (colorSize + colorSpacing) }); if (backgroundColors[i] === 'rainbow') { // Create a rainbow gradient effect for the rainbow button colorBtn.tint = 0xFF0000; // Start with red, will be animated // Add a visual indicator that this is the rainbow option var rainbowText = new Text2("đ", { size: 60, fill: 0xFFFFFF }); rainbowText.anchor.set(0.5, 0.5); rainbowText.x = 0; rainbowText.y = 0; colorBtn.addChild(rainbowText); } else { colorBtn.tint = backgroundColors[i]; } colorBtn.interactive = true; colorBtn.buttonMode = true; colorBtn.colorIndex = i; // Add selection indicator for current background if (i === currentBgIndex) { var indicator = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: (colorSize + 20) / 100, scaleY: (colorSize + 20) / 100, x: 0, y: 0 }); indicator.tint = 0xFFD700; indicator.alpha = 0.8; colorBtn.addChild(indicator); } (function (colorIndex) { colorBtn.down = function (x, y, obj) { currentBgIndex = colorIndex; if (backgroundColors[colorIndex] === 'rainbow') { startRainbowBackground(); } else { stopRainbowBackground(); game.setBackgroundColor(backgroundColors[colorIndex]); } hideBackgroundMenu(); }; })(i); backgroundMenu.addChild(colorBtn); } // Close button var closeBtn = new Text2("Close", { size: 70, fill: 0xFF4444 }); closeBtn.anchor.set(0.5, 0.5); closeBtn.x = 2048 / 2; closeBtn.y = 2732 / 2 + 250; closeBtn.interactive = true; closeBtn.buttonMode = true; closeBtn.down = function (x, y, obj) { hideBackgroundMenu(); }; backgroundMenu.addChild(closeBtn); game.addChild(backgroundMenu); } // Hide background selection menu function hideBackgroundMenu() { if (backgroundMenu) { game.removeChild(backgroundMenu); backgroundMenu.destroy(); backgroundMenu = null; } } // Start rainbow background animation function startRainbowBackground() { stopRainbowBackground(); // Stop any existing rainbow rainbowInterval = LK.setInterval(function () { rainbowHue = (rainbowHue + 2) % 360; // Increment hue var rgb = hslToRgb(rainbowHue / 360, 0.8, 0.5); // High saturation, medium lightness var color = rgb[0] << 16 | rgb[1] << 8 | rgb[2]; game.setBackgroundColor(color); }, 50); // Update every 50ms for smooth animation } // Stop rainbow background animation function stopRainbowBackground() { if (rainbowInterval) { LK.clearInterval(rainbowInterval); rainbowInterval = null; } } // --- Start Game --- createPuzzle(); ; // Show tutorial only for the first level (first time game is loaded) showTutorial(); // Block input while tutorial is active var origGameDown = game.down; game.down = function (x, y, obj) { if (tutorialActive) return; origGameDown(x, y, obj); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// PuzzleTile: Represents a single tile in the puzzle grid.
var PuzzleTile = Container.expand(function () {
var self = Container.call(this);
// Properties
self.gridX = 0; // grid position x
self.gridY = 0; // grid position y
self.correctX = 0; // correct grid position x
self.correctY = 0; // correct grid position y
self.tileIndex = 0; // index in the tile array
self.isEmpty = false; // is this the empty tile?
// The tile's visual
var tileAsset = self.attachAsset('tile', {
anchorX: 0,
anchorY: 0
});
// For MVP, we color each tile differently for visual distinction
self.setColor = function (color) {
tileAsset.color = color;
// Use tween to smoothly animate the tint change for more vibrant effect
tween(tileAsset, {
tint: color
}, {
duration: 300,
easing: tween.easeOut
});
};
// Set the label (number) for the tile
self.setLabel = function (label) {
if (self.labelText) {
self.labelText.setText(label);
} else {
var txt = new Text2(label + '', {
size: 120,
fill: 0x222222
});
txt.anchor.set(0.5, 0.5);
txt.x = tileAsset.width / 2;
txt.y = tileAsset.height / 2;
self.addChild(txt);
self.labelText = txt;
}
};
// Hide label for empty tile
self.hideLabel = function () {
if (self.labelText) {
self.labelText.visible = false;
}
};
// Show label
self.showLabel = function () {
if (self.labelText) {
self.labelText.visible = true;
}
};
// Animate to a new position
self.moveTo = function (x, y, duration) {
tween(self, {
x: x,
y: y
}, {
duration: duration || 200,
easing: tween.cubicOut
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// In the future, this can be replaced with a real image or a procedurally generated pattern.
// For MVP, we use a colored box as a placeholder for each tile, with a random color per game.
// We'll use a single image asset for the puzzle, which will be randomly generated each game.
// --- Puzzle Settings ---
var gridSize = 4; // 4x4 grid (15-puzzle style)
var tileSize = 400; // px, will be scaled to fit screen
var tileSpacing = 12; // px gap between tiles
var puzzleOffsetX = 0;
var puzzleOffsetY = 0;
var puzzleWidth = gridSize * tileSize + (gridSize - 1) * tileSpacing;
var puzzleHeight = gridSize * tileSize + (gridSize - 1) * tileSpacing;
// --- State ---
var tiles = []; // 2D array [y][x] of PuzzleTile
var tileList = []; // flat array of all PuzzleTile
var emptyTile = null; // reference to the empty tile
var moveCount = 0;
var startTime = 0;
var timerInterval = null;
var isSolved = false;
// --- UI ---
var moveText = new Text2('Moves: 0', {
size: 90,
fill: 0xFFFFFF
});
moveText.anchor.set(0, 0); // Anchor to top-left for easier positioning
LK.gui.topLeft.addChild(moveText); // Add to topLeft GUI container
var timeText = new Text2('Time: 0s', {
size: 90,
fill: 0xFFFFFF
});
timeText.anchor.set(0.5, 0);
LK.gui.top.addChild(timeText); // Keep timeText in the top-center
// Position UI
moveText.x = 20; // Small margin from the left edge
moveText.y = 120; // Position below the platform menu icon area
timeText.x = LK.gui.top.width / 2; // Center timeText
timeText.y = 20;
// --- Remix Button ---
var remixBtn = new Text2('Remix', {
size: 90,
fill: 0xFFD700
});
remixBtn.anchor.set(0.5, 0);
remixBtn.x = LK.gui.top.width / 2;
remixBtn.y = 120;
remixBtn.interactive = true;
remixBtn.buttonMode = true;
remixBtn.down = function (x, y, obj) {
if (isSolved) return;
shufflePuzzle();
moveCount = 0;
startTime = Date.now();
updateUI();
};
LK.gui.top.addChild(remixBtn);
// --- New Round Button ---
var newRoundBtn = new Text2('New Round', {
size: 90,
fill: 0x4CAF50 // A nice green color for the new round button
});
newRoundBtn.anchor.set(0.5, 0);
newRoundBtn.x = LK.gui.top.width / 2;
newRoundBtn.y = remixBtn.y + remixBtn.height + 30; // Position below the Remix button with some padding
newRoundBtn.interactive = true;
newRoundBtn.buttonMode = true;
newRoundBtn.visible = false; // Initially hidden
newRoundBtn.down = function (x, y, obj) {
createPuzzle(); // This will re-initialize the game, hide this button, and reset isSolved
};
LK.gui.top.addChild(newRoundBtn);
// --- Background Menu Button ---
var bgMenuBtn = new Text2('BG', {
size: 60,
fill: 0xFFFFFF
});
bgMenuBtn.anchor.set(1, 0);
bgMenuBtn.x = LK.gui.topRight.width - 20;
bgMenuBtn.y = 20;
bgMenuBtn.interactive = true;
bgMenuBtn.buttonMode = true;
bgMenuBtn.down = function (x, y, obj) {
showBackgroundMenu();
};
LK.gui.topRight.addChild(bgMenuBtn);
// --- Background Selection Variables ---
var backgroundMenu = null;
var backgroundColors = [0x222222,
// Dark gray (default)
0x00FF00,
// Bright green
0x0080FF,
// Bright blue
0xFF00FF,
// Bright magenta
0xFF0040,
// Bright red
0xFF8000,
// Bright orange
0x8000FF,
// Bright purple
'rainbow' // Rainbow option
];
var currentBgIndex = 0;
// --- Rainbow Background Variables ---
var rainbowInterval = null;
var rainbowHue = 0;
// --- Helper Functions ---
// Generate a random color palette for the puzzle
function generateColorPalette(n) {
var colors = [];
var baseHue = Math.floor(Math.random() * 360);
for (var i = 0; i < n; i++) {
// HSL to RGB conversion with more vibrant saturation and lightness
var hue = (baseHue + i * (360 / n)) % 360;
var rgb = hslToRgb(hue / 360, 0.9, 0.7); // Increased saturation to 0.9 and lightness to 0.7
var color = rgb[0] << 16 | rgb[1] << 8 | rgb[2];
colors.push(color);
}
return colors;
}
// HSL to RGB helper
function hslToRgb(h, s, l) {
var r, g, b;
if (s == 0) {
r = g = b = l;
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
// Shuffle an array in-place
function shuffleArray(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
// Check if the puzzle is solved
function checkSolved() {
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
var tile = tiles[y][x];
if (tile.gridX !== tile.correctX || tile.gridY !== tile.correctY) {
return false;
}
}
}
return true;
}
// Update move and time UI
function updateUI() {
moveText.setText('Moves: ' + moveCount);
var elapsed = Math.floor((Date.now() - startTime) / 1000);
timeText.setText('Time: ' + elapsed + 's');
}
// --- Puzzle Generation ---
function createPuzzle() {
if (typeof newRoundBtn !== 'undefined' && newRoundBtn) {
newRoundBtn.visible = false;
}
if (typeof hideTutorial === 'function') {
hideTutorial();
}
// Remove old tiles
for (var i = 0; i < tileList.length; i++) {
tileList[i].destroy();
}
tiles = [];
tileList = [];
emptyTile = null;
isSolved = false;
moveCount = 0;
updateUI();
// Generate color palette
var numTiles = gridSize * gridSize;
var colors = generateColorPalette(numTiles - 1);
// Calculate tile size to fit screen
var maxWidth = 2048 - 200;
var maxHeight = 2732 - 400;
tileSize = Math.floor(Math.min((maxWidth - (gridSize - 1) * tileSpacing) / gridSize, (maxHeight - (gridSize - 1) * tileSpacing) / gridSize));
puzzleWidth = gridSize * tileSize + (gridSize - 1) * tileSpacing;
puzzleHeight = gridSize * tileSize + (gridSize - 1) * tileSpacing;
puzzleOffsetX = Math.floor((2048 - puzzleWidth) / 2);
puzzleOffsetY = Math.floor((2732 - puzzleHeight) / 2) + 80;
// Create tiles in solved order
var idx = 0;
for (var y = 0; y < gridSize; y++) {
tiles[y] = [];
for (var x = 0; x < gridSize; x++) {
var tile = new PuzzleTile();
tile.gridX = x;
tile.gridY = y;
tile.correctX = x;
tile.correctY = y;
tile.tileIndex = idx;
tile.x = puzzleOffsetX + x * (tileSize + tileSpacing);
tile.y = puzzleOffsetY + y * (tileSize + tileSpacing);
tile.width = tileSize;
tile.height = tileSize;
tile.setColor(colors[idx] || 0x222222);
tile.setLabel(idx + 1);
tile.isEmpty = false;
tileList.push(tile);
tiles[y][x] = tile;
game.addChild(tile);
idx++;
}
}
// Make last tile the empty tile
var lastTile = tiles[gridSize - 1][gridSize - 1];
lastTile.isEmpty = true;
lastTile.setColor(0x222222);
lastTile.hideLabel();
emptyTile = lastTile;
// Shuffle tiles
shufflePuzzle();
// Start timer
startTime = Date.now();
if (timerInterval) LK.clearInterval(timerInterval);
timerInterval = LK.setInterval(updateUI, 500);
}
// Shuffle the puzzle by simulating random valid moves
function shufflePuzzle() {
var moves = [[0, 1], [1, 0], [0, -1], [-1, 0]];
var prevX = emptyTile.gridX,
prevY = emptyTile.gridY;
var shuffleCount = 200 + Math.floor(Math.random() * 100);
for (var i = 0; i < shuffleCount; i++) {
var possible = [];
for (var d = 0; d < moves.length; d++) {
var nx = emptyTile.gridX + moves[d][0];
var ny = emptyTile.gridY + moves[d][1];
if (nx >= 0 && nx < gridSize && ny >= 0 && ny < gridSize) {
if (!(nx === prevX && ny === prevY)) {
possible.push([nx, ny]);
}
}
}
if (possible.length === 0) continue;
var pick = possible[Math.floor(Math.random() * possible.length)];
var tile = tiles[pick[1]][pick[0]];
swapWithEmpty(tile, false);
prevX = emptyTile.gridX;
prevY = emptyTile.gridY;
}
// After shuffling, update all tile positions visually
for (var y = 0; y < gridSize; y++) {
for (var x = 0; x < gridSize; x++) {
var tile = tiles[y][x];
tile.x = puzzleOffsetX + x * (tileSize + tileSpacing);
tile.y = puzzleOffsetY + y * (tileSize + tileSpacing);
tile.gridX = x;
tile.gridY = y;
}
}
}
// Swap a tile with the empty tile (if adjacent)
function swapWithEmpty(tile, animate) {
if (tile.isEmpty) return false;
var dx = Math.abs(tile.gridX - emptyTile.gridX);
var dy = Math.abs(tile.gridY - emptyTile.gridY);
if (dx === 1 && dy === 0 || dx === 0 && dy === 1) {
// Swap in tiles array
var tx = tile.gridX,
ty = tile.gridY;
var ex = emptyTile.gridX,
ey = emptyTile.gridY;
tiles[ty][tx] = emptyTile;
tiles[ey][ex] = tile;
// Swap grid positions
var tmpX = tile.gridX,
tmpY = tile.gridY;
tile.gridX = emptyTile.gridX;
tile.gridY = emptyTile.gridY;
emptyTile.gridX = tmpX;
emptyTile.gridY = tmpY;
// Animate movement
var tileTargetX = puzzleOffsetX + tile.gridX * (tileSize + tileSpacing);
var tileTargetY = puzzleOffsetY + tile.gridY * (tileSize + tileSpacing);
var emptyTargetX = puzzleOffsetX + emptyTile.gridX * (tileSize + tileSpacing);
var emptyTargetY = puzzleOffsetY + emptyTile.gridY * (tileSize + tileSpacing);
if (animate !== false) {
tile.moveTo(tileTargetX, tileTargetY, 120);
emptyTile.moveTo(emptyTargetX, emptyTargetY, 120);
} else {
tile.x = tileTargetX;
tile.y = tileTargetY;
emptyTile.x = emptyTargetX;
emptyTile.y = emptyTargetY;
}
return true;
}
return false;
}
// --- Input Handling ---
// Find which tile was pressed
function getTileAtPos(x, y) {
for (var i = 0; i < tileList.length; i++) {
var tile = tileList[i];
if (tile.isEmpty) continue;
var tx = tile.x,
ty = tile.y;
var tw = tile.width,
th = tile.height;
if (x >= tx && x <= tx + tw && y >= ty && y <= ty + th) {
return tile;
}
}
return null;
}
// Handle tap/click on the puzzle
game.down = function (x, y, obj) {
if (isSolved) return;
var tile = getTileAtPos(x, y);
if (!tile) return;
var moved = swapWithEmpty(tile, true);
if (moved) {
moveCount++;
updateUI();
if (checkSolved()) {
isSolved = true;
updateUI();
LK.effects.flashScreen(0x44ff44, 800);
// Instead of showing YouWin screen, show the New Round button
if (typeof newRoundBtn !== 'undefined' && newRoundBtn) {
newRoundBtn.visible = true;
}
// LK.setTimeout(function () { // Original YouWin call removed
// LK.showYouWin();
// }, 900);
}
}
};
// --- Game Update ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Tutorial Overlay ---
var tutorialOverlay = null;
var tutorialStep = 0;
var tutorialActive = false;
// Show tutorial overlay for the first level
function showTutorial() {
tutorialActive = true;
tutorialStep = 0;
if (tutorialOverlay) {
tutorialOverlay.destroy();
tutorialOverlay = null;
}
tutorialOverlay = new Container();
var bg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 20,
scaleY: 20,
x: 2048 / 2,
y: 2732 / 2
});
bg.alpha = 0.7;
tutorialOverlay.addChild(bg);
var tutText = new Text2("Welcome to Puzzle It!\n\nWatch how to move the tiles...", {
size: 100,
fill: 0xffffff
});
tutText.anchor.set(0.5, 0.5);
tutText.x = 2048 / 2;
tutText.y = 2732 / 2 - 200;
tutorialOverlay.addChild(tutText);
var tutBtn = new Text2("Got it!", {
size: 80,
fill: 0x4CAF50
});
tutBtn.anchor.set(0.5, 0.5);
tutBtn.x = 2048 / 2;
tutBtn.y = 2732 / 2 + 300;
tutBtn.interactive = true;
tutBtn.buttonMode = true;
tutBtn.visible = false; // Hide button initially
tutorialOverlay.addChild(tutBtn);
// Add the tutorial overlay to the game directly instead of LK.gui.center
game.addChild(tutorialOverlay);
// Set up the button's click handler after it's added to the display tree
tutBtn.down = function (x, y, obj) {
hideTutorial();
};
// Start the step-by-step tutorial
startTutorialSteps(tutText, tutBtn);
}
// Start step-by-step tutorial movements
function startTutorialSteps(tutText, tutBtn) {
var stepDelay = 2000; // 2 seconds between steps
var moveDelay = 1000; // 1 second to show movement
var steps = [{
text: "Step 1: Move tile 15 up...",
tileToMove: function tileToMove() {
// Find tile with number 15
for (var i = 0; i < tileList.length; i++) {
if (tileList[i].labelText && tileList[i].labelText.text === "15") {
return tileList[i];
}
}
return null;
}
}, {
text: "Step 2: Move tile 14 right...",
tileToMove: function tileToMove() {
// Find tile with number 14
for (var i = 0; i < tileList.length; i++) {
if (tileList[i].labelText && tileList[i].labelText.text === "14") {
return tileList[i];
}
}
return null;
}
}, {
text: "Step 3: Move tile 11 down...",
tileToMove: function tileToMove() {
// Find tile with number 11
for (var i = 0; i < tileList.length; i++) {
if (tileList[i].labelText && tileList[i].labelText.text === "11") {
return tileList[i];
}
}
return null;
}
}];
var currentStep = 0;
function performNextStep() {
if (currentStep >= steps.length) {
// Tutorial complete
tutText.setText("Great! Now you try.\n\nTap a tile next to the empty space to move it.\n\nPut the numbers in order to win.");
tutBtn.visible = true;
return;
}
var step = steps[currentStep];
tutText.setText(step.text);
// Wait a moment, then perform the move
LK.setTimeout(function () {
var tile = step.tileToMove();
if (tile && swapWithEmpty(tile, true)) {
// Move was successful, wait for animation then continue
LK.setTimeout(function () {
currentStep++;
performNextStep();
}, moveDelay);
} else {
// Move failed, skip to next step
currentStep++;
performNextStep();
}
}, stepDelay);
}
// Start the tutorial steps
performNextStep();
}
// Hide tutorial overlay
function hideTutorial() {
tutorialActive = false;
if (tutorialOverlay) {
game.removeChild(tutorialOverlay);
tutorialOverlay.destroy();
tutorialOverlay = null;
}
}
// Show background color selection menu
function showBackgroundMenu() {
if (backgroundMenu) {
hideBackgroundMenu();
return;
}
backgroundMenu = new Container();
// Semi-transparent background
var menuBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 12,
scaleY: 8,
x: 2048 / 2,
y: 2732 / 2
});
menuBg.alpha = 0.9;
menuBg.tint = 0x000000;
backgroundMenu.addChild(menuBg);
// Title
var titleText = new Text2("Choose Background", {
size: 80,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2 - 300;
backgroundMenu.addChild(titleText);
// Color options
var colorsPerRow = 4;
var colorSize = 120;
var colorSpacing = 40;
var startX = 2048 / 2 - (colorsPerRow * colorSize + (colorsPerRow - 1) * colorSpacing) / 2 + colorSize / 2;
var startY = 2732 / 2 - 100;
for (var i = 0; i < backgroundColors.length; i++) {
var row = Math.floor(i / colorsPerRow);
var col = i % colorsPerRow;
var colorBtn = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: colorSize / 100,
scaleY: colorSize / 100,
x: startX + col * (colorSize + colorSpacing),
y: startY + row * (colorSize + colorSpacing)
});
if (backgroundColors[i] === 'rainbow') {
// Create a rainbow gradient effect for the rainbow button
colorBtn.tint = 0xFF0000; // Start with red, will be animated
// Add a visual indicator that this is the rainbow option
var rainbowText = new Text2("đ", {
size: 60,
fill: 0xFFFFFF
});
rainbowText.anchor.set(0.5, 0.5);
rainbowText.x = 0;
rainbowText.y = 0;
colorBtn.addChild(rainbowText);
} else {
colorBtn.tint = backgroundColors[i];
}
colorBtn.interactive = true;
colorBtn.buttonMode = true;
colorBtn.colorIndex = i;
// Add selection indicator for current background
if (i === currentBgIndex) {
var indicator = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: (colorSize + 20) / 100,
scaleY: (colorSize + 20) / 100,
x: 0,
y: 0
});
indicator.tint = 0xFFD700;
indicator.alpha = 0.8;
colorBtn.addChild(indicator);
}
(function (colorIndex) {
colorBtn.down = function (x, y, obj) {
currentBgIndex = colorIndex;
if (backgroundColors[colorIndex] === 'rainbow') {
startRainbowBackground();
} else {
stopRainbowBackground();
game.setBackgroundColor(backgroundColors[colorIndex]);
}
hideBackgroundMenu();
};
})(i);
backgroundMenu.addChild(colorBtn);
}
// Close button
var closeBtn = new Text2("Close", {
size: 70,
fill: 0xFF4444
});
closeBtn.anchor.set(0.5, 0.5);
closeBtn.x = 2048 / 2;
closeBtn.y = 2732 / 2 + 250;
closeBtn.interactive = true;
closeBtn.buttonMode = true;
closeBtn.down = function (x, y, obj) {
hideBackgroundMenu();
};
backgroundMenu.addChild(closeBtn);
game.addChild(backgroundMenu);
}
// Hide background selection menu
function hideBackgroundMenu() {
if (backgroundMenu) {
game.removeChild(backgroundMenu);
backgroundMenu.destroy();
backgroundMenu = null;
}
}
// Start rainbow background animation
function startRainbowBackground() {
stopRainbowBackground(); // Stop any existing rainbow
rainbowInterval = LK.setInterval(function () {
rainbowHue = (rainbowHue + 2) % 360; // Increment hue
var rgb = hslToRgb(rainbowHue / 360, 0.8, 0.5); // High saturation, medium lightness
var color = rgb[0] << 16 | rgb[1] << 8 | rgb[2];
game.setBackgroundColor(color);
}, 50); // Update every 50ms for smooth animation
}
// Stop rainbow background animation
function stopRainbowBackground() {
if (rainbowInterval) {
LK.clearInterval(rainbowInterval);
rainbowInterval = null;
}
}
// --- Start Game ---
createPuzzle();
;
// Show tutorial only for the first level (first time game is loaded)
showTutorial();
// Block input while tutorial is active
var origGameDown = game.down;
game.down = function (x, y, obj) {
if (tutorialActive) return;
origGameDown(x, y, obj);
};