/**** * 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