Advertisement
Huvaakoodia

Desert Strife AI - Version 4 (Post-Jam)

Oct 5th, 2016
348
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 15.67 KB | None | 0 0
  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. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement