Files
core/src/sammo/RandUtil.php
T
Hide_D 556d6be15e feat: 해시 기반 간이 DRBG, 그에 기반한 RandUtil (#208)
기존의 Util 내의 함수는 mt_rand에 기반하고 있어서 일반적으론 충분하지만,
게임 내부에서 랜덤 값을 정교하게 제어하고 싶은 경우에는 적절하지 않았음.

따라서 직접 seed를 제어할 수 있는 난수생성기를 추가.
SHA512(seed || blockIdx) 의 형태로 동작하는 DRBG이며, FIPS 표준을 따르는 RNG는 아니지만 암호학적으로 안전한 형태로 구현함.

PHP, TS 두가지 형태로 구현.

Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/208
Co-authored-by: hide_d <hided62@gmail.com>
Co-committed-by: hide_d <hided62@gmail.com>
2022-03-12 23:53:16 +09:00

147 lines
3.5 KiB
PHP

<?php
namespace sammo;
class RandUtil
{
public function __construct(protected RNG $rng)
{
}
public function nextFloat1(): float
{
return $this->rng->nextFloat1();
}
public function nextRange(int|float $min, int|float $max): float
{
$range = $max - $min;
return $this->nextFloat1() * $range + $min;
}
public function nextRangeInt(int $min, int $max): int
{
$range = $max - $min;
if ($range > $this->rng->getMaxInt()) {
throw new \InvalidArgumentException("Invalid random int range");
}
return $this->rng->nextInt($range) + $min;
}
public function nextInt(?int $max = null): int{
return $this->rng->nextInt($max);
}
public function nextBit(): bool
{
return $this->rng->nextBits(1) !== "\0";
}
public function nextBool(int|float $prob = 0.5): bool
{
if ($prob >= 1) {
return true;
}
return $this->nextFloat1() < $prob;
}
public function shuffle(array $srcArray): array
{
if(!$srcArray){
return $srcArray;
}
$cnt = count($srcArray);
if ($cnt > $this->rng->getMaxInt()) {
throw new \InvalidArgumentException("Invalid random int range");
}
$result = [];
foreach ($srcArray as $val) {
$result[] = $val;
}
//PHP의 range는 max 포함
foreach (range(0, $cnt - 1) as $srcIdx) {
$destIdx = $this->rng->nextInt($cnt - $srcIdx - 1) + $srcIdx;
if($srcIdx === $destIdx){
continue;
}
$tmpVal = $result[$srcIdx];
$result[$srcIdx] = $result[$destIdx];
$result[$destIdx] = $tmpVal;
}
return $result;
}
public function shuffleAssoc(array $srcArray): array
{
if(!$srcArray){
return $srcArray;
}
$result = [];
foreach ($this->shuffle(array_keys($srcArray)) as $key) {
$result[$key] = $srcArray[$key];
}
return $result;
}
public function choice(array $items)
{
$keys = array_keys($items);
$keyIdx = $this->rng->nextInt(count($keys) - 1);
return $items[$keys[$keyIdx]];
}
public function choiceUsingWeight(array $items)
{
$sum = 0;
foreach ($items as $value) {
if ($value <= 0) {
continue;
}
$sum += $value;
}
$rd = $this->nextFloat1() * $sum;
foreach ($items as $item => $value) {
if ($value <= 0) {
$value = 0;
}
if ($rd <= $value) {
return $item;
}
$rd -= $value;
}
//fallback. 이곳으로 빠지지 않음
end($items);
return $items[key($items)][0];
}
public function choiceUsingWeightPair(array $items)
{
$sum = 0;
foreach ($items as [$item, $value]) {
if ($value <= 0) {
continue;
}
$sum += $value;
}
$rd = $this->nextFloat1() * $sum;
foreach ($items as [$item, $value]) {
if ($value <= 0) {
$value = 0;
}
if ($rd <= $value) {
return $item;
}
$rd -= $value;
}
//fallback. 이곳으로 빠지지 않음
end($items);
return $items[key($items)][0];
}
}