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