Spaces:
Running
Running
I want to create a simple horse breeding, training, and racing game. The game loop is to buy horses and breed them. Each breeding has a chance of getting a trait, positive or negative, from the parents. Horses have four stats: - Speed - Endurance - Beauty - Fertility In addition, a horse has a gender. These stats have a genetic value and a modified value. The genetic value is what can be obtained by breeding. In addition, traits can modify stats. For example, a "Slow but steady" trait can decrease speed but increase endurance. Training can be done three times between races. Training has a chance of injuring the horse, in which case they cannot participate in the next race. In races, only speed and endurance are used. During the race, each horse has a temporary endurance and speed value that starts at the endurance and speed values. The temporary endurance is decreased by the temporary speed value each lap. When the temporary endurance reaches 50%, the speed reduces by 25%. When the temporary endurance reaches 25%, the speed reduces by another 25%, and when the temporary endurance reaches <10%, the speed reduces to 10% too, and no further reduction in endurance or speed happens until the end of the race. For breeding, each horse has a fertility that starts at 100% and decreases by 25% each time it is bred. The chance that its genetic value and traits are passed on depends on the remaining fertility. When fertility reaches 0%, the horse can no longer breed. Beauty is not required for the first version of the game. The player starts with $10K and must buy a horse on a market. There will be three horses each round that the player can purchase, of various values. At least one horse is always available below the player's remaining money. The cost of a horse should be a reflection of its stats. The player can earn more money by winning races or selling offspring or breeding services. The value of offspring and breeding services depends on the stats. The value should in total yield around 1/4 of the value of the horse. When choosing breeding, a player can also breed with their own horses if they have eligible male and female hoses. If the player has a female horse only, they must pay someone to mate, around 1/4 of the male horse's value, but the player gets offspring in return. If the player has a male horse only, they immediately get paid around 1/4 of the value of their horse. - Initial Deployment
8ceaa24
verified
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Stable Masters - Horse Breeding & Racing</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @keyframes raceHorse { | |
| 0% { transform: translateX(0); } | |
| 100% { transform: translateX(calc(100% - 100px)); } | |
| } | |
| .race-track { | |
| height: 60px; | |
| background-image: repeating-linear-gradient( | |
| 90deg, | |
| #f0fdf4, | |
| #f0fdf4 40px, | |
| #bbf7d0 40px, | |
| #bbf7d0 80px | |
| ); | |
| position: relative; | |
| } | |
| .horse-runner { | |
| position: absolute; | |
| left: 0; | |
| animation: raceHorse linear forwards; | |
| height: 50px; | |
| width: 80px; | |
| background-size: contain; | |
| background-repeat: no-repeat; | |
| filter: drop-shadow(0 0 2px rgba(0,0,0,0.5)); | |
| } | |
| .stats-bar { | |
| height: 8px; | |
| border-radius: 4px; | |
| } | |
| /* Horse gender colors */ | |
| .horse-male { | |
| background-color: #93c5fd; | |
| } | |
| .horse-female { | |
| background-color: #f9a8d4; | |
| } | |
| /* Stat colors */ | |
| .stat-speed { | |
| background-color: #ef4444; | |
| } | |
| .stat-endurance { | |
| background-color: #3b82f6; | |
| } | |
| .stat-beauty { | |
| background-color: #8b5cf6; | |
| } | |
| .stat-fertility { | |
| background-color: #10b981; | |
| } | |
| /* Modal styles */ | |
| .game-modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0,0,0,0.7); | |
| z-index: 1000; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .modal-content { | |
| background-color: white; | |
| border-radius: 0.5rem; | |
| padding: 1.5rem; | |
| max-width: 90%; | |
| max-height: 90vh; | |
| overflow-y: auto; | |
| } | |
| /* Tooltip styles */ | |
| .tooltip { | |
| position: relative; | |
| display: inline-block; | |
| } | |
| .tooltip .tooltip-text { | |
| visibility: hidden; | |
| width: 200px; | |
| background-color: #333; | |
| color: #fff; | |
| text-align: center; | |
| border-radius: 6px; | |
| padding: 5px; | |
| position: absolute; | |
| z-index: 1; | |
| bottom: 125%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| .tooltip:hover .tooltip-text { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-green-50 min-h-screen"> | |
| <div id="game-container" class="container mx-auto px-4 py-8"> | |
| <!-- Game Header --> | |
| <header class="mb-8 text-center"> | |
| <h1 class="text-4xl font-bold text-green-800 mb-2">Stable Masters</h1> | |
| <p class="text-lg text-green-600 mb-4">Breed. Train. Race. Win.</p> | |
| <div class="bg-white rounded-lg shadow-md p-4 flex justify-between items-center mb-6"> | |
| <div id="money-display" class="text-2xl font-bold text-yellow-600"> | |
| <i class="fas fa-coins mr-2"></i> $10,000 | |
| </div> | |
| <div id="day-display" class="text-xl font-semibold text-gray-700"> | |
| <i class="far fa-calendar-alt mr-2"></i> Day 1 | |
| </div> | |
| <button id="next-day-btn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded transition"> | |
| <i class="fas fa-calendar-plus mr-2"></i>Next Day | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Game Area --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Left Panel - Stable --> | |
| <div id="stable-panel" class="bg-white rounded-lg shadow-md p-4"> | |
| <h2 class="text-2xl font-bold text-green-800 mb-4 border-b-2 pb-2 flex justify-between"> | |
| <span><i class="fas fa-horse-head mr-2"></i>Your Stable</span> | |
| <span id="stable-count" class="text-gray-600">0 horses</span> | |
| </h2> | |
| <div id="stable-horses" class="space-y-4"> | |
| <p class="text-gray-500 italic">No horses in your stable yet. Visit the market to buy your first horse!</p> | |
| </div> | |
| </div> | |
| <!-- Center Panel - Actions --> | |
| <div id="actions-panel" class="bg-white rounded-lg shadow-md p-4"> | |
| <h2 class="text-2xl font-bold text-green-800 mb-4 border-b-2 pb-2"> | |
| <i class="fas fa-tasks mr-2"></i>Actions | |
| </h2> | |
| <div class="space-y-4"> | |
| <button id="market-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded transition flex items-center justify-between"> | |
| <span><i class="fas fa-shopping-cart mr-2"></i>Visit Market</span> | |
| <i class="fas fa-arrow-right"></i> | |
| </button> | |
| <button id="breed-btn" disabled class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded transition flex items-center justify-between opacity-50 cursor-not-allowed"> | |
| <span><i class="fas fa-dna mr-2"></i>Breed Horses</span> | |
| <i class="fas fa-arrow-right"></i> | |
| </button> | |
| <button id="train-btn" disabled class="w-full bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-3 px-4 rounded transition flex items-center justify-between opacity-50 cursor-not-allowed"> | |
| <span><i class="fas fa-dumbbell mr-2"></i>Train Horses</span> | |
| <i class="fas fa-arrow-right"></i> | |
| </button> | |
| <button id="race-btn" disabled class="w-full bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-4 rounded transition flex items-center justify-between opacity-50 cursor-not-allowed"> | |
| <span><i class="fas fa-flag-checkered mr-2"></i>Enter Race</span> | |
| <i class="fas fa-arrow-right"></i> | |
| </button> | |
| <button id="sell-btn" disabled class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded transition flex items-center justify-between opacity-50 cursor-not-allowed"> | |
| <span><i class="fas fa-hand-holding-usd mr-2"></i>Sell Horses</span> | |
| <i class="fas fa-arrow-right"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Right Panel - Racing & Events --> | |
| <div id="events-panel" class="bg-white rounded-lg shadow-md p-4"> | |
| <h2 class="text-2xl font-bold text-green-800 mb-4 border-b-2 pb-2"> | |
| <i class="fas fa-trophy mr-2"></i>Racing & Events | |
| </h2> | |
| <div class="space-y-6"> | |
| <div id="upcoming-races"> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-2">Upcoming Races</h3> | |
| <div class="bg-gray-100 p-3 rounded-lg"> | |
| <p class="text-gray-700">No races scheduled yet.</p> | |
| </div> | |
| </div> | |
| <div id="race-results" class="hidden"> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-2">Race Results</h3> | |
| <div class="bg-gray-100 p-3 rounded-lg"> | |
| <div id="race-track" class="race-track mb-4"> | |
| <!-- Horses will be added here dynamically --> | |
| </div> | |
| <div id="race-results-list" class="space-y-2"> | |
| <!-- Results will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="game-messages"> | |
| <h3 class="text-lg font-semibold text-gray-700 mb-2">Game Messages</h3> | |
| <div class="bg-gray-100 p-3 rounded-lg h-48 overflow-y-auto"> | |
| <p class="text-gray-700">Welcome to Stable Masters! Buy your first horse to get started.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Market Modal --> | |
| <div id="market-modal" class="game-modal"> | |
| <div class="modal-content w-11/12 lg:w-3/4 bg-white"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-2xl font-bold text-green-800"> | |
| <i class="fas fa-shopping-cart mr-2"></i>Horse Market | |
| </h2> | |
| <button id="close-market-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times text-2xl"></i> | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4" id="market-horses"> | |
| <!-- Market horses will be added here --> | |
| </div> | |
| <div class="text-center text-gray-500 italic" id="no-horses-msg"> | |
| Loading horses available for purchase... | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Breeding Modal --> | |
| <div id="breed-modal" class="game-modal"> | |
| <div class="modal-content w-11/12 lg:w-3/4 bg-white"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-2xl font-bold text-purple-800"> | |
| <i class="fas fa-dna mr-2"></i>Breeding Center | |
| </h2> | |
| <button id="close-breed-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times text-2xl"></i> | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> | |
| <div id="sires-list" class="border-2 border-gray-200 rounded-lg p-4"> | |
| <h3 class="text-lg font-semibold text-purple-700 mb-3">Select Sire (Male)</h3> | |
| <div id="sire-options" class="space-y-3"> | |
| <!-- Male horses will be added here --> | |
| </div> | |
| </div> | |
| <div id="dams-list" class="border-2 border-gray-200 rounded-lg p-4"> | |
| <h3 class="text-lg font-semibold text-purple-700 mb-3">Select Dam (Female)</h3> | |
| <div id="dam-options" class="space-y-3"> | |
| <!-- Female horses will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="breeding-options" class="hidden"> | |
| <div class="bg-purple-100 p-4 rounded-lg mb-4"> | |
| <h3 class="text-lg font-semibold text-purple-800 mb-2">Breeding Preview</h3> | |
| <div id="breeding-preview" class="grid grid-cols-1 md:grid-cols-2 gap-4"> | |
| <!-- Breeding preview will be shown here --> | |
| </div> | |
| </div> | |
| <button id="confirm-breed-btn" class="w-full bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-4 rounded transition"> | |
| <i class="fas fa-dna mr-2"></i>Confirm Breeding | |
| </button> | |
| </div> | |
| <div id="external-breeding" class="hidden"> | |
| <div class="bg-yellow-100 p-4 rounded-lg"> | |
| <h3 class="text-lg font-semibold text-yellow-800 mb-2">External Breeding Options</h3> | |
| <div id="external-options" class="space-y-3"> | |
| <!-- External breeding options will be shown here --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Training Modal --> | |
| <div id="train-modal" class="game-modal"> | |
| <div class="modal-content w-11/12 lg:w-3/4 bg-white"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-2xl font-bold text-yellow-800"> | |
| <i class="fas fa-dumbbell mr-2"></i>Training Grounds | |
| </h2> | |
| <button id="close-train-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times text-2xl"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <p class="text-gray-700">Select a horse to train. Each training session has a chance to increase stats but also a risk of injury.</p> | |
| <p class="text-sm text-gray-500">Each horse can be trained 3 times between races.</p> | |
| </div> | |
| <div id="trainable-horses" class="space-y-3"> | |
| <!-- Trainable horses will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Sell Modal --> | |
| <div id="sell-modal" class="game-modal"> | |
| <div class="modal-content w-11/12 lg:w-3/4 bg-white"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-2xl font-bold text-indigo-800"> | |
| <i class="fas fa-hand-holding-usd mr-2"></i>Sell Horses | |
| </h2> | |
| <button id="close-sell-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times text-2xl"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <p class="text-gray-700">Select horses to sell for cash or offer breeding services for male horses.</p> | |
| </div> | |
| <div id="sellable-horses" class="space-y-3"> | |
| <!-- Sellable horses will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Race Modal --> | |
| <div id="race-modal" class="game-modal"> | |
| <div class="modal-content w-11/12 lg:w-3/4 bg-white"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-2xl font-bold text-red-800"> | |
| <i class="fas fa-flag-checkered mr-2"></i>Race Entry | |
| </h2> | |
| <button id="close-race-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times text-2xl"></i> | |
| </button> | |
| </div> | |
| <div class="mb-4"> | |
| <p class="text-gray-700">Select a horse to enter in today's race. Winning earns prize money based on the horse's performance.</p> | |
| <p class="text-sm text-gray-500">Injured horses cannot race. Check training history for injury status.</p> | |
| </div> | |
| <div id="racable-horses" class="space-y-3"> | |
| <!-- Racable horses will be added here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Notification Modal --> | |
| <div id="notification-modal" class="game-modal"> | |
| <div class="modal-content w-11/12 sm:w-96 bg-white"> | |
| <div class="flex justify-end mb-2"> | |
| <button id="close-notification-btn" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div id="notification-content" class="text-center p-4"> | |
| <!-- Notification content will be added here --> | |
| </div> | |
| <button id="notification-ok-btn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded transition mt-2"> | |
| OK | |
| </button> | |
| </div> | |
| </div> | |
| <script> | |
| // Game state | |
| const gameState = { | |
| day: 1, | |
| money: 10000, | |
| stable: [], | |
| market: [], | |
| raceInProgress: false, | |
| raceResults: [], | |
| notifications: [], | |
| lastAction: null, | |
| trainedHorses: new Set(), | |
| injuredHorses: new Set() | |
| }; | |
| // Horse traits | |
| const horseTraits = [ | |
| { name: "Fast Starter", effect: { speed: 5, endurance: -2 } }, | |
| { name: "Slow but Steady", effect: { speed: -3, endurance: 5 } }, | |
| { name: "High Stamina", effect: { endurance: 6 } }, | |
| { name: "Sprinter", effect: { speed: 8, endurance: -4 } }, | |
| { name: "Fragile", effect: { endurance: -4 } }, | |
| { name: "Strong", effect: { endurance: 3, speed: 2 } }, | |
| { name: "Lazy", effect: { speed: -3, endurance: -2 } }, | |
| { name: "Energetic", effect: { speed: 3, endurance: 3 } }, | |
| { name: "Proud", effect: { beauty: 5 } }, | |
| { name: "Fertile Bloodline", effect: { fertility: 15 } }, | |
| { name: "Unlucky", effect: { speed: -2, endurance: -2 } } | |
| ]; | |
| // DOM elements | |
| const elements = { | |
| moneyDisplay: document.getElementById('money-display'), | |
| dayDisplay: document.getElementById('day-display'), | |
| stableHorses: document.getElementById('stable-horses'), | |
| stableCount: document.getElementById('stable-count'), | |
| nextDayBtn: document.getElementById('next-day-btn'), | |
| marketBtn: document.getElementById('market-btn'), | |
| breedBtn: document.getElementById('breed-btn'), | |
| trainBtn: document.getElementById('train-btn'), | |
| raceBtn: document.getElementById('race-btn'), | |
| sellBtn: document.getElementById('sell-btn'), | |
| marketModal: document.getElementById('market-modal'), | |
| breedModal: document.getElementById('breed-modal'), | |
| trainModal: document.getElementById('train-modal'), | |
| sellModal: document.getElementById('sell-modal'), | |
| raceModal: document.getElementById('race-modal'), | |
| notificationModal: document.getElementById('notification-modal'), | |
| marketHorses: document.getElementById('market-horses'), | |
| sireOptions: document.getElementById('sire-options'), | |
| damOptions: document.getElementById('dam-options'), | |
| breedingOptions: document.getElementById('breeding-options'), | |
| breedingPreview: document.getElementById('breeding-preview'), | |
| externalBreeding: document.getElementById('external-breeding'), | |
| externalOptions: document.getElementById('external-options'), | |
| trainableHorses: document.getElementById('trainable-horses'), | |
| sellableHorses: document.getElementById('sellable-horses'), | |
| racableHorses: document.getElementById('racable-horses'), | |
| notificationContent: document.getElementById('notification-content'), | |
| raceTrack: document.getElementById('race-track'), | |
| raceResults: document.getElementById('race-results'), | |
| raceResultsList: document.getElementById('race-results-list'), | |
| upcomingRaces: document.getElementById('upcoming-races'), | |
| gameMessages: document.querySelector('#game-messages div') | |
| }; | |
| // Event listeners | |
| document.getElementById('close-market-btn').addEventListener('click', () => elements.marketModal.style.display = 'none'); | |
| document.getElementById('close-breed-btn').addEventListener('click', () => elements.breedModal.style.display = 'none'); | |
| document.getElementById('close-train-btn').addEventListener('click', () => elements.trainModal.style.display = 'none'); | |
| document.getElementById('close-sell-btn').addEventListener('click', () => elements.sellModal.style.display = 'none'); | |
| document.getElementById('close-race-btn').addEventListener('click', () => elements.raceModal.style.display = 'none'); | |
| document.getElementById('close-notification-btn').addEventListener('click', () => elements.notificationModal.style.display = 'none'); | |
| document.getElementById('notification-ok-btn').addEventListener('click', () => elements.notificationModal.style.display = 'none'); | |
| elements.nextDayBtn.addEventListener('click', nextDay); | |
| elements.marketBtn.addEventListener('click', openMarket); | |
| elements.breedBtn.addEventListener('click', openBreeding); | |
| elements.trainBtn.addEventListener('click', openTraining); | |
| elements.sellBtn.addEventListener('click', openSell); | |
| elements.raceBtn.addEventListener('click', openRace); | |
| // Initialize game | |
| initGame(); | |
| function initGame() { | |
| updateUI(); | |
| generateMarketHorses(); | |
| addGameMessage("Welcome to Stable Masters! Buy your first horse to get started."); | |
| } | |
| function updateUI() { | |
| // Update money and day displays | |
| elements.moneyDisplay.innerHTML = `<i class="fas fa-coins mr-2"></i> $${gameState.money.toLocaleString()}`; | |
| elements.dayDisplay.innerHTML = `<i class="far fa-calendar-alt mr-2"></i> Day ${gameState.day}`; | |
| // Update stable count | |
| const horseCount = gameState.stable.length; | |
| elements.stableCount.textContent = `${horseCount} ${horseCount === 1 ? 'horse' : 'horses'}`; | |
| // Enable/disable action buttons based on game state | |
| elements.breedBtn.disabled = !canBreed(); | |
| if (!elements.breedBtn.disabled) { | |
| elements.breedBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } else { | |
| elements.breedBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| } | |
| elements.trainBtn.disabled = gameState.stable.length === 0; | |
| if (!elements.trainBtn.disabled) { | |
| elements.trainBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } else { | |
| elements.trainBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| } | |
| elements.raceBtn.disabled = gameState.stable.length === 0; | |
| if (!elements.raceBtn.disabled) { | |
| elements.raceBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } else { | |
| elements.raceBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| } | |
| elements.sellBtn.disabled = gameState.stable.length === 0; | |
| if (!elements.sellBtn.disabled) { | |
| elements.sellBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } else { | |
| elements.sellBtn.classList.add('opacity-50', 'cursor-not-allowed'); | |
| } | |
| // Update upcoming races display | |
| updateUpcomingRaces(); | |
| } | |
| function updateStableDisplay() { | |
| elements.stableHorses.innerHTML = ''; | |
| if (gameState.stable.length === 0) { | |
| elements.stableHorses.innerHTML = '<p class="text-gray-500 italic">No horses in your stable yet. Visit the market to buy your first horse!</p>'; | |
| return; | |
| } | |
| gameState.stable.forEach((horse, index) => { | |
| const horseCard = document.createElement('div'); | |
| horseCard.className = 'bg-gray-50 rounded-lg p-3 border border-gray-200'; | |
| const genderClass = horse.gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = horse.gender === 'male' ? 'mars' : 'venus'; | |
| const injuredClass = gameState.injuredHorses.has(horse.id) ? 'bg-red-100 border-red-300' : ''; | |
| horseCard.className += ` ${injuredClass}`; | |
| const trainedTimes = horse.trainingCount || 0; | |
| const trainedIndicator = trainedTimes > 0 ? `<span class="text-xs bg-yellow-100 text-yellow-800 px-2 py-1 rounded">Trained ${trainedTimes}/3</span>` : ''; | |
| const injuredIndicator = gameState.injuredHorses.has(horse.id) ? `<span class="text-xs bg-red-100 text-red-800 px-2 py-1 rounded">Injured</span>` : ''; | |
| horseCard.innerHTML = ` | |
| <div class="flex justify-between items-start mb-2"> | |
| <h3 class="font-bold text-lg ${genderClass}"> | |
| <i class="fas fa-${genderIcon} mr-2"></i>${horse.name} | |
| </h3> | |
| <div class="flex space-x-2"> | |
| ${trainedIndicator} | |
| ${injuredIndicator} | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 mb-3"> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Speed</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-speed" style="width: ${horse.speed * 7}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.speed}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Endurance</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-endurance" style="width: ${horse.endurance * 7}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.endurance}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Beauty</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-beauty" style="width: ${horse.beauty * 7}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.beauty}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Fertility</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-fertility" style="width: ${horse.fertility * 1.5}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.fertility}%</span> | |
| </div> | |
| </div> | |
| </div> | |
| ${horse.traits.length > 0 ? | |
| `<div class="text-xs mb-2"> | |
| <span class="font-semibold">Traits:</span> ${horse.traits.map(t => t.name).join(', ')} | |
| </div>` : '' | |
| } | |
| <div class="text-xs text-gray-500"> | |
| Value: $${calculateHorseValue(horse).toLocaleString()} | |
| </div> | |
| `; | |
| elements.stableHorses.appendChild(horseCard); | |
| }); | |
| } | |
| function generateMarketHorses() { | |
| gameState.market = []; | |
| // Determine how many horses to generate (3, but ensure at least one is affordable) | |
| const count = 3; | |
| let hasAffordable = false; | |
| for (let i = 0; i < count; i++) { | |
| const affordable = !hasAffordable || Math.random() > 0.5; | |
| const horse = generateHorse(affordable ? gameState.money * 0.8 : gameState.money * 2); | |
| gameState.market.push(horse); | |
| if (horse.price <= gameState.money) { | |
| hasAffordable = true; | |
| } | |
| } | |
| // If we didn't get any affordable horses, replace the last one with an affordable one | |
| if (!hasAffordable) { | |
| const affordableHorse = generateHorse(gameState.money * 0.8); | |
| gameState.market[count - 1] = affordableHorse; | |
| } | |
| } | |
| function generateHorse(targetPrice) { | |
| const gender = Math.random() > 0.5 ? 'male' : 'female'; | |
| const name = generateHorseName(gender); | |
| // Base stats (5-15) | |
| const speed = 5 + Math.floor(Math.random() * 11); | |
| const endurance = 5 + Math.floor(Math.random() * 11); | |
| const beauty = 5 + Math.floor(Math.random() * 11); | |
| const fertility = 80 + Math.floor(Math.random() * 21); // 80-100% | |
| // Generate 0-2 random traits | |
| const traits = []; | |
| const traitCount = Math.floor(Math.random() * 3); | |
| for (let i = 0; i < traitCount; i++) { | |
| const traitIndex = Math.floor(Math.random() * horseTraits.length); | |
| traits.push({...horseTraits[traitIndex]}); | |
| } | |
| // Apply trait effects | |
| let effectiveSpeed = speed; | |
| let effectiveEndurance = endurance; | |
| let effectiveBeauty = beauty; | |
| let effectiveFertility = fertility; | |
| traits.forEach(trait => { | |
| effectiveSpeed += trait.effect.speed || 0; | |
| effectiveEndurance += trait.effect.endurance || 0; | |
| effectiveBeauty += trait.effect.beauty || 0; | |
| effectiveFertility += trait.effect.fertility || 0; | |
| }); | |
| // Clamp values | |
| effectiveSpeed = Math.max(1, Math.min(20, effectiveSpeed)); | |
| effectiveEndurance = Math.max(1, Math.min(20, effectiveEndurance)); | |
| effectiveBeauty = Math.max(1, Math.min(20, effectiveBeauty)); | |
| effectiveFertility = Math.max(0, Math.min(100, effectiveFertility)); | |
| // Calculate price based on stats | |
| let price = (effectiveSpeed * 500) + (effectiveEndurance * 400) + (effectiveBeauty * 300); | |
| // Adjust price for traits and fertility | |
| price *= 1 + (traits.length * 0.15); | |
| price *= 0.8 + (effectiveFertility / 100 * 0.4); | |
| // If target price is specified, adjust towards it while maintaining relative stat values | |
| if (targetPrice) { | |
| const adjustmentFactor = targetPrice / price * (0.8 + Math.random() * 0.4); | |
| price = Math.floor(price * adjustmentFactor); | |
| } | |
| // Add some randomness to final price | |
| price = Math.floor(price * (0.9 + Math.random() * 0.2)); | |
| return { | |
| id: Date.now() + Math.floor(Math.random() * 1000), | |
| name, | |
| gender, | |
| speed, | |
| endurance, | |
| beauty, | |
| fertility, | |
| traits, | |
| price, | |
| effectiveSpeed, | |
| effectiveEndurance, | |
| effectiveBeauty, | |
| effectiveFertility, | |
| parents: [], | |
| trainingCount: 0 | |
| }; | |
| } | |
| function generateHorseName(gender) { | |
| const prefixes = ['Thunder', 'Silver', 'Golden', 'Midnight', 'Royal', 'Wild', 'Swift', 'Black', 'White', 'Red']; | |
| const suffixes = ['Star', 'Shadow', 'Fire', 'Dream', 'Spirit', 'Wind', 'Storm', 'Mystery', 'Blaze', 'Dancer']; | |
| const maleSpecific = ['King', 'Prince', 'Duke', 'Sir', 'Knight']; | |
| const femaleSpecific = ['Queen', 'Princess', 'Lady', 'Grace', 'Rose']; | |
| let name; | |
| if (gender === 'male' && Math.random() > 0.5) { | |
| name = maleSpecific[Math.floor(Math.random() * maleSpecific.length)]; | |
| } else if (gender === 'female' && Math.random() > 0.5) { | |
| name = femaleSpecific[Math.floor(Math.random() * femaleSpecific.length)]; | |
| } else { | |
| name = prefixes[Math.floor(Math.random() * prefixes.length)] + ' ' + suffixes[Math.floor(Math.random() * suffixes.length)]; | |
| } | |
| return name; | |
| } | |
| function updateMarketDisplay() { | |
| elements.marketHorses.innerHTML = ''; | |
| if (gameState.market.length === 0) { | |
| elements.noHorsesMsg.classList.remove('hidden'); | |
| return; | |
| } | |
| gameState.market.forEach(horse => { | |
| const horseCard = document.createElement('div'); | |
| horseCard.className = 'bg-gray-50 rounded-lg p-4 border border-gray-300'; | |
| const genderClass = horse.gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = horse.gender === 'male' ? 'mars' : 'venus'; | |
| const canAfford = gameState.money >= horse.price; | |
| const buttonClass = canAfford ? 'bg-green-600 hover:bg-green-700' : 'bg-gray-400 cursor-not-allowed'; | |
| horseCard.innerHTML = ` | |
| <div class="flex justify-between items-start mb-3"> | |
| <h3 class="font-bold text-lg ${genderClass}"> | |
| <i class="fas fa-${genderIcon} mr-2"></i>${horse.name} | |
| </h3> | |
| <span class="font-bold text-yellow-600">$${horse.price.toLocaleString()}</span> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 mb-3"> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Speed</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-speed" style="width: ${horse.effectiveSpeed * 7}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveSpeed}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Endurance</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-endurance" style="width: ${horse.effectiveEndurance * 7}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveEndurance}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Beauty</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-beauty" style="width: ${horse.effectiveBeauty * 7}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveBeauty}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Fertility</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-fertility" style="width: ${horse.effectiveFertility * 1.5}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveFertility}%</span> | |
| </div> | |
| </div> | |
| </div> | |
| ${horse.traits.length > 0 ? | |
| `<div class="text-xs mb-3"> | |
| <span class="font-semibold">Traits:</span> ${horse.traits.map(t => t.name).join(', ')} | |
| </div>` : '' | |
| } | |
| <button class="w-full ${buttonClass} text-white font-bold py-2 px-4 rounded transition buy-horse-btn" | |
| data-horse-id="${horse.id}" ${canAfford ? '' : 'disabled'}> | |
| <i class="fas fa-shopping-cart mr-2"></i>Buy Horse | |
| </button> | |
| `; | |
| elements.marketHorses.appendChild(horseCard); | |
| }); | |
| // Add event listeners to buy buttons | |
| document.querySelectorAll('.buy-horse-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const horseId = parseInt(this.getAttribute('data-horse-id')); | |
| const horse = gameState.market.find(h => h.id === horseId); | |
| if (horse && gameState.money >= horse.price) { | |
| buyHorse(horse); | |
| } | |
| }); | |
| }); | |
| } | |
| function buyHorse(horse) { | |
| gameState.money -= horse.price; | |
| gameState.stable.push(horse); | |
| gameState.market = gameState.market.filter(h => h.id !== horse.id); | |
| addGameMessage(`You purchased ${horse.name} for $${horse.price.toLocaleString()}.`); | |
| // Enable breeding if possible | |
| if (canBreed()) { | |
| elements.breedBtn.disabled = false; | |
| elements.breedBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } | |
| updateUI(); | |
| updateStableDisplay(); | |
| elements.marketModal.style.display = 'none'; | |
| } | |
| function canBreed() { | |
| if (gameState.stable.length < 2) return false; | |
| const hasMale = gameState.stable.some(h => h.gender === 'male' && h.effectiveFertility > 0); | |
| const hasFemale = gameState.stable.some(h => h.gender === 'female' && h.effectiveFertility > 0); | |
| return hasMale && hasFemale; | |
| } | |
| function openMarket() { | |
| if (gameState.market.length === 0) { | |
| generateMarketHorses(); | |
| } | |
| updateMarketDisplay(); | |
| elements.marketModal.style.display = 'flex'; | |
| } | |
| function openBreeding() { | |
| if (!canBreed()) { | |
| showNotification("Breeding Error", "You need at least one male and one female horse with fertility remaining to breed."); | |
| return; | |
| } | |
| // Separate male and female horses with fertility | |
| const sires = gameState.stable.filter(h => h.gender === 'male' && h.effectiveFertility > 0); | |
| const dams = gameState.stable.filter(h => h.gender === 'female' && h.effectiveFertility > 0); | |
| // Populate sire options | |
| elements.sireOptions.innerHTML = ''; | |
| sires.forEach(sire => { | |
| const sireCard = createBreedingOptionCard(sire, 'sire'); | |
| elements.sireOptions.appendChild(sireCard); | |
| }); | |
| // Populate dam options | |
| elements.damOptions.innerHTML = ''; | |
| dams.forEach(dam => { | |
| const damCard = createBreedingOptionCard(dam, 'dam'); | |
| elements.damOptions.appendChild(damCard); | |
| }); | |
| // Hide breeding preview initially | |
| elements.breedingOptions.classList.add('hidden'); | |
| elements.externalBreeding.classList.add('hidden'); | |
| // Show breeding modal | |
| elements.breedModal.style.display = 'flex'; | |
| } | |
| function createBreedingOptionCard(horse, role) { | |
| const card = document.createElement('div'); | |
| card.className = 'bg-gray-50 rounded-lg p-3 border border-gray-300 flex items-center justify-between'; | |
| card.dataset.horseId = horse.id; | |
| card.dataset.role = role; | |
| const genderClass = horse.gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = horse.gender === 'male' ? 'mars' : 'venus'; | |
| card.innerHTML = ` | |
| <div class="flex items-center"> | |
| <div class="mr-3 ${genderClass} font-bold"> | |
| <i class="fas fa-${genderIcon}"></i> | |
| </div> | |
| <div> | |
| <div class="font-semibold">${horse.name}</div> | |
| <div class="text-xs"> | |
| <span class="font-semibold">Fertility:</span> ${horse.effectiveFertility}% | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-right"> | |
| <div class="font-semibold text-sm">$${calculateBreedingValue(horse).toLocaleString()}</div> | |
| <div class="text-xs text-gray-500">breed value</div> | |
| </div> | |
| `; | |
| card.addEventListener('click', function() { | |
| selectBreedingHorse(horse, role); | |
| }); | |
| return card; | |
| } | |
| function selectBreedingHorse(horse, role) { | |
| // Remove highlight from all options | |
| document.querySelectorAll(`#${role}-options > div`).forEach(opt => { | |
| opt.classList.remove('border-blue-500', 'border-2', 'bg-blue-50'); | |
| }); | |
| // Highlight selected option | |
| document.querySelector(`#${role}-options > div[data-horse-id="${horse.id}"]`) | |
| .classList.add('border-blue-500', 'border-2', 'bg-blue-50'); | |
| // Check if we have both sire and dam selected to show breeding preview | |
| const selectedSire = document.querySelector('#sire-options > div.border-blue-500'); | |
| const selectedDam = document.querySelector('#dam-options > div.border-blue-500'); | |
| if (selectedSire && selectedDam) { | |
| showBreedingPreview(selectedSire.dataset.horseId, selectedDam.dataset.horseId); | |
| } | |
| } | |
| function showBreedingPreview(sireId, damId) { | |
| const sire = gameState.stable.find(h => h.id === parseInt(sireId)); | |
| const dam = gameState.stable.find(h => h.id === parseInt(damId)); | |
| if (!sire || !dam) return; | |
| // Calculate potential offspring stats (average with randomness) | |
| const speed = Math.floor((sire.speed + dam.speed) / 2 + (Math.random() * 4 - 2)); | |
| const endurance = Math.floor((sire.endurance + dam.endurance) / 2 + (Math.random() * 4 - 2)); | |
| const beauty = Math.floor((sire.beauty + dam.beauty) / 2 + (Math.random() * 4 - 2)); | |
| const fertility = 70 + Math.floor(Math.random() * 31); // 70-100% | |
| // Determine traits (inherit from parents with chance) | |
| const traits = []; | |
| // Combine all parent traits and randomly inherit some | |
| const allTraits = [...sire.traits, ...dam.traits]; | |
| allTraits.forEach(trait => { | |
| if (Math.random() * 100 < (sire.effectiveFertility + dam.effectiveFertility) / 4) { | |
| traits.push({...trait}); | |
| } | |
| }); | |
| // Small chance for new random trait | |
| if (Math.random() < 0.1) { | |
| const randomTraitIndex = Math.floor(Math.random() * horseTraits.length); | |
| traits.push({...horseTraits[randomTraitIndex]}); | |
| } | |
| // Apply trait effects to preview stats (just for display) | |
| let previewSpeed = speed; | |
| let previewEndurance = endurance; | |
| let previewBeauty = beauty; | |
| let previewFertility = fertility; | |
| traits.forEach(trait => { | |
| previewSpeed += trait.effect.speed || 0; | |
| previewEndurance += trait.effect.endurance || 0; | |
| previewBeauty += trait.effect.beauty || 0; | |
| previewFertility += trait.effect.fertility || 0; | |
| }); | |
| // Clamp preview values | |
| previewSpeed = Math.max(1, Math.min(20, previewSpeed)); | |
| previewEndurance = Math.max(1, Math.min(20, previewEndurance)); | |
| previewBeauty = Math.max(1, Math.min(20, previewBeauty)); | |
| previewFertility = Math.max(0, Math.min(100, previewFertility)); | |
| // Determine gender | |
| const gender = Math.random() > 0.5 ? 'male' : 'female'; | |
| const genderClass = gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = gender === 'male' ? 'mars' : 'venus'; | |
| // Update breeding preview | |
| elements.breedingPreview.innerHTML = ` | |
| <div> | |
| <h4 class="font-bold">${sire.name}</h4> | |
| <div class="text-xs mb-2"> | |
| <span class="font-semibold">Speed:</span> ${sire.speed}, | |
| <span class="font-semibold">Endurance:</span> ${sire.endurance} | |
| </div> | |
| <div class="text-xs"> | |
| <span class="font-semibold">Traits:</span> ${sire.traits.length > 0 ? sire.traits.map(t => t.name).join(', ') : 'None'} | |
| </div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-4xl mb-2">+</div> | |
| <div class="text-sm">Fertility: Sire (${sire.effectiveFertility}%) + Dam (${dam.effectiveFertility}%)</div> | |
| </div> | |
| <div> | |
| <h4 class="font-bold">${dam.name}</h4> | |
| <div class="text-xs mb-2"> | |
| <span class="font-semibold">Speed:</span> ${dam.speed}, | |
| <span class="font-semibold">Endurance:</span> ${dam.endurance} | |
| </div> | |
| <div class="text-xs"> | |
| <span class="font-semibold">Traits:</span> ${dam.traits.length > 0 ? dam.traits.map(t => t.name).join(', ') : 'None'} | |
| </div> | |
| </div> | |
| <div class="col-span-3"> | |
| <div class="border-t-2 my-2 border-purple-200"></div> | |
| <h4 class="font-bold ${genderClass} mb-2"> | |
| <i class="fas fa-${genderIcon} mr-2"></i>Potential Offspring | |
| </h4> | |
| <div class="grid grid-cols-3 gap-2 mb-2"> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Speed</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-speed" style="width: ${previewSpeed * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${previewSpeed}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Endurance</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-endurance" style="width: ${previewEndurance * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${previewEndurance}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Fertility</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-fertility" style="width: ${previewFertility * 1.5}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${previewFertility}%</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-xs"> | |
| <span class="font-semibold">Possible Traits:</span> ${traits.length > 0 ? traits.map(t => t.name).join(', ') : 'None'} | |
| </div> | |
| </div> | |
| `; | |
| // Show breeding options | |
| elements.breedingOptions.classList.remove('hidden'); | |
| // Set up confirm breeding button | |
| elements.confirmBreedBtn.onclick = () => { | |
| breedHorses(sire, dam); | |
| }; | |
| } | |
| function breedHorses(sire, dam) { | |
| // Genetic mixing | |
| const speed = Math.floor((sire.speed + dam.speed) / 2 + (Math.random() * 4 - 2)); | |
| const endurance = Math.floor((sire.endurance + dam.endurance) / 2 + (Math.random() * 4 - 2)); | |
| const beauty = Math.floor((sire.beauty + dam.beauty) / 2 + (Math.random() * 4 - 2)); | |
| const fertility = 70 + Math.floor(Math.random() * 31); // 70-100% | |
| const gender = Math.random() > 0.5 ? 'male' : 'female'; | |
| // Determine name (combine parts of parent names) | |
| const sireNameParts = sire.name.split(' '); | |
| const damNameParts = dam.name.split(' '); | |
| const prefix = sireNameParts.length > 1 ? sireNameParts[0] : damNameParts[0]; | |
| const suffix = damNameParts.length > 1 ? damNameParts[1] : sireNameParts[0]; | |
| const name = `${prefix} ${suffix}`; | |
| // Inherit traits | |
| const traits = []; | |
| // Combine all parent traits and randomly inherit some | |
| const allTraits = [...sire.traits, ...dam.traits]; | |
| allTraits.forEach(trait => { | |
| if (Math.random() * 100 < (sire.effectiveFertility + dam.effectiveFertility) / 4) { | |
| traits.push({...trait}); | |
| } | |
| }); | |
| // Small chance for new random trait | |
| if (Math.random() < 0.1) { | |
| const randomTraitIndex = Math.floor(Math.random() * horseTraits.length); | |
| traits.push({...horseTraits[randomTraitIndex]}); | |
| } | |
| // Apply trait effects | |
| let effectiveSpeed = speed; | |
| let effectiveEndurance = endurance; | |
| let effectiveBeauty = beauty; | |
| let effectiveFertility = fertility; | |
| traits.forEach(trait => { | |
| effectiveSpeed += trait.effect.speed || 0; | |
| effectiveEndurance += trait.effect.endurance || 0; | |
| effectiveBeauty += trait.effect.beauty || 0; | |
| effectiveFertility += trait.effect.fertility || 0; | |
| }); | |
| // Clamp values | |
| effectiveSpeed = Math.max(1, Math.min(20, effectiveSpeed)); | |
| effectiveEndurance = Math.max(1, Math.min(20, effectiveEndurance)); | |
| effectiveBeauty = Math.max(1, Math.min(20, effectiveBeauty)); | |
| effectiveFertility = Math.max(0, Math.min(100, effectiveFertility)); | |
| // Create offspring | |
| const offspring = { | |
| id: Date.now() + Math.floor(Math.random() * 1000), | |
| name, | |
| gender, | |
| speed, | |
| endurance, | |
| beauty, | |
| fertility, | |
| effectiveSpeed, | |
| effectiveEndurance, | |
| effectiveBeauty, | |
| effectiveFertility, | |
| traits, | |
| parents: [sire.id, dam.id], | |
| trainingCount: 0 | |
| }; | |
| // Add to stable | |
| gameState.stable.push(offspring); | |
| // Decrease parent fertility | |
| sire.fertility = Math.max(0, sire.fertility - 25); | |
| sire.effectiveFertility = Math.max(0, Math.min(100, sire.fertility + (sire.traits.reduce((sum, t) => sum + (t.effect.fertility || 0), 0)))); | |
| dam.fertility = Math.max(0, dam.fertility - 25); | |
| dam.effectiveFertility = Math.max(0, Math.min(100, dam.fertility + (dam.traits.reduce((sum, t) => sum + (t.effect.fertility || 0), 0)))); | |
| // Update UI | |
| updateUI(); | |
| updateStableDisplay(); | |
| elements.breedModal.style.display = 'none'; | |
| addGameMessage(`You bred ${sire.name} and ${dam.name}. Their offspring ${name} has been added to your stable!`); | |
| // Show birth notification | |
| showBirthNotification(offspring); | |
| } | |
| function showBirthNotification(horse) { | |
| const genderClass = horse.gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = horse.gender === 'male' ? 'mars' : 'venus'; | |
| showNotification("New Foal Born!", ` | |
| <div class="text-center mb-4"> | |
| <div class="${genderClass} text-4xl mb-2 inline-block rounded-full p-3"> | |
| <i class="fas fa-${genderIcon}"></i> | |
| </div> | |
| <h3 class="text-xl font-bold">${horse.name}</h3> | |
| <div class="text-sm">${horse.gender === 'male' ? 'Colt' : 'Filly'}</div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-3 text-sm"> | |
| <div class="text-center"> | |
| <div class="text-xs text-gray-500 mb-1">Speed</div> | |
| <div class="font-semibold">${horse.effectiveSpeed}</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-xs text-gray-500 mb-1">Endurance</div> | |
| <div class="font-semibold">${horse.effectiveEndurance}</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-xs text-gray-500 mb-1">Beauty</div> | |
| <div class="font-semibold">${horse.effectiveBeauty}</div> | |
| </div> | |
| <div class="text-center"> | |
| <div class="text-xs text-gray-500 mb-1">Fertility</div> | |
| <div class="font-semibold">${horse.effectiveFertility}%</div> | |
| </div> | |
| </div> | |
| ${horse.traits.length > 0 ? | |
| `<div class="mt-4 text-sm"> | |
| <span class="font-semibold">Traits:</span> ${horse.traits.map(t => t.name).join(', ')} | |
| </div>` : '' | |
| } | |
| `); | |
| } | |
| function openTraining() { | |
| elements.trainableHorses.innerHTML = ''; | |
| gameState.stable.forEach(horse => { | |
| // Skip injured horses | |
| if (gameState.injuredHorses.has(horse.id)) return; | |
| // Skip horses that have been trained 3 times already | |
| if (horse.trainingCount >= 3) return; | |
| const horseCard = document.createElement('div'); | |
| horseCard.className = 'bg-gray-50 rounded-lg p-3 border border-gray-300'; | |
| const genderClass = horse.gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = horse.gender === 'male' ? 'mars' : 'venus'; | |
| const trainedTimes = horse.trainingCount || 0; | |
| const trainedText = trainedTimes > 0 ? ` (Trained ${trainedTimes}/3 times)` : ''; | |
| horseCard.innerHTML = ` | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="font-semibold ${genderClass}"> | |
| <i class="fas fa-${genderIcon} mr-2"></i>${horse.name}${trainedText} | |
| </h3> | |
| <button class="bg-yellow-600 hover:bg-yellow-700 text-white text-sm font-bold py-1 px-3 rounded transition train-horse-btn" data-horse-id="${horse.id}"> | |
| Train | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2"> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Speed</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-speed" style="width: ${horse.effectiveSpeed * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveSpeed}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Endurance</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-endurance" style="width: ${horse.effectiveEndurance * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveEndurance}</span> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| horseCard.querySelector('.train-horse-btn').addEventListener('click', () => trainHorse(horse)); | |
| elements.trainableHorses.appendChild(horseCard); | |
| }); | |
| if (elements.trainableHorses.children.length === 0) { | |
| elements.trainableHorses.innerHTML = '<p class="text-gray-500 italic">No horses available for training. All horses have been trained 3 times or are injured.</p>'; | |
| } | |
| elements.trainModal.style.display = 'flex'; | |
| } | |
| function trainHorse(horse) { | |
| // 20% chance of injury | |
| const injured = Math.random() < 0.2; | |
| // Stat improvements (1-3 points randomly assigned) | |
| const statPool = ['speed', 'endurance']; | |
| const statsToImprove = Math.floor(Math.random() * statPool.length) + 1; | |
| const improvedStats = {}; | |
| for (let i = 0; i < statsToImprove; i++) { | |
| const stat = statPool[Math.floor(Math.random() * statPool.length)]; | |
| improvedStats[stat] = (improvedStats[stat] || 0) + 1; | |
| } | |
| // Apply improvements | |
| let message = `Training ${horse.name}: `; | |
| const improvements = []; | |
| if (improvedStats.speed) { | |
| horse.speed = Math.min(20, horse.speed + improvedStats.speed); | |
| horse.effectiveSpeed = horse.speed; | |
| // Apply trait effects again | |
| horse.traits.forEach(trait => { | |
| horse.effectiveSpeed += trait.effect.speed || 0; | |
| }); | |
| horse.effectiveSpeed = Math.max(1, Math.min(20, horse.effectiveSpeed)); | |
| improvements.push(`Speed +${improvedStats.speed}`); | |
| } | |
| if (improvedStats.endurance) { | |
| horse.endurance = Math.min(20, horse.endurance + improvedStats.endurance); | |
| horse.effectiveEndurance = horse.endurance; | |
| // Apply trait effects again | |
| horse.traits.forEach(trait => { | |
| horse.effectiveEndurance += trait.effect.endurance || 0; | |
| }); | |
| horse.effectiveEndurance = Math.max(1, Math.min(20, horse.effectiveEndurance)); | |
| improvements.push(`Endurance +${improvedStats.endurance}`); | |
| } | |
| if (improvements.length > 0) { | |
| message += `Improved ${improvements.join(' and ')}. `; | |
| } else { | |
| message += `No stat improvements this time. `; | |
| } | |
| // Apply injury if it happened | |
| if (injured) { | |
| gameState.injuredHorses.add(horse.id); | |
| message += `Unfortunately, ${horse.name} was injured and can't race until healed!`; | |
| } else { | |
| message += "No injuries occurred."; | |
| } | |
| // Increment training count | |
| horse.trainingCount = (horse.trainingCount || 0) + 1; | |
| // Update UI | |
| updateStableDisplay(); | |
| addGameMessage(message); | |
| // Close training modal and reopen to refresh list | |
| elements.trainModal.style.display = 'none'; | |
| openTraining(); | |
| } | |
| function openRace() { | |
| elements.racableHorses.innerHTML = ''; | |
| gameState.stable.forEach(horse => { | |
| // Skip injured horses | |
| if (gameState.injuredHorses.has(horse.id)) return; | |
| const horseCard = document.createElement('div'); | |
| horseCard.className = 'bg-gray-50 rounded-lg p-3 border border-gray-300'; | |
| const genderClass = horse.gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = horse.gender === 'male' ? 'mars' : 'venus'; | |
| const trainedTimes = horse.trainingCount || 0; | |
| horseCard.innerHTML = ` | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="font-semibold ${genderClass}"> | |
| <i class="fas fa-${genderIcon} mr-2"></i>${horse.name} | |
| </h3> | |
| <button class="bg-red-600 hover:bg-red-700 text-white text-sm font-bold py-1 px-3 rounded transition race-horse-btn" data-horse-id="${horse.id}"> | |
| Enter Race | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 mb-2"> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Speed</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-speed" style="width: ${horse.effectiveSpeed * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveSpeed}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Endurance</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-endurance" style="width: ${horse.effectiveEndurance * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveEndurance}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-xs"> | |
| <span class="font-semibold">Training:</span> ${trainedTimes}/3 sessions completed | |
| </div> | |
| `; | |
| horseCard.querySelector('.race-horse-btn').addEventListener('click', () => startRace(horse)); | |
| elements.racableHorses.appendChild(horseCard); | |
| }); | |
| if (elements.racableHorses.children.length === 0) { | |
| elements.racableHorses.innerHTML = '<p class="text-gray-500 italic">No horses available for racing. All horses are injured or you have no horses.</p>'; | |
| } | |
| elements.raceModal.style.display = 'flex'; | |
| } | |
| function startRace(playerHorse) { | |
| elements.raceModal.style.display = 'none'; | |
| // Generate 3-5 competitor horses based on player horse's stats | |
| const competitorCount = 3 + Math.floor(Math.random() * 3); | |
| const competitors = []; | |
| for (let i = 0; i < competitorCount; i++) { | |
| // Create competitors with stats slightly varied from player's horse | |
| const speedVariation = Math.floor(Math.random() * 5) - 2; | |
| const enduranceVariation = Math.floor(Math.random() * 5) - 2; | |
| competitors.push({ | |
| id: Date.now() + Math.floor(Math.random() * 1000) + i, | |
| name: generateCompetitorName(), | |
| effectiveSpeed: Math.max(5, Math.min(20, playerHorse.effectiveSpeed + speedVariation)), | |
| effectiveEndurance: Math.max(5, Math.min(20, playerHorse.effectiveEndurance + enduranceVariation)), | |
| color: getRandomHorseColor(), | |
| position: i + 1 | |
| }); | |
| } | |
| // Add player's horse to competitors | |
| const playerHorseCopy = { | |
| ...playerHorse, | |
| color: '#3b82f6', // Blue for player's horse | |
| position: 0 | |
| }; | |
| competitors.push(playerHorseCopy); | |
| // Shuffle competitors | |
| const shuffledCompetitors = [...competitors].sort(() => Math.random() - 0.5); | |
| // Display race track | |
| elements.raceTrack.innerHTML = ''; | |
| elements.raceResultsList.innerHTML = ''; | |
| elements.raceResults.classList.remove('hidden'); | |
| shuffledCompetitors.forEach((horse, index) => { | |
| const horseDiv = document.createElement('div'); | |
| horseDiv.className = 'horse-runner'; | |
| horseDiv.style.top = `${index * 18}px`; | |
| horseDiv.style.backgroundImage = `url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="${encodeURIComponent(horse.color)}" d="M448 96V80c0-8.8-7.2-16-16-16h-16c-8.8 0-16 7.2-16 16v16h-32V80c0-8.8-7.2-16-16-16h-16c-8.8 0-16 7.2-16 16v16h-32V80c0-8.8-7.2-16-16-16h-16c-8.8 0-16 7.2-16 16v16h-32V80c0-8.8-7.2-16-16-16h-16c-8.8 0-16 7.2-16 16v16H96V80c0-8.8-7.2-16-16-16H64c-8.8 0-16 7.2-16 16v16H32C14.3 96 0 110.3 0 128v96c0 17.7 14.3 32 32 32h32v96c0 17.7 14.3 32 32 32h32v96c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32v-96h32v96c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32v-96h32v96c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32v-96h32c17.7 0 32-14.3 32-32V256h32c17.7 0 32-14.3 32-32V128c0-17.7-14.3-32-32-32h-32z"/></svg>')`; | |
| horseDiv.id = `horse-${horse.id}`; | |
| horseDiv.dataset.horseId = horse.id; | |
| horseDiv.dataset.speed = horse.effectiveSpeed; | |
| horseDiv.dataset.endurance = horse.effectiveEndurance; | |
| horseDiv.dataset.currentSpeed = horse.effectiveSpeed; | |
| horseDiv.dataset.currentEndurance = horse.effectiveEndurance; | |
| horseDiv.dataset.lap = 0; | |
| horseDiv.dataset.finished = 0; | |
| elements.raceTrack.appendChild(horseDiv); | |
| }); | |
| // Run the race simulation | |
| simulateRace(shuffledCompetitors, playerHorse); | |
| } | |
| function simulateRace(competitors, playerHorse) { | |
| const totalLaps = 3; | |
| let finishedHorses = 0; | |
| const results = []; | |
| const raceInterval = setInterval(() => { | |
| competitors.forEach(horse => { | |
| if (horse.finished) return; | |
| // Move horse based on current speed | |
| const horseElement = document.getElementById(`horse-${horse.id}`); | |
| if (!horseElement) return; | |
| // Update lap if needed | |
| const currentPosition = parseFloat(getComputedStyle(horseElement).transform.split(',')[4]) || 0; | |
| const trackWidth = elements.raceTrack.offsetWidth - 100; // Rough width | |
| const lapWidth = trackWidth / totalLaps; | |
| const newLap = Math.min(totalLaps, Math.floor(currentPosition / lapWidth) + 1); | |
| if (newLap !== parseInt(horseElement.dataset.lap)) { | |
| horseElement.dataset.lap = newLap; | |
| // Check if race is finished for this horse | |
| if (newLap >= totalLaps) { | |
| horseElement.dataset.finished = 1; | |
| horse.finished = true; | |
| finishedHorses++; | |
| // Record result | |
| const position = finishedHouses; | |
| results.push({ | |
| ...horse, | |
| position: finishedHorses | |
| }); | |
| // Show finish animation | |
| horseElement.style.animation = 'none'; | |
| horseElement.style.transform = 'translateX(calc(100% - 100px))'; | |
| // Add to results list | |
| const resultItem = document.createElement('div'); | |
| resultItem.className = 'flex items-center justify-between py-1 px-2'; | |
| if (horse.id === playerHorse.id) { | |
| resultItem.className += ' bg-blue-50'; | |
| } | |
| resultItem.innerHTML = ` | |
| <div class="flex items-center"> | |
| <span class="font-bold text-lg mr-2">${finishedHorses}.</span> | |
| <span class="font-semibold">${horse.name}</span> | |
| </div> | |
| <div class="text-right"> | |
| <span class="text-sm">Speed: ${horse.effectiveSpeed}, Endurance: ${horse.effectiveEndurance}</span> | |
| </div> | |
| `; | |
| elements.raceResultsList.appendChild(resultItem); | |
| // Check if race is over | |
| if (finishedHorses === competitors.length) { | |
| clearInterval(raceInterval); | |
| finalizeRace(results, playerHorse); | |
| } | |
| return; | |
| } | |
| } | |
| // Calculate speed adjustments based on endurance | |
| let currentEndurance = parseInt(horseElement.dataset.currentEndurance); | |
| let currentSpeed = parseInt(horseElement.dataset.currentSpeed); | |
| let speedMultiplier = 1; | |
| if (currentEndurance <= 10) { | |
| speedMultiplier = 0.1; | |
| } else if (currentEndurance <= 25) { | |
| speedMultiplier = 0.25; | |
| } else if (currentEndurance <= 50) { | |
| speedMultiplier = 0.5; | |
| } | |
| // Reduce endurance by speed value | |
| currentEndurance -= currentSpeed * 0.5; | |
| // Don't let endurance go below 0 | |
| if (currentEndurance < 1) currentEndurance = 1; | |
| // Update element data | |
| horseElement.dataset.currentEndurance = Math.floor(currentEndurance); | |
| horseElement.dataset.currentSpeed = Math.floor(currentSpeed * speedMultiplier); | |
| // Update animation duration based on current speed | |
| const animationDuration = Math.max(1, 10 - (currentSpeed * speedMultiplier * 0.5)); | |
| horseElement.style.animationDuration = `${animationDuration}s`; | |
| }); | |
| }, 100); | |
| // Start animations | |
| competitors.forEach(horse => { | |
| const horseElement = document.getElementById(`horse-${horse.id}`); | |
| if (horseElement) { | |
| horseElement.style.animationName = 'raceHorse'; | |
| horseElement.style.animationDuration = `${10 - (horse.effectiveSpeed * 0.5)}s`; | |
| horseElement.style.animationTimingFunction = 'linear'; | |
| horseElement.style.animationFillMode = 'forwards'; | |
| } | |
| }); | |
| } | |
| function finalizeRace(results, playerHorse) { | |
| // Find player's result | |
| const playerResult = results.find(r => r.id === playerHorse.id); | |
| if (playerResult) { | |
| // Calculate prize money based on position | |
| let prize = 0; | |
| switch(playerResult.position) { | |
| case 1: prize = 10000; break; | |
| case 2: prize = 5000; break; | |
| case 3: prize = 2500; break; | |
| default: prize = 1000; | |
| } | |
| // Adjust prize by horse stats (higher stats = better prize) | |
| prize = Math.floor(prize * (1 + (playerHorse.effectiveSpeed + playerHorse.effectiveEndurance) / 40)); | |
| gameState.money += prize; | |
| // Reset training count for this horse | |
| playerHorse.trainingCount = 0; | |
| // Add game message | |
| addGameMessage( | |
| playerResult.position <= 3 ? | |
| `🎉 ${playerHorse.name} finished ${ordinalSuffix(playerResult.position)} and earned you $${prize.toLocaleString()}!` : | |
| `${playerHorse.name} finished ${ordinalSuffix(playerResult.position)} in the race. No prize money earned.` | |
| ); | |
| // Update UI | |
| updateUI(); | |
| updateStableDisplay(); | |
| } | |
| } | |
| function openSell() { | |
| elements.sellableHorses.innerHTML = ''; | |
| gameState.stable.forEach(horse => { | |
| const horseCard = document.createElement('div'); | |
| horseCard.className = 'bg-gray-50 rounded-lg p-3 border border-gray-300'; | |
| const genderClass = horse.gender === 'male' ? 'horse-male' : 'horse-female'; | |
| const genderIcon = horse.gender === 'male' ? 'mars' : 'venus'; | |
| const canBreedExternally = horse.effectiveFertility > 0; | |
| const canOfferService = horse.gender === 'male' && canBreedExternally; | |
| const canSell = horse.gender === 'female' || !canOfferService; | |
| horseCard.innerHTML = ` | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="font-semibold ${genderClass}"> | |
| <i class="fas fa-${genderIcon} mr-2"></i>${horse.name} | |
| </h3> | |
| <div class="text-right"> | |
| <div class="font-semibold">$${calculateHorseValue(horse).toLocaleString()}</div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 mb-3"> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Speed</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-speed" style="width: ${horse.effectiveSpeed * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveSpeed}</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="text-xs text-gray-500 mb-1">Endurance</div> | |
| <div class="flex items-center"> | |
| <div class="stats-bar stat-endurance" style="width: ${horse.effectiveEndurance * 6}px;"></div> | |
| <span class="text-xs ml-2 font-semibold">${horse.effectiveEndurance}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-between space-x-2"> | |
| ${canSell ? | |
| `<button class="flex-1 bg-gray-600 hover:bg-gray-700 text-white text-sm font-bold py-1 px-3 rounded transition sell-horse-btn" data-horse-id="${horse.id}"> | |
| Sell | |
| </button>` : '' | |
| } | |
| ${canOfferService ? | |
| `<button class="flex-1 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-bold py-1 px-3 rounded transition offer-service-btn" data-horse-id="${horse.id}"> | |
| Offer Breeding | |
| </button>` : '' | |
| } | |
| </div> | |
| `; | |
| if (canSell) { | |
| horseCard.querySelector('.sell-horse-btn').addEventListener('click', () => sellHorse(horse)); | |
| } | |
| if (canOfferService) { | |
| horseCard.querySelector('.offer-service-btn').addEventListener('click', () => offerBreedingService(horse)); | |
| } | |
| elements.sellableHorses.appendChild(horseCard); | |
| }); | |
| elements.sellModal.style.display = 'flex'; | |
| } | |
| function sellHorse(horse) { | |
| const value = calculateHorseValue(horse); | |
| gameState.money += value; | |
| gameState.stable = gameState.stable.filter(h => h.id !== horse.id); | |
| // If this horse was injured, remove from injured set | |
| if (gameState.injuredHorses.has(horse.id)) { | |
| gameState.injuredHorses.delete(horse.id); | |
| } | |
| addGameMessage(`You sold ${horse.name} for $${value.toLocaleString()}.`); | |
| updateUI(); | |
| updateStableDisplay(); | |
| elements.sellModal.style.display = 'none'; | |
| } | |
| function offerBreedingService(horse) { | |
| const earnings = Math.floor(calculateBreedingValue(horse)); | |
| gameState.money += earnings; | |
| // Reduce fertility by 25% | |
| horse.fertility = Math.max(0, horse.fertility - 25); | |
| horse.effectiveFertility = Math.max(0, Math.min(100, horse.fertility + (horse.traits.reduce((sum, t) => sum + (t.effect.fertility || 0), 0)))); | |
| addGameMessage(`You earned $${earnings.toLocaleString()} by offering ${horse.name} for breeding.`); | |
| updateUI(); | |
| updateStableDisplay(); | |
| elements.sellModal.style.display = 'none'; | |
| } | |
| function nextDay() { | |
| gameState.day++; | |
| // Heal injured horses | |
| gameState.injuredHorses = new Set(); | |
| // Reset training counts | |
| gameState.stable.forEach(horse => { | |
| horse.trainingCount = 0; | |
| }); | |
| // Generate new market horses | |
| generateMarketHorses(); | |
| addGameMessage(`A new day begins! All injuries healed and training limits reset.`); | |
| updateUI(); | |
| updateStableDisplay(); | |
| } | |
| function calculateHorseValue(horse) { | |
| // Base value on stats and traits | |
| let value = (horse.effectiveSpeed * 500) + (horse.effectiveEndurance * 400) + (horse.effectiveBeauty * 300); | |
| // Adjust for fertility - higher fertility = higher value | |
| value *= 0.8 + (horse.effectiveFertility / 100); | |
| // Add bonus for traits | |
| value *= 1 + (horse.traits.length * 0.1); | |
| return Math.floor(value * 0.8); // Only worth 80% of "market value" | |
| } | |
| function calculateBreedingValue(horse) { | |
| return Math.floor(calculateHorseValue(horse) * 0.25); | |
| } | |
| function generateCompetitorName() { | |
| const prefixes = ['Thunder', 'Swift', 'Black', 'Silver', 'Golden', 'Midnight', 'Royal', 'Wild']; | |
| const suffixes = ['Runner', 'Hoof', 'Storm', 'Shadow', 'Dash', 'Flash', 'Wind']; | |
| return `${prefixes[Math.floor(Math.random() * prefixes.length)]}'s ${suffixes[Math.floor(Math.random() * suffixes.length)]}`; | |
| } | |
| function getRandomHorseColor() { | |
| const colors = [ | |
| '#a855f7', // purple | |
| '#ef4444', // red | |
| '#10b981', // emerald | |
| '#f59e0b', // amber | |
| '#6366f1', // indigo | |
| '#ec4899', // pink | |
| '#14b8a6', // teal | |
| '#f97316' // orange | |
| ]; | |
| return colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| function ordinalSuffix(num) { | |
| const j = num % 10; | |
| const k = num % 100; | |
| if (j == 1 && k != 11) { | |
| return num + "st"; | |
| } | |
| if (j == 2 && k != 12) { | |
| return num + "nd"; | |
| } | |
| if (j == 3 && k != 13) { | |
| return num + "rd"; | |
| } | |
| return num + "th"; | |
| } | |
| function addGameMessage(message) { | |
| const messageElement = document.createElement('p'); | |
| messageElement.className = 'text-gray-700 mb-1'; | |
| messageElement.textContent = `Day ${gameState.day}: ${message}`; | |
| elements.gameMessages.insertBefore(messageElement, elements.gameMessages.firstChild); | |
| // Limit to 10 messages | |
| if (elements.gameMessages.children.length > 10) { | |
| elements.gameMessages.removeChild(elements.gameMessages.lastChild); | |
| } | |
| } | |
| function showNotification(title, content) { | |
| elements.notificationContent.innerHTML = ` | |
| <h3 class="text-xl font-bold mb-2">${title}</h3> | |
| <div>${content}</div> | |
| `; | |
| elements.notificationModal.style.display = 'flex'; | |
| } | |
| function updateUpcomingRaces() { | |
| elements.upcomingRaces.innerHTML = ` | |
| <h3 class="text-lg font-semibold text-gray-700 mb-2">Upcoming Races</h3> | |
| <div class="bg-gray-100 p-3 rounded-lg"> | |
| <p class="text-gray-700">Race Day ${gameState.day + (3 - (gameState.day % 3))} - Prize pool: $12,000</p> | |
| </div> | |
| `; | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=furuknap/stablemasters" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |