优化游玩方式
This commit is contained in:
@@ -162,21 +162,21 @@ class GameController extends BaseController
|
||||
if ($direction !== null) {
|
||||
$direction = (int) $direction;
|
||||
}
|
||||
$ante = $request->post('ante');
|
||||
if ($ante !== null) {
|
||||
$ante = (int) $ante;
|
||||
}
|
||||
if (!in_array($direction, [0, 1], true)) {
|
||||
return $this->fail('direction must be 0 or 1', ReturnCode::PARAMS_ERROR);
|
||||
}
|
||||
if (!is_int($ante) || $ante <= 0) {
|
||||
return $this->fail('ante must be a positive integer', ReturnCode::PARAMS_ERROR);
|
||||
}
|
||||
|
||||
$player = DicePlayer::find($userId);
|
||||
if (!$player) {
|
||||
return $this->fail('User not found', ReturnCode::NOT_FOUND);
|
||||
}
|
||||
$minEv = DiceRewardConfig::getCachedMinRealEv();
|
||||
$minCoin = abs($minEv + 100);
|
||||
$coin = (float) $player->coin;
|
||||
if ($coin < $minCoin) {
|
||||
$msg = ApiLang::translateParams('Balance %s is less than %s, cannot continue', [$coin, $minCoin], $request);
|
||||
return $this->success([], $msg);
|
||||
}
|
||||
|
||||
$lockName = 'play_start_' . $userId;
|
||||
$lockResult = Db::query('SELECT GET_LOCK(?, 30) as l', [$lockName]);
|
||||
@@ -185,7 +185,7 @@ class GameController extends BaseController
|
||||
}
|
||||
try {
|
||||
$logic = new PlayStartLogic();
|
||||
$data = $logic->run($userId, (int)$direction);
|
||||
$data = $logic->run($userId, (int) $direction, $ante);
|
||||
|
||||
$lang = $request->header('lang', 'zh');
|
||||
if (!is_string($lang) || $lang === '') {
|
||||
|
||||
@@ -108,6 +108,7 @@ class GameLogic
|
||||
'player_id' => $playerId,
|
||||
'admin_id' => $adminId,
|
||||
'use_coins' => $cost,
|
||||
'ante' => 1,
|
||||
'total_ticket_count' => $addTotal,
|
||||
'paid_ticket_count' => $addPaid,
|
||||
'free_ticket_count' => $addFree,
|
||||
|
||||
@@ -8,6 +8,7 @@ use app\api\util\ApiLang;
|
||||
use app\api\service\LotteryService;
|
||||
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||
use app\dice\model\play_record\DicePlayRecord;
|
||||
use app\dice\model\ante_config\DiceAnteConfig;
|
||||
use app\dice\model\player\DicePlayer;
|
||||
use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
|
||||
use app\dice\model\player_wallet_record\DicePlayerWalletRecord;
|
||||
@@ -34,8 +35,12 @@ class PlayStartLogic
|
||||
/** 对局状态:超时/失败 */
|
||||
public const RECORD_STATUS_TIMEOUT = 0;
|
||||
|
||||
/** 开启对局最低余额 = |DiceRewardConfig 最小 real_ev + 100| */
|
||||
private const MIN_COIN_EXTRA = 100;
|
||||
/** 单注费用(对应原票价 100) */
|
||||
private const UNIT_COST = 100;
|
||||
/** 免费抽奖注数缓存 key 前缀(用于强制下一局注数一致) */
|
||||
private const FREE_ANTE_KEY_PREFIX = 'api:game:free_ante:';
|
||||
/** 免费抽奖注数缓存过期(秒) */
|
||||
private const FREE_ANTE_TTL = 86400 * 7;
|
||||
/** 豹子号中大奖额外平台币(无 BIGWIN 配置时兜底) */
|
||||
private const SUPER_WIN_BONUS = 500;
|
||||
/** 可触发超级大奖的 grid_number(5=全1 10=全2 15=全3 20=全4 25=全5 30=全6) */
|
||||
@@ -47,36 +52,60 @@ class PlayStartLogic
|
||||
* 执行一局游戏
|
||||
* @param int $playerId 玩家ID
|
||||
* @param int $direction 方向 0=无/顺时针 1=中奖/逆时针(前端 direction)
|
||||
* @param int $ante 注数(必须在 DiceAnteConfig.mult 中存在)
|
||||
* @return array 成功返回 DicePlayRecord 数据;余额不足时抛 ApiException,message 为约定文案
|
||||
*/
|
||||
public function run(int $playerId, int $direction): array
|
||||
public function run(int $playerId, int $direction, int $ante): array
|
||||
{
|
||||
$player = DicePlayer::find($playerId);
|
||||
if (!$player) {
|
||||
throw new ApiException('User not found');
|
||||
}
|
||||
|
||||
$minEv = DiceRewardConfig::getCachedMinRealEv();
|
||||
$minCoin = abs($minEv + self::MIN_COIN_EXTRA);
|
||||
$coin = (float) $player->coin;
|
||||
if ($coin < $minCoin) {
|
||||
throw new ApiException(ApiLang::translateParams('当前玩家余额%s小于%s无法继续游戏', [$coin, $minCoin]));
|
||||
if ($ante <= 0) {
|
||||
throw new ApiException('ante must be a positive integer');
|
||||
}
|
||||
|
||||
$paid = (int) ($player->paid_ticket_count ?? 0);
|
||||
$free = (int) ($player->free_ticket_count ?? 0);
|
||||
if ($paid + $free <= 0) {
|
||||
throw new ApiException('Insufficient lottery tickets');
|
||||
// 注数合规校验:ante 必须存在于 dice_ante_config.mult
|
||||
$anteConfigModel = new DiceAnteConfig();
|
||||
$exists = $anteConfigModel->where('mult', $ante)->count();
|
||||
if ($exists <= 0) {
|
||||
throw new ApiException('当前注数不合规,请选择正确的注数');
|
||||
}
|
||||
|
||||
// 免费抽奖:不再使用抽奖券作为开始条件,仅用 free_ticket_count 表示“免费抽奖次数”
|
||||
$freeCount = (int) ($player->free_ticket_count ?? 0);
|
||||
$isFree = $freeCount > 0;
|
||||
$ticketType = $isFree ? self::LOTTERY_TYPE_FREE : self::LOTTERY_TYPE_PAID;
|
||||
|
||||
// 若为免费抽奖:注数必须与上一次触发免费抽奖时的注数一致
|
||||
if ($isFree) {
|
||||
$requiredAnte = Cache::get(self::FREE_ANTE_KEY_PREFIX . $playerId);
|
||||
if ($requiredAnte !== null && $requiredAnte !== '' && (int) $requiredAnte !== $ante) {
|
||||
throw new ApiException('免费抽奖注数必须与上一次一致,请修改注数后继续');
|
||||
}
|
||||
}
|
||||
|
||||
$lotteryService = LotteryService::getOrCreate($playerId);
|
||||
$ticketType = LotteryService::drawTicketType($paid, $free);
|
||||
$configType0 = DiceLotteryPoolConfig::where('name', 'default')->find();
|
||||
$configType1 = DiceLotteryPoolConfig::where('name', 'killScore')->find();
|
||||
if (!$configType0) {
|
||||
throw new ApiException('Lottery pool config not found (name=default required)');
|
||||
}
|
||||
|
||||
// 余额校验:统一校验 ante * min(real_ev)
|
||||
$minEv = DiceRewardConfig::getCachedMinRealEv();
|
||||
$needMinBalance = abs((float) $minEv) * $ante;
|
||||
if ($coin < $needMinBalance) {
|
||||
throw new ApiException('未达抽奖余额 ' . $needMinBalance . ',无法开始游戏');
|
||||
}
|
||||
|
||||
// 付费抽奖:开始前扣除费用 ante * 100,不足则提示余额不足
|
||||
$paidAmount = $ticketType === self::LOTTERY_TYPE_PAID ? ($ante * self::UNIT_COST) : 0;
|
||||
if ($ticketType === self::LOTTERY_TYPE_PAID && $coin < $paidAmount) {
|
||||
throw new ApiException('余额不足');
|
||||
}
|
||||
|
||||
// 彩金池累计盈利:用于判断是否触发杀分(不再依赖单个玩家累计盈利)
|
||||
// 该值来自 dice_lottery_pool_config.profit_amount
|
||||
$poolProfitTotal = $configType0->profit_amount ?? 0;
|
||||
@@ -133,7 +162,8 @@ class PlayStartLogic
|
||||
$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;
|
||||
|
||||
// 豹子判定:5/30 必豹子;10/15/20/25 按 DiceRewardConfig 中 BIGWIN 该点数的 weight 判定(0-10000,10000=100%)
|
||||
// 杀分档位:不触发豹子,5/30 已在上方抽取时排除,10/15/20/25 仅生成非豹子组合
|
||||
@@ -166,7 +196,8 @@ 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;
|
||||
// 中 BIGWIN 豹子:不走原奖励流程,不记录原奖励,不触发 T5 再来一次,仅发放豹子奖金
|
||||
$rewardWinCoin = 0;
|
||||
$realEv = 0;
|
||||
@@ -203,6 +234,8 @@ class PlayStartLogic
|
||||
$rewardId,
|
||||
$configName,
|
||||
$ticketType,
|
||||
$ante,
|
||||
$paidAmount,
|
||||
$winCoin,
|
||||
$superWinCoin,
|
||||
$rewardWinCoin,
|
||||
@@ -221,11 +254,13 @@ class PlayStartLogic
|
||||
'admin_id' => $adminId,
|
||||
'lottery_config_id' => $configId,
|
||||
'lottery_type' => $ticketType,
|
||||
'ante' => $ante,
|
||||
'paid_amount' => $paidAmount,
|
||||
'is_win' => $isWin,
|
||||
'win_coin' => $winCoin,
|
||||
'super_win_coin' => $superWinCoin,
|
||||
'reward_win_coin' => $rewardWinCoin,
|
||||
'use_coins' => 0,
|
||||
'use_coins' => $paidAmount,
|
||||
'direction' => $direction,
|
||||
'reward_config_id' => $rewardId,
|
||||
'start_index' => $startIndex,
|
||||
@@ -241,34 +276,40 @@ class PlayStartLogic
|
||||
throw new \RuntimeException('玩家不存在');
|
||||
}
|
||||
$coinBefore = (float) $p->coin;
|
||||
$coinAfter = $coinBefore + $winCoin;
|
||||
// 开始前先扣付费金额,再加中奖金额(免费抽奖 paid_amount=0)
|
||||
$coinAfter = $coinBefore - $paidAmount + $winCoin;
|
||||
$p->coin = $coinAfter;
|
||||
$p->total_ticket_count = max(0, (int) $p->total_ticket_count - 1);
|
||||
if ($ticketType === self::LOTTERY_TYPE_PAID) {
|
||||
$p->paid_ticket_count = max(0, (int) $p->paid_ticket_count - 1);
|
||||
} else {
|
||||
// 不再使用抽奖券作为抽奖条件:付费不扣抽奖次数;免费抽奖仅消耗 free_ticket_count
|
||||
if ($ticketType === self::LOTTERY_TYPE_FREE) {
|
||||
$p->free_ticket_count = max(0, (int) $p->free_ticket_count - 1);
|
||||
}
|
||||
|
||||
// 若本局中奖档位为 T5,则额外赠送 1 次免费抽奖次数(总次数也 +1),并记录抽奖券获取记录
|
||||
if ($isTierT5) {
|
||||
$p->free_ticket_count = (int) $p->free_ticket_count + 1;
|
||||
$p->total_ticket_count = (int) $p->total_ticket_count + 1;
|
||||
|
||||
DicePlayerTicketRecord::create([
|
||||
'player_id' => $playerId,
|
||||
'admin_id' => $adminId,
|
||||
'ante' => $ante,
|
||||
'free_ticket_count' => 1,
|
||||
'remark' => '中奖结果为T5',
|
||||
]);
|
||||
// 记录免费抽奖注数,用于强制下一局注数一致
|
||||
Cache::set(self::FREE_ANTE_KEY_PREFIX . $playerId, $ante, self::FREE_ANTE_TTL);
|
||||
} else {
|
||||
// 若本次消耗了最后一次免费抽奖,则清理注数锁
|
||||
if ($ticketType === self::LOTTERY_TYPE_FREE && (int) $p->free_ticket_count <= 0) {
|
||||
Cache::delete(self::FREE_ANTE_KEY_PREFIX . $playerId);
|
||||
}
|
||||
}
|
||||
|
||||
$p->save();
|
||||
|
||||
// 彩金池累计盈利累加在 name=default 彩金池上:
|
||||
// 付费券:每局按“当前中奖金额(含 BIGWIN) - 抽奖券费用 100”
|
||||
// 付费:每局按“当前中奖金额(含 BIGWIN) - 抽奖费用(ante*100)”
|
||||
// 免费券:取消票价成本 100,只计入中奖金额
|
||||
$perPlayProfit = ($ticketType === self::LOTTERY_TYPE_PAID) ? ($winCoin - 100.0) : $winCoin;
|
||||
$perPlayProfit = ($ticketType === self::LOTTERY_TYPE_PAID) ? ($winCoin - (float) $paidAmount) : $winCoin;
|
||||
$addProfit = $perPlayProfit;
|
||||
try {
|
||||
DiceLotteryPoolConfig::where('id', $type0ConfigId)->update([
|
||||
@@ -285,7 +326,8 @@ class PlayStartLogic
|
||||
DicePlayerWalletRecord::create([
|
||||
'player_id' => $playerId,
|
||||
'admin_id' => $adminId,
|
||||
'coin' => $winCoin,
|
||||
// 钱包流水记录本局净变化:-付费金额 + 中奖金额(免费抽奖付费金额为 0)
|
||||
'coin' => $winCoin - (float) $paidAmount,
|
||||
'type' => self::WALLET_TYPE_DRAW,
|
||||
'wallet_before' => $coinBefore,
|
||||
'wallet_after' => $coinAfter,
|
||||
@@ -336,7 +378,6 @@ class PlayStartLogic
|
||||
$arr['tier'] = $tier ?? '';
|
||||
// 记录完数据后返回当前玩家余额与抽奖次数
|
||||
$arr['coin'] = $updated ? (float) $updated->coin : 0;
|
||||
$arr['total_ticket_count'] = $updated ? (int) $updated->total_ticket_count : 0;
|
||||
return $arr;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,9 +64,9 @@ class DicePlayRecordController extends BaseController
|
||||
// 按当前筛选条件统计:平台总盈利 = 付费抽奖(lottery_type=0)次数×100 - 玩家总收益(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;
|
||||
|
||||
@@ -22,10 +22,13 @@ use think\model\relation\BelongsTo;
|
||||
* @property $admin_id 关联玩家所属管理员ID(DicePlayer.admin_id)
|
||||
* @property $lottery_config_id 彩金池配置
|
||||
* @property $lottery_type 抽奖类型
|
||||
* @property $ante 底注/注数(dice_ante_config.mult)
|
||||
* @property $paid_amount 付费金额(付费局=ante*100,免费局=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 中大奖平台币(豹子时发放)
|
||||
* @property $reward_win_coin 摇色子中奖平台币
|
||||
* @property $use_coins 消耗平台币(兼容字段:付费局=paid_amount,免费局=0)
|
||||
* @property $direction 方向:0=顺时针,1=逆时针
|
||||
* @property $reward_config_id 奖励配置id
|
||||
* @property $lottery_id 奖池
|
||||
|
||||
@@ -19,6 +19,7 @@ use think\model\relation\BelongsTo;
|
||||
* @property $player_id 玩家id
|
||||
* @property $admin_id 关联玩家所属管理员ID(DicePlayer.admin_id)
|
||||
* @property $use_coins 消耗硬币
|
||||
* @property $ante 底注/注数(历史购买记录默认为1;T5再来一次写入本次注数)
|
||||
* @property $total_ticket_count 总抽奖次数
|
||||
* @property $paid_ticket_count 购买抽奖次数
|
||||
* @property $free_ticket_count 赠送抽奖次数
|
||||
|
||||
Reference in New Issue
Block a user