/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
balance: 1000
});
/****
* Classes
****/
// Button class
var GameButton = Container.expand(function () {
var self = Container.call(this);
self.bg = null;
self.label = null;
self.setAsset = function (assetId, labelText, color) {
if (self.bg) self.bg.destroy();
self.bg = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
if (color !== undefined) self.bg.tint = color;
if (self.label) self.label.destroy();
self.label = new Text2(labelText, {
size: 60,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
};
self.setText = function (txt) {
if (self.label) self.label.setText(txt);
};
return self;
});
// GraphBackground class: draws the multiplier vs time graph as a grid and curve
var GraphBackground = Container.expand(function () {
var self = Container.call(this);
// Graph area (leave margin for axes)
self.graphX = 180;
self.graphY = 420;
self.graphW = 2048 - 360;
self.graphH = 900;
// For curve drawing
self.maxTicks = 600; // 10s at 60fps
self.maxMultiplier = 20;
// Draw grid lines and axes
self.drawGrid = function () {
// Remove previous children
while (self.children.length) self.children[0].destroy();
// Draw horizontal grid lines (multiplier)
for (var i = 1; i <= 5; i++) {
var y = self.graphY + self.graphH - i * self.graphH / 5;
var line = LK.getAsset('betBtn', {
anchorX: 0,
anchorY: 0,
x: self.graphX,
y: y
});
line.width = self.graphW;
line.height = 4;
line.tint = 0x2c3e50;
line.alpha = 0.18;
self.addChild(line);
// Multiplier label
var mult = (i * self.maxMultiplier / 5).toFixed(0) + "x";
var label = new Text2(mult, {
size: 38,
fill: "#fff",
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
label.anchor.set(1, 0.5);
label.x = self.graphX - 18;
label.y = y + 2;
label.alpha = 0.7;
self.addChild(label);
}
// Draw vertical grid lines (time)
for (var j = 0; j <= 6; j++) {
var x = self.graphX + j * self.graphW / 6;
var vline = LK.getAsset('betBtn', {
anchorX: 0,
anchorY: 0,
x: x,
y: self.graphY
});
vline.width = 4;
vline.height = self.graphH;
vline.tint = 0x2c3e50;
vline.alpha = 0.18;
self.addChild(vline);
// Time label
var t = (j * self.maxTicks / 60 / 6).toFixed(1) + "s";
var tlabel = new Text2(t, {
size: 34,
fill: "#fff",
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
tlabel.anchor.set(0.5, 0);
tlabel.x = x;
tlabel.y = self.graphY + self.graphH + 8;
tlabel.alpha = 0.7;
self.addChild(tlabel);
}
};
// Draw the curve for the current round
self.drawCurve = function (ticksElapsed, crashed) {
// Remove previous curve if any
if (self.curveNodes) {
for (var i = 0; i < self.curveNodes.length; i++) {
self.curveNodes[i].destroy();
}
}
self.curveNodes = [];
// Draw curve as small dots for each tick
var lastX = null,
lastY = null;
var maxDrawTicks = crashed ? ticksElapsed : Math.min(ticksElapsed, self.maxTicks);
for (var t = 0; t <= maxDrawTicks; t += 2) {
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, t * 2)) / 100);
if (mult > self.maxMultiplier) break;
var px = self.graphX + t / self.maxTicks * self.graphW;
var py = self.graphY + self.graphH - mult / self.maxMultiplier * self.graphH;
// Draw dot
var dot = LK.getAsset('chip', {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py
});
dot.width = 18;
dot.height = 18;
dot.tint = crashed && t === maxDrawTicks ? 0xff3333 : 0x3a8ee6;
dot.alpha = crashed && t === maxDrawTicks ? 1 : 0.7;
self.addChild(dot);
self.curveNodes.push(dot);
// Optionally, connect with a line (simulate with thin box)
if (lastX !== null && lastY !== null) {
var dx = px - lastX;
var dy = py - lastY;
var dist = Math.sqrt(dx * dx + dy * dy);
var line = LK.getAsset('betBtn', {
anchorX: 0,
anchorY: 0.5,
x: lastX,
y: lastY
});
line.width = dist;
line.height = 8;
line.tint = 0x3a8ee6;
line.alpha = 0.5;
// Angle
line.rotation = Math.atan2(dy, dx);
self.addChild(line);
self.curveNodes.push(line);
}
lastX = px;
lastY = py;
}
};
self.drawGrid();
return self;
});
// Default balance
// Plane class
var Plane = Container.expand(function () {
var self = Container.call(this);
var planeGfx = self.attachAsset('plane', {
anchorX: 0.5,
anchorY: 0.5
});
self.crashed = false;
self.setCrashed = function () {
self.crashed = true;
// Show crash effect
var crash = LK.getAsset('crash', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
self.addChild(crash);
tween(crash, {
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
crash.destroy();
}
});
// Fade out plane
tween(planeGfx, {
alpha: 0
}, {
duration: 700
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a2233
});
/****
* Game Code
****/
// New sounds added to the game
// Plane explosion sound effect
// Hype/energetic soundtrack for flight
// --- Game State Variables ---
// Plane (the flying object)
// Bet button
// Cashout button
// Crash explosion
// Virtual chip
// --- Game State Variables ---
// Reset all balances to 1000 on page refresh
var balance = 1000;
storage.balance = 1000;
var betAmount = 100;
var minBet = 10;
var maxBet = 1000;
var betStep = 10;
var inRound = false;
var hasBet = false;
var hasCashedOut = false;
var roundMultiplier = 1.00;
var roundStartTick = 0;
var crashTick = 0;
var cashoutMultiplier = 0;
var plane = null;
var crashHappened = false;
var lastTick = 0;
var betBtn = null;
var cashoutBtn = null;
var betAmountTxt = null;
var balanceTxt = null;
var multiplierTxt = null;
var infoTxt = null;
var chipIcon = null;
// --- Player and Bot Colors ---
var playerColor = 0xFFE066; // gold/yellow for player
// Bot color codes: Jack (blue), Arthur (pink), John (orange)
var botColors = [0x3a8ee6, 0xff69b4, 0xffa500]; // blue, pink, orange
var botNameColors = [0x3a8ee6, 0xff69b4, 0xffa500]; // name text color matches dot color
// --- Bot State ---
var bots = [{
name: "Jack",
balance: 1000,
betAmount: 0,
hasBet: false,
hasCashedOut: false,
cashoutMultiplier: 0,
displayTxt: null,
chipIcon: null,
win: 0,
color: botColors[0],
nameColor: botNameColors[0],
dotColor: botColors[0]
}, {
name: "Arthur",
balance: 1000,
betAmount: 0,
hasBet: false,
hasCashedOut: false,
cashoutMultiplier: 0,
displayTxt: null,
chipIcon: null,
win: 0,
color: botColors[1],
nameColor: botNameColors[1],
dotColor: botColors[1]
}, {
name: "John",
balance: 1000,
betAmount: 0,
hasBet: false,
hasCashedOut: false,
cashoutMultiplier: 0,
displayTxt: null,
chipIcon: null,
win: 0,
color: botColors[2],
nameColor: botNameColors[2],
dotColor: botColors[2]
}];
// Set win target for all to 2000
var botWinTarget = 2000;
var botWinner = null;
// --- GUI Elements ---
// --- Graph Background ---
var graphBg = new GraphBackground();
game.addChild(graphBg);
// --- GUI Elements ---
multiplierTxt = new Text2("1.00x", {
size: 180,
fill: 0xFFE066,
stroke: "#222",
strokeThickness: 10,
dropShadow: true,
dropShadowColor: "#000",
dropShadowDistance: 8,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 8,
fontWeight: "bold"
});
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.y = 40;
LK.gui.top.addChild(multiplierTxt);
// Info text (centered, below multiplier, with shadow and spacing)
infoTxt = new Text2("Place your bet!", {
size: 70,
fill: "#fff",
stroke: "#222",
strokeThickness: 6,
dropShadow: true,
dropShadowColor: "#000",
dropShadowDistance: 4,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 6,
fontWeight: "bold"
});
infoTxt.anchor.set(0.5, 0);
infoTxt.y = multiplierTxt.y + multiplierTxt.height + 20;
LK.gui.top.addChild(infoTxt);
balanceTxt = new Text2("Balance: $" + balance, {
size: 60,
fill: playerColor,
stroke: "#222",
strokeThickness: 5,
fontWeight: "bold"
});
balanceTxt.anchor.set(0.5, 1);
// Bet amount display (larger, bolder)
betAmountTxt = new Text2("Bet: $" + betAmount, {
size: 90,
fill: "#fff",
stroke: "#222",
strokeThickness: 6,
fontWeight: "bold"
});
betAmountTxt.anchor.set(1, 0.5);
betAmountTxt.x = 2048 - 60;
LK.gui.topRight.addChild(betAmountTxt);
// --- Bet Button ---
betBtn = new GameButton();
betBtn.setAsset('betBtn', "BET", 0x2ecc40);
betBtn.bg.width = 520;
betBtn.bg.height = 180;
betBtn.bg.radius = 40;
betBtn.bg.dropShadow = true;
betBtn.bg.dropShadowColor = 0x222222;
betBtn.bg.dropShadowDistance = 8;
betBtn.bg.dropShadowAngle = Math.PI / 2;
betBtn.bg.dropShadowBlur = 8;
// Centered horizontally, near bottom
betBtn.x = 2048 / 2;
betBtn.y = 2732 - 180;
betBtn.anchorX = 0.5;
betBtn.anchorY = 0.5;
game.addChild(betBtn);
// --- Balance text and chip icon above bet button (centered) ---
// Center balance text horizontally above bet button
balanceTxt.anchor.set(0.5, 1);
balanceTxt.x = betBtn.x;
balanceTxt.y = betBtn.y - betBtn.bg.height / 2 - 30;
// --- Player win/lose indicator text (above balance) ---
var playerWinFloatTxt = null;
// Center balance text horizontally above bet button
game.addChild(balanceTxt);
// --- Add bot balance and chip display above bet button ---
var botDisplayYOffset = 60;
for (var i = 0; i < bots.length; i++) {
// Balance text for bot
var botTxt = new Text2(bots[i].name + ": $" + bots[i].balance, {
size: 44,
fill: bots[i].nameColor,
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
botTxt.anchor.set(0.5, 1);
botTxt.x = betBtn.x - 400 + i * 400;
botTxt.y = balanceTxt.y - 120;
bots[i].displayTxt = botTxt;
game.addChild(botTxt);
}
// Update bet button text to "BET 100"
betBtn.setText("BET " + betAmount);
// --- Cashout Button ---
cashoutBtn = new GameButton();
cashoutBtn.setAsset('cashoutBtn', "CASHOUT", 0xe67e22);
cashoutBtn.bg.width = 520;
cashoutBtn.bg.height = 180;
cashoutBtn.bg.radius = 40;
cashoutBtn.bg.dropShadow = true;
cashoutBtn.bg.dropShadowColor = 0x222222;
cashoutBtn.bg.dropShadowDistance = 8;
cashoutBtn.bg.dropShadowAngle = Math.PI / 2;
cashoutBtn.bg.dropShadowBlur = 8;
// Centered horizontally, same Y as betBtn
cashoutBtn.x = 2048 / 2;
cashoutBtn.y = 2732 - 180;
cashoutBtn.anchorX = 0.5;
cashoutBtn.anchorY = 0.5;
game.addChild(cashoutBtn);
cashoutBtn.visible = false;
// --- Bet Amount Adjust (plus/minus buttons) ---
var betMinusBtn = new GameButton();
betMinusBtn.setAsset('betBtn', "-", 0x16a085);
betMinusBtn.bg.width = 120;
betMinusBtn.bg.height = 120;
betMinusBtn.bg.radius = 60;
betMinusBtn.x = 2048 - 420;
betMinusBtn.y = 180;
LK.gui.topRight.addChild(betMinusBtn);
var betPlusBtn = new GameButton();
betPlusBtn.setAsset('betBtn', "+", 0x16a085);
betPlusBtn.bg.width = 120;
betPlusBtn.bg.height = 120;
betPlusBtn.bg.radius = 60;
betPlusBtn.x = 2048 - 180;
betPlusBtn.y = 180;
LK.gui.topRight.addChild(betPlusBtn);
betMinusBtn.down = function (x, y, obj) {
if (inRound) return;
betAmount -= betStep;
if (betAmount < minBet) betAmount = maxBet;
betAmountTxt.setText("Bet: $" + betAmount);
};
betPlusBtn.down = function (x, y, obj) {
if (inRound) return;
betAmount += betStep;
if (betAmount > maxBet) betAmount = minBet;
betAmountTxt.setText("Bet: $" + betAmount);
};
// --- Bet Button Handler ---
betBtn.down = function (x, y, obj) {
if (inRound || hasBet) return;
if (balance < betAmount) {
infoTxt.setText("Not enough balance!");
return;
}
hasBet = true;
infoTxt.setText("Waiting for takeoff...");
betBtn.setText("WAIT");
betBtn.bg.tint = 0xaaaaaa;
// Deduct bet immediately
balance -= betAmount;
storage.balance = balance;
balanceTxt.setText("Balance: $" + balance);
if (typeof balanceTxt.setFill === "function") balanceTxt.setFill(playerColor);
// Start round after short delay
LK.setTimeout(startRound, 900);
};
// --- Cashout Button Handler ---
cashoutBtn.down = function (x, y, obj) {
if (!inRound || !hasBet || hasCashedOut || crashHappened) return;
hasCashedOut = true;
cashoutMultiplier = roundMultiplier;
var win = Math.floor(betAmount * cashoutMultiplier);
balance += win;
storage.balance = balance;
balanceTxt.setText("Balance: $" + balance);
if (typeof balanceTxt.setFill === "function") balanceTxt.setFill(playerColor);
infoTxt.setText("You cashed out: $" + win + " (" + cashoutMultiplier.toFixed(2) + "x)");
// Show floating win text above balance
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
playerWinFloatTxt = new Text2("+" + win + " chips", {
size: 48,
fill: 0x3FDC5A,
stroke: "#222",
strokeThickness: 5,
fontWeight: "bold"
});
playerWinFloatTxt.anchor.set(0.5, 1);
playerWinFloatTxt.x = balanceTxt.x;
playerWinFloatTxt.y = balanceTxt.y - 60;
game.addChild(playerWinFloatTxt);
tween(playerWinFloatTxt, {
y: playerWinFloatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
}
});
// Show color-coded dot on graph at cashout point
if (typeof graphBg !== "undefined") {
var cashoutTick = LK.ticks - roundStartTick;
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, cashoutTick * 2)) / 100);
if (mult > graphBg.maxMultiplier) mult = graphBg.maxMultiplier;
var px = graphBg.graphX + cashoutTick / graphBg.maxTicks * graphBg.graphW;
var py = graphBg.graphY + graphBg.graphH - mult / graphBg.maxMultiplier * graphBg.graphH;
var dot = LK.getAsset('chip', {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py
});
dot.width = 32;
dot.height = 32;
dot.tint = playerColor;
dot.alpha = 1;
game.addChild(dot);
// Track for removal next round
if (!game.cashoutDots) game.cashoutDots = [];
game.cashoutDots.push(dot);
// Animate dot pop
dot.scaleX = dot.scaleY = 0.5;
tween(dot, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
// Optionally fade out after a while
LK.setTimeout(function () {
dot.alpha = 0.7;
}, 1200);
}
cashoutBtn.visible = false;
betBtn.visible = false;
// Animate plane off screen
if (plane) {
tween(plane, {
y: -200
}, {
duration: 800,
easing: tween.cubicOut,
onFinish: function onFinish() {
endRound();
}
});
} else {
endRound();
}
};
// --- Start Round ---
function startRound() {
inRound = true;
crashHappened = false;
hasCashedOut = false;
cashoutMultiplier = 0;
roundMultiplier = 1.00;
roundStartTick = LK.ticks;
// Remove all previous player/bot cashout dots
if (typeof game.cashoutDots !== "undefined" && game.cashoutDots && game.cashoutDots.length) {
for (var i = 0; i < game.cashoutDots.length; i++) {
if (game.cashoutDots[i] && typeof game.cashoutDots[i].destroy === "function") {
game.cashoutDots[i].destroy();
}
}
game.cashoutDots = [];
} else {
game.cashoutDots = [];
}
// Remove player win/lose indicator if present
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
// Random crash time: between 2s and 8s (120-480 ticks)
crashTick = roundStartTick + 120 + Math.floor(Math.random() * 360);
// Place plane at bottom center
if (plane) plane.destroy();
plane = new Plane();
plane.x = 2048 / 2;
plane.y = 2732 - 600;
game.addChild(plane);
// Animate plane takeoff
tween(plane, {
y: 2732 / 2
}, {
duration: 1200,
easing: tween.cubicOut
});
// Start hype soundtrack
LK.playMusic('hype_flight', {
fade: {
start: 0,
end: 1,
duration: 600
}
});
// UI
betBtn.visible = false;
cashoutBtn.visible = true;
cashoutBtn.setText("CASHOUT");
cashoutBtn.bg.tint = 0xe67e22;
infoTxt.setText("Flying... Tap CASHOUT to win!");
multiplierTxt.setText("1.00x");
}
// --- End Round ---
function endRound() {
inRound = false;
hasBet = false;
hasCashedOut = false;
betBtn.visible = true;
betBtn.setText("BET");
betBtn.bg.tint = 0x2ecc40;
cashoutBtn.visible = false;
if (plane) {
plane.destroy();
plane = null;
}
// If player lost, show info
if (crashHappened && !cashoutMultiplier) {
infoTxt.setText("Crashed! You lost $" + betAmount);
} else if (!crashHappened && cashoutMultiplier) {
infoTxt.setText("You won $" + Math.floor(betAmount * cashoutMultiplier) + "!");
} else {
infoTxt.setText("Place your bet!");
}
// Reset multiplier display
multiplierTxt.setText("1.00x");
// Update bet amount display
betAmountTxt.setText("Bet: $" + betAmount);
}
// --- Crash Handler ---
function handleCrash() {
crashHappened = true;
// Stop hype soundtrack and play explosion sound
LK.stopMusic();
LK.getSound('plane_explode').play();
if (plane) plane.setCrashed();
cashoutBtn.visible = false;
betBtn.visible = false;
if (!hasCashedOut) {
cashoutMultiplier = 0;
// Show crash info
infoTxt.setText("Crashed! You lost $" + betAmount);
// Show floating lose text above balance
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
playerWinFloatTxt = new Text2("-" + betAmount + " chips", {
size: 48,
fill: 0xFF3333,
stroke: "#222",
strokeThickness: 5,
fontWeight: "bold"
});
playerWinFloatTxt.anchor.set(0.5, 1);
playerWinFloatTxt.x = balanceTxt.x;
playerWinFloatTxt.y = balanceTxt.y - 60;
game.addChild(playerWinFloatTxt);
tween(playerWinFloatTxt, {
y: playerWinFloatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
}
});
// End round after short delay
LK.setTimeout(endRound, 1200);
} else {
// Already cashed out, just end round
LK.setTimeout(endRound, 800);
}
}
// --- Main Game Update ---
game.update = function () {
if (!inRound) {
// Reset bot round state if not in round
for (var b = 0; b < bots.length; b++) {
bots[b].hasBet = false;
bots[b].hasCashedOut = false;
bots[b].betAmount = 0;
bots[b].cashoutMultiplier = 0;
bots[b].win = 0;
}
return;
}
// Calculate multiplier: Exponential growth, e.g. 1.00x to ~20x in 8s
var ticksElapsed = LK.ticks - roundStartTick;
// Multiplier formula: 1.002^(ticksElapsed*2) for smooth curve
roundMultiplier = Math.max(1, Math.floor(100 * Math.pow(1.002, ticksElapsed * 2)) / 100);
multiplierTxt.setText(roundMultiplier.toFixed(2) + "x");
// --- Bot logic: Place bets at start of round ---
for (var b = 0; b < bots.length; b++) {
var bot = bots[b];
// Place bet if not already bet and round just started
if (!bot.hasBet && ticksElapsed < 10) {
// All bots always bet 100 chips if enough balance
var botBet = 100;
if (bot.balance >= botBet) {
bot.betAmount = botBet;
bot.balance -= botBet;
bot.hasBet = true;
bot.hasCashedOut = false;
bot.cashoutMultiplier = 0;
bot.win = 0;
// Reset win/lose status color and text
if (bot.displayTxt) {
bot.displayTxt.setText(bot.name + ": $" + bot.balance);
if (typeof bot.displayTxt.setFill === "function") {
bot.displayTxt.setFill("#fff");
}
}
// Remove any win/lose floating text if present
if (bot.winFloatTxt) {
bot.winFloatTxt.destroy();
bot.winFloatTxt = null;
}
}
}
}
// --- Bot logic: Cash out at random multiplier ---
for (var b = 0; b < bots.length; b++) {
var bot = bots[b];
if (bot.hasBet && !bot.hasCashedOut && !crashHappened) {
// Each bot picks a random cashout multiplier at bet time, with 75% winrate hack
if (!bot.targetCashout) {
// Calculate the crash multiplier for this round
var crashTicks = crashTick - roundStartTick;
var crashMult = Math.max(1, Math.floor(100 * Math.pow(1.002, crashTicks * 2)) / 100);
// 75% chance to win (cashout before crash), 25% to lose (cashout after crash)
var willWin = Math.random() < 0.75;
if (willWin) {
// Pick a random cashout between 1.5x and just before crash multiplier (with margin)
var minMult = 1.5;
var maxMult = Math.max(minMult + 0.1, crashMult - 0.2); // leave margin to avoid edge
if (maxMult < minMult) maxMult = minMult + 0.1;
bot.targetCashout = minMult + Math.random() * (maxMult - minMult);
// Clamp to max 5x for realism
if (bot.targetCashout > 5) bot.targetCashout = 1.5 + Math.random() * 3.5;
} else {
// Pick a random cashout after crash (guaranteed lose)
var minLose = crashMult + 0.1;
var maxLose = Math.max(minLose + 0.1, crashMult + 2.0);
bot.targetCashout = minLose + Math.random() * (maxLose - minLose);
// Clamp to max 8x
if (bot.targetCashout > 8) bot.targetCashout = 6 + Math.random() * 2;
}
}
if (roundMultiplier >= bot.targetCashout) {
// Cash out!
bot.hasCashedOut = true;
bot.cashoutMultiplier = roundMultiplier;
bot.win = Math.floor(bot.betAmount * bot.cashoutMultiplier) - bot.betAmount;
bot.balance += bot.betAmount + bot.win;
// Show color-coded dot on graph at cashout point
if (typeof graphBg !== "undefined") {
var cashoutTick = ticksElapsed;
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, cashoutTick * 2)) / 100);
if (mult > graphBg.maxMultiplier) mult = graphBg.maxMultiplier;
var px = graphBg.graphX + cashoutTick / graphBg.maxTicks * graphBg.graphW;
var py = graphBg.graphY + graphBg.graphH - mult / graphBg.maxMultiplier * graphBg.graphH;
var dot = LK.getAsset('chip', {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py
});
dot.width = 32;
dot.height = 32;
dot.tint = bot.dotColor;
dot.alpha = 1;
game.addChild(dot);
// Track for removal next round
if (!game.cashoutDots) game.cashoutDots = [];
game.cashoutDots.push(dot);
// Animate dot pop
dot.scaleX = dot.scaleY = 0.5;
tween(dot, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
// Optionally fade out after a while
LK.setTimeout(function () {
dot.alpha = 0.7;
}, 1200);
}
// Show win status: green text, floating "+chips"
if (bot.displayTxt) {
bot.displayTxt.setText(bot.name + ": $" + bot.balance);
if (typeof bot.displayTxt.setFill === "function") {
bot.displayTxt.setFill("#3fdc5a");
}
// Show floating "+chips" above bot balance
if (bot.win > 0) {
var floatTxt = new Text2("+" + bot.win + " chips", {
size: 38,
fill: 0x3FDC5A,
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
floatTxt.anchor.set(0.5, 1);
floatTxt.x = bot.displayTxt.x;
floatTxt.y = bot.displayTxt.y - 60;
game.addChild(floatTxt);
bot.winFloatTxt = floatTxt;
// Animate float up and fade out
tween(floatTxt, {
y: floatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
floatTxt.destroy();
}
});
}
}
}
}
// If crash happened and bot didn't cash out, reset targetCashout
if (crashHappened && !bot.hasCashedOut) {
bot.targetCashout = null;
}
}
// --- Bot logic: Handle crash (lose bet if not cashed out) ---
if (crashHappened) {
for (var b = 0; b < bots.length; b++) {
var bot = bots[b];
if (bot.hasBet && !bot.hasCashedOut) {
// Lost bet, nothing to add
bot.betAmount = 0;
bot.cashoutMultiplier = 0;
bot.targetCashout = null;
// Show lose status: red text and floating lose text
if (bot.displayTxt) {
bot.displayTxt.setText(bot.name + ": $" + bot.balance);
if (typeof bot.displayTxt.setFill === "function") {
bot.displayTxt.setFill("#ff3333");
}
// Show floating "-100 chips" above bot balance
var floatTxt = new Text2("-100 chips", {
size: 38,
fill: 0xFF3333,
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
floatTxt.anchor.set(0.5, 1);
floatTxt.x = bot.displayTxt.x;
floatTxt.y = bot.displayTxt.y - 60;
game.addChild(floatTxt);
bot.winFloatTxt = floatTxt;
// Animate float up and fade out
tween(floatTxt, {
y: floatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
floatTxt.destroy();
}
});
}
}
}
}
// --- Eliminate player if balance is 0 ---
if (balance <= 0 && !botWinner) {
infoTxt.setText("You are eliminated!");
betBtn.visible = false;
cashoutBtn.visible = false;
// Optionally show lose popup
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
return;
}
// --- Eliminate bots at 0 balance ---
for (var b = 0; b < bots.length; b++) {
if (bots[b].balance <= 0 && !bots[b].eliminated) {
bots[b].eliminated = true;
if (bots[b].displayTxt) {
bots[b].displayTxt.setText(bots[b].name + ": ELIMINATED");
if (typeof bots[b].displayTxt.setFill === "function") {
bots[b].displayTxt.setFill("#888");
}
}
}
}
// --- Player win detection ---
if (!botWinner) {
if (balance >= botWinTarget) {
botWinner = "You";
infoTxt.setText("You win the game!");
LK.setTimeout(function () {
LK.showYouWin();
}, 1800);
} else {
for (var b = 0; b < bots.length; b++) {
if (bots[b].balance >= botWinTarget) {
botWinner = bots[b].name;
infoTxt.setText(botWinner + " wins the game!");
LK.setTimeout(function () {
LK.showYouWin();
}, 1800);
}
}
}
}
// Animate plane moving along the graph curve and make camera follow
if (plane && !plane.crashed) {
// Calculate the graph tip position for the current tick
var graphX = graphBg.graphX;
var graphY = graphBg.graphY;
var graphW = graphBg.graphW;
var graphH = graphBg.graphH;
var maxTicks = graphBg.maxTicks;
var maxMultiplier = graphBg.maxMultiplier;
// Calculate current multiplier
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, ticksElapsed * 2)) / 100);
if (mult > maxMultiplier) mult = maxMultiplier;
// Calculate the tip position on the graph
var px = graphX + ticksElapsed / maxTicks * graphW;
var py = graphY + graphH - mult / maxMultiplier * graphH;
// Clamp px, py to graph area
if (px < graphX) px = graphX;
if (px > graphX + graphW) px = graphX + graphW;
if (py < graphY) py = graphY;
if (py > graphY + graphH) py = graphY + graphH;
// Place plane at the tip of the graph, perfectly aligned to the curve
// Adjust for plane's width/height so its nose is at the tip
if (plane.children && plane.children.length > 0) {
var planeGfx = plane.children[0];
// Assume plane is facing right, anchorX: 0.5, anchorY: 0.5
// Move plane so its front (right edge) is at (px, py)
// Offset by half width to the left
plane.x = px + planeGfx.width / 2 - planeGfx.width; // nose exactly at tip
plane.y = py;
// --- Calculate tangent angle for rotation ---
// Use two points: current tick and a small delta before (or after if at start)
var deltaT = 2;
var t0 = Math.max(0, ticksElapsed - deltaT);
var t1 = ticksElapsed;
var mult0 = Math.max(1, Math.floor(100 * Math.pow(1.002, t0 * 2)) / 100);
var mult1 = Math.max(1, Math.floor(100 * Math.pow(1.002, t1 * 2)) / 100);
if (mult0 > maxMultiplier) mult0 = maxMultiplier;
if (mult1 > maxMultiplier) mult1 = maxMultiplier;
var px0 = graphX + t0 / maxTicks * graphW;
var py0 = graphY + graphH - mult0 / maxMultiplier * graphH;
var px1 = graphX + t1 / maxTicks * graphW;
var py1 = graphY + graphH - mult1 / maxMultiplier * graphH;
var dx = px1 - px0;
var dy = py1 - py0;
// If dx,dy is zero (shouldn't happen), fallback to 0
var angle = 0;
if (dx !== 0 || dy !== 0) {
angle = Math.atan2(dy, dx);
}
// Set plane rotation to match tangent
planeGfx.rotation = angle;
} else {
plane.x = px;
plane.y = py;
}
// Camera follow: shift the graphBg and all game elements so the plane stays visible and centered horizontally if possible
// Calculate desired camera offset so plane is centered, but clamp so graph doesn't go off screen
var desiredCenterX = 2048 / 2;
var cameraOffsetX = desiredCenterX - px;
// Clamp cameraOffsetX so graphBg doesn't go off screen
var minOffsetX = 2048 - (graphX + graphW); // rightmost: graph right edge at screen right
var maxOffsetX = -graphX; // leftmost: graph left edge at screen left
if (cameraOffsetX < minOffsetX) cameraOffsetX = minOffsetX;
if (cameraOffsetX > maxOffsetX) cameraOffsetX = maxOffsetX;
// Apply camera offset to graphBg and plane (plane is already at px,py so just move graphBg)
graphBg.x = cameraOffsetX;
// If you have other game elements that should move with the camera, offset them by cameraOffsetX as well
}
// Draw/update graph curve
if (typeof graphBg !== "undefined") {
graphBg.drawCurve(ticksElapsed, crashHappened);
}
// Crash?
if (LK.ticks >= crashTick && !crashHappened) {
handleCrash();
}
};
// --- Reset on Game Over (handled by LK) ---
// --- Touchscreen: Prevent elements in top left 100x100
// --- Rainbow punchscale text at bottom center ---
var winRaceTxt = new Text2("(The first to reach 2000 chips wins)", {
size: 70,
fill: 0xFF0000,
stroke: "#222",
strokeThickness: 6,
fontWeight: "bold"
});
winRaceTxt.anchor.set(0.5, 0);
// Position just below the bet button, with a small margin
winRaceTxt.x = betBtn.x;
winRaceTxt.y = betBtn.y + betBtn.bg.height / 2 + 30;
winRaceTxt.scaleX = winRaceTxt.scaleY = 0.7;
game.addChild(winRaceTxt);
// Animate rainbow color and punchscale
var rainbowHue = 0;
var punchScaleDir = 1;
var punchScale = 1;
game.updateWinRaceTxt = function () {
// Rainbow color
rainbowHue += 2;
if (rainbowHue > 360) rainbowHue = 0;
// Convert hue to rgb
var h = rainbowHue / 60;
var c = 1,
x = 1 - Math.abs(h % 2 - 1);
var r = 0,
g = 0,
b = 0;
if (h < 1) {
r = c;
g = x;
} else if (h < 2) {
r = x;
g = c;
} else if (h < 3) {
g = c;
b = x;
} else if (h < 4) {
g = x;
b = c;
} else if (h < 5) {
r = x;
b = c;
} else {
r = c;
b = x;
}
var rgb = Math.floor(r * 255) << 16 | Math.floor(g * 255) << 8 | Math.floor(b * 255);
if (typeof winRaceTxt.setFill === "function") winRaceTxt.setFill(rgb);
// Punchscale
if (punchScaleDir === 1) {
punchScale += 0.012;
if (punchScale >= 1.13) punchScaleDir = -1;
} else {
punchScale -= 0.012;
if (punchScale <= 1.0) punchScaleDir = 1;
}
winRaceTxt.scaleX = winRaceTxt.scaleY = punchScale;
};
// Patch into main game update
var _oldUpdate = game.update;
game.update = function () {
if (typeof game.updateWinRaceTxt === "function") game.updateWinRaceTxt();
if (_oldUpdate) _oldUpdate.apply(this, arguments);
};
// --- Initial UI State ---
betBtn.visible = true;
cashoutBtn.visible = false;
infoTxt.setText("Place your bet!");
multiplierTxt.setText("1.00x");
balanceTxt.setText("Balance: $" + balance);
if (typeof balanceTxt.setFill === "function") balanceTxt.setFill(playerColor);
betAmountTxt.setText("Bet: $" + betAmount); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
balance: 1000
});
/****
* Classes
****/
// Button class
var GameButton = Container.expand(function () {
var self = Container.call(this);
self.bg = null;
self.label = null;
self.setAsset = function (assetId, labelText, color) {
if (self.bg) self.bg.destroy();
self.bg = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
if (color !== undefined) self.bg.tint = color;
if (self.label) self.label.destroy();
self.label = new Text2(labelText, {
size: 60,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
};
self.setText = function (txt) {
if (self.label) self.label.setText(txt);
};
return self;
});
// GraphBackground class: draws the multiplier vs time graph as a grid and curve
var GraphBackground = Container.expand(function () {
var self = Container.call(this);
// Graph area (leave margin for axes)
self.graphX = 180;
self.graphY = 420;
self.graphW = 2048 - 360;
self.graphH = 900;
// For curve drawing
self.maxTicks = 600; // 10s at 60fps
self.maxMultiplier = 20;
// Draw grid lines and axes
self.drawGrid = function () {
// Remove previous children
while (self.children.length) self.children[0].destroy();
// Draw horizontal grid lines (multiplier)
for (var i = 1; i <= 5; i++) {
var y = self.graphY + self.graphH - i * self.graphH / 5;
var line = LK.getAsset('betBtn', {
anchorX: 0,
anchorY: 0,
x: self.graphX,
y: y
});
line.width = self.graphW;
line.height = 4;
line.tint = 0x2c3e50;
line.alpha = 0.18;
self.addChild(line);
// Multiplier label
var mult = (i * self.maxMultiplier / 5).toFixed(0) + "x";
var label = new Text2(mult, {
size: 38,
fill: "#fff",
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
label.anchor.set(1, 0.5);
label.x = self.graphX - 18;
label.y = y + 2;
label.alpha = 0.7;
self.addChild(label);
}
// Draw vertical grid lines (time)
for (var j = 0; j <= 6; j++) {
var x = self.graphX + j * self.graphW / 6;
var vline = LK.getAsset('betBtn', {
anchorX: 0,
anchorY: 0,
x: x,
y: self.graphY
});
vline.width = 4;
vline.height = self.graphH;
vline.tint = 0x2c3e50;
vline.alpha = 0.18;
self.addChild(vline);
// Time label
var t = (j * self.maxTicks / 60 / 6).toFixed(1) + "s";
var tlabel = new Text2(t, {
size: 34,
fill: "#fff",
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
tlabel.anchor.set(0.5, 0);
tlabel.x = x;
tlabel.y = self.graphY + self.graphH + 8;
tlabel.alpha = 0.7;
self.addChild(tlabel);
}
};
// Draw the curve for the current round
self.drawCurve = function (ticksElapsed, crashed) {
// Remove previous curve if any
if (self.curveNodes) {
for (var i = 0; i < self.curveNodes.length; i++) {
self.curveNodes[i].destroy();
}
}
self.curveNodes = [];
// Draw curve as small dots for each tick
var lastX = null,
lastY = null;
var maxDrawTicks = crashed ? ticksElapsed : Math.min(ticksElapsed, self.maxTicks);
for (var t = 0; t <= maxDrawTicks; t += 2) {
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, t * 2)) / 100);
if (mult > self.maxMultiplier) break;
var px = self.graphX + t / self.maxTicks * self.graphW;
var py = self.graphY + self.graphH - mult / self.maxMultiplier * self.graphH;
// Draw dot
var dot = LK.getAsset('chip', {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py
});
dot.width = 18;
dot.height = 18;
dot.tint = crashed && t === maxDrawTicks ? 0xff3333 : 0x3a8ee6;
dot.alpha = crashed && t === maxDrawTicks ? 1 : 0.7;
self.addChild(dot);
self.curveNodes.push(dot);
// Optionally, connect with a line (simulate with thin box)
if (lastX !== null && lastY !== null) {
var dx = px - lastX;
var dy = py - lastY;
var dist = Math.sqrt(dx * dx + dy * dy);
var line = LK.getAsset('betBtn', {
anchorX: 0,
anchorY: 0.5,
x: lastX,
y: lastY
});
line.width = dist;
line.height = 8;
line.tint = 0x3a8ee6;
line.alpha = 0.5;
// Angle
line.rotation = Math.atan2(dy, dx);
self.addChild(line);
self.curveNodes.push(line);
}
lastX = px;
lastY = py;
}
};
self.drawGrid();
return self;
});
// Default balance
// Plane class
var Plane = Container.expand(function () {
var self = Container.call(this);
var planeGfx = self.attachAsset('plane', {
anchorX: 0.5,
anchorY: 0.5
});
self.crashed = false;
self.setCrashed = function () {
self.crashed = true;
// Show crash effect
var crash = LK.getAsset('crash', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
self.addChild(crash);
tween(crash, {
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
crash.destroy();
}
});
// Fade out plane
tween(planeGfx, {
alpha: 0
}, {
duration: 700
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a2233
});
/****
* Game Code
****/
// New sounds added to the game
// Plane explosion sound effect
// Hype/energetic soundtrack for flight
// --- Game State Variables ---
// Plane (the flying object)
// Bet button
// Cashout button
// Crash explosion
// Virtual chip
// --- Game State Variables ---
// Reset all balances to 1000 on page refresh
var balance = 1000;
storage.balance = 1000;
var betAmount = 100;
var minBet = 10;
var maxBet = 1000;
var betStep = 10;
var inRound = false;
var hasBet = false;
var hasCashedOut = false;
var roundMultiplier = 1.00;
var roundStartTick = 0;
var crashTick = 0;
var cashoutMultiplier = 0;
var plane = null;
var crashHappened = false;
var lastTick = 0;
var betBtn = null;
var cashoutBtn = null;
var betAmountTxt = null;
var balanceTxt = null;
var multiplierTxt = null;
var infoTxt = null;
var chipIcon = null;
// --- Player and Bot Colors ---
var playerColor = 0xFFE066; // gold/yellow for player
// Bot color codes: Jack (blue), Arthur (pink), John (orange)
var botColors = [0x3a8ee6, 0xff69b4, 0xffa500]; // blue, pink, orange
var botNameColors = [0x3a8ee6, 0xff69b4, 0xffa500]; // name text color matches dot color
// --- Bot State ---
var bots = [{
name: "Jack",
balance: 1000,
betAmount: 0,
hasBet: false,
hasCashedOut: false,
cashoutMultiplier: 0,
displayTxt: null,
chipIcon: null,
win: 0,
color: botColors[0],
nameColor: botNameColors[0],
dotColor: botColors[0]
}, {
name: "Arthur",
balance: 1000,
betAmount: 0,
hasBet: false,
hasCashedOut: false,
cashoutMultiplier: 0,
displayTxt: null,
chipIcon: null,
win: 0,
color: botColors[1],
nameColor: botNameColors[1],
dotColor: botColors[1]
}, {
name: "John",
balance: 1000,
betAmount: 0,
hasBet: false,
hasCashedOut: false,
cashoutMultiplier: 0,
displayTxt: null,
chipIcon: null,
win: 0,
color: botColors[2],
nameColor: botNameColors[2],
dotColor: botColors[2]
}];
// Set win target for all to 2000
var botWinTarget = 2000;
var botWinner = null;
// --- GUI Elements ---
// --- Graph Background ---
var graphBg = new GraphBackground();
game.addChild(graphBg);
// --- GUI Elements ---
multiplierTxt = new Text2("1.00x", {
size: 180,
fill: 0xFFE066,
stroke: "#222",
strokeThickness: 10,
dropShadow: true,
dropShadowColor: "#000",
dropShadowDistance: 8,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 8,
fontWeight: "bold"
});
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.y = 40;
LK.gui.top.addChild(multiplierTxt);
// Info text (centered, below multiplier, with shadow and spacing)
infoTxt = new Text2("Place your bet!", {
size: 70,
fill: "#fff",
stroke: "#222",
strokeThickness: 6,
dropShadow: true,
dropShadowColor: "#000",
dropShadowDistance: 4,
dropShadowAngle: Math.PI / 2,
dropShadowBlur: 6,
fontWeight: "bold"
});
infoTxt.anchor.set(0.5, 0);
infoTxt.y = multiplierTxt.y + multiplierTxt.height + 20;
LK.gui.top.addChild(infoTxt);
balanceTxt = new Text2("Balance: $" + balance, {
size: 60,
fill: playerColor,
stroke: "#222",
strokeThickness: 5,
fontWeight: "bold"
});
balanceTxt.anchor.set(0.5, 1);
// Bet amount display (larger, bolder)
betAmountTxt = new Text2("Bet: $" + betAmount, {
size: 90,
fill: "#fff",
stroke: "#222",
strokeThickness: 6,
fontWeight: "bold"
});
betAmountTxt.anchor.set(1, 0.5);
betAmountTxt.x = 2048 - 60;
LK.gui.topRight.addChild(betAmountTxt);
// --- Bet Button ---
betBtn = new GameButton();
betBtn.setAsset('betBtn', "BET", 0x2ecc40);
betBtn.bg.width = 520;
betBtn.bg.height = 180;
betBtn.bg.radius = 40;
betBtn.bg.dropShadow = true;
betBtn.bg.dropShadowColor = 0x222222;
betBtn.bg.dropShadowDistance = 8;
betBtn.bg.dropShadowAngle = Math.PI / 2;
betBtn.bg.dropShadowBlur = 8;
// Centered horizontally, near bottom
betBtn.x = 2048 / 2;
betBtn.y = 2732 - 180;
betBtn.anchorX = 0.5;
betBtn.anchorY = 0.5;
game.addChild(betBtn);
// --- Balance text and chip icon above bet button (centered) ---
// Center balance text horizontally above bet button
balanceTxt.anchor.set(0.5, 1);
balanceTxt.x = betBtn.x;
balanceTxt.y = betBtn.y - betBtn.bg.height / 2 - 30;
// --- Player win/lose indicator text (above balance) ---
var playerWinFloatTxt = null;
// Center balance text horizontally above bet button
game.addChild(balanceTxt);
// --- Add bot balance and chip display above bet button ---
var botDisplayYOffset = 60;
for (var i = 0; i < bots.length; i++) {
// Balance text for bot
var botTxt = new Text2(bots[i].name + ": $" + bots[i].balance, {
size: 44,
fill: bots[i].nameColor,
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
botTxt.anchor.set(0.5, 1);
botTxt.x = betBtn.x - 400 + i * 400;
botTxt.y = balanceTxt.y - 120;
bots[i].displayTxt = botTxt;
game.addChild(botTxt);
}
// Update bet button text to "BET 100"
betBtn.setText("BET " + betAmount);
// --- Cashout Button ---
cashoutBtn = new GameButton();
cashoutBtn.setAsset('cashoutBtn', "CASHOUT", 0xe67e22);
cashoutBtn.bg.width = 520;
cashoutBtn.bg.height = 180;
cashoutBtn.bg.radius = 40;
cashoutBtn.bg.dropShadow = true;
cashoutBtn.bg.dropShadowColor = 0x222222;
cashoutBtn.bg.dropShadowDistance = 8;
cashoutBtn.bg.dropShadowAngle = Math.PI / 2;
cashoutBtn.bg.dropShadowBlur = 8;
// Centered horizontally, same Y as betBtn
cashoutBtn.x = 2048 / 2;
cashoutBtn.y = 2732 - 180;
cashoutBtn.anchorX = 0.5;
cashoutBtn.anchorY = 0.5;
game.addChild(cashoutBtn);
cashoutBtn.visible = false;
// --- Bet Amount Adjust (plus/minus buttons) ---
var betMinusBtn = new GameButton();
betMinusBtn.setAsset('betBtn', "-", 0x16a085);
betMinusBtn.bg.width = 120;
betMinusBtn.bg.height = 120;
betMinusBtn.bg.radius = 60;
betMinusBtn.x = 2048 - 420;
betMinusBtn.y = 180;
LK.gui.topRight.addChild(betMinusBtn);
var betPlusBtn = new GameButton();
betPlusBtn.setAsset('betBtn', "+", 0x16a085);
betPlusBtn.bg.width = 120;
betPlusBtn.bg.height = 120;
betPlusBtn.bg.radius = 60;
betPlusBtn.x = 2048 - 180;
betPlusBtn.y = 180;
LK.gui.topRight.addChild(betPlusBtn);
betMinusBtn.down = function (x, y, obj) {
if (inRound) return;
betAmount -= betStep;
if (betAmount < minBet) betAmount = maxBet;
betAmountTxt.setText("Bet: $" + betAmount);
};
betPlusBtn.down = function (x, y, obj) {
if (inRound) return;
betAmount += betStep;
if (betAmount > maxBet) betAmount = minBet;
betAmountTxt.setText("Bet: $" + betAmount);
};
// --- Bet Button Handler ---
betBtn.down = function (x, y, obj) {
if (inRound || hasBet) return;
if (balance < betAmount) {
infoTxt.setText("Not enough balance!");
return;
}
hasBet = true;
infoTxt.setText("Waiting for takeoff...");
betBtn.setText("WAIT");
betBtn.bg.tint = 0xaaaaaa;
// Deduct bet immediately
balance -= betAmount;
storage.balance = balance;
balanceTxt.setText("Balance: $" + balance);
if (typeof balanceTxt.setFill === "function") balanceTxt.setFill(playerColor);
// Start round after short delay
LK.setTimeout(startRound, 900);
};
// --- Cashout Button Handler ---
cashoutBtn.down = function (x, y, obj) {
if (!inRound || !hasBet || hasCashedOut || crashHappened) return;
hasCashedOut = true;
cashoutMultiplier = roundMultiplier;
var win = Math.floor(betAmount * cashoutMultiplier);
balance += win;
storage.balance = balance;
balanceTxt.setText("Balance: $" + balance);
if (typeof balanceTxt.setFill === "function") balanceTxt.setFill(playerColor);
infoTxt.setText("You cashed out: $" + win + " (" + cashoutMultiplier.toFixed(2) + "x)");
// Show floating win text above balance
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
playerWinFloatTxt = new Text2("+" + win + " chips", {
size: 48,
fill: 0x3FDC5A,
stroke: "#222",
strokeThickness: 5,
fontWeight: "bold"
});
playerWinFloatTxt.anchor.set(0.5, 1);
playerWinFloatTxt.x = balanceTxt.x;
playerWinFloatTxt.y = balanceTxt.y - 60;
game.addChild(playerWinFloatTxt);
tween(playerWinFloatTxt, {
y: playerWinFloatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
}
});
// Show color-coded dot on graph at cashout point
if (typeof graphBg !== "undefined") {
var cashoutTick = LK.ticks - roundStartTick;
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, cashoutTick * 2)) / 100);
if (mult > graphBg.maxMultiplier) mult = graphBg.maxMultiplier;
var px = graphBg.graphX + cashoutTick / graphBg.maxTicks * graphBg.graphW;
var py = graphBg.graphY + graphBg.graphH - mult / graphBg.maxMultiplier * graphBg.graphH;
var dot = LK.getAsset('chip', {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py
});
dot.width = 32;
dot.height = 32;
dot.tint = playerColor;
dot.alpha = 1;
game.addChild(dot);
// Track for removal next round
if (!game.cashoutDots) game.cashoutDots = [];
game.cashoutDots.push(dot);
// Animate dot pop
dot.scaleX = dot.scaleY = 0.5;
tween(dot, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
// Optionally fade out after a while
LK.setTimeout(function () {
dot.alpha = 0.7;
}, 1200);
}
cashoutBtn.visible = false;
betBtn.visible = false;
// Animate plane off screen
if (plane) {
tween(plane, {
y: -200
}, {
duration: 800,
easing: tween.cubicOut,
onFinish: function onFinish() {
endRound();
}
});
} else {
endRound();
}
};
// --- Start Round ---
function startRound() {
inRound = true;
crashHappened = false;
hasCashedOut = false;
cashoutMultiplier = 0;
roundMultiplier = 1.00;
roundStartTick = LK.ticks;
// Remove all previous player/bot cashout dots
if (typeof game.cashoutDots !== "undefined" && game.cashoutDots && game.cashoutDots.length) {
for (var i = 0; i < game.cashoutDots.length; i++) {
if (game.cashoutDots[i] && typeof game.cashoutDots[i].destroy === "function") {
game.cashoutDots[i].destroy();
}
}
game.cashoutDots = [];
} else {
game.cashoutDots = [];
}
// Remove player win/lose indicator if present
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
// Random crash time: between 2s and 8s (120-480 ticks)
crashTick = roundStartTick + 120 + Math.floor(Math.random() * 360);
// Place plane at bottom center
if (plane) plane.destroy();
plane = new Plane();
plane.x = 2048 / 2;
plane.y = 2732 - 600;
game.addChild(plane);
// Animate plane takeoff
tween(plane, {
y: 2732 / 2
}, {
duration: 1200,
easing: tween.cubicOut
});
// Start hype soundtrack
LK.playMusic('hype_flight', {
fade: {
start: 0,
end: 1,
duration: 600
}
});
// UI
betBtn.visible = false;
cashoutBtn.visible = true;
cashoutBtn.setText("CASHOUT");
cashoutBtn.bg.tint = 0xe67e22;
infoTxt.setText("Flying... Tap CASHOUT to win!");
multiplierTxt.setText("1.00x");
}
// --- End Round ---
function endRound() {
inRound = false;
hasBet = false;
hasCashedOut = false;
betBtn.visible = true;
betBtn.setText("BET");
betBtn.bg.tint = 0x2ecc40;
cashoutBtn.visible = false;
if (plane) {
plane.destroy();
plane = null;
}
// If player lost, show info
if (crashHappened && !cashoutMultiplier) {
infoTxt.setText("Crashed! You lost $" + betAmount);
} else if (!crashHappened && cashoutMultiplier) {
infoTxt.setText("You won $" + Math.floor(betAmount * cashoutMultiplier) + "!");
} else {
infoTxt.setText("Place your bet!");
}
// Reset multiplier display
multiplierTxt.setText("1.00x");
// Update bet amount display
betAmountTxt.setText("Bet: $" + betAmount);
}
// --- Crash Handler ---
function handleCrash() {
crashHappened = true;
// Stop hype soundtrack and play explosion sound
LK.stopMusic();
LK.getSound('plane_explode').play();
if (plane) plane.setCrashed();
cashoutBtn.visible = false;
betBtn.visible = false;
if (!hasCashedOut) {
cashoutMultiplier = 0;
// Show crash info
infoTxt.setText("Crashed! You lost $" + betAmount);
// Show floating lose text above balance
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
playerWinFloatTxt = new Text2("-" + betAmount + " chips", {
size: 48,
fill: 0xFF3333,
stroke: "#222",
strokeThickness: 5,
fontWeight: "bold"
});
playerWinFloatTxt.anchor.set(0.5, 1);
playerWinFloatTxt.x = balanceTxt.x;
playerWinFloatTxt.y = balanceTxt.y - 60;
game.addChild(playerWinFloatTxt);
tween(playerWinFloatTxt, {
y: playerWinFloatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
if (playerWinFloatTxt) {
playerWinFloatTxt.destroy();
playerWinFloatTxt = null;
}
}
});
// End round after short delay
LK.setTimeout(endRound, 1200);
} else {
// Already cashed out, just end round
LK.setTimeout(endRound, 800);
}
}
// --- Main Game Update ---
game.update = function () {
if (!inRound) {
// Reset bot round state if not in round
for (var b = 0; b < bots.length; b++) {
bots[b].hasBet = false;
bots[b].hasCashedOut = false;
bots[b].betAmount = 0;
bots[b].cashoutMultiplier = 0;
bots[b].win = 0;
}
return;
}
// Calculate multiplier: Exponential growth, e.g. 1.00x to ~20x in 8s
var ticksElapsed = LK.ticks - roundStartTick;
// Multiplier formula: 1.002^(ticksElapsed*2) for smooth curve
roundMultiplier = Math.max(1, Math.floor(100 * Math.pow(1.002, ticksElapsed * 2)) / 100);
multiplierTxt.setText(roundMultiplier.toFixed(2) + "x");
// --- Bot logic: Place bets at start of round ---
for (var b = 0; b < bots.length; b++) {
var bot = bots[b];
// Place bet if not already bet and round just started
if (!bot.hasBet && ticksElapsed < 10) {
// All bots always bet 100 chips if enough balance
var botBet = 100;
if (bot.balance >= botBet) {
bot.betAmount = botBet;
bot.balance -= botBet;
bot.hasBet = true;
bot.hasCashedOut = false;
bot.cashoutMultiplier = 0;
bot.win = 0;
// Reset win/lose status color and text
if (bot.displayTxt) {
bot.displayTxt.setText(bot.name + ": $" + bot.balance);
if (typeof bot.displayTxt.setFill === "function") {
bot.displayTxt.setFill("#fff");
}
}
// Remove any win/lose floating text if present
if (bot.winFloatTxt) {
bot.winFloatTxt.destroy();
bot.winFloatTxt = null;
}
}
}
}
// --- Bot logic: Cash out at random multiplier ---
for (var b = 0; b < bots.length; b++) {
var bot = bots[b];
if (bot.hasBet && !bot.hasCashedOut && !crashHappened) {
// Each bot picks a random cashout multiplier at bet time, with 75% winrate hack
if (!bot.targetCashout) {
// Calculate the crash multiplier for this round
var crashTicks = crashTick - roundStartTick;
var crashMult = Math.max(1, Math.floor(100 * Math.pow(1.002, crashTicks * 2)) / 100);
// 75% chance to win (cashout before crash), 25% to lose (cashout after crash)
var willWin = Math.random() < 0.75;
if (willWin) {
// Pick a random cashout between 1.5x and just before crash multiplier (with margin)
var minMult = 1.5;
var maxMult = Math.max(minMult + 0.1, crashMult - 0.2); // leave margin to avoid edge
if (maxMult < minMult) maxMult = minMult + 0.1;
bot.targetCashout = minMult + Math.random() * (maxMult - minMult);
// Clamp to max 5x for realism
if (bot.targetCashout > 5) bot.targetCashout = 1.5 + Math.random() * 3.5;
} else {
// Pick a random cashout after crash (guaranteed lose)
var minLose = crashMult + 0.1;
var maxLose = Math.max(minLose + 0.1, crashMult + 2.0);
bot.targetCashout = minLose + Math.random() * (maxLose - minLose);
// Clamp to max 8x
if (bot.targetCashout > 8) bot.targetCashout = 6 + Math.random() * 2;
}
}
if (roundMultiplier >= bot.targetCashout) {
// Cash out!
bot.hasCashedOut = true;
bot.cashoutMultiplier = roundMultiplier;
bot.win = Math.floor(bot.betAmount * bot.cashoutMultiplier) - bot.betAmount;
bot.balance += bot.betAmount + bot.win;
// Show color-coded dot on graph at cashout point
if (typeof graphBg !== "undefined") {
var cashoutTick = ticksElapsed;
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, cashoutTick * 2)) / 100);
if (mult > graphBg.maxMultiplier) mult = graphBg.maxMultiplier;
var px = graphBg.graphX + cashoutTick / graphBg.maxTicks * graphBg.graphW;
var py = graphBg.graphY + graphBg.graphH - mult / graphBg.maxMultiplier * graphBg.graphH;
var dot = LK.getAsset('chip', {
anchorX: 0.5,
anchorY: 0.5,
x: px,
y: py
});
dot.width = 32;
dot.height = 32;
dot.tint = bot.dotColor;
dot.alpha = 1;
game.addChild(dot);
// Track for removal next round
if (!game.cashoutDots) game.cashoutDots = [];
game.cashoutDots.push(dot);
// Animate dot pop
dot.scaleX = dot.scaleY = 0.5;
tween(dot, {
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
// Optionally fade out after a while
LK.setTimeout(function () {
dot.alpha = 0.7;
}, 1200);
}
// Show win status: green text, floating "+chips"
if (bot.displayTxt) {
bot.displayTxt.setText(bot.name + ": $" + bot.balance);
if (typeof bot.displayTxt.setFill === "function") {
bot.displayTxt.setFill("#3fdc5a");
}
// Show floating "+chips" above bot balance
if (bot.win > 0) {
var floatTxt = new Text2("+" + bot.win + " chips", {
size: 38,
fill: 0x3FDC5A,
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
floatTxt.anchor.set(0.5, 1);
floatTxt.x = bot.displayTxt.x;
floatTxt.y = bot.displayTxt.y - 60;
game.addChild(floatTxt);
bot.winFloatTxt = floatTxt;
// Animate float up and fade out
tween(floatTxt, {
y: floatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
floatTxt.destroy();
}
});
}
}
}
}
// If crash happened and bot didn't cash out, reset targetCashout
if (crashHappened && !bot.hasCashedOut) {
bot.targetCashout = null;
}
}
// --- Bot logic: Handle crash (lose bet if not cashed out) ---
if (crashHappened) {
for (var b = 0; b < bots.length; b++) {
var bot = bots[b];
if (bot.hasBet && !bot.hasCashedOut) {
// Lost bet, nothing to add
bot.betAmount = 0;
bot.cashoutMultiplier = 0;
bot.targetCashout = null;
// Show lose status: red text and floating lose text
if (bot.displayTxt) {
bot.displayTxt.setText(bot.name + ": $" + bot.balance);
if (typeof bot.displayTxt.setFill === "function") {
bot.displayTxt.setFill("#ff3333");
}
// Show floating "-100 chips" above bot balance
var floatTxt = new Text2("-100 chips", {
size: 38,
fill: 0xFF3333,
stroke: "#222",
strokeThickness: 4,
fontWeight: "bold"
});
floatTxt.anchor.set(0.5, 1);
floatTxt.x = bot.displayTxt.x;
floatTxt.y = bot.displayTxt.y - 60;
game.addChild(floatTxt);
bot.winFloatTxt = floatTxt;
// Animate float up and fade out
tween(floatTxt, {
y: floatTxt.y - 60,
alpha: 0
}, {
duration: 1200,
onFinish: function onFinish() {
floatTxt.destroy();
}
});
}
}
}
}
// --- Eliminate player if balance is 0 ---
if (balance <= 0 && !botWinner) {
infoTxt.setText("You are eliminated!");
betBtn.visible = false;
cashoutBtn.visible = false;
// Optionally show lose popup
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
return;
}
// --- Eliminate bots at 0 balance ---
for (var b = 0; b < bots.length; b++) {
if (bots[b].balance <= 0 && !bots[b].eliminated) {
bots[b].eliminated = true;
if (bots[b].displayTxt) {
bots[b].displayTxt.setText(bots[b].name + ": ELIMINATED");
if (typeof bots[b].displayTxt.setFill === "function") {
bots[b].displayTxt.setFill("#888");
}
}
}
}
// --- Player win detection ---
if (!botWinner) {
if (balance >= botWinTarget) {
botWinner = "You";
infoTxt.setText("You win the game!");
LK.setTimeout(function () {
LK.showYouWin();
}, 1800);
} else {
for (var b = 0; b < bots.length; b++) {
if (bots[b].balance >= botWinTarget) {
botWinner = bots[b].name;
infoTxt.setText(botWinner + " wins the game!");
LK.setTimeout(function () {
LK.showYouWin();
}, 1800);
}
}
}
}
// Animate plane moving along the graph curve and make camera follow
if (plane && !plane.crashed) {
// Calculate the graph tip position for the current tick
var graphX = graphBg.graphX;
var graphY = graphBg.graphY;
var graphW = graphBg.graphW;
var graphH = graphBg.graphH;
var maxTicks = graphBg.maxTicks;
var maxMultiplier = graphBg.maxMultiplier;
// Calculate current multiplier
var mult = Math.max(1, Math.floor(100 * Math.pow(1.002, ticksElapsed * 2)) / 100);
if (mult > maxMultiplier) mult = maxMultiplier;
// Calculate the tip position on the graph
var px = graphX + ticksElapsed / maxTicks * graphW;
var py = graphY + graphH - mult / maxMultiplier * graphH;
// Clamp px, py to graph area
if (px < graphX) px = graphX;
if (px > graphX + graphW) px = graphX + graphW;
if (py < graphY) py = graphY;
if (py > graphY + graphH) py = graphY + graphH;
// Place plane at the tip of the graph, perfectly aligned to the curve
// Adjust for plane's width/height so its nose is at the tip
if (plane.children && plane.children.length > 0) {
var planeGfx = plane.children[0];
// Assume plane is facing right, anchorX: 0.5, anchorY: 0.5
// Move plane so its front (right edge) is at (px, py)
// Offset by half width to the left
plane.x = px + planeGfx.width / 2 - planeGfx.width; // nose exactly at tip
plane.y = py;
// --- Calculate tangent angle for rotation ---
// Use two points: current tick and a small delta before (or after if at start)
var deltaT = 2;
var t0 = Math.max(0, ticksElapsed - deltaT);
var t1 = ticksElapsed;
var mult0 = Math.max(1, Math.floor(100 * Math.pow(1.002, t0 * 2)) / 100);
var mult1 = Math.max(1, Math.floor(100 * Math.pow(1.002, t1 * 2)) / 100);
if (mult0 > maxMultiplier) mult0 = maxMultiplier;
if (mult1 > maxMultiplier) mult1 = maxMultiplier;
var px0 = graphX + t0 / maxTicks * graphW;
var py0 = graphY + graphH - mult0 / maxMultiplier * graphH;
var px1 = graphX + t1 / maxTicks * graphW;
var py1 = graphY + graphH - mult1 / maxMultiplier * graphH;
var dx = px1 - px0;
var dy = py1 - py0;
// If dx,dy is zero (shouldn't happen), fallback to 0
var angle = 0;
if (dx !== 0 || dy !== 0) {
angle = Math.atan2(dy, dx);
}
// Set plane rotation to match tangent
planeGfx.rotation = angle;
} else {
plane.x = px;
plane.y = py;
}
// Camera follow: shift the graphBg and all game elements so the plane stays visible and centered horizontally if possible
// Calculate desired camera offset so plane is centered, but clamp so graph doesn't go off screen
var desiredCenterX = 2048 / 2;
var cameraOffsetX = desiredCenterX - px;
// Clamp cameraOffsetX so graphBg doesn't go off screen
var minOffsetX = 2048 - (graphX + graphW); // rightmost: graph right edge at screen right
var maxOffsetX = -graphX; // leftmost: graph left edge at screen left
if (cameraOffsetX < minOffsetX) cameraOffsetX = minOffsetX;
if (cameraOffsetX > maxOffsetX) cameraOffsetX = maxOffsetX;
// Apply camera offset to graphBg and plane (plane is already at px,py so just move graphBg)
graphBg.x = cameraOffsetX;
// If you have other game elements that should move with the camera, offset them by cameraOffsetX as well
}
// Draw/update graph curve
if (typeof graphBg !== "undefined") {
graphBg.drawCurve(ticksElapsed, crashHappened);
}
// Crash?
if (LK.ticks >= crashTick && !crashHappened) {
handleCrash();
}
};
// --- Reset on Game Over (handled by LK) ---
// --- Touchscreen: Prevent elements in top left 100x100
// --- Rainbow punchscale text at bottom center ---
var winRaceTxt = new Text2("(The first to reach 2000 chips wins)", {
size: 70,
fill: 0xFF0000,
stroke: "#222",
strokeThickness: 6,
fontWeight: "bold"
});
winRaceTxt.anchor.set(0.5, 0);
// Position just below the bet button, with a small margin
winRaceTxt.x = betBtn.x;
winRaceTxt.y = betBtn.y + betBtn.bg.height / 2 + 30;
winRaceTxt.scaleX = winRaceTxt.scaleY = 0.7;
game.addChild(winRaceTxt);
// Animate rainbow color and punchscale
var rainbowHue = 0;
var punchScaleDir = 1;
var punchScale = 1;
game.updateWinRaceTxt = function () {
// Rainbow color
rainbowHue += 2;
if (rainbowHue > 360) rainbowHue = 0;
// Convert hue to rgb
var h = rainbowHue / 60;
var c = 1,
x = 1 - Math.abs(h % 2 - 1);
var r = 0,
g = 0,
b = 0;
if (h < 1) {
r = c;
g = x;
} else if (h < 2) {
r = x;
g = c;
} else if (h < 3) {
g = c;
b = x;
} else if (h < 4) {
g = x;
b = c;
} else if (h < 5) {
r = x;
b = c;
} else {
r = c;
b = x;
}
var rgb = Math.floor(r * 255) << 16 | Math.floor(g * 255) << 8 | Math.floor(b * 255);
if (typeof winRaceTxt.setFill === "function") winRaceTxt.setFill(rgb);
// Punchscale
if (punchScaleDir === 1) {
punchScale += 0.012;
if (punchScale >= 1.13) punchScaleDir = -1;
} else {
punchScale -= 0.012;
if (punchScale <= 1.0) punchScaleDir = 1;
}
winRaceTxt.scaleX = winRaceTxt.scaleY = punchScale;
};
// Patch into main game update
var _oldUpdate = game.update;
game.update = function () {
if (typeof game.updateWinRaceTxt === "function") game.updateWinRaceTxt();
if (_oldUpdate) _oldUpdate.apply(this, arguments);
};
// --- Initial UI State ---
betBtn.visible = true;
cashoutBtn.visible = false;
infoTxt.setText("Place your bet!");
multiplierTxt.setText("1.00x");
balanceTxt.setText("Balance: $" + balance);
if (typeof balanceTxt.setFill === "function") balanceTxt.setFill(playerColor);
betAmountTxt.setText("Bet: $" + betAmount);