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); }
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); }