diff --git a/ai.js b/ai.js index 4d383f5..61f358b 100644 --- a/ai.js +++ b/ai.js @@ -263,6 +263,9 @@ function assignNewTask(cit) { case "Soldier": soldierTasks(cit); break; + case "Planner": + plannerTasks(cit); + break; default: cit.task = null; cit.target = null; @@ -385,9 +388,181 @@ function soldierTasks(cit) { builderTasks(cit); } +function plannerTasks(cit) { + // Check if it's time to plan a new building + if(Math.random() < 0.05) { + cit.task = "planBuilding"; + cit.target = null; + return; + } + + // Help with other tasks when not planning + if(Math.random() < 0.5) { + builderTasks(cit); + } else { + // Explore the map to find good building locations + cit.task = "explore"; + cit.target = { + x: randInt(-500, 500), + y: randInt(-500, 500) + }; + } +} + /********************************************************************** * NEW TASK HANDLERS **********************************************************************/ +function planBuildingTask(cit) { + // Find a good spot to place a new building + let buildingType = null; + let buildingCost = 0; + + // Decide what type of building to place based on current needs + const houseCount = buildings.filter(b => b.buildingType === "House" && b.completed).length; + const marketCount = buildings.filter(b => b.buildingType === "Market" && b.completed).length; + const hospitalCount = buildings.filter(b => b.buildingType === "Hospital" && b.completed).length; + const schoolCount = buildings.filter(b => b.buildingType === "School" && b.completed).length; + + // Prioritize based on what the city needs + if (houseCount < 3 || Math.random() < 0.5) { + buildingType = "House"; + buildingCost = COST_HOUSE; + } else if (marketCount < 1 && money >= COST_MARKET) { + buildingType = "Market"; + buildingCost = COST_MARKET; + } else if (hospitalCount < 1 && money >= COST_HOSPITAL) { + buildingType = "Hospital"; + buildingCost = COST_HOSPITAL; + } else if (schoolCount < 1 && money >= COST_SCHOOL) { + buildingType = "School"; + buildingCost = COST_SCHOOL; + } else if (Math.random() < 0.3) { + buildingType = "Road"; + buildingCost = COST_ROAD; + } else { + buildingType = "House"; + buildingCost = COST_HOUSE; + } + + // Check if we can afford it + if (money < buildingCost) { + cit.task = null; + return; + } + + // Find a good location - away from other buildings but not too far from city center + let bestX = 0, bestY = 0; + let bestScore = -Infinity; + + for (let attempt = 0; attempt < 10; attempt++) { + // Try to find locations expanding outward from existing buildings + let existingBuilding = buildings[Math.floor(Math.random() * buildings.length)]; + if (!existingBuilding) { + // If no buildings yet, start near center + existingBuilding = { x: 0, y: 0 }; + } + + const direction = Math.random() * Math.PI * 2; + const distance = randInt(100, 300); + const testX = existingBuilding.x + Math.cos(direction) * distance; + const testY = existingBuilding.y + Math.sin(direction) * distance; + + // Skip if not valid placement + if (!isValidPlacement(testX, testY)) continue; + + // Calculate score based on: + // - Distance from other buildings (not too close) + // - Distance from center (not too far) + // - Terrain type + + let minDistToBuilding = Infinity; + buildings.forEach(b => { + const dist = distance(testX, testY, b.x, b.y); + if (dist < minDistToBuilding) minDistToBuilding = dist; + }); + + const distToCenter = distance(testX, testY, 0, 0); + + // Score calculation - higher is better + let score = 0; + + // Prefer some distance from other buildings + if (minDistToBuilding < 50) score -= 100; + else if (minDistToBuilding < 100) score += minDistToBuilding - 50; + else score += 50; + + // Prefer not too far from center + score -= distToCenter * 0.1; + + if (score > bestScore) { + bestScore = score; + bestX = testX; + bestY = testY; + } + } + + // If we found a good spot, place the building + if (bestScore > -Infinity) { + addMoney(-buildingCost, `Planner: ${buildingType}`); + + let newBuilding; + switch (buildingType) { + case "House": + newBuilding = createHouseSite(bestX, bestY); + break; + case "Road": + // Find nearest building to connect to + let nearest = null; + let minDist = Infinity; + buildings.forEach(b => { + if (b.buildingType !== "Road") { + const dist = distance(bestX, bestY, b.x, b.y); + if (dist < minDist) { + minDist = dist; + nearest = b; + } + } + }); + + if (nearest) { + newBuilding = createRoadSite(bestX, bestY, nearest.x, nearest.y); + } else { + newBuilding = createRoadSite(bestX - 50, bestY, bestX + 50, bestY); + } + break; + case "Market": + newBuilding = createMarketSite(bestX, bestY); + break; + case "Hospital": + newBuilding = createHospitalSite(bestX, bestY); + break; + case "School": + newBuilding = createSchoolSite(bestX, bestY); + break; + } + + buildings.push(newBuilding); + logAction(`${cit.name} [Planner] placed a new ${buildingType} site at (${Math.floor(bestX)}, ${Math.floor(bestY)})`); + } + + cit.task = null; +} + +function exploreTask(cit) { + if (!cit.target) { + cit.task = null; + return; + } + + moveToward(cit, cit.target.x, cit.target.y, 0.5); + + // When reached exploration point, find a new task + if (distance(cit.x, cit.y, cit.target.x, cit.target.y) < 10) { + cit.task = null; + cit.target = null; + } +} + function huntWolfTask(cit) { let wolf = cit.target; if(!wolf || wolf.dead) { diff --git a/events.js b/events.js index f2baf13..dcb526e 100644 --- a/events.js +++ b/events.js @@ -96,6 +96,11 @@ function setupBuyButtons() { logAction("Click on map to place a new Soldier citizen."); }); + document.getElementById('buyPlannerBtn').addEventListener('click', () => { + purchaseMode = "Planner"; + logAction("Click on map to place a new Planner citizen."); + }); + document.getElementById('buyMarketBtn').addEventListener('click', () => { purchaseMode = "Market"; logAction("Click on map to place a Market site."); @@ -395,6 +400,17 @@ function setupCanvasClick() { } break; + case "Planner": + if(money >= COST_PLANNER) { + addMoney(-COST_PLANNER, "Buy Planner"); + let c = createCitizen(randomName(), worldX, worldY, "Planner"); + citizens.push(c); + logAction(`Purchased new Planner @(${Math.floor(worldX)},${Math.floor(worldY)})`); + } else { + logAction("Not enough money to buy Planner!"); + } + break; + case "Spawner": if(money >= COST_SPAWNER) { addMoney(-COST_SPAWNER, "Buy Spawner"); diff --git a/game.js b/game.js index 4bdc5f8..5d5579d 100644 --- a/game.js +++ b/game.js @@ -36,6 +36,7 @@ const COST_SCHOOL = 450; const COST_SPAWNER = 500; const COST_TREE = 50; const COST_SOLDIER = 250; +const COST_PLANNER = 1000; // Terrain costs const COST_WATER = 20; @@ -90,14 +91,14 @@ const SCHOOL_WOOD_REQUIRED = 65; const SCHOOL_BUILD_RATE = 0.12; // Professions -const PROFESSIONS = ["Farmer", "Builder", "Merchant", "Doctor", "Teacher", "Soldier"]; +const PROFESSIONS = ["Farmer", "Builder", "Merchant", "Doctor", "Teacher", "Soldier", "Planner"]; // Animals const STARTING_RABBITS = 10; -const STARTING_WOLVES = 3; +const STARTING_WOLVES = 5; const RABBIT_HUNGER_INCREMENT = 0.003; -const RABBIT_REPRO_COOLDOWN = 3000; -const RABBIT_REPRO_CHANCE = 0.0005; +const RABBIT_REPRO_COOLDOWN = 5000; +const RABBIT_REPRO_CHANCE = 0.0002; const WOLF_SPEED = 0.7; // Faster than rabbits /********************************************************************** @@ -187,6 +188,16 @@ function initWorld() { // Spawn wolves at the corners of the map spawnWolvesAtCorners(); + + // Spawn additional wolves in random locations + for(let i=0; i<5; i++) { + let x, y; + do { + x = randInt(-1500,1500); + y = randInt(-1500,1500); + } while (!isValidPlacement(x, y)); + animals.push(createAnimal("Wolf", x, y)); + } requestAnimationFrame(update); } diff --git a/index.html b/index.html index 4b798ce..2650bb4 100644 --- a/index.html +++ b/index.html @@ -194,6 +194,7 @@ +