public CombinedMovementAndFiringDistanceCalculation GetShortestAttackDistanceFromCurrentTankPosition( int tankIndex, Cell target) { DirectionalMatrix <DistanceCalculation> movementDistanceMatrix = GameStateCalculationCache.GetDistanceMatrixFromTankByTankIndex(tankIndex); return(GetShortestAttackDistanceGivenTheSourcePointsMovementMatrix(movementDistanceMatrix, target)); }
public FiringLineMatrix(Point topLeft, int width, int height, ElementExtentType extentType, TurnCalculationCache turnCalcationCache, GameStateCalculationCache gameStateCalculationCache, EdgeOffsetType lastSupportedEdgeOffsetType = EdgeOffsetType.Centre) { directionalMatrixOfFiringLinesByEdgeOffset = new DirectionalMatrix <Line <FiringDistance>[]>(topLeft, width, height); LastSupportedEdgeOffsetType = lastSupportedEdgeOffsetType; ExtentType = extentType; TurnCalcationCache = turnCalcationCache; GameStateCalculationCache = gameStateCalculationCache; }
/// <summary> /// AttackTargetDistanceCalculator constructor /// </summary> /// <param name="targetElementType">The type of element (base, tank or bullet) which is being attacked</param> /// <param name="firingLineMatrix">The firing line matrix must be applicable to the target element type's extent</param> /// <param name="gameStateCalculationCache"></param> /// <param name="turnCalculationCache"></param> public AttackTargetDistanceCalculator( ElementType targetElementType, FiringLineMatrix firingLineMatrix, GameStateCalculationCache gameStateCalculationCache, TurnCalculationCache turnCalculationCache) : this() { FiringLineMatrix = firingLineMatrix; GameStateCalculationCache = gameStateCalculationCache; TurnCalculationCache = turnCalculationCache; TargetElementType = targetElementType; }
public static TankAction[] GetTanksActionsOnOutgoingShortestAttackPathFromCurrentTankPosition( int tankIndex, CombinedMovementAndFiringDistanceCalculation combinedCalculation, GameStateCalculationCache gameStateCalcCache, bool keepMovingCloserOnFiringLastBullet = false) { DirectionalMatrix <DistanceCalculation> distanceMatrix = gameStateCalcCache.GetDistanceMatrixFromTankByTankIndex(tankIndex); Node[] nodes = GetOutgoingNodesOnShortestAttackPath( combinedCalculation, distanceMatrix, keepMovingCloserOnFiringLastBullet); return(ConvertNodesToTankActions(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); }