<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()
说明:不要用 time() 直接播种,否则同秒内会“撞种”。不要把 mt_srand() 反复调用在一段分配流程里——在一次分配过程开始时播种一次即可。
场景:有 6 个属性(力、敏、智、体、运、意志),总点数固定为 30,每项需在 [1,10] 区间。
/**
* 将 totalPoints 按均匀随机切成 n 份,满足每份均 ≥ minVal、≤ maxVal,且总和不变。
* 若限制过紧导致不可行,自动回退重尝试(最多 retries 次)。
*/
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> < </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> < 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> < </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>) => </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>) <= </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 太大导致可行域很窄时,可能多次重采样才命中。
如果不同属性希望有不同“权重”(例如“运气”稍稀有),可以按权重抽取额外的“加成点”。下面是简单、易读的分段采样方案(严格按权重无偏):
/**
* 根据权重数组返回索引(无偏)
* @param array<float> </span><span><span class="hljs-subst">$weights</span></span><span> 非负,至少一项 > 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> <= 0) throw new InvalidArgumentException('权重总和必须 > 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> => </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> < </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> < 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;
}
有时你希望“多数中庸、少数极端”。可用 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>));
}
做法:先用随机切割保证“总量公平”,再对每个属性加上一个零和的正态微调(正负相抵,保持总和不变),以塑造“多数中庸、少量突出”的体感。
例如 4 名玩家抢 4 套“预生成”的属性模板,可以:
// Fisher–Yates 洗牌:无偏
function shuffle_unbiased(array &</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> > 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> < 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再做一次稳定洗牌)
上线前做 Monte Carlo 验证:
// 粗略统计均值(示意)
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> < </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> < 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>) => </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>);
}
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> < 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) 零和微调(保持总和不变,塑造“多数中庸”)
</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> < 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> > 0 && </span><span><span class="hljs-subst">$attrs</span></span><span>[</span><span><span class="hljs-subst">$idx</span></span><span>] > 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> < 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>) => 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> > 0 && </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">$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> < 0 && </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">$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>;
}
把 mt_srand() 的“种子”与对局上下文绑定,可得到可复现的伪随机;用随机切割、权重采样与零和微调等手法,就能在满足约束的前提下构造随机但公平的属性分配。若涉及价值博弈或强对抗场景,考虑切换到 random_int() 等密码学安全的随机源,同时记录“算法版本 + 种子 + 输入”,为争议处理与观战回放提供证据。
HTML;