Files
chatroom/app/Jobs/RunHorseRaceJob.php
2026-04-11 23:46:05 +08:00

175 lines
5.1 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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 ?? []
));
$startMsg = [
'id' => $chatState->nextMessageId(1),
'room_id' => 1,
'from_user' => '系统传音',
'to_user' => '大家',
'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);
$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();
$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);
}
}