private void MovementTestInternal(Location from, Location to, Movement.Mover moverFunc, bool isLegal, string moveName) { var actualTo = moverFunc(from, Movement.Blue.CanWrap, Movement.Red.CanWrap, Movement.UnmarkedEdges.CannotWrap); if (actualTo == Location.Undefined) { Assert.IsFalse(isLegal, "{0} move from {1} was illegal but was expected to be allowed.", moveName, from); } else { Assert.True(isLegal, "{0} move from {1} was allowed but was expected to be illegal", moveName, from); Assert.AreEqual(to, actualTo, "{0} move from {1} was expected to go to {2} but actually went to {3}.", moveName, from, to, actualTo); Console.WriteLine($"{moveName} from {from} leads to {to}"); } }
private static IEnumerable <ProjectedMove> MoveGenerator(Location loc, int size, Movement.Mover mover, BitMeasurement blocks, bool isRed) { var tPrev = loc; for (var i = 1; i <= size; i++) { var tCur = mover(tPrev, isRed ? Movement.Blue.CanWrap : Movement.Blue.CannotWrap, isRed ? Movement.Red.CannotWrap : Movement.Red.CanWrap, Movement.UnmarkedEdges.CannotWrap); if (!Movement.IsValidLocation(tCur) || blocks[tCur]) { yield break; } yield return(new ProjectedMove { From = loc, Size = size, To = tCur, Wrap = !Movement.IsValidLocation(mover(tPrev, Movement.Blue.CannotWrap, Movement.Red.CannotWrap, Movement.UnmarkedEdges.CannotWrap)) }); tPrev = tCur; } }
/// <summary> /// Checks if a move is allowed, possibly restricting to moves which pass through the enemy's wall. /// </summary> /// <param name="state">Game state to check</param> /// <param name="requireWrapAround">If true, only returns null if the move is otherwise allowed AND /// if it can be done while passing through the opponent's wall.</param> /// <returns>Null if the move is allowed, or a brief text explanation of why it cannot be performed</returns> public string CheckLocationTargetReachable(State state, bool requireWrapAround, int maxLength) { // First decode the difference between the board coordinates of source and destination. // Least significant four bits are the column number, mapping directly to 0=A and 8=I. // Most significant five bits are the number of half-rows from the top / blue side of the board. var(horizontalDiff, verticalDiff) = MovementVector(Location, Target); // Valid moves ALWAYS follow the straight lines on the board, even if they pass through an enemy wall. // Vertical moves have no horizontal difference and move an even number of vertical spaces. // Diagonal moves always have the same magnitude of horizontal difference and vertical difference. if ((horizontalDiff != 0 || Math.Abs(verticalDiff) % 2 == 1) && Math.Abs(horizontalDiff) != Math.Abs(verticalDiff)) { return($"Movement Follows Board Lines, and No Line Connects {Location} With {Target}"); } var blueWallWrapAround = Side == ActionSide.Red ? Movement.Blue.CanWrap : Movement.Blue.CannotWrap; var redWallWrapAround = Side == ActionSide.Blue ? Movement.Red.CanWrap : Movement.Red.CannotWrap; // Set of board locations which can be reached (for reporting to the user in case of illegal move) var reachable = new HashSet <Location>(); // Holds the two possible directions the user might be moving -- to be filled in immediately below Movement.Mover[] movers; if (horizontalDiff == 0) { //This must be a vertical move, so only consider spaces reachable going north or south. movers = new Movement.Mover[] { Movement.North, Movement.South }; } else if (Math.Sign(horizontalDiff) == Math.Sign(verticalDiff)) { //This must be a northwest or southeast move, so only consider spaces reachable from those directions. movers = new Movement.Mover[] { Movement.SouthEast, Movement.NorthWest }; } else { //This must be a northeast or southwest move, so only consider spaces reachable from those directions. movers = new Movement.Mover[] { Movement.NorthEast, Movement.SouthWest }; } //We take advantage of a property of direct vs wrap-around moves: direct moves always have a manhattan //distance of exactly 2. North/South moves vertically 2 spaces; Diagonal moves vertically 1 space and horizontally 1 space. //So if a single step movement bounds a manhattan distance greater than 2, the step must have wrapped around. foreach (var mover in movers) { var consideredCell = Location; var wrapAroundSeen = false; // Stack size controls how many spaces we can move, so progressively check straight line moves of each length. for (var i = 0; i < maxLength; i++) { // test the considered move and find its movement vector var newCell = mover(consideredCell, blueWallWrapAround, redWallWrapAround, Movement.UnmarkedEdges.CannotWrap); if (newCell == Location.Undefined) { break; } var(horizontal, vertical) = MovementVector(consideredCell, newCell); consideredCell = newCell; // if the last single step move has a manhattan distance over 2, this move passed through an enemy wall. if (Math.Abs(horizontal) + Math.Abs(vertical) > 2) { wrapAroundSeen = true; } // illegal to move onto or through blockades, so stop considering this movement direction if we find one. if (state[consideredCell] == Cell.Block) { break; } if (consideredCell == Target && (wrapAroundSeen || !requireWrapAround)) { return(null); } // this cell is reachable but wasn't our target. Add it to the reachable list and continue checking. reachable.Add(consideredCell); } } // target was other than one of the reachable cells, so return an error. var cells = string.Join(", ", reachable.ToArray()); return($"Stack Size {maxLength} Piece At {Location} Cannot Reach {Target} (but can reach: {cells})"); }