/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // UnicornCell: Represents a single cell in the Sudoku grid var UnicornCell = Container.expand(function () { var self = Container.call(this); // Cell background (for selection/highlight) var bg = self.attachAsset('cellBg', { width: cellSize - 8, height: cellSize - 8, color: 0xffffff, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.bg = bg; // The unicorn icon (if any) self.icon = null; self.unicornIndex = null; // 0-8, or null if empty // Row, col for logic self.row = 0; self.col = 0; // Is this a given (pre-filled) cell? self.isGiven = false; // Show error highlight self.showError = function (on) { if (on) { tween(self.bg, { color: 0xffaaaa }, { duration: 200, easing: tween.easeIn }); } else { tween(self.bg, { color: 0xffffff }, { duration: 200, easing: tween.easeOut }); } }; // Set unicorn icon self.setUnicorn = function (idx, animate) { if (self.icon) { self.removeChild(self.icon); self.icon = null; } self.unicornIndex = idx; if (idx !== null) { var icon = self.attachAsset('unicorn' + idx, { width: cellSize * 0.7, height: cellSize * 0.7, anchorX: 0.5, anchorY: 0.5 }); self.icon = icon; if (animate) { icon.scaleX = 0.2; icon.scaleY = 0.2; tween(icon, { scaleX: 1, scaleY: 1 }, { duration: 180, easing: tween.bounceOut }); } } }; // Remove unicorn icon self.clearUnicorn = function () { if (self.icon) { self.removeChild(self.icon); self.icon = null; } self.unicornIndex = null; }; // Selection highlight self.setSelected = function (on) { if (on) { tween(self.bg, { color: 0xcce6ff }, { duration: 120 }); } else { tween(self.bg, { color: 0xffffff }, { duration: 120 }); } }; return self; }); // UnicornPicker: The row of unicorn icons for selection var UnicornPicker = Container.expand(function () { var self = Container.call(this); self.icons = []; self.visible = false; self.selectedIdx = null; // Add vertical space above icons var pickerTopPadding = 36; // extra space above icons // Create 4 unicorn icons for (var i = 0; i < 4; i++) { var icon = self.attachAsset('unicorn' + i, { width: pickerIconSize, height: pickerIconSize, anchorX: -1, anchorY: -1, x: i * (pickerIconSize + pickerGap), y: pickerTopPadding // shift icons down by padding }); icon.idx = i; self.icons.push(icon); } // Background var pickerBg = self.attachAsset('pickerBg', { width: 4 * pickerIconSize + 3 * pickerGap + 32, height: pickerIconSize + 32 + pickerTopPadding, // increase height for top padding color: 0xf7e6ff, shape: 'box', anchorX: 0, anchorY: 0 }); pickerBg.x = -16; pickerBg.y = -16; self.setChildIndex(pickerBg, 0); // Highlight selected self.setSelected = function (idx) { for (var i = 0; i < self.icons.length; i++) { self.icons[i].alpha = i === idx ? 1 : 0.7; self.icons[i].scaleX = self.icons[i].scaleY = i === idx ? 1.15 : 1; } self.selectedIdx = idx; }; // Show/hide self.show = function (x, y) { self.x = x; self.y = y; self.visible = true; }; self.hide = function () { self.visible = false; }; return self; }); /**** * Initialize Game ****/ /**** * Game Constants ****/ // Board layout // --- Game Initialization --- var game = new LK.Game({ backgroundColor: 0xf7e6ff }); /**** * Game Code ****/ // Add unicorn image assets (replace with your actual unicorn image asset ids or use placeholder images) // Board layout /**** * Game Constants ****/ // --- Game State --- var gridSize = 4; var cellSize = 320; // 4*320 = 1280, fits well in 2048 width var boardSize = cellSize * gridSize; var boardOffsetX = (2048 - boardSize) / 2; var boardOffsetY = 320; // Picker var pickerIconSize = 180; var pickerGap = 32; // For error highlight var errorFlashDuration = 400; // --- Asset Initialization --- // 4 unicorn icons for (var i = 0; i < 4; i++) {} // Cell background // Picker background var board = []; // 2D array of UnicornCell var givenBoard = []; // 2D array of unicorn indices (0-3) or null var solutionBoard = []; // 2D array of unicorn indices (0-3) var selectedCell = null; var picker = null; var timerTxt = null; var startTime = null; var timerInterval = null; var errorCells = []; var completed = false; // --- Sudoku Puzzle Generation --- // For MVP, use a static puzzle and solution (easy level) // 4x4 puzzle, unicorn indices 0-3, 2x2 boxes var staticPuzzle = [[null, 1, null, 2], [3, null, null, null], [null, null, null, 1], [2, null, 3, null]]; // Solution for the above puzzle (0-3 for unicorn indices, valid for 2x2 boxes) var staticSolution = [[0, 1, 2, 3], [3, 2, 1, 0], [1, 3, 0, 2], [2, 0, 3, 1]]; // --- Helper Functions --- // Returns true if placing unicornIdx at (row, col) is valid function isValidMove(row, col, unicornIdx) { // Check row for (var c = 0; c < gridSize; c++) { if (c !== col && board[row][c].unicornIndex === unicornIdx) { return false; } } // Check col for (var r = 0; r < gridSize; r++) { if (r !== row && board[r][col].unicornIndex === unicornIdx) { return false; } } // Check 2x2 box for 4x4 var boxRow = Math.floor(row / 2) * 2; var boxCol = Math.floor(col / 2) * 2; for (var r = boxRow; r < boxRow + 2; r++) { for (var c = boxCol; c < boxCol + 2; c++) { if ((r !== row || c !== col) && board[r][c].unicornIndex === unicornIdx) { return false; } } } return true; } // Returns true if the board is completely and correctly filled function isBoardComplete() { for (var r = 0; r < gridSize; r++) { for (var c = 0; c < gridSize; c++) { var cell = board[r][c]; if (cell.unicornIndex === null) { return false; } if (!isValidMove(r, c, cell.unicornIndex)) { return false; } } } return true; } // Highlight errors for current board state function highlightErrors() { // Clear previous for (var i = 0; i < errorCells.length; i++) { errorCells[i].showError(false); } errorCells = []; // Check for errors for (var r = 0; r < gridSize; r++) { for (var c = 0; c < gridSize; c++) { var cell = board[r][c]; if (cell.unicornIndex === null) { continue; } // Only highlight if not a given if (!cell.isGiven && !isValidMove(r, c, cell.unicornIndex)) { cell.showError(true); errorCells.push(cell); } } } } // Deselect all cells function deselectAllCells() { for (var r = 0; r < gridSize; r++) { for (var c = 0; c < gridSize; c++) { if (typeof board[r][c].setSelected === "function") { board[r][c].setSelected(false); } } } selectedCell = null; } // --- UI Setup --- // Board container var boardContainer = new Container(); boardContainer.x = boardOffsetX; boardContainer.y = boardOffsetY; game.addChild(boardContainer); // Draw grid lines (thick for boxes, thin for cells) for (var i = 0; i <= gridSize; i++) { // For 4x4, thick lines at 0, 2, 4 (every 2 cells) var isBoxLine = i % 2 === 0; var lineW = isBoxLine ? 8 : 3; var lineColor = isBoxLine ? 0x9b59b6 : 0xd1b3e0; // Vertical var vLine = LK.getAsset('vLine' + i, { width: lineW, height: boardSize, color: lineColor, shape: 'box', anchorX: 0.5, anchorY: 0 }); vLine.x = i * cellSize; vLine.y = 0; boardContainer.addChild(vLine); // Horizontal var hLine = LK.getAsset('hLine' + i, { width: boardSize, height: lineW, color: lineColor, shape: 'box', anchorX: 0, anchorY: 0.5 }); hLine.x = 0; hLine.y = i * cellSize; boardContainer.addChild(hLine); } // Create cells for (var r = 0; r < gridSize; r++) { board[r] = []; for (var c = 0; c < gridSize; c++) { // Ensure UnicornCell is defined before this point and all cells are instances of UnicornCell var cell = new UnicornCell(); cell.x = c * cellSize + cellSize / 2; cell.y = r * cellSize + cellSize / 2; cell.row = r; cell.col = c; boardContainer.addChild(cell); board[r][c] = cell; } } // Fill in given cells and solution for (var r = 0; r < gridSize; r++) { givenBoard[r] = []; solutionBoard[r] = []; for (var c = 0; c < gridSize; c++) { var idx = staticPuzzle[r][c]; var cell = board[r][c]; if (idx !== null) { cell.setUnicorn(idx, false); cell.isGiven = true; cell.bg.alpha = 0.7; } givenBoard[r][c] = idx; solutionBoard[r][c] = staticSolution[r][c]; } } // --- Picker UI --- // Place picker at the bottom of the board, centered horizontally, with margin from the board picker = new UnicornPicker(); picker.visible = false; // Calculate picker position: center horizontally, below the board with margin var pickerMargin = 60; picker.x = boardOffsetX + (boardSize - (9 * pickerIconSize + 8 * pickerGap + 32)) / 2; picker.y = boardOffsetY + boardSize + pickerMargin; game.addChild(picker); // --- Title & Timer UI (side by side) --- var titleTimerContainer = new Container(); // Title var titleTxt = new Text2('Unicorn Sudoku', { size: 110, fill: 0xA569BD }); titleTxt.anchor.set(0, 0); // left align titleTxt.x = 0; titleTxt.y = 0; titleTimerContainer.addChild(titleTxt); // Timer timerTxt = new Text2('00:00', { size: 90, fill: 0x7D3C98 }); timerTxt.anchor.set(1, 0); // right align timerTxt.x = 900; // enough space for title, adjust as needed timerTxt.y = 10; titleTimerContainer.addChild(timerTxt); // Set container width and center it titleTimerContainer.x = (2048 - 900) / 2; titleTimerContainer.y = 0; LK.gui.top.addChild(titleTimerContainer); // --- Instructions UI --- var instrTxt = new Text2('Tap a cell, then pick a unicorn to fill it!', { size: 60, fill: 0x7D3C98 }); instrTxt.anchor.set(0.5, 0); instrTxt.y = 120; LK.gui.top.addChild(instrTxt); // --- Event Handlers --- // Cell tap: select cell and show picker function onCellDown(x, y, obj) { if (completed) { return; } var cell = obj; if (cell.isGiven) { return; } // If cell is already filled, do nothing if (cell.unicornIndex !== null) { return; } deselectAllCells(); if (typeof cell.setSelected === "function") { cell.setSelected(true); } selectedCell = cell; // Determine possible unicorns for this cell var possible = []; if (cell.unicornIndex === null) { for (var i = 0; i < 4; i++) { if (isValidMove(cell.row, cell.col, i)) { possible.push(i); } } } else { // If cell is not empty, allow all (for clearing) for (var i = 0; i < 4; i++) { possible.push(i); } } // Show picker below the cell, but keep inside screen // Defensive: cell.position may be undefined, so fallback to cell.x/cell.y var cellPos = cell && cell.position ? cell.position : { x: cell.x, y: cell.y }; var global = boardContainer.toGlobal(cellPos); var pickerX = Math.max(40, Math.min(global.x - pickerIconSize * 2, 2048 - pickerIconSize * 4 - 40)); var pickerY = Math.min(global.y + cellSize / 2 + 30, 2732 - pickerIconSize - 80); picker.show(pickerX, pickerY); picker.setSelected(null); // Show only possible unicorns in picker for (var i = 0; i < picker.icons.length; i++) { picker.icons[i].visible = possible.indexOf(i) !== -1; } } // Picker tap: set unicorn in selected cell function onPickerDown(x, y, obj) { if (!selectedCell || completed) { return; } var idx = obj.idx; if (idx === undefined) { return; } // If already has this unicorn, clear if (selectedCell.unicornIndex === idx) { selectedCell.clearUnicorn(); picker.setSelected(null); } else { // Only allow placing if the unicorn is correct for this cell var correctIdx = solutionBoard[selectedCell.row][selectedCell.col]; if (idx === correctIdx) { selectedCell.setUnicorn(idx, true); picker.setSelected(idx); // Remove this unicorn from picker options for this cell picker.icons[idx].visible = false; } else { // Optionally, flash error or ignore selectedCell.showError(true); LK.setTimeout(function () { selectedCell.showError(false); }, errorFlashDuration); } } highlightErrors(); // Check for win if (isBoardComplete()) { completed = true; picker.hide(); deselectAllCells(); LK.effects.flashScreen(0x7d3c98, 800); LK.setTimeout(function () { LK.showYouWin(); }, 900); } } // Hide picker if tap outside function onGameDown(x, y, obj) { // If tap is not on a cell or picker, hide picker and deselect if (obj && (obj instanceof UnicornCell || obj === picker || picker.icons.indexOf(obj) !== -1)) { return; } picker.hide(); deselectAllCells(); } // --- Attach event handlers --- // Attach to each cell for (var r = 0; r < gridSize; r++) { for (var c = 0; c < gridSize; c++) { var cell = board[r][c]; cell.down = onCellDown; } } // Attach to picker icons for (var i = 0; i < 4; i++) { picker.icons[i].down = onPickerDown; } // Attach to game for outside tap game.down = onGameDown; // --- Timer Logic --- function formatTime(secs) { var m = Math.floor(secs / 60); var s = secs % 60; return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s; } function updateTimer() { if (completed) { return; } var now = Date.now(); var elapsed = Math.floor((now - startTime) / 1000); timerTxt.setText(formatTime(elapsed)); } // --- Game Start --- function startGame() { completed = false; deselectAllCells(); picker.hide(); highlightErrors(); startTime = Date.now(); timerTxt.setText('00:00'); if (timerInterval) { LK.clearInterval(timerInterval); } timerInterval = LK.setInterval(updateTimer, 1000); } startGame(); // --- Game Update (not used for logic, but required) --- game.update = function () { // No per-frame logic needed for MVP };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// UnicornCell: Represents a single cell in the Sudoku grid
var UnicornCell = Container.expand(function () {
var self = Container.call(this);
// Cell background (for selection/highlight)
var bg = self.attachAsset('cellBg', {
width: cellSize - 8,
height: cellSize - 8,
color: 0xffffff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.bg = bg;
// The unicorn icon (if any)
self.icon = null;
self.unicornIndex = null; // 0-8, or null if empty
// Row, col for logic
self.row = 0;
self.col = 0;
// Is this a given (pre-filled) cell?
self.isGiven = false;
// Show error highlight
self.showError = function (on) {
if (on) {
tween(self.bg, {
color: 0xffaaaa
}, {
duration: 200,
easing: tween.easeIn
});
} else {
tween(self.bg, {
color: 0xffffff
}, {
duration: 200,
easing: tween.easeOut
});
}
};
// Set unicorn icon
self.setUnicorn = function (idx, animate) {
if (self.icon) {
self.removeChild(self.icon);
self.icon = null;
}
self.unicornIndex = idx;
if (idx !== null) {
var icon = self.attachAsset('unicorn' + idx, {
width: cellSize * 0.7,
height: cellSize * 0.7,
anchorX: 0.5,
anchorY: 0.5
});
self.icon = icon;
if (animate) {
icon.scaleX = 0.2;
icon.scaleY = 0.2;
tween(icon, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.bounceOut
});
}
}
};
// Remove unicorn icon
self.clearUnicorn = function () {
if (self.icon) {
self.removeChild(self.icon);
self.icon = null;
}
self.unicornIndex = null;
};
// Selection highlight
self.setSelected = function (on) {
if (on) {
tween(self.bg, {
color: 0xcce6ff
}, {
duration: 120
});
} else {
tween(self.bg, {
color: 0xffffff
}, {
duration: 120
});
}
};
return self;
});
// UnicornPicker: The row of unicorn icons for selection
var UnicornPicker = Container.expand(function () {
var self = Container.call(this);
self.icons = [];
self.visible = false;
self.selectedIdx = null;
// Add vertical space above icons
var pickerTopPadding = 36; // extra space above icons
// Create 4 unicorn icons
for (var i = 0; i < 4; i++) {
var icon = self.attachAsset('unicorn' + i, {
width: pickerIconSize,
height: pickerIconSize,
anchorX: -1,
anchorY: -1,
x: i * (pickerIconSize + pickerGap),
y: pickerTopPadding // shift icons down by padding
});
icon.idx = i;
self.icons.push(icon);
}
// Background
var pickerBg = self.attachAsset('pickerBg', {
width: 4 * pickerIconSize + 3 * pickerGap + 32,
height: pickerIconSize + 32 + pickerTopPadding,
// increase height for top padding
color: 0xf7e6ff,
shape: 'box',
anchorX: 0,
anchorY: 0
});
pickerBg.x = -16;
pickerBg.y = -16;
self.setChildIndex(pickerBg, 0);
// Highlight selected
self.setSelected = function (idx) {
for (var i = 0; i < self.icons.length; i++) {
self.icons[i].alpha = i === idx ? 1 : 0.7;
self.icons[i].scaleX = self.icons[i].scaleY = i === idx ? 1.15 : 1;
}
self.selectedIdx = idx;
};
// Show/hide
self.show = function (x, y) {
self.x = x;
self.y = y;
self.visible = true;
};
self.hide = function () {
self.visible = false;
};
return self;
});
/****
* Initialize Game
****/
/****
* Game Constants
****/
// Board layout
// --- Game Initialization ---
var game = new LK.Game({
backgroundColor: 0xf7e6ff
});
/****
* Game Code
****/
// Add unicorn image assets (replace with your actual unicorn image asset ids or use placeholder images)
// Board layout
/****
* Game Constants
****/
// --- Game State ---
var gridSize = 4;
var cellSize = 320; // 4*320 = 1280, fits well in 2048 width
var boardSize = cellSize * gridSize;
var boardOffsetX = (2048 - boardSize) / 2;
var boardOffsetY = 320;
// Picker
var pickerIconSize = 180;
var pickerGap = 32;
// For error highlight
var errorFlashDuration = 400;
// --- Asset Initialization ---
// 4 unicorn icons
for (var i = 0; i < 4; i++) {}
// Cell background
// Picker background
var board = []; // 2D array of UnicornCell
var givenBoard = []; // 2D array of unicorn indices (0-3) or null
var solutionBoard = []; // 2D array of unicorn indices (0-3)
var selectedCell = null;
var picker = null;
var timerTxt = null;
var startTime = null;
var timerInterval = null;
var errorCells = [];
var completed = false;
// --- Sudoku Puzzle Generation ---
// For MVP, use a static puzzle and solution (easy level)
// 4x4 puzzle, unicorn indices 0-3, 2x2 boxes
var staticPuzzle = [[null, 1, null, 2], [3, null, null, null], [null, null, null, 1], [2, null, 3, null]];
// Solution for the above puzzle (0-3 for unicorn indices, valid for 2x2 boxes)
var staticSolution = [[0, 1, 2, 3], [3, 2, 1, 0], [1, 3, 0, 2], [2, 0, 3, 1]];
// --- Helper Functions ---
// Returns true if placing unicornIdx at (row, col) is valid
function isValidMove(row, col, unicornIdx) {
// Check row
for (var c = 0; c < gridSize; c++) {
if (c !== col && board[row][c].unicornIndex === unicornIdx) {
return false;
}
}
// Check col
for (var r = 0; r < gridSize; r++) {
if (r !== row && board[r][col].unicornIndex === unicornIdx) {
return false;
}
}
// Check 2x2 box for 4x4
var boxRow = Math.floor(row / 2) * 2;
var boxCol = Math.floor(col / 2) * 2;
for (var r = boxRow; r < boxRow + 2; r++) {
for (var c = boxCol; c < boxCol + 2; c++) {
if ((r !== row || c !== col) && board[r][c].unicornIndex === unicornIdx) {
return false;
}
}
}
return true;
}
// Returns true if the board is completely and correctly filled
function isBoardComplete() {
for (var r = 0; r < gridSize; r++) {
for (var c = 0; c < gridSize; c++) {
var cell = board[r][c];
if (cell.unicornIndex === null) {
return false;
}
if (!isValidMove(r, c, cell.unicornIndex)) {
return false;
}
}
}
return true;
}
// Highlight errors for current board state
function highlightErrors() {
// Clear previous
for (var i = 0; i < errorCells.length; i++) {
errorCells[i].showError(false);
}
errorCells = [];
// Check for errors
for (var r = 0; r < gridSize; r++) {
for (var c = 0; c < gridSize; c++) {
var cell = board[r][c];
if (cell.unicornIndex === null) {
continue;
}
// Only highlight if not a given
if (!cell.isGiven && !isValidMove(r, c, cell.unicornIndex)) {
cell.showError(true);
errorCells.push(cell);
}
}
}
}
// Deselect all cells
function deselectAllCells() {
for (var r = 0; r < gridSize; r++) {
for (var c = 0; c < gridSize; c++) {
if (typeof board[r][c].setSelected === "function") {
board[r][c].setSelected(false);
}
}
}
selectedCell = null;
}
// --- UI Setup ---
// Board container
var boardContainer = new Container();
boardContainer.x = boardOffsetX;
boardContainer.y = boardOffsetY;
game.addChild(boardContainer);
// Draw grid lines (thick for boxes, thin for cells)
for (var i = 0; i <= gridSize; i++) {
// For 4x4, thick lines at 0, 2, 4 (every 2 cells)
var isBoxLine = i % 2 === 0;
var lineW = isBoxLine ? 8 : 3;
var lineColor = isBoxLine ? 0x9b59b6 : 0xd1b3e0;
// Vertical
var vLine = LK.getAsset('vLine' + i, {
width: lineW,
height: boardSize,
color: lineColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0
});
vLine.x = i * cellSize;
vLine.y = 0;
boardContainer.addChild(vLine);
// Horizontal
var hLine = LK.getAsset('hLine' + i, {
width: boardSize,
height: lineW,
color: lineColor,
shape: 'box',
anchorX: 0,
anchorY: 0.5
});
hLine.x = 0;
hLine.y = i * cellSize;
boardContainer.addChild(hLine);
}
// Create cells
for (var r = 0; r < gridSize; r++) {
board[r] = [];
for (var c = 0; c < gridSize; c++) {
// Ensure UnicornCell is defined before this point and all cells are instances of UnicornCell
var cell = new UnicornCell();
cell.x = c * cellSize + cellSize / 2;
cell.y = r * cellSize + cellSize / 2;
cell.row = r;
cell.col = c;
boardContainer.addChild(cell);
board[r][c] = cell;
}
}
// Fill in given cells and solution
for (var r = 0; r < gridSize; r++) {
givenBoard[r] = [];
solutionBoard[r] = [];
for (var c = 0; c < gridSize; c++) {
var idx = staticPuzzle[r][c];
var cell = board[r][c];
if (idx !== null) {
cell.setUnicorn(idx, false);
cell.isGiven = true;
cell.bg.alpha = 0.7;
}
givenBoard[r][c] = idx;
solutionBoard[r][c] = staticSolution[r][c];
}
}
// --- Picker UI ---
// Place picker at the bottom of the board, centered horizontally, with margin from the board
picker = new UnicornPicker();
picker.visible = false;
// Calculate picker position: center horizontally, below the board with margin
var pickerMargin = 60;
picker.x = boardOffsetX + (boardSize - (9 * pickerIconSize + 8 * pickerGap + 32)) / 2;
picker.y = boardOffsetY + boardSize + pickerMargin;
game.addChild(picker);
// --- Title & Timer UI (side by side) ---
var titleTimerContainer = new Container();
// Title
var titleTxt = new Text2('Unicorn Sudoku', {
size: 110,
fill: 0xA569BD
});
titleTxt.anchor.set(0, 0); // left align
titleTxt.x = 0;
titleTxt.y = 0;
titleTimerContainer.addChild(titleTxt);
// Timer
timerTxt = new Text2('00:00', {
size: 90,
fill: 0x7D3C98
});
timerTxt.anchor.set(1, 0); // right align
timerTxt.x = 900; // enough space for title, adjust as needed
timerTxt.y = 10;
titleTimerContainer.addChild(timerTxt);
// Set container width and center it
titleTimerContainer.x = (2048 - 900) / 2;
titleTimerContainer.y = 0;
LK.gui.top.addChild(titleTimerContainer);
// --- Instructions UI ---
var instrTxt = new Text2('Tap a cell, then pick a unicorn to fill it!', {
size: 60,
fill: 0x7D3C98
});
instrTxt.anchor.set(0.5, 0);
instrTxt.y = 120;
LK.gui.top.addChild(instrTxt);
// --- Event Handlers ---
// Cell tap: select cell and show picker
function onCellDown(x, y, obj) {
if (completed) {
return;
}
var cell = obj;
if (cell.isGiven) {
return;
}
// If cell is already filled, do nothing
if (cell.unicornIndex !== null) {
return;
}
deselectAllCells();
if (typeof cell.setSelected === "function") {
cell.setSelected(true);
}
selectedCell = cell;
// Determine possible unicorns for this cell
var possible = [];
if (cell.unicornIndex === null) {
for (var i = 0; i < 4; i++) {
if (isValidMove(cell.row, cell.col, i)) {
possible.push(i);
}
}
} else {
// If cell is not empty, allow all (for clearing)
for (var i = 0; i < 4; i++) {
possible.push(i);
}
}
// Show picker below the cell, but keep inside screen
// Defensive: cell.position may be undefined, so fallback to cell.x/cell.y
var cellPos = cell && cell.position ? cell.position : {
x: cell.x,
y: cell.y
};
var global = boardContainer.toGlobal(cellPos);
var pickerX = Math.max(40, Math.min(global.x - pickerIconSize * 2, 2048 - pickerIconSize * 4 - 40));
var pickerY = Math.min(global.y + cellSize / 2 + 30, 2732 - pickerIconSize - 80);
picker.show(pickerX, pickerY);
picker.setSelected(null);
// Show only possible unicorns in picker
for (var i = 0; i < picker.icons.length; i++) {
picker.icons[i].visible = possible.indexOf(i) !== -1;
}
}
// Picker tap: set unicorn in selected cell
function onPickerDown(x, y, obj) {
if (!selectedCell || completed) {
return;
}
var idx = obj.idx;
if (idx === undefined) {
return;
}
// If already has this unicorn, clear
if (selectedCell.unicornIndex === idx) {
selectedCell.clearUnicorn();
picker.setSelected(null);
} else {
// Only allow placing if the unicorn is correct for this cell
var correctIdx = solutionBoard[selectedCell.row][selectedCell.col];
if (idx === correctIdx) {
selectedCell.setUnicorn(idx, true);
picker.setSelected(idx);
// Remove this unicorn from picker options for this cell
picker.icons[idx].visible = false;
} else {
// Optionally, flash error or ignore
selectedCell.showError(true);
LK.setTimeout(function () {
selectedCell.showError(false);
}, errorFlashDuration);
}
}
highlightErrors();
// Check for win
if (isBoardComplete()) {
completed = true;
picker.hide();
deselectAllCells();
LK.effects.flashScreen(0x7d3c98, 800);
LK.setTimeout(function () {
LK.showYouWin();
}, 900);
}
}
// Hide picker if tap outside
function onGameDown(x, y, obj) {
// If tap is not on a cell or picker, hide picker and deselect
if (obj && (obj instanceof UnicornCell || obj === picker || picker.icons.indexOf(obj) !== -1)) {
return;
}
picker.hide();
deselectAllCells();
}
// --- Attach event handlers ---
// Attach to each cell
for (var r = 0; r < gridSize; r++) {
for (var c = 0; c < gridSize; c++) {
var cell = board[r][c];
cell.down = onCellDown;
}
}
// Attach to picker icons
for (var i = 0; i < 4; i++) {
picker.icons[i].down = onPickerDown;
}
// Attach to game for outside tap
game.down = onGameDown;
// --- Timer Logic ---
function formatTime(secs) {
var m = Math.floor(secs / 60);
var s = secs % 60;
return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
}
function updateTimer() {
if (completed) {
return;
}
var now = Date.now();
var elapsed = Math.floor((now - startTime) / 1000);
timerTxt.setText(formatTime(elapsed));
}
// --- Game Start ---
function startGame() {
completed = false;
deselectAllCells();
picker.hide();
highlightErrors();
startTime = Date.now();
timerTxt.setText('00:00');
if (timerInterval) {
LK.clearInterval(timerInterval);
}
timerInterval = LK.setInterval(updateTimer, 1000);
}
startGame();
// --- Game Update (not used for logic, but required) ---
game.update = function () {
// No per-frame logic needed for MVP
};