LCOV - code coverage report
Current view: top level - simulation/ai/petra - config.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 107 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 5 0.0 %

          Line data    Source code
       1             : // These integers must be sequential
       2           0 : PETRA.DIFFICULTY_SANDBOX = 0;
       3           0 : PETRA.DIFFICULTY_VERY_EASY = 1;
       4           0 : PETRA.DIFFICULTY_EASY = 2;
       5           0 : PETRA.DIFFICULTY_MEDIUM = 3;
       6           0 : PETRA.DIFFICULTY_HARD = 4;
       7           0 : PETRA.DIFFICULTY_VERY_HARD = 5;
       8             : 
       9           0 : PETRA.Config = function(difficulty = PETRA.DIFFICULTY_MEDIUM, behavior)
      10             : {
      11           0 :         this.difficulty = difficulty;
      12             : 
      13             :         // for instance "balanced", "aggressive" or "defensive"
      14           0 :         this.behavior = behavior || "random";
      15             : 
      16             :         // debug level: 0=none, 1=sanity checks, 2=debug, 3=detailed debug, -100=serializatio debug
      17           0 :         this.debug = 0;
      18             : 
      19           0 :         this.chat = true;       // false to prevent AI's chats
      20             : 
      21           0 :         this.popScaling = 1;    // scale factor depending on the max population
      22             : 
      23           0 :         this.Military = {
      24             :                 "towerLapseTime": 90, // Time to wait between building 2 towers
      25             :                 "fortressLapseTime": 390,     // Time to wait between building 2 fortresses
      26             :                 "popForBarracks1": 25,
      27             :                 "popForBarracks2": 95,
      28             :                 "popForForge": 65,
      29             :                 "numSentryTowers": 1
      30             :         };
      31             : 
      32           0 :         this.DamageTypeImportance = {
      33             :                 "Hack": 0.085,
      34             :                 "Pierce": 0.075,
      35             :                 "Crush": 0.065,
      36             :                 "Fire": 0.095
      37             :         };
      38             : 
      39           0 :         this.Economy = {
      40             :                 "popPhase2": 38,      // How many units we want before aging to phase2.
      41             :                 "workPhase3": 65,     // How many workers we want before aging to phase3.
      42             :                 "workPhase4": 80,     // How many workers we want before aging to phase4 or higher.
      43             :                 "popForDock": 25,
      44             :                 "targetNumWorkers": 40,       // dummy, will be changed later
      45             :                 "targetNumTraders": 5,        // Target number of traders
      46             :                 "targetNumFishers": 1,        // Target number of fishers per sea
      47             :                 "supportRatio": 0.35, // fraction of support workers among the workforce
      48             :                 "provisionFields": 2
      49             :         };
      50             : 
      51             :         // Note: attack settings are set directly in attack_plan.js
      52             :         // defense
      53           0 :         this.Defense =
      54             :         {
      55             :                 "defenseRatio": { "ally": 1.4, "neutral": 1.8, "own": 2 },      // ratio of defenders/attackers.
      56             :                 "armyCompactSize": 2000,      // squared. Half-diameter of an army.
      57             :                 "armyBreakawaySize": 3500,    // squared.
      58             :                 "armyMergeSize": 1400 // squared.
      59             :         };
      60             : 
      61             :         // Additional buildings that the AI does not yet know when to build
      62             :         // and that it will try to build on phase 3 when enough resources.
      63           0 :         this.buildings =
      64             :         {
      65             :                 "default": [],
      66             :                 "athen": [
      67             :                         "structures/{civ}/gymnasium",
      68             :                         "structures/{civ}/prytaneion",
      69             :                         "structures/{civ}/theater"
      70             :                 ],
      71             :                 "brit": [],
      72             :                 "cart": [
      73             :                         "structures/{civ}/embassy_celtic",
      74             :                         "structures/{civ}/embassy_iberian",
      75             :                         "structures/{civ}/embassy_italic"
      76             :                 ],
      77             :                 "gaul": [
      78             :                         "structures/{civ}/assembly"
      79             :                 ],
      80             :                 "han": [
      81             :                         "structures/{civ}/academy"
      82             :                 ],
      83             :                 "iber": [
      84             :                         "structures/{civ}/monument"
      85             :                 ],
      86             :                 "kush": [
      87             :                         "structures/{civ}/camp_blemmye",
      88             :                         "structures/{civ}/camp_noba",
      89             :                         "structures/{civ}/pyramid_large",
      90             :                         "structures/{civ}/pyramid_small",
      91             :                         "structures/{civ}/temple_amun"
      92             :                 ],
      93             :                 "mace": [
      94             :                         "structures/{civ}/theater"
      95             :                 ],
      96             :                 "maur": [
      97             :                         "structures/{civ}/palace",
      98             :                         "structures/{civ}/pillar_ashoka"
      99             :                 ],
     100             :                 "pers": [
     101             :                         "structures/{civ}/apadana"
     102             :                 ],
     103             :                 "ptol": [
     104             :                         "structures/{civ}/library",
     105             :                         "structures/{civ}/theater"
     106             :                 ],
     107             :                 "rome": [
     108             :                         "structures/{civ}/army_camp",
     109             :                         "structures/{civ}/temple_vesta"
     110             :                 ],
     111             :                 "sele": [
     112             :                         "structures/{civ}/theater"
     113             :                 ],
     114             :                 "spart": [
     115             :                         "structures/{civ}/syssiton",
     116             :                         "structures/{civ}/theater"
     117             :                 ]
     118             :         };
     119             : 
     120           0 :         this.priorities =
     121             :         {
     122             :                 "villager": 30,      // should be slightly lower than the citizen soldier one to not get all the food
     123             :                 "citizenSoldier": 60,
     124             :                 "trader": 50,
     125             :                 "healer": 20,
     126             :                 "ships": 70,
     127             :                 "house": 350,
     128             :                 "dropsites": 200,
     129             :                 "field": 400,
     130             :                 "dock": 90,
     131             :                 "corral": 100,
     132             :                 "economicBuilding": 90,
     133             :                 "militaryBuilding": 130,
     134             :                 "defenseBuilding": 70,
     135             :                 "civilCentre": 950,
     136             :                 "majorTech": 700,
     137             :                 "minorTech": 250,
     138             :                 "wonder": 1000,
     139             :                 "emergency": 1000    // used only in emergency situations, should be the highest one
     140             :         };
     141             : 
     142             :         // Default personality (will be updated in setConfig)
     143           0 :         this.personality =
     144             :         {
     145             :                 "aggressive": 0.5,
     146             :                 "cooperative": 0.5,
     147             :                 "defensive": 0.5
     148             :         };
     149             : 
     150             :         // See PETRA.QueueManager.prototype.wantedGatherRates()
     151           0 :         this.queues =
     152             :         {
     153             :                 "firstTurn": {
     154             :                         "food": 10,
     155             :                         "wood": 10,
     156             :                         "default": 0
     157             :                 },
     158             :                 "short": {
     159             :                         "food": 200,
     160             :                         "wood": 200,
     161             :                         "default": 100
     162             :                 },
     163             :                 "medium": {
     164             :                         "default": 0
     165             :                 },
     166             :                 "long": {
     167             :                         "default": 0
     168             :                 }
     169             :         };
     170             : 
     171           0 :         this.garrisonHealthLevel = { "low": 0.4, "medium": 0.55, "high": 0.7 };
     172             : 
     173           0 :         this.unusedNoAllyTechs = [
     174             :                 "Player/sharedLos",
     175             :                 "Market/InternationalBonus",
     176             :                 "Player/sharedDropsites"
     177             :         ];
     178             : 
     179           0 :         this.criticalPopulationFactors = [
     180             :                 0.8,
     181             :                 0.8,
     182             :                 0.7,
     183             :                 0.6,
     184             :                 0.5,
     185             :                 0.35
     186             :         ];
     187             : 
     188           0 :         this.criticalStructureFactors = [
     189             :                 0.8,
     190             :                 0.8,
     191             :                 0.7,
     192             :                 0.6,
     193             :                 0.5,
     194             :                 0.35
     195             :         ];
     196             : 
     197           0 :         this.criticalRootFactors = [
     198             :                 0.8,
     199             :                 0.8,
     200             :                 0.67,
     201             :                 0.5,
     202             :                 0.35,
     203             :                 0.2
     204             :         ];
     205             : };
     206             : 
     207           0 : PETRA.Config.prototype.setConfig = function(gameState)
     208             : {
     209           0 :         if (this.difficulty > PETRA.DIFFICULTY_SANDBOX)
     210             :         {
     211             :                 // Setup personality traits according to the user choice:
     212             :                 // The parameter used to define the personality is basically the aggressivity or (1-defensiveness)
     213             :                 // as they are anticorrelated, although some small smearing to decorelate them will be added.
     214             :                 // And for each user choice, this parameter can vary between min and max
     215           0 :                 let personalityList = {
     216             :                         "random": { "min": 0, "max": 1 },
     217             :                         "defensive": { "min": 0, "max": 0.27 },
     218             :                         "balanced": { "min": 0.37, "max": 0.63 },
     219             :                         "aggressive": { "min": 0.73, "max": 1 }
     220             :                 };
     221           0 :                 let behavior = randFloat(-0.5, 0.5);
     222             :                 // make agressive and defensive quite anticorrelated (aggressive ~ 1 - defensive) but not completelety
     223           0 :                 let variation = 0.15 * randFloat(-1, 1) * Math.sqrt(Math.square(0.5) - Math.square(behavior));
     224           0 :                 let aggressive = Math.max(Math.min(behavior + variation, 0.5), -0.5) + 0.5;
     225           0 :                 let defensive = Math.max(Math.min(-behavior + variation, 0.5), -0.5) + 0.5;
     226           0 :                 let min = personalityList[this.behavior].min;
     227           0 :                 let max = personalityList[this.behavior].max;
     228           0 :                 this.personality = {
     229             :                         "aggressive": min + aggressive * (max - min),
     230             :                         "defensive": 1 - max + defensive * (max - min),
     231             :                         "cooperative": randFloat(0, 1)
     232             :                 };
     233             :         }
     234             :         // Petra usually uses the continuous values of personality.aggressive and personality.defensive
     235             :         // to define its behavior according to personality. But when discontinuous behavior is needed,
     236             :         // it uses the following personalityCut which should be set such that:
     237             :         // behavior="aggressive" => personality.aggressive > personalityCut.strong &&
     238             :         //                          personality.defensive  < personalityCut.weak
     239             :         // and inversely for behavior="defensive"
     240           0 :         this.personalityCut = { "weak": 0.3, "medium": 0.5, "strong": 0.7 };
     241             : 
     242           0 :         if (gameState.playerData.teamsLocked)
     243           0 :                 this.personality.cooperative = Math.min(1, this.personality.cooperative + 0.30);
     244           0 :         else if (gameState.getAlliedVictory())
     245           0 :                 this.personality.cooperative = Math.min(1, this.personality.cooperative + 0.15);
     246             : 
     247             :         // changing settings based on difficulty or personality
     248           0 :         this.Military.towerLapseTime = Math.round(this.Military.towerLapseTime * (1.1 - 0.2 * this.personality.defensive));
     249           0 :         this.Military.fortressLapseTime = Math.round(this.Military.fortressLapseTime * (1.1 - 0.2 * this.personality.defensive));
     250           0 :         this.priorities.defenseBuilding = Math.round(this.priorities.defenseBuilding * (0.9 + 0.2 * this.personality.defensive));
     251             : 
     252           0 :         if (this.difficulty < PETRA.DIFFICULTY_EASY)
     253             :         {
     254           0 :                 this.popScaling = 0.5;
     255           0 :                 this.Economy.supportRatio = 0.5;
     256           0 :                 this.Economy.provisionFields = 1;
     257           0 :                 this.Military.numSentryTowers = this.personality.defensive > this.personalityCut.strong ? 1 : 0;
     258             :         }
     259           0 :         else if (this.difficulty < PETRA.DIFFICULTY_MEDIUM)
     260             :         {
     261           0 :                 this.popScaling = 0.7;
     262           0 :                 this.Economy.supportRatio = 0.4;
     263           0 :                 this.Economy.provisionFields = 1;
     264           0 :                 this.Military.numSentryTowers = this.personality.defensive > this.personalityCut.strong ? 1 : 0;
     265             :         }
     266             :         else
     267             :         {
     268           0 :                 if (this.difficulty == PETRA.DIFFICULTY_MEDIUM)
     269           0 :                         this.Military.numSentryTowers = 1;
     270             :                 else
     271           0 :                         this.Military.numSentryTowers = 2;
     272           0 :                 if (this.personality.defensive > this.personalityCut.strong)
     273           0 :                         ++this.Military.numSentryTowers;
     274           0 :                 else if (this.personality.defensive < this.personalityCut.weak)
     275           0 :                         --this.Military.numSentryTowers;
     276             : 
     277           0 :                 if (this.personality.aggressive > this.personalityCut.strong)
     278             :                 {
     279           0 :                         this.Military.popForBarracks1 = 12;
     280           0 :                         this.Economy.popPhase2 = 50;
     281           0 :                         this.priorities.healer = 10;
     282             :                 }
     283             :         }
     284             : 
     285           0 :         let maxPop = gameState.getPopulationMax();
     286           0 :         if (this.difficulty < PETRA.DIFFICULTY_EASY)
     287           0 :                 this.Economy.targetNumWorkers = Math.max(1, Math.min(40, maxPop));
     288           0 :         else if (this.difficulty < PETRA.DIFFICULTY_MEDIUM)
     289           0 :                 this.Economy.targetNumWorkers = Math.max(1, Math.min(60, Math.floor(maxPop/2)));
     290             :         else
     291           0 :                 this.Economy.targetNumWorkers = Math.max(1, Math.min(120, Math.floor(maxPop/3)));
     292           0 :         this.Economy.targetNumTraders = 2 + this.difficulty;
     293             : 
     294             : 
     295           0 :         if (gameState.getVictoryConditions().has("wonder"))
     296             :         {
     297           0 :                 this.Economy.workPhase3 = Math.floor(0.9 * this.Economy.workPhase3);
     298           0 :                 this.Economy.workPhase4 = Math.floor(0.9 * this.Economy.workPhase4);
     299             :         }
     300             : 
     301           0 :         if (maxPop < 300)
     302           0 :                 this.popScaling *= Math.sqrt(maxPop / 300);
     303             : 
     304           0 :         this.Military.popForBarracks1 = Math.min(Math.max(Math.floor(this.Military.popForBarracks1 * this.popScaling), 12), Math.floor(maxPop/5));
     305           0 :         this.Military.popForBarracks2 = Math.min(Math.max(Math.floor(this.Military.popForBarracks2 * this.popScaling), 45), Math.floor(maxPop*2/3));
     306           0 :         this.Military.popForForge = Math.min(Math.max(Math.floor(this.Military.popForForge * this.popScaling), 30), Math.floor(maxPop/2));
     307           0 :         this.Economy.popPhase2 = Math.min(Math.max(Math.floor(this.Economy.popPhase2 * this.popScaling), 20), Math.floor(maxPop/2));
     308           0 :         this.Economy.workPhase3 = Math.min(Math.max(Math.floor(this.Economy.workPhase3 * this.popScaling), 40), Math.floor(maxPop*2/3));
     309           0 :         this.Economy.workPhase4 = Math.min(Math.max(Math.floor(this.Economy.workPhase4 * this.popScaling), 45), Math.floor(maxPop*2/3));
     310           0 :         this.Economy.targetNumTraders = Math.round(this.Economy.targetNumTraders * this.popScaling);
     311           0 :         this.Economy.targetNumWorkers = Math.max(this.Economy.targetNumWorkers, this.Economy.popPhase2);
     312           0 :         this.Economy.workPhase3 = Math.min(this.Economy.workPhase3, this.Economy.targetNumWorkers);
     313           0 :         this.Economy.workPhase4 = Math.min(this.Economy.workPhase4, this.Economy.targetNumWorkers);
     314           0 :         if (this.difficulty < PETRA.DIFFICULTY_EASY)
     315           0 :                 this.Economy.workPhase3 = Infinity;     // prevent the phasing to city phase
     316             : 
     317           0 :         this.emergencyValues = {
     318             :                 "population": this.criticalPopulationFactors[this.difficulty],
     319             :                 "structures": this.criticalStructureFactors[this.difficulty],
     320             :                 "roots": this.criticalRootFactors[this.difficulty],
     321             :         };
     322             : 
     323           0 :         this.Cheat(gameState);
     324             : 
     325           0 :         if (this.debug < 2)
     326           0 :                 return;
     327           0 :         API3.warn(" >>>  Petra bot: personality = " + uneval(this.personality));
     328             : };
     329             : 
     330           0 : PETRA.Config.prototype.Cheat = function(gameState)
     331             : {
     332             :         // Sandbox, Very Easy, Easy, Medium, Hard, Very Hard
     333             :         // rate apply on resource stockpiling as gathering and trading
     334             :         // time apply on building, upgrading, packing, training and technologies
     335           0 :         const rate = [ 0.42, 0.56, 0.75, 1.00, 1.25, 1.56 ];
     336           0 :         const time = [ 1.40, 1.25, 1.10, 1.00, 1.00, 1.00 ];
     337           0 :         const AIDiff = Math.min(this.difficulty, rate.length - 1);
     338           0 :         SimEngine.QueryInterface(Sim.SYSTEM_ENTITY, Sim.IID_ModifiersManager).AddModifiers("AI Bonus", {
     339             :                 "ResourceGatherer/BaseSpeed": [{ "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] }],
     340             :                 "Trader/GainMultiplier": [{ "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] }],
     341             :                 "Cost/BuildTime": [{ "affects": ["Unit", "Structure"], "multiply": time[AIDiff] }],
     342             :         }, gameState.playerData.entity);
     343             : };
     344             : 
     345           0 : PETRA.Config.prototype.Serialize = function()
     346             : {
     347           0 :         var data = {};
     348           0 :         for (let key in this)
     349           0 :                 if (this.hasOwnProperty(key) && key != "debug")
     350           0 :                         data[key] = this[key];
     351           0 :         return data;
     352             : };
     353             : 
     354           0 : PETRA.Config.prototype.Deserialize = function(data)
     355             : {
     356           0 :         for (let key in data)
     357           0 :                 this[key] = data[key];
     358             : };

Generated by: LCOV version 1.14