/// <summary> /// Checks if the given position is a choke point by looking in 8 directions. /// </summary> /// <param name="pos"> The position to check. </param> /// <param name="width"> The width of a choke point. </param> /// <returns> True if the position is a choke point; false otherwise. </returns> private bool IsPositionChokePoint(Position pos, int width) { // The directions represented as a position, along with how far there is to the nearest cliff. var directions = new List <Tuple <Position, int> > { new Tuple <Position, int>(new Position(-1, 0), -1), // West new Tuple <Position, int>(new Position(1, 0), -1), // East new Tuple <Position, int>(new Position(0, -1), -1), // South new Tuple <Position, int>(new Position(0, 1), -1), // North new Tuple <Position, int>(new Position(1, -1), -1), // South-east new Tuple <Position, int>(new Position(-1, 1), -1), // North-west new Tuple <Position, int>(new Position(-1, -1), -1), // South-west new Tuple <Position, int>(new Position(1, 1), -1) // North-east }; // The opposite directions. var directionCombinations = new Dictionary <int, int> { { 0, 1 }, // West + East { 2, 3 }, // South + north { 4, 5 }, // South-east + north-west { 6, 7 } // South-west + north-east }; // Find the nearest cliff in each of the 8 directions. for (var i = 0; i < width + 1; i++) { foreach (var dc in directionCombinations) { var dir1 = directions[dc.Key].Item1; var dir2 = directions[dc.Value].Item1; var newPos1 = new Position(pos.Item1 + (dir1.Item1 * i), pos.Item2 + (dir1.Item2 * i)); var newPos2 = new Position(pos.Item1 + (dir2.Item1 * i), pos.Item2 + (dir2.Item2 * i)); if (MapHelper.WithinMapBounds(newPos1, this.map.XSize, this.map.YSize)) { if (directions[dc.Key].Item2 < 0 && this.map.HeightLevels[newPos1.Item1, newPos1.Item2] == Enums.HeightLevel.Cliff) { directions[dc.Key] = new Tuple <Position, int>(dir1, i); } } if (MapHelper.WithinMapBounds(newPos2, this.map.XSize, this.map.YSize)) { if (directions[dc.Value].Item2 < 0 && this.map.HeightLevels[newPos2.Item1, newPos2.Item2] == Enums.HeightLevel.Cliff) { directions[dc.Value] = new Tuple <Position, int>(dir2, i); } } } } // The directions to check the ranges between to get a rather clear view var finalCombinations = new List <Position> { new Position(0, 1), new Position(0, 4), new Position(0, 7), new Position(1, 6), new Position(1, 5), new Position(2, 3), new Position(2, 5), new Position(2, 7), new Position(3, 6), new Position(3, 4) }; // Check the range to cliffs for every interesting direction combination foreach (var fc in finalCombinations) { var dir1Value = directions[fc.Item1].Item2; var dir2Value = directions[fc.Item2].Item2; if (dir1Value < 0) { continue; } if (dir2Value < 0) { continue; } // -1 because otherwise we count the start position twice. if ((dir1Value + dir2Value) - 1 <= width) { return(true); } } return(false); }
/// <summary> /// Checks if the position is a ramp with a choke point of the given width or not. /// </summary> /// <param name="pos"> The position to check for a choke point. </param> /// <param name="width"> The width a place should have to be considered a choke point. </param> /// <returns> True if the place is a choke point; false otherwise. </returns> private bool IsRampChokePoint(Position pos, int width) { var originalRampType = this.map.HeightLevels[pos.Item1, pos.Item2]; var horizontalFirstEncounter = this.map.HeightLevels[pos.Item1, pos.Item2]; var verticalFirstEncounter = this.map.HeightLevels[pos.Item1, pos.Item2]; // Figure out what is hit first when going either right or up for (var i = 0; i < 10; i++) { // Check to the right if (MapHelper.WithinMapBounds(pos.Item1, pos.Item2 + i, this.xSize, this.ySize)) { var right = this.map.HeightLevels[pos.Item1, pos.Item2 + i]; if (horizontalFirstEncounter == originalRampType && right != originalRampType) { horizontalFirstEncounter = this.map.HeightLevels[pos.Item1, pos.Item2 + i]; } } // Check up if (MapHelper.WithinMapBounds(pos.Item1 + i, pos.Item2, this.xSize, this.ySize)) { var up = this.map.HeightLevels[pos.Item1 + i, pos.Item2]; if (verticalFirstEncounter == originalRampType && up != originalRampType) { verticalFirstEncounter = this.map.HeightLevels[pos.Item1 + i, pos.Item2]; } } } // Decide which way the the edges of the ramp are. var directionChange = (horizontalFirstEncounter == Enums.HeightLevel.Cliff && verticalFirstEncounter != Enums.HeightLevel.Cliff) ? new Position(0, 1) : new Position(1, 0); var firstPos = new Position(pos.Item1, pos.Item2); var secondPos = new Position(pos.Item1, pos.Item2); // Find the two sides of the ramp. for (var i = 0; i < 10; i++) { // If not within the map, don't bother. if (!MapHelper.WithinMapBounds( pos.Item1 + (i * directionChange.Item1), pos.Item2 + (i * directionChange.Item2), this.xSize, this.ySize)) { continue; } if (!MapHelper.WithinMapBounds( pos.Item1 - (i * directionChange.Item1), pos.Item2 - (i * directionChange.Item2), this.xSize, this.ySize)) { continue; } if (firstPos.Equals(pos) && this.map.HeightLevels[pos.Item1 + (i * directionChange.Item1), pos.Item2 + (i * directionChange.Item2)] == Enums.HeightLevel.Cliff) { firstPos = new Position( pos.Item1 + (i * directionChange.Item1), pos.Item2 + (i * directionChange.Item2)); } if (secondPos.Equals(pos) && this.map.HeightLevels[pos.Item1 - (i * directionChange.Item1), pos.Item2 - (i * directionChange.Item2)] == Enums.HeightLevel.Cliff) { secondPos = new Position( pos.Item1 - (i * directionChange.Item1), pos.Item2 - (i * directionChange.Item2)); } } // Check the actual width. var actualWidth = Math.Abs(firstPos.Item1 - secondPos.Item1) + Math.Abs(firstPos.Item2 - secondPos.Item2) - 1; return(actualWidth <= width); }
/// <summary> /// Figures out how many steps of the path between the start bases the two Xel'Naga towers cover. /// If the towers cover more than one base, their worth is halved. If they cover a start base, their worth is cut in four (these two stack). /// </summary> /// <returns> A value between 0.0 and 1.0 multiplied by significance based on how many steps that are covered. </returns> private double XelNagaPlacement() { // If no Xel'Naga tower are found, return 0. if (this.xelNagaPosition2 == null) { return(0d); } double stepsCovered2 = this.pathBetweenStartBases.Count( step => MapHelper.CloseTo(step, this.xelNagaPosition2, this.mfo.DistanceToXelNaga)); double max2 = this.mfo.StepsInXelNagaRangeMax; double min2 = this.mfo.StepsInXelNagaRangeMin; var actual2 = (stepsCovered2 > max2) ? max2 - (stepsCovered2 - max2) : stepsCovered2; if (actual2 < min2) { actual2 = min2; } var normalized2 = (actual2 - min2) / (max2 - min2); var basesInVision2 = this.bases.Count(@base => MapHelper.CloseTo(this.xelNagaPosition2, @base)); var startBaseInVision2 = MapHelper.CloseTo(this.xelNagaPosition2, this.startBasePosition1) || MapHelper.CloseTo(this.xelNagaPosition2, this.startBasePosition2); if (basesInVision2 >= 2) { normalized2 /= 2; } if (startBaseInVision2) { normalized2 /= 4; } // If only one Xel'Naga tower is found, return the significance for just that one, but halved. if (this.xelNagaPosition1 == null) { return((normalized2 * this.mfo.XelNagaPlacementSignificance) / 2d); } double stepsCovered1 = this.pathBetweenStartBases.Count( step => MapHelper.CloseTo(step, this.xelNagaPosition1, this.mfo.DistanceToXelNaga)); double max1 = this.mfo.StepsInXelNagaRangeMax; double min1 = this.mfo.StepsInXelNagaRangeMin; var actual1 = (stepsCovered1 > max1) ? max1 - (stepsCovered1 - max1) : stepsCovered1; if (actual1 < min1) { actual1 = min1; } var normalized1 = (actual1 - min1) / (max1 - min1); var basesInVision1 = this.bases.Count(@base => MapHelper.CloseTo(this.xelNagaPosition1, @base)); var startBaseInVision1 = MapHelper.CloseTo(this.xelNagaPosition1, this.startBasePosition1) || MapHelper.CloseTo(this.xelNagaPosition1, this.startBasePosition2); if (basesInVision1 >= 2) { normalized1 /= 2; } if (startBaseInVision1) { normalized1 /= 4; } return(((normalized1 + normalized2) / 2d) * this.mfo.XelNagaPlacementSignificance); }
/// <summary> /// Calculates the fitness for the map. /// </summary> /// <returns> A double representing the fitness of the map. The higher, the better. </returns> public double CalculateFitness() { this.bases = new List <Position>(); // Find a startbase. No need to find all start bases, as the other base should be a complete mirror of this base. for (var tempY = this.ySize - 1; tempY >= 0; tempY--) { for (var tempX = 0; tempX < this.xSize; tempX++) { // Find start base 1 if (this.map.MapItems[tempX, tempY] == Enums.Item.StartBase && this.startBasePosition2 == null) { this.startBasePosition2 = new Tuple <int, int>(tempX + 2, tempY - 2); } // Find start base 2 if (this.map.MapItems[tempX, tempY] == Enums.Item.StartBase && this.startBasePosition1 == null && this.startBasePosition2 != null) { if (Math.Abs(tempX - this.startBasePosition2.Item1) > 3 || Math.Abs(tempY - this.startBasePosition2.Item2) > 3) { this.startBasePosition1 = new Position(tempX + 2, tempY - 2); } } // Check for highest level if ((this.map.HeightLevels[tempX, tempY] == Enums.HeightLevel.Height1 || this.map.HeightLevels[tempX, tempY] == Enums.HeightLevel.Height2) && this.map.HeightLevels[tempX, tempY] > this.heighestLevel) { this.heighestLevel = this.map.HeightLevels[tempX, tempY]; } var tempPos = new Position(tempX, tempY); // Check if the area is a base if (this.map.MapItems[tempX, tempY] == Enums.Item.Base && !MapHelper.CloseToAny(tempPos, this.bases, 5)) { this.bases.Add(new Position(tempX + 2, tempY - 2)); } // Save the first XelNaga tower found if (this.map.MapItems[tempX, tempY] == Enums.Item.XelNagaTower && this.xelNagaPosition2 == null) { this.xelNagaPosition2 = tempPos; } // Save the second XelNaga tower found if (this.map.MapItems[tempX, tempY] == Enums.Item.XelNagaTower && this.xelNagaPosition2 != null && this.xelNagaPosition1 == null && !MapHelper.CloseTo(tempPos, this.xelNagaPosition2, 4)) { this.xelNagaPosition1 = tempPos; } } } // If no start bases are found, the map is not feasible and running fitness calculations is not worth it. if (this.startBasePosition1 == null || this.startBasePosition2 == null) { this.FitnessValues = new MapFitnessValues( -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness, -this.mfo.MaxTotalFitness); return(this.FitnessValues.TotalFitness); } this.pathBetweenStartBases = this.mapPathfinding.FindPathFromTo( this.startBasePosition1, this.startBasePosition2, this.mfo.PathfindingIgnoreDestructibleRocks); var baseSpace = this.BaseSpace(); var baseHeight = this.BaseHeightLevel(); var pathBetweenStartBasesFitness = this.PathBetweenStartBases(); var newHeightReached = this.NewHeightReached(); var distanceToNaturalExpansion = this.DistanceToNaturalExpansion(); var distanceToNonNaturalExpansions = this.DistanceToNonNaturalExpansions(); var expansionsAvailable = this.ExpansionsAvailable(); var chokePoints = this.ChokePoints(); var xelNagaPlacement = this.XelNagaPlacement(); var startBaseOpeness = this.StartBaseOpeness(); var baseOpeness = this.BaseOpeness(); this.FitnessValues = new MapFitnessValues( baseSpace, baseHeight, pathBetweenStartBasesFitness, newHeightReached, distanceToNaturalExpansion, distanceToNonNaturalExpansions, expansionsAvailable, chokePoints, xelNagaPlacement, startBaseOpeness, baseOpeness); return(this.FitnessValues.TotalFitness); }