/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballAsset = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.radius = ballAsset.width / 2; self.vx = 0; self.vy = 0; self.isActive = true; self.update = function () { if (!self.isActive) return; // If the ball has landed in a slot, freeze its position and velocity if (self.hasLandedInSlot) { self.vx = 0; self.vy = 0; return; } // Gravity self.vy += 1.2; // Clamp vy if (self.vy > 40) self.vy = 40; // Move self.x += self.vx; self.y += self.vy; // Wall bounce if (self.x < self.radius) { self.x = self.radius; self.vx = -self.vx * 0.7; } if (self.x > 2048 - self.radius) { self.x = 2048 - self.radius; self.vx = -self.vx * 0.7; } }; // Animate on slot win self.celebrate = function (color) { tween(ballAsset, { tint: color }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { tween(ballAsset, { tint: 0xf1c40f }, { duration: 400 }); } }); }; return self; }); // Peg class var Peg = Container.expand(function () { var self = Container.call(this); var pegAsset = self.attachAsset('peg', { anchorX: 0.5, anchorY: 0.5 }); return self; }); // Slot class var Slot = Container.expand(function () { var self = Container.call(this); // Slot types: common, rare, epic, legendary self.type = 'common'; self.points = 10; self.prob = 0.5; self.index = 0; self.width = 220; self.height = 80; self.bg = null; self.label = null; self.highlight = null; self.showProb = false; self.init = function (type, points, prob, index) { self.type = type; self.points = points; self.prob = prob; self.index = index; var assetId = 'slot_' + type; self.bg = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Custom label for zero-point trap if (type === 'zero') { self.label = new Text2("0", { size: 60, fill: 0xFF6666 }); // Optionally, add a "No Points" sublabel for clarity self.sublabel = new Text2("No Points", { size: 32, fill: 0xFF6666 }); self.sublabel.anchor.set(0.5, 0.5); self.sublabel.y = 32; self.addChild(self.sublabel); } else { self.label = new Text2(points + '', { size: 60, fill: "#fff" }); } self.label.anchor.set(0.5, 0.5); self.label.y = 0; self.addChild(self.label); self.width = self.bg.width; self.height = self.bg.height; }; self.showHighlight = function () { if (self.highlight) return; self.highlight = self.attachAsset('slot_highlight', { anchorX: 0.5, anchorY: 0.5, alpha: 0.18 }); self.setChildIndex(self.highlight, 0); }; self.hideHighlight = function () { if (self.highlight) { self.removeChild(self.highlight); self.highlight = null; } }; self.showProbability = function () { if (self.showProb) return; self.showProb = true; self.probTxt = new Text2(Math.round(self.prob * 100) + "%", { size: 38, fill: "#fff" }); self.probTxt.anchor.set(0.5, 0.5); self.probTxt.y = 38; self.addChild(self.probTxt); }; self.hideProbability = function () { if (!self.showProb) return; self.showProb = false; if (self.probTxt) { self.removeChild(self.probTxt); self.probTxt = null; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222b3a }); /**** * Game Code ****/ // visually distinct for zero-point trap // Music // Sound effects // Slot highlight // Slot backgrounds (different colors for rarity) // Ball // Pegs // Play background music LK.playMusic('bgmusic', { fade: { start: 0, end: 1, duration: 1200 } }); // Score display var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // High score display (top right, prominent) var highScore = storage.highScore || 0; var highScoreTxt = new Text2('High: ' + highScore, { size: 70, fill: 0xFFD700 }); highScoreTxt.anchor.set(1, 0); // top right highScoreTxt.x = 2048 - 40; highScoreTxt.y = 0; LK.gui.top.addChild(highScoreTxt); // Peg grid parameters var pegRows = 8; var pegsPerRow = [7, 8, 7, 8, 7, 8, 7, 8]; var pegSpacingX = 240; var pegSpacingY = 180; var pegStartY = 400; var pegStartX = 2048 / 2 - pegSpacingX * 3.5; // Slot definitions (mirrored, symmetric layout with zero-point slots at both ends) // 0 - 10 - 50 - 100 - 300 - 500 - 300 - 100 - 50 - 10 - 0 // Probability weights for each slot (should sum to 1.0) var slotDefs = [ // Far left: zero-point trap { type: 'zero', points: 0, prob: 0.08 }, // Next: 10 { type: 'common', points: 10, prob: 0.13 }, // Next: 50 { type: 'rare', points: 50, prob: 0.10 }, // Next: 100 { type: 'epic', points: 100, prob: 0.08 }, // Next: 300 { type: 'legendary', points: 300, prob: 0.07 }, // Center: 500 { type: 'legendary', points: 500, prob: 0.04 }, // Next: 300 { type: 'legendary', points: 300, prob: 0.07 }, // Next: 100 { type: 'epic', points: 100, prob: 0.08 }, // Next: 50 { type: 'rare', points: 50, prob: 0.10 }, // Next: 10 { type: 'common', points: 10, prob: 0.13 }, // Far right: zero-point trap { type: 'zero', points: 0, prob: 0.08 }]; // (Total: 0.08+0.13+0.10+0.08+0.07+0.04+0.07+0.08+0.10+0.13+0.08 = 0.96, close to 1.0) // Slot positions var slotCount = slotDefs.length; var slotWidth = 220; var slotSpacing = 20; var slotHeight = LK.getAsset('slot_common', { anchorX: 0.5, anchorY: 0.5 }).height; var slotsTotalWidth = slotCount * slotWidth + (slotCount - 1) * slotSpacing; var slotsStartX = 2048 / 2 - slotsTotalWidth / 2 + slotWidth / 2; var slotsY = 2732 - 180; // Arrays for game objects var pegs = []; var slots = []; var balls = []; var activeBall = null; var canDrop = true; var lastTouchedSlot = null; // Create pegs grid for (var row = 0; row < pegRows; row++) { var count = pegsPerRow[row]; var y = pegStartY + row * pegSpacingY; var x0 = 2048 / 2 - (count - 1) / 2 * pegSpacingX; for (var col = 0; col < count; col++) { var peg = new Peg(); peg.x = x0 + col * pegSpacingX; peg.y = y; game.addChild(peg); pegs.push(peg); } } // Create slots for (var i = 0; i < slotCount; i++) { var def = slotDefs[i]; var slot = new Slot(); slot.init(def.type, def.points, def.prob, i); slot.x = slotsStartX + i * (slotWidth + slotSpacing); slot.y = slotsY; game.addChild(slot); slots.push(slot); } // Draw slot walls (visual separation) for (var i = 0; i <= slotCount; i++) { var wall = LK.getAsset('peg', { anchorX: 0.5, anchorY: 0.5, width: 30, height: 120, color: 0x34495e }); wall.x = slotsStartX - slotWidth / 2 + i * (slotWidth + slotSpacing); wall.y = slotsY - 40; game.addChild(wall); } // Ball drop area var dropY = 180; var dropX = 2048 / 2; // Drop indicator var dropIndicator = LK.getAsset('ball', { anchorX: 0.5, anchorY: 0.5, alpha: 0.25 }); dropIndicator.x = dropX; dropIndicator.y = dropY; game.addChild(dropIndicator); // Allow dragging drop indicator var draggingDrop = false; game.down = function (x, y, obj) { // Only allow drop if no active ball if (canDrop && y < 400) { draggingDrop = true; moveDropIndicator(x); } }; game.move = function (x, y, obj) { if (draggingDrop) { moveDropIndicator(x); } // Slot hover for probability var slot = getSlotAt(x, y); if (slot !== lastTouchedSlot) { if (lastTouchedSlot) { lastTouchedSlot.hideHighlight(); lastTouchedSlot.hideProbability(); } if (slot) { slot.showHighlight(); slot.showProbability(); } lastTouchedSlot = slot; } }; game.up = function (x, y, obj) { if (draggingDrop) { draggingDrop = false; // Drop ball if (canDrop) { spawnBall(dropIndicator.x, dropIndicator.y); } } // Remove slot highlight if (lastTouchedSlot) { lastTouchedSlot.hideHighlight(); lastTouchedSlot.hideProbability(); lastTouchedSlot = null; } }; function moveDropIndicator(x) { // Clamp to game area var minX = 80; var maxX = 2048 - 80; dropIndicator.x = Math.max(minX, Math.min(maxX, x)); } // Spawn a new ball function spawnBall(x, y) { canDrop = false; // Deduct 10 points for each drop LK.setScore(LK.getScore() - 10); scoreTxt.setText(LK.getScore()); var ball = new Ball(); ball.x = x; ball.y = y; ball.vx = 0; ball.vy = 0; balls.push(ball); activeBall = ball; game.addChild(ball); } // Get slot at position function getSlotAt(x, y) { for (var i = 0; i < slots.length; i++) { var slot = slots[i]; if (x > slot.x - slot.width / 2 && x < slot.x + slot.width / 2 && y > slot.y - slot.height / 2 && y < slot.y + slot.height / 2) { return slot; } } return null; } // Ball-peg collision function ballPegCollision(ball, peg) { var dx = ball.x - peg.x; var dy = ball.y - peg.y; var dist = Math.sqrt(dx * dx + dy * dy); var minDist = ball.radius + peg.width / 2 - 2; if (dist < minDist) { // Nudge ball out var angle = Math.atan2(dy, dx); var overlap = minDist - dist; ball.x += Math.cos(angle) * overlap; ball.y += Math.sin(angle) * overlap; // Bounce var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy); var normalAngle = angle; var tangentAngle = angle + Math.PI / 2; // Randomize bounce var bounceAngle = normalAngle + (Math.random() - 0.5) * 0.7; var newVx = Math.cos(bounceAngle) * speed * 0.7; var newVy = Math.sin(bounceAngle) * speed * 0.7; ball.vx = newVx; ball.vy = newVy; // Play sound LK.getSound('peg_hit').play(); } } // Ball-slot detection and reward function checkBallSlot(ball) { // Only trigger once per ball if (!ball.isActive) return false; // Check if ball has reached the slot area (bottom of the screen) if (ball.y + ball.radius >= slotsY - slotHeight / 2) { ball.isActive = false; ball.hasLandedInSlot = true; // Mark as landed to freeze movement // Weighted random selection of slot based on defined probabilities var r = Math.random(); var acc = 0; var chosenSlot = null; for (var i = 0; i < slots.length; i++) { acc += slots[i].prob; if (r < acc) { chosenSlot = slots[i]; break; } } // Fallback in case of floating point error if (!chosenSlot) chosenSlot = slots[slots.length - 1]; // Instantly move ball to chosen slot center and freeze it there ball.x = chosenSlot.x; ball.y = chosenSlot.y; ball.vx = 0; ball.vy = 0; // Celebrate and update score if (chosenSlot.type !== 'zero') { LK.setScore(LK.getScore() + chosenSlot.points); scoreTxt.setText(LK.getScore()); ball.celebrate(0x2ecc40); LK.getSound('slot_win').play(); } else { // No points, visually indicate trap ball.celebrate(0xff6666); // Optionally, play a "fail" sound if available // LK.getSound('fail').play(); // Score already deducted on drop, nothing to add scoreTxt.setText(LK.getScore()); } // Fade out and destroy ball tween(ball, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { ball.destroy(); } }); // Allow next drop after short delay LK.setTimeout(function () { canDrop = true; }, 700); return true; } return false; } // Remove balls that fall out of bounds function cleanupBalls() { for (var i = balls.length - 1; i >= 0; i--) { var ball = balls[i]; if (ball.y > 2732 + 100 || ball.alpha === 0) { ball.destroy(); balls.splice(i, 1); if (activeBall === ball) activeBall = null; canDrop = true; } } } // Game over state var isGameOver = false; var gameOverTxt = null; var restartTimeout = null; function triggerGameOver() { if (isGameOver) return; isGameOver = true; // Show Game Over message if (!gameOverTxt) { gameOverTxt = new Text2("Game Over", { size: 180, fill: 0xFF4444 }); gameOverTxt.anchor.set(0.5, 0.5); } gameOverTxt.x = 2048 / 2; gameOverTxt.y = 2732 / 2 - 100; LK.gui.center.addChild(gameOverTxt); // Show high score below Game Over if (!gameOverTxt.highScoreLabel) { gameOverTxt.highScoreLabel = new Text2("High Score: " + highScore, { size: 90, fill: 0xFFD700 }); gameOverTxt.highScoreLabel.anchor.set(0.5, 0.5); } gameOverTxt.highScoreLabel.setText("High Score: " + highScore); gameOverTxt.highScoreLabel.x = 2048 / 2; gameOverTxt.highScoreLabel.y = 2732 / 2 + 40; LK.gui.center.addChild(gameOverTxt.highScoreLabel); // Save and update high score if needed var finalScore = LK.getScore(); if (finalScore > highScore) { highScore = finalScore; storage.highScore = highScore; highScoreTxt.setText('High: ' + highScore); } // Reset score LK.setScore(0); scoreTxt.setText("0"); // Remove all balls and stop drops for (var i = balls.length - 1; i >= 0; i--) { if (balls[i] && typeof balls[i].destroy === "function") { balls[i].destroy(); } balls.splice(i, 1); } activeBall = null; canDrop = false; // Remove any slot highlights if (lastTouchedSlot) { lastTouchedSlot.hideHighlight(); lastTouchedSlot.hideProbability(); lastTouchedSlot = null; } // Restart after 2.2 seconds if (restartTimeout) LK.clearTimeout(restartTimeout); restartTimeout = LK.setTimeout(function () { // Remove Game Over text if (gameOverTxt && gameOverTxt.parent) { gameOverTxt.parent.removeChild(gameOverTxt); } // Remove high score label from game over screen if (gameOverTxt && gameOverTxt.highScoreLabel && gameOverTxt.highScoreLabel.parent) { gameOverTxt.highScoreLabel.parent.removeChild(gameOverTxt.highScoreLabel); } // Reset drop indicator dropIndicator.x = dropX; dropIndicator.y = dropY; // Allow new drop canDrop = true; isGameOver = false; // Show score as 0 scoreTxt.setText("0"); }, 2200); } // Main game update game.update = function () { // Check for game over if (!isGameOver && LK.getScore() < -20) { triggerGameOver(); return; } // Update balls for (var i = 0; i < balls.length; i++) { var ball = balls[i]; ball.update(); // Collide with pegs for (var j = 0; j < pegs.length; j++) { ballPegCollision(ball, pegs[j]); } // Check slot if (checkBallSlot(ball)) continue; } cleanupBalls(); }; // Show initial score scoreTxt.setText(LK.getScore());
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = ballAsset.width / 2;
self.vx = 0;
self.vy = 0;
self.isActive = true;
self.update = function () {
if (!self.isActive) return;
// If the ball has landed in a slot, freeze its position and velocity
if (self.hasLandedInSlot) {
self.vx = 0;
self.vy = 0;
return;
}
// Gravity
self.vy += 1.2;
// Clamp vy
if (self.vy > 40) self.vy = 40;
// Move
self.x += self.vx;
self.y += self.vy;
// Wall bounce
if (self.x < self.radius) {
self.x = self.radius;
self.vx = -self.vx * 0.7;
}
if (self.x > 2048 - self.radius) {
self.x = 2048 - self.radius;
self.vx = -self.vx * 0.7;
}
};
// Animate on slot win
self.celebrate = function (color) {
tween(ballAsset, {
tint: color
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(ballAsset, {
tint: 0xf1c40f
}, {
duration: 400
});
}
});
};
return self;
});
// Peg class
var Peg = Container.expand(function () {
var self = Container.call(this);
var pegAsset = self.attachAsset('peg', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Slot class
var Slot = Container.expand(function () {
var self = Container.call(this);
// Slot types: common, rare, epic, legendary
self.type = 'common';
self.points = 10;
self.prob = 0.5;
self.index = 0;
self.width = 220;
self.height = 80;
self.bg = null;
self.label = null;
self.highlight = null;
self.showProb = false;
self.init = function (type, points, prob, index) {
self.type = type;
self.points = points;
self.prob = prob;
self.index = index;
var assetId = 'slot_' + type;
self.bg = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Custom label for zero-point trap
if (type === 'zero') {
self.label = new Text2("0", {
size: 60,
fill: 0xFF6666
});
// Optionally, add a "No Points" sublabel for clarity
self.sublabel = new Text2("No Points", {
size: 32,
fill: 0xFF6666
});
self.sublabel.anchor.set(0.5, 0.5);
self.sublabel.y = 32;
self.addChild(self.sublabel);
} else {
self.label = new Text2(points + '', {
size: 60,
fill: "#fff"
});
}
self.label.anchor.set(0.5, 0.5);
self.label.y = 0;
self.addChild(self.label);
self.width = self.bg.width;
self.height = self.bg.height;
};
self.showHighlight = function () {
if (self.highlight) return;
self.highlight = self.attachAsset('slot_highlight', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.18
});
self.setChildIndex(self.highlight, 0);
};
self.hideHighlight = function () {
if (self.highlight) {
self.removeChild(self.highlight);
self.highlight = null;
}
};
self.showProbability = function () {
if (self.showProb) return;
self.showProb = true;
self.probTxt = new Text2(Math.round(self.prob * 100) + "%", {
size: 38,
fill: "#fff"
});
self.probTxt.anchor.set(0.5, 0.5);
self.probTxt.y = 38;
self.addChild(self.probTxt);
};
self.hideProbability = function () {
if (!self.showProb) return;
self.showProb = false;
if (self.probTxt) {
self.removeChild(self.probTxt);
self.probTxt = null;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222b3a
});
/****
* Game Code
****/
// visually distinct for zero-point trap
// Music
// Sound effects
// Slot highlight
// Slot backgrounds (different colors for rarity)
// Ball
// Pegs
// Play background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score display (top right, prominent)
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('High: ' + highScore, {
size: 70,
fill: 0xFFD700
});
highScoreTxt.anchor.set(1, 0); // top right
highScoreTxt.x = 2048 - 40;
highScoreTxt.y = 0;
LK.gui.top.addChild(highScoreTxt);
// Peg grid parameters
var pegRows = 8;
var pegsPerRow = [7, 8, 7, 8, 7, 8, 7, 8];
var pegSpacingX = 240;
var pegSpacingY = 180;
var pegStartY = 400;
var pegStartX = 2048 / 2 - pegSpacingX * 3.5;
// Slot definitions (mirrored, symmetric layout with zero-point slots at both ends)
// 0 - 10 - 50 - 100 - 300 - 500 - 300 - 100 - 50 - 10 - 0
// Probability weights for each slot (should sum to 1.0)
var slotDefs = [
// Far left: zero-point trap
{
type: 'zero',
points: 0,
prob: 0.08
},
// Next: 10
{
type: 'common',
points: 10,
prob: 0.13
},
// Next: 50
{
type: 'rare',
points: 50,
prob: 0.10
},
// Next: 100
{
type: 'epic',
points: 100,
prob: 0.08
},
// Next: 300
{
type: 'legendary',
points: 300,
prob: 0.07
},
// Center: 500
{
type: 'legendary',
points: 500,
prob: 0.04
},
// Next: 300
{
type: 'legendary',
points: 300,
prob: 0.07
},
// Next: 100
{
type: 'epic',
points: 100,
prob: 0.08
},
// Next: 50
{
type: 'rare',
points: 50,
prob: 0.10
},
// Next: 10
{
type: 'common',
points: 10,
prob: 0.13
},
// Far right: zero-point trap
{
type: 'zero',
points: 0,
prob: 0.08
}];
// (Total: 0.08+0.13+0.10+0.08+0.07+0.04+0.07+0.08+0.10+0.13+0.08 = 0.96, close to 1.0)
// Slot positions
var slotCount = slotDefs.length;
var slotWidth = 220;
var slotSpacing = 20;
var slotHeight = LK.getAsset('slot_common', {
anchorX: 0.5,
anchorY: 0.5
}).height;
var slotsTotalWidth = slotCount * slotWidth + (slotCount - 1) * slotSpacing;
var slotsStartX = 2048 / 2 - slotsTotalWidth / 2 + slotWidth / 2;
var slotsY = 2732 - 180;
// Arrays for game objects
var pegs = [];
var slots = [];
var balls = [];
var activeBall = null;
var canDrop = true;
var lastTouchedSlot = null;
// Create pegs grid
for (var row = 0; row < pegRows; row++) {
var count = pegsPerRow[row];
var y = pegStartY + row * pegSpacingY;
var x0 = 2048 / 2 - (count - 1) / 2 * pegSpacingX;
for (var col = 0; col < count; col++) {
var peg = new Peg();
peg.x = x0 + col * pegSpacingX;
peg.y = y;
game.addChild(peg);
pegs.push(peg);
}
}
// Create slots
for (var i = 0; i < slotCount; i++) {
var def = slotDefs[i];
var slot = new Slot();
slot.init(def.type, def.points, def.prob, i);
slot.x = slotsStartX + i * (slotWidth + slotSpacing);
slot.y = slotsY;
game.addChild(slot);
slots.push(slot);
}
// Draw slot walls (visual separation)
for (var i = 0; i <= slotCount; i++) {
var wall = LK.getAsset('peg', {
anchorX: 0.5,
anchorY: 0.5,
width: 30,
height: 120,
color: 0x34495e
});
wall.x = slotsStartX - slotWidth / 2 + i * (slotWidth + slotSpacing);
wall.y = slotsY - 40;
game.addChild(wall);
}
// Ball drop area
var dropY = 180;
var dropX = 2048 / 2;
// Drop indicator
var dropIndicator = LK.getAsset('ball', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.25
});
dropIndicator.x = dropX;
dropIndicator.y = dropY;
game.addChild(dropIndicator);
// Allow dragging drop indicator
var draggingDrop = false;
game.down = function (x, y, obj) {
// Only allow drop if no active ball
if (canDrop && y < 400) {
draggingDrop = true;
moveDropIndicator(x);
}
};
game.move = function (x, y, obj) {
if (draggingDrop) {
moveDropIndicator(x);
}
// Slot hover for probability
var slot = getSlotAt(x, y);
if (slot !== lastTouchedSlot) {
if (lastTouchedSlot) {
lastTouchedSlot.hideHighlight();
lastTouchedSlot.hideProbability();
}
if (slot) {
slot.showHighlight();
slot.showProbability();
}
lastTouchedSlot = slot;
}
};
game.up = function (x, y, obj) {
if (draggingDrop) {
draggingDrop = false;
// Drop ball
if (canDrop) {
spawnBall(dropIndicator.x, dropIndicator.y);
}
}
// Remove slot highlight
if (lastTouchedSlot) {
lastTouchedSlot.hideHighlight();
lastTouchedSlot.hideProbability();
lastTouchedSlot = null;
}
};
function moveDropIndicator(x) {
// Clamp to game area
var minX = 80;
var maxX = 2048 - 80;
dropIndicator.x = Math.max(minX, Math.min(maxX, x));
}
// Spawn a new ball
function spawnBall(x, y) {
canDrop = false;
// Deduct 10 points for each drop
LK.setScore(LK.getScore() - 10);
scoreTxt.setText(LK.getScore());
var ball = new Ball();
ball.x = x;
ball.y = y;
ball.vx = 0;
ball.vy = 0;
balls.push(ball);
activeBall = ball;
game.addChild(ball);
}
// Get slot at position
function getSlotAt(x, y) {
for (var i = 0; i < slots.length; i++) {
var slot = slots[i];
if (x > slot.x - slot.width / 2 && x < slot.x + slot.width / 2 && y > slot.y - slot.height / 2 && y < slot.y + slot.height / 2) {
return slot;
}
}
return null;
}
// Ball-peg collision
function ballPegCollision(ball, peg) {
var dx = ball.x - peg.x;
var dy = ball.y - peg.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = ball.radius + peg.width / 2 - 2;
if (dist < minDist) {
// Nudge ball out
var angle = Math.atan2(dy, dx);
var overlap = minDist - dist;
ball.x += Math.cos(angle) * overlap;
ball.y += Math.sin(angle) * overlap;
// Bounce
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
var normalAngle = angle;
var tangentAngle = angle + Math.PI / 2;
// Randomize bounce
var bounceAngle = normalAngle + (Math.random() - 0.5) * 0.7;
var newVx = Math.cos(bounceAngle) * speed * 0.7;
var newVy = Math.sin(bounceAngle) * speed * 0.7;
ball.vx = newVx;
ball.vy = newVy;
// Play sound
LK.getSound('peg_hit').play();
}
}
// Ball-slot detection and reward
function checkBallSlot(ball) {
// Only trigger once per ball
if (!ball.isActive) return false;
// Check if ball has reached the slot area (bottom of the screen)
if (ball.y + ball.radius >= slotsY - slotHeight / 2) {
ball.isActive = false;
ball.hasLandedInSlot = true; // Mark as landed to freeze movement
// Weighted random selection of slot based on defined probabilities
var r = Math.random();
var acc = 0;
var chosenSlot = null;
for (var i = 0; i < slots.length; i++) {
acc += slots[i].prob;
if (r < acc) {
chosenSlot = slots[i];
break;
}
}
// Fallback in case of floating point error
if (!chosenSlot) chosenSlot = slots[slots.length - 1];
// Instantly move ball to chosen slot center and freeze it there
ball.x = chosenSlot.x;
ball.y = chosenSlot.y;
ball.vx = 0;
ball.vy = 0;
// Celebrate and update score
if (chosenSlot.type !== 'zero') {
LK.setScore(LK.getScore() + chosenSlot.points);
scoreTxt.setText(LK.getScore());
ball.celebrate(0x2ecc40);
LK.getSound('slot_win').play();
} else {
// No points, visually indicate trap
ball.celebrate(0xff6666);
// Optionally, play a "fail" sound if available
// LK.getSound('fail').play();
// Score already deducted on drop, nothing to add
scoreTxt.setText(LK.getScore());
}
// Fade out and destroy ball
tween(ball, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
ball.destroy();
}
});
// Allow next drop after short delay
LK.setTimeout(function () {
canDrop = true;
}, 700);
return true;
}
return false;
}
// Remove balls that fall out of bounds
function cleanupBalls() {
for (var i = balls.length - 1; i >= 0; i--) {
var ball = balls[i];
if (ball.y > 2732 + 100 || ball.alpha === 0) {
ball.destroy();
balls.splice(i, 1);
if (activeBall === ball) activeBall = null;
canDrop = true;
}
}
}
// Game over state
var isGameOver = false;
var gameOverTxt = null;
var restartTimeout = null;
function triggerGameOver() {
if (isGameOver) return;
isGameOver = true;
// Show Game Over message
if (!gameOverTxt) {
gameOverTxt = new Text2("Game Over", {
size: 180,
fill: 0xFF4444
});
gameOverTxt.anchor.set(0.5, 0.5);
}
gameOverTxt.x = 2048 / 2;
gameOverTxt.y = 2732 / 2 - 100;
LK.gui.center.addChild(gameOverTxt);
// Show high score below Game Over
if (!gameOverTxt.highScoreLabel) {
gameOverTxt.highScoreLabel = new Text2("High Score: " + highScore, {
size: 90,
fill: 0xFFD700
});
gameOverTxt.highScoreLabel.anchor.set(0.5, 0.5);
}
gameOverTxt.highScoreLabel.setText("High Score: " + highScore);
gameOverTxt.highScoreLabel.x = 2048 / 2;
gameOverTxt.highScoreLabel.y = 2732 / 2 + 40;
LK.gui.center.addChild(gameOverTxt.highScoreLabel);
// Save and update high score if needed
var finalScore = LK.getScore();
if (finalScore > highScore) {
highScore = finalScore;
storage.highScore = highScore;
highScoreTxt.setText('High: ' + highScore);
}
// Reset score
LK.setScore(0);
scoreTxt.setText("0");
// Remove all balls and stop drops
for (var i = balls.length - 1; i >= 0; i--) {
if (balls[i] && typeof balls[i].destroy === "function") {
balls[i].destroy();
}
balls.splice(i, 1);
}
activeBall = null;
canDrop = false;
// Remove any slot highlights
if (lastTouchedSlot) {
lastTouchedSlot.hideHighlight();
lastTouchedSlot.hideProbability();
lastTouchedSlot = null;
}
// Restart after 2.2 seconds
if (restartTimeout) LK.clearTimeout(restartTimeout);
restartTimeout = LK.setTimeout(function () {
// Remove Game Over text
if (gameOverTxt && gameOverTxt.parent) {
gameOverTxt.parent.removeChild(gameOverTxt);
}
// Remove high score label from game over screen
if (gameOverTxt && gameOverTxt.highScoreLabel && gameOverTxt.highScoreLabel.parent) {
gameOverTxt.highScoreLabel.parent.removeChild(gameOverTxt.highScoreLabel);
}
// Reset drop indicator
dropIndicator.x = dropX;
dropIndicator.y = dropY;
// Allow new drop
canDrop = true;
isGameOver = false;
// Show score as 0
scoreTxt.setText("0");
}, 2200);
}
// Main game update
game.update = function () {
// Check for game over
if (!isGameOver && LK.getScore() < -20) {
triggerGameOver();
return;
}
// Update balls
for (var i = 0; i < balls.length; i++) {
var ball = balls[i];
ball.update();
// Collide with pegs
for (var j = 0; j < pegs.length; j++) {
ballPegCollision(ball, pegs[j]);
}
// Check slot
if (checkBallSlot(ball)) continue;
}
cleanupBalls();
};
// Show initial score
scoreTxt.setText(LK.getScore());