In this tutorial, you'll learn how to extend the CardsStarterKit framework to create a simple interactive magic trick called the "9-Card Mind Reader." This trick teaches you the fundamentals of:
CardsGame base classGameRuleTarget Audience: MonoGame developers new to card games
Estimated Time: 2-3 hours
Difficulty: Beginner
The 9-Card Mind Reader is a classic mathematical card trick:
By placing the selected pile in the middle position twice, the card always ends up in the center position (5th card). Simple mathematics makes it foolproof!
We'll organize our magic trick code similarly to the Blackjack implementation:
3-Games/MagicTrick/
├── Core/
│ ├── MagicTrickCardGame.cs
│ ├── MagicTrickGameState.cs
├── Players/
│ └── MagicTrickPlayer.cs
├── Rules/
│ ├── CardSelectionRule.cs
│ └── RevealRule.cs
└── UI/
└── (We'll use existing Button class from Blackjack)
Create these directories:
mkdir -p 3-Games/MagicTrick/Core
mkdir -p 3-Games/MagicTrick/Players
mkdir -p 3-Games/MagicTrick/Rules
mkdir -p 3-Games/MagicTrick/UI
The magic trick has distinct phases, so we'll use a state machine to control flow.
Create: 3-Games/MagicTrick/Core/MagicTrickGameState.cs
namespace MagicTrick
{
/// <summary>
/// Represents the various states of the magic trick game
/// </summary>
public enum MagicTrickGameState
{
/// <summary>
/// Initial setup - dealing 9 cards
/// </summary>
Dealing,
/// <summary>
/// Player is selecting a card mentally
/// </summary>
PlayerSelecting,
/// <summary>
/// First pile selection phase
/// </summary>
FirstPileSelection,
/// <summary>
/// Cards being rearranged after first selection
/// </summary>
FirstRearrange,
/// <summary>
/// Second pile selection phase
/// </summary>
SecondPileSelection,
/// <summary>
/// Final rearrangement
/// </summary>
SecondRearrange,
/// <summary>
/// Revealing the selected card
/// </summary>
Revealing,
/// <summary>
/// Trick complete - show result
/// </summary>
Complete
}
}
Why This Approach?
Our player needs minimal state - just tracking which pile they selected.
Create: 3-Games/MagicTrick/Players/MagicTrickPlayer.cs
using CardsFramework.Players;
using CardsFramework.Game;
namespace MagicTrick
{
/// <summary>
/// Represents the spectator in the magic trick
/// </summary>
public class MagicTrickPlayer : Player
{
/// <summary>
/// Which pile (0, 1, or 2) the player selected in the current round
/// </summary>
public int SelectedPile { get; set; }
/// <summary>
/// Whether the player has made their selection
/// </summary>
public bool HasSelected { get; set; }
/// <summary>
/// Creates a new magic trick player
/// </summary>
/// <param name="name">Player's name</param>
/// <param name="game">Reference to the game instance</param>
public MagicTrickPlayer(string name, CardsGame game)
: base(name, game)
{
SelectedPile = -1;
HasSelected = false;
}
/// <summary>
/// Resets the player state for a new trick
/// </summary>
public void ResetSelection()
{
SelectedPile = -1;
HasSelected = false;
}
}
}
Key Points:
Player base class (gives us Name, Game, Hand)SelectedPile: Tracks which of the 3 piles contains their card (0=left, 1=middle, 2=right)HasSelected: Simple flag to prevent double-selectionResetSelection(): Prepares for a new trickThis rule checks if the player has made their pile selection and triggers the next phase.
Create: 3-Games/MagicTrick/Rules/CardSelectionRule.cs
using System;
using CardsFramework.Rules;
namespace MagicTrick
{
/// <summary>
/// Event arguments for card selection events
/// </summary>
public class CardSelectionEventArgs : EventArgs
{
public MagicTrickPlayer Player { get; set; }
public int SelectedPile { get; set; }
}
/// <summary>
/// Rule that fires when the player selects a pile
/// </summary>
public class CardSelectionRule : GameRule
{
private readonly MagicTrickPlayer player;
private bool previousHasSelected;
public CardSelectionRule(MagicTrickPlayer player)
{
this.player = player;
this.previousHasSelected = false;
}
/// <summary>
/// Checks if the player has made a new selection
/// </summary>
public override void Check()
{
// Only fire event when selection changes from false to true
if (player.HasSelected && !previousHasSelected)
{
previousHasSelected = true;
// Fire the event
FireRuleMatch(new CardSelectionEventArgs
{
Player = player,
SelectedPile = player.SelectedPile
});
}
// Reset tracking when starting a new selection phase
if (!player.HasSelected)
{
previousHasSelected = false;
}
}
}
}
How It Works:
GameRule base classRuleMatch event only when selection transitions from false → trueThis rule determines when we're ready to reveal the selected card.
Create: 3-Games/MagicTrick/Rules/RevealRule.cs
using System;
using CardsFramework.Rules;
using CardsFramework.Cards;
namespace MagicTrick
{
/// <summary>
/// Event arguments for reveal events
/// </summary>
public class RevealEventArgs : EventArgs
{
public TraditionalCard RevealedCard { get; set; }
}
/// <summary>
/// Rule that fires when it's time to reveal the selected card
/// </summary>
public class RevealRule : GameRule
{
private readonly MagicTrickCardGame game;
private bool hasRevealed;
public RevealRule(MagicTrickCardGame game)
{
this.game = game;
this.hasRevealed = false;
}
/// <summary>
/// Checks if we're in the revealing state
/// </summary>
public override void Check()
{
if (game.State == MagicTrickGameState.Revealing && !hasRevealed)
{
hasRevealed = true;
// The 5th card (index 4) is always the selected card after two rounds
if (game.TableCards.Count >= 5)
{
FireRuleMatch(new RevealEventArgs
{
RevealedCard = game.TableCards[4]
});
}
}
// Reset for next trick
if (game.State == MagicTrickGameState.Dealing)
{
hasRevealed = false;
}
}
}
}
Key Points:
Revealing stateTableCards[4] is always at index 4 after two rearrangementsThis is the core of our implementation. We'll build it in sections.
Create: 3-Games/MagicTrick/Core/MagicTrickCardGame.cs
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using CardsFramework.Cards;
using CardsFramework.Game;
using CardsFramework.Players;
using CardsFramework.UI;
using CardsFramework.Rules;
using CardsFramework.Core;
namespace MagicTrick
{
/// <summary>
/// Main game class for the 9-card magic trick
/// </summary>
public class MagicTrickCardGame : CardsGame
{
#region Fields
// Game state
private MagicTrickGameState currentState;
public MagicTrickGameState State
{
get { return currentState; }
set { currentState = value; }
}
// The 9 cards currently on the table
public List<TraditionalCard> TableCards { get; private set; }
// Animated components for displaying cards in 3x3 grid
private List<AnimatedCardsGameComponent> animatedCards;
// UI Components
private Button buttonPile1;
private Button buttonPile2;
private Button buttonPile3;
private Button buttonContinue;
private Button buttonNewTrick;
// The player (spectator)
private MagicTrickPlayer player;
// Game rules
private CardSelectionRule cardSelectionRule;
private RevealRule revealRule;
// Display text for instructions
private string instructionText;
private Vector2 instructionPosition;
private SpriteFont instructionFont;
// Card layout constants
private const int CardsPerRow = 3;
private const int TotalCards = 9;
private const float CardSpacingX = 120f;
private const float CardSpacingY = 160f;
private Vector2 gridStartPosition;
// State transition timer
private float stateTransitionTimer;
private float stateTransitionDelay;
private MagicTrickGameState nextState;
private bool waitingForStateTransition;
#endregion
#region Initialization
private ScreenManager screenManager;
/// <summary>
/// Creates a new magic trick game
/// </summary>
/// <param name="gameTable">The game table for layout</param>
/// <param name="screenManager">The screen manager for SpriteBatch and Content</param>
public MagicTrickCardGame(GameTable gameTable, ScreenManager screenManager)
: base(
decks: 1, // Only need 1 deck
jokersInDeck: 0, // No jokers
suits: CardSuit.AllSuits, // All suits available
cardValues: CardValue.NonJokers, // Standard cards only
minimumPlayers: 1, // Single player
maximumPlayers: 1, // Single player
gameTable: gameTable,
theme: "Default")
{
this.screenManager = screenManager;
TableCards = new List<TraditionalCard>();
animatedCards = new List<AnimatedCardsGameComponent>();
instructionText = "";
}
/// <summary>
/// Initializes the game components
/// </summary>
public override void Initialize()
{
base.Initialize();
// Calculate grid start position (centered on screen)
int screenWidth = GraphicsDevice.Viewport.Width;
int screenHeight = GraphicsDevice.Viewport.Height;
gridStartPosition = new Vector2(
(screenWidth - (CardSpacingX * (CardsPerRow - 1))) / 2 - 50,
100
);
instructionPosition = new Vector2(screenWidth / 2, 50);
// Start with dealing state
currentState = MagicTrickGameState.Dealing;
}
/// <summary>
/// Loads content and creates UI components
/// </summary>
public override void LoadContent()
{
base.LoadContent();
// Load instruction font
instructionFont = screenManager.Font;
// Get input state from screenManager
InputState input = new InputState();
// Create buttons using the actual Button constructor
// Buttons use texture names, not Texture2D objects
// Pile 1 button (left)
buttonPile1 = new Button(
"ButtonRegular", // Regular texture
"ButtonPressed", // Pressed texture
input, // Input state
this, // CardsGame
screenManager.SpriteBatch,
screenManager.GlobalTransformation
);
buttonPile1.Click += ButtonPile1_Click;
buttonPile1.Visible = false;
Game.Components.Add(buttonPile1);
// Pile 2 button (middle)
buttonPile2 = new Button(
"ButtonRegular",
"ButtonPressed",
input,
this,
screenManager.SpriteBatch,
screenManager.GlobalTransformation
);
buttonPile2.Click += ButtonPile2_Click;
buttonPile2.Visible = false;
Game.Components.Add(buttonPile2);
// Pile 3 button (right)
buttonPile3 = new Button(
"ButtonRegular",
"ButtonPressed",
input,
this,
screenManager.SpriteBatch,
screenManager.GlobalTransformation
);
buttonPile3.Click += ButtonPile3_Click;
buttonPile3.Visible = false;
Game.Components.Add(buttonPile3);
// Continue button
buttonContinue = new Button(
"ButtonRegular",
"ButtonPressed",
input,
this,
screenManager.SpriteBatch,
screenManager.GlobalTransformation
);
buttonContinue.Click += ButtonContinue_Click;
buttonContinue.Visible = false;
Game.Components.Add(buttonContinue);
// New Trick button
buttonNewTrick = new Button(
"ButtonRegular",
"ButtonPressed",
input,
this,
screenManager.SpriteBatch,
screenManager.GlobalTransformation
);
buttonNewTrick.Click += ButtonNewTrick_Click;
buttonNewTrick.Visible = false;
Game.Components.Add(buttonNewTrick);
}
#endregion
What We've Set Up:
Add these methods to handle players:
#region Player Management
/// <summary>
/// Adds a player to the game
/// </summary>
public override void AddPlayer(Player newPlayer)
{
if (!(newPlayer is MagicTrickPlayer))
{
throw new ArgumentException("Player must be of type MagicTrickPlayer");
}
base.AddPlayer(newPlayer);
player = (MagicTrickPlayer)newPlayer;
// Initialize rules now that we have a player
cardSelectionRule = new CardSelectionRule(player);
cardSelectionRule.RuleMatch += CardSelectionRule_RuleMatch;
Rules.Add(cardSelectionRule);
revealRule = new RevealRule(this);
revealRule.RuleMatch += RevealRule_RuleMatch;
Rules.Add(revealRule);
}
/// <summary>
/// Gets the current player
/// </summary>
public override Player GetCurrentPlayer()
{
return player;
}
#endregion
Key Points:
#region Card Management
/// <summary>
/// Deals 9 cards into a 3x3 grid
/// </summary>
public override void Deal()
{
// Clear previous cards
ClearTable();
// Shuffle the deck
dealer.Shuffle();
// Deal 9 cards to the table
for (int i = 0; i < TotalCards; i++)
{
TraditionalCard card = dealer[i];
TableCards.Add(card);
// Create animated component for this card
AnimatedCardsGameComponent animatedCard = new AnimatedCardsGameComponent(
card,
this,
screenManager.SpriteBatch,
screenManager.GlobalTransformation
);
animatedCard.LoadContent();
// Calculate position in 3x3 grid
int row = i / CardsPerRow;
int col = i % CardsPerRow;
Vector2 targetPosition = gridStartPosition + new Vector2(
col * CardSpacingX,
row * CardSpacingY
);
animatedCard.CurrentPosition = targetPosition;
animatedCard.IsFaceDown = false; // Show cards face-up
animatedCards.Add(animatedCard);
Game.Components.Add(animatedCard);
}
}
/// <summary>
/// Clears all cards from the table
/// </summary>
private void ClearTable()
{
// Remove animated components
foreach (var animatedCard in animatedCards)
{
Game.Components.Remove(animatedCard);
}
animatedCards.Clear();
TableCards.Clear();
}
/// <summary>
/// Rearranges cards after pile selection
/// </summary>
/// <param name="selectedPile">Which pile (0, 1, 2) contains the player's card</param>
private void RearrangeCards(int selectedPile)
{
// Collect cards by column (pile)
List<TraditionalCard> pile1 = new List<TraditionalCard>(); // Left column
List<TraditionalCard> pile2 = new List<TraditionalCard>(); // Middle column
List<TraditionalCard> pile3 = new List<TraditionalCard>(); // Right column
// Group cards into columns
for (int i = 0; i < TableCards.Count; i++)
{
int col = i % CardsPerRow;
if (col == 0)
pile1.Add(TableCards[i]);
else if (col == 1)
pile2.Add(TableCards[i]);
else
pile3.Add(TableCards[i]);
}
// Rearrange: Put selected pile in the middle
// This is the key to the trick!
List<TraditionalCard> rearranged = new List<TraditionalCard>();
if (selectedPile == 0) // Left pile selected
{
rearranged.AddRange(pile2); // Other pile first
rearranged.AddRange(pile1); // Selected pile in middle
rearranged.AddRange(pile3); // Other pile last
}
else if (selectedPile == 1) // Middle pile selected
{
rearranged.AddRange(pile1);
rearranged.AddRange(pile2); // Already in middle
rearranged.AddRange(pile3);
}
else // Right pile selected
{
rearranged.AddRange(pile1);
rearranged.AddRange(pile3); // Selected pile in middle
rearranged.AddRange(pile2);
}
// Update table cards
TableCards.Clear();
TableCards.AddRange(rearranged);
// Redeal cards in new order
RedealCards();
}
/// <summary>
/// Redeals cards to table in their current order
/// </summary>
private void RedealCards()
{
// Remove old animated components
foreach (var animatedCard in animatedCards)
{
Game.Components.Remove(animatedCard);
}
animatedCards.Clear();
// Create new animated components in new positions
for (int i = 0; i < TableCards.Count; i++)
{
AnimatedCardsGameComponent animatedCard = new AnimatedCardsGameComponent(
TableCards[i],
this,
screenManager.SpriteBatch,
screenManager.GlobalTransformation
);
animatedCard.LoadContent();
// Calculate position in 3x3 grid
int row = i / CardsPerRow;
int col = i % CardsPerRow;
Vector2 targetPosition = gridStartPosition + new Vector2(
col * CardSpacingX,
row * CardSpacingY
);
animatedCard.CurrentPosition = targetPosition;
animatedCard.IsFaceDown = false;
animatedCards.Add(animatedCard);
Game.Components.Add(animatedCard);
}
}
#endregion
The Magic Explained:
RearrangeCards() is where the trick happens! #region Game Flow
/// <summary>
/// Starts the trick
/// </summary>
public override void StartPlaying()
{
currentState = MagicTrickGameState.Dealing;
Deal();
// Move to selection phase after brief delay
ScheduleStateTransition(MagicTrickGameState.PlayerSelecting, 1000f);
}
/// <summary>
/// Schedules a state transition after a delay
/// </summary>
/// <param name="newState">The state to transition to</param>
/// <param name="delayMs">Delay in milliseconds</param>
private void ScheduleStateTransition(MagicTrickGameState newState, float delayMs)
{
nextState = newState;
stateTransitionDelay = delayMs;
stateTransitionTimer = 0;
waitingForStateTransition = true;
}
/// <summary>
/// Updates the game state each frame
/// </summary>
public override void Update(GameTime gameTime)
{
base.Update(gameTime);
// Handle state transition timer
if (waitingForStateTransition)
{
stateTransitionTimer += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
if (stateTransitionTimer >= stateTransitionDelay)
{
currentState = nextState;
waitingForStateTransition = false;
}
}
// Check rules
CheckRules();
// Update UI based on current state
UpdateUIForState();
}
/// <summary>
/// Updates button visibility and instruction text based on state
/// </summary>
private void UpdateUIForState()
{
// Hide all buttons first
buttonPile1.Visible = false;
buttonPile2.Visible = false;
buttonPile3.Visible = false;
buttonContinue.Visible = false;
buttonNewTrick.Visible = false;
switch (currentState)
{
case MagicTrickGameState.Dealing:
instructionText = "Watch carefully as the cards are dealt...";
break;
case MagicTrickGameState.PlayerSelecting:
instructionText = "Mentally select one card. Remember it!\nClick Continue when ready.";
buttonContinue.Visible = true;
break;
case MagicTrickGameState.FirstPileSelection:
instructionText = "Which pile contains your card?\n(Cards in each column are a pile)";
buttonPile1.Visible = true;
buttonPile2.Visible = true;
buttonPile3.Visible = true;
break;
case MagicTrickGameState.FirstRearrange:
instructionText = "Watch as I rearrange the cards...";
break;
case MagicTrickGameState.SecondPileSelection:
instructionText = "Now, which pile contains your card?";
buttonPile1.Visible = true;
buttonPile2.Visible = true;
buttonPile3.Visible = true;
break;
case MagicTrickGameState.SecondRearrange:
instructionText = "One more rearrangement...";
break;
case MagicTrickGameState.Revealing:
instructionText = "Your card is...";
break;
case MagicTrickGameState.Complete:
// Get the revealed card for display
if (TableCards.Count >= 5)
{
TraditionalCard revealedCard = TableCards[4];
instructionText = $"Your card is the {revealedCard.Value} of {revealedCard.Type}!\n\nDid I guess correctly?";
}
buttonNewTrick.Visible = true;
break;
}
}
#endregion
State Machine Logic:
#region Event Handlers
/// <summary>
/// Handles pile 1 (left) button click
/// </summary>
private void ButtonPile1_Click(object sender, EventArgs e)
{
SelectPile(0);
}
/// <summary>
/// Handles pile 2 (middle) button click
/// </summary>
private void ButtonPile2_Click(object sender, EventArgs e)
{
SelectPile(1);
}
/// <summary>
/// Handles pile 3 (right) button click
/// </summary>
private void ButtonPile3_Click(object sender, EventArgs e)
{
SelectPile(2);
}
/// <summary>
/// Handles pile selection
/// </summary>
private void SelectPile(int pileIndex)
{
player.SelectedPile = pileIndex;
player.HasSelected = true;
// Rule will detect this change and fire event
}
/// <summary>
/// Handles continue button click
/// </summary>
private void ButtonContinue_Click(object sender, EventArgs e)
{
if (currentState == MagicTrickGameState.PlayerSelecting)
{
currentState = MagicTrickGameState.FirstPileSelection;
}
}
/// <summary>
/// Handles new trick button click
/// </summary>
private void ButtonNewTrick_Click(object sender, EventArgs e)
{
player.ResetSelection();
StartPlaying();
}
/// <summary>
/// Handles card selection rule match
/// </summary>
private void CardSelectionRule_RuleMatch(object sender, EventArgs e)
{
CardSelectionEventArgs args = (CardSelectionEventArgs)e;
if (currentState == MagicTrickGameState.FirstPileSelection)
{
// First selection made
currentState = MagicTrickGameState.FirstRearrange;
// Rearrange cards with selected pile in middle
RearrangeCards(args.SelectedPile);
// Reset for next selection
player.ResetSelection();
// Move to second selection after delay
ScheduleStateTransition(MagicTrickGameState.SecondPileSelection, 1500f);
}
else if (currentState == MagicTrickGameState.SecondPileSelection)
{
// Second selection made
currentState = MagicTrickGameState.SecondRearrange;
// Rearrange again
RearrangeCards(args.SelectedPile);
// Move to reveal after delay
ScheduleStateTransition(MagicTrickGameState.Revealing, 1500f);
}
}
/// <summary>
/// Handles reveal rule match
/// </summary>
private void RevealRule_RuleMatch(object sender, EventArgs e)
{
RevealEventArgs args = (RevealEventArgs)e;
// Highlight the revealed card (center card)
if (animatedCards.Count >= 5)
{
// You could add a scale animation or glow effect here
// For simplicity, we'll just move to complete state
}
// Move to complete state
System.Threading.Tasks.Task.Delay(2000).ContinueWith(t =>
{
currentState = MagicTrickGameState.Complete;
});
}
#endregion
Event Flow:
SelectPile() → Updates player stateCardSelectionRule detects state change → Fires RuleMatchCardSelectionRule_RuleMatch() → Rearranges cards → Advances stateRevealRule fires → Shows result #region Drawing
/// <summary>
/// Draws the game
/// </summary>
public override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
// Draw instruction text
if (!string.IsNullOrEmpty(instructionText) && instructionFont != null)
{
SpriteBatch spriteBatch = (SpriteBatch)Game.Services.GetService(typeof(SpriteBatch));
if (spriteBatch != null)
{
// Measure text for centering
Vector2 textSize = instructionFont.MeasureString(instructionText);
Vector2 centeredPosition = new Vector2(
instructionPosition.X - textSize.X / 2,
instructionPosition.Y
);
// Draw text with shadow for readability
spriteBatch.DrawString(instructionFont, instructionText,
centeredPosition + new Vector2(2, 2), Color.Black);
spriteBatch.DrawString(instructionFont, instructionText,
centeredPosition, Color.White);
}
}
}
#endregion
#region Utilities
/// <summary>
/// Gets the value of a card (not used in magic trick, but required by base class)
/// </summary>
/// <remarks>
/// IMPORTANT: The CardsGame base class declares CardValue() as an abstract method,
/// so every game MUST override it. Even though the magic trick doesn't need card
/// values for scoring, we provide a standard implementation here. Games like Blackjack
/// and Gin Rummy use this for actual scoring logic.
/// </remarks>
public override int CardValue(TraditionalCard card)
{
// Magic trick doesn't need card values, but we implement for completeness
// This is a standard card value mapping (Aces=1, Face cards=10)
switch (card.Value)
{
case CardValue.Ace:
return 1;
case CardValue.Two:
return 2;
case CardValue.Three:
return 3;
case CardValue.Four:
return 4;
case CardValue.Five:
return 5;
case CardValue.Six:
return 6;
case CardValue.Seven:
return 7;
case CardValue.Eight:
return 8;
case CardValue.Nine:
return 9;
case CardValue.Ten:
case CardValue.Jack:
case CardValue.Queen:
case CardValue.King:
return 10;
default:
return 0;
}
}
#endregion
}
}
Drawing:
AnimatedCardsGameComponentCreate: 2-Core/Screens/MagicTrickGameplayScreen.cs
using System;
using Microsoft.Xna.Framework;
using CardsFramework.MagicTrick;
using CardsFramework.UI;
using CardsFramework.Core;
namespace CardsStarterKit
{
/// <summary>
/// Screen that hosts the magic trick game
/// </summary>
public class MagicTrickGameplayScreen : GameScreen
{
private MagicTrickCardGame magicTrickGame;
public MagicTrickGameplayScreen()
{
EnabledGestures = Microsoft.Xna.Framework.Input.Touch.GestureType.Tap;
}
/// <summary>
/// Loads content and initializes the game
/// </summary>
public override void LoadContent()
{
base.LoadContent();
// Create game table
GameTable gameTable = new GameTable(ScreenManager.Game, 1); // 1 player position
// Create the magic trick game
magicTrickGame = new MagicTrickCardGame(gameTable);
ScreenManager.Game.Components.Add(magicTrickGame);
magicTrickGame.Initialize();
magicTrickGame.LoadContent();
// Add the player
MagicTrickPlayer player = new MagicTrickPlayer("You", magicTrickGame);
magicTrickGame.AddPlayer(player);
// Start the trick
magicTrickGame.StartPlaying();
}
/// <summary>
/// Handles input
/// </summary>
public override void HandleInput(InputState input)
{
base.HandleInput(input);
// Handle back button / escape to return to menu
if (input.IsPauseGame(null))
{
ScreenManager.AddScreen(new PauseScreen(), null);
}
}
/// <summary>
/// Updates the screen
/// </summary>
public override void Update(GameTime gameTime, bool otherScreenHasFocus, bool coveredByOtherScreen)
{
base.Update(gameTime, otherScreenHasFocus, coveredByOtherScreen);
}
/// <summary>
/// Cleanup when exiting
/// </summary>
public override void UnloadContent()
{
if (magicTrickGame != null)
{
ScreenManager.Game.Components.Remove(magicTrickGame);
}
base.UnloadContent();
}
}
}
Integration:
GameTable for layoutMagicTrickCardGameModify: 2-Core/Screens/MainMenuScreen.cs
Find the constructor where menu entries are added and add:
// Add magic trick menu entry
MenuEntry magicTrickMenuEntry = new MenuEntry("Magic Trick");
magicTrickMenuEntry.Selected += MagicTrickMenuEntrySelected;
menuEntries.Add(magicTrickMenuEntry);
Then add the event handler method:
/// <summary>
/// Handles Magic Trick menu selection
/// </summary>
private void MagicTrickMenuEntrySelected(object sender, EventArgs e)
{
ScreenManager.AddScreen(new MagicTrickGameplayScreen(), null);
}
dotnet build
# For macOS/Linux
dotnet run --project Platforms/DesktopGL/CardsStarterKit.DesktopGL.csproj
# For Windows
dotnet run --project Platforms/WindowsDX/CardsStarterKit.WindowsDX.csproj
Try this test:
The math always works!
In the Revealing state, highlight the center card:
// In RevealRule_RuleMatch method:
if (animatedCards.Count >= 5)
{
AnimatedCardsGameComponent centerCard = animatedCards[4];
// Add scale animation to make it pulse
ScaleGameComponentAnimation scaleAnim = new ScaleGameComponentAnimation(centerCard)
{
Duration = TimeSpan.FromSeconds(1),
ScaleFactor = 1.2f,
IsLooped = true,
AnimationCycles = 3
};
centerCard.AnimationsList.Add(scaleAnim);
}
Make the rearrangement more dramatic:
// In RearrangeCards method, before RedealCards():
// Animate cards flying off screen then back
foreach (var animatedCard in animatedCards)
{
Vector2 offscreenPos = new Vector2(-500, animatedCard.CurrentPosition.Y);
TransitionGameComponentAnimation transition = new TransitionGameComponentAnimation(
animatedCard,
offscreenPos,
TimeSpan.FromSeconds(0.5)
);
animatedCard.AnimationsList.Add(transition);
}
// Then delay RedealCards() until animation completes
// In LoadContent:
SoundEffect cardDealSound = Game.Content.Load<SoundEffect>(@"Sounds\CardPlace");
SoundEffect revealSound = Game.Content.Load<SoundEffect>(@"Sounds\Reveal");
// Play at appropriate times:
cardDealSound.Play(); // When dealing
revealSound.Play(); // When revealing
Extending CardsGame:
Deal(), AddPlayer(), GetCurrentPlayer()Creating Game Rules:
GameRuleCheck() methodRuleMatch events when conditions are metWorking with Animations:
AnimatedCardsGameComponent for card renderingUI Management:
Button classSpriteBatch for custom text renderingGame Flow:
This trick demonstrates a key card game concept: controlled card positioning through mathematical manipulation. The same pattern appears in:
Try modifying the trick:
This framework makes it easy to experiment with card logic without worrying about rendering or input handling!
LoadContent() was calledIsFaceDown = false is setEnabledGestures includes GestureType.TapVisible propertyGame.Components.Add(button) was calledRearrangeCards() logicTableCards order after each rearrangeUpdate() to track state changes3-Games/MagicTrick/Core/MagicTrickGameState.cs3-Games/MagicTrick/Core/MagicTrickCardGame.cs3-Games/MagicTrick/Players/MagicTrickPlayer.cs3-Games/MagicTrick/Rules/CardSelectionRule.cs3-Games/MagicTrick/Rules/RevealRule.cs2-Core/Screens/MagicTrickGameplayScreen.cs2-Core/Screens/MainMenuScreen.csCongratulations! You've built a complete interactive magic trick using the CardsStarterKit framework. You now understand:
This foundation prepares you to build more complex card games. The next tutorial covers Gin Rummy, which introduces multi-phase gameplay, meld detection, scoring, and NPC opponents.
Happy coding and enjoy amazing your friends with your magic trick!