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); }
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); } } } }
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); }
//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"); }
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); }
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); }
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); }
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; }