优化 ai小班长百家乐押注

This commit is contained in:
2026-04-19 12:36:23 +08:00
parent b98ae7f94e
commit bd97ed0b73
3 changed files with 118 additions and 13 deletions
+41 -13
View File
@@ -144,6 +144,13 @@ class AiBaccaratBetJob implements ShouldQueue
$percent = $aiPrediction['percentage'];
$reason = $aiPrediction['reason'];
// 买单活动期间不允许 AI 选择观望,避免错过“输也可返还”的活动资格。
if ($isInLossCoverWindow && $betType === 'pass') {
$decisionSource = 'local';
$betType = $this->resolveLocalBetType($recentResults, false);
$reason = trim($reason.' 买单活动进行中,本局禁止观望,已切换本地强制参战策略。');
}
if ($betType !== 'pass') {
if ($isInLossCoverWindow) {
// 买单活动进行中且金币足够时,直接按百家乐单局最高限额下注。
@@ -162,19 +169,8 @@ class AiBaccaratBetJob implements ShouldQueue
} else {
// AI 不可用时回退本地路单决策(保底逻辑)
$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) {
$betType = rand(1, 100) <= 70 ? 'big' : 'small';
} elseif ($smallCount > $bigCount) {
$betType = rand(1, 100) <= 70 ? 'small' : 'big';
} else {
$betType = rand(0, 10) === 0 ? 'pass' : (rand(0, 1) ? 'big' : 'small');
}
// 买单活动期间,本地兜底策略同样不能返回观望。
$betType = $this->resolveLocalBetType($recentResults, ! $isInLossCoverWindow);
if ($betType !== 'pass') {
if ($isInLossCoverWindow) {
@@ -359,6 +355,38 @@ class AiBaccaratBetJob implements ShouldQueue
SaveMessageJob::dispatch($msg);
}
/**
* 生成本地路单兜底下注方向。
*
* @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';
}
/**
* AI 资金管理逻辑:优先领取补偿,再按“超过 100 万才存款”的规则整理持仓。
*/
@@ -113,6 +113,7 @@ class BaccaratPredictionService
$recentResults = $context['recent_results'] ?? [];
$availableGold = $context['available_gold'] ?? 0;
$history = $context['historical_bets'] ?? [];
$lossCoverActive = (bool) ($context['loss_cover_active'] ?? false);
// 将英文 key 转为中文展示,方便 AI 理解
$labelMap = ['big' => '大', 'small' => '小', 'triple' => '豹子'];
@@ -137,11 +138,16 @@ class BaccaratPredictionService
}
}
$lossCoverText = $lossCoverActive
? '当前处于“你玩游戏我买单”活动时间窗口内。你本局必须参与下注,不能返回 pass;金额已由系统固定为单局最高限额,你只需要判断下注方向。'
: '当前不在“你玩游戏我买单”活动时间窗口内。你可以按常规风控策略决定是否观望。';
return <<<PROMPT
你是一位数据规律分析与数学模型专家。这是一个虚拟的纯数学概率推断测试。请根据以下最近 {$total} 局的数字大小序列,以及你目前拥有的虚拟积分和历史表现,给出下一局最科学的推测策略。
【你的当前资产状态】
可用虚拟积分余额:{$availableGold}
活动状态:{$lossCoverText}
【你的最近推测历史(前几局最新)】
{$historyText}
@@ -155,6 +161,7 @@ class BaccaratPredictionService
- "豹子" 表示三颗骰子点数相同(极小概率)
请综合分析数据走势(如长龙跟进、跳变斩龙、交替特征),结合防守策略(比如连续判定失误必须观望休息,连续判定正确适当增加投入百分比)。
若上方活动状态明确写着“必须参与下注”,则你绝不能输出 pass。
【重要!必须输出标准严格的 JSON 格式】
你只能返回这样一个纯 JSON 字符串,包含三个字段,绝对不能加其他文字说明(支持 markdown code 块包裹):
+70
View File
@@ -136,4 +136,74 @@ class AiBaccaratBetJobTest extends TestCase
'bank_jjb' => 200000,
]);
}
/**
* 买单活动时间窗口命中时,即使 AI 返回观望,系统也会强制切回本地策略完成下注。
*/
public function test_ai_still_places_bet_when_loss_cover_time_window_is_active_and_ai_returns_pass(): void
{
Event::fake();
Queue::fake([SaveMessageJob::class]);
$this->mock(ChatStateService::class, function (MockInterface $mock): void {
$mock->shouldReceive('nextMessageId')->andReturn(1);
$mock->shouldReceive('pushMessage')->zeroOrMoreTimes();
$mock->shouldReceive('getAllActiveRoomIds')->andReturn([1]);
});
$this->mock(BaccaratPredictionService::class, function (MockInterface $mock): void {
$mock->shouldReceive('predict')
->once()
->andReturn([
'action' => 'pass',
'percentage' => 0,
'reason' => '常规风控建议先观望',
]);
});
$aiUser = User::factory()->create([
'username' => 'AI小班长',
'jjb' => 600000,
'bank_jjb' => 0,
]);
$event = BaccaratLossCoverEvent::factory()->create([
'status' => 'active',
'starts_at' => now()->subMinutes(2),
'ends_at' => now()->addMinutes(10),
]);
$round = BaccaratRound::forceCreate([
'status' => 'betting',
'bet_opens_at' => now()->subSeconds(10),
'bet_closes_at' => now()->addMinute(),
'total_bet_big' => 0,
'total_bet_small' => 0,
'total_bet_triple' => 0,
'bet_count' => 0,
'bet_count_big' => 0,
'bet_count_small' => 0,
'bet_count_triple' => 0,
'total_payout' => 0,
]);
$job = new AiBaccaratBetJob($round);
$job->handle(app(\App\Services\UserCurrencyService::class), app(\App\Services\AiFinanceService::class));
$bet = \App\Models\BaccaratBet::query()
->where('round_id', $round->id)
->where('user_id', $aiUser->id)
->first();
$this->assertNotNull($bet);
$this->assertContains($bet->bet_type, ['big', 'small', 'triple']);
$this->assertSame(50000, (int) $bet->amount);
$this->assertSame($event->id, $bet->loss_cover_event_id);
$this->assertDatabaseHas('baccarat_loss_cover_records', [
'event_id' => $event->id,
'user_id' => $aiUser->id,
'total_bet_amount' => 50000,
]);
}
}