public static BulletSituation CreateBulletSituation(GameState gameState, TankSituation tankSituation, int bulletIndex, int bulletId) { MobileState bulletState = gameState.GetMobileState(bulletIndex); MobileState tankState = gameState.GetMobileState(bulletIndex - Constants.MIN_BULLET_INDEX); BulletSituation bulletSituation; if (!bulletState.IsActive) { bulletSituation = new BulletSituation(tankSituation, bulletIndex, bulletId) { IsActive = false, TickOffsetWhenTankCanFireAgain = 0, TankStateAtTimeOfFiring = tankState, BulletStateAtTimeOfFiring = bulletState, BulletCalculationsByTick = new BulletCalculationByTick[0] }; return(bulletSituation); } // Assume the bullet has just been fired (not correct, but it shouldn't matter): bulletSituation = new BulletSituation(tankSituation, bulletIndex, bulletId) { TickFired = gameState.Tick, TankStateAtTimeOfFiring = tankState, BulletStateAtTimeOfFiring = bulletState, IsActive = true }; GenerateBulletTimeline(gameState, bulletSituation); return(bulletSituation); }
// TODO: Use or remove... public static void UpdateBulletSituation(GameState gameState, BulletSituation bulletSituation) { MobileState bulletState = gameState.GetMobileState(bulletSituation.BulletIndex); if (!bulletState.IsActive) { bulletSituation.IsActive = false; bulletSituation.BulletCalculationsByTick = new BulletCalculationByTick[0]; } else { // Re-generate the bullet timeline every turn, since the board may have changed: GenerateBulletTimeline(gameState, bulletSituation); } }
/// <summary> /// Create a hypothetical bullet situation for firing a bullet /// </summary> /// <param name="gameState"></param> /// <param name="hypotheticalTankSituation"> /// This must be a hypothetical tank situation in a hypothetical game situation. /// It may not be the real situation, otherwise actual data will get overwritten. /// </param> /// <param name="tickFired"></param> /// <param name="bulletIndex"></param> /// <param name="bulletId"></param> /// <returns></returns> public static BulletSituation CreateHypotheticalBulletSituationForNewlyFiredBullet(GameState gameState, TankSituation hypotheticalTankSituation, int tickFired, int bulletIndex, int bulletId) { MobileState bulletState = gameState.GetMobileState(bulletIndex); MobileState tankState = gameState.GetMobileState(bulletIndex - Constants.MIN_BULLET_INDEX); BulletSituation bulletSituation = new BulletSituation(hypotheticalTankSituation, bulletIndex, bulletId) { TickFired = tickFired, TankStateAtTimeOfFiring = tankState, BulletStateAtTimeOfFiring = bulletState, IsActive = true }; GenerateBulletTimeline(gameState, bulletSituation); return(bulletSituation); }
private void EvaluateBulletSituation(Move move, TankSituation tankSituation, BulletSituation bulletSituation, int ticksUntil_p_i_CanFireAgain, ref int worstSlack, ref TankAction tankActionToAddressWorstSlack, ref bool isScenarioApplicable) { bool isScenarioApplicableForThisBulletSituation = false; int bestSlackForThisBulletSituation = Constants.UNREACHABLE_DISTANCE; TankAction bestTankActionForThisBulletSituation = TankAction.NONE; foreach (BulletCalculationByTick bulletCalc in bulletSituation.BulletCalculationsByTick) { if (!bulletCalc.AreTanksAtRisk) { continue; } int ticksToEscape = bulletCalc.Tick - GameState.Tick; if (ticksToEscape < 0) { continue; } // Are you in the firing line: if (bulletCalc.TankCentrePointsThatDie.ContainsPoint(tankSituation.TankState.Pos)) { List <BulletSurvivalTactic> bulletTactics = new List <BulletSurvivalTactic>(); isScenarioApplicableForThisBulletSituation = true; // Suppose the bullet just misses the tank on turn n, with t ticks to escape... int closestEscapeTick = ticksToEscape * 2 / 3; // 2t - n + 1 = 2n + 1 so 3n = 2t int furthestEscapeTick = 2 * ticksToEscape + 1; // 2t + n + 1 = 2n so n = 2t + 1 for (int tk = closestEscapeTick; tk <= furthestEscapeTick; tk++) { if (tk >= bulletSituation.BulletCalculationsByTick.Length) { break; } BulletCalculationByTick bulletCalc_tk = bulletSituation.BulletCalculationsByTick[tk]; if (!bulletCalc_tk.AreTanksAtRisk) { break; } ticksToEscape = bulletCalc_tk.Tick - GameState.Tick; // Fire at the bullet: foreach (MobileState headOnState in bulletCalc_tk.ClosestTankStatesThatCanShootBulletHeadOn) { // Move into position to confront the bullet: AddBulletSurvivalTactic(move, ticksUntil_p_i_CanFireAgain, ticksToEscape, bulletTactics, headOnState, bulletSituation.TickOffsetWhenTankCanFireAgain, isConfrontingBullet: true); } // Other moves to dodge the bullet: foreach (Point survivalPoint in bulletCalc_tk.ClosestTankCentrePointsThatSurviveClockwise) { Direction dir = bulletSituation.BulletMovementDir.Clockwise(); AddBulletSurvivalTactic(move, ticksUntil_p_i_CanFireAgain, ticksToEscape, bulletTactics, survivalPoint, dir, bulletSituation.TickOffsetWhenTankCanFireAgain, isConfrontingBullet: false); } foreach (Point survivalPoint in bulletCalc_tk.ClosestTankCentrePointsThatSurviveAntiClockwise) { Direction dir = bulletSituation.BulletMovementDir.AntiClockwise(); AddBulletSurvivalTactic(move, ticksUntil_p_i_CanFireAgain, ticksToEscape, bulletTactics, survivalPoint, dir, bulletSituation.TickOffsetWhenTankCanFireAgain, isConfrontingBullet: false); } } var bulletTacticsByTankAction = bulletTactics.GroupBy(tactic => tactic.InitialTankAction); foreach (var grouping in bulletTacticsByTankAction) { TankAction tankAction = grouping.Key; BulletSurvivalTactic bestTacticForTankAction = grouping.OrderBy(tactic => tactic.Value).First(); tankSituation.TankActionSituationsPerTankAction[(int)tankAction].Value += bestTacticForTankAction.Value; // Note that this is a combination of the slack value and the disarmament value if (bestTacticForTankAction.Slack < bestSlackForThisBulletSituation) { bestSlackForThisBulletSituation = bestTacticForTankAction.Slack; bestTankActionForThisBulletSituation = tankAction; } } // We've made our escape plans. Don't continue iterating through bullet calculations... break; } // If any of the danger points are adjacent to your current point, // prevent moving into the bullet by add a large negative value to the move, // with the size of the value depending on the slack... foreach (TankActionSituation tankActionSituation in tankSituation.TankActionSituationsPerTankAction) { if (bulletCalc.TankCentrePointsThatDie.ContainsPoint(tankActionSituation.NewTankState.Pos)) { // Calculate the slack values and tank action value adjustment... double value = ScenarioValueFunctions.AvoidWalkingIntoABulletFunction.Evaluate(ticksToEscape); tankActionSituation.Value += value; } } } if (isScenarioApplicableForThisBulletSituation) { if (bestSlackForThisBulletSituation > worstSlack) { worstSlack = bestSlackForThisBulletSituation; tankActionToAddressWorstSlack = bestTankActionForThisBulletSituation; } isScenarioApplicable = true; } }
private static void GenerateBulletTimeline(GameState gameState, BulletSituation bulletSituation) { MobileState bulletState = bulletSituation.BulletStateAtTimeOfFiring; TurnCalculationCache turnCalcCache = Game.Current.Turns[bulletSituation.TickFired].CalculationCache; Direction bulletMovementDir = bulletSituation.BulletMovementDir; Direction oppositeDir = bulletMovementDir.GetOpposite(); Cell cell = turnCalcCache.CellMatrix[bulletState.Pos]; Line <Point> pointsInBulletPath = cell.LineFromCellToEdgeOfBoardByDirection[(int)bulletState.Dir]; int maxTickCount = (pointsInBulletPath.Length + 2) / 2; BulletCalculationByTick[] bulletCalcsByTick = new BulletCalculationByTick[maxTickCount]; int tickOffset; for (tickOffset = 0; tickOffset < maxTickCount; tickOffset++) { BulletCalculationByTick bulletCalc = new BulletCalculationByTick { Tick = bulletSituation.TickFired + tickOffset, TickOffset = tickOffset }; bulletCalcsByTick[tickOffset] = bulletCalc; bool areTanksAtRisk = false; Rectangle dangerRect = new Rectangle(); Point[] bulletPoints = new Point[2]; Point[,] adjacentTankPointsByRotationTypeAndPhase = new Point[Constants.ROTATION_TYPE_COUNT, 2]; int bulletPhase; int startPhase = (tickOffset == 0) ? 1 : 0; for (bulletPhase = startPhase; bulletPhase < 2; bulletPhase++) { int pointIndex = 2 * tickOffset - 1 + bulletPhase; if (pointIndex >= pointsInBulletPath.Length) { // Bullet moved off the edge of the board: bulletCalc.IsDestroyed = true; bulletSituation.TickOffsetWhenTankCanFireAgain = tickOffset; break; } Point bulletPoint = pointsInBulletPath[pointIndex]; bulletPoints[bulletPhase] = bulletPoint; Cell bulletCell = turnCalcCache.CellMatrix[bulletPoint]; if ((!bulletCell.IsValid) || gameState.Walls[bulletPoint]) { bulletCalc.IsDestroyed = true; bulletSituation.TickOffsetWhenTankCanFireAgain = tickOffset; break; } areTanksAtRisk = true; TankLocation tankLoc = turnCalcCache.TankLocationMatrix[bulletPoint]; if (bulletPhase == 0 || tickOffset == 0) { dangerRect = tankLoc.TankBody; } else { dangerRect = dangerRect.Merge(tankLoc.TankBody); } foreach (RotationType rotationType in BoardHelper.AllRotationTypes) { Direction rotatedDir = bulletMovementDir.GetRotatedDirection(rotationType); switch (rotationType) { case RotationType.Clockwise: case RotationType.AntiClockwise: // A bit more complex than expected... must move the escape point forwards so that ticks to escape is correct: adjacentTankPointsByRotationTypeAndPhase[(int)rotationType, bulletPhase] = bulletPoint + rotatedDir.GetOffset(Constants.TANK_OUTER_EDGE_OFFSET) + bulletMovementDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK); break; case RotationType.None: adjacentTankPointsByRotationTypeAndPhase[(int)rotationType, bulletPhase] = bulletPoint + rotatedDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK); // Add the extra 2 spaces, so that the tank still has a turn in which to fire, since bullets move first break; case RotationType.OneEightyDegrees: adjacentTankPointsByRotationTypeAndPhase[(int)rotationType, bulletPhase] = bulletPoint + rotatedDir.GetOffset(Constants.TANK_OUTER_EDGE_OFFSET + 1); // Add the extra +1 offset, so the tank can also turn to face the firing tank break; } } // The safe position must be } bulletCalc.AreTanksAtRisk = areTanksAtRisk; if (areTanksAtRisk) { bulletCalc.TankCentrePointsThatDie = dangerRect; if (tickOffset != 0) { Point pointMovingInBehindBulletFacingFiringTank = adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.OneEightyDegrees, 0]; bulletCalc.ClosestTankStateMovingInBehindBulletFacingFiringTank = new MobileState(pointMovingInBehindBulletFacingFiringTank, oppositeDir, isActive: true); } if (bulletPhase == 2) { if (tickOffset == 0) { // Only include calculations relative to the second bullet point: Point pointMovingInBehindBulletFacingFiringTank = adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.OneEightyDegrees, 1]; bulletCalc.ClosestTankStateMovingInBehindBulletFacingFiringTank = new MobileState(pointMovingInBehindBulletFacingFiringTank, oppositeDir, isActive: true); bulletCalc.BulletPoints = new Point[] { bulletPoints[1] }; bulletCalc.ClosestTankCentrePointsThatSurviveAntiClockwise = new Point[] { adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.AntiClockwise, 1] }; bulletCalc.ClosestTankCentrePointsThatSurviveClockwise = new Point[] { adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.Clockwise, 1] }; bulletCalc.ClosestTankStatesThatCanShootBulletHeadOn = new MobileState[] { new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.None, 1], oppositeDir, isActive: true) }; bulletCalc.ClosestTankStatesThatCanShootBullet = new MobileState[] { new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.AntiClockwise, 1] + oppositeDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK), bulletMovementDir.GetRotatedDirection(RotationType.Clockwise), isActive: true), new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.Clockwise, 1] + oppositeDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK), bulletMovementDir.GetRotatedDirection(RotationType.AntiClockwise), isActive: true), bulletCalc.ClosestTankStatesThatCanShootBulletHeadOn[0] }; } else { // Include calculations relative to the first and second bullet point: bulletCalc.BulletPoints = new Point[] { bulletPoints[0], bulletPoints[1] }; bulletCalc.ClosestTankCentrePointsThatSurviveAntiClockwise = new Point[] { adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.AntiClockwise, 0], adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.AntiClockwise, 1] }; bulletCalc.ClosestTankCentrePointsThatSurviveClockwise = new Point[] { adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.Clockwise, 0], adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.Clockwise, 1] }; bulletCalc.ClosestTankStatesThatCanShootBulletHeadOn = new MobileState[] { new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.None, 0], oppositeDir, isActive: true), new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.None, 1], oppositeDir, isActive: true) }; bulletCalc.ClosestTankStatesThatCanShootBullet = new MobileState[] { new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.AntiClockwise, 1] + oppositeDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK), bulletMovementDir.GetRotatedDirection(RotationType.Clockwise), isActive: true), new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.Clockwise, 1] + oppositeDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK), bulletMovementDir.GetRotatedDirection(RotationType.AntiClockwise), isActive: true), bulletCalc.ClosestTankStatesThatCanShootBulletHeadOn[0], bulletCalc.ClosestTankStatesThatCanShootBulletHeadOn[1] }; } } else { // The second bullet point never occurred as the bullet was destroyed on the first one. // So only include points and tank states relative to the first bullet point: bulletCalc.BulletPoints = new Point[] { bulletPoints[0] }; bulletCalc.ClosestTankCentrePointsThatSurviveAntiClockwise = new Point[] { adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.AntiClockwise, 0] }; bulletCalc.ClosestTankCentrePointsThatSurviveClockwise = new Point[] { adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.Clockwise, 0] }; MobileState headOnConfrontationState = new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.None, 0], oppositeDir, isActive: true); bulletCalc.ClosestTankStatesThatCanShootBulletHeadOn = new MobileState[] { headOnConfrontationState }; bulletCalc.ClosestTankStatesThatCanShootBullet = new MobileState[] { new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.AntiClockwise, 0] + oppositeDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK), bulletMovementDir.GetRotatedDirection(RotationType.Clockwise), isActive: true), new MobileState( adjacentTankPointsByRotationTypeAndPhase[(int)RotationType.Clockwise, 0] + oppositeDir.GetOffset(Constants.TANK_EXTENT_OFFSET + Constants.BULLET_MOVEMENTS_PER_TICK), bulletMovementDir.GetRotatedDirection(RotationType.Clockwise), isActive: true), headOnConfrontationState }; } } if (bulletCalc.IsDestroyed) { break; } } if (tickOffset + 1 < maxTickCount) { Array.Resize(ref bulletCalcsByTick, tickOffset + 1); } bulletSituation.BulletCalculationsByTick = bulletCalcsByTick; }