NationStates Jolt Archive


Need help from a fellow programmer!

Neo Bretonnia
16-07-2008, 21:48
So I wanted to be able to determine configurations and loadouts for my units and experiment with different enemies, so I wrote a program to simulate hand to hand fights between units in Warhammer.

Alright so my program is pretty much done, but there seems to be a problem with the random number generator I'm using in my code. I've thoroughly checked the program and can find no logic errors, yet when I run the following simulation:

Bretonnian MAAs vs. VC Skeletons

The Bretonnians will refuse to charge (fail leadership) 70% of the time when the Skeletons are wielding spears and 85% of the time when the Skeletons have a hand weapon...

And yet the Leadership test has nothing whatsoever to do with the defending unit's weapon choice...

I think what's happening is the cheesy random number generator that comes with .NET is somehow being influenced by this. Either that, or somehow I'm missing something in code.

Anybody have any ideas on where I can get a decent randomizer?
Extreme Ironing
16-07-2008, 22:17
Define random ;)
Rambhutan
16-07-2008, 22:30
Mersenne twister is supposedly pretty good
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
Myrmidonisia
16-07-2008, 22:38
I always go to the Numerical Recipes book.

http://www.nrbook.com/a/bookcpdf/c7-0.pdf

Not sure if this is the kind of random number generator that you want. I use it to generate shaped noise, not mortal combat.
Hotwife
17-07-2008, 00:44
So I wanted to be able to determine configurations and loadouts for my units and experiment with different enemies, so I wrote a program to simulate hand to hand fights between units in Warhammer.

Alright so my program is pretty much done, but there seems to be a problem with the random number generator I'm using in my code. I've thoroughly checked the program and can find no logic errors, yet when I run the following simulation:

Bretonnian MAAs vs. VC Skeletons

The Bretonnians will refuse to charge (fail leadership) 70% of the time when the Skeletons are wielding spears and 85% of the time when the Skeletons have a hand weapon...

And yet the Leadership test has nothing whatsoever to do with the defending unit's weapon choice...

I think what's happening is the cheesy random number generator that comes with .NET is somehow being influenced by this. Either that, or somehow I'm missing something in code.

Anybody have any ideas on where I can get a decent randomizer?

If you were writing in Java, you could use the one written by CERN, which is probably the best randomizer out there. The library is called Colt 1.2.0, and it's been written up in mathematics journals as the library to use.

But you're fooling with Microsoft stuff, so your out of luck.
Neo Bretonnia
17-07-2008, 01:39
If you were writing in Java, you could use the one written by CERN, which is probably the best randomizer out there. The library is called Colt 1.2.0, and it's been written up in mathematics journals as the library to use.

But you're fooling with Microsoft stuff, so your out of luck.

Well, if they'd publish the source code I could easily transcribe it into C# which is vastly superior to Java :D
Hotwife
17-07-2008, 01:41
Well, if they'd publish the source code I could easily transcribe it into C# which is vastly superior to Java :D

Obviously CERN doesn't think so.
Neo Bretonnia
17-07-2008, 03:44
Obviously CERN doesn't think so.

Oh, well if CERN doesn't think so I better go tell my boss so we can switch the whole company over from .NET to Java.
Hotwife
17-07-2008, 03:47
Oh, well if CERN doesn't think so I better go tell my boss so we can switch the whole company over from .NET to Java.

I've been brought in to switch an entire set of failed software projects from .NET to Java.
Neo Bretonnia
17-07-2008, 05:04
I've been brought in to switch an entire set of failed software projects from .NET to Java.

Do you have to turn even the lightest threads into a pissing match?
Neo Bretonnia
17-07-2008, 05:06
So anyway thanks to those with useful replies. I hope to get back to you with good news. In the meantime, anybody want to help me test this puppy?
Myrmidonisia
17-07-2008, 13:37
I've been brought in to switch an entire set of failed software projects from .NET to Java.
And neither of those give you the raw control of machine language... We've created a whole generation of programmers that don't have the faintest idea of how a computer works.
Hotwife
17-07-2008, 13:38
Do you have to turn even the lightest threads into a pissing match?

You're the one who started with the comments about .NET being uber.

I have every right to disagree.
Hotwife
17-07-2008, 13:40
And neither of those give you the raw control of machine language... We've created a whole generation of programmers that don't have the faintest idea of how a computer works.

True.

I've found a lot of people who are "programmers" who end up failing because they didn't understand basic things like "atomicity of transactions".

I like the oral questions they asked at Johns Hopkins for the MS in CS (just to get in).

1. What is the minimum number of instructions necessary to run a serial computer?

2. What are they?

3. Why?

You have 30 minutes to explain. You get a whiteboard and you get to talk.

A lot of people who do really well on their application in terms of grades and test scores draw a complete blank when asked these questions.
Velka Morava
17-07-2008, 14:10
Ahem, I assume you are using the Random() function...
Try to put a fixed seed and see what happens...
Neo Bretonnia
17-07-2008, 14:10
And neither of those give you the raw control of machine language... We've created a whole generation of programmers that don't have the faintest idea of how a computer works.

There's definitely a lack of emphasis in the educational track. When I was working on my degree one of the classes was a computer architecture course. The level of detail was pathetic and all one had to do to get an 'A' in the course was to fill in the quiz as he lectured.

There was so much information to be learned there, I think on some level this was the only way the instructor could cope with having to cover an amount of information that should really have been split into two or more courses.
Neo Bretonnia
17-07-2008, 14:11
You're the one who started with the comments about .NET being uber.

I have every right to disagree.

Actually, you said this first:

If you were writing in Java, you could use the one written by CERN, which is probably the best randomizer out there. The library is called Colt 1.2.0, and it's been written up in mathematics journals as the library to use.

But you're fooling with Microsoft stuff, so your out of luck.

/pissing match
Neo Bretonnia
17-07-2008, 14:12
Ahem, I assume you are using the Random() function...
Try to put a fixed seed and see what happens...

I'll try that, but I've also been advised to use some kind of thread pause, since with .NET the randomizer seeds from the current system time, and a fast enough processor might be generating the same set of pseudo random numbers for separate instances of the Random object. I'll try it both ways and see what happens.
Hotwife
17-07-2008, 14:18
/pissing match

It's not a pissing match. I have every right to disagree, and I see nothing wrong with the manner in which I disagreed.

Just because I don't agree with you doesn't make it a pissing match.

I don't see you providing any world-class examples of add-ons for C#, either.

This isn't a forum for patting Neo B on the back, saying, "well done you little genius". I'm not here to stroke your ego.
Neo Bretonnia
17-07-2008, 14:35
Ok so I've done the following:

Seeded the random number generator with a fixed Int value and
Let it default seed form the system clock but added a 5ms sleep for the thread after each time.

I'm still getting pretty consistent results at 72% charge failure vs. 84% over 10,000 battles.

This is really bizarre.
Velka Morava
17-07-2008, 14:52
I'll try that, but I've also been advised to use some kind of thread pause, since with .NET the randomizer seeds from the current system time, and a fast enough processor might be generating the same set of pseudo random numbers for separate instances of the Random object. I'll try it both ways and see what happens.

He He... You definitively should read the help files.
Remarks
If your application requires different random number sequences, invoke this constructor repeatedly with different seed values. One way to produce a unique seed value is to make it time-dependent. For example, derive the seed value from the system clock.

However, if your application runs on a fast computer the system clock might not have time to change between invocations of this constructor; the seed value might be the same for different instances of Random. In that case, apply an algorithm to differentiate the seed value in each invocation.

For instance, the following C# expressions use a bitwise complement operation to generate two different seed values even if the system time value is the same.

Copy Code
Random rdm1 = new Random(unchecked((int)DateTime.Now.Ticks));
Random rdm2 = new Random(~unchecked((int)DateTime.Now.Ticks));
Neo Bretonnia
17-07-2008, 14:55
He He... You definitively should read the help files.

Yeah when I went to read those the installer wanted the disk, which I don't have access to ATM :p

Inevitably I'll find out it has nothing to do with the randomizer and there's some other issue with the code... just watch...
Velka Morava
17-07-2008, 14:57
Ok so I've done the following:

Seeded the random number generator with a fixed Int value and
Let it default seed form the system clock but added a 5ms sleep for the thread after each time.

I'm still getting pretty consistent results at 72% charge failure vs. 84% over 10,000 battles.

This is really bizarre.

The way I see it, you have some flaw in your code that makes the weapon play some role...
The way I would cope with the problem is having the program show the actual generated numbers and see if there are any discrepancies between the two result sets...
Velka Morava
17-07-2008, 14:59
Yeah when I went to read those the installer wanted the disk, which I don't have access to ATM :p

Inevitably I'll find out it has nothing to do with the randomizer and there's some other issue with the code... just watch...

If you are using Express i can give you a link to the DVD image...
It's free from Microsoft :eek:
Neo Bretonnia
17-07-2008, 15:02
If you are using Express i can give you a link to the DVD image...
It's free from Microsoft :eek:

Thankya, although this is a work computer so I just need to bug the support guy when I catch him to get the disk and/or the image from our share.
Myrmidonisia
17-07-2008, 15:03
Inevitably I'll find out it has nothing to do with the randomizer and there's some other issue with the code... just watch...
If you can isolate the randomizer and test it alone, you have confidence that it was either okay or defective, then you can move on. Better to bisect the problem and narrow the scope as early as you can, with as little effort as possible.
Velka Morava
17-07-2008, 15:04
Ok so I've done the following:

Seeded the random number generator with a fixed Int value and
Let it default seed form the system clock but added a 5ms sleep for the thread after each time.

I'm still getting pretty consistent results at 72% charge failure vs. 84% over 10,000 battles.

This is really bizarre.

Try the fixed seed trick. As the generated number is the same in all cases you should immediately see if the number changes (pseudorandom numbers in fact, that's why you randomize timer... Randomize timer? Gosh, BASIC!!!).
Velka Morava
17-07-2008, 15:07
If you can isolate the randomizer and test it alone, you have confidence that it was either okay or defective, then you can move on. Better to bisect the problem and narrow the scope as early as you can, with as little effort as possible.

Well, if he's using the Random class it would mean dissecting the mscorlib.dll...
Kind of timeconsuming IMO.
Neo Bretonnia
17-07-2008, 15:08
The way I see it, you have some flaw in your code that makes the weapon play some role...
The way I would cope with the problem is having the program show the actual generated numbers and see if there are any discrepancies between the two result sets...

Yeah I keep thinking the same but here's the relevant section of code where the leadership test is rolled:

(The defender is fear causing, the charger is not, and the defender is immune to psych)

private bool declareCharge()
{
chargerFearCausing = this.chkChargerCauseFear.Checked;
defenderFearCausing = this.chkDefenderCauseFear.Checked;

bool chargerTerrorCausing = this.chkChargerCauseTerror.Checked;
bool defenderTerrorCausing = this.chkDefenderCauseTerror.Checked;
bool chargerImmuneToPsychology = this.chkChargerImmunetoPsych.Checked;
bool defenderImmuneToPsychology = this.chkDefenderImmunetoPsych.Checked;

...

if(defenderImmuneToPsychology)
{
if(defenderFearCausing || defenderTerrorCausing)
{
if(!rollLeadership(Convert.ToInt16(this.txtCLd.Text)))
{
failedCharges ++;
this.lboxReport.Items.Add("Charger failed to charge!");
return false;
}
else
return true;
}
else
return true;
}

return true;
}

private bool rollLeadership(int Leadership)
{
Dice.Dice dice = new Dice.Dice(6,2);
if(dice.Roll() <= Leadership)
return true;
else
return false;
}

The weapon types are radio buttons that aren't referenced here at all.

I think I will add a display to show all the generated numbers for both and see how they compare...
Velka Morava
17-07-2008, 15:11
NNOOOOOO!!!!!

I've got work to do, I was just having a cofee break...
Myrmidonisia
17-07-2008, 15:14
Well, if he's using the Random class it would mean dissecting the mscorlib.dll...
Kind of timeconsuming IMO.
Not that low-level. Test at the easiest level there is, then decide which part needs further testing. It may take the form of just generating two tables of outcomes and seeing if they differ. It may take the form of something else. But bisecting a problem is a way to get your focus on the right part.
Velka Morava
17-07-2008, 15:17
code of the Dice.Dice class?
also the leadership determination code would be useful...
Neo Bretonnia
17-07-2008, 15:22
Alright so predictably, when I put in a fixed seed value it gave me 100% charger wins results.

So when I went back and set a Thread.Sleep(1) in the correct function (gawd)

I now have consistent 79% failure to charge results in both cases of defender weapons.

So it would seem that this pseudo random number generator blows (we knew that) and that the temporary fix is to cripple the program's performance by making it pause for a millisecond every time it rolls dice, which could happen several dozen times in each battle, and I like to run at least a thousand to get a decent statistical average.

So I am still on the lookout for a nicer randomizer to put in my Dice class, but at least this eliminates the rest of the code as being the source of the problem.

Thank you guys for the advice!
Velka Morava
17-07-2008, 15:22
Once again.
Use a fixed seed, not the timer... Or, better yet, just comment out the randomization part of the Dice.Dice (I'm making assumptions here...) and stick there an integer and see what happens.
That way you can go through the code knowing that the results MUST be the same.
Neo Bretonnia
17-07-2008, 15:24
code of the Dice.Dice class?
also the leadership determination code would be useful...

Just if you're curious, here's the relevant function: (Yeah, this is in VB.NET... I originally built this class as a way of getting used to VB.NET when I started this job. Otherwise I always code in C# and I'm even getting away with doing some of my work projects in C# as well ;) )

Public Function Roll() As Integer
Dim total As Integer
Dim i As Integer
Static RandomNumGen As New System.Random
Thread.Sleep(1)
total = 0

For i = 1 To number
total = total + RandomNumGen.Next(1, sides + 1)
Next

Roll = total
End Function

sides and number are member variables for the number of sides on a die and the number of dice to roll. (In this case, initialized to 2d6.)
Neo Bretonnia
17-07-2008, 15:33
Once again.
Use a fixed seed, not the timer... Or, better yet, just comment out the randomization part of the Dice.Dice (I'm making assumptions here...) and stick there an integer and see what happens.
That way you can go through the code knowing that the results MUST be the same.

Yah that's exactly what happened when I put the fixed seed in the right function ;)

My Roll() function is overloaded. The other version takes the sides and number as parameters, and that's the one I had originally fiddled with.
Hotwife
17-07-2008, 17:41
Alright so predictably, when I put in a fixed seed value it gave me 100% charger wins results.

So when I went back and set a Thread.Sleep(1) in the correct function (gawd)

I now have consistent 79% failure to charge results in both cases of defender weapons.

So it would seem that this pseudo random number generator blows (we knew that) and that the temporary fix is to cripple the program's performance by making it pause for a millisecond every time it rolls dice, which could happen several dozen times in each battle, and I like to run at least a thousand to get a decent statistical average.

So I am still on the lookout for a nicer randomizer to put in my Dice class, but at least this eliminates the rest of the code as being the source of the problem.

Thank you guys for the advice!

Can't you access the source code for the randomizer itself?

If not, why not? I tend not to trust code where I can't see the source.
Neo Bretonnia
17-07-2008, 18:22
Can't you access the source code for the randomizer itself?

If not, why not? I tend not to trust code where I can't see the source.

Why would I need to?

What's happening here is this: The Random() object is using the ticks of the system clock as its seed value. If the system is running fast enough (and it is) then repeated instances of the Random() object will happen within a single tick of the system clock, thus seeding them with the same value. Thus, instances of Random() that are created close enough together in time will generate the same string of values.

By putting the thread to sleep for one millisecond each time I instantiate a Random(), I guarantee that each will have its own unique seed value.
Velka Morava
17-07-2008, 18:52
Well, you could generate an array of random results at the beginning of each usage and get rid of the 1 ms sleep time. IRC the rand function should generate around 50 results per seed before reiterating.

Actually, no matter how good your algorithm is you still are getting pseudorandom results.
Rambhutan
17-07-2008, 19:04
You might be better off comparmentalising things by having different random number streams for each character class, rather than them sharing a single stream via the dice class. You could then store and reload each seed used within each class if you needed to replay things for testing purposes. Game coding complete by Mike McShaffry has a nice random number generator class, based on the Mersenne twister, that can provide multiple streams of random numbers.
Rambhutan
17-07-2008, 19:20
#define CMATH_N 624
#define CMATH_M 397
#define CMATH_MATRIX_A 0x9908b0df
#define CMATH_UPPER_MASK 0x80000000
#define CMATH_LOWER_MASK 0x7fffffff

#define CMATH_TEMPERING_MASK_B 0x9d2c5680
#define CMATH_TEMPERING_MASK_C 0xefc60000
#define CMATH_TEMPERING_SHIFT_U(y) (y >> 11)
#define CMATH_TEMPERING_SHIFT_S(y) (y << 7)
#define CMATH_TEMPERING_SHIFT_T(y) (y << 15)
#define CMATH_TEMPERING_SHIFT_L(y) (y >> 18)

#include <iostream>

class CRandom
{
public:
CRandom();

unsigned int Random(unsigned int n);
void SetRandomSeed(unsigned int n);
unsigned int GetRandomSeed();
void Randomize();

private:
unsigned int rseed;
unsigned long mt[CMATH_N];
int mti;
};

CRandom::CRandom()
: rseed(1),
mti(CMATH_N + 1)
{}

unsigned int CRandom::Random(unsigned int n)
{
unsigned long y;
static unsigned long mag01[2] = {0x0, CMATH_MATRIX_A};

if (n == 0)
{
return 0;
}

if (mti >= CMATH_N)
{
int kk;

if (mti == CMATH_N + 1)
{
SetRandomSeed(4357);
}

for (kk = 0; kk < CMATH_N - CMATH_M; kk++)
{
y = (mt[kk]&CMATH_UPPER_MASK)|(mt[kk+1]&CMATH_LOWER_MASK);
mt[kk] = mt[kk+CMATH_M] ^ (y >> 1) ^ mag01[y & 0x1];
}

for (; kk < CMATH_N - 1; kk++)
{
y = (mt[kk]&CMATH_UPPER_MASK)|(mt[kk+1]&CMATH_LOWER_MASK);
mt[kk] = mt[kk+(CMATH_M - CMATH_N)] ^ (y >> 1) ^ mag01[y & 0x1];
}

y = (mt[CMATH_N - 1]&CMATH_UPPER_MASK)|(mt[0]&CMATH_LOWER_MASK);
mt[CMATH_N - 1] = mt[CMATH_M - 1] ^ (y >> 1) ^ mag01[y & 0x1];

mti = 0;
}

y = mt[mti++];
y ^= CMATH_TEMPERING_SHIFT_U(y);
y ^= CMATH_TEMPERING_SHIFT_S(y) & CMATH_TEMPERING_MASK_B;
y ^= CMATH_TEMPERING_SHIFT_T(y) & CMATH_TEMPERING_MASK_C;
y ^= CMATH_TEMPERING_SHIFT_L(y);

return (y % n);
}

void CRandom::SetRandomSeed(unsigned int n)
{
mt[0] = n & 0xffffffff;
for (mti = 1; mti < CMATH_N; mti++)
{
mt[mti] = (69069 * mt[mti - 1]) & 0xffffffff;
}
rseed = n;
}

unsigned int CRandom::GetRandomSeed()
{
return rseed;
}

void CRandom::Randomize()
{
SetRandomSeed(time(NULL));
}

int main()
{
CRandom r;
r.Randomize();
std::cout << r.Random(100) << std::endl;
return 0;
}
Neo Bretonnia
17-07-2008, 19:35
Woof. been awhile since I've read C++ code but that's no obstacle :)

Thanks! Since I have a separate Dice class I should be able to integrate this into it nicely.
Velka Morava
17-07-2008, 19:36
Copy/Paste/Save ;)
Rambhutan
17-07-2008, 21:12
Woof. been awhile since I've read C++ code but that's no obstacle :)

Thanks! Since I have a separate Dice class I should be able to integrate this into it nicely.

C# implementation of the same method here
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/mt19937ar.cs
Neo Bretonnia
17-07-2008, 21:27
C# implementation of the same method here
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/mt19937ar.cs

WOOHOO! Thanks!
Neo Bretonnia
17-07-2008, 21:32
C# implementation of the same method here
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/VERSIONS/C-LANG/mt19937ar.cs

So now here's the question... In looking at the code I see it still uses the system clock as a seed. Wouldn't it still have the same problem if it's initialized more than once within the same tick?
Rambhutan
17-07-2008, 22:07
So now here's the question... In looking at the code I see it still uses the system clock as a seed. Wouldn't it still have the same problem if it's initialized more than once within the same tick?

Are you re-seeding everytime you need a random number? If so don't -just do it once at the start then just let it generate numbers as you need them.

At this point I should say I am not a great programmer - I just have quite a few games programming books.
Rambhutan
18-07-2008, 00:52
Insomnia so I may be at cross purposes to what you are asking. The PRNG, once initialised with a seed, will give you a pseudo random number every time you ask it for one (without the need for a new seed). If you re-initialise it with a new seed everytime you want a random number, the numbers you get will only be as random as the seeds are random - and depending on how you get the seeds that will be a problem. So if you build the code for the CRandom class into your dice class you would initialise with a seed only once when the program starts. In your dice class you would have a member function that takes as a parameter something like numberOfDiceSides which would be used like this

CRandom r;
r.Randomize();
unsigned int num = r.Random(numberOfDiceSides);

and this should return a random number in the range 0 to (numberOfDiceSides -1)

Or is the problem you are having that the PRNG is always being seeded with the same number?
Neo Bretonnia
18-07-2008, 03:44
Insomnia so I may be at cross purposes to what you are asking. The PRNG, once initialised with a seed, will give you a pseudo random number every time you ask it for one (without the need for a new seed). If you re-initialise it with a new seed everytime you want a random number, the numbers you get will only be as random as the seeds are random - and depending on how you get the seeds that will be a problem. So if you build the code for the CRandom class into your dice class you would initialise with a seed only once when the program starts. In your dice class you would have a member function that takes as a parameter something like numberOfDiceSides which would be used like this

CRandom r;
r.Randomize();
unsigned int num = r.Random(numberOfDiceSides);

and this should return a random number in the range 0 to (numberOfDiceSides -1)

Or is the problem you are having that the PRNG is always being seeded with the same number?

Actually you're right because what I had been doing was creating a whole new instance of the Random object each time I needed to roll the dice. I think I'm going to redesign my dice class to use one and persist the seed.
Myrmidonisia
18-07-2008, 03:51
Actually you're right because what I had been doing was creating a whole new instance of the Random object each time I needed to roll the dice. I think I'm going to redesign my dice class to use one and persist the seed.
Isn't this the way it always seems to be? The most perplexing problems are caused by some simple oversight. We need to figure out a way to implement the law of parsimony when debugging a piece of code.
Neo Bretonnia
18-07-2008, 13:20
Isn't this the way it always seems to be? The most perplexing problems are caused by some simple oversight. We need to figure out a way to implement the law of parsimony when debugging a piece of code.

Part of it is I wrote my Dice class last year when I first started this job and needed to get used to VB.NET. (Previously I had been working with C#) so I wrote the class in VB.NET as a sort of practice... So I wasn't really putting much thought into the implementation and just learning syntax.

But that's the beauty of encapsulation... I can completely redesign the dice class form the ground up and plug it right back into my simulator!
Hotwife
18-07-2008, 16:31
Why would I need to?

What's happening here is this: The Random() object is using the ticks of the system clock as its seed value. If the system is running fast enough (and it is) then repeated instances of the Random() object will happen within a single tick of the system clock, thus seeding them with the same value. Thus, instances of Random() that are created close enough together in time will generate the same string of values.

By putting the thread to sleep for one millisecond each time I instantiate a Random(), I guarantee that each will have its own unique seed value.

If could see the source code, you could definitely see what it's doing, without guessing, which is what you're doing now by testing and throwing sleep in there.

I can see the source code for the Colt library, and mathematical commentary on the code is also published. For anyone doing math, that gives you a lot of confidence and assurance in code - the code for unit tests is also available.

I know that it does what the documentation says it does, and that it is mathematically sound and reliable.

Counting on, "it does what I think it does because the vendor told me" is not a viable substitute for me. Maybe in a game you can live with it, but when I'm writing code to do random sampling of areas within an image, or doing math to perform Hall edge detection or other methods, it's not viable at all, and the clients I work for will never accept it.
Hotwife
20-07-2008, 18:19
We can see Neo Brettonia's face-value acceptance of code from Microsoft here (something the only the inexperienced take part in).