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