public static void AddFiringLineNodesToRoute(FiringDistance firingDistance, Direction movementDirection, Node[] nodes, ref int nodeIndex, bool keepMovingCloserOnFiringLastBullet) { Point currPos = firingDistance.StartingTankPosition; Point movementOffset = movementDirection.GetOffset(); Node node; foreach (FiringActionSet actionSet in firingDistance.FiringActionsSets) { int movementsRequired = actionSet.TicksToShootNextWall - 1; // After firing a killer shot, the tank can move onto something. // So give the option of not moving closer after that point. if (actionSet.IsFinalShot && !keepMovingCloserOnFiringLastBullet) { node = new Node(ActionType.Firing, movementDirection, currPos); nodes[nodeIndex] = node; nodeIndex++; return; } // Move then fire on first action of current firing set, unless it's the final shot. // On the final shot, we don't want to accidentally move into a bullet when we could shoot it. // Or move right next to a tank, allowing it to shoot us, when we could shoot first. if (actionSet.CanMoveOnceBeforeFiring && !actionSet.IsFinalShot) { currPos = currPos + movementOffset; node = new Node(ActionType.Moving, movementDirection, currPos); nodes[nodeIndex] = node; nodeIndex++; movementsRequired--; } node = new Node(ActionType.Firing, movementDirection, currPos); nodes[nodeIndex] = node; nodeIndex++; while (movementsRequired > 0) { currPos = currPos + movementOffset; node = new Node(ActionType.Moving, movementDirection, currPos); nodes[nodeIndex] = node; nodeIndex++; movementsRequired--; } } }
public static Node[] GetNodesOnFiringLine(Line <FiringDistance> firingLine, int firingLineIndex, bool keepMovingCloserOnFiringLastBullet) { FiringDistance firingDistance = firingLine.Items[firingLineIndex]; int nodeCount = keepMovingCloserOnFiringLastBullet ? firingDistance.TicksTillTargetShot : firingDistance.TicksTillLastShotFired; Node[] nodes = new Node[nodeCount]; int nodeIndex = 0; Direction movementDirection = firingLine.DirectionOfLine.GetOpposite(); // Since the lines go outwards from the target, but movement is inward AddFiringLineNodesToRoute(firingDistance, movementDirection, nodes, ref nodeIndex, keepMovingCloserOnFiringLastBullet); return(nodes); }
public static Line <FiringDistance> GetFiringDistancesToPoint(Cell target, Direction directionOfMovement, TurnCalculationCache turnCalcCache, GameStateCalculationCache gameStateCache) { Direction outwardDirection = directionOfMovement.GetOpposite(); Line <Point> line = target.LineFromCellToEdgeOfBoardByDirection[(int)outwardDirection]; Line <FiringDistance> firingLine = new Line <FiringDistance>(line.StartOfLine, line.DirectionOfLine, line.Length); // The following distances are from the POINT IN FRONT OF THE TANK WHERE THE BULLET IS FIRED: int indexOfNextShootableWallSegment = 0; int indexOfNextUnshootableWallSegment = 0; int firingCount = 1; int prevIndexOfNextShootableWallSegment = 0; int prevIndexOfNextUnshootableWallSegment = 0; int prevTicksTillTargetShot = 1; bool isValid = true; Point startingPoint = target.Position; /* Fake a conditional breakpoint... * if ((startingPoint == new Point(18, 72)) && (outwardDirection == Direction.UP)) * { * DebugHelper.LogDebugMessage("FiringDistanceCalculator", "Conditional breakpoint reached"); * } */ for (int i = 0; i < firingLine.Length; i++) { Point tankFiringPoint = line[i]; Point tankCentrePoint = tankFiringPoint + Constants.TANK_OUTER_EDGE_OFFSET * outwardDirection.GetOffset(); FiringDistance dist = new FiringDistance { TankFiringPoint = tankFiringPoint, StartingTankPosition = tankCentrePoint }; firingLine[i] = dist; /* Fake a conditional breakpoint... * if ((tankFiringPoint == new Point(18, 62)) && (outwardDirection == Direction.UP)) * { * DebugHelper.LogDebugMessage("FiringDistanceCalculator", "Conditional breakpoint reached"); * } */ TankLocation tankLoc; SegmentState segStateOnLeadingOutsideEdge = SegmentState.Clear; // NB: value assigned just to stop the compiler complaining Cell tankCentreCell = turnCalcCache.CellMatrix[tankCentrePoint]; isValid = tankCentreCell.IsValid; if (isValid) { tankLoc = turnCalcCache.TankLocationMatrix[tankCentrePoint]; segStateOnLeadingOutsideEdge = gameStateCache.TankOuterEdgeMatrix[tankCentrePoint][(int)directionOfMovement]; isValid = isValid && tankLoc.IsValid; } if (isValid) { // This is not the closest point to the target: if (segStateOnLeadingOutsideEdge == SegmentState.OutOfBounds) { isValid = false; } else { indexOfNextShootableWallSegment = (segStateOnLeadingOutsideEdge == SegmentState.ShootableWall) ? i : prevIndexOfNextShootableWallSegment; indexOfNextUnshootableWallSegment = (segStateOnLeadingOutsideEdge == SegmentState.UnshootablePartialWall) ? i : prevIndexOfNextUnshootableWallSegment; /* The test harness has bugs where a tank can overlap with a wall * (e.g. some maps have starting positions like this). * So don't consider whether there is a shootable wall at the target point. * Also don't consider it anyway, because we might be shooting at * a possible future point of an enemy tank, not its current point. */ if ((segStateOnLeadingOutsideEdge == SegmentState.ShootableWall) && (i > 0)) { firingCount++; } dist.IndexOfNextShootableWallSegment = indexOfNextShootableWallSegment; dist.IndexOfNextUnshootableWallSegment = indexOfNextUnshootableWallSegment; // Save these variables for the next tick, as they are about to be overwritten: prevIndexOfNextShootableWallSegment = indexOfNextShootableWallSegment; prevIndexOfNextUnshootableWallSegment = indexOfNextUnshootableWallSegment; // Calculate the number of ticks required to shoot the target: int indexOfTankFiringPoint = i; bool canTankMoveStill = indexOfTankFiringPoint > indexOfNextUnshootableWallSegment; int ticksTillTargetShot = 0; int ticksTillLastShotFired = 0; FiringActionSet[] firingActionSets = new FiringActionSet[firingCount]; dist.FiringActionsSets = firingActionSets; int firingActionSetIndex = 0; bool isFirstShot = true; while (true) { bool isFinalShot = indexOfNextShootableWallSegment == 0; // Calculate the number of ticks to destroy the target: int distanceToNewShootableWall = indexOfTankFiringPoint - indexOfNextShootableWallSegment + 1; int ticksToShootNextWall = 1 + (distanceToNewShootableWall >> 1); // Ticks = 1 tick to fire + Ceiling((distanceToNextShootableWallSegment - 1)/2.0) // = 1 + floor( distanceToNextShootableWallSegment / 2.0 ) // = 1 + distanceToNextShootableWallSegment / 2 // = 1 + (distanceToNextShootableWallSegment >> 1) ticksTillTargetShot += ticksToShootNextWall; ticksTillLastShotFired += (isFinalShot ? 1 : ticksToShootNextWall); if (isFirstShot) { dist.DoesFiringLineStartWithLongDistanceShot = (i > 1) && (distanceToNewShootableWall > 2); // Because a wall two away (or less) can be reached through move, shoot, move (shortest path steps) // in the same time as shoot, move, move. isFirstShot = false; } int newIndexOfTankFiringPoint = indexOfTankFiringPoint; if (canTankMoveStill) { newIndexOfTankFiringPoint = indexOfTankFiringPoint - ticksToShootNextWall + 1; // stationary for 1 tick (to fire), then moving closer on remaining ticks if (newIndexOfTankFiringPoint < indexOfNextUnshootableWallSegment) { // The tank can't get through an unshootable wall segment: newIndexOfTankFiringPoint = indexOfNextUnshootableWallSegment; canTankMoveStill = false; } } FiringActionSet firingActionSet = new FiringActionSet( (byte)indexOfTankFiringPoint, (byte)ticksToShootNextWall, numberOfMovesMade: (byte)(indexOfTankFiringPoint - newIndexOfTankFiringPoint), canMoveOnceBeforeFiring: canTankMoveStill && (distanceToNewShootableWall % 2 == 0), isFinalShot: (indexOfNextShootableWallSegment == 0) ); firingActionSets[firingActionSetIndex] = firingActionSet; firingActionSetIndex++; if (indexOfNextShootableWallSegment == 0) { break; } indexOfTankFiringPoint = newIndexOfTankFiringPoint; indexOfNextShootableWallSegment = firingLine[indexOfNextShootableWallSegment - 1].IndexOfNextShootableWallSegment; } dist.TicksTillTargetShot = ticksTillTargetShot; dist.TicksTillLastShotFired = ticksTillLastShotFired; dist.EndingTankPosition = line[indexOfTankFiringPoint] + Constants.TANK_OUTER_EDGE_OFFSET * outwardDirection.GetOffset(); /* If the next closer space is 1 tick closer, * then the tank could just move there along a normal non-firing line path: * [The exception is if it is blocked by an unshootable partial wall] */ if ((ticksTillTargetShot == prevTicksTillTargetShot + 1) && ((i > prevIndexOfNextUnshootableWallSegment))) { dist.CanMoveOrFire = true; } Debug.Assert(ticksTillTargetShot >= prevTicksTillTargetShot); prevTicksTillTargetShot = ticksTillTargetShot; } } if (isValid) { dist.IsValid = true; } else { dist.TicksTillTargetShot = Constants.UNREACHABLE_DISTANCE;; } } return(firingLine); }