class BuildALotGame { constructor() { this.lots = []; this.money = 50000; this.workers = 3; this.maxWorkers = 3; this.materials = 100; this.maxMaterials = 100; this.day = 1; this.speed = 1; this.selectedAction = null; this.level = 1; this.buildings = { empty: { name: 'Empty Lot', icon: 'square', color: 'gray', rent: 0 }, cottage: { name: 'Cottage', icon: 'home', color: 'blue', cost: 3000, materials: 50, workers: 1, time: 3, rent: 150, upgradeCost: 1500, sellPrice: 4000 }, house: { name: 'House', icon: 'building-2', color: 'indigo', cost: 6000, materials: 100, workers: 2, time: 5, rent: 400, upgradeCost: 3000, sellPrice: 8000 }, mansion: { name: 'Mansion', icon: 'castle', color: 'purple', cost: 15000, materials: 250, workers: 3, time: 8, rent: 1200, upgradeCost: 7500, sellPrice: 20000 } }; this.levels = [ { name: "First Steps", goals: [ { type: 'money', target: 75000, current: 0, label: 'Have $75,000' }, { type: 'cottages', target: 2, current: 0, label: 'Own 2 Cottages' } ], startingMoney: 50000, lotCount: 8 }, { name: "Growing Town", goals: [ { type: 'houses', target: 3, current: 0, label: 'Own 3 Houses' }, { type: 'money', target: 100000, current: 0, label: 'Have $100,000' } ], startingMoney: 60000, lotCount: 12 }, { name: "Mansion District", goals: [ { type: 'mansions', target: 2, current: 0, label: 'Own 2 Mansions' }, { type: 'upgraded', target: 5, current: 0, label: 'Upgrade 5 Properties' } ], startingMoney: 80000, lotCount: 16 } ]; this.init(); } init() { this.loadLevel(0); this.startGameLoop(); this.updateUI(); } loadLevel(levelIndex) { this.level = levelIndex + 1; const levelData = this.levels[levelIndex]; this.money = levelData.startingMoney; this.workers = 3; this.maxWorkers = 3; this.materials = 100; this.maxMaterials = 100; this.day = 1; this.lots = []; // Create lots for (let i = 0; i < levelData.lotCount; i++) { this.lots.push({ id: i, owned: i < 2, // Start with 2 owned lots building: 'empty', level: 0, constructing: false, constructionProgress: 0, constructionTime: 0, upgradeLevel: 0 }); } // Reset goals levelData.goals.forEach(goal => goal.current = 0); this.currentGoals = JSON.parse(JSON.stringify(levelData.goals)); this.currentLevelData = levelData; this.updateGoals(); this.renderGrid(); this.updateUI(); } renderGrid() { const grid = document.getElementById('game-grid'); grid.innerHTML = ''; this.lots.forEach(lot => { const lotEl = document.createElement('div'); lotEl.className = `lot relative rounded-2xl border-2 cursor-pointer overflow-hidden ${this.getLotClasses(lot)}`; lotEl.onclick = () => this.handleLotClick(lot); const building = this.buildings[lot.building]; let content = ''; if (!lot.owned) { content = `
FOR SALE $5,000
`; } else if (lot.constructing) { content = `
`; } else { const rentMultiplier = 1 + (lot.upgradeLevel * 0.5); const currentRent = Math.floor(building.rent * rentMultiplier); content = `
${building.name} ${lot.upgradeLevel > 0 ? `★${lot.upgradeLevel}` : ''} ${currentRent > 0 ? `$${currentRent}/day` : ''}
`; } lotEl.innerHTML = content; grid.appendChild(lotEl); }); lucide.createIcons(); } getLotClasses(lot) { if (!lot.owned) return 'border-gray-300 bg-gray-50'; if (lot.constructing) return 'border-amber-400'; const building = this.buildings[lot.building]; return `border-${building.color}-200 bg-${building.color}-50`; } handleLotClick(lot) { if (!this.selectedAction) return; switch (this.selectedAction) { case 'buy': this.buyLot(lot); break; case 'build-cottage': case 'build-house': case 'build-mansion': const type = this.selectedAction.replace('build-', ''); this.build(lot, type); break; case 'upgrade': this.upgrade(lot); break; case 'sell': this.sell(lot); break; } } buyLot(lot) { if (lot.owned) { this.showToast('Lot already owned!', 'error'); return; } if (this.money < 5000) { this.showToast('Not enough money!', 'error'); return; } this.money -= 5000; lot.owned = true; this.showToast('Lot purchased!', 'success'); this.cancelAction(); this.updateUI(); this.renderGrid(); this.checkGoals(); } build(lot, type) { if (!lot.owned || lot.building !== 'empty' || lot.constructing) { this.showToast('Cannot build here!', 'error'); return; } const building = this.buildings[type]; if (this.money < building.cost) { this.showToast('Not enough money!', 'error'); return; } if (this.materials < building.materials) { this.showToast('Not enough materials!', 'error'); return; } if (this.workers < building.workers) { this.showToast('Not enough workers!', 'error'); return; } this.money -= building.cost; this.materials -= building.materials; this.workers -= building.workers; lot.constructing = true; lot.constructionProgress = 0; lot.constructionTime = building.time; lot.pendingBuilding = type; this.showToast(`Building ${building.name}...`, 'info'); this.cancelAction(); this.updateUI(); this.renderGrid(); } upgrade(lot) { if (!lot.owned || lot.building === 'empty' || lot.constructing) { this.showToast('Cannot upgrade!', 'error'); return; } const building = this.buildings[lot.building]; const upgradeCost = building.upgradeCost * (lot.upgradeLevel + 1); if (this.money < upgradeCost) { this.showToast('Not enough money!', 'error'); return; } if (this.workers < 1) { this.showToast('Need 1 worker!', 'error'); return; } this.money -= upgradeCost; this.workers -= 1; lot.constructing = true; lot.constructionProgress = 0; lot.constructionTime = 2; lot.pendingUpgrade = true; this.showToast('Upgrading...', 'info'); this.cancelAction(); this.updateUI(); this.renderGrid(); } sell(lot) { if (!lot.owned || lot.constructing) { this.showToast('Cannot sell!', 'error'); return; } const building = this.buildings[lot.building]; let sellPrice = lot.building === 'empty' ? 4000 : building.sellPrice; sellPrice = Math.floor(sellPrice * (1 + lot.upgradeLevel * 0.3)); this.money += sellPrice; lot.owned = false; lot.building = 'empty'; lot.upgradeLevel = 0; this.showToast(`Sold for $${sellPrice}!`, 'success'); this.cancelAction(); this.updateUI(); this.renderGrid(); this.checkGoals(); } hireWorker() { if (this.money < 1000) { this.showToast('Not enough money!', 'error'); return; } this.money -= 1000; this.maxWorkers += 1; this.workers += 1; this.updateUI(); this.showToast('Worker hired!', 'success'); } buyMaterials() { if (this.money < 100) { this.showToast('Not enough money!', 'error'); return; } if (this.materials >= this.maxMaterials) { this.showToast('Storage full!', 'error'); return; } this.money -= 100; this.materials = Math.min(this.maxMaterials, this.materials + 50); this.updateUI(); this.showToast('Materials purchased!', 'success'); } setAction(action) { this.selectedAction = action; // Update UI to show selected action document.querySelectorAll('#action-buttons button').forEach(btn => { btn.classList.remove('ring-2', 'ring-indigo-500', 'bg-indigo-50'); }); const btn = document.getElementById(`btn-${action.replace('build-', '')}`); if (btn) { btn.classList.add('ring-2', 'ring-indigo-500', 'bg-indigo-50'); } const info = document.getElementById('action-info'); const text = document.getElementById('action-text'); const texts = { 'buy': 'Select an empty lot to purchase ($5,000)', 'build-cottage': 'Select an owned empty lot to build Cottage', 'build-house': 'Select an owned empty lot to build House', 'build-mansion': 'Select an owned empty lot to build Mansion', 'upgrade': 'Select a building to upgrade', 'sell': 'Select a property to sell' }; text.textContent = texts[action] || 'Select a target'; info.classList.remove('hidden'); } cancelAction() { this.selectedAction = null; document.querySelectorAll('#action-buttons button').forEach(btn => { btn.classList.remove('ring-2', 'ring-indigo-500', 'bg-indigo-50'); }); document.getElementById('action-info').classList.add('hidden'); } updateUI() { document.getElementById('money-display').textContent = `$${this.money.toLocaleString()}`; document.getElementById('workers-display').textContent = `${this.workers} / ${this.maxWorkers}`; document.getElementById('materials-display').textContent = `${this.materials} / ${this.maxMaterials}`; document.getElementById('day-display').textContent = this.day; // Update speed buttons [1, 2, 3].forEach(speed => { const btn = document.getElementById(`speed-${speed}`); if (this.speed === speed) { btn.classList.remove('bg-gray-200', 'text-gray-700'); btn.classList.add('bg-indigo-600', 'text-white'); } else { btn.classList.add('bg-gray-200', 'text-gray-700'); btn.classList.remove('bg-indigo-600', 'text-white'); } }); // Enable/disable action buttons based on resources this.updateActionButtons(); } updateActionButtons() { const buttons = { 'btn-cottage': { money: 3000, materials: 50, workers: 1 }, 'btn-house': { money: 6000, materials: 100, workers: 2 }, 'btn-mansion': { money: 15000, materials: 250, workers: 3 }, 'btn-upgrade': { money: 1500, workers: 1 }, 'btn-sell': { owned: true }, 'btn-buy': { money: 5000 } }; Object.entries(buttons).forEach(([id, reqs]) => { const btn = document.getElementById(id); let enabled = true; if (reqs.money && this.money < reqs.money) enabled = false; if (reqs.materials && this.materials < reqs.materials) enabled = false; if (reqs.workers && this.workers < reqs.workers) enabled = false; if (enabled) { btn.disabled = false; btn.classList.remove('opacity-50', 'cursor-not-allowed'); } else { btn.disabled = true; btn.classList.add('opacity-50', 'cursor-not-allowed'); } }); } updateGoals() { const container = document.getElementById('goals-container'); container.innerHTML = ''; this.currentGoals.forEach(goal => { const div = document.createElement('div'); const completed = goal.current >= goal.target; div.className = `flex items-center gap-2 ${completed ? 'text-green-600' : 'text-gray-600'}`; div.innerHTML = ` ${goal.label} (${goal.current}/${goal.target}) `; container.appendChild(div); }); lucide.createIcons(); } checkGoals() { let allCompleted = true; this.currentGoals.forEach(goal => { switch(goal.type) { case 'money': goal.current = this.money; break; case 'cottages': goal.current = this.lots.filter(l => l.building === 'cottage').length; break; case 'houses': goal.current = this.lots.filter(l => l.building === 'house').length; break; case 'mansions': goal.current = this.lots.filter(l => l.building === 'mansion').length; break; case 'upgraded': goal.current = this.lots.filter(l => l.upgradeLevel > 0).length; break; } if (goal.current < goal.target) allCompleted = false; }); this.updateGoals(); if (allCompleted) { setTimeout(() => { document.getElementById('level-modal').classList.remove('hidden'); }, 500); } // Check game over if (this.money < 100 && this.workers === this.maxWorkers && !this.lots.some(l => l.owned && this.buildings[l.building].rent > 0)) { document.getElementById('gameover-modal').classList.remove('hidden'); } } startGameLoop() { setInterval(() => { // Day progression this.day += 0.1 * this.speed; // Construction progress this.lots.forEach(lot => { if (lot.constructing) { lot.constructionProgress += 0.1 * this.speed; if (lot.constructionProgress >= lot.constructionTime) { // Construction complete lot.constructing = false; if (lot.pendingUpgrade) { lot.upgradeLevel++; delete lot.pendingUpgrade; this.workers += 1; } else { lot.building = lot.pendingBuilding; delete lot.pendingBuilding; const building = this.buildings[lot.building]; this.workers += building.workers; } this.renderGrid(); this.checkGoals(); } } }); // Rent collection (daily) if (Math.floor(this.day) > Math.floor(this.day - 0.1 * this.speed)) { this.collectRent(); } this.updateUI(); // Update construction progress bars this.lots.forEach((lot, idx) => { if (lot.constructing) { const progressBars = document.querySelectorAll('.progress-bar'); if (progressBars[idx]) { progressBars[idx].style.width = `${(lot.constructionProgress / lot.constructionTime) * 100}%`; } } }); }, 100); } collectRent() { let totalRent = 0; this.lots.forEach(lot => { if (lot.owned && !lot.constructing && lot.building !== 'empty') { const building = this.buildings[lot.building]; const rentMultiplier = 1 + (lot.upgradeLevel * 0.5); const rent = Math.floor(building.rent * rentMultiplier); totalRent += rent; // Show floating money this.showFloatingMoney(lot.id, rent); } }); this.money += totalRent; this.updateUI(); this.checkGoals(); } showFloatingMoney(lotId, amount) { const grid = document.getElementById('game-grid'); const lotEl = grid.children[lotId]; if (lotEl) { const float = document.createElement('div'); float.className = 'absolute top-0 left-1/2 transform -translate-x-1/2 text-green-600 font-bold text-sm money-float z-10 pointer-events-none'; float.textContent = `+$${amount}`; lotEl.style.position = 'relative'; lotEl.appendChild(float); setTimeout(() => float.remove(), 1500); } } showToast(message, type = 'info') { const toast = document.createElement('div'); const colors = { success: 'bg-green-500', error: 'bg-red-500', info: 'bg-blue-500' }; toast.className = `fixed top-20 left-1/2 transform -translate-x-1/2 ${colors[type]} text-white px-6 py-3 rounded-full shadow-lg z-50 bounce-in`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 2000); } nextLevel() { document.getElementById('level-modal').classList.add('hidden'); if (this.level < this.levels.length) { this.loadLevel(this.level); } else { alert('Congratulations! You beat all levels!'); this.loadLevel(0); } } restartLevel() { document.getElementById('gameover-modal').classList.add('hidden'); this.loadLevel(this.level - 1); } } // Initialize game const game = new BuildALotGame();