/// <summary> /// Find the best move for a unit. /// </summary> /// <param name="u">The unit to move</param> /// <param name="board">The board to find units on</param> /// <param name="task">The task the unit should do</param> /// <returns>A move that is decided as the best move for this unit this turn.</returns> public static Move BestMove(Unit u, Tile[,] board, Task task) { if (task == Task.improve) { //Get dictionary of amount of friendly units of type. Dictionary <UnitType, int> friendlies = AIUtil.Count(board, new LookupConstraint(new PlayerConstraint(false))); UnitType build = UnitType.builder; //The unit that needs to be built bool needToBuild = false; //Whether there is a unit to build //Find the first type in the build order that is not built yet. foreach (UnitType test in BUILD_ORDER) { if (!friendlies.ContainsKey(test)) { build = test; needToBuild = true; break; } if (friendlies[test] == 0) { build = test; needToBuild = true; break; } friendlies[test]--; } if (!needToBuild) { return(null); } //Check if the build target can be built (resources wise) if (!(build.Cost() <= Game.instance.computer.resources || COMPUTER_INFINATE_RESOURCES)) { return(null); } //Checks if the current unit is able to build the type if (u.GetBuildable().Contains(build)) { //Find all possibilites of building List <Move> options = u.BuildAroundMove(board, build, 1); //Make sure there is enough moves left for the unit to build options.RemoveAll((o) => { return(o.cost > u.movesLeft); }); //If there is an option to build the target, do it. if (options.Count > 0) { Move res = options[0]; return(res); } //Otherwise, if the unit is a builder, move to the nearest tile where building will be possible. else { if (u.type == UnitType.builder) { //Find path to the nearest tile where the build target can be built List <ALocation> path = PathFinder.PathToNearestTile(u.type, board, u.TileX, u.TileY, build.BuildableTiles()[0]); //If a non empty path is found, walk it if (path != null && path.Count != 0) { return(new MovementMove(u, path[0].x, path[0].y)); } } } } } if (task == Task.forward) { //Find the resource tile that is open Tile target = null; if (board[3, 2].buildingOn == null) { target = board[3, 2]; } if (board[4, 2].buildingOn == null) { target = board[4, 2]; } if (board[2, 3].buildingOn == null) { target = board[2, 3]; } if (board[2, 4].buildingOn == null) { target = board[2, 4]; } if (target != null) { //Find all build options of the harvester of the open tile. List <Move> options = u.BuildAroundMove(board, target.type.GetHarvesterType(), 1); //If a good build option is found, return it. options.RemoveAll((o) => { return(o.cost > u.movesLeft); }); if (options.Count > 0) { Move res = options[0]; return(res); } //Otherwise, find a path to that tile. else { List <ALocation> path = PathFinder.PathToNearestTile(u.type, board, u.px / Tile.TILE_WIDTH, u.py / Tile.TILE_HEIGHT, target.type); if (path != null && path.Count != 0) { return(new MovementMove(u, path[0].x, path[0].y)); } } } } if (task == Task.defend) { //First see if the unit can attack. If so, attack. List <Move> attacks = u.DefaultAttackAroundMove(board); if (attacks.Count > 0) { return(attacks[0]); } //If the unit can't attack, find a path to the nearest enemy unit in the friendly base. List <ALocation> path = PathFinder.PathToNearestUnit(u.type, board, u.TileX, u.TileY, new LookupConstraint(new PlayerConstraint(true), new DistanceConstraint(10, 10, 4))); if (path != null && path.Count > 0) { //If a path is found, move along it. int i = Math.Min(Math.Max(0, path.Count - 2), u.movesLeft - 1); return(new MovementMove(u, path[i].x, path[i].y)); } //If no path is found, push instead of defending. return(BestMove(u, board, Task.push)); } if (task == Task.push) { if (u.type.GetDamage() > 0) { //Find the move that maximizes map control, calculating 3 moves ahead. return(DepthSearch.GetBestMoveMin(u, board)); } if (u.type.IsTraining()) { //Build a unit. List <Move> options = u.BuildAroundMove(board, u.GetBuildable()[0], 1).Where(m => m.cost <= u.movesLeft).ToList(); if (options.Count == 0) { return(null); } Move build = options[0]; return(build); } } if (task == Task.destroy) { //Try to attack a nearby unit. List <Move> attacks = u.DefaultAttackAroundMove(board); if (attacks.Count > 0) { return(attacks[0]); } //If no nearby units, find the nearest unit and go towards it. List <ALocation> path = PathFinder.PathToNearestUnit(u.type, board, u.TileX, u.TileY, true); if (path == null || path.Count == 0) { return(null); } int i = Math.Min(Math.Max(0, path.Count - 2), u.movesLeft - 1); return(new MovementMove(u, path[i].x, path[i].y)); } if (task == Task.retreat) { //Find a path to the friendly base. List <ALocation> path = PathFinder.PathToCoordinates(u.type, board, u.TileX, u.TileY, 10, 10); if (path == null || path.Count == 0) { //If no path is found, find a path to a tile that is close to the friendly base. List <ALocation> path2 = PathFinder.PathToNearestTile(u.type, board, u.TileX, u.TileY, new TileLookupConstraint(new DistanceTileConstraint(10, 10, 3))); if (path2 == null || path2.Count == 0) { return(null); } int i2 = Math.Min(Math.Max(0, path2.Count - 2), u.movesLeft - 1); return(new MovementMove(u, path2[i2].x, path2[i2].y)); } int i = Math.Min(Math.Max(0, path.Count - 2), u.movesLeft - 1); return(new MovementMove(u, path[i].x, path[i].y)); } return(null); }