优化ai小班长

This commit is contained in:
2026-04-12 22:25:18 +08:00
parent f8d5a3b250
commit ef407a8c6e
7 changed files with 608 additions and 91 deletions
+79 -84
View File
@@ -23,6 +23,8 @@ use App\Models\BaccaratRound;
use App\Models\GameConfig;
use App\Models\Sysparam;
use App\Models\User;
use App\Services\AiFinanceService;
use App\Services\BaccaratLossCoverService;
use App\Services\BaccaratPredictionService;
use App\Services\ChatStateService;
use App\Services\UserCurrencyService;
@@ -35,15 +37,24 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Redis;
/**
* 控制 AI小班长在百家乐中的下注、观望与资金调度行为。
*/
class AiBaccaratBetJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* 注入当前需要处理的百家乐局次。
*/
public function __construct(
public readonly BaccaratRound $round,
) {}
public function handle(UserCurrencyService $currency): void
/**
* 执行 AI小班长本局百家乐的完整决策流程。
*/
public function handle(UserCurrencyService $currency, AiFinanceService $aiFinance): void
{
// 1. 检查总开关与游戏开关
if (Sysparam::getValue('chatbot_enabled', '0') !== '1' || ! GameConfig::isEnabled('baccarat')) {
@@ -55,10 +66,8 @@ class AiBaccaratBetJob implements ShouldQueue
return;
}
$chatState = app(ChatStateService::class);
// 2. 资金管理:自动存款与领取补偿
$this->manageFinances($user, $chatState);
$this->manageFinances($user, $aiFinance);
$round = $this->round->fresh();
if (! $round || ! $round->isBettingOpen()) {
@@ -75,14 +84,20 @@ class AiBaccaratBetJob implements ShouldQueue
$minBet = (int) ($config['min_bet'] ?? 100);
$maxBet = (int) ($config['max_bet'] ?? 50000);
// 至少保留 100万 金币底仓(确保有充足资金参与多轮下注)
$reserve = 1000000;
$availableGold = ($user->jjb ?? 0) - $reserve;
if ($availableGold < $minBet) {
// 5. 查询当前是否命中“你玩游戏我买单”活动窗口。
$lossCoverService = app(BaccaratLossCoverService::class);
$lossCoverEvent = $lossCoverService->findEventForBetTime(now());
$hasActiveLossCover = $lossCoverEvent?->status === 'active';
// 买单活动进行中时允许 AI 全仓下注;平时只动用当前手上的 jjb,不再强制保留 100 万。
$bettableGold = $hasActiveLossCover
? $aiFinance->getTotalGoldAssets($user)
: $aiFinance->getSpendableGold($user);
if ($bettableGold < $minBet) {
return; // 资金不足以支撑最小下注
}
// 5. 获取近期路单和 AI 历史下注
// 6. 获取近期路单和 AI 历史下注
$recentResults = BaccaratRound::query()
->where('status', 'settled')
->orderByDesc('id')
@@ -108,12 +123,13 @@ class AiBaccaratBetJob implements ShouldQueue
})
->toArray();
// 5. 调用 AI 接口出具统筹策略
// 7. 调用 AI 接口出具统筹策略
$predictionService = app(BaccaratPredictionService::class);
$context = [
'recent_results' => $recentResults,
'available_gold' => $availableGold,
'available_gold' => $bettableGold,
'historical_bets' => $historicalBets,
'loss_cover_active' => $hasActiveLossCover,
];
$aiPrediction = $predictionService->predict($context);
@@ -128,14 +144,19 @@ class AiBaccaratBetJob implements ShouldQueue
$percent = $aiPrediction['percentage'];
$reason = $aiPrediction['reason'];
// 限定单局最高下注不超过可用金额的 5% 以防止 AI "乱梭哈" 破产
$percent = min(5, max(0, $percent));
if ($betType !== 'pass') {
$amount = (int) round($availableGold * ($percent / 100.0));
$amount = max($minBet, min($amount, $maxBet));
if ($amount > $user->jjb) {
$amount = $user->jjb;
if ($hasActiveLossCover) {
// 买单活动进行中时,AI 可放心动用全部总资产,因为本局若输可返还。
$amount = $bettableGold;
$reason = trim($reason.' 买单活动进行中,本局采用总资产全额下注策略。');
} else {
// 非买单活动期间,限定单局最高下注不超过手头金币的 5% 以防止 AI 破产。
$percent = min(5, max(0, $percent));
$amount = (int) round($bettableGold * ($percent / 100.0));
$amount = max($minBet, min($amount, $maxBet));
if ($amount > $bettableGold) {
$amount = $bettableGold;
}
}
}
} else {
@@ -156,9 +177,15 @@ class AiBaccaratBetJob implements ShouldQueue
}
if ($betType !== 'pass') {
$percent = rand(2, 5) / 100.0;
$amount = (int) round($availableGold * $percent);
$amount = max($minBet, min($amount, $maxBet));
if ($hasActiveLossCover) {
// 本地兜底策略命中买单活动时,同样执行总资产全额下注。
$amount = $bettableGold;
$reason = '买单活动进行中,采用本地总资产全额下注兜底策略。';
} else {
$percent = rand(2, 5) / 100.0;
$amount = (int) round($bettableGold * $percent);
$amount = max($minBet, min($amount, $maxBet));
}
}
}
@@ -180,6 +207,14 @@ class AiBaccaratBetJob implements ShouldQueue
return;
}
// 买单活动期间允许全仓下注;非活动期间只检查当前手上金币是否够本次下注。
$isReadyToSpend = $hasActiveLossCover
? $aiFinance->prepareAllInSpend($user, $amount)
: $aiFinance->prepareSpend($user, $amount);
if (! $isReadyToSpend) {
return;
}
// 二次校验,防止大模型接口调用太慢导致下注时该局已关闭
$round->refresh();
if (! $round->isBettingOpen()) {
@@ -188,12 +223,17 @@ class AiBaccaratBetJob implements ShouldQueue
return;
}
// 6. 执行下注 (同 BaccaratController::bet 逻辑)
DB::transaction(function () use ($user, $round, $betType, $amount, $currency) {
// 8. 执行下注 (同 BaccaratController::bet 逻辑)
DB::transaction(function () use ($user, $round, $betType, $amount, $currency, $lossCoverEvent, $lossCoverService) {
$lockedUser = User::query()->lockForUpdate()->find($user->id);
if (! $lockedUser || (int) ($lockedUser->jjb ?? 0) < $amount) {
return;
}
// 幂等:同一局只能下一注
$existing = BaccaratBet::query()
->where('round_id', $round->id)
->where('user_id', $user->id)
->where('user_id', $lockedUser->id)
->lockForUpdate()
->exists();
@@ -203,7 +243,7 @@ class AiBaccaratBetJob implements ShouldQueue
// 扣除金币
$currency->change(
$user,
$lockedUser,
'gold',
-$amount,
CurrencySource::BACCARAT_BET,
@@ -213,14 +253,20 @@ class AiBaccaratBetJob implements ShouldQueue
);
// 写入下注记录
BaccaratBet::create([
$bet = BaccaratBet::create([
'round_id' => $round->id,
'user_id' => $user->id,
'user_id' => $lockedUser->id,
'loss_cover_event_id' => $lossCoverEvent?->id,
'bet_type' => $betType,
'amount' => $amount,
'status' => 'pending',
]);
// 命中买单活动的下注需要登记到活动聚合记录里,确保后续能正确补偿返还。
if ($lossCoverEvent) {
$lossCoverService->registerBet($bet);
}
// 更新局次汇总统计
$field = 'total_bet_'.$betType;
$countField = 'bet_count_'.$betType;
@@ -232,6 +278,9 @@ class AiBaccaratBetJob implements ShouldQueue
event(new \App\Events\BaccaratPoolUpdated($round));
});
// 下注完成后,若手上金币仍高于 100 万,则把超出的部分继续回存银行。
$aiFinance->bankExcessGold($user);
// 下注成功后,在聊天室发送一条普通聊天消息告知大家
$this->broadcastBetMessage($user, $round->room_id ?? 1, $betType, $amount, $decisionSource);
}
@@ -311,9 +360,9 @@ class AiBaccaratBetJob implements ShouldQueue
}
/**
* AI 资金管理逻辑:自动存款、领取买单补偿
* AI 资金管理逻辑:优先领取补偿,再按“超过 100 万才存款”的规则整理持仓。
*/
private function manageFinances(User $user, ChatStateService $chatState): void
private function manageFinances(User $user, AiFinanceService $aiFinance): void
{
// 1. 检查是否有“买单”活动补偿可领取 (jjb 较低时优先领取)
$lossCoverService = app(\App\Services\BaccaratLossCoverService::class);
@@ -330,60 +379,6 @@ class AiBaccaratBetJob implements ShouldQueue
}
}
// 2. 自动存款逻辑:长期目标 1000万 / 3000万
$bankTarget1 = 10000000;
$bankTarget2 = 30000000;
$currentBank = (int) $user->bank_jjb;
// 如果手上金币超过 10万,且存款未达 3000万,则进行存款
if ($user->jjb > 1000000 && $currentBank < $bankTarget2) {
$depositAmount = $user->jjb - 500000; // 留 5万在手上作为本金
// 如果存款快到目标了,按需存入
if ($currentBank < $bankTarget1 && ($currentBank + $depositAmount) > $bankTarget1) {
$depositAmount = $bankTarget1 - $currentBank;
}
DB::transaction(function () use ($user, $depositAmount, $currentBank, $bankTarget1, $bankTarget2, $chatState) {
$user->decrement('jjb', $depositAmount);
$user->increment('bank_jjb', $depositAmount);
$newBank = $user->bank_jjb;
\App\Models\BankLog::create([
'user_id' => $user->id,
'type' => 'deposit',
'amount' => $depositAmount,
'balance_after' => $newBank,
]);
// 达成阶段性目标时大声宣布
$milestone = 0;
if ($currentBank < $bankTarget1 && $newBank >= $bankTarget1) {
$milestone = 1000; // 1000万
} elseif ($currentBank < $bankTarget2 && $newBank >= $bankTarget2) {
$milestone = 3000; // 3000万
}
if ($milestone > 0) {
$content = "🏆 🎉 <b>【全站公告】</b> 恭喜 <b>AI小班长</b> 达成理财新高度!<br/>他在银行的存款已成功突破 <span style='color:#e11d48;font-weight:bold;'>{$milestone}万</span> 金币!💰✨";
$msg = [
'id' => $chatState->nextMessageId(1),
'room_id' => 1,
'from_user' => '系统传音',
'to_user' => '大家',
'content' => $content,
'is_secret' => false,
'font_color' => '#f59e0b',
'action' => '大声宣告',
'sent_at' => now()->toDateTimeString(),
];
$chatState->pushMessage(1, $msg);
broadcast(new MessageSent(1, $msg));
SaveMessageJob::dispatch($msg);
}
});
Log::info("AI小班长自动存款: Amount: {$depositAmount}, New Bank Balance: {$user->bank_jjb}");
}
$aiFinance->rebalanceHoldings($user);
}
}