/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Block class: a draggable block with a given shape and color
var Block = Container.expand(function () {
var self = Container.call(this);
// Properties
self.shape = null; // {cells, size}
self.color = null; // asset id
self.cellSize = 210;
self.cells = []; // graphical cell assets
self.ghostCells = []; // for ghost preview
self.isDragging = false;
self.gridX = null; // grid position if placed
self.gridY = null;
// Set up block with shape and color
self.setup = function (shape, color) {
self.shape = shape;
self.color = color;
// Remove old cells
for (var i = 0; i < self.cells.length; ++i) self.removeChild(self.cells[i]);
self.cells = [];
// Add new cells
for (var i = 0; i < shape.cells.length; ++i) {
var c = shape.cells[i];
var cell = self.attachAsset(color, {
anchorX: 0.5,
anchorY: 0.5,
x: c[0] * self.cellSize,
y: c[1] * self.cellSize
});
self.cells.push(cell);
}
// Center anchor
var w = shape.size[0] * self.cellSize,
h = shape.size[1] * self.cellSize;
self.pivot.x = w / 2 - self.cellSize / 2;
self.pivot.y = h / 2 - self.cellSize / 2;
};
// Show ghost preview at given grid position
self.showGhost = function (gridX, gridY, grid) {
self.hideGhost();
for (var i = 0; i < self.shape.cells.length; ++i) {
var c = self.shape.cells[i];
var gx = gridX + c[0],
gy = gridY + c[1];
if (gx < 0 || gx >= grid.cols || gy < 0 || gy >= grid.rows) continue;
var ghost = LK.getAsset(self.color, {
anchorX: 0.5,
anchorY: 0.5,
x: grid.gridOriginX + gx * self.cellSize + self.cellSize / 2,
y: grid.gridOriginY + gy * self.cellSize + self.cellSize / 2,
alpha: 0.7 // More visible ghost
});
ghost.tint = 0xffffff; // Add white tint for extra contrast
self.ghostCells.push(ghost);
grid.addChild(ghost);
}
};
self.hideGhost = function () {
for (var i = 0; i < self.ghostCells.length; ++i) {
if (self.ghostCells[i].parent) self.ghostCells[i].parent.removeChild(self.ghostCells[i]);
}
self.ghostCells = [];
};
// Animate block on placement
self.animatePlace = function () {
for (var i = 0; i < self.cells.length; ++i) {
var cell = self.cells[i];
cell.scaleX = 1.2;
cell.scaleY = 1.2;
tween(cell, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
};
return self;
});
// Grid class: the main play area
var Grid = Container.expand(function () {
var self = Container.call(this);
self.rows = 9;
self.cols = 9;
self.cellSize = 210;
self.grid = []; // 2D array: grid[y][x] = {block: Block, color: assetId}
self.cellNodes = []; // graphical cell backgrounds
self.gridOriginX = 0;
self.gridOriginY = 0;
// Set up grid
self.setup = function () {
// Center grid with padding to ensure full visibility
var totalW = self.cols * self.cellSize;
var totalH = self.rows * self.cellSize;
var paddingX = 100; // Padding to keep away from edges
var paddingY = 300; // Extra padding for tray area at bottom
self.gridOriginX = Math.floor((2048 - totalW) / 2);
self.gridOriginY = Math.floor((2732 - totalH - paddingY) / 2) + paddingY / 2;
// Draw cells
for (var y = 0; y < self.rows; ++y) {
self.grid[y] = [];
self.cellNodes[y] = [];
for (var x = 0; x < self.cols; ++x) {
self.grid[y][x] = null;
var cell = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
x: self.gridOriginX + x * self.cellSize + self.cellSize / 2,
y: self.gridOriginY + y * self.cellSize + self.cellSize / 2,
scaleX: 0.95,
// Scale down to make guidelines thinner
scaleY: 0.95 // Scale down to make guidelines thinner
});
self.cellNodes[y][x] = cell;
}
}
};
// Check if a block can be placed at gridX,gridY
self.canPlace = function (block, gridX, gridY) {
for (var i = 0; i < block.shape.cells.length; ++i) {
var c = block.shape.cells[i];
var x = gridX + c[0],
y = gridY + c[1];
if (x < 0 || x >= self.cols || y < 0 || y >= self.rows) return false;
if (self.grid[y][x]) return false;
}
return true;
};
// Place a block at gridX,gridY
self.placeBlock = function (block, gridX, gridY) {
for (var i = 0; i < block.shape.cells.length; ++i) {
var c = block.shape.cells[i];
var x = gridX + c[0],
y = gridY + c[1];
self.grid[y][x] = {
color: block.color
};
// Animate cell
var cell = self.cellNodes[y][x];
cell.scaleX = 1.2;
cell.scaleY = 1.2;
// Set tint to the color value of the asset
if (block.color === 'block_blue') cell.tint = 0x3b82f6;else if (block.color === 'block_red') cell.tint = 0xef4444;else if (block.color === 'block_green') cell.tint = 0x22c55e;else if (block.color === 'block_yellow') cell.tint = 0xfacc15;else if (block.color === 'block_purple') cell.tint = 0xa21caf;else if (block.color === 'block_orange') cell.tint = 0xf97316;
tween(cell, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
};
// Clear full rows/columns, return number of cleared lines
self.clearLines = function () {
var cleared = 0;
var toClearRows = [];
var toClearCols = [];
// Check rows
for (var y = 0; y < self.rows; ++y) {
var full = true;
for (var x = 0; x < self.cols; ++x) {
if (!self.grid[y][x]) {
full = false;
break;
}
}
if (full) toClearRows.push(y);
}
// Check columns
for (var x = 0; x < self.cols; ++x) {
var full = true;
for (var y = 0; y < self.rows; ++y) {
if (!self.grid[y][x]) {
full = false;
break;
}
}
if (full) toClearCols.push(x);
}
// Clear rows
for (var i = 0; i < toClearRows.length; ++i) {
var y = toClearRows[i];
for (var x = 0; x < self.cols; ++x) {
self.grid[y][x] = null;
var cell = self.cellNodes[y][x];
cell.tint = 0xe0e0e0;
// Animate
cell.alpha = 0.2;
tween(cell, {
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
cleared++;
}
// Clear columns
for (var i = 0; i < toClearCols.length; ++i) {
var x = toClearCols[i];
for (var y = 0; y < self.rows; ++y) {
self.grid[y][x] = null;
var cell = self.cellNodes[y][x];
cell.tint = 0xe0e0e0;
cell.alpha = 0.2;
tween(cell, {
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
cleared++;
}
return cleared;
};
// Check if any of the given blocks can be placed anywhere
self.anyValidPlacement = function (blocks) {
for (var b = 0; b < blocks.length; ++b) {
var block = blocks[b];
for (var y = 0; y <= self.rows - block.shape.size[1]; ++y) {
for (var x = 0; x <= self.cols - block.shape.size[0]; ++x) {
if (self.canPlace(block, x, y)) return true;
}
}
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a174e
});
/****
* Game Code
****/
// --- Game variables ---
/*
We use only shapes for blocks and grid cells. Each block color is a unique asset.
Block shapes: 1x1, 1x2, 2x1, 2x2, 1x3, 3x1, L-shape (2x2 with one missing), etc.
Colors: blue, red, green, yellow, purple, orange.
Grid cell: light gray.
*/
// Block shapes definition
var BLOCK_SHAPES = [
// Each shape: {cells: [[x,y],...], size: [w,h]}
{
cells: [[0, 0]],
size: [1, 1]
},
// single
{
cells: [[0, 0], [1, 0]],
size: [2, 1]
},
// 1x2 horizontal
{
cells: [[0, 0], [0, 1]],
size: [1, 2]
},
// 2x1 vertical
{
cells: [[0, 0], [1, 0], [0, 1], [1, 1]],
size: [2, 2]
},
// 2x2
{
cells: [[0, 0], [1, 0], [2, 0]],
size: [3, 1]
},
// 1x3 horizontal
{
cells: [[0, 0], [0, 1], [0, 2]],
size: [1, 3]
},
// 3x1 vertical
{
cells: [[0, 0], [1, 0], [0, 1]],
size: [2, 2]
},
// L-shape (top-left)
{
cells: [[1, 0], [0, 1], [1, 1]],
size: [2, 2]
},
// L-shape (top-right)
{
cells: [[0, 0], [1, 0], [1, 1]],
size: [2, 2]
},
// L-shape (bottom-right)
{
cells: [[0, 0], [0, 1], [1, 1]],
size: [2, 2]
} // L-shape (bottom-left)
];
// Block colors
var BLOCK_COLORS = ['block_blue', 'block_red', 'block_green', 'block_yellow', 'block_purple', 'block_orange'];
// Utility: pick random element from array
function randArr(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
var grid = new Grid();
grid.setup();
game.addChild(grid);
var score = LK.getScore();
var scoreTxt = new Text2('0', {
size: 120,
fill: 0x222222
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Block tray: 3 blocks to choose from
var trayBlocks = [];
var trayY = 2732 - 220;
var trayX0 = 2048 / 2 - 2 * 270;
var traySpacing = 360;
// Drag state
var dragBlock = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
// Helper: create a new random block
function createRandomBlock() {
var block = new Block();
var shape = randArr(BLOCK_SHAPES);
var color = randArr(BLOCK_COLORS);
block.setup(shape, color);
return block;
}
// Helper: refill tray with up to 3 blocks
function refillTray() {
// Remove old blocks
for (var i = 0; i < trayBlocks.length; ++i) {
if (trayBlocks[i].parent) trayBlocks[i].parent.removeChild(trayBlocks[i]);
}
trayBlocks = [];
for (var i = 0; i < 3; ++i) {
var block = createRandomBlock();
block.x = trayX0 + i * traySpacing;
block.y = trayY;
block.scaleX = block.scaleY = 1.1;
trayBlocks.push(block);
game.addChild(block);
}
}
// Helper: update score display
function updateScore(val) {
LK.setScore(val);
score = LK.getScore();
scoreTxt.setText(score);
}
// Helper: get grid cell under (x, y) in game coordinates
function getGridCellAt(x, y) {
var gx = Math.floor((x - grid.gridOriginX) / grid.cellSize);
var gy = Math.floor((y - grid.gridOriginY) / grid.cellSize);
if (gx < 0 || gx >= grid.cols || gy < 0 || gy >= grid.rows) return null;
return {
x: gx,
y: gy
};
}
// Helper: snap block to tray
function snapBlockToTray(block) {
var idx = trayBlocks.indexOf(block);
if (idx >= 0) {
block.x = trayX0 + idx * traySpacing;
block.y = trayY;
block.scaleX = block.scaleY = 1.1;
block.zIndex = 0;
}
}
// --- Input handlers ---
// Find block in tray under pointer
function blockUnderPointer(x, y) {
for (var i = 0; i < trayBlocks.length; ++i) {
var block = trayBlocks[i];
// Check bounding box
var bx = block.x - block.pivot.x * block.scaleX;
var by = block.y - block.pivot.y * block.scaleY;
var bw = block.shape.size[0] * block.cellSize * block.scaleX;
var bh = block.shape.size[1] * block.cellSize * block.scaleY;
if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) {
return block;
}
}
return null;
}
// Drag start
game.down = function (x, y, obj) {
if (dragBlock) return;
var block = blockUnderPointer(x, y);
if (block) {
dragBlock = block;
dragBlock.isDragging = true;
dragBlock.zIndex = 100;
dragOffsetX = x - dragBlock.x;
dragOffsetY = y - dragBlock.y;
// Animate up
tween(dragBlock, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 80,
easing: tween.easeOut
});
}
};
// Drag move
game.move = function (x, y, obj) {
if (!dragBlock) return;
dragBlock.x = x - dragOffsetX;
dragBlock.y = y - dragOffsetY;
// Show ghost preview if over grid
dragBlock.hideGhost();
var cell = getGridCellAt(x, y);
if (cell) {
var can = grid.canPlace(dragBlock, cell.x, cell.y);
if (can) {
dragBlock.showGhost(cell.x, cell.y, grid);
}
}
};
// Drag end
game.up = function (x, y, obj) {
if (!dragBlock) return;
// Try to find the nearest valid cell to drop the block, even if not perfectly centered
var cell = getGridCellAt(x, y);
var placed = false;
var foundCell = null;
if (cell) {
// Try the cell under pointer first
if (grid.canPlace(dragBlock, cell.x, cell.y)) {
foundCell = cell;
} else {
// Search nearby cells within a 1-cell radius for a valid placement
var minDist = 99999;
var bestCell = null;
for (var dy = -1; dy <= 1; ++dy) {
for (var dx = -1; dx <= 1; ++dx) {
var cx = cell.x + dx;
var cy = cell.y + dy;
if (cx < 0 || cx >= grid.cols || cy < 0 || cy >= grid.rows) continue;
if (grid.canPlace(dragBlock, cx, cy)) {
// Prefer the closest cell to pointer
var dist = Math.abs(dx) + Math.abs(dy);
if (dist < minDist) {
minDist = dist;
bestCell = {
x: cx,
y: cy
};
}
}
}
}
if (bestCell) foundCell = bestCell;
}
}
if (foundCell) {
// Place block
grid.placeBlock(dragBlock, foundCell.x, foundCell.y);
dragBlock.animatePlace();
dragBlock.hideGhost();
if (dragBlock.parent) dragBlock.parent.removeChild(dragBlock);
var idx = trayBlocks.indexOf(dragBlock);
if (idx >= 0) trayBlocks.splice(idx, 1);
LK.getSound('place').play();
// Clear lines
var cleared = grid.clearLines();
if (cleared > 0) {
updateScore(LK.getScore() + cleared * 10);
LK.getSound('clear').play();
} else {
updateScore(LK.getScore() + 1);
}
placed = true;
// If tray empty, refill
if (trayBlocks.length === 0) {
refillTray();
}
// Check for game over
if (!grid.anyValidPlacement(trayBlocks)) {
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
if (!placed) {
// Snap back to tray
snapBlockToTray(dragBlock);
dragBlock.hideGhost();
}
// Animate down
tween(dragBlock, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 80,
easing: tween.easeOut
});
dragBlock.isDragging = false;
dragBlock = null;
};
// --- Game update loop ---
game.update = function () {
// No per-frame logic needed for now
};
// --- Start game ---
LK.setScore(0);
updateScore(0);
refillTray(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Block class: a draggable block with a given shape and color
var Block = Container.expand(function () {
var self = Container.call(this);
// Properties
self.shape = null; // {cells, size}
self.color = null; // asset id
self.cellSize = 210;
self.cells = []; // graphical cell assets
self.ghostCells = []; // for ghost preview
self.isDragging = false;
self.gridX = null; // grid position if placed
self.gridY = null;
// Set up block with shape and color
self.setup = function (shape, color) {
self.shape = shape;
self.color = color;
// Remove old cells
for (var i = 0; i < self.cells.length; ++i) self.removeChild(self.cells[i]);
self.cells = [];
// Add new cells
for (var i = 0; i < shape.cells.length; ++i) {
var c = shape.cells[i];
var cell = self.attachAsset(color, {
anchorX: 0.5,
anchorY: 0.5,
x: c[0] * self.cellSize,
y: c[1] * self.cellSize
});
self.cells.push(cell);
}
// Center anchor
var w = shape.size[0] * self.cellSize,
h = shape.size[1] * self.cellSize;
self.pivot.x = w / 2 - self.cellSize / 2;
self.pivot.y = h / 2 - self.cellSize / 2;
};
// Show ghost preview at given grid position
self.showGhost = function (gridX, gridY, grid) {
self.hideGhost();
for (var i = 0; i < self.shape.cells.length; ++i) {
var c = self.shape.cells[i];
var gx = gridX + c[0],
gy = gridY + c[1];
if (gx < 0 || gx >= grid.cols || gy < 0 || gy >= grid.rows) continue;
var ghost = LK.getAsset(self.color, {
anchorX: 0.5,
anchorY: 0.5,
x: grid.gridOriginX + gx * self.cellSize + self.cellSize / 2,
y: grid.gridOriginY + gy * self.cellSize + self.cellSize / 2,
alpha: 0.7 // More visible ghost
});
ghost.tint = 0xffffff; // Add white tint for extra contrast
self.ghostCells.push(ghost);
grid.addChild(ghost);
}
};
self.hideGhost = function () {
for (var i = 0; i < self.ghostCells.length; ++i) {
if (self.ghostCells[i].parent) self.ghostCells[i].parent.removeChild(self.ghostCells[i]);
}
self.ghostCells = [];
};
// Animate block on placement
self.animatePlace = function () {
for (var i = 0; i < self.cells.length; ++i) {
var cell = self.cells[i];
cell.scaleX = 1.2;
cell.scaleY = 1.2;
tween(cell, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
};
return self;
});
// Grid class: the main play area
var Grid = Container.expand(function () {
var self = Container.call(this);
self.rows = 9;
self.cols = 9;
self.cellSize = 210;
self.grid = []; // 2D array: grid[y][x] = {block: Block, color: assetId}
self.cellNodes = []; // graphical cell backgrounds
self.gridOriginX = 0;
self.gridOriginY = 0;
// Set up grid
self.setup = function () {
// Center grid with padding to ensure full visibility
var totalW = self.cols * self.cellSize;
var totalH = self.rows * self.cellSize;
var paddingX = 100; // Padding to keep away from edges
var paddingY = 300; // Extra padding for tray area at bottom
self.gridOriginX = Math.floor((2048 - totalW) / 2);
self.gridOriginY = Math.floor((2732 - totalH - paddingY) / 2) + paddingY / 2;
// Draw cells
for (var y = 0; y < self.rows; ++y) {
self.grid[y] = [];
self.cellNodes[y] = [];
for (var x = 0; x < self.cols; ++x) {
self.grid[y][x] = null;
var cell = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
x: self.gridOriginX + x * self.cellSize + self.cellSize / 2,
y: self.gridOriginY + y * self.cellSize + self.cellSize / 2,
scaleX: 0.95,
// Scale down to make guidelines thinner
scaleY: 0.95 // Scale down to make guidelines thinner
});
self.cellNodes[y][x] = cell;
}
}
};
// Check if a block can be placed at gridX,gridY
self.canPlace = function (block, gridX, gridY) {
for (var i = 0; i < block.shape.cells.length; ++i) {
var c = block.shape.cells[i];
var x = gridX + c[0],
y = gridY + c[1];
if (x < 0 || x >= self.cols || y < 0 || y >= self.rows) return false;
if (self.grid[y][x]) return false;
}
return true;
};
// Place a block at gridX,gridY
self.placeBlock = function (block, gridX, gridY) {
for (var i = 0; i < block.shape.cells.length; ++i) {
var c = block.shape.cells[i];
var x = gridX + c[0],
y = gridY + c[1];
self.grid[y][x] = {
color: block.color
};
// Animate cell
var cell = self.cellNodes[y][x];
cell.scaleX = 1.2;
cell.scaleY = 1.2;
// Set tint to the color value of the asset
if (block.color === 'block_blue') cell.tint = 0x3b82f6;else if (block.color === 'block_red') cell.tint = 0xef4444;else if (block.color === 'block_green') cell.tint = 0x22c55e;else if (block.color === 'block_yellow') cell.tint = 0xfacc15;else if (block.color === 'block_purple') cell.tint = 0xa21caf;else if (block.color === 'block_orange') cell.tint = 0xf97316;
tween(cell, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
};
// Clear full rows/columns, return number of cleared lines
self.clearLines = function () {
var cleared = 0;
var toClearRows = [];
var toClearCols = [];
// Check rows
for (var y = 0; y < self.rows; ++y) {
var full = true;
for (var x = 0; x < self.cols; ++x) {
if (!self.grid[y][x]) {
full = false;
break;
}
}
if (full) toClearRows.push(y);
}
// Check columns
for (var x = 0; x < self.cols; ++x) {
var full = true;
for (var y = 0; y < self.rows; ++y) {
if (!self.grid[y][x]) {
full = false;
break;
}
}
if (full) toClearCols.push(x);
}
// Clear rows
for (var i = 0; i < toClearRows.length; ++i) {
var y = toClearRows[i];
for (var x = 0; x < self.cols; ++x) {
self.grid[y][x] = null;
var cell = self.cellNodes[y][x];
cell.tint = 0xe0e0e0;
// Animate
cell.alpha = 0.2;
tween(cell, {
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
cleared++;
}
// Clear columns
for (var i = 0; i < toClearCols.length; ++i) {
var x = toClearCols[i];
for (var y = 0; y < self.rows; ++y) {
self.grid[y][x] = null;
var cell = self.cellNodes[y][x];
cell.tint = 0xe0e0e0;
cell.alpha = 0.2;
tween(cell, {
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
cleared++;
}
return cleared;
};
// Check if any of the given blocks can be placed anywhere
self.anyValidPlacement = function (blocks) {
for (var b = 0; b < blocks.length; ++b) {
var block = blocks[b];
for (var y = 0; y <= self.rows - block.shape.size[1]; ++y) {
for (var x = 0; x <= self.cols - block.shape.size[0]; ++x) {
if (self.canPlace(block, x, y)) return true;
}
}
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a174e
});
/****
* Game Code
****/
// --- Game variables ---
/*
We use only shapes for blocks and grid cells. Each block color is a unique asset.
Block shapes: 1x1, 1x2, 2x1, 2x2, 1x3, 3x1, L-shape (2x2 with one missing), etc.
Colors: blue, red, green, yellow, purple, orange.
Grid cell: light gray.
*/
// Block shapes definition
var BLOCK_SHAPES = [
// Each shape: {cells: [[x,y],...], size: [w,h]}
{
cells: [[0, 0]],
size: [1, 1]
},
// single
{
cells: [[0, 0], [1, 0]],
size: [2, 1]
},
// 1x2 horizontal
{
cells: [[0, 0], [0, 1]],
size: [1, 2]
},
// 2x1 vertical
{
cells: [[0, 0], [1, 0], [0, 1], [1, 1]],
size: [2, 2]
},
// 2x2
{
cells: [[0, 0], [1, 0], [2, 0]],
size: [3, 1]
},
// 1x3 horizontal
{
cells: [[0, 0], [0, 1], [0, 2]],
size: [1, 3]
},
// 3x1 vertical
{
cells: [[0, 0], [1, 0], [0, 1]],
size: [2, 2]
},
// L-shape (top-left)
{
cells: [[1, 0], [0, 1], [1, 1]],
size: [2, 2]
},
// L-shape (top-right)
{
cells: [[0, 0], [1, 0], [1, 1]],
size: [2, 2]
},
// L-shape (bottom-right)
{
cells: [[0, 0], [0, 1], [1, 1]],
size: [2, 2]
} // L-shape (bottom-left)
];
// Block colors
var BLOCK_COLORS = ['block_blue', 'block_red', 'block_green', 'block_yellow', 'block_purple', 'block_orange'];
// Utility: pick random element from array
function randArr(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
var grid = new Grid();
grid.setup();
game.addChild(grid);
var score = LK.getScore();
var scoreTxt = new Text2('0', {
size: 120,
fill: 0x222222
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Block tray: 3 blocks to choose from
var trayBlocks = [];
var trayY = 2732 - 220;
var trayX0 = 2048 / 2 - 2 * 270;
var traySpacing = 360;
// Drag state
var dragBlock = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
// Helper: create a new random block
function createRandomBlock() {
var block = new Block();
var shape = randArr(BLOCK_SHAPES);
var color = randArr(BLOCK_COLORS);
block.setup(shape, color);
return block;
}
// Helper: refill tray with up to 3 blocks
function refillTray() {
// Remove old blocks
for (var i = 0; i < trayBlocks.length; ++i) {
if (trayBlocks[i].parent) trayBlocks[i].parent.removeChild(trayBlocks[i]);
}
trayBlocks = [];
for (var i = 0; i < 3; ++i) {
var block = createRandomBlock();
block.x = trayX0 + i * traySpacing;
block.y = trayY;
block.scaleX = block.scaleY = 1.1;
trayBlocks.push(block);
game.addChild(block);
}
}
// Helper: update score display
function updateScore(val) {
LK.setScore(val);
score = LK.getScore();
scoreTxt.setText(score);
}
// Helper: get grid cell under (x, y) in game coordinates
function getGridCellAt(x, y) {
var gx = Math.floor((x - grid.gridOriginX) / grid.cellSize);
var gy = Math.floor((y - grid.gridOriginY) / grid.cellSize);
if (gx < 0 || gx >= grid.cols || gy < 0 || gy >= grid.rows) return null;
return {
x: gx,
y: gy
};
}
// Helper: snap block to tray
function snapBlockToTray(block) {
var idx = trayBlocks.indexOf(block);
if (idx >= 0) {
block.x = trayX0 + idx * traySpacing;
block.y = trayY;
block.scaleX = block.scaleY = 1.1;
block.zIndex = 0;
}
}
// --- Input handlers ---
// Find block in tray under pointer
function blockUnderPointer(x, y) {
for (var i = 0; i < trayBlocks.length; ++i) {
var block = trayBlocks[i];
// Check bounding box
var bx = block.x - block.pivot.x * block.scaleX;
var by = block.y - block.pivot.y * block.scaleY;
var bw = block.shape.size[0] * block.cellSize * block.scaleX;
var bh = block.shape.size[1] * block.cellSize * block.scaleY;
if (x >= bx && x <= bx + bw && y >= by && y <= by + bh) {
return block;
}
}
return null;
}
// Drag start
game.down = function (x, y, obj) {
if (dragBlock) return;
var block = blockUnderPointer(x, y);
if (block) {
dragBlock = block;
dragBlock.isDragging = true;
dragBlock.zIndex = 100;
dragOffsetX = x - dragBlock.x;
dragOffsetY = y - dragBlock.y;
// Animate up
tween(dragBlock, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 80,
easing: tween.easeOut
});
}
};
// Drag move
game.move = function (x, y, obj) {
if (!dragBlock) return;
dragBlock.x = x - dragOffsetX;
dragBlock.y = y - dragOffsetY;
// Show ghost preview if over grid
dragBlock.hideGhost();
var cell = getGridCellAt(x, y);
if (cell) {
var can = grid.canPlace(dragBlock, cell.x, cell.y);
if (can) {
dragBlock.showGhost(cell.x, cell.y, grid);
}
}
};
// Drag end
game.up = function (x, y, obj) {
if (!dragBlock) return;
// Try to find the nearest valid cell to drop the block, even if not perfectly centered
var cell = getGridCellAt(x, y);
var placed = false;
var foundCell = null;
if (cell) {
// Try the cell under pointer first
if (grid.canPlace(dragBlock, cell.x, cell.y)) {
foundCell = cell;
} else {
// Search nearby cells within a 1-cell radius for a valid placement
var minDist = 99999;
var bestCell = null;
for (var dy = -1; dy <= 1; ++dy) {
for (var dx = -1; dx <= 1; ++dx) {
var cx = cell.x + dx;
var cy = cell.y + dy;
if (cx < 0 || cx >= grid.cols || cy < 0 || cy >= grid.rows) continue;
if (grid.canPlace(dragBlock, cx, cy)) {
// Prefer the closest cell to pointer
var dist = Math.abs(dx) + Math.abs(dy);
if (dist < minDist) {
minDist = dist;
bestCell = {
x: cx,
y: cy
};
}
}
}
}
if (bestCell) foundCell = bestCell;
}
}
if (foundCell) {
// Place block
grid.placeBlock(dragBlock, foundCell.x, foundCell.y);
dragBlock.animatePlace();
dragBlock.hideGhost();
if (dragBlock.parent) dragBlock.parent.removeChild(dragBlock);
var idx = trayBlocks.indexOf(dragBlock);
if (idx >= 0) trayBlocks.splice(idx, 1);
LK.getSound('place').play();
// Clear lines
var cleared = grid.clearLines();
if (cleared > 0) {
updateScore(LK.getScore() + cleared * 10);
LK.getSound('clear').play();
} else {
updateScore(LK.getScore() + 1);
}
placed = true;
// If tray empty, refill
if (trayBlocks.length === 0) {
refillTray();
}
// Check for game over
if (!grid.anyValidPlacement(trayBlocks)) {
LK.getSound('fail').play();
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
if (!placed) {
// Snap back to tray
snapBlockToTray(dragBlock);
dragBlock.hideGhost();
}
// Animate down
tween(dragBlock, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 80,
easing: tween.easeOut
});
dragBlock.isDragging = false;
dragBlock = null;
};
// --- Game update loop ---
game.update = function () {
// No per-frame logic needed for now
};
// --- Start game ---
LK.setScore(0);
updateScore(0);
refillTray();