using Snake.Core; namespace Snake.Avalonia.Views; /// /// Interpolation helpers for smooth snake animation. /// public static class AnimationHelper { /// /// Ease-out cubic for smooth deceleration (head overshoot feel). /// public static double EaseOutCubic(double t) { t = Math.Clamp(t, 0.0, 1.0); return 1.0 - Math.Pow(1.0 - t, 3); } /// /// Ease-in-out quad for general smooth interpolation. /// public static double EaseInOutQuad(double t) { t = Math.Clamp(t, 0.0, 1.0); return t < 0.5 ? 2.0 * t * t : 1.0 - Math.Pow(-2.0 * t + 2.0, 2) / 2.0; } /// /// Interpolates between two positions with given t value. /// Returns the visual position for rendering. /// public static (double X, double Y) InterpolatePosition( Position from, Position to, double t, double cellSize, bool useEasing = false) { var eased = useEasing ? EaseOutCubic(t) : t; var x = from.X + (to.X - from.X) * eased; var y = from.Y + (to.Y - from.Y) * eased; return (x * cellSize + cellSize * 0.5, y * cellSize + cellSize * 0.5); } /// /// Maps a current segment index to its previous position for interpolation. /// PreviousSegments is the full snapshot before Move(). /// Segments count may differ from PreviousSegments if the snake grew. /// public static Position GetPreviousPosition( int segmentIndex, IReadOnlyList currentSegments, IReadOnlyList previousSegments, Direction direction) { if (previousSegments.Count == 0) return currentSegments[segmentIndex]; // Head (index 0) always interpolates from the old head if (segmentIndex == 0) return previousSegments[0]; // For body segments, the mapping depends on whether snake grew var grew = currentSegments.Count > previousSegments.Count; if (grew) { // When growing: all old segments kept their positions, // new head was added at front. Body segments shifted by 1. // current[i] (i>=1) = old[i-1] var prevIdx = segmentIndex - 1; if (prevIdx < previousSegments.Count) return previousSegments[prevIdx]; } else { // When not growing: tail removed, new head added. // current[0] = new head, current[1] = old head, etc. // current[i] (i>=1) was at old[i-1] var prevIdx = segmentIndex - 1; if (prevIdx < previousSegments.Count) return previousSegments[prevIdx]; } // Fallback: return current position (no interpolation) return currentSegments[segmentIndex]; } /// /// Computes a pulsating radius for food glow effect. /// public static double PulsateRadius(double baseRadius, double elapsedSeconds, double amplitude = 0.15, double frequency = 2.5) { return baseRadius * (1.0 + amplitude * Math.Sin(elapsedSeconds * Math.PI * 2.0 * frequency)); } /// /// Computes opacity for score popup fade-out animation. /// public static double FadeOutOpacity(double elapsedSeconds, double durationSeconds = 1.0) { var t = Math.Clamp(elapsedSeconds / durationSeconds, 0.0, 1.0); return 1.0 - t; } }