Random weighting in C#
I'm creating a game in C#, and a crucial part of the game is randomness. It's essentially a wrestling simulator, where 'moves' are selected on 开发者_JS百科a table depending on a number of factors, such as wrestlers attributes and current momentum.
Then out of all the moves that that match this criteria, a random one is selected to be executed using the Random object and Skip/Take in LINQ But this is really not enough. What I want to do is weight moves probability of being chosen (I already have a column on the moves table for this, for an integer between 1 and 100). How would I implement this weighting into my random row selection?
this is not too difficult. I have no code to work with so I assume you have objects Move
with an attribute Weight
inside a array and all weights sum up to 100.0 (don't really matter).
Now you sort the array by weights decending, pick a random number between 0 and 99 and iterate through all this decreasing your random number. As soon as it not positive anymore you stop and pick the current index/move
var value = rnd.NextDouble()*100.0;
foreach(var move in moves.OrderByDescending(m => m.Weight))
{
value -= move.Weight;
if (value <= 0) return move;
}
of course you can cache the ordering or even the picks into a big array and use a random-index into this (for performance) but the priciple should be clear I hope.
As George suggested - here is a version where you can drop to assume that the weights sum up to 100:
double _weightSum;
...
// initialize the Sum somewhere
_weightSum = moves.SumBy(m => m.Weight);
Move GetRandomMove()
{
var value = rnd.NextDouble()*weightSum;
foreach(var move in moves.OrderByDescending(m => m.Weight))
{
value -= move.Weight;
if (value <= 0) return move;
}
}
Some code would be helpful for me to understand exactly what you need, but I suggest using the Random .NET library function. Some documentation can be found here:
http://msdn.microsoft.com/en-us/library/system.random.aspx
This example generates 5 random integers
Random rand = new Random();
Console.WriteLine("Five random integer values:");
for (int ctr = 0; ctr <= 4; ctr++)
Console.Write("{0,15:N0}", rand.Next());
Console.WriteLine();
This will seed it with the current time to make it "more random". If you want reproducible tests, you can seed it with a constant while testing.
Add up the total weight of each possible move.
Divide each one by that total, so you've normalized the range to 0..1.
Get a random number in that range. Choose a consistent order for each move and pick the one that the random number is within.
I looked at doing basically the same thing as Carsten, but with Linq.
given moves is a collection of Moves each with an integer property of Weight
public Move PickRandomMove()
{
var allMovesWeight = moves.Sum(m => m.Weight);
// pick a unit of weight at random, then shift it along by the weight of the
// first move so that there will always be an element in the TakeWhile results
var randomPick = new Random().Next(0, allMovesWeight) + moves.First().Weight;
return moves.TakeWhile(move => (randomPick -= move.Weight) > 0).Last();
}
I suspect there's a clearer way of expressing how the TakeWhile is working, but hopefully you get the idea
精彩评论