/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // IceBlock: Represents a single moving or stacked ice block var IceBlock = Container.expand(function () { var self = Container.call(this); // Attach the ice block asset var block = self.attachAsset('iceBlock', { anchorX: 0.5, anchorY: 1 }); // Set block size (will be updated on creation) self.setSize = function (w, h) { block.width = w; block.height = h; self.width = w; self.height = h; }; // Animate block drop self.dropTo = function (targetY, duration, onFinish) { tween(self, { y: targetY }, { duration: duration, easing: tween.cubicIn, onFinish: onFinish }); }; // Animate block shake (for miss) self.shake = function () { var origX = self.x; tween(self, { x: origX - 30 }, { duration: 60, easing: tween.linear, onFinish: function onFinish() { tween(self, { x: origX + 30 }, { duration: 60, easing: tween.linear, onFinish: function onFinish() { tween(self, { x: origX }, { duration: 60 }); } }); } }); }; return self; }); // IceShard: Represents a falling broken piece var IceShard = Container.expand(function () { var self = Container.call(this); var shard = self.attachAsset('iceShard', { anchorX: 0.5, anchorY: 1 }); self.setSize = function (w, h) { shard.width = w; shard.height = h; self.width = w; self.height = h; }; // Animate falling and fading out self.fallAndFade = function (_onFinish) { tween(self, { y: self.y + 400, alpha: 0 }, { duration: 600, easing: tween.cubicIn, onFinish: function onFinish() { if (_onFinish) _onFinish(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a2a3a }); /**** * Game Code ****/ // Shard (falling broken piece) // Tower base (static base block) // Ice block (main block for stacking) // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var BASE_Y = GAME_HEIGHT - 200; var BLOCK_START_Y = 400; var BLOCK_MIN_WIDTH = 120; var BLOCK_HEIGHT = 100; var BLOCK_MOVE_SPEED = 12; // px per frame // Game state var stack = []; // Array of stacked blocks (bottom to top) var movingBlock = null; // The currently moving block var movingDir = 1; // 1: right, -1: left var movingSpeed = BLOCK_MOVE_SPEED; var blockWidth = 600; var blockHeight = BLOCK_HEIGHT; var blockX = GAME_WIDTH / 2; var blockY = BLOCK_START_Y; var canDrop = true; var score = 0; var gameOver = false; // Score display var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Tower base var baseBlock = new IceBlock(); baseBlock.setSize(700, 120); baseBlock.x = GAME_WIDTH / 2; baseBlock.y = BASE_Y; game.addChild(baseBlock); stack.push(baseBlock); // Helper: Add a new moving block at the top function spawnMovingBlock() { blockWidth = stack[stack.length - 1].width; if (blockWidth < BLOCK_MIN_WIDTH) { endGame(); return; } blockHeight = BLOCK_HEIGHT; blockX = 0 + blockWidth / 2 + 80; // Start from left blockY = BLOCK_START_Y; movingDir = 1; movingSpeed = BLOCK_MOVE_SPEED + Math.floor(score / 10) * 2; // Slightly faster as score increases movingBlock = new IceBlock(); movingBlock.setSize(blockWidth, blockHeight); movingBlock.x = blockX; movingBlock.y = blockY; movingBlock.alpha = 1; game.addChild(movingBlock); canDrop = true; } // Helper: End the game function endGame() { if (gameOver) return; gameOver = true; LK.showGameOver(); } // Helper: Update score display function updateScore() { scoreTxt.setText(score); } // Helper: Drop the moving block function dropBlock() { if (!movingBlock || !canDrop || gameOver) return; canDrop = false; // Find the top block to stack on var prevBlock = stack[stack.length - 1]; var prevX = prevBlock.x; var prevY = prevBlock.y; var prevW = prevBlock.width; // Target Y for stacking var targetY = prevY - blockHeight; // Animate drop movingBlock.dropTo(targetY, 220, function () { // Check overlap var leftEdge = movingBlock.x - blockWidth / 2; var rightEdge = movingBlock.x + blockWidth / 2; var prevLeft = prevX - prevW / 2; var prevRight = prevX + prevW / 2; var overlapLeft = Math.max(leftEdge, prevLeft); var overlapRight = Math.min(rightEdge, prevRight); var overlapWidth = overlapRight - overlapLeft; if (overlapWidth <= 0) { // Missed completely movingBlock.shake(); tween(movingBlock, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { endGame(); } }); return; } // If overhang, break off the left or right part var overhangLeft = overlapLeft - leftEdge; var overhangRight = rightEdge - overlapRight; // Animate left shard if (overhangLeft > 2) { var shardL = new IceShard(); shardL.setSize(overhangLeft, blockHeight); shardL.x = leftEdge + overhangLeft / 2; shardL.y = targetY; game.addChild(shardL); shardL.fallAndFade(function () { shardL.destroy(); }); } // Animate right shard if (overhangRight > 2) { var shardR = new IceShard(); shardR.setSize(overhangRight, blockHeight); shardR.x = rightEdge - overhangRight / 2; shardR.y = targetY; game.addChild(shardR); shardR.fallAndFade(function () { shardR.destroy(); }); } // Shrink the block to the overlap movingBlock.setSize(overlapWidth, blockHeight); movingBlock.x = overlapLeft + overlapWidth / 2; movingBlock.y = targetY; stack.push(movingBlock); // Update score score += 1; updateScore(); // Move camera up if needed (optional: not implemented, as LK handles scaling) // Spawn next block spawnMovingBlock(); }); } // Handle tap/click to drop block game.down = function (x, y, obj) { if (canDrop && !gameOver) { dropBlock(); } }; // Main game update loop game.update = function () { if (gameOver) return; // Move the moving block horizontally if (movingBlock && canDrop) { blockX += movingDir * movingSpeed; // Clamp and bounce at edges var minX = blockWidth / 2 + 80; var maxX = GAME_WIDTH - blockWidth / 2 - 80; if (blockX > maxX) { blockX = maxX; movingDir = -1; } if (blockX < minX) { blockX = minX; movingDir = 1; } movingBlock.x = blockX; } }; // Start the game function startGame() { // Reset state for (var i = stack.length - 1; i >= 1; i--) { stack[i].destroy(); stack.splice(i, 1); } if (movingBlock) { movingBlock.destroy(); movingBlock = null; } score = 0; updateScore(); gameOver = false; spawnMovingBlock(); } // On game start startGame(); // On game over, restart game when LK resets // (LK will re-initialize the Game class, so no need to handle reset here)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// IceBlock: Represents a single moving or stacked ice block
var IceBlock = Container.expand(function () {
var self = Container.call(this);
// Attach the ice block asset
var block = self.attachAsset('iceBlock', {
anchorX: 0.5,
anchorY: 1
});
// Set block size (will be updated on creation)
self.setSize = function (w, h) {
block.width = w;
block.height = h;
self.width = w;
self.height = h;
};
// Animate block drop
self.dropTo = function (targetY, duration, onFinish) {
tween(self, {
y: targetY
}, {
duration: duration,
easing: tween.cubicIn,
onFinish: onFinish
});
};
// Animate block shake (for miss)
self.shake = function () {
var origX = self.x;
tween(self, {
x: origX - 30
}, {
duration: 60,
easing: tween.linear,
onFinish: function onFinish() {
tween(self, {
x: origX + 30
}, {
duration: 60,
easing: tween.linear,
onFinish: function onFinish() {
tween(self, {
x: origX
}, {
duration: 60
});
}
});
}
});
};
return self;
});
// IceShard: Represents a falling broken piece
var IceShard = Container.expand(function () {
var self = Container.call(this);
var shard = self.attachAsset('iceShard', {
anchorX: 0.5,
anchorY: 1
});
self.setSize = function (w, h) {
shard.width = w;
shard.height = h;
self.width = w;
self.height = h;
};
// Animate falling and fading out
self.fallAndFade = function (_onFinish) {
tween(self, {
y: self.y + 400,
alpha: 0
}, {
duration: 600,
easing: tween.cubicIn,
onFinish: function onFinish() {
if (_onFinish) _onFinish();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a2a3a
});
/****
* Game Code
****/
// Shard (falling broken piece)
// Tower base (static base block)
// Ice block (main block for stacking)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BASE_Y = GAME_HEIGHT - 200;
var BLOCK_START_Y = 400;
var BLOCK_MIN_WIDTH = 120;
var BLOCK_HEIGHT = 100;
var BLOCK_MOVE_SPEED = 12; // px per frame
// Game state
var stack = []; // Array of stacked blocks (bottom to top)
var movingBlock = null; // The currently moving block
var movingDir = 1; // 1: right, -1: left
var movingSpeed = BLOCK_MOVE_SPEED;
var blockWidth = 600;
var blockHeight = BLOCK_HEIGHT;
var blockX = GAME_WIDTH / 2;
var blockY = BLOCK_START_Y;
var canDrop = true;
var score = 0;
var gameOver = false;
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Tower base
var baseBlock = new IceBlock();
baseBlock.setSize(700, 120);
baseBlock.x = GAME_WIDTH / 2;
baseBlock.y = BASE_Y;
game.addChild(baseBlock);
stack.push(baseBlock);
// Helper: Add a new moving block at the top
function spawnMovingBlock() {
blockWidth = stack[stack.length - 1].width;
if (blockWidth < BLOCK_MIN_WIDTH) {
endGame();
return;
}
blockHeight = BLOCK_HEIGHT;
blockX = 0 + blockWidth / 2 + 80; // Start from left
blockY = BLOCK_START_Y;
movingDir = 1;
movingSpeed = BLOCK_MOVE_SPEED + Math.floor(score / 10) * 2; // Slightly faster as score increases
movingBlock = new IceBlock();
movingBlock.setSize(blockWidth, blockHeight);
movingBlock.x = blockX;
movingBlock.y = blockY;
movingBlock.alpha = 1;
game.addChild(movingBlock);
canDrop = true;
}
// Helper: End the game
function endGame() {
if (gameOver) return;
gameOver = true;
LK.showGameOver();
}
// Helper: Update score display
function updateScore() {
scoreTxt.setText(score);
}
// Helper: Drop the moving block
function dropBlock() {
if (!movingBlock || !canDrop || gameOver) return;
canDrop = false;
// Find the top block to stack on
var prevBlock = stack[stack.length - 1];
var prevX = prevBlock.x;
var prevY = prevBlock.y;
var prevW = prevBlock.width;
// Target Y for stacking
var targetY = prevY - blockHeight;
// Animate drop
movingBlock.dropTo(targetY, 220, function () {
// Check overlap
var leftEdge = movingBlock.x - blockWidth / 2;
var rightEdge = movingBlock.x + blockWidth / 2;
var prevLeft = prevX - prevW / 2;
var prevRight = prevX + prevW / 2;
var overlapLeft = Math.max(leftEdge, prevLeft);
var overlapRight = Math.min(rightEdge, prevRight);
var overlapWidth = overlapRight - overlapLeft;
if (overlapWidth <= 0) {
// Missed completely
movingBlock.shake();
tween(movingBlock, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
endGame();
}
});
return;
}
// If overhang, break off the left or right part
var overhangLeft = overlapLeft - leftEdge;
var overhangRight = rightEdge - overlapRight;
// Animate left shard
if (overhangLeft > 2) {
var shardL = new IceShard();
shardL.setSize(overhangLeft, blockHeight);
shardL.x = leftEdge + overhangLeft / 2;
shardL.y = targetY;
game.addChild(shardL);
shardL.fallAndFade(function () {
shardL.destroy();
});
}
// Animate right shard
if (overhangRight > 2) {
var shardR = new IceShard();
shardR.setSize(overhangRight, blockHeight);
shardR.x = rightEdge - overhangRight / 2;
shardR.y = targetY;
game.addChild(shardR);
shardR.fallAndFade(function () {
shardR.destroy();
});
}
// Shrink the block to the overlap
movingBlock.setSize(overlapWidth, blockHeight);
movingBlock.x = overlapLeft + overlapWidth / 2;
movingBlock.y = targetY;
stack.push(movingBlock);
// Update score
score += 1;
updateScore();
// Move camera up if needed (optional: not implemented, as LK handles scaling)
// Spawn next block
spawnMovingBlock();
});
}
// Handle tap/click to drop block
game.down = function (x, y, obj) {
if (canDrop && !gameOver) {
dropBlock();
}
};
// Main game update loop
game.update = function () {
if (gameOver) return;
// Move the moving block horizontally
if (movingBlock && canDrop) {
blockX += movingDir * movingSpeed;
// Clamp and bounce at edges
var minX = blockWidth / 2 + 80;
var maxX = GAME_WIDTH - blockWidth / 2 - 80;
if (blockX > maxX) {
blockX = maxX;
movingDir = -1;
}
if (blockX < minX) {
blockX = minX;
movingDir = 1;
}
movingBlock.x = blockX;
}
};
// Start the game
function startGame() {
// Reset state
for (var i = stack.length - 1; i >= 1; i--) {
stack[i].destroy();
stack.splice(i, 1);
}
if (movingBlock) {
movingBlock.destroy();
movingBlock = null;
}
score = 0;
updateScore();
gameOver = false;
spawnMovingBlock();
}
// On game start
startGame();
// On game over, restart game when LK resets
// (LK will re-initialize the Game class, so no need to handle reset here)