Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using UnityEngine;
- using System.Collections.Generic;
- public class AI
- {
- #region public interface
- public class AIMove
- {
- public ArmyView Army;
- public TileView Target;
- public float Distance;
- public AIMove(ArmyView army, TileView target, float distance)
- {
- Army = army;
- Target = target;
- Distance = distance;
- }
- }
- public static AIMove FindBestMove(List<ArmyView> allies, List<ArmyView> enemies, TileView[,] world)
- {
- if (enemies.Count == 0) return null;
- var moves = new List<AIMove>();
- foreach (var ally in allies)
- {
- if (ally.Disabled || ally.InPanic) continue;
- if (!ally.CanMove) continue;
- if (ally.TechnologyExcavationIndex > 0) continue;//always excavate to the end!
- int maxMovementRange = ally.GetMovementRange();
- if (ally.HasTank && ally.TankFuel < maxMovementRange)
- {
- //tanks don't move if out of fuel when they have the advantage
- if (allies.Count == 1 && enemies.Count == 1 && enemies[0].HasTank)
- {
- //except when there are only two tanks remaining
- }
- else
- {
- float allySupport = EvaluateTeamSupport(allies);
- float enemySupport = EvaluateTeamSupport(enemies);
- if (allySupport < enemySupport) maxMovementRange = 0;
- }
- }
- //find closest enemy
- ArmyView closestEnemy = null;
- float closestEnemyDistance = float.MaxValue;
- var heatMap = GenerateHeatMap(ally, enemies, world);
- foreach (var enemy in enemies)
- {
- //can find path to target?
- var path = PathFinder.FindPath(world, ally.CurrentTile.Position, enemy.CurrentTile.Position, ally.TeamID, 10000, null, heatMap);
- if (path == null) continue;
- int enemyThreatLevel = 0;
- enemyThreatLevel = Mathf.Max(0, EvaluateThreatValue(ally, enemy));
- int positionThreatLevel = 0;
- if (maxMovementRange == 0)
- {
- //too long path!
- if (path.next != null && path.next.next != null)
- {
- continue;
- }
- }
- else
- {
- //calculate threat level of the path end position (at movement range)
- int movesLeft = enemy.GetMovementRange();
- while (movesLeft > 0)
- {
- movesLeft--;
- var tile = world[path.position.X, path.position.Y];
- if (tile.CurrentArmy != null && tile.CurrentArmy == enemy) break;
- if (movesLeft == 0 && tile.CurrentArmy != null && tile.CurrentArmy.TeamID != ally.TeamID) break;
- path = path.next;
- }
- positionThreatLevel = heatMap[path.position.X, path.position.Y];
- }
- int threatLevel = Mathf.Max(enemyThreatLevel, positionThreatLevel);
- if (threatLevel < 0) threatLevel = 0;
- //check if closest target
- float distance = Distance(ally, enemy) + threatLevel;
- if (distance < closestEnemyDistance)
- {
- closestEnemyDistance = distance;
- closestEnemy = enemy;
- }
- }
- //find closest technology
- TileView closestTech = null;
- float closestTechDistance = float.MaxValue;
- foreach (var tile in world)
- {
- //only check legal targets
- if (tile.Type != TileView.TypeID.Technology || tile.TechnologyCount == 0 || tile.CurrentArmy != null) continue;
- //can find path to target?
- if (PathFinder.FindPath(world, ally.CurrentTile.Position, tile.Position, ally.TeamID) == null)
- {
- continue;
- }
- //check if closest target
- float distance = Distance(ally.CurrentTile, tile);
- if (distance < closestTechDistance)
- {
- closestTechDistance = distance;
- closestTech = tile;
- }
- }
- //not moving
- if (closestEnemy == null) continue;
- //choose target -> enemy is default target
- TileView targetTile = closestEnemy.CurrentTile;
- float targetDistance = closestEnemyDistance;
- //go for tech if it is far away enough from closest enemy
- if (closestTech != null && closestTechDistance + 2 < closestEnemyDistance)
- {
- targetTile = closestTech;
- targetDistance = closestTechDistance;
- }
- moves.Add(new AIMove(ally, targetTile, targetDistance));
- }
- //choose best move -> attacks first, tech second
- bool bestIsAttack = false;
- float bestDistance = float.MaxValue;
- AIMove bestMove = null;
- for (int i = 0; i < moves.Count; i++)
- {
- var move = moves[i];
- if (moves[i].Target.CurrentArmy != null)
- {
- //attack
- if (move.Distance < bestDistance)
- {
- bestIsAttack = true;
- bestDistance = move.Distance;
- bestMove = move;
- }
- }
- else if (!bestIsAttack)
- {
- //tech
- if (move.Distance < bestDistance)
- {
- bestMove = move;
- bestDistance = move.Distance;
- }
- }
- }
- return bestMove;
- }
- public static AIMove FindFleeMove(ArmyView ally, List<ArmyView> allies, List<ArmyView> enemies, TileView[,] world)
- {
- AIMove move = null;
- int width = world.GetLength(0);
- int height = world.GetLength(1);
- var heatMap = new int[width, height];
- heatMap = GenerateHeatMap(ally, allies, enemies, world);
- var path = PathFinder.FindEdgePath(world, new Point3D(ally.CurrentTile.X, ally.CurrentTile.Y), ally.TeamID, 10000, null, heatMap);
- while (path != null)
- {
- var tile = world[path.position.X, path.position.Y];
- if (tile != ally.CurrentTile)
- {
- move = new AIMove(ally, tile, 1);
- break;
- }
- path = path.next;
- }
- return move;
- }
- /// <summary>
- /// Generates a heatmap with enemy threat values only.
- /// </summary>
- public static int[,] GenerateHeatMap(ArmyView ally, List<ArmyView> enemies, TileView[,] world)
- {
- int width = world.GetLength(0);
- int height = world.GetLength(1);
- var heatmap = new int[width, height];
- for (int i = 0; i < enemies.Count; i++)
- {
- var enemy = enemies[i];
- int value = EvaluateThreatValue(ally, enemy);
- var depthArray = new int[width, height];
- AddHeatmapValuesR(enemy.TeamID, enemy.CurrentTile, enemy.GetMovementRange() + 1, world, width, height, depthArray, heatmap, value);
- }
- if (!ally.InPanic)
- {
- //prefer horizontal movement
- int x = ally.CurrentTile.X, y = ally.CurrentTile.Y;
- if (Helpers.InsideArea(x + 1, y, 0, 0, width, height) && heatmap[x + 1, y] <= 0) heatmap[x + 1, y] -= 1;
- if (Helpers.InsideArea(x - 1, y, 0, 0, width, height) && heatmap[x - 1, y] <= 0) heatmap[x - 1, y] -= 1;
- }
- return heatmap;
- }
- /// <summary>
- /// Generates a heatmap with enemy threat values (positive) and ally support values (negative).
- /// Threat values override support values.
- /// </summary>
- public static int[,] GenerateHeatMap(ArmyView ally, List<ArmyView> allies, List<ArmyView> enemies, TileView[,] world)
- {
- int width = world.GetLength(0);
- int height = world.GetLength(1);
- var heatmap = GenerateHeatMap(ally, enemies, world);
- for (int i = 0; i < allies.Count; i++)
- {
- var ally2 = allies[i];
- if (ally2 == ally) continue;
- int value = EvaluateSupportValue(ally2);
- var depthArray = new int[width, height];
- AddHeatmapValuesR(ally2.TeamID, ally2.CurrentTile, ally2.GetMovementRange() + 1, world, width, height, depthArray, heatmap, value, true);
- }
- return heatmap;
- }
- /// <summary>
- /// Positive value is threatening, negative non-threatening
- /// </summary>
- public static int EvaluateThreatValue(ArmyView ally, ArmyView enemy)
- {
- float value = 0;
- float meleeHitChance = BattleFunctions.MeleeHitChance * 0.01f;
- float rifleHitChance = BattleFunctions.RifleHitChance * 0.01f;
- float bazookaHitChance = BattleFunctions.BazookaHitChance * 0.01f;
- int bazookaHitDamage = BattleFunctions.BazookaHitTankDamage;
- //calculate unit amounts
- int allyMeleeUnits, allyRifleUnits, allyBazookaUnits, allyTankUnits;
- BattleFunctions.CalculateUnits(ally, enemy, out allyMeleeUnits, out allyRifleUnits, out allyBazookaUnits, out allyTankUnits);
- int enemyMeleeUnits, enemyRifleUnits, enemyBazookaUnits, enemyTankUnits;
- BattleFunctions.CalculateUnits(enemy, ally, out enemyMeleeUnits, out enemyRifleUnits, out enemyBazookaUnits, out enemyTankUnits);
- //assume the worst if we don't know the enemy ammo amounts
- if (!enemy.IsRifleAmmoKnown) enemyRifleUnits = enemy.RifleAmount;
- if (!enemy.IsBazookaAmmoKnown) enemyBazookaUnits = enemy.BazookaAmount;
- //infantry values
- float meleeValue = allyMeleeUnits * meleeHitChance - enemyMeleeUnits * meleeHitChance;
- float rifleValue = allyRifleUnits * rifleHitChance - enemyRifleUnits * rifleHitChance;
- //tank values
- int enemyTankValue = 0;
- if (enemy.HasTank)
- {
- //we don't know the enemy tank health -> expect full health
- enemyTankValue = EvaluateTankValue(100);
- int bazookaCount = Mathf.FloorToInt(allyBazookaUnits * bazookaHitChance);
- enemyTankValue = (int)(enemyTankValue * ((100 - bazookaHitDamage * bazookaCount) / 100f));
- }
- int allyTankValue = 0;
- if (ally.HasTank)
- {
- allyTankValue = EvaluateTankValue(ally.TankHP);
- int bazookaCount = Mathf.FloorToInt(enemyBazookaUnits * bazookaHitChance);
- allyTankValue = (int)(allyTankValue * ((100 - bazookaHitDamage * bazookaCount) / 100f));
- }
- //add up all values
- value += meleeValue + rifleValue + allyTankValue - enemyTankValue;
- if (value == 0) value = -1;//always a small threat if enemy present
- int sign = (int)Mathf.Sign(value);
- return -sign * Mathf.CeilToInt(Mathf.Abs(value));
- }
- /// <summary>
- /// Evaluates the amount of support this ally gives.
- /// Negative value.
- /// </summary>
- public static int EvaluateSupportValue(ArmyView ally)
- {
- //calculate units amounts
- int allyMeleeUnits, allyRifleUnits, allyBazookaUnits, allyTankUnits;
- BattleFunctions.CalculateUnits(ally, null, out allyMeleeUnits, out allyRifleUnits, out allyBazookaUnits, out allyTankUnits);
- float value = allyMeleeUnits * BattleFunctions.MeleeHitChance * 0.01f + allyRifleUnits * BattleFunctions.RifleHitChance * 0.01f + allyTankUnits * EvaluateTankValue(ally.TankHP);
- int sign = (int)Mathf.Sign(value);
- return -sign * Mathf.CeilToInt(Mathf.Abs(value));
- }
- public static int EvaluateTankValue(int tankHP)
- {
- if (tankHP <= 0) return 0;
- return Mathf.RoundToInt((tankHP / (float)BattleFunctions.MeleeHitTankDamage) * 0.25f);
- }
- /// <summary>
- /// Calculates the total threat level around the unit (in 8 directions)
- /// </summary>
- public static int EvaluateThreatLevelAroundUnit(ArmyView army, TileView[,] world)
- {
- int totalThreatValue = 0;
- int width = world.GetLength(0);
- int height = world.GetLength(1);
- for (int i = 0; i < BattleFunctions.AllDirectionsLength; i++)
- {
- int x = army.CurrentTile.X + BattleFunctions.DirectionsX[i];
- int y = army.CurrentTile.Y + BattleFunctions.DirectionsY[i];
- if (!Helpers.InsideArea(x, y, 0, 0, width, height)) continue;
- //check for non panicking units
- var neighbour = world[x, y].CurrentArmy;
- if (neighbour == null || neighbour.InPanic) continue;
- if (neighbour.TeamID == army.TeamID)
- {
- //ally -> add a morale boost based on ally size
- totalThreatValue += EvaluateSupportValue(neighbour);
- }
- else
- {
- //enemy -> add any positive threat value into the total
- int threatValue = EvaluateThreatValue(army, neighbour);
- if (threatValue > 0)
- totalThreatValue += threatValue;
- }
- }
- return totalThreatValue;
- }
- public static int EvaluateTeamSupport(List<ArmyView> allies)
- {
- int support = 0;
- for (int i = 0; i < allies.Count; i++)
- {
- support -= AI.EvaluateSupportValue(allies[i]);
- }
- return support;
- }
- #endregion
- #region private interface
- /// <summary>
- /// Recursively adds a value to the heatmap on every tile within the rage
- /// </summary>
- 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)
- {
- if (depth == 0) return;
- int x = tile.X;
- int y = tile.Y;
- //add value
- if (depthArray[x, y] == 0)
- {
- if (onlyAddValueIfZeroOrLess)
- {
- if (heatmap[x, y] <= 0) heatmap[x, y] += value;
- }
- else
- heatmap[x, y] += value;
- }
- depthArray[x, y] = depth;
- //check all cardinal directions
- for (int i = 0; i < BattleFunctions.CardinalDirectionsLength; i++)
- {
- x = tile.X + BattleFunctions.DirectionsX[i];
- y = tile.Y + BattleFunctions.DirectionsY[i];
- if (Helpers.InsideArea(x, y, 0, 0, width, height))
- {
- //already searched -> don't search again
- if (depthArray[x, y] >= depth) continue;
- var neighbour = world[x, y];
- //stop if tile occupied by an enemy
- if (neighbour.CurrentArmy != null && neighbour.CurrentArmy.TeamID != teamID) continue;
- AddHeatmapValuesR(teamID, neighbour, depth - 1, world, width, height, depthArray, heatmap, value, onlyAddValueIfZeroOrLess);
- }
- }
- }
- private static float Distance(ArmyView army1, ArmyView army2)
- {
- return Distance(army1.CurrentTile, army2.CurrentTile);
- }
- private static float Distance(TileView tile1, TileView tile2)
- {
- return Vector2.Distance(tile1.Position, tile2.Position);
- }
- #endregion
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement