User prompt
let's create a special mechanic that charges up 8 level 2 balls. these 8 balls are aranged on a grid at the top of the screen, and each ball is represented as a 50% transparent icon, which means it's inactive. after the player drops a ball, the first ball becomes full visible so 100% alpha. the second ball drop, the next icon becomes fully visible and so on until the 8th icon, meaning all 8 icons are now 100% visible. On the 9th drop, these balls become active, and they are all 8 of them dropp at the same time as the 9th active ball is eing dropped too. Then the cycle resets, the icons are inactive again, so show them as 50% transparent, and then on the 10th ball drop the cycle repeats and the icons start charing up again ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
when dropping a ball, dont always drop it straight down, add a slight variation so each drop can drop it slightly to the left or the right from the point of dropping. and also move the starting Y position for the active balls 100 pixels lower. like add a slight angle of 10 degrees to the left and 10 degrees to the right, so the ball shoots with a sort of an arch from the spawn point
User prompt
when dropping a ball, dont always drop it straight down, add a slight variation so each drop can drop it slightly to the left or the right from the point of dropping. and also move the starting Y position for the active balls 100 pixels lower
User prompt
after dropping a ball, instantly show the next in line instead of waiting a short delay
User prompt
when balls get in contact with walls, they keep rotating the balls, ensure walls also add friction to the balls rotation so they dont kep rotating indefinitely
User prompt
the smaller the ball's level is, the more it should bounce. like, larger more evolved balls should be less impacted by the force of smaller balls and bounce less. keeps the current value for the level 10 ball, but as the balls value level decerases towards 1, it should be more bouncy
User prompt
remove the bounce sound effect
User prompt
create a new sound named Merge and play it every time 2 balls merge
User prompt
the latest collision detection improvement made the hitbox area for certain areas too large, leaving like an aura around them, making it worse, not they push balls away outside their actual size
User prompt
improve collision detection hitbox between balls
User prompt
make the fruits hitbox area be the same as the actual asset size
User prompt
cchange the size of all fruits, so level 1 starts from 150 pixels, level two is 200 pixels and so on, so increase each level by 50 pixels each
User prompt
the balls keep spinning indefinitely, they need to have friction so they halt after a while, especially while touching other resting balls
User prompt
rename the asset name too. like here the name still doesnt have a prefix. "LK.init.shape('coconut', {width:900, height:900, color:0x8c7e75, shape:'ellipse'})" add a prefix to the actuall assets name too
User prompt
please rename all fruits so their format is 1.Cherry and 2.Apple and so on. start each fruit with it's level number so I can easily see each fruit level numerically
User prompt
apply this scoring formula when merging balls. so when two level one balls merge, they award a point. this is the points for each level. Ball Level Merging Score 1 1 2 2 3 3 4 5 5 8 6 13 7 21 8 34 9 55 10 89
User prompt
Please fix the bug: 'fruit is not defined' in or related to this line: 'if (Math.abs(fruit.vx) < 0.5 && Math.abs(fruit.vy) < 0.5) {' Line Number: 59
User prompt
the balls rotation is still bugged, especially for balls on top of other balls that don't touch the ground, they seem to keep spinning, never coming to a rest
User prompt
it seems the spinning of the ball is not correlated to the force of impact. they do rotate, but the rotation doesn't look natural. they either rotate too much from a small itneraction, or sometimes stopp too abruptly
User prompt
balls reverted back to spinning uncontrolably, as if there's no friction. balls should have friction when interacting with the ground or other balls, so they dont keep spinning forever
User prompt
great, but now some ball lost collision and dont collide with each other
User prompt
nice, it works great now! amazing job! but curently I need to hold the actuall ball for it to move, when instead I should be able to press anywhere for that to happen
User prompt
we need to simplify things. remove the preview that shows the next ball and thenextfruitbg and instead of showing the next ball as a static icon in a fixed place, place that at the top of the screen, as the next dropping fruit. it remains in the same Y axis, but it now becomes part of the gameplay rather than of UI. while the player keeps the finger on the screen, the active ball follows it, but only moving on the X axis, and when the figner is released, the ball drops and then the next one pawns in it's place, so there's always an active ball that is about to be dropped
User prompt
upon release, the balls dont drop, they simply diappear
User prompt
we need to simplify things. remove the preview that shows the next ball and thenextfruitbg and instead of showing the next ball as a static icon in a fixed place, place that at the top of the screen, as the next dropping fruit. it remains in the same Y axis, but it now becomes part of the gameplay rather than of UI. while the player keeps the finger on the screen, the active ball follows it, but only moving on the X axis, and when the figner is released, the ball drops and then the next one pawns in it's place, so there's always an active ball that is about to be dropped
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Fruit = Container.expand(function (type) { var self = Container.call(this); // Handle both direct type object and type name if (typeof type === 'string') { // Convert type string to actual FruitType object self.type = FruitTypes[type.toUpperCase()]; } else { self.type = type; } self.vx = 0; self.vy = 0; self.rotation = 0; self.angularVelocity = 0; self.angularFriction = 0.96; // Increased from 0.97 for more friction self.groundAngularFriction = 0.85; // Additional friction when touching ground self.gravity = 1.8; // Doubled gravity to make fruits drop faster self.friction = 0.98; self.elasticity = 0.7; self.merging = false; self.isStatic = false; // Will be true for the active fruit at top self.maxAngularVelocity = 0.15; // Add maximum angular velocity cap // Only attempt to attach the asset if self.type exists and has necessary properties if (self.type && self.type.id && self.type.points && self.type.size) { var fruitGraphics = self.attachAsset(self.type.id, { anchorX: 0.5, anchorY: 0.5 }); // No point value shown on fruit } else { // This will be initialized properly when the game is fully loaded console.log("Warning: Fruit type not available yet or missing required properties"); } self.update = function () { if (self.isStatic || self.merging) { return; } // Apply gravity self.vy += self.gravity; // Apply velocity self.x += self.vx; self.y += self.vy; // Apply rotation self.rotation += self.angularVelocity; // Apply friction self.vx *= self.friction; self.vy *= self.friction; // Apply angular friction self.angularVelocity *= self.angularFriction; // Clamp angular velocity to prevent excessive spinning self.angularVelocity = Math.min(Math.max(self.angularVelocity, -self.maxAngularVelocity), self.maxAngularVelocity); // Check collisions with other fruits for (var i = 0; i < fruits.length; i++) { var other = fruits[i]; if (other === self || other.merging || self.merging) { continue; } // Skip collision resolution if same fruit type (they should merge instead) if (self.type === other.type) { continue; } // Calculate distance between centers var dx = other.x - self.x; var dy = other.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Calculate minimum distance before collision (sum of radii) var minDistance = (self.type.size + other.type.size) / 2; // If collision detected if (distance < minDistance) { // Normalize direction vector var nx = dx / distance; var ny = dy / distance; // Calculate penetration depth var penetration = minDistance - distance; // Move fruits apart based on penetration self.x -= nx * penetration * 0.5; self.y -= ny * penetration * 0.5; other.x += nx * penetration * 0.5; other.y += ny * penetration * 0.5; // Calculate relative velocity var relVelocityX = other.vx - self.vx; var relVelocityY = other.vy - self.vy; // Calculate velocity along normal var velAlongNormal = relVelocityX * nx + relVelocityY * ny; // Only resolve if objects are moving toward each other if (velAlongNormal > 0) { continue; } // Calculate collision impulse var e = self.elasticity; // coefficient of restitution var j = -(1 + e) * velAlongNormal; j /= 2; // Assume equal mass for simplicity // Apply impulse self.vx -= j * nx; self.vy -= j * ny; other.vx += j * nx; other.vy += j * ny; // Calculate rotational effect based on impact point and fruit sizes // Perpendicular vector to normal var px = -ny; var py = nx; // Approximate lever arm length (distance from center to impact point along the fruit's edge) var selfRadius = self.type.size / 2; var otherRadius = other.type.size / 2; // Calculate rotational impulse based on impact point // Higher impulse = faster rotation with a significantly reduced factor var rotationalFactor = 0.003; // Reduced from 0.008 to 0.003 var selfRotation = j * (px * self.vx + py * self.vy) * rotationalFactor; var otherRotation = j * (px * other.vx + py * other.vy) * rotationalFactor; // Apply rotational impulse inversely proportional to radius (smaller fruits rotate faster) // But cap the maximum rotation that can be applied in a single collision var maxAngularChange = 0.05; // Maximum angular velocity change per collision selfRotation = Math.min(Math.max(selfRotation / (selfRadius * 0.05), -maxAngularChange), maxAngularChange); otherRotation = Math.min(Math.max(otherRotation / (otherRadius * 0.05), -maxAngularChange), maxAngularChange); self.angularVelocity += selfRotation; other.angularVelocity -= otherRotation; // Apply additional friction to angular velocity when fruits are in contact self.angularVelocity *= 0.9; // Friction during collision other.angularVelocity *= 0.9; // Friction during collision // Play bounce sound if significant collision if (Math.abs(j) > 2) { LK.getSound('bounce').play(); } } } // Wall collision if (self.x < wallLeft.x + wallLeft.width / 2 + self.type.size / 2) { self.x = wallLeft.x + wallLeft.width / 2 + self.type.size / 2; self.vx = -self.vx * self.elasticity; // Add rotation on wall impact - direction based on vertical velocity self.angularVelocity += self.vy * 0.005; // Reduced from 0.01 to 0.005 if (Math.abs(self.vx) > 1) { LK.getSound('bounce').play(); } } if (self.x > wallRight.x - wallRight.width / 2 - self.type.size / 2) { self.x = wallRight.x - wallRight.width / 2 - self.type.size / 2; self.vx = -self.vx * self.elasticity; // Add rotation on wall impact - direction based on vertical velocity self.angularVelocity -= self.vy * 0.005; // Reduced from 0.01 to 0.005 if (Math.abs(self.vx) > 1) { LK.getSound('bounce').play(); } } // Floor collision if (self.y > gameFloor.y - gameFloor.height / 2 - self.type.size / 2) { self.y = gameFloor.y - gameFloor.height / 2 - self.type.size / 2; self.vy = -self.vy * self.elasticity; // Apply ground friction to rotation when in contact with floor // Apply direct rotation based on horizontal velocity, but much gentler if (Math.abs(self.vx) > 0.5) { // Only influence rotation direction, not add to existing rotation self.angularVelocity = self.vx * 0.01; // Reduced significantly } // Apply stronger ground friction to slow rotation self.angularVelocity *= self.groundAngularFriction; // If we're barely moving, stop completely if (Math.abs(self.vy) < 2) { self.vy = 0; } // If we're barely rotating, stop completely with a higher threshold if (Math.abs(self.angularVelocity) < 0.03) { // Increased threshold for stopping rotation self.angularVelocity = 0; } if (Math.abs(self.vy) > 1) { LK.getSound('bounce').play(); } } }; self.merge = function (otherFruit) { if (self.merging || !self.type.next) { return; } self.merging = true; otherFruit.merging = true; // Calculate midpoint between fruits for new fruit position var midX = (self.x + otherFruit.x) / 2; var midY = (self.y + otherFruit.y) / 2; // Create merge animation tween(self, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut }); tween(otherFruit, { alpha: 0, scaleX: 0.5, scaleY: 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Create new merged fruit var nextType = FruitTypes[self.type.next.toUpperCase()]; var newFruit = new Fruit(nextType); newFruit.x = midX; newFruit.y = midY; newFruit.scaleX = 0.5; newFruit.scaleY = 0.5; game.addChild(newFruit); fruits.push(newFruit); // Add merge points LK.setScore(LK.getScore() + nextType.points); updateScoreDisplay(); // Animate new fruit growing tween(newFruit, { scaleX: 1, scaleY: 1 }, { duration: 300, easing: tween.bounceOut }); // Remove old fruits var indexSelf = fruits.indexOf(self); if (indexSelf !== -1) { fruits.splice(indexSelf, 1); } self.destroy(); var indexOther = fruits.indexOf(otherFruit); if (indexOther !== -1) { fruits.splice(indexOther, 1); } otherFruit.destroy(); // Play merge sound LK.getSound('merge').play(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf6e58d }); /**** * Game Code ****/ // Game variables var FruitTypes = { CHERRY: { id: 'cherry', size: 100, points: 1, next: 'grape' }, GRAPE: { id: 'grape', size: 200, points: 2, next: 'apple' }, APPLE: { id: 'apple', size: 300, points: 3, next: 'orange' }, ORANGE: { id: 'orange', size: 400, points: 4, next: 'watermelon' }, WATERMELON: { id: 'watermelon', size: 500, points: 5, next: 'pineapple' }, PINEAPPLE: { id: 'pineapple', size: 600, points: 6, next: 'melon' }, MELON: { id: 'melon', size: 700, points: 7, next: 'peach' }, PEACH: { id: 'peach', size: 800, points: 8, next: 'coconut' }, COCONUT: { id: 'coconut', size: 900, points: 9, next: 'durian' }, DURIAN: { id: 'durian', size: 1000, points: 10, next: null } }; var gameWidth = 2048; var gameHeight = 2732; var fruits = []; var nextFruit = null; // Now holds the actual active fruit object var nextFruitDisplay = null; // For cleanup purposes var canDropFruit = true; var wallLeft, wallRight, gameFloor; var dropPoint = { x: gameWidth / 2, y: 200 }; var gameOver = false; var scoreText; // Setup game boundaries function setupBoundaries() { // Left wall wallLeft = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallLeft.x = 0; wallLeft.y = gameHeight / 2; // Right wall wallRight = game.addChild(LK.getAsset('wall', { anchorX: 0.5, anchorY: 0.5 })); wallRight.x = gameWidth; wallRight.y = gameHeight / 2; // Floor gameFloor = game.addChild(LK.getAsset('floor', { anchorX: 0.5, anchorY: 0.5 })); gameFloor.x = gameWidth / 2; gameFloor.y = gameHeight; } // Create new active fruit at the top of the screen function createNextFruit() { // Determine which fruit to spawn (for now just start with smaller fruits) var fruitProbability = Math.random(); var fruitType; if (fruitProbability < 0.6) { fruitType = FruitTypes.CHERRY; } else if (fruitProbability < 0.85) { fruitType = FruitTypes.GRAPE; } else { fruitType = FruitTypes.APPLE; } // Clean up old next fruit if it exists if (nextFruitDisplay) { nextFruitDisplay.destroy(); } // Create active fruit that will be dropped var activeFruit = new Fruit(fruitType); activeFruit.x = gameWidth / 2; activeFruit.y = dropPoint.y; activeFruit.isStatic = true; // Make it static until dropped game.addChild(activeFruit); // Store the active fruit nextFruit = activeFruit; nextFruitDisplay = activeFruit; } // Drop the active fruit at specified position function dropFruit(x) { if (!canDropFruit || gameOver || !nextFruit) { return; } // Make the active fruit start falling var droppingFruit = nextFruit; droppingFruit.isStatic = false; droppingFruit.x = x; // Add small random x velocity to make it interesting droppingFruit.vx = (Math.random() - 0.5) * 3; fruits.push(droppingFruit); // Play drop sound LK.getSound('drop').play(); // Create a new active fruit createNextFruit(); // Prevent spam dropping canDropFruit = false; LK.setTimeout(function () { canDropFruit = true; }, 500); } // Update score display function updateScoreDisplay() { scoreText.setText("SCORE: " + LK.getScore()); } // Setup UI function setupUI() { // Score display scoreText = new Text2("SCORE: 0", { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); scoreText.y = 30; } // Check for fruit collisions function checkFruitCollisions() { for (var i = 0; i < fruits.length; i++) { for (var j = i + 1; j < fruits.length; j++) { var fruit1 = fruits[i]; var fruit2 = fruits[j]; // Skip if either fruit is already merging if (fruit1.merging || fruit2.merging) { continue; } // Calculate distance between centers var dx = fruit2.x - fruit1.x; var dy = fruit2.y - fruit1.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if they are overlapping var combinedRadius = (fruit1.type.size + fruit2.type.size) / 2; // Use a smaller threshold for merging to prevent overlap issues if (distance < combinedRadius * 0.9) { // Only proceed with merge if fruits are the same type if (fruit1.type === fruit2.type) { // Merge fruits immediately upon contact, regardless of velocity fruit1.merge(fruit2); break; } } } } } // Check if game is over (too many fruits on screen or stacked too high) function checkGameOver() { if (gameOver) { return; } if (fruits.length > 30) { gameOver = true; LK.showGameOver(); return; } // Check if any fruits are too high for (var i = 0; i < fruits.length; i++) { // Only check for game over if fruit is high AND has had time to settle if (fruits[i].y < 300 && !fruits[i].merging) { var isFruitMoving = Math.abs(fruits[i].vx) > 0.5 || Math.abs(fruits[i].vy) > 0.5; // Add a countdown to ensure the fruit has truly stopped moving if (!isFruitMoving) { // Initialize the stable timer if not already set if (fruits[i].stableTimer === undefined) { fruits[i].stableTimer = 60; // Give 1 second (60 frames) to confirm stability } else { fruits[i].stableTimer--; if (fruits[i].stableTimer <= 0) { gameOver = true; LK.showGameOver(); return; } } } else { // Reset timer if the fruit starts moving again fruits[i].stableTimer = undefined; } } } } // Initialize game function initGame() { LK.setScore(0); gameOver = false; fruits = []; // Start background music LK.playMusic('bgmusic'); // Setup game elements setupBoundaries(); setupUI(); updateScoreDisplay(); createNextFruit(); } // Track if finger is down var fingerDown = false; var lastFingerX = 0; // Event handlers game.down = function (x, y) { // Don't allow interaction if game is over if (gameOver) { return; } // Store finger position fingerDown = true; lastFingerX = x; // Clamp to valid game area var minX = wallLeft.x + wallLeft.width / 2 + Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100); var maxX = wallRight.x - wallRight.width / 2 - Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100); lastFingerX = Math.max(minX, Math.min(maxX, lastFingerX)); // Move active fruit to finger position if (nextFruit && nextFruit.isStatic) { nextFruit.x = lastFingerX; } }; // Add move handler to follow finger on X axis game.move = function (x, y) { if (!fingerDown || !nextFruit || !nextFruit.isStatic) { return; } // Clamp to valid game area var minX = wallLeft.x + wallLeft.width / 2 + Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100); var maxX = wallRight.x - wallRight.width / 2 - Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100); lastFingerX = Math.max(minX, Math.min(maxX, x)); // Move active fruit to finger position on X axis only nextFruit.x = lastFingerX; }; // Add up handler to drop fruit when finger is released game.up = function () { if (!fingerDown || !nextFruit || !nextFruit.isStatic) { return; } // Drop the fruit at current finger position dropFruit(lastFingerX); fingerDown = false; }; // Game update loop game.update = function () { // Check for fruit collisions checkFruitCollisions(); // Check game over conditions checkGameOver(); }; // Initialize the game initGame();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Fruit = Container.expand(function (type) {
var self = Container.call(this);
// Handle both direct type object and type name
if (typeof type === 'string') {
// Convert type string to actual FruitType object
self.type = FruitTypes[type.toUpperCase()];
} else {
self.type = type;
}
self.vx = 0;
self.vy = 0;
self.rotation = 0;
self.angularVelocity = 0;
self.angularFriction = 0.96; // Increased from 0.97 for more friction
self.groundAngularFriction = 0.85; // Additional friction when touching ground
self.gravity = 1.8; // Doubled gravity to make fruits drop faster
self.friction = 0.98;
self.elasticity = 0.7;
self.merging = false;
self.isStatic = false; // Will be true for the active fruit at top
self.maxAngularVelocity = 0.15; // Add maximum angular velocity cap
// Only attempt to attach the asset if self.type exists and has necessary properties
if (self.type && self.type.id && self.type.points && self.type.size) {
var fruitGraphics = self.attachAsset(self.type.id, {
anchorX: 0.5,
anchorY: 0.5
});
// No point value shown on fruit
} else {
// This will be initialized properly when the game is fully loaded
console.log("Warning: Fruit type not available yet or missing required properties");
}
self.update = function () {
if (self.isStatic || self.merging) {
return;
}
// Apply gravity
self.vy += self.gravity;
// Apply velocity
self.x += self.vx;
self.y += self.vy;
// Apply rotation
self.rotation += self.angularVelocity;
// Apply friction
self.vx *= self.friction;
self.vy *= self.friction;
// Apply angular friction
self.angularVelocity *= self.angularFriction;
// Clamp angular velocity to prevent excessive spinning
self.angularVelocity = Math.min(Math.max(self.angularVelocity, -self.maxAngularVelocity), self.maxAngularVelocity);
// Check collisions with other fruits
for (var i = 0; i < fruits.length; i++) {
var other = fruits[i];
if (other === self || other.merging || self.merging) {
continue;
}
// Skip collision resolution if same fruit type (they should merge instead)
if (self.type === other.type) {
continue;
}
// Calculate distance between centers
var dx = other.x - self.x;
var dy = other.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate minimum distance before collision (sum of radii)
var minDistance = (self.type.size + other.type.size) / 2;
// If collision detected
if (distance < minDistance) {
// Normalize direction vector
var nx = dx / distance;
var ny = dy / distance;
// Calculate penetration depth
var penetration = minDistance - distance;
// Move fruits apart based on penetration
self.x -= nx * penetration * 0.5;
self.y -= ny * penetration * 0.5;
other.x += nx * penetration * 0.5;
other.y += ny * penetration * 0.5;
// Calculate relative velocity
var relVelocityX = other.vx - self.vx;
var relVelocityY = other.vy - self.vy;
// Calculate velocity along normal
var velAlongNormal = relVelocityX * nx + relVelocityY * ny;
// Only resolve if objects are moving toward each other
if (velAlongNormal > 0) {
continue;
}
// Calculate collision impulse
var e = self.elasticity; // coefficient of restitution
var j = -(1 + e) * velAlongNormal;
j /= 2; // Assume equal mass for simplicity
// Apply impulse
self.vx -= j * nx;
self.vy -= j * ny;
other.vx += j * nx;
other.vy += j * ny;
// Calculate rotational effect based on impact point and fruit sizes
// Perpendicular vector to normal
var px = -ny;
var py = nx;
// Approximate lever arm length (distance from center to impact point along the fruit's edge)
var selfRadius = self.type.size / 2;
var otherRadius = other.type.size / 2;
// Calculate rotational impulse based on impact point
// Higher impulse = faster rotation with a significantly reduced factor
var rotationalFactor = 0.003; // Reduced from 0.008 to 0.003
var selfRotation = j * (px * self.vx + py * self.vy) * rotationalFactor;
var otherRotation = j * (px * other.vx + py * other.vy) * rotationalFactor;
// Apply rotational impulse inversely proportional to radius (smaller fruits rotate faster)
// But cap the maximum rotation that can be applied in a single collision
var maxAngularChange = 0.05; // Maximum angular velocity change per collision
selfRotation = Math.min(Math.max(selfRotation / (selfRadius * 0.05), -maxAngularChange), maxAngularChange);
otherRotation = Math.min(Math.max(otherRotation / (otherRadius * 0.05), -maxAngularChange), maxAngularChange);
self.angularVelocity += selfRotation;
other.angularVelocity -= otherRotation;
// Apply additional friction to angular velocity when fruits are in contact
self.angularVelocity *= 0.9; // Friction during collision
other.angularVelocity *= 0.9; // Friction during collision
// Play bounce sound if significant collision
if (Math.abs(j) > 2) {
LK.getSound('bounce').play();
}
}
}
// Wall collision
if (self.x < wallLeft.x + wallLeft.width / 2 + self.type.size / 2) {
self.x = wallLeft.x + wallLeft.width / 2 + self.type.size / 2;
self.vx = -self.vx * self.elasticity;
// Add rotation on wall impact - direction based on vertical velocity
self.angularVelocity += self.vy * 0.005; // Reduced from 0.01 to 0.005
if (Math.abs(self.vx) > 1) {
LK.getSound('bounce').play();
}
}
if (self.x > wallRight.x - wallRight.width / 2 - self.type.size / 2) {
self.x = wallRight.x - wallRight.width / 2 - self.type.size / 2;
self.vx = -self.vx * self.elasticity;
// Add rotation on wall impact - direction based on vertical velocity
self.angularVelocity -= self.vy * 0.005; // Reduced from 0.01 to 0.005
if (Math.abs(self.vx) > 1) {
LK.getSound('bounce').play();
}
}
// Floor collision
if (self.y > gameFloor.y - gameFloor.height / 2 - self.type.size / 2) {
self.y = gameFloor.y - gameFloor.height / 2 - self.type.size / 2;
self.vy = -self.vy * self.elasticity;
// Apply ground friction to rotation when in contact with floor
// Apply direct rotation based on horizontal velocity, but much gentler
if (Math.abs(self.vx) > 0.5) {
// Only influence rotation direction, not add to existing rotation
self.angularVelocity = self.vx * 0.01; // Reduced significantly
}
// Apply stronger ground friction to slow rotation
self.angularVelocity *= self.groundAngularFriction;
// If we're barely moving, stop completely
if (Math.abs(self.vy) < 2) {
self.vy = 0;
}
// If we're barely rotating, stop completely with a higher threshold
if (Math.abs(self.angularVelocity) < 0.03) {
// Increased threshold for stopping rotation
self.angularVelocity = 0;
}
if (Math.abs(self.vy) > 1) {
LK.getSound('bounce').play();
}
}
};
self.merge = function (otherFruit) {
if (self.merging || !self.type.next) {
return;
}
self.merging = true;
otherFruit.merging = true;
// Calculate midpoint between fruits for new fruit position
var midX = (self.x + otherFruit.x) / 2;
var midY = (self.y + otherFruit.y) / 2;
// Create merge animation
tween(self, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
easing: tween.easeOut
});
tween(otherFruit, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create new merged fruit
var nextType = FruitTypes[self.type.next.toUpperCase()];
var newFruit = new Fruit(nextType);
newFruit.x = midX;
newFruit.y = midY;
newFruit.scaleX = 0.5;
newFruit.scaleY = 0.5;
game.addChild(newFruit);
fruits.push(newFruit);
// Add merge points
LK.setScore(LK.getScore() + nextType.points);
updateScoreDisplay();
// Animate new fruit growing
tween(newFruit, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
// Remove old fruits
var indexSelf = fruits.indexOf(self);
if (indexSelf !== -1) {
fruits.splice(indexSelf, 1);
}
self.destroy();
var indexOther = fruits.indexOf(otherFruit);
if (indexOther !== -1) {
fruits.splice(indexOther, 1);
}
otherFruit.destroy();
// Play merge sound
LK.getSound('merge').play();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xf6e58d
});
/****
* Game Code
****/
// Game variables
var FruitTypes = {
CHERRY: {
id: 'cherry',
size: 100,
points: 1,
next: 'grape'
},
GRAPE: {
id: 'grape',
size: 200,
points: 2,
next: 'apple'
},
APPLE: {
id: 'apple',
size: 300,
points: 3,
next: 'orange'
},
ORANGE: {
id: 'orange',
size: 400,
points: 4,
next: 'watermelon'
},
WATERMELON: {
id: 'watermelon',
size: 500,
points: 5,
next: 'pineapple'
},
PINEAPPLE: {
id: 'pineapple',
size: 600,
points: 6,
next: 'melon'
},
MELON: {
id: 'melon',
size: 700,
points: 7,
next: 'peach'
},
PEACH: {
id: 'peach',
size: 800,
points: 8,
next: 'coconut'
},
COCONUT: {
id: 'coconut',
size: 900,
points: 9,
next: 'durian'
},
DURIAN: {
id: 'durian',
size: 1000,
points: 10,
next: null
}
};
var gameWidth = 2048;
var gameHeight = 2732;
var fruits = [];
var nextFruit = null; // Now holds the actual active fruit object
var nextFruitDisplay = null; // For cleanup purposes
var canDropFruit = true;
var wallLeft, wallRight, gameFloor;
var dropPoint = {
x: gameWidth / 2,
y: 200
};
var gameOver = false;
var scoreText;
// Setup game boundaries
function setupBoundaries() {
// Left wall
wallLeft = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
wallLeft.x = 0;
wallLeft.y = gameHeight / 2;
// Right wall
wallRight = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
wallRight.x = gameWidth;
wallRight.y = gameHeight / 2;
// Floor
gameFloor = game.addChild(LK.getAsset('floor', {
anchorX: 0.5,
anchorY: 0.5
}));
gameFloor.x = gameWidth / 2;
gameFloor.y = gameHeight;
}
// Create new active fruit at the top of the screen
function createNextFruit() {
// Determine which fruit to spawn (for now just start with smaller fruits)
var fruitProbability = Math.random();
var fruitType;
if (fruitProbability < 0.6) {
fruitType = FruitTypes.CHERRY;
} else if (fruitProbability < 0.85) {
fruitType = FruitTypes.GRAPE;
} else {
fruitType = FruitTypes.APPLE;
}
// Clean up old next fruit if it exists
if (nextFruitDisplay) {
nextFruitDisplay.destroy();
}
// Create active fruit that will be dropped
var activeFruit = new Fruit(fruitType);
activeFruit.x = gameWidth / 2;
activeFruit.y = dropPoint.y;
activeFruit.isStatic = true; // Make it static until dropped
game.addChild(activeFruit);
// Store the active fruit
nextFruit = activeFruit;
nextFruitDisplay = activeFruit;
}
// Drop the active fruit at specified position
function dropFruit(x) {
if (!canDropFruit || gameOver || !nextFruit) {
return;
}
// Make the active fruit start falling
var droppingFruit = nextFruit;
droppingFruit.isStatic = false;
droppingFruit.x = x;
// Add small random x velocity to make it interesting
droppingFruit.vx = (Math.random() - 0.5) * 3;
fruits.push(droppingFruit);
// Play drop sound
LK.getSound('drop').play();
// Create a new active fruit
createNextFruit();
// Prevent spam dropping
canDropFruit = false;
LK.setTimeout(function () {
canDropFruit = true;
}, 500);
}
// Update score display
function updateScoreDisplay() {
scoreText.setText("SCORE: " + LK.getScore());
}
// Setup UI
function setupUI() {
// Score display
scoreText = new Text2("SCORE: 0", {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
scoreText.y = 30;
}
// Check for fruit collisions
function checkFruitCollisions() {
for (var i = 0; i < fruits.length; i++) {
for (var j = i + 1; j < fruits.length; j++) {
var fruit1 = fruits[i];
var fruit2 = fruits[j];
// Skip if either fruit is already merging
if (fruit1.merging || fruit2.merging) {
continue;
}
// Calculate distance between centers
var dx = fruit2.x - fruit1.x;
var dy = fruit2.y - fruit1.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if they are overlapping
var combinedRadius = (fruit1.type.size + fruit2.type.size) / 2;
// Use a smaller threshold for merging to prevent overlap issues
if (distance < combinedRadius * 0.9) {
// Only proceed with merge if fruits are the same type
if (fruit1.type === fruit2.type) {
// Merge fruits immediately upon contact, regardless of velocity
fruit1.merge(fruit2);
break;
}
}
}
}
}
// Check if game is over (too many fruits on screen or stacked too high)
function checkGameOver() {
if (gameOver) {
return;
}
if (fruits.length > 30) {
gameOver = true;
LK.showGameOver();
return;
}
// Check if any fruits are too high
for (var i = 0; i < fruits.length; i++) {
// Only check for game over if fruit is high AND has had time to settle
if (fruits[i].y < 300 && !fruits[i].merging) {
var isFruitMoving = Math.abs(fruits[i].vx) > 0.5 || Math.abs(fruits[i].vy) > 0.5;
// Add a countdown to ensure the fruit has truly stopped moving
if (!isFruitMoving) {
// Initialize the stable timer if not already set
if (fruits[i].stableTimer === undefined) {
fruits[i].stableTimer = 60; // Give 1 second (60 frames) to confirm stability
} else {
fruits[i].stableTimer--;
if (fruits[i].stableTimer <= 0) {
gameOver = true;
LK.showGameOver();
return;
}
}
} else {
// Reset timer if the fruit starts moving again
fruits[i].stableTimer = undefined;
}
}
}
}
// Initialize game
function initGame() {
LK.setScore(0);
gameOver = false;
fruits = [];
// Start background music
LK.playMusic('bgmusic');
// Setup game elements
setupBoundaries();
setupUI();
updateScoreDisplay();
createNextFruit();
}
// Track if finger is down
var fingerDown = false;
var lastFingerX = 0;
// Event handlers
game.down = function (x, y) {
// Don't allow interaction if game is over
if (gameOver) {
return;
}
// Store finger position
fingerDown = true;
lastFingerX = x;
// Clamp to valid game area
var minX = wallLeft.x + wallLeft.width / 2 + Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100);
var maxX = wallRight.x - wallRight.width / 2 - Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100);
lastFingerX = Math.max(minX, Math.min(maxX, lastFingerX));
// Move active fruit to finger position
if (nextFruit && nextFruit.isStatic) {
nextFruit.x = lastFingerX;
}
};
// Add move handler to follow finger on X axis
game.move = function (x, y) {
if (!fingerDown || !nextFruit || !nextFruit.isStatic) {
return;
}
// Clamp to valid game area
var minX = wallLeft.x + wallLeft.width / 2 + Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100);
var maxX = wallRight.x - wallRight.width / 2 - Math.max(100, nextFruit ? nextFruit.type.size / 2 : 100);
lastFingerX = Math.max(minX, Math.min(maxX, x));
// Move active fruit to finger position on X axis only
nextFruit.x = lastFingerX;
};
// Add up handler to drop fruit when finger is released
game.up = function () {
if (!fingerDown || !nextFruit || !nextFruit.isStatic) {
return;
}
// Drop the fruit at current finger position
dropFruit(lastFingerX);
fingerDown = false;
};
// Game update loop
game.update = function () {
// Check for fruit collisions
checkFruitCollisions();
// Check game over conditions
checkGameOver();
};
// Initialize the game
initGame();