I have this code but looks like the random is activating the same gameobject. How can I be sure that not the same will be activated? For example, this code activate 2 gameobjects but sometimes just only one, because the random picked the same gameobjects twice.
for (int i = 0; i < 2; i++)
{
int index = Random.Range(0, EyeButtonArray.Length);
EyeButtonArray[index].SetActive(true);
}
You can shuffle your container randomly and pick the first two elements.
int wanted = 2;
int[] nums = System.Linq.Enumerable.Range(0, EyeButtonArray.Length).ToArray();
System.Random rnd = new System.Random();
nums = nums.OrderBy(x => rnd.Next()).ToArray();
for(int x = 0; x < 2; ++x)
EyeButtonArray[nums[x]].SetActive(true);
Another solution using a HashSet is to keep track of the elements you have picked and keep generating until you pick a unique one.
HashSet<int> RandomElementIdx = new HashSet<int>();
int picked = 0;
int wanted = 2;
while(picked < wanted)
{
int index = Random.Range(0, EyeButtonArray.Length);
if(RandomElementIdx.Contains(index))
continue;
RandomElementIdx.Add(index);
EyeButtonArray[index].SetActive(true);
++picked;
}
The above snippet is assuming your list is greater than or equal to wanted. If it is less, it will result in an infinite loop. It is also not exactly efficient as it will continually try to spawn new numbers until it randomly hits one not used.
It sounds like what you want is
You have a list/array of objects
You want to enable the next two random ones of them
(optionally - not sure on this) you want to disable the two current ones
You could simply "shuffle" your array and then enable the two first of the now shuffled items like e.g.
using System.Linq;
...
// OPTIONAL (but I thought makes sense)
// store the currently enabled objects to also be able to disable them later
// when you enable the next pair
private GameObject[] _currentEnabled;
public void EnableTwoRandom()
{
// OPTIONAL (but I thought makes sense)
// if exists first disable the last selected two buttons
if(_currentEnabled!=null)
{
foreach(var item in _currentEnabled)
{
item.SetActive(false);
}
}
// Pick two random buttons
// This first "shuffles" the buttons and then simply takes the
// first two random elements
_currentEnabled = EyeButtonArray.OrderBy(i => Random.value).Take(2).ToArray();
// enable the new two random buttons
foreach(var item in toEnable)
{
item.SetActive(true);
}
}
See
IEnumerable.OrderBy
Random.value
IEnumerable.Take
if additionally you want to be sure that the new selected buttons are also not equal to the previous ones you could add
// Pick two random buttons
if(_currentEnabled != null)
{
// if there already exists a previous selection
// then exclude this selection from the available options first
_currentEnabled = EyeButtonArray.Except(_currentEnabled).OrderBy(i => Random.value).Take(2).ToArray();
}
else
{
_currentEnabled = EyeButtonArray.OrderBy(i => Random.value).Take(2).ToArray();
}
See
IEnumerable.Except
Related
I've just started my first little project in C# and with WinForms and I am stuck on this one feature for a few days now..
I have an Array with about 60 PictureBoxes in it and when i press a button, I want it to pick one random out of these, but not twice in a row.
I guess I am searching for something like that:
static Random rnd = new Random();
int lastPick;
if (checkBox1.Checked == true)
{
int RandomPick = rnd.Next(pictureBoxArray.Length);
lastPick = RandomPick;
PictureBox picBox = pictureBoxArray[RandomPick **- lastPick**];
picBox.BorderStyle = BorderStyle.FixedSingle;
}
I've also tried to create a List containing my last Pick and tried to use this, but it also didn't work and it gave me an Out of Range Exception.
static Random rnd = new Random();
int lastPick;
List<int> lastNumber = new List<int>();
if (checkBox1.Checked == true)
{
int RandomPick = rnd.Next(pictureBoxArray.Length);
lastPick = RandomPick;
lastNumber.Add(lastPick);
PictureBox picBox = pictureBoxArray[RandomPick - lastNumber.Count];
picBox.BorderStyle = BorderStyle.FixedSingle;
}
Any help or tips to get into the right direction would be appreciated
I feel like you are overcomplicating a problem. You can simply store the latest index in a variable (like you are doing), and then generate a random number until it is something else than the one in the variable. Here's an example code snippet:
int lastPick;
while (true) {
int randomPick = rnd.Next(length);
if (randomPick != lastPick) {
lastPick = randomPick;
// Do things here.
break; // This breaks the loop.
}
// If the previous if-statement was false, we ended
// up with the same number, so this loop will run again
// and try a new number
}
You are close, just pick randomly until the new pick is not the same as the previous one.
int lastPick = -1;
int randomPick = -1;
if (checkBox1.Checked == true)
{
while (randomPick == lastPick)
{
randomPick = rnd.Next(pictureBoxArray.Length);
}
lastPick = randomPick;
PictureBox picBox = pictureBoxArray[randomPick];
picBox.BorderStyle = BorderStyle.FixedSingle;
}
Since the other answers use while loops, I wanted to present a way to do this without a while loop. Create a list of indices initialized to contain all possible indices into your array. This solution requires System.Linq.
Initialize your previous chosen index to -1.
int lastChosenIndex = -1;
Create a list of all possible indices into your array.
List<int> indicesList = Enumerable.Range(0, pictureBoxArray.Length).ToList();
Now when you want an index into your array, you get the index from the indices list.
var randomIndex = random.Next(indicesList.Count - 1);
var randomItem = pictureBoxArray[indicesList[randomIndex]];
We are going to remove this chosen index from the indices list so it cannot be chosen again. First we need to add back the previously removed index (if it is not -1), since it is now a valid selection.
if (lastChosenIndex > -1)
// Use Add so the index into this list doesn't change position
indicesList.Add(lastChosenIndex);
lastChosenIndex = indicesList[randomIndex];
// by removing the index at this position, there is no way to choose it a second time
indicesList.RemoveAt(randomIndex);
The nice thing is, if you wanted to never show a duplicate, you can remove the last chosen index code and it will never show a duplicate. This is a bit long winded compared to the other answers, but wanted to show there is an alternative to using brute force with a while loop.
This question already has answers here:
Most efficient way to randomly "sort" (Shuffle) a list of integers in C#
(13 answers)
Closed 5 years ago.
I need to create a random array of int with certain parameters.
int[] test = new int[80];
Random random = new Random();
I need to assign 1's and 0's, randomly to the array. I know how to do that.
test[position] = Random.Next(0,2);//obviously with a for loop
But I need to have exactly 20 1's, but they need to be randomly positioned in the array. I don't know how to make sure that the 1's are randomly positioned, AND that there are exactly 20 1's. The rest of the positions in the array would be assigned 0.
I think you need to turn your thinking around.
Consider:
var cnt = 20;
while (cnt > 0) {
var r = Random.Next(0, 80);
if (test[r] != 1) {
test[r] = 1;
cnt--;
}
}
Expanding explanation based on comments from CodeCaster. Rather than generate a random value to place in the array, this code generates and index to set. Since C# automatically initializes the test array to 0 these values are already set. So all you need is to add your 1 values. The code generates a random index, tests it to see if it isn't 1, if so it sets the array element and decrements a count (cnt). Once count reaches zero the loop terminates.
This won't properly function if you need more values than 0 and 1 that is true. Of course the questions explicitly stated that these were the needed values.
"This causes horrible runtime performance". What!? Can you produce any prove of that? There is a chance that the index generated will collide with an existing entry. This chance increases as more 1's are added. Worst case is there is a 19/80 (~23%) chance of collision.
If you know you need exactly 20 of one value, a better way to go about this is to pre-populate the array with the required values and then shuffle it.
Something like this should work:
int[] array = new int[80];
for (int i = 0; i < 80; i++)
{
int val = 0;
if (i < 20)
{
val = 1;
}
array[i] = val;
}
Random rnd = new Random();
int[] shuffledArray = array.OrderBy(x => rnd.Next()).ToArray();
You can do
for (int i = 0; i < 20; i++)
{
var rand = random.Next(0,80);
if (test[rand] == 1)
{
i--;
continue;
}
test[rand] = 1;
}
Remaining are automatically zero.
I have the following code :
int GetRandNumber()
{
Random r = new Random();
return r.Next() % 6 + 1;
}
I am developing a windows store app. I have a textbox in which a single number from 1 to 6 is generated. For every number I have made a specific event. I need to make sure that every number is generated only once so that the events do not repeat.
Looks like you just need to shuffle numbers. I'd suggest to create an array of numbers from 1 to 6 and just shuffle the array. You can see some code here for how to shuffle an array/list.
First of all you need to be careful with this implementation, if you call GetRandomNumber() multiple times very close together it will give you the same result. A better function would be
int GetRandNumber(Random r)
{
return r.Next(1, 7); //This does the same as "r.Next() % 6 + 1" but removes bias.
}
//used like
Random rand = new Random();
foreach(var foo in bar)
{
//The same instance of rand is used for each call instead of a new one each time.
foo.SomeProp = GetRandNumber(rand);
}
However that is completely separate from what you need to do, you should not be generating random numbers between 1 though 6. What you need to do is make a list of 1 though 6 then shuffle the list, not use random numbers at all (well you will likely use them during the shuffle but that is a implementation detail)
List<int> numbers = new List<int>();
for(int i = 1; i <= 6; i++)
{
numbers.Add(i);
}
MyShuffleListFunction(numbers);
There are plenty of examples on this site on how to make a shuffle function.
Maybe I'm wrong, but as I understand you want something like this as an output:
344213266125
To achieve this you should keep track which numbers are already generated, and stop if all has been "rolled" at least once.
So let's have a bool array initialized to 6 false values, and after each random number generation (after each roll) set the array's corresponing element to true. Check if there are any false values in the array, if not then "roll again".
Also, you might consider generating the numbers according to the other answers, as they are of course right: this is not an ideal way.
Update
Okay, based on your question's update I also update my pseudocode (or maybe I should call this prose):
Keep track of the already "rolled" numbers (as above). When generating a new number check if it has been already generated or not. If it's been rolled before, then roll again, until you roll a new one: then update the array accordingly. Also be sure to check if there is a valid number or all has been rolled before at the beginning of the roll...
But now it seems for me that what you really are looking for is simply a shuffle, which has been proposed in other answers.
Perhaps this will work for you. The approach is to record the random results in a List and to skip the output if the results already exists in the list.
class Program
{
static int tmp;
static void Main(string[] args)
{
List<int> alreadyFired = new List<int>();
while (alreadyFired.Count != 6)
{
Random rng = new Random();
int diceresult = rng.Next(1,7);
foreach (var item in alreadyFired)
{
if (item == diceresult)
{
tmp = item;
}
}
if (!(tmp == diceresult))
{
alreadyFired.Add(diceresult);
Console.WriteLine(diceresult);
}
}
Console.WriteLine("no events left");
Console.ReadKey();
}
}
I am working on a game in c# but that detail is not really neccessary to solve my problem.
At I high level here is what I want:
I have a set that could have any number of items in it.
I want to randomly select 10 items from that set.
If the set has less than 10 items in then I expect to select the same
item more than once.
I want to ensure every item is selected at least once.
What would be the algorithm for this?
Sorry I'm not sure if this is an appropriate place to ask, but I've got no pen and paper to hand and I can't quite get my head round what's needed so appreciate the help.
In addition I might also want to add weights to the items to
increase/decrease chance of selection, so if you are able to
incorporate that into your answer that would be fab.
Finally thought I should mention that my set is actually a List<string>, which might be relevent if you prefer to give a full answer rather than psuedo code.
This is what I use to randomize an array. It takes an integer array and randomly sorts that list a certain amount of times determined by the random number (r).
private int[] randomizeArray(int[] i)
{
int L = i.Length - 1;
int c = 0;
int r = random.Next(L);
int prev = 0;
int curr = 0;
int temp;
while (c < r)
{
curr = random.Next(0, L);
if (curr != prev)
{
temp = i[prev];
i[prev] = i[curr];
i[curr] = temp;
c++;
}
}
return i;
}
If you look for effective code, my answer isnt it. In theory, create some collection you can remove from that will mirror your set. Then select random member of the object from it ...and remove, this will garantee items wont repeat(if possible).
Random rnd = new Random();
List<int> indexes = new List<int>(items.Count);
for (int i = 0; i < items.Count; i++)
indexes.Add(i);
List<string> selectedItems = new List<string>(10);
int tmp;
for(int i = 0; i < 10; i++)
{
tmp = rnd.Next(1,10000); //something big
if(indexes.Count > 0)
{
selectedItems.Add(yourItems[indexes[tmp%indexes.Count]]);
indexes.RemoveAt(tmp%indexes.Count);
}
else
selectedItems.Add(yourItems[rnd.Next(0,9)]); //you ran out of unique items
}
where items is your list and yourItems is list of selected items, you dont need to store them if you want process them right away
Perhaps shuffle the collection and pick elements from the front until you have the required amount.
Once you've gone through all the elements, you should perhaps shuffle it again, so you don't just repeat the same sequence.
The basic algorithm for shuffling: (in pseudo-code)
for i from n − 1 downto 1 do
j ← random integer with 0 ≤ j ≤ i
exchange a[j] and a[i]
With the above algorithm (or a minor variation), it's possible to just shuffle until you reach the required number of elements, no need to shuffle the whole thing.
I'll start by saying I am very much a beginner programmer, this is essentially my first real project outside of using learning material. I've been making a 'Simon Says' style game (the game where you repeat the pattern generated by the computer) using C# and XNA, the actual game is complete and working fine but while creating it, I wanted to also create a 'top 10' scoreboard. The scoreboard would record player name, level (how many 'rounds' they've completed) and combo (how many buttons presses they got correct), the scoreboard would then be sorted by combo score. This led me to XML, the first time using it, and I eventually got to the point of having an XML file that recorded the top 10 scores. The XML file is managed within a scoreboard class, which is also responsible for adding new scores and sorting scores. Which gets me to the point... I'd like some feedback on the way I've gone about sorting the score list and how I could have done it better, I have no other way to gain feedback =(. I know .NET features Array.Sort() but I wasn't too sure of how to use it as it's not just a single array that needs to be sorted. When a new score needs to be entered into the scoreboard, the player name and level also have to be added. These are stored within an 'array of arrays' (10 = for 'top 10' scores)
scoreboardComboData = new int[10]; // Combo
scoreboardTextData = new string[2][];
scoreboardTextData[0] = new string[10]; // Name
scoreboardTextData[1] = new string[10]; // Level as string
The scoreboard class works as follows:
- Checks to see if 'scoreboard.xml' exists, if not it creates it
- Initialises above arrays and adds any player data from scoreboard.xml, from previous run
- when AddScore(name, level, combo) is called the sort begins
- Another method can also be called that populates the XML file with above array data
The sort checks to see if the new score (combo) is less than or equal to any recorded scores within the scoreboardComboData array (if it's greater than a score, it moves onto the next element). If so, it moves all scores below the score it is less than or equal to down one element, essentially removing the last score and then places the new score within the element below the score it is less than or equal to. If the score is greater than all recorded scores, it moves all scores down one and inserts the new score within the first element. If it's the only score, it simply adds it to the first element. When a new score is added, the Name and Level data is also added to their relevant arrays, in the same way. What a tongue twister. Below is the AddScore method, I've added comments in the hope that it makes things clearer O_o. You can get the actual source file HERE. Below the method is an example of the quickest way to add a score to follow through with a debugger.
public static void AddScore(string name, string level, int combo)
{
// If the scoreboard has not yet been filled, this adds another 'active'
// array element each time a new score is added. The actual array size is
// defined within PopulateScoreBoard() (set to 10 - for 'top 10'
if (totalScores < scoreboardComboData.Length)
totalScores++;
// Does the scoreboard even need sorting?
if (totalScores > 1)
{
for (int i = totalScores - 1; i > - 1; i--)
{
// Check to see if score (combo) is greater than score stored in
// array
if (combo > scoreboardComboData[i] && i != 0)
{
// If so continue to next element
continue;
}
// Check to see if score (combo) is less or equal to element 'i'
// score && that the element is not the last in the
// array, if so the score does not need to be added to the scoreboard
else if (combo <= scoreboardComboData[i] && i != scoreboardComboData.Length - 1)
{
// If the score is lower than element 'i' and greater than the last
// element within the array, it needs to be added to the scoreboard. This is achieved
// by moving each element under element 'i' down an element. The new score is then inserted
// into the array under element 'i'
for (int j = totalScores - 1; j > i; j--)
{
// Name and level data are moved down in their relevant arrays
scoreboardTextData[0][j] = scoreboardTextData[0][j - 1];
scoreboardTextData[1][j] = scoreboardTextData[1][j - 1];
// Score (combo) data is moved down in relevant array
scoreboardComboData[j] = scoreboardComboData[j - 1];
}
// The new Name, level and score (combo) data is inserted into the relevant array under element 'i'
scoreboardTextData[0][i + 1] = name;
scoreboardTextData[1][i + 1] = level;
scoreboardComboData[i + 1] = combo;
break;
}
// If the method gets the this point, it means that the score is greater than all scores within
// the array and therefore cannot be added in the above way. As it is not less than any score within
// the array.
else if (i == 0)
{
// All Names, levels and scores are moved down within their relevant arrays
for (int j = totalScores - 1; j != 0; j--)
{
scoreboardTextData[0][j] = scoreboardTextData[0][j - 1];
scoreboardTextData[1][j] = scoreboardTextData[1][j - 1];
scoreboardComboData[j] = scoreboardComboData[j - 1];
}
// The new number 1 top name, level and score, are added into the first element
// within each of their relevant arrays.
scoreboardTextData[0][0] = name;
scoreboardTextData[1][0] = level;
scoreboardComboData[0] = combo;
break;
}
// If the methods get to this point, the combo score is not high enough
// to be on the top10 score list and therefore needs to break
break;
}
}
// As totalScores < 1, the current score is the first to be added. Therefore no checks need to be made
// and the Name, Level and combo data can be entered directly into the first element of their relevant
// array.
else
{
scoreboardTextData[0][0] = name;
scoreboardTextData[1][0] = level;
scoreboardComboData[0] = combo;
}
}
}
Example for adding score:
private static void Initialize()
{
scoreboardDoc = new XmlDocument();
if (!File.Exists("Scoreboard.xml"))
GenerateXML("Scoreboard.xml");
PopulateScoreBoard("Scoreboard.xml");
// ADD TEST SCORES HERE!
AddScore("EXAMPLE", "10", 100);
AddScore("EXAMPLE2", "24", 999);
PopulateXML("Scoreboard.xml");
}
In it's current state the source file is just used for testing, initialize is called within main and PopulateScoreBoard handles the majority of other initialising, so nothing else is needed, except to add a test score.
I thank you for your time!
As a programming tip, you should replace the 3 different arrays with a single array of Score Objects, each of these objects would have all 3 properties you mentioned (Name, Level and Score). You can then create a comparator-like function (based on the score attribute) for it which can be used to sort it via the Arrays.Sort() method.
If you want to keep you current 3 arrays then you can look up sorting algorithms here: http://en.wikipedia.org/wiki/Sorting_algorithm#Comparison_of_algorithms and just make it so that any changes are done to the 3 arrays simultaneously instead of one by one (since you keep the data in them synchronized by index).
You can consider placing your user name, level, and score in a separate class (let's call it ScoreBoardEntry) that inherits from IComparable and then write a comparator. This will allow you to use the built in sort function of List and will be a neater solution than just writing your own.
Your code should look something like this:
1) Create/load a List < ScoreBoardEntry> with all the previous scores
2) push the new score into it
3) Sort List
4) Print only first 10 entries
I wrote some code for you. I've been learning some new-ish type C# stuff like linq, IEnumerable, and yield and kind of wanted to put them together, so hopefully it helps.
you might like the snippet editor i used - linqpad - it's good for learning/experimenting
void Main()
{
string [] playerNames = {"Aaron", "Rick", "Josie"};
var sim = new GameSimulator(playerNames, 1000000, 5000);
var scores = sim.GetScores(5);
var sorted = scores.OrderBy(c=>c.Score).Reverse();
var top = sorted.Take(2);
// uncomment if using linq pad
//scores.Dump("scores");
//sorted.Dump("sorted");
//top.Dump("top");
}
class GameSimulator
{
public GameSimulator(string [] playerNames, int maxScore, int maxLevel)
{
this.playerNames = playerNames;
this.maxScore = maxScore;
this.maxLevel = maxLevel;
}
private string [] playerNames;
private int maxScore;
private int maxLevel;
public IEnumerable<ScoreBoardEntry> GetScores(int numGames)
{
for (int i = 0; i < numGames; i++)
{
string randomName = playerNames[random.Next(0, playerNames.Length-1)];
int randomScore = random.Next(1, maxScore);
int randomLevel = random.Next(1, maxLevel);
yield return new ScoreBoardEntry(randomName, randomLevel, randomScore);
}
}
static private Random random = new Random();
}
class ScoreBoardEntry
{
public ScoreBoardEntry(string playerName, int levenNumber, int score)
{
PlayerName = playerName;
LevelNumber = levenNumber;
Score = score;
}
public string PlayerName { set; get; }
public int LevelNumber { set; get; }
public int Score { set; get; }
}