1.优化抽奖券的抽奖逻辑

This commit is contained in:
2026-06-03 17:36:57 +08:00
parent 307a942b8e
commit 5eb0ac24cd
5 changed files with 119 additions and 51 deletions

View File

@@ -54,14 +54,16 @@
"weightTest": {
"title": "One-Click Weight Test",
"alertTitle": "Bonus pool logic",
"alertBody": "Test mode is non-kill by default. When test kill mode is enabled, rules match production: compare default pool profit_amount to the safety line, with kill_enabled on and a killScore config present.",
"alertBody": "Kill switching is off by default. Enable “Test kill mode” below and set a test safety line (independent from the lottery pool config) to simulate kill triggers.",
"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 on: start from default pool profit_amount and accumulate each spin (paid: win_coin - paid_amount; free: win_coin). Once profit >= safety line and kill_enabled is on, subsequent paid draws use killScore; free draws still use the name=free pool.",
"labelKillModeEnabled": "Enable test kill mode",
"labelTestSafetyLine": "default safety line",
"killEnabledOn": "kill on",
"killEnabledOff": "kill off",
"poolProfitPrefix": "pool profit ",
"killModeHint": "When test kill mode is on: start from default pool profit_amount and accumulate each spin (paid: win_coin - paid_amount; free: win_coin). Once profit >= the test safety line below, subsequent paid draws use killScore; free draws still use the name=free pool.",
"killModePanelTitle": "Test kill mode",
"killModeSwitchOn": "On",
"killModeSwitchOff": "Off",
"labelTestSafetyLine": "Test safety line",
"testSafetyLineHint": "Used for this weight test only, independent from the pool config safety line. Use a lower value to observe kill switching quickly.",
"poolProfitRef": "Reference: default pool profit {profit}, pool config safety line {line}",
"killModeOffHint": "When off, all draws follow paid/free settings without kill switching.",
"sectionPaid": "Paid draws",
"sectionFreeAfterPlayAgain": "Free draw tier odds (after play-again)",
"tierProbHintFreeChain": "When using custom tier odds: T1T5 below apply when a free draw runs (tier roll; combined with dice_reward row weights).",

View File

@@ -54,14 +54,16 @@
"weightTest": {
"title": "一键测试权重",
"alertTitle": "彩金池逻辑说明",
"alertBody": "测试模式默认不启用杀分切换;开启「测试内杀分」后,判定规则与线上一致:以 default 彩金池累计盈利profit_amount对比安全线且需 kill_enabled=开启、存在 killScore 配置。",
"alertBody": "测试模式默认不启用杀分切换;开启下方「测试内杀分」后,可单独设置测试安全线(与彩金池配置中的安全线无关),用于模拟杀分触发。",
"chainModeHint": "模拟方式:只配置付费抽奖次数(顺/逆时针)。付费抽到「再来一次」或 T5 时,下一局自动为免费抽奖,底注与触发局相同,抽奖类型记为免费、付费金额记为 0。免费抽奖的档位概率由下方「免费抽奖」配置决定含通过再来一次触发的后续免费局。",
"killModeHint": "杀分开关开启后:从 default 奖池当前 profit_amount 起步,测试内逐局累加(付费=win_coin-paid_amount免费=win_coin当累计盈利 ≥ 安全线且 kill_enabled 开启时,后续付费抽奖切 killScore免费抽奖仍走 name=free 奖池。",
"labelKillModeEnabled": "开启测试内杀分",
"labelTestSafetyLine": "default 安全线",
"killEnabledOn": "杀分已开启",
"killEnabledOff": "杀分已关闭",
"poolProfitPrefix": "当前池盈利 ",
"killModeHint": "杀分开关开启后:从 default 奖池当前 profit_amount 起步逐局累加(付费=win_coin-paid_amount免费=win_coin当累计盈利 ≥ 下方「测试安全线」时,后续付费抽奖切 killScore免费抽奖仍走 name=free 奖池。",
"killModePanelTitle": "测试内杀分",
"killModeSwitchOn": "已开启",
"killModeSwitchOff": "已关闭",
"labelTestSafetyLine": "测试安全线",
"testSafetyLineHint": "仅用于本次权重测试,与彩金池配置页的安全线独立;可设较小值以便快速观察杀分切换效果。",
"poolProfitRef": "参考:当前 default 池盈利 {profit},彩金池配置安全线 {line}",
"killModeOffHint": "关闭时全程按付费/免费配置抽档,不模拟杀分切换。",
"sectionPaid": "付费抽奖",
"sectionFreeAfterPlayAgain": "免费抽奖(再来一次后的档位概率)",
"tierProbHintFreeChain": "当使用自定义档位时:以下为「免费抽奖」时 T1T5 的档位概率(仅在有免费局时参与摇档,与 dice_reward 格子权重共同决定结果)。",

View File

@@ -20,7 +20,7 @@
<ElForm :model="form" label-width="108px" class="weight-test-form">
<ElRow :gutter="16">
<ElCol :span="8">
<ElCol :span="12">
<ElFormItem :label="$t('page.weightTest.labelAnte')" prop="ante_config_id" required>
<ElSelect
v-model="form.ante_config_id"
@@ -38,21 +38,43 @@
</ElSelect>
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem :label="$t('page.weightTest.labelKillModeEnabled')" prop="kill_mode_enabled">
<ElSwitch v-model="form.kill_mode_enabled" />
</ElFormItem>
</ElCol>
<ElCol :span="8">
<ElFormItem :label="$t('page.weightTest.labelTestSafetyLine')">
<ElInput
:model-value="defaultPoolSafetyLineText"
readonly
:disabled="!form.kill_mode_enabled"
</ElRow>
<div class="kill-mode-panel">
<div class="kill-mode-head">
<div class="kill-mode-title">{{ $t('page.weightTest.killModePanelTitle') }}</div>
<ElSwitch
v-model="form.kill_mode_enabled"
:active-text="$t('page.weightTest.killModeSwitchOn')"
:inactive-text="$t('page.weightTest.killModeSwitchOff')"
inline-prompt
/>
</div>
<div v-if="form.kill_mode_enabled" class="kill-mode-body">
<ElFormItem
:label="$t('page.weightTest.labelTestSafetyLine')"
prop="test_safety_line"
class="kill-safety-item"
label-width="96px"
>
<ElInputNumber
v-model="form.test_safety_line"
:min="0"
:step="100"
controls-position="right"
style="width: 100%"
/>
</ElFormItem>
</ElCol>
</ElRow>
<div class="kill-mode-hint">{{ $t('page.weightTest.testSafetyLineHint') }}</div>
<div v-if="defaultPoolInfo" class="kill-mode-ref">
{{ $t('page.weightTest.poolProfitRef', {
profit: defaultPoolInfo.profit_amount,
line: defaultPoolInfo.safety_line
}) }}
</div>
</div>
<div v-else class="kill-mode-off-hint">{{ $t('page.weightTest.killModeOffHint') }}</div>
</div>
<ElRow :gutter="20" class="section-row">
<ElCol :span="12">
@@ -218,17 +240,8 @@
free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
paid_s_count: 100,
paid_n_count: 100,
kill_mode_enabled: false
})
const defaultPoolSafetyLineText = computed(() => {
const info = defaultPoolInfo.value
if (!info) {
return '-'
}
const killText = info.kill_enabled === 1
? t('page.weightTest.killEnabledOn')
: t('page.weightTest.killEnabledOff')
return `${info.safety_line}${killText}${t('page.weightTest.poolProfitPrefix')}${info.profit_amount}`
kill_mode_enabled: false,
test_safety_line: 5000
})
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
const paidLotteryOptions = computed(() =>
@@ -373,7 +386,7 @@
free_n_count: 0,
chain_free_mode: true,
kill_mode_enabled: form.kill_mode_enabled,
test_safety_line: defaultPoolInfo.value?.safety_line ?? 0
test_safety_line: form.test_safety_line
}
if (form.paid_lottery_config_id != null) {
payload.paid_lottery_config_id = form.paid_lottery_config_id
@@ -402,6 +415,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) {
@@ -498,6 +515,54 @@
}
}
.kill-mode-panel {
margin-bottom: 14px;
padding: 12px 14px;
border: 1px solid var(--el-border-color-lighter);
border-radius: var(--el-border-radius-base);
background: var(--el-fill-color-light);
}
.kill-mode-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.kill-mode-title {
font-size: 13px;
font-weight: 600;
color: var(--el-text-color-primary);
line-height: 1.4;
}
.kill-mode-body {
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed var(--el-border-color);
}
.kill-safety-item {
margin-bottom: 8px;
:deep(.el-form-item__label) {
white-space: nowrap;
}
}
.kill-mode-hint,
.kill-mode-off-hint,
.kill-mode-ref {
font-size: 12px;
line-height: 1.5;
color: var(--el-text-color-secondary);
}
.kill-mode-ref {
margin-top: 4px;
color: var(--el-text-color-placeholder);
}
.section-row {
margin-top: 4px;
}

View File

@@ -96,7 +96,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测试内启用杀分规则与线上一致default.profit_amount + safety_line + kill_enabled
* kill_mode_enabled=1测试内启用杀分当模拟池盈利达到 test_safety_line 后,付费抽奖切到 killScore
*/
#[Permission('一键测试权重', 'dice:reward:index:startWeightTest')]
public function startWeightTest(Request $request): Response

View File

@@ -17,7 +17,7 @@ use support\think\Db;
* 一键测试权重:单进程后台执行模拟摇色子,写入 dice_play_record_test 并更新 dice_reward_config_record 进度
* 抽奖规则与 PlayStartLogic 一致:
* - 付费未杀分按模拟玩家档位权重抽档lottery_config_id 记 default
* - 付费杀分:default.profit_amount + safety_line + kill_enabled 满足后切 killScore
* - 付费杀分:测试内杀分开启且模拟池盈利 >= test_safety_line 后切 killScore
* - 免费券name=free 奖池(无则 default排除 5/30 豹子
*/
class WeightTestRunner
@@ -115,10 +115,12 @@ class WeightTestRunner
DiceReward::clearRequestInstance();
$killModeEnabled = (int) ($record->kill_mode_enabled ?? 0) === 1;
$safetyLine = (int) ($configType0->safety_line ?? 0);
$dbKillEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1;
$testSafetyLine = (int) ($record->test_safety_line ?? 5000);
if ($testSafetyLine < 0) {
$testSafetyLine = 0;
}
// 彩金池累计盈利:与线上一致,从 default.profit_amount 起步并在测试内逐局累加
// 彩金池累计盈利:从 default.profit_amount 起步并在测试内逐局累加,与杀分判定值一致
$poolProfitTotal = (float) ($configType0->profit_amount ?? 0);
// 付费未杀分时的模拟玩家档位权重(自定义 > 快照 > 兜底奖池)
@@ -145,8 +147,7 @@ class WeightTestRunner
$paidPlayerWeights,
$freeTierWeightsCustom,
$killModeEnabled,
$safetyLine,
$dbKillEnabled,
$testSafetyLine,
$poolProfitTotal,
$resultCounts,
$tierCounts,
@@ -186,8 +187,7 @@ class WeightTestRunner
?array $paidPlayerWeights,
?array $freeTierWeightsCustom,
bool $killModeEnabled,
int $safetyLine,
bool $dbKillEnabled,
int $testSafetyLine,
float &$poolProfitTotal,
array &$resultCounts,
array &$tierCounts,
@@ -211,8 +211,7 @@ class WeightTestRunner
if ($isPaid) {
$useKillForPaid = $killModeEnabled
&& $dbKillEnabled
&& $poolProfitTotal >= $safetyLine
&& $poolProfitTotal >= $testSafetyLine
&& $killPoolConfig !== null;
if ($useKillForPaid) {
$cfg = $killPoolConfig;