#region Copyright & License Information /* * Copyright 2007-2015 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. For more information, * see COPYING. */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; using Moq; using NUnit.Framework; using OpenRA; using OpenRA.Mods.Common.Pathfinder; using OpenRA.Mods.Common.Traits; using OpenRA.Test; namespace PathfinderTests { [TestFixture] public class PathfinderTests { const int Width = 128; const int Height = 128; IWorld world; IMap map; IActor actor; [SetUp] public void Setup() { map = new FakeMap(Width, Height); var worldactor = new FakeActor(); world = new FakeWorld(map, worldactor); actor = new FakeActor(world); } IMap BuildFakeMap(int mapWidth, int mapHeight) { var map = new Mock(); map.SetupGet(m => m.TileShape).Returns(TileShape.Rectangle); map.Setup(m => m.MapSize).Returns(new int2(mapWidth, mapHeight)); map.Setup(m => m.Contains(It.Is(pos => pos.X >= 0 && pos.X < mapWidth && pos.Y >= 0 && pos.Y < mapHeight))).Returns(true); return map.Object; } IWorld BuildFakeWorld(IMap map) { var world = new Mock(); world.SetupGet(m => m.Map).Returns(map); world.SetupGet(m => m.WorldActor).Returns(new Mock().Object); return world.Object; } static bool IsValidPos(CPos pos, int mapWidth, int mapHeight) { return pos.X >= 0 && pos.X < mapWidth && pos.Y >= 0 && pos.Y < mapHeight; } [Test] [Ignore] public void FindPathOnRoughTerrainTest() { // Arrange // Create the MobileInfo Mock. Playing with this can help to // check the different paths and points a unit can walk into var mi = new FakeMobileInfo(pos => !(!IsValidPos(pos, Width, Height) || (pos.X == 50 && pos.Y < 100) || (pos.X == 100 && pos.Y > 50))); var from = new CPos(1, 1); var target = new CPos(125, 75); IPathSearch search; Stopwatch stopwatch; List path1 = null; List path2 = null; List path3 = null; List path4 = null; List path5 = null; List path6 = null; List path7 = null; List path8 = null; var pathfinder = new PathFinder(world); // Act stopwatch = new Stopwatch(); foreach (var a in Enumerable.Range(1, 50)) { search = PathSearch.FromPoint(world, mi, actor, from, target, true); stopwatch.Start(); path5 = pathfinder.FindPath(search); stopwatch.Stop(); search = PathSearch.FromPoint(world, mi, actor, new CPos(0, 0), new CPos(51, 100), true); stopwatch.Start(); path6 = pathfinder.FindPath(search); stopwatch.Stop(); search = PathSearch.FromPoint(world, mi, actor, new CPos(0, 0), new CPos(49, 50), true); stopwatch.Start(); path7 = pathfinder.FindPath(search); stopwatch.Stop(); search = PathSearch.FromPoint(world, mi, actor, new CPos(127, 0), new CPos(50, 101), true); stopwatch.Start(); path8 = pathfinder.FindPath(search); } Console.WriteLine("I took " + stopwatch.ElapsedMilliseconds + " ms with new pathfinder"); IPathSearch search2; stopwatch = new Stopwatch(); foreach (var a in Enumerable.Range(1, 50)) { search = PathSearch.FromPoint(world, mi, actor, from, target, true); search2 = PathSearch.FromPoint(world, mi, actor, target, from, true).Reverse(); stopwatch.Start(); path5 = pathfinder.FindBidiPath(search, search2); stopwatch.Stop(); search = PathSearch.FromPoint(world, mi, actor, new CPos(0, 0), new CPos(51, 100), true); search2 = PathSearch.FromPoint(world, mi, actor, new CPos(51, 100), new CPos(0, 0), true).Reverse(); stopwatch.Start(); path6 = pathfinder.FindBidiPath(search, search2); stopwatch.Stop(); search = PathSearch.FromPoint(world, mi, actor, new CPos(0, 0), new CPos(49, 50), true); search2 = PathSearch.FromPoint(world, mi, actor, new CPos(49, 50), new CPos(0, 0), true).Reverse(); stopwatch.Start(); path7 = pathfinder.FindBidiPath(search, search2); stopwatch.Stop(); search = PathSearch.FromPoint(world, mi, actor, new CPos(127, 0), new CPos(50, 101), true); search2 = PathSearch.FromPoint(world, mi, actor, new CPos(50, 101), new CPos(127, 0), true).Reverse(); stopwatch.Start(); path8 = pathfinder.FindBidiPath(search, search2); } Console.WriteLine("I took " + stopwatch.ElapsedMilliseconds + " ms with new FindBidipathfinder"); } /// /// We can't rely on floating point math to be deterministic across all runtimes. /// The cases that use this will need to be changed to use integer math /// public const double Sqrt2 = 1.414; static int Est1(CPos here, CPos destination) { 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); // Min cost to arrive from once cell to an adjacent one // (125 according to tests) const int D = 100; // According to the information link, this is the shape of the function. // We just extract factors to simplify. var h = D * straight + (D * Sqrt2 - 2 * D) * diag; return (int)(h * 1.001); } static int Est2(CPos here, CPos destination) { 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); // HACK: this relies on fp and cell-size assumptions. var h = (100 * diag * Sqrt2) + 100 * (straight - (2 * diag)); return (int)(h * 1.001); } /// /// Tests the refactor of the default heuristic for pathFinder /// [Test] public void EstimatorsTest() { Assert.AreEqual(Est1(new CPos(0, 0), new CPos(20, 30)), Est2(new CPos(0, 0), new CPos(20, 30))); } [Test] public void Remove1000StoredPaths() { var world = new Mock(); world.SetupGet(m => m.WorldTick).Returns(50); var pathCacheStorage = new PathCacheStorage(world.Object); var stopwatch = new Stopwatch(); for (var i = 0; i < 1100; i++) { if (i == 100) { // Let's make the world tick further so we can trigger the removals // when storing more stuff world.SetupGet(m => m.WorldTick).Returns(110); stopwatch.Start(); } pathCacheStorage.Store(i.ToString(), new List()); if (i == 100) { stopwatch.Stop(); Console.WriteLine("I took " + stopwatch.ElapsedMilliseconds + " ms to remove 1000 stored paths"); } } } /// /// Test for the future feature of path smoothing for Pathfinder /// [Test] public void RayCastingTest() { // Arrange var sut = new RayCaster(); CPos source = new CPos(1, 3); CPos target = new CPos(3, 0); // Act var valid = sut.RayCast(source, target); // Assert } } public class RayCaster { // Algorithm obtained in http://playtechs.blogspot.co.uk/2007/03/raytracing-on-grid.html public IEnumerable RayCast(CPos source, CPos target) { int dx = Math.Abs(target.X - source.X); int dy = Math.Abs(target.Y - source.Y); int x = source.X; int y = source.Y; int x_inc = (target.X > source.X) ? 1 : -1; int y_inc = (target.Y > source.Y) ? 1 : -1; int error = dx - dy; dx *= 2; dy *= 2; for (int n = 1 + dx + dy; n > 0; --n) { yield return new CPos(x, y); if (error > 0) { x += x_inc; error -= dy; } else { y += y_inc; error += dx; } } } public bool RayClear() { return true; } } }