#region Copyright & License Information /* * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; namespace OpenRA.Mods.Common.Pathfinder { public interface IPathSearch : IDisposable { /// /// The Graph used by the A* /// IGraph Graph { get; } /// /// Stores the analyzed nodes by the expand function /// IEnumerable> Considered { get; } Player Owner { get; } int MaxCost { get; } IPathSearch Reverse(); IPathSearch WithCustomBlocker(Func customBlock); IPathSearch WithIgnoredActor(Actor b); IPathSearch WithHeuristic(Func h); IPathSearch WithHeuristicWeight(int percentage); IPathSearch WithCustomCost(Func w); IPathSearch WithoutLaneBias(); IPathSearch FromPoint(CPos from); /// /// Decides whether a location is a target based on its estimate /// (An estimate of 0 means that the location and the unit's goal /// are the same. There could be multiple goals). /// /// The location to assess /// Whether the location is a target bool IsTarget(CPos location); bool CanExpand { get; } CPos Expand(); } public abstract class BasePathSearch : IPathSearch { public IGraph Graph { get; set; } protected IPriorityQueue OpenQueue { get; private set; } public abstract IEnumerable> Considered { get; } public Player Owner { get { return Graph.Actor.Owner; } } public int MaxCost { get; protected set; } public bool Debug { get; set; } protected Func heuristic; protected Func isGoal; protected int heuristicWeightPercentage; // This member is used to compute the ID of PathSearch. // Essentially, it represents a collection of the initial // points considered and their Heuristics to reach // the target. It pretty match identifies, in conjunction of the Actor, // a deterministic set of calculations protected readonly IPriorityQueue StartPoints; private readonly int cellCost, diagonalCellCost; protected BasePathSearch(IGraph graph) { Graph = graph; OpenQueue = new PriorityQueue(GraphConnection.ConnectionCostComparer); StartPoints = new PriorityQueue(GraphConnection.ConnectionCostComparer); MaxCost = 0; heuristicWeightPercentage = 100; // Determine the minimum possible cost for moving horizontally between cells based on terrain speeds. // The minimum possible cost diagonally is then Sqrt(2) times more costly. cellCost = graph.Actor.Trait().Locomotor.Info.TerrainSpeeds.Values.Min(ti => ti.Cost); diagonalCellCost = cellCost * 141421 / 100000; } /// /// Default: Diagonal distance heuristic. More information: /// http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html /// /// A delegate that calculates the estimation for a node protected Func DefaultEstimator(CPos destination) { return here => { var diag = Math.Min(Math.Abs(here.X - destination.X), Math.Abs(here.Y - destination.Y)); var straight = Math.Abs(here.X - destination.X) + Math.Abs(here.Y - destination.Y); // According to the information link, this is the shape of the function. // We just extract factors to simplify. // Possible simplification: var h = Constants.CellCost * (straight + (Constants.Sqrt2 - 2) * diag); return (cellCost * straight + (diagonalCellCost - 2 * cellCost) * diag) * heuristicWeightPercentage / 100; }; } public IPathSearch Reverse() { Graph.InReverse = true; return this; } public IPathSearch WithCustomBlocker(Func customBlock) { Graph.CustomBlock = customBlock; return this; } public IPathSearch WithIgnoredActor(Actor b) { Graph.IgnoreActor = b; return this; } public IPathSearch WithHeuristic(Func h) { heuristic = h; return this; } public IPathSearch WithHeuristicWeight(int percentage) { heuristicWeightPercentage = percentage; return this; } public IPathSearch WithCustomCost(Func w) { Graph.CustomCost = w; return this; } public IPathSearch WithoutLaneBias() { Graph.LaneBias = 0; return this; } public IPathSearch FromPoint(CPos from) { if (Graph.World.Map.Contains(from)) AddInitialCell(from); return this; } protected abstract void AddInitialCell(CPos cell); public bool IsTarget(CPos location) { return isGoal(location); } public bool CanExpand { get { return !OpenQueue.Empty; } } public abstract CPos Expand(); protected virtual void Dispose(bool disposing) { if (disposing) Graph.Dispose(); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }