public void UpdateTankActionSituation(TankSituation tankSituation, TankAction tankAction, GameState newGameState)
        {
            TankAction[] tankActions = new TankAction[] { TankAction.NONE, TankAction.NONE, TankAction.NONE, TankAction.NONE };
            tankActions[tankSituation.Tank.Index] = tankAction;
            List <Point> wallsRemoved = new List <Point>();

            MutableGameStateEngine.ApplyAllActions(newGameState as MutableGameState, tankActions, wallsRemoved);
            bool isAdjacentWallRemoved = false;

            if (!tankSituation.TankState.IsActive)
            {
                IsValid = (TankAction == TankAction.NONE);
                return;
            }
            else
            {
                IsValid = true;
            }

            if (tankAction == TankAction.FIRE)
            {
                Direction    movementDir = tankSituation.TankState.Dir;
                TankLocation tankLoc
                    = Game.Current.Turns[newGameState.Tick].CalculationCache.TankLocationMatrix[tankSituation.TankState.Pos];
                Point adjacentWallPoint = tankLoc.OutsideEdgesByDirection[(int)movementDir].CentreCell.Position;
                isAdjacentWallRemoved = wallsRemoved.Any(
                    wallPoint => wallPoint == adjacentWallPoint
                    );
            }

            NewGameState          = newGameState;
            WallsRemoved          = wallsRemoved.ToArray();
            IsAdjacentWallRemoved = isAdjacentWallRemoved;
            NewTankState          = NewGameState.GetMobileState(TankSituation.Tank.Index);
        }
        public DirectionalMatrix <DistanceCalculation> GetDistanceMatrixFromTankByTankIndex(int tankIndex)
        {
            if (distanceMatricesFromTankByTankIndex == null)
            {
                distanceMatricesFromTankByTankIndex = new DirectionalMatrix <DistanceCalculation> [Constants.TANK_COUNT];
            }
            if (distanceMatricesFromTankByTankIndex[tankIndex] == null)
            {
                TurnCalculationCache turnCalcCache = TurnCalculationCache;

                // Don't ride over your own base!
                Tank         tank       = Game.Current.Elements[tankIndex] as Tank;
                Base         @base      = tank.Player.Base;
                TankLocation tankLoc    = turnCalcCache.TankLocationMatrix[@base.Pos];
                Rectangle[]  tabooAreas = new Rectangle[] { tankLoc.TankBody };

                DistanceCalculator distanceCalculator = new DistanceCalculator();
                distanceCalculator.Walls = GameState.Walls;
                distanceCalculator.TankOuterEdgeMatrix = TankOuterEdgeMatrix;
                distanceCalculator.CellMatrix          = turnCalcCache.CellMatrix;
                distanceCalculator.TabooAreas          = tabooAreas;

                MobileState tankState = GameState.GetMobileState(tankIndex);

                distanceMatricesFromTankByTankIndex[tankIndex]
                    = distanceCalculator.CalculateShortestDistancesFromTank(ref tankState);
            }
            return(distanceMatricesFromTankByTankIndex[tankIndex]);
        }
        public DirectionalMatrix <DistanceCalculation> GetIncomingDistanceMatrixForBase(int playerIndex)
        {
            if (incomingDistanceMatricesByBase == null)
            {
                incomingDistanceMatricesByBase = new DirectionalMatrix <DistanceCalculation> [Constants.PLAYERS_PER_GAME];
            }
            DirectionalMatrix <DistanceCalculation> incomingDistanceMatrix = incomingDistanceMatricesByBase[playerIndex];

            if (incomingDistanceMatrix == null)
            {
                Base @base    = Game.Current.Players[playerIndex].Base;
                Base @ownBase = Game.Current.Players[1 - playerIndex].Base;
                TurnCalculationCache turnCalcCache = TurnCalculationCache;
                Cell baseCell = turnCalcCache.CellMatrix[@base.Pos];
                AttackTargetDistanceCalculator attackCalculator = new AttackTargetDistanceCalculator(
                    ElementType.BASE, FiringLinesForPointsMatrix, this, turnCalcCache);
                // Don't move over your own base:
                TankLocation tankLoc = turnCalcCache.TankLocationMatrix[@ownBase.Pos];
                attackCalculator.TabooAreas = new Rectangle[] { tankLoc.TankHalo };
                incomingDistanceMatrix
                    = attackCalculator.CalculateMatrixOfShortestDistancesToTargetCell(baseCell);
                incomingDistanceMatricesByBase[playerIndex] = incomingDistanceMatrix;
            }
            return(incomingDistanceMatrix);
        }
Beispiel #4
0
        private void AddNodesToQueueForMovingOverTarget(
            DirectionalMatrix <DistanceCalculation> attackMatrix,
            TwoValuedCircularBuffer <Node> bfsQueue, TankLocation tankLocationAtTargetPoint)
        {
            // Give a distance of zero for internal points, but don't add them to the queue:
            DistanceCalculation zeroDistCalc = new DistanceCalculation(0, new Node());

            foreach (Point interiorPoint in tankLocationAtTargetPoint.TankBody.GetPoints())
            {
                foreach (Direction dir in BoardHelper.AllRealDirections)
                {
                    attackMatrix[dir, interiorPoint] = zeroDistCalc;
                }
            }

            // Calculate the tank positions that will destroy the base or bullet via movement from various directions:
            foreach (Direction movementDir in MovementDirections)
            {
                Direction oppositeDir = movementDir.GetOpposite();

                // Get the inside edge (segment) of the tank centred at the base in the opposite direction:
                Segment tankPositionsInDirection = tankLocationAtTargetPoint.InsideEdgesByDirection[(int)oppositeDir];

                // For each point on the segment add it to the bfs queue with a distance of zero:
                foreach (Cell tankCellInDir in tankPositionsInDirection.Cells)
                {
                    if (tankCellInDir.IsValid)
                    {
                        Node node = new Node(ActionType.Moving, movementDir, tankCellInDir.Position);
                        bfsQueue.Add(node, 0);
                    }
                }
            }
        }
Beispiel #5
0
        public DirectionalMatrix <DistanceCalculation> GetCustomDistanceMatrixFromTank(
            int playerIndex, int tankNumber, int ticksWithoutFiring, Rectangle restrictedBoardArea)
        {
            Tank tank = Game.Current.Players[playerIndex].Tanks[tankNumber];
            TurnCalculationCache turnCalcCache = Game.Current.Turns[GameState.Tick].CalculationCache;

            // Don't ride over your own base!
            Base         @base   = tank.Player.Base;
            TankLocation tankLoc = turnCalcCache.TankLocationMatrix[@base.Pos];

            Rectangle[] tabooAreas = new Rectangle[] { tankLoc.TankBody };

            DistanceCalculator distanceCalculator = new DistanceCalculator();

            distanceCalculator.Walls = GameState.Walls;
            distanceCalculator.TankOuterEdgeMatrix    = GameState.CalculationCache.TankOuterEdgeMatrix;
            distanceCalculator.CellMatrix             = turnCalcCache.CellMatrix;
            distanceCalculator.TabooAreas             = tabooAreas;
            distanceCalculator.TicksWithoutFiring     = ticksWithoutFiring;
            distanceCalculator.RestrictedMovementArea = restrictedBoardArea;
            MobileState tankState = GameState.GetMobileState(tank.Index);
            DirectionalMatrix <DistanceCalculation> distanceMatrix
                = distanceCalculator.CalculateShortestDistancesFromTank(ref tankState);

            return(distanceMatrix);
        }
Beispiel #6
0
 //TextBox GravTest = new TextBox();
 private void CreateControls()
 {
     this.FormBorderStyle      = FormBorderStyle.FixedSingle;
     this.MaximizeBox          = false;
     AimingContainer.Location  = new Point(0, 0);
     AimingContainer.Height    = this.Height;
     AimingContainer.Width     = this.Width;
     AimingContainer.BackColor = Color.Lime;
     AimingContainer.Image     = new Bitmap(AimingContainer.Width, AimingContainer.Height);
     TankLocation.Image        = new Bitmap(7, 7);
     Graphics.FromImage(TankLocation.Image).FillEllipse(Brushes.Orange, 0, 0, 7, 7);
     TankLocation.Refresh();
     VariationCB.Location = new Point(10, 50);
     VariationCB.Items.Add("Default/Single Shot");
     VariationCB.Items.Add("Three-Ball");
     VariationCB.Items.Add("Five-Ball");
     VariationCB.Items.Add("Rainbow");
     VariationCB.Items.Add("Yin Yang");
     VariationCB.Items.Add("Counter 3000");
     //VariationCB.Items.Add("Gravies"); //Broken pls ignore
     VariationCB.Items.Add("Payload");
     VariationCB.SelectedIndex = 0;
     VariationCB.DropDownStyle = ComboBoxStyle.DropDownList;
     TMCB.Location             = new Point(10, 10);
     TMCB.Size       = new Size(VariationCB.Width, 20);
     TMCB.Padding    = new Padding(5, 0, 0, 0);
     TMCB.Checked    = true;
     TMCB.Text       = "Top Most Window";
     TMCB.Click     += TopMostClick;
     AutoCB.Location = new Point(10, 30);
     AutoCB.Size     = new Size(VariationCB.Width, 20);
     AutoCB.Padding  = new Padding(5, 0, 0, 0);
     AutoCB.Checked  = true;
     AutoCB.Text     = "Auto Position";
     AutoCB.Click   += AutoCbClick;
     //VariationCB.SelectedIndexChanged += DrawTracer;
     OffsetDict.Add("Power", 0x20);
     OffsetDict.Add("Angle", 0x1c);
     TracerCheckTimer.Tick    += TracerCheck_Tick;
     TracerCheckTimer.Interval = 10;
     TracerCheckTimer.Enabled  = true;
     SignatureTimer.Tick      += SignatureTimer_Tick;
     SignatureTimer.Interval   = 1000;
     SignatureTimer.Enabled    = true;
     //GravTest.Location = new Point(500, 500);
     //GravTest.Text = "1.48";
     this.Text = "SSL-Steam";
     //this.Controls.Add(GravTest);
     this.Controls.Add(VariationCB);
     this.Controls.Add(TMCB);
     this.Controls.Add(AutoCB);
     this.Controls.Add(AimingContainer);
     this.TopMost = true;
     Log.Add("Controls loaded");
 }
Beispiel #7
0
        public static DirectionalMatrix <TankState> CalculateTankStateMatrix(Matrix <TankLocation> locationMatrix)
        {
            DirectionalMatrix <TankState> tankStateMatrix
                = new DirectionalMatrix <TankState>(
                      locationMatrix.TopLeft, locationMatrix.Width, locationMatrix.Height);

            for (int x = locationMatrix.TopLeft.X; x <= locationMatrix.BottomRight.X; x++)
            {
                for (int y = locationMatrix.TopLeft.Y; y <= locationMatrix.BottomRight.Y; y++)
                {
                    TankLocation tankLoc = locationMatrix[x, y];
                    foreach (Direction dir in BoardHelper.AllRealDirections)
                    {
                        tankStateMatrix[dir, x, y] = tankLoc.TankStatesByDirection[(int)dir];
                    }
                }
            }

            return(tankStateMatrix);
        }
Beispiel #8
0
        private void AddBulletSurvivalTactic(Move move, int ticksUntil_p_i_CanFireAgain, int ticksToEscape,
                                             List <BulletSurvivalTactic> bulletTactics, MobileState survivalState,
                                             int tickOffsetWhenTankCanFireAgain, bool isConfrontingBullet)
        {
            Cell survivalCell = TurnCalculationCache.CellMatrix[survivalState.Pos];

            if (!survivalCell.IsValid)
            {
                return;
            }

            TankLocation tankLocAtSurvivalState = TurnCalculationCache.TankLocationMatrix[survivalState.Pos];

            if (!tankLocAtSurvivalState.IsValid)
            {
                return;
            }

            int distanceToSurvivalState;

            TankAction[] tankActions;
            if (ticksUntil_p_i_CanFireAgain > 0)
            {
                Rectangle tankBody                  = TurnCalculationCache.TankLocationMatrix[survivalState.Pos].TankBody;
                int       expansionFactor           = Math.Min(DEFAULT_EXPANSION, ticksToEscape);
                Rectangle restrictedCalculationArea = tankBody.Merge(survivalState.Pos.ToPointRectangle()).Expand(expansionFactor);
                DirectionalMatrix <DistanceCalculation> customDistanceMatrix = base.GetCustomDistanceMatrixFromTank(
                    move.p, move.i, ticksUntil_p_i_CanFireAgain + 1, restrictedCalculationArea);
                distanceToSurvivalState = customDistanceMatrix[survivalState].Distance;
                tankActions             = GetTankActionsToMoveToPointUsingCustomDistanceMatrix(
                    customDistanceMatrix, survivalState.Dir, survivalState.Pos);
            }
            else
            {
                distanceToSurvivalState = GetDistanceFromTankToPoint(move.p, move.i, survivalState);
                tankActions             = GetTankActionsToMoveToPoint(move.p, move.i, survivalState.Dir, survivalState.Pos);
            }
            if (distanceToSurvivalState != Constants.UNREACHABLE_DISTANCE)
            {
                TankAction initialTankAction
                    = tankActions != null && tankActions.Length > 0
                    ? tankActions[0]
                    : (isConfrontingBullet && ticksToEscape <= 1
                        ? TankAction.FIRE // Assume we are already in position to shoot the bullet
                        : TankAction.NONE
                       );
                int escapeSlack
                    = distanceToSurvivalState - ticksToEscape
                      + (isConfrontingBullet ? 1 : 0); // if confronting, then an extra turn is required to fire
                double escapeValue = ScenarioValueFunctions.DodgeBulletFunction.Evaluate(escapeSlack);
                int    disarmamentSlack
                    = isConfrontingBullet || escapeSlack > 0
                    ? -ticksToEscape
                    : 1 - tickOffsetWhenTankCanFireAgain;
                double disarmamentValue = ScenarioValueFunctions.ProlongEnemyDisarmamentFunction.Evaluate(disarmamentSlack);
                double combinedValue    = escapeValue + disarmamentValue;

                bulletTactics.Add(
                    new BulletSurvivalTactic
                {
                    TargetState       = survivalState,
                    TicksToEscape     = ticksToEscape,
                    Slack             = escapeSlack,
                    InitialTankAction = initialTankAction,
                    Value             = combinedValue
                });
            }
        }
        private static void DoBulletPathCalculations(GameState gameState,
                                                     TurnCalculationCache turnCalcCache, List <BulletPathCalculation> bulletPathCalculations,
                                                     int bulletIndex, MobileState bulletState)
        {
            BulletPathCalculation bulletPathCalc = new BulletPathCalculation
            {
                Bullet      = Game.Current.Elements[bulletIndex] as Bullet,
                BulletState = bulletState
            };

            bulletPathCalculations.Add(bulletPathCalc);

            BulletPathPoint[] bulletPathPoints = new BulletPathPoint[500];
            int bulletPathPointCount           = 0;

            Cell         cell = turnCalcCache.CellMatrix[bulletState.Pos];
            Line <Point> pointsInBulletPath = cell.LineFromCellToEdgeOfBoardByDirection[(int)bulletState.Dir];
            int          tickOffset         = 0;

            for (int i = 1; i < pointsInBulletPath.Length; i++)
            {
                Point bulletPoint     = pointsInBulletPath[i];
                Cell  bulletPointCell = turnCalcCache.CellMatrix[bulletPoint];
                if (!bulletPointCell.IsValid)
                {
                    break;
                }

                if (gameState.Walls[bulletPoint])
                {
                    bulletPathCalc.TicksTillBulletDestroyed = tickOffset;
                    break;
                }
                TankLocation tankLoc    = turnCalcCache.TankLocationMatrix[bulletPoint];
                Rectangle    dangerArea = tankLoc.TankBody;

                BulletPathPoint bulletPathPoint = new BulletPathPoint
                {
                    BulletPoint   = pointsInBulletPath[i],
                    DangerArea    = dangerArea,
                    Tick          = gameState.Tick + tickOffset,
                    TicksToEscape = tickOffset,
                    MovementPhase = (i - 1) % 2
                };
                bulletPathPoints[bulletPathPointCount] = bulletPathPoint;
                bulletPathPointCount++;
                for (int p = 0; p < Constants.PLAYERS_PER_GAME; p++)
                {
                    Base @base = Game.Current.Players[p].Base;
                    if (bulletPoint == @base.Pos)
                    {
                        bulletPathCalc.BaseThreatened           = @base;
                        bulletPathCalc.TicksTillBulletDestroyed = tickOffset;
                    }
                }
                if (i % 2 == 0)
                {
                    tickOffset++;
                }
            }

            Array.Resize(ref bulletPathPoints, bulletPathPointCount);
            bulletPathCalc.BulletPathPoints = bulletPathPoints;
        }
        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);
        }
Beispiel #11
0
        public static Matrix <TankLocation> Calculate(Turn turn, BitMatrix board, Matrix <Cell> cellMatrix,
                                                      Matrix <Segment> verticalMovementSegmentMatrix, Matrix <Segment> horizontalMovementSegmentMatrix)
        {
            Matrix <TankLocation> tankLocationMatrix = new Matrix <TankLocation>(board.Width, board.Height);

            for (int x = board.TopLeft.X; x <= board.BottomRight.X; x++)
            {
                for (int y = board.TopLeft.Y; y <= board.BottomRight.Y; y++)
                {
                    TankLocation tankLoc = new TankLocation();
                    tankLocationMatrix[x, y] = tankLoc;

                    tankLoc.CentreCell = cellMatrix[x, y];

                    int leftInnerEdgeX   = x - Constants.TANK_EXTENT_OFFSET;
                    int rightInnerEdgeX  = x + Constants.TANK_EXTENT_OFFSET;
                    int topInnerEdgeY    = y - Constants.TANK_EXTENT_OFFSET;
                    int bottomInnerEdgeY = y + Constants.TANK_EXTENT_OFFSET;

                    tankLoc.IsValid
                        = (leftInnerEdgeX >= turn.LeftBoundary) && (rightInnerEdgeX <= turn.RightBoundary) &&
                          (topInnerEdgeY >= 0) && (bottomInnerEdgeY < board.Height);

                    tankLoc.TankBody = new Rectangle(
                        (short)leftInnerEdgeX,
                        (short)topInnerEdgeY,
                        (short)rightInnerEdgeX,
                        (short)bottomInnerEdgeY);

                    tankLoc.InteriorOfTankBody = new Rectangle(
                        (short)(leftInnerEdgeX + 1),
                        (short)(topInnerEdgeY + 1),
                        (short)(rightInnerEdgeX - 1),
                        (short)(bottomInnerEdgeY - 1));

                    // Calculate inside edges:
                    tankLoc.InsideEdgesByDirection[(int)Direction.UP]    = verticalMovementSegmentMatrix[x, topInnerEdgeY];
                    tankLoc.InsideEdgesByDirection[(int)Direction.DOWN]  = verticalMovementSegmentMatrix[x, bottomInnerEdgeY];
                    tankLoc.InsideEdgesByDirection[(int)Direction.LEFT]  = horizontalMovementSegmentMatrix[leftInnerEdgeX, y];
                    tankLoc.InsideEdgesByDirection[(int)Direction.RIGHT] = horizontalMovementSegmentMatrix[rightInnerEdgeX, y];

                    // Calculate outside edges:
                    int leftOuterEdgeX   = leftInnerEdgeX - 1;
                    int rightOuterEdgeX  = rightInnerEdgeX + 1;
                    int topOuterEdgeY    = topInnerEdgeY - 1;
                    int bottomOuterEdgeY = bottomInnerEdgeY + 1;

                    tankLoc.OutsideEdgesByDirection[(int)Direction.UP]    = verticalMovementSegmentMatrix[x, topOuterEdgeY];
                    tankLoc.OutsideEdgesByDirection[(int)Direction.DOWN]  = verticalMovementSegmentMatrix[x, bottomOuterEdgeY];
                    tankLoc.OutsideEdgesByDirection[(int)Direction.LEFT]  = horizontalMovementSegmentMatrix[leftOuterEdgeX, y];
                    tankLoc.OutsideEdgesByDirection[(int)Direction.RIGHT] = horizontalMovementSegmentMatrix[rightOuterEdgeX, y];

                    tankLoc.TankHalo = new Rectangle(
                        (short)leftOuterEdgeX,
                        (short)topOuterEdgeY,
                        (short)rightOuterEdgeX,
                        (short)bottomOuterEdgeY);

                    // Calculate TankStates in each direction:
                    foreach (Direction dir in BoardHelper.AllRealDirections)
                    {
                        TankState tankState = new TankState
                        {
                            Dir      = dir,
                            Location = tankLoc
                        };
                        tankState.OutsideLeadingEdge = tankLoc.OutsideEdgesByDirection[(int)dir];
                        tankState.InsideTrailingEdge = tankLoc.InsideEdgesByDirection[(int)dir.GetOpposite()];

                        tankLoc.TankStatesByDirection[(int)dir] = tankState;

                        // Calculate
                        foreach (EdgeOffset edgeOffset in TankHelper.EdgeOffsets)
                        {
                            Point edgePoint = TankHelper.GetPointOnTankEdge(tankLoc.CentreCell.Position, dir, edgeOffset);
                            tankLoc.CellsOnEdgeByDirectionAndEdgeOffset[(int)dir, (int)edgeOffset] = cellMatrix[edgePoint];
                        }
                    }
                }
            }

            return(tankLocationMatrix);
        }
Beispiel #12
0
        public DirectionalMatrix <DistanceCalculation> CalculateMatrixOfShortestDistancesToTargetCell(Cell target)
        {
            if (MovementDirections == null)
            {
                MovementDirections = BoardHelper.AllRealDirections;
            }
            if (EdgeOffsets == null)
            {
                EdgeOffsets = FiringLineMatrix.EdgeOffsets;
            }

            BitMatrix walls = GameStateCalculationCache.GameState.Walls;
            DirectionalMatrix <DistanceCalculation> attackMatrix
                = new DirectionalMatrix <DistanceCalculation>(walls.Width, walls.Height);
            TwoValuedCircularBuffer <Node> bfsQueue = new TwoValuedCircularBuffer <Node>(CircularBufferCapacityRequired);

            // Note: the target point will not have a distance, since it will be shot, not moved to:
            TankLocation tankLocationAtTargetPoint = TurnCalculationCache.TankLocationMatrix[target.Position];

            if ((TargetElementType == ElementType.BASE) || (TargetElementType == ElementType.BULLET && AllowDestroyingABulletByMovingIntoIt))
            {
                AddNodesToQueueForMovingOverTarget(attackMatrix, bfsQueue, tankLocationAtTargetPoint);
            }
            else
            if (TargetElementType == ElementType.TANK)
            {
                // TODO: Make points taboo if they would lead to both tank bodies overlapping, or moving directly in front of an enemy tank

                /* TODO: a. Is this needed? Won't it break some algorithms?
                 *       b. Check that points are in the board area
                 * foreach (Point point in tankLocationAtTargetPoint.TankHalo.GetPoints())
                 * {
                 *  foreach (Direction dir in BoardHelper.AllRealDirections)
                 *  {
                 *      attackMatrix[dir, point.X, point.Y] = new DistanceCalculation(-1, new Node());
                 *  }
                 * }
                 */
            }
            else
            {
                // Bullet that can't be moved into...

                // TODO: Make points taboo within the "outline" of a tank body centred on the bullet
            }

            if (TabooAreas != null)
            {
                foreach (Rectangle rect in TabooAreas)
                {
                    foreach (Point point in rect.GetPoints())
                    {
                        foreach (Direction dir in BoardHelper.AllRealDirections)
                        {
                            if (TurnCalculationCache.CellMatrix[point].IsValid)
                            {
                                attackMatrix[dir, point.X, point.Y] = new DistanceCalculation(TABOO_DISTANCE, new Node());
                            }
                        }
                    }
                }
            }

            // Use the firing distance calculations for the target point, and add the initial points to the BFS queue:
            FiringLineSummary[,] firingLineSummariesByMovementDirAndEdgeOffset
                = new FiringLineSummary[Constants.RELEVANT_DIRECTION_COUNT, Constants.EDGE_OFFSET_COUNT];
            bool areFiringLinesStillActive = TryInitializeFiringLinesAndAddInitialFiringLineNodesToQueue(
                target, bfsQueue, firingLineSummariesByMovementDirAndEdgeOffset);

            int currDistance = 0;

            while (true)
            {
                if (areFiringLinesStillActive && bfsQueue.Size == 0)
                {
                    // Get nodes from the firing line/s with the next shortest distance:
                    areFiringLinesStillActive = TryAddFiringLineNodesWithNextShortestDistance(
                        bfsQueue, firingLineSummariesByMovementDirAndEdgeOffset, out currDistance);
                }
                if (bfsQueue.Size == 0)
                {
                    break;
                }

                CircularBufferItem <Node> bufferItem = bfsQueue.Remove();
                Node currNode = bufferItem.Item;
                if (bufferItem.Value > currDistance)
                {
                    if (bufferItem.Value == Constants.UNREACHABLE_DISTANCE)
                    {
                        break;
                    }

                    currDistance = bufferItem.Value;

                    // Add firing line nodes with the new distance to the queue:
                    if (areFiringLinesStillActive)
                    {
                        areFiringLinesStillActive = TryAddNextFiringLineNodesToQueue(bfsQueue,
                                                                                     firingLineSummariesByMovementDirAndEdgeOffset, currDistance);
                    }
                }

                int adjDistance = currDistance + 1;

                // Get each node adjacent to the current node:
                SegmentState   innerEdgeStateInNodeDir = GameStateCalculationCache.TankInnerEdgeMatrix[currNode.X, currNode.Y][(int)currNode.Dir];
                SegmentState[] outerEdgeStates         = GameStateCalculationCache.TankOuterEdgeMatrix[currNode.X, currNode.Y];
                SegmentState   outerEdgeStateInNodeDir = outerEdgeStates[(int)currNode.Dir];

#if CONDITIONAL_BREAKPOINT_AttackTargetDistanceCalculator_CalculateMatrixOfShortestDistancesToTargetCell
                System.Diagnostics.Debug.Assert(currNode.X != 39 || currNode.Y != 45 || currNode.Dir != Direction.UP, "Breakpoint");
#endif

                // Node[] adjacentNodes = currNode.GetAdjacentIncomingNodes(innerEdgeStateInNodeDir, outerEdgeStateInNodeDir, outerEdgeStateInOppositeDir);
                // Insert inline for better performance...
                // **********************

                Node[] adjacentNodes     = new Node[MAX_POSSIBLE_PRECEDING_NODE_COUNT];
                byte   adjacentNodeCount = 0;

                if (currNode.ActionType == ActionType.FiringLine || currNode.ActionType == ActionType.Firing)
                {
                    // Firing and/or the firing line can only be invoked if the tank is first facing in the correct direction:
                    Node positioningNode = new Node(ActionType.Moving, currNode.Dir /*movementDir*/, currNode.X, currNode.Y);
                    adjacentNodes[0]  = positioningNode;
                    adjacentNodeCount = 1;
                }
                else
                {
                    // So now the destination node (this) must be a moving node in the given direction...
                    Node adjacentNode;

                    // If there is a blocking wall in its direction of movement,
                    // then any of the other movement/positioning nodes on the same cell
                    // can be a preceding node on the path:
                    if (outerEdgeStateInNodeDir == SegmentState.ShootableWall || outerEdgeStateInNodeDir == SegmentState.UnshootablePartialWall)
                    {
                        foreach (Direction otherDir in BoardHelper.AllRealDirections)
                        {
                            if (otherDir != currNode.Dir)
                            {
                                adjacentNode = new Node(ActionType.Moving, otherDir, currNode.X, currNode.Y);
                                adjacentNodes[adjacentNodeCount] = adjacentNode;
                                adjacentNodeCount++;
                            }
                        }
                    }

                    // Ignore invalid prior cells:
                    if (outerEdgeStates[(int)(currNode.Dir.GetOpposite())] != SegmentState.OutOfBounds)
                    {
                        // Get the adjacent cell's position:
                        int newX = currNode.X;
                        int newY = currNode.Y;
                        switch (currNode.Dir)
                        {
                        case Direction.UP:
                            newY++;
                            break;

                        case Direction.DOWN:
                            newY--;
                            break;

                        case Direction.LEFT:
                            newX++;
                            break;

                        case Direction.RIGHT:
                            newX--;
                            break;
                        }

                        switch (innerEdgeStateInNodeDir)
                        {
                        case SegmentState.Clear:
                            // Add all 4 directions on the adjacent cell
                            foreach (Direction otherDir in BoardHelper.AllRealDirections)
                            {
                                adjacentNode = new Node(ActionType.Moving, otherDir, newX, newY);
                                adjacentNodes[adjacentNodeCount] = adjacentNode;
                                adjacentNodeCount++;
                            }
                            break;

                        case SegmentState.ShootableWall:
                            // Add the firing node in the current direction on the adjacent cell:
                            adjacentNode = new Node(ActionType.Firing, currNode.Dir, newX, newY);
                            adjacentNodes[adjacentNodeCount] = adjacentNode;
                            adjacentNodeCount++;
                            break;
                        }
                    }
                }

                // **********************
                // End of inlined section

                for (int n = 0; n < adjacentNodeCount; n++)
                {
                    Node adj = adjacentNodes[n];

                    // Note: adj will never be a firing line node, as they are not incoming nodes for any other node type.

                    if (adj.ActionType == ActionType.Moving)
                    {
                        // Check if the node already has a distance i.e. has it already been expanded?
                        if (attackMatrix[adj.Dir, adj.X, adj.Y].CodedDistance == 0)
                        {
                            // Set the new shortest distance:
                            attackMatrix[adj.Dir, adj.X, adj.Y] = new DistanceCalculation(adjDistance, currNode);

                            // Add to the queue to be expanded:
                            bfsQueue.Add(adj, adjDistance);
                        }
                    }
                    else
                    {
                        // It would be useful to have the firing nodes as well, as a tank could currently be firing.
                        // However this will double the storage requirements.
                        // Rather just do quick calculations to estimate the distance.
                        // For example:
                        // 1. Get the shortest distance from the current node.
                        // 2. If firing is on the shortest distance from the current node, then subtract 1 from the distance (since it's already firing).
                        // 3. If it's not on the shortest distance (but it still might be a shortest path, due to multiple equal paths),
                        //    then get one plus the shortest distance from the adjacent node in the firing direction.
                        //    a. If this is less than the shortest distance, use it as the shortest distance.
                        //    b. Otherwise use the shortest distance path instead (don't make use of the space fired at).
                        bfsQueue.Add(adj, adjDistance);
                    }
                }
            }
            return(attackMatrix);
        }
        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;
        }