round->fresh(); if (! $round || ! $round->isBettingOpen()) { return; } $user = User::where('username', 'AI小班长')->first(); if (! $user) { return; } // 2. 检查连输惩罚超时 if (Redis::exists('ai_baccarat_timeout')) { return; // 还在禁赛期 } // 3. 检查余额与限额 $config = GameConfig::forGame('baccarat')?->params ?? []; $minBet = (int) ($config['min_bet'] ?? 100); $maxBet = (int) ($config['max_bet'] ?? 50000); // 至少保留 2000 金币底仓 $availableGold = ($user->jjb ?? 0) - 2000; if ($availableGold < $minBet) { return; // 资金不足以支撑最小下注 } // 下注金额:可用余额的 2% ~ 5%,并在 min_bet 和 max_bet 之间 $percent = rand(2, 5) / 100.0; $amount = (int) round($availableGold * $percent); $amount = max($minBet, min($amount, $maxBet)); // 如果依然大于实际 jjb (保险兜底),则放弃 if ($amount > $user->jjb) { return; } // 4. 获取近期路单(最多取 20 局) $recentResults = BaccaratRound::query() ->where('status', 'settled') ->orderByDesc('id') ->limit(20) ->pluck('result') ->toArray(); // 5. 优先调用 AI 接口预测下注方向 $predictionService = app(BaccaratPredictionService::class); $aiPrediction = $predictionService->predict($recentResults); $decisionSource = 'ai'; $betType = $aiPrediction; // AI 不可用时回退本地路单决策(保底逻辑) if ($betType === null) { $decisionSource = 'local'; $bigCount = count(array_filter($recentResults, fn (string $r) => $r === 'big')); $smallCount = count(array_filter($recentResults, fn (string $r) => $r === 'small')); $strategy = rand(1, 100); if ($strategy <= 10) { $betType = 'triple'; // 10% 概率博豹子 } elseif ($bigCount > $smallCount) { // 大偏热:70% 概率顺势买大,30% 逆势买小 $betType = rand(1, 100) <= 70 ? 'big' : 'small'; } elseif ($smallCount > $bigCount) { $betType = rand(1, 100) <= 70 ? 'small' : 'big'; } else { $betType = rand(0, 1) ? 'big' : 'small'; } } // 记录 AI 小班长本次决策日志 $labelMap = ['big' => '大', 'small' => '小', 'triple' => '豹子']; Log::channel('daily')->info('AI小班长百家乐决策', [ 'round_id' => $round->id, 'decision_source' => $decisionSource === 'ai' ? 'AI接口预测' : '本地路单兜底', 'ai_prediction' => $aiPrediction ? $labelMap[$aiPrediction] : 'null(不可用)', 'final_bet' => $labelMap[$betType] ?? $betType, 'bet_amount' => $amount, 'roadmap_20' => implode('→', array_map(fn (string $r) => $labelMap[$r] ?? $r, array_reverse($recentResults))), ]); // 5. 执行下注 (同 BaccaratController::bet 逻辑) DB::transaction(function () use ($user, $round, $betType, $amount, $currency) { // 幂等:同一局只能下一注 $existing = BaccaratBet::query() ->where('round_id', $round->id) ->where('user_id', $user->id) ->lockForUpdate() ->exists(); if ($existing) { return; } // 扣除金币 $currency->change( $user, 'gold', -$amount, CurrencySource::BACCARAT_BET, "AI小班长百家乐 #{$round->id} 押 ".match ($betType) { 'big' => '大', 'small' => '小', default => '豹子' }, ); // 写入下注记录 BaccaratBet::create([ 'round_id' => $round->id, 'user_id' => $user->id, 'bet_type' => $betType, 'amount' => $amount, 'status' => 'pending', ]); // 更新局次汇总统计 $field = 'total_bet_'.$betType; $countField = 'bet_count_'.$betType; $round->increment($field, $amount); $round->increment($countField); $round->increment('bet_count'); // 广播各选项的最新押注人数(让前台看到 AI 下注的人数增长) event(new \App\Events\BaccaratPoolUpdated($round)); }); // 下注成功后,在聊天室发送一条普通聊天消息告知大家 $this->broadcastBetMessage($user, $round->room_id ?? 1, $betType, $amount, $decisionSource); } /** * 广播 AI小班长本次下注情况到聊天室 * * 以普通聊天消息形式发送,发送对象为大家。 * * @param User $user AI小班长用户对象 * @param int $roomId 目标房间 ID * @param string $betType 下注方向:big|small|triple * @param int $amount 下注金额 * @param string $decisionSource 决策来源:ai | local */ private function broadcastBetMessage( User $user, int $roomId, string $betType, int $amount, string $decisionSource, ): void { $chatState = app(ChatStateService::class); $labelMap = ['big' => '大【骰子点数 11~17】', 'small' => '小【骰子点数 4~10】', 'triple' => '豹子【三骰同点】']; $betLabel = $labelMap[$betType] ?? $betType; $sourceTag = $decisionSource === 'ai' ? '🤖 AI分析' : '📊路单统计'; $content = "{$sourceTag} 小班长投了 {$amount} 金币,压【{$betLabel}】,大家加油!🎲"; $msg = [ 'id' => $chatState->nextMessageId($roomId), 'room_id' => $roomId, 'from_user' => $user->username, 'to_user' => '大家', 'content' => $content, 'is_secret' => false, 'font_color' => null, 'action' => '说', 'sent_at' => now()->toDateTimeString(), ]; $chatState->pushMessage($roomId, $msg); broadcast(new MessageSent($roomId, $msg)); SaveMessageJob::dispatch($msg); } }