stablemasters / index.html
furuknap's picture
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
<!DOCTYPE html>
<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>