Exemplo n.º 1
0
        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;
        }