현재 위치: > 최신 기사 목록> 무작위이지만 공정한 설정을 달성하기 위해 게임 개발에 문자 속성을 할당하는 방법은 무엇입니까?

무작위이지만 공정한 설정을 달성하기 위해 게임 개발에 문자 속성을 할당하는 방법은 무엇입니까?

gitbox 2025-08-30
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-keyword">echo</span></span><span> <span class="hljs-string"><<<HTML
<!--
  이 의견、기능과 스타일은 데모에만 존재합니다,텍스트와 관련이 없습니다。
  텍스트 만 필요하다면,다음 수평선에서 읽기 시작하십시오。
-->
<style>
  body { font-family: system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial; line-height: 1.65; max-width: 820px; margin: 2rem auto; padding: 0 1rem; }
  pre { overflow: auto; padding: .75rem; background: #f6f8fa; border-radius: 6px; }
  code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
</style>
<hr>

<h1>mt_srand 게임 개발에서 문자 속성을 할당하는 방법,무작위이지만 공정한 설정을 달성합니다?</h1>

<p><strong>요약</strong>:<code>mt_srand()

참고 : 시간 ()을 사용하여 직접 뿌리는 것이 아니라면 같은 초 안에 "나사"됩니다. 할당 프로세스에서 mt_srand ()를 반복적으로 호출하지 마십시오. 할당 프로세스가 시작될 때 한 번 뿌리십시오.

3. "총 고정 된"속성 지점의 공정한 세분화 : 임의의 컷 / 스타 및 바

시나리오 : 6 가지 속성 (힘, 감도, 지능, 물리적, 운동 및 의지)이 있으며 총점 수는 30으로 고정되며 각 항목은 [1,10] 범위에 있어야합니다.

 
/**
 * 할 것이다 totalPoints 무작위로 고르게 자릅니다 n 공유하다,满足每공유하다均 ≥ minVal、≤ maxVal,그리고 합은 변하지 않습니다。
 * 제한이 너무 빡빡하면 가능하지 않습니다.,자동 롤백 시도(최대 retries 2 차)。
 */
function random_partition_constrained(int </span><span><span class="hljs-subst">$n</span></span><span>, int </span><span><span class="hljs-subst">$totalPoints</span></span><span>, int </span><span><span class="hljs-subst">$minVal</span></span><span>, int </span><span><span class="hljs-subst">$maxVal</span></span><span>, int </span><span><span class="hljs-subst">$retries</span></span><span> = 256): array {
    for (</span><span><span class="hljs-subst">$t</span></span><span> = 0; </span><span><span class="hljs-subst">$t</span></span><span> &lt; </span><span><span class="hljs-subst">$retries</span></span><span>; </span><span><span class="hljs-subst">$t</span></span><span>++) {
        // 먼저 하한을 공제하십시오,남아 있습니다 R 무료 할당하십시오
        </span><span><span class="hljs-subst">$R</span></span><span> = </span><span><span class="hljs-subst">$totalPoints</span></span><span> - </span><span><span class="hljs-subst">$n</span></span><span> * </span><span><span class="hljs-subst">$minVal</span></span><span>;
        if (</span><span><span class="hljs-subst">$R</span></span><span> &lt; 0) throw new InvalidArgumentException('하한의 합은 총점 수를 초과합니다.');
        // 생성하다 n-1 개별“컷 포인트”,오른쪽 [0, R] 절단조차 수행하십시오
        </span><span><span class="hljs-subst">$cuts</span></span><span> = [];
        for (</span><span><span class="hljs-subst">$i</span></span><span> = 0; </span><span><span class="hljs-subst">$i</span></span><span> &lt; </span><span><span class="hljs-subst">$n</span></span><span> - 1; </span><span><span class="hljs-subst">$i</span></span><span>++) { </span><span><span class="hljs-subst">$cuts</span></span><span>[] = mt_rand(0, </span><span><span class="hljs-subst">$R</span></span><span>); }
        sort(</span><span><span class="hljs-subst">$cuts</span></span><span>);
        </span><span><span class="hljs-subst">$parts</span></span><span> = [];
        </span><span><span class="hljs-subst">$prev</span></span><span> = 0;
        foreach (</span><span><span class="hljs-subst">$cuts</span></span><span> as </span><span><span class="hljs-subst">$c</span></span><span>) { </span><span><span class="hljs-subst">$parts</span></span><span>[] = </span><span><span class="hljs-subst">$c</span></span><span> - </span><span><span class="hljs-subst">$prev</span></span><span>; </span><span><span class="hljs-subst">$prev</span></span><span> = </span><span><span class="hljs-subst">$c</span></span><span>; }
        </span><span><span class="hljs-subst">$parts</span></span><span>[] = </span><span><span class="hljs-subst">$R</span></span><span> - </span><span><span class="hljs-subst">$prev</span></span><span>;
        // 하한으로 다시 추가하십시오,상한을 확인하십시오
        </span><span><span class="hljs-subst">$vals</span></span><span> = array_map(fn(</span><span><span class="hljs-subst">$x</span></span><span>) =&gt; </span><span><span class="hljs-subst">$x</span></span><span> + </span><span><span class="hljs-subst">$minVal</span></span><span>, </span><span><span class="hljs-subst">$parts</span></span><span>);
        if (max(</span><span><span class="hljs-subst">$vals</span></span><span>) &lt;= </span><span><span class="hljs-subst">$maxVal</span></span><span>) return </span><span><span class="hljs-subst">$vals</span></span><span>;
    }
    throw new RuntimeException('구속 조건 아래에서 찾을 수 없습니다,상한과 하한을 완화하거나 총점 수를 늘리십시오.。');
}

// 예:6 항목 속성,총 30 가리키다,각 항목 [1,10]
</span><span><span class="hljs-subst">$attrs</span></span><span> = random_partition_constrained(n: 6, totalPoints: 30, minVal: 1, maxVal: 10);
// </span><span><span class="hljs-subst">$attrs</span></span><span> 좋다 [7, 4, 2, 6, 3, 8],총和恒为 30,그리고 모든 범위 내

이 방법에는 세 가지 장점이 있습니다. 합계에 대한 엄격한 제어 , 편견이없는 항목 (제약 조건이 실현 가능하다는 전제에 따라). 단순성이 달성됩니다 . Maxval이 너무 작거나 Minval이 너무 커지면 실행 가능한 도메인이 매우 좁으며 여러 리 샘플링이 사용될 수 있습니다.

4. 선호도와 희귀 성 : 가중치 편견없는 추출 (별칭 또는 세그먼트 샘플링)

다른 속성이 다른 "가중치"(예 : "luck")을 원한다면 (약간 드문 경우), 추가 "첨가점"을 무게로 추출 할 수 있습니다. 다음은 간단하고 읽기 쉬운 세그먼트 샘플링 체계입니다 (바이어스가없는 가중치에 따라) :

 
/**
 * 무게 배열을 기반으로 반환 인덱스(편견이 없습니다)
 * @param array&lt;float&gt; </span><span><span class="hljs-subst">$weights</span></span><span> 비 음성,적어도 하나 &gt; 0
 */
function weighted_pick(array </span><span><span class="hljs-subst">$weights</span></span><span>): int {
    </span><span><span class="hljs-subst">$sum</span></span><span> = array_sum(</span><span><span class="hljs-subst">$weights</span></span><span>);
    if (</span><span><span class="hljs-subst">$sum</span></span><span> &lt;= 0) throw new InvalidArgumentException('权重총和必须 &gt; 0');
    </span><span><span class="hljs-subst">$r</span></span><span> = mt_rand() / mt_getrandmax() * </span><span><span class="hljs-subst">$sum</span></span><span>; // [0, sum)
    </span><span><span class="hljs-subst">$acc</span></span><span> = 0.0;
    foreach (</span><span><span class="hljs-subst">$weights</span></span><span> as </span><span><span class="hljs-subst">$i</span></span><span> =&gt; </span><span><span class="hljs-subst">$w</span></span><span>) {
        </span><span><span class="hljs-subst">$acc</span></span><span> += </span><span><span class="hljs-subst">$w</span></span><span>;
        if (</span><span><span class="hljs-subst">$r</span></span><span> &lt; </span><span><span class="hljs-subst">$acc</span></span><span>) return </span><span><span class="hljs-subst">$i</span></span><span>;
    }
    return array_key_last(</span><span><span class="hljs-subst">$weights</span></span><span>); // 드리프트 보호
}

// 예:임의의 세분화를 제공하십시오 6 항목을 추가하십시오 3 가리키다“드문 보너스”,무게 바이어스 6 목(~ 할 것이다)
</span><span><span class="hljs-subst">$weights</span></span><span> = [1,1,1,1,1,2.5];
</span><span><span class="hljs-subst">$bonus</span></span><span> = [0,0,0,0,0,0];
for (</span><span><span class="hljs-subst">$k</span></span><span> = 0; </span><span><span class="hljs-subst">$k</span></span><span> &lt; 3; </span><span><span class="hljs-subst">$k</span></span><span>++) {
    </span><span><span class="hljs-subst">$idx</span></span><span> = weighted_pick(</span><span><span class="hljs-subst">$weights</span></span><span>);
    </span><span><span class="hljs-subst">$bonus</span></span><span>[</span><span><span class="hljs-subst">$idx</span></span><span>] += 1;
}

5. 제어 변동 : "대략 정상"랜덤 사용 (Box-Muller)

때때로 당신은 "대부분 온화하고 소수의 극단"을 원합니다. Box – Muller를 사용하여 균일 분포를 정규 분포로 변환 한 다음 간격으로 자릅니다.

 
// 생성하다 ~N(0,1)
function randn(): float {
    do {
        </span><span><span class="hljs-subst">$u</span></span><span> = mt_rand() / mt_getrandmax();
        </span><span><span class="hljs-subst">$v</span></span><span> = mt_rand() / mt_getrandmax();
    } while (</span><span><span class="hljs-subst">$u</span></span><span> == 0.0); // 피하다 log(0)
    return sqrt(-2.0 * log(</span><span><span class="hljs-subst">$u</span></span><span>)) * cos(2.0 * M_PI * </span><span><span class="hljs-subst">$v</span></span><span>);
}

// 존재하다 [min, max] 생성“벨 모양”배포
function randn_clamped(float </span><span><span class="hljs-subst">$min</span></span><span>, float </span><span><span class="hljs-subst">$max</span></span><span>, float </span><span><span class="hljs-subst">$mean</span></span><span>, float </span><span><span class="hljs-subst">$std</span></span><span>): float {
    </span><span><span class="hljs-subst">$x</span></span><span> = </span><span><span class="hljs-subst">$mean</span></span><span> + </span><span><span class="hljs-subst">$std</span></span><span> * randn();
    return max(</span><span><span class="hljs-subst">$min</span></span><span>, min(</span><span><span class="hljs-subst">$max</span></span><span>, </span><span><span class="hljs-subst">$x</span></span><span>));
}

방법 : 먼저 임의의 절단을 사용하여 "총 수량 공정성"을 보장 한 다음 각 속성에 0 의 정상 미세 조정을 추가하여 (양의 오프셋, 합계를 변경하지 않음) "대부분의 중재 및 소량의 두드러진"의 체세포 감각을 형성하십시오.

6. 여러 경쟁의 "공정 분포": 먼저 재 포장 한 다음 발행

예를 들어, 4 명의 플레이어는 4 세트의 "사전 생성 된"속성 템플릿 세트를 잡을 수 있습니다.

  1. MatchId를 사용하여 전체 게임을 뿌리십시오.
  2. 사전 생성 된 4 세트의 템플릿;
  3. Fisher – Yates는 플레이어 입국 순서대로 해시에 따라 셔플을 셔플합니다 .
 
// Fisher–Yates 수축:편견이 없습니다
function shuffle_unbiased(array &amp;</span><span><span class="hljs-subst">$arr</span></span><span>): void {
    </span><span><span class="hljs-subst">$n</span></span><span> = count(</span><span><span class="hljs-subst">$arr</span></span><span>);
    for (</span><span><span class="hljs-subst">$i</span></span><span> = </span><span><span class="hljs-subst">$n</span></span><span> - 1; </span><span><span class="hljs-subst">$i</span></span><span> &gt; 0; </span><span><span class="hljs-subst">$i</span></span><span>--) {
        </span><span><span class="hljs-subst">$j</span></span><span> = mt_rand(0, </span><span><span class="hljs-subst">$i</span></span><span>);
        [</span><span><span class="hljs-subst">$arr</span></span><span>[</span><span><span class="hljs-subst">$i</span></span><span>], </span><span><span class="hljs-subst">$arr</span></span><span>[</span><span><span class="hljs-subst">$j</span></span><span>]] = [</span><span><span class="hljs-subst">$arr</span></span><span>[</span><span><span class="hljs-subst">$j</span></span><span>], </span><span><span class="hljs-subst">$arr</span></span><span>[</span><span><span class="hljs-subst">$i</span></span><span>]];
    }
}

// 预생성하다模板 + 수축分配
</span><span><span class="hljs-subst">$seed</span></span><span> = seed_from_context('ROOM-9F2A', 12, 'ALL'); // 사용 ALL 전체 게임을 나타냅니다
mt_srand(</span><span><span class="hljs-subst">$seed</span></span><span>);
</span><span><span class="hljs-subst">$templates</span></span><span> = [];
for (</span><span><span class="hljs-subst">$t</span></span><span> = 0; </span><span><span class="hljs-subst">$t</span></span><span> &lt; 4; </span><span><span class="hljs-subst">$t</span></span><span>++) {
    </span><span><span class="hljs-subst">$templates</span></span><span>[] = random_partition_constrained(6, 30, 1, 10);
}
shuffle_unbiased(</span><span><span class="hljs-subst">$templates</span></span><span>);
// 之后按玩家顺序一人取一개별模板(或사용玩家ID再做一2 차稳定수축)

7. "장기 공정성"의 관행을 확인하십시오.

온라인으로 가기 전에 Monte Carlo를 확인하십시오.

  1. 컨텍스트 세트 (다른 ​​플레이어/객실/시즌)를 수정하고 10W 시간을 실행하십시오.
  2. 통계 각 속성의 상한 및 하한을 치는 평균, 분산 및 주파수;
  3. 체중 선택에 대한 카이-제곱 테스트 (또는 적어도 주파수 간격에 대한 신뢰 구간 추정치);
  4. 상관 관계를 확인하십시오 (예 : 플레이어 ID에 대한 속성의 바람직하지 않은 커플 링).
 
// 거친 통계적 평균(개략도)
function simulate_means(int </span><span><span class="hljs-subst">$runs</span></span><span> = 100000): array {
    </span><span><span class="hljs-subst">$sum</span></span><span> = array_fill(0, 6, 0.0);
    for (</span><span><span class="hljs-subst">$i</span></span><span> = 0; </span><span><span class="hljs-subst">$i</span></span><span> &lt; </span><span><span class="hljs-subst">$runs</span></span><span>; </span><span><span class="hljs-subst">$i</span></span><span>++) {
        mt_srand(</span><span><span class="hljs-subst">$i</span></span><span>); // 다른 씨앗
        </span><span><span class="hljs-subst">$vals</span></span><span> = random_partition_constrained(6, 30, 1, 10);
        for (</span><span><span class="hljs-subst">$k</span></span><span> = 0; </span><span><span class="hljs-subst">$k</span></span><span> &lt; 6; </span><span><span class="hljs-subst">$k</span></span><span>++) { </span><span><span class="hljs-subst">$sum</span></span><span>[</span><span><span class="hljs-subst">$k</span></span><span>] += </span><span><span class="hljs-subst">$vals</span></span><span>[</span><span><span class="hljs-subst">$k</span></span><span>]; }
    }
    return array_map(fn(</span><span><span class="hljs-subst">$x</span></span><span>) =&gt; </span><span><span class="hljs-subst">$x</span></span><span> / </span><span><span class="hljs-subst">$runs</span></span><span>, </span><span><span class="hljs-subst">$sum</span></span><span>);
}

8. 엔지니어링 및 안전 예방 조치

  • 한 번의 프로세스와 종자 한 번 : 동일한 프로세스에서 상관 관계를 유발하는 MT_SRAND ()의 여러 번 피하십시오.
  • 안전한 목적으로 mt_rand ()를 사용하지 마십시오 . 경제적 가치 (Unboxing, Trading, Competition Ranking) 또는 체적 방지 판단이 포함될 때 Random_int () (CSPRNG)를 사용하십시오.
  • 교차 일관성 : 서버와 클라이언트가 모두 임의성에 참여하는 경우 알고리즘과 매개 변수를 통합하거나 서버의 결과를 무작위 화하여 발행해야합니다.
  • 버전의 고형화 : 씨앗은 $ salt = 'v1' 의 "버전 번호"와 혼합되어 앞으로 이전 재생을 파괴하지 않고 알고리즘을 조정합니다.

9. 통합 예 (재현 가능, 고정 총 금액, 체중으로 미세 조정)

 
function assign_attributes(string </span><span><span class="hljs-subst">$matchId</span></span><span>, int </span><span><span class="hljs-subst">$season</span></span><span>, string </span><span><span class="hljs-subst">$playerId</span></span><span>): array {
    // 1) 파종
    mt_srand(seed_from_context(</span><span><span class="hljs-subst">$matchId</span></span><span>, </span><span><span class="hljs-subst">$season</span></span><span>, </span><span><span class="hljs-subst">$playerId</span></span><span>, 'attr-v1'));

    // 2) 기본 세분화:6 목、총 30、각 항목 [1, 10]
    </span><span><span class="hljs-subst">$attrs</span></span><span> = random_partition_constrained(6, 30, 1, 10);

    // 3) 드문 보너스:먼저 선호되었습니다 6 목
    </span><span><span class="hljs-subst">$weights</span></span><span> = [1,1,1,1,1,2.5];
    for (</span><span><span class="hljs-subst">$i</span></span><span> = 0; </span><span><span class="hljs-subst">$i</span></span><span> &lt; 3; </span><span><span class="hljs-subst">$i</span></span><span>++) { </span><span><span class="hljs-subst">$attrs</span></span><span>[weighted_pick(</span><span><span class="hljs-subst">$weights</span></span><span>)] += 1; }

    // 4) Zerosum 미세 조정(保持총和不变,모양“가장 평범한”)
    </span><span><span class="hljs-subst">$mean</span></span><span> = array_sum(</span><span><span class="hljs-subst">$attrs</span></span><span>) / count(</span><span><span class="hljs-subst">$attrs</span></span><span>);
    </span><span><span class="hljs-subst">$delta</span></span><span> = 0.0;
    for (</span><span><span class="hljs-subst">$k</span></span><span> = 0; </span><span><span class="hljs-subst">$k</span></span><span> &lt; count(</span><span><span class="hljs-subst">$attrs</span></span><span>); </span><span><span class="hljs-subst">$k</span></span><span>++) {
        </span><span><span class="hljs-subst">$adj</span></span><span> = (int) round(randn() * 0.6); // 작은 변동,조정 가능한 표준 편차
        </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$k</span></span><span>] += </span><span><span class="hljs-subst">$adj</span></span><span>;
        </span><span><span class="hljs-subst">$delta</span></span><span> += </span><span><span class="hljs-subst">$adj</span></span><span>;
    }
    // 抵消총和偏移(반올림 오류를 고르게 반환합니다)
    while (</span><span><span class="hljs-subst">$delta</span></span><span> != 0) {
        </span><span><span class="hljs-subst">$idx</span></span><span> = mt_rand(0, count(</span><span><span class="hljs-subst">$attrs</span></span><span>)-1);
        if (</span><span><span class="hljs-subst">$delta</span></span><span> &gt; 0 &amp;&amp; </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$idx</span></span><span>] &gt; 1) { </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$idx</span></span><span>]--; </span><span><span class="hljs-subst">$delta</span></span><span>--; }
        elseif (</span><span><span class="hljs-subst">$delta</span></span><span> &lt; 0) { </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$idx</span></span><span>]++; </span><span><span class="hljs-subst">$delta</span></span><span>++; }
    }

    // 5) 작물 간격(잘라면,동일한 금액/向其他목借还,保持총和)
    </span><span><span class="hljs-subst">$min</span></span><span> = 1; </span><span><span class="hljs-subst">$max</span></span><span> = 12;
    </span><span><span class="hljs-subst">$sumBefore</span></span><span> = array_sum(</span><span><span class="hljs-subst">$attrs</span></span><span>);
    </span><span><span class="hljs-subst">$attrs</span></span><span> = array_map(fn(</span><span><span class="hljs-subst">$v</span></span><span>) =&gt; max(</span><span><span class="hljs-subst">$min</span></span><span>, min(</span><span><span class="hljs-subst">$max</span></span><span>, </span><span><span class="hljs-subst">$v</span></span><span>)), </span><span><span class="hljs-subst">$attrs</span></span><span>);
    </span><span><span class="hljs-subst">$sumAfter</span></span><span> = array_sum(</span><span><span class="hljs-subst">$attrs</span></span><span>);
    // 반환 또는 보충
    </span><span><span class="hljs-subst">$diff</span></span><span> = </span><span><span class="hljs-subst">$sumBefore</span></span><span> - </span><span><span class="hljs-subst">$sumAfter</span></span><span>;
    while (</span><span><span class="hljs-subst">$diff</span></span><span> != 0) {
        </span><span><span class="hljs-subst">$i</span></span><span> = mt_rand(0, count(</span><span><span class="hljs-subst">$attrs</span></span><span>)-1);
        if (</span><span><span class="hljs-subst">$diff</span></span><span> &gt; 0 &amp;&amp; </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$i</span></span><span>] &lt; </span><span><span class="hljs-subst">$max</span></span><span>) { </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$i</span></span><span>]++; </span><span><span class="hljs-subst">$diff</span></span><span>--; }
        elseif (</span><span><span class="hljs-subst">$diff</span></span><span> &lt; 0 &amp;&amp; </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$i</span></span><span>] &gt; </span><span><span class="hljs-subst">$min</span></span><span>) { </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$i</span></span><span>]--; </span><span><span class="hljs-subst">$diff</span></span><span>++; }
    }
    return </span><span><span class="hljs-subst">$attrs</span></span><span>;
}

10. 결론

MT_SRAND () 의 "시드"를 게임 컨텍스트와 결합하면 재현 가능한 의사 랜덤을 초래할 수 있습니다. 무작위 절단, 중량 샘플링 및 제로섬 미세 조정 및 기타 방법을 사용하여, 만족스러운 제약 조건의 전제에 임의이지만 공정한 속성 할당을 구성 할 수 있습니다. 가치 게임 또는 강력한 대립 시나리오가 포함 된 경우 Random_int () 와 같은 랜덤 소스로 전환하고 "알고리즘 버전 + 종자 + 입력"을 녹음하여 분쟁 처리 및 재생보기에 대한 증거를 제공하십시오.

HTML;