/****
* 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();