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