Position actuelle: Accueil> Derniers articles> Comment attribuer des attributs de personnages dans le développement de jeux pour réaliser des paramètres aléatoires mais équitables?

Comment attribuer des attributs de personnages dans le développement de jeux pour réaliser des paramètres aléatoires mais équitables?

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
<!--
  Ce commentaire、Les fonctions et les styles n&#39;existent que pour la démonstration,N&#39;a rien à voir avec le texte。
  Si vous avez seulement besoin du texte,Veuillez commencer à lire à partir de la ligne horizontale suivante。
-->
<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 Comment affecter les attributs de personnages dans le développement de jeux,Atteindre des paramètres aléatoires mais équitables?</h1>

<p><strong>résumé</strong>:<code>mt_srand()

Remarque : N'utilisez pas de temps () pour semer directement, sinon vous "visquerez" dans la même seconde. N'appelez pas à plusieurs reprises mt_srand () dans un processus d'allocation - le truquez-le une fois au début d'un processus d'allocation.

3. Segmentation équitable des points d'attribut "total fixe": coupe aléatoire / étoiles et barres

Scénario: Il y a 6 attributs (force, sensibilité, intelligence, physique, mouvement et volonté), le nombre total de points est fixé à 30, et chaque élément doit être dans la plage [1,10].

 
/**
 * Volonté totalPoints Coupez-le en aléatoire uniformément n partager,满足每partager均 ≥ minVal、≤ maxVal,Et la somme reste inchangée。
 * Si les restrictions sont trop serrées, ce n&#39;est pas possible,Tentative de recul automatique(la plupart retries De premier ordre)。
 */
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>++) {
        // Déduire d&#39;abord la limite inférieure,Restant R Faire une allocation gratuite
        </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('La somme de la limite inférieure dépasse le nombre total de points');
        // générer n-1 autonome“Point de coupure”,droite [0, R] Effectuer même la coupe
        </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>;
        // Ajouter à la limite inférieure,Et vérifier la limite supérieure
        </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('Divisé non trouvé sous contrainte,Veuillez détendre les limites supérieures et inférieures ou augmenter le nombre total de points。');
}

// Exemple:6 Propriétés de l&#39;article,total 30 indiquer,Chaque élément [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> Comme [7, 4, 2, 6, 3, 8],total和恒为 30,Et tout dans la gamme

Cette méthode présente trois avantages: un contrôle strict sur la somme , des éléments impartiaux (sous la prémisse que les contraintes sont réalisables), et la simplicité est obtenue . Il convient de noter que lorsque Maxval est trop petit ou que Minval est trop grand, le domaine réalisable est très étroit, plusieurs rééchantillons peuvent être utilisés pour frapper.

4. Préférence et rareté: extraction impartiale pondérée (alias ou échantillonnage segmenté)

Si différents attributs veulent des "poids" différents (par exemple, la "chance" est légèrement rare), vous pouvez extraire des "points d'addition" supplémentaires en poids. Ce qui suit est un schéma d'échantillonnage segmenté simple et facile à lire (strictement en fonction des poids sans biais):

 
/**
 * Indice de retour en fonction de la matrice de poids(Impartial)
 * @param array&lt;float&gt; </span><span><span class="hljs-subst">$weights</span></span><span> Non négatif,Au moins un &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('权重total和必须 &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>); // Protection contre la dérive
}

// Exemple:Donner une segmentation aléatoire 6 Ajouter des articles à 3 indiquer“Bonus rare”,Biais de poids 6 article(volonté)
</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. FLUCUATIONS DE CONTRÔLE: UTILISER "APPROCHABLEMENT NORMALE" ALAMPEL (BOX-MULLER)

Parfois, vous voulez "signifier surtout, quelques extrêmes". Utilisez Box-Muller pour convertir la distribution uniforme en une distribution normale, puis le recadrer en intervalle:

 
// générer ~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); // éviter 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>);
}

// exister [min, max] Généré sur“Forme de cloche”distribué
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>));
}

Méthode: Utilisez d'abord la coupe aléatoire pour assurer la "équité de la quantité totale", puis ajoutez un réglage fin normal de la somme nulle à chaque attribut (décalage positif et négatif, gardez la somme inchangée) pour façonner le somatosensoriel de "la plus modération et une petite quantité proéminente".

6. "Distribution équitable" de plusieurs compétitifs: remaniement d'abord puis délivrer

Par exemple, 4 joueurs saisissent 4 ensembles de modèles d'attribut "pré-générés", vous pouvez:

  1. Utilisez Mathid pour semer tout le jeu;
  2. 4 ensembles de modèles pré-générés;
  3. Fisher - Yates mélangent selon le hachage dans l'ordre de l'entrée des joueurs, un ensemble par personne pour éviter "l'avantage du siège".
 
// Fisher–Yates Rétrécir:Impartial
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>]];
    }
}

// 预générer模板 + Rétrécir分配
</span><span><span class="hljs-subst">$seed</span></span><span> = seed_from_context('ROOM-9F2A', 12, 'ALL'); // utiliser ALL Représente tout le jeu
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>);
// 之后按玩家顺序一人取一autonome模板(或utiliser玩家ID再做一De premier ordre稳定Rétrécir)

7. Vérifiez la pratique de "l'équité à long terme"

Vérifiez Monte Carlo avant d'aller en ligne:

  1. Corrigez un ensemble de contextes (différents joueurs / chambres / saisons), exécutez 10W fois;
  2. Statistiques la moyenne, la variance et la fréquence de la frappe des limites supérieures et inférieures de chaque attribut;
  3. Test du chi carré pour la sélection de poids (ou du moins faire des estimations d'intervalle de confiance pour l'écart de fréquence);
  4. Vérifiez la corrélation (comme un couplage indésirable d'un attribut à l'ID du joueur).
 
// Moyenne statistique approximative(Schématique)
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>); // Différentes graines
        </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. Précautions d'ingénierie et de sécurité

  • Un processus et des graines une fois : évitez plusieurs fois de mt_srand () dans le même processus provoquant une corrélation.
  • N'utilisez pas mt_rand () à des fins sûres : lorsqu'elle implique une valeur économique (déballage, trading, classement de la concurrence) ou un jugement anti-modification, utilisez random_int () (csprng).
  • Cohérence transversale : si le serveur et le client participent au hasard, il est nécessaire d'unifier l'algorithme et les paramètres, ou de randomiser les résultats sur le serveur et de les émettre.
  • Solidification de la version : La graine est mélangée avec le "numéro de version" de $ salt = 'v1' pour ajuster l'algorithme à l'avenir sans détruire l'ancienne lecture.

9. Exemple d'intégration (reproductible, quantité totale fixe, affinure avec poids)

 
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) semis
    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) Segmentation de base:6 article、total 30、Chaque élément [1, 10]
    </span><span><span class="hljs-subst">$attrs</span></span><span> = random_partition_constrained(6, 30, 1, 10);

    // 3) Bonus rare:Préféré d&#39;abord 6 article
    </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) Zérosum fin(保持total和不变,forme“Le plus médiocre”)
    </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); // Petites fluctuations,Écart-type réglable
        </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>;
    }
    // 抵消total和偏移(Renvoie l&#39;erreur d&#39;arrondissement uniformément)
    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) Intervalle de culture(Si vous coupez,Montant égal de/向其他article借还,保持total和)
    </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>);
    // Retour ou reconstituer
    </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. Conclusion

La liaison de la "graine" de mt_srand () avec le contexte de jeu peut entraîner un pseudo-aléatoire reproductible; En utilisant la coupe aléatoire, l'échantillonnage de poids et le réglage fin à somme nulle et d'autres méthodes, une allocation d'attribut aléatoire mais juste peut être construite sur la prémisse des contraintes satisfaisantes. Si cela implique un jeu de valeur ou des scénarios de confrontation forts, envisagez de passer à des sources aléatoires telles que random_int () , et enregistrez "l'algorithme version + semence + entrée" pour fournir des preuves de la gestion des différends et de la relecture.

Html;