From afd6113927b43ac316aa0f923212c7129f0d8573 Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Fri, 27 Mar 2026 17:50:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=80=E9=94=AE=E6=B5=8B=E8=AF=95=E6=9D=83?= =?UTF-8?q?=E9=87=8D-=E6=96=B0=E5=A2=9E=E5=AE=89=E5=85=A8=E7=BA=BF?= =?UTF-8?q?=E6=9D=80=E5=88=86=E6=9C=BA=E5=88=B6=EF=BC=8C=E4=BF=9D=E8=AF=81?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=B0=E6=8D=AE=E5=90=88=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/locales/langs/en/dice/reward.json | 6 +- .../langs/en/dice/reward_config_record.json | 5 ++ .../src/locales/langs/zh/dice/reward.json | 6 +- .../langs/zh/dice/reward_config_record.json | 5 ++ .../src/views/plugin/dice/api/reward/index.ts | 2 + .../index/modules/weight-test-dialog.vue | 27 ++++++- .../dice/reward_config_record/index/index.vue | 6 ++ .../index/modules/table-search.vue | 26 +++++++ .../reward/DiceRewardController.php | 3 + .../DiceRewardConfigRecordController.php | 2 + .../DiceRewardConfigRecordLogic.php | 7 ++ .../reward_config_record/WeightTestRunner.php | 72 ++++++++----------- .../DiceRewardConfigRecord.php | 18 +++++ 13 files changed, 140 insertions(+), 45 deletions(-) diff --git a/saiadmin-artd/src/locales/langs/en/dice/reward.json b/saiadmin-artd/src/locales/langs/en/dice/reward.json index 62a5576..4853717 100644 --- a/saiadmin-artd/src/locales/langs/en/dice/reward.json +++ b/saiadmin-artd/src/locales/langs/en/dice/reward.json @@ -56,8 +56,11 @@ "weightTest": { "title": "One-Click Weight Test", "alertTitle": "Bonus pool logic", - "alertBody": "Same as playStart draw: uses name=default safety line and kill switch; when profit is below the line, paid tickets use player tier weights (custom below), free tickets use killScore; when profit reaches the line and kill is on, both use killScore.", + "alertBody": "Test mode is non-kill by default. You can enable kill mode below with switch + safety line: once simulated player cumulative profit reaches the line, paid draws switch to killScore.", "chainModeHint": "Simulation: set paid spin counts only (CW/CCW). If a paid draw hits “play again” (or T5), the next draw is free with the same ante, lottery type free, paid amount 0. Free-draw tier odds are configured below (including chained free plays).", + "killModeHint": "When test kill mode is enabled: use simulated player cumulative profit as trigger; once cumulative profit >= safety line, subsequent paid draws use killScore. Free draws still follow the configured free settings.", + "labelKillModeEnabled": "Enable test kill mode", + "labelTestSafetyLine": "Test safety line", "sectionPaid": "Paid draws", "sectionFreeAfterPlayAgain": "Free draw tier odds (after play-again)", "tierProbHintFreeChain": "When using custom tier odds: T1–T5 below apply when a free draw runs (tier roll; combined with dice_reward row weights).", @@ -80,6 +83,7 @@ "btnCancel": "Cancel", "warnAnte": "Ante must be greater than 0", "warnPaidSpins": "Paid clockwise + counter-clockwise spin counts must be greater than 0", + "warnTestSafetyLine": "Test safety line must be greater than or equal to 0", "warnTotalSpins": "At least one of paid/free direction spin counts must be greater than 0", "warnPaidTierSumPositive": "When no paid pool is selected, T1–T5 odds sum must be greater than 0", "warnPaidTierSumMax": "Paid T1–T5 odds sum cannot exceed 100%", diff --git a/saiadmin-artd/src/locales/langs/en/dice/reward_config_record.json b/saiadmin-artd/src/locales/langs/en/dice/reward_config_record.json index 1f13525..cc5eeff 100644 --- a/saiadmin-artd/src/locales/langs/en/dice/reward_config_record.json +++ b/saiadmin-artd/src/locales/langs/en/dice/reward_config_record.json @@ -2,6 +2,10 @@ "toolbar": { "viewDetail": "View Detail" }, + "search": { + "paidPlannedSpins": "Planned paid spins", + "ante": "Ante" + }, "table": { "id": "ID", "clockwiseAbbr": "CW", @@ -12,6 +16,7 @@ "chainModeYes": "Yes", "chainModeNo": "No", "paidPlannedSpins": "Planned paid spins", + "ante": "Ante", "playAgainCount": "Play-again count", "progressDraws": "{over} done", "progressFailed": "{over} before fail", diff --git a/saiadmin-artd/src/locales/langs/zh/dice/reward.json b/saiadmin-artd/src/locales/langs/zh/dice/reward.json index 7d31478..0b9cbc3 100644 --- a/saiadmin-artd/src/locales/langs/zh/dice/reward.json +++ b/saiadmin-artd/src/locales/langs/zh/dice/reward.json @@ -56,8 +56,11 @@ "weightTest": { "title": "一键测试权重", "alertTitle": "彩金池逻辑说明", - "alertBody": "与 playStart 抽奖逻辑一致:使用 name=default 的安全线、杀分开关;盈利未达安全线时,付费抽奖券使用玩家自身权重(下方自定义档位),免费抽奖券使用 killScore 配置;盈利达到安全线且杀分开启时,付费/免费均使用 killScore 配置。", + "alertBody": "测试模式默认不启用杀分切换;可通过下方“杀分开关 + 安全线”在测试内启用:当模拟玩家累计盈利达到安全线后,付费抽奖切换到 killScore。", "chainModeHint": "模拟方式:只配置付费抽奖次数(顺/逆时针)。付费抽到「再来一次」或 T5 时,下一局自动为免费抽奖,底注与触发局相同,抽奖类型记为免费、付费金额记为 0。免费抽奖的档位概率由下方「免费抽奖」配置决定(含通过再来一次触发的后续免费局)。", + "killModeHint": "杀分开关开启后:以“模拟玩家累计盈利”作为判定值;当累计盈利 >= 安全线时,后续付费抽奖按 killScore 配置抽取;免费抽奖仍按“免费抽奖配置”执行。", + "labelKillModeEnabled": "开启测试内杀分", + "labelTestSafetyLine": "测试安全线", "sectionPaid": "付费抽奖", "sectionFreeAfterPlayAgain": "免费抽奖(再来一次后的档位概率)", "tierProbHintFreeChain": "当使用自定义档位时:以下为「免费抽奖」时 T1~T5 的档位概率(仅在有免费局时参与摇档,与 dice_reward 格子权重共同决定结果)。", @@ -80,6 +83,7 @@ "btnCancel": "取消", "warnAnte": "底注 ante 必须大于 0", "warnPaidSpins": "付费抽奖顺时针与逆时针次数之和须大于 0", + "warnTestSafetyLine": "测试安全线必须大于或等于 0", "warnTotalSpins": "付费或免费至少一种方向次数之和大于 0", "warnPaidTierSumPositive": "付费未选奖池时,T1~T5 档位概率之和需大于 0", "warnPaidTierSumMax": "付费档位概率 T1~T5 之和不能超过 100%", diff --git a/saiadmin-artd/src/locales/langs/zh/dice/reward_config_record.json b/saiadmin-artd/src/locales/langs/zh/dice/reward_config_record.json index 53147de..0fb6ebe 100644 --- a/saiadmin-artd/src/locales/langs/zh/dice/reward_config_record.json +++ b/saiadmin-artd/src/locales/langs/zh/dice/reward_config_record.json @@ -2,6 +2,10 @@ "toolbar": { "viewDetail": "查看详情" }, + "search": { + "paidPlannedSpins": "计划付费次数", + "ante": "底注" + }, "table": { "id": "ID", "clockwiseAbbr": "顺", @@ -12,6 +16,7 @@ "chainModeYes": "是", "chainModeNo": "否", "paidPlannedSpins": "计划付费次数", + "ante": "底注", "playAgainCount": "再来一次次数", "progressDraws": "已完成 {over} 次", "progressFailed": "失败前 {over} 次", diff --git a/saiadmin-artd/src/views/plugin/dice/api/reward/index.ts b/saiadmin-artd/src/views/plugin/dice/api/reward/index.ts index 51feb2f..59490d0 100644 --- a/saiadmin-artd/src/views/plugin/dice/api/reward/index.ts +++ b/saiadmin-artd/src/views/plugin/dice/api/reward/index.ts @@ -66,6 +66,8 @@ export default { paid_lottery_config_id?: number free_lottery_config_id?: number chain_free_mode?: boolean + kill_mode_enabled?: boolean + test_safety_line?: number s_count?: number n_count?: number paid_s_count?: number diff --git a/saiadmin-artd/src/views/plugin/dice/reward/index/modules/weight-test-dialog.vue b/saiadmin-artd/src/views/plugin/dice/reward/index/modules/weight-test-dialog.vue index 8c5b2bc..beea694 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward/index/modules/weight-test-dialog.vue +++ b/saiadmin-artd/src/views/plugin/dice/reward/index/modules/weight-test-dialog.vue @@ -14,10 +14,25 @@ {{ $t('page.weightTest.chainModeHint') }} + + {{ $t('page.weightTest.killModeHint') }} + + + + + + +
{{ $t('page.weightTest.sectionPaid') }}
, free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record, paid_s_count: 100, - paid_n_count: 100 + paid_n_count: 100, + kill_mode_enabled: false, + test_safety_line: 5000 }) const lotteryOptions = ref>([]) /** 付费抽奖券可选档位:name=default */ @@ -242,7 +259,9 @@ paid_n_count: form.paid_n_count, free_s_count: 0, free_n_count: 0, - chain_free_mode: true + chain_free_mode: true, + kill_mode_enabled: form.kill_mode_enabled, + test_safety_line: form.test_safety_line } if (form.paid_lottery_config_id != null) { payload.paid_lottery_config_id = form.paid_lottery_config_id @@ -266,6 +285,10 @@ ElMessage.warning(t('page.weightTest.warnPaidSpins')) return false } + if (form.kill_mode_enabled && (form.test_safety_line == null || form.test_safety_line < 0)) { + ElMessage.warning(t('page.weightTest.warnTestSafetyLine')) + return false + } const needPaidTier = form.paid_lottery_config_id == null const needFreeTier = form.free_lottery_config_id == null if (needPaidTier) { diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/index.vue b/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/index.vue index a2b7cc5..070f26f 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/index.vue +++ b/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/index.vue @@ -225,6 +225,12 @@ width: 120, align: 'center' }, + { + prop: 'ante', + label: 'page.table.ante', + width: 90, + align: 'center' + }, { prop: 'play_again_count', label: 'page.table.playAgainCount', diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/modules/table-search.vue b/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/modules/table-search.vue index d1d8c79..b45fa16 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/modules/table-search.vue +++ b/saiadmin-artd/src/views/plugin/dice/reward_config_record/index/modules/table-search.vue @@ -8,6 +8,32 @@ @search="handleSearch" @expand="handleExpand" > + + + + + + + + + + diff --git a/server/app/dice/controller/reward/DiceRewardController.php b/server/app/dice/controller/reward/DiceRewardController.php index f0046b0..ea4d62e 100644 --- a/server/app/dice/controller/reward/DiceRewardController.php +++ b/server/app/dice/controller/reward/DiceRewardController.php @@ -85,6 +85,7 @@ class DiceRewardController extends BaseController * 参数:lottery_config_id 可选;paid_tier_weights / free_tier_weights 自定义档位; * paid_s_count, paid_n_count * chain_free_mode=1:仅按付费次数模拟;付费抽到再来一次/T5 则在队列中插入免费局(同底注、lottery_type=免费、paid_amount=0) + * kill_mode_enabled=1:测试内启用杀分;当模拟玩家累计盈利达到 test_safety_line 后,付费抽奖切到 killScore */ #[Permission('一键测试权重', 'dice:reward:index:startWeightTest')] public function startWeightTest(Request $request): Response @@ -100,6 +101,8 @@ class DiceRewardController extends BaseController 'paid_tier_weights' => $post['paid_tier_weights'] ?? null, 'free_tier_weights' => $post['free_tier_weights'] ?? null, 'chain_free_mode' => $post['chain_free_mode'] ?? null, + 'kill_mode_enabled' => $post['kill_mode_enabled'] ?? null, + 'test_safety_line' => $post['test_safety_line'] ?? null, ]; $adminId = isset($this->adminInfo['id']) ? (int) $this->adminInfo['id'] : null; try { diff --git a/server/app/dice/controller/reward_config_record/DiceRewardConfigRecordController.php b/server/app/dice/controller/reward_config_record/DiceRewardConfigRecordController.php index 8e3ea33..e6bbfd4 100644 --- a/server/app/dice/controller/reward_config_record/DiceRewardConfigRecordController.php +++ b/server/app/dice/controller/reward_config_record/DiceRewardConfigRecordController.php @@ -38,6 +38,8 @@ class DiceRewardConfigRecordController extends BaseController public function index(Request $request): Response { $where = $request->more([ + ['paid_planned_spins', ''], + ['ante', ''], ]); $query = $this->logic->search($where); $data = $this->logic->getList($query); diff --git a/server/app/dice/logic/reward_config_record/DiceRewardConfigRecordLogic.php b/server/app/dice/logic/reward_config_record/DiceRewardConfigRecordLogic.php index dba7339..006130a 100644 --- a/server/app/dice/logic/reward_config_record/DiceRewardConfigRecordLogic.php +++ b/server/app/dice/logic/reward_config_record/DiceRewardConfigRecordLogic.php @@ -270,6 +270,11 @@ class DiceRewardConfigRecordLogic extends BaseLogic $paidS = isset($params['paid_s_count']) ? (int) $params['paid_s_count'] : 0; $paidN = isset($params['paid_n_count']) ? (int) $params['paid_n_count'] : 0; $chainFreeMode = !empty($params['chain_free_mode']); + $killModeEnabled = !empty($params['kill_mode_enabled']); + $testSafetyLine = isset($params['test_safety_line']) ? (int) $params['test_safety_line'] : 5000; + if ($testSafetyLine < 0) { + throw new ApiException('test_safety_line must be greater than or equal to 0'); + } foreach ([$paidS, $paidN] as $c) { if ($c !== 0 && !in_array($c, $allowed, true)) { @@ -398,6 +403,8 @@ class DiceRewardConfigRecordLogic extends BaseLogic $record = new DiceRewardConfigRecord(); $plannedPaidSpins = $paidS + $paidN; $record->chain_free_mode = $chainFreeMode ? 1 : 0; + $record->kill_mode_enabled = $killModeEnabled ? 1 : 0; + $record->test_safety_line = $testSafetyLine; $record->paid_planned_spins = $plannedPaidSpins; // 总抽奖次数与 test_count 仅在任务成功结束时写入(见 WeightTestRunner::markSuccess) $record->test_count = 0; diff --git a/server/app/dice/logic/reward_config_record/WeightTestRunner.php b/server/app/dice/logic/reward_config_record/WeightTestRunner.php index 1c46660..d83d4f3 100644 --- a/server/app/dice/logic/reward_config_record/WeightTestRunner.php +++ b/server/app/dice/logic/reward_config_record/WeightTestRunner.php @@ -14,7 +14,7 @@ use support\think\Db; /** * 一键测试权重:单进程后台执行模拟摇色子,写入 dice_play_record_test 并更新 dice_reward_config_record 进度 - * 抽奖逻辑与 PlayStartLogic 一致:使用 name=default 的安全线、杀分开关;盈利<安全线时付费用玩家权重、免费用 killScore;盈利>=安全线且杀分开启时付费/免费均用 killScore + * 支持测试内杀分:当模拟玩家累计盈利达到安全线后,付费抽奖切换到 killScore */ class WeightTestRunner { @@ -41,8 +41,7 @@ class WeightTestRunner ]; /** - * 执行指定测试记录:按付费/免费、顺/逆方向交替模拟(付费顺→付费逆→免费顺→免费逆),每 10 条写入一次测试表并更新进度 - * 使用与 playStart 相同的彩金池逻辑:name=default 的安全线/kill_enabled;付费用 paid_tier_weights(玩家权重)或 killScore;免费用 killScore + * 执行指定测试记录:按付费次数模拟,若命中 T5 则链式插入免费局(同方向同底注) * @param int $recordId dice_reward_config_record.id */ public function run(int $recordId): void @@ -68,8 +67,6 @@ class WeightTestRunner $this->markFailed($recordId, '彩金池配置 name=default 不存在'); return; } - $safetyLine = (int) ($configType0->safety_line ?? 0); - $killEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1; $paidTierWeightsCustom = (is_array($record->paid_tier_weights ?? null) && $record->paid_tier_weights !== []) ? $record->paid_tier_weights @@ -103,8 +100,14 @@ class WeightTestRunner DiceRewardConfig::clearRequestInstance(); DiceReward::clearRequestInstance(); - // 彩金池累计盈利:用于判断是否触发杀分(不再依赖单个玩家累计盈利) - $poolProfitTotal = floatval($configType0->profit_amount ?? 0); + $killModeEnabled = (int) ($record->kill_mode_enabled ?? 0) === 1; + $testSafetyLine = (int) ($record->test_safety_line ?? 5000); + if ($testSafetyLine < 0) { + $testSafetyLine = 0; + } + + // 测试内“玩家累计盈利”:用于控制付费局是否切换杀分 + $playerProfitTotal = 0.0; $playLogic = new PlayStartLogic(); $resultCounts = []; @@ -121,13 +124,12 @@ class WeightTestRunner $ante, $paidPoolConfig, $freePoolConfig, + $configType1, $paidTierWeightsCustom, $freeTierWeightsCustom, - $configType0, - $configType1, - $safetyLine, - $killEnabled, - $poolProfitTotal, + $killModeEnabled, + $testSafetyLine, + $playerProfitTotal, $resultCounts, $tierCounts, $buffer, @@ -157,13 +159,12 @@ class WeightTestRunner int $ante, $paidPoolConfig, $freePoolConfig, + $killPoolConfig, ?array $paidTierWeightsCustom, ?array $freeTierWeightsCustom, - $configType0, - $configType1, - int $safetyLine, - bool $killEnabled, - float &$poolProfitTotal, + bool $killModeEnabled, + int $testSafetyLine, + float &$playerProfitTotal, array &$resultCounts, array &$tierCounts, array &$buffer, @@ -185,17 +186,23 @@ class WeightTestRunner $lotteryType = $isPaid ? 0 : 1; if ($isPaid) { - $usePoolWeights = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null; - $cfg = $usePoolWeights ? $configType1 : $paidPoolConfig; - $customWeights = $usePoolWeights ? null : $paidTierWeightsCustom; + $useKillForPaid = $killModeEnabled && $playerProfitTotal >= $testSafetyLine && $killPoolConfig !== null; + if ($useKillForPaid) { + $cfg = $killPoolConfig; + $customWeights = null; + } else { + $cfg = $paidPoolConfig; + $customWeights = $paidTierWeightsCustom; + } } else { - $useKillMode = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null; - $cfg = $useKillMode ? $configType1 : $freePoolConfig; - $customWeights = $useKillMode ? null : $freeTierWeightsCustom; + $cfg = $freePoolConfig; + $customWeights = $freeTierWeightsCustom; } $row = $playLogic->simulateOnePlay($cfg, $dir, $lotteryType, $playAnte, $customWeights); - $this->accumulateProfitForDefault($row, $lotteryType, $cfg, $configType0, $poolProfitTotal); + $winCoin = (float) ($row['win_coin'] ?? 0); + $paidAmount = (float) ($row['paid_amount'] ?? 0); + $playerProfitTotal += $winCoin - $paidAmount; $this->aggregate($row, $resultCounts, $tierCounts); $buffer[] = $this->rowForInsert($row, $recordId); $done++; @@ -210,23 +217,6 @@ class WeightTestRunner } } - /** - * 累加彩金池累计盈利,用于触发杀分,与 PlayStartLogic 一致 - * @param int $lotteryType 0=付费券,1=免费券 - * @param object $usedConfig 本次使用的奖池配置(仅用于校验非空) - * @param object $configType0 name=default 的彩金池 - * @param float $playerProfitTotal 实际为“彩金池累计盈利”滚动值 - */ - private function accumulateProfitForDefault(array $row, int $lotteryType, $usedConfig, $configType0, float &$playerProfitTotal): void - { - if (($lotteryType !== 0 && $lotteryType !== 1) || $usedConfig === null || $configType0 === null || !isset($row['win_coin'])) { - return; - } - $winCoin = (float) $row['win_coin']; - $paidAmount = (float) ($row['paid_amount'] ?? 0); - $playerProfitTotal += $lotteryType === 0 ? ($winCoin - $paidAmount) : $winCoin; - } - private function aggregate(array $row, array &$resultCounts, array &$tierCounts): void { $grid = (int) ($row['roll_number_for_count'] ?? $row['roll_number'] ?? 0); diff --git a/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php b/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php index 6b2963d..0324580 100644 --- a/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php +++ b/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php @@ -30,6 +30,8 @@ use think\model\relation\HasMany; * @property int $paid_s_count 付费抽奖顺时针次数 * @property int $paid_n_count 付费抽奖逆时针次数 * @property int $chain_free_mode 1=链式再来一次免费抽奖 + * @property int $kill_mode_enabled 测试内杀分开关 1=开启 + * @property int $test_safety_line 测试内安全线(模拟玩家累计盈利阈值) * @property int $paid_planned_spins 计划付费抽奖次数(顺+逆) * @property int $play_again_count 再来一次次数(T5触发次数) * @property array|null $paid_tier_weights 付费自定义档位权重 T1-T5 @@ -68,6 +70,22 @@ class DiceRewardConfigRecord extends BaseModel return $this->hasMany(DicePlayRecordTest::class, 'reward_config_record_id', 'id'); } + /** 计划付费抽奖次数(顺+逆) */ + public function searchPaidPlannedSpinsAttr($query, $value): void + { + if ($value !== '' && $value !== null) { + $query->where('paid_planned_spins', '=', $value); + } + } + + /** 底注/注数(dice_ante_config.mult) */ + public function searchAnteAttr($query, $value): void + { + if ($value !== '' && $value !== null) { + $query->where('ante', '=', $value); + } + } + /** * 根据关联的 DicePlayRecordTest 统计平台赚取平台币 * platform_profit = 关联的付费(lottery_type=0)付费金额求和(paid_amount) - 关联的 win_coin 求和