/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Block = Container.expand(function (color, width) {
var self = Container.call(this);
// Default values if not provided
color = color || 0x3498db;
width = width || 512;
// Create block graphics
var blockGraphics = self.attachAsset('block', {
anchorX: 0.5,
anchorY: 0.5,
width: width,
tint: color
});
// State tracking
self.placed = false;
self.originalWidth = width;
self.fallDirection = 0; // 0: none, -1: left, 1: right
self.movementSpeed = 8;
self.movementDirection = 1; // 1: right, -1: left
self.scale.y = 0;
self.targetY = 0;
self.initialY = 0;
// Resize the block (used when block partially falls off)
self.resize = function (newWidth, offsetX) {
if (newWidth <= 0) {
return;
}
// Immediately set the width to match exactly without animation
blockGraphics.width = newWidth;
// Update the original width to reflect the new truncated size
self.originalWidth = newWidth;
if (offsetX !== undefined) {
// Immediately update position
self.x += offsetX;
}
};
// Drop the block into position with animation
self.dropIntoPlace = function (targetYPos) {
self.targetY = targetYPos;
self.initialY = self.y;
// First expand the y-scale
tween(self.scale, {
y: 1
}, {
duration: 400,
easing: tween.bounceOut
});
// Then animate moving down to its target position
tween(self, {
y: targetYPos
}, {
duration: 500,
easing: tween.bounceOut
});
};
// Update method called every frame
self.update = function () {
if (!self.placed) {
// Move block back and forth
self.x += self.movementSpeed * self.movementDirection;
// Check if we need to change direction
if (self.x > 2048 - self.originalWidth / 2) {
self.movementDirection = -1;
} else if (self.x < self.originalWidth / 2) {
self.movementDirection = 1;
}
} else if (self.fallDirection !== 0) {
// If block is set to fall, make it fall and rotate
self.y += 15;
self.rotation += 0.1 * self.fallDirection;
// Remove the block when it's off-screen
if (self.y > 2732 + 200) {
self.destroy();
}
}
};
return self;
});
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
// Define fruit types and their properties - now as berry types
var fruitTypes = {
apple: {
width: 60,
height: 60,
color: 0xe74c3c,
shape: 'ellipse'
},
banana: {
width: 70,
height: 70,
color: 0xf1c40f,
shape: 'ellipse'
},
orange: {
width: 55,
height: 55,
color: 0xf39c12,
shape: 'ellipse'
},
grape: {
width: 45,
height: 45,
color: 0x9b59b6,
shape: 'ellipse'
},
watermelon: {
width: 65,
height: 65,
color: 0x2ecc71,
shape: 'ellipse'
}
};
// Set default if type not provided or invalid
type = fruitTypes[type] ? type : 'apple';
// Initialize fruit properties
var props = fruitTypes[type];
// Create main berry shape
var fruitGraphics = self.attachAsset('fruit', {
anchorX: 0.5,
anchorY: 0.5,
tint: props.color,
width: props.width,
height: props.height,
alpha: 0.85
});
// Add a subtle glow effect
tween(fruitGraphics, {
alpha: 0.95
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Add movement properties
self.speedY = 2 + Math.random() * 3;
self.speedX = (Math.random() * 2 - 1) * 2;
self.rotationSpeed = Math.random() * 0.04 - 0.02;
self.width = props.width;
self.height = props.height;
// Track last position for collision detection
self.lastY = self.y;
self.lastX = self.x;
self.value = 0;
switch (type) {
case 'apple':
self.value = 1;
break;
case 'banana':
self.value = 2;
break;
case 'orange':
self.value = 5;
break;
case 'grape':
self.value = 3;
break;
case 'watermelon':
self.value = 10;
break;
}
// Update method called every frame
self.update = function () {
// Store last position
self.lastY = self.y;
self.lastX = self.x;
// Move fruit
self.y -= self.speedY;
self.x += self.speedX;
// Rotate fruit
self.rotation += self.rotationSpeed;
// Bounce off walls
if (self.x < self.width / 2 || self.x > 2048 - self.width / 2) {
self.speedX *= -1;
}
// Remove if off screen at top
if (self.y < -100) {
self.destroy();
}
};
return self;
});
var ScoreDisplay = Container.expand(function () {
var self = Container.call(this);
// Background for score
var background = self.attachAsset('scoreBackground', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
// Score text
self.scoreText = new Text2('0', {
size: 60,
fill: 0xFFFFFF
});
self.scoreText.anchor.set(0.5, 0.5);
self.addChild(self.scoreText);
// Height text
self.heightText = new Text2('Height: 0m', {
size: 30,
fill: 0xFFFFFF
});
self.heightText.anchor.set(0.5, 0.5);
self.heightText.y = 50;
self.addChild(self.heightText);
// Update score display
self.updateScore = function (score, height) {
self.scoreText.setText(score.toString());
self.heightText.setText('Height: ' + height.toFixed(1) + 'm');
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Black background
});
/****
* Game Code
****/
// Set real background with gradient
game.setBackgroundColor(0x1e3c72); // Deep blue gradient start
// Game state variables
var score = 0;
var towerHeight = 0;
var gameActive = true;
var level = 1;
var blockSpeed = 8;
var perfectMatches = 0;
var blockColors = [0x3498db,
// Blue
0xe74c3c,
// Red
0x2ecc71,
// Green
0xf39c12,
// Orange
0x9b59b6,
// Purple
0x1abc9c // Turquoise
];
// Background and music variables
var backgrounds = [];
var currentBackground = 0;
var musicTracks = ['backgroundMusic', 'backgroundMusic1', 'backgroundMusic2'];
var currentMusicTrack = 0;
var backgroundChangeInterval = 22 * 60; // 22 seconds at 60fps
var backgroundChangeTimer = 0;
var backgroundUpdateTimer = 0;
// Fruit variables
var fruits = [];
var fruitTypes = ['apple', 'banana', 'orange', 'grape', 'watermelon'];
var lastFruitTime = 0;
var fruitGenerationRate = 120; // frames between fruit generation
// Game objects
var blocks = [];
var currentBlock = null;
var baseFloor = null;
var scoreDisplay = null;
var lastBlockPosition = {
x: 2048 / 2,
width: 512
};
var dropY = 200; // Starting Y position for dropping blocks
// Setup backgrounds and transition to next background
function setupBackgrounds() {
// Create all three backgrounds
var bg1 = LK.getAsset('background1', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
bg1.x = 2048 / 2;
bg1.y = 2732 / 2;
var bg2 = LK.getAsset('background2', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
bg2.x = 2048 / 2;
bg2.y = 2732 / 2;
var bg3 = LK.getAsset('background3', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
bg3.x = 2048 / 2;
bg3.y = 2732 / 2;
// Add backgrounds to game and array
game.addChild(bg1);
game.addChild(bg2);
game.addChild(bg3);
backgrounds = [bg1, bg2, bg3];
// Show first background
tween(backgrounds[0], {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
}
// Change to next background
function changeBackground() {
var nextBg = (currentBackground + 1) % backgrounds.length;
// Fade out current background
tween(backgrounds[currentBackground], {
alpha: 0
}, {
duration: 1200,
easing: tween.easeInOut
});
// Fade in next background
tween(backgrounds[nextBg], {
alpha: 1
}, {
duration: 1200,
easing: tween.easeInOut
});
currentBackground = nextBg;
// Do not update music track here to avoid interrupting current music
}
// Change music to match current background
function changeMusic() {
// Each background has its associated music track
// This function is no longer needed as we handle music transitions
// in the onMusicEnd callback to ensure music is not interrupted
}
// Initialize the game
function initGame() {
// Setup backgrounds
setupBackgrounds();
// Create base floor with animation
baseFloor = LK.getAsset('baseFloor', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
baseFloor.x = 2048 / 2;
baseFloor.y = 2732 - 300;
baseFloor.scale.x = 0.5;
game.addChild(baseFloor);
// Animate the base floor appearing
tween(baseFloor, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(baseFloor.scale, {
x: 1
}, {
duration: 800,
easing: tween.elasticOut
});
// Set last block position to be above the base floor (not overlapping)
// Use the baseFloor width as the initial width for the first block
lastBlockPosition = {
x: baseFloor.x,
width: baseFloor.width,
// The first block will have the same width as baseFloor
y: baseFloor.y - baseFloor.height / 2 // Position above the baseFloor with no overlap
};
// Initialize score display with animation
scoreDisplay = new ScoreDisplay();
scoreDisplay.x = 2048 / 2;
scoreDisplay.y = -50; // Start off-screen
scoreDisplay.alpha = 0;
game.addChild(scoreDisplay);
// Add "Glaud" text in the upper right corner
var glaudText = new Text2('Glaud', {
size: 54,
fill: 0xFF8800 // Orange color for "Glaud"
});
glaudText.anchor.set(1, 0); // Anchor to top right
glaudText.x = 2048 - 20; // 20px from right edge
glaudText.y = 20; // 20px from top edge
glaudText.alpha = 0.7; // Slightly transparent
game.addChild(glaudText);
// Animate score display
tween(scoreDisplay, {
y: 100,
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
// Reset game state variables
score = 0;
towerHeight = 0;
gameActive = true;
level = 1;
blockSpeed = 8;
perfectMatches = 0;
blocks = [];
fruits = [];
lastFruitTime = 0;
backgroundChangeTimer = 0;
// Set the drop Y position to be above the base floor (not overlapping)
dropY = baseFloor.y - baseFloor.height;
// Create first block with a slight delay for better game flow
LK.setTimeout(function () {
createNewBlock();
}, 500);
// Play only the first music track, the others will play sequentially
currentMusicTrack = 0;
LK.playMusic(musicTracks[currentMusicTrack], {
loop: false,
fade: {
start: 0,
end: 1,
duration: 1000
}
});
}
// Create a new block to drop
function createNewBlock() {
if (!gameActive) {
return;
}
// Choose a random color that's different from the last block
var color;
do {
color = blockColors[Math.floor(Math.random() * blockColors.length)];
} while (blocks.length > 0 && blocks[blocks.length - 1].tint === color && blockColors.length > 1);
// Use the width from lastBlockPosition directly - this is already properly updated after cutting
var topBlockWidth = lastBlockPosition.width;
// Create new block with the exact width of the last block position
currentBlock = new Block(color, topBlockWidth);
currentBlock.x = lastBlockPosition.x; // Position at the same x as the last block
currentBlock.y = lastBlockPosition.y - currentBlock.height; // Position above the last block with no overlap
currentBlock.movementSpeed = blockSpeed;
// Scale initially set to 0 in y axis (will animate in)
currentBlock.scale.y = 0;
// Add block to the game and blocks array
game.addChild(currentBlock);
blocks.push(currentBlock);
// Animate the block entry - already positioned correctly, just need a small animation
tween(currentBlock, {
y: currentBlock.y + 5 // Small bounce effect
}, {
duration: 500,
easing: tween.bounceOut
});
// Animate the scale of the block
tween(currentBlock.scale, {
y: 1
}, {
duration: 400,
easing: tween.easeOut
});
// Apply a subtle shadow effect
if (blocks.length > 1) {
// Get reference to the top block in the tower
var topBlock = null;
for (var i = blocks.length - 2; i >= 0; i--) {
if (blocks[i].placed && !blocks[i].fallDirection) {
topBlock = blocks[i];
break;
}
}
if (topBlock) {
// Slight tint adjustment to create shadow effect on blocks beneath
tween(topBlock, {
alpha: 0.9
}, {
duration: 500,
easing: tween.linear
});
}
}
}
// Handle game clicks/taps
function handleTap() {
if (!currentBlock || !gameActive) {
return;
}
// Calculate overlap with previous block
var overlapLeft = Math.max(lastBlockPosition.x - lastBlockPosition.width / 2, currentBlock.x - currentBlock.originalWidth / 2);
var overlapRight = Math.min(lastBlockPosition.x + lastBlockPosition.width / 2, currentBlock.x + currentBlock.originalWidth / 2);
var overlapWidth = overlapRight - overlapLeft;
// If blocks don't overlap at all, game over
if (overlapWidth <= 0) {
gameOver();
return;
}
// Play place block sound
LK.getSound('placeBlock').play();
// Mark the block as placed
currentBlock.placed = true;
// Calculate the new position and width for the current block
var newCenterX = overlapLeft + overlapWidth / 2;
var newWidth = overlapWidth;
// Handle partial block
if (newWidth < currentBlock.originalWidth) {
// Determine which side falls off
var fallBlockWidth = currentBlock.originalWidth - newWidth;
var fallBlock = new Block(currentBlock.tint, fallBlockWidth);
if (currentBlock.x < newCenterX) {
// Left part falls off
fallBlock.x = overlapLeft - fallBlockWidth / 2;
fallBlock.fallDirection = -1;
currentBlock.resize(newWidth, newCenterX - currentBlock.x);
} else {
// Right part falls off
fallBlock.x = overlapRight + fallBlockWidth / 2;
fallBlock.fallDirection = 1;
currentBlock.resize(newWidth, newCenterX - currentBlock.x);
}
fallBlock.y = currentBlock.y;
fallBlock.placed = true;
fallBlock.scale.y = 1;
game.addChild(fallBlock);
blocks.push(fallBlock);
// Animate falling block with rotation
tween(fallBlock, {
rotation: fallBlock.fallDirection * Math.PI * 0.5
}, {
duration: 800,
easing: tween.easeIn
});
// Play falling sound
LK.getSound('fallBlock').play();
} else {
// Perfect match!
perfectMatches++;
LK.getSound('perfectMatch').play();
// Enhanced visual effect for perfect match
LK.effects.flashObject(currentBlock, 0xFFFFFF, 300);
// Extra animation to emphasize perfect match
tween(currentBlock.scale, {
x: 1.1,
y: 1.05
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentBlock.scale, {
x: 1,
y: 1
}, {
duration: 150,
easing: tween.bounceOut
});
}
});
}
// Smoothly move the current block to its final position
tween.stop(currentBlock, {
x: true
});
tween(currentBlock, {
x: newCenterX
}, {
duration: 300,
easing: tween.easeOut
});
// Calculate score based on match accuracy
var accuracy = newWidth / currentBlock.originalWidth;
var blockScore = Math.round(accuracy * 100);
// Perfect bonus
if (accuracy >= 0.95) {
blockScore += 50;
}
score += blockScore;
// Update tower height (each block adds height)
towerHeight += currentBlock.height / 150; // Convert to "meters"
// Update last block position for next block
// Store the new width after cutting for the next block to be sized correctly
lastBlockPosition = {
x: newCenterX,
// Use the new center position after resizing
width: newWidth,
// Use the exact new width after cutting
y: currentBlock.y - currentBlock.height // Position above with no overlap
};
// Update score display
scoreDisplay.updateScore(score, towerHeight);
// Move the tower down if it's getting too high
if (currentBlock.y < 2732 / 2) {
var shiftAmount = 100;
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].placed && !blocks[i].fallDirection) {
// Animate the shift
tween.stop(blocks[i], {
y: true
});
tween(blocks[i], {
y: blocks[i].y + shiftAmount
}, {
duration: 400,
easing: tween.easeInOut
});
}
}
lastBlockPosition.y += shiftAmount;
}
// Increase difficulty based on tower height
increaseGameDifficulty();
// Create a new block with a slight delay for better game flow
LK.setTimeout(function () {
createNewBlock();
}, 300);
}
// Increase game difficulty as tower gets higher
function increaseGameDifficulty() {
// Increase movement speed based on tower height
level = Math.floor(towerHeight / 10) + 1;
blockSpeed = 8 + level;
// Every 5 levels, reduce block width by 10% (min 30% of original)
var widthReduction = Math.min(0.7, 1 - Math.floor(level / 5) * 0.1);
lastBlockPosition.width = Math.max(150, baseFloor.width * widthReduction);
}
// Game over function
function gameOver() {
gameActive = false;
// Play falling sound
LK.getSound('fallBlock').play();
// Make current block fall with enhanced animation
if (currentBlock) {
currentBlock.fallDirection = currentBlock.x > 2048 / 2 ? 1 : -1;
// Enhance the fall animation with tween
tween(currentBlock, {
y: currentBlock.y + 300,
rotation: currentBlock.fallDirection * Math.PI
}, {
duration: 1000,
easing: tween.easeIn
});
}
// Create a cascade effect where blocks fall one after another
var fallDelay = 100;
for (var i = blocks.length - 2; i >= 0; i--) {
// Only animate placed blocks that aren't already falling
if (blocks[i].placed && blocks[i].fallDirection === 0) {
// Determine random fall direction
var dir = Math.random() > 0.5 ? 1 : -1;
// Create closure to preserve the block reference
(function (block, delay, direction) {
LK.setTimeout(function () {
block.fallDirection = direction;
// Add tween animation for falling
tween(block, {
y: block.y + 300,
rotation: direction * Math.PI * (0.5 + Math.random() * 0.5)
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeIn
});
}, delay);
})(blocks[i], fallDelay * (blocks.length - i), dir);
}
}
// Flash screen with animation
LK.effects.flashScreen(0xff0000, 500);
// Show game over after a 5 second delay
LK.setTimeout(function () {
LK.showGameOver();
}, 5000);
}
// Function to handle music end and play next track
function onMusicEnd() {
currentMusicTrack = (currentMusicTrack + 1) % musicTracks.length;
LK.playMusic(musicTracks[currentMusicTrack], {
loop: false,
fade: {
start: 0,
end: 1,
duration: 1000
}
});
}
// Listen for music end event to chain tracks
LK.onMusicEnd = onMusicEnd;
// Game tick update
game.update = function () {
// Update all blocks
for (var i = blocks.length - 1; i >= 0; i--) {
if (blocks[i]) {
blocks[i].update();
}
}
// Change backgrounds at 22-second intervals instead of by tower height
backgroundChangeTimer++;
if (gameActive && backgroundChangeTimer >= backgroundChangeInterval) {
backgroundChangeTimer = 0;
changeBackground();
}
// Background transition effect timer
backgroundUpdateTimer++;
if (backgroundUpdateTimer >= 300) {
// Every 5 seconds
backgroundUpdateTimer = 0;
// Subtle animation for active background
var activeBg = backgrounds[currentBackground];
tween(activeBg.scale, {
x: 1.05,
y: 1.05
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(activeBg.scale, {
x: 1.0,
y: 1.0
}, {
duration: 2000,
easing: tween.easeInOut
});
}
});
}
// Generate fruits periodically when game is active
if (gameActive && LK.ticks - lastFruitTime > fruitGenerationRate) {
lastFruitTime = LK.ticks;
// Generate multiple berries at once for a richer background effect
var batchSize = 1 + Math.floor(Math.random() * 3); // Generate 1-3 berries at once
for (var b = 0; b < batchSize; b++) {
// Create a random fruit
var fruitType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)];
var fruit = new Fruit(fruitType);
// Position at bottom of screen with random x
fruit.x = Math.random() * 2048;
fruit.y = 2732 + 50 + Math.random() * 100; // Vary initial height
// Vary initial velocity for more natural movement
fruit.speedY = 1.5 + Math.random() * 2.5;
fruit.speedX = (Math.random() * 2 - 1) * 2;
// Scale randomly for variety
var scale = 0.8 + Math.random() * 0.4;
fruit.scale.set(scale, scale);
// Add to game
game.addChild(fruit);
fruits.push(fruit);
}
// Adjust generation rate based on level (faster fruit generation at higher levels)
fruitGenerationRate = Math.max(50, 100 - level * 5);
}
// Update fruits and check for collisions
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i]) {
fruits[i].update();
// Check if fruit has been tapped
if (fruits[i].tapped) {
// Add points based on fruit value
score += fruits[i].value;
scoreDisplay.updateScore(score, towerHeight);
// Remove the fruit
fruits[i].destroy();
fruits.splice(i, 1);
}
// Remove if off screen
if (fruits[i] && fruits[i].y < -100) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
}
};
// Event handlers
game.down = function (x, y, obj) {
// First check if we tapped on a fruit
var fruitTapped = false;
for (var i = 0; i < fruits.length; i++) {
// Convert tap position to fruit's parent coordinates
// Check if parent exists before calling toLocal
var localPos;
if (fruits[i] && fruits[i].parent) {
localPos = fruits[i].parent.toLocal({
x: x,
y: y
});
} else {
localPos = {
x: x,
y: y
}; // Fallback if parent doesn't exist
}
// Check if tap is within fruit bounds (slightly larger hit area for better UX)
if (Math.abs(localPos.x - fruits[i].x) < fruits[i].width * 0.6 && Math.abs(localPos.y - fruits[i].y) < fruits[i].height * 0.6) {
// Mark fruit as tapped
fruits[i].tapped = true;
// Enhanced visual effects for tapped fruit
LK.effects.flashObject(fruits[i], 0xFFFFFF, 150);
// Create a burst animation effect
tween(fruits[i].scale, {
x: fruits[i].scale.x * 1.5,
y: fruits[i].scale.y * 1.5
}, {
duration: 150,
easing: tween.easeOut
});
// Also make it fade out
tween(fruits[i], {
alpha: 0
}, {
duration: 200,
easing: tween.easeOut
});
fruitTapped = true;
break;
}
}
// If no fruit was tapped, handle normal block tap
if (!fruitTapped) {
handleTap();
}
};
// Initialize the game
initGame(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Block = Container.expand(function (color, width) {
var self = Container.call(this);
// Default values if not provided
color = color || 0x3498db;
width = width || 512;
// Create block graphics
var blockGraphics = self.attachAsset('block', {
anchorX: 0.5,
anchorY: 0.5,
width: width,
tint: color
});
// State tracking
self.placed = false;
self.originalWidth = width;
self.fallDirection = 0; // 0: none, -1: left, 1: right
self.movementSpeed = 8;
self.movementDirection = 1; // 1: right, -1: left
self.scale.y = 0;
self.targetY = 0;
self.initialY = 0;
// Resize the block (used when block partially falls off)
self.resize = function (newWidth, offsetX) {
if (newWidth <= 0) {
return;
}
// Immediately set the width to match exactly without animation
blockGraphics.width = newWidth;
// Update the original width to reflect the new truncated size
self.originalWidth = newWidth;
if (offsetX !== undefined) {
// Immediately update position
self.x += offsetX;
}
};
// Drop the block into position with animation
self.dropIntoPlace = function (targetYPos) {
self.targetY = targetYPos;
self.initialY = self.y;
// First expand the y-scale
tween(self.scale, {
y: 1
}, {
duration: 400,
easing: tween.bounceOut
});
// Then animate moving down to its target position
tween(self, {
y: targetYPos
}, {
duration: 500,
easing: tween.bounceOut
});
};
// Update method called every frame
self.update = function () {
if (!self.placed) {
// Move block back and forth
self.x += self.movementSpeed * self.movementDirection;
// Check if we need to change direction
if (self.x > 2048 - self.originalWidth / 2) {
self.movementDirection = -1;
} else if (self.x < self.originalWidth / 2) {
self.movementDirection = 1;
}
} else if (self.fallDirection !== 0) {
// If block is set to fall, make it fall and rotate
self.y += 15;
self.rotation += 0.1 * self.fallDirection;
// Remove the block when it's off-screen
if (self.y > 2732 + 200) {
self.destroy();
}
}
};
return self;
});
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
// Define fruit types and their properties - now as berry types
var fruitTypes = {
apple: {
width: 60,
height: 60,
color: 0xe74c3c,
shape: 'ellipse'
},
banana: {
width: 70,
height: 70,
color: 0xf1c40f,
shape: 'ellipse'
},
orange: {
width: 55,
height: 55,
color: 0xf39c12,
shape: 'ellipse'
},
grape: {
width: 45,
height: 45,
color: 0x9b59b6,
shape: 'ellipse'
},
watermelon: {
width: 65,
height: 65,
color: 0x2ecc71,
shape: 'ellipse'
}
};
// Set default if type not provided or invalid
type = fruitTypes[type] ? type : 'apple';
// Initialize fruit properties
var props = fruitTypes[type];
// Create main berry shape
var fruitGraphics = self.attachAsset('fruit', {
anchorX: 0.5,
anchorY: 0.5,
tint: props.color,
width: props.width,
height: props.height,
alpha: 0.85
});
// Add a subtle glow effect
tween(fruitGraphics, {
alpha: 0.95
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Add movement properties
self.speedY = 2 + Math.random() * 3;
self.speedX = (Math.random() * 2 - 1) * 2;
self.rotationSpeed = Math.random() * 0.04 - 0.02;
self.width = props.width;
self.height = props.height;
// Track last position for collision detection
self.lastY = self.y;
self.lastX = self.x;
self.value = 0;
switch (type) {
case 'apple':
self.value = 1;
break;
case 'banana':
self.value = 2;
break;
case 'orange':
self.value = 5;
break;
case 'grape':
self.value = 3;
break;
case 'watermelon':
self.value = 10;
break;
}
// Update method called every frame
self.update = function () {
// Store last position
self.lastY = self.y;
self.lastX = self.x;
// Move fruit
self.y -= self.speedY;
self.x += self.speedX;
// Rotate fruit
self.rotation += self.rotationSpeed;
// Bounce off walls
if (self.x < self.width / 2 || self.x > 2048 - self.width / 2) {
self.speedX *= -1;
}
// Remove if off screen at top
if (self.y < -100) {
self.destroy();
}
};
return self;
});
var ScoreDisplay = Container.expand(function () {
var self = Container.call(this);
// Background for score
var background = self.attachAsset('scoreBackground', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
// Score text
self.scoreText = new Text2('0', {
size: 60,
fill: 0xFFFFFF
});
self.scoreText.anchor.set(0.5, 0.5);
self.addChild(self.scoreText);
// Height text
self.heightText = new Text2('Height: 0m', {
size: 30,
fill: 0xFFFFFF
});
self.heightText.anchor.set(0.5, 0.5);
self.heightText.y = 50;
self.addChild(self.heightText);
// Update score display
self.updateScore = function (score, height) {
self.scoreText.setText(score.toString());
self.heightText.setText('Height: ' + height.toFixed(1) + 'm');
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Black background
});
/****
* Game Code
****/
// Set real background with gradient
game.setBackgroundColor(0x1e3c72); // Deep blue gradient start
// Game state variables
var score = 0;
var towerHeight = 0;
var gameActive = true;
var level = 1;
var blockSpeed = 8;
var perfectMatches = 0;
var blockColors = [0x3498db,
// Blue
0xe74c3c,
// Red
0x2ecc71,
// Green
0xf39c12,
// Orange
0x9b59b6,
// Purple
0x1abc9c // Turquoise
];
// Background and music variables
var backgrounds = [];
var currentBackground = 0;
var musicTracks = ['backgroundMusic', 'backgroundMusic1', 'backgroundMusic2'];
var currentMusicTrack = 0;
var backgroundChangeInterval = 22 * 60; // 22 seconds at 60fps
var backgroundChangeTimer = 0;
var backgroundUpdateTimer = 0;
// Fruit variables
var fruits = [];
var fruitTypes = ['apple', 'banana', 'orange', 'grape', 'watermelon'];
var lastFruitTime = 0;
var fruitGenerationRate = 120; // frames between fruit generation
// Game objects
var blocks = [];
var currentBlock = null;
var baseFloor = null;
var scoreDisplay = null;
var lastBlockPosition = {
x: 2048 / 2,
width: 512
};
var dropY = 200; // Starting Y position for dropping blocks
// Setup backgrounds and transition to next background
function setupBackgrounds() {
// Create all three backgrounds
var bg1 = LK.getAsset('background1', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
bg1.x = 2048 / 2;
bg1.y = 2732 / 2;
var bg2 = LK.getAsset('background2', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
bg2.x = 2048 / 2;
bg2.y = 2732 / 2;
var bg3 = LK.getAsset('background3', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
bg3.x = 2048 / 2;
bg3.y = 2732 / 2;
// Add backgrounds to game and array
game.addChild(bg1);
game.addChild(bg2);
game.addChild(bg3);
backgrounds = [bg1, bg2, bg3];
// Show first background
tween(backgrounds[0], {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
}
// Change to next background
function changeBackground() {
var nextBg = (currentBackground + 1) % backgrounds.length;
// Fade out current background
tween(backgrounds[currentBackground], {
alpha: 0
}, {
duration: 1200,
easing: tween.easeInOut
});
// Fade in next background
tween(backgrounds[nextBg], {
alpha: 1
}, {
duration: 1200,
easing: tween.easeInOut
});
currentBackground = nextBg;
// Do not update music track here to avoid interrupting current music
}
// Change music to match current background
function changeMusic() {
// Each background has its associated music track
// This function is no longer needed as we handle music transitions
// in the onMusicEnd callback to ensure music is not interrupted
}
// Initialize the game
function initGame() {
// Setup backgrounds
setupBackgrounds();
// Create base floor with animation
baseFloor = LK.getAsset('baseFloor', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
baseFloor.x = 2048 / 2;
baseFloor.y = 2732 - 300;
baseFloor.scale.x = 0.5;
game.addChild(baseFloor);
// Animate the base floor appearing
tween(baseFloor, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
tween(baseFloor.scale, {
x: 1
}, {
duration: 800,
easing: tween.elasticOut
});
// Set last block position to be above the base floor (not overlapping)
// Use the baseFloor width as the initial width for the first block
lastBlockPosition = {
x: baseFloor.x,
width: baseFloor.width,
// The first block will have the same width as baseFloor
y: baseFloor.y - baseFloor.height / 2 // Position above the baseFloor with no overlap
};
// Initialize score display with animation
scoreDisplay = new ScoreDisplay();
scoreDisplay.x = 2048 / 2;
scoreDisplay.y = -50; // Start off-screen
scoreDisplay.alpha = 0;
game.addChild(scoreDisplay);
// Add "Glaud" text in the upper right corner
var glaudText = new Text2('Glaud', {
size: 54,
fill: 0xFF8800 // Orange color for "Glaud"
});
glaudText.anchor.set(1, 0); // Anchor to top right
glaudText.x = 2048 - 20; // 20px from right edge
glaudText.y = 20; // 20px from top edge
glaudText.alpha = 0.7; // Slightly transparent
game.addChild(glaudText);
// Animate score display
tween(scoreDisplay, {
y: 100,
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
// Reset game state variables
score = 0;
towerHeight = 0;
gameActive = true;
level = 1;
blockSpeed = 8;
perfectMatches = 0;
blocks = [];
fruits = [];
lastFruitTime = 0;
backgroundChangeTimer = 0;
// Set the drop Y position to be above the base floor (not overlapping)
dropY = baseFloor.y - baseFloor.height;
// Create first block with a slight delay for better game flow
LK.setTimeout(function () {
createNewBlock();
}, 500);
// Play only the first music track, the others will play sequentially
currentMusicTrack = 0;
LK.playMusic(musicTracks[currentMusicTrack], {
loop: false,
fade: {
start: 0,
end: 1,
duration: 1000
}
});
}
// Create a new block to drop
function createNewBlock() {
if (!gameActive) {
return;
}
// Choose a random color that's different from the last block
var color;
do {
color = blockColors[Math.floor(Math.random() * blockColors.length)];
} while (blocks.length > 0 && blocks[blocks.length - 1].tint === color && blockColors.length > 1);
// Use the width from lastBlockPosition directly - this is already properly updated after cutting
var topBlockWidth = lastBlockPosition.width;
// Create new block with the exact width of the last block position
currentBlock = new Block(color, topBlockWidth);
currentBlock.x = lastBlockPosition.x; // Position at the same x as the last block
currentBlock.y = lastBlockPosition.y - currentBlock.height; // Position above the last block with no overlap
currentBlock.movementSpeed = blockSpeed;
// Scale initially set to 0 in y axis (will animate in)
currentBlock.scale.y = 0;
// Add block to the game and blocks array
game.addChild(currentBlock);
blocks.push(currentBlock);
// Animate the block entry - already positioned correctly, just need a small animation
tween(currentBlock, {
y: currentBlock.y + 5 // Small bounce effect
}, {
duration: 500,
easing: tween.bounceOut
});
// Animate the scale of the block
tween(currentBlock.scale, {
y: 1
}, {
duration: 400,
easing: tween.easeOut
});
// Apply a subtle shadow effect
if (blocks.length > 1) {
// Get reference to the top block in the tower
var topBlock = null;
for (var i = blocks.length - 2; i >= 0; i--) {
if (blocks[i].placed && !blocks[i].fallDirection) {
topBlock = blocks[i];
break;
}
}
if (topBlock) {
// Slight tint adjustment to create shadow effect on blocks beneath
tween(topBlock, {
alpha: 0.9
}, {
duration: 500,
easing: tween.linear
});
}
}
}
// Handle game clicks/taps
function handleTap() {
if (!currentBlock || !gameActive) {
return;
}
// Calculate overlap with previous block
var overlapLeft = Math.max(lastBlockPosition.x - lastBlockPosition.width / 2, currentBlock.x - currentBlock.originalWidth / 2);
var overlapRight = Math.min(lastBlockPosition.x + lastBlockPosition.width / 2, currentBlock.x + currentBlock.originalWidth / 2);
var overlapWidth = overlapRight - overlapLeft;
// If blocks don't overlap at all, game over
if (overlapWidth <= 0) {
gameOver();
return;
}
// Play place block sound
LK.getSound('placeBlock').play();
// Mark the block as placed
currentBlock.placed = true;
// Calculate the new position and width for the current block
var newCenterX = overlapLeft + overlapWidth / 2;
var newWidth = overlapWidth;
// Handle partial block
if (newWidth < currentBlock.originalWidth) {
// Determine which side falls off
var fallBlockWidth = currentBlock.originalWidth - newWidth;
var fallBlock = new Block(currentBlock.tint, fallBlockWidth);
if (currentBlock.x < newCenterX) {
// Left part falls off
fallBlock.x = overlapLeft - fallBlockWidth / 2;
fallBlock.fallDirection = -1;
currentBlock.resize(newWidth, newCenterX - currentBlock.x);
} else {
// Right part falls off
fallBlock.x = overlapRight + fallBlockWidth / 2;
fallBlock.fallDirection = 1;
currentBlock.resize(newWidth, newCenterX - currentBlock.x);
}
fallBlock.y = currentBlock.y;
fallBlock.placed = true;
fallBlock.scale.y = 1;
game.addChild(fallBlock);
blocks.push(fallBlock);
// Animate falling block with rotation
tween(fallBlock, {
rotation: fallBlock.fallDirection * Math.PI * 0.5
}, {
duration: 800,
easing: tween.easeIn
});
// Play falling sound
LK.getSound('fallBlock').play();
} else {
// Perfect match!
perfectMatches++;
LK.getSound('perfectMatch').play();
// Enhanced visual effect for perfect match
LK.effects.flashObject(currentBlock, 0xFFFFFF, 300);
// Extra animation to emphasize perfect match
tween(currentBlock.scale, {
x: 1.1,
y: 1.05
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(currentBlock.scale, {
x: 1,
y: 1
}, {
duration: 150,
easing: tween.bounceOut
});
}
});
}
// Smoothly move the current block to its final position
tween.stop(currentBlock, {
x: true
});
tween(currentBlock, {
x: newCenterX
}, {
duration: 300,
easing: tween.easeOut
});
// Calculate score based on match accuracy
var accuracy = newWidth / currentBlock.originalWidth;
var blockScore = Math.round(accuracy * 100);
// Perfect bonus
if (accuracy >= 0.95) {
blockScore += 50;
}
score += blockScore;
// Update tower height (each block adds height)
towerHeight += currentBlock.height / 150; // Convert to "meters"
// Update last block position for next block
// Store the new width after cutting for the next block to be sized correctly
lastBlockPosition = {
x: newCenterX,
// Use the new center position after resizing
width: newWidth,
// Use the exact new width after cutting
y: currentBlock.y - currentBlock.height // Position above with no overlap
};
// Update score display
scoreDisplay.updateScore(score, towerHeight);
// Move the tower down if it's getting too high
if (currentBlock.y < 2732 / 2) {
var shiftAmount = 100;
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].placed && !blocks[i].fallDirection) {
// Animate the shift
tween.stop(blocks[i], {
y: true
});
tween(blocks[i], {
y: blocks[i].y + shiftAmount
}, {
duration: 400,
easing: tween.easeInOut
});
}
}
lastBlockPosition.y += shiftAmount;
}
// Increase difficulty based on tower height
increaseGameDifficulty();
// Create a new block with a slight delay for better game flow
LK.setTimeout(function () {
createNewBlock();
}, 300);
}
// Increase game difficulty as tower gets higher
function increaseGameDifficulty() {
// Increase movement speed based on tower height
level = Math.floor(towerHeight / 10) + 1;
blockSpeed = 8 + level;
// Every 5 levels, reduce block width by 10% (min 30% of original)
var widthReduction = Math.min(0.7, 1 - Math.floor(level / 5) * 0.1);
lastBlockPosition.width = Math.max(150, baseFloor.width * widthReduction);
}
// Game over function
function gameOver() {
gameActive = false;
// Play falling sound
LK.getSound('fallBlock').play();
// Make current block fall with enhanced animation
if (currentBlock) {
currentBlock.fallDirection = currentBlock.x > 2048 / 2 ? 1 : -1;
// Enhance the fall animation with tween
tween(currentBlock, {
y: currentBlock.y + 300,
rotation: currentBlock.fallDirection * Math.PI
}, {
duration: 1000,
easing: tween.easeIn
});
}
// Create a cascade effect where blocks fall one after another
var fallDelay = 100;
for (var i = blocks.length - 2; i >= 0; i--) {
// Only animate placed blocks that aren't already falling
if (blocks[i].placed && blocks[i].fallDirection === 0) {
// Determine random fall direction
var dir = Math.random() > 0.5 ? 1 : -1;
// Create closure to preserve the block reference
(function (block, delay, direction) {
LK.setTimeout(function () {
block.fallDirection = direction;
// Add tween animation for falling
tween(block, {
y: block.y + 300,
rotation: direction * Math.PI * (0.5 + Math.random() * 0.5)
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeIn
});
}, delay);
})(blocks[i], fallDelay * (blocks.length - i), dir);
}
}
// Flash screen with animation
LK.effects.flashScreen(0xff0000, 500);
// Show game over after a 5 second delay
LK.setTimeout(function () {
LK.showGameOver();
}, 5000);
}
// Function to handle music end and play next track
function onMusicEnd() {
currentMusicTrack = (currentMusicTrack + 1) % musicTracks.length;
LK.playMusic(musicTracks[currentMusicTrack], {
loop: false,
fade: {
start: 0,
end: 1,
duration: 1000
}
});
}
// Listen for music end event to chain tracks
LK.onMusicEnd = onMusicEnd;
// Game tick update
game.update = function () {
// Update all blocks
for (var i = blocks.length - 1; i >= 0; i--) {
if (blocks[i]) {
blocks[i].update();
}
}
// Change backgrounds at 22-second intervals instead of by tower height
backgroundChangeTimer++;
if (gameActive && backgroundChangeTimer >= backgroundChangeInterval) {
backgroundChangeTimer = 0;
changeBackground();
}
// Background transition effect timer
backgroundUpdateTimer++;
if (backgroundUpdateTimer >= 300) {
// Every 5 seconds
backgroundUpdateTimer = 0;
// Subtle animation for active background
var activeBg = backgrounds[currentBackground];
tween(activeBg.scale, {
x: 1.05,
y: 1.05
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(activeBg.scale, {
x: 1.0,
y: 1.0
}, {
duration: 2000,
easing: tween.easeInOut
});
}
});
}
// Generate fruits periodically when game is active
if (gameActive && LK.ticks - lastFruitTime > fruitGenerationRate) {
lastFruitTime = LK.ticks;
// Generate multiple berries at once for a richer background effect
var batchSize = 1 + Math.floor(Math.random() * 3); // Generate 1-3 berries at once
for (var b = 0; b < batchSize; b++) {
// Create a random fruit
var fruitType = fruitTypes[Math.floor(Math.random() * fruitTypes.length)];
var fruit = new Fruit(fruitType);
// Position at bottom of screen with random x
fruit.x = Math.random() * 2048;
fruit.y = 2732 + 50 + Math.random() * 100; // Vary initial height
// Vary initial velocity for more natural movement
fruit.speedY = 1.5 + Math.random() * 2.5;
fruit.speedX = (Math.random() * 2 - 1) * 2;
// Scale randomly for variety
var scale = 0.8 + Math.random() * 0.4;
fruit.scale.set(scale, scale);
// Add to game
game.addChild(fruit);
fruits.push(fruit);
}
// Adjust generation rate based on level (faster fruit generation at higher levels)
fruitGenerationRate = Math.max(50, 100 - level * 5);
}
// Update fruits and check for collisions
for (var i = fruits.length - 1; i >= 0; i--) {
if (fruits[i]) {
fruits[i].update();
// Check if fruit has been tapped
if (fruits[i].tapped) {
// Add points based on fruit value
score += fruits[i].value;
scoreDisplay.updateScore(score, towerHeight);
// Remove the fruit
fruits[i].destroy();
fruits.splice(i, 1);
}
// Remove if off screen
if (fruits[i] && fruits[i].y < -100) {
fruits[i].destroy();
fruits.splice(i, 1);
}
}
}
};
// Event handlers
game.down = function (x, y, obj) {
// First check if we tapped on a fruit
var fruitTapped = false;
for (var i = 0; i < fruits.length; i++) {
// Convert tap position to fruit's parent coordinates
// Check if parent exists before calling toLocal
var localPos;
if (fruits[i] && fruits[i].parent) {
localPos = fruits[i].parent.toLocal({
x: x,
y: y
});
} else {
localPos = {
x: x,
y: y
}; // Fallback if parent doesn't exist
}
// Check if tap is within fruit bounds (slightly larger hit area for better UX)
if (Math.abs(localPos.x - fruits[i].x) < fruits[i].width * 0.6 && Math.abs(localPos.y - fruits[i].y) < fruits[i].height * 0.6) {
// Mark fruit as tapped
fruits[i].tapped = true;
// Enhanced visual effects for tapped fruit
LK.effects.flashObject(fruits[i], 0xFFFFFF, 150);
// Create a burst animation effect
tween(fruits[i].scale, {
x: fruits[i].scale.x * 1.5,
y: fruits[i].scale.y * 1.5
}, {
duration: 150,
easing: tween.easeOut
});
// Also make it fade out
tween(fruits[i], {
alpha: 0
}, {
duration: 200,
easing: tween.easeOut
});
fruitTapped = true;
break;
}
}
// If no fruit was tapped, handle normal block tap
if (!fruitTapped) {
handleTap();
}
};
// Initialize the game
initGame();
Glaud text. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Produce an image that says G. This G will be big.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
bakstenen muurpatroon. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows