/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BombDice class var BombDice = Container.expand(function () { var self = Container.call(this); self.isBomb = true; // Attach bomb asset, anchor center self.bombAsset = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5 }); // Add bomb label self.label = new Text2('', { size: 100, fill: 0xFFFFFF }); self.label.anchor.set(0.5, 0.5); self.label.x = 0; self.label.y = 0; self.addChild(self.label); // Called every tick self.update = function () { self.y += self.fallSpeed; }; // On tap self.down = function (x, y, obj) { // Score: subtract 5 (minimum 0) var newScore = LK.getScore() - 5; if (newScore < 0) { newScore = 0; } LK.setScore(newScore); LK.getSound('bombTap').play(); // Set penalty state for 90 ticks (~1.5s at 60fps) isPenalty = true; penaltyTicksLeft = 90; // penaltyTxt.visible = true; // Do not show penalty text // Subtract 2 seconds from timer (minimum 0) timeLeft -= 2; if (timeLeft < 0) { timeLeft = 0; } timerTxt.setText(timeLeft); // Animate: shake and fade out tween(self, { x: self.x + 30 }, { duration: 60, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { x: self.x - 60 }, { duration: 80, easing: tween.easeIn, onFinish: function onFinish() { tween(self, { x: self.x + 30, alpha: 0 }, { duration: 100, onFinish: function onFinish() { self.destroy(); LK.effects.flashScreen(0xffffff, 120); // Quick white flash after explosion // Manual screen shake using tween plugin var originalX = game.x || 0; var originalY = game.y || 0; var shakeDuration = 400; var shakeAmplitude = 16; var shakeTicks = Math.floor(shakeDuration / 16); // ~60fps var shakeCount = 0; function doShake() { if (shakeCount >= shakeTicks) { game.x = originalX; game.y = originalY; return; } // Random offset game.x = originalX + (Math.random() - 0.5) * shakeAmplitude; game.y = originalY + (Math.random() - 0.5) * shakeAmplitude; shakeCount++; LK.setTimeout(doShake, 16); } doShake(); } }); } }); } }); // Remove from dice array in main game removeDice(self); }; return self; }); // Dice class (for dice 1-6) var Dice = Container.expand(function () { var self = Container.call(this); // Dice value (1-6) self.value = 1; self.isBomb = false; // Attach dice asset, anchor center self.diceAsset = null; // Dice label (number) self.label = null; // Set up dice self.setup = function (value) { self.value = value; self.isBomb = false; var assetId = 'dice' + value; self.diceAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Add number label self.label = new Text2('' + value, { size: 28, fill: 0x222222 }); self.label.anchor.set(0.5, 0.5); self.label.x = 0; self.label.y = 0; self.addChild(self.label); }; // Called every tick self.update = function () { self.y += self.fallSpeed; }; // On tap self.down = function (x, y, obj) { if (self.isBomb) { return; } // Should not happen, but safety // Score: add dice value (1-6) LK.setScore(LK.getScore() + self.value); LK.getSound('diceTap').play(); // If this is the 6th die, add +2 seconds to timer (max 999 for safety) if (self.value === 6) { timeLeft += 2; if (timeLeft > 999) { timeLeft = 999; } timerTxt.setText(timeLeft); } // Animate: scale up and fade out tween(self, { scaleX: 1.4, scaleY: 1.4, alpha: 0 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); } }); // Remove from dice array in main game removeDice(self); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // Add background photo image var bgPhoto = LK.getAsset('backgroundPhoto', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 2048 / 1500, scaleY: 2732 / 4000 }); game.addChild(bgPhoto); // Play background music // Dice assets: 6 dice (1-6), all box shapes, different colors // Bomb dice: ellipse, red // Sounds // Music (background, desert style) LK.playMusic('desertbg'); // Dice array (all falling dice) var diceArr = []; // Remove dice helper function removeDice(diceObj) { for (var i = diceArr.length - 1; i >= 0; i--) { if (diceArr[i] === diceObj) { diceArr.splice(i, 1); break; } } } // Score label var scoreLabelTxt = new Text2('Score', { size: 60, fill: 0x7C4A03 }); scoreLabelTxt.anchor.set(0.5, 0); scoreLabelTxt.x = 0; scoreLabelTxt.y = 0; LK.gui.top.addChild(scoreLabelTxt); // Score value var scoreTxt = new Text2('0', { size: 120, fill: 0x7C4A03 }); scoreTxt.anchor.set(0.5, 0); scoreTxt.x = 0; scoreTxt.y = 60; LK.gui.top.addChild(scoreTxt); // Timer label var timerLabelTxt = new Text2('Time Left', { size: 60, fill: 0x7C4A03 }); timerLabelTxt.anchor.set(0.5, 0); timerLabelTxt.x = 0; timerLabelTxt.y = 200; LK.gui.top.addChild(timerLabelTxt); // Timer value var timerTxt = new Text2('60', { size: 90, fill: 0x7C4A03 }); timerTxt.anchor.set(0.5, 0); timerTxt.x = 0; timerTxt.y = 260; LK.gui.top.addChild(timerTxt); // Penalty indicator (hidden and never shown) var penaltyTxt = new Text2('PENALTY!', { size: 90, fill: 0xff2222 }); penaltyTxt.anchor.set(0.5, 0); penaltyTxt.visible = false; penaltyTxt.x = 0; penaltyTxt.y = 140; // Do NOT add penaltyTxt to LK.gui.top, so it never appears // Penalty state var isPenalty = false; var penaltyTicksLeft = 0; // Score bonus state removed // Game timer var timeLeft = 60; // seconds var timerInterval = LK.setInterval(function () { timeLeft--; if (timeLeft < 0) { timeLeft = 0; } timerTxt.setText(timeLeft); if (timeLeft === 0) { // Play timer end sound effect LK.getSound('timerEnd').play(); // End game LK.showGameOver(); } }, 1000); // Dice spawn timer var spawnTick = 0; var spawnInterval = 36; // ~0.6s at 60fps, will randomize // Helper: spawn a dice (random type) function spawnDice() { // Random X position (avoid edges) var margin = 120; var x = margin + Math.random() * (2048 - 2 * margin); var y = -100; // Bomb chance: 1 in 8 var isBomb = Math.random() < 0.125; var diceObj; if (isBomb) { diceObj = new BombDice(); } else { // Weighted random dice value: 2,3,4,5 are most likely, 1 and 6 are less likely // Probabilities: 1 (10%), 2 (20%), 3 (20%), 4 (20%), 5 (20%), 6 (10%) // Prevent 6 from spawning in first 10 seconds var r = Math.random(); var value; var elapsedTime = 60 - timeLeft; if (elapsedTime < 10) { // Only allow 1-5, reweight probabilities // 1 (11.1%), 2 (22.2%), 3 (22.2%), 4 (22.2%), 5 (22.2%) if (r < 0.111) { value = 1; } else if (r < 0.333) { value = 2; } else if (r < 0.555) { value = 3; } else if (r < 0.777) { value = 4; } else { value = 5; } } else { if (r < 0.10) { value = 1; } else if (r < 0.30) { value = 2; } else if (r < 0.50) { value = 3; } else if (r < 0.70) { value = 4; } else if (r < 0.90) { value = 5; } else { value = 6; } } diceObj = new Dice(); diceObj.setup(value); } diceObj.x = x; diceObj.y = y; diceObj.scaleX = 1; diceObj.scaleY = 1; diceObj.alpha = 1; // Set fall speed: dice value 1 falls slowest, 6 fastest if (!isBomb) { // Value 1: slowest (6 px/frame), Value 6: fastest (12 px/frame) diceObj.fallSpeed = 6 + (diceObj.value - 1) * (6 / 5); } else { // Bombs: fall speed matches die value 6 (fastest) diceObj.fallSpeed = 12; } // Add to game game.addChild(diceObj); diceArr.push(diceObj); } // Touch/click handler game.down = function (x, y, obj) { // Check if any dice is tapped (from topmost to bottom) for (var i = diceArr.length - 1; i >= 0; i--) { var d = diceArr[i]; // Convert global to local for dice var local = d.toLocal(game.toGlobal({ x: x, y: y })); // Check if inside dice bounds (centered) if (Math.abs(local.x) <= d.width / 2 && Math.abs(local.y) <= d.height / 2) { // Call dice down handler d.down(x, y, obj); return; } } }; // Main update loop game.update = function () { // Play sound every 100 points milestone if (typeof lastScoreSound === "undefined") { lastScoreSound = 0; } var currentScore = LK.getScore(); if (currentScore >= 100 && Math.floor(currentScore / 100) > Math.floor(lastScoreSound / 100)) { LK.getSound('milestone100').play(); lastScoreSound = currentScore; } // Penalty effect: apply penalty during penaltyTicksLeft if (isPenalty) { penaltyTicksLeft--; // During penalty, subtract 1 point every 15 ticks (~0.25s) if (penaltyTicksLeft % 15 === 0 && LK.getScore() > 0) { LK.setScore(Math.max(0, LK.getScore() - 1)); } if (penaltyTicksLeft <= 0) { isPenalty = false; // penaltyTxt.visible = false;//{29} // Already hidden, do nothing } } // Score bonus effect removed // Update score text scoreTxt.setText(LK.getScore()); // Spawn dice at intervals (randomize interval for variety) spawnTick++; if (spawnTick >= spawnInterval) { // Determine how many dice to spawn based on elapsed time // Start with 1, add 1 every 20 seconds (max 6 at 100+ seconds) var elapsed = 60 - timeLeft; var diceToSpawn = 1 + Math.floor(elapsed / 20); if (diceToSpawn > 6) diceToSpawn = 6; for (var i = 0; i < diceToSpawn; i++) { spawnDice(); } spawnTick = 0; // Next interval: 24-48 ticks (~0.4-0.8s) spawnInterval = 24 + Math.floor(Math.random() * 25); } // Update all dice, remove if off screen for (var i = diceArr.length - 1; i >= 0; i--) { var d = diceArr[i]; d.update(); // --- Dice collision detection and response --- // Now allow all dice (including bombs) to collide with each other for (var j = i - 1; j >= 0; j--) { var d2 = diceArr[j]; // Only check if both are not destroyed and not the same object if (d !== d2) { // Simple circle collision (use half width as radius) var dx = d.x - d2.x; var dy = d.y - d2.y; var dist = Math.sqrt(dx * dx + dy * dy); var minDist = (d.width + d2.width) / 2 * 0.85; // 0.85 fudge for overlap if (dist < minDist && dist > 0) { // Push them apart equally var overlap = (minDist - dist) / 2; var nx = dx / dist; var ny = dy / dist; d.x += nx * overlap; d.y += ny * overlap; d2.x -= nx * overlap; d2.y -= ny * overlap; // Exchange a bit of fallSpeed (simulate bounce) var temp = d.fallSpeed; d.fallSpeed = d2.fallSpeed; d2.fallSpeed = temp; } } } if (d.y > 2732 + 120) { // Missed dice, just remove d.destroy(); diceArr.splice(i, 1); } } }; // On game over, clear timers game.on('destroy', function () { LK.clearInterval(timerInterval); // Remove all dice for (var i = diceArr.length - 1; i >= 0; i--) { diceArr[i].destroy(); } diceArr = []; });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BombDice class
var BombDice = Container.expand(function () {
var self = Container.call(this);
self.isBomb = true;
// Attach bomb asset, anchor center
self.bombAsset = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
// Add bomb label
self.label = new Text2('', {
size: 100,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.label.x = 0;
self.label.y = 0;
self.addChild(self.label);
// Called every tick
self.update = function () {
self.y += self.fallSpeed;
};
// On tap
self.down = function (x, y, obj) {
// Score: subtract 5 (minimum 0)
var newScore = LK.getScore() - 5;
if (newScore < 0) {
newScore = 0;
}
LK.setScore(newScore);
LK.getSound('bombTap').play();
// Set penalty state for 90 ticks (~1.5s at 60fps)
isPenalty = true;
penaltyTicksLeft = 90;
// penaltyTxt.visible = true; // Do not show penalty text
// Subtract 2 seconds from timer (minimum 0)
timeLeft -= 2;
if (timeLeft < 0) {
timeLeft = 0;
}
timerTxt.setText(timeLeft);
// Animate: shake and fade out
tween(self, {
x: self.x + 30
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
x: self.x - 60
}, {
duration: 80,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self, {
x: self.x + 30,
alpha: 0
}, {
duration: 100,
onFinish: function onFinish() {
self.destroy();
LK.effects.flashScreen(0xffffff, 120); // Quick white flash after explosion
// Manual screen shake using tween plugin
var originalX = game.x || 0;
var originalY = game.y || 0;
var shakeDuration = 400;
var shakeAmplitude = 16;
var shakeTicks = Math.floor(shakeDuration / 16); // ~60fps
var shakeCount = 0;
function doShake() {
if (shakeCount >= shakeTicks) {
game.x = originalX;
game.y = originalY;
return;
}
// Random offset
game.x = originalX + (Math.random() - 0.5) * shakeAmplitude;
game.y = originalY + (Math.random() - 0.5) * shakeAmplitude;
shakeCount++;
LK.setTimeout(doShake, 16);
}
doShake();
}
});
}
});
}
});
// Remove from dice array in main game
removeDice(self);
};
return self;
});
// Dice class (for dice 1-6)
var Dice = Container.expand(function () {
var self = Container.call(this);
// Dice value (1-6)
self.value = 1;
self.isBomb = false;
// Attach dice asset, anchor center
self.diceAsset = null;
// Dice label (number)
self.label = null;
// Set up dice
self.setup = function (value) {
self.value = value;
self.isBomb = false;
var assetId = 'dice' + value;
self.diceAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Add number label
self.label = new Text2('' + value, {
size: 28,
fill: 0x222222
});
self.label.anchor.set(0.5, 0.5);
self.label.x = 0;
self.label.y = 0;
self.addChild(self.label);
};
// Called every tick
self.update = function () {
self.y += self.fallSpeed;
};
// On tap
self.down = function (x, y, obj) {
if (self.isBomb) {
return;
} // Should not happen, but safety
// Score: add dice value (1-6)
LK.setScore(LK.getScore() + self.value);
LK.getSound('diceTap').play();
// If this is the 6th die, add +2 seconds to timer (max 999 for safety)
if (self.value === 6) {
timeLeft += 2;
if (timeLeft > 999) {
timeLeft = 999;
}
timerTxt.setText(timeLeft);
}
// Animate: scale up and fade out
tween(self, {
scaleX: 1.4,
scaleY: 1.4,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
// Remove from dice array in main game
removeDice(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Add background photo image
var bgPhoto = LK.getAsset('backgroundPhoto', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2048 / 1500,
scaleY: 2732 / 4000
});
game.addChild(bgPhoto);
// Play background music
// Dice assets: 6 dice (1-6), all box shapes, different colors
// Bomb dice: ellipse, red
// Sounds
// Music (background, desert style)
LK.playMusic('desertbg');
// Dice array (all falling dice)
var diceArr = [];
// Remove dice helper
function removeDice(diceObj) {
for (var i = diceArr.length - 1; i >= 0; i--) {
if (diceArr[i] === diceObj) {
diceArr.splice(i, 1);
break;
}
}
}
// Score label
var scoreLabelTxt = new Text2('Score', {
size: 60,
fill: 0x7C4A03
});
scoreLabelTxt.anchor.set(0.5, 0);
scoreLabelTxt.x = 0;
scoreLabelTxt.y = 0;
LK.gui.top.addChild(scoreLabelTxt);
// Score value
var scoreTxt = new Text2('0', {
size: 120,
fill: 0x7C4A03
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.x = 0;
scoreTxt.y = 60;
LK.gui.top.addChild(scoreTxt);
// Timer label
var timerLabelTxt = new Text2('Time Left', {
size: 60,
fill: 0x7C4A03
});
timerLabelTxt.anchor.set(0.5, 0);
timerLabelTxt.x = 0;
timerLabelTxt.y = 200;
LK.gui.top.addChild(timerLabelTxt);
// Timer value
var timerTxt = new Text2('60', {
size: 90,
fill: 0x7C4A03
});
timerTxt.anchor.set(0.5, 0);
timerTxt.x = 0;
timerTxt.y = 260;
LK.gui.top.addChild(timerTxt);
// Penalty indicator (hidden and never shown)
var penaltyTxt = new Text2('PENALTY!', {
size: 90,
fill: 0xff2222
});
penaltyTxt.anchor.set(0.5, 0);
penaltyTxt.visible = false;
penaltyTxt.x = 0;
penaltyTxt.y = 140;
// Do NOT add penaltyTxt to LK.gui.top, so it never appears
// Penalty state
var isPenalty = false;
var penaltyTicksLeft = 0;
// Score bonus state removed
// Game timer
var timeLeft = 60; // seconds
var timerInterval = LK.setInterval(function () {
timeLeft--;
if (timeLeft < 0) {
timeLeft = 0;
}
timerTxt.setText(timeLeft);
if (timeLeft === 0) {
// Play timer end sound effect
LK.getSound('timerEnd').play();
// End game
LK.showGameOver();
}
}, 1000);
// Dice spawn timer
var spawnTick = 0;
var spawnInterval = 36; // ~0.6s at 60fps, will randomize
// Helper: spawn a dice (random type)
function spawnDice() {
// Random X position (avoid edges)
var margin = 120;
var x = margin + Math.random() * (2048 - 2 * margin);
var y = -100;
// Bomb chance: 1 in 8
var isBomb = Math.random() < 0.125;
var diceObj;
if (isBomb) {
diceObj = new BombDice();
} else {
// Weighted random dice value: 2,3,4,5 are most likely, 1 and 6 are less likely
// Probabilities: 1 (10%), 2 (20%), 3 (20%), 4 (20%), 5 (20%), 6 (10%)
// Prevent 6 from spawning in first 10 seconds
var r = Math.random();
var value;
var elapsedTime = 60 - timeLeft;
if (elapsedTime < 10) {
// Only allow 1-5, reweight probabilities
// 1 (11.1%), 2 (22.2%), 3 (22.2%), 4 (22.2%), 5 (22.2%)
if (r < 0.111) {
value = 1;
} else if (r < 0.333) {
value = 2;
} else if (r < 0.555) {
value = 3;
} else if (r < 0.777) {
value = 4;
} else {
value = 5;
}
} else {
if (r < 0.10) {
value = 1;
} else if (r < 0.30) {
value = 2;
} else if (r < 0.50) {
value = 3;
} else if (r < 0.70) {
value = 4;
} else if (r < 0.90) {
value = 5;
} else {
value = 6;
}
}
diceObj = new Dice();
diceObj.setup(value);
}
diceObj.x = x;
diceObj.y = y;
diceObj.scaleX = 1;
diceObj.scaleY = 1;
diceObj.alpha = 1;
// Set fall speed: dice value 1 falls slowest, 6 fastest
if (!isBomb) {
// Value 1: slowest (6 px/frame), Value 6: fastest (12 px/frame)
diceObj.fallSpeed = 6 + (diceObj.value - 1) * (6 / 5);
} else {
// Bombs: fall speed matches die value 6 (fastest)
diceObj.fallSpeed = 12;
}
// Add to game
game.addChild(diceObj);
diceArr.push(diceObj);
}
// Touch/click handler
game.down = function (x, y, obj) {
// Check if any dice is tapped (from topmost to bottom)
for (var i = diceArr.length - 1; i >= 0; i--) {
var d = diceArr[i];
// Convert global to local for dice
var local = d.toLocal(game.toGlobal({
x: x,
y: y
}));
// Check if inside dice bounds (centered)
if (Math.abs(local.x) <= d.width / 2 && Math.abs(local.y) <= d.height / 2) {
// Call dice down handler
d.down(x, y, obj);
return;
}
}
};
// Main update loop
game.update = function () {
// Play sound every 100 points milestone
if (typeof lastScoreSound === "undefined") {
lastScoreSound = 0;
}
var currentScore = LK.getScore();
if (currentScore >= 100 && Math.floor(currentScore / 100) > Math.floor(lastScoreSound / 100)) {
LK.getSound('milestone100').play();
lastScoreSound = currentScore;
}
// Penalty effect: apply penalty during penaltyTicksLeft
if (isPenalty) {
penaltyTicksLeft--;
// During penalty, subtract 1 point every 15 ticks (~0.25s)
if (penaltyTicksLeft % 15 === 0 && LK.getScore() > 0) {
LK.setScore(Math.max(0, LK.getScore() - 1));
}
if (penaltyTicksLeft <= 0) {
isPenalty = false;
// penaltyTxt.visible = false;//{29} // Already hidden, do nothing
}
}
// Score bonus effect removed
// Update score text
scoreTxt.setText(LK.getScore());
// Spawn dice at intervals (randomize interval for variety)
spawnTick++;
if (spawnTick >= spawnInterval) {
// Determine how many dice to spawn based on elapsed time
// Start with 1, add 1 every 20 seconds (max 6 at 100+ seconds)
var elapsed = 60 - timeLeft;
var diceToSpawn = 1 + Math.floor(elapsed / 20);
if (diceToSpawn > 6) diceToSpawn = 6;
for (var i = 0; i < diceToSpawn; i++) {
spawnDice();
}
spawnTick = 0;
// Next interval: 24-48 ticks (~0.4-0.8s)
spawnInterval = 24 + Math.floor(Math.random() * 25);
}
// Update all dice, remove if off screen
for (var i = diceArr.length - 1; i >= 0; i--) {
var d = diceArr[i];
d.update();
// --- Dice collision detection and response ---
// Now allow all dice (including bombs) to collide with each other
for (var j = i - 1; j >= 0; j--) {
var d2 = diceArr[j];
// Only check if both are not destroyed and not the same object
if (d !== d2) {
// Simple circle collision (use half width as radius)
var dx = d.x - d2.x;
var dy = d.y - d2.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = (d.width + d2.width) / 2 * 0.85; // 0.85 fudge for overlap
if (dist < minDist && dist > 0) {
// Push them apart equally
var overlap = (minDist - dist) / 2;
var nx = dx / dist;
var ny = dy / dist;
d.x += nx * overlap;
d.y += ny * overlap;
d2.x -= nx * overlap;
d2.y -= ny * overlap;
// Exchange a bit of fallSpeed (simulate bounce)
var temp = d.fallSpeed;
d.fallSpeed = d2.fallSpeed;
d2.fallSpeed = temp;
}
}
}
if (d.y > 2732 + 120) {
// Missed dice, just remove
d.destroy();
diceArr.splice(i, 1);
}
}
};
// On game over, clear timers
game.on('destroy', function () {
LK.clearInterval(timerInterval);
// Remove all dice
for (var i = diceArr.length - 1; i >= 0; i--) {
diceArr[i].destroy();
}
diceArr = [];
});