//-----------------------------------------------------------------------------
// RiffleShuffleAnimation.cs
//
// Implements a classic riffle shuffle animation where the deck is split
// in half and cards cascade together in an interleaving pattern
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace CardsFramework
{
///
/// Riffle shuffle: splits deck into two halves, then interleaves them
/// with a cascading visual effect. This is the classic casino shuffle.
///
public class RiffleShuffleAnimation : ShuffleAnimation
{
///
/// How far apart the two halves split (in pixels)
///
public float SplitDistance { get; set; } = 120f;
///
/// Maximum rotation angle for cards during cascade (in radians)
///
public float MaxRotation { get; set; } = 0.15f;
///
/// Height of the cascade arc
///
public float CascadeHeight { get; set; } = 60f;
///
/// Number of times to repeat the shuffle
///
public int ShuffleCycles { get; set; } = 2;
///
/// Creates a new riffle shuffle animation
///
public RiffleShuffleAnimation(CardsGame cardGame, Vector2 position, TimeSpan duration, Vector2 cardSize)
: base(cardGame, position, duration, cardSize)
{
}
///
/// Creates the animated cards with riffle shuffle animations
///
public override List CreateAnimatedCards(
List deck,
SpriteBatch spriteBatch,
Matrix globalTransformation)
{
var animatedCards = new List();
// Only show a subset of cards for a clear visual effect (every 3rd card)
int cardsToShow = Math.Min(18, deck.Count / 3); // Show ~18 cards max
int step = deck.Count / cardsToShow;
// Split into two halves
int halfPoint = cardsToShow / 2;
// Time calculations for animation phases
TimeSpan splitDuration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds * 0.2);
TimeSpan cascadeDuration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds * 0.6);
TimeSpan gatherDuration = TimeSpan.FromMilliseconds(Duration.TotalMilliseconds * 0.2);
for (int i = 0; i < cardsToShow; i++)
{
int deckIndex = i * step;
if (deckIndex >= deck.Count) break;
// Determine which half this card belongs to
bool isLeftHalf = i < halfPoint;
int cardIndexInHalf = isLeftHalf ? i : (i - halfPoint);
int totalInHalf = halfPoint;
// Create card component with slight vertical offset for depth
float depthOffset = i * 0.5f;
var cardComponent = CreateCardComponent(deck[deckIndex], spriteBatch, globalTransformation,
Position + new Vector2(0, depthOffset), true);
animatedCards.Add(cardComponent);
// Phase 1: Split the deck into two piles
Vector2 splitPosition = Position + new Vector2(
isLeftHalf ? -SplitDistance : SplitDistance,
depthOffset);
AddTransition(
cardComponent,
splitPosition,
TimeSpan.Zero,
splitDuration);
// Phase 2: Cascade/riffle together with visible arc
// Stagger the cascade timing so cards drop one by one
double cascadeProgress = (double)cardIndexInHalf / totalInHalf;
TimeSpan cascadeDelay = splitDuration +
TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * cascadeProgress * 0.7);
// Create a high arc path for visibility
Vector2 midPoint = Position + new Vector2(
isLeftHalf ? -SplitDistance / 2 : SplitDistance / 2,
-CascadeHeight);
Vector2 finalPosition = Position + new Vector2(
(isLeftHalf ? -10 : 10) + Random.Next(-8, 8), // Slight spread
depthOffset + Random.Next(-3, 3));
// Arc up
AddTransition(
cardComponent,
midPoint,
cascadeDelay,
TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * 0.2));
// Arc down to center
AddTransition(
cardComponent,
finalPosition,
cascadeDelay + TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * 0.2),
TimeSpan.FromMilliseconds(cascadeDuration.TotalMilliseconds * 0.3));
// Phase 3: Gather into neat pile
TimeSpan gatherDelay = splitDuration + cascadeDuration;
AddTransition(
cardComponent,
Position,
gatherDelay,
gatherDuration);
}
return animatedCards;
}
}
}