简体   繁体   中英

How to randomize images in a slotmachine C#

So I'm making a slot machine in C#. I'm really new to C# and I am really bad at it. Up to this point my project has been going fine. But now I want to randomize the images shown, when the 'spin' Button is clicked.

I've tried a lot of different things. The solutions I have found are either with the use of a PictureBox or nothing close to what I'm working on.

If someone could take a look at my code and push me in the right direction, I would be really grateful.

Thanks in advance!

using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;

namespace Prb.Slot.Machine.Wpf
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {

        int CoinInsert = 0;

        private static Random random;

        public enum SlotMachineIcon
        {
            Banana,
            BigWin,
            Cherry,
            Lemon,
            Orange,
            Plum,
            Seven,
            Strawberry,
            Watermelon
        }

        public MainWindow()
        {
            InitializeComponent();
        }

        private static void Init()
        {
            if (random == null) random = new Random();
        }
        public static int Random(int min, int max)
        {
            Init();
            return random.Next(min, max);
        }

        void UpdateImage(Image wpfImage, SlotMachineIcon newIcon)
        {

            DirectoryInfo directoryInfo = new DirectoryInfo(Environment.CurrentDirectory);
            directoryInfo = new DirectoryInfo(directoryInfo.Parent.Parent.Parent.Parent.FullName);
            Uri uri = new Uri($"{directoryInfo.FullName}/images/{newIcon}.png");
            wpfImage.Source = new BitmapImage(uri);
        }


        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            lblCoinsInserted.Content = 0;
            lblCoinBalance.Content = 0;
            lblCoinsWon.Content = 0;

            UpdateImage(imgLeft, SlotMachineIcon.Cherry);
            UpdateImage(imgMiddle, SlotMachineIcon.Banana);
            UpdateImage(imgRight, SlotMachineIcon.Seven);

        }

        private void btnInsertCoins_Click(object sender, RoutedEventArgs e)
        {
            
            int.TryParse(txtInsertCoins.Text, out int InsertCoins);

            if (InsertCoins > 0)
            {
                CoinInsert += int.Parse(txtInsertCoins.Text.ToString());
                lblCoinBalance.Content = (int)lblCoinBalance.Content + Convert.ToInt32(txtInsertCoins.Text);
                lblCoinsInserted.Content = CoinInsert;
                txtInsertCoins.Clear();
            }
            else
            {
                MessageBox.Show("Gelieve strikt positieve getallen in te vullen", "Ongeldig aantal munten", MessageBoxButton.OK, MessageBoxImage.Warning);
                txtInsertCoins.Clear();
            }
        }

        private void btnSpin_Click(object sender, RoutedEventArgs e)
        {
            int InsertedCoins = Convert.ToInt32(lblCoinsInserted.Content);
            int CoinsBalance = Convert.ToInt32(lblCoinBalance.Content);

            /*var v = Enum.GetValues(typeof(SlotMachineIcon));
            int number = random.Next(10);*/

            if (InsertedCoins == 0 | CoinsBalance == 0)
            {
                MessageBox.Show("Gelieve eerst munten in te werpen", "Geen munten ingeworpen", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
            else
            {
                lblCoinBalance.Content = CoinsBalance -  1;

                UpdateImage(imgLeft, SlotMachineIcon.Strawberry);
                UpdateImage(imgMiddle, SlotMachineIcon.Watermelon);
                UpdateImage(imgRight, SlotMachineIcon.Watermelon);

            }
        }


    }
}

Edit: moved out random declaration as @EmondErno pointed it out.

This method returns a random icon every time you call it:

private Random random = new();

private SlotMachineIcon GetRandomIcon()
{
    return (SlotMachineIcon)random.Next(10); //don't forget to update this number if you add or remove icons
}

Then call it in every UpdateImage method like:

UpdateImage(imgLeft, GetRandomIcon());
UpdateImage(imgMiddle, GetRandomIcon());
UpdateImage(imgRight, GetRandomIcon());

You're trying to do everything in the code behind, which is a terrible mistake for many reasons, among which your program will get hard to maintain read and update at some point and you are tight coupling the view and the logic of your program. You want to follow the MVVM pattern and put only in the code behind only the logic of the view (no data).

Also in your code, you're reinventing the updating system that already exists in WPF, you want to use the databinding and WPF updating system and get rid of all "update icon" logic in your program.

This is a ViewModel that you could use (.net 5.0):

public class SlotViewModel: ISlotViewModel, INotifyPropertyChanged
{
    private Random _r = new();
    private int _slotChoicesCount;
    private SlotSet _currentSlotSet;
    private ICommand _spinCommand;

    public SlotViewModel()
    {
        _slotChoicesCount = Enum.GetNames(typeof(SlotMachineIcon)).Length;
    }

    private SlotSet GetNewSet() => new(Enumerable.Range(0,3).Select(o => (SlotMachineIcon)_r.Next(_slotChoicesCount)).ToList());

    public SlotSet CurrentSlotSet
    {
        get => _currentSlotSet;
        set
        {
            if (Equals(value, _currentSlotSet)) return;
            _currentSlotSet = value;
            OnPropertyChanged();
        }
    }

    public ICommand SpinCommand => _spinCommand ??= new DelegateCommand(s => { CurrentSlotSet = GetNewSet(); }, s => true);

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The most important part is that your ViewModel implements INotifyPropertyChanged . When you uses SpinCommand , it updates the property CurrentSlotSet , and that's all you need to worry about. All the rest is taken care of by the WPF databinding system.

SlotSet is a convenient way to present an immutable result:

public class SlotSet
{
    public SlotMachineIcon Left { get; }
    public SlotMachineIcon Middle { get; }
    public SlotMachineIcon Right { get; }

    public SlotSet(IList<SlotMachineIcon> triad)
    {
        Left = triad[0];
        Middle = triad[1];
        Right = triad[2];
    }

    public bool IsWinner => Left == Middle && Middle == Right; // just an example
}

ISlotViewModel is the interface (contract) that your ViewModel satisfies.

public interface ISlotViewModel
{
    ICommand SpinCommand { get; }

    SlotSet CurrentSlotSet { get; set; }
}

The helper class DelegateCommand:

public class DelegateCommand : ICommand
{
    private readonly Predicate<object> _canExecute;
    private readonly Action<object> _execute;

    public event EventHandler CanExecuteChanged;

    public DelegateCommand(Action<object> execute, Predicate<object> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);

    public void Execute(object parameter) => _execute(parameter);

    public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}

In your View, XAML part, your only need something as simple as this:

        <Button Command="{Binding SpinCommand}">spin</Button>
        <StackPanel Orientation="Horizontal">
            <Image Source="{Binding CurrentSlotSet.Left}"/>
            <Image Source="{Binding CurrentSlotSet.Middle}"/>
            <Image Source="{Binding CurrentSlotSet.Right}"/>
        </StackPanel>

And in the Windows markup has this:

    xmlns:local="clr-namespace:SlotMachine"
    d:DataContext="{d:DesignInstance Type=local:SlotViewModel, IsDesignTimeCreatable=True}"

The code behind is as simple as this:

    public ISlotViewModel ViewModel
    {
        get { return (ISlotViewModel)DataContext; }
        set { DataContext = value; }
    }

    public SlotView() // or MainWindow
    {
        InitializeComponent();
        ViewModel = new SlotViewModel();
    }

The only thing missing here is to add a converter in each of your <Image , which will convert a SlotMachineIcon value into the image path.

PS: if you don't have resharper, you may need this class too:

  [AttributeUsage(AttributeTargets.Method)]
  public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
  {
    public NotifyPropertyChangedInvocatorAttribute() { }
    public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
    {
      ParameterName = parameterName;
    }

    [CanBeNull] public string ParameterName { get; }
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM