slot_machine_ui

A highly customizable slot-machine-style UI widget for Flutter, featuring smooth reel animations, a skeuomorphic design, and optional sound effects.

Pub Version Flutter Package License: MIT

Slot Machine Demo


📋 Table of Contents


✨ Features

  • 🎰 Smooth reel animations with configurable spin duration
  • 🎨 Clean skeuomorphic-inspired design with optional inner frame
  • 🎵 Advanced sound system with custom win/lose logic
  • 🔧 Highly customizable layout, colors, and behavior
  • 📱 Responsive sizing for different screen widths
  • 🎮 Controller-based API for programmatic control
  • 🌐 Cross-platform support (iOS, Android, Web, Desktop)

📦 Installation

Add the dependency to your pubspec.yaml:

dependencies:
  slot_machine_ui: ^1.3.6

Then run:

flutter pub get

Or install directly:

flutter pub add slot_machine_ui

🚀 Basic Usage

import 'package:flutter/material.dart';
import 'package:slot_machine_ui/slot_machine_ui.dart';

class MySlotMachine extends StatefulWidget {
  @override
  State<MySlotMachine> createState() => _MySlotMachineState();
}

class _MySlotMachineState extends State<MySlotMachine> {
  final _controller = SlotMachineController();
  final _symbols = ['🍒', '🍋', '7️⃣', '💎', '⭐', '🔔'];

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SlotMachineWidget(
          primaryColor: Colors.redAccent,
          showInnerFrame: true,
          width: 300,
          controller: _controller,
          symbols: _symbols,
          onSpinStart: () {
            debugPrint('Spin started');
          },
          onResult: (results) {
            debugPrint('Spin result: $results');
          },
          onSpinEnd: () {
            debugPrint('Spin ended');
          },
        ),
      ),
    );
  }
}

🎨 Advanced Usage

Custom Colors and Layout

SlotMachineWidget(
  controller: _controller,
  symbols: _symbols,
  numberOfReels: 4,
  visibleSymbolsPerReel: 3,
  width: 400,
  primaryColor: Color(0xFF2C3E50),
  accentColor: Color(0xFFE74C3C),
  reelBackgroundColor: Colors.white,
  buttonPosition: SpinButtonPosition.below,
  buttonText: 'PULL',
  showInnerFrame: true,  // Add decorative inner frame
)

Customizing Animation Speed

Control the spin animation duration with the spinDuration parameter (in milliseconds):

SlotMachineWidget(
  controller: _controller,
  symbols: _symbols,
  spinDuration: 5000,  // 5 seconds - slow, dramatic spin
)

// Fast spin (1.5 seconds)
SlotMachineWidget(
  controller: _controller,
  symbols: _symbols,
  spinDuration: 1500,
)

// Default speed (3 seconds)
SlotMachineWidget(
  controller: _controller,
  symbols: _symbols,
  // spinDuration: 3000 (default, can be omitted)
)

How it works: The spinDuration controls the entire animation sequence:

  • 60% of the time: all reels spin together
  • 30% of the time: reels stop one by one (staggered)
  • 10% of the time: final pause before showing results

The spin sound plays throughout the entire duration until the result sound plays.

Using Image Assets

First, add your images to pubspec.yaml:

flutter:
  assets:
    - assets/images/cherry.png
    - assets/images/lemon.png
    - assets/images/seven.png

Then use them in your widget:

SlotMachineWidget(
  controller: _controller,
  symbols: [
    'assets/images/cherry.png',
    'assets/images/lemon.png',
    'assets/images/seven.png',
  ],
  useImageAssets: true,  // ⚠️ Don't forget this!
)

Advanced Sound System with Custom Win Logic

The latest version (1.3.6) includes a powerful sound system that lets you define custom win conditions and play different sounds based on the results.

Basic Sound Setup

  1. Add sound files to your project's pubspec.yaml:
flutter:
  assets:
    - assets/sounds/spin.mp3
    - assets/sounds/win.mp3
    - assets/sounds/lose.mp3
  1. Configure basic sounds:
SlotMachineWidget(
  controller: _controller,
  symbols: _symbols,
  // Both path formats are supported:
  spinSoundAsset: 'sounds/spin.mp3',           // ✅ Recommended
  winSoundAsset: 'assets/sounds/win.mp3',      // Plays on default win
  loseSoundAsset: 'assets/sounds/lose.mp3',    // Plays on default lose
)

Custom Win/Lose Logic with onGetWinSound

The onGetWinSound callback gives you complete control over which sound plays based on the spin results. This is perfect for:

  • Multiple win types (jackpot, big win, small win)
  • Special symbol combinations
  • Custom game logic

How it works:

  • Return a sound asset path (String) to indicate a win and play that sound
  • Return null to play the loseSoundAsset
  • If you don't provide onGetWinSound, it uses default logic (all symbols in first row match = win)

Example 1: Different sounds for different wins

SlotMachineWidget(
  controller: _controller,
  symbols: ['🍒', '🍋', '💎', '7️⃣'],
  spinSoundAsset: 'sounds/spin.mp3',
  loseSoundAsset: 'sounds/lose.mp3',
  onGetWinSound: (results) {
    // results is List<List<String>> - one list per reel
    // results[0] = first reel, results[0][0] = top symbol of first reel
    
    // Check for jackpot (all 7s in top row)
    if (results.every((reel) => reel[0] == '7️⃣')) {
      return 'sounds/jackpot.mp3';
    }
    
    // Check for diamond win (all diamonds in top row)
    if (results.every((reel) => reel[0] == '💎')) {
      return 'sounds/big_win.mp3';
    }
    
    // Check for any matching top row
    if (results.every((reel) => reel[0] == results[0][0])) {
      return 'sounds/small_win.mp3';
    }
    
    // No win - return null to play lose sound
    return null;
  },
)

Example 2: Check multiple rows for wins

onGetWinSound: (results) {
  // Check all rows for matches
  for (int row = 0; row < results[0].length; row++) {
    bool rowMatches = results.every((reel) => reel[row] == results[0][row]);
    if (rowMatches) {
      return 'sounds/win.mp3';
    }
  }
  return null;  // No matches found
},

Example 3: Diagonal wins

onGetWinSound: (results) {
  // Check diagonal (top-left to bottom-right)
  if (results.length == 3 && results[0].length == 3) {
    if (results[0][0] == results[1][1] && results[1][1] == results[2][2]) {
      return 'sounds/diagonal_win.mp3';
    }
  }
  return null;
},

Example 4: Special symbol combinations

onGetWinSound: (results) {
  final topRow = results.map((reel) => reel[0]).toList();
  
  // Check for specific combination: cherry-lemon-cherry
  if (topRow.length == 3 && 
      topRow[0] == '🍒' && 
      topRow[1] == '🍋' && 
      topRow[2] == '🍒') {
    return 'sounds/special_combo.mp3';
  }
  
  // Check if at least 2 symbols match
  final symbolCounts = <String, int>{};
  for (var symbol in topRow) {
    symbolCounts[symbol] = (symbolCounts[symbol] ?? 0) + 1;
  }
  
  if (symbolCounts.values.any((count) => count >= 2)) {
    return 'sounds/partial_win.mp3';
  }
  
  return null;
},

Sound behavior:

  • Spin sound plays from the start of the animation until just before the result
  • The spin sound automatically stops before the result sound plays
  • onGetWinSound is called after all reels stop, before any result sound plays
  • If onGetWinSound returns a path, that sound plays
  • If onGetWinSound returns null, the loseSoundAsset plays
  • If onGetWinSound is not provided, default win logic is used (top row match = winSoundAsset, else loseSoundAsset)

Supported audio formats: MP3, WAV, OGG, AAC

Platform notes:

  • iOS/Android: Works out of the box
  • Web: May require user interaction before playing (browser security)
  • Desktop: Fully supported

Technical details: Sound playback uses the audioplayers package, which is included as a dependency. If audio fails to load, the widget gracefully falls back to haptic feedback.

Troubleshooting:

  • Ensure asset paths in pubspec.yaml are properly indented
  • Run flutter clean && flutter pub get after adding new assets
  • Check that audio files exist at the specified paths
  • Test on actual devices (not just emulator) for best results

Programmatic Control

// Spin programmatically
_controller.spin();

// Check if spinning
if (_controller.isSpinning) {
  print('Currently spinning!');
}

// Get results
if (_controller.finalResults != null) {
  print('Last result: ${_controller.finalResults}');
}

Callbacks

SlotMachineWidget(
  controller: _controller,
  symbols: _symbols,
  onSpinStart: () {
    print('Spin started!');
  },
  onResult: (results) {
    // Check for wins
    bool hasWin = results.every((reel) => reel[0] == results[0][0]);
    if (hasWin) {
      print('Winner! 🎉');
    }
  },
  onSpinEnd: () {
    print('Spin completed!');
  },
)

⚙️ Customization Options

Parameter Type Default Description
controller SlotMachineController required Controller to manage state
symbols List<String> required List of symbols (emoji or asset paths)
useImageAssets bool false Whether symbols are image asset paths
numberOfReels int 3 Number of reels (columns)
visibleSymbolsPerReel int 3 Visible symbols per reel (rows)
width double? null Width of machine (auto-sized if null)
spinDuration int 3000 Total animation duration in milliseconds
primaryColor Color Color(0xFFE0E0E0) Main machine body color
accentColor Color Color(0xFFFF3333) Spin button color
reelBackgroundColor Color Color(0xFFFAFAFA) Background color of reels
showSpinButton bool true Whether to show the spin button
showInnerFrame bool false Whether to show decorative inner frame
buttonPosition SpinButtonPosition below Position of button (below/above/left/right)
buttonText String 'SPIN' Text on the spin button
spinSoundAsset String? null Asset path for spin sound
winSoundAsset String? null Asset path for default win sound
loseSoundAsset String? null Asset path for lose sound
onGetWinSound String? Function(List<List<String>>)? null Callback to determine which sound to play based on results
onResult Function? null Callback with final grid results
onSpinStart Function? null Callback when spin starts
onSpinEnd Function? null Callback when spin ends

Button Positions

enum SpinButtonPosition {
  below,  // Button below the machine
  above,  // Button above the machine
  right,  // Button to the right
  left,   // Button to the left
}

📱 Examples

Check out the example folder for complete examples:

  • Basic emoji slot machine - Simple setup with emoji symbols
  • Custom themed machine - Custom colors and image assets
  • Sound-enabled machine - With spin and win sound effects
  • Advanced sound logic - Custom win detection with multiple sounds
  • Programmatic control - Controlling spins from code
  • Variable speed - Different animation durations

To run the example app:

cd example
flutter run

💡 Tips

  1. Performance: Use 3-6 symbols for best performance
  2. Responsive Design: Leave width as null to auto-size based on screen width
  3. Win Detection: Use onGetWinSound callback for custom win logic and sound selection
  4. Accessibility: Consider adding haptic feedback for better UX
  5. Asset Optimization: Compress images and use appropriate formats (PNG for symbols, MP3/AAC for sounds)
  6. Animation Speed:
    • Fast spins (1000-2000ms) work well for casual games
    • Medium spins (2500-3500ms) provide balanced excitement
    • Slow spins (4000-6000ms) create dramatic tension
  7. Sound Design: Create different sounds for various win types to enhance user experience

❓ FAQ

Q: Can I use this in a production gambling app?
A: This package is for educational and entertainment purposes. Ensure compliance with local gambling laws and regulations.

Q: Why isn't my sound playing?
A: Make sure the asset paths are correctly added to pubspec.yaml and the files exist in your project.

Q: How do I detect a win?
A: Use the onGetWinSound callback for custom win logic:

onGetWinSound: (results) {
  // Check if all reels have the same top symbol
  bool isWin = results.every((reel) => reel[0] == results[0][0]);
  return isWin ? 'sounds/win.mp3' : null;
}

Q: What's the difference between winSoundAsset and onGetWinSound?
A: winSoundAsset is a fallback used when onGetWinSound is not provided or when you're using default win logic. onGetWinSound gives you full control to play different sounds for different types of wins.

Q: Can I customize the animation duration?
A: Yes! Use the spinDuration parameter to control the total animation time in milliseconds. Default is 3000ms (3 seconds).

Q: Why aren't my images showing up?
A: Make sure you've set useImageAssets: true when using image paths in the symbols list.

Q: Does this work on web?
A: Yes! The package is tested on all Flutter platforms (iOS, Android, Web, Desktop).

Q: Can the spin sound play for the entire duration?
A: Yes! The spin sound automatically plays throughout the entire animation (controlled by spinDuration) and stops just before the result sound plays.

Q: How do I check multiple rows for wins?
A: Loop through rows in onGetWinSound:

onGetWinSound: (results) {
  for (int row = 0; row < results[0].length; row++) {
    bool rowMatches = results.every((reel) => reel[row] == results[0][row]);
    if (rowMatches) return 'sounds/win.mp3';
  }
  return null;
}

🤝 Contributing

Contributions are welcome! Here's how you can help:

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please make sure to:

  • Follow the existing code style
  • Add tests for new features
  • Update documentation as needed

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


⚠️ Disclaimer

This package is for educational and entertainment purposes only. Please ensure compliance with local gambling laws and regulations if used in production applications.


🙏 Acknowledgments

  • Inspired by classic slot machine designs
  • Built with Flutter and ❤️

Made with Flutter 💙

Libraries

slot_machine_ui