重新优化中奖权重计算方式
This commit is contained in:
@@ -36,9 +36,9 @@ class PlayStartLogic
|
||||
private const MIN_COIN_EXTRA = 100;
|
||||
/** 豹子号中大奖额外平台币(无 BIGWIN 配置时兜底) */
|
||||
private const SUPER_WIN_BONUS = 500;
|
||||
/** 可触发超级大奖的 grid_number(5=全1 10=全2 15=全3 20=全4 25=全5 30=全6);其中 5 和 30 固定 100% 出豹子 */
|
||||
/** 可触发超级大奖的 grid_number(5=全1 10=全2 15=全3 20=全4 25=全5 30=全6) */
|
||||
private const SUPER_WIN_GRID_NUMBERS = [5, 10, 15, 20, 25, 30];
|
||||
/** grid_number 为 5 或 30 时豹子概率固定 100%(DiceRewardConfig tier=BIGWIN 约定) */
|
||||
/** 5 和 30 抽到即豹子,不参与 BIGWIN 权重判定;10/15/20/25 按 BIGWIN weight 判定是否豹子 */
|
||||
private const SUPER_WIN_ALWAYS_GRID_NUMBERS = [5, 30];
|
||||
|
||||
/**
|
||||
@@ -85,10 +85,9 @@ class PlayStartLogic
|
||||
$safetyLine = (int) ($config->safety_line ?? 0);
|
||||
$usePoolWeights = $poolProfit >= $safetyLine;
|
||||
|
||||
// 按上述规则抽取档位;若该档位无奖励或该方向下均无可用路径则重新摇取档位
|
||||
// 按档位 T1-T5 抽取后,直接按该档位内 weight 抽取一条 DiceRewardConfig,得到 grid_number
|
||||
$maxTierRetry = 10;
|
||||
$chosen = null;
|
||||
$startCandidates = [];
|
||||
$tier = null;
|
||||
for ($tierAttempt = 0; $tierAttempt < $maxTierRetry; $tierAttempt++) {
|
||||
$tier = $usePoolWeights
|
||||
@@ -99,58 +98,55 @@ class PlayStartLogic
|
||||
Log::warning("档位 {$tier} 无任何奖励配置,重新摇取档位");
|
||||
continue;
|
||||
}
|
||||
$maxRewardRetry = count($tierRewards);
|
||||
for ($attempt = 0; $attempt < $maxRewardRetry; $attempt++) {
|
||||
try {
|
||||
$chosen = self::drawRewardByWeight($tierRewards);
|
||||
$chosenId = (int) ($chosen['id'] ?? 0);
|
||||
if ($direction === 0) {
|
||||
$startCandidates = DiceRewardConfig::getCachedBySEndIndex($chosenId);
|
||||
} else {
|
||||
$startCandidates = DiceRewardConfig::getCachedByNEndIndex($chosenId);
|
||||
} catch (\RuntimeException $e) {
|
||||
if ($e->getMessage() === self::EXCEPTION_WEIGHT_ALL_ZERO) {
|
||||
Log::warning("档位 {$tier} 下所有奖励权重均为 0,重新摇取档位");
|
||||
continue;
|
||||
}
|
||||
if (!empty($startCandidates)) {
|
||||
break 2;
|
||||
}
|
||||
Log::warning("方向 {$direction} 下无 s_end_index/n_end_index={$chosenId} 的配置,重新摇取");
|
||||
throw $e;
|
||||
}
|
||||
Log::warning("方向 {$direction} 下档位 {$tier} 所有奖励均无可用路径配置,重新摇取档位");
|
||||
break;
|
||||
}
|
||||
if (empty($startCandidates)) {
|
||||
Log::error("方向 {$direction} 下多次摇取档位后仍无可用路径配置");
|
||||
throw new ApiException('该方向下暂无可用路径配置');
|
||||
if ($chosen === null) {
|
||||
Log::error("多次摇取档位后仍无有效权重配置");
|
||||
throw new ApiException('暂无可用奖励配置');
|
||||
}
|
||||
$chosenId = (int) ($chosen['id'] ?? 0);
|
||||
$startRecord = $startCandidates[array_rand($startCandidates)];
|
||||
|
||||
$startIndex = (int) ($startRecord['id'] ?? 0);
|
||||
$targetIndex = $direction === 0
|
||||
? (int) ($startRecord['s_end_index'] ?? 0)
|
||||
: (int) ($startRecord['n_end_index'] ?? 0);
|
||||
$rollNumber = (int) ($startRecord['grid_number'] ?? 0);
|
||||
// 起始索引:根据 direction 取 s_start_index(顺时针)或 n_start_index(逆时针);结束索引:当前中奖配置 id
|
||||
$startIndex = $direction === 0
|
||||
? (int) ($chosen['s_start_index'] ?? 0)
|
||||
: (int) ($chosen['n_start_index'] ?? 0);
|
||||
$targetIndex = $chosenId;
|
||||
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
|
||||
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
||||
$rewardWinCoin = 100 + $realEv; // 摇色子中奖平台币 = 100 + DiceRewardConfig.real_ev
|
||||
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
|
||||
// T5(再来一次):摇色子中奖平台币 = 配置的 real_ev;其他档位 = 100 + real_ev
|
||||
$rewardWinCoin = $isTierT5 ? $realEv : (100 + $realEv);
|
||||
|
||||
// 当抽到的 grid_number 为 5/10/15/20/25/30 时,可出豹子;其中 grid_number=5 与 30 固定 100% 豹子(BIGWIN 约定)
|
||||
// 流程:先抽档位 T1-T5,再按档位内权重抽色子点数;5 和 30 抽到即豹子,10/15/20/25 按 BIGWIN weight 判定是否中大奖(豹子如 [4,4,4,4,4])
|
||||
$superWinCoin = 0;
|
||||
$isWin = 0;
|
||||
$bigWinRealEv = 0.0; // BIGWIN 档位的真实资金结算,用于从彩金池盈利中一并扣除
|
||||
$bigWinRealEv = 0.0;
|
||||
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
|
||||
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
|
||||
$alwaysSuperWin = in_array($rollNumber, self::SUPER_WIN_ALWAYS_GRID_NUMBERS, true);
|
||||
$doSuperWin = $alwaysSuperWin;
|
||||
if (!$doSuperWin) {
|
||||
$weight = $bigWinConfig !== null
|
||||
? max(0.0, min(100.0, (float) ($bigWinConfig['weight'] ?? 0)))
|
||||
: 100.0;
|
||||
$roll = mt_rand(1, 10000) / 10000;
|
||||
$doSuperWin = $roll <= $weight / 100;
|
||||
? max(1, min(10000, (int) ($bigWinConfig['weight'] ?? 1)))
|
||||
: 10000;
|
||||
$roll = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX);
|
||||
$doSuperWin = $roll < ($weight / 10000.0);
|
||||
}
|
||||
if ($doSuperWin) {
|
||||
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
||||
$isWin = 1;
|
||||
if ($bigWinConfig !== null) {
|
||||
$bigWinRealEv = (float) ($bigWinConfig['real_ev'] ?? 0);
|
||||
$superWinCoin = 100 + $bigWinRealEv;
|
||||
$superWinCoin = $bigWinRealEv;
|
||||
} else {
|
||||
$superWinCoin = self::SUPER_WIN_BONUS;
|
||||
}
|
||||
@@ -174,7 +170,6 @@ class PlayStartLogic
|
||||
$configId = (int) $config->id;
|
||||
$rewardId = $chosenId;
|
||||
$configName = (string) ($config->name ?? '');
|
||||
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
|
||||
$adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
|
||||
try {
|
||||
Db::transaction(function () use (
|
||||
@@ -322,34 +317,39 @@ class PlayStartLogic
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/** 该组配置权重均为 0 时抛出,供调用方重试 */
|
||||
private const EXCEPTION_WEIGHT_ALL_ZERO = 'REWARD_WEIGHT_ALL_ZERO';
|
||||
|
||||
/**
|
||||
* T1-T5 档位内按 DiceRewardConfig.weight 抽取一条配置;权重均为 0 或未设时等权随机
|
||||
* @param array<int, array> $rewards 该档位配置列表(每条含 weight、id、real_ev 等)
|
||||
* @return array 抽中的一条配置
|
||||
* 按权重抽取一条配置:仅 weight>0 参与抽取(weight=0 不会被摇到)
|
||||
* 使用 [0, total) 浮点随机,支持最小权重 0.1%(如 weight=0.1),避免整数随机导致小权重失真
|
||||
* 全部 weight 为 0 时抛出 RuntimeException(EXCEPTION_WEIGHT_ALL_ZERO)
|
||||
*/
|
||||
private static function drawRewardByWeight(array $rewards): array
|
||||
{
|
||||
if (empty($rewards)) {
|
||||
throw new \InvalidArgumentException('rewards 不能为空');
|
||||
}
|
||||
$weights = [];
|
||||
$candidateWeights = [];
|
||||
foreach ($rewards as $i => $row) {
|
||||
$w = isset($row['weight']) ? max(0, (float) $row['weight']) : 0;
|
||||
$weights[$i] = $w;
|
||||
}
|
||||
$total = array_sum($weights);
|
||||
if ($total <= 0) {
|
||||
return $rewards[array_rand($rewards)];
|
||||
}
|
||||
$r = mt_rand(1, (int) $total);
|
||||
$acc = 0;
|
||||
foreach ($weights as $i => $w) {
|
||||
$acc += $w;
|
||||
if ($r <= $acc) {
|
||||
return $rewards[$i];
|
||||
$w = isset($row['weight']) ? (float) $row['weight'] : 0.0;
|
||||
if ($w > 0) {
|
||||
$candidateWeights[$i] = $w;
|
||||
}
|
||||
}
|
||||
return $rewards[array_key_last($rewards)];
|
||||
$total = (float) array_sum($candidateWeights);
|
||||
if ($total > 0) {
|
||||
$r = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX) * $total;
|
||||
$acc = 0.0;
|
||||
foreach ($candidateWeights as $i => $w) {
|
||||
$acc += $w;
|
||||
if ($r < $acc) {
|
||||
return $rewards[$i];
|
||||
}
|
||||
}
|
||||
return $rewards[array_key_last($candidateWeights)];
|
||||
}
|
||||
throw new \RuntimeException(self::EXCEPTION_WEIGHT_ALL_ZERO);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -368,7 +368,7 @@ class PlayStartLogic
|
||||
if (empty($candidates)) {
|
||||
break;
|
||||
}
|
||||
$idx = $candidates[array_rand($candidates)];
|
||||
$idx = $candidates[random_int(0, count($candidates) - 1)];
|
||||
$arr[$idx]++;
|
||||
}
|
||||
shuffle($arr);
|
||||
@@ -403,8 +403,8 @@ class PlayStartLogic
|
||||
$arr = $super;
|
||||
$maxAttempts = 20;
|
||||
for ($a = 0; $a < $maxAttempts; $a++) {
|
||||
$idx = array_rand($arr);
|
||||
$j = array_rand($arr);
|
||||
$idx = random_int(0, count($arr) - 1);
|
||||
$j = random_int(0, count($arr) - 1);
|
||||
if ($idx === $j) {
|
||||
$j = ($j + 1) % 5;
|
||||
}
|
||||
|
||||
@@ -119,9 +119,9 @@ class LotteryService
|
||||
{
|
||||
$total = array_sum($weights);
|
||||
if ($total <= 0) {
|
||||
return $tiers[array_rand($tiers)];
|
||||
return $tiers[random_int(0, count($tiers) - 1)];
|
||||
}
|
||||
$r = mt_rand(1, $total);
|
||||
$r = random_int(1, $total);
|
||||
$acc = 0;
|
||||
foreach ($weights as $i => $w) {
|
||||
$acc += $w;
|
||||
@@ -145,7 +145,7 @@ class LotteryService
|
||||
return 0;
|
||||
}
|
||||
$total = $paid + $free;
|
||||
$r = mt_rand(1, $total);
|
||||
$r = random_int(1, $total);
|
||||
return $r <= $paid ? 0 : 1;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user