public override void ChooseMovesAsPBar(MoveResult moveResult)
        {
            Move move = moveResult.Move;

            LogDebugMessage("*** ANTAGONIST (PBar = {0}) ***", move.pBar);
            LogDebugMessage("Slack: {0}", moveResult.Slack);

            double valueOfMove = ScenarioValueFunctions.ClearRunAtBaseScenarioValueFunction.Evaluate(moveResult.Slack);

            LogDebugMessage("Value of move: {0}", valueOfMove);

            // Defend against an enemy attack on your base:
            for (int tankNumber = 0; tankNumber < Constants.TANKS_PER_PLAYER; tankNumber++)
            {
                LogDebugMessage("Tank number: {0}", tankNumber);
                TankActionRecommendation recommendation = moveResult.GetRecommendedTankActionsByPlayerAndTankNumber(move.pBar, tankNumber);
                if (recommendation.IsAMoveRecommended)
                {
                    TankSituation tankSituation
                        = GameSituation.GetTankSituationByPlayerAndTankNumber(move.pBar, tankNumber);
                    TankAction recommendedTankAction = recommendation.RecommendedTankAction;
                    LogDebugMessage("Recommended tank action: {0}", recommendedTankAction);

                    tankSituation.AdjustTankActionValue(recommendedTankAction, valueOfMove);
                }
                else
                {
                    LogDebugMessage("No moves recommended");
                }
            }
        }
        public override void ChooseMovesAsP(MoveResult moveResult)
        {
            Move move = moveResult.Move;

            LogDebugMessage("*** PROTAGONIST (P = {0}) ***", move.p);
            LogDebugMessage("Slack: {0}", moveResult.Slack);

            double valueOfMove = ScenarioValueFunctions.ClearRunAtBaseScenarioValueFunction.Evaluate(moveResult.Slack);

            LogDebugMessage("Value of move: {0}", valueOfMove);

            // Attack the enemy base:
            LogDebugMessage("Tank number: {0}", move.i);
            TankActionRecommendation recommendation
                = moveResult.GetRecommendedTankActionsByPlayerAndTankNumber(move.p, move.i);

            if (recommendation.IsAMoveRecommended)
            {
                TankSituation tankSituation         = GameSituation.GetTankSituationByPlayerAndTankNumber(move.p, move.i);
                TankAction    recommendedTankAction = recommendation.RecommendedTankAction;
                LogDebugMessage("Recommended tank action: {0}", recommendedTankAction);

                tankSituation.AdjustTankActionValue(recommendedTankAction, valueOfMove);
            }
            else
            {
                LogDebugMessage("No moves recommended");
            }
        }
        public override MoveResult EvaluateLeafNodeMove(Move move)
        {
            TankAction[] actions_p_i;
            TankAction[] actions_p_iBar = new TankAction[0];
            TankAction[] actions_pBar_j;
            TankAction[] actions_pBar_jBar;

            MobileState tankState_j    = GetTankState(move.pBar, move.j);
            MobileState tankState_jBar = GetTankState(move.pBar, move.jBar);

            MoveResult moveResult = new MoveResult(move);

            if (!IsValid(move))
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Invalid;
                return(moveResult);
            }

            // Get the attack distance of player p's tank i to the enemy base:
            int A_p_i = GetAttackDistanceOfTankToEnemyBaseFromDirection(move.p, move.i, move.dir1);

            if (A_p_i >= Constants.UNREACHABLE_DISTANCE)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Invalid;
                return(moveResult);
            }

            // In this scenario the other tank (iBar) is either dead or too far from the action to be of any use:
            int A_p_iBar = GetAttackDistanceOfTankToEnemyBaseFromDirection(move.p, move.iBar, move.dir1);

            if (A_p_iBar < A_p_i)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Invalid;
                return(moveResult);
            }

            // Set the recommended action for the attacking tank:
            actions_p_i = GetActionsToAttackEnemyBaseFromDirection(move.p, move.i, move.dir1);
            TankActionRecommendation tankActionRec = new TankActionRecommendation
            {
                IsAMoveRecommended    = true,
                RecommendedTankAction = actions_p_i[0]
            };

            moveResult.SetTankActionRecommendation(move.p, move.i, tankActionRec);

            // *** The role for tank p_i is to attack the enemy base along direction dir1.

            // Get the minimum attack distance of player pBar's tanks:
            int A_pBar_j    = GetAttackDistanceOfTankToEnemyBase(move.pBar, move.j);
            int A_pBar_jBar = GetAttackDistanceOfTankToEnemyBase(move.pBar, move.jBar);
            int A_pBar_MIN  = Math.Min(A_pBar_j, A_pBar_jBar);

            // iBar is not part of this scenario. Ensure they can't interfere with the defence:
            int D_p_iBar = GetCentralLineOfFireDefenceDistanceToHomeBase(move.p, move.iBar);

            if (D_p_iBar <= A_pBar_MIN)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Invalid;
                return(moveResult);
            }

            // Calculate slack A as p's attack distance less pBar's attack distance
            int slackA = A_p_i - A_pBar_MIN;

            // *** slackA is the attack slack (p's attack distance less pBar's best attack distance)

            // Get the minimum defence distances of player pBar's tank j to the base:
            int D_pBar_j    = GetLineOfFireDefenceDistanceToHomeBaseByIncomingAttackDirection(move.pBar, move.j, move.dir1);
            int D_pBar_jBar = GetLineOfFireDefenceDistanceToHomeBaseByIncomingAttackDirection(move.pBar, move.jBar, move.dir1);
            int D_pBar_MIN  = Math.Min(D_pBar_j, D_pBar_jBar);

            // Calculate slack D as p's attack distance less pBar's defence distance
            // (to the same base and on the same direction of attack):
            int slackD = A_p_i - D_pBar_MIN;

            // *** slackD is the defence slack (defender distance to defence less attacker distance to attack with direction dir1

            // Get the overall slack (distance to activating this scenario):
            int slack = Math.Max(slackA, slackD);

            moveResult.Slack = slack;
            if (slack < 0)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Current;
            }
            else
            if (slack <= EVALUATION_OUTCOME_CLOSE_THRESHOLD)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Close;
            }
            else
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Possible;
            }

            // Calculate best defensive actions for the defender:
            bool pBar_j_defends;
            bool pBar_jBar_defends;

            if (slackD < slackA)
            {
                // Defending is the smallest slack, so put best defensive effort here:
                if (D_pBar_j < D_pBar_jBar)
                {
                    pBar_j_defends    = true;
                    pBar_jBar_defends = false;

                    // *** Tank goal: pBar_j defends base from incoming attack with direction dir1
                    // *** Tank goal: pBar_jBar does nothing
                }
                else
                {
                    pBar_j_defends    = false;
                    pBar_jBar_defends = true;

                    // *** Tank goal: pBar_j does nothing
                    // *** Tank goal: pBar_jBar defends base from incoming attack with direction dir1
                }
            }
            else
            {
                // Attacking is the smallest slack, so put most effort here:
                if (A_pBar_j < A_pBar_jBar)
                {
                    pBar_j_defends    = false;
                    pBar_jBar_defends = true;

                    // *** Tank goal: pBar_j does nothing
                    // *** Tank goal: pBar_jBar defends base from incoming attack with direction dir1
                }
                else
                {
                    pBar_j_defends    = true;
                    pBar_jBar_defends = false;

                    // *** Tank goal: pBar_j defends base from incoming attack with direction dir1
                    // *** Tank goal: pBar_jBar does nothing
                }
            }

            // Determine best action for pBar.j:
            // TODO: Check if is alive, is locked down, is locked in a quadrant, already has a move assigned, etc.
            if (pBar_j_defends)
            {
                actions_pBar_j
                    = base.GetActionsToReachLineOfFireDefencePointByIncomingAttackDirection(move.pBar, move.j, move.dir1);
            }
            else
            {
                actions_pBar_j = GetActionsToAttackEnemyBase(move.pBar, move.j);
            }
            if (actions_pBar_j != null && actions_pBar_j.Length > 0)
            {
                tankActionRec = new TankActionRecommendation
                {
                    IsAMoveRecommended    = true,
                    RecommendedTankAction = actions_pBar_j[0]
                };
                moveResult.SetTankActionRecommendation(move.pBar, move.j, tankActionRec);
            }

            // Determine best action for pBar.jBar:
            // TODO: Check if is alive, is locked down, is locked in a quadrant, already has a move assigned, etc.
            if (pBar_jBar_defends)
            {
                actions_pBar_jBar
                    = base.GetActionsToReachLineOfFireDefencePointByIncomingAttackDirection(move.pBar, move.jBar, move.dir1);
            }
            else
            {
                actions_pBar_jBar = GetActionsToAttackEnemyBase(move.pBar, move.jBar);
            }

            if (actions_pBar_jBar != null && actions_pBar_jBar.Length > 0)
            {
                tankActionRec = new TankActionRecommendation
                {
                    IsAMoveRecommended    = true,
                    RecommendedTankAction = actions_pBar_jBar[0]
                };
                moveResult.SetTankActionRecommendation(move.pBar, move.jBar, tankActionRec);
            }

            return(moveResult);
        }
        // Tank t_p_i is the tank that locks down the enemy tank
        // Tank t_pBar_j is the enemy tank that gets locked down
        // Tank t_p_j is the one that finishes off the locked down tank
        // Tank t_pBar_jBar is the other enemy tank. It must not be able to attack t_p_i sooner than t_p_iBar can attack t_pBar_j
        // Note: Rely on scenario 1 to prevent a situation where the locked down enemy tank is destroyed, but the other enemy tank reaches the base first
        // dir1 is the direction that t_p_i attacks t_pBar_j from (and their attack direction is the opposite)
        // dir2 is the direction that t_p_iBar attack t_pBar_j from - and must be different from dir1
        // dir3 is the direction that t_pBar_jBar attacks t_p_i from - and must be different from dir1.GetOpposite()
        public override MoveResult EvaluateLeafNodeMove(Move move)
        {
            MoveResult moveResult = new MoveResult(move);

            if (!IsValid(move))
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Invalid;
                return(moveResult);
            }

            MobileState tankState_i = GetTankState_i(move);
            MobileState tankState_j = GetTankState_j(move);

            // Note that the following could be Direction.None if in line...
            Direction horizDir = tankState_i.Pos.GetHorizontalDirectionToPoint(tankState_j.Pos);
            Direction vertDir  = tankState_i.Pos.GetVerticalDirectionToPoint(tankState_j.Pos);

            // Don't try and attack from a different direction than the two obvious ones:
            if ((move.dir1 == horizDir.GetOpposite()) || (move.dir1 == vertDir.GetOpposite()))
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Invalid;
                return(moveResult);
            }

            int tank_i_moveDistance_to_j = base.GetDistanceFromTankToTargetTank(move.p, move.i, move.pBar, move.j);

            if (tank_i_moveDistance_to_j > THRESHOLD_DISTANCE_CONSIDERED_CLOSE_TO_LOCKDOWN)
            {
                moveResult = EvaluateLeafNodeMoveWhenTanksAreNotCloseToLockDown(
                    move, tankState_i, tankState_j, horizDir, vertDir);
                return(moveResult);
            }

            // ***************************************************
            // We are in close-range lock-down mode.

            Direction movementDir;

            if (horizDir != move.dir1)
            {
                movementDir = horizDir;
            }
            else
            {
                movementDir = vertDir;
            }

            int tank_j_attackDistance_to_i = base.GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                move.pBar, move.j, tankState_i.Pos, move.dir1.GetOpposite(), TankHelper.EdgeOffsets);
            int tank_i_attackDistance_to_j = base.GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                move.p, move.i, tankState_i.Pos, move.dir1.GetOpposite(), TankHelper.EdgeOffsets);
            Point newPos = tankState_i.Pos;

            if (movementDir == Direction.NONE)
            {
                // In lock-down position. Hope we don't get shot!
                movementDir = move.dir1;
                bool         moveCloser  = tank_j_attackDistance_to_i > 3;
                TankAction[] tankActions = GetTankActionsFromTankToAttackTankAtPointAlongDirectionOfMovement(
                    move.p, move.i, tankState_j.Pos, move.dir1, new EdgeOffset[] { EdgeOffset.Centre },
                    keepMovingCloserOnFiringLastBullet: moveCloser);
                if (tankActions.Length > 0)
                {
                    TankActionRecommendation tankActRec = new TankActionRecommendation
                    {
                        IsAMoveRecommended    = true,
                        RecommendedTankAction = tankActions[0]
                    };
                    moveResult.SetTankActionRecommendation(move.p, move.i, tankActRec);
                }
            }
            else
            {
                // Move towards the enemy tank, but be careful not to become a target:

                // TODO: Check not walking into a bullet

                // Time has run out for the competition. Just move, don't calculate if it's a good idea...
                Point movementOffset = movementDir.GetOffset();
                newPos = tankState_i.Pos + movementOffset;
                TankAction[] tankActions = GetTankActionsToMoveToPoint(move.p, move.i, movementDir, newPos);
                if (tankActions.Length > 0)
                {
                    TankActionRecommendation tankActRec = new TankActionRecommendation
                    {
                        IsAMoveRecommended    = true,
                        RecommendedTankAction = tankActions[0]
                    };
                    moveResult.SetTankActionRecommendation(move.p, move.i, tankActRec);
                }

                // TODO: For future improvement...
                // We want to move close to the enemy, but not so close that he can attack us.
                // - unless we are on the same line as him, as then the lock-down can commence.
                // So his attack distance to us must be kept above a certain number, based on how far from the centre line we are:

                // tank_j_attackDistance_to_i = base.GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                //    move.pBar, move.j, newPos, move.dir1.GetOpposite(), TankHelper.EdgeOffsets);
            }

            int A_p_iBar = GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                move.p, move.iBar, tankState_j.Pos, move.dir2, TankHelper.EdgeOffsets);
            int A_pBar_jBar
                = GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                      move.pBar, move.jBar, newPos, move.dir3, TankHelper.EdgeOffsets);

            int slack = A_p_iBar - A_pBar_jBar;

            moveResult.Slack = slack;

            // p_iBar can now commence the attack (actually it's premature, but I'm running out of time!):
            TankAction[] tankActions_p_iBar = GetTankActionsFromTankToAttackTankAtPointAlongDirectionOfMovement(
                move.p, move.iBar, tankState_j.Pos, move.dir2, TankHelper.EdgeOffsets, keepMovingCloserOnFiringLastBullet: false);
            if (tankActions_p_iBar.Length > 0)
            {
                TankActionRecommendation tankActRec = new TankActionRecommendation
                {
                    IsAMoveRecommended    = true,
                    RecommendedTankAction = tankActions_p_iBar[0],
                };
                moveResult.SetTankActionRecommendation(move.p, move.iBar, tankActRec);
            }

            // Tank pBar_j can commence attack on target p_i:
            TankAction[] tankActions_pBar_j = GetTankActionsFromTankToAttackTankAtPointAlongDirectionOfMovement(
                move.pBar, move.j, newPos, move.dir3, TankHelper.EdgeOffsets, keepMovingCloserOnFiringLastBullet: false);
            if (tankActions_pBar_j.Length > 0)
            {
                TankActionRecommendation tankActRec = new TankActionRecommendation
                {
                    IsAMoveRecommended    = true,
                    RecommendedTankAction = tankActions_pBar_j[0],
                };
                moveResult.SetTankActionRecommendation(move.pBar, move.j, tankActRec);
            }

            if (slack < 0)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Current;
            }
            else
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Close;
            }
            return(moveResult);
        }
        public MoveResult EvaluateLeafNodeMoveWhenTanksAreNotCloseToLockDown(Move move,
                                                                             MobileState tankState_i, MobileState tankState_j, Direction horizDir, Direction vertDir)
        {
            MoveResult moveResult = new MoveResult(move);

            // Find the Voronoi point on the straight line between the two tanks as an estimate of where the lock-down could take place:
            Point[] zigZagPoints       = tankState_i.Pos.GetPointsOnZigZagLineToTargetPoint(tankState_j.Pos);
            int     minDiff            = Constants.UNREACHABLE_DISTANCE;
            int     distToMinDiffPoint = Constants.UNREACHABLE_DISTANCE;
            Point   minDiffPoint       = new Point();

            foreach (Point pointOnLine in zigZagPoints)
            {
                int dist1    = GetDistanceFromTankToPoint(move.p, move.i, move.dir1, pointOnLine);
                int dist2    = GetDistanceFromTankToPoint(move.pBar, move.j, move.dir1.GetOpposite(), pointOnLine);
                int distDiff = Math.Abs(dist1 - dist2);
                if (distDiff < minDiff)
                {
                    minDiff            = distDiff;
                    minDiffPoint       = pointOnLine;
                    distToMinDiffPoint = dist1;
                }
            }

            if (minDiff == Constants.UNREACHABLE_DISTANCE)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Invalid;
                return(moveResult);
            }

            Rectangle boardBoundary = new Rectangle(0, 0, (short)(GameState.Walls.Width - 1), (short)(GameState.Walls.Height - 1));

            Point estimated_pos_pBar_j = minDiffPoint + move.dir1.GetOffset(4);  // 4 points away on one side

            estimated_pos_pBar_j = estimated_pos_pBar_j.BringIntoBounds(boardBoundary);

            Point estimated_pos_p_i = minDiffPoint + move.dir1.GetOpposite().GetOffset(4);  // 4 points away on the other side

            estimated_pos_p_i = estimated_pos_p_i.BringIntoBounds(boardBoundary);

            int slack_lock_down = distToMinDiffPoint;

            int A_p_iBar
                = GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                      move.p, move.iBar, estimated_pos_pBar_j, move.dir2, TankHelper.EdgeOffsets);
            int A_pBar_jBar
                = GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                      move.pBar, move.jBar, estimated_pos_p_i, move.dir3, TankHelper.EdgeOffsets);
            int slack_Bar = A_p_iBar - A_pBar_jBar;
            int slack     = Math.Max(slack_Bar, slack_lock_down);

            // slack_Bar is symmetrical. If it's above zero, then we are in trouble.
            // If below, then he's in trouble. So choose actions based on this slack:
            if (slack_Bar < 0)
            {
                // Move tank p_i closer to the estimated interception point with pBar_j:
                TankAction[] tankActions_move_p_i = GetTankActionsToMoveToPoint(move.p, move.i, move.dir1, estimated_pos_pBar_j);
                if (tankActions_move_p_i.Length > 0)
                {
                    moveResult.SetTankActionRecommendation(move.p, move.i,
                                                           new TankActionRecommendation
                    {
                        IsAMoveRecommended    = true,
                        RecommendedTankAction = tankActions_move_p_i[0]
                    });
                }

                // Move tank pBar_j further away from attackers p_i and p_iBar:
                // (Also ideally towards the edge of the board, if that's not where the attack is coming from, to act as a decoy)
                int        maxDistanceFromAttackers = int.MinValue;
                TankAction escapeAction             = TankAction.NONE;

                foreach (Direction escapeDir in BoardHelper.AllRealDirections)
                {
                    Point adjacentPoint = tankState_j.Pos + escapeDir.GetOffset();
                    TurnCalculationCache turnCalcCache = Game.Current.Turns[GameState.Tick].CalculationCache;
                    TankLocation         tankLoc       = turnCalcCache.TankLocationMatrix[adjacentPoint];
                    if (tankLoc.IsValid)
                    {
                        int adj_moveDistance_pBar_j = GetDistanceFromTankToPoint(move.pBar, move.j, escapeDir, adjacentPoint);
                        if (adj_moveDistance_pBar_j < Constants.UNREACHABLE_DISTANCE)
                        {
                            int adj_attackDistance_p_i = GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                                move.p, move.i, adjacentPoint, move.dir1, TankHelper.EdgeOffsets);
                            int adj_attackDistance_p_iBar = GetAttackDistanceFromTankToTankAtPointAlongDirectionOfMovement(
                                move.p, move.iBar, adjacentPoint, move.dir2, TankHelper.EdgeOffsets);
                            int adj_attackDistance_SUM = adj_attackDistance_p_i + adj_attackDistance_p_iBar - adj_moveDistance_pBar_j;

                            if (adj_attackDistance_SUM < maxDistanceFromAttackers)
                            {
                                TankAction[] escapeActions = GetTankActionsToMoveToPoint(move.pBar, move.j, escapeDir, adjacentPoint);
                                if (escapeActions.Length > 0)
                                {
                                    maxDistanceFromAttackers = adj_attackDistance_SUM;
                                    escapeAction             = escapeActions[0];
                                }
                            }
                        }
                    }
                }

                if (escapeAction != TankAction.NONE)
                {
                    TankActionRecommendation tankActRec = new TankActionRecommendation
                    {
                        IsAMoveRecommended    = true,
                        RecommendedTankAction = escapeAction
                    };
                    moveResult.SetTankActionRecommendation(move.pBar, move.j, tankActRec);
                }
            }

            // Move tank p_iBar closer to lock down target pBar_j (don't attack yet, because lock-down hasn't occurred):
            TankAction[] tankActions_p_iBar = GetTankActionsToMoveToPoint(move.p, move.iBar, move.dir2, estimated_pos_pBar_j);
            if (tankActions_p_iBar.Length > 0)
            {
                TankActionRecommendation tankActRec = new TankActionRecommendation
                {
                    IsAMoveRecommended    = true,
                    RecommendedTankAction = tankActions_p_iBar[0],
                };
                moveResult.SetTankActionRecommendation(move.p, move.iBar, tankActRec);
            }

            // Move tank pBar_j closer to lock down target p_i:
            TankAction[] tankActions_pBar_j = GetTankActionsToMoveToPoint(move.pBar, move.j, move.dir3, estimated_pos_p_i);
            if (tankActions_pBar_j.Length > 0)
            {
                TankActionRecommendation tankActRec = new TankActionRecommendation
                {
                    IsAMoveRecommended    = true,
                    RecommendedTankAction = tankActions_pBar_j[0],
                };
                moveResult.SetTankActionRecommendation(move.pBar, move.j, tankActRec);
            }

            if (slack < 0)
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Close;
            }
            else
            {
                moveResult.EvaluationOutcome = ScenarioEvaluationOutcome.Possible;
            }
            return(moveResult);
        }