RiffleShuffleAnimation.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. //-----------------------------------------------------------------------------
  2. // RiffleShuffleAnimation.cs
  3. //
  4. // Implements a classic riffle shuffle animation where the deck is split
  5. // in half and cards cascade together in an interleaving pattern
  6. //-----------------------------------------------------------------------------
  7. using System;
  8. using System.Collections.Generic;
  9. using Microsoft.Xna.Framework;
  10. using Microsoft.Xna.Framework.Graphics;
  11. namespace CardsFramework
  12. {
  13. /// <summary>
  14. /// Riffle shuffle: splits deck into two halves, then interleaves them
  15. /// with a cascading visual effect. This is the classic casino shuffle.
  16. /// </summary>
  17. public class RiffleShuffleAnimation : ShuffleAnimation
  18. {
  19. // Animation timing ratios (must sum to 1.0)
  20. private const float SplitPhaseRatio = 0.2f;
  21. private const float CascadePhaseRatio = 0.6f;
  22. private const float GatherPhaseRatio = 0.2f;
  23. // Card display settings
  24. private const int MaxVisibleCards = 18;
  25. private const int CardDisplayStep = 3; // Show every 3rd card
  26. private const float CardDepthSpacing = 0.5f;
  27. // Cascade animation settings
  28. private const float CascadeArcRatio = 0.2f; // Arc up takes 20% of cascade time
  29. private const float CascadeDownRatio = 0.3f; // Arc down takes 30% of cascade time
  30. private const float CascadeStaggerRatio = 0.7f; // Cards stagger over 70% of cascade phase
  31. // Randomization ranges
  32. private const int FinalPositionRandomX = 8; // ±8 pixels horizontal
  33. private const int FinalPositionRandomY = 3; // ±3 pixels vertical
  34. private const int FinalOffsetX = 10; // Offset between left/right halves
  35. /// <summary>
  36. /// How far apart the two halves split (in pixels)
  37. /// </summary>
  38. public float SplitDistance { get; set; } = 120f;
  39. /// <summary>
  40. /// Maximum rotation angle for cards during cascade (in radians)
  41. /// </summary>
  42. public float MaxRotation { get; set; } = 0.15f;
  43. /// <summary>
  44. /// Height of the cascade arc
  45. /// </summary>
  46. public float CascadeHeight { get; set; } = 60f;
  47. /// <summary>
  48. /// Number of times to repeat the shuffle
  49. /// </summary>
  50. public int ShuffleCycles { get; set; } = 2;
  51. /// <summary>
  52. /// Creates a new riffle shuffle animation
  53. /// </summary>
  54. public RiffleShuffleAnimation(CardsGame cardGame, Vector2 position, TimeSpan duration, Vector2 cardSize)
  55. : base(cardGame, position, duration, cardSize)
  56. {
  57. }
  58. /// <summary>
  59. /// Creates the animated cards with riffle shuffle animations
  60. /// </summary>
  61. public override List<AnimatedCardsGameComponent> CreateAnimatedCards(
  62. List<TraditionalCard> deck,
  63. SpriteBatch spriteBatch,
  64. Matrix globalTransformation)
  65. {
  66. var animatedCards = new List<AnimatedCardsGameComponent>();
  67. // Only show a subset of cards for a clear visual effect
  68. int cardsToShow = Math.Min(MaxVisibleCards, deck.Count / CardDisplayStep);
  69. int step = deck.Count / cardsToShow;
  70. // Split into two halves
  71. int halfPoint = cardsToShow / 2;
  72. // Time calculations for animation phases
  73. TimeSpan splitDuration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds * SplitPhaseRatio);
  74. TimeSpan cascadeDuration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds * CascadePhaseRatio);
  75. TimeSpan gatherDuration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds * GatherPhaseRatio);
  76. for (int i = 0; i < cardsToShow; i++)
  77. {
  78. int deckIndex = i * step;
  79. if (deckIndex >= deck.Count) break;
  80. // Determine which half this card belongs to
  81. bool isLeftHalf = i < halfPoint;
  82. int cardIndexInHalf = isLeftHalf ? i : (i - halfPoint);
  83. int totalInHalf = halfPoint;
  84. // Create card component with slight vertical offset for depth
  85. float depthOffset = i * CardDepthSpacing;
  86. var cardComponent = CreateCardComponent(deck[deckIndex], spriteBatch, globalTransformation,
  87. Position + new Vector2(0, depthOffset), true);
  88. animatedCards.Add(cardComponent);
  89. // Phase 1: Split the deck into two piles
  90. Vector2 splitPosition = Position + new Vector2(
  91. isLeftHalf ? -SplitDistance : SplitDistance,
  92. depthOffset);
  93. AddTransition(
  94. cardComponent,
  95. splitPosition,
  96. TimeSpan.Zero,
  97. splitDuration);
  98. // Phase 2: Cascade/riffle together with visible arc
  99. // Stagger the cascade timing so cards drop one by one
  100. double cascadeProgress = (double)cardIndexInHalf / totalInHalf;
  101. TimeSpan cascadeDelay = splitDuration +
  102. TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * cascadeProgress * CascadeStaggerRatio);
  103. // Create a high arc path for visibility
  104. Vector2 midPoint = Position + new Vector2(
  105. isLeftHalf ? -SplitDistance / 2 : SplitDistance / 2,
  106. -CascadeHeight);
  107. Vector2 finalPosition = Position + new Vector2(
  108. (isLeftHalf ? -FinalOffsetX : FinalOffsetX) + Random.Next(-FinalPositionRandomX, FinalPositionRandomX),
  109. depthOffset + Random.Next(-FinalPositionRandomY, FinalPositionRandomY));
  110. // Arc up
  111. AddTransition(
  112. cardComponent,
  113. midPoint,
  114. cascadeDelay,
  115. TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * CascadeArcRatio));
  116. // Rotate during arc up - left cards tilt left, right cards tilt right
  117. float targetRotation = isLeftHalf ? -MaxRotation : MaxRotation;
  118. AddRotation(
  119. cardComponent,
  120. 0f,
  121. targetRotation,
  122. cascadeDelay,
  123. TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * CascadeArcRatio));
  124. // Arc down to center
  125. AddTransition(
  126. cardComponent,
  127. finalPosition,
  128. cascadeDelay + TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * CascadeArcRatio),
  129. TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * CascadeDownRatio));
  130. // Rotate back to flat during arc down
  131. AddRotation(
  132. cardComponent,
  133. targetRotation,
  134. 0f,
  135. cascadeDelay + TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * CascadeArcRatio),
  136. TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * CascadeDownRatio));
  137. // Phase 3: Gather into neat pile
  138. TimeSpan gatherDelay = splitDuration + cascadeDuration;
  139. AddTransition(
  140. cardComponent,
  141. Position,
  142. gatherDelay,
  143. gatherDuration);
  144. }
  145. return animatedCards;
  146. }
  147. }
  148. }