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