User prompt
make the tile a little more vibrant ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add rainbow color
User prompt
remove the color yellow and a rainbow
User prompt
make the color more vibrante ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when you open the menu make it bigger
User prompt
give more color choices
User prompt
the background is not changing
User prompt
make a menu button to change the background
User prompt
remove the bug in the tutorial
User prompt
only solve the first row
User prompt
there is a bug in the tutorial
User prompt
there is a bug
User prompt
there is a bug
User prompt
make the tutorial move the block until it is complete in the first level ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
there is a bug when the tutorial move the block in the first level ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make the tutorial move the block step by step ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make the got it button a little bigger
User prompt
there is a bug the tutorial button does not work
User prompt
make the tutorial button work
User prompt
make the tutorial button smaller
User prompt
put the the welcome to puzzle it button in the middle and make it small
User prompt
make a tutorial in the first level
User prompt
when level compele show new round button
User prompt
move the moves: block to the up left
User prompt
add a remix button t remix the block
/**** * 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; tileAsset.tint = color; }; // 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.5, 0); LK.gui.top.addChild(moveText); var timeText = new Text2('Time: 0s', { size: 90, fill: 0xFFFFFF }); timeText.anchor.set(0.5, 0); LK.gui.top.addChild(timeText); // Position UI moveText.x = LK.gui.top.width / 2 - 200; moveText.y = 20; timeText.x = LK.gui.top.width / 2 + 200; 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); // --- 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 var hue = (baseHue + i * (360 / n)) % 360; var rgb = hslToRgb(hue / 360, 0.6, 0.6); 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() { // 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); LK.setTimeout(function () { LK.showYouWin(); }, 900); } } }; // --- Game Update --- game.update = function () { // No per-frame logic needed for MVP }; // --- Start Game --- createPuzzle();
/****
* 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;
tileAsset.tint = color;
};
// 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.5, 0);
LK.gui.top.addChild(moveText);
var timeText = new Text2('Time: 0s', {
size: 90,
fill: 0xFFFFFF
});
timeText.anchor.set(0.5, 0);
LK.gui.top.addChild(timeText);
// Position UI
moveText.x = LK.gui.top.width / 2 - 200;
moveText.y = 20;
timeText.x = LK.gui.top.width / 2 + 200;
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);
// --- 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
var hue = (baseHue + i * (360 / n)) % 360;
var rgb = hslToRgb(hue / 360, 0.6, 0.6);
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() {
// 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);
LK.setTimeout(function () {
LK.showYouWin();
}, 900);
}
}
};
// --- Game Update ---
game.update = function () {
// No per-frame logic needed for MVP
};
// --- Start Game ---
createPuzzle();