/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Horse class for both TV and player horses
var Horse = Container.expand(function () {
var self = Container.call(this);
// Asset: Each horse gets a colored ellipse as body and a number label
var colorList = [0x8e44ad, 0xe67e22, 0x16a085, 0xc0392b, 0x2980b9, 0xf1c40f, 0x2ecc71, 0x34495e, 0xd35400, 0x1abc9c, 0x7f8c8d, 0x27ae60, 0x9b59b6, 0x2c3e50, 0xe74c3c, 0x3498db, 0xf39c12, 0x95a5a6, 0x22313f, 0x6ab04c];
// colorIndex is set on instance
self.colorIndex = 0;
self.number = 1; // 1-20
self.isPlayer = false; // true if this is the player's horse
// Stats
self.speed = 10;
self.stamina = 10;
self.luck = 10;
// For race
self.lane = 0; // 0-5
self.progress = 0; // 0-1
self.energy = 0; // stamina left
self.finished = false;
self.finishTime = 0;
// Visuals
self.body = self.attachAsset('horseBody_' + self.colorIndex, {
anchorX: 0.5,
anchorY: 0.5,
width: 90 * 1.32,
height: 60 * 1.32,
color: colorList[self.colorIndex % colorList.length],
shape: 'ellipse'
});
self.label = new Text2(self.number + '', {
size: 40,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// For player horse, show "YOU" label
self.youLabel = null;
if (self.isPlayer) {
self.youLabel = new Text2("YOU", {
size: 32,
fill: "#fff"
});
self.youLabel.anchor.set(0.5, 0);
self.youLabel.y = 40;
self.addChild(self.youLabel);
}
// Set color and number
self.setHorse = function (colorIndex, number, isPlayer) {
self.colorIndex = colorIndex;
self.number = number;
self.isPlayer = !!isPlayer;
self.body.tint = colorList[self.colorIndex % colorList.length];
self.label.setText(self.number + '');
if (self.isPlayer && !self.youLabel) {
self.youLabel = new Text2("YOU", {
size: 32,
fill: "#fff"
});
self.youLabel.anchor.set(0.5, 0);
self.youLabel.y = 40;
self.addChild(self.youLabel);
}
if (!self.isPlayer && self.youLabel) {
self.removeChild(self.youLabel);
self.youLabel = null;
}
};
// Set stats
self.setStats = function (speed, stamina, luck) {
self.speed = speed;
self.stamina = stamina;
self.luck = luck;
};
// Reset for race
self.resetRace = function () {
self.progress = 0;
self.energy = self.stamina;
self.finished = false;
self.finishTime = 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// --- GLOBALS ---
var NUM_HORSES = 40;
var NUM_LANES = 6;
// Set race track to 90% and bet area to 10% of available width (2048px - TV_LEFT - margin)
var TV_LEFT = 120;
var TOTAL_WIDTH = 2048 - TV_LEFT - 60; // 60px right margin
var BET_AREA_WIDTH = Math.floor(TOTAL_WIDTH * 0.10); // 10% for bet area
var RACE_LENGTH = Math.floor(TOTAL_WIDTH * 0.90); // 90% for race track
var TV_TOP = 220; // px from top (moved down by 100)
var TV_HEIGHT = 700; // px, height of TV race area
var TV_WIDTH = RACE_LENGTH; // TV board is now just the race track width
var LANE_HEIGHT = 100 * 1.15;
var HORSE_SIZE = 90;
var PLAYER_HORSE_Y = 2000; // moved up by 200
var PLAYER_HORSE_X = 220;
var GOLD_START = 100;
var BET_COST = 10;
var UPGRADE_COST = 20;
var BET_PRIZE = 40;
var PLAYER_PRIZE = 100;
// --- STATE ---
var gold = typeof storage.gold === "number" ? storage.gold : GOLD_START; // Persist gold in storage
var playerStats = storage.playerStats || {
speed: 10,
stamina: 10,
luck: 10
};
var playerHorseNum = storage.playerHorseNum || 20; // always 20
var horses = []; // all 20 horses (Horse instances, not attached)
var tvHorses = []; // 6 horses in current race (Horse instances, attached)
var playerHorse = null; // player's horse (Horse instance, attached)
var raceInProgress = false;
var raceTimer = 0;
var raceResults = [];
var betHorseNum = null; // number of horse player bet on (1-20)
var betHorseUniqueNum = null; // unique horse number (1-20) the player bet on, for payout validation
var playerEntered = false; // did player enter their horse?
var raceCountdown = 0; // ticks left before race starts
var raceMessage = '';
var messageTimeout = null;
// --- UI ELEMENTS ---
var goldTxt = new Text2('', {
size: 70,
fill: 0xFFD700
});
goldTxt.anchor.set(0, 0);
LK.gui.top.addChild(goldTxt);
goldTxt.x = 120;
goldTxt.y = 10;
var betTxt = new Text2('', {
size: 50,
fill: "#fff"
});
betTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(betTxt);
betTxt.x = 2048 / 2;
betTxt.y = 10;
var messageTxt = new Text2('', {
size: 60,
fill: "#fff"
});
messageTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(messageTxt);
messageTxt.visible = false;
// --- TV RACE BOARD ---
var tvBoard = LK.getAsset('tvBoard', {
anchorX: 0,
anchorY: 0,
width: TV_WIDTH,
height: TV_HEIGHT,
color: 0x222244,
shape: 'box',
x: TV_LEFT,
y: TV_TOP
});
game.addChild(tvBoard);
// --- TV LANE LINES ---
for (var i = 1; i < NUM_LANES; i++) {
var line = LK.getAsset('laneLine' + i, {
anchorX: 0,
anchorY: 0,
width: TV_WIDTH,
height: 4,
color: 0x444466,
shape: 'box',
x: TV_LEFT,
y: TV_TOP + i * LANE_HEIGHT
});
game.addChild(line);
}
// --- TV FINISH LINE ---
var finishLine = LK.getAsset('finishLine', {
anchorX: 0,
anchorY: 0,
width: 8,
height: TV_HEIGHT,
color: 0xffffff,
shape: 'box',
x: TV_LEFT + RACE_LENGTH,
y: TV_TOP
});
game.addChild(finishLine);
// --- PLAYER HORSE AREA ---
var playerArea = LK.getAsset('playerArea', {
anchorX: 0,
anchorY: 0,
width: 500,
height: 700,
// increased height for more stat visibility
color: 0x222244,
shape: 'box',
x: 60,
y: PLAYER_HORSE_Y - 60 // move area a bit lower to match new height
});
game.addChild(playerArea);
// --- PLAYER HORSE STATS TEXT ---
var playerStatsTxt = new Text2('', {
size: 48,
fill: "#fff"
});
playerStatsTxt.anchor.set(0, 0);
playerStatsTxt.x = playerArea.x + 20;
playerStatsTxt.y = playerArea.y + 400; // move stats text lower for new area
game.addChild(playerStatsTxt);
// --- UPGRADE BUTTON ---
var upgradeBtn = LK.getAsset('upgradeBtn', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 80,
color: 0x27ae60,
shape: 'box',
x: playerArea.x + 130,
y: playerArea.y + 600 // move button lower for new area
});
game.addChild(upgradeBtn);
var upgradeTxt = new Text2('Upgrade (20)', {
size: 36,
fill: "#fff"
});
upgradeTxt.anchor.set(0.5, 0.5);
upgradeTxt.x = upgradeBtn.x;
upgradeTxt.y = upgradeBtn.y;
game.addChild(upgradeTxt);
// --- ENTER RACE BUTTON ---
var enterBtn = LK.getAsset('enterBtn', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 80,
color: 0x2980b9,
shape: 'box',
x: playerArea.x + 350,
y: playerArea.y + 600 // move button lower for new area
});
game.addChild(enterBtn);
var enterTxt = new Text2('Enter Race', {
size: 36,
fill: "#fff"
});
enterTxt.anchor.set(0.5, 0.5);
enterTxt.x = enterBtn.x;
enterTxt.y = enterBtn.y;
game.addChild(enterTxt);
// --- BET BUTTONS (for each TV horse) ---
var betBtns = [];
var betBtnTxts = [];
for (var i = 0; i < NUM_LANES; i++) {
var betAreaWidth = BET_AREA_WIDTH;
var btn = LK.getAsset('betBtn' + i, {
anchorX: 0.5,
anchorY: 0.5,
width: 120 * 1.32,
height: 60 * 1.32,
color: 0xe67e22,
shape: 'box',
// Place bet buttons in the center of the new bet area, right after the finish line
x: TV_LEFT + RACE_LENGTH + betAreaWidth / 2,
y: TV_TOP + i * LANE_HEIGHT + LANE_HEIGHT / 2
});
game.addChild(btn);
betBtns.push(btn);
// Use bet image as button instead of text
var betImgBtn = LK.getAsset('bet', {
anchorX: 0.5,
anchorY: 0.5,
// Increase by 15%
width: 90 * 1.32 * 1.15,
height: 90 * 1.32 * 1.15,
x: btn.x,
y: btn.y
});
game.addChild(betImgBtn);
betBtnTxts.push(betImgBtn);
}
// --- HORSE ASSET INIT (for all horses) ---
for (var i = 0; i < NUM_HORSES; i++) {
var h = new Horse();
h.setHorse(i, i + 1, false);
// Random stats for TV horses
var s = 40 + Math.floor(Math.random() * 61); // 40-100
var st = 40 + Math.floor(Math.random() * 61);
var l = 40 + Math.floor(Math.random() * 61);
h.setStats(s, st, l);
horses.push(h);
}
// --- PLAYER HORSE ANIMATION FRAMES ---
var playerHorseFrames = [];
var playerHorseFrameNames = ['ourhorse', 'ourhorse2', 'ourhorse3', 'ourhorse4', 'ourhorse5'];
for (var i = 0; i < playerHorseFrameNames.length; i++) {
// Use ourhorse textures as animation frames for player horse
// Adjust width +5%, height -5%
playerHorseFrames.push(LK.getAsset(playerHorseFrameNames[i], {
anchorX: 0.5,
anchorY: 0.5,
width: 100 * 1.05,
height: 100 * 0.95
}));
}
// --- PLAYER HORSE INIT ---
playerHorse = new Horse();
playerHorse.setHorse(NUM_HORSES - 1, playerHorseNum, true);
playerHorse.setStats(playerStats.speed, playerStats.stamina, playerStats.luck);
// Add animation state
playerHorse.animFrame = 0;
playerHorse.animTick = 0;
playerHorse.animSprites = [];
// Remove default body
if (playerHorse.body && playerHorse.body.parent === playerHorse) {
playerHorse.removeChild(playerHorse.body);
}
// Add all frames, only show the first
for (var i = 0; i < playerHorseFrames.length; i++) {
var sprite = playerHorseFrames[i];
sprite.visible = i === 0;
playerHorse.addChild(sprite);
playerHorse.animSprites.push(sprite);
}
playerHorse.updateAnimation = function () {
this.animTick++;
if (this.animTick % 12 === 0) {
// Change frame every 12 ticks (~5 FPS)
this.animSprites[this.animFrame].visible = false;
this.animFrame = (this.animFrame + 1) % this.animSprites.length;
this.animSprites[this.animFrame].visible = true;
}
};
// Move player horse up to match new area
playerHorse.x = PLAYER_HORSE_X;
playerHorse.y = PLAYER_HORSE_Y + 100;
game.addChild(playerHorse);
// --- FUNCTIONS ---
function updateGoldText() {
goldTxt.setText("Gold: " + gold);
}
function updatePlayerStatsText() {
playerStatsTxt.setText("Speed: " + playerHorse.speed + "\nStamina: " + playerHorse.stamina + "\nLuck: " + playerHorse.luck);
}
function showMessage(msg, duration) {
messageTxt.setText(msg);
messageTxt.visible = true;
if (messageTimeout) {
LK.clearTimeout(messageTimeout);
}
if (duration) {
messageTimeout = LK.setTimeout(function () {
messageTxt.visible = false;
}, duration);
}
}
function hideMessage() {
messageTxt.visible = false;
if (messageTimeout) {
LK.clearTimeout(messageTimeout);
}
}
function saveState() {
// Persist gold, player stats and horse number
storage.gold = gold;
storage.playerStats = {
speed: playerHorse.speed,
stamina: playerHorse.stamina,
luck: playerHorse.luck
};
storage.playerHorseNum = playerHorseNum;
}
// Pick 6 random horses for TV race (optionally include player horse)
function pickRaceHorses(includePlayer) {
var pool = [];
for (var i = 0; i < NUM_HORSES - 1; i++) {
pool.push(i);
} // 0-18
if (includePlayer) {
pool.push(NUM_HORSES - 1);
} // 19
var selected = [];
while (selected.length < NUM_LANES) {
var idx = Math.floor(Math.random() * pool.length);
selected.push(pool[idx]);
pool.splice(idx, 1);
}
return selected;
}
// Start a new race (after betting phase)
function startRace() {
raceInProgress = true;
raceResults = [];
raceTimer = 0;
raceMessage = '';
hideMessage();
// Reset TV horses
for (var i = 0; i < tvHorses.length; i++) {
game.removeChild(tvHorses[i]);
}
tvHorses = [];
// Pick horses
var includePlayer = playerEntered;
var selected;
// Use the stored selected horses from betting phase if available
if (game.selectedRaceHorseIndices && Array.isArray(game.selectedRaceHorseIndices) && game.selectedRaceHorseIndices.length === NUM_LANES) {
selected = game.selectedRaceHorseIndices.slice();
} else {
selected = pickRaceHorses(includePlayer);
}
for (var i = 0; i < NUM_LANES; i++) {
var hidx = selected[i];
var h;
// If playerEntered, always put playerHorse in the last lane (bottom)
if (playerEntered && i === NUM_LANES - 1) {
h = playerHorse;
h.setHorse(NUM_HORSES - 1, playerHorseNum, true);
h.setStats(playerHorse.speed, playerHorse.stamina, playerHorse.luck);
} else if (hidx === NUM_HORSES - 1 && !playerEntered) {
// Defensive: if player not entered but random picked player horse, use a TV horse instead
// Pick a non-player horse from horses not already in tvHorses
for (var alt = 0; alt < NUM_HORSES - 1; alt++) {
var alreadyUsed = false;
for (var t = 0; t < tvHorses.length; t++) {
if (tvHorses[t] === horses[alt]) {
alreadyUsed = true;
}
}
if (!alreadyUsed) {
hidx = alt;
break;
}
}
h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
} else if (hidx === NUM_HORSES - 1 && playerEntered) {
// If playerEntered, skip this slot (playerHorse will be added at last lane)
// Instead, pick a non-player horse
for (var alt = 0; alt < NUM_HORSES - 1; alt++) {
var alreadyUsed = false;
for (var t = 0; t < tvHorses.length; t++) {
if (tvHorses[t] === horses[alt]) {
alreadyUsed = true;
}
}
if (!alreadyUsed) {
hidx = alt;
break;
}
}
h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
} else {
h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
}
h.lane = i;
h.resetRace();
h.x = TV_LEFT + 40;
h.y = TV_TOP + i * LANE_HEIGHT + LANE_HEIGHT / 2;
h.scaleX = 1.1;
h.scaleY = 1.1;
game.addChild(h);
tvHorses.push(h);
}
// Set bet buttons
for (var i = 0; i < NUM_LANES; i++) {
betBtns[i].visible = false;
betBtnTxts[i].visible = false;
}
// Start race after short countdown
raceCountdown = 60; // 1 second
showMessage("Race starting!", 1000);
}
// End race, show results, pay out
function endRace() {
raceInProgress = false;
var winner = raceResults[0];
var winnerNum = winner.number;
var playerWin = false;
var playerPlace = -1;
// Find the actual TV horse object the player bet on (by unique number)
var betHorseObj = null;
if (betHorseUniqueNum !== null) {
for (var i = 0; i < tvHorses.length; i++) {
if (tvHorses[i].number === betHorseUniqueNum) {
betHorseObj = tvHorses[i];
break;
}
}
}
for (var i = 0; i < raceResults.length; i++) {
if (raceResults[i].isPlayer) {
playerPlace = i + 1;
break;
}
}
var msg = "Winner: #" + winnerNum;
if (betHorseUniqueNum !== null) {
// Find the placement of the bet horse in the results
var betPlace = -1;
if (betHorseObj) {
for (var i = 0; i < raceResults.length; i++) {
if (raceResults[i] === betHorseObj) {
betPlace = i + 1;
break;
}
}
}
if (betPlace === 1) {
gold += 50;
msg += "\nYour bet WON! (+50)";
playerWin = true;
} else if (betPlace === 2) {
gold += 30;
msg += "\nYour bet placed 2nd! (+30)";
playerWin = true;
} else if (betPlace === 3) {
gold += 10;
msg += "\nYour bet placed 3rd! (+10)";
playerWin = true;
} else {
msg += "\nYou lost your bet.";
}
}
if (playerEntered) {
if (playerPlace === 1) {
gold += PLAYER_PRIZE;
msg += "\nYour horse WON! (+100)";
} else if (playerPlace > 0) {
msg += "\nYour horse placed #" + playerPlace;
} else {
msg += "\nYour horse did not finish.";
}
}
updateGoldText();
saveState();
showMessage(msg, 2500);
// Move player horse back to player area after race ends
if (playerHorse && playerHorse.parent === game) {
// Remove from TV area if present
for (var i = 0; i < tvHorses.length; i++) {
if (tvHorses[i] === playerHorse) {
game.removeChild(playerHorse);
break;
}
}
// Reset position to player area
playerHorse.x = PLAYER_HORSE_X;
playerHorse.y = PLAYER_HORSE_Y + 100;
game.addChild(playerHorse);
}
// After a delay, start new betting phase
LK.setTimeout(function () {
startBettingPhase();
}, 2500);
}
// Start betting phase: show 6 horses, allow bet/enter
function startBettingPhase() {
raceInProgress = false;
playerEntered = false;
betHorseNum = null;
betHorseUniqueNum = null;
raceResults = [];
hideMessage();
// Remove TV horses
for (var i = 0; i < tvHorses.length; i++) {
game.removeChild(tvHorses[i]);
}
tvHorses = [];
// Always re-add player horse to player area after race
if (playerHorse && playerHorse.parent !== game) {
playerHorse.x = PLAYER_HORSE_X;
playerHorse.y = PLAYER_HORSE_Y + 100;
game.addChild(playerHorse);
}
// Pick 6 horses (player horse not included yet)
var selected = pickRaceHorses(false);
// Store selected horses for this betting phase, so they are reused for the race
game.selectedRaceHorseIndices = selected.slice();
for (var i = 0; i < NUM_LANES; i++) {
var hidx = selected[i];
var h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
h.lane = i;
h.resetRace();
h.x = TV_LEFT + 40;
h.y = TV_TOP + i * LANE_HEIGHT + LANE_HEIGHT / 2;
h.scaleX = 1.1;
h.scaleY = 1.1;
game.addChild(h);
tvHorses.push(h);
}
// Set bet buttons
for (var i = 0; i < NUM_LANES; i++) {
betBtns[i].visible = true;
betBtnTxts[i].visible = true;
// No text to set, betBtnTxts now holds image buttons
}
betTxt.setText("Pick a horse to bet on, or enter your own!");
}
// --- BUTTON HANDLERS ---
// Bet buttons
for (var i = 0; i < NUM_LANES; i++) {
(function (idx) {
// Set handler on both the colored box and the image button for full compatibility
betBtns[idx].down = function (x, y, obj) {
if (raceInProgress || betHorseNum !== null || gold < BET_COST) {
return;
}
if (!tvHorses[idx]) {
return;
} // Defensive: avoid undefined
betHorseNum = idx; // index in tvHorses for UI, but not for payout
betHorseUniqueNum = tvHorses[idx].number; // store the actual horse number for payout
gold -= BET_COST;
updateGoldText();
saveState();
betTxt.setText("Bet placed on #" + betHorseUniqueNum + ". Waiting for race...");
for (var j = 0; j < NUM_LANES; j++) {
betBtns[j].visible = false;
betBtnTxts[j].visible = false;
}
// Start race after short delay
LK.setTimeout(function () {
startRace();
}, 1000);
};
// Also set handler on the image button
betBtnTxts[idx].down = betBtns[idx].down;
})(i);
}
// Upgrade button
upgradeBtn.down = function (x, y, obj) {
if (raceInProgress) {
return;
}
if (gold < UPGRADE_COST) {
showMessage("Not enough gold!", 1200);
return;
}
gold -= UPGRADE_COST;
updateGoldText();
// Randomly pick stat to upgrade
var statNames = ['speed', 'stamina', 'luck'];
var idx = Math.floor(Math.random() * 3);
var stat = statNames[idx];
playerHorse[stat] += 1 + Math.floor(Math.random() * 3); // +1~3
updatePlayerStatsText();
saveState();
showMessage("Upgraded " + stat + "!", 1000);
};
// Enter race button
enterBtn.down = function (x, y, obj) {
if (raceInProgress || playerEntered) {
return;
}
// Only allow if player's horse is not already in TV horses
for (var i = 0; i < tvHorses.length; i++) {
if (tvHorses[i].isPlayer) {
showMessage("Already entered!", 1000);
return;
}
}
playerEntered = true;
betTxt.setText("You entered your horse! Waiting for race...");
for (var j = 0; j < NUM_LANES; j++) {
betBtns[j].visible = false;
betBtnTxts[j].visible = false;
}
// Start race after short delay
LK.setTimeout(function () {
startRace();
}, 1000);
};
// --- WORK AREA & BUTTON (for 0 gold) ---
var workArea = null;
var workBtn = null;
var workBtnTxt = null;
var workTimerTxt = null;
var workTimer = 0;
var workActive = false;
var workTimeout = null;
// --- GAME UPDATE LOOP ---
game.update = function () {
// Animate player horse if present
if (playerHorse && playerHorse.animSprites && playerHorse.animSprites.length > 0) {
playerHorse.updateAnimation();
}
// Show/hide work area if gold is 0
if (gold === 0 && !workArea) {
// Create work area on the right, symmetric to playerArea
workArea = LK.getAsset('playerArea', {
anchorX: 0,
anchorY: 0,
width: 500,
height: 700,
color: 0x222244,
shape: 'box',
x: 2048 - 60 - 500,
y: PLAYER_HORSE_Y - 60
});
game.addChild(workArea);
// Work button
workBtn = LK.getAsset('upgradeBtn', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 80,
color: 0xf368bd,
shape: 'box',
x: workArea.x + 370,
y: workArea.y + 600
});
game.addChild(workBtn);
workBtnTxt = new Text2('Work!', {
size: 36,
fill: "#fff"
});
workBtnTxt.anchor.set(0.5, 0.5);
workBtnTxt.x = workBtn.x;
workBtnTxt.y = workBtn.y;
game.addChild(workBtnTxt);
workTimerTxt = new Text2('', {
size: 40,
fill: "#fff"
});
workTimerTxt.anchor.set(0.5, 0.5);
workTimerTxt.x = workBtn.x;
workTimerTxt.y = workBtn.y + 70;
workTimerTxt.visible = false;
game.addChild(workTimerTxt);
workBtn.down = function (x, y, obj) {
if (workActive) {
return;
}
workActive = true;
workBtn.visible = false;
workBtnTxt.visible = false;
workTimer = 15;
workTimerTxt.visible = true;
workTimerTxt.setText("15");
// Start countdown
workTimeout = LK.setInterval(function () {
workTimer--;
if (workTimer > 0) {
workTimerTxt.setText(workTimer + "");
} else {
LK.clearInterval(workTimeout);
workTimeout = null;
workTimerTxt.visible = false;
gold += 10;
updateGoldText();
saveState();
workActive = false;
// Remove work area and button
if (workArea && workArea.parent === game) {
game.removeChild(workArea);
}
if (workBtn && workBtn.parent === game) {
game.removeChild(workBtn);
}
if (workBtnTxt && workBtnTxt.parent === game) {
game.removeChild(workBtnTxt);
}
if (workTimerTxt && workTimerTxt.parent === game) {
game.removeChild(workTimerTxt);
}
workArea = null;
workBtn = null;
workBtnTxt = null;
workTimerTxt = null;
}
}, 1000);
};
}
// Remove work area if gold > 0
if (gold > 0 && workArea) {
if (workTimeout) {
LK.clearInterval(workTimeout);
workTimeout = null;
}
if (workArea && workArea.parent === game) {
game.removeChild(workArea);
}
if (workBtn && workBtn.parent === game) {
game.removeChild(workBtn);
}
if (workBtnTxt && workBtnTxt.parent === game) {
game.removeChild(workBtnTxt);
}
if (workTimerTxt && workTimerTxt.parent === game) {
game.removeChild(workTimerTxt);
}
workArea = null;
workBtn = null;
workBtnTxt = null;
workTimerTxt = null;
workActive = false;
}
// Update UI
updateGoldText();
updatePlayerStatsText();
// Race countdown
if (raceCountdown > 0) {
raceCountdown--;
if (raceCountdown === 0) {
showMessage("Go!", 600);
}
return;
}
// Race in progress
if (raceInProgress) {
var finishedCount = 0;
for (var i = 0; i < tvHorses.length; i++) {
var h = tvHorses[i];
if (h.finished) {
finishedCount++;
continue;
}
// Calculate move
var baseSpeed = h.speed / 100 * 8 + 2; // 2-10 px per tick
var luckFactor = 1 + (Math.random() - 0.5) * (h.luck / 200); // up to +/- luck/200
var move = baseSpeed * luckFactor;
if (h.energy > 0) {
h.energy -= 0.04;
} else {
move *= 0.6; // tired
}
h.progress += move / RACE_LENGTH;
if (h.progress >= 1) {
h.progress = 1;
h.finished = true;
h.finishTime = raceTimer;
raceResults.push(h);
}
h.x = TV_LEFT + 40 + h.progress * (RACE_LENGTH - 80);
}
raceTimer++;
// If all finished, end race
if (raceResults.length === tvHorses.length) {
endRace();
}
}
};
// --- INITIALIZE ---
updateGoldText();
updatePlayerStatsText();
startBettingPhase(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Horse class for both TV and player horses
var Horse = Container.expand(function () {
var self = Container.call(this);
// Asset: Each horse gets a colored ellipse as body and a number label
var colorList = [0x8e44ad, 0xe67e22, 0x16a085, 0xc0392b, 0x2980b9, 0xf1c40f, 0x2ecc71, 0x34495e, 0xd35400, 0x1abc9c, 0x7f8c8d, 0x27ae60, 0x9b59b6, 0x2c3e50, 0xe74c3c, 0x3498db, 0xf39c12, 0x95a5a6, 0x22313f, 0x6ab04c];
// colorIndex is set on instance
self.colorIndex = 0;
self.number = 1; // 1-20
self.isPlayer = false; // true if this is the player's horse
// Stats
self.speed = 10;
self.stamina = 10;
self.luck = 10;
// For race
self.lane = 0; // 0-5
self.progress = 0; // 0-1
self.energy = 0; // stamina left
self.finished = false;
self.finishTime = 0;
// Visuals
self.body = self.attachAsset('horseBody_' + self.colorIndex, {
anchorX: 0.5,
anchorY: 0.5,
width: 90 * 1.32,
height: 60 * 1.32,
color: colorList[self.colorIndex % colorList.length],
shape: 'ellipse'
});
self.label = new Text2(self.number + '', {
size: 40,
fill: "#fff"
});
self.label.anchor.set(0.5, 0.5);
self.addChild(self.label);
// For player horse, show "YOU" label
self.youLabel = null;
if (self.isPlayer) {
self.youLabel = new Text2("YOU", {
size: 32,
fill: "#fff"
});
self.youLabel.anchor.set(0.5, 0);
self.youLabel.y = 40;
self.addChild(self.youLabel);
}
// Set color and number
self.setHorse = function (colorIndex, number, isPlayer) {
self.colorIndex = colorIndex;
self.number = number;
self.isPlayer = !!isPlayer;
self.body.tint = colorList[self.colorIndex % colorList.length];
self.label.setText(self.number + '');
if (self.isPlayer && !self.youLabel) {
self.youLabel = new Text2("YOU", {
size: 32,
fill: "#fff"
});
self.youLabel.anchor.set(0.5, 0);
self.youLabel.y = 40;
self.addChild(self.youLabel);
}
if (!self.isPlayer && self.youLabel) {
self.removeChild(self.youLabel);
self.youLabel = null;
}
};
// Set stats
self.setStats = function (speed, stamina, luck) {
self.speed = speed;
self.stamina = stamina;
self.luck = luck;
};
// Reset for race
self.resetRace = function () {
self.progress = 0;
self.energy = self.stamina;
self.finished = false;
self.finishTime = 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// --- GLOBALS ---
var NUM_HORSES = 40;
var NUM_LANES = 6;
// Set race track to 90% and bet area to 10% of available width (2048px - TV_LEFT - margin)
var TV_LEFT = 120;
var TOTAL_WIDTH = 2048 - TV_LEFT - 60; // 60px right margin
var BET_AREA_WIDTH = Math.floor(TOTAL_WIDTH * 0.10); // 10% for bet area
var RACE_LENGTH = Math.floor(TOTAL_WIDTH * 0.90); // 90% for race track
var TV_TOP = 220; // px from top (moved down by 100)
var TV_HEIGHT = 700; // px, height of TV race area
var TV_WIDTH = RACE_LENGTH; // TV board is now just the race track width
var LANE_HEIGHT = 100 * 1.15;
var HORSE_SIZE = 90;
var PLAYER_HORSE_Y = 2000; // moved up by 200
var PLAYER_HORSE_X = 220;
var GOLD_START = 100;
var BET_COST = 10;
var UPGRADE_COST = 20;
var BET_PRIZE = 40;
var PLAYER_PRIZE = 100;
// --- STATE ---
var gold = typeof storage.gold === "number" ? storage.gold : GOLD_START; // Persist gold in storage
var playerStats = storage.playerStats || {
speed: 10,
stamina: 10,
luck: 10
};
var playerHorseNum = storage.playerHorseNum || 20; // always 20
var horses = []; // all 20 horses (Horse instances, not attached)
var tvHorses = []; // 6 horses in current race (Horse instances, attached)
var playerHorse = null; // player's horse (Horse instance, attached)
var raceInProgress = false;
var raceTimer = 0;
var raceResults = [];
var betHorseNum = null; // number of horse player bet on (1-20)
var betHorseUniqueNum = null; // unique horse number (1-20) the player bet on, for payout validation
var playerEntered = false; // did player enter their horse?
var raceCountdown = 0; // ticks left before race starts
var raceMessage = '';
var messageTimeout = null;
// --- UI ELEMENTS ---
var goldTxt = new Text2('', {
size: 70,
fill: 0xFFD700
});
goldTxt.anchor.set(0, 0);
LK.gui.top.addChild(goldTxt);
goldTxt.x = 120;
goldTxt.y = 10;
var betTxt = new Text2('', {
size: 50,
fill: "#fff"
});
betTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(betTxt);
betTxt.x = 2048 / 2;
betTxt.y = 10;
var messageTxt = new Text2('', {
size: 60,
fill: "#fff"
});
messageTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(messageTxt);
messageTxt.visible = false;
// --- TV RACE BOARD ---
var tvBoard = LK.getAsset('tvBoard', {
anchorX: 0,
anchorY: 0,
width: TV_WIDTH,
height: TV_HEIGHT,
color: 0x222244,
shape: 'box',
x: TV_LEFT,
y: TV_TOP
});
game.addChild(tvBoard);
// --- TV LANE LINES ---
for (var i = 1; i < NUM_LANES; i++) {
var line = LK.getAsset('laneLine' + i, {
anchorX: 0,
anchorY: 0,
width: TV_WIDTH,
height: 4,
color: 0x444466,
shape: 'box',
x: TV_LEFT,
y: TV_TOP + i * LANE_HEIGHT
});
game.addChild(line);
}
// --- TV FINISH LINE ---
var finishLine = LK.getAsset('finishLine', {
anchorX: 0,
anchorY: 0,
width: 8,
height: TV_HEIGHT,
color: 0xffffff,
shape: 'box',
x: TV_LEFT + RACE_LENGTH,
y: TV_TOP
});
game.addChild(finishLine);
// --- PLAYER HORSE AREA ---
var playerArea = LK.getAsset('playerArea', {
anchorX: 0,
anchorY: 0,
width: 500,
height: 700,
// increased height for more stat visibility
color: 0x222244,
shape: 'box',
x: 60,
y: PLAYER_HORSE_Y - 60 // move area a bit lower to match new height
});
game.addChild(playerArea);
// --- PLAYER HORSE STATS TEXT ---
var playerStatsTxt = new Text2('', {
size: 48,
fill: "#fff"
});
playerStatsTxt.anchor.set(0, 0);
playerStatsTxt.x = playerArea.x + 20;
playerStatsTxt.y = playerArea.y + 400; // move stats text lower for new area
game.addChild(playerStatsTxt);
// --- UPGRADE BUTTON ---
var upgradeBtn = LK.getAsset('upgradeBtn', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 80,
color: 0x27ae60,
shape: 'box',
x: playerArea.x + 130,
y: playerArea.y + 600 // move button lower for new area
});
game.addChild(upgradeBtn);
var upgradeTxt = new Text2('Upgrade (20)', {
size: 36,
fill: "#fff"
});
upgradeTxt.anchor.set(0.5, 0.5);
upgradeTxt.x = upgradeBtn.x;
upgradeTxt.y = upgradeBtn.y;
game.addChild(upgradeTxt);
// --- ENTER RACE BUTTON ---
var enterBtn = LK.getAsset('enterBtn', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 80,
color: 0x2980b9,
shape: 'box',
x: playerArea.x + 350,
y: playerArea.y + 600 // move button lower for new area
});
game.addChild(enterBtn);
var enterTxt = new Text2('Enter Race', {
size: 36,
fill: "#fff"
});
enterTxt.anchor.set(0.5, 0.5);
enterTxt.x = enterBtn.x;
enterTxt.y = enterBtn.y;
game.addChild(enterTxt);
// --- BET BUTTONS (for each TV horse) ---
var betBtns = [];
var betBtnTxts = [];
for (var i = 0; i < NUM_LANES; i++) {
var betAreaWidth = BET_AREA_WIDTH;
var btn = LK.getAsset('betBtn' + i, {
anchorX: 0.5,
anchorY: 0.5,
width: 120 * 1.32,
height: 60 * 1.32,
color: 0xe67e22,
shape: 'box',
// Place bet buttons in the center of the new bet area, right after the finish line
x: TV_LEFT + RACE_LENGTH + betAreaWidth / 2,
y: TV_TOP + i * LANE_HEIGHT + LANE_HEIGHT / 2
});
game.addChild(btn);
betBtns.push(btn);
// Use bet image as button instead of text
var betImgBtn = LK.getAsset('bet', {
anchorX: 0.5,
anchorY: 0.5,
// Increase by 15%
width: 90 * 1.32 * 1.15,
height: 90 * 1.32 * 1.15,
x: btn.x,
y: btn.y
});
game.addChild(betImgBtn);
betBtnTxts.push(betImgBtn);
}
// --- HORSE ASSET INIT (for all horses) ---
for (var i = 0; i < NUM_HORSES; i++) {
var h = new Horse();
h.setHorse(i, i + 1, false);
// Random stats for TV horses
var s = 40 + Math.floor(Math.random() * 61); // 40-100
var st = 40 + Math.floor(Math.random() * 61);
var l = 40 + Math.floor(Math.random() * 61);
h.setStats(s, st, l);
horses.push(h);
}
// --- PLAYER HORSE ANIMATION FRAMES ---
var playerHorseFrames = [];
var playerHorseFrameNames = ['ourhorse', 'ourhorse2', 'ourhorse3', 'ourhorse4', 'ourhorse5'];
for (var i = 0; i < playerHorseFrameNames.length; i++) {
// Use ourhorse textures as animation frames for player horse
// Adjust width +5%, height -5%
playerHorseFrames.push(LK.getAsset(playerHorseFrameNames[i], {
anchorX: 0.5,
anchorY: 0.5,
width: 100 * 1.05,
height: 100 * 0.95
}));
}
// --- PLAYER HORSE INIT ---
playerHorse = new Horse();
playerHorse.setHorse(NUM_HORSES - 1, playerHorseNum, true);
playerHorse.setStats(playerStats.speed, playerStats.stamina, playerStats.luck);
// Add animation state
playerHorse.animFrame = 0;
playerHorse.animTick = 0;
playerHorse.animSprites = [];
// Remove default body
if (playerHorse.body && playerHorse.body.parent === playerHorse) {
playerHorse.removeChild(playerHorse.body);
}
// Add all frames, only show the first
for (var i = 0; i < playerHorseFrames.length; i++) {
var sprite = playerHorseFrames[i];
sprite.visible = i === 0;
playerHorse.addChild(sprite);
playerHorse.animSprites.push(sprite);
}
playerHorse.updateAnimation = function () {
this.animTick++;
if (this.animTick % 12 === 0) {
// Change frame every 12 ticks (~5 FPS)
this.animSprites[this.animFrame].visible = false;
this.animFrame = (this.animFrame + 1) % this.animSprites.length;
this.animSprites[this.animFrame].visible = true;
}
};
// Move player horse up to match new area
playerHorse.x = PLAYER_HORSE_X;
playerHorse.y = PLAYER_HORSE_Y + 100;
game.addChild(playerHorse);
// --- FUNCTIONS ---
function updateGoldText() {
goldTxt.setText("Gold: " + gold);
}
function updatePlayerStatsText() {
playerStatsTxt.setText("Speed: " + playerHorse.speed + "\nStamina: " + playerHorse.stamina + "\nLuck: " + playerHorse.luck);
}
function showMessage(msg, duration) {
messageTxt.setText(msg);
messageTxt.visible = true;
if (messageTimeout) {
LK.clearTimeout(messageTimeout);
}
if (duration) {
messageTimeout = LK.setTimeout(function () {
messageTxt.visible = false;
}, duration);
}
}
function hideMessage() {
messageTxt.visible = false;
if (messageTimeout) {
LK.clearTimeout(messageTimeout);
}
}
function saveState() {
// Persist gold, player stats and horse number
storage.gold = gold;
storage.playerStats = {
speed: playerHorse.speed,
stamina: playerHorse.stamina,
luck: playerHorse.luck
};
storage.playerHorseNum = playerHorseNum;
}
// Pick 6 random horses for TV race (optionally include player horse)
function pickRaceHorses(includePlayer) {
var pool = [];
for (var i = 0; i < NUM_HORSES - 1; i++) {
pool.push(i);
} // 0-18
if (includePlayer) {
pool.push(NUM_HORSES - 1);
} // 19
var selected = [];
while (selected.length < NUM_LANES) {
var idx = Math.floor(Math.random() * pool.length);
selected.push(pool[idx]);
pool.splice(idx, 1);
}
return selected;
}
// Start a new race (after betting phase)
function startRace() {
raceInProgress = true;
raceResults = [];
raceTimer = 0;
raceMessage = '';
hideMessage();
// Reset TV horses
for (var i = 0; i < tvHorses.length; i++) {
game.removeChild(tvHorses[i]);
}
tvHorses = [];
// Pick horses
var includePlayer = playerEntered;
var selected;
// Use the stored selected horses from betting phase if available
if (game.selectedRaceHorseIndices && Array.isArray(game.selectedRaceHorseIndices) && game.selectedRaceHorseIndices.length === NUM_LANES) {
selected = game.selectedRaceHorseIndices.slice();
} else {
selected = pickRaceHorses(includePlayer);
}
for (var i = 0; i < NUM_LANES; i++) {
var hidx = selected[i];
var h;
// If playerEntered, always put playerHorse in the last lane (bottom)
if (playerEntered && i === NUM_LANES - 1) {
h = playerHorse;
h.setHorse(NUM_HORSES - 1, playerHorseNum, true);
h.setStats(playerHorse.speed, playerHorse.stamina, playerHorse.luck);
} else if (hidx === NUM_HORSES - 1 && !playerEntered) {
// Defensive: if player not entered but random picked player horse, use a TV horse instead
// Pick a non-player horse from horses not already in tvHorses
for (var alt = 0; alt < NUM_HORSES - 1; alt++) {
var alreadyUsed = false;
for (var t = 0; t < tvHorses.length; t++) {
if (tvHorses[t] === horses[alt]) {
alreadyUsed = true;
}
}
if (!alreadyUsed) {
hidx = alt;
break;
}
}
h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
} else if (hidx === NUM_HORSES - 1 && playerEntered) {
// If playerEntered, skip this slot (playerHorse will be added at last lane)
// Instead, pick a non-player horse
for (var alt = 0; alt < NUM_HORSES - 1; alt++) {
var alreadyUsed = false;
for (var t = 0; t < tvHorses.length; t++) {
if (tvHorses[t] === horses[alt]) {
alreadyUsed = true;
}
}
if (!alreadyUsed) {
hidx = alt;
break;
}
}
h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
} else {
h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
}
h.lane = i;
h.resetRace();
h.x = TV_LEFT + 40;
h.y = TV_TOP + i * LANE_HEIGHT + LANE_HEIGHT / 2;
h.scaleX = 1.1;
h.scaleY = 1.1;
game.addChild(h);
tvHorses.push(h);
}
// Set bet buttons
for (var i = 0; i < NUM_LANES; i++) {
betBtns[i].visible = false;
betBtnTxts[i].visible = false;
}
// Start race after short countdown
raceCountdown = 60; // 1 second
showMessage("Race starting!", 1000);
}
// End race, show results, pay out
function endRace() {
raceInProgress = false;
var winner = raceResults[0];
var winnerNum = winner.number;
var playerWin = false;
var playerPlace = -1;
// Find the actual TV horse object the player bet on (by unique number)
var betHorseObj = null;
if (betHorseUniqueNum !== null) {
for (var i = 0; i < tvHorses.length; i++) {
if (tvHorses[i].number === betHorseUniqueNum) {
betHorseObj = tvHorses[i];
break;
}
}
}
for (var i = 0; i < raceResults.length; i++) {
if (raceResults[i].isPlayer) {
playerPlace = i + 1;
break;
}
}
var msg = "Winner: #" + winnerNum;
if (betHorseUniqueNum !== null) {
// Find the placement of the bet horse in the results
var betPlace = -1;
if (betHorseObj) {
for (var i = 0; i < raceResults.length; i++) {
if (raceResults[i] === betHorseObj) {
betPlace = i + 1;
break;
}
}
}
if (betPlace === 1) {
gold += 50;
msg += "\nYour bet WON! (+50)";
playerWin = true;
} else if (betPlace === 2) {
gold += 30;
msg += "\nYour bet placed 2nd! (+30)";
playerWin = true;
} else if (betPlace === 3) {
gold += 10;
msg += "\nYour bet placed 3rd! (+10)";
playerWin = true;
} else {
msg += "\nYou lost your bet.";
}
}
if (playerEntered) {
if (playerPlace === 1) {
gold += PLAYER_PRIZE;
msg += "\nYour horse WON! (+100)";
} else if (playerPlace > 0) {
msg += "\nYour horse placed #" + playerPlace;
} else {
msg += "\nYour horse did not finish.";
}
}
updateGoldText();
saveState();
showMessage(msg, 2500);
// Move player horse back to player area after race ends
if (playerHorse && playerHorse.parent === game) {
// Remove from TV area if present
for (var i = 0; i < tvHorses.length; i++) {
if (tvHorses[i] === playerHorse) {
game.removeChild(playerHorse);
break;
}
}
// Reset position to player area
playerHorse.x = PLAYER_HORSE_X;
playerHorse.y = PLAYER_HORSE_Y + 100;
game.addChild(playerHorse);
}
// After a delay, start new betting phase
LK.setTimeout(function () {
startBettingPhase();
}, 2500);
}
// Start betting phase: show 6 horses, allow bet/enter
function startBettingPhase() {
raceInProgress = false;
playerEntered = false;
betHorseNum = null;
betHorseUniqueNum = null;
raceResults = [];
hideMessage();
// Remove TV horses
for (var i = 0; i < tvHorses.length; i++) {
game.removeChild(tvHorses[i]);
}
tvHorses = [];
// Always re-add player horse to player area after race
if (playerHorse && playerHorse.parent !== game) {
playerHorse.x = PLAYER_HORSE_X;
playerHorse.y = PLAYER_HORSE_Y + 100;
game.addChild(playerHorse);
}
// Pick 6 horses (player horse not included yet)
var selected = pickRaceHorses(false);
// Store selected horses for this betting phase, so they are reused for the race
game.selectedRaceHorseIndices = selected.slice();
for (var i = 0; i < NUM_LANES; i++) {
var hidx = selected[i];
var h = horses[hidx];
h.setHorse(hidx, hidx + 1, false);
h.lane = i;
h.resetRace();
h.x = TV_LEFT + 40;
h.y = TV_TOP + i * LANE_HEIGHT + LANE_HEIGHT / 2;
h.scaleX = 1.1;
h.scaleY = 1.1;
game.addChild(h);
tvHorses.push(h);
}
// Set bet buttons
for (var i = 0; i < NUM_LANES; i++) {
betBtns[i].visible = true;
betBtnTxts[i].visible = true;
// No text to set, betBtnTxts now holds image buttons
}
betTxt.setText("Pick a horse to bet on, or enter your own!");
}
// --- BUTTON HANDLERS ---
// Bet buttons
for (var i = 0; i < NUM_LANES; i++) {
(function (idx) {
// Set handler on both the colored box and the image button for full compatibility
betBtns[idx].down = function (x, y, obj) {
if (raceInProgress || betHorseNum !== null || gold < BET_COST) {
return;
}
if (!tvHorses[idx]) {
return;
} // Defensive: avoid undefined
betHorseNum = idx; // index in tvHorses for UI, but not for payout
betHorseUniqueNum = tvHorses[idx].number; // store the actual horse number for payout
gold -= BET_COST;
updateGoldText();
saveState();
betTxt.setText("Bet placed on #" + betHorseUniqueNum + ". Waiting for race...");
for (var j = 0; j < NUM_LANES; j++) {
betBtns[j].visible = false;
betBtnTxts[j].visible = false;
}
// Start race after short delay
LK.setTimeout(function () {
startRace();
}, 1000);
};
// Also set handler on the image button
betBtnTxts[idx].down = betBtns[idx].down;
})(i);
}
// Upgrade button
upgradeBtn.down = function (x, y, obj) {
if (raceInProgress) {
return;
}
if (gold < UPGRADE_COST) {
showMessage("Not enough gold!", 1200);
return;
}
gold -= UPGRADE_COST;
updateGoldText();
// Randomly pick stat to upgrade
var statNames = ['speed', 'stamina', 'luck'];
var idx = Math.floor(Math.random() * 3);
var stat = statNames[idx];
playerHorse[stat] += 1 + Math.floor(Math.random() * 3); // +1~3
updatePlayerStatsText();
saveState();
showMessage("Upgraded " + stat + "!", 1000);
};
// Enter race button
enterBtn.down = function (x, y, obj) {
if (raceInProgress || playerEntered) {
return;
}
// Only allow if player's horse is not already in TV horses
for (var i = 0; i < tvHorses.length; i++) {
if (tvHorses[i].isPlayer) {
showMessage("Already entered!", 1000);
return;
}
}
playerEntered = true;
betTxt.setText("You entered your horse! Waiting for race...");
for (var j = 0; j < NUM_LANES; j++) {
betBtns[j].visible = false;
betBtnTxts[j].visible = false;
}
// Start race after short delay
LK.setTimeout(function () {
startRace();
}, 1000);
};
// --- WORK AREA & BUTTON (for 0 gold) ---
var workArea = null;
var workBtn = null;
var workBtnTxt = null;
var workTimerTxt = null;
var workTimer = 0;
var workActive = false;
var workTimeout = null;
// --- GAME UPDATE LOOP ---
game.update = function () {
// Animate player horse if present
if (playerHorse && playerHorse.animSprites && playerHorse.animSprites.length > 0) {
playerHorse.updateAnimation();
}
// Show/hide work area if gold is 0
if (gold === 0 && !workArea) {
// Create work area on the right, symmetric to playerArea
workArea = LK.getAsset('playerArea', {
anchorX: 0,
anchorY: 0,
width: 500,
height: 700,
color: 0x222244,
shape: 'box',
x: 2048 - 60 - 500,
y: PLAYER_HORSE_Y - 60
});
game.addChild(workArea);
// Work button
workBtn = LK.getAsset('upgradeBtn', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 80,
color: 0xf368bd,
shape: 'box',
x: workArea.x + 370,
y: workArea.y + 600
});
game.addChild(workBtn);
workBtnTxt = new Text2('Work!', {
size: 36,
fill: "#fff"
});
workBtnTxt.anchor.set(0.5, 0.5);
workBtnTxt.x = workBtn.x;
workBtnTxt.y = workBtn.y;
game.addChild(workBtnTxt);
workTimerTxt = new Text2('', {
size: 40,
fill: "#fff"
});
workTimerTxt.anchor.set(0.5, 0.5);
workTimerTxt.x = workBtn.x;
workTimerTxt.y = workBtn.y + 70;
workTimerTxt.visible = false;
game.addChild(workTimerTxt);
workBtn.down = function (x, y, obj) {
if (workActive) {
return;
}
workActive = true;
workBtn.visible = false;
workBtnTxt.visible = false;
workTimer = 15;
workTimerTxt.visible = true;
workTimerTxt.setText("15");
// Start countdown
workTimeout = LK.setInterval(function () {
workTimer--;
if (workTimer > 0) {
workTimerTxt.setText(workTimer + "");
} else {
LK.clearInterval(workTimeout);
workTimeout = null;
workTimerTxt.visible = false;
gold += 10;
updateGoldText();
saveState();
workActive = false;
// Remove work area and button
if (workArea && workArea.parent === game) {
game.removeChild(workArea);
}
if (workBtn && workBtn.parent === game) {
game.removeChild(workBtn);
}
if (workBtnTxt && workBtnTxt.parent === game) {
game.removeChild(workBtnTxt);
}
if (workTimerTxt && workTimerTxt.parent === game) {
game.removeChild(workTimerTxt);
}
workArea = null;
workBtn = null;
workBtnTxt = null;
workTimerTxt = null;
}
}, 1000);
};
}
// Remove work area if gold > 0
if (gold > 0 && workArea) {
if (workTimeout) {
LK.clearInterval(workTimeout);
workTimeout = null;
}
if (workArea && workArea.parent === game) {
game.removeChild(workArea);
}
if (workBtn && workBtn.parent === game) {
game.removeChild(workBtn);
}
if (workBtnTxt && workBtnTxt.parent === game) {
game.removeChild(workBtnTxt);
}
if (workTimerTxt && workTimerTxt.parent === game) {
game.removeChild(workTimerTxt);
}
workArea = null;
workBtn = null;
workBtnTxt = null;
workTimerTxt = null;
workActive = false;
}
// Update UI
updateGoldText();
updatePlayerStatsText();
// Race countdown
if (raceCountdown > 0) {
raceCountdown--;
if (raceCountdown === 0) {
showMessage("Go!", 600);
}
return;
}
// Race in progress
if (raceInProgress) {
var finishedCount = 0;
for (var i = 0; i < tvHorses.length; i++) {
var h = tvHorses[i];
if (h.finished) {
finishedCount++;
continue;
}
// Calculate move
var baseSpeed = h.speed / 100 * 8 + 2; // 2-10 px per tick
var luckFactor = 1 + (Math.random() - 0.5) * (h.luck / 200); // up to +/- luck/200
var move = baseSpeed * luckFactor;
if (h.energy > 0) {
h.energy -= 0.04;
} else {
move *= 0.6; // tired
}
h.progress += move / RACE_LENGTH;
if (h.progress >= 1) {
h.progress = 1;
h.finished = true;
h.finishTime = raceTimer;
raceResults.push(h);
}
h.x = TV_LEFT + 40 + h.progress * (RACE_LENGTH - 80);
}
raceTimer++;
// If all finished, end race
if (raceResults.length === tvHorses.length) {
endRace();
}
}
};
// --- INITIALIZE ---
updateGoldText();
updatePlayerStatsText();
startBettingPhase();