262 lines
9.4 KiB
PHP
262 lines
9.4 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | saiadmin [ saiadmin快速开发框架 ]
|
||
// +----------------------------------------------------------------------
|
||
// | Author: your name
|
||
// +----------------------------------------------------------------------
|
||
namespace app\dice\logic\reward_config;
|
||
|
||
use app\dice\logic\reward\DiceRewardLogic;
|
||
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||
use app\dice\model\reward\DiceRewardConfig;
|
||
use app\dice\model\reward_config_record\DiceRewardConfigRecord;
|
||
use plugin\saiadmin\basic\think\BaseLogic;
|
||
use plugin\saiadmin\exception\ApiException;
|
||
use plugin\saiadmin\utils\Helper;
|
||
use support\Log;
|
||
|
||
/**
|
||
* 奖励配置逻辑层(DiceRewardConfig)
|
||
* weight 1-10000,各档位权重和不限制
|
||
*/
|
||
class DiceRewardConfigLogic extends BaseLogic
|
||
{
|
||
/** weight 取值范围 */
|
||
private const WEIGHT_MIN = 1;
|
||
private const WEIGHT_MAX = 10000;
|
||
|
||
public function __construct()
|
||
{
|
||
$this->model = new DiceRewardConfig();
|
||
}
|
||
|
||
/**
|
||
* 新增:保存后刷新缓存(权重已迁移至 dice_reward 表)
|
||
*/
|
||
public function add(array $data): mixed
|
||
{
|
||
$result = parent::add($data);
|
||
DiceRewardConfig::refreshCache();
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 修改:保存后刷新缓存;BIGWIN 的 weight 直接写入 dice_reward_config 表,抽奖时从 Config 读取
|
||
*/
|
||
public function edit($id, array $data): mixed
|
||
{
|
||
$result = parent::edit($id, $data);
|
||
DiceRewardConfig::refreshCache();
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 为列表/分页数据中的 BIGWIN 行附加 weight(来自 DiceReward 缓存)
|
||
*/
|
||
public function enrichBigwinWeight(array $listResult): array
|
||
{
|
||
$key = isset($listResult['data']) ? 'data' : (isset($listResult['records']) ? 'records' : null);
|
||
if ($key === null || empty($listResult[$key])) {
|
||
return $listResult;
|
||
}
|
||
$rewardLogic = new DiceRewardLogic();
|
||
foreach ($listResult[$key] as $i => $row) {
|
||
if (isset($row['tier']) && $row['tier'] === 'BIGWIN' && isset($row['grid_number'])) {
|
||
$listResult[$key][$i]['weight'] = $rewardLogic->getBigwinWeightByGridNumber((int) $row['grid_number']);
|
||
}
|
||
}
|
||
return $listResult;
|
||
}
|
||
|
||
/**
|
||
* 删除后刷新缓存
|
||
*/
|
||
public function destroy($ids): bool
|
||
{
|
||
$result = parent::destroy($ids);
|
||
if ($result) {
|
||
DiceRewardConfig::refreshCache();
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 按档位分组返回奖励配置列表(仅配置,权重在 dice_reward 表;权重配比请用 DiceRewardLogic::getListGroupedByTierWithDirection)
|
||
*/
|
||
public function getListGroupedByTier(): array
|
||
{
|
||
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'];
|
||
$list = $this->model->whereIn('tier', $tiers)->order('tier')->order('id')->select()->toArray();
|
||
$grouped = [];
|
||
foreach ($tiers as $t) {
|
||
$grouped[$t] = [];
|
||
}
|
||
foreach ($list as $row) {
|
||
$tier = isset($row['tier']) ? (string) $row['tier'] : '';
|
||
if ($tier !== '' && isset($grouped[$tier])) {
|
||
$grouped[$tier][] = $row;
|
||
}
|
||
}
|
||
return $grouped;
|
||
}
|
||
|
||
/** 测试时档位权重均为 0 的异常标识 */
|
||
private const EXCEPTION_WEIGHT_ALL_ZERO = 'REWARD_WEIGHT_ALL_ZERO';
|
||
|
||
/**
|
||
* 按权重抽取一条配置(与 PlayStartLogic 抽奖逻辑一致,仅 weight>0 参与)
|
||
*/
|
||
private static function drawRewardByWeight(array $rewards): array
|
||
{
|
||
if (empty($rewards)) {
|
||
throw new \InvalidArgumentException('rewards 不能为空');
|
||
}
|
||
$candidateWeights = [];
|
||
foreach ($rewards as $i => $row) {
|
||
$w = isset($row['weight']) ? (float) $row['weight'] : 0.0;
|
||
if ($w > 0) {
|
||
$candidateWeights[$i] = $w;
|
||
}
|
||
}
|
||
$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);
|
||
}
|
||
|
||
/**
|
||
* 按档位权重数组抽取 T1-T5
|
||
*/
|
||
private static function drawTierByWeightArray(array $tiers, array $weights): string
|
||
{
|
||
$total = array_sum($weights);
|
||
if ($total <= 0) {
|
||
return $tiers[random_int(0, count($tiers) - 1)];
|
||
}
|
||
$r = random_int(1, (int) $total);
|
||
$acc = 0;
|
||
foreach ($weights as $i => $w) {
|
||
$acc += (int) $w;
|
||
if ($r <= $acc) {
|
||
return $tiers[$i];
|
||
}
|
||
}
|
||
return $tiers[count($tiers) - 1];
|
||
}
|
||
|
||
/**
|
||
* 运行权重配比测试:仅按当前配置在内存中模拟 N 次抽奖,统计各 grid_number 落点数量。
|
||
* 不创建任何游玩记录(DicePlayRecord)、不扣券、不写钱包,仅用于验证权重配比效果。
|
||
*
|
||
* @param int $testCount 测试次数 100/500/1000/5000/10000
|
||
* @param bool $saveRecord 是否保存到 dice_reward_config_record(测试记录表,非游玩记录)
|
||
* @param int|null $adminId 执行人管理员ID
|
||
* @param int|null $lotteryConfigId 奖池配置ID(DiceLotteryPoolConfig),用于设定 T1-T5 档位概率;不传则使用 type=0 的配置或均等
|
||
* @return array{counts: array<int,int>, record_id: int|null} counts 为 grid_number=>出现次数
|
||
*/
|
||
public function runWeightTest(int $testCount, bool $saveRecord = true, ?int $adminId = null, ?int $lotteryConfigId = null): array
|
||
{
|
||
$allowedCounts = [100, 500, 1000, 5000, 10000];
|
||
if (!in_array($testCount, $allowedCounts, true)) {
|
||
throw new ApiException('测试次数仅支持 100、500、1000、5000、10000');
|
||
}
|
||
|
||
$grouped = [];
|
||
foreach (['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'] as $t) {
|
||
$grouped[$t] = $this->model::getCachedByTierForDirection($t, 0);
|
||
}
|
||
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
|
||
$tierWeights = [1, 1, 1, 1, 1];
|
||
$config = null;
|
||
if ($lotteryConfigId !== null && $lotteryConfigId > 0) {
|
||
$config = DiceLotteryPoolConfig::find($lotteryConfigId);
|
||
}
|
||
if (!$config) {
|
||
$config = DiceLotteryPoolConfig::where('type', 0)->find();
|
||
}
|
||
if ($config) {
|
||
$tierWeights = [
|
||
(int) ($config->t1_weight ?? 0),
|
||
(int) ($config->t2_weight ?? 0),
|
||
(int) ($config->t3_weight ?? 0),
|
||
(int) ($config->t4_weight ?? 0),
|
||
(int) ($config->t5_weight ?? 0),
|
||
];
|
||
if (array_sum($tierWeights) <= 0) {
|
||
$tierWeights = [1, 1, 1, 1, 1];
|
||
}
|
||
}
|
||
|
||
$counts = [];
|
||
$maxRetry = 20;
|
||
for ($i = 0; $i < $testCount; $i++) {
|
||
$tier = self::drawTierByWeightArray($tiers, $tierWeights);
|
||
$rewards = $grouped[$tier] ?? [];
|
||
if (empty($rewards)) {
|
||
continue;
|
||
}
|
||
$attempt = 0;
|
||
while ($attempt < $maxRetry) {
|
||
try {
|
||
$chosen = self::drawRewardByWeight($rewards);
|
||
$gridNumber = isset($chosen['grid_number']) ? (int) $chosen['grid_number'] : 0;
|
||
if ($gridNumber >= 5 && $gridNumber <= 30) {
|
||
$counts[$gridNumber] = ($counts[$gridNumber] ?? 0) + 1;
|
||
}
|
||
break;
|
||
} catch (\RuntimeException $e) {
|
||
if ($e->getMessage() === self::EXCEPTION_WEIGHT_ALL_ZERO) {
|
||
$attempt++;
|
||
continue;
|
||
}
|
||
throw $e;
|
||
}
|
||
}
|
||
}
|
||
|
||
$snapshot = [];
|
||
foreach ($grouped as $tierKey => $rows) {
|
||
foreach ($rows as $row) {
|
||
$snapshot[] = [
|
||
'id' => (int) ($row['id'] ?? 0),
|
||
'grid_number' => (int) ($row['grid_number'] ?? 0),
|
||
'tier' => (string) ($row['tier'] ?? ''),
|
||
'weight' => (int) ($row['weight'] ?? 0),
|
||
];
|
||
}
|
||
}
|
||
|
||
$tierWeightsSnapshot = [
|
||
'T1' => $tierWeights[0] ?? 0,
|
||
'T2' => $tierWeights[1] ?? 0,
|
||
'T3' => $tierWeights[2] ?? 0,
|
||
'T4' => $tierWeights[3] ?? 0,
|
||
'T5' => $tierWeights[4] ?? 0,
|
||
];
|
||
$recordId = null;
|
||
if ($saveRecord) {
|
||
$record = new DiceRewardConfigRecord();
|
||
$record->test_count = $testCount;
|
||
$record->weight_config_snapshot = $snapshot;
|
||
$record->tier_weights_snapshot = $tierWeightsSnapshot;
|
||
$record->lottery_config_id = $config ? (int) $config->id : null;
|
||
$record->result_counts = $counts;
|
||
$record->admin_id = $adminId;
|
||
$record->create_time = date('Y-m-d H:i:s');
|
||
$record->save();
|
||
$recordId = (int) $record->id;
|
||
}
|
||
|
||
return ['counts' => $counts, 'record_id' => $recordId];
|
||
}
|
||
}
|