优化游玩记录DicePlayRecord

This commit is contained in:
2026-03-07 14:40:33 +08:00
parent 316506b597
commit 6632923213
13 changed files with 286 additions and 55 deletions

View File

@@ -150,6 +150,7 @@ class GameController extends OpenController
'start_index' => 0,
'target_index' => 0,
'roll_array' => '[]',
'roll_number' => 0,
'status' => PlayStartLogic::RECORD_STATUS_TIMEOUT,
]);
} catch (\Exception $inner) {

View File

@@ -34,8 +34,10 @@ class PlayStartLogic
/** 开启对局最低余额 = |DiceRewardConfig 最小 real_ev + 100| */
private const MIN_COIN_EXTRA = 100;
/** 豹子号中大奖额外平台币(可从 dice_config 等配置读取 */
/** 豹子号中大奖额外平台币(无 BIGWIN 配置时兜底 */
private const SUPER_WIN_BONUS = 500;
/** 可触发超级大奖的 grid_number5=全1 10=全2 15=全3 20=全4 25=全5 */
private const SUPER_WIN_GRID_NUMBERS = [5, 10, 15, 20, 25];
/**
* 执行一局游戏
@@ -112,7 +114,30 @@ class PlayStartLogic
? (int) ($startRecord['s_end_index'] ?? 0)
: (int) ($startRecord['n_end_index'] ?? 0);
$rollNumber = (int) ($startRecord['grid_number'] ?? 0);
$rollArray = $this->generateRollArrayFromSum($rollNumber);
$realEv = (float) ($chosen['real_ev'] ?? 0);
$rewardWinCoin = 100 + $realEv; // 摇色子中奖平台币 = 100 + DiceRewardConfig.real_ev
// 当抽到的 grid_number 为 5/10/15/20/25 时,从缓存查 tier=BIGWIN 同 grid_number 的配置,按 weight 决定是否生成豹子组合
$superWinCoin = 0;
$isWin = 0;
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
$weight = $bigWinConfig !== null
? max(0.0, min(100.0, (float) ($bigWinConfig['weight'] ?? 0)))
: 100.0;
$roll = mt_rand(1, 10000) / 10000;
if ($roll <= $weight / 100) {
$rollArray = $this->getSuperWinRollArray($rollNumber);
$isWin = 1;
$superWinCoin = $bigWinConfig !== null
? 100 + (float) ($bigWinConfig['real_ev'] ?? 0)
: self::SUPER_WIN_BONUS;
} else {
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
}
} else {
$rollArray = $this->generateRollArrayFromSum($rollNumber);
}
Log::info(sprintf(
'摇取点数 roll_number=%d, 方向=%d, start_index=%d, target_index=%d',
@@ -121,17 +146,6 @@ class PlayStartLogic
$startIndex,
$targetIndex
));
$realEv = (float) ($chosen['real_ev'] ?? 0);
$rewardWinCoin = 100 + $realEv; // 摇色子中奖平台币 = 100 + DiceRewardConfig.real_ev
$isSuperWin = DicePlayRecord::isSuperWin($rollArray);
// 豹子中大奖时从缓存查 tier=BIGWIN 且 grid_number=roll_number 的奖励配置,取 real_ev 计算中大奖平台币
$superWinCoin = 0;
if ($isSuperWin) {
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
$superWinCoin = $bigWinConfig !== null
? 100 + (float) ($bigWinConfig['real_ev'] ?? 0)
: self::SUPER_WIN_BONUS;
}
$winCoin = $superWinCoin + $rewardWinCoin; // 赢取平台币 = 中大奖 + 摇色子中奖
$record = null;
@@ -139,7 +153,6 @@ class PlayStartLogic
$rewardId = $chosenId;
$configName = (string) ($config->name ?? '');
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
$isWin = $isSuperWin ? 1 : 0;
try {
Db::transaction(function () use (
$playerId,
@@ -173,6 +186,7 @@ class PlayStartLogic
'start_index' => $startIndex,
'target_index' => $targetIndex,
'roll_array' => is_array($rollArray) ? json_encode($rollArray) : $rollArray,
'roll_number' => is_array($rollArray) ? array_sum($rollArray) : 0,
'lottery_name' => $configName,
'status' => self::RECORD_STATUS_SUCCESS,
]);
@@ -239,6 +253,7 @@ class PlayStartLogic
'start_index' => $startIndex,
'target_index' => 0,
'roll_array' => '[]',
'roll_number' => 0,
'status' => self::RECORD_STATUS_TIMEOUT,
]);
} catch (\Throwable $_) {
@@ -290,4 +305,51 @@ class PlayStartLogic
shuffle($arr);
return array_values($arr);
}
/**
* 豹子组合grid_number 5->[1,1,1,1,1]10->[2,2,2,2,2]15->[3,3,3,3,3]20->[4,4,4,4,4]25->[5,5,5,5,5]
* @return int[]
*/
private function getSuperWinRollArray(int $gridNumber): array
{
$n = (int) ($gridNumber / 5);
$n = max(1, min(5, $n));
return array_fill(0, 5, $n);
}
/**
* 生成总和为 $sum 且非豹子的 5 个色子1-6sum=5 时仅 [1,1,1,1,1] 可能,仍返回该组合
* @return int[]
*/
private function generateNonSuperWinRollArrayWithSum(int $sum): array
{
$sum = max(5, min(30, $sum));
$super = $this->getSuperWinRollArray($sum);
if ($sum === 5) {
return $super;
}
$arr = $super;
$maxAttempts = 20;
for ($a = 0; $a < $maxAttempts; $a++) {
$idx = array_rand($arr);
$j = array_rand($arr);
if ($idx === $j) {
$j = ($j + 1) % 5;
}
$i = $idx;
if ($arr[$i] >= 2 && $arr[$j] <= 5) {
$arr[$i]--;
$arr[$j]++;
shuffle($arr);
return array_values($arr);
}
if ($arr[$i] <= 5 && $arr[$j] >= 2) {
$arr[$i]++;
$arr[$j]--;
shuffle($arr);
return array_values($arr);
}
}
return $this->generateRollArrayFromSum($sum);
}
}

View File

@@ -46,6 +46,8 @@ class DicePlayRecordController extends BaseController
['is_win', ''],
['win_coin_min', ''],
['win_coin_max', ''],
['roll_number_min', ''],
['roll_number_max', ''],
['reward_ui_text', ''],
['reward_tier', ''],
['direction', ''],

View File

@@ -43,16 +43,18 @@ class DicePlayRecordLogic extends BaseLogic
}
/**
* 将 roll_array 从数组转为 JSON 字符串
* 将 roll_array 转为 JSON 字符串,并确保 roll_number 与摇取点数一致
*/
private function normalizeRollArray(array $data): array
{
if (!array_key_exists('roll_array', $data)) {
return $data;
}
$val = $data['roll_array'];
if (is_array($val)) {
$data['roll_array'] = json_encode($val, JSON_UNESCAPED_UNICODE);
if (array_key_exists('roll_array', $data)) {
$val = $data['roll_array'];
if (is_array($val)) {
$data['roll_array'] = json_encode($val, JSON_UNESCAPED_UNICODE);
if (!isset($data['roll_number'])) {
$data['roll_number'] = array_sum($val);
}
}
}
return $data;
}

View File

@@ -13,6 +13,7 @@ use app\dice\model\reward_config\DiceRewardConfig;
/**
* 奖励配置逻辑层
* weight 仅 tier=BIGWIN 时可设定,保存时非 BIGWIN 强制 weight=0
*/
class DiceRewardConfigLogic extends BaseLogic
{
@@ -24,4 +25,36 @@ class DiceRewardConfigLogic extends BaseLogic
$this->model = new DiceRewardConfig();
}
/**
* 新增前:非 BIGWIN 时强制 weight=0
*/
public function add(array $data): mixed
{
$data = $this->normalizeWeightByTier($data);
return parent::add($data);
}
/**
* 修改前:非 BIGWIN 时强制 weight=0
*/
public function edit($id, array $data): mixed
{
$data = $this->normalizeWeightByTier($data);
return parent::edit($id, $data);
}
/**
* 仅 tier=BIGWIN 时保留 weight且限制 0-100否则强制为 0
*/
private function normalizeWeightByTier(array $data): array
{
$tier = isset($data['tier']) ? (string) $data['tier'] : '';
if ($tier !== 'BIGWIN') {
$data['weight'] = 0;
return $data;
}
$w = isset($data['weight']) ? (float) $data['weight'] : 0;
$data['weight'] = max(0, min(100, $w));
return $data;
}
}

View File

@@ -31,6 +31,7 @@ use think\model\relation\BelongsTo;
* @property $start_index 起始索引
* @property $target_index 结束索引
* @property $roll_array 摇取点数,格式:[1,2,3,4,5]5个点数
* @property $roll_number 摇取点数和5个色子点数之和5-30
* @property $lottery_name 奖池名
* @property $status 状态:0=超时/失败 1=成功
* @property $create_time 创建时间
@@ -222,4 +223,20 @@ class DicePlayRecord extends BaseModel
$query->where('direction', '=', $value);
}
}
/** 摇取点数和下限 */
public function searchRollNumberMinAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('roll_number', '>=', $value);
}
}
/** 摇取点数和上限 */
public function searchRollNumberMaxAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('roll_number', '<=', $value);
}
}
}

View File

@@ -20,6 +20,7 @@ use support\think\Cache;
* @property $ui_text 前端显示文本
* @property $real_ev 真实资金结算
* @property $tier 所属档位
* @property $weight 权重%(仅 tier=BIGWIN 时可设定0-100
* @property $s_end_index 顺时针结束索引
* @property $n_end_index 逆时针结束索引
* @property $remark 备注
@@ -80,7 +81,8 @@ class DiceRewardConfig extends BaseModel
}
/**
* 重新从数据库加载并写入缓存(保存时调用),构建列表与索引
* 重新从数据库加载并写入缓存(DiceRewardConfig 新增/修改/删除后调用),构建列表与索引
* 实例化结果含完整行(含 weight供 playStart 从缓存中查找 BIGWIN 的 weight 按概率抽奖
*/
public static function refreshCache(): void
{
@@ -148,10 +150,11 @@ class DiceRewardConfig extends BaseModel
}
/**
* 从缓存按档位 + 色子点数取一条奖励配置(用于超级大奖 tier=BIGWIN + grid_number=roll_number
* 从缓存实例按档位 + 色子点数取一条奖励配置(用于超级大奖 tier=BIGWIN + grid_number=roll_number
* 返回行含 weight0-100playStart 据此概率抽奖weight=100 表示摇到该 roll_number 时 100% 中超级大奖
* @param string $tier 档位,如 BIGWIN
* @param int $gridNumber 色子点数(摇出总和)
* @return array|null 配置行或 null
* @param int $gridNumber 色子点数(摇出总和 roll_number
* @return array|null 配置行(含 weight、real_ev 等)或 null
*/
public static function getCachedByTierAndGridNumber(string $tier, int $gridNumber): ?array
{
@@ -277,4 +280,20 @@ class DiceRewardConfig extends BaseModel
$query->where('tier', '=', $value);
}
}
/** 权重下限(仅 tier=BIGWIN 时有意义) */
public function searchWeightMinAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('weight', '>=', $value);
}
}
/** 权重上限 */
public function searchWeightMaxAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('weight', '<=', $value);
}
}
}

View File

@@ -10,45 +10,56 @@ use plugin\saiadmin\basic\BaseValidate;
/**
* 奖励配置验证器
* weight 仅当 tier=BIGWIN 时可设定,且严格限制 0-100%
*/
class DiceRewardConfigValidate extends BaseValidate
{
/**
* 定义验证规则
*/
protected $rule = [
protected $rule = [
'grid_number' => 'require',
'ui_text' => 'require',
'real_ev' => 'require',
'tier' => 'require',
'ui_text' => 'require',
'real_ev' => 'require',
'tier' => 'require',
'weight' => 'checkWeight',
];
/**
* 定义错误信息
*/
protected $message = [
protected $message = [
'grid_number' => '色子点数必须填写',
'ui_text' => '前端显示文本必须填写',
'real_ev' => '真实资金结算必须填写',
'tier' => '所属档位必须填写',
'ui_text' => '前端显示文本必须填写',
'real_ev' => '真实资金结算必须填写',
'tier' => '所属档位必须填写',
'weight' => '权重仅 tier=BIGWIN 时可设定,且必须为 0-100',
];
/**
* 定义场景
*/
protected $scene = [
'save' => [
'grid_number',
'ui_text',
'real_ev',
'tier',
],
'update' => [
'grid_number',
'ui_text',
'real_ev',
'tier',
],
'save' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'weight'],
'update' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'weight'],
];
/**
* weight仅 tier=BIGWIN 时可设定,严格限制 0-100%
*/
protected function checkWeight($value, $rule = '', $data = []): bool
{
$tier = isset($data['tier']) ? (string) $data['tier'] : '';
if ($tier !== 'BIGWIN') {
return true;
}
$num = is_numeric($value) ? (float) $value : null;
if ($num === null) {
return false;
}
if ($num < 0 || $num > 100) {
return false;
}
return true;
}
}