// Main Game Module - Orchestrates the entire game
const AICivilizationGame = (function() {
// Private variables
let gameState = null;
let lastSaveTime = 0;
const AUTOSAVE_INTERVAL = 30000; // Save every 30 seconds
// Initialize the game
function init() {
console.log('Initializing AI Civilization Game...');
// Load saved game or create a new one
gameState = StorageManager.loadGame() || {
world: WorldGenerator.generateNewWorld(),
turn: 0,
timestamp: Date.now(),
characters: [],
resources: ResourceManager.getInitialResources(),
buildings: [],
technologies: [],
events: []
};
// Initialize each module with the game state
WorldRenderer.init(gameState.world);
CharacterManager.init(gameState.characters);
ResourceManager.init(gameState.resources);
BuildingManager.init(gameState.buildings);
TechnologyManager.init(gameState.technologies);
EventManager.init(gameState.events);
// Create initial characters if new game
if (gameState.characters.length === 0) {
populateInitialCharacters();
}
// Set up the game loop
setupGameLoop();
// Set up the autosave
setInterval(saveGame, AUTOSAVE_INTERVAL);
// Render the initial state
render();
console.log('Game initialized successfully!');
}
// Create initial AI characters
function populateInitialCharacters() {
console.log('Creating initial characters...');
const initialCharacters = [
CharacterFactory.createCharacter('leader', 'Founder', {intelligence: 8, cooperation: 7, creativity: 6}),
CharacterFactory.createCharacter('builder', 'Constructor', {intelligence: 6, cooperation: 8, creativity: 5}),
CharacterFactory.createCharacter('scientist', 'Researcher', {intelligence: 9, cooperation: 5, creativity: 7})
];
initialCharacters.forEach(character => {
CharacterManager.addCharacter(character);
gameState.characters.push(character);
});
}
// Set up the main game loop
function setupGameLoop() {
console.log('Setting up game loop...');
const gameLoop = setInterval(() => {
update();
render();
}, 1000); // Update every second
}
// Update game state
function update() {
gameState.turn++;
// Update each character's state and actions
CharacterManager.updateCharacters(gameState);
// Check for character interactions
CharacterManager.processInteractions(gameState);
// Update resources based on buildings and character actions
ResourceManager.updateResources(gameState);
// Check for new buildings completion
BuildingManager.updateBuildings(gameState);
// Check technology progress
TechnologyManager.updateTechnologies(gameState);
// Generate random events
EventManager.generateEvents(gameState);
// Check if new characters should emerge
if (shouldCreateNewCharacter()) {
createNewCharacter();
}
// Check win/loss conditions
checkGameConditions();
}
// Render the game state to the UI
function render() {
// Update UI elements with current game state
WorldRenderer.render(gameState.world);
UIManager.renderCharacters(gameState.characters);
UIManager.renderResources(gameState.resources);
UIManager.renderBuildings(gameState.buildings);
UIManager.renderTechnologies(gameState.technologies);
UIManager.renderEvents(gameState.events.slice(-5)); // Show latest 5 events
}
// Determine if a new character should emerge
function shouldCreateNewCharacter() {
// Create a new character every 50 turns with some randomness
if (gameState.turn % 50 === 0) {
return Math.random() < 0.7; // 70% chance on eligible turns
}
// Rare chance (1%) of character emerging on any turn
return Math.random() < 0.01;
}
// Create a new character based on current civilization state
function createNewCharacter() {
console.log('Creating a new emergent character...');
// Determine which type of character would be most beneficial
const currentNeeds = analyzeCurrentNeeds();
const newCharacter = CharacterFactory.createEmergentCharacter(currentNeeds, gameState);
// Add to the game
CharacterManager.addCharacter(newCharacter);
gameState.characters.push(newCharacter);
// Announce the new character
EventManager.addEvent({
type: 'character',
message: `A new character named ${newCharacter.name} has emerged in your civilization!`,
details: `Role: ${newCharacter.role}, Special ability: ${newCharacter.specialAbility}`,
turn: gameState.turn
});
}
// Analyze what the civilization currently needs
function analyzeCurrentNeeds() {
// Check resource balance
const resourceBalance = ResourceManager.analyzeBalance();
// Check character role distribution
const roleDistribution = CharacterManager.analyzeRoleDistribution();
// Check technology gaps
const technologyGaps = TechnologyManager.analyzeGaps();
// Check building needs
const buildingNeeds = BuildingManager.analyzeNeeds(gameState.world);
return {
resourceBalance,
roleDistribution,
technologyGaps,
buildingNeeds,
worldState: gameState.world.developmentLevel
};
}
// Check win/loss conditions
function checkGameConditions() {
// For simplicity, we're not implementing end conditions yet
// This would check for civilization collapse or advancement milestones
}
// Save the current game state
function saveGame() {
gameState.timestamp = Date.now();
StorageManager.saveGame(gameState);
lastSaveTime = Date.now();
console.log('Game saved at turn', gameState.turn);
}
// Public API
return {
init,
saveGame,
getGameState: () => gameState
};
})();
// Character Factory - Creates different types of AI characters
const CharacterFactory = (function() {
// Character templates
const templates = {
leader: {
baseIntelligence: 7,
baseCooperation: 6,
baseCreativity: 5,
specialAbilities: ['inspiration', 'governance', 'diplomacy'],
behaviors: ['prioritizeCommunity', 'planLongTerm', 'delegateTasks']
},
builder: {
baseIntelligence: 5,
baseCooperation: 7,
baseCreativity: 6,
specialAbilities: ['construction', 'resourceGathering', 'optimization'],
behaviors: ['focusOnTasks', 'resourceConservation', 'improveInfrastructure']
},
scientist: {
baseIntelligence: 8,
baseCooperation: 4,
baseCreativity: 7,
specialAbilities: ['research', 'invention', 'analysis'],
behaviors: ['seekKnowledge', 'experiment', 'solveProblems']
},
farmer: {
baseIntelligence: 5,
baseCooperation: 8,
baseCreativity: 4,
specialAbilities: ['cultivation', 'animalHusbandry', 'weatherPrediction'],
behaviors: ['workWithNature', 'planSeasons', 'shareHarvest']
},
merchant: {
baseIntelligence: 6,
baseCooperation: 7,
baseCreativity: 6,
specialAbilities: ['negotiation', 'resourceDistribution', 'valueAssessment'],
behaviors: ['seekOpportunities', 'buildNetwork', 'balanceResources']
},
explorer: {
baseIntelligence: 6,
baseCooperation: 5,
baseCreativity: 8,
specialAbilities: ['discovery', 'mapmaking', 'adaptation'],
behaviors: ['exploreBorders', 'documentFindings', 'findResources']
}
};
// Name generators for different character types
const nameGenerators = {
leader: () => {
const prefixes = ['Grand', 'High', 'Chief', 'Elder', 'Wise'];
const roots = ['Lead', 'Command', 'Direct', 'Guide', 'Rule'];
const suffixes = ['er', 'or', 'ant', 'ium', 'us'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
builder: () => {
const prefixes = ['Master', 'Crafty', 'Skilled', 'Artisan', 'Worker'];
const roots = ['Build', 'Craft', 'Construct', 'Form', 'Shape'];
const suffixes = ['er', 'or', 'ist', 'man', 'hand'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
scientist: () => {
const prefixes = ['Prof', 'Dr', 'Theorist', 'Thinker', 'Sage'];
const roots = ['Think', 'Solve', 'Know', 'Learn', 'Discover'];
const suffixes = ['er', 'or', 'ium', 'mind', 'brain'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
farmer: () => {
const prefixes = ['Tender', 'Grower', 'Field', 'Harvest', 'Nature'];
const roots = ['Farm', 'Grow', 'Plant', 'Harvest', 'Tend'];
const suffixes = ['er', 'ist', 'man', 'hand', 'keeper'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
merchant: () => {
const prefixes = ['Trade', 'Market', 'Commerce', 'Wealth', 'Deal'];
const roots = ['Trade', 'Sell', 'Barter', 'Deal', 'Exchange'];
const suffixes = ['er', 'man', 'master', 'broker', 'hand'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
explorer: () => {
const prefixes = ['Brave', 'Bold', 'Venture', 'Journey', 'Quest'];
const roots = ['Explore', 'Discover', 'Venture', 'Seek', 'Find'];
const suffixes = ['er', 'or', 'ist', 'soul', 'heart'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
}
};
// Helper function to get a random item from an array
function getRandomItem(array) {
return array[Math.floor(Math.random() * array.length)];
}
// Create a character with the given role and specified attributes
function createCharacter(role, name, attributes = {}) {
if (!templates[role]) {
console.error(`Unknown character role: ${role}`);
return null;
}
const template = templates[role];
const generatedName = name || nameGenerators[role]();
// Create the character by combining template with custom attributes
const character = {
id: `char_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
name: generatedName,
role: role,
createdTurn: 0, // Will be set when added to the game
intelligence: attributes.intelligence || template.baseIntelligence,
cooperation: attributes.cooperation || template.baseCooperation,
creativity: attributes.creativity || template.baseCreativity,
specialAbility: attributes.specialAbility || getRandomItem(template.specialAbilities),
behaviors: template.behaviors,
currentTask: null,
relationships: {}, // Will track relationships with other characters
knowledge: [], // Will track what this character knows
resources: {}, // Resources currently assigned to this character
experience: 0,
level: 1,
history: [] // Track character's achievements and actions
};
return character;
}
// Create a character with traits influenced by the current game state
function createEmergentCharacter(currentNeeds, gameState) {
// Determine which role would best complement the current civilization
const roleWeights = calculateRoleWeights(currentNeeds);
const role = chooseWeightedRandom(roleWeights);
// Generate a name based on the chosen role
const name = nameGenerators[role]();
// Determine special attributes based on current needs
const attributes = {
intelligence: Math.floor(Math.random() * 4) + 5, // Base 5-8
cooperation: Math.floor(Math.random() * 4) + 5, // Base 5-8
creativity: Math.floor(Math.random() * 4) + 5, // Base 5-8
};
// Boost the most needed attribute based on current needs
if (currentNeeds.technologyGaps.length > 0) {
attributes.intelligence += 2;
}
if (Object.keys(currentNeeds.roleDistribution).length < 3) {
attributes.cooperation += 2;
}
if (currentNeeds.buildingNeeds.urgent) {
attributes.creativity += 2;
}
// Create the character
return createCharacter(role, name, attributes);
}
// Calculate weights for each role based on current needs
function calculateRoleWeights(currentNeeds) {
const weights = {
leader: 1,
builder: 1,
scientist: 1,
farmer: 1,
merchant: 1,
explorer: 1
};
// Adjust weights based on resource balance
if (currentNeeds.resourceBalance.food < 0) weights.farmer += 2;
if (currentNeeds.resourceBalance.materials < 0) weights.builder += 1;
if (currentNeeds.resourceBalance.knowledge < 0) weights.scientist += 2;
if (currentNeeds.resourceBalance.wealth < 0) weights.merchant += 2;
// Adjust weights based on role distribution
for (const role in currentNeeds.roleDistribution) {
weights[role] -= currentNeeds.roleDistribution[role] * 0.5;
}
// Adjust weights based on technology gaps
if (currentNeeds.technologyGaps.includes('construction')) weights.builder += 1;
if (currentNeeds.technologyGaps.includes('agriculture')) weights.farmer += 1;
if (currentNeeds.technologyGaps.includes('science')) weights.scientist += 2;
if (currentNeeds.technologyGaps.includes('trade')) weights.merchant += 1;
if (currentNeeds.technologyGaps.includes('exploration')) weights.explorer += 2;
// Ensure no negative weights
for (const role in weights) {
weights[role] = Math.max(weights[role], 0.1);
}
return weights;
}
// Choose a random item based on weights
function chooseWeightedRandom(weights) {
const totalWeight = Object.values(weights).reduce((sum, weight) => sum + weight, 0);
let random = Math.random() * totalWeight;
for (const item in weights) {
random -= weights[item];
if (random <= 0) {
return item;
}
}
// Fallback
return Object.keys(weights)[0];
}
// Return public API
return {
createCharacter,
createEmergentCharacter
};
})();
// Character Manager - Handles all character-related functions
const CharacterManager = (function() {
// Private variables
let characters = [];
// Initialize with existing characters
function init(existingCharacters) {
characters = existingCharacters || [];
console.log(`Initialized CharacterManager with ${characters.length} characters`);
}
// Add a new character to the game
function addCharacter(character) {
character.createdTurn = AICivilizationGame.getGameState().turn;
character.relationships = {}; // Initialize empty relationships
// Set up initial relationships with existing characters
characters.forEach(existingChar => {
// Generate an initial relationship value (30-70)
const initialRelationship = Math.floor(Math.random() * 40) + 30;
// Higher if roles are complementary
const complementaryBonus = isComplementaryRole(character.role, existingChar.role) ? 10 : 0;
// Higher if cooperation stat is high
const cooperationBonus = (character.cooperation + existingChar.cooperation) / 4;
const relationshipValue = Math.min(initialRelationship + complementaryBonus + cooperationBonus, 100);
// Set relationship in both directions
character.relationships[existingChar.id] = Math.floor(relationshipValue);
existingChar.relationships[character.id] = Math.floor(relationshipValue);
});
characters.push(character);
console.log(`Added character ${character.name} (${character.role})`);
return character;
}
// Check if two roles complement each other
function isComplementaryRole(role1, role2) {
const complementaryPairs = [
['leader', 'builder'],
['scientist', 'builder'],
['farmer', 'merchant'],
['explorer', 'scientist'],
['merchant', 'builder']
];
return complementaryPairs.some(pair =>
(pair[0] === role1 && pair[1] === role2) ||
(pair[0] === role2 && pair[1] === role1)
);
}
// Update all characters state
function updateCharacters(gameState) {
characters.forEach(character => {
// Increase experience
character.experience += 1;
// Level up if enough experience
if (character.experience >= character.level * 10) {
levelUpCharacter(character);
}
// Assign a task if the character doesn't have one
if (!character.currentTask) {
assignTask(character, gameState);
}
// Progress on current task
if (character.currentTask) {
progressTask(character, gameState);
}
// Update relationships based on interactions
updateRelationships(character);
// Make decisions based on character's role and personality
makeDecisions(character, gameState);
});
}
// Level up a character
function levelUpCharacter(character) {
character.level += 1;
character.experience = 0;
// Improve stats based on role
if (character.role === 'leader') character.cooperation += 1;
if (character.role === 'builder') character.creativity += 1;
if (character.role === 'scientist') character.intelligence += 1;
if (character.role === 'farmer') character.cooperation += 1;
if (character.role === 'merchant') character.intelligence += 1;
if (character.role === 'explorer') character.creativity += 1;
// Add event to log
EventManager.addEvent({
type: 'character',
message: `${character.name} has reached level ${character.level}!`,
details: `Improved abilities and gained new insights.`,
turn: AICivilizationGame.getGameState().turn
});
console.log(`${character.name} leveled up to ${character.level}`);
}
// Assign a task to a character
function assignTask(character, gameState) {
const possibleTasks = getPossibleTasks(character, gameState);
const task = chooseTask(character, possibleTasks);
if (task) {
character.currentTask = {
...task,
progress: 0,
startedTurn: gameState.turn
};
console.log(`${character.name} assigned task: ${task.name}`);
}
}
// Get possible tasks for a character based on their role and game state
function getPossibleTasks(character, gameState) {
const tasks = [];
// Common tasks for all roles
tasks.push({
name: 'Gather resources',
duration: 5,
difficulty: 1,
outcome: {
type: 'resources',
details: {
food: character.role === 'farmer' ? 15 : 5,
materials: character.role === 'builder' ? 15 : 5,
knowledge: character.role === 'scientist' ? 15 : 5
}
}
});
// Role-specific tasks
switch (character.role) {
case 'leader':
tasks.push({
name: 'Organize community',
duration: 10,
difficulty: 3,
outcome: {
type: 'community',
details: {
productivityBoost: 0.1,
duration: 20
}
}
});
tasks.push({
name: 'Diplomatic mission',
duration: 15,
difficulty: 4,
outcome: {
type: 'event',
details: {
eventType: 'diplomatic',
chance: 0.7
}
}
});
break;
case 'builder':
// Check for available buildings to construct
const availableBuildings = BuildingManager.getAvailableBuildings(gameState);
availableBuildings.forEach(building => {
tasks.push({
name: `Build ${building.name}`,
duration: building.constructionTime,
difficulty: building.difficulty,
outcome: {
type: 'building',
details: {
buildingId: building.id
}
}
});
});
break;
case 'scientist':
// Check for available technologies to research
const availableTechs = TechnologyManager.getAvailableTechnologies(gameState);
availableTechs.forEach(tech => {
tasks.push({
name: `Research ${tech.name}`,
duration: tech.researchTime,
difficulty: tech.difficulty,
outcome: {
type: 'technology',
details: {
techId: tech.id
}
}
});
});
break;
case 'farmer':
tasks.push({
name: 'Improve agriculture',
duration: 8,
difficulty: 2,
outcome: {
type: 'production',
details: {
resourceType: 'food',
boost: 0.15,
duration: 15
}
}
});
break;
case 'merchant':
tasks.push({
name: 'Establish trade route',
duration: 12,
difficulty: 3,
outcome: {
type: 'trade',
details: {
resourceGain: ['food', 'materials', 'knowledge'],
amount: 5
}
}
});
break;
case 'explorer':
tasks.push({
name: 'Explore new territories',
duration: 15,
difficulty: 4,
outcome: {
type: 'exploration',
details: {
newRegions: Math.floor(Math.random() * 2) + 1,
resourceFind: Math.random() > 0.5
}
}
});
break;
}
return tasks;
}
// Choose a task based on character personality and needs
function chooseTask(character, tasks) {
if (tasks.length === 0) return null;
// Calculate weight for each task based on character attributes
const taskWeights = tasks.map(task => {
let weight = 1;
// Intelligence favors difficult tasks with big payoffs
weight += (character.intelligence / 10) * task.difficulty;
// Creativity favors varied/different tasks from history
if (!hasRecentlyPerformedTask(character, task.name)) {
weight += character.creativity / 10;
}
// Cooperation favors tasks that benefit others
if (task.outcome.type === 'community' || task.outcome.type === 'trade') {
weight += character.cooperation / 10;
}
return {
task,
weight
};
});
// Sort by weight and pick the highest
taskWeights.sort((a, b) => b.weight - a.weight);
return taskWeights[0].task;
}
// Check if character has recently performed a task
function hasRecentlyPerformedTask(character, taskName) {
const recentTasks = character.history.slice(-3);
return recentTasks.some(historyItem =>
historyItem.type === 'task' && historyItem.details.name === taskName
);
}
// Progress the current task
function progressTask(character, gameState) {
const task = character.currentTask;
// Calculate progress increment based on character attributes
let progressIncrement = 1; // Base progress
// Intelligence speeds up progress
progressIncrement += (character.intelligence - 5) * 0.1;
// Creativity helps with difficult tasks
if (task.difficulty > 2) {
progressIncrement += (character.creativity - 5) * 0.1;
}
// Update progress
task.progress += progressIncrement;
// Check if task is complete
if (task.progress >= task.duration) {
completeTask(character, gameState);
}
}
// Complete a task and apply its outcome
function completeTask(character, gameState) {
const task = character.currentTask;
// Apply outcome based on type
switch (task.outcome.type) {
case 'resources':
for (const resource in task.outcome.details) {
ResourceManager.addResource(resource, task.outcome.details[resource]);
}
break;
case 'building':
BuildingManager.constructBuilding(task.outcome.details.buildingId, gameState);
break;
case 'technology':
TechnologyManager.researchTechnology(task.outcome.details.techId, gameState);
break;
case 'community':
// Apply community boost
EventManager.addEvent({
type: 'community',
message: `${character.name} has boosted community productivity!`,
details: `+${task.outcome.details.productivityBoost * 100}% for ${task.outcome.details.duration} turns`,
turn: gameState.turn,
duration: task.outcome.details.duration
});
break;
case 'production':
// Apply production boost
EventManager.addEvent({
type: 'production',
message: `${character.name} has improved ${task.outcome.details.resourceType} production!`,
details: `+${task.outcome.details.boost * 100}% for ${task.outcome.details.duration} turns`,
turn: gameState.turn,
resourceType: task.outcome.details.resourceType,
boost: task.outcome.details.boost,
duration: task.outcome.details.duration
});
break;
case 'trade':
// Apply trade outcome
task.outcome.details.resourceGain.forEach(resource => {
ResourceManager.addResource(resource, task.outcome.details.amount);
});
EventManager.addEvent({
type: 'trade',
message: `${character.name} has established a new trade route!`,
details: `Gaining various resources each turn`,
turn: gameState.turn,
ongoing: true
});
break;
case 'exploration':
// Apply exploration outcome
WorldGenerator.addRegions(task.outcome.details.newRegions);
if (task.outcome.details.resourceFind) {
const resourceType = ['food', 'materials', 'knowledge'][Math.floor(Math.random() * 3)];
const amount = Math.floor(Math.random() * 20) + 10;
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `Found ${amount} ${resourceType} in the process`,
turn: gameState.turn
});
} else {
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `The world map has been expanded`,
turn: gameState.turn
});
}
break;
case 'event':
// Generate a special event
if (Math.random() < task.outcome.details.chance) {
EventManager.generateSpecialEvent(task.outcome.details.eventType, character);
}
break;
}
// Add experience based on task difficulty
character.experience += task.difficulty * 2;
// Add to character history
character.history.push({
type: 'task',
details: {
name: task.name,
completed: gameState.turn
}
});
// Clear current task
character.currentTask = null;
console.log(`${character.name} completed task: ${task.name}`);
}
// Process interactions between characters
function processInteractions(gameState) {
// For each pair of characters
for (let i = 0; i < characters.length; i++) {
for (let j = i + 1; j < characters.length; j++) {
const char1 = characters[i];
const char2 = characters[j];
// Determine if they interact this turn (based on proximity and current tasks)
if (shouldInteract(char1, char2, gameState)) {
// Generate an interaction
const interaction = generateInteraction(char1, char2, gameState);
// Apply the results of the interaction
applyInteraction(interaction, game
I'll create a modular architecture with each major component in its own file, ensuring no file exceeds 12,000 characters. Here's the revised structure:
// main.js - The entry point for the game
// Handles initialization and the main game loop
import { StorageManager } from './storage-manager.js';
import { WorldGenerator } from './world-generator.js';
import { WorldRenderer } from './world-renderer.js';
import { CharacterManager } from './characters/character-manager.js';
import { CharacterFactory } from './characters/character-factory.js';
import { ResourceManager } from './resources/resource-manager.js';
import { BuildingManager } from './buildings/building-manager.js';
import { TechnologyManager } from './technology/technology-manager.js';
import { EventManager } from './events/event-manager.js';
import { UIManager } from './ui/ui-manager.js';
// Main Game Module - Orchestrates the entire game
const AICivilizationGame = (function() {
// Private variables
let gameState = null;
let lastSaveTime = 0;
const AUTOSAVE_INTERVAL = 30000; // Save every 30 seconds
// Initialize the game
function init() {
console.log('Initializing AI Civilization Game...');
// Load saved game or create a new one
gameState = StorageManager.loadGame() || {
world: WorldGenerator.generateNewWorld(),
turn: 0,
timestamp: Date.now(),
characters: [],
resources: ResourceManager.getInitialResources(),
buildings: [],
technologies: [],
events: []
};
// Initialize each module with the game state
WorldRenderer.init(gameState.world);
CharacterManager.init(gameState.characters);
ResourceManager.init(gameState.resources);
BuildingManager.init(gameState.buildings);
TechnologyManager.init(gameState.technologies);
EventManager.init(gameState.events);
// Create initial characters if new game
if (gameState.characters.length === 0) {
populateInitialCharacters();
}
// Set up the game loop
setupGameLoop();
// Set up the autosave
setInterval(saveGame, AUTOSAVE_INTERVAL);
// Render the initial state
render();
console.log('Game initialized successfully!');
}
// Create initial AI characters
function populateInitialCharacters() {
console.log('Creating initial characters...');
const initialCharacters = [
CharacterFactory.createCharacter('leader', 'Founder', {intelligence: 8, cooperation: 7, creativity: 6}),
CharacterFactory.createCharacter('builder', 'Constructor', {intelligence: 6, cooperation: 8, creativity: 5}),
CharacterFactory.createCharacter('scientist', 'Researcher', {intelligence: 9, cooperation: 5, creativity: 7})
];
initialCharacters.forEach(character => {
CharacterManager.addCharacter(character);
gameState.characters.push(character);
});
}
// Set up the main game loop
function setupGameLoop() {
console.log('Setting up game loop...');
const gameLoop = setInterval(() => {
update();
render();
}, 1000); // Update every second
}
// Update game state
function update() {
gameState.turn++;
// Update each character's state and actions
CharacterManager.updateCharacters(gameState);
// Check for character interactions
CharacterManager.processInteractions(gameState);
// Update resources based on buildings and character actions
ResourceManager.updateResources(gameState);
// Check for new buildings completion
BuildingManager.updateBuildings(gameState);
// Check technology progress
TechnologyManager.updateTechnologies(gameState);
// Generate random events
EventManager.generateEvents(gameState);
// Check if new characters should emerge
if (shouldCreateNewCharacter()) {
createNewCharacter();
}
// Check win/loss conditions
checkGameConditions();
}
// Render the game state to the UI
function render() {
// Update UI elements with current game state
WorldRenderer.render(gameState.world);
UIManager.renderCharacters(gameState.characters);
UIManager.renderResources(gameState.resources);
UIManager.renderBuildings(gameState.buildings);
UIManager.renderTechnologies(gameState.technologies);
UIManager.renderEvents(gameState.events.slice(-5)); // Show latest 5 events
}
// Determine if a new character should emerge
function shouldCreateNewCharacter() {
// Create a new character every 50 turns with some randomness
if (gameState.turn % 50 === 0) {
return Math.random() < 0.7; // 70% chance on eligible turns
}
// Rare chance (1%) of character emerging on any turn
return Math.random() < 0.01;
}
// Create a new character based on current civilization state
function createNewCharacter() {
console.log('Creating a new emergent character...');
// Determine which type of character would be most beneficial
const currentNeeds = analyzeCurrentNeeds();
const newCharacter = CharacterFactory.createEmergentCharacter(currentNeeds, gameState);
// Add to the game
CharacterManager.addCharacter(newCharacter);
gameState.characters.push(newCharacter);
// Announce the new character
EventManager.addEvent({
type: 'character',
message: `A new character named ${newCharacter.name} has emerged in your civilization!`,
details: `Role: ${newCharacter.role}, Special ability: ${newCharacter.specialAbility}`,
turn: gameState.turn
});
}
// Analyze what the civilization currently needs
function analyzeCurrentNeeds() {
// Check resource balance
const resourceBalance = ResourceManager.analyzeBalance();
// Check character role distribution
const roleDistribution = CharacterManager.analyzeRoleDistribution();
// Check technology gaps
const technologyGaps = TechnologyManager.analyzeGaps();
// Check building needs
const buildingNeeds = BuildingManager.analyzeNeeds(gameState.world);
return {
resourceBalance,
roleDistribution,
technologyGaps,
buildingNeeds,
worldState: gameState.world.developmentLevel
};
}
// Check win/loss conditions
function checkGameConditions() {
// For simplicity, we're not implementing end conditions yet
// This would check for civilization collapse or advancement milestones
}
// Save the current game state
function saveGame() {
gameState.timestamp = Date.now();
StorageManager.saveGame(gameState);
lastSaveTime = Date.now();
console.log('Game saved at turn', gameState.turn);
}
// Public API
return {
init,
saveGame,
getGameState: () => gameState
};
})();
// Export the game object for usage in other modules and initialization
export { AICivilizationGame };
// Initialize the game when the page loads
window.addEventListener('DOMContentLoaded', () => {
AICivilizationGame.init();
});
// character-factory.js
// Creates different types of AI characters with appropriate attributes and abilities
// Character Factory - Creates different types of AI characters
const CharacterFactory = (function() {
// Character templates
const templates = {
leader: {
baseIntelligence: 7,
baseCooperation: 6,
baseCreativity: 5,
specialAbilities: ['inspiration', 'governance', 'diplomacy'],
behaviors: ['prioritizeCommunity', 'planLongTerm', 'delegateTasks']
},
builder: {
baseIntelligence: 5,
baseCooperation: 7,
baseCreativity: 6,
specialAbilities: ['construction', 'resourceGathering', 'optimization'],
behaviors: ['focusOnTasks', 'resourceConservation', 'improveInfrastructure']
},
scientist: {
baseIntelligence: 8,
baseCooperation: 4,
baseCreativity: 7,
specialAbilities: ['research', 'invention', 'analysis'],
behaviors: ['seekKnowledge', 'experiment', 'solveProblems']
},
farmer: {
baseIntelligence: 5,
baseCooperation: 8,
baseCreativity: 4,
specialAbilities: ['cultivation', 'animalHusbandry', 'weatherPrediction'],
behaviors: ['workWithNature', 'planSeasons', 'shareHarvest']
},
merchant: {
baseIntelligence: 6,
baseCooperation: 7,
baseCreativity: 6,
specialAbilities: ['negotiation', 'resourceDistribution', 'valueAssessment'],
behaviors: ['seekOpportunities', 'buildNetwork', 'balanceResources']
},
explorer: {
baseIntelligence: 6,
baseCooperation: 5,
baseCreativity: 8,
specialAbilities: ['discovery', 'mapmaking', 'adaptation'],
behaviors: ['exploreBorders', 'documentFindings', 'findResources']
}
};
// Name generators for different character types
const nameGenerators = {
leader: () => {
const prefixes = ['Grand', 'High', 'Chief', 'Elder', 'Wise'];
const roots = ['Lead', 'Command', 'Direct', 'Guide', 'Rule'];
const suffixes = ['er', 'or', 'ant', 'ium', 'us'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
builder: () => {
const prefixes = ['Master', 'Crafty', 'Skilled', 'Artisan', 'Worker'];
const roots = ['Build', 'Craft', 'Construct', 'Form', 'Shape'];
const suffixes = ['er', 'or', 'ist', 'man', 'hand'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
scientist: () => {
const prefixes = ['Prof', 'Dr', 'Theorist', 'Thinker', 'Sage'];
const roots = ['Think', 'Solve', 'Know', 'Learn', 'Discover'];
const suffixes = ['er', 'or', 'ium', 'mind', 'brain'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
farmer: () => {
const prefixes = ['Tender', 'Grower', 'Field', 'Harvest', 'Nature'];
const roots = ['Farm', 'Grow', 'Plant', 'Harvest', 'Tend'];
const suffixes = ['er', 'ist', 'man', 'hand', 'keeper'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
merchant: () => {
const prefixes = ['Trade', 'Market', 'Commerce', 'Wealth', 'Deal'];
const roots = ['Trade', 'Sell', 'Barter', 'Deal', 'Exchange'];
const suffixes = ['er', 'man', 'master', 'broker', 'hand'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
},
explorer: () => {
const prefixes = ['Brave', 'Bold', 'Venture', 'Journey', 'Quest'];
const roots = ['Explore', 'Discover', 'Venture', 'Seek', 'Find'];
const suffixes = ['er', 'or', 'ist', 'soul', 'heart'];
return `${getRandomItem(prefixes)} ${getRandomItem(roots)}${getRandomItem(suffixes)}`;
}
};
// Helper function to get a random item from an array
function getRandomItem(array) {
return array[Math.floor(Math.random() * array.length)];
}
// Create a character with the given role and specified attributes
function createCharacter(role, name, attributes = {}) {
if (!templates[role]) {
console.error(`Unknown character role: ${role}`);
return null;
}
const template = templates[role];
const generatedName = name || nameGenerators[role]();
// Create the character by combining template with custom attributes
const character = {
id: `char_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
name: generatedName,
role: role,
createdTurn: 0, // Will be set when added to the game
intelligence: attributes.intelligence || template.baseIntelligence,
cooperation: attributes.cooperation || template.baseCooperation,
creativity: attributes.creativity || template.baseCreativity,
specialAbility: attributes.specialAbility || getRandomItem(template.specialAbilities),
behaviors: template.behaviors,
currentTask: null,
relationships: {}, // Will track relationships with other characters
knowledge: [], // Will track what this character knows
resources: {}, // Resources currently assigned to this character
experience: 0,
level: 1,
history: [] // Track character's achievements and actions
};
return character;
}
// Create a character with traits influenced by the current game state
function createEmergentCharacter(currentNeeds, gameState) {
// Determine which role would best complement the current civilization
const roleWeights = calculateRoleWeights(currentNeeds);
const role = chooseWeightedRandom(roleWeights);
// Generate a name based on the chosen role
const name = nameGenerators[role]();
// Determine special attributes based on current needs
const attributes = {
intelligence: Math.floor(Math.random() * 4) + 5, // Base 5-8
cooperation: Math.floor(Math.random() * 4) + 5, // Base 5-8
creativity: Math.floor(Math.random() * 4) + 5, // Base 5-8
};
// Boost the most needed attribute based on current needs
if (currentNeeds.technologyGaps.length > 0) {
attributes.intelligence += 2;
}
if (Object.keys(currentNeeds.roleDistribution).length < 3) {
attributes.cooperation += 2;
}
if (currentNeeds.buildingNeeds.urgent) {
attributes.creativity += 2;
}
// Create the character
return createCharacter(role, name, attributes);
}
// Calculate weights for each role based on current needs
function calculateRoleWeights(currentNeeds) {
const weights = {
leader: 1,
builder: 1,
scientist: 1,
farmer: 1,
merchant: 1,
explorer: 1
};
// Adjust weights based on resource balance
if (currentNeeds.resourceBalance.food < 0) weights.farmer += 2;
if (currentNeeds.resourceBalance.materials < 0) weights.builder += 1;
if (currentNeeds.resourceBalance.knowledge < 0) weights.scientist += 2;
if (currentNeeds.resourceBalance.wealth < 0) weights.merchant += 2;
// Adjust weights based on role distribution
for (const role in currentNeeds.roleDistribution) {
weights[role] -= currentNeeds.roleDistribution[role] * 0.5;
}
// Adjust weights based on technology gaps
if (currentNeeds.technologyGaps.includes('construction')) weights.builder += 1;
if (currentNeeds.technologyGaps.includes('agriculture')) weights.farmer += 1;
if (currentNeeds.technologyGaps.includes('science')) weights.scientist += 2;
if (currentNeeds.technologyGaps.includes('trade')) weights.merchant += 1;
if (currentNeeds.technologyGaps.includes('exploration')) weights.explorer += 2;
// Ensure no negative weights
for (const role in weights) {
weights[role] = Math.max(weights[role], 0.1);
}
return weights;
}
// Choose a random item based on weights
function chooseWeightedRandom(weights) {
const totalWeight = Object.values(weights).reduce((sum, weight) => sum + weight, 0);
let random = Math.random() * totalWeight;
for (const item in weights) {
random -= weights[item];
if (random <= 0) {
return item;
}
}
// Fallback
return Object.keys(weights)[0];
}
// Return public API
return {
createCharacter,
createEmergentCharacter
};
})();
export { CharacterFactory };
// character-manager.js
// Handles all character-related functions: updating state, assigning tasks, and managing interactions
import { EventManager } from '../events/event-manager.js';
import { BuildingManager } from '../buildings/building-manager.js';
import { TechnologyManager } from '../technology/technology-manager.js';
import { ResourceManager } from '../resources/resource-manager.js';
import { InteractionGenerator } from './interaction-generator.js';
// Character Manager - Handles all character-related functions
const CharacterManager = (function() {
// Private variables
let characters = [];
// Initialize with existing characters
function init(existingCharacters) {
characters = existingCharacters || [];
console.log(`Initialized CharacterManager with ${characters.length} characters`);
}
// Add a new character to the game
function addCharacter(character) {
character.createdTurn = getCurrentTurn();
character.relationships = {}; // Initialize empty relationships
// Set up initial relationships with existing characters
characters.forEach(existingChar => {
// Generate an initial relationship value (30-70)
const initialRelationship = Math.floor(Math.random() * 40) + 30;
// Higher if roles are complementary
const complementaryBonus = isComplementaryRole(character.role, existingChar.role) ? 10 : 0;
// Higher if cooperation stat is high
const cooperationBonus = (character.cooperation + existingChar.cooperation) / 4;
const relationshipValue = Math.min(initialRelationship + complementaryBonus + cooperationBonus, 100);
// Set relationship in both directions
character.relationships[existingChar.id] = Math.floor(relationshipValue);
existingChar.relationships[character.id] = Math.floor(relationshipValue);
});
characters.push(character);
console.log(`Added character ${character.name} (${character.role})`);
return character;
}
// Get the current turn from the game state
function getCurrentTurn() {
// This would normally come from the game state
// But for simplicity we'll access it through a global
return window.AICivilizationGame?.getGameState()?.turn || 0;
}
// Check if two roles complement each other
function isComplementaryRole(role1, role2) {
const complementaryPairs = [
['leader', 'builder'],
['scientist', 'builder'],
['farmer', 'merchant'],
['explorer', 'scientist'],
['merchant', 'builder']
];
return complementaryPairs.some(pair =>
(pair[0] === role1 && pair[1] === role2) ||
(pair[0] === role2 && pair[1] === role1)
);
}
// Update all characters state
function updateCharacters(gameState) {
characters.forEach(character => {
// Increase experience
character.experience += 1;
// Level up if enough experience
if (character.experience >= character.level * 10) {
levelUpCharacter(character);
}
// Assign a task if the character doesn't have one
if (!character.currentTask) {
assignTask(character, gameState);
}
// Progress on current task
if (character.currentTask) {
progressTask(character, gameState);
}
// Update relationships based on proximity
updateRelationships(character);
// Make decisions based on character's role and personality
makeDecisions(character, gameState);
});
}
// Level up a character
function levelUpCharacter(character) {
character.level += 1;
character.experience = 0;
// Improve stats based on role
if (character.role === 'leader') character.cooperation += 1;
if (character.role === 'builder') character.creativity += 1;
if (character.role === 'scientist') character.intelligence += 1;
if (character.role === 'farmer') character.cooperation += 1;
if (character.role === 'merchant') character.intelligence += 1;
if (character.role === 'explorer') character.creativity += 1;
// Add event to log
EventManager.addEvent({
type: 'character',
message: `${character.name} has reached level ${character.level}!`,
details: `Improved abilities and gained new insights.`,
turn: getCurrentTurn()
});
console.log(`${character.name} leveled up to ${character.level}`);
}
// Assign a task to a character
function assignTask(character, gameState) {
const possibleTasks = getPossibleTasks(character, gameState);
const task = chooseTask(character, possibleTasks);
if (task) {
character.currentTask = {
...task,
progress: 0,
startedTurn: gameState.turn
};
console.log(`${character.name} assigned task: ${task.name}`);
}
}
// Get possible tasks for a character based on their role and game state
function getPossibleTasks(character, gameState) {
const tasks = [];
// Common tasks for all roles
tasks.push({
name: 'Gather resources',
duration: 5,
difficulty: 1,
outcome: {
type: 'resources',
details: {
food: character.role === 'farmer' ? 15 : 5,
materials: character.role === 'builder' ? 15 : 5,
knowledge: character.role === 'scientist' ? 15 : 5
}
}
});
// Role-specific tasks
switch (character.role) {
case 'leader':
tasks.push({
name: 'Organize community',
duration: 10,
difficulty: 3,
outcome: {
type: 'community',
details: {
productivityBoost: 0.1,
duration: 20
}
}
});
tasks.push({
name: 'Diplomatic mission',
duration: 15,
difficulty: 4,
outcome: {
type: 'event',
details: {
eventType: 'diplomatic',
chance: 0.7
}
}
});
break;
case 'builder':
// Check for available buildings to construct
const availableBuildings = BuildingManager.getAvailableBuildings(gameState);
availableBuildings.forEach(building => {
tasks.push({
name: `Build ${building.name}`,
duration: building.constructionTime,
difficulty: building.difficulty,
outcome: {
type: 'building',
details: {
buildingId: building.id
}
}
});
});
break;
case 'scientist':
// Check for available technologies to research
const availableTechs = TechnologyManager.getAvailableTechnologies(gameState);
availableTechs.forEach(tech => {
tasks.push({
name: `Research ${tech.name}`,
duration: tech.researchTime,
difficulty: tech.difficulty,
outcome: {
type: 'technology',
details: {
techId: tech.id
}
}
});
});
break;
case 'farmer':
tasks.push({
name: 'Improve agriculture',
duration: 8,
difficulty: 2,
outcome: {
type: 'production',
details: {
resourceType: 'food',
boost: 0.15,
duration: 15
}
}
});
break;
case 'merchant':
tasks.push({
name: 'Establish trade route',
duration: 12,
difficulty: 3,
outcome: {
type: 'trade',
details: {
resourceGain: ['food', 'materials', 'knowledge'],
amount: 5
}
}
});
break;
case 'explorer':
tasks.push({
name: 'Explore new territories',
duration: 15,
difficulty: 4,
outcome: {
type: 'exploration',
details: {
newRegions: Math.floor(Math.random() * 2) + 1,
resourceFind: Math.random() > 0.5
}
}
});
break;
}
return tasks;
}
// Choose a task based on character personality and needs
function chooseTask(character, tasks) {
if (tasks.length === 0) return null;
// Calculate weight for each task based on character attributes
const taskWeights = tasks.map(task => {
let weight = 1;
// Intelligence favors difficult tasks with big payoffs
weight += (character.intelligence / 10) * task.difficulty;
// Creativity favors varied/different tasks from history
if (!hasRecentlyPerformedTask(character, task.name)) {
weight += character.creativity / 10;
}
// Cooperation favors tasks that benefit others
if (task.outcome.type === 'community' || task.outcome.type === 'trade') {
weight += character.cooperation / 10;
}
return {
task,
weight
};
});
// Sort by weight and pick the highest
taskWeights.sort((a, b) => b.weight - a.weight);
return taskWeights[0].task;
}
// Check if character has recently performed a task
function hasRecentlyPerformedTask(character, taskName) {
const recentTasks = character.history.slice(-3);
return recentTasks.some(historyItem =>
historyItem.type === 'task' && historyItem.details.name === taskName
);
}
// Progress the current task
function progressTask(character, gameState) {
const task = character.currentTask;
// Calculate progress increment based on character attributes
let progressIncrement = 1; // Base progress
// Intelligence speeds up progress
progressIncrement += (character.intelligence - 5) * 0.1;
// Creativity helps with difficult tasks
if (task.difficulty > 2) {
progressIncrement += (character.creativity - 5) * 0.1;
}
// Update progress
task.progress += progressIncrement;
// Check if task is complete
if (task.progress >= task.duration) {
completeTask(character, gameState);
}
}
// Complete a task and apply its outcome
function completeTask(character, gameState) {
const task = character.currentTask;
// Apply outcome based on type
switch (task.outcome.type) {
case 'resources':
for (const resource in task.outcome.details) {
ResourceManager.addResource(resource, task.outcome.details[resource]);
}
break;
case 'building':
BuildingManager.constructBuilding(task.outcome.details.buildingId, gameState);
break;
case 'technology':
TechnologyManager.researchTechnology(task.outcome.details.techId, gameState);
break;
case 'community':
// Apply community boost
EventManager.addEvent({
type: 'community',
message: `${character.name} has boosted community productivity!`,
details: `+${task.outcome.details.productivityBoost * 100}% for ${task.outcome.details.duration} turns`,
turn: gameState.turn,
duration: task.outcome.details.duration
});
break;
case 'production':
// Apply production boost
EventManager.addEvent({
type: 'production',
message: `${character.name} has improved ${task.outcome.details.resourceType} production!`,
details: `+${task.outcome.details.boost * 100}% for ${task.outcome.details.duration} turns`,
turn: gameState.turn,
resourceType: task.outcome.details.resourceType,
boost: task.outcome.details.boost,
duration: task.outcome.details.duration
});
break;
case 'trade':
// Apply trade outcome
task.outcome.details.resourceGain.forEach(resource => {
ResourceManager.ad
// character-manager.js
// Handles all character-related functions: updating state, assigning tasks, and managing interactions
import { EventManager } from '../events/event-manager.js';
import { BuildingManager } from '../buildings/building-manager.js';
import { TechnologyManager } from '../technology/technology-manager.js';
import { ResourceManager } from '../resources/resource-manager.js';
import { InteractionGenerator } from './interaction-generator.js';
// Character Manager - Handles all character-related functions
const CharacterManager = (function() {
// Private variables
let characters = [];
// Initialize with existing characters
function init(existingCharacters) {
characters = existingCharacters || [];
console.log(`Initialized CharacterManager with ${characters.length} characters`);
}
// Add a new character to the game
function addCharacter(character) {
character.createdTurn = getCurrentTurn();
character.relationships = {}; // Initialize empty relationships
// Set up initial relationships with existing characters
characters.forEach(existingChar => {
// Generate an initial relationship value (30-70)
const initialRelationship = Math.floor(Math.random() * 40) + 30;
// Higher if roles are complementary
const complementaryBonus = isComplementaryRole(character.role, existingChar.role) ? 10 : 0;
// Higher if cooperation stat is high
const cooperationBonus = (character.cooperation + existingChar.cooperation) / 4;
const relationshipValue = Math.min(initialRelationship + complementaryBonus + cooperationBonus, 100);
// Set relationship in both directions
character.relationships[existingChar.id] = Math.floor(relationshipValue);
existingChar.relationships[character.id] = Math.floor(relationshipValue);
});
characters.push(character);
console.log(`Added character ${character.name} (${character.role})`);
return character;
}
// Get the current turn from the game state
function getCurrentTurn() {
// This would normally come from the game state
// But for simplicity we'll access it through a global
return window.AICivilizationGame?.getGameState()?.turn || 0;
}
// Check if two roles complement each other
function isComplementaryRole(role1, role2) {
const complementaryPairs = [
['leader', 'builder'],
['scientist', 'builder'],
['farmer', 'merchant'],
['explorer', 'scientist'],
['merchant', 'builder']
];
return complementaryPairs.some(pair =>
(pair[0] === role1 && pair[1] === role2) ||
(pair[0] === role2 && pair[1] === role1)
);
}
// Update all characters state
function updateCharacters(gameState) {
characters.forEach(character => {
// Increase experience
character.experience += 1;
// Level up if enough experience
if (character.experience >= character.level * 10) {
levelUpCharacter(character);
}
// Assign a task if the character doesn't have one
if (!character.currentTask) {
assignTask(character, gameState);
}
// Progress on current task
if (character.currentTask) {
progressTask(character, gameState);
}
// Update relationships based on proximity
updateRelationships(character);
// Make decisions based on character's role and personality
makeDecisions(character, gameState);
});
}
// Level up a character
function levelUpCharacter(character) {
character.level += 1;
character.experience = 0;
// Improve stats based on role
if (character.role === 'leader') character.cooperation += 1;
if (character.role === 'builder') character.creativity += 1;
if (character.role === 'scientist') character.intelligence += 1;
if (character.role === 'farmer') character.cooperation += 1;
if (character.role === 'merchant') character.intelligence += 1;
if (character.role === 'explorer') character.creativity += 1;
// Add event to log
EventManager.addEvent({
type: 'character',
message: `${character.name} has reached level ${character.level}!`,
details: `Improved abilities and gained new insights.`,
turn: getCurrentTurn()
});
console.log(`${character.name} leveled up to ${character.level}`);
}
// Assign a task to a character
function assignTask(character, gameState) {
const possibleTasks = getPossibleTasks(character, gameState);
const task = chooseTask(character, possibleTasks);
if (task) {
character.currentTask = {
...task,
progress: 0,
startedTurn: gameState.turn
};
console.log(`${character.name} assigned task: ${task.name}`);
}
}
// Get possible tasks for a character based on their role and game state
function getPossibleTasks(character, gameState) {
const tasks = [];
// Common tasks for all roles
tasks.push({
name: 'Gather resources',
duration: 5,
difficulty: 1,
outcome: {
type: 'resources',
details: {
food: character.role === 'farmer' ? 15 : 5,
materials: character.role === 'builder' ? 15 : 5,
knowledge: character.role === 'scientist' ? 15 : 5
}
}
});
// Role-specific tasks
switch (character.role) {
case 'leader':
tasks.push({
name: 'Organize community',
duration: 10,
difficulty: 3,
outcome: {
type: 'community',
details: {
productivityBoost: 0.1,
duration: 20
}
}
});
tasks.push({
name: 'Diplomatic mission',
duration: 15,
difficulty: 4,
outcome: {
type: 'event',
details: {
eventType: 'diplomatic',
chance: 0.7
}
}
});
break;
case 'builder':
// Check for available buildings to construct
const availableBuildings = BuildingManager.getAvailableBuildings(gameState);
availableBuildings.forEach(building => {
tasks.push({
name: `Build ${building.name}`,
duration: building.constructionTime,
difficulty: building.difficulty,
outcome: {
type: 'building',
details: {
buildingId: building.id
}
}
});
});
break;
case 'scientist':
// Check for available technologies to research
const availableTechs = TechnologyManager.getAvailableTechnologies(gameState);
availableTechs.forEach(tech => {
tasks.push({
name: `Research ${tech.name}`,
duration: tech.researchTime,
difficulty: tech.difficulty,
outcome: {
type: 'technology',
details: {
techId: tech.id
}
}
});
});
break;
case 'farmer':
tasks.push({
name: 'Improve agriculture',
duration: 8,
difficulty: 2,
outcome: {
type: 'production',
details: {
resourceType: 'food',
boost: 0.15,
duration: 15
}
}
});
break;
case 'merchant':
tasks.push({
name: 'Establish trade route',
duration: 12,
difficulty: 3,
outcome: {
type: 'trade',
details: {
resourceGain: ['food', 'materials', 'knowledge'],
amount: 5
}
}
});
break;
case 'explorer':
tasks.push({
name: 'Explore new territories',
duration: 15,
difficulty: 4,
outcome: {
type: 'exploration',
details: {
newRegions: Math.floor(Math.random() * 2) + 1,
resourceFind: Math.random() > 0.5
}
}
});
break;
}
return tasks;
}
// Choose a task based on character personality and needs
function chooseTask(character, tasks) {
if (tasks.length === 0) return null;
// Calculate weight for each task based on character attributes
const taskWeights = tasks.map(task => {
let weight = 1;
// Intelligence favors difficult tasks with big payoffs
weight += (character.intelligence / 10) * task.difficulty;
// Creativity favors varied/different tasks from history
if (!hasRecentlyPerformedTask(character, task.name)) {
weight += character.creativity / 10;
}
// Cooperation favors tasks that benefit others
if (task.outcome.type === 'community' || task.outcome.type === 'trade') {
weight += character.cooperation / 10;
}
return {
task,
weight
};
});
// Sort by weight and pick the highest
taskWeights.sort((a, b) => b.weight - a.weight);
return taskWeights[0].task;
}
// Check if character has recently performed a task
function hasRecentlyPerformedTask(character, taskName) {
const recentTasks = character.history.slice(-3);
return recentTasks.some(historyItem =>
historyItem.type === 'task' && historyItem.details.name === taskName
);
}
// Progress the current task
function progressTask(character, gameState) {
const task = character.currentTask;
// Calculate progress increment based on character attributes
let progressIncrement = 1; // Base progress
// Intelligence speeds up progress
progressIncrement += (character.intelligence - 5) * 0.1;
// Creativity helps with difficult tasks
if (task.difficulty > 2) {
progressIncrement += (character.creativity - 5) * 0.1;
}
// Update progress
task.progress += progressIncrement;
// Check if task is complete
if (task.progress >= task.duration) {
completeTask(character, gameState);
}
}
// Complete a task and apply its outcome
function completeTask(character, gameState) {
const task = character.currentTask;
// Apply outcome based on type
switch (task.outcome.type) {
case 'resources':
for (const resource in task.outcome.details) {
ResourceManager.addResource(resource, task.outcome.details[resource]);
}
break;
case 'building':
BuildingManager.constructBuilding(task.outcome.details.buildingId, gameState);
break;
case 'technology':
TechnologyManager.researchTechnology(task.outcome.details.techId, gameState);
break;
case 'community':
// Apply community boost
EventManager.addEvent({
type: 'community',
message: `${character.name} has boosted community productivity!`,
details: `+${task.outcome.details.productivityBoost * 100}% for ${task.outcome.details.duration} turns`,
turn: gameState.turn,
duration: task.outcome.details.duration
});
break;
case 'production':
// Apply production boost
EventManager.addEvent({
type: 'production',
message: `${character.name} has improved ${task.outcome.details.resourceType} production!`,
details: `+${task.outcome.details.boost * 100}% for ${task.outcome.details.duration} turns`,
turn: gameState.turn,
resourceType: task.outcome.details.resourceType,
boost: task.outcome.details.boost,
duration: task.outcome.details.duration
});
break;
case 'trade':
// Apply trade outcome
task.outcome.details.resourceGain.forEach(resource => {
ResourceManager.addResource(resource, task.outcome.details.amount);
});
EventManager.addEvent({
type: 'trade',
message: `${character.name} has established a new trade route!`,
details: `Gaining various resources each turn`,
turn: gameState.turn,
ongoing: true
});
break;
case 'exploration':
// Apply exploration outcome
WorldGenerator.addRegions(task.outcome.details.newRegions);
if (task.outcome.details.resourceFind) {
const resourceType = ['food', 'materials', 'knowledge'][Math.floor(Math.random() * 3)];
const amount = Math.floor(Math.random() * 20) + 10;
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `Found ${amount} ${resourceType} in the process`,
turn: gameState.turn
});
} else {
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `The world map has been expanded`,
turn: gameState.turn
});
}
break;
case 'event':
// Generate a special event
if (Math.random() < task.outcome.details.chance) {
EventManager.generateSpecialEvent(task.outcome.details.eventType, character);
}
break;
}
// Add experience based on task difficulty
character.experience += task.difficulty * 2;
// Add to character history
character.history.push({
type: 'task',
details: {
name: task.name,
completed: gameState.turn
}
});
// Clear current task
character.currentTask = null;
console.log(`${character.name} completed task: ${task.name}`);
}
// Update relationships based on proximity and interactions
function updateRelationships(character) {
// For now, we'll just have relationships slowly drift toward a neutral value
// A more complex system would consider shared tasks, personality compatibility, etc.
for (const charId in character.relationships) {
const currentValue = character.relationships[charId];
// Drift toward 50 (neutral) at a rate of 1% per turn
const drift = currentValue > 50 ? -0.5 : 0.5;
character.relationships[charId] = Math.min(Math.max(currentValue + drift, 10), 90);
}
}
// Process interactions between characters
function processInteractions(gameState) {
// For each pair of characters, check if they should interact
for (let i = 0; i < characters.length; i++) {
for (let j = i + 1; j < characters.length; j++) {
const char1 = characters[i];
const char2 = characters[j];
// Determine if they interact this turn (based on proximity and current tasks)
if (shouldInteract(char1, char2, gameState)) {
// Generate an interaction
const interaction = InteractionGenerator.generateInteraction(char1, char2, gameState);
// Apply the results of the interaction
applyInteraction(interaction, char1, char2, gameState);
}
}
}
}
// Determine if two characters should interact this turn
function shouldInteract(char1, char2, gameState) {
// Characters with tasks are less likely to interact
const bothBusy = char1.currentTask && char2.currentTask;
if (bothBusy) return Math.random() < 0.1; // 10% chance if both busy
// One character busy
const oneBusy = char1.currentTask || char2.currentTask;
if (oneBusy) return Math.random() < 0.3; // 30% chance if one busy
// Neither busy - higher chance
return Math.random() < 0.6; // 60% chance if neither busy
}
// Apply the results of an interaction
function applyInteraction(interaction, char1, char2, gameState) {
// Update relationships
char1.relationships[char2.id] += interaction.relationshipChange1;
char2.relationships[char1.id] += interaction.relationshipChange2;
// Enforce limits
char1.relationships[char2.id] = Math.min(Math.max(char1.relationships[char2.id], 0), 100);
char2.relationships[char1.id] = Math.min(Math.max(char2.relationships[char1.id], 0), 100);
// Apply other effects
if (interaction.knowledgeTransfer) {
// Transfer knowledge between characters
if (!char1.knowledge.includes(interaction.knowledgeTransfer) &&
char2.knowledge.includes(interaction.knowledgeTransfer)) {
char1.knowledge.push(interaction.knowledgeTransfer);
} else if (!char2.knowledge.includes(interaction.knowledgeTransfer) &&
char1.knowledge.includes(interaction.knowledgeTransfer)) {
char2.knowledge.push(interaction.knowledgeTransfer);
}
}
// Apply resource effects
if (interaction.resourceEffect) {
ResourceManager.addResource(
interaction.resourceEffect.type,
interaction.resourceEffect.amount
);
}
// Log significant interactions
if (Math.abs(interaction.relationshipChange1) > 5 || interaction.resourceEffect) {
EventManager.addEvent({
type: 'interaction',
message: interaction.description,
details: interaction.outcome,
turn: gameState.turn
});
}
}
// Make decisions based on character's role and personality
function makeDecisions(character, gameState) {
// This would be a more complex AI decision-making system
// For now, we'll just have characters occasionally make suggestions
// Only make decisions when not busy with a task
if (character.currentTask) return;
// 5% chance of making a suggestion each turn
if (Math.random() < 0.05) {
generateSuggestion(character, gameState);
}
}
// Generate a suggestion based on character's role and personality
function generateSuggestion(character, gameState) {
let suggestion = null;
// Generate different suggestions based on role
switch (character.role) {
case 'leader':
suggestion = {
type: 'priority',
target: ['infrastructure', 'research', 'expansion'][Math.floor(Math.random() * 3)],
reasoning: `I believe we should focus on ${character.specialAbility} at this time.`
};
break;
case 'builder':
const buildingNeeds = BuildingManager.analyzeNeeds(gameState.world);
suggestion = {
type: 'building',
target: buildingNeeds.recommended,
reasoning: `Our settlement would benefit from more ${buildingNeeds.recommended}.`
};
break;
case 'scientist':
const techGaps = TechnologyManager.analyzeGaps();
suggestion = {
type: 'research',
target: techGaps[0] || 'general advancement',
reasoning: `We should prioritize research in ${techGaps[0] || 'various fields'}.`
};
break;
}
if (suggestion) {
EventManager.addEvent({
type: 'suggestion',
message: `${character.name} suggests focusing on ${suggestion.target}`,
details: suggestion.reasoning,
turn: gameState.turn
});
}
}
// Analyze the distribution of character roles
function analyzeRoleDistribution() {
const distribution = {};
characters.forEach(character => {
if (!distribution[character.role]) {
distribution[character.role] = 0;
}
distribution[character.role]++;
});
// Convert to percentages
const total = characters.length;
for (const role in distribution) {
distribution[role] = distribution[role] / total;
}
return distribution;
}
// Return public API
return {
init,
addCharacter,
updateCharacters,
processInteractions,
analyzeRoleDistribution,
getCharacters: () => characters
};
})();
export { CharacterManager };
// interaction-generator.js
// Generates interactions between AI characters based on their personalities and current state
// InteractionGenerator - Creates meaningful interactions between characters
const InteractionGenerator = (function() {
// Define different types of interactions
const interactionTypes = {
collaboration: {
description: (char1, char2) => `${char1.name} and ${char2.name} collaborate on a task`,
outcomeTemplates: [
"They make significant progress together",
"They share knowledge and insights",
"They develop a new approach to the problem"
],
relationshipEffect: [3, 8],
probability: (char1, char2) => 0.3 + (char1.cooperation + char2.cooperation) * 0.02
},
debate: {
description: (char1, char2) => `${char1.name} and ${char2.name} debate ideas`,
outcomeTemplates: [
"They reach a mutual understanding",
"They agree to disagree on the approach",
"They challenge each other's assumptions"
],
relationshipEffect: [-2, 5],
probability: (char1, char2) => 0.2 + (char1.intelligence + char2.intelligence) * 0.015
},
teaching: {
description: (char1, char2) => `${char1.name} teaches ${char2.name} about their specialty`,
outcomeTemplates: [
`${char2.name} gains valuable insights`,
"Knowledge is successfully transferred",
"A new perspective is shared"
],
relationshipEffect: [1, 7],
probability: (char1, char2) => 0.15 + ((char1.intelligence - char2.intelligence) * 0.01)
},
conflict: {
description: (char1, char2) => `${char1.name} and ${char2.name} have a disagreement`,
outcomeTemplates: [
"They eventually find common ground",
"The conflict remains unresolved for now",
"They decide to revisit the issue later"
],
relationshipEffect: [-7, -1],
probability: (char1, char2) => 0.1 - (char1.cooperation + char2.cooperation) * 0.005 + 0.05
},
support: {
description: (char1, char2) => `${char1.name} offers support to ${char2.name}`,
outcomeTemplates: [
`${char2.name} appreciates the assistance`,
"The offer is well-received",
"A bond of trust is strengthened"
],
relationshipEffect: [2, 6],
probability: (char1, char2) => 0.2 + char1.cooperation * 0.02
},
innovation: {
description: (char1, char2) => `${char1.name} and ${char2.name} brainstorm new ideas`,
outcomeTemplates: [
"They come up with an innovative solution",
"Their combined creativity yields results",
"A promising new approach emerges"
],
relationshipEffect: [2, 7],
probability: (char1, char2) => 0.15 + (char1.creativity + char2.creativity) * 0.015
}
};
// Special interactions based on character roles
const roleBasedInteractions = {
'leader-builder': {
description: (leader, builder) => `${leader.name} coordinates with ${builder.name} on construction priorities`,
outcomeTemplates: [
"They develop an efficient building plan",
"Resources are allocated more effectively",
"Construction efficiency increases"
],
relationshipEffect: [3, 5],
resourceEffect: { type: 'materials', amount: 5 }
},
'scientist-builder': {
description: (scientist, builder) => `${scientist.name} shares technical insights with ${builder.name}`,
outcomeTemplates: [
"New construction techniques are developed",
"Building designs are improved with scientific principles",
"Technical challenges are overcome"
],
relationshipEffect: [2, 6],
knowledgeTransfer: 'construction_techniques'
},
'farmer-merchant': {
description: (farmer, merchant) => `${farmer.name} discusses crop distribution with ${merchant.name}`,
outcomeTemplates: [
"Food distribution becomes more efficient",
"New market opportunities are identified",
"Resource allocation improves"
],
relationshipEffect: [3, 4],
resourceEffect: { type: 'food', amount: 8 }
},
'explorer-scientist': {
description: (explorer, scientist) => `${explorer.name} shares discoveries with ${scientist.name}`,
outcomeTemplates: [
"New research directions emerge",
"Scientific theories are tested with empirical findings",
"Unknown phenomena are documented"
],
relationshipEffect: [4, 5],
knowledgeTransfer: 'natural_phenomena'
}
};
// Generate an interaction between two characters
function generateInteraction(char1, char2, gameState) {
// Check for special role-based interactions
const roleKey1 = `${char1.role}-${char2.role}`;
const roleKey2 = `${char2.role}-${char1.role}`;
if (roleBasedInteractions[roleKey1] && Math.random() < 0.4) {
return createRoleBasedInteraction(char1, char2, roleBasedInteractions[roleKey1]);
} else if (roleBasedInteractions[roleKey2] && Math.random() < 0.4) {
return createRoleBasedInteraction(char2, char1, roleBasedInteractions[roleKey2]);
}
// Otherwise, generate a standard interaction
return createStandardInteraction(char1, char2);
}
// Create a role-based special interaction
function createRoleBasedInteraction(char1, char2, interactionTemplate) {
// Select a random outcome from templates
const outcome = interactionTemplate.outcomeTemplates[
Math.floor(Math.random() * interactionTemplate.outcomeTemplates.length)
];
// Calculate relationship change - slightly randomized
const relationshipChange = interactionTemplate.relationshipEffect;
const change1 = Math.floor(Math.random() * (relationshipChange[1] - relationshipChange[0] + 1)) + relationshipChange[0];
const change2 = Math.floor(Math.random() * (relationshipChange[1] - relationshipChange[0] + 1)) + relationshipChange[0];
return {
type: 'role-based',
description: interactionTemplate.description(char1, char2),
outcome,
relationshipChange1: change1,
relationshipChange2: change2,
resourceEffect: interactionTemplate.resourceEffect,
knowledgeTransfer: interactionTemplate.knowledgeTransfer
};
}
// Create a standard interaction
function createStandardInteraction(char1, char2) {
// Calculate probabilities for each interaction type
const probabilities = {};
let totalProbability = 0;
for (const type in interactionTypes) {
probabilities[type] = interactionTypes[type].probability(char1, char2);
totalProbability += probabilities[type];
}
// Normalize probabilities
for (const type in probabilities) {
probabilities[type] /= totalProbability;
}
// Choose interaction type based on probability
const random = Math.random();
let cumulativeProbability = 0;
let chosenType = Object.keys(interactionTypes)[0]; // Default
for (const type in probabilities) {
cumulativeProbability += probabilities[type];
if (random <= cumulativeProbability) {
chosenType = type;
break;
}
}
const interactionTemplate = interactionTypes[chosenType];
// Select a random outcome from templates
const outcome = interactionTemplate.outcomeTemplates[
Math.floor(Math.random() * interactionTemplate.outcomeTemplates.length)
];
// Calculate relationship change - slightly randomized
const relationshipChange = interactionTemplate.relationshipEffect;
const change1 = Math.floor(Math.random() * (relationshipChange[1] - relationshipChange[0] + 1)) + relationshipChange[0];
const change2 = Math.floor(Math.random() * (relationshipChange[1] - relationshipChange[0] + 1)) + relationshipChange[0];
// Configure knowledge transfer for certain interactions
let knowledgeTransfer = null;
if (chosenType === 'teaching' || chosenType === 'collaboration') {
// 30% chance of knowledge transfer
if (Math.random() < 0.3) {
const possibleFields = ['construction', 'agriculture', 'science', 'exploration', 'trade', 'leadership'];
knowledgeTransfer = possibleFields[Math.floor(Math.random() * possibleFields.length)];
}
}
// Configure resource effect for certain interactions
let resourceEffect = null;
if (chosenType === 'collaboration' || chosenType === 'innovation') {
// 20% chance of resource boost
if (Math.random() < 0.2) {
const resourceTypes = ['food', 'materials', 'knowledge'];
const resourceType = resourceTypes[Math.floor(Math.random() * resourceTypes.length)];
const amount = Math.floor(Math.random() * 5) + 1; // 1-5 resources
resourceEffect = {
type: resourceType,
amount: amount
};
}
}
return {
type: chosenType,
description: interactionTemplate.description(char1, char2),
outcome,
relationshipChange1: change1,
relationshipChange2: change2,
knowledgeTransfer,
resourceEffect
};
}
// Return public API
return {
generateInteraction
};
})();
export { InteractionGenerator };
// character-tasks.js
// Manages the tasks that characters can undertake
import { ResourceManager } from '../resources/resource-manager.js';
import { BuildingManager } from '../buildings/building-manager.js';
import { TechnologyManager } from '../technology/technology-manager.js';
import { EventManager } from '../events/event-manager.js';
// CharacterTasks - Defines and manages tasks for characters
const CharacterTasks = (function() {
// Task categories
const taskCategories = {
RESOURCE: 'resource',
BUILDING: 'building',
TECHNOLOGY: 'technology',
COMMUNITY: 'community',
EXPLORATION: 'exploration',
PRODUCTION: 'production',
TRADE: 'trade',
EVENT: 'event'
};
// Basic tasks available to all characters
const basicTasks = [
{
id: 'gather_resources',
name: 'Gather resources',
category: taskCategories.RESOURCE,
duration: 5,
difficulty: 1,
description: 'Collect basic resources for the civilization',
requiredLevel: 1,
roleBonus: {
'farmer': { food: 10 },
'builder': { materials: 10 },
'scientist': { knowledge: 10 }
},
getOutcome: (character) => {
const baseAmount = 5;
const roleBonus = basicTasks[0].roleBonus[character.role] || {};
return {
type: 'resources',
details: {
food: roleBonus.food || baseAmount,
materials: roleBonus.materials || baseAmount,
knowledge: roleBonus.knowledge || baseAmount
}
};
}
},
{
id: 'rest',
name: 'Rest and recover',
category: taskCategories.COMMUNITY,
duration: 3,
difficulty: 1,
description: 'Take time to recover and gain inspiration',
requiredLevel: 1,
getOutcome: (character) => {
// Resting gives a small boost to future productivity
return {
type: 'character',
details: {
temporaryBoost: {
intelligence: 1,
creativity: 1,
duration: 5
}
}
};
}
}
];
// Role-specific task definitions
const roleTasks = {
leader: [
{
id: 'organize_community',
name: 'Organize community',
category: taskCategories.COMMUNITY,
duration: 10,
difficulty: 3,
description: 'Improve the organization and efficiency of the community',
requiredLevel: 2,
getOutcome: (character) => {
const boostAmount = 0.1 + (character.level * 0.01);
return {
type: 'community',
details: {
productivityBoost: boostAmount,
duration: 15 + character.level
}
};
}
},
{
id: 'diplomatic_mission',
name: 'Diplomatic mission',
category: taskCategories.EVENT,
duration: 15,
difficulty: 4,
description: 'Attempt to negotiate with neighboring groups',
requiredLevel: 3,
getOutcome: (character) => {
const successChance = 0.5 + (character.intelligence / 20) + (character.cooperation / 20);
return {
type: 'event',
details: {
eventType: 'diplomatic',
chance: successChance
}
};
}
},
{
id: 'inspire_innovation',
name: 'Inspire innovation',
category: taskCategories.COMMUNITY,
duration: 8,
difficulty: 4,
description: 'Inspire others to think creatively and develop new ideas',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'community',
details: {
creativityBoost: 0.15,
duration: 10,
researchBoost: 0.1
}
};
}
}
],
builder: [
{
id: 'survey_land',
name: 'Survey land',
category: taskCategories.EXPLORATION,
duration: 5,
difficulty: 2,
description: 'Identify optimal building locations and resource deposits',
requiredLevel: 2,
getOutcome: (character) => {
return {
type: 'building',
details: {
constructionBoost: 0.1,
duration: 15,
resourceFind: Math.random() < 0.3 // 30% chance of finding resources
}
};
}
},
{
id: 'improve_infrastructure',
name: 'Improve infrastructure',
category: taskCategories.BUILDING,
duration: 12,
difficulty: 3,
description: 'Make general improvements to existing buildings and infrastructure',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'building',
details: {
maintenanceReduction: 0.15,
duration: 20,
buildingEfficiencyBoost: 0.05
}
};
}
},
// "Build X" tasks will be dynamically generated based on available buildings
],
scientist: [
{
id: 'analyze_data',
name: 'Analyze data',
category: taskCategories.TECHNOLOGY,
duration: 7,
difficulty: 3,
description: 'Review and analyze accumulated knowledge',
requiredLevel: 2,
getOutcome: (character) => {
return {
type: 'technology',
details: {
knowledgeBoost: 10 + (character.intelligence * 2),
researchBoost: 0.1,
duration: 10
}
};
}
},
{
id: 'experiment',
name: 'Conduct experiments',
category: taskCategories.TECHNOLOGY,
duration: 10,
difficulty: 4,
description: 'Run controlled experiments to test theories',
requiredLevel: 3,
getOutcome: (character) => {
const discoveryChance = 0.3 + (character.creativity / 20);
return {
type: 'technology',
details: {
breakthroughChance: discoveryChance,
knowledgeGain: 15 + (character.intelligence * 1.5)
}
};
}
},
// "Research X" tasks will be dynamically generated based on available technologies
],
farmer: [
{
id: 'improve_agriculture',
name: 'Improve agriculture',
category: taskCategories.PRODUCTION,
duration: 8,
difficulty: 2,
description: 'Develop better farming techniques',
requiredLevel: 2,
getOutcome: (character) => {
const boost = 0.15 + (character.level * 0.02);
return {
type: 'production',
details: {
resourceType: 'food',
boost: boost,
duration: 15
}
};
}
},
{
id: 'develop_irrigation',
name: 'Develop irrigation',
category: taskCategories.BUILDING,
duration: 12,
difficulty: 3,
description: 'Create irrigation systems to improve crop yields',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'production',
details: {
resourceType: 'food',
boost: 0.2,
duration: 25,
permanent: 0.05 // Some permanent boost
}
};
}
},
{
id: 'animal_husbandry',
name: 'Develop animal husbandry',
category: taskCategories.PRODUCTION,
duration: 15,
difficulty: 4,
description: 'Domesticate and breed animals for food and labor',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'production',
details: {
resourceType: 'food',
boost: 0.25,
laborBoost: 0.1,
duration: 20,
permanent: 0.08
}
};
}
}
],
merchant: [
{
id: 'establish_trade_route',
name: 'Establish trade route',
category: taskCategories.TRADE,
duration: 12,
difficulty: 3,
description: 'Set up a new trade route with neighboring regions',
requiredLevel: 2,
getOutcome: (character) => {
const resourceGain = ['food', 'materials', 'knowledge'];
const baseAmount = 5 + character.level;
return {
type: 'trade',
details: {
resourceGain: resourceGain,
amount: baseAmount,
ongoing: true
}
};
}
},
{
id: 'organize_market',
name: 'Organize marketplace',
category: taskCategories.COMMUNITY,
duration: 8,
difficulty: 2,
description: 'Create a central marketplace for resource exchange',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'trade',
details: {
resourceDistributionBoost: 0.15,
wealthGeneration: 10,
duration: 15
}
};
}
},
{
id: 'luxury_trade',
name: 'Trade for luxury goods',
category: taskCategories.TRADE,
duration: 15,
difficulty: 4,
description: 'Seek out and acquire valuable luxury items',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'event',
details: {
eventType: 'luxury',
moraleBoost: 0.2,
duration: 10,
specialResourceChance: 0.7
}
};
}
}
],
explorer: [
{
id: 'explore_territories',
name: 'Explore new territories',
category: taskCategories.EXPLORATION,
duration: 15,
difficulty: 4,
description: 'Venture into unknown areas to expand the map',
requiredLevel: 2,
getOutcome: (character) => {
const regions = Math.floor(Math.random() * 2) + 1;
return {
type: 'exploration',
details: {
newRegions: regions,
resourceFind: Math.random() > 0.5
}
};
}
},
{
id: 'map_resources',
name: 'Map resource deposits',
category: taskCategories.EXPLORATION,
duration: 10,
difficulty: 3,
description: 'Create detailed maps of resource-rich areas',
requiredLevel: 3,
getOutcome: (character) => {
return {
// character-tasks.js
// Manages the tasks that characters can undertake
import { ResourceManager } from '../resources/resource-manager.js';
import { BuildingManager } from '../buildings/building-manager.js';
import { TechnologyManager } from '../technology/technology-manager.js';
import { EventManager } from '../events/event-manager.js';
// CharacterTasks - Defines and manages tasks for characters
const CharacterTasks = (function() {
// Task categories
const taskCategories = {
RESOURCE: 'resource',
BUILDING: 'building',
TECHNOLOGY: 'technology',
COMMUNITY: 'community',
EXPLORATION: 'exploration',
PRODUCTION: 'production',
TRADE: 'trade',
EVENT: 'event'
};
// Basic tasks available to all characters
const basicTasks = [
{
id: 'gather_resources',
name: 'Gather resources',
category: taskCategories.RESOURCE,
duration: 5,
difficulty: 1,
description: 'Collect basic resources for the civilization',
requiredLevel: 1,
roleBonus: {
'farmer': { food: 10 },
'builder': { materials: 10 },
'scientist': { knowledge: 10 }
},
getOutcome: (character) => {
const baseAmount = 5;
const roleBonus = basicTasks[0].roleBonus[character.role] || {};
return {
type: 'resources',
details: {
food: roleBonus.food || baseAmount,
materials: roleBonus.materials || baseAmount,
knowledge: roleBonus.knowledge || baseAmount
}
};
}
},
{
id: 'rest',
name: 'Rest and recover',
category: taskCategories.COMMUNITY,
duration: 3,
difficulty: 1,
description: 'Take time to recover and gain inspiration',
requiredLevel: 1,
getOutcome: (character) => {
// Resting gives a small boost to future productivity
return {
type: 'character',
details: {
temporaryBoost: {
intelligence: 1,
creativity: 1,
duration: 5
}
}
};
}
}
];
// Role-specific task definitions
const roleTasks = {
leader: [
{
id: 'organize_community',
name: 'Organize community',
category: taskCategories.COMMUNITY,
duration: 10,
difficulty: 3,
description: 'Improve the organization and efficiency of the community',
requiredLevel: 2,
getOutcome: (character) => {
const boostAmount = 0.1 + (character.level * 0.01);
return {
type: 'resource',
details: {
resourceFindChance: 0.8,
resourceMultiplier: 1.5,
mappingBonus: {
gatherEfficiency: 0.2,
duration: 30
}
}
};
}
},
{
id: 'scout_dangers',
name: 'Scout for dangers',
category: taskCategories.EXPLORATION,
duration: 8,
difficulty: 3,
description: 'Identify potential threats in the surroundings',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'community',
details: {
threatReduction: 0.3,
duration: 20,
earlyWarningSystem: true
}
};
}
}
]
};
// Get all basic tasks available to any character
function getBasicTasks() {
return [...basicTasks];
}
// Get tasks available to a specific character based on role and level
function getAvailableTasks(character, gameState) {
const tasks = [...getBasicTasks()];
// Add role-specific tasks
if (roleTasks[character.role]) {
roleTasks[character.role].forEach(task => {
if (character.level >= task.requiredLevel) {
tasks.push(task);
}
});
}
// Add building construction tasks for builders
if (character.role === 'builder') {
const availableBuildings = BuildingManager.getAvailableBuildings(gameState);
availableBuildings.forEach(building => {
tasks.push({
id: `build_${building.id}`,
name: `Build ${building.name}`,
category: taskCategories.BUILDING,
duration: building.constructionTime,
difficulty: building.difficulty,
description: `Construct a ${building.name}: ${building.description}`,
requiredLevel: building.requiredLevel || 1,
getOutcome: () => {
return {
type: 'building',
details: {
buildingId: building.id
}
};
}
});
});
}
// Add technology research tasks for scientists
if (character.role === 'scientist') {
const availableTechs = TechnologyManager.getAvailableTechnologies(gameState);
availableTechs.forEach(tech => {
tasks.push({
id: `research_${tech.id}`,
name: `Research ${tech.name}`,
category: taskCategories.TECHNOLOGY,
duration: tech.researchTime,
difficulty: tech.difficulty,
description: `Research ${tech.name}: ${tech.description}`,
requiredLevel: tech.requiredLevel || 1,
getOutcome: () => {
return {
type: 'technology',
details: {
techId: tech.id
}
};
}
});
});
}
return tasks;
}
// Process the outcome of a completed task
function processTaskOutcome(task, character, gameState) {
const outcome = task.getOutcome(character);
console.log(`Processing outcome for task: ${task.name}`, outcome);
switch (outcome.type) {
case 'resources':
for (const resource in outcome.details) {
ResourceManager.addResource(resource, outcome.details[resource]);
}
break;
case 'building':
if (outcome.details.buildingId) {
BuildingManager.constructBuilding(outcome.details.buildingId, gameState);
}
// Apply construction boost if present
if (outcome.details.constructionBoost) {
EventManager.addEvent({
type: 'building',
message: `${character.name} has improved construction efficiency!`,
details: `+${outcome.details.constructionBoost * 100}% construction speed for ${outcome.details.duration} turns`,
turn: gameState.turn,
duration: outcome.details.duration
});
}
// Check for resource find
if (outcome.details.resourceFind) {
const resourceType = ['food', 'materials', 'knowledge'][Math.floor(Math.random() * 3)];
const amount = Math.floor(Math.random() * 15) + 5;
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} found resources during construction!`,
details: `Found ${amount} ${resourceType}`,
turn: gameState.turn
});
}
break;
case 'technology':
if (outcome.details.techId) {
TechnologyManager.researchTechnology(outcome.details.techId, gameState);
}
// Apply knowledge gain
if (outcome.details.knowledgeGain) {
ResourceManager.addResource('knowledge', outcome.details.knowledgeGain);
}
// Apply research boost
if (outcome.details.researchBoost) {
EventManager.addEvent({
type: 'technology',
message: `${character.name} has accelerated research!`,
details: `+${outcome.details.researchBoost * 100}% research speed for ${outcome.details.duration} turns`,
turn: gameState.turn,
duration: outcome.details.duration
});
}
// Check for breakthrough
if (outcome.details.breakthroughChance && Math.random() < outcome.details.breakthroughChance) {
// Generate a research breakthrough
const breakthrough = TechnologyManager.generateBreakthrough(character);
EventManager.addEvent({
type: 'technology',
message: `${character.name} has made a breakthrough in research!`,
details: breakthrough.description,
turn: gameState.turn
});
}
break;
case 'community':
// Apply community effects
let eventDetails = '';
if (outcome.details.productivityBoost) {
eventDetails += `+${outcome.details.productivityBoost * 100}% productivity `;
}
if (outcome.details.creativityBoost) {
eventDetails += `+${outcome.details.creativityBoost * 100}% creativity `;
}
if (outcome.details.threatReduction) {
eventDetails += `${outcome.details.threatReduction * 100}% reduced threat level `;
}
EventManager.addEvent({
type: 'community',
message: `${character.name} has improved the community!`,
details: `${eventDetails} for ${outcome.details.duration} turns`,
turn: gameState.turn,
duration: outcome.details.duration,
effects: outcome.details
});
break;
case 'production':
EventManager.addEvent({
type: 'production',
message: `${character.name} has improved ${outcome.details.resourceType} production!`,
details: `+${outcome.details.boost * 100}% for ${outcome.details.duration} turns`,
turn: gameState.turn,
resourceType: outcome.details.resourceType,
boost: outcome.details.boost,
duration: outcome.details.duration
});
// Apply permanent boost if present
if (outcome.details.permanent) {
// This would update a permanent production multiplier in the resource manager
ResourceManager.addPermanentBoost(outcome.details.resourceType, outcome.details.permanent);
EventManager.addEvent({
type: 'production',
message: `${character.name} has permanently improved ${outcome.details.resourceType} production!`,
details: `+${outcome.details.permanent * 100}% permanent boost`,
turn: gameState.turn
});
}
break;
case 'trade':
// Apply trade outcome
if (outcome.details.resourceGain) {
outcome.details.resourceGain.forEach(resource => {
ResourceManager.addResource(resource, outcome.details.amount);
});
}
// Add ongoing trade route if applicable
if (outcome.details.ongoing) {
// This would register an ongoing effect in the resource manager
ResourceManager.addTradeRoute({
resources: outcome.details.resourceGain,
amount: outcome.details.amount,
founder: character.id
});
EventManager.addEvent({
type: 'trade',
message: `${character.name} has established a new trade route!`,
details: `Gaining various resources each turn`,
turn: gameState.turn,
ongoing: true
});
}
break;
case 'exploration':
// Apply exploration outcome
if (outcome.details.newRegions) {
// This would call into the world generator to expand the map
WorldGenerator.addRegions(outcome.details.newRegions);
}
if (outcome.details.resourceFind) {
const resourceType = ['food', 'materials', 'knowledge'][Math.floor(Math.random() * 3)];
const amount = Math.floor(Math.random() * 20) + 10;
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `Found ${amount} ${resourceType} in the process`,
turn: gameState.turn
});
} else {
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `The world map has been expanded`,
turn: gameState.turn
});
}
break;
case 'event':
// Generate a special event
if (outcome.details.eventType) {
EventManager.generateSpecialEvent(outcome.details.eventType, character, outcome.details);
}
break;
case 'character':
// Apply character-specific effects
if (outcome.details.temporaryBoost) {
character.temporaryBoosts = character.temporaryBoosts || [];
character.temporaryBoosts.push({
...outcome.details.temporaryBoost,
startTurn: gameState.turn
});
EventManager.addEvent({
type: 'character',
message: `${character.name} is feeling refreshed and inspired!`,
details: `Temporarily improved abilities for ${outcome.details.temporaryBoost.duration} turns`,
turn: gameState.turn
});
}
break;
case 'resource':
// Apply resource mapping bonuses
if (outcome.details.mappingBonus) {
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has created detailed resource maps!`,
details: `+${outcome.details.mappingBonus.gatherEfficiency * 100}% resource gathering efficiency for ${outcome.details.mappingBonus.duration} turns`,
turn: gameState.turn,
duration: outcome.details.mappingBonus.duration,
effects: outcome.details.mappingBonus
});
}
// Check for resource finds
if (outcome.details.resourceFindChance && Math.random() < outcome.details.resourceFindChance) {
const resources = ['food', 'materials', 'knowledge'];
const resourceCount = Math.floor(Math.random() * 2) + 1;
for (let i = 0; i < resourceCount; i++) {
const resourceType = resources[Math.floor(Math.random() * resources.length)];
const baseAmount = Math.floor(Math.random() * 15) + 5;
const amount = Math.floor(baseAmount * outcome.details.resourceMultiplier);
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered a ${resourceType} deposit!`,
details: `Added ${amount} ${resourceType} to stockpile`,
turn: gameState.turn
});
}
}
break;
}
return outcome;
}
// Return public API
return {
getBasicTasks,
getAvailableTasks,
processTaskOutcome,
taskCategories
};
})();
export { CharacterTasks };
type: 'community',
details: {
productivityBoost: boostAmount,
duration: 15 + character.level
}
};
}
},
{
id: 'diplomatic_mission',
name: 'Diplomatic mission',
category: taskCategories.EVENT,
duration: 15,
difficulty: 4,
description: 'Attempt to negotiate with neighboring groups',
requiredLevel: 3,
getOutcome: (character) => {
const successChance = 0.5 + (character.intelligence / 20) + (character.cooperation / 20);
return {
type: 'event',
details: {
eventType: 'diplomatic',
chance: successChance
}
};
}
},
{
id: 'inspire_innovation',
name: 'Inspire innovation',
category: taskCategories.COMMUNITY,
duration: 8,
difficulty: 4,
description: 'Inspire others to think creatively and develop new ideas',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'community',
details: {
creativityBoost: 0.15,
duration: 10,
researchBoost: 0.1
}
};
}
}
],
builder: [
{
id: 'survey_land',
name: 'Survey land',
category: taskCategories.EXPLORATION,
duration: 5,
difficulty: 2,
description: 'Identify optimal building locations and resource deposits',
requiredLevel: 2,
getOutcome: (character) => {
return {
type: 'building',
details: {
constructionBoost: 0.1,
duration: 15,
resourceFind: Math.random() < 0.3 // 30% chance of finding resources
}
};
}
},
{
id: 'improve_infrastructure',
name: 'Improve infrastructure',
category: taskCategories.BUILDING,
duration: 12,
difficulty: 3,
description: 'Make general improvements to existing buildings and infrastructure',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'building',
details: {
maintenanceReduction: 0.15,
duration: 20,
buildingEfficiencyBoost: 0.05
}
};
}
},
// "Build X" tasks will be dynamically generated based on available buildings
],
scientist: [
{
id: 'analyze_data',
name: 'Analyze data',
category: taskCategories.TECHNOLOGY,
duration: 7,
difficulty: 3,
description: 'Review and analyze accumulated knowledge',
requiredLevel: 2,
getOutcome: (character) => {
return {
type: 'technology',
details: {
knowledgeBoost: 10 + (character.intelligence * 2),
researchBoost: 0.1,
duration: 10
}
};
}
},
{
id: 'experiment',
name: 'Conduct experiments',
category: taskCategories.TECHNOLOGY,
duration: 10,
difficulty: 4,
description: 'Run controlled experiments to test theories',
requiredLevel: 3,
getOutcome: (character) => {
const discoveryChance = 0.3 + (character.creativity / 20);
return {
type: 'technology',
details: {
breakthroughChance: discoveryChance,
knowledgeGain: 15 + (character.intelligence * 1.5)
}
};
}
},
// "Research X" tasks will be dynamically generated based on available technologies
],
farmer: [
{
id: 'improve_agriculture',
name: 'Improve agriculture',
category: taskCategories.PRODUCTION,
duration: 8,
difficulty: 2,
description: 'Develop better farming techniques',
requiredLevel: 2,
getOutcome: (character) => {
const boost = 0.15 + (character.level * 0.02);
return {
type: 'production',
details: {
resourceType: 'food',
boost: boost,
duration: 15
}
};
}
},
{
id: 'develop_irrigation',
name: 'Develop irrigation',
category: taskCategories.BUILDING,
duration: 12,
difficulty: 3,
description: 'Create irrigation systems to improve crop yields',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'production',
details: {
resourceType: 'food',
boost: 0.2,
duration: 25,
permanent: 0.05 // Some permanent boost
}
};
}
},
{
id: 'animal_husbandry',
name: 'Develop animal husbandry',
category: taskCategories.PRODUCTION,
duration: 15,
difficulty: 4,
description: 'Domesticate and breed animals for food and labor',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'production',
details: {
resourceType: 'food',
boost: 0.25,
laborBoost: 0.1,
duration: 20,
permanent: 0.08
}
};
}
}
],
merchant: [
{
id: 'establish_trade_route',
name: 'Establish trade route',
category: taskCategories.TRADE,
duration: 12,
difficulty: 3,
description: 'Set up a new trade route with neighboring regions',
requiredLevel: 2,
getOutcome: (character) => {
const resourceGain = ['food', 'materials', 'knowledge'];
const baseAmount = 5 + character.level;
return {
type: 'trade',
details: {
resourceGain: resourceGain,
amount: baseAmount,
ongoing: true
}
};
}
},
{
id: 'organize_market',
name: 'Organize marketplace',
category: taskCategories.COMMUNITY,
duration: 8,
difficulty: 2,
description: 'Create a central marketplace for resource exchange',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'trade',
details: {
resourceDistributionBoost: 0.15,
wealthGeneration: 10,
duration: 15
}
};
}
},
{
id: 'luxury_trade',
name: 'Trade for luxury goods',
category: taskCategories.TRADE,
duration: 15,
difficulty: 4,
description: 'Seek out and acquire valuable luxury items',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'event',
details: {
eventType: 'luxury',
moraleBoost: 0.2,
duration: 10,
specialResourceChance: 0.7
}
};
}
}
],
explorer: [
{
id: 'explore_territories',
name: 'Explore new territories',
category: taskCategories.EXPLORATION,
duration: 15,
difficulty: 4,
description: 'Venture into unknown areas to expand the map',
requiredLevel: 2,
getOutcome: (character) => {
const regions = Math.floor(Math.random() * 2) + 1;
return {
type: 'exploration',
details: {
newRegions: regions,
resourceFind: Math.random() > 0.5
}
};
}
},
{
id: 'map_resources',
name: 'Map resource deposits',
category: taskCategories.EXPLORATION,
duration: 10,
difficulty: 3,
description: 'Create detailed maps of resource-rich areas',
requiredLevel: 3,
getOutcome: (character) => {
return {
// storage-manager.js
// Handles saving and loading game state to localStorage
// StorageManager - Persists game state
const StorageManager = (function() {
const STORAGE_KEY = 'ai_civilization_game_state';
// Save the game state to localStorage
function saveGame(gameState) {
try {
const serializedState = JSON.stringify(gameState);
localStorage.setItem(STORAGE_KEY, serializedState);
return true;
} catch (error) {
console.error('Error saving game state:', error);
return false;
}
}
// Load the game state from localStorage
function loadGame() {
try {
const serializedState = localStorage.getItem(STORAGE_KEY);
if (serializedState === null) {
return null; // No saved game
}
return JSON.parse(serializedState);
} catch (error) {
console.error('Error loading game state:', error);
return null;
}
}
// Delete the saved game state
function deleteSave() {
try {
localStorage.removeItem(STORAGE_KEY);
return true;
} catch (error) {
console.error('Error deleting game state:', error);
return false;
}
}
// Export the saved game as a downloadable file
function exportSave() {
try {
const serializedState = localStorage.getItem(STORAGE_KEY);
if (serializedState === null) {
return null; // No saved game
}
const blob = new Blob([serializedState], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ai_civilization_save_${Date.now()}.json`;
document.body.appendChild(a);
a.click();
// Clean up
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
return true;
} catch (error) {
console.error('Error exporting game state:', error);
return false;
}
}
// Import a saved game from a file
function importSave(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
try {
const contents = event.target.result;
const gameState = JSON.parse(contents);
// Validate that this is a proper game state
if (!isValidGameState(gameState)) {
reject(new Error('Invalid save file format'));
return;
}
saveGame(gameState);
resolve(gameState);
} catch (error) {
reject(new Error('Error parsing save file: ' + error.message));
}
};
reader.onerror = () => {
reject(new Error('Error reading file'));
};
reader.readAsText(file);
});
}
// Basic validation to ensure imported save file has expected structure
function isValidGameState(gameState) {
// Check for required properties
const requiredProps = [
'world',
'turn',
'timestamp',
'characters',
'resources',
'buildings',
'technologies',
'events'
];
for (const prop of requiredProps) {
if (!gameState.hasOwnProperty(prop)) {
console.error(`Missing required property: ${prop}`);
return false;
}
}
// Check that characters have the expected structure
if (!Array.isArray(gameState.characters)) {
console.error('Characters must be an array');
return false;
}
// Simple validation passed
return true;
}
// Auto-save setup
function setupAutoSave(gameInstance, interval = 60000) {
setInterval(() => {
console.log('Auto-saving game...');
if (gameInstance && gameInstance.getGameState) {
saveGame(gameInstance.getGameState());
}
}, interval);
}
// Return public API
return {
saveGame,
loadGame,
deleteSave,
exportSave,
importSave,
setupAutoSave
};
})();
export { StorageManager };
// resource-manager.js
// Handles resource management, production, and consumption
// ResourceManager - Tracks and manages all resources
const ResourceManager = (function() {
// Initial resource configuration
const initialResources = {
food: 100,
materials: 50,
knowledge: 20,
wealth: 0
};
// Internal state
let resources = { ...initialResources };
let productionRates = {
food: 5,
materials: 3,
knowledge: 1,
wealth: 0
};
let consumptionRates = {
food: 2,
materials: 1,
knowledge: 0,
wealth: 0
};
let permanentBoosts = {
food: 1,
materials: 1,
knowledge: 1,
wealth: 1
};
let temporaryBoosts = []; // Array of {resourceType, multiplier, duration, startTurn}
let tradeRoutes = []; // Array of ongoing trade routes
// Initialize with existing resources or defaults
function init(existingResources) {
resources = existingResources || { ...initialResources };
console.log('Initialized ResourceManager with resources:', resources);
}
// Get the current resources
function getResources() {
return { ...resources };
}
// Get the initial resources (for new game)
function getInitialResources() {
return { ...initialResources };
}
// Add resources of a specific type
function addResource(type, amount) {
if (resources.hasOwnProperty(type)) {
resources[type] += amount;
console.log(`Added ${amount} ${type}, new total: ${resources[type]}`);
return true;
}
console.error(`Unknown resource type: ${type}`);
return false;
}
// Remove resources of a specific type
function removeResource(type, amount) {
if (!resources.hasOwnProperty(type)) {
console.error(`Unknown resource type: ${type}`);
return false;
}
if (resources[type] < amount) {
console.error(`Not enough ${type}: have ${resources[type]}, need ${amount}`);
return false;
}
resources[type] -= amount;
console.log(`Removed ${amount} ${type}, new total: ${resources[type]}`);
return true;
}
// Update resource production/consumption for the turn
function updateResources(gameState) {
// Apply production rates (with boosts)
for (const resourceType in productionRates) {
const baseProduction = productionRates[resourceType];
// Apply permanent boost
let boostedProduction = baseProduction * permanentBoosts[resourceType];
// Apply temporary boosts
const activeBoosts = getActiveBoosts(resourceType, gameState.turn);
activeBoosts.forEach(boost => {
boostedProduction *= boost.multiplier;
});
// Add production to resources
resources[resourceType] += Math.floor(boostedProduction);
}
// Calculate consumption based on population/buildings
updateConsumptionRates(gameState);
// Apply consumption rates
for (const resourceType in consumptionRates) {
const consumption = consumptionRates[resourceType];
resources[resourceType] = Math.max(0, resources[resourceType] - consumption);
}
// Process trade routes
processTradeRoutes();
// Clean up expired temporary boosts
cleanupExpiredBoosts(gameState.turn);
console.log('Updated resources:', resources);
}
// Update consumption rates based on game state
function updateConsumptionRates(gameState) {
// Basic consumption based on number of characters
const characterCount = gameState.characters.length;
consumptionRates.food = characterCount * 2;
// Building maintenance costs
let buildingMaterialsCost = 0;
gameState.buildings.forEach(building => {
buildingMaterialsCost += building.maintenanceCost || 0;
});
consumptionRates.materials = buildingMaterialsCost;
// Update the consumption rates
console.log('Updated consumption rates:', consumptionRates);
}
// Get active temporary boosts for a resource type
function getActiveBoosts(resourceType, currentTurn) {
return temporaryBoosts.filter(boost =>
boost.resourceType === resourceType &&
boost.startTurn + boost.duration > currentTurn
);
}
// Clean up expired temporary boosts
function cleanupExpiredBoosts(currentTurn) {
temporaryBoosts = temporaryBoosts.filter(boost =>
boost.startTurn + boost.duration > currentTurn
);
}
// Process all trade routes for the turn
function processTradeRoutes() {
tradeRoutes.forEach(route => {
route.resources.forEach(resourceType => {
addResource(resourceType, route.amount);
});
});
}
// Add a new trade route
function addTradeRoute(route) {
tradeRoutes.push(route);
console.log('Added new trade route:', route);
return tradeRoutes.length - 1; // Return the index of the new route
}
// Remove a trade route
function removeTradeRoute(index) {
if (index >= 0 && index < tradeRoutes.length) {
tradeRoutes.splice(index, 1);
console.log(`Removed trade route at index ${index}`);
return true;
}
return false;
}
// Add a permanent production boost for a resource type
function addPermanentBoost(resourceType, multiplier) {
if (permanentBoosts.hasOwnProperty(resourceType)) {
permanentBoosts[resourceType] += multiplier;
console.log(`Added permanent boost to ${resourceType}: now ${permanentBoosts[resourceType]}x`);
return true;
}
return false;
}
// Add a temporary production boost for a resource type
function addTemporaryBoost(resourceType, multiplier, duration, currentTurn) {
temporaryBoosts.push({
resourceType,
multiplier,
duration,
startTurn: currentTurn
});
console.log(`Added temporary ${multiplier}x boost to ${resourceType} for ${duration} turns`);
return true;
}
// Set production rate for a resource type
function setProductionRate(resourceType, rate) {
if (productionRates.hasOwnProperty(resourceType)) {
productionRates[resourceType] = rate;
console.log(`Set ${resourceType} production rate to ${rate}`);
return true;
}
return false;
}
// Analyze resource balance and identify shortages/surpluses
function analyzeBalance() {
const analysis = {};
// Calculate net rate (production - consumption)
for (const resourceType in resources) {
const productionRate = productionRates[resourceType] * permanentBoosts[resourceType];
const consumptionRate = consumptionRates[resourceType];
analysis[resourceType] = productionRate - consumptionRate;
}
// Add qualitative assessment
for (const resourceType in analysis) {
if (analysis[resourceType] < -5) {
analysis[`${resourceType}Status`] = 'critical shortage';
} else if (analysis[resourceType] < 0) {
analysis[`${resourceType}Status`] = 'shortage';
} else if (analysis[resourceType] >= 10) {
analysis[`${resourceType}Status`] = 'surplus';
} else {
analysis[`${resourceType}Status`] = 'balanced';
}
}
return analysis;
}
// Check if there are enough resources for a specific cost
function canAfford(costs) {
for (const resourceType in costs) {
if (!resources.hasOwnProperty(resourceType) || resources[resourceType] < costs[resourceType]) {
return false;
}
}
return true;
}
// Spend resources according to a cost object
function spendResources(costs) {
if (!canAfford(costs)) {
return false;
}
for (const resourceType in costs) {
removeResource(resourceType, costs[resourceType]);
}
return true;
}
// Calculate how many turns until a specific amount of a resource is available
function turnsUntilAvailable(resourceType, amount) {
if (!resources.hasOwnProperty(resourceType)) {
return Infinity;
}
const currentAmount = resources[resourceType];
if (currentAmount >= amount) {
return 0;
}
const netRate = productionRates[resourceType] * permanentBoosts[resourceType] - consumptionRates[resourceType];
if (netRate <= 0) {
return Infinity; // Never going to have enough at current rates
}
const amountNeeded = amount - currentAmount;
return Math.ceil(amountNeeded / netRate);
}
// Return public API
return {
init,
getResources,
getInitialResources,
addResource,
removeResource,
updateResources,
addTradeRoute,
removeTradeRoute,
addPermanentBoost,
addTemporaryBoost,
setProductionRate,
analyzeBalance,
canAfford,
spendResources,
turnsUntilAvailable
};
})();
export { ResourceManager };
// world-generator.js
// Creates and manages the game world and its regions
// WorldGenerator - Creates the game world
const WorldGenerator = (function() {
// Terrain types with their properties
const terrainTypes = {
plains: {
name: 'Plains',
description: 'Flat, grassy areas ideal for agriculture',
resourceModifiers: {
food: 1.5,
materials: 1.0,
knowledge: 1.0
},
buildingModifiers: {
farm: 1.3,
residence: 1.1
},
movementCost: 1,
color: '#9ACD32'
},
forest: {
name: 'Forest',
description: 'Densely wooded areas rich in materials',
resourceModifiers: {
food: 1.2,
materials: 1.4,
knowledge: 1.1
},
buildingModifiers: {
lumberMill: 1.5,
workshop: 1.2
},
movementCost: 1.5,
color: '#228B22'
},
mountain: {
name: 'Mountains',
description: 'Rugged, elevated terrain with mineral deposits',
resourceModifiers: {
food: 0.7,
materials: 1.3,
knowledge: 1.2
},
buildingModifiers: {
mine: 1.6,
observatory: 1.4
},
movementCost: 2,
color: '#808080'
},
river: {
name: 'River',
description: 'Flowing water providing food and transportation',
resourceModifiers: {
food: 1.4,
materials: 1.1,
knowledge: 1.2
},
buildingModifiers: {
fisherHut: 1.5,
waterMill: 1.4,
port: 1.2
},
movementCost: 2,
color: '#4682B4'
},
lake: {
name: 'Lake',
description: 'Calm body of water with abundant fish',
resourceModifiers: {
food: 1.6,
materials: 0.9,
knowledge: 1.1
},
buildingModifiers: {
fisherHut: 1.6,
port: 1.1
},
movementCost: 3,
color: '#6495ED'
},
desert: {
name: 'Desert',
description: 'Arid region with sparse resources',
resourceModifiers: {
food: 0.6,
materials: 0.8,
knowledge: 1.2
},
buildingModifiers: {
well: 1.5,
solarCollector: 1.5
},
movementCost: 1.5,
color: '#F0E68C'
},
hills: {
name: 'Hills',
description: 'Rolling elevated terrain',
// character-tasks.js
// Manages the tasks that characters can undertake
import { ResourceManager } from '../resources/resource-manager.js';
import { BuildingManager } from '../buildings/building-manager.js';
import { TechnologyManager } from '../technology/technology-manager.js';
import { EventManager } from '../events/event-manager.js';
import { WorldGenerator } from '../world-generator.js';
// CharacterTasks - Defines and manages tasks for characters
const CharacterTasks = (function() {
// Task categories
const taskCategories = {
RESOURCE: 'resource',
BUILDING: 'building',
TECHNOLOGY: 'technology',
COMMUNITY: 'community',
EXPLORATION: 'exploration',
PRODUCTION: 'production',
TRADE: 'trade',
EVENT: 'event'
};
// Basic tasks available to all characters
const basicTasks = [
{
id: 'gather_resources',
name: 'Gather resources',
category: taskCategories.RESOURCE,
duration: 5,
difficulty: 1,
description: 'Collect basic resources for the civilization',
requiredLevel: 1,
roleBonus: {
'farmer': { food: 10 },
'builder': { materials: 10 },
'scientist': { knowledge: 10 }
},
getOutcome: (character) => {
const baseAmount = 5;
const roleBonus = basicTasks[0].roleBonus[character.role] || {};
return {
type: 'resources',
details: {
food: roleBonus.food || baseAmount,
materials: roleBonus.materials || baseAmount,
knowledge: roleBonus.knowledge || baseAmount
}
};
}
},
{
id: 'rest',
name: 'Rest and recover',
category: taskCategories.COMMUNITY,
duration: 3,
difficulty: 1,
description: 'Take time to recover and gain inspiration',
requiredLevel: 1,
getOutcome: (character) => {
// Resting gives a small boost to future productivity
return {
type: 'character',
details: {
temporaryBoost: {
intelligence: 1,
creativity: 1,
duration: 5
}
}
};
}
}
];
// Role-specific task definitions
const roleTasks = {
leader: [
{
id: 'organize_community',
name: 'Organize community',
category: taskCategories.COMMUNITY,
duration: 10,
difficulty: 3,
description: 'Improve the organization and efficiency of the community',
requiredLevel: 2,
getOutcome: (character) => {
const boostAmount = 0.1 + (character.level * 0.01);
return {
type: 'community',
details: {
productivityBoost: boostAmount,
duration: 15 + character.level
}
};
}
},
{
id: 'diplomatic_mission',
name: 'Diplomatic mission',
category: taskCategories.EVENT,
duration: 15,
difficulty: 4,
description: 'Attempt to negotiate with neighboring groups',
requiredLevel: 3,
getOutcome: (character) => {
const successChance = 0.5 + (character.intelligence / 20) + (character.cooperation / 20);
return {
type: 'event',
details: {
eventType: 'diplomatic',
chance: successChance
}
};
}
},
{
id: 'inspire_innovation',
name: 'Inspire innovation',
category: taskCategories.COMMUNITY,
duration: 8,
difficulty: 4,
description: 'Inspire others to think creatively and develop new ideas',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'community',
details: {
creativityBoost: 0.15,
duration: 10,
researchBoost: 0.1
}
};
}
}
],
builder: [
{
id: 'survey_land',
name: 'Survey land',
category: taskCategories.EXPLORATION,
duration: 5,
difficulty: 2,
description: 'Identify optimal building locations and resource deposits',
requiredLevel: 2,
getOutcome: (character) => {
return {
type: 'building',
details: {
constructionBoost: 0.1,
duration: 15,
resourceFind: Math.random() < 0.3 // 30% chance of finding resources
}
};
}
},
{
id: 'improve_infrastructure',
name: 'Improve infrastructure',
category: taskCategories.BUILDING,
duration: 12,
difficulty: 3,
description: 'Make general improvements to existing buildings and infrastructure',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'building',
details: {
maintenanceReduction: 0.15,
duration: 20,
buildingEfficiencyBoost: 0.05
}
};
}
}
// "Build X" tasks will be dynamically generated based on available buildings
],
scientist: [
{
id: 'analyze_data',
name: 'Analyze data',
category: taskCategories.TECHNOLOGY,
duration: 7,
difficulty: 3,
description: 'Review and analyze accumulated knowledge',
requiredLevel: 2,
getOutcome: (character) => {
return {
type: 'technology',
details: {
knowledgeBoost: 10 + (character.intelligence * 2),
researchBoost: 0.1,
duration: 10
}
};
}
},
{
id: 'experiment',
name: 'Conduct experiments',
category: taskCategories.TECHNOLOGY,
duration: 10,
difficulty: 4,
description: 'Run controlled experiments to test theories',
requiredLevel: 3,
getOutcome: (character) => {
const discoveryChance = 0.3 + (character.creativity / 20);
return {
type: 'technology',
details: {
breakthroughChance: discoveryChance,
knowledgeGain: 15 + (character.intelligence * 1.5)
}
};
}
}
// "Research X" tasks will be dynamically generated based on available technologies
],
farmer: [
{
id: 'improve_agriculture',
name: 'Improve agriculture',
category: taskCategories.PRODUCTION,
duration: 8,
difficulty: 2,
description: 'Develop better farming techniques',
requiredLevel: 2,
getOutcome: (character) => {
const boost = 0.15 + (character.level * 0.02);
return {
type: 'production',
details: {
resourceType: 'food',
boost: boost,
duration: 15
}
};
}
},
{
id: 'develop_irrigation',
name: 'Develop irrigation',
category: taskCategories.BUILDING,
duration: 12,
difficulty: 3,
description: 'Create irrigation systems to improve crop yields',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'production',
details: {
resourceType: 'food',
boost: 0.2,
duration: 25,
permanent: 0.05 // Some permanent boost
}
};
}
},
{
id: 'animal_husbandry',
name: 'Develop animal husbandry',
category: taskCategories.PRODUCTION,
duration: 15,
difficulty: 4,
description: 'Domesticate and breed animals for food and labor',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'production',
details: {
resourceType: 'food',
boost: 0.25,
laborBoost: 0.1,
duration: 20,
permanent: 0.08
}
};
}
}
],
merchant: [
{
id: 'establish_trade_route',
name: 'Establish trade route',
category: taskCategories.TRADE,
duration: 12,
difficulty: 3,
description: 'Set up a new trade route with neighboring regions',
requiredLevel: 2,
getOutcome: (character) => {
const resourceGain = ['food', 'materials', 'knowledge'];
const baseAmount = 5 + character.level;
return {
type: 'trade',
details: {
resourceGain: resourceGain,
amount: baseAmount,
ongoing: true
}
};
}
},
{
id: 'organize_market',
name: 'Organize marketplace',
category: taskCategories.COMMUNITY,
duration: 8,
difficulty: 2,
description: 'Create a central marketplace for resource exchange',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'trade',
details: {
resourceDistributionBoost: 0.15,
wealthGeneration: 10,
duration: 15
}
};
}
},
{
id: 'luxury_trade',
name: 'Trade for luxury goods',
category: taskCategories.TRADE,
duration: 15,
difficulty: 4,
description: 'Seek out and acquire valuable luxury items',
requiredLevel: 4,
getOutcome: (character) => {
return {
type: 'event',
details: {
eventType: 'luxury',
moraleBoost: 0.2,
duration: 10,
specialResourceChance: 0.7
}
};
}
}
],
explorer: [
{
id: 'explore_territories',
name: 'Explore new territories',
category: taskCategories.EXPLORATION,
duration: 15,
difficulty: 4,
description: 'Venture into unknown areas to expand the map',
requiredLevel: 2,
getOutcome: (character) => {
const regions = Math.floor(Math.random() * 2) + 1;
return {
type: 'exploration',
details: {
newRegions: regions,
resourceFind: Math.random() > 0.5
}
};
}
},
{
id: 'map_resources',
name: 'Map resource deposits',
category: taskCategories.EXPLORATION,
duration: 10,
difficulty: 3,
description: 'Create detailed maps of resource-rich areas',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'resource',
details: {
resourceFindChance: 0.8,
resourceMultiplier: 1.5,
mappingBonus: {
gatherEfficiency: 0.2,
duration: 30
}
}
};
}
},
{
id: 'scout_dangers',
name: 'Scout for dangers',
category: taskCategories.EXPLORATION,
duration: 8,
difficulty: 3,
description: 'Identify potential threats in the surroundings',
requiredLevel: 3,
getOutcome: (character) => {
return {
type: 'community',
details: {
threatReduction: 0.3,
duration: 20,
earlyWarningSystem: true
}
};
}
}
]
};
// Get all basic tasks available to any character
function getBasicTasks() {
return [...basicTasks];
}
// Get tasks available to a specific character based on role and level
function getAvailableTasks(character, gameState) {
const tasks = [...getBasicTasks()];
// Add role-specific tasks
if (roleTasks[character.role]) {
roleTasks[character.role].forEach(task => {
if (character.level >= task.requiredLevel) {
tasks.push(task);
}
});
}
// Add building construction tasks for builders
if (character.role === 'builder') {
const availableBuildings = BuildingManager.getAvailableBuildings(gameState);
availableBuildings.forEach(building => {
tasks.push({
id: `build_${building.id}`,
name: `Build ${building.name}`,
category: taskCategories.BUILDING,
duration: building.constructionTime,
difficulty: building.difficulty,
description: `Construct a ${building.name}: ${building.description}`,
requiredLevel: building.requiredLevel || 1,
getOutcome: () => {
return {
type: 'building',
details: {
buildingId: building.id
}
};
}
});
});
}
// Add technology research tasks for scientists
if (character.role === 'scientist') {
const availableTechs = TechnologyManager.getAvailableTechnologies(gameState);
availableTechs.forEach(tech => {
tasks.push({
id: `research_${tech.id}`,
name: `Research ${tech.name}`,
category: taskCategories.TECHNOLOGY,
duration: tech.researchTime,
difficulty: tech.difficulty,
description: `Research ${tech.name}: ${tech.description}`,
requiredLevel: tech.requiredLevel || 1,
getOutcome: () => {
return {
type: 'technology',
details: {
techId: tech.id
}
};
}
});
});
}
return tasks;
}
// Process the outcome of a completed task
function processTaskOutcome(task, character, gameState) {
const outcome = task.getOutcome(character);
console.log(`Processing outcome for task: ${task.name}`, outcome);
switch (outcome.type) {
case 'resources':
for (const resource in outcome.details) {
ResourceManager.addResource(resource, outcome.details[resource]);
}
break;
case 'building':
if (outcome.details.buildingId) {
BuildingManager.constructBuilding(outcome.details.buildingId, gameState);
}
// Apply construction boost if present
if (outcome.details.constructionBoost) {
EventManager.addEvent({
type: 'building',
message: `${character.name} has improved construction efficiency!`,
details: `+${outcome.details.constructionBoost * 100}% construction speed for ${outcome.details.duration} turns`,
turn: gameState.turn,
duration: outcome.details.duration
});
}
// Check for resource find
if (outcome.details.resourceFind) {
const resourceType = ['food', 'materials', 'knowledge'][Math.floor(Math.random() * 3)];
const amount = Math.floor(Math.random() * 15) + 5;
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} found resources during construction!`,
details: `Found ${amount} ${resourceType}`,
turn: gameState.turn
});
}
break;
case 'technology':
if (outcome.details.techId) {
TechnologyManager.researchTechnology(outcome.details.techId, gameState);
}
// Apply knowledge gain
if (outcome.details.knowledgeGain) {
ResourceManager.addResource('knowledge', outcome.details.knowledgeGain);
}
// Apply research boost
if (outcome.details.researchBoost) {
EventManager.addEvent({
type: 'technology',
message: `${character.name} has accelerated research!`,
details: `+${outcome.details.researchBoost * 100}% research speed for ${outcome.details.duration} turns`,
turn: gameState.turn,
duration: outcome.details.duration
});
}
// Check for breakthrough
if (outcome.details.breakthroughChance && Math.random() < outcome.details.breakthroughChance) {
// Generate a research breakthrough
const breakthrough = TechnologyManager.generateBreakthrough(character);
EventManager.addEvent({
type: 'technology',
message: `${character.name} has made a breakthrough in research!`,
details: breakthrough.description,
turn: gameState.turn
});
}
break;
case 'community':
// Apply community effects
let eventDetails = '';
if (outcome.details.productivityBoost) {
eventDetails += `+${outcome.details.productivityBoost * 100}% productivity `;
}
if (outcome.details.creativityBoost) {
eventDetails += `+${outcome.details.creativityBoost * 100}% creativity `;
}
if (outcome.details.threatReduction) {
eventDetails += `${outcome.details.threatReduction * 100}% reduced threat level `;
}
EventManager.addEvent({
type: 'community',
message: `${character.name} has improved the community!`,
details: `${eventDetails} for ${outcome.details.duration} turns`,
turn: gameState.turn,
duration: outcome.details.duration,
effects: outcome.details
});
break;
case 'production':
EventManager.addEvent({
type: 'production',
message: `${character.name} has improved ${outcome.details.resourceType} production!`,
details: `+${outcome.details.boost * 100}% for ${outcome.details.duration} turns`,
turn: gameState.turn,
resourceType: outcome.details.resourceType,
boost: outcome.details.boost,
duration: outcome.details.duration
});
// Apply permanent boost if present
if (outcome.details.permanent) {
// This would update a permanent production multiplier in the resource manager
ResourceManager.addPermanentBoost(outcome.details.resourceType, outcome.details.permanent);
EventManager.addEvent({
type: 'production',
message: `${character.name} has permanently improved ${outcome.details.resourceType} production!`,
details: `+${outcome.details.permanent * 100}% permanent boost`,
turn: gameState.turn
});
}
break;
case 'trade':
// Apply trade outcome
if (outcome.details.resourceGain) {
outcome.details.resourceGain.forEach(resource => {
ResourceManager.addResource(resource, outcome.details.amount);
});
}
// Add ongoing trade route if applicable
if (outcome.details.ongoing) {
// This would register an ongoing effect in the resource manager
ResourceManager.addTradeRoute({
resources: outcome.details.resourceGain,
amount: outcome.details.amount,
founder: character.id
});
EventManager.addEvent({
type: 'trade',
message: `${character.name} has established a new trade route!`,
details: `Gaining various resources each turn`,
turn: gameState.turn,
ongoing: true
});
}
break;
case 'exploration':
// Apply exploration outcome
if (outcome.details.newRegions) {
// This would call into the world generator to expand the map
WorldGenerator.addRegions(outcome.details.newRegions);
}
if (outcome.details.resourceFind) {
const resourceType = ['food', 'materials', 'knowledge'][Math.floor(Math.random() * 3)];
const amount = Math.floor(Math.random() * 20) + 10;
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `Found ${amount} ${resourceType} in the process`,
turn: gameState.turn
});
} else {
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered new territories!`,
details: `The world map has been expanded`,
turn: gameState.turn
});
}
break;
case 'event':
// Generate a special event
if (outcome.details.eventType) {
EventManager.generateSpecialEvent(outcome.details.eventType, character, outcome.details);
}
break;
case 'character':
// Apply character-specific effects
if (outcome.details.temporaryBoost) {
character.temporaryBoosts = character.temporaryBoosts || [];
character.temporaryBoosts.push({
...outcome.details.temporaryBoost,
startTurn: gameState.turn
});
EventManager.addEvent({
type: 'character',
message: `${character.name} is feeling refreshed and inspired!`,
details: `Temporarily improved abilities for ${outcome.details.temporaryBoost.duration} turns`,
turn: gameState.turn
});
}
break;
case 'resource':
// Apply resource mapping bonuses
if (outcome.details.mappingBonus) {
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has created detailed resource maps!`,
details: `+${outcome.details.mappingBonus.gatherEfficiency * 100}% resource gathering efficiency for ${outcome.details.mappingBonus.duration} turns`,
turn: gameState.turn,
duration: outcome.details.mappingBonus.duration,
effects: outcome.details.mappingBonus
});
}
// Check for resource finds
if (outcome.details.resourceFindChance && Math.random() < outcome.details.resourceFindChance) {
const resources = ['food', 'materials', 'knowledge'];
const resourceCount = Math.floor(Math.random() * 2) + 1;
for (let i = 0; i < resourceCount; i++) {
const resourceType = resources[Math.floor(Math.random() * resources.length)];
const baseAmount = Math.floor(Math.random() * 15) + 5;
const amount = Math.floor(baseAmount * outcome.details.resourceMultiplier);
ResourceManager.addResource(resourceType, amount);
EventManager.addEvent({
type: 'exploration',
message: `${character.name} has discovered a ${resourceType} deposit!`,
details: `Added ${amount} ${resourceType} to stockpile`,
turn: gameState.turn
});
}
}
break;
}
return outcome;
}
// Return public API
return {
getBasicTasks,
getAvailableTasks,
processTaskOutcome,
taskCategories
};
})();
export { CharacterTasks };
// world-generator.js
// Creates and manages the game world and its regions
import { EventManager } from './events/event-manager.js';
// WorldGenerator - Creates the game world
const WorldGenerator = (function() {
// Terrain types with their properties
const terrainTypes = {
plains: {
name: 'Plains',
description: 'Flat, grassy areas ideal for agriculture',
resourceModifiers: {
food: 1.5,
materials: 1.0,
knowledge: 1.0
},
buildingModifiers: {
farm: 1.3,
residence: 1.1
},
movementCost: 1,
color: '#9ACD32'
},
forest: {
name: 'Forest',
description: 'Densely wooded areas rich in materials',
resourceModifiers: {
food: 1.2,
materials: 1.4,
knowledge: 1.1
},
buildingModifiers: {
lumberMill: 1.5,
workshop: 1.2
},
movementCost: 1.5,
color: '#228B22'
},
mountain: {
name: 'Mountains',
description: 'Rugged, elevated terrain with mineral deposits',
resourceModifiers: {
food: 0.7,
materials: 1.3,
knowledge: 1.2
},
buildingModifiers: {
mine: 1.6,
observatory: 1.4
},
movementCost: 2,
color: '#808080'
},
river: {
name: 'River',
description: 'Flowing water providing food and transportation',
resourceModifiers: {
food: 1.4,
materials: 1.1,
knowledge: 1.2
},
buildingModifiers: {
fisherHut: 1.5,
waterMill: 1.4,
port: 1.2
},
movementCost: 2,
color: '#4682B4'
},
lake: {
name: 'Lake',
description: 'Calm body of water with abundant fish',
resourceModifiers: {
food: 1.6,
materials: 0.9,
knowledge: 1.1
},
buildingModifiers: {
fisherHut: 1.6,
port: 1.1
},
movementCost: 3,
color: '#6495ED'
},
desert: {
name: 'Desert',
description: 'Arid region with sparse resources',
resourceModifiers: {
food: 0.6,
materials: 0.8,
knowledge: 1.2
},
buildingModifiers: {
well: 1.5,
solarCollector: 1.5
},
movementCost: 1.5,
color: '#F0E68C'
},
hills: {
name: 'Hills',
description: 'Rolling elevated terrain',
resourceModifiers: {
food: 1.1,
materials: 1.2,
knowledge: 1.0
},
buildingModifiers: {
mine: 1.2,
watchtower: 1.3
},
movementCost: 1.5,
color: '#CD853F'
}
};
// Region types combining multiple terrain types
const regionTypes = {
startingValley: {
name: 'Fertile Valley',
description: 'A sheltered valley with diverse resources',
terrains: ['plains', 'river', 'forest'],
terrainWeights: [0.5, 0.3, 0.2],
resourceBonus: {
food: 1.1,
materials: 1.1
},
difficulty: 1
},
woodlands: {
name: 'Dense Woodlands',
description: 'A thick forest with abundant materials',
terrains: ['forest', 'hills', 'river'],
terrainWeights: [0.7, 0.2, 0.1],
resourceBonus: {
materials: 1.3
},
difficulty: 2
},
highlands: {
name: 'Highland Plateau',
description: 'Elevated terrain with commanding views',
terrains: ['hills', 'mountain', 'plains'],
terrainWeights: [0.5, 0.3, 0.2],
resourceBonus: {
knowledge: 1.2
},
difficulty: 3
},
desert: {
name: 'Arid Wasteland',
description: 'A harsh environment with hidden resources',
terrains: ['desert', 'hills', 'plains'],
terrainWeights: [0.7, 0.2, 0.1],
resourceBonus: {
knowledge: 1.3
},
difficulty: 4
},
riverlands: {
name: 'River Delta',
description: 'A network of rivers with fertile soil',
terrains: ['river', 'plains', 'lake'],
terrainWeights: [0.4, 0.4, 0.2],
resourceBonus: {
food: 1.4
},
difficulty: 2
},
mountains: {
name: 'Mountain Range',
description: 'Imposing peaks rich in minerals',
terrains: ['mountain', 'hills', 'forest'],
terrainWeights: [0.6, 0.3, 0.1],
resourceBonus: {
materials: 1.3,
knowledge: 1.2
},
difficulty: 5
},
lakeshore: {
name: 'Tranquil Lakeshore',
description: 'A peaceful area surrounding a large lake',
terrains: ['lake', 'plains', 'forest'],
terrainWeights: [0.4, 0.4, 0.2],
resourceBonus: {
food: 1.3
},
difficulty: 2
}
};
// Special features that can appear in regions
const specialFeatures = [
{
id: 'ancient_ruins',
name: 'Ancient Ruins',
description: 'Remnants of a past civilization',
effects: {
knowledgeBonus: 0.2,
discoveryChance: 0.3
},
rarity: 0.2
},
{
id: 'mineral_deposit',
name:
// world-generator.js
// Creates and manages the game world and its regions
import { EventManager } from './events/event-manager.js';
// WorldGenerator - Creates the game world
const WorldGenerator = (function() {
// Terrain types with their properties
const terrainTypes = {
plains: {
name: 'Plains',
description: 'Flat, grassy areas ideal for agriculture',
resourceModifiers: {
food: 1.5,
materials: 1.0,
knowledge: 1.0
},
buildingModifiers: {
farm: 1.3,
residence: 1.1
},
movementCost: 1,
color: '#9ACD32'
},
forest: {
name: 'Forest',
description: 'Densely wooded areas rich in materials',
resourceModifiers: {
food: 1.2,
materials: 1.4,
knowledge: 1.1
},
buildingModifiers: {
lumberMill: 1.5,
workshop: 1.2
},
movementCost: 1.5,
color: '#228B22'
},
mountain: {
name: 'Mountains',
description: 'Rugged, elevated terrain with mineral deposits',
resourceModifiers: {
food: 0.7,
materials: 1.3,
knowledge: 1.2
},
buildingModifiers: {
mine: 1.6,
observatory: 1.4
},
movementCost: 2,
color: '#808080'
},
river: {
name: 'River',
description: 'Flowing water providing food and transportation',
resourceModifiers: {
food: 1.4,
materials: 1.1,
knowledge: 1.2
},
buildingModifiers: {
fisherHut: 1.5,
waterMill: 1.4,
port: 1.2
},
movementCost: 2,
color: '#4682B4'
},
lake: {
name: 'Lake',
description: 'Calm body of water with abundant fish',
resourceModifiers: {
food: 1.6,
materials: 0.9,
knowledge: 1.1
},
buildingModifiers: {
fisherHut: 1.6,
port: 1.1
},
movementCost: 3,
color: '#6495ED'
},
desert: {
name: 'Desert',
description: 'Arid region with sparse resources',
resourceModifiers: {
food: 0.6,
materials: 0.8,
knowledge: 1.2
},
buildingModifiers: {
well: 1.5,
solarCollector: 1.5
},
movementCost: 1.5,
color: '#F0E68C'
},
hills: {
name: 'Hills',
description: 'Rolling elevated terrain',
resourceModifiers: {
food: 1.1,
materials: 1.2,
knowledge: 1.0
},
buildingModifiers: {
mine: 1.2,
watchtower: 1.3
},
movementCost: 1.5,
color: '#CD853F'
}
};
// Region types combining multiple terrain types
const regionTypes = {
startingValley: {
name: 'Fertile Valley',
description: 'A sheltered valley with diverse resources',
terrains: ['plains', 'river', 'forest'],
terrainWeights: [0.5, 0.3, 0.2],
resourceBonus: {
food: 1.1,
materials: 1.1
},
difficulty: 1
},
woodlands: {
name: 'Dense Woodlands',
description: 'A thick forest with abundant materials',
terrains: ['forest', 'hills', 'river'],
terrainWeights: [0.7, 0.2, 0.1],
resourceBonus: {
materials: 1.3
},
difficulty: 2
},
highlands: {
name: 'Highland Plateau',
description: 'Elevated terrain with commanding views',
terrains: ['hills', 'mountain', 'plains'],
terrainWeights: [0.5, 0.3, 0.2],
resourceBonus: {
knowledge: 1.2
},
difficulty: 3
},
desert: {
name: 'Arid Wasteland',
description: 'A harsh environment with hidden resources',
terrains: ['desert', 'hills', 'plains'],
terrainWeights: [0.7, 0.2, 0.1],
resourceBonus: {
knowledge: 1.3
},
difficulty: 4
},
riverlands: {
name: 'River Delta',
description: 'A network of rivers with fertile soil',
terrains: ['river', 'plains', 'lake'],
terrainWeights: [0.4, 0.4, 0.2],
resourceBonus: {
food: 1.4
},
difficulty: 2
},
mountains: {
name: 'Mountain Range',
description: 'Imposing peaks rich in minerals',
terrains: ['mountain', 'hills', 'forest'],
terrainWeights: [0.6, 0.3, 0.1],
resourceBonus: {
materials: 1.3,
knowledge: 1.2
},
difficulty: 5
},
lakeshore: {
name: 'Tranquil Lakeshore',
description: 'A peaceful area surrounding a large lake',
terrains: ['lake', 'plains', 'forest'],
terrainWeights: [0.4, 0.4, 0.2],
resourceBonus: {
food: 1.3
},
difficulty: 2
}
};
// Special features that can appear in regions
const specialFeatures = [
{
id: 'ancient_ruins',
name: 'Ancient Ruins',
description: 'Remnants of a past civilization',
effects: {
knowledgeBonus: 0.2,
discoveryChance: 0.3
},
rarity: 0.2
},
{
id: 'mineral_deposit',
name: 'Rich Mineral Deposit',
description: 'An concentrated source of valuable materials',
effects: {
materialsBonus: 0.3
},
rarity: 0.25
},
{
id: 'fertile_soil',
name: 'Exceptionally Fertile Soil',
description: 'Nutrient-rich ground perfect for agriculture',
effects: {
foodBonus: 0.3
},
rarity: 0.25
},
{
id: 'natural_wonder',
name: 'Natural Wonder',
description: 'A breathtaking natural formation',
effects: {
knowledgeBonus: 0.15,
all_resource_bonus: 0.1
},
rarity: 0.1
},
{
id: 'ancient_technology',
name: 'Ancient Technology',
description: 'Mysterious devices from a bygone era',
effects: {
knowledgeBonus: 0.25,
tech_discovery_chance: 0.5
},
rarity: 0.05
}
];
// Generate a new world map
function generateNewWorld() {
console.log('Generating new world...');
// Create the world object
const world = {
name: generateWorldName(),
regions: [],
discoveredRegions: 0,
size: 25, // Total number of potential regions
developmentLevel: 1,
specialFeatures: [],
threats: []
};
// Add the starting region
addStartingRegion(world);
console.log('World generation complete');
return world;
}
// Generate a random name for the world
function generateWorldName() {
const prefixes = ['New', 'Ancient', 'Lost', 'Hidden', 'Mystic', 'Forgotten', 'Eternal', 'Verdant'];
const roots = ['Terra', 'Gaia', 'Earth', 'Land', 'World', 'Realm', 'Domain', 'Haven'];
const suffixes = ['ia', 'is', 'us', 'um', 'or', 'on', 'ar', ''];
return `${prefixes[Math.floor(Math.random() * prefixes.length)]} ${roots[Math.floor(Math.random() * roots.length)]}${suffixes[Math.floor(Math.random() * suffixes.length)]}`;
}
// Add the starting region to the world
function addStartingRegion(world) {
const startingRegion = createRegion('startingValley', 0, 0, true);
startingRegion.discovered = true;
startingRegion.explored = true;
world.regions.push(startingRegion);
world.discoveredRegions = 1;
// Add some adjacent regions that are discovered but not explored
const adjacentDirections = [
{ x: 1, y: 0 }, // East
{ x: 0, y: 1 }, // South
{ x: -1, y: 0 }, // West
{ x: 0, y: -1 } // North
];
// Randomly select 2-3 directions for adjacent regions
const shuffledDirections = shuffleArray([...adjacentDirections]);
const numAdjacent = Math.floor(Math.random() * 2) + 2; // 2-3 adjacent regions
for (let i = 0; i < numAdjacent; i++) {
const dir = shuffledDirections[i];
const regionType = getRandomRegionType(2); // Low difficulty for starting areas
const newRegion = createRegion(regionType, dir.x, dir.y);
newRegion.discovered = true; // Discovered but not explored
newRegion.explored = false;
world.regions.push(newRegion);
}
}
// Create a new region
function createRegion(regionType, x, y, isStartingRegion = false) {
let regionTemplate;
// If a specific region type is requested, use that
if (regionTypes[regionType]) {
regionTemplate = regionTypes[regionType];
} else {
// Otherwise, use the provided regionType as a string key
regionTemplate = regionTypes[regionType];
}
// Generate a region ID
const regionId = `region_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// Create the terrain distribution
const terrainDistribution = {};
for (let i = 0; i < regionTemplate.terrains.length; i++) {
terrainDistribution[regionTemplate.terrains[i]] = regionTemplate.terrainWeights[i];
}
// Add special features (only for non-starting regions, to keep the start simple)
let features = [];
if (!isStartingRegion) {
features = generateSpecialFeatures(regionTemplate);
}
// Create the region object
const region = {
id: regionId,
name: generateRegionName(regionTemplate),
type: regionType,
description: regionTemplate.description,
position: { x, y },
terrainDistribution: terrainDistribution,
resourceModifiers: { ...regionTemplate.resourceBonus },
specialFeatures: features,
discovered: false,
explored: false,
developmentLevel: 0,
buildings: [],
resources: {
food: Math.floor(Math.random() * 20) + 5,
materials: Math.floor(Math.random() * 15) + 5,
knowledge: Math.floor(Math.random() * 10) + 2
}
};
return region;
}
// Generate a random name for a region
function generateRegionName(regionTemplate) {
// Use the region type to influence the name
const regionTypeNames = {
startingValley: ['Verdant', 'Sheltered', 'Peaceful', 'Dawn', 'First', 'Home'],
woodlands: ['Whispering', 'Ancient', 'Mystic', 'Dark', 'Towering', 'Emerald'],
highlands: ['Windy', 'High', 'Cloud', 'Thunder', 'Craggy', 'Sky'],
desert: ['Arid', 'Burning', 'Sun', 'Dust', 'Scorched', 'Dry'],
riverlands: ['Rushing', 'Flowing', 'Blue', 'Fertile', 'Winding', 'Blessed'],
mountains: ['Stone', 'Jagged', 'Frost', 'Rugged', 'Broken', 'Great'],
lakeshore: ['Serene', 'Misty', 'Crystal', 'Calm', 'Tranquil', 'Placid']
};
const suffixes = {
startingValley: ['Valley', 'Dale', 'Hollow', 'Glen', 'Glade'],
woodlands: ['Woods', 'Forest', 'Thicket', 'Grove', 'Wildwood'],
highlands: ['Heights', 'Plateau', 'Rise', 'Bluffs', 'Highland'],
desert: ['Wastes', 'Sands', 'Dunes', 'Expanse', 'Badlands'],
riverlands: ['Delta', 'Banks', 'Forks', 'Flow', 'Meadows'],
mountains: ['Peaks', 'Range', 'Summit', 'Crags', 'Spires'],
lakeshore: ['Lake', 'Shore', 'Waters', 'Bay', 'Cove']
};
const prefix = regionTypeNames[regionTemplate.name] ||
regionTypeNames[Object.keys(regionTypeNames)[0]];
const suffix = suffixes[regionTemplate.name] ||
suffixes[Object.keys(suffixes)[0]];
return `${prefix[Math.floor(Math.random() * prefix.length)]} ${suffix[Math.floor(Math.random() * suffix.length)]}`;
}
// Generate special features for a region
function generateSpecialFeatures(regionTemplate) {
const features = [];
// Calculate the chance of a special feature based on region difficulty
// Higher difficulty means more likely to have special features
const baseChance = 0.1 + (regionTemplate.difficulty * 0.05);
// Determine if this region gets a special feature
if (Math.random() < baseChance) {
// Select a random feature weighted by rarity
const possibleFeatures = specialFeatures.filter(feature => Math.random() < feature.rarity);
if (possibleFeatures.length > 0) {
const selectedFeature = possibleFeatures[Math.floor(Math.random() * possibleFeatures.length)];
features.push({
id: selectedFeature.id,
name: selectedFeature.name,
description: selectedFeature.description,
effects: selectedFeature.effects,
discovered: false
});
}
}
return features;
}
// Get a random region type with optional difficulty constraint
function getRandomRegionType(maxDifficulty = 5) {
const possibleTypes = Object.keys(regionTypes).filter(type =>
regionTypes[type].difficulty <= maxDifficulty
);
return possibleTypes[Math.floor(Math.random() * possibleTypes.length)];
}
// Add new regions to the world
function addRegions(world, numRegions) {
console.log(`Adding ${numRegions} new regions to the world...`);
// Find existing discovered regions to attach new ones to
const discoveredRegions = world.regions.filter(region => region.discovered);
if (discoveredRegions.length === 0) {
console.error('No discovered regions to attach new regions to');
return;
}
// For each new region to add
for (let i = 0; i < numRegions; i++) {
// Pick a random discovered region to attach to
const attachToRegion = discoveredRegions[Math.floor(Math.random() * discoveredRegions.length)];
// Find an empty adjacent space
const adjacentPositions = getAdjacentPositions(attachToRegion.position);
const emptyPositions = adjacentPositions.filter(pos =>
!world.regions.some(region => region.position.x === pos.x && region.position.y === pos.y)
);
if (emptyPositions.length === 0) {
console.log('No empty adjacent positions for new region');
continue;
}
// Select a random empty position
const newPosition = emptyPositions[Math.floor(Math.random() * emptyPositions.length)];
// Determine the region type, with increasing difficulty based on distance from center
const distanceFromCenter = Math.sqrt(newPosition.x * newPosition.x + newPosition.y * newPosition.y);
const maxDifficulty = Math.min(5, 1 + Math.floor(distanceFromCenter / 2));
const regionType = getRandomRegionType(maxDifficulty);
// Create the new region
const newRegion = createRegion(regionType, newPosition.x, newPosition.y);
newRegion.discovered = true; // Discovered but not explored
// Add to world
world.regions.push(newRegion);
// Trigger a discovery event
EventManager.addEvent({
type: 'world',
message: `A new region has been discovered!`,
details: `${newRegion.name} (${newRegion.type}) has been added to the map.`,
turn: window.AICivilizationGame?.getGameState()?.turn || 0
});
}
console.log(`Added ${numRegions} new regions to the world`);
}
// Get adjacent positions to a given position
function getAdjacentPositions(position) {
return [
{ x: position.x + 1, y: position.y }, // East
{ x: position.x, y: position.y + 1 }, // South
{ x: position.x - 1, y: position.y }, // West
{ x: position.x, y: position.y - 1 } // North
];
}
// Explore a region (mark as explored and discover features)
function exploreRegion(world, regionId) {
const region = world.regions.find(r => r.id === regionId);
if (!region) {
console.error(`Region with ID ${regionId} not found`);
return false;
}
if (region.explored) {
console.log(`Region ${region.name} is already explored`);
return false;
}
// Mark as explored
region.explored = true;
// Discover adjacent regions
const adjacentPositions = getAdjacentPositions(region.position);
// Check if there are existing regions at those positions
for (const pos of adjacentPositions) {
const existingRegion = world.regions.find(r =>
r.position.x === pos.x && r.position.y === pos.y
);
if (existingRegion) {
existingRegion.discovered = true;
} else {
// Create a new undiscovered region with a 30% chance
if (Math.random() < 0.3) {
const distanceFromCenter = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
const maxDifficulty = Math.min(5, 1 + Math.floor(distanceFromCenter / 2));
const regionType = getRandomRegionType(maxDifficulty);
const newRegion = createRegion(regionType, pos.x, pos.y);
newRegion.discovered = true;
world.regions.push(newRegion);
// Trigger a discovery event
EventManager.addEvent({
type: 'world',
message: `A new region has been discovered!`,
details: `${newRegion.name} (${newRegion.type}) has been added to the map.`,
turn: window.AICivilizationGame?.getGameState()?.turn || 0
});
}
}
}
// Discover special features
for (const feature of region.specialFeatures) {
// 70% chance to discover each feature during exploration
if (!feature.discovered && Math.random() < 0.7) {
feature.discovered = true;
// Trigger a discovery event
EventManager.addEvent({
type: 'discovery',
message: `A special feature has been discovered in ${region.name}!`,
details: `${feature.name}: ${feature.description}`,
turn: window.AICivilizationGame?.getGameState()?.turn || 0
});
}
}
// Trigger exploration event
EventManager.addEvent({
type: 'exploration',
message: `${region.name} has been fully explored!`,
details: `You now have detailed information about this region.`,
turn: window.AICivilizationGame?.getGameState()?.turn || 0
});
return true;
}
// Utility function to shuffle an array
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
// Return public API
return {
generateNewWorld,
addRegions,
exploreRegion,
getTerrainTypes: () => terrainTypes,
getRegionTypes: () => regionTypes
};
})();
export { WorldGenerator };
// world-renderer.js
// Handles rendering the game world to the UI
import { WorldGenerator } from './world-generator.js';
// WorldRenderer - Visualizes the game world
const WorldRenderer = (function() {
// Private variables
let world = null;
let canvasContext = null;
let tileSize = 60;
let canvasSize = 600;
let centerX = 0;
let centerY = 0;
// Initialize with a world object
function init(worldData) {
world = worldData;
console.log('Initialized WorldRenderer with world:', world.name);
// Set up the canvas if the DOM is available
setupCanvas();
}
// Set up the canvas element
function setupCanvas() {
const mapCanvas = document.getElementById('world-map');
if (!mapCanvas) {
console.error('Map canvas element not found');
// Try to create it
const gameContainer = document.getElementById('game-container');
if (gameContainer) {
const canvas = document.createElement('canvas');
canvas.id = 'world-map';
canvas.width = canvasSize;
canvas.height = canvasSize;
canvas.style.border = '1px solid #ccc';
gameContainer.appendChild(canvas);
canvasContext = canvas.getContext('2d');
// Add event listeners for interaction
canvas.addEventListener('click', handleCanvasClick);
canvas.addEventListener('wheel', handleCanvasZoom);
console.log('Created map canvas element');
} else {
console.error('Game container element not found');
return false;
}
} else {
canvasContext = mapCanvas.getContext('2d');
// Add event listeners for interaction
mapCanvas.addEventListener('click', handleCanvasClick);
mapCanvas.addEventListener('wheel', handleCanvasZoom);
}
return true;
}
// Render the world to the canvas
function render(worldData) {
// Update world data if provided
if (worldData) {
world = worldData;
}
// Check if canvas is available
if (!canvasContext) {
if (!setupCanvas()) {
console.error('Unable to render world: canvas not available');
return;
}
}
// Clear the canvas
canvasContext.clearRect(0, 0, canvasSize, canvasSize);
// Draw the background
canvasContext.fillStyle = '#87CEEB'; // Sky blue
canvasContext.fillRect(0, 0, canvasSize, canvasSize);
// Calculate the center of the canvas
const canvasCenter = canvasSize / 2;
// Draw each region
for (const region of world.regions) {
// Skip if not discovered
if (!region.discovered) continue;
// Calculate position on canvas
const x = canvasCenter + (region.position.x - centerX) * tileSize;
const y = canvasCenter + (region.position.y - centerY) * tileSize;
// Check if the region is within the visible canvas
if (x + tileSize < 0 || x > canvasSize || y + tileSize < 0 || y > canvasSize) {
continue;
}
// Draw the region
drawRegion(region, x, y);
}
// Draw grid lines for better visual clarity
drawGrid();
// Draw a compass
drawCompass();
// Draw region names and features
drawLabels();
}
// Draw a single region on the canvas
function drawRegion(region, x, y) {
// Determine the region color based on terrain distribution
let primaryColor = '#7CFC00'; // Default light green
let secondaryColor = null;
let maxWeight = 0;
// Get the terrain types
const terrainTypes = WorldGenerator.getTerrainTypes();
// Find the primary terrain
for (const terrainType in region.terrainDistribution) {
const weight = region.terrainDistribution[terrainType];
if (weight > maxWeight) {
maxWeight = weight;
primaryColor = terrainTypes[terrainType]?.color || primaryColor;
// If there's a significant secondary terrain, save it for patterns
if (weight >= 0.4 && weight < 0.7) {
let secondaryWeight = 0;
for (const secondaryTerrain in region.terrainDistribution) {
if (secondaryTerrain !== terrainType &&
region.terrainDistribution[secondaryTerrain] > secondaryWeight) {
secondaryWeight = region.terrainDistribution[secondaryTerrain];
secondaryColor = terrainTypes[secondaryTerrain]?.color || null;
}
}
}
}
}
// Draw the base tile
canvasContext.fillStyle = primaryColor;
canvasContext.fillRect(x, y, tileSize, tileSize);
// Draw a pattern if there's a significant secondary terrain
if (secondaryColor) {
drawTerrainPattern(x, y, secondaryColor);
}
// Draw a border - thicker if explored
canvasContext.strokeStyle = region.explored ? '#000' : '#666';
canvasContext.lineWidth = region.explored ? 2 : 1;
canvasContext.strokeRect(x, y, tileSize, tileSize);
// Draw development indicators (buildings, resources)
if (region.explored) {
drawDevelopmentIndicators(region, x, y);
}
// Draw special feature indicators
if (region.specialFeatures && region.specialFeatures.length > 0) {
drawSpecialFeatures(region, x, y);
}
}
// Draw a pattern to represent mixed terrain
function drawTerrainPattern(x, y, color) {
canvasContext.fillStyle = color;
// Draw diagonal stripes
for (let i = 0; i < tileSize; i += 6) {
canvasContext.beginPath();
canvasContext.moveTo(x + i, y);
canvasContext.lineTo(x, y + i);
canvasContext.lineTo(x, y + i + 3);
canvasContext.lineTo(x + i + 3, y);
canvasContext.closePath();
canvasContext.fill();
}
}
// Draw indicators for region development
function drawDevelopmentIndicators(region, x, y) {
// Draw small icons for buildings
if (region.buildings && region.buildings.length > 0) {
const buildingCount = Math.min(region.buildings.length, 4);
for (let i = 0; i < buildingCount; i++) {
canvasContext.fillStyle = '#B22222'; // Firebrick red for buildings
canvasContext.beginPath();
canvasContext.arc(x + 10 + (i * 12), y + 10, 5, 0, 2 * Math.PI);
canvasContext.fill();
}
}
// Draw development level
if (region.developmentLevel > 0) {
canvasContext.fillStyle = '#FFD700'; // Gold
canvasContext.font = '12px Arial';
canvasContext.fillText(
'★'.repeat(region.developmentLevel),
x + 5,
y + tileSize - 5
);
}
// If it's the currently occupied/settled region, mark it
if (region.isSettled) {
canvasContext.strokeStyle = '#FF4500'; // Orange-red
canvasContext.lineWidth = 3;
canvasContext.strokeRect(x + 3, y + 3, tileSize - 6, tileSize - 6);
}
}
// Draw special feature indicators
// world-renderer.js
// Handles rendering the game world to the UI
import { WorldGenerator } from './world-generator.js';
// WorldRenderer - Visualizes the game world
const WorldRenderer = (function() {
// Private variables
let world = null;
let canvasContext = null;
let tileSize = 60;
let canvasSize = 600;
let centerX = 0;
let centerY = 0;
// Initialize with a world object
function init(worldData) {
world = worldData;
console.log('Initialized WorldRenderer with world:', world.name);
// Set up the canvas if the DOM is available
setupCanvas();
}
// Set up the canvas element
function setupCanvas() {
const mapCanvas = document.getElementById('world-map');
if (!mapCanvas) {
console.error('Map canvas element not found');
// Try to create it
const gameContainer = document.getElementById('game-container');
if (gameContainer) {
const canvas = document.createElement('canvas');
canvas.id = 'world-map';
canvas.width = canvasSize;
canvas.height = canvasSize;
canvas.style.border = '1px solid #ccc';
gameContainer.appendChild(canvas);
canvasContext = canvas.getContext('2d');
// Add event listeners for interaction
canvas.addEventListener('click', handleCanvasClick);
canvas.addEventListener('wheel', handleCanvasZoom);
console.log('Created map canvas element');
} else {
console.error('Game container element not found');
return false;
}
} else {
canvasContext = mapCanvas.getContext('2d');
// Add event listeners for interaction
mapCanvas.addEventListener('click', handleCanvasClick);
mapCanvas.addEventListener('wheel', handleCanvasZoom);
}
return true;
}
// Render the world to the canvas
function render(worldData) {
// Update world data if provided
if (worldData) {
world = worldData;
}
// Check if canvas is available
if (!canvasContext) {
if (!setupCanvas()) {
console.error('Unable to render world: canvas not available');
return;
}
}
// Clear the canvas
canvasContext.clearRect(0, 0, canvasSize, canvasSize);
// Draw the background
canvasContext.fillStyle = '#87CEEB'; // Sky blue
canvasContext.fillRect(0, 0, canvasSize, canvasSize);
// Calculate the center of the canvas
const canvasCenter = canvasSize / 2;
// Draw each region
for (const region of world.regions) {
// Skip if not discovered
if (!region.discovered) continue;
// Calculate position on canvas
const x = canvasCenter + (region.position.x - centerX) * tileSize;
const y = canvasCenter + (region.position.y - centerY) * tileSize;
// Check if the region is within the visible canvas
if (x + tileSize < 0 || x > canvasSize || y + tileSize < 0 || y > canvasSize) {
continue;
}
// Draw the region
drawRegion(region, x, y);
}
// Draw grid lines for better visual clarity
drawGrid();
// Draw a compass
drawCompass();
// Draw region names and features
drawLabels();
}
// Draw a single region on the canvas
function drawRegion(region, x, y) {
// Determine the region color based on terrain distribution
let primaryColor = '#7CFC00'; // Default light green
let secondaryColor = null;
let maxWeight = 0;
// Get the terrain types
const terrainTypes = WorldGenerator.getTerrainTypes();
// Find the primary terrain
for (const terrainType in region.terrainDistribution) {
const weight = region.terrainDistribution[terrainType];
if (weight > maxWeight) {
maxWeight = weight;
primaryColor = terrainTypes[terrainType]?.color || primaryColor;
// If there's a significant secondary terrain, save it for patterns
if (weight >= 0.4 && weight < 0.7) {
let secondaryWeight = 0;
for (const secondaryTerrain in region.terrainDistribution) {
if (secondaryTerrain !== terrainType &&
region.terrainDistribution[secondaryTerrain] > secondaryWeight) {
secondaryWeight = region.terrainDistribution[secondaryTerrain];
secondaryColor = terrainTypes[secondaryTerrain]?.color || null;
}
}
}
}
}
// Draw the base tile
canvasContext.fillStyle = primaryColor;
canvasContext.fillRect(x, y, tileSize, tileSize);
// Draw a pattern if there's a significant secondary terrain
if (secondaryColor) {
drawTerrainPattern(x, y, secondaryColor);
}
// Draw a border - thicker if explored
canvasContext.strokeStyle = region.explored ? '#000' : '#666';
canvasContext.lineWidth = region.explored ? 2 : 1;
canvasContext.strokeRect(x, y, tileSize, tileSize);
// Draw development indicators (buildings, resources)
if (region.explored) {
drawDevelopmentIndicators(region, x, y);
}
// Draw special feature indicators
function drawSpecialFeatures(region, x, y) {
// Only show discovered features, or a "?" for unexplored regions with undiscovered features
const discoveredFeatures = region.specialFeatures.filter(feature => feature.discovered);
if (discoveredFeatures.length > 0) {
// Draw icons for discovered features
discoveredFeatures.forEach((feature, index) => {
canvasContext.fillStyle = '#9932CC'; // Purple for special features
canvasContext.beginPath();
canvasContext.arc(x + tileSize - 10, y + 10 + (index * 12), 5, 0, 2 * Math.PI);
canvasContext.fill();
});
}
else if (!region.explored && region.specialFeatures.length > 0) {
// Draw a "?" for unexplored regions with features
canvasContext.fillStyle = '#DDA0DD'; // Light purple
canvasContext.font = '16px Arial Bold';
canvasContext.fillText('?', x + tileSize - 15, y + 20);
}
}
// Draw grid lines
function drawGrid() {
const canvasCenter = canvasSize / 2;
canvasContext.strokeStyle = '#AAA';
canvasContext.lineWidth = 0.5;
// Draw horizontal lines
for (let y = canvasCenter % tileSize; y < canvasSize; y += tileSize) {
canvasContext.beginPath();
canvasContext.moveTo(0, y);
canvasContext.lineTo(canvasSize, y);
canvasContext.stroke();
}
// Draw vertical lines
for (let x = canvasCenter % tileSize; x < canvasSize; x += tileSize) {
canvasContext.beginPath();
canvasContext.moveTo(x, 0);
canvasContext.lineTo(x, canvasSize);
canvasContext.stroke();
}
}
// Draw a compass in the corner
function drawCompass() {
const x = canvasSize - 60;
const y = canvasSize - 60;
const radius = 20;
// Draw compass circle
canvasContext.fillStyle = '#FFF';
canvasContext.beginPath();
canvasContext.arc(x, y, radius, 0, 2 * Math.PI);
canvasContext.fill();
canvasContext.strokeStyle = '#000';
canvasContext.lineWidth = 1;
canvasContext.beginPath();
canvasContext.arc(x, y, radius, 0, 2 * Math.PI);
canvasContext.stroke();
// Draw cardinal directions
canvasContext.fillStyle = '#000';
canvasContext.font = '12px Arial Bold';
canvasContext.textAlign = 'center';
canvasContext.textBaseline = 'middle';
// North
canvasContext.fillText('N', x, y - radius + 8);
// East
canvasContext.fillText('E', x + radius - 8, y);
// South
canvasContext.fillText('S', x, y + radius - 8);
// West
canvasContext.fillText('W', x - radius + 8, y);
}
// Draw region names and other labels
function drawLabels() {
const canvasCenter = canvasSize / 2;
for (const region of world.regions) {
// Skip if not discovered
if (!region.discovered) continue;
// Calculate position on canvas
const x = canvasCenter + (region.position.x - centerX) * tileSize;
const y = canvasCenter + (region.position.y - centerY) * tileSize;
// Check if the region is within the visible canvas
if (x + tileSize < 0 || x > canvasSize || y + tileSize < 0 || y > canvasSize) {
continue;
}
// Draw region name
canvasContext.fillStyle = '#000';
canvasContext.font = '10px Arial';
canvasContext.textAlign = 'center';
// Draw name in center of tile
const name = region.name;
const centerX = x + tileSize / 2;
const centerY = y + tileSize / 2;
// If the name is too long, split it
if (name.length > 15) {
const parts = name.split(' ');
const midpoint = Math.ceil(parts.length / 2);
const firstLine = parts.slice(0, midpoint).join(' ');
const secondLine = parts.slice(midpoint).join(' ');
canvasContext.fillText(firstLine, centerX, centerY - 5);
canvasContext.fillText(secondLine, centerX, centerY + 5);
} else {
canvasContext.fillText(name, centerX, centerY);
}
}
}
// Handle clicking on the canvas
function handleCanvasClick(event) {
// Get the click coordinates relative to the canvas
const rect = event.target.getBoundingClientRect();
const clickX = event.clientX - rect.left;
const clickY = event.clientY - rect.top;
// Convert to grid coordinates
const canvasCenter = canvasSize / 2;
const gridX = Math.floor((clickX - canvasCenter) / tileSize) + centerX;
const gridY = Math.floor((clickY - canvasCenter) / tileSize) + centerY;
// Find if there's a region at this location
const clickedRegion = world.regions.find(region =>
region.position.x === gridX &&
region.position.y === gridY &&
region.discovered
);
if (clickedRegion) {
// Dispatch a custom event with the region data
const clickEvent = new CustomEvent('region-clicked', {
detail: {
regionId: clickedRegion.id,
region: clickedRegion
}
});
document.dispatchEvent(clickEvent);
}
}
// Handle zooming with the mouse wheel
function handleCanvasZoom(event) {
event.preventDefault();
// Zoom in or out
if (event.deltaY < 0) {
// Zoom in
if (tileSize < 100) {
tileSize += 5;
}
} else {
// Zoom out
if (tileSize > 20) {
tileSize -= 5;
}
}
// Re-render
render();
}
// Pan the map view
function panMap(dx, dy) {
centerX += dx;
centerY += dy;
render();
}
// Center the map on a specific region
function centerOnRegion(regionId) {
const region = world.regions.find(r => r.id === regionId);
if (region) {
centerX = region.position.x;
centerY = region.position.y;
render();
return true;
}
return false;
}
// Export detailed region information for UI display
function getRegionDetails(regionId) {
const region = world.regions.find(r => r.id === regionId);
if (!region) {
return null;
}
// Get the terrain types
const terrainTypes = WorldGenerator.getTerrainTypes();
// Format terrain distribution for display
const terrainDetails = Object.entries(region.terrainDistribution)
.map(([terrain, weight]) => ({
name: terrainTypes[terrain]?.name || terrain,
percentage: Math.round(weight * 100),
color: terrainTypes[terrain]?.color || '#7CFC00'
}))
.sort((a, b) => b.percentage - a.percentage);
// Format resource modifiers
const resourceModifiers = Object.entries(region.resourceModifiers)
.map(([resource, modifier]) => ({
resource,
percentage: Math.round((modifier - 1) * 100)
}))
.filter(mod => mod.percentage !== 0);
// Prepare special features info
const featuresInfo = region.specialFeatures
.filter(feature => feature.discovered || region.explored)
.map(feature => ({
name: feature.name,
description: feature.description,
discovered: feature.discovered,
effects: feature.effects
}));
// Return the formatted details
return {
id: region.id,
name: region.name,
description: region.description,
position: region.position,
explored: region.explored,
developmentLevel: region.developmentLevel,
terrainDetails,
resourceModifiers,
resources: region.resources,
specialFeatures: featuresInfo,
buildings: region.buildings
};
}
// Return public API
return {
init,
render,
panMap,
centerOnRegion,
getRegionDetails
};
})();
export { WorldRenderer };
if (region.specialFeatures && region.specialFeatures.length > 0) {
drawSpecialFeatures(region, x, y);
}
}
// Draw a pattern to represent mixed terrain
function drawTerrainPattern(x, y, color) {
canvasContext.fillStyle = color;
// Draw diagonal stripes
for (let i = 0; i < tileSize; i += 6) {
canvasContext.beginPath();
canvasContext.moveTo(x + i, y);
canvasContext.lineTo(x, y + i);
canvasContext.lineTo(x, y + i + 3);
canvasContext.lineTo(x + i + 3, y);
canvasContext.closePath();
canvasContext.fill();
}
}
// Draw indicators for region development
function drawDevelopmentIndicators(region, x, y) {
// Draw small icons for buildings
if (region.buildings && region.buildings.length > 0) {
const buildingCount = Math.min(region.buildings.length, 4);
for (let i = 0; i < buildingCount; i++) {
canvasContext.fillStyle = '#B22222'; // Firebrick red for buildings
canvasContext.beginPath();
canvasContext.arc(x + 10 + (i * 12), y + 10, 5, 0, 2 * Math.PI);
canvasContext.fill();
}
}
// Draw development level
if (region.developmentLevel > 0) {
canvasContext.fillStyle = '#FFD700'; // Gold
canvasContext.font = '12px Arial';
canvasContext.fillText(
'★'.repeat(region.developmentLevel),
x + 5,
y + tileSize - 5
);
}
// If it's the currently occupied/settled region, mark it
if (region.isSettled) {
canvasContext.strokeStyle = '#FF4500'; // Orange-red
canvasContext.lineWidth = 3;
canvasContext.strokeRect(x + 3, y + 3, tileSize - 6, tileSize - 6);
}
}
// Draw special feature indicators
// building-manager.js
// Manages buildings that can be constructed in the game
import { ResourceManager } from './resources/resource-manager.js';
import { EventManager } from './events/event-manager.js';
// BuildingManager - Handles building-related functions
const BuildingManager = (function() {
// Available building types with their properties
const buildingTypes = {
residence: {
id: 'residence',
name: 'Residence',
description: 'Basic housing for characters',
category: 'housing',
constructionTime: 5,
difficulty: 1,
cost: {
materials: 20
},
effects: {
housing: 2
},
maintenance: {
materials: 1
},
requiredTech: null,
requiredLevel: 1,
terrainPreferences: ['plains', 'hills']
},
farm: {
id: 'farm',
name: 'Farm',
description: 'Produces food through agriculture',
category: 'production',
constructionTime: 8,
difficulty: 2,
cost: {
materials: 15
},
effects: {
food: 5
},
maintenance: {
materials: 1
},
requiredTech: 'agriculture',
requiredLevel: 1,
terrainPreferences: ['plains', 'river']
},
lumberMill: {
id: 'lumberMill',
name: 'Lumber Mill',
description: 'Harvests and processes wood',
category: 'production',
constructionTime: 10,
difficulty: 2,
cost: {
materials: 25,
knowledge: 5
},
effects: {
materials: 5
},
maintenance: {
materials: 2
},
requiredTech: 'woodworking',
requiredLevel: 2,
terrainPreferences: ['forest']
},
workshop: {
id: 'workshop',
name: 'Workshop',
description: 'Creates tools and implements',
category: 'production',
constructionTime: 12,
difficulty: 3,
cost: {
materials: 30,
knowledge: 10
},
effects: {
materials: 3,
knowledge: 1
},
maintenance: {
materials: 2
},
requiredTech: 'crafting',
requiredLevel: 2,
terrainPreferences: ['plains', 'hills']
},
mine: {
id: 'mine',
name: 'Mine',
description: 'Extracts minerals and stone',
category: 'production',
constructionTime: 15,
difficulty: 4,
cost: {
materials: 35,
knowledge: 15
},
effects: {
materials: 8
},
maintenance: {
materials: 3
},
requiredTech: 'mining',
requiredLevel: 3,
terrainPreferences: ['mountain', 'hills']
},
library: {
id: 'library',
name: 'Library',
description: 'Stores and organizes knowledge',
category: 'research',
constructionTime: 12,
difficulty: 3,
cost: {
materials: 25,
knowledge: 20
},
effects: {
knowledge: 5,
research_bonus: 0.1
},
maintenance: {
materials: 1
},
requiredTech: 'writing',
requiredLevel: 2,
terrainPreferences: ['plains', 'hills']
},
laboratory: {
id: 'laboratory',
name: 'Laboratory',
description: 'Facilitates scientific experiments',
category: 'research',
constructionTime: 15,
difficulty: 4,
cost: {
materials: 35,
knowledge: 40
},
effects: {
knowledge: 8,
research_bonus: 0.15
},
maintenance: {
materials: 2
},
requiredTech: 'scientific_method',
requiredLevel: 3,
terrainPreferences: ['plains', 'hills', 'mountain']
},
market: {
id: 'market',
name: 'Market',
description: 'Enables trade and resource exchange',
category: 'economy',
constructionTime: 10,
difficulty: 3,
cost: {
materials: 30,
knowledge: 10
},
effects: {
wealth: 5,
trade_efficiency: 0.1
},
maintenance: {
materials: 2
},
requiredTech: 'trade',
requiredLevel: 2,
terrainPreferences: ['plains', 'river']
},
watchtower: {
id: 'watchtower',
name: 'Watchtower',
description: 'Provides visibility and early warnings',
category: 'defense',
constructionTime: 8,
difficulty: 2,
cost: {
materials: 20,
knowledge: 5
},
effects: {
defense: 3,
visibility: 2
},
maintenance: {
materials: 1
},
requiredTech: null,
requiredLevel: 2,
terrainPreferences: ['hills', 'mountain']
},
fisherHut: {
id: 'fisherHut',
name: 'Fisher Hut',
description: 'Harvests fish from water bodies',
category: 'production',
constructionTime: 6,
difficulty: 1,
cost: {
materials: 15
},
effects: {
food: 6
},
maintenance: {
materials: 1
},
requiredTech: null,
requiredLevel: 1,
terrainPreferences: ['river', 'lake']
},
waterMill: {
id: 'waterMill',
name: 'Water Mill',
description: 'Uses water power for processing',
category: 'production',
constructionTime: 12,
difficulty: 3,
cost: {
materials: 25,
knowledge: 15
},
effects: {
food: 3,
materials: 3
},
maintenance: {
materials: 2
},
requiredTech: 'water_power',
requiredLevel: 2,
terrainPreferences: ['river']
},
well: {
id: 'well',
name: 'Well',
description: 'Provides clean water in arid regions',
category: 'infrastructure',
constructionTime: 8,
difficulty: 2,
cost: {
materials: 20
},
effects: {
food: 2,
health: 1
},
maintenance: {
materials: 1
},
requiredTech: null,
requiredLevel: 1,
terrainPreferences: ['plains', 'desert']
},
observatory: {
id: 'observatory',
name: 'Observatory',
description: 'Studies the stars and natural phenomena',
category: 'research',
constructionTime: 15,
difficulty: 4,
cost: {
materials: 30,
knowledge: 50
},
effects: {
knowledge: 10,
research_bonus: 0.2
},
maintenance: {
materials: 2
},
requiredTech: 'astronomy',
requiredLevel: 4,
terrainPreferences: ['mountain', 'hills']
}
};
// Internal state
let buildings = [];
let buildingsInProgress = [];
// Initialize with existing buildings
function init(existingBuildings) {
buildings = existingBuildings || [];
console.log(`Initialized BuildingManager with ${buildings.length} buildings`);
}
// Get all available buildings that can be constructed
function getAvailableBuildings(gameState) {
const availableBuildings = [];
const researchedTechs = gameState.technologies.map(tech => tech.id);
for (const buildingId in buildingTypes) {
const building = buildingTypes[buildingId];
// Check if the required technology is researched or not required
const techRequirementMet = !building.requiredTech || researchedTechs.includes(building.requiredTech);
// Check if the civilization level is high enough
const levelRequirementMet = gameState.world.developmentLevel >= building.requiredLevel;
// Check if resources are sufficient
const canAfford = ResourceManager.canAfford(building.cost);
if (techRequirementMet && levelRequirementMet) {
availableBuildings.push({
...building,
canAfford
});
}
}
return availableBuildings;
}
// Get details about a specific building type
function getBuildingDetails(buildingId) {
return buildingTypes[buildingId] || null;
}
// Start construction of a building
function constructBuilding(buildingId, gameState, regionId = null) {
const buildingType = buildingTypes[buildingId];
if (!buildingType) {
console.error(`Unknown building type: ${buildingId}`);
return false;
}
// Check if we can afford it
if (!ResourceManager.canAfford(buildingType.cost)) {
console.error(`Not enough resources to build ${buildingType.name}`);
return false;
}
// Spend the resources
if (!ResourceManager.spendResources(buildingType.cost)) {
console.error(`Failed to spend resources for ${buildingType.name}`);
return false;
}
// Determine where to build (default to first explored region if none specified)
let targetRegion = regionId;
if (!targetRegion) {
const exploredRegions = gameState.world.regions.filter(region => region.explored);
if (exploredRegions.length > 0) {
targetRegion = exploredRegions[0].id;
} else {
console.error('No explored regions to build in');
return false;
}
}
// Create the construction project
const constructionProject = {
id: `construction_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
buildingId,
buildingName: buildingType.name,
regionId: targetRegion,
progress: 0,
totalWork: buildingType.constructionTime,
startedTurn: gameState.turn
};
// Add to in-progress buildings
buildingsInProgress.push(constructionProject);
// Trigger an event
EventManager.addEvent({
type: 'construction',
message: `Construction of ${buildingType.name} has begun!`,
details: `Construction will take approximately ${buildingType.constructionTime} turns.`,
turn: gameState.turn
});
console.log(`Started construction of ${buildingType.name} in region ${targetRegion}`);
return constructionProject.id;
}
// Update building progress
function updateBuildings(gameState) {
// Process buildings under construction
buildingsInProgress = buildingsInProgress.filter(project => {
// Add progress (default 1 per turn)
// Could be modified by character abilities, technologies, etc.
project.progress += 1;
// Check if complete
if (project.progress >= project.totalWork) {
// Complete the building
completeBuilding(project, gameState);
return false; // Remove from in-progress list
}
return true; // Keep in the list
});
// Process existing buildings (maintenance, effects, etc.)
processExistingBuildings(gameState);
}
// Complete a building construction
function completeBuilding(project, gameState) {
const buildingType = buildingTypes[project.buildingId];
if (!buildingType) {
console.error(`Unknown building type: ${project.buildingId}`);
return false;
}
// Find the target region
const targetRegion = gameState.world.regions.find(region => region.id === project.regionId);
if (!targetRegion) {
console.error(`Target region ${project.regionId} not found`);
return false;
}
// Create the building instance
const building = {
id: `building_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
type: project.buildingId,
name: buildingType.name,
regionId: project.regionId,
built: gameState.turn,
condition: 100, // Max condition
efficiency: calculateEfficiency(buildingType, targetRegion),
workers: []
};
// Add to buildings list
buildings.push(building);
// Add to region buildings
if (!targetRegion.buildings) {
targetRegion.buildings = [];
}
targetRegion.buildings.push(building.id);
// Increase region development level
targetRegion.developmentLevel += 1;
// Trigger an event
EventManager.addEvent({
type: 'construction',
message: `${buildingType.name} construction complete!`,
details: `The ${buildingType.name} is now operational in ${targetRegion.name}.`,
turn: gameState.turn
});
console.log(`Completed construction of ${buildingType.name} in ${targetRegion.name}`);
return building;
}
// Calculate efficiency of a building based on terrain
function calculateEfficiency(buildingType, region) {
let efficiency = 1.0; // Base efficiency
// Check terrain preferences
if (buildingType.terrainPreferences && region.terrainDistribution) {
// Start with a penalty for non-preferred terrain
efficiency = 0.8;
// Calculate boost from preferred terrains
for (const terrain of buildingType.terrainPreferences) {
if (region.terrainDistribution[terrain]) {
efficiency += region.terrainDistribution[terrain] * 0.3;
}
}
}
// Apply region's resource modifiers if applicable
if (region.resourceModifiers) {
for (const resource in buildingType.effects) {
if (region.resourceModifiers[resource]) {
efficiency *= region.resourceModifiers[resource];
}
}
}
// Cap efficiency between 0.5 and 1.5
efficiency = Math.max(0.5, Math.min(1.5, efficiency));
return efficiency;
}
// Process existing buildings' effects and maintenance
function processExistingBuildings(gameState) {
for (const building of buildings) {
const buildingType = buildingTypes[building.type];
if (!buildingType) {
console.error(`Unknown building type: ${building.type}`);
continue;
}
// Apply maintenance costs
if (buildingType.maintenance) {
for (const resource in buildingType.maintenance) {
ResourceManager.removeResource(resource, buildingType.maintenance[resource]);
}
}
// Apply building effects (adjust resource production)
if (buildingType.effects) {
for (const resource in buildingType.effects) {
// Special handling for non-resource effects
if (resource === 'research_bonus' ||
resource === 'trade_efficiency' ||
resource === 'defense' ||
resource === 'visibility' ||
resource === 'housing' ||
resource === 'health') {
// These are handled elsewhere
continue;
}
// Calculate production with efficiency
const production = buildingType.effects[resource] * building.efficiency;
ResourceManager.addResource(resource, production);
}
}
// Deteriorate building condition slightly
building.condition -= 0.2;
// If condition drops below thresholds, reduce efficiency
if (building.condition < 75) {
building.efficiency = calculateEfficiency(buildingType,
gameState.world.regions.find(r => r.id === building.regionId)) * (building.condition / 100);
}
// If condition drops too low, generate a maintenance event
if (building.condition < 50 && Math.random() < 0.2) {
EventManager.addEvent({
type: 'building',
message: `${building.name} requires maintenance`,
details: `The ${building.name} is deteriorating and requires repairs.`,
turn: gameState.turn
});
}
}
}
// Repair a building to restore condition
function repairBuilding(buildingId) {
const building = buildings.find(b => b.id === buildingId);
if (!building) {
console.error(`Building ${buildingId} not found`);
return false;
}
const buildingType = buildingTypes[building.type];
if (!buildingType) {
console.error(`Unknown building type: ${building.type}`);
return false;
}
// Calculate repair cost (25% of original construction cost)
const repairCost = {};
for (const resource in buildingType.cost) {
repairCost[resource] = Math.ceil(buildingType.cost[resource] * 0.25);
}
// Check if we can afford it
if (!ResourceManager.canAfford(repairCost)) {
console.error(`Not enough resources to repair ${building.name}`);
return false;
}
// Spend the resources
if (!ResourceManager.spendResources(repairCost)) {
console.error(`Failed to spend resources for ${building.name} repair`);
return false;
}
// Restore condition
building.condition = 100;
// Recalculate efficiency
const gameState = window.AICivilizationGame?.getGameState();
if (gameState) {
const region = gameState.world.regions.find(r => r.id === building.regionId);
if (region) {
building.efficiency = calculateEfficiency(buildingType, region);
}
}
// Trigger an event
EventManager.addEvent({
type: 'building',
message: `${building.name} has been repaired`,
details: `The ${building.name} is now in optimal condition.`,
turn: gameState?.turn || 0
});
console.log(`Repaired ${building.name}`);
return true;
}
// Get all buildings in a specific region
function getBuildingsInRegion(regionId) {
return buildings.filter(building => building.regionId === regionId);
}
// Analyze building needs based on current game state
function analyzeNeeds(world) {
const needs = {
urgent: false,
recommended: null,
reasons: []
};
// Count building types
const buildingCounts = {};
for (const building of buildings) {
buildingCounts[building.type] = (buildingCounts[building.type] || 0) + 1;
}
// Check for essential building types
if (!buildingCounts.residence || buildingCounts.residence < 2) {
needs.urgent = true;
needs.recommended = 'residence';
needs.reasons.push('Housing shortage');
} else if (!buildingCounts.farm && !buildingCounts.fisherHut) {
needs.urgent = true;
needs.recommended = 'farm';
needs.reasons.push('Food production needed');
} else if (!buildingCounts.lumberMill && world.developmentLevel >= 2) {
needs.recommended = 'lumberMill';
needs.reasons.push('Material production needed');
} else if (!buildingCounts.library && world.developmentLevel >= 2) {
needs.recommended = 'library';
needs.reasons.push('Knowledge production needed');
} else if (!buildingCounts.workshop && world.developmentLevel >= 2) {
needs.recommended = 'workshop';
needs.reasons.push('Craft production needed');
} else if (!buildingCounts.market && world.developmentLevel >= 3) {
needs.recommended = 'market';
needs.reasons.push('Economic development needed');
}
return needs;
}
// Return public API
return {
init,
getAvailableBuildings,
getBuildingDetails,
constructBuilding,
updateBuildings,
repairBuilding,
getBuildingsInRegion,
analyzeNeeds,
getBuildings: () => buildings
};
})();
export { BuildingManager };
// world-renderer.js
// Handles rendering the game world to the UI
import { WorldGenerator } from './world-generator.js';
// WorldRenderer - Visualizes the game world
const WorldRenderer = (function() {
// Private variables
let world = null;
let canvasContext = null;
let tileSize = 60;
let canvasSize = 600;
let centerX = 0;
let centerY = 0;
// Initialize with a world object
function init(worldData) {
world = worldData;
console.log('Initialized WorldRenderer with world:', world.name);
// Set up the canvas if the DOM is available
setupCanvas();
}
// Set up the canvas element
function setupCanvas() {
const mapCanvas = document.getElementById('world-map');
if (!mapCanvas) {
console.error('Map canvas element not found');
// Try to create it
const gameContainer = document.getElementById('game-container');
if (gameContainer) {
const canvas = document.createElement('canvas');
canvas.id = 'world-map';
canvas.width = canvasSize;
canvas.height = canvasSize;
canvas.style.border = '1px solid #ccc';
gameContainer.appendChild(canvas);
canvasContext = canvas.getContext('2d');
// Add event listeners for interaction
canvas.addEventListener('click', handleCanvasClick);
canvas.addEventListener('wheel', handleCanvasZoom);
console.log('Created map canvas element');
} else {
console.error('Game container element not found');
return false;
}
} else {
canvasContext = mapCanvas.getContext('2d');
// Add event listeners for interaction
mapCanvas.addEventListener('click', handleCanvasClick);
mapCanvas.addEventListener('wheel', handleCanvasZoom);
}
return true;
}
// Render the world to the canvas
function render(worldData) {
// Update world data if provided
if (worldData) {
world = worldData;
}
// Check if canvas is available
if (!canvasContext) {
if (!setupCanvas()) {
console.error('Unable to render world: canvas not available');
return;
}
}
// Clear the canvas
canvasContext.clearRect(0, 0, canvasSize, canvasSize);
// Draw the background
canvasContext.fillStyle = '#87CEEB'; // Sky blue
canvasContext.fillRect(0, 0, canvasSize, canvasSize);
// Calculate the center of the canvas
const canvasCenter = canvasSize / 2;
// Draw each region
for (const region of world.regions) {
// Skip if not discovered
if (!region.discovered) continue;
// Calculate position on canvas
const x = canvasCenter + (region.position.x - centerX) * tileSize;
const y = canvasCenter + (region.position.y - centerY) * tileSize;
// Check if the region is within the visible canvas
if (x + tileSize < 0 || x > canvasSize || y + tileSize < 0 || y > canvasSize) {
continue;
}
// Draw the region
drawRegion(region, x, y);
}
// Draw grid lines for better visual clarity
drawGrid();
// Draw a compass
drawCompass();
// Draw region names and features
drawLabels();
}
// Draw a single region on the canvas
function drawRegion(region, x, y) {
// Determine the region color based on terrain distribution
let primaryColor = '#7CFC00'; // Default light green
let secondaryColor = null;
let maxWeight = 0;
// Get the terrain types
const terrainTypes = WorldGenerator.getTerrainTypes();
// Find the primary terrain
for (const terrainType in region.terrainDistribution) {
const weight = region.terrainDistribution[terrainType];
if (weight > maxWeight) {
maxWeight = weight;
primaryColor = terrainTypes[terrainType]?.color || primaryColor;
// If there's a significant secondary terrain, save it for patterns
if (weight >= 0.4 && weight < 0.7) {
let secondaryWeight = 0;
for (const secondaryTerrain in region.terrainDistribution) {
if (secondaryTerrain !== terrainType &&
region.terrainDistribution[secondaryTerrain] > secondaryWeight) {
secondaryWeight = region.terrainDistribution[secondaryTerrain];
secondaryColor = terrainTypes[secondaryTerrain]?.color || null;
}
}
}
}
}
// Draw the base tile
canvasContext.fillStyle = primaryColor;
canvasContext.fillRect(x, y, tileSize, tileSize);
// Draw a pattern if there's a significant secondary terrain
if (secondaryColor) {
drawTerrainPattern(x, y, secondaryColor);
}
// Draw a border - thicker if explored
canvasContext.strokeStyle = region.explored ? '#000' : '#666';
canvasContext.lineWidth = region.explored ? 2 : 1;
canvasContext.strokeRect(x, y, tileSize, tileSize);
// Draw development indicators (buildings, resources)
if (region.explored) {
drawDevelopmentIndicators(region, x, y);
}
// Draw special feature indicators
if (region.specialFeatures && region.specialFeatures.length > 0) {
drawSpecialFeatures(region, x, y);
}
}
// Draw a pattern to represent mixed terrain
function drawTerrainPattern(x, y, color) {
canvasContext.fillStyle = color;
// Draw diagonal stripes
for (let i = 0; i < tileSize; i += 6) {
canvasContext.beginPath();
canvasContext.moveTo(x + i, y);
canvasContext.lineTo(x, y + i);
canvasContext.lineTo(x, y + i + 3);
canvasContext.lineTo(x + i + 3, y);
canvasContext.closePath();
canvasContext.fill();
}
}
// Draw indicators for region development
function drawDevelopmentIndicators(region, x, y) {
// Draw small icons for buildings
if (region.buildings && region.buildings.length > 0) {
const buildingCount = Math.min(region.buildings.length, 4);
for (let i = 0; i < buildingCount; i++) {
canvasContext.fillStyle = '#B22222'; // Firebrick red for buildings
canvasContext.beginPath();
canvasContext.arc(x + 10 + (i * 12), y + 10, 5, 0, 2 * Math.PI);
canvasContext.fill();
}
}
// Draw development level
if (region.developmentLevel > 0) {
canvasContext.fillStyle = '#FFD700'; // Gold
canvasContext.font = '12px Arial';
canvasContext.fillText(
'★'.repeat(region.developmentLevel),
x + 5,
y + tileSize - 5
);
}
// If it's the currently occupied/settled region, mark it
if (region.isSettled) {
canvasContext.strokeStyle = '#FF4500'; // Orange-red
canvasContext.lineWidth = 3;
canvasContext.strokeRect(x + 3, y + 3, tileSize - 6, tileSize - 6);
}
}
// Draw special feature indicators
function drawSpecialFeatures(region, x, y) {
// Only show discovered features, or a "?" for unexplored regions with undiscovered features
const discoveredFeatures = region.specialFeatures.filter(feature => feature.discovered);
if (discoveredFeatures.length > 0) {
// Draw icons for discovered features
discoveredFeatures.forEach((feature, index) => {
canvasContext.fillStyle = '#9932CC'; // Purple for special features
canvasContext.beginPath();
canvasContext.arc(x + tileSize - 10, y + 10 + (index * 12), 5, 0, 2 * Math.PI);
canvasContext.fill();
});
}
else if (!region.explored && region.specialFeatures.length > 0) {
// Draw a "?" for unexplored regions with features
canvasContext.fillStyle = '#DDA0DD'; // Light purple
canvasContext.font = '16px Arial Bold';
canvasContext.fillText('?', x + tileSize - 15, y + 20);
}
}
// Draw grid lines
function drawGrid() {
const canvasCenter = canvasSize / 2;
canvasContext.strokeStyle = '#AAA';
canvasContext.lineWidth = 0.5;
// Draw horizontal lines
for (let y = canvasCenter % tileSize; y < canvasSize; y += tileSize) {
canvasContext.beginPath();
canvasContext.moveTo(0, y);
canvasContext.lineTo(canvasSize, y);
canvasContext.stroke();
}
// Draw vertical lines
for (let x = canvasCenter % tileSize; x < canvasSize; x += tileSize) {
canvasContext.beginPath();
canvasContext.moveTo(x, 0);
canvasContext.lineTo(x, canvasSize);
canvasContext.stroke();
}
}
// Draw a compass in the corner
function drawCompass() {
const x = canvasSize - 60;
const y = canvasSize - 60;
const radius = 20;
// Draw compass circle
canvasContext.fillStyle = '#FFF';
canvasContext.beginPath();
canvasContext.arc(x, y, radius, 0, 2 * Math.PI);
canvasContext.fill();
canvasContext.strokeStyle = '#000';
canvasContext.lineWidth = 1;
canvasContext.beginPath();
canvasContext.arc(x, y, radius, 0, 2 * Math.PI);
canvasContext.stroke();
// Draw cardinal directions
canvasContext.fillStyle = '#000';
canvasContext.font = '12px Arial Bold';
canvasContext.textAlign = 'center';
canvasContext.textBaseline = 'middle';
// North
canvasContext.fillText('N', x, y - radius + 8);
// East
canvasContext.fillText('E', x + radius - 8, y);
// South
canvasContext.fillText('S', x, y + radius - 8);
// West
canvasContext.fillText('W', x - radius + 8, y);
}
// Draw region names and other labels
function drawLabels() {
const canvasCenter = canvasSize / 2;
for (const region of world.regions) {
// Skip if not discovered
if (!region.discovered) continue;
// Calculate position on canvas
const x = canvasCenter + (region.position.x - centerX) * tileSize;
const y = canvasCenter + (region.position.y - centerY) * tileSize;
// Check if the region is within the visible canvas
if (x + tileSize < 0 || x > canvasSize || y + tileSize < 0 || y > canvasSize) {
continue;
}
// Draw region name
canvasContext.fillStyle = '#000';
canvasContext.font = '10px Arial';
canvasContext.textAlign = 'center';
// Draw name in center of tile
const name = region.name;
const centerX = x + tileSize / 2;
const centerY = y + tileSize / 2;
// If the name is too long, split it
if (name.length > 15) {
const parts = name.split(' ');
const midpoint = Math.ceil(parts.length / 2);
const firstLine = parts.slice(0, midpoint).join(' ');
const secondLine = parts.slice(midpoint).join(' ');
canvasContext.fillText(firstLine, centerX, centerY - 5);
canvasContext.fillText(secondLine, centerX, centerY + 5);
} else {
canvasContext.fillText(name, centerX, centerY);
}
}
}
// Handle clicking on the canvas
function handleCanvasClick(event) {
// Get the click coordinates relative to the canvas
const rect = event.target.getBoundingClientRect();
const clickX = event.clientX - rect.left;
const clickY = event.clientY - rect.top;
// Convert to grid coordinates
const canvasCenter = canvasSize / 2;
const gridX = Math.floor((clickX - canvasCenter) / tileSize) + centerX;
const gridY = Math.floor((clickY - canvasCenter) / tileSize) + centerY;
// Find if there's a region at this location
const clickedRegion = world.regions.find(region =>
region.position.x === gridX &&
region.position.y === gridY &&
region.discovered
);
if (clickedRegion) {
// Dispatch a custom event with the region data
const clickEvent = new CustomEvent('region-clicked', {
detail: {
regionId: clickedRegion.id,
region: clickedRegion
}
});
document.dispatchEvent(clickEvent);
}
}
// Handle zooming with the mouse wheel
function handleCanvasZoom(event) {
event.preventDefault();
// Zoom in or out
if (event.deltaY < 0) {
// Zoom in
if (tileSize < 100) {
tileSize += 5;
}
} else {
// Zoom out
if (tileSize > 20) {
tileSize -= 5;
}
}
// Re-render
render();
}
// Pan the map view
function panMap(dx, dy) {
centerX += dx;
centerY += dy;
render();
}
// Center the map on a specific region
function centerOnRegion(regionId) {
const region = world.regions.find(r => r.id === regionId);
if (region) {
centerX = region.position.x;
centerY = region.position.y;
render();
return true;
}
return false;
}
// Export detailed region information for UI display
function getRegionDetails(regionId) {
const region = world.regions.find(r => r.id === regionId);
if (!region) {
return null;
}
// Get the terrain types
const terrainTypes = WorldGenerator.getTerrainTypes();
// Format terrain distribution for display
const terrainDetails = Object.entries(region.terrainDistribution)
.map(([terrain, weight]) => ({
name: terrainTypes[terrain]?.name || terrain,
percentage: Math.round(weight * 100),
color: terrainTypes[terrain]?.color || '#7CFC00'
}))
.sort((a, b) => b.percentage - a.percentage);
// Format resource modifiers
const resourceModifiers = Object.entries(region.resourceModifiers)
.map(([resource, modifier]) => ({
resource,
percentage: Math.round((modifier - 1) * 100)
}))
.filter(mod => mod.percentage !== 0);
// Prepare special features info
const featuresInfo = region.specialFeatures
.filter(feature => feature.discovered || region.explored)
.map(feature => ({
name: feature.name,
description: feature.description,
discovered: feature.discovered,
effects: feature.effects
}));
// Return the formatted details
return {
id: region.id,
name: region.name,
description: region.description,
position: region.position,
explored: region.explored,
developmentLevel: region.developmentLevel,
terrainDetails,
resourceModifiers,
resources: region.resources,
specialFeatures: featuresInfo,
buildings: region.buildings
};
}
// Return public API
return {
init,
render,
panMap,
centerOnRegion,
getRegionDetails
};
})();
export { WorldRenderer };
// technology-manager.js
// Manages research, technologies, and advancements
import { ResourceManager } from './resources/resource-manager.js';
import { EventManager } from './events/event-manager.js';
// TechnologyManager - Handles technology research and advancements
const TechnologyManager = (function() {
// Technology definitions
const technologies = {
agriculture: {
id: 'agriculture',
name: 'Agriculture',
description: 'Organized farming techniques for consistent food production',
category: 'production',
tier: 1,
researchTime: 10,
difficulty: 2,
cost: {
knowledge: 20
},
prerequisites: [],
effects: {
food_production: 0.2,
enables: ['farm']
}
},
woodworking: {
id: 'woodworking',
name: 'Woodworking',
description: 'Techniques for processing and using wood for construction',
category: 'production',
tier: 1,
researchTime: 12,
difficulty: 2,
cost: {
knowledge: 25
},
prerequisites: [],
effects: {
material_production: 0.15,
enables: ['lumberMill']
}
},
writing: {
id: 'writing',
name: 'Writing',
description: 'Recording and sharing knowledge through written language',
category: 'knowledge',
tier: 1,
researchTime: 15,
difficulty: 3,
cost: {
knowledge: 30
},
prerequisites: [],
effects: {
knowledge_production: 0.2,
enables: ['library']
}
},
trade: {
id: 'trade',
name: 'Trade',
description: 'Organized exchange of goods and services',
category: 'economy',
tier: 1,
researchTime: 12,
difficulty: 2,
cost: {
knowledge: 20
},
prerequisites: [],
effects: {
resource_efficiency: 0.1,
enables: ['market']
}
},
crafting: {
id: 'crafting',
name: 'Crafting',
description: 'Creating tools and implements from materials',
category: 'production',
tier: 1,
researchTime: 10,
difficulty: 2,
cost: {
knowledge: 20
},
prerequisites: ['woodworking'],
effects: {
tool_quality: 0.15,
enables: ['workshop']
}
},
mining: {
id: 'mining',
name: 'Mining',
description: 'Extracting minerals and metals from the earth',
category: 'production',
tier: 2,
researchTime: 18,
difficulty: 3,
cost: {
knowledge: 40,
materials: 10
},
prerequisites: ['crafting'],
effects: {
material_production: 0.2,
enables: ['mine']
}
},
water_power: {
id: 'water_power',
name: 'Water Power',
description: 'Harnessing flowing water for mechanical energy',
category: 'energy',
tier: 2,
researchTime: 15,
difficulty: 3,
cost: {
knowledge: 35,
materials: 15
},
prerequisites: ['crafting'],
effects: {
production_efficiency: 0.15,
enables: ['waterMill']
}
},
scientific_method: {
id: 'scientific_method',
name: 'Scientific Method',
description: 'Structured approach to observation and experimentation',
category: 'knowledge',
tier: 2,
researchTime: 20,
difficulty: 4,
cost: {
knowledge: 50
},
prerequisites: ['writing'],
effects: {
research_speed: 0.2,
enables: ['laboratory']
}
},
metallurgy: {
id: 'metallurgy',
name: 'Metallurgy',
description: 'Working with metals to create tools and structures',
category: 'production',
tier: 2,
researchTime: 22,
difficulty: 4,
cost: {
knowledge: 45,
materials: 20
},
prerequisites: ['mining'],
effects: {
tool_quality: 0.25,
building_durability: 0.2
}
},
astronomy: {
id: 'astronomy',
name: 'Astronomy',
description: 'Study of celestial objects and phenomena',
category: 'knowledge',
tier: 3,
researchTime: 25,
difficulty: 4,
cost: {
knowledge: 60
},
prerequisites: ['scientific_method'],
effects: {
knowledge_production: 0.2,
enables: ['observatory']
}
},
construction: {
id: 'construction',
name: 'Advanced Construction',
description: 'Techniques for building larger and more complex structures',
category: 'infrastructure',
tier: 2,
researchTime: 20,
difficulty: 3,
cost: {
knowledge: 40,
materials: 25
},
prerequisites: ['woodworking'],
effects: {
building_efficiency: 0.15,
building_durability: 0.1
}
},
irrigation: {
id: 'irrigation',
name: 'Irrigation',
description: 'Artificial water supply for plants and crops',
category: 'production',
tier: 2,
researchTime: 15,
difficulty: 3,
cost: {
knowledge: 35,
materials: 15
},
prerequisites: ['agriculture', 'water_power'],
effects: {
food_production: 0.25
}
},
animal_husbandry: {
id: 'animal_husbandry',
name: 'Animal Husbandry',
description: 'Breeding and care of domestic animals',
category: 'production',
tier: 2,
researchTime: 18,
difficulty: 3,
cost: {
knowledge: 30,
food: 20
},
prerequisites: ['agriculture'],
effects: {
food_production: 0.2,
transportation_efficiency: 0.1
}
},
medicine: {
id: 'medicine',
name: 'Medicine',
description: 'Treatment and prevention of illness',
category: 'health',
tier: 3,
researchTime: 25,
difficulty: 4,
cost: {
knowledge: 50,
food: 15
},
prerequisites: ['scientific_method'],
effects: {
character_health: 0.2,
population_growth: 0.1
}
},
mechanics: {
id: 'mechanics',
name: 'Mechanics',
description: 'Understanding of forces and motion',
category: 'engineering',
tier: 3,
researchTime: 22,
difficulty: 4,
cost: {
knowledge: 55,
materials: 25
},
prerequisites: ['scientific_method', 'metallurgy'],
effects: {
construction_efficiency: 0.2,
tool_quality: 0.15
}
},
philosophy: {
id: 'philosophy',
name: 'Philosophy',
description: 'Systematic study of fundamental questions',
category: 'knowledge',
tier: 3,
researchTime: 20,
difficulty: 3,
cost: {
knowledge: 45
},
prerequisites: ['writing'],
effects: {
knowledge_production: 0.15,
character_wisdom: 0.1
}
},
education: {
id: 'education',
name: 'Education',
description: 'Formal system of learning and teaching',
category: 'knowledge',
tier: 3,
researchTime: 25,
difficulty: 4,
cost: {
knowledge: 60,
materials: 20
},
prerequisites: ['writing', 'philosophy'],
effects: {
knowledge_production: 0.2,
character_intelligence: 0.15
}
}
};
// Internal state
let researchedTechnologies = [];
let currentResearch = null;
let researchProgress = 0;
let researchBonus = 1.0; // Multiplier for research speed
// Initialize with existing research data
function init(existingTechnologies) {
researchedTechnologies = existingTechnologies || [];
console.log(`Initialized TechnologyManager with ${researchedTechnologies.length} technologies`);
}
// Get available technologies that can be researched
function getAvailableTechnologies(gameState) {
const available = [];
const researchedIds = researchedTechnologies.map(tech => tech.id);
// For each technology
for (const techId in technologies) {
const tech = technologies[techId];
// Skip if already researched
if (researchedIds.includes(techId)) {
continue;
}
// Check if prerequisites are met
const prereqsMet = tech.prerequisites.every(prereq => researchedIds.includes(prereq));
if (!prereqsMet) {
continue;
}
// Check if tier is appropriate for current development level
if (tech.tier > gameState.world.developmentLevel) {
continue;
}
// Check if resources are sufficient
const canAfford = ResourceManager.canAfford(tech.cost);
// Add to available list with affordability flag
available.push({
...tech,
canAfford
});
}
return available;
}
// Start researching a technology
function researchTechnology(techId, gameState) {
// Check if already researching something
if (currentResearch) {
console.error(`Already researching ${currentResearch.name}`);
return false;
}
const tech = technologies[techId];
if (!tech) {
console.error(`Unknown technology: ${techId}`);
return false;
}
// Verify that prerequisites are met
const researchedIds = researchedTechnologies.map(t => t.id);
const prereqsMet = tech.prerequisites.every(prereq => researchedIds.includes(prereq));
if (!prereqsMet) {
console.error(`Prerequisites not met for ${tech.name}`);
return false;
}
// Check if we can afford it
if (!ResourceManager.canAfford(tech.cost)) {
console.error(`Not enough resources to research ${tech.name}`);
return false;
}
// Spend the resources
if (!ResourceManager.spendResources(tech.cost)) {
console.error(`Failed to spend resources for ${tech.name}`);
return false;
}
// Start the research
currentResearch = {
...tech,
startedTurn: gameState.turn
};
researchProgress = 0;
// Trigger an event
EventManager.addEvent({
type: 'research',
message: `Research on ${tech.name} has begun!`,
details: `This technology will take approximately ${tech.researchTime} turns to complete.`,
turn: gameState.turn
});
console.log(`Started research on ${tech.name}`);
return true;
}
// Update research progress
function updateTechnologies(gameState) {
// If researching something, update progress
if (currentResearch) {
// Base progress is 1 per turn
// Modified by research bonus from buildings, characters, etc.
const progressIncrement = 1 * researchBonus;
researchProgress += progressIncrement;
// Check if research is complete
if (researchProgress >= currentResearch.researchTime) {
completeResearch(gameState);
}
}
// Apply technology effects
applyTechnologyEffects();
}
// Complete the current research
function completeResearch(gameState) {
if (!currentResearch) {
console.error('No current research to complete');
return false;
}
// Add to researched technologies
researchedTechnologies.push({
id: currentResearch.id,
name: currentResearch.name,
completedTurn: gameState.turn,
effects: currentResearch.effects
});
// Apply immediate effects
applyTechnologyEffects(currentResearch);
// Trigger an event
EventManager.addEvent({
type: 'research',
message: `Research on ${currentResearch.name} is complete!`,
details: getEffectsDescription(currentResearch.effects),
turn: gameState.turn
});
// Clear current research
const completedResearch = currentResearch;
currentResearch
// technology-manager.js
// Manages research, technologies, and advancements
import { ResourceManager } from './resources/resource-manager.js';
import { EventManager } from './events/event-manager.js';
// TechnologyManager - Handles technology research and advancements
const TechnologyManager = (function() {
// Technology definitions
const technologies = {
agriculture: {
id: 'agriculture',
name: 'Agriculture',
description: 'Organized farming techniques for consistent food production',
category: 'production',
tier: 1,
researchTime: 10,
difficulty: 2,
cost: {
knowledge: 20
},
prerequisites: [],
effects: {
food_production: 0.2,
enables: ['farm']
}
},
woodworking: {
id: 'woodworking',
name: 'Woodworking',
description: 'Techniques for processing and using wood for construction',
category: 'production',
tier: 1,
researchTime: 12,
difficulty: 2,
cost: {
knowledge: 25
},
prerequisites: [],
effects: {
material_production: 0.15,
enables: ['lumberMill']
}
},
writing: {
id: 'writing',
name: 'Writing',
description: 'Recording and sharing knowledge through written language',
category: 'knowledge',
tier: 1,
researchTime: 15,
difficulty: 3,
cost: {
knowledge: 30
},
prerequisites: [],
effects: {
knowledge_production: 0.2,
enables: ['library']
}
},
trade: {
id: 'trade',
name: 'Trade',
description: 'Organized exchange of goods and services',
category: 'economy',
tier: 1,
researchTime: 12,
difficulty: 2,
cost: {
knowledge: 20
},
prerequisites: [],
effects: {
resource_efficiency: 0.1,
enables: ['market']
}
},
crafting: {
id: 'crafting',
name: 'Crafting',
description: 'Creating tools and implements from materials',
category: 'production',
tier: 1,
researchTime: 10,
difficulty: 2,
cost: {
knowledge: 20
},
prerequisites: ['woodworking'],
effects: {
tool_quality: 0.15,
enables: ['workshop']
}
},
mining: {
id: 'mining',
name: 'Mining',
description: 'Extracting minerals and metals from the earth',
category: 'production',
tier: 2,
researchTime: 18,
difficulty: 3,
cost: {
knowledge: 40,
materials: 10
},
prerequisites: ['crafting'],
effects: {
material_production: 0.2,
enables: ['mine']
}
},
water_power: {
id: 'water_power',
name: 'Water Power',
description: 'Harnessing flowing water for mechanical energy',
category: 'energy',
tier: 2,
researchTime: 15,
difficulty: 3,
cost: {
knowledge: 35,
materials: 15
},
prerequisites: ['crafting'],
effects: {
production_efficiency: 0.15,
enables: ['waterMill']
}
},
scientific_method: {
id: 'scientific_method',
name: 'Scientific Method',
description: 'Structured approach to observation and experimentation',
category: 'knowledge',
tier: 2,
researchTime: 20,
difficulty: 4,
cost: {
knowledge: 50
},
prerequisites: ['writing'],
effects: {
research_speed: 0.2,
enables: ['laboratory']
}
},
metallurgy: {
id: 'metallurgy',
name: 'Metallurgy',
description: 'Working with metals to create tools and structures',
category: 'production',
tier: 2,
researchTime: 22,
difficulty: 4,
cost: {
knowledge: 45,
materials: 20
},
prerequisites: ['mining'],
effects: {
tool_quality: 0.25,
building_durability: 0.2
}
},
astronomy: {
id: 'astronomy',
name: 'Astronomy',
description: 'Study of celestial objects and phenomena',
category: 'knowledge',
tier: 3,
researchTime: 25,
difficulty: 4,
cost: {
knowledge: 60
},
prerequisites: ['scientific_method'],
effects: {
knowledge_production: 0.2,
enables: ['observatory']
}
},
construction: {
id: 'construction',
name: 'Advanced Construction',
description: 'Techniques for building larger and more complex structures',
category: 'infrastructure',
tier: 2,
researchTime: 20,
difficulty: 3,
cost: {
knowledge: 40,
materials: 25
},
prerequisites: ['woodworking'],
effects: {
building_efficiency: 0.15,
building_durability: 0.1
}
},
irrigation: {
id: 'irrigation',
name: 'Irrigation',
description: 'Artificial water supply for plants and crops',
category: 'production',
tier: 2,
researchTime: 15,
difficulty: 3,
cost: {
knowledge: 35,
materials: 15
},
prerequisites: ['agriculture', 'water_power'],
effects: {
food_production: 0.25
}
},
animal_husbandry: {
id: 'animal_husbandry',
name: 'Animal Husbandry',
description: 'Breeding and care of domestic animals',
category: 'production',
tier: 2,
researchTime: 18,
difficulty: 3,
cost: {
knowledge: 30,
food: 20
},
prerequisites: ['agriculture'],
effects: {
food_production: 0.2,
transportation_efficiency: 0.1
}
},
medicine: {
id: 'medicine',
name: 'Medicine',
description: 'Treatment and prevention of illness',
category: 'health',
tier: 3,
researchTime: 25,
difficulty: 4,
cost: {
knowledge: 50,
food: 15
},
prerequisites: ['scientific_method'],
effects: {
character_health: 0.2,
population_growth: 0.1
}
},
mechanics: {
id: 'mechanics',
name: 'Mechanics',
description: 'Understanding of forces and motion',
category: 'engineering',
tier: 3,
researchTime: 22,
difficulty: 4,
cost: {
knowledge: 55,
materials: 25
},
prerequisites: ['scientific_method', 'metallurgy'],
effects: {
construction_efficiency: 0.2,
tool_quality: 0.15
}
},
philosophy: {
id: 'philosophy',
name: 'Philosophy',
description: 'Systematic study of fundamental questions',
category: 'knowledge',
tier: 3,
researchTime: 20,
difficulty: 3,
cost: {
knowledge: 45
},
prerequisites: ['writing'],
effects: {
knowledge_production: 0.15,
character_wisdom: 0.1
}
},
education: {
id: 'education',
name: 'Education',
description: 'Formal system of learning and teaching',
category: 'knowledge',
tier: 3,
researchTime: 25,
difficulty: 4,
cost: {
knowledge: 60,
materials: 20
},
prerequisites: ['writing', 'philosophy'],
effects: {
knowledge_production: 0.2,
character_intelligence: 0.15
}
}
};
// Internal state
let researchedTechnologies = [];
let currentResearch = null;
let researchProgress = 0;
let researchBonus = 1.0; // Multiplier for research speed
// Initialize with existing research data
function init(existingTechnologies) {
researchedTechnologies = existingTechnologies || [];
console.log(`Initialized TechnologyManager with ${researchedTechnologies.length} technologies`);
}
// Get available technologies that can be researched
function getAvailableTechnologies(gameState) {
const available = [];
const researchedIds = researchedTechnologies.map(tech => tech.id);
// For each technology
for (const techId in technologies) {
const tech = technologies[techId];
// Skip if already researched
if (researchedIds.includes(techId)) {
continue;
}
// Check if prerequisites are met
const prereqsMet = tech.prerequisites.every(prereq => researchedIds.includes(prereq));
if (!prereqsMet) {
continue;
}
// Check if tier is appropriate for current development level
if (tech.tier > gameState.world.developmentLevel) {
continue;
}
// Check if resources are sufficient
const canAfford = ResourceManager.canAfford(tech.cost);
// Add to available list with affordability flag
available.push({
...tech,
canAfford
});
}
return available;
}
// Start researching a technology
function researchTechnology(techId, gameState) {
// Check if already researching something
if (currentResearch) {
console.error(`Already researching ${currentResearch.name}`);
return false;
}
const tech = technologies[techId];
if (!tech) {
console.error(`Unknown technology: ${techId}`);
return false;
}
// Verify that prerequisites are met
const researchedIds = researchedTechnologies.map(t => t.id);
const prereqsMet = tech.prerequisites.every(prereq => researchedIds.includes(prereq));
if (!prereqsMet) {
console.error(`Prerequisites not met for ${tech.name}`);
return false;
}
// Check if we can afford it
if (!ResourceManager.canAfford(tech.cost)) {
console.error(`Not enough resources to research ${tech.name}`);
return false;
}
// Spend the resources
if (!ResourceManager.spendResources(tech.cost)) {
console.error(`Failed to spend resources for ${tech.name}`);
return false;
}
// Start the research
currentResearch = {
...tech,
startedTurn: gameState.turn
};
researchProgress = 0;
// Trigger an event
EventManager.addEvent({
type: 'research',
message: `Research on ${tech.name} has begun!`,
details: `This technology will take approximately ${tech.researchTime} turns to complete.`,
turn: gameState.turn
});
console.log(`Started research on ${tech.name}`);
return true;
}
// Update research progress
function updateTechnologies(gameState) {
// If researching something, update progress
if (currentResearch) {
// Base progress is 1 per turn
// Modified by research bonus from buildings, characters, etc.
const progressIncrement = 1 * researchBonus;
researchProgress += progressIncrement;
// Check if research is complete
if (researchProgress >= currentResearch.researchTime) {
completeResearch(gameState);
}
}
// Apply technology effects
applyTechnologyEffects();
}
// Complete the current research
function completeResearch(gameState) {
if (!currentResearch) {
console.error('No current research to complete');
return false;
}
// Add to researched technologies
researchedTechnologies.push({
id: currentResearch.id,
name: currentResearch.name,
completedTurn: gameState.turn,
effects: currentResearch.effects
});
// Apply immediate effects
applyTechnologyEffects(currentResearch);
// Trigger an event
EventManager.addEvent({
type: 'research',
message: `Research on ${currentResearch.name} is complete!`,
details: getEffectsDescription(currentResearch.effects),
turn: gameState.turn
});
// Clear current research
const completedResearch = currentResearch;
currentResearch = null;
researchProgress = 0;
// Increase world development level if enough technologies are researched
incrementDevelopmentLevel(gameState);
console.log(`Completed research on ${completedResearch.name}`);
return true;
}
// Format the effects of a technology for display
function getEffectsDescription(effects) {
if (!effects) return '';
const descriptions = [];
for (const effect in effects) {
if (effect === 'enables') {
const enabledItems = effects[effect].join(', ');
descriptions.push(`Enables: ${enabledItems}`);
} else {
const value = effects[effect];
if (typeof value === 'number') {
// Format as percentage for decimal values
const formatted = value >= 1 ? value : `${(value * 100).toFixed(0)}%`;
descriptions.push(`${formatEffectName(effect)}: +${formatted}`);
} else {
descriptions.push(`${formatEffectName(effect)}: ${value}`);
}
}
}
return descriptions.join(', ');
}
// Format effect names for display
function formatEffectName(effectKey) {
// Convert snake_case to Title Case
return effectKey
.split('_')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
// Apply effects from researched technologies
function applyTechnologyEffects() {
// Reset research bonus
researchBonus = 1.0;
// Apply all researched technology effects
for (const tech of researchedTechnologies) {
if (tech.effects) {
// Apply research speed bonus
if (tech.effects.research_speed) {
researchBonus += tech.effects.research_speed;
}
// Other effects are applied in respective systems
// e.g., production bonuses are used by ResourceManager
}
}
}
// Increment world development level if enough technologies are researched
function incrementDevelopmentLevel(gameState) {
const tier1Count = countResearchedByTier(1);
const tier2Count = countResearchedByTier(2);
const tier3Count = countResearchedByTier(3);
// Development level is based on the number of technologies researched at each tier
let newLevel = 1; // Base level
if (tier1Count >= 3) newLevel = 2;
if (tier1Count >= 5 && tier2Count >= 2) newLevel = 3;
if (tier2Count >= 4 && tier3Count >= 1) newLevel = 4;
if (tier3Count >= 3) newLevel = 5;
// Update the world development level if it's higher
if (newLevel > gameState.world.developmentLevel) {
const oldLevel = gameState.world.developmentLevel;
gameState.world.developmentLevel = newLevel;
// Trigger an advancement event
EventManager.addEvent({
type: 'advancement',
message: `Civilization has advanced to level ${newLevel}!`,
details: `New technologies and possibilities are now available.`,
turn: gameState.turn
});
console.log(`Advanced from level ${oldLevel} to ${newLevel}`);
}
}
// Count researched technologies of a specific tier
function countResearchedByTier(tier) {
return researchedTechnologies.filter(tech =>
technologies[tech.id] && technologies[tech.id].tier === tier
).length;
}
// Check if a specific technology is researched
function isTechnologyResearched(techId) {
return researchedTechnologies.some(tech => tech.id === techId);
}
// Get researched technology objects
function getResearchedTechnologies() {
return [...researchedTechnologies];
}
// Get the current research status
function getCurrentResearch() {
if (!currentResearch) return null;
return {
...currentResearch,
progress: researchProgress,
percentage: Math.min(100, Math.floor((researchProgress / currentResearch.researchTime) * 100))
};
}
// Set the research bonus (from buildings, events, etc.)
function setResearchBonus(bonus) {
researchBonus = bonus;
return researchBonus;
}
// Generate a research breakthrough (random advancement)
function generateBreakthrough(character) {
// Get available technologies
const gameState = window.AICivilizationGame?.getGameState();
if (!gameState) return null;
const availableTechs = getAvailableTechnologies(gameState);
if (availableTechs.length === 0) return null;
// Weight techs by character's intelligence and the tech's difficulty (inverse)
const weightedTechs = availableTechs.map(tech => {
// Higher intelligence favors more difficult techs
const intelligenceBonus = (character.intelligence - 5) * 0.05;
// Lower difficulty increases chance
const difficultyFactor = 1 - (tech.difficulty * 0.1);
return {
tech,
weight: 1 + intelligenceBonus + difficultyFactor
};
});
// Choose a tech based on weights
const totalWeight = weightedTechs.reduce((sum, item) => sum + item.weight, 0);
let random = Math.random() * totalWeight;
for (const item of weightedTechs) {
random -= item.weight;
if (random <= 0) {
// Apply a breakthrough bonus to this tech
const breakthroughFactor = 0.3 + ((character.intelligence / 10) * 0.2);
const progressBoost = Math.floor(item.tech.researchTime * breakthroughFactor);
// If we're already researching this tech, boost its progress
if (currentResearch && currentResearch.id === item.tech.id) {
researchProgress += progressBoost;
return {
type: 'progress_boost',
tech: item.tech,
boost: progressBoost,
description: `${character.name} has made a significant breakthrough in ${item.tech.name} research, advancing progress by ${progressBoost} turns!`
};
}
// Otherwise, return info about the potential tech
return {
type: 'new_insight',
tech: item.tech,
boost: progressBoost,
description: `${character.name} has gained valuable insights that would significantly speed up research in ${item.tech.name}.`
};
}
}
// Fallback if something goes wrong
return {
type: 'general_insight',
description: `${character.name} has made some interesting theoretical discoveries that may benefit future research.`
};
}
// Analyze technology gaps
function analyzeGaps() {
const researchedIds = researchedTechnologies.map(tech => tech.id);
const gaps = [];
// Find key technologies that aren't researched
if (!researchedIds.includes('agriculture')) gaps.push('agriculture');
if (!researchedIds.includes('writing')) gaps.push('science');
if (!researchedIds.includes('construction')) gaps.push('construction');
if (!researchedIds.includes('trade')) gaps.push('trade');
if (researchedIds.includes('writing') && !researchedIds.includes('scientific_method')) gaps.push('science');
if (researchedIds.includes('agriculture') && !researchedIds.includes('animal_husbandry')) gaps.push('agriculture');
return gaps;
}
// Return public API
return {
init,
getAvailableTechnologies,
researchTechnology,
updateTechnologies,
isTechnologyResearched,
getResearchedTechnologies,
getCurrentResearch,
setResearchBonus,
generateBreakthrough,
analyzeGaps,
getTechnologyDetails: (techId) => technologies[techId] || null
};
})();
export { TechnologyManager };
// event-manager.js
// Manages game events, notifications, and history
// EventManager - Tracks and manages game events
const EventManager = (function() {
// Event types and their properties
const eventTypes = {
character: {
icon: '👤',
color: '#4682B4' // Steel Blue
},
research: {
icon: '🔬',
color: '#9370DB' // Medium Purple
},
construction: {
icon: '🏗️',
color: '#CD853F' // Peru
},
exploration: {
icon: '🧭',
color: '#2E8B57' // Sea Green
},
production: {
icon: '⚒️',
color: '#B8860B' // Dark Goldenrod
},
trade: {
icon: '🔄',
color: '#20B2AA' // Light Sea Green
},
interaction: {
icon: '💬',
color: '#6A5ACD' // Slate Blue
},
world: {
icon: '🌍',
color: '#3CB371' // Medium Sea Green
},
discovery: {
icon: '💡',
color: '#FFD700' // Gold
},
suggestion: {
icon: '💭',
color: '#9932CC' // Dark Orchid
},
advancement: {
icon: '⭐',
color: '#FF8C00' // Dark Orange
},
building: {
icon: '🏢',
color: '#8B4513' // Saddle Brown
},
community: {
icon: '👪',
color: '#DB7093' // Pale Violet Red
},
diplomatic: {
icon: '🤝',
color: '#4169E1' // Royal Blue
}
};
// Internal state
let events = [];
let listeners = [];
let currentTurn = 0;
// Special events that can occur
const specialEvents = {
diplomatic: [
{
id: 'peaceful_traders',
name: 'Peaceful Traders',
description: 'A group of peaceful traders wishes to establish relations',
outcomes: [
{
description: 'Traders share valuable resources',
effect: (gameState, character) => {
const resourceTypes = ['food', 'materials', 'knowledge'];
const selectedType = resourceTypes[Math.floor(Math.random() * resourceTypes.length)];
const amount = 15 + (character.level * 5);
return {
type: 'resource_gain',
resourceType: selectedType,
amount: amount,
message: `The traders offered ${amount} ${selectedType} as a gesture of goodwill`
};
},
weight: 3
},
{
description: 'Traders offer knowledge exchange',
effect: (gameState, character) => {
const knowledgeGain = 20 + (character.intelligence * 2);
return {
type: 'knowledge_gain',
amount: knowledgeGain,
message: `The traders shared wisdom and information worth ${knowledgeGain} knowledge`
};
},
weight: 2
},
{
description: 'Permanent trade route established',
effect: (gameState, character) => {
const resourceGain = ['food', 'materials'];
const amount = 3 + Math.floor(character.level / 2);
return {
type: 'trade_route',
resources: resourceGain,
amount: amount,
message: `A permanent trade route has been established, providing ${amount} food and materials each turn`
};
},
weight: 1
}
]
},
{
id: 'traveling_scholars',
name: 'Traveling Scholars',
description: 'A group of scholars seeks shelter and wishes to share knowledge',
outcomes: [
{
description: 'Scholars share research insights',
effect: (gameState, character) => {
const researchBoost = 5 + (character.intelligence);
return {
type: 'research_boost',
amount: researchBoost,
message: `The scholars have accelerated current research by ${researchBoost} turns`
};
},
weight: 3
},
{
description: 'Scholars join the community',
effect: (gameState, character) => {
return {
type: 'knowledge_production',
bonus: 0.15,
duration: 20,
message: `Some scholars have decided to stay, increasing knowledge production by 15% for 20 turns`
};
},
weight: 2
},
{
description: 'Technology breakthrough',
effect: (gameState, character) => {
return {
type: 'tech_discovery',
message: `The scholars have provided crucial insights into a new technology`
};
},
weight: 1
}
]
}
],
exploration: [
{
id: 'ancient_ruins',
name: 'Ancient Ruins',
description: 'The remains of an ancient civilization have been discovered',
outcomes: [
{
description: 'Knowledge cache found',
effect: (gameState, character) => {
const knowledgeGain = 25 + (character.intelligence * 3);
return {
type: 'knowledge_gain',
amount: knowledgeGain,
message: `Ancient texts and artifacts have yielded ${knowledgeGain} knowledge`
};
},
weight: 3
},
{
description: 'Ancient technology discovered',
effect: (gameState, character) => {
return {
type: 'tech_discovery',
message: `Studying the ruins has revealed principles of an ancient technology`
};
},
weight: 1
},
{
description: 'Valuable resources uncovered',
effect: (gameState, character) => {
const materialsGain = 30 + (character.level * 5);
return {
type: 'resource_gain',
resourceType: 'materials',
amount: materialsGain,
message: `Valuable ancient materials worth ${materialsGain} have been recovered`
};
},
weight: 2
}
]
},
{
id: 'natural_wonder',
name: 'Natural Wonder',
description: 'An extraordinary natural formation has been discovered',
outcomes: [
{
description: 'Inspiration from natural beauty',
effect: (gameState, character) => {
return {
type: 'character_boost',
stat: 'creativity',
amount: 1,
message: `The wonder's beauty has inspired increased creativity in the community`
};
},
weight: 2
},
{
description: 'Rich natural resources',
effect: (gameState, character) => {
const foodGain = 20 + (character.level * 3);
const materialsGain = 15 + (character.level * 2);
return {
type: 'multi_resource_gain',
resources: {
food: foodGain,
materials: materialsGain
},
message: `The area surrounding the wonder is rich in resources, yielding ${foodGain} food and ${materialsGain} materials`
};
},
weight: 3
},
{
description: 'Scientific phenomenon',
effect: (gameState, character) => {
const knowledgeGain = 20 + (character.intelligence * 2);
return {
type: 'knowledge_gain',
amount: knowledgeGain,
message: `Studying the natural wonder has yielded ${knowledgeGain} knowledge`
};
},
weight: 2
}
]
}
],
luxury: [
{
id: 'exotic_goods',
name: 'Exotic Goods',
description: 'Rare and valuable items from distant lands',
outcomes: [
{
description: 'Community morale boost',
effect: (gameState, character) => {
return {
type: 'community_morale',
bonus: 0.1,
duration: 15,
message: `The exotic goods have boosted community morale by 10% for 15 turns`
};
},
weight: 3
},
{
description: 'Valuable trade items',
effect: (gameState, character) => {
const wealthGain = 15 + (character.level * 3);
return {
type: 'resource_gain',
resourceType: 'wealth',
amount: wealthGain,
message: `The exotic goods can be traded for ${wealthGain} wealth`
};
},
weight: 2
// event-manager.js
// Manages game events, notifications, and history
import { ResourceManager } from './resources/resource-manager.js';
// EventManager - Tracks and manages game events
const EventManager = (function() {
// Event types and their properties
const eventTypes = {
character: {
icon: '👤',
color: '#4682B4' // Steel Blue
},
research: {
icon: '🔬',
color: '#9370DB' // Medium Purple
},
construction: {
icon: '🏗️',
color: '#CD853F' // Peru
},
exploration: {
icon: '🧭',
color: '#2E8B57' // Sea Green
},
production: {
icon: '⚒️',
color: '#B8860B' // Dark Goldenrod
},
trade: {
icon: '🔄',
color: '#20B2AA' // Light Sea Green
},
interaction: {
icon: '💬',
color: '#6A5ACD' // Slate Blue
},
world: {
icon: '🌍',
color: '#3CB371' // Medium Sea Green
},
discovery: {
icon: '💡',
color: '#FFD700' // Gold
},
suggestion: {
icon: '💭',
color: '#9932CC' // Dark Orchid
},
advancement: {
icon: '⭐',
color: '#FF8C00' // Dark Orange
},
building: {
icon: '🏢',
color: '#8B4513' // Saddle Brown
},
community: {
icon: '👪',
color: '#DB7093' // Pale Violet Red
},
diplomatic: {
icon: '🤝',
color: '#4169E1' // Royal Blue
}
};
// Internal state
let events = [];
let listeners = [];
let currentTurn = 0;
// Special events that can occur
const specialEvents = {
diplomatic: [
{
id: 'peaceful_traders',
name: 'Peaceful Traders',
description: 'A group of peaceful traders wishes to establish relations',
outcomes: [
{
description: 'Traders share valuable resources',
effect: (gameState, character) => {
const resourceTypes = ['food', 'materials', 'knowledge'];
const selectedType = resourceTypes[Math.floor(Math.random() * resourceTypes.length)];
const amount = 15 + (character.level * 5);
return {
type: 'resource_gain',
resourceType: selectedType,
amount: amount,
message: `The traders offered ${amount} ${selectedType} as a gesture of goodwill`
};
},
weight: 3
},
{
description: 'Traders offer knowledge exchange',
effect: (gameState, character) => {
const knowledgeGain = 20 + (character.intelligence * 2);
return {
type: 'knowledge_gain',
amount: knowledgeGain,
message: `The traders shared wisdom and information worth ${knowledgeGain} knowledge`
};
},
weight: 2
},
{
description: 'Permanent trade route established',
effect: (gameState, character) => {
const resourceGain = ['food', 'materials'];
const amount = 3 + Math.floor(character.level / 2);
return {
type: 'trade_route',
resources: resourceGain,
amount: amount,
message: `A permanent trade route has been established, providing ${amount} food and materials each turn`
};
},
weight: 1
}
]
},
{
id: 'traveling_scholars',
name: 'Traveling Scholars',
description: 'A group of scholars seeks shelter and wishes to share knowledge',
outcomes: [
{
description: 'Scholars share research insights',
effect: (gameState, character) => {
const researchBoost = 5 + (character.intelligence);
return {
type: 'research_boost',
amount: researchBoost,
message: `The scholars have accelerated current research by ${researchBoost} turns`
};
},
weight: 3
},
{
description: 'Scholars join the community',
effect: (gameState, character) => {
return {
type: 'knowledge_production',
bonus: 0.15,
duration: 20,
message: `Some scholars have decided to stay, increasing knowledge production by 15% for 20 turns`
};
},
weight: 2
},
{
description: 'Technology breakthrough',
effect: (gameState, character) => {
return {
type: 'tech_discovery',
message: `The scholars have provided crucial insights into a new technology`
};
},
weight: 1
}
]
}
],
exploration: [
{
id: 'ancient_ruins',
name: 'Ancient Ruins',
description: 'The remains of an ancient civilization have been discovered',
outcomes: [
{
description: 'Knowledge cache found',
effect: (gameState, character) => {
const knowledgeGain = 25 + (character.intelligence * 3);
return {
type: 'knowledge_gain',
amount: knowledgeGain,
message: `Ancient texts and artifacts have yielded ${knowledgeGain} knowledge`
};
},
weight: 3
},
{
description: 'Ancient technology discovered',
effect: (gameState, character) => {
return {
type: 'tech_discovery',
message: `Studying the ruins has revealed principles of an ancient technology`
};
},
weight: 1
},
{
description: 'Valuable resources uncovered',
effect: (gameState, character) => {
const materialsGain = 30 + (character.level * 5);
return {
type: 'resource_gain',
resourceType: 'materials',
amount: materialsGain,
message: `Valuable ancient materials worth ${materialsGain} have been recovered`
};
},
weight: 2
}
]
},
{
id: 'natural_wonder',
name: 'Natural Wonder',
description: 'An extraordinary natural formation has been discovered',
outcomes: [
{
description: 'Inspiration from natural beauty',
effect: (gameState, character) => {
return {
type: 'character_boost',
stat: 'creativity',
amount: 1,
message: `The wonder's beauty has inspired increased creativity in the community`
};
},
weight: 2
},
{
description: 'Rich natural resources',
effect: (gameState, character) => {
const foodGain = 20 + (character.level * 3);
const materialsGain = 15 + (character.level * 2);
return {
type: 'multi_resource_gain',
resources: {
food: foodGain,
materials: materialsGain
},
message: `The area surrounding the wonder is rich in resources, yielding ${foodGain} food and ${materialsGain} materials`
};
},
weight: 3
},
{
description: 'Scientific phenomenon',
effect: (gameState, character) => {
const knowledgeGain = 20 + (character.intelligence * 2);
return {
type: 'knowledge_gain',
amount: knowledgeGain,
message: `Studying the natural wonder has yielded ${knowledgeGain} knowledge`
};
},
weight: 2
}
]
}
],
luxury: [
{
id: 'exotic_goods',
name: 'Exotic Goods',
description: 'Rare and valuable items from distant lands',
outcomes: [
{
description: 'Community morale boost',
effect: (gameState, character) => {
return {
type: 'community_morale',
bonus: 0.1,
duration: 15,
message: `The exotic goods have boosted community morale by 10% for 15 turns`
};
},
weight: 3
},
{
description: 'Valuable trade items',
effect: (gameState, character) => {
const wealthGain = 15 + (character.level * 3);
return {
type: 'resource_gain',
resourceType: 'wealth',
amount: wealthGain,
message: `The exotic goods can be traded for ${wealthGain} wealth`
};
},
weight: 2
},
{
description: 'Cultural exchange',
effect: (gameState, character) => {
return {
type: 'character_boost',
stat: 'intelligence',
amount: 1,
message: `The cultural exchange has increased intelligence in the community`
};
},
weight: 1
}
]
}
]
};
// Initialize with existing events
function init(existingEvents) {
events = existingEvents || [];
console.log(`Initialized EventManager with ${events.length} events`);
}
// Add a new event
function addEvent(event) {
// Generate a unique ID for the event
event.id = `event_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// Add event type information
if (eventTypes[event.type]) {
event.icon = eventTypes[event.type].icon;
event.color = eventTypes[event.type].color;
} else {
event.icon = '📝';
event.color = '#808080'; // Gray for unknown types
}
// Add timestamp
event.timestamp = Date.now();
// Add to events list
events.push(event);
// Notify listeners
notifyListeners(event);
// Update current turn if higher
if (event.turn > currentTurn) {
currentTurn = event.turn;
}
console.log(`Added event: ${event.message}`);
return event.id;
}
// Notify event listeners
function notifyListeners(event) {
for (const listener of listeners) {
try {
listener(event);
} catch (error) {
console.error('Error in event listener:', error);
}
}
}
// Add an event listener
function addEventListener(callback) {
if (typeof callback === 'function') {
listeners.push(callback);
return listeners.length - 1; // Return the index for removal
}
return -1;
}
// Remove an event listener
function removeEventListener(index) {
if (index >= 0 && index < listeners.length) {
listeners.splice(index, 1);
return true;
}
return false;
}
// Get events for a specific turn
function getEventsForTurn(turn) {
return events.filter(event => event.turn === turn);
}
// Get recent events
function getRecentEvents(count = 10) {
// Sort by turn (descending) and timestamp (descending)
const sortedEvents = [...events].sort((a, b) => {
if (b.turn !== a.turn) return b.turn - a.turn;
return b.timestamp - a.timestamp;
});
return sortedEvents.slice(0, count);
}
// Get events by type
function getEventsByType(type, count = 10) {
const filteredEvents = events.filter(event => event.type === type);
// Sort by turn (descending) and timestamp (descending)
filteredEvents.sort((a, b) => {
if (b.turn !== a.turn) return b.turn - a.turn;
return b.timestamp - a.timestamp;
});
return filteredEvents.slice(0, count);
}
// Generate random events based on game state
function generateEvents(gameState) {
// Only generate events with a certain probability
if (Math.random() < 0.15) { // 15% chance each turn
generateRandomEvent(gameState);
}
// Process ongoing events and remove expired ones
processOngoingEvents(gameState);
}
// Process ongoing events
function processOngoingEvents(gameState) {
// Filter out expired events
const activeEvents = events.filter(event => {
// Check if the event has a duration and is still active
if (event.duration && event.turn + event.duration < gameState.turn) {
// Generate an event that the effect has ended
addEvent({
type: event.type,
message: `${event.message} has ended`,
details: 'The effect has worn off',
turn: gameState.turn
});
return false; // Remove from active events
}
return true; // Keep in active events
});
// Update events list with active events
events = activeEvents;
}
// Generate a random event
function generateRandomEvent(gameState) {
// Different types of random events with weights
const eventOptions = [
{ type: 'resource_discovery', weight: 3 },
{ type: 'character_interaction', weight: 2 },
{ type: 'weather_effect', weight: 2 },
{ type: 'external_contact', weight: 1 }
];
// Calculate total weight
const totalWeight = eventOptions.reduce((sum, option) => sum + option.weight, 0);
// Select event type based on weights
let random = Math.random() * totalWeight;
let selectedType = eventOptions[0].type; // Default
for (const option of eventOptions) {
random -= option.weight;
if (random <= 0) {
selectedType = option.type;
break;
}
}
// Generate the event
switch (selectedType) {
case 'resource_discovery':
generateResourceDiscoveryEvent(gameState);
break;
case 'character_interaction':
generateCharacterInteractionEvent(gameState);
break;
case 'weather_effect':
generateWeatherEvent(gameState);
break;
case 'external_contact':
generateExternalContactEvent(gameState);
break;
}
}
// Generate a resource discovery event
function generateResourceDiscoveryEvent(gameState) {
const resourceTypes = ['food', 'materials', 'knowledge'];
const selectedType = resourceTypes[Math.floor(Math.random() * resourceTypes.length)];
// Amount based on development level
const baseAmount = 5 + (gameState.world.developmentLevel * 3);
const amount = baseAmount + Math.floor(Math.random() * baseAmount);
// Add the resources
ResourceManager.addResource(selectedType, amount);
// Create the event
addEvent({
type: 'discovery',
message: `Unexpected ${selectedType} discovery!`,
details: `Your civilization has found ${amount} ${selectedType} in an unexpected location.`,
turn: gameState.turn
});
}
// Generate a character interaction event
function generateCharacterInteractionEvent(gameState) {
// Need at least 2 characters
if (gameState.characters.length < 2) return;
// Select two random characters
const char1Index = Math.floor(Math.random() * gameState.characters.length);
let char2Index;
do {
char2Index = Math.floor(Math.random() * gameState.characters.length);
} while (char2Index === char1Index);
const char1 = gameState.characters[char1Index];
const char2 = gameState.characters[char2Index];
// Possible interactions
const interactions = [
{
type: 'friendship',
message: `${char1.name} and ${char2.name} have formed a friendship`,
details: 'Their relationship has improved, increasing cooperation.',
effect: () => {
// Increase relationship
char1.relationships[char2.id] = Math.min(100, (char1.relationships[char2.id] || 50) + 10);
char2.relationships[char1.id] = Math.min(100, (char2.relationships[char1.id] || 50) + 10);
// Small productivity boost
return {
type: 'productivity',
bonus: 0.05,
duration: 5
};
},
weight: 3
},
{
type: 'disagreement',
message: `${char1.name} and ${char2.name} have had a disagreement`,
details: 'Their relationship has been strained, temporarily reducing cooperation.',
effect: () => {
// Decrease relationship
char1.relationships[char2.id] = Math.max(20, (char1.relationships[char2.id] || 50) - 5);
char2.relationships[char1.id] = Math.max(20, (char2.relationships[char1.id] || 50) - 5);
// Small productivity decrease
return {
type: 'productivity',
bonus: -0.03,
duration: 3
};
},
weight: 2
},
{
type: 'collaboration',
message: `${char1.name} and ${char2.name} have started collaborating on a project`,
details: 'Their complementary skills have led to increased efficiency.',
effect: () => {
// Modestly increase relationship
char1.relationships[char2.id] = Math.min(100, (char1.relationships[char2.id] || 50) + 5);
char2.relationships[char1.id] = Math.min(100, (char2.relationships[char1.id] || 50) + 5);
// Determine which resource gets boosted based on their roles
let resourceType = 'knowledge';
if (char1.role === 'builder' || char2.role === 'builder') {
resourceType = 'materials';
} else if (char1.role === 'farmer' || char2.role === 'farmer') {
resourceType = 'food';
}
// Resource production boost
return {
type: 'production',
resourceType: resourceType,
bonus: 0.1,
duration: 5
};
},
weight: 4
}
];
// Calculate total weight
const totalWeight = interactions.reduce((sum, option) => sum + option.weight, 0);
// Select interaction based on weights
let random = Math.random() * totalWeight;
let selectedInteraction = interactions[0]; // Default
for (const interaction of interactions) {
random -= interaction.weight;
if (random <= 0) {
selectedInteraction = interaction;
break;
}
}
// Apply the effect
const effect = selectedInteraction.effect();
// Create the event
addEvent({
type: 'interaction',
message: selectedInteraction.message,
details: selectedInteraction.details,
turn: gameState.turn,
effect: effect,
duration: effect.duration
});
}
// Generate a weather event
function generateWeatherEvent(gameState) {
// Possible weather events
const weatherEvents = [
{
type: 'rain',
message: 'Beneficial rainfall has nourished crops',
details: 'The rain has increased food production for a short period.',
effect: {
type: 'production',
resourceType: 'food',
bonus: 0.15,
duration: 3
},
weight: 3
},
{
type: 'drought',
message: 'A mild drought is affecting the region',
details: 'The dry conditions have temporarily reduced food production.',
effect: {
type: 'production',
resourceType: 'food',
bonus: -0.1,
duration: 4
},
weight: 2
},
{
type: 'clear_weather',
message: 'Perfect weather conditions for construction',
details: 'The ideal conditions have increased building efficiency.',
effect: {
type: 'construction',
bonus: 0.2,
duration: 2
},
weight: 2
},
{
type: 'storm',
message: 'A storm has damaged some structures',
details: 'Repairs will be needed, consuming some materials.',
effect: {
type: 'damage',
resourceCost: {
materials: 10
}
},
weight: 1
}
];
// Calculate total weight
const totalWeight = weatherEvents.reduce((sum, option) => sum + option.weight, 0);
// Select weather event based on weights
let random = Math.random() * totalWeight;
let selectedWeather = weatherEvents[0]; // Default
for (const weather of weatherEvents) {
random -= weather.weight;
if (random <= 0) {
selectedWeather = weather;
break;
}
}
// Apply immediate effects
if (selectedWeather.effect.type === 'damage') {
// Deduct resources for repairs
for (const resource in selectedWeather.effect.resourceCost) {
ResourceManager.removeResource(
resource,
selectedWeather.effect.resourceCost[resource]
);
}
}
// Create the event
addEvent({
type: 'world',
message: selectedWeather.message,
details: selectedWeather.details,
turn: gameState.turn,
effect: selectedWeather.effect,
duration: selectedWeather.effect.duration
});
}
// Generate an external contact event
function generateExternalContactEvent(gameState) {
// Only generate these events after a certain development level
if (gameState.world.developmentLevel < 2) return;
// Possible external contact events
const contactEvents = [
{
type: 'nomadic_tribe',
message: 'A nomadic tribe has made contact',
details: 'They offer to trade resources for knowledge.',
options: [
{
text: 'Trade with the tribe',
outcome: {
positive: {
type: 'resource_gain',
resourceType: 'food',
amount: 20,
message: 'The tribe has shared their abundant food supplies.'
},
negative: {
type: 'resource_loss',
resourceType: 'knowledge',
amount: 10,
message: 'You have shared some of your knowledge with them.'
}
}
},
{
text: 'Decline trade',
outcome: {
neutral: {
message: 'The tribe moves on, slightly disappointed.'
}
}
}
],
weight: 3
},
{
type: 'wandering_merchant',
message: 'A wandering merchant has arrived',
details: 'They offer exotic goods in exchange for materials.',
options: [
{
text: 'Trade with the merchant',
outcome: {
positive: {
type: 'resource_gain',
resourceType: 'knowledge',
amount: 15,
message: 'The merchant shares valuable information from their travels.'
},
negative: {
type: 'resource_loss',
resourceType: 'materials',
amount: 15,
message: 'You have traded away some materials.'
}
}
},
{
text: 'Decline trade',
outcome: {
neutral: {
message: 'The merchant leaves to find other trading partners.'
}
}
}
],
weight: 2
}
];
// Calculate total weight
const totalWeight = contactEvents.reduce((sum, option) => sum + option.weight, 0);
// Select contact event based on weights
let random = Math.random() * totalWeight;
let selectedContact = contactEvents[0]; // Default
for (const contact of contactEvents) {
random -= contact.weight;
if (random <= 0) {
selectedContact = contact;
break;
}
}
// For now, automatically choose the first option
// In a full implementation, this would be presented to the player
const selectedOption = selectedContact.options[0];
// Apply outcomes
if (selectedOption.outcome.positive) {
if (selectedOption.outcome.positive.type === 'resource_gain') {
ResourceManager.addResource(
selectedOption.outcome.positive.resourceType,
selectedOption.outcome.positive.amount
);
}
}
if (selectedOption.outcome.negative) {
if (selectedOption.outcome.negative.type === 'resource_loss') {
ResourceManager.removeResource(
selectedOption.outcome.negative.resourceType,
selectedOption.outcome.negative.amount
);
}
}
// Create the event
addEvent({
type: 'interaction',
message: selectedContact.message,
details: `${selectedContact.details} - ${selectedOption.text}: ${selectedOption.outcome.positive ? selectedOption.outcome.positive.message : ''} ${selectedOption.outcome.negative ? selectedOption.outcome.negative.message : ''}`,
turn: gameState.turn
});
}
// Generate a special event based on type
function generateSpecialEvent(eventType, character, options = {}) {
// Check if this event type exists
if (!specialEvents[eventType]) {
console.error(`Unknown special event type: ${eventType}`);
return false;
}
// Get the game state
const gameState = window.AICivilizationGame?.getGameState();
if (!gameState) {
console.error('Unable to get game state for special event');
return false;
}
// Select a random event from this type
const eventsList = specialEvents[eventType];
const selectedEvent = eventsList[Math.floor(Math.random() * eventsList.length)];
// Select an outcome based on weights
const totalWeight = selectedEvent.outcomes.reduce((sum, outcome) => sum + outcome.weight, 0);
let random = Math.random() * totalWeight;
let selectedOutcome = selectedEvent.outcomes[0]; // Default
for (const outcome of selectedEvent.outcomes) {
random -= outcome.weight;
if (random <= 0) {
selectedOutcome = outcome;
break;
}
}
// Apply the effect
const effect = selectedOutcome.effect(gameState, character);
// Process the effect
switch (effect.type) {
case 'resource_gain':
ResourceManager.addResource(effect.resourceType, effect.amount);
break;
case 'knowledge_gain':
ResourceManager.addResource('knowledge', effect.amount);
break;
case 'multi_resource_gain':
for (const resource in effect.resources) {
ResourceManager.addResource(resource, effect.resources[resource]);
}
break;
case 'trade_route':
// Add an ongoing trade route
ResourceManager.addTradeRoute({
resources: effect.resources,
amount: effect.amount,
founder: character.id
});
break;
case 'research_boost':
// This would be handled by the TechnologyManager
// Just create an event for now
break;
case 'tech_discovery':
// This would be handled by the TechnologyManager
// Just create an event for now
break;
case 'character_boost':
// Boost a character stat
character[effect.stat] += effect.amount;
break;
case 'knowledge_production':
case 'community_morale':
// These would be handled by their respective systems
// Just create an event for now
break;
}
// Create the event
addEvent({
type: eventType,
message: `${selectedEvent.name} - ${selectedOutcome.description}`,
details: effect.message,
turn: gameState.turn,
character: character.name,
effect: effect
});
return true;
}
// Return public API
return {
init,
addEvent,
addEventListener,
removeEventListener,
getEventsForTurn,
getRecentEvents,
getEventsByType,
generateEvents,
generateSpecialEvent
};
})();
export { EventManager };
// ui-manager.js
// Manages the game's user interface and DOM interactions
// UIManager - Creates and updates UI elements
const UIManager = (function() {
// DOM element references
let characterContainer = null;
let resourcesContainer = null;
let buildingsContainer = null;
let technologiesContainer = null;
let eventsContainer = null;
let worldInfoContainer = null;
// Initialize the UI
function init() {
console.log('Initializing UI Manager');
// Create main game container if not exists
createGameContainer();
// Create or get UI containers
characterContainer = document.getElementById('characters-container') || createContainer('characters-container', 'Characters');
resourcesContainer = document.getElementById('resources-container') || createContainer('resources-container', 'Resources');
buildingsContainer = document.getElementById('buildings-container') || createContainer('buildings-container', 'Buildings');
technologiesContainer = document.getElementById('technologies-container') || createContainer('technologies-container', 'Technologies');
eventsContainer = document.getElementById('events-container') || createContainer('events-container', 'Events');
worldInfoContainer = document.getElementById('world-info-container') || createContainer('world-info-container', 'World');
// Add event listeners for UI interactions
setupEventListeners();
}
// Create the main game container
function createGameContainer() {
let gameContainer = document.getElementById('game-container');
if (!
// ui-manager.js
// Manages the game's user interface and DOM interactions
// UIManager - Creates and updates UI elements
const UIManager = (function() {
// DOM element references
let characterContainer = null;
let resourcesContainer = null;
let buildingsContainer = null;
let technologiesContainer = null;
let eventsContainer = null;
let worldInfoContainer = null;
// Initialize the UI
function init() {
console.log('Initializing UI Manager');
// Create main game container if not exists
createGameContainer();
// Create or get UI containers
characterContainer = document.getElementById('characters-container') || createContainer('characters-container', 'Characters');
resourcesContainer = document.getElementById('resources-container') || createContainer('resources-container', 'Resources');
buildingsContainer = document.getElementById('buildings-container') || createContainer('buildings-container', 'Buildings');
technologiesContainer = document.getElementById('technologies-container') || createContainer('technologies-container', 'Technologies');
eventsContainer = document.getElementById('events-container') || createContainer('events-container', 'Events');
worldInfoContainer = document.getElementById('world-info-container') || createContainer('world-info-container', 'World');
// Add event listeners for UI interactions
setupEventListeners();
}
// Create the main game container
function createGameContainer() {
let gameContainer = document.getElementById('game-container');
if (!gameContainer) {
gameContainer = document.createElement('div');
gameContainer.id = 'game-container';
gameContainer.className = 'game-container';
// Create game layout
const layout = `
AI Civilization Game
World Map
`;
gameContainer.innerHTML = layout;
document.body.appendChild(gameContainer);
// Add basic styling
addGameStyles();
}
return gameContainer;
}
// Create a container for a UI section
function createContainer(id, title) {
const container = document.createElement('div');
container.id = id;
container.className = 'container';
container.innerHTML = `${title}
`;
return container;
}
// Add basic CSS styles for the game
function addGameStyles() {
const styleElement = document.createElement('style');
styleElement.type = 'text/css';
const styles = `
.game-container {
font-family: Arial, sans-serif;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
}
.game-content {
display: grid;
grid-template-columns: 250px 1fr 250px;
gap: 20px;
margin-bottom: 20px;
}
.left-panel, .right-panel {
display: flex;
flex-direction: column;
gap: 20px;
}
.panel {
background-color: white;
border-radius: 5px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
padding: 15px;
}
.panel h2 {
margin-top: 0;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
font-size: 18px;
}
.container-content {
max-height: 300px;
overflow-y: auto;
}
#world-map {
border: 1px solid #ccc;
background-color: #eaf6ff;
display: block;
margin: 10px 0;
max-width: 100%;
}
#map-controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
}
#map-controls button {
width: 40px;
height: 40px;
border-radius: 5px;
border: 1px solid #ccc;
background-color: white;
cursor: pointer;
font-size: 16px;
}
#map-controls button:hover {
background-color: #f0f0f0;
}
.game-footer {
display: flex;
justify-content: center;
gap: 20px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
.game-footer button {
padding: 10px 20px;
border-radius: 5px;
border: none;
background-color: #4c84ff;
color: white;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s;
}
.game-footer button:hover {
background-color: #3a66cc;
}
.character-card, .building-card, .technology-card {
background-color: #f9f9f9;
border: 1px solid #eee;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
}
.character-card h3, .building-card h3, .technology-card h3 {
margin-top: 0;
font-size: 16px;
}
.resource-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
}
.event-item {
padding: 8px;
border-left: 4px solid #4c84ff;
background-color: #f5f9ff;
margin-bottom: 8px;
font-size: 14px;
}
.event-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-weight: bold;
}
`;
styleElement.appendChild(document.createTextNode(styles));
document.head.appendChild(styleElement);
}
// Setup event listeners for UI interactions
function setupEventListeners() {
// Next turn button
const nextTurnBtn = document.getElementById('next-turn-btn');
if (nextTurnBtn) {
nextTurnBtn.addEventListener('click', () => {
// Get game instance and advance turn
const game = window.AICivilizationGame;
if (game) {
// This would normally call a method on the game object
// For now, log to console
console.log('Next turn button clicked');
}
});
}
// Save game button
const saveGameBtn = document.getElementById('save-game-btn');
if (saveGameBtn) {
saveGameBtn.addEventListener('click', () => {
const game = window.AICivilizationGame;
if (game) {
// This would normally save the game
console.log('Save game button clicked');
}
});
}
// Map controls
setupMapControls();
// Listen for region clicks
document.addEventListener('region-clicked', (event) => {
const regionData = event.detail;
displayRegionDetails(regionData.region);
});
}
// Setup map control buttons
function setupMapControls() {
const zoomInBtn = document.getElementById('map-zoom-in');
const zoomOutBtn = document.getElementById('map-zoom-out');
const panUpBtn = document.getElementById('map-pan-up');
const panDownBtn = document.getElementById('map-pan-down');
const panLeftBtn = document.getElementById('map-pan-left');
const panRightBtn = document.getElementById('map-pan-right');
// Import WorldRenderer if available in global scope
const WorldRenderer = window.WorldRenderer;
if (zoomInBtn && WorldRenderer) {
zoomInBtn.addEventListener('click', () => {
// Zoom in logic
console.log('Zoom in clicked');
});
}
if (zoomOutBtn && WorldRenderer) {
zoomOutBtn.addEventListener('click', () => {
// Zoom out logic
console.log('Zoom out clicked');
});
}
if (panUpBtn && WorldRenderer) {
panUpBtn.addEventListener('click', () => {
WorldRenderer.panMap(0, -1);
});
}
if (panDownBtn && WorldRenderer) {
panDownBtn.addEventListener('click', () => {
WorldRenderer.panMap(0, 1);
});
}
if (panLeftBtn && WorldRenderer) {
panLeftBtn.addEventListener('click', () => {
WorldRenderer.panMap(-1, 0);
});
}
if (panRightBtn && WorldRenderer) {
panRightBtn.addEventListener('click', () => {
WorldRenderer.panMap(1, 0);
});
}
}
// Display details for a clicked region
function displayRegionDetails(region) {
if (!region) return;
// Create a modal or update a details panel
let detailPanel = document.getElementById('region-detail-panel');
if (!detailPanel) {
detailPanel = document.createElement('div');
detailPanel.id = 'region-detail-panel';
detailPanel.className = 'modal';
const gameContainer = document.getElementById('game-container');
if (gameContainer) {
gameContainer.appendChild(detailPanel);
} else {
document.body.appendChild(detailPanel);
}
}
// Generate HTML content for the region details
const content = `
`;
detailPanel.innerHTML = content;
detailPanel.style.display = 'block';
// Add event listeners for modal buttons
const closeButtons = detailPanel.querySelectorAll('.close-btn, .close-modal');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
detailPanel.style.display = 'none';
});
});
// Center map button
const centerButton = detailPanel.querySelector('.center-on-region');
if (centerButton) {
centerButton.addEventListener('click', () => {
const WorldRenderer = window.WorldRenderer;
if (WorldRenderer) {
WorldRenderer.centerOnRegion(region.id);
}
});
}
// Explore button
const exploreButton = detailPanel.querySelector('.explore-btn');
if (exploreButton) {
exploreButton.addEventListener('click', () => {
const regionId = exploreButton.getAttribute('data-region-id');
console.log(`Exploring region ${regionId}`);
// This would call into the game logic to explore the region
});
}
}
// Get color for terrain visualization
function getTerrainColor(terrainType) {
const colors = {
plains: '#9ACD32',
forest: '#228B22',
mountain: '#808080',
river: '#4682B4',
lake: '#6495ED',
desert: '#F0E68C',
hills: '#CD853F'
};
return colors[terrainType] || '#7CFC00';
}
// Render characters to the UI
function renderCharacters(characters) {
if (!characterContainer) return;
const contentDiv = characterContainer.querySelector('.container-content') || characterContainer;
// Clear existing content
contentDiv.innerHTML = '';
// Create character cards
characters.forEach(character => {
const characterCard = document.createElement('div');
characterCard.className = 'character-card';
characterCard.setAttribute('data-id', character.id);
// Character task status
const taskStatus = character.currentTask
? `
Current Task: ${character.currentTask.name}
${Math.round((character.currentTask.progress / character.currentTask.duration) * 100)}%
`
: 'Idle';
characterCard.innerHTML = `
${character.name}
${character.role}
Intelligence: ${character.intelligence}
Cooperation: ${character.cooperation}
Creativity: ${character.creativity}
Level ${character.level}
${taskStatus}
`;
contentDiv.appendChild(characterCard);
// Add click event for task assignment
const assignTaskBtn = characterCard.querySelector('.assign-task-btn');
if (assignTaskBtn) {
assignTaskBtn.addEventListener('click', () => {
showTaskAssignmentDialog(character);
});
}
// Add click event for character details
const detailsBtn = characterCard.querySelector('.character-details-btn');
if (detailsBtn) {
detailsBtn.addEventListener('click', () => {
showCharacterDetailsDialog(character);
});
}
});
}
// Render resources to the UI
function renderResources(resources) {
if (!resourcesContainer) return;
const contentDiv = resourcesContainer.querySelector('.container-content') || resourcesContainer;
// Clear existing content
contentDiv.innerHTML = '';
const resourcesList = contentDiv.querySelector('.resources-list');
// Create resource items
for (const resource in resources) {
const resourceItem = document.createElement('div');
resourceItem.className = 'resource-item';
resourceItem.innerHTML = `
${resource}
${resources[resource]}
`;
resourcesList.appendChild(resourceItem);
}
}
// Render buildings to the UI
function renderBuildings(buildings) {
if (!buildingsContainer) return;
const contentDiv = buildingsContainer.querySelector('.container-content') || buildingsContainer;
// Clear existing content
contentDiv.innerHTML = '';
// Create building cards
buildings.forEach(building => {
const buildingCard = document.createElement('div');
buildingCard.className = 'building-card';
buildingCard.setAttribute('data-id', building.id);
buildingCard.innerHTML = `
${building.name}
Location: ${building.regionId}
Efficiency: ${Math.round(building.efficiency * 100)}%
Condition: ${Math.round(building.condition)}%
`;
contentDiv.appendChild(buildingCard);
// Add click event for repair
const repairBtn = buildingCard.querySelector('.repair-building-btn');
if (repairBtn) {
repairBtn.addEventListener('click', () => {
// Call building repair function
console.log(`Repair building ${building.id}`);
});
}
// Add click event for building details
const detailsBtn = buildingCard.querySelector('.building-details-btn');
if (detailsBtn) {
detailsBtn.addEventListener('click', () => {
showBuildingDetailsDialog(building);
});
}
});
}
// Render technologies to the UI
function renderTechnologies(technologies) {
if (!technologiesContainer) return;
const contentDiv = technologiesContainer.querySelector('.container-content') || technologiesContainer;
// Clear existing content
contentDiv.innerHTML = '';
// Current research
const gameState = window.AICivilizationGame?.getGameState();
const currentResearch = gameState?.currentResearch;
if (currentResearch) {
const researchElement = document.createElement('div');
researchElement.className = 'current-research';
researchElement.innerHTML = `
Researching: ${currentResearch.name}
${Math.round((currentResearch.progress / currentResearch.researchTime) * 100)}% complete
`;
contentDiv.appendChild(researchElement);
}
// Create technology cards
technologies.forEach(tech => {
const techCard = document.createElement('div');
techCard.className = 'technology-card';
techCard.setAttribute('data-id', tech.id);
techCard.innerHTML = `
${tech.name}
Researched on turn ${tech.completedTurn}
${
tech.effects ? Object.entries(tech.effects)
.map(([effect, value]) => `
${effect.replace(/_/g, ' ')}:
${typeof value === 'number' ? (value > 0 ? '+' : '') + (value * 100) + '%' : value}
`).join('') : ''
}
`;
contentDiv.appendChild(techCard);
});
// Add Research button
const researchBtn = document.createElement('button');
researchBtn.className = 'research-btn';
researchBtn.textContent = 'Research New Technology';
researchBtn.addEventListener('click', () => {
showResearchDialog();
});
contentDiv.appendChild(researchBtn);
}
// Render events to the UI
function renderEvents(events) {
if (!eventsContainer) return;
const contentDiv = eventsContainer.querySelector('.container-content') || eventsContainer;
// Clear existing content
contentDiv.innerHTML = '';
// Create event items
events.forEach(event => {
const eventItem = document.createElement('div');
eventItem.className = 'event-item';
eventItem.style.borderLeftColor = event.color || '#4c84ff';
eventItem.innerHTML = `
${event.icon || '📝'} ${event.message}
Turn ${event.turn}
${event.details || ''}
`;
contentDiv.appendChild(eventItem);
});
}
// Show dialog for assigning tasks to characters
function showTaskAssignmentDialog(character) {
// Create a modal dialog
const modalDialog = document.createElement('div');
modalDialog.className = 'modal';
modalDialog.id = 'task-assignment-modal';
// Get available tasks for this character
const gameState = window.AICivilizationGame?.getGameState();
const CharacterTasks = window.CharacterTasks;
let availableTasks = [];
if (gameState && CharacterTasks) {
availableTasks = CharacterTasks.getAvailableTasks(character, gameState);
}
// Group tasks by category
const tasksByCategory = {};
availableTasks.forEach(task => {
if (!tasksByCategory[task.category]) {
tasksByCategory[task.category] = [];
}
tasksByCategory[task.category].push(task);
});
// Generate dialog content
modalDialog.innerHTML = `
`;
// Add to document
document.body.appendChild(modalDialog);
// Add event listeners
const closeButtons = modalDialog.querySelectorAll('.close-btn, .close-modal');
closeButtons.forEach(btn => {
btn.addEventListener('click', () => {
document.body.removeChild(modalDialog);
});
});
// Add event listeners for task assignment
const assignButtons = modalDialog.querySelectorAll('.assign-task');
assignButtons.forEach(btn => {
btn.addEventListener('click', () => {
const taskId = btn.getAttribute('data-task-id');
console.log(`Assigning task ${taskId} to character ${character.id}`);
// Here you would call the game logic to assign the task
document.body.removeChild(modalDialog);
});
});
}
// Show character details dialog
function showCharacterDetailsDialog(character) {
// Create a modal dialog
const modalDialog = document.createElement('div');
modalDialog.className = 'modal';
modalDialog.id = 'character-details-modal';
// Generate relationships display
let relationshipsHtml = 'No relationships established yet.
';
if (character.relationships && Object.keys(character.relationships).length > 0) {
const gameState = window.AICivilizationGame?.getGameState();
const characters = gameState?.characters || [];
relationshipsHtml = `
${
Object.entries(character.relationships).map(([charId, value]) => {
const relatedChar = characters.find(c => c.id === charId);
if (!relatedChar) return '';
Aéius Cercle: Sorry, looks like the ui-manager.js file is too long/big; re-code it with more modularisation and split it apart into smaller file-sizes; let's keep all file-sizes from here onwards small enough so as to not go over 9000 characters. OK?
Claude: You're right, the ui-manager.js file is too large. I'll split it into smaller modules with proper organization. Let's create a modular UI system where each component is in its own file and stays under 9,000 characters.
</>
ui-manager.js - Main UI Manager Module
Click to open code
// ui-manager.js
// Main module for the UI Manager system
import { UIInitializer } from './ui/ui-initializer.js';
import { UICharacterRenderer } from './ui/ui-character-renderer.js';
import { UIResourceRenderer } from './ui/ui-resource-renderer.js';
import { UIBuildingRenderer } from './ui/ui-building-renderer.js';
import { UITechnologyRenderer } from './ui/ui-technology-renderer.js';
import { UIEventRenderer } from './ui/ui-event-renderer.js';
import { UIWorldRenderer } from './ui/ui-world-renderer.js';
import { UIModalManager } from './ui/ui-modal-manager.js';
// UIManager - Main facade for the UI system
const UIManager = (function() {
// Initialize the UI
function init() {
console.log('Initializing UI Manager');
// Initialize the UI structure
UIInitializer.init();
// Set up event listeners
setupEventListeners();
}
// Set up global event listeners
function setupEventListeners() {
// Listen for region clicks
document.addEventListener('region-clicked', (event) => {
const regionData = event.detail;
UIWorldRenderer.displayRegionDetails(regionData.region);
});
// Next turn button
const nextTurnBtn = document.getElementById('next-turn-btn');
if (nextTurnBtn) {
nextTurnBtn.addEventListener('click', () => {
// Get game instance and advance turn
const game = window.AICivilizationGame;
if (game) {
game.advanceTurn();
}
});
}
// Save game button
const saveGameBtn = document.getElementById('save-game-btn');
if (saveGameBtn) {
saveGameBtn.addEventListener('click', () => {
const game = window.AICivilizationGame;
if (game) {
game.saveGame();
}
});
}
}
// Render all UI components
function renderAll(gameState) {
if (!gameState) return;
// Update turn info
updateTurnInfo(gameState.turn);
// Render each component
UICharacterRenderer.renderCharacters(gameState.characters);
UIResourceRenderer.renderResources(gameState.resources);
UIBuildingRenderer.renderBuildings(gameState.buildings);
UITechnologyRenderer.renderTechnologies(gameState.technologies, gameState.currentResearch);
UIEventRenderer.renderEvents(gameState.events.slice(-5)); // Show latest 5 events
UIWorldRenderer.updateWorldInfo(gameState.world);
}
// Update turn information
function updateTurnInfo(turn) {
const turnInfoElement = document.getElementById('turn-info');
if (turnInfoElement) {
turnInfoElement.textContent = `Turn: ${turn}`;
}
}
// Show a notification message
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.innerHTML = `
×
`;
// Add to document
const notificationContainer = document.getElementById('notification-container') ||
createNotificationContainer();
notificationContainer.appendChild(notification);
// Add close button functionality
const closeBtn = notification.querySelector('.notification-close');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
notification.classList.add('fade-out');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
});
}
// Auto-remove after 5 seconds
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => {
if (notification.parentNode) {
notification.parentNode.removeChild(notification);
}
}, 300);
}, 5000);
}
// Create notification container if it doesn't exist
function createNotificationContainer() {
const container = document.createElement('div');
container.id = 'notification-container';
document.body.appendChild(container);
return container;
}
// Expose public API
return {
init,
renderCharacters: UICharacterRenderer.renderCharacters,
renderResources: UIResourceRenderer.renderResources,
renderBuildings: UIBuildingRenderer.renderBuildings,
renderTechnologies: UITechnologyRenderer.renderTechnologies,
renderEvents: UIEventRenderer.renderEvents,
renderAll,
showNotification,
showModal: UIModalManager.showModal,
closeModal: UIModalManager.closeModal
};
})();
export { UIManager };
</>
ui-initializer.js - UI Initialization and Structure
Click to open code
// ui-initializer.js
// Handles creating and setting up the initial UI structure
// UIInitializer - Creates the base structure for the UI
const UIInitializer = (function() {
// Initialize the UI structure
function init() {
console.log('Initializing UI structure');
// Create main game container if not exists
createGameContainer();
// Add game styles
addGameStyles();
}
// Create the main game container and structure
function createGameContainer() {
let gameContainer = document.getElementById('game-container');
if (!gameContainer) {
gameContainer = document.createElement('div');
gameContainer.id = 'game-container';
gameContainer.className = 'game-container';
// Create game layout
const layout = `
AI Civilization Game
Turn: 0
World
Resources
Characters
World Map
Buildings
Technologies
Events
`;
gameContainer.innerHTML = layout;
document.body.appendChild(gameContainer);
}
return gameContainer;
}
// Add basic CSS styles for the game
function addGameStyles() {
// Check if styles already exist
if (document.getElementById('ai-civ-game-styles')) {
return;
}
const styleElement = document.createElement('style');
styleElement.id = 'ai-civ-game-styles';
styleElement.type = 'text/css';
const styles = `
.game-container {
font-family: Arial, sans-serif;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
}
.game-content {
display: grid;
grid-template-columns: 250px 1fr 250px;
gap: 20px;
margin-bottom: 20px;
}
.left-panel, .right-panel {
display: flex;
flex-direction: column;
gap: 20px;
}
.panel {
background-color: white;
border-radius: 5px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
padding: 15px;
}
.panel h2 {
margin-top: 0;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
font-size: 18px;
}
.container-content {
max-height: 300px;
overflow-y: auto;
}
#world-map {
border: 1px solid #ccc;
background-color: #eaf6ff;
display: block;
margin: 10px 0;
max-width: 100%;
}
#map-controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
}
#map-controls button {
width: 40px;
height: 40px;
border-radius: 5px;
border: 1px solid #ccc;
background-color: white;
cursor: pointer;
font-size: 16px;
}
#map-controls button:hover {
background-color: #f0f0f0;
}
.game-footer {
display: flex;
justify-content: center;
gap: 20px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
.game-footer button {
padding: 10px 20px;
border-radius: 5px;
border: none;
background-color: #4c84ff;
color: white;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s;
}
.game-footer button:hover {
background-color: #3a66cc;
}
/* Cards and items styles */
.character-card, .building-card, .technology-card {
background-color: #f9f9f9;
border: 1px solid #eee;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
}
.character-card h3, .building-card h3, .technology-card h3 {
margin-top: 0;
font-size: 16px;
}
.resource-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #eee;
}
.event-item {
padding: 8px;
border-left: 4px solid #4c84ff;
background-color: #f5f9ff;
margin-bottom: 8px;
font-size: 14px;
}
.event-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-weight: bold;
}
/* Progress bars */
.progress-bar {
height: 8px;
background-color: #eee;
border-radius: 4px;
overflow: hidden;
margin: 5px 0;
}
.progress-fill {
height: 100%;
background-color: #4c84ff;
border-radius: 4px;
}
/* Modal dialog styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
overflow-y: auto;
}
.modal-content {
background-color: white;
margin: 50px auto;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 800px;
position: relative;
max-height: 80vh;
overflow-y: auto;
}
.close-btn {
position: absolute;
top: 10px;
right: 15px;
font-size: 24px;
cursor: pointer;
}
.modal-actions {
margin-top: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.modal-actions button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
}
.modal-actions button.primary {
background-color: #4c84ff;
color: white;
}
.modal-actions button:not(.primary) {
background-color: #f0f0f0;
color: #333;
}
/* Notification styles */
#notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1100;
display: flex;
flex-direction: column;
gap: 10px;
}
.notification {
background-color: white;
border-radius: 5px;
padding: 15px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
max-width: 300px;
animation: slide-in 0.3s ease-out;
}
.notification.info {
border-left: 4px solid #4c84ff;
}
.notification.success {
border-left: 4px solid #4CAF50;
}
.notification.warning {
border-left: 4px solid #FF9800;
}
.notification.error {
border-left: 4px solid #F44336;
}
.notification-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.notification-close {
cursor: pointer;
font-size: 18px;
margin-left: 10px;
}
.notification.fade-out {
animation: fade-out 0.3s ease-out forwards;
}
@keyframes slide-in {
0% { transform: translateX(100%); opacity: 0; }
100% { transform: translateX(0); opacity: 1; }
}
@keyframes fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
`;
styleElement.appendChild(document.createTextNode(styles));
document.head.appendChild(styleElement);
}
// Create a container for a UI section (utility function)
function createContainer(id, title) {
const container = document.createElement('div');
container.id = id;
container.className = 'panel';
container.innerHTML = `${title}
`;
return container;
}
// Expose the public API
return {
init,
createContainer
};
})();
export { UIInitializer };
</>
ui-character-renderer.js - Character UI Rendering
Click to open code
// ui-initializer.js
// Handles creating and setting up the initial UI structure
// UIInitializer - Creates the base structure for the UI
const UIInitializer = (function() {
// Initialize the UI structure
function init() {
console.log('Initializing UI structure');
// Create main game container if not exists
createGameContainer();
// Add game styles
addGameStyles();
}
// Create the main game container and structure
function createGameContainer() {
let gameContainer = document.getElementById('game-container');
if (!gameContainer) {
gameContainer = document.createElement('div');
gameContainer.id = 'game-container';
gameContainer.className = 'game-container';
// Create game layout
const layout = `
AI Civilization Game
Turn: 0
World
Resources
Characters
World Map
Buildings
Technologies
Events
`;
gameContainer.innerHTML = layout;
document.body.appendChild(gameContainer);
}
return gameContainer;
}
// Add basic CSS styles for the game
function addGameStyles() {
// Check if styles already exist
if (document.getElementById('ai-civ-game-styles')) {
return;
}
const styleElement = document.createElement('style');
styleElement.id = 'ai-civ-game-styles';
styleElement.type = 'text/css';
const styles = `
.game-container {
font-family: Arial, sans-serif;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 10px;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
}
.game-content {
display: grid;
grid-template-columns: 250px 1fr 250px;
gap: 20px;
margin-bottom: 20px;
}
.left-panel, .right-panel {
display: flex;
flex-direction: column;
gap: 20px;
}
.panel {
background-color: white;
border-radius: 5px;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1);
padding: 15px;
}
.panel h2 {
margin-top: 0;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
font-size: 18px;
}
.container-content {
max-height: 300px;
overflow-y: auto;
}
#world-map {
border: 1px solid #ccc;
background-color: #eaf6ff;
display: block;
margin: 10px 0;
max-width: 100%;
}
#map-controls {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
}
#map-controls button {
width: 40px;
height: 40px;
border-radius: 5px;
border: 1px solid #ccc;
background-color: white;
cursor: pointer;
font-size: 16px;
}
#map-controls button:hover {
background-color: #f0f0f0;
}
.game-footer {
display: flex;
justify-content: center;
gap: 20px;
padding-top: 10px;
border-top: 1px solid #ddd;
}
.game-footer button {
padding: 10px 20px;
border-radius: 5px;
border: none;
background-color: #4c84ff;
color: white;
font-weight: bold;
cursor: pointer;
transition: background-color 0.2s;
}
.game-footer button:hover {
background-color: #3a66cc;
}
/* Cards and items styles */
.character-card, .building-card, .technology-card {
background-color: #f9f9f9;
border: 1px solid #eee;
border-radius: 5px;
padding: 10px;
margin-bottom: 10px;
}
.character-card h3, .building-card h3, .technology-card h3 {
margin-top: 0;
font-size: 16px;
}
.resource-item {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
padding: 5px;
border-bottom: 1px solid #eee;
}
.event-item {
padding: 8px;
border-left: 4px solid #4c84ff;
background-color: #f5f9ff;
margin-bottom: 8px;
font-size: 14px;
}
.event-header {
display: flex;
justify-content: space-between;
margin-bottom: 5px;
font-weight: bold;
}
/* Progress bars */
.progress-bar {
height: 8px;
background-color: #eee;
border-radius: 4px;
overflow: hidden;
margin: 5px 0;
}
.progress-fill {
height: 100%;
background-color: #4c84ff;
border-radius: 4px;
}
/* Modal dialog styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
overflow-y: auto;
}
.modal-content {
background-color: white;
margin: 50px auto;
padding: 20px;
border-radius: 8px;
width: 80%;
max-width: 800px;
position: relative;
max-height: 80vh;
overflow-y: auto;
}
.close-btn {
position: absolute;
top: 10px;
right: 15px;
font-size: 24px;
cursor: pointer;
}
.modal-actions {
margin-top: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.modal-actions button {
padding: 8px 16px;
border-radius: 4px;
border: none;
cursor: pointer;
}
.modal-actions button.primary {
background-color: #4c84ff;
color: white;
}
.modal-actions button:not(.primary) {
background-color: #f0f0f0;
color: #333;
}
/* Notification styles */
#notification-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 1100;
display: flex;
flex-direction: column;
gap: 10px;
}
.notification {
background-color: white;
border-radius: 5px;
padding: 15px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
max-width: 300px;
animation: slide-in 0.3s ease-out;
}
.notification.info {
border-left: 4px solid #4c84ff;
}
.notification.success {
border-left: 4px solid #4CAF50;
}
.notification.warning {
border-left: 4px solid #FF9800;
}
.notification.error {
border-left: 4px solid #F44336;
}
.notification-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.notification-close {
cursor: pointer;
font-size: 18px;
margin-left: 10px;
}
.notification.fade-out {
animation: fade-out 0.3s ease-out forwards;
}
@keyframes slide-in {
0% { transform: translateX(100%); opacity: 0; }
100% { transform: translateX(0); opacity: 1; }
}
@keyframes fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
`;
styleElement.appendChild(document.createTextNode(styles));
document.head.appendChild(styleElement);
}
// Create a container for a UI section (utility function)
function createContainer(id, title) {
const container = document.createElement('div');
container.id = id;
container.className = 'panel';
container.innerHTML = `${title}
`;
return container;
}
// Expose the public API
return {
init,
createContainer
};
})();
export { UIInitializer };
</>
ui-resource-renderer.js - Resource UI Rendering
Click to open code
// ui-resource-renderer.js
// Handles rendering resource information to the UI
// UIResourceRenderer - Renders resources to the UI
const UIResourceRenderer = (function() {
// Resource icons mapping
const resourceIcons = {
food: '🌾',
materials: '🪵',
knowledge: '📚',
wealth: '💰'
};
// Render resources to the UI
function renderResources(resources) {
const resourcesContainer = document.getElementById('resources-container');
if (!resourcesContainer) return;
const contentDiv = resourcesContainer.querySelector('.container-content') || resourcesContainer;
// Clear existing content
contentDiv.innerHTML = '';
const resourcesList = contentDiv.querySelector('.resources-list');
// Create resource items
for (const resource in resources) {
const resourceItem = document.createElement('div');
resourceItem.className = 'resource-item';
// Get resource icon
const icon = resourceIcons[resource] || '📦';
resourceItem.innerHTML = `
${icon} ${resource.charAt(0).toUpperCase() + resource.slice(1)}
${resources[resource]}
`;
resource
Aéius Cercle: Re-code resuming from ui-resource-renderer.js
Claude: I'll continue with the ui-resource-renderer.js file and then proceed with the remaining UI modules.
</>
ui-resource-renderer.js - Resource UI Rendering
Click to open code
// ui-resource-renderer.js
// Handles rendering resource information to the UI
// UIResourceRenderer - Renders resources to the UI
const UIResourceRenderer = (function() {
// Resource icons mapping
const resourceIcons = {
food: '🌾',
materials: '🪵',
knowledge: '📚',
wealth: '💰'
};
// Render resources to the UI
function renderResources(resources) {
const resourcesContainer = document.getElementById('resources-container');
if (!resourcesContainer) return;
const contentDiv = resourcesContainer.querySelector('.container-content') || resourcesContainer;
// Clear existing content
contentDiv.innerHTML = '';
const resourcesList = contentDiv.querySelector('.resources-list');
// Create resource items
for (const resource in resources) {
const resourceItem = document.createElement('div');
resourceItem.className = 'resource-item';
// Get resource icon
const icon = resourceIcons[resource] || '📦';
resourceItem.innerHTML = `
${icon} ${resource.charAt(0).toUpperCase() + resource.slice(1)}
${resources[resource]}
`;
resourcesList.appendChild(resourceItem);
}
// Add production rates if available
const gameState = window.AICivilizationGame?.getGameState();
if (gameState && gameState.productionRates) {
const productionHeader = document.createElement('h3');
productionHeader.textContent = 'Production Per Turn';
contentDiv.appendChild(productionHeader);
const productionList = document.createElement('div');
productionList.className = 'production-list';
for (const resource in gameState.productionRates) {
const rate = gameState.productionRates[resource];
if (rate === 0) continue; // Skip zero production rates
const productionItem = document.createElement('div');
productionItem.className = 'production-item';
const icon = resourceIcons[resource] || '📦';
const sign = rate > 0 ? '+' : '';
productionItem.innerHTML = `
${icon} ${resource.charAt(0).toUpperCase() + resource.slice(1)}
${sign}${rate}/turn
`;
productionList.appendChild(productionItem);
}
contentDiv.appendChild(productionList);
}
}
// Show detailed resource information dialog
function showResourceDetailsDialog() {
const gameState = window.AICivilizationGame?.getGameState();
if (!gameState) return;
const resources = gameState.resources;
const productionRates = gameState.productionRates || {};
const consumptionRates = gameState.consumptionRates || {};
// Calculate net rates
const netRates = {};
for (const resource in productionRates) {
netRates[resource] = (productionRates[resource] || 0) - (consumptionRates[resource] || 0);
}
// Generate table rows for each resource
let resourceRows = '';
for (const resource in resources) {
const icon = resourceIcons[resource] || '📦';
const production = productionRates[resource] || 0;
const consumption = consumptionRates[resource] || 0;
const net = netRates[resource] || 0;
const netClass = net > 0 ? 'positive' : (net < 0 ? 'negative' : '');
const netSign = net > 0 ? '+' : '';
resourceRows += `
${icon} ${resource.charAt(0).toUpperCase() + resource.slice(1)}
${resources[resource]}
+${production}
-${consumption}
${netSign}${net}
${calculateTurnsUntil(resources[resource], net, 100)}
`;
}
// Create the content for the modal
const content = `
Current resource levels and production rates:
Resource
Current
Production
Consumption
Net
Turns to 100
${resourceRows}
Note: Net values show the change per turn. Positive values indicate growth, negative values indicate depletion.
"Turns to 100" shows how many turns until you reach 100 of that resource at the current rate (∞ means it will never reach 100).
`;
// Show the modal (using UIModalManager)
if (window.UIModalManager) {
window.UIModalManager.showModal({
title: 'Resource Details',
content: content,
size: 'medium'
});
} else {
console.error('UIModalManager not available');
}
}
// Calculate how many turns until a resource reaches a target value
function calculateTurnsUntil(current, netRate, target) {
if (netRate <= 0 && current < target) {
return '∞'; // Will never reach target
}
if (current >= target) {
return 'Now';
}
const turns = Math.ceil((target - current) / netRate);
return turns;
}
// Expose the public API
return {
renderResources,
showResourceDetailsDialog
};
})();
export { UIResourceRenderer };
</>
ui-building-renderer.js - Building UI Rendering
Click to open code
// ui-building-renderer.js
// Handles rendering building information to the UI
import { UIModalManager } from './ui-modal-manager.js';
// UIBuildingRenderer - Renders buildings to the UI
const UIBuildingRenderer = (function() {
// Render buildings to the UI
function renderBuildings(buildings) {
const buildingsContainer = document.getElementById('buildings-container');
if (!buildingsContainer) return;
const contentDiv = buildingsContainer.querySelector('.container-content') || buildingsContainer;
// Clear existing content
contentDiv.innerHTML = '';
// Get construction projects in progress
const gameState = window.AICivilizationGame?.getGameState();
const constructionProjects = gameState?.buildingsInProgress || [];
// Display construction projects first
if (constructionProjects.length > 0) {
const constructionHeader = document.createElement('h3');
constructionHeader.textContent = 'Under Construction';
contentDiv.appendChild(constructionHeader);
constructionProjects.forEach(project => {
const constructionItem = document.createElement('div');
constructionItem.className = 'construction-item';
constructionItem.innerHTML = `
${project.buildingName}
Region: ${getRegionName(project.regionId, gameState)}
${Math.round((project.progress / project.totalWork) * 100)}% complete
`;
contentDiv.appendChild(constructionItem);
});
}
// Display existing buildings
if (buildings.length > 0) {
const buildingsHeader = document.createElement('h3');
buildingsHeader.textContent = 'Completed Buildings';
contentDiv.appendChild(buildingsHeader);
// Group buildings by type
const buildingsByType = groupBuildingsByType(buildings);
// Create building cards
for (const type in buildingsByType) {
const typeBuildings = buildingsByType[type];
// Create a collapsed group for each type
const groupElement = document.createElement('div');
groupElement.className = 'building-group';
groupElement.innerHTML = `
${type} (${typeBuildings.length})
`;
const groupContent = groupElement.querySelector('.building-group-content');
// Add building items to the group
typeBuildings.forEach(building => {
const buildingCard = document.createElement('div');
buildingCard.className = 'building-card';
buildingCard.setAttribute('data-id', building.id);
// Determine condition class
let conditionClass = 'good';
if (building.condition < 50) conditionClass = 'poor';
else if (building.condition < 75) conditionClass = 'fair';
buildingCard.innerHTML = `
${building.name}
Location: ${getRegionName(building.regionId, gameState)}
Efficiency: ${Math.round(building.efficiency * 100)}%
Condition: ${Math.round(building.condition)}%
`;
groupContent.appendChild(buildingCard);
// Add click event for repair
const repairBtn = buildingCard.querySelector('.repair-building-btn');
if (repairBtn) {
repairBtn.addEventListener('click', () => {
// Call building repair function
repairBuilding(building.id);
});
}
// Add click event for building details
const detailsBtn = buildingCard.querySelector('.building-details-btn');
if (detailsBtn) {
detailsBtn.addEventListener('click', () => {
showBuildingDetailsDialog(building);
});
}
});
contentDiv.appendChild(groupElement);
// Add toggle functionality for the group
const toggleButton = groupElement.querySelector('.toggle-group');
toggleButton.addEventListener('click', () => {
const content = groupElement.querySelector('.building-group-content');
const isVisible = content.style.display !== 'none';
content.style.display = isVisible ? 'none' : 'block';
toggleButton.textContent = isVisible ? '▶' : '▼';
});
}
}
// Add "Construct New Building" button
const constructButton = document.createElement('button');
constructButton.className = 'new-building-btn';
constructButton.textContent = 'Construct New Building';
constructButton.addEventListener('click', () => {
showConstructionDialog();
});
contentDiv.appendChild(constructButton);
}
// Group buildings by their type
function groupBuildingsByType(buildings) {
const groups = {};
buildings.forEach(building => {
const type = building.type || 'Unknown';
if (!groups[type]) {
groups[type] = [];
}
groups[type].push(building);
});
return groups;
}
// Get region name from ID
function getRegionName(regionId, gameState) {
if (!gameState || !gameState.world || !gameState.world.regions) {
return regionId;
}
const region = gameState.world.regions.find(r => r.id === regionId);
return region ? region.name : regionId;
}
// Show dialog for constructing a new building
function showConstructionDialog() {
const gameState = window.AICivilizationGame?.getGameState();
const BuildingManager = window.BuildingManager;
if (!gameState || !BuildingManager) {
console.error('Game state or BuildingManager not available');
return;
}
// Get available buildings
const availableBuildings = BuildingManager.getAvailableBuildings(gameState);
// Get explored regions for building location
const exploredRegions = gameState.world.regions.filter(region => region.explored);
// Generate building options HTML
let buildingOptionsHtml = '';
if (availableBuildings.length === 0) {
buildingOptionsHtml = 'No buildings available for construction at this time.
';
} else {
// Group by category
const buildingsByCategory = {};
availableBuildings.forEach(building => {
const category = building.category || 'Other';
if (!buildingsByCategory[category]) {
buildingsByCategory[category] = [];
}
buildingsByCategory[category].push(building);
});
// Generate HTML for each category
for (const category in buildingsByCategory) {
buildingOptionsHtml += `
${category.charAt(0).toUpperCase() + category.slice(1)}
`;
}
}
// Generate region options HTML
let regionOptionsHtml = '';
if (exploredRegions.length === 0) {
regionOptionsHtml = 'No explored regions available for construction.
';
} else {
regionOptionsHtml = `
`;
}
// Create the content for the modal
const content = `
${regionOptionsHtml}
${buildingOptionsHtml}
`;
// Show the modal
const modal = UIModalManager.showModal({
title: 'Construct New Building',
content: content,
size: 'large',
onClose: () => {
console.log('Construction dialog closed');
}
});
// Add event listeners for construction buttons
const constructButtons = modal.querySelectorAll('.construct-btn:not([disabled])');
constructButtons.forEach(btn => {
btn.addEventListener('click', () => {
const buildingId = btn.getAttribute('data-building-id');
const regionDropdown = modal.querySelector('#region-dropdown');
const regionId = regionDropdown ? regionDropdown.value : null;
if (buildingId && regionId) {
console.log(`Constructing building ${buildingId} in region ${regionId}`);
BuildingManager.constructBuilding(buildingId, gameState, regionId);
UIModalManager.closeModal();
}
});
});
}
// Show building details dialog
function showBuildingDetailsDialog(building) {
const gameState = window.AICivilizationGame?.getGameState();
const BuildingManager = window.BuildingManager;
if (!gameState || !BuildingManager) {
console.error('Game state or BuildingManager not available');
return;
}
// Get building details and region
const buildingType = BuildingManager.getBuildingDetails(building.type);
const region = gameState.world.regions.find(r => r.id === building.regionId);
if (!buildingType || !region) {
console.error('Building type or region not found');
return;
}
// Determine condition status
let conditionStatus = 'Excellent';
let conditionClass = 'good';
if (building.condition < 25) {
conditionStatus = 'Critical';
conditionClass = 'critical';
} else if (building.condition < 50) {
conditionStatus = 'Poor';
conditionClass = 'poor';
} else if (building.condition < 75) {
conditionStatus = 'Fair';
conditionClass = 'fair';
}
// Calculate maintenance costs per turn
let maintenanceHtml = 'No maintenance required.
';
if (buildingType.maintenance) {
maintenanceHtml = `
${Object.entries(buildingType.maintenance).map(([resource, amount]) => `
${resource}: ${amount}/turn
`).join('')}
`;
}
// Format the building's effects
let effectsHtml = 'No active effects.
';
if (buildingType.effects) {
effectsHtml = `
${Object.entries(buildingType.effects).map(([effect, value]) => {
// Calculate actual value with efficiency
const actualValue = typeof value === 'number' ? value * building.efficiency : value;
return `
${effect.replace(/_/g, ' ')}:
${typeof actualValue === 'number' ?
(actualValue > 0 ? '+' : '') + actualValue.toFixed(2) :
actualValue}
`;
}).join('')}
`;
}
// Create the content for the modal
const content = `
${building.name}
${buildingType.description}
Located in: ${region.name}
Built on turn: ${building.built}
Status
Condition: ${Math.round(building.condition)}% (${conditionStatus})
Efficiency: ${Math.round(building.efficiency * 100)}%
Maintenance
${maintenanceHtml}
Effects
${effectsHtml}
${building.condition < 95 ?
`` :
``
}
`;
// Show the modal
const modal = UIModalManager.showModal({
title: 'Building Details',
content: content,
onClose: () => {
console.log('Building details dialog closed');
}
});
// Add event listener for repair button
const repairBtn = modal.querySelector('.repair-btn');
if (repairBtn) {
repairBtn.addEventListener('click', () => {
const buildingId = repairBtn.getAttribute('data-building-id');
repairBuilding(buildingId);
UIModalManager.closeModal();
});
}
}
// Repair a building
function repairBuilding(buildingId) {
const BuildingManager = window.BuildingManager;
if (!BuildingManager) {
console.error('BuildingManager not available');
return;
}
console.log(`Repairing building ${buildingId}`);
BuildingManager.repairBuilding(buildingId);
}
// Expose the public API
return {
renderBuildings,
showConstructionDialog,
showBuildingDetailsDialog,
repairBuilding
};
})();
export { UIBuildingRenderer };
</>
ui-technology-renderer.js - Technology UI Rendering
Click to open code
// ui-technology-renderer.js
// Handles rendering technology information to the UI
import { UIModalManager } from './ui-modal-manager.js';
// UITechnologyRenderer - Renders technologies to the UI
const UITechnologyRenderer = (function() {
// Render technologies to the UI
function renderTechnologies(technologies, currentResearch) {
const technologiesContainer = document.getElementById('technologies-container');
if (!technologiesContainer) return;
const contentDiv = technologiesContainer.querySelector('.container-content') || technologiesContainer;
// Clear existing content
contentDiv.innerHTML = '';
// Create current research section if there's research in progress
if (currentResearch) {
const currentResearchElement = document.createElement('div');
currentResearchElement.className = 'current-research';
// Calculate progress percentage
const progressPercent = Math.min(100, Math.round((currentResearch.progress / currentResearch.researchTime) * 100));
currentResearchElement.innerHTML = `
Current Research
${currentResearch.name}
${progressPercent}% complete
${currentResearch.description}
Difficulty: ${currentResearch.difficulty}
Remaining: ~${Math.ceil(currentResearch.researchTime - currentResearch.progress)} turns
`;
contentDiv.appendChild(currentResearchElement);
}
// Create researched technologies section
if (technologies.length > 0) {
const researchedElement = document.createElement('div');
researchedElement.className = 'researched-technologies';
// Group technologies by category
const techByCategory = {};
technologies.forEach(tech => {
const category = tech.category || 'uncategorized';
if (!techByCategory[category]) {
techByCategory[category] = [];
}
techByCategory[category].push(tech);
});
// Generate category sections
for (const category in techByCategory) {
const categoryElement = document.createElement('div');
categoryElement.className = 'tech-category';
categoryElement.innerHTML = `
${category.charAt(0).toUpperCase() + category.slice(1)}
`;
const categoryContent = categoryElement.querySelector('.tech-category-content');
// Add technology cards to this category
techByCategory[category].forEach(tech => {
const techCard = document.createElement('div');
techCard.className = 'technology-card';
techCard.setAttribute('data-id', tech.id);
// Format the effects list
let effectsHtml = '';
if (tech.effects) {
effectsHtml = `
${Object.entries(tech.effects).map(([effect, value]) => {
if (effect === 'enables') {
return `Enables: ${Array.isArray(value) ? value.join(', ') : value}`;
} else {
return `${effect.replace(/_/g, ' ')}: ${typeof value === 'number' ? (value > 0 ? '+' : '') + (value * 100).toFixed(0) + '%' : value}`;
}
}).join('')}
`;
}
techCard.innerHTML = `
${tech.name}
Researched on turn ${tech.completedTurn}
${effectsHtml}
`;
categoryContent.appendChild(techCard);
// Add click event for tech details
const detailsBtn = techCard.querySelector('.tech-details-btn');
if (detailsBtn) {
detailsBtn.addEventListener('click', () => {
showTechnologyDetailsDialog(tech);
});
}
});
researchedElement.appendChild(categoryElement);
}
contentDiv.appendChild(researchedElement);
}
// Add "Research New Technology" button
const researchButton = document.createElement('button');
researchButton.className = 'research-btn';
researchButton.textContent = 'Research New Technology';
researchButton.disabled = !!currentResearch; // Disable if already researching
researchButton.addEventListener('click', () => {
showResearchDialog();
});
contentDiv.appendChild(researchButton);
}
// Show dialog for researching a new technology
function showResearchDialog() {
const gameState = window.AICivilizationGame?.getGameState();
const TechnologyManager = window.TechnologyManager;
if (!gameState || !TechnologyManager) {
console.error('Game state or TechnologyManager not available');
return;
}
// Get available technologies
const availableTechs = TechnologyManager.getAvailableTechnologies(gameState);
// Group technologies by tier
const techsByTier = {};
availableTechs.forEach(tech => {
if (!techsByTier[tech.tier]) {
techsByTier[tech.tier] = [];
}
techsByTier[tech.tier].push(tech);
});
// Generate dialog content
let techOptionsHtml = '';
if (availableTechs.length === 0) {
techOptionsHtml = 'No technologies available for research at this time.
';
} else {
// Sort tiers
const tiers = Object.keys(techsByTier).sort((a, b) => a - b);
// Generate HTML for each tier
tiers.forEach(tier => {
techOptionsHtml += `
Massive Blank-Space Added For Code-Clarity/Visibility