游戏-奖励配置-优化样式,新增创建指定表单btn,新增创建奖励权重配置btn

This commit is contained in:
2026-04-13 16:27:25 +08:00
parent 6c94d03ddf
commit c9d21d8216
8 changed files with 1112 additions and 23 deletions

View File

@@ -0,0 +1,273 @@
<?php
declare(strict_types=1);
namespace app\common\library;
/**
* 按「顺/逆时针摇点落格」条数约束生成 26 格档位盘面(写入 tier_reward_form JSON
*/
final class GameRewardTierBoardGenerator
{
private const LEOPARD = [5, 10, 15, 20, 25, 30];
private static function landingCw(int $d): int
{
$start = $d - 5;
return ($start + $d) % 26;
}
/** 图二:逆时针 end = start D若小于 0 则 26 + start D */
private static function landingCcw(int $d): int
{
$start = $d - 5;
$x = $start - $d;
return $x >= 0 ? $x : 26 + $start - $d;
}
/**
* 豹子点数:该次摇取顺、逆落点档位不能为 T4、T5
*
* @param list<string> $tier
*/
private static function leopardOk(array $tier): bool
{
foreach (self::LEOPARD as $d) {
foreach ([self::landingCw($d), self::landingCcw($d)] as $idx) {
$t = $tier[$idx];
if ($t === 'T4' || $t === 'T5') {
return false;
}
}
}
return true;
}
/**
* @param array<string, mixed> $params
* @return array{tier_reward_form: string}
*/
public static function generate(array $params): array
{
$t1Cw = self::intParam($params, 't1_fixed_cw');
$t1Ccw = self::intParam($params, 't1_fixed_ccw');
$t2MinCw = self::intParam($params, 't2_min_cw');
$t2MinCcw = self::intParam($params, 't2_min_ccw');
$t4Cw = self::intParam($params, 't4_fixed_cw');
$t4Ccw = self::intParam($params, 't4_fixed_ccw');
$t5Cw = self::intParam($params, 't5_fixed_cw');
$t5Ccw = self::intParam($params, 't5_fixed_ccw');
$amt1 = self::numParam($params, 'amt_t1');
$amt2 = self::numParam($params, 'amt_t2');
$amt3 = self::numParam($params, 'amt_t3');
$amt4 = self::numParam($params, 'amt_t4');
$bestTier = null;
$bestScore = INF;
for ($attempt = 0; $attempt < 32; $attempt++) {
$tier = self::randomInitialTier($attempt);
$temp = 5.0;
for ($step = 0; $step < 8000; $step++) {
$score = self::score($tier, $t1Cw, $t1Ccw, $t2MinCw, $t2MinCcw, $t4Cw, $t4Ccw, $t5Cw, $t5Ccw);
if ($score < $bestScore) {
$bestScore = $score;
$bestTier = $tier;
}
$i = mt_rand(0, 25);
$j = mt_rand(0, 25);
if ($i === $j) {
continue;
}
$oldI = $tier[$i];
$oldJ = $tier[$j];
$tier[$i] = $oldJ;
$tier[$j] = $oldI;
if (!self::leopardOk($tier)) {
$tier[$i] = $oldI;
$tier[$j] = $oldJ;
continue;
}
$newScore = self::score($tier, $t1Cw, $t1Ccw, $t2MinCw, $t2MinCcw, $t4Cw, $t4Ccw, $t5Cw, $t5Ccw);
$delta = $newScore - $score;
$u = mt_rand() / max(1, mt_getrandmax());
if ($delta < 0 || ($temp > 0.02 && exp(-$delta / $temp) > $u)) {
// keep
} else {
$tier[$i] = $oldI;
$tier[$j] = $oldJ;
}
$temp *= 0.999;
}
}
if ($bestTier === null || $bestScore > 45) {
throw new \RuntimeException('无法在豹子与条数约束下收敛盘面,请调整条数后重试');
}
$json = self::buildJson($bestTier, $amt1, $amt2, $amt3, $amt4);
return ['tier_reward_form' => $json];
}
/** @return list<string> */
private static function randomInitialTier(int $seedBias): array
{
mt_srand((int) (microtime(true) * 1000000) + $seedBias * 10007);
$tier = [];
for ($p = 0; $p < 26; $p++) {
$tier[$p] = ['T1', 'T2', 'T3'][mt_rand(0, 2)];
}
if (!self::leopardOk($tier)) {
for ($p = 0; $p < 26; $p++) {
$tier[$p] = 'T3';
}
}
return $tier;
}
/**
* @param list<string> $tier
*/
private static function score(
array $tier,
int $t1Cw,
int $t1Ccw,
int $t2MinCw,
int $t2MinCcw,
int $t4Cw,
int $t4Ccw,
int $t5Cw,
int $t5Ccw
): float {
if (!self::leopardOk($tier)) {
return 1e9;
}
$h = self::histogram($tier);
$s = 0.0;
$s += ($h['cw']['T1'] - $t1Cw) ** 2;
$s += ($h['ccw']['T1'] - $t1Ccw) ** 2;
$s += max(0, $t2MinCw - $h['cw']['T2']) ** 2 * 8;
$s += max(0, $t2MinCcw - $h['ccw']['T2']) ** 2 * 8;
$s += ($h['cw']['T4'] - $t4Cw) ** 2;
$s += ($h['ccw']['T4'] - $t4Ccw) ** 2;
$s += ($h['cw']['T5'] - $t5Cw) ** 2;
$s += ($h['ccw']['T5'] - $t5Ccw) ** 2;
return $s;
}
/**
* @param list<string> $tier
* @return array{cw: array<string, int>, ccw: array<string, int>}
*/
private static function histogram(array $tier): array
{
$cw = ['T1' => 0, 'T2' => 0, 'T3' => 0, 'T4' => 0, 'T5' => 0];
$ccw = ['T1' => 0, 'T2' => 0, 'T3' => 0, 'T4' => 0, 'T5' => 0];
for ($d = 5; $d <= 30; $d++) {
$cw[$tier[self::landingCw($d)]]++;
$ccw[$tier[self::landingCcw($d)]]++;
}
return ['cw' => $cw, 'ccw' => $ccw];
}
/**
* @param list<string> $tier
*/
private static function remarkForTier(string $t, float $amt2): string
{
if ($t === 'T1') {
return '大奖';
}
if ($t === 'T2') {
return $amt2 < 100 ? '完美回本' : '小赚';
}
if ($t === 'T3') {
return '抽水';
}
if ($t === 'T4') {
return '惩罚';
}
if ($t === 'T5') {
return '再来一次';
}
return '';
}
private static function buildJson(array $tier, float $amt1, float $amt2, float $amt3, float $amt4): string
{
$rows = [];
for ($i = 0; $i < 26; $i++) {
$t = $tier[$i];
if ($t === 'T5') {
$ui = '再来一次';
$uiEn = 'Once again';
$ev = '0';
} else {
$a = match ($t) {
'T1' => $amt1,
'T2' => $amt2,
'T3' => $amt3,
'T4' => $amt4,
default => $amt3,
};
$ui = self::fmtMoney($a);
$uiEn = self::fmtMoney($a);
$ev = self::fmtMoney($a);
}
$rows[] = [
'grid_number' => strval(5 + $i),
'ui_text' => $ui,
'ui_text_en' => $uiEn,
'real_ev' => $ev,
'tier' => $t,
'remark' => self::remarkForTier($t, $amt2),
];
}
return json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
}
private static function fmtMoney(float $v): string
{
if (abs($v - round($v)) < 0.000001) {
return strval((int) round($v));
}
return rtrim(rtrim(sprintf('%.4f', $v), '0'), '.');
}
private static function intParam(array $params, string $key): int
{
$v = $params[$key] ?? 0;
if (is_string($v) && trim($v) === '') {
return 0;
}
if (!is_numeric($v)) {
return 0;
}
return intval(strval($v));
}
private static function numParam(array $params, string $key): float
{
$v = $params[$key] ?? 0;
if (is_string($v) && trim($v) === '') {
return 0.0;
}
if (!is_numeric($v)) {
return 0.0;
}
return floatval(strval($v));
}
}