'datetime', 'bet_closes_at' => 'datetime', 'race_starts_at' => 'datetime', 'race_ends_at' => 'datetime', 'settled_at' => 'datetime', 'horses' => 'array', 'winner_horse_id' => 'integer', 'total_bets' => 'integer', 'total_pool' => 'integer', ]; } /** * 本场所有下注记录。 */ public function bets(): HasMany { return $this->hasMany(HorseBet::class, 'race_id'); } /** * 判断当前是否在押注时间窗口内。 */ public function isBettingOpen(): bool { return $this->status === 'betting' && now()->between($this->bet_opens_at, $this->bet_closes_at); } /** * 查询当前正在进行的场次(状态为 betting 且押注未截止)。 */ public static function currentRace(): ?static { return static::query() ->whereIn('status', ['betting', 'running']) ->latest() ->first(); } /** * 生成参赛马匹列表(根据马匹数量随机选名)。 * * @param int $count 马匹数量 * @return array */ public static function generateHorses(int $count): array { // 可用马匹名池(原版竞技风格) $namePool = [ ['name' => '赤兔', 'emoji' => '🐎'], ['name' => '乌骓', 'emoji' => '🐴'], ['name' => '的卢', 'emoji' => '🎠'], ['name' => '绝影', 'emoji' => '🦄'], ['name' => '紫骍', 'emoji' => '🐎'], ['name' => '爪黄', 'emoji' => '🐴'], ['name' => '汗血', 'emoji' => '🎠'], ['name' => '飞电', 'emoji' => '⚡'], ]; // 随机打乱并取前 N 个 shuffle($namePool); $selected = array_slice($namePool, 0, $count); $horses = []; foreach ($selected as $i => $horse) { $horses[] = [ 'id' => $i + 1, 'name' => $horse['name'], 'emoji' => $horse['emoji'], ]; } return $horses; } /** * 计算本场可派奖总池。 * * 可派奖池 = 系统初始化资金池 + 玩家总下注 - 抽水, * 且至少不低于中奖马匹总下注,避免出现“押中反亏”的体验。 * * @param array $horseBetAmounts */ public static function calcDistributablePool(array $horseBetAmounts, int $housePercent, int $seedPool = 0, int $winnerPool = 0): float { $totalPool = array_sum($horseBetAmounts); $netPlayerPool = $totalPool * (1 - $housePercent / 100); return max($netPlayerPool + max(0, $seedPool), $winnerPool); } /** * 根据注池计算各马匹实时赔率(含系统初始化资金池)。 * * @param array $horseBetAmounts 各马匹的注额数组 [horse_id => amount] * @return array horse_id => 赔率(含本金) */ public static function calcOdds(array $horseBetAmounts, int $housePercent, int $seedPool = 0): array { $totalPool = array_sum($horseBetAmounts); if ($totalPool <= 0) { return array_map(fn () => 1.0, $horseBetAmounts); } $distributablePool = static::calcDistributablePool($horseBetAmounts, $housePercent, $seedPool); $odds = []; foreach ($horseBetAmounts as $horseId => $amount) { if ($amount <= 0) { $odds[$horseId] = round($distributablePool, 2); } else { $odds[$horseId] = round($distributablePool / $amount, 2); } } return $odds; } }