Alex Alex - 3 months ago 14
C# Question

Select random, unique, images from folder on button click

I'm working on a C# card game where I want images to be randomly selected on a button click. Once a card has been chosen, it has to be displayed to the user, and something has to happen so it can't be selected again. I got the first part down, a random image is selected and is shown to the user, but I can't make it so it can't be selected again. Sometimes a card is picked multiple times, sometimes no image is selected at all and I get the error image. This is the code so far.

public partial class Form1 : Form
{
private List<int> useableNumbers;

public Form1()
{
InitializeComponent();
// Creates a list of numbers (card names) that can be chosen from
useableNumbers = new List<int>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54};
settings = new Settings();
}

private void btnDrawCard_Click(object sender, EventArgs e)
{
this.setImage();
}

private void setImage()
{
// Checks if there are still numbers left in the List
if (useableNumbers.Count() == 0)
{
MessageBox.Show("The game has ended");
Application.Exit();
}
else
{
Random r = new Random();
int i = r.Next(useableNumbers.Count());
// Looks for the path the executable is in
string path = System.IO.Path.GetDirectoryName(Application.ExecutablePath) + @"\images\";
// Looks up the image in the images folder, with the name picked by the Random class and with the extension .png
string image = path + i + ".png";
// Sets the image in the pictureBox as the image looked up
pictureBox1.ImageLocation = image;
// Removes the selected image from the List so it can't be used again
useableNumbers.RemoveAt(i);
}
}

private void quitToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}

private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
{
settings.Show();
}


To clarify it a little bit; I have a folder called 'images' in the same folder as the executable. Inside that folder, there are 54 images named '1' to '54' (52 normal cards and two jokers). The numbers in the
useableNumbers
List represent the imagenames inside that folder. When an image is selected, I want to remove that image's name from the List with
useableNumbers.RemoveAt(i);
. Although I do get the message 'The game has ended', I also get the aforementioned problems.

I feel like the
useableNumbers.RemoveAt(i);
doesn't change the indexes of the List, so when, say, '10' gets deleted, it keeps an index of 11 in stead of moving all values down by one, if you know what I mean.

I've also tried storing the images in a List, but couldn't get that to work either so that's why I did it like this. Still new to C#, so maybe there are better ways of doing it.

How can I fix the removing from the list, so I don't get the same image twice or more, or no image at all?

Answer

Instead of this, which gets a random number between 0 and the current size of the usable numbers,

int i = r.Next(useableNumbers.Count());

I would do this instead,

int i = useableNumbers[r.Next(useableNumbers.Count())];

So this chooses from one of the remaining usable numbers instead up to the current size. At the moment you can have duplicates, because, you're just reducing the range of the numbers to pick from - at the start it's zero to 53, but after half way you reduce to half that range and duplicates become more likely. At the end you're guaranteed to get 1. The range shouldn't be changing, you always want a random number of 1 - 54, just not one that has already been chosen. So this new way you're getting a random index into the list to retrieve a value, not just getting a random index and taking that as your value.

Similarly instead of this,

useableNumbers.RemoveAt(i);

Do this,

useableNumbers.RemoveAt(useableNumbers.IndexOf(i));

Which will then remove that number from the list, not the value that is at that position in the list.