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