/* These template methods are called if ApplyMoves() is called. * * The default implementations here are to update the Direction, X and Y properties of the Unit and Bullet objecdts. * When a tank or bullet is destroyed, the corresponding slot in the Tanks and Bullets arrays is set to null. * * Override these template methods in derived classes to implement the appropriate update logic. */ protected virtual void MoveUnit(MoveRequest request) { request.Tank.X = request.NewPosition.X; request.Tank.Y = request.NewPosition.Y; request.Tank.Direction = request.NewDirection; // NOTE: If on the server, override to do whatever is required to generate unit events to move the tank }
private void CancelMove(int moveRequestIndex) { MoveRequest request = Requests[moveRequestIndex]; request.Status = MoveStatus.Cancelled; // Update dependencies on this tank, based on its move being cancelled: for (int i = 0; i < Requests.Length; i++) { if (i != moveRequestIndex && Requests[i].Status == MoveStatus.Unresolved) { // Determine how tank i depends on this tank: DependencyType depType = DependencyMatrix[i, moveRequestIndex]; switch (depType) { case DependencyType.OnNewLeadingEdge: // Since the move is cancelled, the new leading edge will never occur, so is no longer a dependency: DependencyMatrix[i, moveRequestIndex] = DependencyType.None; break; case DependencyType.OnUnchangedBody: case DependencyType.OnOldTrailingEdge: // Since the tank move has been cancelled, its old trailing edge is now blocking tank i's move: CancelMove(i); break; } } } }
/// <summary> /// This method can be called for applying all the resolved moves. /// To make this truly useful, the protected virtual template methods /// lower down will probably need to be overridden in a derived class. /// </summary> public void ApplyMoves() { for (int i = 0; i < Requests.Length; i++) { MoveRequest request = Requests[i]; switch (request.Status) { case MoveStatus.Approved: MoveUnit(request); break; case MoveStatus.Destroyed: DestroyUnit(i); break; case MoveStatus.Cancelled: if (request.NewDirection != request.Tank.Direction) { // The tank is still changing direction ChangeDirectionOfUnit(request); } else { UnitWillNotMove(request); } break; case MoveStatus.NotMoving: UnitWillNotMove(request); break; } } }
/// <summary> /// Resolve the next move /// </summary> /// <param name="wasAMoveResolved"> /// Returns true once all moves are resolved, or a set of circular dependencies is found which prevents further resolution /// </param> /// <param name="nextMoveOnly"> /// Set to true to only run the next move (useful for stepping through). /// Set to false (the default) to complete a single pass through all the tanks, resolving all that can be resolved in that pass. This is more efficient. /// </param> public void ResolveNextMoves(out bool wasAMoveResolved, bool nextMoveOnly = false) { wasAMoveResolved = false; for (int i = 0; i < Requests.Length; i++) { MoveRequest request = Requests[i]; if (request.Status == MoveStatus.Unresolved) { // A move can be resolved if there are no dependencies on another tank: bool canResolve = true; for (int j = 0; j < Constants.TANK_COUNT; j++) { if (i != j && DependencyMatrix[i, j] != DependencyType.None) { canResolve = false; break; } } if (canResolve) { ApproveMove(i); // Note: This might still lead to a destroyed tank if it moves into a bullet wasAMoveResolved = true; if (nextMoveOnly) { break; } } } } }
/// <summary> /// Derived classes can override DestroyUnit to update their status to indicate that the tank was destroyed. /// They should also remove any bullets which were in the cells of request.NewLeadingEdge. /// They could do this by calling RemoveBulletThatATankMovedInto(MoveRequest). /// </summary> /// <param name="request">The movement request for a particular tank</param> protected virtual void DestroyUnit(int moveRequestIndex) { // Remove bullets: MoveRequest request = Requests[moveRequestIndex]; RemoveBulletsThatATankMovedInto(request); Tanks[moveRequestIndex] = null; // NOTE: If on the server, override to do whatever is required to destroy the tank }
protected virtual void RemoveBulletsThatATankMovedInto(MoveRequest request) { for (int b = 0; b < Bullets.Length; b++) { Bullet bullet = Bullets[b]; if (bullet != null && request.NewLeadingEdge.ContainsPoint(new Point(bullet.X, bullet.Y))) { Bullets[b] = null; } } // NOTE: If on the server, override to do whatever is required to destroy any bullets which the tank moved into }
public bool IsTankMovingIntoABullet(MoveRequest request) { for (int b = 0; b < Bullets.Length; b++) { Bullet bullet = Bullets[b]; if (bullet != null && request.NewLeadingEdge.ContainsPoint(new Point(bullet.X, bullet.Y))) { return(true); } } return(false); }
private void CancelAllUnresolvedMoveRequests() { for (int i = 0; i < Requests.Length; i++) { MoveRequest request = Requests[i]; if (request.Status == MoveStatus.Unresolved) { request.Status = MoveStatus.Cancelled; // This is safer than calling CancelMove() which might have unexpected side-effects } } }
public void GenerateMoveRequests() { for (int i = 0; i < Tanks.Length; i++) { Unit tank = Tanks[i]; MoveRequest request = new MoveRequest(tank); /* Note: The constructor automatically calculates properties such as: * Status, NewLeadingEdge, OldTrailingEdge, UnchangedBody, OldPosition, NewPosition */ Requests[i] = request; } }
private void ApproveMove(int moveRequestIndex) { MoveRequest request = Requests[moveRequestIndex]; if (IsTankMovingIntoABullet(request)) { request.Status = MoveStatus.Destroyed; } else { request.Status = MoveStatus.Approved; } // Update dependencies on this tank, based on its move being approved: for (int i = 0; i < Requests.Length; i++) { if (i != moveRequestIndex && Requests[i].Status == MoveStatus.Unresolved) { // Determine how tank i depends on this tank: DependencyType depType = DependencyMatrix[i, moveRequestIndex]; switch (depType) { case DependencyType.OnNewLeadingEdge: // Since the move is approved, the new leading edge becomes a barrier (in theory this should never happen): CancelMove(i); break; case DependencyType.OnUnchangedBody: if (request.Status == MoveStatus.Destroyed && RuleConfiguration.CanATankMoveIntoTheSpaceLeftByATankThatJustMovedIntoABullet) { // Since the tank was destroyed, and the rules allow moving into its space, remove the dependency that was blocking tank i: DependencyMatrix[i, moveRequestIndex] = DependencyType.None; } else { // Otherwise the tank is blocked by the other tank (which was there first, // since there is a dependency on the unchanged cells in its body), so cancel its move: CancelMove(i); } break; case DependencyType.OnOldTrailingEdge: // Since the tank move has been approved, its old trailing edge is no longer blocking tank i's move: DependencyMatrix[i, moveRequestIndex] = DependencyType.None; break; } } } }
public void CancelTankMovesBlockedByWallsOrOutOfBoundsAreas() { for (int i = 0; i < Requests.Length; i++) { MoveRequest request = Requests[i]; if (request.Status == MoveStatus.Unresolved) { Rectangle nle = request.NewLeadingEdge; // Check if out-of-bounds: if (nle.TopLeft.X < 0 || nle.TopLeft.Y < 0 || nle.BottomRight.X >= BoardWidth || nle.BottomRight.Y >= BoardHeight) { CancelMove(i); continue; } // Check if any of the cells in the new leading edge is a wall: if (nle.GetPoints().Any(pt => IsAWall[pt.X, pt.Y])) { CancelMove(i); } } } }
protected TankMovementResolver() { Bullets = new Bullet[Constants.TANK_COUNT]; DependencyMatrix = new DependencyType[Constants.TANK_COUNT, Constants.TANK_COUNT]; Requests = new MoveRequest[Constants.TANK_COUNT]; }
protected virtual void UnitWillNotMove(MoveRequest request) { // NOTE: If on the server, override to do whatever is required for a tank that is not moving }
protected virtual void ChangeDirectionOfUnit(MoveRequest request) { request.Tank.Direction = request.NewDirection; // NOTE: If on the server, override to do whatever is required to generate unit events to move the tank }
public void GenerateDependencyMatrix() { for (int i = 0; i < Requests.Length; i++) { MoveRequest request = Requests[i]; if (request.Status == MoveStatus.Unresolved) { for (int j = 0; j < Requests.Length; j++) { if (j != i) { MoveRequest otherRequest = Requests[j]; switch (otherRequest.Status) { case MoveStatus.TankDoesNotExist: break; case MoveStatus.NotMoving: case MoveStatus.Cancelled: if (request.NewLeadingEdge.IntersectsWith(otherRequest.UnchangedBody)) { DependencyMatrix[i, j] = DependencyType.OnUnchangedBody; } break; case MoveStatus.Approved: case MoveStatus.Unresolved: if (request.NewLeadingEdge.IntersectsWith(otherRequest.UnchangedBody)) { DependencyMatrix[i, j] = DependencyType.OnUnchangedBody; } else if (request.NewLeadingEdge.IntersectsWith(otherRequest.OldTrailingEdge)) { DependencyMatrix[i, j] = DependencyType.OnOldTrailingEdge; } else if (request.NewLeadingEdge.IntersectsWith(otherRequest.NewLeadingEdge)) { if (otherRequest.NewLeadingEdge.IntersectsWith(request.UnchangedBody)) { /* The other tank is trying to move into space already occupied by this tank. * So there is no dependency on the other tank. */ DependencyMatrix[i, j] = DependencyType.None; } else { // Neither tank is already in the space the other is trying to move to, so neither has preference: DependencyMatrix[i, j] = DependencyType.OnNewLeadingEdge; } } else { DependencyMatrix[i, j] = DependencyType.None; } break; } } } } } }