using Azimuth; using System; using UnitTestSharp; namespace Annulus.UnitTests { public class HexLayoutTests : TestFixture { public static readonly double SquareRootOfThree = Math.Sqrt(3); public static HexLayout Unit(HexOrientation orientation) { return new HexLayout(orientation, size: 1, origin: Vector.Zero); } public static Vector OffsetToNeighbor(HexLayout layout, int direction) { if (layout.Orientation == HexOrientation.PointyTop) { return layout.NeighborOffset((PointyHexDirection)direction); } return layout.NeighborOffset((FlatHexDirection)direction); } public static HexIndex NeighborOf(HexLayout layout, HexIndex index, int direction) { if (layout.Orientation == HexOrientation.PointyTop) { return index.Neighbor((PointyHexDirection)direction); } return index.Neighbor((FlatHexDirection)direction); } public static int NearestEdgeNumber(HexLayout layout, Vector point, HexIndex index) { if (layout.Orientation == HexOrientation.PointyTop) { return (int)layout.NearestPointyEdge(point, index); } return (int)layout.NearestFlatEdge(point, index); } public class CtorTests : TestFixture { public void KeepsOrientationSizeAndOrigin() { var layout = new HexLayout(HexOrientation.FlatTop, size: 4, origin: new Vector(7, -2)); CheckEqual(HexOrientation.FlatTop, layout.Orientation); CheckEqual(4, layout.Size); CheckEqual(new Vector(7, -2), layout.Origin); } public void ZeroSize_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new HexLayout(HexOrientation.PointyTop, size: 0, origin: Vector.Zero); } public void NegativeSize_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new HexLayout(HexOrientation.PointyTop, size: -1, origin: Vector.Zero); } public void NaNSize_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); new HexLayout(HexOrientation.PointyTop, size: Scalar.NaN, origin: Vector.Zero); } } public class CenterTests : TestFixture { public void PointyTop_IndexZeroSitsAtTheOrigin() { var layout = new HexLayout(HexOrientation.PointyTop, size: 1, origin: new Vector(3, 4)); CheckEqual(new Vector(3, 4), layout.Center(new HexIndex(0, 0))); } public void PointyTop_EastNeighborIsDirectlyRight() { CheckEqual( new Vector(SquareRootOfThree, 0), Unit(HexOrientation.PointyTop).Center(new HexIndex(1, 0))); } public void PointyTop_SouthEastNeighborIsDownAndHalfAHexRight() { CheckEqual( new Vector(SquareRootOfThree / 2, 1.5), Unit(HexOrientation.PointyTop).Center(new HexIndex(0, 1))); } public void PointyTop_SouthWestNeighborIsDownAndHalfAHexLeft() { CheckEqual( new Vector(-SquareRootOfThree / 2, 1.5), Unit(HexOrientation.PointyTop).Center(new HexIndex(-1, 1))); } public void FlatTop_IndexZeroSitsAtTheOrigin() { var layout = new HexLayout(HexOrientation.FlatTop, size: 1, origin: new Vector(3, 4)); CheckEqual(new Vector(3, 4), layout.Center(new HexIndex(0, 0))); } public void FlatTop_SouthEastNeighborIsDirectlyBelow() { CheckEqual( new Vector(0, SquareRootOfThree), Unit(HexOrientation.FlatTop).Center(new HexIndex(0, 1))); } public void FlatTop_EastNeighborIsDownAndRight() { CheckEqual( new Vector(1.5, SquareRootOfThree / 2), Unit(HexOrientation.FlatTop).Center(new HexIndex(1, 0))); } public void FlatTop_SouthWestNeighborIsDownAndLeft() { CheckEqual( new Vector(-1.5, SquareRootOfThree / 2), Unit(HexOrientation.FlatTop).Center(new HexIndex(-1, 1))); } public void ScalesWithSizeAndShiftsWithOrigin() { var layout = new HexLayout(HexOrientation.PointyTop, size: 2, origin: new Vector(10, 20)); CheckEqual(new Vector(10 + SquareRootOfThree, 23), layout.Center(new HexIndex(0, 1))); } } public class NeighborOffsetTests : TestFixture { public void PointyTop_EastIsDirectlyRight() { CheckEqual( new Vector(SquareRootOfThree, 0), Unit(HexOrientation.PointyTop).NeighborOffset(PointyHexDirection.East)); } public void FlatTop_SouthIsDirectlyBelow() { CheckEqual( new Vector(0, SquareRootOfThree), Unit(HexOrientation.FlatTop).NeighborOffset(FlatHexDirection.South)); } public void FlatTop_SouthEastIsDownAndRight() { CheckEqual( new Vector(1.5, SquareRootOfThree / 2), Unit(HexOrientation.FlatTop).NeighborOffset(FlatHexDirection.SouthEast)); } public void PointyTopDirectionOnAFlatTopLayout_Throws() { CheckThrow(typeof(InvalidOperationException)); Unit(HexOrientation.FlatTop).NeighborOffset(PointyHexDirection.East); } public void FlatTopDirectionOnAPointyTopLayout_Throws() { CheckThrow(typeof(InvalidOperationException)); Unit(HexOrientation.PointyTop).NeighborOffset(FlatHexDirection.South); } public void NotAHexDirection_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Unit(HexOrientation.PointyTop).NeighborOffset((PointyHexDirection)6); } public void OriginDoesNotMoveIt() { var layout = new HexLayout(HexOrientation.PointyTop, size: 1, origin: new Vector(50, -9)); CheckEqual(new Vector(SquareRootOfThree, 0), layout.NeighborOffset(PointyHexDirection.East)); } public void EveryNeighborIsRootThreeSizesAway() { foreach (HexOrientation orientation in new[] { HexOrientation.PointyTop, HexOrientation.FlatTop }) { var layout = new HexLayout(orientation, size: 3, origin: Vector.Zero); for (int direction = 0; direction < HexDirections.Count; ++direction) { CheckEqual(3 * SquareRootOfThree, OffsetToNeighbor(layout, direction).Length()); } } } public void OppositeDirectionsPointOppositeWays() { foreach (HexOrientation orientation in new[] { HexOrientation.PointyTop, HexOrientation.FlatTop }) { HexLayout layout = Unit(orientation); for (int direction = 0; direction < HexDirections.Count; ++direction) { CheckEqual( -OffsetToNeighbor(layout, direction), OffsetToNeighbor(layout, HexDirections.RotatedClockwise(direction, 3))); } } } } public class AxialAtTests : TestFixture { public void TheOrigin_IsAxialZero() { var layout = new HexLayout(HexOrientation.FlatTop, size: 3, origin: new Vector(5, -2)); layout.AxialAt(new Vector(5, -2), out Scalar q, out Scalar r); CheckEqual((Scalar)0, q); CheckEqual((Scalar)0, r); } public void ACenter_IsTheHexIndex() { foreach (HexOrientation orientation in new[] { HexOrientation.PointyTop, HexOrientation.FlatTop }) { var layout = new HexLayout(orientation, size: 1.5, origin: new Vector(4, -8)); layout.AxialAt(layout.Center(new HexIndex(2, -3)), out Scalar q, out Scalar r); CheckEqual((Scalar)2, q); CheckEqual((Scalar)(-3), r); } } public void HalfwayToANeighbor_IsHalfwayInAxialToo() { var layout = Unit(HexOrientation.PointyTop); Vector halfway = layout.NeighborOffset(PointyHexDirection.SouthEast) * 0.5; layout.AxialAt(halfway, out Scalar q, out Scalar r); CheckEqual((Scalar)0, q); CheckEqual((Scalar)0.5, r); } } public class HexesAlongTests : TestFixture { public void WalksTheSegmentBetweenThePoints() { var layout = Unit(HexOrientation.PointyTop); Vector from = layout.Center(new HexIndex(0, 0)); Vector to = layout.Center(new HexIndex(2, 0)); var hexes = new System.Collections.Generic.List(); foreach (HexIndex hex in layout.HexesAlong(from, to)) { hexes.Add(hex); } CheckEqual(new[] { new HexIndex(0, 0), new HexIndex(1, 0), new HexIndex(2, 0) }, hexes); } } public class HexAtTests : TestFixture { [IgnoreTest] public void CheckEveryCenterRoundTrips(HexLayout layout) { for (int q = -6; q <= 6; ++q) { for (int r = -6; r <= 6; ++r) { var index = new HexIndex(q, r); CheckEqual(index, layout.HexAt(layout.Center(index))); } } } [IgnoreTest] public void CheckPointsJustInsideEachEdgeBelongToThatHex(HexLayout layout) { var index = new HexIndex(2, -3); for (int direction = 0; direction < HexDirections.Count; ++direction) { Vector justInside = layout.Center(index) + OffsetToNeighbor(layout, direction) * 0.49; Vector justOutside = layout.Center(index) + OffsetToNeighbor(layout, direction) * 0.51; CheckEqual(index, layout.HexAt(justInside)); CheckEqual(NeighborOf(layout, index, direction), layout.HexAt(justOutside)); } } public void PointyTop_EveryCenterRoundTrips() { CheckEveryCenterRoundTrips( new HexLayout(HexOrientation.PointyTop, size: 1.5, origin: new Vector(4, -8))); } public void FlatTop_EveryCenterRoundTrips() { CheckEveryCenterRoundTrips( new HexLayout(HexOrientation.FlatTop, size: 1.5, origin: new Vector(4, -8))); } public void PointyTop_PointsJustInsideEachEdgeBelongToThatHex() { CheckPointsJustInsideEachEdgeBelongToThatHex( new HexLayout(HexOrientation.PointyTop, size: 2, origin: new Vector(-5, 6))); } public void FlatTop_PointsJustInsideEachEdgeBelongToThatHex() { CheckPointsJustInsideEachEdgeBelongToThatHex( new HexLayout(HexOrientation.FlatTop, size: 2, origin: new Vector(-5, 6))); } public void PointyTop_PointJustInsideEachCornerBelongsToThatHex() { HexLayout layout = Unit(HexOrientation.PointyTop); var index = new HexIndex(-1, 4); for (int corner = 0; corner < 6; ++corner) { Vector center = layout.Center(index); Vector nearCorner = center + (layout.Corner(index, corner) - center) * 0.99; CheckEqual(index, layout.HexAt(nearCorner)); } } public void FlatTop_PointJustInsideEachCornerBelongsToThatHex() { HexLayout layout = Unit(HexOrientation.FlatTop); var index = new HexIndex(-1, 4); for (int corner = 0; corner < 6; ++corner) { Vector center = layout.Center(index); Vector nearCorner = center + (layout.Corner(index, corner) - center) * 0.99; CheckEqual(index, layout.HexAt(nearCorner)); } } } public class NearestEdgeTests : TestFixture { [IgnoreTest] public void CheckPointTowardEachNeighborFindsThatEdge(HexLayout layout) { var index = new HexIndex(-2, 3); for (int direction = 0; direction < HexDirections.Count; ++direction) { Vector point = layout.Center(index) + OffsetToNeighbor(layout, direction) * 0.4; CheckEqual(direction, NearestEdgeNumber(layout, point, index)); } } public void PointyTop_PointTowardEachNeighborFindsThatEdge() { CheckPointTowardEachNeighborFindsThatEdge( new HexLayout(HexOrientation.PointyTop, size: 2, origin: new Vector(1, 1))); } public void FlatTop_PointTowardEachNeighborFindsThatEdge() { CheckPointTowardEachNeighborFindsThatEdge( new HexLayout(HexOrientation.FlatTop, size: 2, origin: new Vector(1, 1))); } public void PointyTop_PointJustRightOfCenterIsEast() { CheckEqual( PointyHexDirection.East, Unit(HexOrientation.PointyTop).NearestPointyEdge(new Vector(0.1, 0), new HexIndex(0, 0))); } public void FlatTop_PointJustBelowCenterIsSouth() { CheckEqual( FlatHexDirection.South, Unit(HexOrientation.FlatTop).NearestFlatEdge(new Vector(0, 0.1), new HexIndex(0, 0))); } public void PointyTopQueryOnAFlatTopLayout_Throws() { CheckThrow(typeof(InvalidOperationException)); Unit(HexOrientation.FlatTop).NearestPointyEdge(Vector.Zero, new HexIndex(0, 0)); } public void FlatTopQueryOnAPointyTopLayout_Throws() { CheckThrow(typeof(InvalidOperationException)); Unit(HexOrientation.PointyTop).NearestFlatEdge(Vector.Zero, new HexIndex(0, 0)); } public void ThePointDecidesNotTheIndex() { HexLayout layout = Unit(HexOrientation.PointyTop); Vector pointLeftOfFarHex = layout.Center(new HexIndex(5, 5)) + new Vector(-0.2, 0); CheckEqual(PointyHexDirection.West, layout.NearestPointyEdge(pointLeftOfFarHex, new HexIndex(5, 5))); } } public class CornerTests : TestFixture { public void PointyTop_CornersStartAtUpperRightAndRunClockwise() { HexLayout layout = Unit(HexOrientation.PointyTop); var index = new HexIndex(0, 0); CheckEqual(new Vector(SquareRootOfThree / 2, -0.5), layout.Corner(index, 0)); CheckEqual(new Vector(SquareRootOfThree / 2, 0.5), layout.Corner(index, 1)); CheckEqual(new Vector(0, 1), layout.Corner(index, 2)); CheckEqual(new Vector(-SquareRootOfThree / 2, 0.5), layout.Corner(index, 3)); CheckEqual(new Vector(-SquareRootOfThree / 2, -0.5), layout.Corner(index, 4)); CheckEqual(new Vector(0, -1), layout.Corner(index, 5)); } public void FlatTop_CornersStartAtTheRightAndRunClockwise() { HexLayout layout = Unit(HexOrientation.FlatTop); var index = new HexIndex(0, 0); CheckEqual(new Vector(1, 0), layout.Corner(index, 0)); CheckEqual(new Vector(0.5, SquareRootOfThree / 2), layout.Corner(index, 1)); CheckEqual(new Vector(-0.5, SquareRootOfThree / 2), layout.Corner(index, 2)); CheckEqual(new Vector(-1, 0), layout.Corner(index, 3)); CheckEqual(new Vector(-0.5, -SquareRootOfThree / 2), layout.Corner(index, 4)); CheckEqual(new Vector(0.5, -SquareRootOfThree / 2), layout.Corner(index, 5)); } public void FollowTheCenterSizeAndOrigin() { var layout = new HexLayout(HexOrientation.FlatTop, size: 3, origin: new Vector(10, 10)); var index = new HexIndex(2, 1); Vector center = layout.Center(index); CheckEqual(center + new Vector(3, 0), layout.Corner(index, 0)); } public void EveryCornerIsOneSizeFromTheCenter() { foreach (HexOrientation orientation in new[] { HexOrientation.PointyTop, HexOrientation.FlatTop }) { var layout = new HexLayout(orientation, size: 2.5, origin: new Vector(1, 2)); var index = new HexIndex(3, -1); for (int corner = 0; corner < 6; ++corner) { CheckEqual(2.5, Vector.Distance(layout.Center(index), layout.Corner(index, corner))); } } } public void CornersNAndNPlusOneBoundTheEdgeInDirectionN() { foreach (HexOrientation orientation in new[] { HexOrientation.PointyTop, HexOrientation.FlatTop }) { HexLayout layout = Unit(orientation); var index = new HexIndex(1, 1); for (int corner = 0; corner < 6; ++corner) { Vector edgeMiddle = (layout.Corner(index, corner) + layout.Corner(index, (corner + 1) % 6)) / 2; Vector towardNeighbor = layout.Center(index) + OffsetToNeighbor(layout, corner) / 2; CheckEqual(towardNeighbor, edgeMiddle); } } } public void NegativeCorner_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Unit(HexOrientation.PointyTop).Corner(new HexIndex(0, 0), -1); } public void CornerSix_Throws() { CheckThrow(typeof(ArgumentOutOfRangeException)); Unit(HexOrientation.PointyTop).Corner(new HexIndex(0, 0), 6); } } } }