[色子游戏]抽奖记录(测试权重)-优化
[API]记录抽奖券DicePlayerTicketRecord
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
"direction": "Direction",
|
||||
"isBigWin": "Is Big Win",
|
||||
"winCoin": "Win Coin",
|
||||
"paidAmount": "Paid Amount",
|
||||
"ante": "Ante",
|
||||
"rewardTier": "Reward Tier",
|
||||
"rollNumber": "Roll Number",
|
||||
"paid": "Paid",
|
||||
@@ -23,6 +25,8 @@
|
||||
"lotteryPoolConfig": "Lottery Pool Config",
|
||||
"drawType": "Draw Type",
|
||||
"isBigWin": "Is Big Win",
|
||||
"paidAmount": "Paid Amount",
|
||||
"ante": "Ante",
|
||||
"winCoin": "Win Coin",
|
||||
"superWinCoin": "Super Win Coin",
|
||||
"rewardWinCoin": "Reward Win Coin",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"search": {
|
||||
"player": "Player",
|
||||
"useCoins": "Use Coins",
|
||||
"ante": "Ante",
|
||||
"totalDrawCount": "Total Draw Count",
|
||||
"paidDrawCount": "Paid Draw Count",
|
||||
"freeDrawCount": "Free Draw Count",
|
||||
@@ -29,6 +30,7 @@
|
||||
"id": "ID",
|
||||
"playerUsername": "Player Username",
|
||||
"useCoins": "Use Coins",
|
||||
"ante": "Ante",
|
||||
"totalDrawCount": "Total Draw Count",
|
||||
"paidDrawCount": "Paid Draw Count",
|
||||
"freeDrawCount": "Free Draw Count",
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"stepFree": "Free ticket",
|
||||
"labelLotteryTypePaid": "Test pool type",
|
||||
"labelLotteryTypeFree": "Test pool type",
|
||||
"labelAnte": "Ante",
|
||||
"placeholderPaidPool": "Leave empty for custom tier odds below (default: default)",
|
||||
"placeholderFreePool": "Leave empty for custom tier odds below (default: killScore)",
|
||||
"tierProbHint": "Custom tier odds (T1–T5), each 0–100%, sum of five must not exceed 100%",
|
||||
@@ -73,6 +74,7 @@
|
||||
"btnNext": "Next",
|
||||
"btnStart": "Start test",
|
||||
"btnCancel": "Cancel",
|
||||
"warnAnte": "Ante must be greater than 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%",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"platformProfit": "Platform Profit",
|
||||
"totalDrawCount": "Total Draw Count",
|
||||
"createdBy": "Created By",
|
||||
"remark": "Remark",
|
||||
"createTime": "Create Time",
|
||||
"statusFail": "Failed",
|
||||
"statusDone": "Done",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"placeholderLotteryPool": "请选择彩金池配置",
|
||||
"drawType": "抽奖类型",
|
||||
"paid": "付费",
|
||||
"free": "赠送",
|
||||
"free": "免费",
|
||||
"isBigWin": "是否中大奖",
|
||||
"noBigWin": "无",
|
||||
"bigWin": "中大奖",
|
||||
@@ -53,7 +53,7 @@
|
||||
"nameFuzzy": "名称模糊",
|
||||
"uiTextFuzzy": "前端显示文本模糊",
|
||||
"paid": "付费",
|
||||
"free": "赠送",
|
||||
"free": "免费",
|
||||
"noBigWin": "无",
|
||||
"bigWin": "中大奖",
|
||||
"clockwise": "顺时针",
|
||||
|
||||
@@ -8,10 +8,12 @@
|
||||
"direction": "方向",
|
||||
"isBigWin": "是否中大奖",
|
||||
"winCoin": "赢取平台币",
|
||||
"paidAmount": "付费金额",
|
||||
"ante": "底注",
|
||||
"rewardTier": "奖励档位",
|
||||
"rollNumber": "摇取点数和",
|
||||
"paid": "付费",
|
||||
"free": "赠送",
|
||||
"free": "免费",
|
||||
"clockwise": "顺时针",
|
||||
"anticlockwise": "逆时针",
|
||||
"noBigWin": "无",
|
||||
@@ -23,6 +25,8 @@
|
||||
"lotteryPoolConfig": "彩金池配置",
|
||||
"drawType": "抽奖类型",
|
||||
"isBigWin": "是否中大奖",
|
||||
"paidAmount": "付费金额",
|
||||
"ante": "底注",
|
||||
"winCoin": "赢取平台币",
|
||||
"superWinCoin": "中大奖平台币",
|
||||
"rewardWinCoin": "摇色子中奖平台币",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"search": {
|
||||
"player": "玩家",
|
||||
"useCoins": "消耗硬币",
|
||||
"ante": "底注",
|
||||
"totalDrawCount": "总抽奖次数",
|
||||
"paidDrawCount": "购买抽奖次数",
|
||||
"freeDrawCount": "赠送抽奖次数",
|
||||
@@ -29,6 +30,7 @@
|
||||
"id": "ID",
|
||||
"playerUsername": "玩家用户名",
|
||||
"useCoins": "消耗硬币",
|
||||
"ante": "底注",
|
||||
"totalDrawCount": "总抽奖次数",
|
||||
"paidDrawCount": "购买抽奖次数",
|
||||
"freeDrawCount": "赠送抽奖次数",
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
"stepFree": "免费抽奖券",
|
||||
"labelLotteryTypePaid": "测试数据档位类型",
|
||||
"labelLotteryTypeFree": "测试数据档位类型",
|
||||
"labelAnte": "底注 ante",
|
||||
"placeholderPaidPool": "不选则下方自定义档位概率(默认 default)",
|
||||
"placeholderFreePool": "不选则下方自定义档位概率(默认 killScore)",
|
||||
"tierProbHint": "自定义档位概率(T1~T5),每档 0-100%,五档之和不能超过 100%",
|
||||
@@ -73,6 +74,7 @@
|
||||
"btnNext": "下一步",
|
||||
"btnStart": "开始测试",
|
||||
"btnCancel": "取消",
|
||||
"warnAnte": "底注 ante 必须大于 0",
|
||||
"warnTotalSpins": "付费或免费至少一种方向次数之和大于 0",
|
||||
"warnPaidTierSumPositive": "付费未选奖池时,T1~T5 档位概率之和需大于 0",
|
||||
"warnPaidTierSumMax": "付费档位概率 T1~T5 之和不能超过 100%",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"platformProfit": "平台赚取金额",
|
||||
"totalDrawCount": "总抽奖次数",
|
||||
"createdBy": "创建管理员",
|
||||
"remark": "备注",
|
||||
"createTime": "创建时间",
|
||||
"statusFail": "失败",
|
||||
"statusDone": "完成",
|
||||
|
||||
@@ -61,6 +61,7 @@ export default {
|
||||
* 可选 lottery_config_id;不选则传 paid_tier_weights / free_tier_weights(T1-T5)
|
||||
*/
|
||||
startWeightTest(params: {
|
||||
ante?: number
|
||||
lottery_config_id?: number
|
||||
paid_lottery_config_id?: number
|
||||
free_lottery_config_id?: number
|
||||
|
||||
@@ -58,19 +58,37 @@
|
||||
<!-- 抽奖类型 -->
|
||||
<template #lottery_type="{ row }">
|
||||
<ElTag size="small" :type="row.lottery_type === 0 ? 'warning' : 'success'">
|
||||
{{ row.lottery_type === 0 ? t('page.search.paid') : row.lottery_type === 1 ? t('page.search.free') : '-' }}
|
||||
{{
|
||||
row.lottery_type === 0
|
||||
? t('page.search.paid')
|
||||
: row.lottery_type === 1
|
||||
? t('page.search.free')
|
||||
: '-'
|
||||
}}
|
||||
</ElTag>
|
||||
</template>
|
||||
<!-- 是否中大奖 -->
|
||||
<template #is_win="{ row }">
|
||||
<ElTag size="small" :type="row.is_win === 1 ? 'success' : 'info'">
|
||||
{{ row.is_win === 0 ? t('page.search.noBigWin') : row.is_win === 1 ? t('page.search.bigWin') : '-' }}
|
||||
{{
|
||||
row.is_win === 0
|
||||
? t('page.search.noBigWin')
|
||||
: row.is_win === 1
|
||||
? t('page.search.bigWin')
|
||||
: '-'
|
||||
}}
|
||||
</ElTag>
|
||||
</template>
|
||||
<!-- 方向 -->
|
||||
<template #direction="{ row }">
|
||||
<ElTag size="small" :type="row.direction === 0 ? 'primary' : 'warning'">
|
||||
{{ row.direction === 0 ? t('page.search.clockwise') : row.direction === 1 ? t('page.search.anticlockwise') : '-' }}
|
||||
{{
|
||||
row.direction === 0
|
||||
? t('page.search.clockwise')
|
||||
: row.direction === 1
|
||||
? t('page.search.anticlockwise')
|
||||
: '-'
|
||||
}}
|
||||
</ElTag>
|
||||
</template>
|
||||
<!-- 摇取点数 -->
|
||||
@@ -132,6 +150,8 @@
|
||||
lottery_type: undefined,
|
||||
direction: undefined,
|
||||
is_win: undefined,
|
||||
paid_amount: undefined,
|
||||
ante: undefined,
|
||||
win_coin_min: undefined,
|
||||
win_coin_max: undefined,
|
||||
reward_tier: undefined,
|
||||
@@ -208,9 +228,16 @@
|
||||
columnsFactory: () => [
|
||||
{ type: 'selection' },
|
||||
{ prop: 'id', label: 'page.table.id', width: 80 },
|
||||
{ prop: 'lottery_config_id', label: 'page.table.lotteryPoolConfig', width: 120, useSlot: true },
|
||||
{
|
||||
prop: 'lottery_config_id',
|
||||
label: 'page.table.lotteryPoolConfig',
|
||||
width: 120,
|
||||
useSlot: true
|
||||
},
|
||||
{ prop: 'lottery_type', label: 'page.table.drawType', width: 100, useSlot: true },
|
||||
{ prop: 'is_win', label: 'page.table.isBigWin', width: 100, useSlot: true },
|
||||
{ prop: 'paid_amount', label: 'page.table.paidAmount', width: 130 },
|
||||
{ prop: 'ante', label: 'page.table.ante', width: 90 },
|
||||
{ prop: 'win_coin', label: 'page.table.winCoin', width: 110 },
|
||||
{ prop: 'super_win_coin', label: 'page.table.superWinCoin', width: 120 },
|
||||
{ prop: 'reward_win_coin', label: 'page.table.rewardWinCoin', width: 140 },
|
||||
@@ -222,7 +249,13 @@
|
||||
{ prop: 'reward_config_id', label: 'page.table.rewardConfig', width: 100, useSlot: true },
|
||||
{ prop: 'status', label: 'page.table.status', width: 80, useSlot: true },
|
||||
{ prop: 'create_time', label: 'page.table.createTime', width: 170 },
|
||||
{ prop: 'operation', label: 'table.actions.operation', width: 100, fixed: 'right', useSlot: true }
|
||||
{
|
||||
prop: 'operation',
|
||||
label: 'table.actions.operation',
|
||||
width: 100,
|
||||
fixed: 'right',
|
||||
useSlot: true
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -28,6 +28,24 @@
|
||||
<el-option :label="$t('page.search.anticlockwise')" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('page.search.ante')" prop="ante">
|
||||
<el-input-number
|
||||
v-model="formData.ante"
|
||||
:placeholder="$t('table.searchBar.all')"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('page.search.paidAmount')" prop="paid_amount">
|
||||
<el-input-number
|
||||
v-model="formData.paid_amount"
|
||||
:placeholder="$t('table.searchBar.all')"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('page.search.isBigWin')" prop="is_win">
|
||||
<el-select v-model="formData.is_win" :placeholder="$t('form.placeholderSelect')" clearable style="width: 100%">
|
||||
<el-option :label="$t('page.search.noBigWin')" :value="0" />
|
||||
@@ -153,6 +171,8 @@
|
||||
lottery_config_id: null,
|
||||
lottery_type: null,
|
||||
is_win: null,
|
||||
ante: 1,
|
||||
paid_amount: 0,
|
||||
win_coin: 0,
|
||||
direction: null,
|
||||
reward_tier: undefined as string | undefined,
|
||||
|
||||
@@ -32,6 +32,28 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item :label="$t('page.search.paidAmount')" prop="paid_amount">
|
||||
<el-input-number
|
||||
v-model="formData.paid_amount"
|
||||
:placeholder="$t('table.searchBar.all')"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item :label="$t('page.search.ante')" prop="ante">
|
||||
<el-input-number
|
||||
v-model="formData.ante"
|
||||
:placeholder="$t('table.searchBar.all')"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item :label="$t('page.search.winCoin')" prop="win_coin_min">
|
||||
<div class="range-wrap">
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
username: undefined,
|
||||
use_coins_min: undefined,
|
||||
use_coins_max: undefined,
|
||||
ante: undefined,
|
||||
total_ticket_count_min: undefined,
|
||||
total_ticket_count_max: undefined,
|
||||
paid_ticket_count_min: undefined,
|
||||
@@ -136,6 +137,7 @@
|
||||
formatter: (row: Record<string, any>) => usernameFormatter(row)
|
||||
},
|
||||
{ prop: 'use_coins', label: 'page.table.useCoins', align: 'center' },
|
||||
{ prop: 'ante', label: 'page.table.ante', align: 'center' },
|
||||
{ prop: 'total_ticket_count', label: 'page.table.totalDrawCount', align: 'center' },
|
||||
{ prop: 'paid_ticket_count', label: 'page.table.paidDrawCount', align: 'center' },
|
||||
{ prop: 'free_ticket_count', label: 'page.table.freeDrawCount', align: 'center' },
|
||||
|
||||
@@ -34,6 +34,18 @@
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item :label="$t('page.search.ante')" prop="ante">
|
||||
<el-input-number
|
||||
v-model="formData.ante"
|
||||
:placeholder="$t('table.searchBar.all')"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
controls-position="right"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-bind="setSpan(6)">
|
||||
<el-form-item :label="$t('page.search.totalDrawCount')" prop="total_ticket_count_min">
|
||||
<div class="range-wrap">
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
{{ $t('page.weightTest.alertBody') }}
|
||||
</ElAlert>
|
||||
<ElForm ref="formRef" :model="form" label-width="140px">
|
||||
<ElFormItem :label="$t('page.weightTest.labelAnte')" prop="ante" required>
|
||||
<ElInputNumber v-model="form.ante" :min="1" :step="1" style="width: 100%" />
|
||||
</ElFormItem>
|
||||
<ElSteps :active="currentStep" finish-status="success" simple class="steps-wrap">
|
||||
<ElStep :title="$t('page.weightTest.stepPaid')" />
|
||||
<ElStep :title="$t('page.weightTest.stepFree')" />
|
||||
@@ -187,6 +190,7 @@
|
||||
const formRef = ref()
|
||||
const currentStep = ref(0)
|
||||
const form = reactive({
|
||||
ante: 1,
|
||||
paid_lottery_config_id: undefined as number | undefined,
|
||||
free_lottery_config_id: undefined as number | undefined,
|
||||
paid_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
|
||||
@@ -270,6 +274,7 @@
|
||||
|
||||
function buildPayload() {
|
||||
const payload: Record<string, unknown> = {
|
||||
ante: form.ante,
|
||||
paid_s_count: form.paid_s_count,
|
||||
paid_n_count: form.paid_n_count,
|
||||
free_s_count: form.free_s_count,
|
||||
@@ -289,6 +294,10 @@
|
||||
}
|
||||
|
||||
function validateForm(): boolean {
|
||||
if (form.ante == null || form.ante <= 0) {
|
||||
ElMessage.warning(t('page.weightTest.warnAnte'))
|
||||
return false
|
||||
}
|
||||
if (form.paid_s_count + form.paid_n_count + form.free_s_count + form.free_n_count <= 0) {
|
||||
ElMessage.warning(t('page.weightTest.warnTotalSpins'))
|
||||
return false
|
||||
|
||||
@@ -214,6 +214,13 @@
|
||||
align: 'center',
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{
|
||||
prop: 'remark',
|
||||
label: 'page.table.remark',
|
||||
width: 220,
|
||||
align: 'center',
|
||||
showOverflowTooltip: true
|
||||
},
|
||||
{ prop: 'create_time', label: 'page.table.createTime', width: 170, align: 'center' },
|
||||
{
|
||||
prop: 'operation',
|
||||
|
||||
@@ -284,6 +284,21 @@ class PlayStartLogic
|
||||
$p->free_ticket_count = max(0, (int) $p->free_ticket_count - 1);
|
||||
}
|
||||
|
||||
// 记录每次游玩:写入抽奖券记录(用于后台“抽奖券记录”追踪付费/免费游玩与消耗)
|
||||
$isPaidPlay = $ticketType === self::LOTTERY_TYPE_PAID;
|
||||
$paidCnt = $isPaidPlay ? 1 : 0;
|
||||
$freeCnt = $isPaidPlay ? 0 : 1;
|
||||
DicePlayerTicketRecord::create([
|
||||
'player_id' => $playerId,
|
||||
'admin_id' => $adminId,
|
||||
'use_coins' => $paidAmount,
|
||||
'ante' => $ante,
|
||||
'total_ticket_count' => $paidCnt + $freeCnt,
|
||||
'paid_ticket_count' => $paidCnt,
|
||||
'free_ticket_count' => $freeCnt,
|
||||
'remark' => ($isPaidPlay ? '付费游玩' : '免费游玩') . '|play_record_id=' . $record->id,
|
||||
]);
|
||||
|
||||
// 若本局中奖档位为 T5,则额外赠送 1 次免费抽奖次数(总次数也 +1),并记录抽奖券获取记录
|
||||
if ($isTierT5) {
|
||||
$p->free_ticket_count = (int) $p->free_ticket_count + 1;
|
||||
@@ -509,10 +524,11 @@ class PlayStartLogic
|
||||
* @param \app\dice\model\lottery_pool_config\DiceLotteryPoolConfig|null $config 奖池配置,自定义档位时可为 null
|
||||
* @param int $direction 0=顺时针 1=逆时针
|
||||
* @param int $lotteryType 0=付费 1=免费
|
||||
* @param int $ante 底注/注数(dice_ante_config.mult)
|
||||
* @param array|null $customTierWeights 自定义档位权重 ['T1'=>x, 'T2'=>x, ...],非空时忽略 config 的档位权重
|
||||
* @return array 可直接用于 DicePlayRecordTest::create 的字段 + tier(用于统计档位概率)
|
||||
*/
|
||||
public function simulateOnePlay($config, int $direction, int $lotteryType = 0, ?array $customTierWeights = null): array
|
||||
public function simulateOnePlay($config, int $direction, int $lotteryType = 0, int $ante = 1, ?array $customTierWeights = null): array
|
||||
{
|
||||
$rewardInstance = DiceReward::getCachedInstance();
|
||||
$byTierDirection = $rewardInstance['by_tier_direction'] ?? [];
|
||||
@@ -558,8 +574,8 @@ class PlayStartLogic
|
||||
$targetIndex = (int) ($chosen['end_index'] ?? 0);
|
||||
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
|
||||
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
||||
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
|
||||
$rewardWinCoin = $isTierT5 ? $realEv : (100 + $realEv);
|
||||
// 玩家始终增加:(100 + real_ev) * ante
|
||||
$rewardWinCoin = (self::UNIT_COST + $realEv) * $ante;
|
||||
|
||||
$superWinCoin = 0;
|
||||
$isWin = 0;
|
||||
@@ -588,8 +604,11 @@ class PlayStartLogic
|
||||
if ($doSuperWin) {
|
||||
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
||||
$isWin = 1;
|
||||
$superWinCoin = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
|
||||
$bigWinEv = $bigWinRealEv > 0 ? $bigWinRealEv : self::SUPER_WIN_BONUS;
|
||||
$superWinCoin = (self::UNIT_COST + $bigWinEv) * $ante;
|
||||
$rewardWinCoin = 0;
|
||||
// 中豹子时不走原奖励流程
|
||||
$realEv = 0.0;
|
||||
} else {
|
||||
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
|
||||
}
|
||||
@@ -603,6 +622,7 @@ class PlayStartLogic
|
||||
$rewardId = ($isWin === 1 && $superWinCoin > 0) ? 0 : $targetIndex;
|
||||
$configName = $config !== null ? (string) ($config->name ?? '') : '自定义';
|
||||
$costRealEv = $realEv + ($isWin === 1 ? $bigWinRealEv : 0.0);
|
||||
$paidAmount = $lotteryType === 0 ? ($ante * self::UNIT_COST) : 0;
|
||||
|
||||
return [
|
||||
'player_id' => 0,
|
||||
@@ -611,9 +631,11 @@ class PlayStartLogic
|
||||
'lottery_type' => $lotteryType,
|
||||
'is_win' => $isWin,
|
||||
'win_coin' => $winCoin,
|
||||
'ante' => $ante,
|
||||
'paid_amount' => $paidAmount,
|
||||
'super_win_coin' => $superWinCoin,
|
||||
'reward_win_coin' => $rewardWinCoin,
|
||||
'use_coins' => 0,
|
||||
'use_coins' => $paidAmount,
|
||||
'direction' => $direction,
|
||||
'reward_config_id' => $rewardId,
|
||||
'start_index' => $startIndex,
|
||||
|
||||
@@ -43,18 +43,20 @@ class DicePlayRecordTestController extends BaseController
|
||||
['is_win', ''],
|
||||
['win_coin_min', ''],
|
||||
['win_coin_max', ''],
|
||||
['paid_amount', ''],
|
||||
['ante', ''],
|
||||
['reward_tier', ''],
|
||||
['roll_number', ''],
|
||||
]);
|
||||
$query = $this->logic->search($where);
|
||||
$query->with(['diceLotteryPoolConfig', 'diceRewardConfig']);
|
||||
|
||||
// 按当前筛选条件统计:平台总盈利 = 付费抽奖(lottery_type=0)次数×100 - 玩家总收益(win_coin 求和)
|
||||
// 按当前筛选条件统计:平台总盈利 = 付费金额(paid_amount 求和) - 玩家总收益(win_coin 求和)
|
||||
$sumQuery = clone $query;
|
||||
$playerTotalWin = (float) $sumQuery->sum('win_coin');
|
||||
$paidCountQuery = clone $query;
|
||||
$paidCount = (int) $paidCountQuery->where('lottery_type', 0)->count();
|
||||
$totalWinCoin = $paidCount * 100 - $playerTotalWin;
|
||||
$paidAmountQuery = clone $query;
|
||||
$paidAmount = (float) $paidAmountQuery->where('lottery_type', 0)->sum('paid_amount');
|
||||
$totalWinCoin = $paidAmount - $playerTotalWin;
|
||||
|
||||
$data = $this->logic->getList($query);
|
||||
$data['total_win_coin'] = $totalWinCoin;
|
||||
|
||||
@@ -42,6 +42,7 @@ class DicePlayerTicketRecordController extends BaseController
|
||||
['username', ''],
|
||||
['use_coins_min', ''],
|
||||
['use_coins_max', ''],
|
||||
['ante', ''],
|
||||
['total_ticket_count_min', ''],
|
||||
['total_ticket_count_max', ''],
|
||||
['paid_ticket_count_min', ''],
|
||||
|
||||
@@ -90,6 +90,7 @@ class DiceRewardController extends BaseController
|
||||
{
|
||||
$post = is_array($request->post()) ? $request->post() : [];
|
||||
$params = [
|
||||
'ante' => $post['ante'] ?? null,
|
||||
'lottery_config_id' => $post['lottery_config_id'] ?? null,
|
||||
'paid_lottery_config_id' => $post['paid_lottery_config_id'] ?? null,
|
||||
'free_lottery_config_id' => $post['free_lottery_config_id'] ?? null,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
namespace app\dice\logic\reward_config_record;
|
||||
|
||||
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||
use app\dice\model\ante_config\DiceAnteConfig;
|
||||
use app\dice\model\reward\DiceReward;
|
||||
use app\dice\model\reward\DiceRewardConfig;
|
||||
use app\dice\model\reward_config_record\DiceRewardConfigRecord;
|
||||
@@ -250,6 +251,15 @@ class DiceRewardConfigRecordLogic extends BaseLogic
|
||||
$adminId = $adminIdOrFreeS !== null && $adminIdOrFreeS !== '' ? (int) $adminIdOrFreeS : null;
|
||||
}
|
||||
$allowed = [100, 500, 1000, 5000];
|
||||
$ante = isset($params['ante']) ? intval($params['ante']) : 1;
|
||||
if ($ante <= 0) {
|
||||
throw new ApiException('ante must be greater than 0');
|
||||
}
|
||||
$anteExists = DiceAnteConfig::where('mult', $ante)->count();
|
||||
if ($anteExists <= 0) {
|
||||
throw new ApiException('ante not allowed: ' . $ante);
|
||||
}
|
||||
|
||||
$lotteryConfigId = isset($params['lottery_config_id']) ? (int) $params['lottery_config_id'] : 0;
|
||||
$paidConfigId = isset($params['paid_lottery_config_id']) ? (int) $params['paid_lottery_config_id'] : 0;
|
||||
$freeConfigId = isset($params['free_lottery_config_id']) ? (int) $params['free_lottery_config_id'] : 0;
|
||||
@@ -407,6 +417,7 @@ class DiceRewardConfigRecordLogic extends BaseLogic
|
||||
$record->result_counts = [];
|
||||
$record->tier_counts = null;
|
||||
$record->bigwin_weight = $bigwinWeights ?: null;
|
||||
$record->ante = $ante;
|
||||
$record->admin_id = $adminId;
|
||||
$record->create_time = date('Y-m-d H:i:s');
|
||||
$record->save();
|
||||
|
||||
@@ -33,6 +33,7 @@ class WeightTestRunner
|
||||
return;
|
||||
}
|
||||
|
||||
$ante = is_numeric($record->ante ?? null) ? intval($record->ante) : 1;
|
||||
$paidS = (int) ($record->paid_s_count ?? 0);
|
||||
$paidN = (int) ($record->paid_n_count ?? 0);
|
||||
$freeS = (int) ($record->free_s_count ?? 0);
|
||||
@@ -60,28 +61,40 @@ class WeightTestRunner
|
||||
$safetyLine = (int) ($configType0->safety_line ?? 0);
|
||||
$killEnabled = ((int) ($configType0->kill_enabled ?? 1)) === 1;
|
||||
|
||||
$paidTierWeights = (is_array($record->paid_tier_weights ?? null) && $record->paid_tier_weights !== [])
|
||||
$paidTierWeightsCustom = (is_array($record->paid_tier_weights ?? null) && $record->paid_tier_weights !== [])
|
||||
? $record->paid_tier_weights
|
||||
: [
|
||||
'T1' => (int) ($configType0->t1_weight ?? 0),
|
||||
'T2' => (int) ($configType0->t2_weight ?? 0),
|
||||
'T3' => (int) ($configType0->t3_weight ?? 0),
|
||||
'T4' => (int) ($configType0->t4_weight ?? 0),
|
||||
'T5' => (int) ($configType0->t5_weight ?? 0),
|
||||
];
|
||||
if (array_sum($paidTierWeights) <= 0) {
|
||||
$this->markFailed($recordId, '需提供 paid_tier_weights(玩家权重,盈利未达安全线时付费抽奖使用)或选择 default 奖池');
|
||||
return;
|
||||
: null;
|
||||
$freeTierWeightsCustom = (is_array($record->free_tier_weights ?? null) && $record->free_tier_weights !== [])
|
||||
? $record->free_tier_weights
|
||||
: null;
|
||||
|
||||
$paidPoolConfigId = (int) ($record->paid_lottery_config_id ?? 0);
|
||||
$freePoolConfigId = (int) ($record->free_lottery_config_id ?? 0);
|
||||
|
||||
$paidPoolConfig = $paidPoolConfigId > 0 ? DiceLotteryPoolConfig::find($paidPoolConfigId) : $configType0;
|
||||
if (!$paidPoolConfig) {
|
||||
$paidPoolConfig = $configType0;
|
||||
}
|
||||
$freePoolConfig = $freePoolConfigId > 0 ? DiceLotteryPoolConfig::find($freePoolConfigId) : $configType1;
|
||||
if (!$freePoolConfig) {
|
||||
$freePoolConfig = $configType0;
|
||||
}
|
||||
|
||||
$freeConfig = $configType1 !== null ? $configType1 : $configType0;
|
||||
if ($paidTierWeightsCustom !== null && array_sum($paidTierWeightsCustom) <= 0) {
|
||||
$this->markFailed($recordId, 'paid_tier_weights(玩家权重)之和必须大于 0');
|
||||
return;
|
||||
}
|
||||
if ($freeTierWeightsCustom !== null && array_sum($freeTierWeightsCustom) <= 0) {
|
||||
$this->markFailed($recordId, 'free_tier_weights(免费玩家权重)之和必须大于 0');
|
||||
return;
|
||||
}
|
||||
|
||||
// 每次测试开始前清空进程内静态缓存,强制从共享缓存读取最新 BIGWIN/奖励配置,与数据库一致
|
||||
DiceRewardConfig::clearRequestInstance();
|
||||
DiceReward::clearRequestInstance();
|
||||
|
||||
// 彩金池累计盈利:用于判断是否触发杀分(不再依赖单个玩家累计盈利)
|
||||
$poolProfitTotal = $configType0->profit_amount ?? 0;
|
||||
$poolProfitTotal = floatval($configType0->profit_amount ?? 0);
|
||||
|
||||
$playLogic = new PlayStartLogic();
|
||||
$resultCounts = [];
|
||||
@@ -92,9 +105,9 @@ class WeightTestRunner
|
||||
try {
|
||||
for ($i = 0; $i < $paidS; $i++) {
|
||||
$usePoolWeights = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
||||
$paidConfig = $usePoolWeights ? $configType1 : $configType0;
|
||||
$customWeights = $usePoolWeights ? null : $paidTierWeights;
|
||||
$row = $playLogic->simulateOnePlay($paidConfig, 0, 0, $customWeights);
|
||||
$paidConfig = $usePoolWeights ? $configType1 : $paidPoolConfig;
|
||||
$customWeights = $usePoolWeights ? null : $paidTierWeightsCustom;
|
||||
$row = $playLogic->simulateOnePlay($paidConfig, 0, 0, $ante, $customWeights);
|
||||
$this->accumulateProfitForDefault($row, 0, $paidConfig, $configType0, $poolProfitTotal);
|
||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
||||
@@ -103,9 +116,9 @@ class WeightTestRunner
|
||||
}
|
||||
for ($i = 0; $i < $paidN; $i++) {
|
||||
$usePoolWeights = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
||||
$paidConfig = $usePoolWeights ? $configType1 : $configType0;
|
||||
$customWeights = $usePoolWeights ? null : $paidTierWeights;
|
||||
$row = $playLogic->simulateOnePlay($paidConfig, 1, 0, $customWeights);
|
||||
$paidConfig = $usePoolWeights ? $configType1 : $paidPoolConfig;
|
||||
$customWeights = $usePoolWeights ? null : $paidTierWeightsCustom;
|
||||
$row = $playLogic->simulateOnePlay($paidConfig, 1, 0, $ante, $customWeights);
|
||||
$this->accumulateProfitForDefault($row, 0, $paidConfig, $configType0, $poolProfitTotal);
|
||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
||||
@@ -113,7 +126,10 @@ class WeightTestRunner
|
||||
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
|
||||
}
|
||||
for ($i = 0; $i < $freeS; $i++) {
|
||||
$row = $playLogic->simulateOnePlay($freeConfig, 0, 1, null);
|
||||
$useKillMode = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
||||
$freeConfig = $useKillMode ? $configType1 : $freePoolConfig;
|
||||
$customWeights = $useKillMode ? null : $freeTierWeightsCustom;
|
||||
$row = $playLogic->simulateOnePlay($freeConfig, 0, 1, $ante, $customWeights);
|
||||
$this->accumulateProfitForDefault($row, 1, $freeConfig, $configType0, $poolProfitTotal);
|
||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
||||
@@ -121,7 +137,10 @@ class WeightTestRunner
|
||||
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
|
||||
}
|
||||
for ($i = 0; $i < $freeN; $i++) {
|
||||
$row = $playLogic->simulateOnePlay($freeConfig, 1, 1, null);
|
||||
$useKillMode = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
||||
$freeConfig = $useKillMode ? $configType1 : $freePoolConfig;
|
||||
$customWeights = $useKillMode ? null : $freeTierWeightsCustom;
|
||||
$row = $playLogic->simulateOnePlay($freeConfig, 1, 1, $ante, $customWeights);
|
||||
$this->accumulateProfitForDefault($row, 1, $freeConfig, $configType0, $poolProfitTotal);
|
||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
||||
@@ -153,7 +172,8 @@ class WeightTestRunner
|
||||
return;
|
||||
}
|
||||
$winCoin = (float) $row['win_coin'];
|
||||
$playerProfitTotal += $lotteryType === 0 ? ($winCoin - 100.0) : $winCoin;
|
||||
$paidAmount = (float) ($row['paid_amount'] ?? 0);
|
||||
$playerProfitTotal += $lotteryType === 0 ? ($winCoin - $paidAmount) : $winCoin;
|
||||
}
|
||||
|
||||
private function aggregate(array $row, array &$resultCounts, array &$tierCounts): void
|
||||
@@ -176,6 +196,7 @@ class WeightTestRunner
|
||||
$keys = [
|
||||
'player_id', 'admin_id', 'lottery_config_id', 'lottery_type', 'is_win', 'win_coin',
|
||||
'super_win_coin', 'reward_win_coin', 'use_coins', 'direction', 'reward_config_id',
|
||||
'ante', 'paid_amount',
|
||||
'start_index', 'target_index', 'roll_array', 'roll_number', 'lottery_name', 'status',
|
||||
];
|
||||
foreach ($keys as $k) {
|
||||
@@ -219,7 +240,7 @@ class WeightTestRunner
|
||||
|
||||
/**
|
||||
* 标记测试成功并记录平台总盈利 platform_profit
|
||||
* 通过关联 DicePlayRecordTest(reward_config_record_id)统计:付费(lottery_type=0)次数×100 - win_coin 求和
|
||||
* 通过关联 DicePlayRecordTest(reward_config_record_id)统计:付费金额 paid_amount 求和 - win_coin 求和
|
||||
*/
|
||||
private function markSuccess(int $recordId, array $resultCounts, array $tierCounts): void
|
||||
{
|
||||
|
||||
@@ -19,9 +19,11 @@ use think\model\relation\BelongsTo;
|
||||
*
|
||||
* @property $id ID
|
||||
* @property $lottery_config_id 彩金池配置id
|
||||
* @property $lottery_type 抽奖类型:0=付费,1=赠送
|
||||
* @property $lottery_type 抽奖类型:0=付费,1=免费
|
||||
* @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 $direction 方向:0=顺时针,1=逆时针
|
||||
* @property $reward_config_id 奖励配置id
|
||||
* @property $create_time 创建时间
|
||||
@@ -77,7 +79,7 @@ class DicePlayRecordTest extends BaseModel
|
||||
return $this->belongsTo(DiceRewardConfigRecord::class, 'reward_config_record_id', 'id');
|
||||
}
|
||||
|
||||
/** 抽奖类型 0=付费 1=赠送 */
|
||||
/** 抽奖类型 0=付费 1=免费 */
|
||||
public function searchLotteryTypeAttr($query, $value)
|
||||
{
|
||||
if ($value !== '' && $value !== null) {
|
||||
@@ -117,6 +119,22 @@ class DicePlayRecordTest extends BaseModel
|
||||
}
|
||||
}
|
||||
|
||||
/** 付费金额(付费局=ante*100,免费局=0) */
|
||||
public function searchPaidAmountAttr($query, $value)
|
||||
{
|
||||
if ($value !== '' && $value !== null) {
|
||||
$query->where('paid_amount', '=', $value);
|
||||
}
|
||||
}
|
||||
|
||||
/** 底注/注数(dice_ante_config.mult) */
|
||||
public function searchAnteAttr($query, $value)
|
||||
{
|
||||
if ($value !== '' && $value !== null) {
|
||||
$query->where('ante', '=', $value);
|
||||
}
|
||||
}
|
||||
|
||||
/** 中奖档位(按 reward_config_id 对应 DiceRewardConfig.tier) */
|
||||
public function searchRewardTierAttr($query, $value)
|
||||
{
|
||||
|
||||
@@ -144,4 +144,12 @@ class DicePlayerTicketRecord extends BaseModel
|
||||
$query->where('create_time', '<=', $value);
|
||||
}
|
||||
}
|
||||
|
||||
/** 底注/注数(ante) */
|
||||
public function searchAnteAttr($query, $value)
|
||||
{
|
||||
if ($value !== '' && $value !== null) {
|
||||
$query->where('ante', '=', $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ use think\model\relation\HasMany;
|
||||
* @property int $over_play_count 已完成次数
|
||||
* @property int $status 状态 -1失败 0进行中 1成功
|
||||
* @property string|null $remark 失败时记录原因
|
||||
* @property int|null $ante 底注/注数(dice_ante_config.mult)
|
||||
* @property int $s_count 顺时针模拟次数(兼容旧数据)
|
||||
* @property int $n_count 逆时针模拟次数(兼容旧数据)
|
||||
* @property int $paid_s_count 付费抽奖顺时针次数
|
||||
@@ -70,18 +71,18 @@ class DiceRewardConfigRecord extends BaseModel
|
||||
|
||||
/**
|
||||
* 根据关联的 DicePlayRecordTest 统计平台赚取平台币
|
||||
* platform_profit = 关联的付费(lottery_type=0)抽取次数 × 100 - 关联的 win_coin 求和
|
||||
* platform_profit = 关联的付费(lottery_type=0)付费金额求和(paid_amount) - 关联的 win_coin 求和
|
||||
* @param int $recordId dice_reward_config_record.id
|
||||
* @return float
|
||||
*/
|
||||
public static function computePlatformProfitFromRelated(int $recordId): float
|
||||
{
|
||||
$paidCount = DicePlayRecordTest::where('reward_config_record_id', $recordId)
|
||||
$paidAmount = (float) DicePlayRecordTest::where('reward_config_record_id', $recordId)
|
||||
->where('lottery_type', 0)
|
||||
->count();
|
||||
->sum('paid_amount');
|
||||
$sumWinCoin = (float) DicePlayRecordTest::where('reward_config_record_id', $recordId)
|
||||
->sum('win_coin');
|
||||
return round($paidCount * 100 - $sumWinCoin, 2);
|
||||
return round($paidAmount - $sumWinCoin, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ class DicePlayRecordTestValidate extends BaseValidate
|
||||
*/
|
||||
protected $message = [
|
||||
'lottery_config_id' => '彩金池配置id必须填写',
|
||||
'lottery_type' => '抽奖类型:0=付费,1=赠送必须填写',
|
||||
'lottery_type' => '抽奖类型:0=付费,1=免费必须填写',
|
||||
'is_win' => '中大奖:0=无,1=中奖必须填写',
|
||||
'direction' => '方向:0=顺时针,1=逆时针必须填写',
|
||||
'reward_config_id' => '奖励配置id必须填写',
|
||||
|
||||
Reference in New Issue
Block a user