const TERRITORY_PLAYER_MASK = 0x3F; //TODO: Make this cope with negative cell values function Map(gameState, originalMap){ // get the map to find out the correct dimensions var gameMap = gameState.getMap(); this.width = gameMap.width; this.height = gameMap.height; this.length = gameMap.data.length; if (originalMap){ this.map = originalMap; }else{ this.map = new Uint16Array(this.length); } this.cellSize = gameState.cellSize; } Map.prototype.gamePosToMapPos = function(p){ return [Math.round(p[0]/this.cellSize), Math.round(p[1]/this.cellSize)]; }; Map.prototype.point = function(p){ var q = this.gamePosToMapPos(p); return this.map[q[0] + this.width * q[1]]; }; Map.createObstructionMap = function(gameState, template){ var passabilityMap = gameState.getMap(); var territoryMap = gameState.ai.territoryMap; // default values var placementType = "land"; var buildOwn = true; var buildAlly = true; var buildNeutral = true; var buildEnemy = false; // If there is a template then replace the defaults if (template){ placementType = template.buildPlacementType(); buildOwn = template.hasBuildTerritory("own"); buildAlly = template.hasBuildTerritory("ally"); buildNeutral = template.hasBuildTerritory("neutral"); buildEnemy = template.hasBuildTerritory("enemy"); } var obstructionMask = gameState.getPassabilityClassMask("foundationObstruction"); // Only accept valid land tiles (we don't handle docks yet) switch(placementType){ case "shore": obstructionMask |= gameState.getPassabilityClassMask("building-shore"); break; case "land": default: obstructionMask |= gameState.getPassabilityClassMask("building-land"); break; } var playerID = gameState.getPlayerID(); var obstructionTiles = new Uint16Array(passabilityMap.data.length); for (var i = 0; i < passabilityMap.data.length; ++i) { var tilePlayer = (territoryMap.data[i] & TERRITORY_PLAYER_MASK); var invalidTerritory = ( (!buildOwn && tilePlayer == playerID) || (!buildAlly && gameState.isPlayerAlly(tilePlayer) && tilePlayer != playerID) || (!buildNeutral && tilePlayer == 0) || (!buildEnemy && gameState.isPlayerEnemy(tilePlayer) && tilePlayer != 0) ); var tileAccessible = (gameState.ai.accessibility.map[i] == 1); obstructionTiles[i] = (!tileAccessible || invalidTerritory || (passabilityMap.data[i] & obstructionMask)) ? 0 : 65535; } var map = new Map(gameState, obstructionTiles); if (template && template.buildDistance()){ var minDist = template.buildDistance().MinDistance; var category = template.buildDistance().FromCategory; if (minDist !== undefined && category !== undefined){ gameState.getOwnEntities().forEach(function(ent) { if (ent.buildCategory() === category && ent.position()){ var pos = ent.position(); var x = Math.round(pos[0] / gameState.cellSize); var z = Math.round(pos[1] / gameState.cellSize); map.addInfluence(x, z, minDist/gameState.cellSize, -65535, 'constant'); } }); } } return map; }; Map.createTerritoryMap = function(gameState) { var map = gameState.ai.territoryMap; var ret = new Map(gameState, map.data); ret.getOwner = function(p) { return this.point(p) & TERRITORY_PLAYER_MASK; } return ret; }; Map.prototype.addInfluence = function(cx, cy, maxDist, strength, type) { strength = strength ? strength : maxDist; type = type ? type : 'linear'; var x0 = Math.max(0, cx - maxDist); var y0 = Math.max(0, cy - maxDist); var x1 = Math.min(this.width, cx + maxDist); var y1 = Math.min(this.height, cy + maxDist); var maxDist2 = maxDist * maxDist; var str = 0; switch (type){ case 'linear': str = strength / maxDist; break; case 'quadratic': str = strength / maxDist2; break; case 'constant': str = strength; break; } for ( var y = y0; y < y1; ++y) { for ( var x = x0; x < x1; ++x) { var dx = x - cx; var dy = y - cy; var r2 = dx*dx + dy*dy; if (r2 < maxDist2){ var quant = 0; switch (type){ case 'linear': var r = Math.sqrt(r2); quant = str * (maxDist - r); break; case 'quadratic': quant = str * (maxDist2 - r2); break; case 'constant': quant = str; break; } if (-1 * quant > this.map[x + y * this.width]){ this.map[x + y * this.width] = 0; //set anything which would have gone negative to 0 }else{ this.map[x + y * this.width] += quant; } } } } }; Map.prototype.sumInfluence = function(cx, cy, radius){ var x0 = Math.max(0, cx - radius); var y0 = Math.max(0, cy - radius); var x1 = Math.min(this.width, cx + radius); var y1 = Math.min(this.height, cy + radius); var radius2 = radius * radius; var sum = 0; for ( var y = y0; y < y1; ++y) { for ( var x = x0; x < x1; ++x) { var dx = x - cx; var dy = y - cy; var r2 = dx*dx + dy*dy; if (r2 < radius2){ sum += this.map[x + y * this.width]; } } } return sum; }; /** * Make each cell's 16-bit value at least one greater than each of its * neighbours' values. (If the grid is initialised with 0s and 65535s, the * result of each cell is its Manhattan distance to the nearest 0.) * * TODO: maybe this should be 8-bit (and clamp at 255)? */ Map.prototype.expandInfluences = function() { var w = this.width; var h = this.height; var grid = this.map; for ( var y = 0; y < h; ++y) { var min = 65535; for ( var x = 0; x < w; ++x) { var g = grid[x + y * w]; if (g > min) grid[x + y * w] = min; else if (g < min) min = g; ++min; } for ( var x = w - 2; x >= 0; --x) { var g = grid[x + y * w]; if (g > min) grid[x + y * w] = min; else if (g < min) min = g; ++min; } } for ( var x = 0; x < w; ++x) { var min = 65535; for ( var y = 0; y < h; ++y) { var g = grid[x + y * w]; if (g > min) grid[x + y * w] = min; else if (g < min) min = g; ++min; } for ( var y = h - 2; y >= 0; --y) { var g = grid[x + y * w]; if (g > min) grid[x + y * w] = min; else if (g < min) min = g; ++min; } } }; Map.prototype.findBestTile = function(radius, obstructionTiles){ // Find the best non-obstructed tile var bestIdx = 0; var bestVal = -1; for ( var i = 0; i < this.length; ++i) { if (obstructionTiles.map[i] > radius) { var v = this.map[i]; if (v > bestVal) { bestVal = v; bestIdx = i; } } } return [bestIdx, bestVal]; }; // Multiplies current map by the parameter map pixelwise Map.prototype.multiply = function(map){ for (var i = 0; i < this.length; i++){ this.map[i] *= map.map[i]; } }; Map.prototype.dumpIm = function(name, threshold){ name = name ? name : "default.png"; threshold = threshold ? threshold : 256; Engine.DumpImage(name, this.map, this.width, this.height, threshold); };