/****
* 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;
// --- 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.isEmpty) continue;
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(); ===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,354 @@
-/****
+/****
+* 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: 0x000000
-});
\ No newline at end of file
+ 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;
+// --- 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.isEmpty) continue;
+ 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();
\ No newline at end of file