Files
idle-game/js/game.js
T

416 lines
12 KiB
JavaScript

/**
* Cosmic Garden - Main Game Engine
* Handles game state, tick loop, and core logic
*/
const Game = {
// Core game state
state: {
stardust: 0,
totalStardust: 0,
totalClicks: 0,
clickPower: 1,
startTime: Date.now(),
lastSaveTime: Date.now(),
buildings: {},
upgrades: [],
achievements: [],
stellarEssence: 0,
prestigeCount: 0,
totalPrestigeEssence: 0,
prestigeUpgrades: []
},
// Configuration
config: {
tickRate: 100, // ms per tick (10 ticks/sec)
autoSaveInterval: 30000, // 30 seconds
costScaling: 1.15 // Cost multiplier per building level
},
/**
* Initialize the game
*/
init() {
this.loadState();
this.setupEventListeners();
this.startGameLoop();
this.startAutoSave();
Audio.init();
UI.init();
Stats.init();
PrestigeUI.init();
console.log('🌌 Cosmic Garden initialized!');
},
/**
* Setup event listeners
*/
setupEventListeners() {
// Harvest button
document.getElementById('harvest-btn').addEventListener('click', (e) => {
this.harvest(e);
});
// Save buttons
document.getElementById('save-btn').addEventListener('click', () => {
this.save();
UI.showNotification('Game saved!', 'success');
});
document.getElementById('export-btn').addEventListener('click', () => {
SaveSystem.exportSave();
});
document.getElementById('import-btn').addEventListener('click', () => {
UI.showImportModal();
});
document.getElementById('confirm-import').addEventListener('click', () => {
const data = document.getElementById('import-data').value;
if (data) {
this.loadFromData(data);
UI.hideImportModal();
UI.showNotification('Save imported!', 'success');
}
});
document.getElementById('cancel-import').addEventListener('click', () => {
UI.hideImportModal();
});
document.getElementById('reset-btn').addEventListener('click', () => {
if (confirm('Are you sure you want to reset all progress? This cannot be undone!')) {
this.reset();
}
});
// Tab switching
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const tab = e.target.dataset.tab;
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
e.target.classList.add('active');
document.getElementById(`${tab}-tab`).classList.add('active');
});
});
},
/**
* Harvest stardust by clicking
*/
harvest(event) {
const amount = this.getClickPower();
this.addStardust(amount);
this.state.totalClicks++;
// Audio feedback
Audio.playClick();
// Visual feedback
UI.showFloatingText(event.clientX, event.clientY, `+${this.formatNumber(amount)}`);
Particles.createClickParticles(event.clientX, event.clientY, 8);
UI.update();
},
/**
* Add stardust to the game
*/
addStardust(amount) {
this.state.stardust += amount;
this.state.totalStardust += amount;
},
/**
* Get current click power
*/
getClickPower() {
let power = 1;
// Apply upgrade bonuses
for (const upgradeId of this.state.upgrades) {
const upgrade = UPGRADES[upgradeId];
if (upgrade && upgrade.clickPowerBonus) {
power *= upgrade.clickPowerBonus;
}
}
// Apply prestige multipliers
const prestigeMultiplier = PRESTIGE.getMultiplier();
power *= prestigeMultiplier;
// Apply prestige upgrades
for (const upgradeId of (this.state.prestigeUpgrades || [])) {
const upgrade = PRESTIGE_UPGRADES[upgradeId];
if (upgrade && upgrade.effect === 'clickMultiplier') {
power *= upgrade.value;
}
}
return Math.floor(power);
},
/**
* Calculate production per second
*/
getProductionPerSecond() {
let total = 0;
for (const [buildingId, count] of Object.entries(this.state.buildings)) {
if (count === 0) continue;
const building = BUILDINGS[buildingId];
if (!building) continue;
let production = building.baseProduction * count;
// Apply prestige multiplier
const prestigeMultiplier = PRESTIGE.getMultiplier();
production *= prestigeMultiplier;
// Apply prestige upgrades
for (const upgradeId of (this.state.prestigeUpgrades || [])) {
const upgrade = PRESTIGE_UPGRADES[upgradeId];
if (upgrade && upgrade.effect === 'productionMultiplier') {
production *= upgrade.value;
}
}
// Apply upgrade bonuses
for (const upgradeId of this.state.upgrades) {
const upgrade = UPGRADES[upgradeId];
if (upgrade && upgrade.buildingBonus && upgrade.buildingBonus[buildingId]) {
production *= upgrade.buildingBonus[buildingId];
}
if (upgrade && upgrade.globalBonus) {
production *= upgrade.globalBonus;
}
}
total += production;
}
return total;
},
/**
* Get building cost
*/
getBuildingCost(buildingId) {
const building = BUILDINGS[buildingId];
const count = this.state.buildings[buildingId] || 0;
let cost = Math.floor(building.baseCost * Math.pow(this.config.costScaling, count));
// Apply prestige cost reduction
for (const upgradeId of (this.state.prestigeUpgrades || [])) {
const upgrade = PRESTIGE_UPGRADES[upgradeId];
if (upgrade && upgrade.effect === 'costReduction') {
cost *= upgrade.value;
}
}
return Math.floor(cost);
},
/**
* Buy a building
*/
buyBuilding(buildingId) {
const cost = this.getBuildingCost(buildingId);
if (this.state.stardust >= cost) {
this.state.stardust -= cost;
this.state.buildings[buildingId] = (this.state.buildings[buildingId] || 0) + 1;
UI.showNotification(`Bought ${BUILDINGS[buildingId].name}!`, 'success');
// Audio feedback
Audio.playPurchase();
// Particle effect on building purchase
const card = document.getElementById(`building-${buildingId}`);
if (card) {
const rect = card.getBoundingClientRect();
Particles.createBuildingEffect(rect.left + rect.width / 2, rect.top + rect.height / 2);
}
UI.update();
return true;
}
return false;
},
/**
* Buy an upgrade
*/
buyUpgrade(upgradeId) {
const upgrade = UPGRADES[upgradeId];
if (!upgrade || this.state.upgrades.includes(upgradeId)) {
return false;
}
if (this.state.stardust >= upgrade.cost) {
// Check requirements
if (upgrade.requirements) {
for (const [reqId, reqCount] of Object.entries(upgrade.requirements)) {
const currentCount = this.state.buildings[reqId] || 0;
if (currentCount < reqCount) {
UI.showNotification('Requirements not met!', 'warning');
return false;
}
}
}
this.state.stardust -= upgrade.cost;
this.state.upgrades.push(upgradeId);
UI.showNotification(`Unlocked: ${upgrade.name}!`, 'success');
UI.update();
return true;
}
return false;
},
/**
* Game tick - called every config.tickRate ms
*/
tick() {
const production = this.getProductionPerSecond();
const tickSeconds = this.config.tickRate / 1000;
if (production > 0) {
this.addStardust(production * tickSeconds);
}
UI.update();
Achievements.check();
},
/**
* Start the game loop
*/
startGameLoop() {
setInterval(() => this.tick(), this.config.tickRate);
},
/**
* Start auto-save
*/
startAutoSave() {
setInterval(() => {
this.save();
document.getElementById('last-saved').textContent =
`Last saved: ${new Date().toLocaleTimeString()}`;
}, this.config.autoSaveInterval);
},
/**
* Save game state
*/
save() {
this.state.lastSaveTime = Date.now();
localStorage.setItem('cosmicGardenSave', JSON.stringify(this.state));
},
/**
* Load game state
*/
loadState() {
const saved = localStorage.getItem('cosmicGardenSave');
if (saved) {
try {
const parsed = JSON.parse(saved);
this.state = { ...this.state, ...parsed };
// Calculate offline progress
const offlineTime = (Date.now() - this.state.lastSaveTime) / 1000;
if (offlineTime > 60) {
const production = this.getProductionPerSecond();
let offlineRate = 0.5; // 50% offline efficiency
for (const upgradeId of (this.state.prestigeUpgrades || [])) {
const upgrade = PRESTIGE_UPGRADES[upgradeId];
if (upgrade && upgrade.effect === 'offlineBonus') {
offlineRate = upgrade.value;
}
}
const offlineEarnings = production * offlineTime * offlineRate;
if (offlineEarnings > 0) {
this.addStardust(offlineEarnings);
UI.showNotification(
`Welcome back! Earned ${this.formatNumber(offlineEarnings)} ✨ while away`,
'success'
);
}
}
} catch (e) {
console.error('Failed to load save:', e);
}
}
},
/**
* Load from imported data
*/
loadFromData(data) {
try {
const parsed = JSON.parse(atob(data));
this.state = { ...this.state, ...parsed };
UI.update();
} catch (e) {
UI.showNotification('Invalid save data!', 'warning');
}
},
/**
* Export save as base64
*/
exportSave() {
this.save();
const data = btoa(JSON.stringify(this.state));
navigator.clipboard.writeText(data).then(() => {
UI.showNotification('Save copied to clipboard!', 'success');
});
},
/**
* Reset game
*/
reset() {
localStorage.removeItem('cosmicGardenSave');
this.state = {
stardust: 0,
totalStardust: 0,
totalClicks: 0,
clickPower: 1,
startTime: Date.now(),
lastSaveTime: Date.now(),
buildings: {},
upgrades: [],
achievements: [],
stellarEssence: 0,
prestigeCount: 0,
totalPrestigeEssence: 0,
prestigeUpgrades: []
};
UI.update();
UI.showNotification('Game reset!', 'warning');
},
/**
* Format numbers with commas
*/
formatNumber(num) {
if (num >= 1e12) return (num / 1e12).toFixed(2) + 'T';
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
return Math.floor(num).toLocaleString();
}
};
// Make Game globally accessible
window.Game = Game;