예제 #1
0
        private void FindShortestPathsWithRestrictionsOnFiring(MobileState tankState, DirectionalMatrix <DistanceCalculation> distanceMatrix)
        {
            TwoValuedCircularBuffer <Node> bfsQueue = new TwoValuedCircularBuffer <Node>(CircularBufferCapacityRequired);
            Node currNode       = new Node(ActionType.Moving, tankState.Dir, tankState.Pos);
            int  adjDistance    = 1;
            bool nodesToProcess = true;
            bool canFire        = TicksWithoutFiring < adjDistance;

            while (nodesToProcess)
            {
                // Get each node adjacent to the current node:
                Node[] adjacentNodes
                    = canFire
                    ? currNode.GetAdjacentOutgoingNodes(TankOuterEdgeMatrix[currNode.X, currNode.Y])
                    : currNode.GetAdjacentOutgoingNodesWithoutFiring(TankOuterEdgeMatrix[currNode.X, currNode.Y]);
                foreach (Node adj in adjacentNodes)
                {
                    if (adj.ActionType == ActionType.Moving)
                    {
                        // Check if the node already has a distance i.e. has it already been expanded?
                        if (distanceMatrix[adj.Dir, adj.X, adj.Y].CodedDistance == 0)
                        {
                            // Set the new shortest distance:
                            distanceMatrix[adj.Dir, adj.X, adj.Y] = new DistanceCalculation(adjDistance, currNode);

                            // Add to the queue to be expanded:
                            bfsQueue.Add(adj, adjDistance);
                        }
                    }
                    else
                    {
                        // A firing node can only be reached in one way (from the moving node on the same cell).
                        // So we don't need to check if it is the shortest path to the node (it must be).
                        // And we aren't interested in it, so no need to store it.
                        // Hence just add it to the queue (it is a "convenience" node to allow a BFS):
                        bfsQueue.Add(adj, adjDistance);
                    }
                }

                if (bfsQueue.Size == 0)
                {
                    nodesToProcess = false;
                }
                else
                {
                    CircularBufferItem <Node> nextItem = bfsQueue.Remove();
                    currNode    = nextItem.Item;
                    adjDistance = nextItem.Value + 1;
                    canFire     = adjDistance > TicksWithoutFiring;
                }
            }
        }
 public void Add(CircularBufferItem <T> bufferItem)
 {
     Add(bufferItem.Item, bufferItem.Value);
 }
예제 #3
0
        public DirectionalMatrix <DistanceCalculation> CalculateMatrixOfShortestDistancesToTargetCell(Cell target)
        {
            if (MovementDirections == null)
            {
                MovementDirections = BoardHelper.AllRealDirections;
            }
            if (EdgeOffsets == null)
            {
                EdgeOffsets = FiringLineMatrix.EdgeOffsets;
            }

            BitMatrix walls = GameStateCalculationCache.GameState.Walls;
            DirectionalMatrix <DistanceCalculation> attackMatrix
                = new DirectionalMatrix <DistanceCalculation>(walls.Width, walls.Height);
            TwoValuedCircularBuffer <Node> bfsQueue = new TwoValuedCircularBuffer <Node>(CircularBufferCapacityRequired);

            // Note: the target point will not have a distance, since it will be shot, not moved to:
            TankLocation tankLocationAtTargetPoint = TurnCalculationCache.TankLocationMatrix[target.Position];

            if ((TargetElementType == ElementType.BASE) || (TargetElementType == ElementType.BULLET && AllowDestroyingABulletByMovingIntoIt))
            {
                AddNodesToQueueForMovingOverTarget(attackMatrix, bfsQueue, tankLocationAtTargetPoint);
            }
            else
            if (TargetElementType == ElementType.TANK)
            {
                // TODO: Make points taboo if they would lead to both tank bodies overlapping, or moving directly in front of an enemy tank

                /* TODO: a. Is this needed? Won't it break some algorithms?
                 *       b. Check that points are in the board area
                 * foreach (Point point in tankLocationAtTargetPoint.TankHalo.GetPoints())
                 * {
                 *  foreach (Direction dir in BoardHelper.AllRealDirections)
                 *  {
                 *      attackMatrix[dir, point.X, point.Y] = new DistanceCalculation(-1, new Node());
                 *  }
                 * }
                 */
            }
            else
            {
                // Bullet that can't be moved into...

                // TODO: Make points taboo within the "outline" of a tank body centred on the bullet
            }

            if (TabooAreas != null)
            {
                foreach (Rectangle rect in TabooAreas)
                {
                    foreach (Point point in rect.GetPoints())
                    {
                        foreach (Direction dir in BoardHelper.AllRealDirections)
                        {
                            if (TurnCalculationCache.CellMatrix[point].IsValid)
                            {
                                attackMatrix[dir, point.X, point.Y] = new DistanceCalculation(TABOO_DISTANCE, new Node());
                            }
                        }
                    }
                }
            }

            // Use the firing distance calculations for the target point, and add the initial points to the BFS queue:
            FiringLineSummary[,] firingLineSummariesByMovementDirAndEdgeOffset
                = new FiringLineSummary[Constants.RELEVANT_DIRECTION_COUNT, Constants.EDGE_OFFSET_COUNT];
            bool areFiringLinesStillActive = TryInitializeFiringLinesAndAddInitialFiringLineNodesToQueue(
                target, bfsQueue, firingLineSummariesByMovementDirAndEdgeOffset);

            int currDistance = 0;

            while (true)
            {
                if (areFiringLinesStillActive && bfsQueue.Size == 0)
                {
                    // Get nodes from the firing line/s with the next shortest distance:
                    areFiringLinesStillActive = TryAddFiringLineNodesWithNextShortestDistance(
                        bfsQueue, firingLineSummariesByMovementDirAndEdgeOffset, out currDistance);
                }
                if (bfsQueue.Size == 0)
                {
                    break;
                }

                CircularBufferItem <Node> bufferItem = bfsQueue.Remove();
                Node currNode = bufferItem.Item;
                if (bufferItem.Value > currDistance)
                {
                    if (bufferItem.Value == Constants.UNREACHABLE_DISTANCE)
                    {
                        break;
                    }

                    currDistance = bufferItem.Value;

                    // Add firing line nodes with the new distance to the queue:
                    if (areFiringLinesStillActive)
                    {
                        areFiringLinesStillActive = TryAddNextFiringLineNodesToQueue(bfsQueue,
                                                                                     firingLineSummariesByMovementDirAndEdgeOffset, currDistance);
                    }
                }

                int adjDistance = currDistance + 1;

                // Get each node adjacent to the current node:
                SegmentState   innerEdgeStateInNodeDir = GameStateCalculationCache.TankInnerEdgeMatrix[currNode.X, currNode.Y][(int)currNode.Dir];
                SegmentState[] outerEdgeStates         = GameStateCalculationCache.TankOuterEdgeMatrix[currNode.X, currNode.Y];
                SegmentState   outerEdgeStateInNodeDir = outerEdgeStates[(int)currNode.Dir];

#if CONDITIONAL_BREAKPOINT_AttackTargetDistanceCalculator_CalculateMatrixOfShortestDistancesToTargetCell
                System.Diagnostics.Debug.Assert(currNode.X != 39 || currNode.Y != 45 || currNode.Dir != Direction.UP, "Breakpoint");
#endif

                // Node[] adjacentNodes = currNode.GetAdjacentIncomingNodes(innerEdgeStateInNodeDir, outerEdgeStateInNodeDir, outerEdgeStateInOppositeDir);
                // Insert inline for better performance...
                // **********************

                Node[] adjacentNodes     = new Node[MAX_POSSIBLE_PRECEDING_NODE_COUNT];
                byte   adjacentNodeCount = 0;

                if (currNode.ActionType == ActionType.FiringLine || currNode.ActionType == ActionType.Firing)
                {
                    // Firing and/or the firing line can only be invoked if the tank is first facing in the correct direction:
                    Node positioningNode = new Node(ActionType.Moving, currNode.Dir /*movementDir*/, currNode.X, currNode.Y);
                    adjacentNodes[0]  = positioningNode;
                    adjacentNodeCount = 1;
                }
                else
                {
                    // So now the destination node (this) must be a moving node in the given direction...
                    Node adjacentNode;

                    // If there is a blocking wall in its direction of movement,
                    // then any of the other movement/positioning nodes on the same cell
                    // can be a preceding node on the path:
                    if (outerEdgeStateInNodeDir == SegmentState.ShootableWall || outerEdgeStateInNodeDir == SegmentState.UnshootablePartialWall)
                    {
                        foreach (Direction otherDir in BoardHelper.AllRealDirections)
                        {
                            if (otherDir != currNode.Dir)
                            {
                                adjacentNode = new Node(ActionType.Moving, otherDir, currNode.X, currNode.Y);
                                adjacentNodes[adjacentNodeCount] = adjacentNode;
                                adjacentNodeCount++;
                            }
                        }
                    }

                    // Ignore invalid prior cells:
                    if (outerEdgeStates[(int)(currNode.Dir.GetOpposite())] != SegmentState.OutOfBounds)
                    {
                        // Get the adjacent cell's position:
                        int newX = currNode.X;
                        int newY = currNode.Y;
                        switch (currNode.Dir)
                        {
                        case Direction.UP:
                            newY++;
                            break;

                        case Direction.DOWN:
                            newY--;
                            break;

                        case Direction.LEFT:
                            newX++;
                            break;

                        case Direction.RIGHT:
                            newX--;
                            break;
                        }

                        switch (innerEdgeStateInNodeDir)
                        {
                        case SegmentState.Clear:
                            // Add all 4 directions on the adjacent cell
                            foreach (Direction otherDir in BoardHelper.AllRealDirections)
                            {
                                adjacentNode = new Node(ActionType.Moving, otherDir, newX, newY);
                                adjacentNodes[adjacentNodeCount] = adjacentNode;
                                adjacentNodeCount++;
                            }
                            break;

                        case SegmentState.ShootableWall:
                            // Add the firing node in the current direction on the adjacent cell:
                            adjacentNode = new Node(ActionType.Firing, currNode.Dir, newX, newY);
                            adjacentNodes[adjacentNodeCount] = adjacentNode;
                            adjacentNodeCount++;
                            break;
                        }
                    }
                }

                // **********************
                // End of inlined section

                for (int n = 0; n < adjacentNodeCount; n++)
                {
                    Node adj = adjacentNodes[n];

                    // Note: adj will never be a firing line node, as they are not incoming nodes for any other node type.

                    if (adj.ActionType == ActionType.Moving)
                    {
                        // Check if the node already has a distance i.e. has it already been expanded?
                        if (attackMatrix[adj.Dir, adj.X, adj.Y].CodedDistance == 0)
                        {
                            // Set the new shortest distance:
                            attackMatrix[adj.Dir, adj.X, adj.Y] = new DistanceCalculation(adjDistance, currNode);

                            // Add to the queue to be expanded:
                            bfsQueue.Add(adj, adjDistance);
                        }
                    }
                    else
                    {
                        // It would be useful to have the firing nodes as well, as a tank could currently be firing.
                        // However this will double the storage requirements.
                        // Rather just do quick calculations to estimate the distance.
                        // For example:
                        // 1. Get the shortest distance from the current node.
                        // 2. If firing is on the shortest distance from the current node, then subtract 1 from the distance (since it's already firing).
                        // 3. If it's not on the shortest distance (but it still might be a shortest path, due to multiple equal paths),
                        //    then get one plus the shortest distance from the adjacent node in the firing direction.
                        //    a. If this is less than the shortest distance, use it as the shortest distance.
                        //    b. Otherwise use the shortest distance path instead (don't make use of the space fired at).
                        bfsQueue.Add(adj, adjDistance);
                    }
                }
            }
            return(attackMatrix);
        }
        public static DirectionalMatrix <MultiPathDistanceCalculation> CalculateShortestDistancesFromTank
            (ref MobileState tankState, BitMatrix walls, Matrix <SegmentState[]> tankEdgeMatrix)
        {
            DirectionalMatrix <MultiPathDistanceCalculation> distanceMatrix
                = new DirectionalMatrix <MultiPathDistanceCalculation>(walls.Width, walls.Height);

            distanceMatrix[tankState.Dir, tankState.Pos] = new MultiPathDistanceCalculation(0, new Node());

            TwoValuedCircularBuffer <Node> bfsQueue = new TwoValuedCircularBuffer <Node>(SUGGESTED_CIRCULAR_BUFFER_CAPACITY_REQUIRED);

            Node currNode       = new Node(ActionType.Moving, tankState.Dir, tankState.Pos);
            int  adjDistance    = 1;
            bool nodesToProcess = true;

            while (nodesToProcess)
            {
                // Get each node adjacent to the current node:
                Node[] adjacentNodes = currNode.GetAdjacentOutgoingNodes(tankEdgeMatrix[currNode.X, currNode.Y]);
                foreach (Node adj in adjacentNodes)
                {
                    if (adj.ActionType == ActionType.Moving)
                    {
                        // Check if the node already has a distance i.e. has it already been expanded?
                        if (distanceMatrix[adj.Dir, adj.X, adj.Y].CodedDistance == 0)
                        {
                            // Set the new shortest distance:
                            distanceMatrix[adj.Dir, adj.X, adj.Y] = new MultiPathDistanceCalculation(adjDistance, currNode);

                            // Add to the queue to be expanded:
                            bfsQueue.Add(adj, adjDistance);
                        }
                        else
                        {
                            MultiPathDistanceCalculation distanceCalc = distanceMatrix[adj.Dir, adj.X, adj.Y];
                            if (distanceCalc.Distance == adjDistance)
                            {
                                distanceCalc.PriorNodesByPriorDir[(int)currNode.Dir] = currNode;
                                // TODO: Is the following necessary? Probably not, because the array is a reference, not a struct:
                                // distanceMatrix[adj.Dir, adj.X, adj.Y] = distanceCalc;
                            }
                        }
                    }
                    else
                    {
                        // A firing node can only be reached in one way (from the moving node on the same cell).
                        // So we don't need to check if it is the shortest path to the node (it must be).
                        // And we aren't interested in it, so no need to store it.
                        // Hence just add it to the queue (it is a "convenience" node to allow a BFS):
                        bfsQueue.Add(adj, adjDistance);
                    }
                }

                if (bfsQueue.Size == 0)
                {
                    nodesToProcess = false;
                }
                else
                {
                    CircularBufferItem <Node> nextItem = bfsQueue.Remove();
                    currNode    = nextItem.Item;
                    adjDistance = nextItem.Value + 1;
                }
            }
            return(distanceMatrix);
        }