/// <summary> /// Gets a random ball direction which does not end up in a hole within the first direction. /// If no valid direction is found within the defined calculation steps, the last calculated direction is returned. /// </summary> /// <param name="window">The window.</param> /// <param name="ballPosition">The ball position.</param> /// <param name="maxCalculationSteps">The maximum calculation steps.</param> /// <returns> /// The direction /// </returns> public static Vector GetRandomBallDirection(Window window, Point ballPosition, int maxCalculationSteps = 2000) { if (window == null) { throw new ArgumentNullException("window"); } var directionsCalculated = 0; var random = new Random(DateTime.Now.GetHashCode()); while (true) { var clickPosition = new Point( random.Next(0, GameConfiguration.GameWindowWidth), random.Next(0, GameConfiguration.GameWindowHeight)); // Set the direction and negate it, because the ball has to move away from the queue var direction = new Vector(clickPosition.X, clickPosition.Y) - new Vector(ballPosition.X, ballPosition.Y); direction.Negate(); directionsCalculated++; // The direction is valid if there are no holes in the window if (!window.Holes.Any()) { return(direction); } // Create a fake point outside of the window in the ball direction var intersectionTestPoint = ballPosition + (direction * 10); var intersectionFound = false; // Check for an intersection between the ball and a hole in the set direction foreach (var hole in window.Holes) { Point?firstIntersection; Point?secondIntersection; var intersection = CalculationHelpers.CalculateLineSphereIntersection( hole.CenterPosition, hole.Radius, ballPosition, intersectionTestPoint, out firstIntersection, out secondIntersection); // Cancel the check because we have an intersection if (intersection > 0) { Tracer.Debug(string.Format("GameHelpers :: GetRandomBallDirection :: Found an intersection between click position {0}, ball position {1}, direction {2} and hole position {3}", clickPosition, ballPosition, direction, hole.CenterPosition)); intersectionFound = true; break; } } // Return the current direction if there was no valid direction within the defined tries if (directionsCalculated == maxCalculationSteps) { Tracer.Debug(string.Format("GameHelpers :: GetRandomBallDirection :: Returning direction {0} because the amount of tries is reached", direction)); return(direction); } // If we have a intersection, we need another try to find a valid direction if (intersectionFound) { continue; } Tracer.Debug(string.Format("GameHelpers :: GetRandomBallDirection :: Returning direction {0} after {1} tries", direction, directionsCalculated)); return(direction); } }
/// <summary> /// Calculates the ball animation tasks. /// </summary> /// <param name="initialWindow">The initial window.</param> /// <param name="startPosition">The start position.</param> /// <param name="initialDirection">The initial direction.</param> /// <returns>The calculated animation queue</returns> private Queue <BallAnimationTask> CalculateBallAnimationTasks(Models.Window initialWindow, Point startPosition, Vector initialDirection) { var animationQueue = new Queue <BallAnimationTask>(); var isLastAnimation = false; var currentWindow = initialWindow; var currentBallPosition = startPosition; var currentDirection = initialDirection; var stepCounter = 0; var currentTask = new BallAnimationTask { Window = currentWindow }; var previousIntersection = Intersection.None; var windowPreviouslyChanged = false; const double BallRadius = GameConfiguration.BallDiameter / 2; while (!isLastAnimation) { // Ensure an end of the animation stepCounter++; if (stepCounter == 30) { isLastAnimation = true; currentTask.IsLastAnimation = true; currentTask.IntersectsWithHole = false; } var borderPositionCorrection = new Point(); // Calculate the ignorable intersection var ignoredIntersection = Intersection.None; if (previousIntersection != Intersection.None) { if (windowPreviouslyChanged) { switch (previousIntersection) { case Intersection.TopWall: ignoredIntersection = Intersection.BottomWall; break; case Intersection.LeftWall: ignoredIntersection = Intersection.RightWall; break; case Intersection.RightWall: ignoredIntersection = Intersection.LeftWall; break; case Intersection.BottomWall: ignoredIntersection = Intersection.TopWall; break; } } else { ignoredIntersection = previousIntersection; } } var currentIntersection = Intersection.None; Point? intersectionPosition = null; Models.Window neighbourWindow = null; // Generate a point outside of the window var intersectionTestPoint = currentBallPosition + (currentDirection * 1000); // Check for hole intersection if (currentWindow.Holes != null) { foreach (var hole in currentWindow.Holes) { Point?firstIntersection; Point?secondIntersection; var intersection = CalculationHelpers.CalculateLineSphereIntersection( hole.CenterPosition, hole.Radius, currentBallPosition, intersectionTestPoint, out firstIntersection, out secondIntersection); if (intersection > 0 && firstIntersection != null) { currentIntersection = Intersection.Hole; // This hole intersection is only relevant, if the distance between this intersection and the ball is the shortest if (intersectionPosition == null || currentBallPosition.DistanceTo((Point)firstIntersection) < currentBallPosition.DistanceTo((Point)intersectionPosition)) { intersectionPosition = firstIntersection; } } } } // Check for top wall hit if (intersectionPosition == null && ignoredIntersection != Intersection.TopWall) { intersectionPosition = CalculationHelpers.GetLineIntersection( currentBallPosition, intersectionTestPoint, new Point(0, 0), new Point(GameConfiguration.GameWindowWidth, 0)); if (intersectionPosition != null) { previousIntersection = currentIntersection = Intersection.TopWall; var upperWindow = this.CurrentGame.Map.Windows.FirstOrDefault(w => w.X == currentWindow.X && w.Y == currentWindow.Y - 1); if (upperWindow != null) { neighbourWindow = upperWindow; } else { borderPositionCorrection.Y += BallRadius; } } } if (intersectionPosition == null && ignoredIntersection != Intersection.LeftWall) { // Check for left wall hit intersectionPosition = CalculationHelpers.GetLineIntersection( currentBallPosition, intersectionTestPoint, new Point(0, 0), new Point(0, GameConfiguration.GameWindowWidth)); if (intersectionPosition != null) { previousIntersection = currentIntersection = Intersection.LeftWall; var leftWindow = this.CurrentGame.Map.Windows.FirstOrDefault(w => w.X == currentWindow.X - 1 && w.Y == currentWindow.Y); if (leftWindow != null) { neighbourWindow = leftWindow; } else { borderPositionCorrection.X += BallRadius; } } } if (intersectionPosition == null && ignoredIntersection != Intersection.RightWall) { // Check for right wall hit intersectionPosition = CalculationHelpers.GetLineIntersection( currentBallPosition, intersectionTestPoint, new Point(GameConfiguration.GameWindowWidth, 0), new Point(GameConfiguration.GameWindowWidth, GameConfiguration.GameWindowWidth)); if (intersectionPosition != null) { previousIntersection = currentIntersection = Intersection.RightWall; var rightWindow = this.CurrentGame.Map.Windows.FirstOrDefault(w => w.X == currentWindow.X + 1 && w.Y == currentWindow.Y); if (rightWindow != null) { neighbourWindow = rightWindow; } else { borderPositionCorrection.X -= BallRadius; } } } if (intersectionPosition == null && ignoredIntersection != Intersection.BottomWall) { // Check for bottom wall hit intersectionPosition = CalculationHelpers.GetLineIntersection( currentBallPosition, intersectionTestPoint, new Point(0, GameConfiguration.GameWindowWidth), new Point(GameConfiguration.GameWindowWidth, GameConfiguration.GameWindowWidth)); if (intersectionPosition != null) { previousIntersection = currentIntersection = Intersection.BottomWall; var bottomWindow = this.CurrentGame.Map.Windows.FirstOrDefault(w => w.X == currentWindow.X && w.Y == currentWindow.Y + 1); if (bottomWindow != null) { neighbourWindow = bottomWindow; } else { borderPositionCorrection.Y -= BallRadius; } } } if (currentIntersection != Intersection.None && intersectionPosition != null) { var newPosition = (Point)intersectionPosition; // Apply correction newPosition.X += borderPositionCorrection.X; newPosition.Y += borderPositionCorrection.Y; currentTask.Steps.Add(AnimationHelpers.GetPointAnimation(currentBallPosition, newPosition)); // If the ball intersects a hole, end the calculation after this step if (currentIntersection == Intersection.Hole) { currentTask.IsLastAnimation = true; currentTask.IntersectsWithHole = true; animationQueue.Enqueue(currentTask); return(animationQueue); } currentBallPosition = newPosition; // Ball will leave the window if (neighbourWindow != null) { animationQueue.Enqueue(currentTask); currentWindow = neighbourWindow; currentTask = new BallAnimationTask { Window = currentWindow }; windowPreviouslyChanged = true; // Set new initial position for the next window switch (currentIntersection) { case Intersection.TopWall: currentBallPosition.Y = GameConfiguration.GameWindowWidth; break; case Intersection.LeftWall: currentBallPosition.X = GameConfiguration.GameWindowWidth; break; case Intersection.RightWall: currentBallPosition.X = 0; break; case Intersection.BottomWall: currentBallPosition.Y = 0; break; } } else { // Change ball direction within the current window if (currentIntersection == Intersection.TopWall || currentIntersection == Intersection.BottomWall) { currentDirection.Y *= -1; } else { currentDirection.X *= -1; } windowPreviouslyChanged = false; } } } // Add the last animation task to the queue because we had no wall hit (timeout) if (currentTask.Steps.Count > 0) { animationQueue.Enqueue(currentTask); } return(animationQueue); }