From 6ed34b97df346278bc3f5e5604bf348a2b074136 Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Tue, 31 Mar 2026 17:23:16 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=96=B0=E8=AE=BE=E7=BD=AE=E6=8A=BD?= =?UTF-8?q?=E5=A5=96=E5=BA=95=E6=B3=A8=E9=87=91=E9=A2=9D=E4=B8=BA1?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E9=A1=B5=E9=9D=A2=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../langs/en/dice/lottery_pool_config.json | 2 +- .../langs/zh/dice/lottery_pool_config.json | 2 +- .../plugin/dice/ante_config/index/index.vue | 16 ++++-- .../plugin/dice/play_record/index/index.vue | 9 ++-- .../dice/play_record_test/index/index.vue | 4 +- .../views/plugin/dice/player/index/index.vue | 2 +- .../dice/player_wallet_record/index/index.vue | 8 +-- .../index/modules/edit-dialog.vue | 19 +++---- .../views/plugin/dice/reward/index/index.vue | 6 +-- .../index/modules/weight-edit-dialog.vue | 6 +-- .../index/modules/weight-ratio-dialog.vue | 6 +-- .../plugin/dice/reward_config/index/index.vue | 13 ++++- .../utils/generateIndexByRules.ts | 4 +- .../dice/reward_config_record/index/index.vue | 2 +- server/app/api/controller/GameController.php | 3 +- server/app/api/logic/GameLogic.php | 14 ++--- server/app/api/logic/PlayStartLogic.php | 54 +++++++++---------- .../play_record/DicePlayRecordController.php | 4 +- .../DicePlayRecordTestController.php | 4 +- .../dice/model/play_record/DicePlayRecord.php | 2 +- .../play_record_test/DicePlayRecordTest.php | 4 +- .../DiceRewardConfigRecord.php | 2 +- ...e_play_record_add_ante_and_paid_amount.sql | 2 +- 项目文档.md | 24 ++++----- 24 files changed, 118 insertions(+), 94 deletions(-) diff --git a/saiadmin-artd/src/locales/langs/en/dice/lottery_pool_config.json b/saiadmin-artd/src/locales/langs/en/dice/lottery_pool_config.json index b6c6cd4..db55fc6 100644 --- a/saiadmin-artd/src/locales/langs/en/dice/lottery_pool_config.json +++ b/saiadmin-artd/src/locales/langs/en/dice/lottery_pool_config.json @@ -23,7 +23,7 @@ "poolName": "Pool Name", "playerProfit": "Player Total Profit (profit_amount):", "realtime": "Live", - "profitCalcHint": "Profit per round: paid = win_coin (incl. BIGWIN) - paid_amount (= ante×100); free = win_coin. Refreshes every 2s while open.", + "profitCalcHint": "Profit per round: paid = win_coin (incl. BIGWIN) - paid_amount (= ante×1); free = win_coin. Refreshes every 2s while open.", "tierRuleTitle": "Tier Rule", "tierRuleContent": "When player profit in this pool is below safety line, use player T*_weight; when above or equal, use pool T*_weight (kill).", "killScoreWeights": "Kill weights", diff --git a/saiadmin-artd/src/locales/langs/zh/dice/lottery_pool_config.json b/saiadmin-artd/src/locales/langs/zh/dice/lottery_pool_config.json index dc1bf1a..7442ab1 100644 --- a/saiadmin-artd/src/locales/langs/zh/dice/lottery_pool_config.json +++ b/saiadmin-artd/src/locales/langs/zh/dice/lottery_pool_config.json @@ -23,7 +23,7 @@ "poolName": "池子名称", "playerProfit": "玩家累计盈利(profit_amount):", "realtime": "实时", - "profitCalcHint": "计算方式:付费每局按“赢取平台币 win_coin(含 BIGWIN)减去付费金额 压注金额paid_amount(= 压注倍数ante×100)”累加;免费每局按“玩家赢得平台币win_coin”累加。弹窗打开期间每 2 秒自动刷新", + "profitCalcHint": "计算方式:付费每局按“赢取平台币 win_coin(含 BIGWIN)减去付费金额 压注金额paid_amount(= 压注倍数ante×1)”累加;免费每局按“玩家赢得平台币win_coin”累加。弹窗打开期间每 2 秒自动刷新", "tierRuleTitle": "抽奖档位规则", "tierRuleContent": "当玩家在当前彩金池的累计盈利 低于安全线 时,按 玩家 的 T*_weight 权重抽取档位;当累计盈利 高于或等于安全线 时,按 当前彩金池 的 T*_weight 权重抽取档位(杀分)。", "killScoreWeights": "杀分权重", diff --git a/saiadmin-artd/src/views/plugin/dice/ante_config/index/index.vue b/saiadmin-artd/src/views/plugin/dice/ante_config/index/index.vue index 4da7ac2..e23a089 100644 --- a/saiadmin-artd/src/views/plugin/dice/ante_config/index/index.vue +++ b/saiadmin-artd/src/views/plugin/dice/ante_config/index/index.vue @@ -6,7 +6,11 @@ @@ -501,11 +503,18 @@ typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev) ? row.real_ev : Number(row.real_ev) - const text = Number.isNaN(n) ? '' : String(n) + const text = Number.isNaN(n) ? '' : Number(n).toFixed(2) row.ui_text = text row.ui_text_en = text } + function formatMoney2(val: unknown): string { + if (val === '' || val === null || val === undefined) return '-' + const n = typeof val === 'number' ? val : Number(val) + if (!Number.isFinite(n)) return '-' + return n.toFixed(2) + } + async function handleCreateRewardReference() { try { await ElMessageBox.confirm( diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts b/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts index a186bab..5d71790 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts +++ b/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts @@ -278,13 +278,13 @@ function uiTextByTierWhenStandards( if (tier === 'T5') { return { ui_text: '再来一次', ui_text_en: 'Once again' } } - const value = String(realEv) + const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv) return { ui_text: value, ui_text_en: value } } /** 展示文案:直接使用真实结算值(中英文相同) */ function uiTextFromRealEv(realEv: number): { ui_text: string; ui_text_en: string } { - const value = String(realEv) + const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv) return { ui_text: value, ui_text_en: value } } 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 070f26f..e7f79ca 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 @@ -149,7 +149,7 @@ if (v === null || v === undefined || v === '') return dash const n = Number(v) if (Number.isNaN(n)) return dash - return String(n) + return n.toFixed(2) } /** 链式再来一次:1=是(新库字段),JSON 旧数据用 tier_weights_snapshot.chain_free_mode */ diff --git a/server/app/api/controller/GameController.php b/server/app/api/controller/GameController.php index 4fac01d..4a23e07 100644 --- a/server/app/api/controller/GameController.php +++ b/server/app/api/controller/GameController.php @@ -74,7 +74,7 @@ class GameController extends BaseController * 购买抽奖券 * POST /api/game/buyLotteryTickets * header: token(由 TokenMiddleware 注入 request->player_id) - * body: count = 1 | 5 | 10(1次/100coin, 5次/500coin, 10次/1000coin) + * body: count = 1 | 5 | 10(1次/1coin, 5次/5coin, 10次/10coin) */ // public function buyLotteryTickets(Request $request): Response // { @@ -217,6 +217,7 @@ class GameController extends BaseController } } } + $data['tier'] = $data['reward_tier'] ?? ''; return $this->success($data); } catch (ApiException $e) { diff --git a/server/app/api/logic/GameLogic.php b/server/app/api/logic/GameLogic.php index 9dd3a8d..6003126 100644 --- a/server/app/api/logic/GameLogic.php +++ b/server/app/api/logic/GameLogic.php @@ -17,9 +17,9 @@ use support\think\Db; class GameLogic { public const PACKAGES = [ - 1 => ['coin' => 100, 'paid' => 1, 'free' => 0], // 1次/100coin - 5 => ['coin' => 500, 'paid' => 5, 'free' => 1], // 5张/500coin(5购买+1赠送,共6次) - 10 => ['coin' => 1000, 'paid' => 10, 'free' => 3], // 10张/1000coin(10购买+3赠送,共13次) + 1 => ['coin' => 1, 'paid' => 1, 'free' => 0], // 1次/1coin + 5 => ['coin' => 5, 'paid' => 5, 'free' => 1], // 5张/5coin(5购买+1赠送,共6次) + 10 => ['coin' => 10, 'paid' => 10, 'free' => 3], // 10张/10coin(10购买+3赠送,共13次) ]; /** 钱包流水类型:购买抽奖次数 */ @@ -52,7 +52,7 @@ class GameLogic throw new ApiException('Insufficient balance'); } - $coinAfter = $coinBefore - $cost; + $coinAfter = round($coinBefore - $cost, 2); $totalBefore = (int) ($player->total_ticket_count ?? 0); $paidBefore = (int) ($player->paid_ticket_count ?? 0); $freeBefore = (int) ($player->free_ticket_count ?? 0); @@ -94,7 +94,7 @@ class GameLogic DicePlayerWalletRecord::create([ 'player_id' => $playerId, 'admin_id' => $adminId, - 'coin' => -$cost, + 'coin' => round(-$cost, 2), 'type' => self::WALLET_TYPE_BUY_DRAW, 'wallet_before' => $coinBefore, 'wallet_after' => $coinAfter, @@ -107,7 +107,7 @@ class GameLogic DicePlayerTicketRecord::create([ 'player_id' => $playerId, 'admin_id' => $adminId, - 'use_coins' => $cost, + 'use_coins' => round($cost, 2), 'ante' => 1, 'total_ticket_count' => $addTotal, 'paid_ticket_count' => $addPaid, @@ -121,7 +121,7 @@ class GameLogic } return [ - 'coin' => (float) $coinAfter, + 'coin' => round((float) $coinAfter, 2), 'total_ticket_count' => (int) $totalAfter, 'paid_ticket_count' => (int) $paidAfter, 'free_ticket_count' => (int) $freeAfter, diff --git a/server/app/api/logic/PlayStartLogic.php b/server/app/api/logic/PlayStartLogic.php index 826a6d0..26c1b02 100644 --- a/server/app/api/logic/PlayStartLogic.php +++ b/server/app/api/logic/PlayStartLogic.php @@ -37,8 +37,8 @@ class PlayStartLogic /** 对局状态:超时/失败 */ public const RECORD_STATUS_TIMEOUT = 0; - /** 单注费用(对应原票价 100) */ - private const UNIT_COST = 100; + /** 单注费用(抽奖券基础费用) */ + private const UNIT_COST = 1.0; /** 免费抽奖注数缓存 key 前缀(用于强制下一局注数一致) */ private const FREE_ANTE_KEY_PREFIX = 'api:game:free_ante:'; /** 免费抽奖注数缓存过期(秒) */ @@ -122,8 +122,8 @@ class PlayStartLogic throw new ApiException('未达抽奖余额 ' . $needMinBalance . ',无法开始游戏'); } - // 付费抽奖:开始前扣除费用 ante * 100,不足则提示余额不足 - $paidAmount = $ticketType === self::LOTTERY_TYPE_PAID ? ($ante * self::UNIT_COST) : 0; + // 付费抽奖:开始前扣除费用 ante * UNIT_COST,不足则提示余额不足 + $paidAmount = $ticketType === self::LOTTERY_TYPE_PAID ? round($ante * self::UNIT_COST, 2) : 0.0; if ($ticketType === self::LOTTERY_TYPE_PAID && $coin < $paidAmount) { throw new ApiException('余额不足'); } @@ -188,12 +188,12 @@ class PlayStartLogic if ($isTierT5 === false && (string) ($tier ?? '') === 'T5') { $isTierT5 = true; } - // 摇色子中奖:按 dice_reward_config.real_ev 直接结算(已乘 ante);不再叠加票价 100 - $rewardWinCoin = $realEv * $ante; + // 摇色子中奖:按 dice_reward_config.real_ev 直接结算(已乘 ante) + $rewardWinCoin = round($realEv * $ante, 2); // 豹子判定:5/30 必豹子;10/15/20/25 按 DiceRewardConfig 中 BIGWIN 该点数的 weight 判定(0-10000,10000=100%) // 杀分档位:不触发豹子,5/30 已在上方抽取时排除,10/15/20/25 仅生成非豹子组合 - $superWinCoin = 0; + $superWinCoin = 0.0; $isWin = 0; $bigWinRealEv = 0.0; if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) { @@ -223,10 +223,10 @@ class PlayStartLogic $rollArray = $this->getSuperWinRollArray($rollNumber); $isWin = 1; $bigWinEv = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS; - $superWinCoin = $bigWinEv * $ante; + $superWinCoin = round($bigWinEv * $ante, 2); // 中 BIGWIN 豹子:不走原奖励流程,不记录原奖励,不触发 T5 再来一次,仅发放豹子奖金 - $rewardWinCoin = 0; - $realEv = 0; + $rewardWinCoin = 0.0; + $realEv = 0.0; $isTierT5 = false; } else { $rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber); @@ -243,7 +243,7 @@ class PlayStartLogic $startIndex, $targetIndex )); - $winCoin = $superWinCoin + $rewardWinCoin; // 赢取平台币 = 中大奖 + 摇色子中奖(豹子时 rewardWinCoin 已为 0) + $winCoin = round($superWinCoin + $rewardWinCoin, 2); // 赢取平台币 = 中大奖 + 摇色子中奖(豹子时 rewardWinCoin 已为 0) $record = null; $configId = (int) $config->id; @@ -302,7 +302,7 @@ class PlayStartLogic } $coinBefore = (float) $p->coin; // 开始前先扣付费金额,再加中奖金额(免费抽奖 paid_amount=0) - $coinAfter = $coinBefore - $paidAmount + $winCoin; + $coinAfter = round($coinBefore - $paidAmount + $winCoin, 2); $p->coin = $coinAfter; // 免费抽奖消耗:优先消耗 free_ticket.count,耗尽则清空 free_ticket;否则兼容旧 free_ticket_count if ($ticketType === self::LOTTERY_TYPE_FREE) { @@ -400,13 +400,13 @@ class PlayStartLogic $p->save(); // 彩金池累计盈利累加在 name=default 彩金池上: - // 付费:每局按「本局赢取平台币 win_coin - 抽奖费用 paid_amount(ante*100)」 + // 付费:每局按「本局赢取平台币 win_coin - 抽奖费用 paid_amount(ante*UNIT_COST)」 // 免费券:paid_amount=0,只计入 win_coin - $perPlayProfit = ($ticketType === self::LOTTERY_TYPE_PAID) ? ($winCoin - (float) $paidAmount) : $winCoin; - $addProfit = $perPlayProfit; + $perPlayProfit = ($ticketType === self::LOTTERY_TYPE_PAID) ? ($winCoin - $paidAmount) : $winCoin; + $addProfit = round($perPlayProfit, 2); try { DiceLotteryPoolConfig::where('id', $type0ConfigId)->update([ - 'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . (float) $addProfit), + 'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . sprintf('%.2f', $addProfit)), ]); } catch (\Throwable $e) { Log::warning('彩金池盈利累加失败', [ @@ -418,11 +418,11 @@ class PlayStartLogic // 钱包流水拆分:先记录购券扣费,再记录抽奖结果(中奖/惩罚) if ($paidAmount > 0) { - $walletAfterBuy = $coinBefore - $paidAmount; + $walletAfterBuy = round($coinBefore - $paidAmount, 2); DicePlayerWalletRecord::create([ 'player_id' => $playerId, 'admin_id' => $adminId, - 'coin' => -$paidAmount, + 'coin' => round(-$paidAmount, 2), 'type' => self::WALLET_TYPE_BUY_DRAW, 'wallet_before' => $coinBefore, 'wallet_after' => $walletAfterBuy, @@ -437,7 +437,7 @@ class PlayStartLogic 'admin_id' => $adminId, 'coin' => $winCoin, 'type' => self::WALLET_TYPE_DRAW, - 'wallet_before' => $walletBeforeDraw, + 'wallet_before' => round($walletBeforeDraw, 2), 'wallet_after' => $coinAfter, 'remark' => $drawRemark, ]); @@ -484,9 +484,9 @@ class PlayStartLogic $arr['roll_number'] = is_array($arr['roll_array'] ?? null) ? array_sum($arr['roll_array']) : 0; $arr['reward_tier'] = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? ''); // 记录完数据后返回当前玩家余额与抽奖次数 - $arr['coin'] = $updated ? (float) $updated->coin : 0; + $arr['coin'] = $updated ? round((float) $updated->coin, 2) : 0.0; // 本局从玩家货币中扣除的金额:付费抽奖为 ante*UNIT_COST,免费抽奖为 0(与 paid_amount 一致) - $arr['use_coin'] = $paidAmount; + $arr['use_coin'] = round($paidAmount, 2); return $arr; } @@ -669,9 +669,9 @@ class PlayStartLogic $rollNumber = (int) ($chosen['grid_number'] ?? 0); $realEv = (float) ($chosen['real_ev'] ?? 0); // 摇色子中奖:按 real_ev 直接结算(与正式抽奖 run() 一致) - $rewardWinCoin = $realEv * $ante; + $rewardWinCoin = round($realEv * $ante, 2); - $superWinCoin = 0; + $superWinCoin = 0.0; $isWin = 0; $bigWinRealEv = 0.0; if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) { @@ -699,8 +699,8 @@ class PlayStartLogic $rollArray = $this->getSuperWinRollArray($rollNumber); $isWin = 1; $bigWinEv = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS; - $superWinCoin = $bigWinEv * $ante; - $rewardWinCoin = 0; + $superWinCoin = round($bigWinEv * $ante, 2); + $rewardWinCoin = 0.0; // 中豹子时不走原奖励流程 $realEv = 0.0; } else { @@ -711,11 +711,11 @@ class PlayStartLogic $rollArray = $this->generateRollArrayFromSum($rollNumber); } - $winCoin = $superWinCoin + $rewardWinCoin; + $winCoin = round($superWinCoin + $rewardWinCoin, 2); $configId = $config !== null ? (int) $config->id : 0; $configName = $config !== null ? (string) ($config->name ?? '') : '自定义'; $costRealEv = $realEv + ($isWin === 1 ? $bigWinRealEv : 0.0); - $paidAmount = $lotteryType === 0 ? ($ante * self::UNIT_COST) : 0; + $paidAmount = $lotteryType === 0 ? round($ante * self::UNIT_COST, 2) : 0.0; $rewardTier = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? ''); // 与写入记录的 reward_tier 完全一致:仅当展示档位为 T5(非豹子大奖)时触发「再来一次」链式免费局 $grantsFreeTicket = ($rewardTier === 'T5'); diff --git a/server/app/dice/controller/play_record/DicePlayRecordController.php b/server/app/dice/controller/play_record/DicePlayRecordController.php index 6ef355f..6582668 100644 --- a/server/app/dice/controller/play_record/DicePlayRecordController.php +++ b/server/app/dice/controller/play_record/DicePlayRecordController.php @@ -59,12 +59,12 @@ class DicePlayRecordController extends BaseController 'diceLotteryPoolConfig', ]); - // 按当前筛选条件统计:平台总盈利 = 付费抽奖(lottery_type=0)次数×100 - 玩家总收益(win_coin 求和) + // 按当前筛选条件统计:平台总盈利 = 付费金额(paid_amount 求和) - 玩家总收益(win_coin 求和) $sumQuery = clone $query; $playerTotalWin = (float) $sumQuery->sum('win_coin'); $paidAmountQuery = clone $query; $paidAmount = (float) $paidAmountQuery->where('lottery_type', 0)->sum('paid_amount'); - $totalWinCoin = $paidAmount - $playerTotalWin; + $totalWinCoin = round($paidAmount - $playerTotalWin, 2); $data = $this->logic->getList($query); $data['total_win_coin'] = $totalWinCoin; diff --git a/server/app/dice/controller/play_record_test/DicePlayRecordTestController.php b/server/app/dice/controller/play_record_test/DicePlayRecordTestController.php index 64800b4..14d0b37 100644 --- a/server/app/dice/controller/play_record_test/DicePlayRecordTestController.php +++ b/server/app/dice/controller/play_record_test/DicePlayRecordTestController.php @@ -30,7 +30,7 @@ class DicePlayRecordTestController extends BaseController } /** - * 数据列表,并在结果中附带当前筛选条件下测试数据的平台总盈利 total_win_coin(付费抽奖次数×100 - 玩家总收益) + * 数据列表,并在结果中附带当前筛选条件下测试数据的平台总盈利 total_win_coin(付费金额 paid_amount 求和 - 玩家总收益) * @param Request $request * @return Response */ @@ -57,7 +57,7 @@ class DicePlayRecordTestController extends BaseController $playerTotalWin = (float) $sumQuery->sum('win_coin'); $paidAmountQuery = clone $query; $paidAmount = (float) $paidAmountQuery->where('lottery_type', 0)->sum('paid_amount'); - $totalWinCoin = $paidAmount - $playerTotalWin; + $totalWinCoin = round($paidAmount - $playerTotalWin, 2); $data = $this->logic->getList($query); $data['total_win_coin'] = $totalWinCoin; diff --git a/server/app/dice/model/play_record/DicePlayRecord.php b/server/app/dice/model/play_record/DicePlayRecord.php index 4f3f6b0..ecd5c75 100644 --- a/server/app/dice/model/play_record/DicePlayRecord.php +++ b/server/app/dice/model/play_record/DicePlayRecord.php @@ -23,7 +23,7 @@ use think\model\relation\BelongsTo; * @property $lottery_config_id 彩金池配置 * @property $lottery_type 抽奖类型 * @property $ante 底注/注数(dice_ante_config.mult) - * @property $paid_amount 付费金额(付费局=ante*100,免费局=0) + * @property $paid_amount 付费金额(付费局=ante*1,免费局=0) * @property $is_win 是否中大奖:豹子号[1,1,1,1,1]~[6,6,6,6,6]为1,否则0 * @property $win_coin 赢取平台币(= super_win_coin + reward_win_coin) * @property $super_win_coin 中大奖平台币(豹子时发放) diff --git a/server/app/dice/model/play_record_test/DicePlayRecordTest.php b/server/app/dice/model/play_record_test/DicePlayRecordTest.php index bbcafdd..0d00c2e 100644 --- a/server/app/dice/model/play_record_test/DicePlayRecordTest.php +++ b/server/app/dice/model/play_record_test/DicePlayRecordTest.php @@ -22,7 +22,7 @@ use think\model\relation\BelongsTo; * @property $is_win 中大奖:0=无,1=中奖 * @property $win_coin 赢取平台币 * @property int|null $ante 底注/注数(dice_ante_config.mult) - * @property int|null $paid_amount 付费金额(付费局=ante*100,免费局=0) + * @property int|null $paid_amount 付费金额(付费局=ante*1,免费局=0) * @property $direction 方向:0=顺时针,1=逆时针 * @property $reward_tier 中奖档位:T1,T2,T3,T4,T5,BIGWIN * @property $create_time 创建时间 @@ -113,7 +113,7 @@ class DicePlayRecordTest extends BaseModel } } - /** 付费金额(付费局=ante*100,免费局=0) */ + /** 付费金额(付费局=ante*1,免费局=0) */ public function searchPaidAmountAttr($query, $value) { if ($value !== '' && $value !== null) { diff --git a/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php b/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php index 0324580..24ad200 100644 --- a/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php +++ b/server/app/dice/model/reward_config_record/DiceRewardConfigRecord.php @@ -38,7 +38,7 @@ use think\model\relation\HasMany; * @property array|null $free_tier_weights 免费自定义档位权重 T1-T5 * @property array $result_counts 落点统计 grid_number=>出现次数 * @property array|null $tier_counts 档位出现次数 T1=>count - * @property float|null $platform_profit 平台赚取金额(付费抽取次数×100-玩家总收益) + * @property float|null $platform_profit 平台赚取金额(付费金额 paid_amount 求和-玩家总收益) * @property array|null $bigwin_weight 测试时 BIGWIN 档位权重快照(JSON:grid_number=>weight) * @property int|null $admin_id 执行测试的管理员ID * @property string|null $create_time 创建时间 diff --git a/server/db/dice_play_record_add_ante_and_paid_amount.sql b/server/db/dice_play_record_add_ante_and_paid_amount.sql index e4b2e75..b5c8de3 100644 --- a/server/db/dice_play_record_add_ante_and_paid_amount.sql +++ b/server/db/dice_play_record_add_ante_and_paid_amount.sql @@ -1,4 +1,4 @@ -- DicePlayRecord 新增注数与付费金额字段 ALTER TABLE `dice_play_record` ADD COLUMN `ante` int unsigned NOT NULL DEFAULT 1 COMMENT '底注/注数(必须为 dice_ante_config.mult 中存在的值)' AFTER `lottery_type`, - ADD COLUMN `paid_amount` int unsigned NOT NULL DEFAULT 0 COMMENT '付费金额(付费局=ante*100,免费局=0)' AFTER `ante`; + ADD COLUMN `paid_amount` int unsigned NOT NULL DEFAULT 0 COMMENT '付费金额(付费局=ante*1,免费局=0)' AFTER `ante`; diff --git a/项目文档.md b/项目文档.md index d573836..2d29ad6 100644 --- a/项目文档.md +++ b/项目文档.md @@ -18,11 +18,11 @@ | --- | --- | | **平台币 `coin`** | 玩家钱包余额;付费开局、购买套餐、中奖结算均围绕该字段。 | | **注数 `ante`** | 倍数因子,须存在于表 `dice_ante_config` 的 `mult` 中;接口 `/api/game/anteConfig` 返回可选注数。 | -| **单注费用** | 付费抽奖时,开局前扣除 **`ante × 100`** 平台币(代码常量 `UNIT_COST = 100`,即「单注 100 币」口径)。 | +| **单注费用** | 付费抽奖时,开局前扣除 **`ante × 1`** 平台币(代码常量 `UNIT_COST = 1`,即「单注 1 币」口径)。 | | **方向 `direction`** | 开局参数:`0` 与 `1` 对应两套奖励数据(顺时针/逆时针或「无 / 中奖」分支,由前端与配置表共同约定);服务端在 **档位确定后**,按当前方向从 `DiceReward` 缓存结构中取该档位下的条目再按权重抽取。 | | **档位 T1–T5** | 中奖层级;先抽档位,再在该档位 + 当前方向下按 `weight` 抽一条奖励配置。 | | **`grid_number`(5–30)** | 与「五颗骰子点数之和」一致:最小 5(全 1),最大 30(全 6);用于关联奖励行与后续生成 `roll_array`。 | -| **`real_ev`** | 奖励配置中的期望调节项;**普通中奖**结算为 **`(100 + real_ev) × ante`**(付费局在开局已扣 `ante×100`,净效果依 `real_ev` 而定)。 | +| **`real_ev`** | 奖励配置中的期望调节项;**普通中奖**结算为 **`real_ev × ante`**(付费局在开局已扣 `ante×1`,净效果依 `real_ev` 而定)。 | --- @@ -33,9 +33,9 @@ 2. **(可选)购买「抽奖券」套餐** `POST /api/game/buyLotteryTickets`,`count` 仅支持 `1`、`5`、`10`: - - 1:100 币 → 1 次付费计数 + 0 次赠送 - - 5:500 币 → 5 次付费 + **1 次赠送**(共 6 次计入总次数) - - 10:1000 币 → 10 次付费 + **3 次赠送**(共 13 次) + - 1:1 币 → 1 次付费计数 + 0 次赠送 + - 5:5 币 → 5 次付费 + **1 次赠送**(共 6 次计入总次数) + - 10:10 币 → 10 次付费 + **3 次赠送**(共 13 次) 会更新玩家身上的 `total_ticket_count` / `paid_ticket_count` / `free_ticket_count`,并记钱包与券流水。 @@ -43,8 +43,8 @@ `POST /api/game/playStart`,需传 **`direction`(0 或 1)** 与 **`ante`(正整数,且须在底注配置中)**。 4. **付费 vs 免费** - - **免费抽奖**:当 `free_ticket_count > 0` 时,本局视为免费类型:不扣 `ante×100`,但会消耗 **1 次** `free_ticket_count`。 - - **付费抽奖**:不依赖「券张数是否大于 0」;只要非免费局,开局前扣 **`ante × 100`**。 + - **免费抽奖**:当 `free_ticket_count > 0` 时,本局视为免费类型:不扣 `ante×1`,但会消耗 **1 次** `free_ticket_count`。 + - **付费抽奖**:不依赖「券张数是否大于 0」;只要非免费局,开局前扣 **`ante × 1`**。 > **重要**:当前实现已**不再用「抽奖券张数」作为能否开局的条件**;`buyLotteryTickets` 更新的是统计与赠送次数,**真正开局仍看余额、注数、免费次数等规则**(见下节)。 @@ -61,7 +61,7 @@ - 用户存在;`ante` 合法。 - **最低余额**:`coin ≥ abs(min_real_ev) × ante`(`min_real_ev` 来自全表 `DiceRewardConfig` 缓存),防止极端负 EV 下余额不足以覆盖风险口径。 -- 付费局:`coin ≥ ante × 100`。 +- 付费局:`coin ≥ ante × 1`。 ### 4.2 使用哪套「档位权重」:默认奖池 vs 杀分奖池 @@ -89,12 +89,12 @@ ### 4.4 普通奖与「豹子 / BIGWIN」 -- 若本次抽中的 `grid_number` **不是**「豹子集合」`{5,10,15,20,25,30}`:按点数和生成 5 个 1–6 的骰子(和为 `grid_number`),**普通奖金** = **`(100 + real_ev) × ante`**(付费局已预先扣除 `ante×100`)。 +- 若本次抽中的 `grid_number` **不是**「豹子集合」`{5,10,15,20,25,30}`:按点数和生成 5 个 1–6 的骰子(和为 `grid_number`),**普通奖金** = **`real_ev × ante`**(付费局已预先扣除 `ante×1`)。 - 若点数和落在豹子集合: - **`grid_number` 为 5 或 30**:若**非**杀分路径,**必定**按豹子结算(五颗相同点数)。 - **10 / 15 / 20 / 25**:读取 `DiceRewardConfig` 中 **`tier = BIGWIN`** 且对应该 `grid_number` 的配置,用其 **`weight`(0–10000,10000=100%)** 随机决定是否视为真豹子;否则生成**非豹子**但点数和不变的骰子组合。 - - **真豹子**时:奖金按 **`(100 + big_win_real_ev) × ante`** 发放(`big_win_real_ev` 来自 BIGWIN 配置;若未配则用代码兜底常量);并**不计入**当次普通 `reward_win` 那条配置(与「中豹子不走普通奖」逻辑一致,详见代码注释)。 + - **真豹子**时:奖金按 **`big_win_real_ev × ante`** 发放(`big_win_real_ev` 来自 BIGWIN 配置;若未配则用代码兜底常量);并**不计入**当次普通 `reward_win` 那条配置(与「中豹子不走普通奖」逻辑一致,详见代码注释)。 杀分路径下:**不触发**豹子奖,仅展示非豹子组合。 @@ -106,12 +106,12 @@ 在 **`default`** 那条池子上更新 **`profit_amount`**: -- **付费局**:本局贡献 `+= (本局总中奖 win_coin) - (本局付费 paid_amount)`,其中 `paid_amount = ante × 100`。 +- **付费局**:本局贡献 `+= (本局总中奖 win_coin) - (本局付费 paid_amount)`,其中 `paid_amount = ante × 1`。 - **免费局**:`+= win_coin`(无票价成本,`paid_amount = 0`)。 该累计值与 **`safety_line`、 `kill_enabled`** 共同决定下一局付费是否进入 **killScore** 档位权重(见 4.2)。 -> 注意:仓库中部分数据库迁移脚本对 `profit_amount` 的注释可能仍沿用旧口径(例如按 `100-real_ev` 解释)。当前线上行为应以 `PlayStartLogic` 中对 `profit_amount` 的实际累加逻辑为准。 +> 注意:仓库中部分数据库迁移脚本对 `profit_amount` 的注释可能仍沿用旧口径。当前行为应以 `PlayStartLogic` 中对 `profit_amount` 的实际累加逻辑为准。 ---