
출처: http://xkcd.com/221/
포스팅을 시작하기 전에 미려 알려드리고자 할 것이 있습니다. 이 글은 제가 랜덤(random) 에 관해서 오랜 기간 연구하고서 쓰는 것이 아닙니다. 단지 필요에 의해 단기간(네..반나절 정도) 리서치 하고 실무에 적용했던 내용을 나중에 또 삽질하기 싫어서 기록해 두는 것입니다. 그러므로 난수에 대해 조회가 깊은 분들은 이 글을 보고 피식 웃어 넘기시면 됩니다. 다들 뉴비일때가 있는거잖아요. (MT와 WELL알고리즘으로 귀결이 되는 글입니다.)
발단은 이렇습니다. 제목에 있다시피(있어 보이려고 영어로 썼습니다. 난 도시남자니까) 게임에서는 난수를 발생시켜야 할 경우가 많습니다. 아이템이 떨어질 확률, 크리티컬 공격이 나올 확률 등등. 그런데 이 난수들이 예측 가능하거나 난수답지 않으면 문제가 생기죠. 어떤 몬스터가 정확히 11번째 공격에서 궁극 스킬을 사용한다라고 유저들이 예측(학습)해 버리면 그 공격을 모두 피할 것입니다. 10번째에서 살짝 빠져주면 되니까요. 난수를 넣는다고 넣었는데 예측이 된다거나 특정 범위의 숫자가 잘 나오지 않는다거나 하면 문제가 되죠. 전 맵에 걸쳐 고르게 사과가 떨어져야 하는데 특정 동산에서만 사과가 잘 떨어진다거나 하는 기획자의 의도와 다른 상황이 만들어져서는 안된다는 것입니다. 도입이 되게 길었네요.
보통의 c/c++ 프로그래머들이 사용하는 난수 발생 함수는 rand() 입니다.
srand( (unsigned int)time(NULL) ); // 이렇게 시간 값으로 시드 주고
int nRandNumber = rand() % 100; // 0~99까지 난수 발생
저도 이 함수를 신봉했습니다. 선배들의 입에서, 책에서, 학교에서 이렇게 하라고 했으니까요. time값을 시드로 넘겨 주고 나타나는 현란한 난수들에 압도당했던 학부생의 감동이 10년째 가시질 않아서 이것만 죽어라 사용합니다. 헌데.
The most common distribution used in games is a uniform distribution, where equally
likely random integers are needed in a range [a,b] . A common mistake is to use C code
like (rand()%(b-a+1)) + a. The mistake is that not all values are equally likely to
occur due to modulus wrapping around. This only works if b-a+1 divides
RAND_MAX+1. For example, if RAND_MAX is 32767, then trying to generate numbers in
the range [0,32766] using this method causes 0 to be twice as likely as any other value.
출처: http://www.lomont.org/Math/Papers/2008/Lomont_PRNG_2008.pdf
이런 글을 발견하게 됩니다. 특정 상황에서 니가 예상한거보다 0이 두배는 많이 나올거다.. 라고 하는군요. 사실 현재 프로젝트에서 발생하는 난수에 문제가 있다는 것을 감지하고 리서치 중에 발견한 것입니다. 시물레이터를 만들어 돌려 보아도 어느 정도 패턴이 보이는 난수가 발생한다는 것을 확인했습니다. 그럼 어떡해야죠? 고쳐야죠. 우린 프로그래머니까.
일단 프로그래밍계의 지식in 사이트인 stackoverflow.com에 이런 글이 있습니다.
http://stackoverflow.com/questions/1046714/what-is-a-good-random-number-generator-for-a-game
오호. 이거면 되겠군요. 여러 답변이 있습니다만 눈에 띄는 것이 Mersenne Twister와 WELL512 입니다. shuffle bag이니 하는 방법들도 기발하지만 일단 꼼수이므로 패스하고요.
일단 Mesenn Twister(이하 MT)를 봅시다. 요녀석이 특징이 매우 긴 period를 갖는다는 것입니다. 우리가 보통 사용하는 rand()의 경우 2^32의 period를 갖게 됩니다. 하지만 MT의 경우 2^19937-1의 period입니다. 음청크죠. 또 무슨 테스트를 패스했네. 빠르네. 어쩌네 하는데 전 뉴비라서 이해하지 못하구요. 결정적으로 와닿을만한 것은 바로. R, MATLAB, Ruby, Python등의 언어에서 기본 난수 알고리즘으로 채택되었다는 것입니다.
For many applications the Mersenne twister is quickly becoming the pseudorandom number generator of choice; for example, it is the default in R, Maple, MATLAB, Gretl and the two popular scripting languages Python[3] and Ruby[4]. Since the library is portable, freely available, and quickly generates high quality pseudorandom numbers, it is rarely a bad choice.
어때요 두근두근 하죠. 실제로 C++에서도 BOOST LIBRARY를 이용해 MT를 사용할 수 있습니다. 저도 그렇게 하면 간단했죠. 헌데 왜 저는 WELL512로 눈을 돌렸을까요. 바로. BOOST를 설치하기 귀찮았기 때문입니다….아니 난 괜찮은데 나때문에 다른 사람들 컴퓨터에 모두 boost가 깔려야 하는 상황을 팀원들이 달가워 하지 않을 것이기에…(네 전 A형 프로그래머니까요)
WELL512는 MT의 디자이너가 10년 후에 고안한 난수 발생 알고리즘입니다. 그의 주장에 따르면 MT보다 40% 빠르고 코드도 더 간단합니다. 보실까요?
/* initialize state to random bits */
static unsigned long state[16];
/* init should also reset this to 0 */
static unsigned int index = 0;
/* return 32 bit random number */
unsigned long WELLRNG512(void)
{
unsigned long a, b, c, d;
a = state[index];
c = state[(index+13)&15];
b = a^c^(a<<16)^(c<<15); c = state[(index+9)&15]; c ^= (c>>11);
a = state[index] = b^c;
d = a^((a<<5)&0xDA442D20UL);
index = (index + 15)&15;
a = state[index];
state[index] = a^b^d^(a<<2)^(b<<18)^(c<<28);
return state[index];
}
이게 다입니다. period는 2^512 입니다. 그렇다 해도 일반 PC로 저걸 세는데 10^100(영이 백개!!)년이 걸린다고 하는군요. (googol years 라고 부릅니다.) state만 적절히 초기화 해주고 WELLRNG512함수를 호출하면 32비트 정수(난수)가 리턴됩니다.
간단한 시물레이터로 두 난수 발생기를 시뮬레이팅 해 보았을 때의 차이를 보여드리겠습니다.

C/C++의 rand함수

WELL512 알고리즘
차이가 보이시나요? 일반적인 용도로 rand함수 정도면 충분한 randomness를 보장한다고 생각하지만 게임과 같은 상황이라면 MT나 WELL같은 알고리즘을 고려해 보시는 것이 좋을 것 같습니다. 제가 위에 보여드린 코드를 그대로 가져다 쓰셔도 상관 없겠지만(public domain 입니다. 그래도 땡큐레터 한장 정도는 보내달라고 하는군요) boost library를 사용하시는 것이 더 모양새 나는 코드가 되지 않을까 생각합니다.
참고 웹페이지
http://en.wikipedia.org/wiki/Pseudorandom_number_generator
http://en.wikipedia.org/wiki/Linear_congruential_generator
http://en.wikipedia.org/wiki/Mersenne_Twister
http://stackoverflow.com/questions/1046714/what-is-a-good-random-number-generator-for-a-game
http://www.iro.umontreal.ca/~panneton/WELLRNG.html
http://kaioa.com/node/53