card_game 1.1.1 copy "card_game: ^1.1.1" to clipboard
card_game: ^1.1.1 copied to clipboard

A declarative Flutter package for building card games with drag-and-drop, animations, and flexible layouts. Examples include Solitaire and War.

card_game #

A declarative Flutter package for building card games with beautiful animations. Create card games with minimal code by defining the rules and letting the package handle the rest!

Table of Contents #

Features #

  • 🎴 Support for standard playing cards and numeric cards out of the box
  • 🎯 Drag and drop support with validation
  • ✨ Smooth animations for card movements and flips
  • 📏 Flexible layout options with rows, columns, and decks
  • 📱 Responsive design that works across all screen sizes
  • 🎨 Customizable card rendering
  • 🎮 No controllers needed - fully declarative API
  • 🎲 Example implementations of popular card games (Solitaire, War, Memory Match, etc.)
  • 🎯 Built-in validation system for card movements

Examples #

Each example demonstrates different features of the card_game package. Check out the example project for the complete source code.

Game Preview Description Key Concepts
Tower of Hanoi Tower of Hanoi A puzzle game where you move a stack of numbered disks between three columns, following the rule that larger numbers can't be placed on smaller ones. • Custom numeric cards with CardGame<int, int>
• Basic drag-and-drop with validation
maxGrabStackSize to limit moves to one card
• Simple state management
War War A two-player card game where each player draws from their deck, and the highest card wins both cards. SuitedCard with deckCardStyle()
• Flipped cards with CardDeck.flipped
• Card value comparison with SuitedCardValueMapper
• Automatic card flip animations
Memory Match Memory Match Find matching pairs of cards by flipping them over two at a time. Cards with matching ranks are removed from play. • Grid layout with multiple CardDecks
• Card flipping on press
• Disabled card dragging
Golf Solitaire Golf Solitaire Remove cards that are one rank away from the top card of the waste pile. Kings and Aces are considered one apart. • Complex card comparison with SuitedCardDistanceMapper
Solitaire Solitaire The classic solitaire game. Build foundation piles by suit from Ace to King, and move cards between columns following standard rules. • Advanced card movement rules
• Multiple card dragging
• Complex state management
• Card stacking with flipped and unflipped cards

Implementing Your First Game #

Let's walk through implementing the Tower of Hanoi example, as it's the simplest game that demonstrates the core concepts:

class TowerOfHanoi extends HookWidget {
  // Number of disks to include in the game
  final int amount;

  const TowerOfHanoi({super.key, this.amount = 4});

  // Initialize the game state with all disks in the first column
  // Each disk is represented by a number, where larger numbers represent larger disks
  List<List<int>> get initialCards => [
    // First column contains all disks, largest (amount) to smallest (1)
    List.generate(amount, (i) => amount - i),
    // Second and third columns start empty
    <int>[],
    <int>[],
  ];

  @override
  Widget build(BuildContext context) {
    // Use Flutter Hooks to manage the state of our columns
    // Each column is a list of integers representing the disks
    final cardsState = useState(initialCards);

    return CardGame<int, int>(
      // Use the built-in numeric card style to render our disks as numbers
      style: numericCardStyle(),
      children: [
        SafeArea(
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: cardsState.value
                    .mapIndexed((i, states) => CardColumn<int, int>(
                  // Each column needs a unique identifier
                  value: i,
                  // The cards (disks) currently in this column
                  values: states,
                  // Only allow moving one disk at a time
                  maxGrabStackSize: 1,
                  // Define the rules for when a disk can be placed here
                  canMoveCardHere: (move) {
                    final movingCard = move.cardValues.last;
                    final movingOnto = states.lastOrNull;
                    // Allow moves if:
                    // 1. Column is empty (movingOnto == null)
                    // 2. Moving disk is smaller than the top disk
                    return movingOnto == null || movingCard < movingOnto;
                  },
                  // Handle the actual movement of cards between columns
                  onCardMovedHere: (move) {
                    // Create a copy of the current state
                    final newCards = [...cardsState.value];
                    // Remove the moving card from its original column
                    // Since maxGrabStackSize is 1, we know we're only removing one card
                    newCards[move.fromGroupValue].removeLast();
                    // Add the moved card to its new column
                    // We use move.cardValues.first because maxGrabStackSize: 1 ensures
                    // there's exactly one card being moved at a time
                    newCards[i].add(move.cardValues.first);
                    // Update the state, triggering a rebuild
                    cardsState.value = newCards;
                  },
                ))
                    .toList(),
              ),
              Spacer(),
              // Reset button to start the game over
              ElevatedButton(
                onPressed: () => cardsState.value = initialCards,
                style: ButtonStyle(
                    shape: WidgetStatePropertyAll(CircleBorder())),
                child: Icon(Icons.restart_alt),
              ),
            ],
          ),
        ),
      ],
    );
  }
}
copied to clipboard

This implementation shows:

  1. Using numeric cards with the built-in numericCardStyle()
  2. Setting up three columns using CardColumn
  3. Implementing drag-and-drop rules with canMoveCardHere
  4. Managing state with Flutter Hooks
  5. Animating card movements automatically

For more complex examples, check out the implementations of the other games in the example folder.

Running the Examples #

To try out the examples:

  1. Clone the repository
  2. Navigate to the example directory
  3. Run flutter pub get
  4. Run flutter run and select the example you want to try

Getting Started #

Add the package to your pubspec.yaml:

dependencies:
  card_game: ^1.0.0
copied to clipboard

Core Concepts #

Card Types #

The package uses two generic types:

  • T: The type of your cards (e.g., SuitedCard for playing cards or int for numeric cards)
  • G: The type for group identifiers (can be any type, often dynamic, String, or int)

Card Groups #

Three types of card groups are available:

  1. CardDeck: Shows only the top card, useful for draw piles
  2. CardRow: Displays cards horizontally with customizable spacing
  3. CardColumn: Displays cards vertically with customizable spacing

Card Styles #

Built-in styles:

  • deckCardStyle(): Renders standard playing cards with a red back
  • numericCardStyle(): Renders numeric cards

Custom styles can be created by implementing CardGameStyle<T>.

Customization #

Card Movement Rules #

Control card movement with the following parameters:

CardColumn<T, G>(
  // ... other params
  canCardBeGrabbed: (index, card) => true, // Control which cards can be grabbed
  maxGrabStackSize: 1, // Limit how many cards can be grabbed at once
  canMoveCardHere: (moveDetails) => true, // Define rules for accepting cards
  onCardMovedHere: (moveDetails) {}, // Handle successful moves
)
copied to clipboard

Card Interaction and Appearance #

Control card interaction and visual state:

CardColumn<T, G>(
  // ... other params
  onCardPressed: (card) {
    // Handle card press events
    // Useful for flipping cards, selecting cards, or triggering game actions
  },
  isCardFlipped: (index, card) {
    // Control whether specific cards are shown face-down
    // Return true to show the card's back, false to show its face
    return shouldCardBeFlipped(index, card);
  },
)
copied to clipboard

Common use cases:

  • Memory Match: Use onCardPressed to flip cards when clicked
  • Solitaire: Use isCardFlipped to hide cards in the tableau
  • War: Use isCardFlipped with CardDeck.flipped for face-down draw piles

Custom Card Styles #

Create your own card style:

CardGameStyle<MyCard>(
  cardSize: Size(64, 89),
  cardBuilder: (card, isFlipped, state) => MyCustomCard(
    card: card,
    isFlipped: isFlipped,
    state: state,
  ),
  emptyGroupBuilder: (state) => MyCustomEmptySpace(state: state),
)
copied to clipboard

Tips #

  • Each card value (T) must be unique within the game
  • Each group value (G) must be unique within the game
  • Always explicitly specify type parameters for CardGroups to match the parent CardGame. For example, if you have CardGame<SuitedCard, String>, your groups should be CardColumn<SuitedCard, String>, CardRow<SuitedCard, String>, or CardDeck<SuitedCard, String>. This helps prevent type errors and makes the code more maintainable
  • Use flutter_hooks or your preferred state management solution
  • Changes to card positions and flips are automatically animated
  • Card states (regular, highlighted, error) are handled automatically during drag and drop by default
21
likes
150
points
197
downloads
screenshot

Publisher

verified publisherjlogical.com

Weekly Downloads

2024.08.30 - 2025.03.14

A declarative Flutter package for building card games with drag-and-drop, animations, and flexible layouts. Examples include Solitaire and War.

Repository (GitHub)
View/report issues

Topics

#flutter #widget #ui

Documentation

API reference

License

MIT (license)

Dependencies

collection, equatable, flutter, playing_cards, provider

More

Packages that depend on card_game