public void StationaryMelee() { Board board = new Board(BoardCommon.GRID_12X8); Combatant attacker = new Combatant("Attacker", board, new Point(5, 4)); Combatant defender = new Combatant("Defender", board, new Point(7, 4)); board.AddPawn(attacker); board.AddPawn(defender); attacker.Health = 10; defender.Health = 10; attacker.BaseStats = new Stats() { Attack = 10, Stamina = 10 }; MeleeAttackSkill attack = new MeleeAttackSkill(attacker, new Point[] { Point.Right, 2 * Point.Right }) { ActionPoints = 3 }; attack.SetDirection(CardinalDirection.East); attacker.AddSkill(attack); attack.Fire(); board.BeginTurn(); Assert.AreEqual(10, attacker.ActionPoints); board.Turn(); Assert.AreEqual(0, defender.Health); Assert.AreEqual(7, attacker.ActionPoints); }
/// <summary> /// Lights up cells visible from the current position. Clear all lighting before calling. /// </summary> /// <param name="grid">The cell grid definition.</param> /// <param name="gridPosn">The player's position within the grid.</param> /// <param name="viewRadius">Maximum view distance; can be a fractional value.</param> public static HashSet<Point> ComputeVisibility(Board board, Point gridPosn, float viewRadius) { //Debug.Assert(gridPosn.x >= 0 && gridPosn.x < grid.xDim); //Debug.Assert(gridPosn.y >= 0 && gridPosn.y < grid.yDim); HashSet<Point> visible = new HashSet<Point>(); // Viewer's cell is always visible. visible.Add(gridPosn); // Cast light into cells for each of 8 octants. // // The left/right inverse slope values are initially 1 and 0, indicating a diagonal // and a horizontal line. These aren't strictly correct, as the view area is supposed // to be based on corners, not center points. We only really care about one side of the // wall at the edges of the octant though. // // NOTE: depending on the compiler, it's possible that passing the octant transform // values as four integers rather than an object reference would speed things up. // It's much tidier this way though. for (int txidx = 0; txidx < s_octantTransform.Length; txidx++) { CastLight(board, gridPosn, viewRadius, 1, 1.0f, 0.0f, s_octantTransform[txidx], visible); } return visible; }
public void PawnMovedButNotInBoard() { Board board = new Board(BoardCommon.GRID_12X8); Pawn pawn = new Pawn("Add", board, new Point(board.Width / 2, board.Height / 2)); // Expect an ArgumentException because the pawn doesn't exist on the board yet Assert.Throws<ArgumentException>(delegate () { pawn.Offset(Point.One); }, "Offset was called on a Pawn that was not added to a Board"); }
public void GridWidthAndHeight() { int[,] grid = { {0, 0, 0, 0 }, {0, 0, 0, 0 }, {0, 0, 0, 0 } }; Board board = new Board(grid); Assert.AreEqual(board.Width, 4); Assert.AreEqual(board.Height, 3); }
public void ApproachMeleeRange() { Board board = new Board( new int[,] { { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }); Combatant attacker = new Combatant("Attacker", board, new Point(1, 7)); Combatant defender = new Combatant("Defender", board, new Point(10, 1)); board.AddPawn(attacker); board.AddPawn(defender); attacker.Health = 10; defender.Health = 10; attacker.BaseStats = new Stats() { Attack = 10, Stamina = 25 }; attacker.AddPawnToView(defender); MeleeAttackSkill attack = new MeleeAttackSkill(attacker, new Point[] { Point.Right, 2 * Point.Right }) { ActionPoints = 3 }; attacker.AddSkill(attack); WalkSkill walk = new WalkSkill(attacker); attacker.AddSkill(walk); LostGen.Decision.ApproachMeleeRange approach = new LostGen.Decision.ApproachMeleeRange(attacker); approach.Target = defender; approach.Setup(); approach.Run(); board.BeginTurn(); Assert.AreEqual(25, attacker.ActionPoints); board.Turn(); Assert.AreEqual(new Point(10, 3), attacker.Position); }
public void OnStateUpdate(LostGen.Board world) { var toRemove = _activePawns.Keys.Except(world.Pawns.Keys); var toAdd = world.Pawns.Keys.Except(_activePawns.Keys); foreach (var key in toRemove) { _activePawns[key].QueueFree(); } foreach (var key in toAdd) { var pawn = (PawnCharacter)_pawnScene.Instance(); pawn.PlayerID = key; AddChild(pawn); pawn.OnStateUpdate(world); } }
public void CollisionTest() { // Arrange Board board = new Board(BoardCommon.GRID_12X8); Point pos1 = new Point(board.Width / 2, board.Height / 2); // Center of board Point pos2 = pos1 + (3 * Point.Right); // Two tiles to the right of center List<Point> footprint = new List<Point>(new Point[] { Point.Zero, Point.Up, Point.Down, Point.Left, Point.Right }); Pawn pawn1 = new Pawn("First", board, pos1, footprint, true, true, true); Pawn pawn2 = new Pawn("Second", board, pos2, footprint, true, true, true); board.AddPawn(pawn1); board.AddPawn(pawn2); bool p1Triggered = false; bool p2Triggered = false; Pawn.CollisionDelegate p1Collisions = delegate (Pawn source, Pawn other) { p1Triggered = true; Assert.AreSame(source, pawn1); Assert.AreSame(other, pawn2); }; Pawn.CollisionDelegate p2Collisions = delegate (Pawn source, Pawn other) { p2Triggered = true; Assert.AreSame(source, pawn2); Assert.AreSame(other, pawn1); }; pawn1.CollisionEntered += p1Collisions; pawn2.CollisionEntered += p2Collisions; // Act pawn1.Position = pawn1.Position + Point.Right; // Assert Assert.IsTrue(p1Triggered, "Pawn 1's OnCollisionEnter was not triggered"); Assert.IsTrue(p2Triggered, "Pawn 2's OnCollisionEnter was not triggered"); }
public void MoveIntoWall() { Board board = new Board(BoardCommon.GRID_12X8); Pawn pawn = new Pawn("Mover", board, Point.One, null, true, true); board.AddPawn(pawn); Point expectedPosition = new Point(2, 1); bool moveRight = pawn.Offset(Point.Right); Point position1 = pawn.Position; bool moveUp = pawn.Offset(Point.Up); Point position2 = pawn.Position; Assert.IsTrue(moveRight, "Offset returned false when moving right"); Assert.AreEqual(expectedPosition, position1, "Pawn failed to move right to open tile"); Assert.IsFalse(moveUp, "Offset returned true when moving up into a wall"); Assert.AreEqual(expectedPosition, position2, "Pawn failed to stay on one goddamn place"); }
private void ArrangeBoard(int[,] grid, Point start, Point end, out Board board, out Combatant pawn) { board = new Board(grid); pawn = new Combatant("Walker", board, Point.One); Stats stats = new Stats() { Stamina = 100 }; pawn.BaseStats = stats; board.AddPawn(pawn); WalkSkill walk = new WalkSkill(pawn); pawn.AddSkill(walk); walk.SetTarget(end); board.BeginTurn(); }
/// <summary> /// Construct a new Node /// </summary> /// <param name="board"></param> /// <param name="point"></param> /// <param name="lookup"></param> public Node(Board board, Point point, EdgeCostLookup lookup = null) { if (board == null) { throw new ArgumentNullException("board"); } if (lookup == null) { throw new ArgumentNullException("lookup"); } _board = board; _point = point; _edgeCostLookup = lookup; }
/// <summary> /// Recursively casts light into cells. Operates on a single octant. /// </summary> /// <param name="grid">The cell grid definition.</param> /// <param name="gridPosn">The player's position within the grid.</param> /// <param name="viewRadius">The view radius; can be a fractional value.</param> /// <param name="startColumn">Current column; pass 1 as initial value.</param> /// <param name="leftViewSlope">Slope of the left (upper) view edge; pass 1.0 as /// the initial value.</param> /// <param name="rightViewSlope">Slope of the right (lower) view edge; pass 0.0 as /// the initial value.</param> /// <param name="txfrm">Coordinate multipliers for the octant transform.</param> /// /// Maximum recursion depth is (Ceiling(viewRadius)). private static void CastLight(Board grid, Point gridPosn, float viewRadius, int startColumn, float leftViewSlope, float rightViewSlope, OctantTransform txfrm, HashSet<Point> visible) { //Debug.Assert(leftViewSlope >= rightViewSlope); // Used for distance test. float viewRadiusSq = viewRadius * viewRadius; int viewCeiling = (int)Math.Ceiling(viewRadius); // Set true if the previous cell we encountered was blocked. bool prevWasBlocked = false; // As an optimization, when scanning past a block we keep track of the // rightmost corner (bottom-right) of the last one seen. If the next cell // is empty, we can use this instead of having to compute the top-right corner // of the empty cell. float savedRightSlope = -1; int xDim = grid.Width; int yDim = grid.Height; // Outer loop: walk across each column, stopping when we reach the visibility limit. for (int currentCol = startColumn; currentCol <= viewCeiling; currentCol++) { int xc = currentCol; // Inner loop: walk down the current column. We start at the top, where X==Y. // // TODO: we waste time walking across the entire column when the view area // is narrow. Experiment with computing the possible range of cells from // the slopes, and iterate over that instead. for (int yc = currentCol; yc >= 0; yc--) { // Translate local coordinates to grid coordinates. For the various octants // we need to invert one or both values, or swap X for Y. int gridX = gridPosn.X + xc * txfrm.xx + yc * txfrm.xy; int gridY = gridPosn.Y + xc * txfrm.yx + yc * txfrm.yy; // Range-check the values. This lets us avoid the slope division for blocks // that are outside the grid. // // Note that, while we will stop at a solid column of blocks, we do always // start at the top of the column, which may be outside the grid if we're (say) // checking the first octant while positioned at the north edge of the map. if (gridX < 0 || gridX >= xDim || gridY < 0 || gridY >= yDim) { continue; } // Compute slopes to corners of current block. We use the top-left and // bottom-right corners. If we were iterating through a quadrant, rather than // an octant, we'd need to flip the corners we used when we hit the midpoint. // // Note these values will be outside the view angles for the blocks at the // ends -- left value > 1, right value < 0. float leftBlockSlope = (yc + 0.5f) / (xc - 0.5f); float rightBlockSlope = (yc - 0.5f) / (xc + 0.5f); // Check to see if the block is outside our view area. Note that we allow // a "corner hit" to make the block visible. Changing the tests to >= / <= // will reduce the number of cells visible through a corner (from a 3-wide // swath to a single diagonal line), and affect how far you can see past a block // as you approach it. This is mostly a matter of personal preference. if (rightBlockSlope > leftViewSlope) { // Block is above the left edge of our view area; skip. continue; } else if (leftBlockSlope < rightViewSlope) { // Block is below the right edge of our view area; we're done. break; } // This cell is visible, given infinite vision range. If it's also within // our finite vision range, light it up. // // To avoid having a single lit cell poking out N/S/E/W, use a fractional // viewRadius, e.g. 8.5. // // TODO: we're testing the middle of the cell for visibility. If we tested // the bottom-left corner, we could say definitively that no part of the // cell is visible, and reduce the view area as if it were a wall. This // could reduce iteration at the corners. float distanceSquared = xc * xc + yc * yc; if (distanceSquared <= viewRadiusSq) { visible.Add(new Point(gridX, gridY)); } bool curBlocked = grid.IsOpaque(new Point(gridX, gridY)); if (prevWasBlocked) { if (curBlocked) { // Still traversing a column of walls. savedRightSlope = rightBlockSlope; } else { // Found the end of the column of walls. Set the left edge of our // view area to the right corner of the last wall we saw. prevWasBlocked = false; leftViewSlope = savedRightSlope; } } else { if (curBlocked) { // Found a wall. Split the view area, recursively pursuing the // part to the left. The leftmost corner of the wall we just found // becomes the right boundary of the view area. // // If this is the first block in the column, the slope of the top-left // corner will be greater than the initial view slope (1.0). Handle // that here. if (leftBlockSlope <= leftViewSlope) { CastLight(grid, gridPosn, viewRadius, currentCol + 1, leftViewSlope, leftBlockSlope, txfrm, visible); } // Once that's done, we keep searching to the right (down the column), // looking for another opening. prevWasBlocked = true; savedRightSlope = rightBlockSlope; } } } // Open areas are handled recursively, with the function continuing to search to // the right (down the column). If we reach the bottom of the column without // finding an open cell, then the area defined by our view area is completely // obstructed, and we can stop working. if (prevWasBlocked) { break; } } }
public void OnStateUpdate(LostGen.Board state) { var self = state.Pawns[PlayerID]; Translation = new Vector3(self.Position.X, self.Position.Y, self.Position.Z); }
public void SolidPawnsMoveIntoSharedCell() { Board board = new Board(BoardCommon.GRID_12X8); Point pos1 = new Point(6, 4); Point pos2 = new Point(8, 4); Point pos3 = new Point(7, 4); Pawn pawn1 = new Pawn("First", board, pos1, null, true, true, true); Pawn pawn2 = new Pawn("Second", board, pos2, null, true, true, true); board.AddPawn(pawn1); board.AddPawn(pawn2); Assert.IsTrue(pawn1.Offset(Point.Right),"Pawn tried to move from " + pos1 + " to " + pos3 + " but was blocked by something"); Assert.IsFalse(pawn2.Offset(Point.Left), "Pawn moved from " + pos2 + " to " + pos3 + " when it should've been blocked"); Assert.AreEqual(pos3, pawn1.Position, "Pawn was not able to move from " + pos1 + " to " + pos3); Assert.AreEqual(pos2, pawn2.Position, "Pawn was able to move from " + pos3 + " to " + pos2); }
public void MoveSolidPawnIntoSolidPawn() { Board board = new Board(BoardCommon.GRID_12X8); Point pos1 = new Point(6, 4); Point pos2 = new Point(8, 4); Pawn pawn1 = new Pawn("First", board, pos1, null, true, true, true); Pawn pawn2 = new Pawn("Second", board, pos2, null, true, true, true); board.AddPawn(pawn1); board.AddPawn(pawn2); Point expectedPosition = new Point(7, 4); bool firstMove = pawn2.Offset(Point.Left); Point firstPosition = pawn2.Position; bool secondMove = pawn2.Offset(Point.Left); Point secondPosition = pawn2.Position; Assert.IsTrue(firstMove, "Pawn was not able to move left one tile"); Assert.IsFalse(secondMove, "Pawn somehow passed through another solid pawn"); Assert.AreEqual(expectedPosition, firstPosition, "Pawn could not move to free tile"); Assert.AreEqual(expectedPosition, secondPosition, "Pawn somehow passed through another solid pawn"); }
protected virtual int Heuristic(Board.Node start, Board.Node end) { return Point.TaxicabDistance(start.Point, end.Point); }
public WalkSkill(Combatant owner) : base(owner, "Walk", "Move across tiles within a limited range") { _board = Owner.Board; }
public void SolidPawnsMoveIntoEachOther() { Board board = new Board(BoardCommon.GRID_12X8); Point pos1 = new Point(6, 4); Point pos2 = new Point(7, 4); Pawn pawn1 = new Pawn("First", board, pos1, null, true, true, true); Pawn pawn2 = new Pawn("Second", board, pos2, null, true, true, true); board.AddPawn(pawn1); board.AddPawn(pawn2); Assert.IsFalse(pawn1.Offset(Point.Right)); Assert.IsFalse(pawn2.Offset(Point.Left)); Assert.AreEqual(pos1, pawn1.Position); Assert.AreEqual(pos2, pawn2.Position); }