1.修复抽奖档位不统一的问题

This commit is contained in:
2026-06-02 17:04:55 +08:00
parent 13dacc8fdd
commit c0d5258aee
8 changed files with 55 additions and 54 deletions

View File

@@ -8,7 +8,7 @@
"tabIndex": "Reward Index",
"tabBigwin": "Big Win Weights",
"tipIndex": "Dice points must be between 5 and 30 and unique in this table.",
"tierRecommendRules": "[Settlement vs tier] T1 (big prize): >2; T2 (small win): 2≥amount>1; T3 (rake): 1≥amount>0; T4 (try again): =0; T5 (penalty): 0>amount. Set recommended settlement per tier below. The Tier column is auto-calculated from settlement and cannot be edited manually.",
"tierRecommendRules": "[Settlement vs tier] T1 (big prize): >2; T2 (small win): 2≥amount>1; T3 (rake): 1≥amount>0; T4 (penalty): 0>amount; T5 (try again): =0. Set recommended settlement per tier below. The Tier column is auto-calculated from settlement and cannot be edited manually.",
"tierRecommendRealEv": "Recommended settlement",
"tierRecommendAutoMatch": "Auto-match tier when settlement changes",
"tierRecommendApplyAmount": "Fill recommended amount for rows with tier set",
@@ -17,8 +17,8 @@
"tierRecommendMatchTier": "Match all tiers from settlement",
"tierRecommendMatchTierOk": "Matched tier for {n} row(s) from settlement",
"tierRecommendMatchTierNone": "No rows to match",
"tierRecommendT4UiText": "再来一次",
"tierRecommendT4UiTextEn": "Once again",
"tierRecommendT5UiText": "再来一次",
"tierRecommendT5UiTextEn": "Once again",
"colTierAutoHint": "Auto-matched from settlement",
"tipBigwin": "Left to right: big-win points (read-only), display text, real EV, remark, weight (0~10000). Points 5 and 30 are fixed at 100%. This tab saves big-win weights only.",
"colId": "Index (id)",
@@ -52,7 +52,7 @@
"createRefPreviewClockwise": "Clockwise",
"createRefPreviewCounterclockwise": "Counter-clockwise",
"createRefPreviewTipUnchanged": "Dice points mapping is unchanged: weights in the preview are reused from current dice_reward; importing will not override existing weights.",
"createRefPreviewTipChanged": "Dice points mapping has changed: preview weights use the default value (1). After importing, adjust weights in the Dice Reward page if needed.",
"createRefPreviewTipChanged": "Dice points mapping has changed: preview weights use defaults (100 by default; sums 5/10/15/20/25/30 default to 1). After importing, adjust weights in the Dice Reward page if needed.",
"createRefPreviewSkipped": "{n} dice point(s) are missing in the reward index and were skipped (please complete all 26 points from 5 to 30).",
"createRefPreviewRefresh": "Refresh preview",
"createRefPreviewImport": "Import",
@@ -79,12 +79,12 @@
"infoNoBigwin": "No BIGWIN rows. Set tier to BIGWIN in the Reward Index tab first.",
"btnRuleGenerate": "Generate by rules",
"ruleGenerateTitle": "Generate reward index by rules",
"ruleGenerateRules": "[Generation logic (same as Create Reward Reference)]\n• 26 cells ordered by id ascending are positions 025; each rows grid_number is 530 and unique.\n• Roll D (530): start at the cell whose grid_number equals D (start_index); clockwise landing = (start position + D) mod 26; counter-clockwise = start D (if negative, +26).\n• Each reference rows “dice points” column is the roll D; tier / real_ev / display text come from the config at the landing id.\n\n[Leopard rolls]\nFor rolls 5, 10, 15, 20, 25, 30, clockwise and counter-clockwise landing tiers must NOT be T4 or T5 (avoid leopard roll + try again / penalty).\n\n[Settlement vs tier]\nT1: >2; T2: 2≥amount>1; T3: 1≥amount>0; T4: =0; T5: 0>amount. Set recommended settlement per tier below.\n\n[Inputs in this dialog]\nCount: T1/T4/T5 are fixed; T2 is minimum. Clockwise and counter-clockwise weighted counts must each satisfy the entered values.\nSettlement standard: same tier uses the same value. T1T3 and T5 use ui_text = settlement; T4 is fixed to \"再来一次\" / \"Once again\".",
"ruleGenerateRules": "[Generation logic (same as Create Reward Reference)]\n• 26 cells ordered by id ascending are positions 025; each rows grid_number is 530 and unique.\n• Roll D (530): start at the cell whose grid_number equals D (start_index); clockwise landing = (start position + D) mod 26; counter-clockwise = start D (if negative, +26).\n• Each reference rows “dice points” column is the roll D; tier / real_ev / display text come from the config at the landing id.\n\n[Leopard rolls]\nFor rolls 5, 10, 15, 20, 25, 30, clockwise and counter-clockwise landing tiers must NOT be T4 or T5 (avoid leopard roll + penalty / try again).\n\n[Settlement vs tier]\nT1: >2; T2: 2≥amount>1; T3: 1≥amount>0; T4: 0>amount; T5: =0. Set recommended settlement per tier below.\n\n[Inputs in this dialog]\nCount: T1/T4/T5 are fixed; T2 is minimum. Clockwise and counter-clockwise weighted counts must each satisfy the entered values.\nSettlement standard: same tier uses the same value. T1T4 use ui_text = settlement; T5 is fixed to \"再来一次\" / \"Once again\".",
"ruleGenT1Row": "T1 (big prize)",
"ruleGenT2Row": "T2 (small win / break-even)",
"ruleGenT3RealEvOnly": "T3 (rake)",
"ruleGenT4Row": "T4 (try again)",
"ruleGenT5Row": "T5 (penalty)",
"ruleGenT4Row": "T4 (penalty)",
"ruleGenT5Row": "T5 (try again)",
"ruleGenMinCount": "Min count",
"ruleGenFixedCount": "Fixed count (CW & CCW)",
"ruleGenRealEvStd": "real_ev standard",
@@ -92,8 +92,8 @@
"ruleGenInvalidT1RealEv": "T1 (big prize) settlement must satisfy: value > 2",
"ruleGenInvalidT2RealEv": "T2 (small win) settlement must satisfy: 1 < value ≤ 2",
"ruleGenInvalidT3RealEv": "T3 (rake) settlement must satisfy: 0 < value ≤ 1",
"ruleGenInvalidT4RealEv": "T4 (try again) settlement must be 0",
"ruleGenInvalidT5RealEv": "T5 (penalty) settlement must satisfy: value < 0",
"ruleGenInvalidT4RealEv": "T4 (penalty) settlement must satisfy: value < 0",
"ruleGenInvalidT5RealEv": "T5 (try again) settlement must be 0",
"ruleGenT1Min": "T1 fixed count (CW & CCW)",
"ruleGenT2Min": "T2 min (CW & CCW)",
"ruleGenT4Max": "T4 fixed count (CW & CCW)",

View File

@@ -8,7 +8,7 @@
"tabIndex": "奖励索引",
"tabBigwin": "大奖权重",
"tipIndex": "色子点数须在 530 之间且本表内不重复。",
"tierRecommendRules": "【结算金额与档位】【大奖】T1结算金额>2【小赚】T22>=结算金额>1【抽水】T31>=结算金额>0再来一次】T4结算金额=0【惩罚】T50>结算金额。下方可为各档位填写推荐结算金额;表格中「所属档位」随结算金额自动计算,不可手动修改。",
"tierRecommendRules": "【结算金额与档位】【大奖】T1结算金额>2【小赚】T22>=结算金额>1【抽水】T31>=结算金额>0惩罚】T40>结算金额;【再来一次】T5结算金额=0。下方可为各档位填写推荐结算金额;表格中「所属档位」随结算金额自动计算,不可手动修改。",
"tierRecommendRealEv": "推荐结算金额",
"tierRecommendAutoMatch": "修改结算金额时自动匹配档位",
"tierRecommendApplyAmount": "将推荐金额填入已选档位的行",
@@ -17,8 +17,8 @@
"tierRecommendMatchTier": "按结算金额匹配全部档位",
"tierRecommendMatchTierOk": "已根据结算金额为 {n} 行匹配档位",
"tierRecommendMatchTierNone": "没有可匹配档位的行",
"tierRecommendT4UiText": "再来一次",
"tierRecommendT4UiTextEn": "Once again",
"tierRecommendT5UiText": "再来一次",
"tierRecommendT5UiTextEn": "Once again",
"colTierAutoHint": "根据结算金额自动匹配",
"tipBigwin": "从左至右:中大奖点数(不可改)、显示信息、实际中奖、备注、权重(0~10000)。点数 5、30 权重固定 100%。本表单独立提交,仅提交大奖权重。",
"colId": "索引(id)",
@@ -52,7 +52,7 @@
"createRefPreviewClockwise": "顺时针",
"createRefPreviewCounterclockwise": "逆时针",
"createRefPreviewTipUnchanged": "检测到色子点数映射未变化预览中权重将复用当前奖励对照表dice_reward的权重导入时不会覆盖现有权重。",
"createRefPreviewTipChanged": "检测到色子点数映射已变化预览中权重将使用默认值1确认导入后可再到「奖励对照」页面调整权重。",
"createRefPreviewTipChanged": "检测到色子点数映射已变化:预览中权重将使用默认值(默认 100点数和为 5/10/15/20/25/30 默认 1确认导入后可再到「奖励对照」页面调整权重。",
"createRefPreviewSkipped": "有 {n} 个点数在当前奖励索引中缺失,已跳过生成(请先补齐 530 共 26 个点数)。",
"createRefPreviewRefresh": "刷新预览",
"createRefPreviewImport": "确认导入",
@@ -79,12 +79,12 @@
"infoNoBigwin": "暂无 BIGWIN 档位配置,请先在「奖励索引」中设置 tier 为 BIGWIN",
"btnRuleGenerate": "按规则生成",
"ruleGenerateTitle": "按规则生成奖励索引",
"ruleGenerateRules": "【生成逻辑(与创建奖励对照一致)】\n• 盘面 26 格按 id 升序为位置 025每条配置的 grid_number 为 530 且不重复。\n• 摇取点数 D530起点为「grid_number=D」所在格位的 id即 start_index顺时针落点位置 = (起点位置 + D) mod 26逆时针落点 = 起点位置 D若小于 0 则 +26。\n• 对照表每条记录的「色子点数」列为摇取点数 D档位、真实结算、显示文案取自落点格位对应 id 的配置。\n\n【豹子摇取点数】\n摇取点数为 5、10、15、20、25、30 时,其顺/逆时针落点档位不能为 T4、T5避免对照表上出现豹子点数 + 再来一次/惩罚)。\n\n【结算金额 与 档位】\n【大奖】T1>2【小赚】T22>=金额>1【抽水】T31>=金额>0再来一次】T4=0【惩罚】T50>金额。下方可为各档位填写推荐结算金额标准,生成时写入配置。\n\n【本弹窗输入】\n条数T1/T4/T5「固定」T2「不少于」——顺时针与逆时针的加权条数每条摇取结果计一次须分别满足所填数值T1、T4 与 T5 分开填写。\n结算金额 标准:同档位各格使用同一数值。生成时 T1T3、T5 的显示文本 = 结算金额T4 固定为「再来一次」/「Once again」。备注仍区分完美回本/小赚等。",
"ruleGenerateRules": "【生成逻辑(与创建奖励对照一致)】\n• 盘面 26 格按 id 升序为位置 025每条配置的 grid_number 为 530 且不重复。\n• 摇取点数 D530起点为「grid_number=D」所在格位的 id即 start_index顺时针落点位置 = (起点位置 + D) mod 26逆时针落点 = 起点位置 D若小于 0 则 +26。\n• 对照表每条记录的「色子点数」列为摇取点数 D档位、真实结算、显示文案取自落点格位对应 id 的配置。\n\n【豹子摇取点数】\n摇取点数为 5、10、15、20、25、30 时,其顺/逆时针落点档位不能为 T4、T5避免对照表上出现豹子点数 + 惩罚/再来一次)。\n\n【结算金额 与 档位】\n【大奖】T1>2【小赚】T22>=金额>1【抽水】T31>=金额>0惩罚】T40>金额【再来一次】T5=0。下方可为各档位填写推荐结算金额标准,生成时写入配置。\n\n【本弹窗输入】\n条数T1/T4/T5「固定」T2「不少于」——顺时针与逆时针的加权条数每条摇取结果计一次须分别满足所填数值T1、T4 与 T5 分开填写。\n结算金额 标准:同档位各格使用同一数值。生成时 T1T4 的显示文本 = 结算金额T5 固定为「再来一次」/「Once again」。备注仍区分完美回本/小赚等。",
"ruleGenT1Row": "T1 大奖",
"ruleGenT2Row": "T2 小赚/回本",
"ruleGenT3RealEvOnly": "T3 抽水",
"ruleGenT4Row": "T4 再来一次",
"ruleGenT5Row": "T5 惩罚",
"ruleGenT4Row": "T4 惩罚",
"ruleGenT5Row": "T5 再来一次",
"ruleGenMinCount": "最少条数",
"ruleGenFixedCount": "固定条数(顺/逆)",
"ruleGenRealEvStd": "结算金额",
@@ -92,8 +92,8 @@
"ruleGenInvalidT1RealEv": "T1大奖结算金额须满足值 > 2",
"ruleGenInvalidT2RealEv": "T2小赚结算金额须满足1 < 值 ≤ 2",
"ruleGenInvalidT3RealEv": "T3抽水结算金额须满足0 < 值 ≤ 1",
"ruleGenInvalidT4RealEv": "T4再来一次)结算金额须 0",
"ruleGenInvalidT5RealEv": "T5惩罚)结算金额须满足:值 < 0",
"ruleGenInvalidT4RealEv": "T4惩罚)结算金额须满足:值 < 0",
"ruleGenInvalidT5RealEv": "T5再来一次)结算金额须 0",
"ruleGenT1Min": "T1 固定条数(顺/逆)",
"ruleGenT2Min": "T2 最少条数(顺/逆)",
"ruleGenT4Max": "T4 固定条数(顺/逆)",

View File

@@ -29,7 +29,7 @@
<span class="tier-recommend-hint">{{ $t('page.configPage.tierRecommendRealEv') }}</span>
<ElInputNumber
v-model="tierRecommend[tk]"
:disabled="tk === 'T4'"
:disabled="tk === 'T5'"
:step="0.1"
:precision="2"
controls-position="right"
@@ -613,9 +613,9 @@
function applyRecommendRealEvToRow(row: IndexRow, tier: TierRecommendKey) {
const ev = tierRecommend[tier]
row.real_ev = ev
if (tier === 'T4') {
row.ui_text = t('page.configPage.tierRecommendT4UiText')
row.ui_text_en = t('page.configPage.tierRecommendT4UiTextEn')
if (tier === 'T5') {
row.ui_text = t('page.configPage.tierRecommendT5UiText')
row.ui_text_en = t('page.configPage.tierRecommendT5UiTextEn')
} else {
applyRealEvDisplay(row, ev)
}
@@ -675,9 +675,9 @@
function handleRealEvChange(row: IndexRow) {
const n = rowRealEvNumber(row)
syncRowTierFromRealEv(row)
if (row.tier === 'T4') {
row.ui_text = t('page.configPage.tierRecommendT4UiText')
row.ui_text_en = t('page.configPage.tierRecommendT4UiTextEn')
if (row.tier === 'T5') {
row.ui_text = t('page.configPage.tierRecommendT5UiText')
row.ui_text_en = t('page.configPage.tierRecommendT5UiTextEn')
} else {
applyRealEvDisplay(row, n)
}

View File

@@ -58,8 +58,8 @@ export const DEFAULT_TIER_REAL_EV_STANDARDS: TierRealEvStandards = {
T1: 3,
T2: 1.5,
T3: 0.5,
T4: 0,
T5: -0.4
T4: -0.4,
T5: 0
}
/**
@@ -67,13 +67,19 @@ export const DEFAULT_TIER_REAL_EV_STANDARDS: TierRealEvStandards = {
* T1 大奖:>2
* T2 小赚2>=金额>1
* T3 抽水1>=金额>0
* T4 再来一次:=0
* T5 惩罚0>金额
* T4 惩罚0>金额
* T5 再来一次:=0
*/
export function inferTierFromRealEv(realEv: number): IndexTier | '' {
if (!Number.isFinite(realEv)) {
return ''
}
if (realEv === 0) {
return 'T5'
}
if (realEv < 0) {
return 'T4'
}
if (realEv > 2) {
return 'T1'
}
@@ -83,12 +89,6 @@ export function inferTierFromRealEv(realEv: number): IndexTier | '' {
if (realEv > 0 && realEv <= 1) {
return 'T3'
}
if (realEv === 0) {
return 'T4'
}
if (realEv < 0) {
return 'T5'
}
return ''
}
@@ -105,10 +105,10 @@ export function validateTierRealEvStandards(s: TierRealEvStandards): string | nu
if (!Number.isFinite(s.T3) || !(s.T3 > 0 && s.T3 <= 1)) {
return 'ruleGenInvalidT3RealEv'
}
if (!Number.isFinite(s.T4) || s.T4 !== 0) {
if (!Number.isFinite(s.T4) || !(s.T4 < 0)) {
return 'ruleGenInvalidT4RealEv'
}
if (!Number.isFinite(s.T5) || s.T5 >= 0) {
if (!Number.isFinite(s.T5) || s.T5 !== 0) {
return 'ruleGenInvalidT5RealEv'
}
return null
@@ -305,7 +305,7 @@ function uiTextByTierWhenStandards(
tier: IndexTier,
realEv: number
): { ui_text: string; ui_text_en: string } {
if (tier === 'T4') {
if (tier === 'T5') {
return { ui_text: '再来一次', ui_text_en: 'Once again' }
}
const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv)
@@ -377,13 +377,13 @@ export function buildRowsFromTiers(
const f = uiTextByTierWhenStandards(tier, real_ev)
ui_text = f.ui_text
ui_text_en = f.ui_text_en
remark = '再来一次'
remark = '惩罚'
} else {
real_ev = standards.T5
const f = uiTextByTierWhenStandards(tier, real_ev)
ui_text = f.ui_text
ui_text_en = f.ui_text_en
remark = '惩罚'
remark = '再来一次'
}
} else if (tier === 'T1') {
real_ev = 101 + ((id * 17 + grid_number * 3) % 398)
@@ -415,17 +415,17 @@ export function buildRowsFromTiers(
ui_text_en = f.ui_text_en
remark = '抽水'
} else if (tier === 'T4') {
real_ev = 0
ui_text = '再来一次'
ui_text_en = 'Once again'
remark = '再来一次'
} else {
t4Seq++
real_ev = -101 - t4Seq * 15
const f = uiTextFromRealEv(real_ev)
ui_text = f.ui_text
ui_text_en = f.ui_text_en
remark = '惩罚'
} else {
real_ev = 0
ui_text = '再来一次'
ui_text_en = 'Once again'
remark = '再来一次'
}
rows.push({
id,

View File

@@ -187,9 +187,9 @@ class PlayStartLogic
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
$realEv = (float) ($chosen['real_ev'] ?? 0);
// T5/再来一次:以奖励行 tier 为准,并以摇奖档位 $tier 兜底(与 reward_tier 展示一致,避免 dice_reward 行缺 tier 时不发券)
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
if ($isTierT5 === false && (string) ($tier ?? '') === 'T5') {
$isTierT5 = true;
$isTierPlayAgain = (string) ($chosen['tier'] ?? '') === 'T5';
if ($isTierPlayAgain === false && (string) ($tier ?? '') === 'T5') {
$isTierPlayAgain = true;
}
// 摇色子中奖:按 dice_reward_config.real_ev 直接结算(已乘 ante
$rewardWinCoin = round($realEv * $ante, 2);
@@ -230,7 +230,7 @@ class PlayStartLogic
// 中 BIGWIN 豹子:不走原奖励流程,不记录原奖励,不触发 T5 再来一次,仅发放豹子奖金
$rewardWinCoin = 0.0;
$realEv = 0.0;
$isTierT5 = false;
$isTierPlayAgain = false;
} else {
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
}
@@ -276,7 +276,7 @@ class PlayStartLogic
$startIndex,
$targetIndex,
$rollArray,
$isTierT5,
$isTierPlayAgain,
$tier,
&$record,
&$settledWinCoin
@@ -372,7 +372,7 @@ class PlayStartLogic
// 若本局中奖档位为 T5则额外赠送 1 次免费抽奖次数:
// - 新结构:写入 free_ticketante=本局注数count+1
// - 兼容旧结构free_ticket_count +1
if ($isTierT5) {
if ($isTierPlayAgain) {
$ft = $p->free_ticket ?? null;
$ftAnte = null;
$ftCount = 0;
@@ -654,6 +654,7 @@ class PlayStartLogic
*/
public function simulateOnePlay($config, int $direction, int $lotteryType = 0, int $ante = 1, ?array $customTierWeights = null, ?int $configDeptId = null): array
{
$useKillMode = false;
$rewardInstance = DiceReward::getCachedInstance($configDeptId);
$byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
$maxTierRetry = 10;

View File

@@ -95,7 +95,7 @@ class DiceRewardController extends BaseController
* 一键测试权重:创建测试记录并启动单进程后台执行,写入 dice_play_record_test
* 参数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
* chain_free_mode=1仅按付费次数模拟付费抽到再来一次/T4 则在队列中插入免费局同底注、lottery_type=免费、paid_amount=0
* kill_mode_enabled=1测试内启用杀分当模拟玩家累计盈利达到 test_safety_line 后,付费抽奖切到 killScore
*/
#[Permission('一键测试权重', 'dice:reward:index:startWeightTest')]

View File

@@ -19,7 +19,7 @@ use think\model\relation\BelongsTo;
* @property $player_id 玩家id
* @property $admin_id 关联玩家所属管理员IDDicePlayer.admin_id
* @property $use_coins 消耗硬币
* @property $ante 底注/注数历史购买记录默认为1T5再来一次写入本次注数)
* @property $ante 底注/注数历史购买记录默认为1T4再来一次写入本次注数)
* @property $total_ticket_count 总抽奖次数
* @property $paid_ticket_count 购买抽奖次数
* @property $free_ticket_count 赠送抽奖次数

View File

@@ -83,7 +83,7 @@
其中地图的索引可以按照需求点击图中的按规则生成
并且规则尽可能符合:结算金额>2 → T12>=结算金额>1 → T21>=结算金额>0 → T3结算金额=0 → T4再来一次0>结算金额 → T5惩罚
并且规则尽可能符合:结算金额>2 → T12>=结算金额>1 → T21>=结算金额>0 → T30>结算金额 → T4惩罚);结算金额=0 → T5再来一次
![image.png](/docs/picture/guide_16.png)