Files

175 lines
5.1 KiB
PHP
Raw Permalink Normal View History

<?php
/**
* 文件功能:赛马跑马动画广播 + 结算队列任务
*
* 押注截止后触发,模拟跑马进度并实时广播,
* 跑完后确定获胜者并调度结算任务。
*
* @author ChatRoom Laravel
*
* @version 1.0.0
*/
namespace App\Jobs;
use App\Events\HorseRaceProgress;
use App\Events\MessageSent;
use App\Models\GameConfig;
use App\Models\HorseBet;
use App\Models\HorseRace;
use App\Services\ChatStateService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class RunHorseRaceJob implements ShouldQueue
{
use Queueable;
/**
* 最大重试次数。
*/
public int $tries = 2;
/**
* @param HorseRace $race 要运行的场次
*/
public function __construct(
public readonly HorseRace $race,
) {}
/**
* 执行跑马过程并广播进度,最后触发结算。
*/
public function handle(ChatStateService $chatState): void
{
$race = $this->race->fresh();
// 防止重复执行
if (! $race || $race->status !== 'betting') {
return;
}
// 乐观锁:CAS 改状态为 running
$updated = HorseRace::query()
->where('id', $race->id)
->where('status', 'betting')
->update([
'status' => 'running',
'race_starts_at' => now(),
]);
if (! $updated) {
return;
}
$race->refresh();
// 公屏通知:跑马开始
$horseList = implode(' ', array_map(
fn ($h) => "{$h['emoji']}{$h['name']}",
$race->horses ?? []
));
2026-04-11 23:46:05 +08:00
$startMsg = [
'id' => $chatState->nextMessageId(1),
'room_id' => 1,
'from_user' => '系统传音',
'to_user' => '大家',
2026-04-11 23:46:05 +08:00
'content' => "🏇 【赛马】第 #{$race->id} 场押注截止!马匹已进入跑道,比赛开始!参赛阵容:{$horseList}",
'is_secret' => false,
'font_color' => '#336699',
'action' => '大声宣告',
'sent_at' => now()->toDateTimeString(),
];
$chatState->pushMessage(1, $startMsg);
broadcast(new MessageSent(1, $startMsg));
SaveMessageJob::dispatch($startMsg);
$config = GameConfig::forGame('horse_racing')?->params ?? [];
$raceDuration = (int) ($config['race_duration'] ?? 30);
2026-04-11 16:27:04 +08:00
$seedPool = (int) ($config['seed_pool'] ?? 0);
$horses = $race->horses ?? [];
$horseCount = count($horses);
// 初始化各马匹进度(0~100),每步随机增量
$positions = array_fill_keys(array_column($horses, 'id'), 0);
$speeds = [];
foreach ($horses as $horse) {
// 基础速度:随机值,确保比赛有悬念(均值接近 race_duration 步完成)
$speeds[$horse['id']] = random_int(2, 5);
}
// 跑马循环:模拟进度广播(此 job 为同步阻塞广播,每步 sleep 1 秒)
$step = 0;
$maxSteps = $raceDuration;
$winnerId = null;
while ($step < $maxSteps && $winnerId === null) {
$step++;
foreach ($horses as $horse) {
$horseId = $horse['id'];
// 随机冲刺(小概率加速)
$boost = random_int(0, 10) >= 9 ? random_int(5, 15) : 0;
$positions[$horseId] = min(100, $positions[$horseId] + $speeds[$horseId] + $boost);
}
// 检查是否有马到达终点
$finishedHorses = array_filter($positions, fn ($p) => $p >= 100);
$finished = ! empty($finishedHorses);
if ($finished) {
// 取进度最高的马为冠军(若并列取 id 最小的)
arsort($finishedHorses);
$winnerId = (int) array_key_first($finishedHorses);
}
// 广播当前帧进度
broadcast(new HorseRaceProgress($race->id, $positions, $finished, $winnerId ?? $this->leadingHorse($positions)));
if ($finished) {
break;
}
sleep(1);
}
// 如果时间到还没分出胜负,取最高进度的马为赢家
if ($winnerId === null) {
arsort($positions);
$winnerId = (int) array_key_first($positions);
}
// 更新场次记录
$race->update([
'race_ends_at' => now(),
'winner_horse_id' => $winnerId,
]);
// 计算注池统计
$totalBets = HorseBet::query()->where('race_id', $race->id)->count();
2026-04-11 16:27:04 +08:00
$totalPool = $seedPool + HorseBet::query()->where('race_id', $race->id)->sum('amount');
$race->update([
'total_bets' => $totalBets,
'total_pool' => $totalPool,
]);
// 触发结算任务
CloseHorseRaceJob::dispatch($race->fresh());
}
/**
* 获取当前领跑马匹 ID(进度最高)。
*
* @param array<int, int> $positions
*/
private function leadingHorse(array $positions): int
{
arsort($positions);
return (int) array_key_first($positions);
}
}