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