/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { best: 0 }); /**** * Classes ****/ // IceBlock class: represents a single moving or stacked ice block var IceBlock = Container.expand(function () { var self = Container.call(this); // Default block size (will be set on creation) self.blockWidth = 900; self.blockHeight = 70; self.color = 0xB3E6FF; // Light blue ice color // Create and attach the block asset var blockAsset = self.attachAsset('iceBlock', { width: self.blockWidth, height: self.blockHeight, color: self.color, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Update block asset size if changed self.setSize = function (w, h) { self.blockWidth = w; self.blockHeight = h; blockAsset.width = w; blockAsset.height = h; }; // Set color (for possible effects) self.setColor = function (color) { self.color = color; blockAsset.color = color; }; // Animate block to a new position (used for drop) self.animateTo = function (targetY, duration, onFinish) { tween(self, { y: targetY }, { duration: duration, easing: tween.cubicOut, onFinish: onFinish }); }; // Animate block shrink (used for misaligned drops) self.animateShrink = function (newWidth, duration, _onFinish) { tween(self, { blockWidth: newWidth }, { duration: duration, easing: tween.cubicInOut, onFinish: function onFinish() { self.setSize(newWidth, self.blockHeight); if (_onFinish) _onFinish(); } }); }; // For collision and alignment calculations self.getLeft = function () { return self.x - self.blockWidth / 2; }; self.getRight = function () { return self.x + self.blockWidth / 2; }; // For updating asset size if tweened self.update = function () { // If tweening blockWidth, update asset if (blockAsset.width !== self.blockWidth) { blockAsset.width = self.blockWidth; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Tween plugin for block drop and shrink animations // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; // Add background image to the game scene var bg = LK.getAsset('iceBg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: GAME_WIDTH, height: GAME_HEIGHT }); game.addChild(bg); var START_BLOCK_WIDTH = 900; var BLOCK_HEIGHT = 70; var BLOCK_COLOR = 0xB3E6FF; var BLOCK_SPEED = 16; // px per frame var MIN_BLOCK_WIDTH = 80; // Minimum width before game over var DROP_ANIMATION_TIME = 180; // ms var SHRINK_ANIMATION_TIME = 120; // ms var START_Y = GAME_HEIGHT - 400; // Y position of the first block // Game state var stack = []; // Array of stacked blocks (bottom to top) var movingBlock = null; // The current moving block var movingDirection = 1; // 1 = right, -1 = left var isDropping = false; // Prevents double drop var scoreTxt = null; var bestTxt = null; var currentScore = 0; var bestScore = 0; // Special ability state var specialReady = true; var specialCooldown = 0; // ms remaining var SPECIAL_COOLDOWN_TIME = 5000; // 5 seconds cooldown var specialBtn = null; // Storage for best score if (typeof storage.best === 'number') { bestScore = storage.best; } // Score display scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); bestTxt = new Text2('Best: ' + bestScore, { size: 60, fill: 0xAEEFFF }); bestTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bestTxt); bestTxt.y = 120; // Special ability button specialBtn = new Text2('ICE POWER', { size: 70, fill: specialReady ? 0x00eaff : 0x888888 }); specialBtn.anchor.set(0.5, 0.5); LK.gui.bottom.addChild(specialBtn); specialBtn.y = -120; // Raise above bottom edge specialBtn.x = 0; // Centered // Cooldown text var cooldownTxt = new Text2('', { size: 40, fill: 0xAEEFFF }); cooldownTxt.anchor.set(0.5, 0.5); LK.gui.bottom.addChild(cooldownTxt); cooldownTxt.y = -40; cooldownTxt.x = 0; // Helper: update score display function updateScoreDisplay() { scoreTxt.setText(currentScore); if (currentScore > bestScore) { bestScore = currentScore; storage.best = bestScore; bestTxt.setText('Best: ' + bestScore); } } // Helper: center X for a block function getCenterX() { return GAME_WIDTH / 2; } // Helper: spawn a new moving block function spawnMovingBlock(width) { var block = new IceBlock(); block.setSize(width, BLOCK_HEIGHT); block.setColor(BLOCK_COLOR); // Start above the last block, or at START_Y if first var y = START_Y - stack.length * BLOCK_HEIGHT; block.y = y; block.blockHeight = BLOCK_HEIGHT; // Start at left or right edge, alternate each time if (stack.length % 2 === 0) { block.x = 0 + width / 2; movingDirection = 1; } else { block.x = GAME_WIDTH - width / 2; movingDirection = -1; } game.addChild(block); return block; } // Helper: get top block (last in stack) function getTopBlock() { if (stack.length === 0) return null; return stack[stack.length - 1]; } // Helper: drop the moving block function dropBlock() { if (!movingBlock || isDropping) return; isDropping = true; // Find where the block should land (on top of stack or at START_Y) var targetY = START_Y - stack.length * BLOCK_HEIGHT; // Animate drop movingBlock.animateTo(targetY, DROP_ANIMATION_TIME, function () { // After drop, check alignment var prevBlock = getTopBlock(); var overlapLeft, overlapRight, overlapWidth; if (prevBlock) { // Calculate overlap var left = Math.max(movingBlock.getLeft(), prevBlock.getLeft()); var right = Math.min(movingBlock.getRight(), prevBlock.getRight()); overlapWidth = right - left; if (overlapWidth <= 0 || overlapWidth < MIN_BLOCK_WIDTH) { // No overlap or too small: game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } // Shrink block to overlap var newWidth = overlapWidth; var newX = left + overlapWidth / 2; // Animate shrink and move to center of overlap tween(movingBlock, { x: newX }, { duration: SHRINK_ANIMATION_TIME, easing: tween.cubicInOut }); movingBlock.animateShrink(newWidth, SHRINK_ANIMATION_TIME, function () { // Add to stack stack.push(movingBlock); // Play block placed sound LK.getSound('a').play(); currentScore++; updateScoreDisplay(); // Next block nextTurn(newWidth); }); } else { // First block, always succeeds stack.push(movingBlock); // Play block placed sound LK.getSound('a').play(); currentScore = 1; updateScoreDisplay(); nextTurn(movingBlock.blockWidth); } }); } // Helper: start next turn function nextTurn(width) { // Check win condition (optional: e.g. 50 blocks) if (currentScore >= 50) { LK.showYouWin(); return; } // Spawn next moving block movingBlock = spawnMovingBlock(width); isDropping = false; } // Game move handler: tap/click anywhere to drop game.down = function (x, y, obj) { // Check if special button pressed if (specialBtn && obj && obj.target === specialBtn && specialReady && movingBlock && !isDropping) { // Activate special ability: perfect align the block specialReady = false; specialCooldown = SPECIAL_COOLDOWN_TIME; specialBtn.setText('COOLDOWN'); specialBtn.setStyle({ fill: 0x888888 }); // Instantly align moving block to top of stack (or center if first) var prevBlock = getTopBlock(); if (prevBlock) { movingBlock.x = prevBlock.x; movingBlock.blockWidth = prevBlock.blockWidth; movingBlock.setSize(prevBlock.blockWidth, BLOCK_HEIGHT); } else { movingBlock.x = getCenterX(); } // Drop the block as if perfectly aligned dropBlock(); return; } // Normal drop if (!isDropping) { dropBlock(); } }; // Main game update loop game.update = function () { // Move the moving block horizontally if (movingBlock && !isDropping) { movingBlock.x += movingDirection * BLOCK_SPEED; // Bounce at edges var halfW = movingBlock.blockWidth / 2; if (movingBlock.x + halfW > GAME_WIDTH) { movingBlock.x = GAME_WIDTH - halfW; movingDirection = -1; } if (movingBlock.x - halfW < 0) { movingBlock.x = halfW; movingDirection = 1; } } // Update all blocks (for tweened width) for (var i = 0; i < stack.length; i++) { if (stack[i].update) stack[i].update(); } if (movingBlock && movingBlock.update) movingBlock.update(); // Special ability cooldown logic if (!specialReady && specialCooldown > 0) { specialCooldown -= 1000 / 60; // Approx ms per frame at 60fps if (specialCooldown <= 0) { specialReady = true; specialBtn.setText('ICE POWER'); specialBtn.setStyle({ fill: 0x00eaff }); cooldownTxt.setText(''); } else { // Show seconds left var secs = Math.ceil(specialCooldown / 1000); cooldownTxt.setText('Ready in ' + secs + 's'); } } else if (specialReady) { cooldownTxt.setText(''); } }; // Game reset: clear stack and start new game function resetGame() { // Remove all blocks for (var i = 0; i < stack.length; i++) { stack[i].destroy(); } stack = []; if (movingBlock) { movingBlock.destroy(); movingBlock = null; } currentScore = 0; updateScoreDisplay(); // Start first block movingBlock = spawnMovingBlock(START_BLOCK_WIDTH); isDropping = false; // Reset special ability specialReady = true; specialCooldown = 0; if (specialBtn) { specialBtn.setText('ICE POWER'); specialBtn.setStyle({ fill: 0x00eaff }); } if (typeof cooldownTxt !== 'undefined') { cooldownTxt.setText(''); } } // Listen for game over to reset LK.on('gameover', function () { // Delay reset to allow game over screen LK.setTimeout(function () { resetGame(); }, 800); }); // Listen for you win to reset LK.on('youwin', function () { LK.setTimeout(function () { resetGame(); }, 1200); }); // Start the game resetGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
best: 0
});
/****
* Classes
****/
// IceBlock class: represents a single moving or stacked ice block
var IceBlock = Container.expand(function () {
var self = Container.call(this);
// Default block size (will be set on creation)
self.blockWidth = 900;
self.blockHeight = 70;
self.color = 0xB3E6FF; // Light blue ice color
// Create and attach the block asset
var blockAsset = self.attachAsset('iceBlock', {
width: self.blockWidth,
height: self.blockHeight,
color: self.color,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Update block asset size if changed
self.setSize = function (w, h) {
self.blockWidth = w;
self.blockHeight = h;
blockAsset.width = w;
blockAsset.height = h;
};
// Set color (for possible effects)
self.setColor = function (color) {
self.color = color;
blockAsset.color = color;
};
// Animate block to a new position (used for drop)
self.animateTo = function (targetY, duration, onFinish) {
tween(self, {
y: targetY
}, {
duration: duration,
easing: tween.cubicOut,
onFinish: onFinish
});
};
// Animate block shrink (used for misaligned drops)
self.animateShrink = function (newWidth, duration, _onFinish) {
tween(self, {
blockWidth: newWidth
}, {
duration: duration,
easing: tween.cubicInOut,
onFinish: function onFinish() {
self.setSize(newWidth, self.blockHeight);
if (_onFinish) _onFinish();
}
});
};
// For collision and alignment calculations
self.getLeft = function () {
return self.x - self.blockWidth / 2;
};
self.getRight = function () {
return self.x + self.blockWidth / 2;
};
// For updating asset size if tweened
self.update = function () {
// If tweening blockWidth, update asset
if (blockAsset.width !== self.blockWidth) {
blockAsset.width = self.blockWidth;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Tween plugin for block drop and shrink animations
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
// Add background image to the game scene
var bg = LK.getAsset('iceBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: GAME_WIDTH,
height: GAME_HEIGHT
});
game.addChild(bg);
var START_BLOCK_WIDTH = 900;
var BLOCK_HEIGHT = 70;
var BLOCK_COLOR = 0xB3E6FF;
var BLOCK_SPEED = 16; // px per frame
var MIN_BLOCK_WIDTH = 80; // Minimum width before game over
var DROP_ANIMATION_TIME = 180; // ms
var SHRINK_ANIMATION_TIME = 120; // ms
var START_Y = GAME_HEIGHT - 400; // Y position of the first block
// Game state
var stack = []; // Array of stacked blocks (bottom to top)
var movingBlock = null; // The current moving block
var movingDirection = 1; // 1 = right, -1 = left
var isDropping = false; // Prevents double drop
var scoreTxt = null;
var bestTxt = null;
var currentScore = 0;
var bestScore = 0;
// Special ability state
var specialReady = true;
var specialCooldown = 0; // ms remaining
var SPECIAL_COOLDOWN_TIME = 5000; // 5 seconds cooldown
var specialBtn = null;
// Storage for best score
if (typeof storage.best === 'number') {
bestScore = storage.best;
}
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
bestTxt = new Text2('Best: ' + bestScore, {
size: 60,
fill: 0xAEEFFF
});
bestTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(bestTxt);
bestTxt.y = 120;
// Special ability button
specialBtn = new Text2('ICE POWER', {
size: 70,
fill: specialReady ? 0x00eaff : 0x888888
});
specialBtn.anchor.set(0.5, 0.5);
LK.gui.bottom.addChild(specialBtn);
specialBtn.y = -120; // Raise above bottom edge
specialBtn.x = 0; // Centered
// Cooldown text
var cooldownTxt = new Text2('', {
size: 40,
fill: 0xAEEFFF
});
cooldownTxt.anchor.set(0.5, 0.5);
LK.gui.bottom.addChild(cooldownTxt);
cooldownTxt.y = -40;
cooldownTxt.x = 0;
// Helper: update score display
function updateScoreDisplay() {
scoreTxt.setText(currentScore);
if (currentScore > bestScore) {
bestScore = currentScore;
storage.best = bestScore;
bestTxt.setText('Best: ' + bestScore);
}
}
// Helper: center X for a block
function getCenterX() {
return GAME_WIDTH / 2;
}
// Helper: spawn a new moving block
function spawnMovingBlock(width) {
var block = new IceBlock();
block.setSize(width, BLOCK_HEIGHT);
block.setColor(BLOCK_COLOR);
// Start above the last block, or at START_Y if first
var y = START_Y - stack.length * BLOCK_HEIGHT;
block.y = y;
block.blockHeight = BLOCK_HEIGHT;
// Start at left or right edge, alternate each time
if (stack.length % 2 === 0) {
block.x = 0 + width / 2;
movingDirection = 1;
} else {
block.x = GAME_WIDTH - width / 2;
movingDirection = -1;
}
game.addChild(block);
return block;
}
// Helper: get top block (last in stack)
function getTopBlock() {
if (stack.length === 0) return null;
return stack[stack.length - 1];
}
// Helper: drop the moving block
function dropBlock() {
if (!movingBlock || isDropping) return;
isDropping = true;
// Find where the block should land (on top of stack or at START_Y)
var targetY = START_Y - stack.length * BLOCK_HEIGHT;
// Animate drop
movingBlock.animateTo(targetY, DROP_ANIMATION_TIME, function () {
// After drop, check alignment
var prevBlock = getTopBlock();
var overlapLeft, overlapRight, overlapWidth;
if (prevBlock) {
// Calculate overlap
var left = Math.max(movingBlock.getLeft(), prevBlock.getLeft());
var right = Math.min(movingBlock.getRight(), prevBlock.getRight());
overlapWidth = right - left;
if (overlapWidth <= 0 || overlapWidth < MIN_BLOCK_WIDTH) {
// No overlap or too small: game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
// Shrink block to overlap
var newWidth = overlapWidth;
var newX = left + overlapWidth / 2;
// Animate shrink and move to center of overlap
tween(movingBlock, {
x: newX
}, {
duration: SHRINK_ANIMATION_TIME,
easing: tween.cubicInOut
});
movingBlock.animateShrink(newWidth, SHRINK_ANIMATION_TIME, function () {
// Add to stack
stack.push(movingBlock);
// Play block placed sound
LK.getSound('a').play();
currentScore++;
updateScoreDisplay();
// Next block
nextTurn(newWidth);
});
} else {
// First block, always succeeds
stack.push(movingBlock);
// Play block placed sound
LK.getSound('a').play();
currentScore = 1;
updateScoreDisplay();
nextTurn(movingBlock.blockWidth);
}
});
}
// Helper: start next turn
function nextTurn(width) {
// Check win condition (optional: e.g. 50 blocks)
if (currentScore >= 50) {
LK.showYouWin();
return;
}
// Spawn next moving block
movingBlock = spawnMovingBlock(width);
isDropping = false;
}
// Game move handler: tap/click anywhere to drop
game.down = function (x, y, obj) {
// Check if special button pressed
if (specialBtn && obj && obj.target === specialBtn && specialReady && movingBlock && !isDropping) {
// Activate special ability: perfect align the block
specialReady = false;
specialCooldown = SPECIAL_COOLDOWN_TIME;
specialBtn.setText('COOLDOWN');
specialBtn.setStyle({
fill: 0x888888
});
// Instantly align moving block to top of stack (or center if first)
var prevBlock = getTopBlock();
if (prevBlock) {
movingBlock.x = prevBlock.x;
movingBlock.blockWidth = prevBlock.blockWidth;
movingBlock.setSize(prevBlock.blockWidth, BLOCK_HEIGHT);
} else {
movingBlock.x = getCenterX();
}
// Drop the block as if perfectly aligned
dropBlock();
return;
}
// Normal drop
if (!isDropping) {
dropBlock();
}
};
// Main game update loop
game.update = function () {
// Move the moving block horizontally
if (movingBlock && !isDropping) {
movingBlock.x += movingDirection * BLOCK_SPEED;
// Bounce at edges
var halfW = movingBlock.blockWidth / 2;
if (movingBlock.x + halfW > GAME_WIDTH) {
movingBlock.x = GAME_WIDTH - halfW;
movingDirection = -1;
}
if (movingBlock.x - halfW < 0) {
movingBlock.x = halfW;
movingDirection = 1;
}
}
// Update all blocks (for tweened width)
for (var i = 0; i < stack.length; i++) {
if (stack[i].update) stack[i].update();
}
if (movingBlock && movingBlock.update) movingBlock.update();
// Special ability cooldown logic
if (!specialReady && specialCooldown > 0) {
specialCooldown -= 1000 / 60; // Approx ms per frame at 60fps
if (specialCooldown <= 0) {
specialReady = true;
specialBtn.setText('ICE POWER');
specialBtn.setStyle({
fill: 0x00eaff
});
cooldownTxt.setText('');
} else {
// Show seconds left
var secs = Math.ceil(specialCooldown / 1000);
cooldownTxt.setText('Ready in ' + secs + 's');
}
} else if (specialReady) {
cooldownTxt.setText('');
}
};
// Game reset: clear stack and start new game
function resetGame() {
// Remove all blocks
for (var i = 0; i < stack.length; i++) {
stack[i].destroy();
}
stack = [];
if (movingBlock) {
movingBlock.destroy();
movingBlock = null;
}
currentScore = 0;
updateScoreDisplay();
// Start first block
movingBlock = spawnMovingBlock(START_BLOCK_WIDTH);
isDropping = false;
// Reset special ability
specialReady = true;
specialCooldown = 0;
if (specialBtn) {
specialBtn.setText('ICE POWER');
specialBtn.setStyle({
fill: 0x00eaff
});
}
if (typeof cooldownTxt !== 'undefined') {
cooldownTxt.setText('');
}
}
// Listen for game over to reset
LK.on('gameover', function () {
// Delay reset to allow game over screen
LK.setTimeout(function () {
resetGame();
}, 800);
});
// Listen for you win to reset
LK.on('youwin', function () {
LK.setTimeout(function () {
resetGame();
}, 1200);
});
// Start the game
resetGame();