Britbot
EnemyGroup.cs
Go to the documentation of this file.
00001 #region Usings
00002 
00003 using System;
00004 using System.Collections.Generic;
00005 using System.Linq;
00006 using Pirates;
00007 
00008 #endregion
00009 
00010 namespace Britbot
00011 {
00015     public class EnemyGroup : ITarget
00016     {
00017         #region Static Fields & Consts
00018 
00022         private static int _idCount;
00023 
00024         #endregion
00025 
00026         #region Fields & Properies
00027 
00031         public readonly int Id;
00032 
00036         private int _lastAssignmentTurn;
00037 
00041         private Queue<Direction> _lastDirections;
00042 
00046         private Queue<int> _lastMaxFightPower;
00047 
00051         public Location PrevLoc { get; set; }
00052 
00056         public List<int> EnemyPirates { get; private set; }
00057 
00058         #endregion
00059 
00060         #region Constructors & Initializers
00061 
00065         public EnemyGroup()
00066         {
00067             this.Id = EnemyGroup._idCount++;
00068             this.EnemyPirates = new List<int>();
00069             this.PrevLoc = new Location(0, 0);
00070             this._lastDirections = new Queue<Direction>();
00071             this._lastMaxFightPower = new Queue<int>();
00072         }
00073 
00074         #endregion
00075 
00076         #region Interface Implementations
00077 
00083         public Score GetScore(Group origin)
00084         {
00085             //first check if groups direction is stable, otherwise disqualify
00086             if (this.GetHeadingSabilityCoeff() < Magic.HeadingStabilityCoeff)
00087                 return null;
00088 
00089             //check if the enemy group isn't in spawn - else disqualify
00090             if (!Bot.Game.IsPassable(this.GetLocation()))
00091                 return null;
00092 
00093             //next check if it even possible to catch the ship, otherwise disqualify
00094             if (!Navigator.IsReachable(origin.GetLocation(), GetLocation(), this.GetHeading()))
00095                 return null;
00096 
00097             //Reduce the score in proportion to distance. Lower score is worse. Mind the minus sign!
00098             double distance = Navigator.CalcDistFromLine(origin.GetLocation(), this.GetLocation(), this.GetHeading());
00099 
00100             //consider attack radius
00101             distance -= Math.Sqrt(Bot.Game.GetAttackRadius());
00102             distance = Math.Max(distance, 0);
00103 
00104             //if the origin group is strong enough to take the enemy group add its score
00105             if (origin.FightCount() > this.GetMaxFightPower())
00106                 return new Score(this, TargetType.EnemyGroup, 0, this.EnemyPirates.Count, distance,
00107                     Bot.Game.GetSpawnTurns(), 0);
00108 
00109             //if this enemygroup will defeat the origin group, disqualify
00110             return null;
00111         }
00112 
00117         public Location GetLocation()
00118         {
00119             return this.GetLocation(false);
00120         }
00121 
00131         private Location GetLocation(bool forcePirate)
00132         {
00133             //Get a list of all location of the enemy pirates in this group
00134             List<Location> locs = new List<Location>();
00135 
00136             if (this.EnemyPirates == null)
00137                 return new Location(0, 0);
00138 
00139             foreach (int e in this.EnemyPirates)
00140             {
00141                 Pirate enemyPirate = Bot.Game.GetEnemyPirate(e);
00142 
00143                 if (enemyPirate != null)
00144                     locs.Add(enemyPirate.Loc);
00145             }
00146 
00147             //sum all the locations!
00148             int totalCol = locs.Sum(loc => loc.Col);
00149             int totalRow = locs.Sum(loc => loc.Row);
00150 
00151             Location averageLocation = new Location(0, 0);
00152 
00153             //return the average location
00154             if (locs.Count != 0)
00155                 averageLocation = new Location(totalRow / locs.Count, totalCol / locs.Count);
00156 
00157             if (forcePirate)
00158             {
00159                 int minDistance = Bot.Game.GetCols() + Bot.Game.GetCols();
00160                 Pirate pete = null;
00161 
00162                 //iterate over all the pirate and find the one with the minimun distance to the average location
00163                 foreach (Pirate pirate in this.EnemyPirates.ConvertAll(p => Bot.Game.GetEnemyPirate(p)))
00164                 {
00165                     if (pirate.IsLost)
00166                         continue;
00167 
00168                     int currDistance = Bot.Game.Distance(averageLocation, pirate.Loc);
00169                     if (currDistance < minDistance)
00170                     {
00171                         minDistance = currDistance;
00172                         pete = pirate;
00173                     }
00174                 }
00175 
00176                 //set the returned location to the central pirate location
00177                 if (pete != null)
00178                     averageLocation = pete.Loc;
00179             }
00180             return averageLocation;
00181         }
00182 
00191         public Direction GetDirection(Group group)
00192         {
00193             //calculates the direction based on the geographical data from the game
00194             //first check if stationary
00195             if (Math.Abs(this.GetHeading().Norm()) < Magic.VectorTolerance)
00196                 return Navigator.CalculateDirectionToStationeryTarget(group.FindCenter(true), group.Heading,
00197                     this.GetLocation());
00198             
00199             //otherwise
00200             return Navigator.CalculateDirectionToMovingTarget(group.FindCenter(true), group.Heading, GetLocation(),
00201                 this.GetHeading());
00202         }
00203 
00208         public TargetType GetTargetType()
00209         {
00210             return TargetType.EnemyGroup;
00211         }
00212 
00217         public string GetDescription()
00218         {
00219             string s = "Enemy Group, Pirates: ";
00220             foreach (int pirate in EnemyPirates)
00221                 s += " " + pirate;
00222 
00223             return s;
00224         }
00225 
00231         public bool Equals(ITarget operandB)
00232         {
00233             EnemyGroup enemyGroup = operandB as EnemyGroup;
00234             if (enemyGroup != null)
00235             {
00236                 EnemyGroup b = enemyGroup;
00237                 return this.Id == b.Id;
00238             }
00239 
00240             return false;
00241         }
00242 
00246         public void TargetAssignmentEvent()
00247         {
00248             this._lastAssignmentTurn = Bot.Game.GetTurn();
00249         }
00250 
00255         public void TargetDeAssignmentEvent()
00256         {
00257             //this defines what is the minimum turn number till it is legit to change target (on average)
00258              //check if time from the last assignment raises suspition of inteligence in the enemy
00259             if (Bot.Game.GetTurn() - this._lastAssignmentTurn < Magic.minimumTillItIsOkToDropTarget)
00260             {
00261                 Enemy.EnemyIntelligenceSuspicionCounter++;
00262             }
00263         }
00264 
00265         #endregion
00266 
00271         public override int GetHashCode()
00272         {
00273             unchecked
00274             {
00275                 int hashCode = this.Id;
00276                 hashCode = (hashCode * 397) ^ this._lastAssignmentTurn;
00277                 hashCode = (hashCode * 397) ^ (this._lastDirections != null ? this._lastDirections.GetHashCode() : 0);
00278                 hashCode = (hashCode * 397) ^
00279                            (this._lastMaxFightPower != null ? this._lastMaxFightPower.GetHashCode() : 0);
00280                 hashCode = (hashCode * 397) ^ (this.PrevLoc != null ? this.PrevLoc.GetHashCode() : 0);
00281                 hashCode = (hashCode * 397) ^ (this.EnemyPirates != null ? this.EnemyPirates.GetHashCode() : 0);
00282                 return hashCode;
00283             }
00284         }
00285         
00291         private int MaxFightCount()
00292         {
00293             //initialize maximum variable
00294             int maxFightCount = 0;
00295 
00296             //go over all pirates
00297             foreach (int pirate in this.EnemyPirates)
00298             {
00299                 //convert to pirate
00300                 Pirate pete = Bot.Game.GetEnemyPirate(pirate);
00301 
00302                 //check if this pirate counts
00303                 if (Bot.Game.isCapturing(pete))
00304                     continue;
00305                 //count how many pirates are supporting him
00306                 int supportCount = 0;
00307 
00308                 //go over all other pirates
00309                 foreach (int otherPirate in this.EnemyPirates)
00310                 {
00311                     //convert to pirate
00312                     Pirate otherPete = Bot.Game.GetEnemyPirate(otherPirate);
00313 
00314                     //check if this pirate counts
00315                     if (Bot.Game.isCapturing(otherPete))
00316                         continue;
00317 
00318                     //check if in range
00319                     if (Bot.Game.EuclidianDistanceSquared(pete.Loc, otherPete.Loc) <= Bot.Game.GetAttackRadius())
00320                         supportCount++;
00321                 }
00322 
00323                 //check if the support count is bigger then the maximum
00324                 if (maxFightCount < supportCount)
00325                     maxFightCount = supportCount;
00326             }
00327 
00328             //return result
00329             return maxFightCount;
00330         }
00331 
00339         private int InRangeGroupDistance(EnemyGroup eg, Group group)
00340         {
00341             Pirate enemyPirate = null, myPirate = null;
00342 
00343             int minDistance = Bot.Game.GetCols() + Bot.Game.GetRows();
00344 
00345             //find the two pirate from the two group with the minimum distance between
00346             for (int i = 0; i < eg.EnemyPirates.Count; i++)
00347             {
00348                 Pirate p = Bot.Game.GetEnemyPirate(i);
00349 
00350                 for (int j = 0; j < group.Pirates.Count; j++)
00351                 {
00352                     Pirate aPirate = Bot.Game.GetMyPirate(group.Pirates[j]);
00353 
00354                     int distance = Bot.Game.Distance(p, aPirate);
00355 
00356                     if (distance >= minDistance)
00357                         continue;
00358 
00359                     minDistance = distance;
00360                     enemyPirate = p;
00361                     myPirate = aPirate;
00362                 }
00363             }
00364 
00365             //return the distance between these pirates with the range in mind
00366             return Bot.Game.Distance(enemyPirate.Loc, myPirate.Loc) - Bot.Game.GetAttackRadius() * 2;
00367         }
00368 
00374         public bool IsInGroup(int enemyPirate)
00375         {
00376             Pirate ePirate = Bot.Game.GetEnemyPirate(enemyPirate);
00377 
00378             //check if the pirate is null
00379             if (ePirate == null)
00380                 return false;
00381 
00382             //Check if the given pirate is close (max of 2 distance units) to any of the pirates already in this group
00383             if (ePirate.IsLost)
00384                 return false;
00385 
00386             return
00387                 this.EnemyPirates.ConvertAll(e => Bot.Game.GetEnemyPirate(e))
00388                     .Any(e => Bot.Game.IsReallyInRange(ePirate.Loc, e.Loc));
00389         }
00390 
00397         public static bool IsInGroup(List<int> group, int enemyPirate)
00398         {
00399             Pirate ePirate = Bot.Game.GetEnemyPirate(enemyPirate);
00400 
00401             if (ePirate.IsLost)
00402                 return false;
00403 
00404             //Check if the given pirate is close (max of 2 distance units) to any of the pirates already in this group
00405             return
00406                 group.ConvertAll(e => Bot.Game.GetEnemyPirate(e))
00407                     .Select(ep => Bot.Game.Distance(ep, ePirate))
00408                     .Concat(new int[] {})
00409                     .Min() <= 2;
00410         }
00411 
00417         public double MinimalSquaredDistanceTo(Location location)
00418         {
00419             double min = Bot.Game.GetCols() + Bot.Game.GetRows();
00420             foreach (int pirate in EnemyPirates)
00421             {
00422                 if (Bot.Game.EuclidianDistanceSquared(location, this.GetLocation()) < min)
00423                     min = Bot.Game.EuclidianDistanceSquared(location, this.GetLocation());
00424             }
00425             return min;
00426         }
00427 
00433         public double MinimalETATo(Location location)
00434         {
00435             double min = Bot.Game.GetCols() + Bot.Game.GetRows();
00436             foreach (int pirate in EnemyPirates)
00437             {
00438                 if (Bot.Game.Distance(location, this.GetLocation()) < min)
00439                     min = Bot.Game.EuclidianDistanceSquared(location, this.GetLocation());
00440             }
00441             return min;
00442         }
00443 
00448         public SmartIsland GuessTarget()
00449         {
00450             //Sort the islands by distance from this enemy group
00451             List<SmartIsland> sortedByDistance = SmartIsland.IslandList;
00452             sortedByDistance.Sort(
00453                 (a, b) =>
00454                     Bot.Game.Distance(b.Loc, this.GetLocation()).CompareTo(Bot.Game.Distance(a.Loc, this.GetLocation())));
00455 
00456             foreach (SmartIsland isle in sortedByDistance)
00457             {
00458                 //check if distance is smaller then tolerance margin
00459                 if (Navigator.CalcDistFromLine(isle.GetLocation(), GetLocation(), this.GetHeading()) < Magic.toleranceMargin)
00460                     return isle;
00461             }
00462 
00463             return null;
00464         }
00465 
00473         public bool IsApproachingIsland(SmartIsland sIsland)
00474         {
00475             //if the group is stationary just check if it is close
00476             if (this.GetHeading().NormSquared() <= 2)
00477             {
00478                 return Bot.Game.EuclidianDistanceSquared(this.GetLocation(), sIsland.Loc) <=
00479                        Magic.ApproachDistanceSquaredOfStationaryTarget;
00480             }
00481             //check if the enemy group is on the island
00482             if (this.MinimalSquaredDistanceTo(sIsland.Loc) < 20)
00483                 return true;
00484 
00485             //calculate the difference vector between the enemy group and the island
00486             HeadingVector difference = HeadingVector.CalcDifference(this.GetLocation(), sIsland.Loc);
00487 
00488             //check if it isn't moving in the general direction of the island, if so return false
00489             if (difference * this.GetHeading() <= 0)
00490             {
00491                 return false;
00492             }
00493             //read the distance between the trajectory of the enemy group and the island
00494             double distance = Navigator.CalcDistFromLine(sIsland.Loc, this.GetLocation(), this.GetHeading());
00495             /*Logger.Write("Loc " + sIsland.Loc + " line loc: " + this.GetLocation().ToString() + " heading: " + this.GetHeading().ToString());
00496             Logger.Write("Distance: " + distance);*/
00497             //compare it to the constant defined in magic
00498             return distance <= Magic.EnemyPredictionSensitivity;
00499         }
00500 
00506         public double GetMaxFightPower()
00507         {
00508             //check if LastMaxFightPower isnt empty
00509             if (this._lastMaxFightPower.Count == 0)
00510                 return 0;
00511             //otherwise
00512             return this._lastMaxFightPower.Average();
00513         }
00514 
00519         public void Update()
00520         {
00521             //get the new direction of the last turn
00522             Direction newDirection = Bot.Game.GetDirections(this.PrevLoc, this.GetLocation())[0];
00523 
00524             //update previous location
00525             PrevLoc = GetLocation();
00526 
00527             //update directions
00528             this._lastDirections.Enqueue(newDirection);
00529 
00530             //check if we need to throw irrelevant stuff out
00531             if (this._lastDirections.Count > Magic.EnemyLocationQueueSize)
00532             {
00533                 this._lastDirections.Dequeue();
00534             }
00535 
00536             //update maximum fire power
00537             this._lastMaxFightPower.Enqueue(this.MaxFightCount());
00538 
00539             //check if we need to throw irrelevant stuff out
00540             if (this._lastMaxFightPower.Count > Magic.EnemyLocationQueueSize)
00541             {
00542                 this._lastMaxFightPower.Dequeue();
00543             }
00544         }
00545 
00551         public HeadingVector GetHeading()
00552         {
00553             //creating an accumulator heading vector with (0,0) values
00554             HeadingVector hv = new HeadingVector();
00555 
00556             //going over the last directions of this group and adding them up
00557             foreach (Direction dir in this._lastDirections)
00558             {
00559                 //temporal variable for conversion
00560                 HeadingVector currHeading = new HeadingVector(dir);
00561                 hv += currHeading;
00562             }
00563 
00564             //return result
00565             return hv;
00566         }
00567 
00574         public double GetHeadingSabilityCoeff()
00575         {
00576             //count actual moves
00577             int moveCount = 0;
00578 
00579             //go over all the direction and compare to Direction.Nothing
00580             foreach (Direction dir in this._lastDirections)
00581             {
00582                 if (dir != Direction.NOTHING)
00583                     moveCount++;
00584             }
00585 
00586             //if move count is 0 then target is stationary and thus very stable
00587             if (moveCount == 0)
00588                 return 1;
00589             //otherwise return that
00590             return this.GetHeading().Norm1() / moveCount;
00591         }
00592 
00593         public override string ToString()
00594         {
00595             return "EnemyGroup- id: " + this.Id + ", fight power: " + this.GetMaxFightPower()
00596                    + ", Heading: " + this.GetHeading() + " location: " + GetLocation();
00597         }
00598 
00604         protected bool Equals(EnemyGroup other)
00605         {
00606             if (this.EnemyPirates.Count != other.EnemyPirates.Count)
00607                 return false;
00608 
00609             for (int i = 0; i < this.EnemyPirates.Count; i++)
00610                 if (this.EnemyPirates[i] != other.EnemyPirates[i])
00611                     return false;
00612 
00613             return true;
00614         }
00615 
00621         public override bool Equals(object obj)
00622         {
00623             if (object.ReferenceEquals(null, obj))
00624                 return false;
00625             if (object.ReferenceEquals(this, obj))
00626                 return true;
00627             if (obj.GetType() != this.GetType())
00628                 return false;
00629             return Equals((EnemyGroup) obj);
00630         }
00631 
00632         public bool IsFormed()
00633         {
00634             Location[][] ringLocations = new Location[Group.GetRingCount(this.EnemyPirates.Count)][];
00635 
00636             //set the pivot
00637             Location pivot = this.GetLocation(true);
00638 
00639             //set the locations
00640             for (int i = 0; i < ringLocations.Length; i++)
00641             {
00642                 ringLocations[i] = Group.GenerateRingLocations(pivot, i).ToArray();
00643             }
00644 
00645             //excluding the 0th ring which is special
00646             int maxRing = ringLocations.Length - 2;
00647 
00648             //this is basic summation of arithmetic sequence excluding the first ting
00649             int maxEmptySpots = Group.GetStructureVolume(maxRing) - this.EnemyPirates.Count;
00650 
00651             //iterate over all the rings
00652             for (int i = 0; i < ringLocations.Length; i++)
00653             {
00654                 //iterate over all the location in each ring
00655                 for (int k = 0; k < ringLocations[i].Length; k++)
00656                 {
00657                     if (Bot.Game.GetPirateOn(ringLocations[i][k]) == null)
00658                         if (i == maxRing)
00659                             maxEmptySpots--;
00660                         else
00661                             return false;
00662                 }
00663             }
00664 
00665             if (maxEmptySpots < 0)
00666                 return false;
00667 
00668             return true;
00669         }
00670     }
00671 }