LCOV - code coverage report
Current view: top level - simulation/ai/common-api - terrain-analysis.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 214 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 11 0.0 %

          Line data    Source code
       1           0 : var API3 = function(m)
       2             : {
       3             : 
       4             : /**
       5             :  * TerrainAnalysis, inheriting from the Map Component.
       6             :  *
       7             :  * This creates a suitable passability map.
       8             :  * This is part of the Shared Script, and thus should only be used for things that are non-player specific.
       9             :  * This.map is a map of the world, where particular stuffs are pointed with a value
      10             :  * For example, impassable land is 0, water is 200, areas near tree (ie forest grounds) are 41…
      11             :  * This is intended for use with 8 bit maps for reduced memory usage.
      12             :  * Upgraded from QuantumState's original TerrainAnalysis for qBot.
      13             :  */
      14             : 
      15           0 : m.TerrainAnalysis = function()
      16             : {
      17             : };
      18             : 
      19           0 : m.copyPrototype(m.TerrainAnalysis, m.Map);
      20             : 
      21           0 : m.TerrainAnalysis.prototype.IMPASSABLE = 0;
      22             : /**
      23             : * non-passable by land units
      24             : */
      25           0 : m.TerrainAnalysis.prototype.DEEP_WATER = 200;
      26             : /**
      27             : * passable by land units and water units
      28             : */
      29           0 : m.TerrainAnalysis.prototype.SHALLOW_WATER = 201;
      30             : /**
      31             : * passable by land units
      32             : */
      33           0 : m.TerrainAnalysis.prototype.LAND = 255;
      34             : 
      35           0 : m.TerrainAnalysis.prototype.init = function(sharedScript, rawState)
      36             : {
      37           0 :         let passabilityMap = rawState.passabilityMap;
      38           0 :         this.width = passabilityMap.width;
      39           0 :         this.height = passabilityMap.height;
      40           0 :         this.cellSize = passabilityMap.cellSize;
      41             : 
      42           0 :         let obstructionMaskLand = rawState.passabilityClasses["default-terrain-only"];
      43           0 :         let obstructionMaskWater = rawState.passabilityClasses["ship-terrain-only"];
      44             : 
      45           0 :         let obstructionTiles = new Uint8Array(passabilityMap.data.length);
      46             : 
      47             : 
      48           0 :         for (let i = 0; i < passabilityMap.data.length; ++i)
      49             :         {
      50           0 :                 obstructionTiles[i] = (passabilityMap.data[i] & obstructionMaskLand) ? this.IMPASSABLE : this.LAND;
      51             : 
      52           0 :                 if (!(passabilityMap.data[i] & obstructionMaskWater) && obstructionTiles[i] === this.IMPASSABLE)
      53           0 :                         obstructionTiles[i] = this.DEEP_WATER;
      54           0 :                 else if (!(passabilityMap.data[i] & obstructionMaskWater) && obstructionTiles[i] === this.LAND)
      55           0 :                         obstructionTiles[i] = this.SHALLOW_WATER;
      56             :         }
      57             : 
      58           0 :         this.Map(rawState, "passability", obstructionTiles);
      59             : };
      60             : 
      61             : /**
      62             :  * Accessibility inherits from TerrainAnalysis
      63             :  *
      64             :  * This can easily and efficiently determine if any two points are connected.
      65             :  * it can also determine if any point is "probably" reachable, assuming the unit can get close enough
      66             :  * for optimizations it's called after the TerrainAnalyser has finished initializing his map
      67             :  * so this can use the land regions already.
      68             :  */
      69           0 : m.Accessibility = function()
      70             : {
      71             : };
      72             : 
      73           0 : m.copyPrototype(m.Accessibility, m.TerrainAnalysis);
      74             : 
      75           0 : m.Accessibility.prototype.init = function(rawState, terrainAnalyser)
      76             : {
      77           0 :         this.Map(rawState, "passability", terrainAnalyser.map);
      78           0 :         this.landPassMap = new Uint16Array(terrainAnalyser.length);
      79           0 :         this.navalPassMap = new Uint16Array(terrainAnalyser.length);
      80             : 
      81           0 :         this.maxRegions = 65535;
      82           0 :         this.regionSize = [];
      83           0 :         this.regionType = []; // "inaccessible", "land" or "water";
      84             :         // ID of the region associated with an array of region IDs.
      85           0 :         this.regionLinks = [];
      86             : 
      87             :         // initialized to 0, it's more optimized to start at 1 (I'm checking that if it's not 0, then it's already aprt of a region, don't touch);
      88             :         // However I actually store all unpassable as region 1 (because if I don't, on some maps the toal nb of region is over 256, and it crashes as the mapis 8bit.)
      89             :         // So start at 2.
      90           0 :         this.regionID = 2;
      91             : 
      92           0 :         for (let i = 0; i < this.landPassMap.length; ++i)
      93             :         {
      94           0 :                 if (this.map[i] !== this.IMPASSABLE)
      95             :                 {       // any non-painted, non-inacessible area.
      96           0 :                         if (this.landPassMap[i] == 0 && this.floodFill(i, this.regionID, false))
      97           0 :                                 this.regionType[this.regionID++] = "land";
      98           0 :                         if (this.navalPassMap[i] == 0 && this.floodFill(i, this.regionID, true))
      99           0 :                                 this.regionType[this.regionID++] = "water";
     100             :                 }
     101           0 :                 else if (this.landPassMap[i] == 0)
     102             :                 {       // any non-painted, inacessible area.
     103           0 :                         this.floodFill(i, 1, false);
     104           0 :                         this.floodFill(i, 1, true);
     105             :                 }
     106             :         }
     107             : 
     108             :         // calculating region links. Regions only touching diagonaly are not linked.
     109             :         // since we're checking all of them, we'll check from the top left to the bottom right
     110           0 :         let w = this.width;
     111           0 :         for (let x = 0; x < this.width-1; ++x)
     112             :         {
     113           0 :                 for (let y = 0; y < this.height-1; ++y)
     114             :                 {
     115             :                         // checking right.
     116           0 :                         let thisLID = this.landPassMap[x+y*w];
     117           0 :                         let thisNID = this.navalPassMap[x+y*w];
     118           0 :                         let rightLID = this.landPassMap[x+1+y*w];
     119           0 :                         let rightNID = this.navalPassMap[x+1+y*w];
     120           0 :                         let bottomLID = this.landPassMap[x+y*w+w];
     121           0 :                         let bottomNID = this.navalPassMap[x+y*w+w];
     122           0 :                         if (thisLID > 1)
     123             :                         {
     124           0 :                                 if (rightNID > 1)
     125           0 :                                         if (this.regionLinks[thisLID].indexOf(rightNID) === -1)
     126           0 :                                                 this.regionLinks[thisLID].push(rightNID);
     127           0 :                                 if (bottomNID > 1)
     128           0 :                                         if (this.regionLinks[thisLID].indexOf(bottomNID) === -1)
     129           0 :                                                 this.regionLinks[thisLID].push(bottomNID);
     130             :                         }
     131           0 :                         if (thisNID > 1)
     132             :                         {
     133           0 :                                 if (rightLID > 1)
     134           0 :                                         if (this.regionLinks[thisNID].indexOf(rightLID) === -1)
     135           0 :                                                 this.regionLinks[thisNID].push(rightLID);
     136           0 :                                 if (bottomLID > 1)
     137           0 :                                         if (this.regionLinks[thisNID].indexOf(bottomLID) === -1)
     138           0 :                                                 this.regionLinks[thisNID].push(bottomLID);
     139           0 :                                 if (thisLID > 1)
     140           0 :                                         if (this.regionLinks[thisNID].indexOf(thisLID) === -1)
     141           0 :                                                 this.regionLinks[thisNID].push(thisLID);
     142             :                         }
     143             :                 }
     144             :         }
     145             : 
     146             :         // Engine.DumpImage("LandPassMap.png", this.landPassMap, this.width, this.height, 255);
     147             :         // Engine.DumpImage("NavalPassMap.png", this.navalPassMap, this.width, this.height, 255);
     148             : };
     149             : 
     150           0 : m.Accessibility.prototype.getAccessValue = function(position, onWater)
     151             : {
     152           0 :         let gamePos = this.gamePosToMapPos(position);
     153           0 :         if (onWater)
     154           0 :                 return this.navalPassMap[gamePos[0] + this.width*gamePos[1]];
     155           0 :         let ret = this.landPassMap[gamePos[0] + this.width*gamePos[1]];
     156           0 :         if (ret === 1)
     157             :         {
     158             :                 // quick spiral search.
     159           0 :                 let indx = [ [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1]];
     160           0 :                 for (let i of indx)
     161             :                 {
     162           0 :                         let id0 = gamePos[0] + i[0];
     163           0 :                         let id1 = gamePos[1] + i[1];
     164           0 :                         if (id0 < 0 || id0 >= this.width || id1 < 0 || id1 >= this.width)
     165           0 :                                 continue;
     166           0 :                         ret = this.landPassMap[id0 + this.width*id1];
     167           0 :                         if (ret !== 1)
     168           0 :                                 return ret;
     169             :                 }
     170             :         }
     171           0 :         return ret;
     172             : };
     173             : 
     174           0 : m.Accessibility.prototype.getTrajectTo = function(start, end)
     175             : {
     176           0 :         let pstart = this.gamePosToMapPos(start);
     177           0 :         let istart = pstart[0] + pstart[1]*this.width;
     178           0 :         let pend = this.gamePosToMapPos(end);
     179           0 :         let iend = pend[0] + pend[1]*this.width;
     180             : 
     181           0 :         let onLand = true;
     182           0 :         if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] > 1)
     183           0 :                 onLand = false;
     184           0 :         if (this.landPassMap[istart] <= 1 && this.navalPassMap[istart] <= 1)
     185           0 :                 return false;
     186             : 
     187           0 :         let endRegion = this.landPassMap[iend];
     188           0 :         if (endRegion <= 1 && this.navalPassMap[iend] > 1)
     189           0 :                 endRegion = this.navalPassMap[iend];
     190           0 :         else if (endRegion <= 1)
     191           0 :                 return false;
     192             : 
     193           0 :         let startRegion = onLand ? this.landPassMap[istart] : this.navalPassMap[istart];
     194           0 :         return this.getTrajectToIndex(startRegion, endRegion);
     195             : };
     196             : 
     197             : /**
     198             :  * Return a "path" of accessibility indexes from one point to another, including the start and the end indexes
     199             :  * this can tell you what sea zone you need to have a dock on, for example.
     200             :  * assumes a land unit unless start point is over deep water.
     201             :  */
     202           0 : m.Accessibility.prototype.getTrajectToIndex = function(istart, iend)
     203             : {
     204           0 :         if (istart === iend)
     205           0 :                 return [istart];
     206             : 
     207           0 :         let trajects = new Set();
     208           0 :         let explored = new Set();
     209           0 :         trajects.add([istart]);
     210           0 :         explored.add(istart);
     211           0 :         while (trajects.size)
     212             :         {
     213           0 :                 for (let traj of trajects)
     214             :                 {
     215           0 :                         let ilast = traj[traj.length-1];
     216           0 :                         for (let inew of this.regionLinks[ilast])
     217             :                         {
     218           0 :                                 if (inew === iend)
     219           0 :                                         return traj.concat(iend);
     220           0 :                                 if (explored.has(inew))
     221           0 :                                         continue;
     222           0 :                                 trajects.add(traj.concat(inew));
     223           0 :                                 explored.add(inew);
     224             :                         }
     225           0 :                         trajects.delete(traj);
     226             :                 }
     227             :         }
     228           0 :         return undefined;
     229             : };
     230             : 
     231           0 : m.Accessibility.prototype.getRegionSize = function(position, onWater)
     232             : {
     233           0 :         let pos = this.gamePosToMapPos(position);
     234           0 :         let index = pos[0] + pos[1]*this.width;
     235           0 :         let ID = onWater === true ? this.navalPassMap[index] : this.landPassMap[index];
     236           0 :         if (this.regionSize[ID] === undefined)
     237           0 :                 return 0;
     238           0 :         return this.regionSize[ID];
     239             : };
     240             : 
     241           0 : m.Accessibility.prototype.getRegionSizei = function(index, onWater)
     242             : {
     243           0 :         if (this.regionSize[this.landPassMap[index]] === undefined && (!onWater || this.regionSize[this.navalPassMap[index]] === undefined))
     244           0 :                 return 0;
     245           0 :         if (onWater && this.regionSize[this.navalPassMap[index]] > this.regionSize[this.landPassMap[index]])
     246           0 :                 return this.regionSize[this.navalPassMap[index]];
     247           0 :         return this.regionSize[this.landPassMap[index]];
     248             : };
     249             : 
     250             : /** Implementation of a fast flood fill. Reasonably good performances for JS. */
     251           0 : m.Accessibility.prototype.floodFill = function(startIndex, value, onWater)
     252             : {
     253           0 :         if (value > this.maxRegions)
     254             :         {
     255           0 :                 error("AI accessibility map: too many regions.");
     256           0 :                 this.landPassMap[startIndex] = 1;
     257           0 :                 this.navalPassMap[startIndex] = 1;
     258           0 :                 return false;
     259             :         }
     260             : 
     261           0 :         if (!onWater && this.landPassMap[startIndex] != 0 || onWater && this.navalPassMap[startIndex] != 0)
     262           0 :                 return false;   // already painted.
     263             : 
     264           0 :         let floodFor = "land";
     265           0 :         if (this.map[startIndex] === this.IMPASSABLE)
     266             :         {
     267           0 :                 this.landPassMap[startIndex] = 1;
     268           0 :                 this.navalPassMap[startIndex] = 1;
     269           0 :                 return false;
     270             :         }
     271             : 
     272           0 :         if (onWater === true)
     273             :         {
     274           0 :                 if (this.map[startIndex] !== this.DEEP_WATER && this.map[startIndex] !== this.SHALLOW_WATER)
     275             :                 {
     276           0 :                         this.navalPassMap[startIndex] = 1;      // impassable for naval
     277           0 :                         return false;   // do nothing
     278             :                 }
     279           0 :                 floodFor = "water";
     280             :         }
     281           0 :         else if (this.map[startIndex] === this.DEEP_WATER)
     282             :         {
     283           0 :                 this.landPassMap[startIndex] = 1;       // impassable for land
     284           0 :                 return false;
     285             :         }
     286             : 
     287             :         // here we'll be able to start.
     288           0 :         for (let i = this.regionSize.length; i <= value; ++i)
     289             :         {
     290           0 :                 this.regionLinks.push([]);
     291           0 :                 this.regionSize.push(0);
     292           0 :                 this.regionType.push("inaccessible");
     293             :         }
     294           0 :         let w = this.width;
     295           0 :         let h = this.height;
     296             : 
     297           0 :         let y = 0;
     298             :         // Get x and y from index
     299           0 :         let IndexArray = [startIndex];
     300             :         let newIndex;
     301           0 :         while(IndexArray.length)
     302             :         {
     303           0 :                 newIndex = IndexArray.pop();
     304             : 
     305           0 :                 y = 0;
     306           0 :                 let loop = false;
     307             :                 // vertical iteration
     308           0 :                 do {
     309           0 :                         --y;
     310           0 :                         loop = false;
     311           0 :                         let index = newIndex + w*y;
     312           0 :                         if (index < 0)
     313           0 :                                 break;
     314           0 :                         if (floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== this.IMPASSABLE && this.map[index] !== this.DEEP_WATER)
     315           0 :                                 loop = true;
     316           0 :                         else if (floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === this.DEEP_WATER || this.map[index] === this.SHALLOW_WATER))
     317           0 :                                 loop = true;
     318             :                         else
     319           0 :                                 break;
     320             :                 } while (loop === true);        // should actually break
     321           0 :                 ++y;
     322           0 :                 let reachLeft = false;
     323           0 :                 let reachRight = false;
     324             :                 let index;
     325           0 :                 do {
     326           0 :                         index = newIndex + w*y;
     327             : 
     328           0 :                         if (floodFor === "land" && this.landPassMap[index] === 0 && this.map[index] !== this.IMPASSABLE && this.map[index] !== this.DEEP_WATER)
     329             :                         {
     330           0 :                                 this.landPassMap[index] = value;
     331           0 :                                 this.regionSize[value]++;
     332             :                         }
     333           0 :                         else if (floodFor === "water" && this.navalPassMap[index] === 0 && (this.map[index] === this.DEEP_WATER || this.map[index] === this.SHALLOW_WATER))
     334             :                         {
     335           0 :                                 this.navalPassMap[index] = value;
     336           0 :                                 this.regionSize[value]++;
     337             :                         }
     338             :                         else
     339           0 :                                 break;
     340             : 
     341           0 :                         if (index%w > 0)
     342             :                         {
     343           0 :                                 if (floodFor === "land" && this.landPassMap[index -1] === 0 && this.map[index -1] !== this.IMPASSABLE && this.map[index -1] !== this.DEEP_WATER)
     344             :                                 {
     345           0 :                                         if (!reachLeft)
     346             :                                         {
     347           0 :                                                 IndexArray.push(index -1);
     348           0 :                                                 reachLeft = true;
     349             :                                         }
     350             :                                 }
     351           0 :                                 else if (floodFor === "water" && this.navalPassMap[index -1] === 0 && (this.map[index -1] === this.DEEP_WATER || this.map[index -1] === this.SHALLOW_WATER))
     352             :                                 {
     353           0 :                                         if (!reachLeft)
     354             :                                         {
     355           0 :                                                 IndexArray.push(index -1);
     356           0 :                                                 reachLeft = true;
     357             :                                         }
     358             :                                 }
     359           0 :                                 else if (reachLeft)
     360           0 :                                         reachLeft = false;
     361             :                         }
     362             : 
     363           0 :                         if (index%w < w - 1)
     364             :                         {
     365           0 :                                 if (floodFor === "land" && this.landPassMap[index +1] === 0 && this.map[index +1] !== this.IMPASSABLE && this.map[index +1] !== this.DEEP_WATER)
     366             :                                 {
     367           0 :                                         if (!reachRight)
     368             :                                         {
     369           0 :                                                 IndexArray.push(index +1);
     370           0 :                                                 reachRight = true;
     371             :                                         }
     372             :                                 }
     373           0 :                                 else if (floodFor === "water" && this.navalPassMap[index +1] === 0 && (this.map[index +1] === this.DEEP_WATER || this.map[index +1] === this.SHALLOW_WATER))
     374             :                                 {
     375           0 :                                         if (!reachRight)
     376             :                                         {
     377           0 :                                                 IndexArray.push(index +1);
     378           0 :                                                 reachRight = true;
     379             :                                         }
     380             :                                 }
     381           0 :                                 else if (reachRight)
     382           0 :                                         reachRight = false;
     383             :                         }
     384           0 :                         ++y;
     385             :                 } while (index/w < h-1);     // should actually break
     386             :         }
     387           0 :         return true;
     388             : };
     389             : 
     390           0 : return m;
     391             : 
     392             : }(API3);

Generated by: LCOV version 1.14