diff --git a/saiadmin-artd/src/views/plugin/dice/play_record/index/index.vue b/saiadmin-artd/src/views/plugin/dice/play_record/index/index.vue index dde5119..4d6ec42 100644 --- a/saiadmin-artd/src/views/plugin/dice/play_record/index/index.vue +++ b/saiadmin-artd/src/views/plugin/dice/play_record/index/index.vue @@ -117,6 +117,8 @@ is_win: undefined, win_coin_min: undefined, win_coin_max: undefined, + roll_number_min: undefined, + roll_number_max: undefined, reward_ui_text: undefined, reward_tier: undefined, direction: undefined @@ -190,6 +192,7 @@ { prop: 'start_index', label: '起始索引', width: 90 }, { prop: 'target_index', label: '终点索引', width: 90 }, { prop: 'roll_array', label: '摇取点数', width: 140, useSlot: true }, + { prop: 'roll_number', label: '摇取点数和', width: 110, sortable: true }, { prop: 'reward_config_id', label: '奖励配置', diff --git a/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/edit-dialog.vue b/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/edit-dialog.vue index 7be20f6..b51ceb1 100644 --- a/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/edit-dialog.vue +++ b/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/edit-dialog.vue @@ -142,6 +142,17 @@
固定 5 个数,每个 1~6
+ + + { @@ -319,6 +332,10 @@ } } }) + // 若后端未返回 roll_number,根据摇取点数计算 + if (formData.roll_number == null && formData.rollArrayItems.length === 5) { + formData.roll_number = formData.rollArrayItems.reduce((s, n) => s + (n ?? 0), 0) || null + } } /** 将接口的 roll_array 转为固定 5 项数组,不足补 null */ @@ -355,10 +372,12 @@ const payload = { ...formData } as Record // 将 5 个输入值拼成 [1,2,3,4,5] 格式,确保每项为 1~6 的整数 const items = formData.rollArrayItems - payload.roll_array = items.map((n) => { + const rollArray = items.map((n) => { const v = n != null ? Number(n) : 1 return Math.min(6, Math.max(1, Number.isNaN(v) ? 1 : Math.floor(v))) }) + payload.roll_array = rollArray + payload.roll_number = formData.roll_number ?? rollArray.reduce((s, n) => s + n, 0) delete payload.rollArrayItems if (props.dialogType === 'add') { delete payload.id diff --git a/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/table-search.vue b/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/table-search.vue index d0653a4..b7d90ae 100644 --- a/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/table-search.vue +++ b/saiadmin-artd/src/views/plugin/dice/play_record/index/modules/table-search.vue @@ -63,6 +63,31 @@ + + +
+ + + +
+
+
diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue b/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue index 0846882..4bc02ce 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue +++ b/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue @@ -122,6 +122,7 @@ { prop: 'ui_text', label: '前端显示文本', align: 'center' }, { prop: 'real_ev', label: '真实资金结算', align: 'center' }, { prop: 'tier', label: '所属档位', sortable: true, align: 'center' }, + { prop: 'weight', label: '权重(%)', width: 100, align: 'center' }, // { prop: 'create_time', label: '创建时间', sortable: true, align: 'center' }, { prop: 'operation', diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/edit-dialog.vue b/saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/edit-dialog.vue index d33abe4..63d89e1 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/edit-dialog.vue +++ b/saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/edit-dialog.vue @@ -34,8 +34,12 @@ + + + + void) => { + if (formData.tier !== 'BIGWIN') { + callback() + return + } + const n = value != null ? Number(value) : NaN + if (Number.isNaN(n) || n < 0 || n > 100) { + callback(new Error('权重仅 BIGWIN 可设定,且必须为 0-100%')) + return + } + callback() + }, + trigger: 'blur' + } + ] }) /** @@ -107,6 +128,7 @@ ui_text: '', real_ev: '', tier: '', + weight: 0 as number, remark: '' } @@ -141,14 +163,21 @@ } /** - * 初始化表单数据 + * 初始化表单数据(数值字段转为 number,便于滑块/输入框正确回显) */ const initForm = () => { - if (props.data) { - for (const key in formData) { - if (props.data[key] != null && props.data[key] != undefined) { - ;(formData as any)[key] = props.data[key] - } + if (!props.data) return + const numKeys = ['id', 'grid_number', 'real_ev', 'weight'] + for (const key of Object.keys(formData)) { + if (!(key in props.data)) continue + const val = props.data[key] + if (val == null || val === undefined) continue + if (numKeys.includes(key)) { + const numVal = Number(val) + ;(formData as Record)[key] = + key === 'id' ? numVal || null : Number.isNaN(numVal) ? 0 : numVal + } else { + ;(formData as Record)[key] = val ?? '' } } } @@ -168,11 +197,18 @@ if (!formRef.value) return try { await formRef.value.validate() + const payload = { ...formData } + if (payload.tier !== 'BIGWIN') { + payload.weight = 0 + } else { + const w = Number(payload.weight) + payload.weight = Number.isNaN(w) ? 0 : Math.max(0, Math.min(100, w)) + } if (props.dialogType === 'add') { - await api.save(formData) + await api.save(payload) ElMessage.success('新增成功') } else { - await api.update(formData) + await api.update(payload) ElMessage.success('修改成功') } emit('success') diff --git a/server/app/api/controller/GameController.php b/server/app/api/controller/GameController.php index 072f097..75384cf 100644 --- a/server/app/api/controller/GameController.php +++ b/server/app/api/controller/GameController.php @@ -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) { diff --git a/server/app/api/logic/PlayStartLogic.php b/server/app/api/logic/PlayStartLogic.php index 011c7e1..a7749af 100644 --- a/server/app/api/logic/PlayStartLogic.php +++ b/server/app/api/logic/PlayStartLogic.php @@ -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_number(5=全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-6);sum=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); + } } diff --git a/server/app/dice/controller/play_record/DicePlayRecordController.php b/server/app/dice/controller/play_record/DicePlayRecordController.php index abe2f83..bd17270 100644 --- a/server/app/dice/controller/play_record/DicePlayRecordController.php +++ b/server/app/dice/controller/play_record/DicePlayRecordController.php @@ -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', ''], diff --git a/server/app/dice/logic/play_record/DicePlayRecordLogic.php b/server/app/dice/logic/play_record/DicePlayRecordLogic.php index 86b7526..e99a548 100644 --- a/server/app/dice/logic/play_record/DicePlayRecordLogic.php +++ b/server/app/dice/logic/play_record/DicePlayRecordLogic.php @@ -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; } diff --git a/server/app/dice/logic/reward_config/DiceRewardConfigLogic.php b/server/app/dice/logic/reward_config/DiceRewardConfigLogic.php index 2640b8a..a9457dc 100644 --- a/server/app/dice/logic/reward_config/DiceRewardConfigLogic.php +++ b/server/app/dice/logic/reward_config/DiceRewardConfigLogic.php @@ -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; + } } diff --git a/server/app/dice/model/play_record/DicePlayRecord.php b/server/app/dice/model/play_record/DicePlayRecord.php index aee8bf4..8171aba 100644 --- a/server/app/dice/model/play_record/DicePlayRecord.php +++ b/server/app/dice/model/play_record/DicePlayRecord.php @@ -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); + } + } } diff --git a/server/app/dice/model/reward_config/DiceRewardConfig.php b/server/app/dice/model/reward_config/DiceRewardConfig.php index a5517d7..2291ea1 100644 --- a/server/app/dice/model/reward_config/DiceRewardConfig.php +++ b/server/app/dice/model/reward_config/DiceRewardConfig.php @@ -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) + * 返回行含 weight(0-100):playStart 据此概率抽奖,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); + } + } } diff --git a/server/app/dice/validate/reward_config/DiceRewardConfigValidate.php b/server/app/dice/validate/reward_config/DiceRewardConfigValidate.php index 4aadbc6..d615d76 100644 --- a/server/app/dice/validate/reward_config/DiceRewardConfigValidate.php +++ b/server/app/dice/validate/reward_config/DiceRewardConfigValidate.php @@ -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; + } }