/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Block class: represents a single swinging/dropping block var Block = Container.expand(function () { var self = Container.call(this); // Block properties self.blockWidth = 600; // Default, will be set on creation self.blockHeight = 100; // Constant height self.color = 0x4a90e2; // Default color, can be changed // State self.isDropping = false; self.isLanded = false; self.swingDirection = 1; // 1: right, -1: left self.swingSpeed = 8; // px per frame self.swingRange = 700; // How far from center to swing self.baseY = 350; // Y position for swinging self.targetY = 0; // Where to land self.minX = 0; // Left limit for swinging self.maxX = 0; // Right limit for swinging // Graphics var blockAsset = self.attachAsset('blockShape', { width: self.blockWidth, height: self.blockHeight, color: self.color, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); // Set block size and color self.setBlock = function (width, color) { self.blockWidth = width; blockAsset.width = width; blockAsset.height = self.blockHeight; if (color !== undefined) { self.color = color; blockAsset.color = color; } }; // Set swing limits self.setSwingLimits = function (minX, maxX) { self.minX = minX; self.maxX = maxX; }; // Start swinging self.startSwing = function () { self.isDropping = false; self.isLanded = false; self.y = self.baseY; self.swingDirection = 1; }; // Drop the block self.drop = function () { self.isDropping = true; }; // Called every frame self.update = function () { if (!self.isDropping && !self.isLanded) { // Swing left/right self.x += self.swingDirection * self.swingSpeed; if (self.x > self.maxX) { self.x = self.maxX; self.swingDirection = -1; } if (self.x < self.minX) { self.x = self.minX; self.swingDirection = 1; } } else if (self.isDropping && !self.isLanded) { // Drop down self.y += 32; // Drop speed if (self.y >= self.targetY) { self.y = self.targetY; self.isDropping = false; self.isLanded = true; } } }; // Animate block cut (for overhangs) self.animateCut = function (cutX, cutWidth, _onFinish) { // Animate the overhanging piece falling off var overhang = LK.getAsset('blockShape', { width: cutWidth, height: self.blockHeight, color: 0xcccccc, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: cutX, y: self.y }); game.addChild(overhang); tween(overhang, { y: self.y + 400, alpha: 0 }, { duration: 600, easing: tween.easeIn, onFinish: function onFinish() { overhang.destroy(); if (_onFinish) _onFinish(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var BLOCK_START_WIDTH = 600; var BLOCK_HEIGHT = 100; var BLOCK_MIN_WIDTH = 80; var SWING_MARGIN = 200; // Margin from edge for swinging var TOWER_BASE_Y = GAME_HEIGHT - 350; // Where the first block lands // Game state var blocks = []; var currentBlock = null; var lastBlock = null; var isGameOver = false; var score = 0; // Day/Night state var isDay = true; var dayNightTimer = 0; var DAY_DURATION = 1800; // frames (~30s at 60fps) var NIGHT_DURATION = 1200; // frames (~20s at 60fps) var dayColor = 0x87ceeb; // Light blue var nightColor = 0x222a36; // Dark blue // Combo system state var combo = 0; var maxCombo = 0; var comboTxt = new Text2('', { size: 80, fill: 0xFFD700 }); comboTxt.anchor.set(0.5, 0); comboTxt.visible = false; LK.gui.top.addChild(comboTxt); // Score display var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Helper: get random color for block function getBlockColor(idx) { var palette = [0x4a90e2, 0xf5a623, 0x7ed321, 0xd0021b, 0x9013fe, 0x50e3c2]; return palette[idx % palette.length]; } // Helper: create a new block function createBlock(width, y, color) { var block = new Block(); block.setBlock(width, color); block.baseY = 350; block.targetY = y; block.setSwingLimits(SWING_MARGIN + width / 2, GAME_WIDTH - SWING_MARGIN - width / 2); block.x = GAME_WIDTH / 2; block.y = block.baseY; block.startSwing(); return block; } // Start the game function startGame() { // Reset state for (var i = 0; i < blocks.length; i++) { blocks[i].destroy(); } blocks = []; currentBlock = null; lastBlock = null; isGameOver = false; score = 0; scoreTxt.setText(score); // Reset combo system combo = 0; maxCombo = 0; comboTxt.setText(''); comboTxt.visible = false; // Reset day/night state isDay = true; dayNightTimer = 0; game.setBackgroundColor(dayColor); // Place the first block (static, as tower base) var baseBlock = new Block(); baseBlock.setBlock(BLOCK_START_WIDTH, getBlockColor(0)); baseBlock.x = GAME_WIDTH / 2; baseBlock.y = TOWER_BASE_Y; baseBlock.isDropping = false; baseBlock.isLanded = true; game.addChild(baseBlock); blocks.push(baseBlock); lastBlock = baseBlock; // Place the first swinging block spawnNextBlock(); } // Spawn the next swinging block function spawnNextBlock() { var idx = blocks.length; var width = lastBlock.blockWidth; var color = getBlockColor(idx); var y = lastBlock.y - BLOCK_HEIGHT; var block = createBlock(width, y, color); game.addChild(block); blocks.push(block); currentBlock = block; } // Handle tap to drop block game.down = function (x, y, obj) { if (isGameOver) return; if (!currentBlock || currentBlock.isDropping || currentBlock.isLanded) return; currentBlock.drop(); }; // Main update loop game.update = function () { if (isGameOver) return; // Day/Night transition logic dayNightTimer++; if (isDay && dayNightTimer >= DAY_DURATION) { isDay = false; dayNightTimer = 0; // Smooth transition to night tween(game, { backgroundColor: nightColor }, { duration: 1200, onUpdate: function onUpdate() { // Interpolate color for smoothness var t = this.progress; var r1 = dayColor >> 16 & 0xff, g1 = dayColor >> 8 & 0xff, b1 = dayColor & 0xff; var r2 = nightColor >> 16 & 0xff, g2 = nightColor >> 8 & 0xff, b2 = nightColor & 0xff; var r = Math.round(r1 + (r2 - r1) * t); var g = Math.round(g1 + (g2 - g1) * t); var b = Math.round(b1 + (b2 - b1) * t); game.setBackgroundColor(r << 16 | g << 8 | b); } }); } if (!isDay && dayNightTimer >= NIGHT_DURATION) { isDay = true; dayNightTimer = 0; // Smooth transition to day tween(game, { backgroundColor: dayColor }, { duration: 1200, onUpdate: function onUpdate() { var t = this.progress; var r1 = nightColor >> 16 & 0xff, g1 = nightColor >> 8 & 0xff, b1 = nightColor & 0xff; var r2 = dayColor >> 16 & 0xff, g2 = dayColor >> 8 & 0xff, b2 = dayColor & 0xff; var r = Math.round(r1 + (r2 - r1) * t); var g = Math.round(g1 + (g2 - g1) * t); var b = Math.round(b1 + (b2 - b1) * t); game.setBackgroundColor(r << 16 | g << 8 | b); } }); } // Update all blocks for (var i = 0; i < blocks.length; i++) { blocks[i].update(); } // If current block has landed, check alignment if (currentBlock && currentBlock.isLanded && !isGameOver) { // Check overlap with last block var prev = lastBlock; var curr = currentBlock; var prevLeft = prev.x - prev.blockWidth / 2; var prevRight = prev.x + prev.blockWidth / 2; var currLeft = curr.x - curr.blockWidth / 2; var currRight = curr.x + curr.blockWidth / 2; var overlapLeft = Math.max(prevLeft, currLeft); var overlapRight = Math.min(prevRight, currRight); var overlapWidth = overlapRight - overlapLeft; if (overlapWidth <= 0) { // No overlap: game over endGame(); return; } // If block is perfectly aligned, no cut var perfectAlign = Math.abs(curr.x - prev.x) < 2 && overlapWidth === curr.blockWidth; if (overlapWidth < curr.blockWidth) { // Cut off overhangs var cutLeft = currLeft < prevLeft; var cutRight = currRight > prevRight; // Animate left overhang if (cutLeft) { var cutW = prevLeft - currLeft; var cutX = curr.x - curr.blockWidth / 2 + cutW / 2; curr.animateCut(cutX, cutW); } // Animate right overhang if (cutRight) { var cutW = currRight - prevRight; var cutX = curr.x + curr.blockWidth / 2 - cutW / 2; curr.animateCut(cutX, cutW); } // Shrink block to overlap curr.setBlock(overlapWidth, curr.color); curr.x = (overlapLeft + overlapRight) / 2; } // Combo system: check for perfect alignment if (perfectAlign) { combo += 1; if (combo > maxCombo) maxCombo = combo; comboTxt.setText('COMBO x' + combo); comboTxt.visible = true; comboTxt.x = GAME_WIDTH / 2; comboTxt.y = 180; // Animate combo text comboTxt.alpha = 1; tween(comboTxt, { alpha: 0 }, { duration: 900, onFinish: function onFinish() { comboTxt.visible = false; } }); } else { combo = 0; comboTxt.visible = false; } // Update score score += 1; scoreTxt.setText(score); // Prepare for next block lastBlock = curr; // If block is too small, game over if (curr.blockWidth < BLOCK_MIN_WIDTH) { endGame(); return; } // If tower reaches top, do nothing (game continues) // Spawn next block spawnNextBlock(); } }; // End the game function endGame() { isGameOver = true; // Flash screen red LK.effects.flashScreen(0xff0000, 800); // Show game over popup LK.showGameOver(); } // Asset initialization (shapes) // Start the game on load startGame(); ; // Play relaxing background music (loops by default) LK.playMusic('relaxing_music');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Block class: represents a single swinging/dropping block
var Block = Container.expand(function () {
var self = Container.call(this);
// Block properties
self.blockWidth = 600; // Default, will be set on creation
self.blockHeight = 100; // Constant height
self.color = 0x4a90e2; // Default color, can be changed
// State
self.isDropping = false;
self.isLanded = false;
self.swingDirection = 1; // 1: right, -1: left
self.swingSpeed = 8; // px per frame
self.swingRange = 700; // How far from center to swing
self.baseY = 350; // Y position for swinging
self.targetY = 0; // Where to land
self.minX = 0; // Left limit for swinging
self.maxX = 0; // Right limit for swinging
// Graphics
var blockAsset = self.attachAsset('blockShape', {
width: self.blockWidth,
height: self.blockHeight,
color: self.color,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Set block size and color
self.setBlock = function (width, color) {
self.blockWidth = width;
blockAsset.width = width;
blockAsset.height = self.blockHeight;
if (color !== undefined) {
self.color = color;
blockAsset.color = color;
}
};
// Set swing limits
self.setSwingLimits = function (minX, maxX) {
self.minX = minX;
self.maxX = maxX;
};
// Start swinging
self.startSwing = function () {
self.isDropping = false;
self.isLanded = false;
self.y = self.baseY;
self.swingDirection = 1;
};
// Drop the block
self.drop = function () {
self.isDropping = true;
};
// Called every frame
self.update = function () {
if (!self.isDropping && !self.isLanded) {
// Swing left/right
self.x += self.swingDirection * self.swingSpeed;
if (self.x > self.maxX) {
self.x = self.maxX;
self.swingDirection = -1;
}
if (self.x < self.minX) {
self.x = self.minX;
self.swingDirection = 1;
}
} else if (self.isDropping && !self.isLanded) {
// Drop down
self.y += 32; // Drop speed
if (self.y >= self.targetY) {
self.y = self.targetY;
self.isDropping = false;
self.isLanded = true;
}
}
};
// Animate block cut (for overhangs)
self.animateCut = function (cutX, cutWidth, _onFinish) {
// Animate the overhanging piece falling off
var overhang = LK.getAsset('blockShape', {
width: cutWidth,
height: self.blockHeight,
color: 0xcccccc,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: cutX,
y: self.y
});
game.addChild(overhang);
tween(overhang, {
y: self.y + 400,
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
overhang.destroy();
if (_onFinish) _onFinish();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BLOCK_START_WIDTH = 600;
var BLOCK_HEIGHT = 100;
var BLOCK_MIN_WIDTH = 80;
var SWING_MARGIN = 200; // Margin from edge for swinging
var TOWER_BASE_Y = GAME_HEIGHT - 350; // Where the first block lands
// Game state
var blocks = [];
var currentBlock = null;
var lastBlock = null;
var isGameOver = false;
var score = 0;
// Day/Night state
var isDay = true;
var dayNightTimer = 0;
var DAY_DURATION = 1800; // frames (~30s at 60fps)
var NIGHT_DURATION = 1200; // frames (~20s at 60fps)
var dayColor = 0x87ceeb; // Light blue
var nightColor = 0x222a36; // Dark blue
// Combo system state
var combo = 0;
var maxCombo = 0;
var comboTxt = new Text2('', {
size: 80,
fill: 0xFFD700
});
comboTxt.anchor.set(0.5, 0);
comboTxt.visible = false;
LK.gui.top.addChild(comboTxt);
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: get random color for block
function getBlockColor(idx) {
var palette = [0x4a90e2, 0xf5a623, 0x7ed321, 0xd0021b, 0x9013fe, 0x50e3c2];
return palette[idx % palette.length];
}
// Helper: create a new block
function createBlock(width, y, color) {
var block = new Block();
block.setBlock(width, color);
block.baseY = 350;
block.targetY = y;
block.setSwingLimits(SWING_MARGIN + width / 2, GAME_WIDTH - SWING_MARGIN - width / 2);
block.x = GAME_WIDTH / 2;
block.y = block.baseY;
block.startSwing();
return block;
}
// Start the game
function startGame() {
// Reset state
for (var i = 0; i < blocks.length; i++) {
blocks[i].destroy();
}
blocks = [];
currentBlock = null;
lastBlock = null;
isGameOver = false;
score = 0;
scoreTxt.setText(score);
// Reset combo system
combo = 0;
maxCombo = 0;
comboTxt.setText('');
comboTxt.visible = false;
// Reset day/night state
isDay = true;
dayNightTimer = 0;
game.setBackgroundColor(dayColor);
// Place the first block (static, as tower base)
var baseBlock = new Block();
baseBlock.setBlock(BLOCK_START_WIDTH, getBlockColor(0));
baseBlock.x = GAME_WIDTH / 2;
baseBlock.y = TOWER_BASE_Y;
baseBlock.isDropping = false;
baseBlock.isLanded = true;
game.addChild(baseBlock);
blocks.push(baseBlock);
lastBlock = baseBlock;
// Place the first swinging block
spawnNextBlock();
}
// Spawn the next swinging block
function spawnNextBlock() {
var idx = blocks.length;
var width = lastBlock.blockWidth;
var color = getBlockColor(idx);
var y = lastBlock.y - BLOCK_HEIGHT;
var block = createBlock(width, y, color);
game.addChild(block);
blocks.push(block);
currentBlock = block;
}
// Handle tap to drop block
game.down = function (x, y, obj) {
if (isGameOver) return;
if (!currentBlock || currentBlock.isDropping || currentBlock.isLanded) return;
currentBlock.drop();
};
// Main update loop
game.update = function () {
if (isGameOver) return;
// Day/Night transition logic
dayNightTimer++;
if (isDay && dayNightTimer >= DAY_DURATION) {
isDay = false;
dayNightTimer = 0;
// Smooth transition to night
tween(game, {
backgroundColor: nightColor
}, {
duration: 1200,
onUpdate: function onUpdate() {
// Interpolate color for smoothness
var t = this.progress;
var r1 = dayColor >> 16 & 0xff,
g1 = dayColor >> 8 & 0xff,
b1 = dayColor & 0xff;
var r2 = nightColor >> 16 & 0xff,
g2 = nightColor >> 8 & 0xff,
b2 = nightColor & 0xff;
var r = Math.round(r1 + (r2 - r1) * t);
var g = Math.round(g1 + (g2 - g1) * t);
var b = Math.round(b1 + (b2 - b1) * t);
game.setBackgroundColor(r << 16 | g << 8 | b);
}
});
}
if (!isDay && dayNightTimer >= NIGHT_DURATION) {
isDay = true;
dayNightTimer = 0;
// Smooth transition to day
tween(game, {
backgroundColor: dayColor
}, {
duration: 1200,
onUpdate: function onUpdate() {
var t = this.progress;
var r1 = nightColor >> 16 & 0xff,
g1 = nightColor >> 8 & 0xff,
b1 = nightColor & 0xff;
var r2 = dayColor >> 16 & 0xff,
g2 = dayColor >> 8 & 0xff,
b2 = dayColor & 0xff;
var r = Math.round(r1 + (r2 - r1) * t);
var g = Math.round(g1 + (g2 - g1) * t);
var b = Math.round(b1 + (b2 - b1) * t);
game.setBackgroundColor(r << 16 | g << 8 | b);
}
});
}
// Update all blocks
for (var i = 0; i < blocks.length; i++) {
blocks[i].update();
}
// If current block has landed, check alignment
if (currentBlock && currentBlock.isLanded && !isGameOver) {
// Check overlap with last block
var prev = lastBlock;
var curr = currentBlock;
var prevLeft = prev.x - prev.blockWidth / 2;
var prevRight = prev.x + prev.blockWidth / 2;
var currLeft = curr.x - curr.blockWidth / 2;
var currRight = curr.x + curr.blockWidth / 2;
var overlapLeft = Math.max(prevLeft, currLeft);
var overlapRight = Math.min(prevRight, currRight);
var overlapWidth = overlapRight - overlapLeft;
if (overlapWidth <= 0) {
// No overlap: game over
endGame();
return;
}
// If block is perfectly aligned, no cut
var perfectAlign = Math.abs(curr.x - prev.x) < 2 && overlapWidth === curr.blockWidth;
if (overlapWidth < curr.blockWidth) {
// Cut off overhangs
var cutLeft = currLeft < prevLeft;
var cutRight = currRight > prevRight;
// Animate left overhang
if (cutLeft) {
var cutW = prevLeft - currLeft;
var cutX = curr.x - curr.blockWidth / 2 + cutW / 2;
curr.animateCut(cutX, cutW);
}
// Animate right overhang
if (cutRight) {
var cutW = currRight - prevRight;
var cutX = curr.x + curr.blockWidth / 2 - cutW / 2;
curr.animateCut(cutX, cutW);
}
// Shrink block to overlap
curr.setBlock(overlapWidth, curr.color);
curr.x = (overlapLeft + overlapRight) / 2;
}
// Combo system: check for perfect alignment
if (perfectAlign) {
combo += 1;
if (combo > maxCombo) maxCombo = combo;
comboTxt.setText('COMBO x' + combo);
comboTxt.visible = true;
comboTxt.x = GAME_WIDTH / 2;
comboTxt.y = 180;
// Animate combo text
comboTxt.alpha = 1;
tween(comboTxt, {
alpha: 0
}, {
duration: 900,
onFinish: function onFinish() {
comboTxt.visible = false;
}
});
} else {
combo = 0;
comboTxt.visible = false;
}
// Update score
score += 1;
scoreTxt.setText(score);
// Prepare for next block
lastBlock = curr;
// If block is too small, game over
if (curr.blockWidth < BLOCK_MIN_WIDTH) {
endGame();
return;
}
// If tower reaches top, do nothing (game continues)
// Spawn next block
spawnNextBlock();
}
};
// End the game
function endGame() {
isGameOver = true;
// Flash screen red
LK.effects.flashScreen(0xff0000, 800);
// Show game over popup
LK.showGameOver();
}
// Asset initialization (shapes)
// Start the game on load
startGame();
;
// Play relaxing background music (loops by default)
LK.playMusic('relaxing_music');