Huvaakoodia

Desert Strife AI - Version 4 (Post-Jam)

Oct 5th, 2016
266
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3.  
  4. public class AI
  5. {
  6.     #region public interface
  7.     public class AIMove
  8.     {
  9.         public ArmyView Army;
  10.         public TileView Target;
  11.         public float Distance;
  12.  
  13.         public AIMove(ArmyView army, TileView target, float distance)
  14.         {
  15.             Army = army;
  16.             Target = target;
  17.             Distance = distance;
  18.         }
  19.     }
  20.  
  21.     public static AIMove FindBestMove(List<ArmyView> allies, List<ArmyView> enemies, TileView[,] world)
  22.     {
  23.         if (enemies.Count == 0) return null;
  24.  
  25.         var moves = new List<AIMove>();
  26.         foreach (var ally in allies)
  27.         {
  28.             if (ally.Disabled || ally.InPanic) continue;
  29.             if (!ally.CanMove) continue;
  30.             if (ally.TechnologyExcavationIndex > 0) continue;//always excavate to the end!
  31.  
  32.             int maxMovementRange = ally.GetMovementRange();
  33.             if (ally.HasTank && ally.TankFuel < maxMovementRange)
  34.             {
  35.                 //tanks don't move if out of fuel when they have the advantage
  36.                 if (allies.Count == 1 && enemies.Count == 1 && enemies[0].HasTank)
  37.                 {
  38.                     //except when there are only two tanks remaining
  39.                 }
  40.                 else
  41.                 {
  42.                     float allySupport = EvaluateTeamSupport(allies);
  43.                     float enemySupport = EvaluateTeamSupport(enemies);
  44.  
  45.                     if (allySupport < enemySupport) maxMovementRange = 0;
  46.                 }
  47.             }
  48.             //find closest enemy
  49.             ArmyView closestEnemy = null;
  50.             float closestEnemyDistance = float.MaxValue;
  51.             var heatMap = GenerateHeatMap(ally, enemies, world);
  52.  
  53.             foreach (var enemy in enemies)
  54.             {
  55.                 //can find path to target?
  56.  
  57.                 var path = PathFinder.FindPath(world, ally.CurrentTile.Position, enemy.CurrentTile.Position, ally.TeamID, 10000, null, heatMap);
  58.                 if (path == null) continue;
  59.  
  60.                 int enemyThreatLevel = 0;
  61.                 enemyThreatLevel = Mathf.Max(0, EvaluateThreatValue(ally, enemy));
  62.                 int positionThreatLevel = 0;
  63.  
  64.                 if (maxMovementRange == 0)
  65.                 {
  66.                     //too long path!
  67.                     if (path.next != null && path.next.next != null)
  68.                     {
  69.                         continue;
  70.                     }
  71.                 }
  72.                 else
  73.                 {
  74.                     //calculate threat level of the path end position (at movement range)
  75.                     int movesLeft = enemy.GetMovementRange();
  76.                     while (movesLeft > 0)
  77.                     {
  78.                         movesLeft--;
  79.                         var tile = world[path.position.X, path.position.Y];
  80.                         if (tile.CurrentArmy != null && tile.CurrentArmy == enemy) break;
  81.                         if (movesLeft == 0 && tile.CurrentArmy != null && tile.CurrentArmy.TeamID != ally.TeamID) break;
  82.                         path = path.next;
  83.                     }
  84.                     positionThreatLevel = heatMap[path.position.X, path.position.Y];
  85.                 }
  86.                 int threatLevel = Mathf.Max(enemyThreatLevel, positionThreatLevel);
  87.                 if (threatLevel < 0) threatLevel = 0;
  88.  
  89.                 //check if closest target
  90.                 float distance = Distance(ally, enemy) + threatLevel;
  91.                 if (distance < closestEnemyDistance)
  92.                 {
  93.                     closestEnemyDistance = distance;
  94.                     closestEnemy = enemy;
  95.                 }
  96.             }
  97.  
  98.             //find closest technology
  99.             TileView closestTech = null;
  100.             float closestTechDistance = float.MaxValue;
  101.             foreach (var tile in world)
  102.             {
  103.                 //only check legal targets
  104.                 if (tile.Type != TileView.TypeID.Technology || tile.TechnologyCount == 0 || tile.CurrentArmy != null) continue;
  105.  
  106.                 //can find path to target?
  107.                 if (PathFinder.FindPath(world, ally.CurrentTile.Position, tile.Position, ally.TeamID) == null)
  108.                 {
  109.                     continue;
  110.                 }
  111.  
  112.                 //check if closest target
  113.                 float distance = Distance(ally.CurrentTile, tile);
  114.                 if (distance < closestTechDistance)
  115.                 {
  116.                     closestTechDistance = distance;
  117.                     closestTech = tile;
  118.                 }
  119.             }
  120.  
  121.             //not moving
  122.             if (closestEnemy == null) continue;
  123.  
  124.             //choose target -> enemy is default target
  125.                 TileView targetTile = closestEnemy.CurrentTile;
  126.             float targetDistance = closestEnemyDistance;
  127.  
  128.             //go for tech if it is far away enough from closest enemy
  129.             if (closestTech != null && closestTechDistance + 2 < closestEnemyDistance)
  130.             {
  131.                 targetTile = closestTech;
  132.                 targetDistance = closestTechDistance;
  133.             }
  134.  
  135.             moves.Add(new AIMove(ally, targetTile, targetDistance));
  136.         }
  137.  
  138.         //choose best move -> attacks first, tech second
  139.         bool bestIsAttack = false;
  140.         float bestDistance = float.MaxValue;
  141.         AIMove bestMove = null;
  142.         for (int i = 0; i < moves.Count; i++)
  143.         {
  144.             var move = moves[i];
  145.             if (moves[i].Target.CurrentArmy != null)
  146.             {
  147.                 //attack
  148.                 if (move.Distance < bestDistance)
  149.                 {
  150.                     bestIsAttack = true;
  151.                     bestDistance = move.Distance;
  152.                     bestMove = move;
  153.                 }
  154.             }
  155.             else if (!bestIsAttack)
  156.             {
  157.                 //tech
  158.                 if (move.Distance < bestDistance)
  159.                 {
  160.                     bestMove = move;
  161.                     bestDistance = move.Distance;
  162.                 }
  163.             }
  164.         }
  165.         return bestMove;
  166.     }
  167.  
  168.     public static AIMove FindFleeMove(ArmyView ally, List<ArmyView> allies, List<ArmyView> enemies, TileView[,] world)
  169.     {
  170.         AIMove move = null;
  171.         int width = world.GetLength(0);
  172.         int height = world.GetLength(1);
  173.         var heatMap = new int[width, height];
  174.  
  175.         heatMap = GenerateHeatMap(ally, allies, enemies, world);
  176.         var path = PathFinder.FindEdgePath(world, new Point3D(ally.CurrentTile.X, ally.CurrentTile.Y), ally.TeamID, 10000, null, heatMap);
  177.  
  178.         while (path != null)
  179.         {
  180.             var tile = world[path.position.X, path.position.Y];
  181.  
  182.             if (tile != ally.CurrentTile)
  183.             {
  184.                 move = new AIMove(ally, tile, 1);
  185.                 break;
  186.             }
  187.             path = path.next;
  188.         }
  189.         return move;
  190.     }
  191.  
  192.     /// <summary>
  193.     /// Generates a heatmap with enemy threat values only.
  194.     /// </summary>
  195.     public static int[,] GenerateHeatMap(ArmyView ally, List<ArmyView> enemies, TileView[,] world)
  196.     {
  197.         int width = world.GetLength(0);
  198.         int height = world.GetLength(1);
  199.         var heatmap = new int[width, height];
  200.  
  201.         for (int i = 0; i < enemies.Count; i++)
  202.         {
  203.             var enemy = enemies[i];
  204.             int value = EvaluateThreatValue(ally, enemy);
  205.  
  206.             var depthArray = new int[width, height];
  207.             AddHeatmapValuesR(enemy.TeamID, enemy.CurrentTile, enemy.GetMovementRange() + 1, world, width, height, depthArray, heatmap, value);
  208.         }
  209.  
  210.         if (!ally.InPanic)
  211.         {
  212.             //prefer horizontal movement
  213.             int x = ally.CurrentTile.X, y = ally.CurrentTile.Y;
  214.             if (Helpers.InsideArea(x + 1, y, 0, 0, width, height) && heatmap[x + 1, y] <= 0) heatmap[x + 1, y] -= 1;
  215.             if (Helpers.InsideArea(x - 1, y, 0, 0, width, height) && heatmap[x - 1, y] <= 0) heatmap[x - 1, y] -= 1;
  216.         }
  217.         return heatmap;
  218.     }
  219.  
  220.     /// <summary>
  221.     /// Generates a heatmap with enemy threat values (positive) and ally support values (negative).
  222.     /// Threat values override support values.
  223.     /// </summary>
  224.     public static int[,] GenerateHeatMap(ArmyView ally, List<ArmyView> allies, List<ArmyView> enemies, TileView[,] world)
  225.     {
  226.         int width = world.GetLength(0);
  227.         int height = world.GetLength(1);
  228.  
  229.         var heatmap = GenerateHeatMap(ally, enemies, world);
  230.  
  231.         for (int i = 0; i < allies.Count; i++)
  232.         {
  233.             var ally2 = allies[i];
  234.             if (ally2 == ally) continue;
  235.  
  236.             int value = EvaluateSupportValue(ally2);
  237.  
  238.             var depthArray = new int[width, height];
  239.             AddHeatmapValuesR(ally2.TeamID, ally2.CurrentTile, ally2.GetMovementRange() + 1, world, width, height, depthArray, heatmap, value, true);
  240.         }
  241.         return heatmap;
  242.     }
  243.  
  244.     /// <summary>
  245.     /// Positive value is threatening, negative non-threatening
  246.     /// </summary>
  247.     public static int EvaluateThreatValue(ArmyView ally, ArmyView enemy)
  248.     {
  249.         float value = 0;
  250.         float meleeHitChance = BattleFunctions.MeleeHitChance * 0.01f;
  251.         float rifleHitChance = BattleFunctions.RifleHitChance * 0.01f;
  252.         float bazookaHitChance = BattleFunctions.BazookaHitChance * 0.01f;
  253.         int bazookaHitDamage = BattleFunctions.BazookaHitTankDamage;
  254.  
  255.         //calculate unit amounts
  256.         int allyMeleeUnits, allyRifleUnits, allyBazookaUnits, allyTankUnits;
  257.         BattleFunctions.CalculateUnits(ally, enemy, out allyMeleeUnits, out allyRifleUnits, out allyBazookaUnits, out allyTankUnits);
  258.  
  259.         int enemyMeleeUnits, enemyRifleUnits, enemyBazookaUnits, enemyTankUnits;
  260.         BattleFunctions.CalculateUnits(enemy, ally, out enemyMeleeUnits, out enemyRifleUnits, out enemyBazookaUnits, out enemyTankUnits);
  261.  
  262.         //assume the worst if we don't know the enemy ammo amounts
  263.         if (!enemy.IsRifleAmmoKnown) enemyRifleUnits = enemy.RifleAmount;
  264.         if (!enemy.IsBazookaAmmoKnown) enemyBazookaUnits = enemy.BazookaAmount;
  265.  
  266.         //infantry values
  267.         float meleeValue = allyMeleeUnits * meleeHitChance - enemyMeleeUnits * meleeHitChance;
  268.         float rifleValue = allyRifleUnits * rifleHitChance - enemyRifleUnits * rifleHitChance;
  269.  
  270.         //tank values
  271.         int enemyTankValue = 0;
  272.         if (enemy.HasTank)
  273.         {
  274.             //we don't know the enemy tank health -> expect full health
  275.             enemyTankValue = EvaluateTankValue(100);
  276.             int bazookaCount = Mathf.FloorToInt(allyBazookaUnits * bazookaHitChance);
  277.             enemyTankValue = (int)(enemyTankValue * ((100 - bazookaHitDamage * bazookaCount) / 100f));
  278.         }
  279.  
  280.         int allyTankValue = 0;
  281.         if (ally.HasTank)
  282.         {
  283.             allyTankValue = EvaluateTankValue(ally.TankHP);
  284.             int bazookaCount = Mathf.FloorToInt(enemyBazookaUnits * bazookaHitChance);
  285.             allyTankValue = (int)(allyTankValue * ((100 - bazookaHitDamage * bazookaCount) / 100f));
  286.         }
  287.  
  288.         //add up all values
  289.         value += meleeValue + rifleValue + allyTankValue - enemyTankValue;
  290.  
  291.         if (value == 0) value = -1;//always a small threat if enemy present
  292.         int sign = (int)Mathf.Sign(value);
  293.         return -sign * Mathf.CeilToInt(Mathf.Abs(value));
  294.     }
  295.  
  296.     /// <summary>
  297.     /// Evaluates the amount of support this ally gives.
  298.     /// Negative value.
  299.     /// </summary>
  300.     public static int EvaluateSupportValue(ArmyView ally)
  301.     {
  302.         //calculate units amounts
  303.         int allyMeleeUnits, allyRifleUnits, allyBazookaUnits, allyTankUnits;
  304.         BattleFunctions.CalculateUnits(ally, null, out allyMeleeUnits, out allyRifleUnits, out allyBazookaUnits, out allyTankUnits);
  305.  
  306.         float value = allyMeleeUnits * BattleFunctions.MeleeHitChance * 0.01f + allyRifleUnits * BattleFunctions.RifleHitChance * 0.01f + allyTankUnits * EvaluateTankValue(ally.TankHP);
  307.  
  308.         int sign = (int)Mathf.Sign(value);
  309.         return -sign * Mathf.CeilToInt(Mathf.Abs(value));
  310.     }
  311.  
  312.     public static int EvaluateTankValue(int tankHP)
  313.     {
  314.         if (tankHP <= 0) return 0;
  315.         return Mathf.RoundToInt((tankHP / (float)BattleFunctions.MeleeHitTankDamage) * 0.25f);
  316.     }
  317.  
  318.     /// <summary>
  319.     /// Calculates the total threat level around the unit (in 8 directions)
  320.     /// </summary>
  321.     public static int EvaluateThreatLevelAroundUnit(ArmyView army, TileView[,] world)
  322.     {
  323.         int totalThreatValue = 0;
  324.         int width = world.GetLength(0);
  325.         int height = world.GetLength(1);
  326.  
  327.         for (int i = 0; i < BattleFunctions.AllDirectionsLength; i++)
  328.         {
  329.             int x = army.CurrentTile.X + BattleFunctions.DirectionsX[i];
  330.             int y = army.CurrentTile.Y + BattleFunctions.DirectionsY[i];
  331.  
  332.             if (!Helpers.InsideArea(x, y, 0, 0, width, height)) continue;
  333.  
  334.             //check for non panicking units
  335.             var neighbour = world[x, y].CurrentArmy;
  336.             if (neighbour == null || neighbour.InPanic) continue;
  337.  
  338.             if (neighbour.TeamID == army.TeamID)
  339.             {
  340.                 //ally -> add a morale boost based on ally size
  341.                 totalThreatValue += EvaluateSupportValue(neighbour);
  342.             }
  343.             else
  344.             {
  345.                 //enemy -> add any positive threat value into the total
  346.                 int threatValue = EvaluateThreatValue(army, neighbour);
  347.                 if (threatValue > 0)
  348.                     totalThreatValue += threatValue;
  349.             }
  350.         }
  351.         return totalThreatValue;
  352.     }
  353.  
  354.     public static int EvaluateTeamSupport(List<ArmyView> allies)
  355.     {
  356.         int support = 0;
  357.         for (int i = 0; i < allies.Count; i++)
  358.         {
  359.             support -= AI.EvaluateSupportValue(allies[i]);
  360.         }
  361.         return support;
  362.     }
  363.  
  364.     #endregion
  365.     #region private interface
  366.  
  367.     /// <summary>
  368.     /// Recursively adds a value to the heatmap on every tile within the rage
  369.     /// </summary>
  370.     private static void AddHeatmapValuesR(int teamID, TileView tile, int depth, TileView[,] world, int width, int height, int[,] depthArray, int[,] heatmap, int value, bool onlyAddValueIfZeroOrLess = false)
  371.     {
  372.         if (depth == 0) return;
  373.  
  374.         int x = tile.X;
  375.         int y = tile.Y;
  376.        
  377.         //add value
  378.         if (depthArray[x, y] == 0)
  379.         {
  380.             if (onlyAddValueIfZeroOrLess)
  381.             {
  382.                 if (heatmap[x, y] <= 0) heatmap[x, y] += value;
  383.             }
  384.             else
  385.                 heatmap[x, y] += value;
  386.         }
  387.  
  388.         depthArray[x, y] = depth;
  389.  
  390.         //check all cardinal directions
  391.         for (int i = 0; i < BattleFunctions.CardinalDirectionsLength; i++)
  392.         {
  393.             x = tile.X + BattleFunctions.DirectionsX[i];
  394.             y = tile.Y + BattleFunctions.DirectionsY[i];
  395.  
  396.             if (Helpers.InsideArea(x, y, 0, 0, width, height))
  397.             {
  398.                 //already searched -> don't search again
  399.                 if (depthArray[x, y] >= depth) continue;
  400.  
  401.                 var neighbour = world[x, y];
  402.  
  403.                 //stop if tile occupied by an enemy
  404.                 if (neighbour.CurrentArmy != null && neighbour.CurrentArmy.TeamID != teamID) continue;
  405.  
  406.                 AddHeatmapValuesR(teamID, neighbour, depth - 1, world, width, height, depthArray, heatmap, value, onlyAddValueIfZeroOrLess);
  407.             }
  408.         }
  409.     }
  410.  
  411.     private static float Distance(ArmyView army1, ArmyView army2)
  412.     {
  413.         return Distance(army1.CurrentTile, army2.CurrentTile);
  414.     }
  415.  
  416.     private static float Distance(TileView tile1, TileView tile2)
  417.     {
  418.         return Vector2.Distance(tile1.Position, tile2.Position);
  419.     }
  420.     #endregion
  421. }
RAW Paste Data

Adblocker detected! Please consider disabling it...

We've detected AdBlock Plus or some other adblocking software preventing Pastebin.com from fully loading.

We don't have any obnoxious sound, or popup ads, we actively block these annoying types of ads!

Please add Pastebin.com to your ad blocker whitelist or disable your adblocking software.

×