2026-03-26 11:49:36 +08:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件功能:AI小班长自动参与百家乐下注
|
|
|
|
|
|
*
|
|
|
|
|
|
* 在每局百家乐开启时延迟调度执行:
|
|
|
|
|
|
* 1. 检查是否存在连输休息惩罚(1小时)
|
2026-04-12 22:42:32 +08:00
|
|
|
|
* 2. 检查可用金币与活动时间窗,确定本局下注额度
|
2026-03-28 20:35:43 +08:00
|
|
|
|
* 3. 调用 AI 接口预测路单走势决定下注方向(AI 不可用时回退本地决策)
|
2026-03-26 11:49:36 +08:00
|
|
|
|
* 4. 提交下注
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author ChatRoom Laravel
|
|
|
|
|
|
*
|
|
|
|
|
|
* @version 1.0.0
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
namespace App\Jobs;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Enums\CurrencySource;
|
2026-03-28 21:38:34 +08:00
|
|
|
|
use App\Events\MessageSent;
|
2026-03-26 11:49:36 +08:00
|
|
|
|
use App\Models\BaccaratBet;
|
|
|
|
|
|
use App\Models\BaccaratRound;
|
|
|
|
|
|
use App\Models\GameConfig;
|
|
|
|
|
|
use App\Models\Sysparam;
|
|
|
|
|
|
use App\Models\User;
|
2026-04-12 22:25:18 +08:00
|
|
|
|
use App\Services\AiFinanceService;
|
|
|
|
|
|
use App\Services\BaccaratLossCoverService;
|
2026-03-28 20:35:43 +08:00
|
|
|
|
use App\Services\BaccaratPredictionService;
|
2026-03-28 21:38:34 +08:00
|
|
|
|
use App\Services\ChatStateService;
|
2026-03-26 11:49:36 +08:00
|
|
|
|
use App\Services\UserCurrencyService;
|
|
|
|
|
|
use Illuminate\Bus\Queueable;
|
|
|
|
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
|
|
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
|
|
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
|
|
|
|
use Illuminate\Queue\SerializesModels;
|
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
2026-03-28 21:11:44 +08:00
|
|
|
|
use Illuminate\Support\Facades\Log;
|
2026-03-26 11:49:36 +08:00
|
|
|
|
use Illuminate\Support\Facades\Redis;
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 控制 AI小班长在百家乐中的下注、观望与资金调度行为。
|
|
|
|
|
|
*/
|
2026-03-26 11:49:36 +08:00
|
|
|
|
class AiBaccaratBetJob implements ShouldQueue
|
|
|
|
|
|
{
|
|
|
|
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 注入当前需要处理的百家乐局次。
|
|
|
|
|
|
*/
|
2026-03-26 11:49:36 +08:00
|
|
|
|
public function __construct(
|
|
|
|
|
|
public readonly BaccaratRound $round,
|
|
|
|
|
|
) {}
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 执行 AI小班长本局百家乐的完整决策流程。
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function handle(UserCurrencyService $currency, AiFinanceService $aiFinance): void
|
2026-03-26 11:49:36 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 1. 检查总开关与游戏开关
|
|
|
|
|
|
if (Sysparam::getValue('chatbot_enabled', '0') !== '1' || ! GameConfig::isEnabled('baccarat')) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 18:50:41 +08:00
|
|
|
|
$user = User::where('username', 'AI小班长')->first();
|
|
|
|
|
|
if (! $user) {
|
2026-03-26 11:49:36 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 18:50:41 +08:00
|
|
|
|
// 2. 资金管理:自动存款与领取补偿
|
2026-04-12 22:25:18 +08:00
|
|
|
|
$this->manageFinances($user, $aiFinance);
|
2026-04-12 18:50:41 +08:00
|
|
|
|
|
|
|
|
|
|
$round = $this->round->fresh();
|
|
|
|
|
|
if (! $round || ! $round->isBettingOpen()) {
|
2026-03-26 11:49:36 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 18:50:41 +08:00
|
|
|
|
// 3. 检查连输惩罚超时
|
2026-03-26 11:49:36 +08:00
|
|
|
|
if (Redis::exists('ai_baccarat_timeout')) {
|
|
|
|
|
|
return; // 还在禁赛期
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 18:50:41 +08:00
|
|
|
|
// 4. 检查余额与限额
|
2026-03-26 11:49:36 +08:00
|
|
|
|
$config = GameConfig::forGame('baccarat')?->params ?? [];
|
|
|
|
|
|
$minBet = (int) ($config['min_bet'] ?? 100);
|
|
|
|
|
|
$maxBet = (int) ($config['max_bet'] ?? 50000);
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
// 5. 查询当前是否命中“你玩游戏我买单”活动窗口。
|
|
|
|
|
|
$lossCoverService = app(BaccaratLossCoverService::class);
|
|
|
|
|
|
$lossCoverEvent = $lossCoverService->findEventForBetTime(now());
|
2026-04-12 22:42:32 +08:00
|
|
|
|
$isInLossCoverWindow = $lossCoverEvent !== null;
|
2026-04-12 22:25:18 +08:00
|
|
|
|
|
2026-04-12 22:42:32 +08:00
|
|
|
|
// 买单活动进行中时允许 AI 统筹“手上 + 银行”总资产;平时只动用当前手上的 jjb。
|
|
|
|
|
|
$bettableGold = $isInLossCoverWindow
|
2026-04-12 22:25:18 +08:00
|
|
|
|
? $aiFinance->getTotalGoldAssets($user)
|
|
|
|
|
|
: $aiFinance->getSpendableGold($user);
|
|
|
|
|
|
if ($bettableGold < $minBet) {
|
2026-03-26 11:49:36 +08:00
|
|
|
|
return; // 资金不足以支撑最小下注
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
// 6. 获取近期路单和 AI 历史下注
|
2026-03-26 11:49:36 +08:00
|
|
|
|
$recentResults = BaccaratRound::query()
|
|
|
|
|
|
->where('status', 'settled')
|
|
|
|
|
|
->orderByDesc('id')
|
2026-03-28 20:35:43 +08:00
|
|
|
|
->limit(20)
|
2026-03-26 11:49:36 +08:00
|
|
|
|
->pluck('result')
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
2026-04-02 10:21:20 +08:00
|
|
|
|
$historicalBets = BaccaratBet::query()
|
|
|
|
|
|
->join('baccarat_rounds', 'baccarat_bets.round_id', '=', 'baccarat_rounds.id')
|
|
|
|
|
|
->where('baccarat_bets.user_id', $user->id)
|
|
|
|
|
|
->where('baccarat_rounds.status', 'settled')
|
|
|
|
|
|
->orderByDesc('baccarat_rounds.id')
|
|
|
|
|
|
->limit(10)
|
|
|
|
|
|
->select('baccarat_bets.*')
|
|
|
|
|
|
->get()
|
|
|
|
|
|
->map(function ($bet) {
|
|
|
|
|
|
return [
|
|
|
|
|
|
'round_id' => $bet->round_id,
|
|
|
|
|
|
'bet_type' => $bet->bet_type,
|
|
|
|
|
|
'amount' => $bet->amount,
|
|
|
|
|
|
'profit' => $bet->profit ?? 0,
|
|
|
|
|
|
];
|
|
|
|
|
|
})
|
|
|
|
|
|
->toArray();
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
// 7. 调用 AI 接口出具统筹策略
|
2026-03-28 20:35:43 +08:00
|
|
|
|
$predictionService = app(BaccaratPredictionService::class);
|
2026-04-02 10:21:20 +08:00
|
|
|
|
$context = [
|
|
|
|
|
|
'recent_results' => $recentResults,
|
2026-04-12 22:25:18 +08:00
|
|
|
|
'available_gold' => $bettableGold,
|
2026-04-02 10:21:20 +08:00
|
|
|
|
'historical_bets' => $historicalBets,
|
2026-04-12 22:42:32 +08:00
|
|
|
|
'loss_cover_active' => $isInLossCoverWindow,
|
2026-04-02 10:21:20 +08:00
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$aiPrediction = $predictionService->predict($context);
|
2026-03-28 21:11:44 +08:00
|
|
|
|
$decisionSource = 'ai';
|
2026-03-28 20:35:43 +08:00
|
|
|
|
|
2026-04-02 10:21:20 +08:00
|
|
|
|
$betType = 'pass';
|
|
|
|
|
|
$amount = 0;
|
|
|
|
|
|
$reason = '';
|
|
|
|
|
|
|
|
|
|
|
|
if ($aiPrediction) {
|
|
|
|
|
|
$betType = $aiPrediction['action'];
|
|
|
|
|
|
$percent = $aiPrediction['percentage'];
|
|
|
|
|
|
$reason = $aiPrediction['reason'];
|
|
|
|
|
|
|
2026-04-19 12:36:23 +08:00
|
|
|
|
// 买单活动期间不允许 AI 选择观望,避免错过“输也可返还”的活动资格。
|
|
|
|
|
|
if ($isInLossCoverWindow && $betType === 'pass') {
|
|
|
|
|
|
$decisionSource = 'local';
|
|
|
|
|
|
$betType = $this->resolveLocalBetType($recentResults, false);
|
|
|
|
|
|
$reason = trim($reason.' 买单活动进行中,本局禁止观望,已切换本地强制参战策略。');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 10:21:20 +08:00
|
|
|
|
if ($betType !== 'pass') {
|
2026-04-12 22:42:32 +08:00
|
|
|
|
if ($isInLossCoverWindow) {
|
|
|
|
|
|
// 买单活动进行中且金币足够时,直接按百家乐单局最高限额下注。
|
|
|
|
|
|
$amount = min($bettableGold, $maxBet);
|
|
|
|
|
|
$reason = trim($reason.' 买单活动进行中,本局按百家乐最高限额下注。');
|
2026-04-12 22:25:18 +08:00
|
|
|
|
} 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;
|
|
|
|
|
|
}
|
2026-04-02 10:21:20 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// AI 不可用时回退本地路单决策(保底逻辑)
|
2026-03-28 21:11:44 +08:00
|
|
|
|
$decisionSource = 'local';
|
2026-04-19 12:36:23 +08:00
|
|
|
|
// 买单活动期间,本地兜底策略同样不能返回观望。
|
|
|
|
|
|
$betType = $this->resolveLocalBetType($recentResults, ! $isInLossCoverWindow);
|
2026-04-02 10:21:20 +08:00
|
|
|
|
|
|
|
|
|
|
if ($betType !== 'pass') {
|
2026-04-12 22:42:32 +08:00
|
|
|
|
if ($isInLossCoverWindow) {
|
|
|
|
|
|
// 本地兜底策略命中买单活动时,同样优先按百家乐最高限额下注。
|
|
|
|
|
|
$amount = min($bettableGold, $maxBet);
|
|
|
|
|
|
$reason = '买单活动进行中,采用本地最高限额下注兜底策略。';
|
2026-04-12 22:25:18 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
$percent = rand(2, 5) / 100.0;
|
|
|
|
|
|
$amount = (int) round($bettableGold * $percent);
|
|
|
|
|
|
$amount = max($minBet, min($amount, $maxBet));
|
|
|
|
|
|
}
|
2026-03-28 20:35:43 +08:00
|
|
|
|
}
|
2026-03-26 11:49:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 21:11:44 +08:00
|
|
|
|
// 记录 AI 小班长本次决策日志
|
2026-04-02 10:21:20 +08:00
|
|
|
|
$labelMap = ['big' => '大', 'small' => '小', 'triple' => '豹子', 'pass' => '观望'];
|
2026-03-28 21:11:44 +08:00
|
|
|
|
Log::channel('daily')->info('AI小班长百家乐决策', [
|
|
|
|
|
|
'round_id' => $round->id,
|
2026-04-02 10:21:20 +08:00
|
|
|
|
'decision_source' => $decisionSource === 'ai' ? 'AI接口策略' : '本地路单兜底',
|
|
|
|
|
|
'action' => $labelMap[$betType] ?? $betType,
|
2026-03-28 21:11:44 +08:00
|
|
|
|
'bet_amount' => $amount,
|
2026-04-02 10:21:20 +08:00
|
|
|
|
'reason' => $reason,
|
2026-03-28 21:11:44 +08:00
|
|
|
|
'roadmap_20' => implode('→', array_map(fn (string $r) => $labelMap[$r] ?? $r, array_reverse($recentResults))),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2026-04-02 10:21:20 +08:00
|
|
|
|
if ($betType === 'pass') {
|
|
|
|
|
|
// 观望时仅广播,不执行真正的金币扣除下注逻辑
|
|
|
|
|
|
$this->broadcastPassMessage($user, $round->room_id ?? 1, $reason);
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 22:42:32 +08:00
|
|
|
|
// 买单活动期间允许为本次高额下注从银行调拨;非活动期间只检查当前手上金币是否够本次下注。
|
|
|
|
|
|
$isReadyToSpend = $isInLossCoverWindow
|
2026-04-12 22:25:18 +08:00
|
|
|
|
? $aiFinance->prepareAllInSpend($user, $amount)
|
|
|
|
|
|
: $aiFinance->prepareSpend($user, $amount);
|
|
|
|
|
|
if (! $isReadyToSpend) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 15:49:32 +08:00
|
|
|
|
// 二次校验,防止大模型接口调用太慢导致下注时该局已关闭
|
|
|
|
|
|
$round->refresh();
|
|
|
|
|
|
if (! $round->isBettingOpen()) {
|
|
|
|
|
|
Log::channel('daily')->warning("AI小班长百家乐下注拦截:由于AI接口思考耗时,这局#{$round->id}投注已截止。");
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 11:49:36 +08:00
|
|
|
|
// 幂等:同一局只能下一注
|
|
|
|
|
|
$existing = BaccaratBet::query()
|
|
|
|
|
|
->where('round_id', $round->id)
|
2026-04-12 22:25:18 +08:00
|
|
|
|
->where('user_id', $lockedUser->id)
|
2026-03-26 11:49:36 +08:00
|
|
|
|
->lockForUpdate()
|
|
|
|
|
|
->exists();
|
|
|
|
|
|
|
|
|
|
|
|
if ($existing) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 扣除金币
|
|
|
|
|
|
$currency->change(
|
2026-04-12 22:25:18 +08:00
|
|
|
|
$lockedUser,
|
2026-03-26 11:49:36 +08:00
|
|
|
|
'gold',
|
|
|
|
|
|
-$amount,
|
|
|
|
|
|
CurrencySource::BACCARAT_BET,
|
|
|
|
|
|
"AI小班长百家乐 #{$round->id} 押 ".match ($betType) {
|
|
|
|
|
|
'big' => '大', 'small' => '小', default => '豹子'
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 写入下注记录
|
2026-04-12 22:25:18 +08:00
|
|
|
|
$bet = BaccaratBet::create([
|
2026-03-26 11:49:36 +08:00
|
|
|
|
'round_id' => $round->id,
|
2026-04-12 22:25:18 +08:00
|
|
|
|
'user_id' => $lockedUser->id,
|
|
|
|
|
|
'loss_cover_event_id' => $lossCoverEvent?->id,
|
2026-03-26 11:49:36 +08:00
|
|
|
|
'bet_type' => $betType,
|
|
|
|
|
|
'amount' => $amount,
|
|
|
|
|
|
'status' => 'pending',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
// 命中买单活动的下注需要登记到活动聚合记录里,确保后续能正确补偿返还。
|
|
|
|
|
|
if ($lossCoverEvent) {
|
|
|
|
|
|
$lossCoverService->registerBet($bet);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 11:49:36 +08:00
|
|
|
|
// 更新局次汇总统计
|
|
|
|
|
|
$field = 'total_bet_'.$betType;
|
2026-03-28 18:06:48 +08:00
|
|
|
|
$countField = 'bet_count_'.$betType;
|
2026-03-26 11:49:36 +08:00
|
|
|
|
$round->increment($field, $amount);
|
2026-03-28 18:06:48 +08:00
|
|
|
|
$round->increment($countField);
|
2026-03-26 11:49:36 +08:00
|
|
|
|
$round->increment('bet_count');
|
2026-03-28 18:06:48 +08:00
|
|
|
|
|
|
|
|
|
|
// 广播各选项的最新押注人数(让前台看到 AI 下注的人数增长)
|
|
|
|
|
|
event(new \App\Events\BaccaratPoolUpdated($round));
|
2026-03-26 11:49:36 +08:00
|
|
|
|
});
|
2026-03-28 21:38:34 +08:00
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
// 下注完成后,若手上金币仍高于 100 万,则把超出的部分继续回存银行。
|
|
|
|
|
|
$aiFinance->bankExcessGold($user);
|
|
|
|
|
|
|
2026-03-28 21:38:34 +08:00
|
|
|
|
// 下注成功后,在聊天室发送一条普通聊天消息告知大家
|
|
|
|
|
|
$this->broadcastBetMessage($user, $round->room_id ?? 1, $betType, $amount, $decisionSource);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-02 10:21:20 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 广播 AI小班长的观望文案到聊天室
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param User $user AI小班长用户
|
|
|
|
|
|
* @param int $roomId 聊天室 ID
|
|
|
|
|
|
* @param string $reason 观望理由
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function broadcastPassMessage(User $user, int $roomId, string $reason): void
|
|
|
|
|
|
{
|
|
|
|
|
|
if (empty($reason)) {
|
|
|
|
|
|
$reason = '风大雨大,保本最大,这把我决定观望一下!';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$chatState = app(ChatStateService::class);
|
|
|
|
|
|
$content = "🌟 🎲 【百家乐】 <b>{$user->username}</b> 本局选择挂机观望:✨ <br/><span style='color:#666;'>[🤖 策略分析] {$reason}</span>";
|
|
|
|
|
|
|
|
|
|
|
|
$msg = [
|
|
|
|
|
|
'id' => $chatState->nextMessageId($roomId),
|
|
|
|
|
|
'room_id' => $roomId,
|
|
|
|
|
|
'from_user' => '系统传音',
|
|
|
|
|
|
'to_user' => '大家',
|
|
|
|
|
|
'content' => $content,
|
|
|
|
|
|
'is_secret' => false,
|
|
|
|
|
|
'font_color' => '#d97706',
|
|
|
|
|
|
'action' => '',
|
|
|
|
|
|
'sent_at' => now()->toDateTimeString(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
$chatState->pushMessage($roomId, $msg);
|
|
|
|
|
|
broadcast(new MessageSent($roomId, $msg));
|
|
|
|
|
|
SaveMessageJob::dispatch($msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 21:38:34 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 广播 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);
|
2026-03-28 21:44:50 +08:00
|
|
|
|
$labelMap = ['big' => '大', 'small' => '小', 'triple' => '豹子'];
|
2026-04-12 18:50:41 +08:00
|
|
|
|
$label = $labelMap[$betType] ?? $betType;
|
2026-03-28 21:38:34 +08:00
|
|
|
|
|
2026-04-12 18:50:41 +08:00
|
|
|
|
$sourceText = $decisionSource === 'ai' ? '🤖 经过深度算法预测,本局我看好:' : '📊 观察了下最近的路单,这把我觉得是:';
|
|
|
|
|
|
$content = "🌟 🎲 【百家乐】 <b>{$user->username}</b> 已下注:<span style='color:#1d4ed8;font-weight:bold;'>{$label}</span> (".number_format($amount)." 金币)<br/><span style='color:#666;'>{$sourceText} {$label}!</span>";
|
2026-03-28 21:38:34 +08:00
|
|
|
|
|
|
|
|
|
|
$msg = [
|
|
|
|
|
|
'id' => $chatState->nextMessageId($roomId),
|
|
|
|
|
|
'room_id' => $roomId,
|
2026-03-28 22:07:12 +08:00
|
|
|
|
'from_user' => '系统传音',
|
2026-03-28 21:38:34 +08:00
|
|
|
|
'to_user' => '大家',
|
|
|
|
|
|
'content' => $content,
|
|
|
|
|
|
'is_secret' => false,
|
2026-04-12 18:50:41 +08:00
|
|
|
|
'font_color' => '#8b5cf6',
|
|
|
|
|
|
'action' => '动态播报',
|
2026-03-28 21:38:34 +08:00
|
|
|
|
'sent_at' => now()->toDateTimeString(),
|
|
|
|
|
|
];
|
|
|
|
|
|
$chatState->pushMessage($roomId, $msg);
|
|
|
|
|
|
broadcast(new MessageSent($roomId, $msg));
|
|
|
|
|
|
SaveMessageJob::dispatch($msg);
|
2026-03-26 11:49:36 +08:00
|
|
|
|
}
|
2026-04-12 18:50:41 +08:00
|
|
|
|
|
2026-04-19 12:36:23 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 生成本地路单兜底下注方向。
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param array<int, string> $recentResults 最近已结算局次结果
|
|
|
|
|
|
* @param bool $allowPass 是否允许返回观望
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function resolveLocalBetType(array $recentResults, bool $allowPass): string
|
|
|
|
|
|
{
|
|
|
|
|
|
$bigCount = count(array_filter($recentResults, fn (string $result) => $result === 'big'));
|
|
|
|
|
|
$smallCount = count(array_filter($recentResults, fn (string $result) => $result === 'small'));
|
|
|
|
|
|
|
|
|
|
|
|
// 默认保留少量押豹子概率,维持 AI 小班长原本的下注风格。
|
|
|
|
|
|
if (rand(1, 100) <= 10) {
|
|
|
|
|
|
return 'triple';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($bigCount > $smallCount) {
|
|
|
|
|
|
return rand(1, 100) <= 70 ? 'big' : 'small';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($smallCount > $bigCount) {
|
|
|
|
|
|
return rand(1, 100) <= 70 ? 'small' : 'big';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 只有非买单活动时才允许空仓;活动期间必须至少押大或押小。
|
|
|
|
|
|
if ($allowPass && rand(0, 10) === 0) {
|
|
|
|
|
|
return 'pass';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return rand(0, 1) === 0 ? 'big' : 'small';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 18:50:41 +08:00
|
|
|
|
/**
|
2026-04-12 22:25:18 +08:00
|
|
|
|
* AI 资金管理逻辑:优先领取补偿,再按“超过 100 万才存款”的规则整理持仓。
|
2026-04-12 18:50:41 +08:00
|
|
|
|
*/
|
2026-04-12 22:25:18 +08:00
|
|
|
|
private function manageFinances(User $user, AiFinanceService $aiFinance): void
|
2026-04-12 18:50:41 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 1. 检查是否有“买单”活动补偿可领取 (jjb 较低时优先领取)
|
2026-04-12 21:42:55 +08:00
|
|
|
|
$lossCoverService = app(\App\Services\BaccaratLossCoverService::class);
|
|
|
|
|
|
$pendingEvents = \App\Models\BaccaratLossCoverEvent::where('status', 'claimable')->get();
|
|
|
|
|
|
foreach ($pendingEvents as $event) {
|
|
|
|
|
|
$record = \App\Models\BaccaratLossCoverRecord::where('event_id', $event->id)
|
|
|
|
|
|
->where('user_id', $user->id)
|
|
|
|
|
|
->where('claim_status', 'pending')
|
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
|
|
if ($record && $record->compensation_amount > 0) {
|
|
|
|
|
|
$lossCoverService->claim($event, $user);
|
|
|
|
|
|
Log::info("AI小班长自动领取活动补偿: Event #{$event->id}, Amount: {$record->compensation_amount}");
|
2026-04-12 18:50:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-12 22:25:18 +08:00
|
|
|
|
$aiFinance->rebalanceHoldings($user);
|
2026-04-12 18:50:41 +08:00
|
|
|
|
}
|
2026-03-26 11:49:36 +08:00
|
|
|
|
}
|