feat: 实现 AI 钓鱼与百家乐游戏的参与逻辑,并支持后台面板配置开关

This commit is contained in:
2026-03-26 11:49:36 +08:00
parent 532dc20a2d
commit a68e82107e
9 changed files with 422 additions and 78 deletions
+150
View File
@@ -0,0 +1,150 @@
<?php
/**
* 文件功能:AI小班长自动参与百家乐下注
*
* 在每局百家乐开启时延迟调度执行:
* 1. 检查是否存在连输休息惩罚(1小时)
* 2. 检查可用金币,确保留存底金
* 3. 获取近期路单进行简单决策
* 4. 提交下注
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Jobs;
use App\Enums\CurrencySource;
use App\Models\BaccaratBet;
use App\Models\BaccaratRound;
use App\Models\GameConfig;
use App\Models\Sysparam;
use App\Models\User;
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;
use Illuminate\Support\Facades\Redis;
class AiBaccaratBetJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public readonly BaccaratRound $round,
) {}
public function handle(UserCurrencyService $currency): void
{
// 1. 检查总开关与游戏开关
if (Sysparam::getValue('chatbot_enabled', '0') !== '1' || ! GameConfig::isEnabled('baccarat')) {
return;
}
$round = $this->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. 决策逻辑:简单分析近期路单
// 取最近 10 局
$recentResults = BaccaratRound::query()
->where('status', 'settled')
->orderByDesc('id')
->limit(10)
->pluck('result')
->toArray();
$bigCount = count(array_filter($recentResults, fn ($r) => $r === 'big'));
$smallCount = count(array_filter($recentResults, fn ($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';
}
// 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;
$round->increment($field, $amount);
$round->increment('bet_count');
});
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
/**
* 文件功能:AI小班长钓鱼动作异步任务
*
* 模拟 AI 抛竿等待一定时间后收竿的过程。
* 收竿时自动调用 FishingService 结算结果并播报。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Jobs;
use App\Models\User;
use App\Services\FishingService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
class AiFishingJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @param User $aiUser AI小班长
* @param int $roomId 此事件广播的房间ID
*/
public function __construct(
public readonly User $aiUser,
public readonly int $roomId,
) {}
/**
* 任务执行
*/
public function handle(FishingService $fishingService): void
{
// 通过业务服务计算钓鱼结果并广播,标记 isAi 为 true
$fishingService->processCatch($this->aiUser, $this->roomId, true);
}
}
+25
View File
@@ -25,6 +25,7 @@ use App\Services\UserCurrencyService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Redis;
class CloseBaccaratRoundJob implements ShouldQueue
{
@@ -103,6 +104,10 @@ class CloseBaccaratRoundJob implements ShouldQueue
$bet->update(['status' => 'lost', 'payout' => 0]);
$losers[] = "{$username}-{$bet->amount}";
if ($username === 'AI小班长') {
$this->handleAiLoseStreak();
}
continue;
}
@@ -121,9 +126,17 @@ class CloseBaccaratRoundJob implements ShouldQueue
);
$totalPayout += $payout;
$winners[] = "{$username}+".number_format($payout);
if ($username === 'AI小班长') {
Redis::del('ai_baccarat_lose_streak'); // 赢了清空连输
}
} else {
$bet->update(['status' => 'lost', 'payout' => 0]);
$losers[] = "{$username}-".number_format($bet->amount);
if ($username === 'AI小班长') {
$this->handleAiLoseStreak();
}
}
}
});
@@ -149,6 +162,18 @@ class CloseBaccaratRoundJob implements ShouldQueue
$this->pushResultMessage($round, $chatState, $winners, $losers);
}
/**
* 处理 AI 小班长连输逻辑
*/
private function handleAiLoseStreak(): void
{
$streak = Redis::incr('ai_baccarat_lose_streak');
if ($streak >= 3) {
Redis::setex('ai_baccarat_timeout', 3600, 'timeout'); // 连输三次,停赛1小时
Redis::del('ai_baccarat_lose_streak');
}
}
/**
* 向公屏发送开奖结果系统消息(含各用户输赢情况)。
*
+7
View File
@@ -89,6 +89,13 @@ class OpenBaccaratRoundJob implements ShouldQueue
broadcast(new MessageSent(1, $msg));
SaveMessageJob::dispatch($msg);
// 如果允许 AI 参与,延迟一定时间派发 AI 下注任务
$baccaratEnabled = \App\Models\Sysparam::getValue('chatbot_baccarat_enabled', '0') === '1';
if (\App\Models\Sysparam::getValue('chatbot_enabled', '0') === '1' && $baccaratEnabled) {
$aiDelay = rand(10, min(40, max(10, $betSeconds - 5))); // 随机在 10 ~ (倒数前5秒) 之间下注
\App\Jobs\AiBaccaratBetJob::dispatch($round)->delay(now()->addSeconds($aiDelay));
}
// 在下注截止时安排结算任务
CloseBaccaratRoundJob::dispatch($round)->delay($closesAt);
}