기존의 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>
147 lines
3.5 KiB
PHP
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];
|
|
}
|
|
}
|