/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Horse class: represents a single horse in a race var Horse = Container.expand(function () { var self = Container.call(this); // Asset: Each horse is a colored ellipse with a number label and name var horseColor = self.horseColor || 0x888888; // Make the horse visual more prominent: increase size and add pulsing animation during race var horseAsset = self.attachAsset('horseShape', { width: 180, height: 90, color: horseColor, shape: 'ellipse', anchorX: 0.5, anchorY: 0.5 }); // Add a subtle pulsing animation to the horse when racing self.pulseTween = null; function startPulse() { if (self.pulseTween) return; self.pulseTween = tween(horseAsset, { scaleX: 1.15, scaleY: 0.92 }, { duration: 320, easing: tween.sineInOut, onFinish: function onFinish() { tween(horseAsset, { scaleX: 1, scaleY: 1 }, { duration: 320, easing: tween.sineInOut, onFinish: function onFinish() { self.pulseTween = null; if (self.isRacing) startPulse(); } }); } }); } function stopPulse() { if (self.pulseTween) { tween.stop(horseAsset, { scaleX: true, scaleY: true }); self.pulseTween = null; } horseAsset.scaleX = 1; horseAsset.scaleY = 1; } // Number label (top left of horse) var numberText = new Text2(self.horseNumber ? String(self.horseNumber) : '?', { size: 44, fill: 0xFFFFFF }); numberText.anchor.set(0, 0); numberText.x = -50; numberText.y = -28; self.addChild(numberText); // Name label removed (no name label on horse) // Horse properties self.horseNumber = self.horseNumber || 1; self.odd = self.odd || 2.0; self.lane = self.lane || 1; self.speed = 0; self.progress = 0; // 0 to 1 self.isRacing = false; self.horseName = ""; // will be set in prepareForRace // For animation self.update = function () { if (self.isRacing) { // Move horse forward based on speed self.progress += self.speed; if (self.progress > 1) self.progress = 1; // Update x position self.x = self.startX + (self.finishX - self.startX) * self.progress; } }; // Set up for race self.prepareForRace = function (startX, finishX, laneY, odd, horseNumber, color, horseName) { self.startX = startX; self.finishX = finishX; self.y = laneY; self.x = startX; self.progress = 0; self.odd = odd; self.horseNumber = horseNumber; horseAsset.tint = color; numberText.setText(String(horseNumber)); self.horseColor = color; self.horseName = horseName || ""; // nameText.setText(self.horseName); // removed, no name label }; // Start racing self.startRace = function (speed) { self.isRacing = true; self.speed = speed; startPulse(); }; // Stop racing self.stopRace = function () { self.isRacing = false; stopPulse(); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a3d5c }); /**** * Game Code ****/ // --- Game Constants --- var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var LANE_COUNT = 5; var RACE_COUNT = 9; var HORSE_COLORS = [0xf44336, 0x2196f3, 0x4caf50, 0xffeb3b, 0x9c27b0, 0xff9800, 0x00bcd4, 0x795548, 0x607d8b]; var LANE_HEIGHT = 180; // Move the track down to align with the start button (which is at y: 480 in betPanel, which is at TRACK_TOP - 120) // The start button's y in global coordinates is TRACK_TOP - 120 + 480 = TRACK_TOP + 360 // We want the top of the track to be just below the start button, so set TRACK_TOP accordingly var TRACK_TOP = 900; // was 600, now moved down by 300px var TRACK_LEFT = 200; var TRACK_RIGHT = GAME_WIDTH - 200; var HORSE_SIZE = 180; var START_BALANCE = 1000; // --- Track Lines (Start/Finish) --- var startLine = null; var finishLine = null; // --- Game State --- var currentRace = 1; var playerBalance = START_BALANCE; var horses = []; var raceOdds = []; var raceResults = []; var playerBets = []; var isBettingOpen = true; var winningHorse = null; var raceInProgress = false; var raceFinished = false; var raceTimer = null; var betButtons = []; var betAmountInputs = []; var betTexts = []; var infoText = null; var balanceText = null; var raceText = null; var betPanel = null; var resultPanel = null; var nextRaceButton = null; // --- GUI Setup --- // Balance display balanceText = new Text2("Balance: $" + playerBalance, { size: 80, fill: 0xFFFFFF }); balanceText.anchor.set(0.5, 0); LK.gui.top.addChild(balanceText); // Race number display raceText = new Text2("Race 1 / " + RACE_COUNT, { size: 60, fill: 0xFFFFFF }); raceText.anchor.set(0.5, 0); LK.gui.top.addChild(raceText); raceText.y = 90; // Info text (centered) infoText = new Text2("", { size: 70, fill: 0xFFFBE6 }); infoText.anchor.set(0.5, 0.5); LK.gui.center.addChild(infoText); // --- Helper Functions --- function getLaneY(lane) { return TRACK_TOP + (lane - 1) * LANE_HEIGHT; } function randomOdd() { // Odds between 1.5 and 6.0, rounded to 1 decimal var odd = 1.5 + Math.random() * 4.5; return Math.round(odd * 10) / 10; } function shuffleArray(arr) { // Fisher-Yates shuffle for (var i = arr.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var t = arr[i]; arr[i] = arr[j]; arr[j] = t; } return arr; } function resetBets() { playerBets = []; for (var i = 0; i < LANE_COUNT; i++) { playerBets.push(0); } } function updateBalanceText() { balanceText.setText("Balance: $" + playerBalance); } function updateRaceText() { raceText.setText("Race " + currentRace + " / " + RACE_COUNT); } function showInfo(msg, duration) { infoText.setText(msg); infoText.visible = true; if (duration) { LK.setTimeout(function () { infoText.setText(""); infoText.visible = false; }, duration); } } function showResultPanel(winning, payout) { if (!resultPanel) { resultPanel = new Container(); var bg = LK.getAsset('resultBg', { width: 900, height: 500, color: 0x222222, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); resultPanel.addChild(bg); resultPanel.x = GAME_WIDTH / 2; resultPanel.y = GAME_HEIGHT / 2; resultPanel.zIndex = 1000; resultPanel.visible = false; game.addChild(resultPanel); resultPanel.resultText = new Text2("", { size: 80, fill: 0xFFFBE6 }); resultPanel.resultText.anchor.set(0.5, 0.5); resultPanel.resultText.y = -60; resultPanel.addChild(resultPanel.resultText); resultPanel.payoutText = new Text2("", { size: 60, fill: 0xFFFFFF }); resultPanel.payoutText.anchor.set(0.5, 0.5); resultPanel.payoutText.y = 60; resultPanel.addChild(resultPanel.payoutText); } resultPanel.resultText.setText("Winner: #" + winning); if (payout > 0) { resultPanel.payoutText.setText("You won $" + payout + "!"); } else { resultPanel.payoutText.setText("No winnings this race."); } resultPanel.visible = true; LK.setTimeout(function () { resultPanel.visible = false; }, 1800); } // --- Bet Panel Setup --- function setupBetPanel() { if (betPanel) { betPanel.destroy(); betPanel = null; } betPanel = new Container(); betPanel.x = GAME_WIDTH / 2; // Position betPanel directly under the racetrack betPanel.y = TRACK_TOP + LANE_COUNT * LANE_HEIGHT + 60; game.addChild(betPanel); // Play fun background music (looping, fade in) LK.playMusic('funbgmusic', { fade: { start: 0, end: 0.7, duration: 800 } }); // Title removed: No 'Place Your Bets!' post // Add 'BET' post/title to betPanel var betLabel = new Text2("BET", { size: 60, fill: 0xFFFBE6 }); betLabel.anchor.set(0.5, 0.5); betLabel.x = 0; betLabel.y = -100; betPanel.addChild(betLabel); // For each horse, show odds, bet amount, and a button to increase bet betButtons = []; betAmountInputs = []; betTexts = []; for (var i = 0; i < LANE_COUNT; i++) { (function (idx) { var laneY = i * 90; // Horse number and odds var horseLabel = new Text2("#" + (idx + 1) + " (" + raceOdds[idx] + "x)", { size: 48, fill: 0xFFFFFF }); horseLabel.anchor.set(0, 0.5); horseLabel.x = -320; horseLabel.y = laneY; betPanel.addChild(horseLabel); // Bet amount text var betText = new Text2("$" + playerBets[idx], { size: 48, fill: 0xFFFBE6 }); betText.anchor.set(0.5, 0.5); betText.x = 0; betText.y = laneY; betPanel.addChild(betText); betTexts.push(betText); // Bet + button var betBtn = LK.getAsset('betBtn', { width: 90, height: 60, color: 0x4caf50, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: 180, y: laneY }); betPanel.addChild(betBtn); betButtons.push(betBtn); // Bet - button var betMinusBtn = LK.getAsset('betMinusBtn', { width: 90, height: 60, color: 0xf44336, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: 90, y: laneY }); betPanel.addChild(betMinusBtn); // + Button event betBtn.down = function (x, y, obj) { if (!isBettingOpen) return; // Animate button press: scale down then back up tween(betBtn, { scaleX: 0.88, scaleY: 0.88 }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(betBtn, { scaleX: 1, scaleY: 1 }, { duration: 80, easing: tween.cubicOut }); } }); // Play button press sound LK.getSound('botton').play(); if (playerBalance >= 10) { playerBets[idx] += 10; playerBalance -= 10; betTexts[idx].setText("$" + playerBets[idx]); updateBalanceText(); } }; // - Button event betMinusBtn.down = function (x, y, obj) { if (!isBettingOpen) return; // Animate button press: scale down then back up tween(betMinusBtn, { scaleX: 0.88, scaleY: 0.88 }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(betMinusBtn, { scaleX: 1, scaleY: 1 }, { duration: 80, easing: tween.cubicOut }); } }); // Play button press sound LK.getSound('botton').play(); if (playerBets[idx] >= 10) { playerBets[idx] -= 10; playerBalance += 10; betTexts[idx].setText("$" + playerBets[idx]); updateBalanceText(); } }; })(i); } // Start Race button var startBtn = LK.getAsset('startBtn', { width: 320, height: 90, color: 0x2196f3, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: 0, y: 480 }); betPanel.addChild(startBtn); var startBtnText = new Text2("Start Race", { size: 54, fill: 0xFFFFFF }); startBtnText.anchor.set(0.5, 0.5); startBtnText.x = 0; startBtnText.y = 480; betPanel.addChild(startBtnText); startBtn.down = function (x, y, obj) { if (!isBettingOpen) return; // Animate button press: scale down then back up tween(startBtn, { scaleX: 0.92, scaleY: 0.92 }, { duration: 70, easing: tween.cubicOut, onFinish: function onFinish() { tween(startBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, easing: tween.cubicOut }); } }); // Play button press sound LK.getSound('horserace').play(); // Must bet at least on one horse var totalBet = 0; for (var i = 0; i < LANE_COUNT; i++) totalBet += playerBets[i]; if (totalBet === 0) { showInfo("Place a bet to start!", 1200); return; } isBettingOpen = false; betPanel.visible = false; // Stop fun background music LK.stopMusic(); // Play horse racing sound (already played above) startRace(); }; } // --- Race Setup --- function setupRace() { // Remove previous horses for (var i = 0; i < horses.length; i++) { horses[i].destroy(); } horses = []; // Remove previous lines if they exist if (startLine) { startLine.destroy(); startLine = null; } if (finishLine) { finishLine.destroy(); finishLine = null; } // --- RACE HEADER & HORSE NAMES --- // Remove previous race header/names if present if (game.raceHeader) { game.raceHeader.destroy(); game.raceHeader = null; } if (game.horseNameLabels) { for (var i = 0; i < game.horseNameLabels.length; i++) { game.horseNameLabels[i].destroy(); } game.horseNameLabels = null; } // Race header (e.g. "Race 1") game.raceHeader = new Text2("Race " + currentRace, { size: 90, fill: 0xFFFBE6 }); game.raceHeader.anchor.set(0.5, 0.5); game.raceHeader.x = GAME_WIDTH / 2; // Move race header to just above the new TRACK_TOP game.raceHeader.y = TRACK_TOP - 180; game.addChild(game.raceHeader); // Example horse names (can be replaced with dynamic names if desired) var HORSE_NAMES = ["Thunderbolt", "Blue Streak", "Green Flash", "Gold Rush", "Purple Haze", "Orange Blaze", "Aqua Jet", "Brown Sugar", "Steel Runner"]; // --- Race meter (startLine and finishLine) --- startLine = LK.getAsset('finalBg', { width: 48, //{2O} // Increased from 12 to 48 for a wider line height: LANE_COUNT * LANE_HEIGHT + 40, color: 0xffffff, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: TRACK_LEFT, y: TRACK_TOP + LANE_COUNT * LANE_HEIGHT / 2 }); game.addChild(startLine); finishLine = LK.getAsset('finalBg', { width: 48, //{2V} // Increased from 12 to 48 for a wider line height: LANE_COUNT * LANE_HEIGHT + 40, color: 0xffd700, shape: 'box', anchorX: 0.5, anchorY: 0.5, x: TRACK_RIGHT, y: TRACK_TOP + LANE_COUNT * LANE_HEIGHT / 2 }); game.addChild(finishLine); // Generate odds for this race raceOdds = []; for (var i = 0; i < LANE_COUNT; i++) { raceOdds.push(randomOdd()); } // Shuffle horse colors for variety var colorOrder = []; for (var i = 0; i < LANE_COUNT; i++) colorOrder.push(HORSE_COLORS[i % HORSE_COLORS.length]); shuffleArray(colorOrder); // Create horses game.horseNameLabels = []; for (var i = 0; i < LANE_COUNT; i++) { var horse = new Horse(); // Slide horses to just above the start line (bottom of the start race section) var laneY = getLaneY(i + 1); // Offset horses so their center is just above the start line, and slide down a little more (e.g. +32px) var horseY = laneY + LANE_HEIGHT / 2 - HORSE_SIZE / 2 + 32; horse.prepareForRace(TRACK_LEFT, TRACK_RIGHT, horseY, raceOdds[i], i + 1, colorOrder[i], HORSE_NAMES[i] // pass horse name ); horses.push(horse); game.addChild(horse); // Add horse name label positioned on the horse, just below the top, so as not to cover the top var nameLabel = new Text2(HORSE_NAMES[i], { size: 48, fill: colorOrder[i], // Use the horse's color for the name label stroke: 0x000000, // Add black outline for readability strokeThickness: 8 }); nameLabel.anchor.set(0.5, 0); // Move the name label up a little more (from +2 to -10) nameLabel.x = horse.x; nameLabel.y = horse.y - HORSE_SIZE / 2 - 10; game.addChild(nameLabel); game.horseNameLabels.push(nameLabel); } resetBets(); isBettingOpen = true; raceInProgress = false; raceFinished = false; winningHorse = null; updateRaceText(); setupBetPanel(); showInfo("Race " + currentRace + ": Place your bets!", 1200); } // --- Race Logic --- function startRace() { raceInProgress = true; raceFinished = false; // Only show race shout if the game is not over and not finished if (currentRace <= RACE_COUNT && !raceFinished) { showInfo("They're off!", 1000); } // Assign random speeds, but ensure one winner // SLOWER: Reduce base and winner speed var baseSpeed = 0.003 + Math.random() * 0.001; // All horses move at least this fast (halved) var winnerIdx = Math.floor(Math.random() * LANE_COUNT); var winnerSpeed = baseSpeed + 0.001 + Math.random() * 0.001; // winner is also slower for (var i = 0; i < LANE_COUNT; i++) { if (i === winnerIdx) { horses[i].startRace(winnerSpeed); } else { // Randomize, but not faster than winner var s = baseSpeed + Math.random() * (winnerSpeed - baseSpeed - 0.0005); horses[i].startRace(s); } } winningHorse = winnerIdx + 1; // Stop the race and sound after 5 seconds if (raceTimer) { LK.clearTimeout(raceTimer); } raceTimer = LK.setTimeout(function () { if (raceInProgress && !raceFinished) { // Stop all horses for (var i = 0; i < LANE_COUNT; i++) { horses[i].stopRace(); } raceInProgress = false; raceFinished = true; // Stop the horse race sound LK.getSound('horserace').stop && LK.getSound('horserace').stop(); // If the winner hasn't crossed, finish the race anyway finishRace(); } }, 5000); } function finishRace() { raceInProgress = false; raceFinished = true; // Stop all horses for (var i = 0; i < LANE_COUNT; i++) { horses[i].stopRace(); } // Calculate payout var payout = 0; if (playerBets[winningHorse - 1] > 0) { payout = Math.floor(playerBets[winningHorse - 1] * raceOdds[winningHorse - 1]); playerBalance += payout; } updateBalanceText(); showResultPanel(winningHorse, payout); // Store result raceResults.push({ race: currentRace, winner: winningHorse, odds: raceOdds.slice(), bets: playerBets.slice(), payout: payout }); // Next race or end if (currentRace < RACE_COUNT) { LK.setTimeout(function () { currentRace++; setupRace(); }, 2000); } else { LK.setTimeout(function () { endGame(); }, 2200); } } function endGame() { // Show final result var finalPanel = new Container(); var bg = LK.getAsset('finalBg', { width: 1100, height: 700, color: 0x222222, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); finalPanel.addChild(bg); finalPanel.x = GAME_WIDTH / 2; finalPanel.y = GAME_HEIGHT / 2; finalPanel.zIndex = 2000; game.addChild(finalPanel); var title = new Text2("Derby Complete!", { size: 100, fill: 0xFFFBE6 }); title.anchor.set(0.5, 0.5); title.y = -220; finalPanel.addChild(title); var bal = new Text2("Final Balance: $" + playerBalance, { size: 80, fill: 0xFFFFFF }); bal.anchor.set(0.5, 0.5); bal.y = -80; finalPanel.addChild(bal); var summary = new Text2("Thanks for playing!", { size: 60, fill: 0xFFFBE6 }); summary.anchor.set(0.5, 0.5); summary.y = 60; finalPanel.addChild(summary); // --- Detailed Derby Summary (below the flag image) --- var derbySummaryText = "The derby is over!\n\n"; derbySummaryText += "You completed " + RACE_COUNT + " races.\n"; derbySummaryText += "Final Balance: $" + playerBalance + "\n\n"; derbySummaryText += "Race Results:\n"; for (var i = 0; i < raceResults.length; i++) { var res = raceResults[i]; var betStr = ""; for (var j = 0; j < LANE_COUNT; j++) { if (res.bets[j] > 0) { betStr += "#" + (j + 1) + ": $" + res.bets[j] + " "; } } derbySummaryText += "Race " + res.race + ": Winner #" + res.winner + " (odds " + res.odds[res.winner - 1] + "x)" + (betStr ? " | Your bets: " + betStr : "") + (res.payout > 0 ? " | Won: $" + res.payout : "") + "\n"; } var derbySummary = new Text2(derbySummaryText, { size: 38, fill: 0xFFFBE6, wordWrap: true, wordWrapWidth: 950, align: "left" }); derbySummary.anchor.set(0.5, 0); derbySummary.y = 140; finalPanel.addChild(derbySummary); // Show "You Win" if balance > start, else "Game Over" if (playerBalance > START_BALANCE) { LK.setTimeout(function () { LK.showYouWin(); }, 1800); } else { LK.setTimeout(function () { LK.showGameOver(); }, 1800); } } // --- Game Update Loop --- game.update = function () { if (raceInProgress) { // Update horses var finishedCount = 0; for (var i = 0; i < horses.length; i++) { horses[i].update(); // Keep the horse name label on the horse, just below the top, following its x and y if (game.horseNameLabels && game.horseNameLabels[i]) { game.horseNameLabels[i].x = horses[i].x; game.horseNameLabels[i].y = horses[i].y - HORSE_SIZE / 2 - 10; // Always update the text in case horse names change dynamically game.horseNameLabels[i].setText(horses[i].horseName || ""); } if (horses[i].progress >= 1) finishedCount++; } // If winner crosses finish, finish race if (!raceFinished && horses[winningHorse - 1].progress >= 1) { // Stop the horse race sound immediately when winner finishes LK.getSound('horserace').stop && LK.getSound('horserace').stop(); finishRace(); } } }; // --- Start the first race --- setupRace(); updateBalanceText(); updateRaceText();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Horse class: represents a single horse in a race
var Horse = Container.expand(function () {
var self = Container.call(this);
// Asset: Each horse is a colored ellipse with a number label and name
var horseColor = self.horseColor || 0x888888;
// Make the horse visual more prominent: increase size and add pulsing animation during race
var horseAsset = self.attachAsset('horseShape', {
width: 180,
height: 90,
color: horseColor,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
// Add a subtle pulsing animation to the horse when racing
self.pulseTween = null;
function startPulse() {
if (self.pulseTween) return;
self.pulseTween = tween(horseAsset, {
scaleX: 1.15,
scaleY: 0.92
}, {
duration: 320,
easing: tween.sineInOut,
onFinish: function onFinish() {
tween(horseAsset, {
scaleX: 1,
scaleY: 1
}, {
duration: 320,
easing: tween.sineInOut,
onFinish: function onFinish() {
self.pulseTween = null;
if (self.isRacing) startPulse();
}
});
}
});
}
function stopPulse() {
if (self.pulseTween) {
tween.stop(horseAsset, {
scaleX: true,
scaleY: true
});
self.pulseTween = null;
}
horseAsset.scaleX = 1;
horseAsset.scaleY = 1;
}
// Number label (top left of horse)
var numberText = new Text2(self.horseNumber ? String(self.horseNumber) : '?', {
size: 44,
fill: 0xFFFFFF
});
numberText.anchor.set(0, 0);
numberText.x = -50;
numberText.y = -28;
self.addChild(numberText);
// Name label removed (no name label on horse)
// Horse properties
self.horseNumber = self.horseNumber || 1;
self.odd = self.odd || 2.0;
self.lane = self.lane || 1;
self.speed = 0;
self.progress = 0; // 0 to 1
self.isRacing = false;
self.horseName = ""; // will be set in prepareForRace
// For animation
self.update = function () {
if (self.isRacing) {
// Move horse forward based on speed
self.progress += self.speed;
if (self.progress > 1) self.progress = 1;
// Update x position
self.x = self.startX + (self.finishX - self.startX) * self.progress;
}
};
// Set up for race
self.prepareForRace = function (startX, finishX, laneY, odd, horseNumber, color, horseName) {
self.startX = startX;
self.finishX = finishX;
self.y = laneY;
self.x = startX;
self.progress = 0;
self.odd = odd;
self.horseNumber = horseNumber;
horseAsset.tint = color;
numberText.setText(String(horseNumber));
self.horseColor = color;
self.horseName = horseName || "";
// nameText.setText(self.horseName); // removed, no name label
};
// Start racing
self.startRace = function (speed) {
self.isRacing = true;
self.speed = speed;
startPulse();
};
// Stop racing
self.stopRace = function () {
self.isRacing = false;
stopPulse();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a3d5c
});
/****
* Game Code
****/
// --- Game Constants ---
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var LANE_COUNT = 5;
var RACE_COUNT = 9;
var HORSE_COLORS = [0xf44336, 0x2196f3, 0x4caf50, 0xffeb3b, 0x9c27b0, 0xff9800, 0x00bcd4, 0x795548, 0x607d8b];
var LANE_HEIGHT = 180;
// Move the track down to align with the start button (which is at y: 480 in betPanel, which is at TRACK_TOP - 120)
// The start button's y in global coordinates is TRACK_TOP - 120 + 480 = TRACK_TOP + 360
// We want the top of the track to be just below the start button, so set TRACK_TOP accordingly
var TRACK_TOP = 900; // was 600, now moved down by 300px
var TRACK_LEFT = 200;
var TRACK_RIGHT = GAME_WIDTH - 200;
var HORSE_SIZE = 180;
var START_BALANCE = 1000;
// --- Track Lines (Start/Finish) ---
var startLine = null;
var finishLine = null;
// --- Game State ---
var currentRace = 1;
var playerBalance = START_BALANCE;
var horses = [];
var raceOdds = [];
var raceResults = [];
var playerBets = [];
var isBettingOpen = true;
var winningHorse = null;
var raceInProgress = false;
var raceFinished = false;
var raceTimer = null;
var betButtons = [];
var betAmountInputs = [];
var betTexts = [];
var infoText = null;
var balanceText = null;
var raceText = null;
var betPanel = null;
var resultPanel = null;
var nextRaceButton = null;
// --- GUI Setup ---
// Balance display
balanceText = new Text2("Balance: $" + playerBalance, {
size: 80,
fill: 0xFFFFFF
});
balanceText.anchor.set(0.5, 0);
LK.gui.top.addChild(balanceText);
// Race number display
raceText = new Text2("Race 1 / " + RACE_COUNT, {
size: 60,
fill: 0xFFFFFF
});
raceText.anchor.set(0.5, 0);
LK.gui.top.addChild(raceText);
raceText.y = 90;
// Info text (centered)
infoText = new Text2("", {
size: 70,
fill: 0xFFFBE6
});
infoText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(infoText);
// --- Helper Functions ---
function getLaneY(lane) {
return TRACK_TOP + (lane - 1) * LANE_HEIGHT;
}
function randomOdd() {
// Odds between 1.5 and 6.0, rounded to 1 decimal
var odd = 1.5 + Math.random() * 4.5;
return Math.round(odd * 10) / 10;
}
function shuffleArray(arr) {
// Fisher-Yates shuffle
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
return arr;
}
function resetBets() {
playerBets = [];
for (var i = 0; i < LANE_COUNT; i++) {
playerBets.push(0);
}
}
function updateBalanceText() {
balanceText.setText("Balance: $" + playerBalance);
}
function updateRaceText() {
raceText.setText("Race " + currentRace + " / " + RACE_COUNT);
}
function showInfo(msg, duration) {
infoText.setText(msg);
infoText.visible = true;
if (duration) {
LK.setTimeout(function () {
infoText.setText("");
infoText.visible = false;
}, duration);
}
}
function showResultPanel(winning, payout) {
if (!resultPanel) {
resultPanel = new Container();
var bg = LK.getAsset('resultBg', {
width: 900,
height: 500,
color: 0x222222,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
resultPanel.addChild(bg);
resultPanel.x = GAME_WIDTH / 2;
resultPanel.y = GAME_HEIGHT / 2;
resultPanel.zIndex = 1000;
resultPanel.visible = false;
game.addChild(resultPanel);
resultPanel.resultText = new Text2("", {
size: 80,
fill: 0xFFFBE6
});
resultPanel.resultText.anchor.set(0.5, 0.5);
resultPanel.resultText.y = -60;
resultPanel.addChild(resultPanel.resultText);
resultPanel.payoutText = new Text2("", {
size: 60,
fill: 0xFFFFFF
});
resultPanel.payoutText.anchor.set(0.5, 0.5);
resultPanel.payoutText.y = 60;
resultPanel.addChild(resultPanel.payoutText);
}
resultPanel.resultText.setText("Winner: #" + winning);
if (payout > 0) {
resultPanel.payoutText.setText("You won $" + payout + "!");
} else {
resultPanel.payoutText.setText("No winnings this race.");
}
resultPanel.visible = true;
LK.setTimeout(function () {
resultPanel.visible = false;
}, 1800);
}
// --- Bet Panel Setup ---
function setupBetPanel() {
if (betPanel) {
betPanel.destroy();
betPanel = null;
}
betPanel = new Container();
betPanel.x = GAME_WIDTH / 2;
// Position betPanel directly under the racetrack
betPanel.y = TRACK_TOP + LANE_COUNT * LANE_HEIGHT + 60;
game.addChild(betPanel);
// Play fun background music (looping, fade in)
LK.playMusic('funbgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 800
}
});
// Title removed: No 'Place Your Bets!' post
// Add 'BET' post/title to betPanel
var betLabel = new Text2("BET", {
size: 60,
fill: 0xFFFBE6
});
betLabel.anchor.set(0.5, 0.5);
betLabel.x = 0;
betLabel.y = -100;
betPanel.addChild(betLabel);
// For each horse, show odds, bet amount, and a button to increase bet
betButtons = [];
betAmountInputs = [];
betTexts = [];
for (var i = 0; i < LANE_COUNT; i++) {
(function (idx) {
var laneY = i * 90;
// Horse number and odds
var horseLabel = new Text2("#" + (idx + 1) + " (" + raceOdds[idx] + "x)", {
size: 48,
fill: 0xFFFFFF
});
horseLabel.anchor.set(0, 0.5);
horseLabel.x = -320;
horseLabel.y = laneY;
betPanel.addChild(horseLabel);
// Bet amount text
var betText = new Text2("$" + playerBets[idx], {
size: 48,
fill: 0xFFFBE6
});
betText.anchor.set(0.5, 0.5);
betText.x = 0;
betText.y = laneY;
betPanel.addChild(betText);
betTexts.push(betText);
// Bet + button
var betBtn = LK.getAsset('betBtn', {
width: 90,
height: 60,
color: 0x4caf50,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: 180,
y: laneY
});
betPanel.addChild(betBtn);
betButtons.push(betBtn);
// Bet - button
var betMinusBtn = LK.getAsset('betMinusBtn', {
width: 90,
height: 60,
color: 0xf44336,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: 90,
y: laneY
});
betPanel.addChild(betMinusBtn);
// + Button event
betBtn.down = function (x, y, obj) {
if (!isBettingOpen) return;
// Animate button press: scale down then back up
tween(betBtn, {
scaleX: 0.88,
scaleY: 0.88
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(betBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.cubicOut
});
}
});
// Play button press sound
LK.getSound('botton').play();
if (playerBalance >= 10) {
playerBets[idx] += 10;
playerBalance -= 10;
betTexts[idx].setText("$" + playerBets[idx]);
updateBalanceText();
}
};
// - Button event
betMinusBtn.down = function (x, y, obj) {
if (!isBettingOpen) return;
// Animate button press: scale down then back up
tween(betMinusBtn, {
scaleX: 0.88,
scaleY: 0.88
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(betMinusBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.cubicOut
});
}
});
// Play button press sound
LK.getSound('botton').play();
if (playerBets[idx] >= 10) {
playerBets[idx] -= 10;
playerBalance += 10;
betTexts[idx].setText("$" + playerBets[idx]);
updateBalanceText();
}
};
})(i);
}
// Start Race button
var startBtn = LK.getAsset('startBtn', {
width: 320,
height: 90,
color: 0x2196f3,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 480
});
betPanel.addChild(startBtn);
var startBtnText = new Text2("Start Race", {
size: 54,
fill: 0xFFFFFF
});
startBtnText.anchor.set(0.5, 0.5);
startBtnText.x = 0;
startBtnText.y = 480;
betPanel.addChild(startBtnText);
startBtn.down = function (x, y, obj) {
if (!isBettingOpen) return;
// Animate button press: scale down then back up
tween(startBtn, {
scaleX: 0.92,
scaleY: 0.92
}, {
duration: 70,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(startBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.cubicOut
});
}
});
// Play button press sound
LK.getSound('horserace').play();
// Must bet at least on one horse
var totalBet = 0;
for (var i = 0; i < LANE_COUNT; i++) totalBet += playerBets[i];
if (totalBet === 0) {
showInfo("Place a bet to start!", 1200);
return;
}
isBettingOpen = false;
betPanel.visible = false;
// Stop fun background music
LK.stopMusic();
// Play horse racing sound (already played above)
startRace();
};
}
// --- Race Setup ---
function setupRace() {
// Remove previous horses
for (var i = 0; i < horses.length; i++) {
horses[i].destroy();
}
horses = [];
// Remove previous lines if they exist
if (startLine) {
startLine.destroy();
startLine = null;
}
if (finishLine) {
finishLine.destroy();
finishLine = null;
}
// --- RACE HEADER & HORSE NAMES ---
// Remove previous race header/names if present
if (game.raceHeader) {
game.raceHeader.destroy();
game.raceHeader = null;
}
if (game.horseNameLabels) {
for (var i = 0; i < game.horseNameLabels.length; i++) {
game.horseNameLabels[i].destroy();
}
game.horseNameLabels = null;
}
// Race header (e.g. "Race 1")
game.raceHeader = new Text2("Race " + currentRace, {
size: 90,
fill: 0xFFFBE6
});
game.raceHeader.anchor.set(0.5, 0.5);
game.raceHeader.x = GAME_WIDTH / 2;
// Move race header to just above the new TRACK_TOP
game.raceHeader.y = TRACK_TOP - 180;
game.addChild(game.raceHeader);
// Example horse names (can be replaced with dynamic names if desired)
var HORSE_NAMES = ["Thunderbolt", "Blue Streak", "Green Flash", "Gold Rush", "Purple Haze", "Orange Blaze", "Aqua Jet", "Brown Sugar", "Steel Runner"];
// --- Race meter (startLine and finishLine) ---
startLine = LK.getAsset('finalBg', {
width: 48,
//{2O} // Increased from 12 to 48 for a wider line
height: LANE_COUNT * LANE_HEIGHT + 40,
color: 0xffffff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: TRACK_LEFT,
y: TRACK_TOP + LANE_COUNT * LANE_HEIGHT / 2
});
game.addChild(startLine);
finishLine = LK.getAsset('finalBg', {
width: 48,
//{2V} // Increased from 12 to 48 for a wider line
height: LANE_COUNT * LANE_HEIGHT + 40,
color: 0xffd700,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5,
x: TRACK_RIGHT,
y: TRACK_TOP + LANE_COUNT * LANE_HEIGHT / 2
});
game.addChild(finishLine);
// Generate odds for this race
raceOdds = [];
for (var i = 0; i < LANE_COUNT; i++) {
raceOdds.push(randomOdd());
}
// Shuffle horse colors for variety
var colorOrder = [];
for (var i = 0; i < LANE_COUNT; i++) colorOrder.push(HORSE_COLORS[i % HORSE_COLORS.length]);
shuffleArray(colorOrder);
// Create horses
game.horseNameLabels = [];
for (var i = 0; i < LANE_COUNT; i++) {
var horse = new Horse();
// Slide horses to just above the start line (bottom of the start race section)
var laneY = getLaneY(i + 1);
// Offset horses so their center is just above the start line, and slide down a little more (e.g. +32px)
var horseY = laneY + LANE_HEIGHT / 2 - HORSE_SIZE / 2 + 32;
horse.prepareForRace(TRACK_LEFT, TRACK_RIGHT, horseY, raceOdds[i], i + 1, colorOrder[i], HORSE_NAMES[i] // pass horse name
);
horses.push(horse);
game.addChild(horse);
// Add horse name label positioned on the horse, just below the top, so as not to cover the top
var nameLabel = new Text2(HORSE_NAMES[i], {
size: 48,
fill: colorOrder[i],
// Use the horse's color for the name label
stroke: 0x000000,
// Add black outline for readability
strokeThickness: 8
});
nameLabel.anchor.set(0.5, 0);
// Move the name label up a little more (from +2 to -10)
nameLabel.x = horse.x;
nameLabel.y = horse.y - HORSE_SIZE / 2 - 10;
game.addChild(nameLabel);
game.horseNameLabels.push(nameLabel);
}
resetBets();
isBettingOpen = true;
raceInProgress = false;
raceFinished = false;
winningHorse = null;
updateRaceText();
setupBetPanel();
showInfo("Race " + currentRace + ": Place your bets!", 1200);
}
// --- Race Logic ---
function startRace() {
raceInProgress = true;
raceFinished = false;
// Only show race shout if the game is not over and not finished
if (currentRace <= RACE_COUNT && !raceFinished) {
showInfo("They're off!", 1000);
}
// Assign random speeds, but ensure one winner
// SLOWER: Reduce base and winner speed
var baseSpeed = 0.003 + Math.random() * 0.001; // All horses move at least this fast (halved)
var winnerIdx = Math.floor(Math.random() * LANE_COUNT);
var winnerSpeed = baseSpeed + 0.001 + Math.random() * 0.001; // winner is also slower
for (var i = 0; i < LANE_COUNT; i++) {
if (i === winnerIdx) {
horses[i].startRace(winnerSpeed);
} else {
// Randomize, but not faster than winner
var s = baseSpeed + Math.random() * (winnerSpeed - baseSpeed - 0.0005);
horses[i].startRace(s);
}
}
winningHorse = winnerIdx + 1;
// Stop the race and sound after 5 seconds
if (raceTimer) {
LK.clearTimeout(raceTimer);
}
raceTimer = LK.setTimeout(function () {
if (raceInProgress && !raceFinished) {
// Stop all horses
for (var i = 0; i < LANE_COUNT; i++) {
horses[i].stopRace();
}
raceInProgress = false;
raceFinished = true;
// Stop the horse race sound
LK.getSound('horserace').stop && LK.getSound('horserace').stop();
// If the winner hasn't crossed, finish the race anyway
finishRace();
}
}, 5000);
}
function finishRace() {
raceInProgress = false;
raceFinished = true;
// Stop all horses
for (var i = 0; i < LANE_COUNT; i++) {
horses[i].stopRace();
}
// Calculate payout
var payout = 0;
if (playerBets[winningHorse - 1] > 0) {
payout = Math.floor(playerBets[winningHorse - 1] * raceOdds[winningHorse - 1]);
playerBalance += payout;
}
updateBalanceText();
showResultPanel(winningHorse, payout);
// Store result
raceResults.push({
race: currentRace,
winner: winningHorse,
odds: raceOdds.slice(),
bets: playerBets.slice(),
payout: payout
});
// Next race or end
if (currentRace < RACE_COUNT) {
LK.setTimeout(function () {
currentRace++;
setupRace();
}, 2000);
} else {
LK.setTimeout(function () {
endGame();
}, 2200);
}
}
function endGame() {
// Show final result
var finalPanel = new Container();
var bg = LK.getAsset('finalBg', {
width: 1100,
height: 700,
color: 0x222222,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
finalPanel.addChild(bg);
finalPanel.x = GAME_WIDTH / 2;
finalPanel.y = GAME_HEIGHT / 2;
finalPanel.zIndex = 2000;
game.addChild(finalPanel);
var title = new Text2("Derby Complete!", {
size: 100,
fill: 0xFFFBE6
});
title.anchor.set(0.5, 0.5);
title.y = -220;
finalPanel.addChild(title);
var bal = new Text2("Final Balance: $" + playerBalance, {
size: 80,
fill: 0xFFFFFF
});
bal.anchor.set(0.5, 0.5);
bal.y = -80;
finalPanel.addChild(bal);
var summary = new Text2("Thanks for playing!", {
size: 60,
fill: 0xFFFBE6
});
summary.anchor.set(0.5, 0.5);
summary.y = 60;
finalPanel.addChild(summary);
// --- Detailed Derby Summary (below the flag image) ---
var derbySummaryText = "The derby is over!\n\n";
derbySummaryText += "You completed " + RACE_COUNT + " races.\n";
derbySummaryText += "Final Balance: $" + playerBalance + "\n\n";
derbySummaryText += "Race Results:\n";
for (var i = 0; i < raceResults.length; i++) {
var res = raceResults[i];
var betStr = "";
for (var j = 0; j < LANE_COUNT; j++) {
if (res.bets[j] > 0) {
betStr += "#" + (j + 1) + ": $" + res.bets[j] + " ";
}
}
derbySummaryText += "Race " + res.race + ": Winner #" + res.winner + " (odds " + res.odds[res.winner - 1] + "x)" + (betStr ? " | Your bets: " + betStr : "") + (res.payout > 0 ? " | Won: $" + res.payout : "") + "\n";
}
var derbySummary = new Text2(derbySummaryText, {
size: 38,
fill: 0xFFFBE6,
wordWrap: true,
wordWrapWidth: 950,
align: "left"
});
derbySummary.anchor.set(0.5, 0);
derbySummary.y = 140;
finalPanel.addChild(derbySummary);
// Show "You Win" if balance > start, else "Game Over"
if (playerBalance > START_BALANCE) {
LK.setTimeout(function () {
LK.showYouWin();
}, 1800);
} else {
LK.setTimeout(function () {
LK.showGameOver();
}, 1800);
}
}
// --- Game Update Loop ---
game.update = function () {
if (raceInProgress) {
// Update horses
var finishedCount = 0;
for (var i = 0; i < horses.length; i++) {
horses[i].update();
// Keep the horse name label on the horse, just below the top, following its x and y
if (game.horseNameLabels && game.horseNameLabels[i]) {
game.horseNameLabels[i].x = horses[i].x;
game.horseNameLabels[i].y = horses[i].y - HORSE_SIZE / 2 - 10;
// Always update the text in case horse names change dynamically
game.horseNameLabels[i].setText(horses[i].horseName || "");
}
if (horses[i].progress >= 1) finishedCount++;
}
// If winner crosses finish, finish race
if (!raceFinished && horses[winningHorse - 1].progress >= 1) {
// Stop the horse race sound immediately when winner finishes
LK.getSound('horserace').stop && LK.getSound('horserace').stop();
finishRace();
}
}
};
// --- Start the first race ---
setupRace();
updateBalanceText();
updateRaceText();
image of a racehorse. In-Game asset. 2d. High contrast. No shadows
pressable round button. In-Game asset. 2d. High contrast. No shadows
blue
green rectangle-shaped pressable button. In-Game asset. 2d. High contrast. No shadows
a line consisting of small squares in black and white is vertical from what happened at the races. In-Game asset. 2d. High contrast. No shadows