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}");
            }
        }
Exemple #2
0
        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;
            }
        }
Exemple #3
0
        /// <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})");
        }