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); $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 = HorseBet::query()->where('race_id', $race->id)->sum('amount'); $race->update([ 'total_bets' => $totalBets, 'total_pool' => $totalPool, ]); // 触发结算任务 CloseHorseRaceJob::dispatch($race->fresh()); } /** * 获取当前领跑马匹 ID(进度最高)。 * * @param array $positions */ private function leadingHorse(array $positions): int { arsort($positions); return (int) array_key_first($positions); } }