160 lines
4.5 KiB
PHP
160 lines
4.5 KiB
PHP
<?php
|
|
|
|
/**
|
|
* 文件功能:赛马竞猜局次模型
|
|
*
|
|
* 代表一场赛马比赛,包含参赛马匹信息、场次状态、
|
|
* 注池统计以及赛果信息。提供当前场次查询和赔率计算方法。
|
|
*
|
|
* @author ChatRoom Laravel
|
|
*
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class HorseRace extends Model
|
|
{
|
|
protected $fillable = [
|
|
'status',
|
|
'bet_opens_at',
|
|
'bet_closes_at',
|
|
'race_starts_at',
|
|
'race_ends_at',
|
|
'horses',
|
|
'winner_horse_id',
|
|
'total_bets',
|
|
'total_pool',
|
|
'settled_at',
|
|
];
|
|
|
|
/**
|
|
* 属性类型转换。
|
|
*/
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'bet_opens_at' => '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<int, array{id: int, name: string, emoji: string}>
|
|
*/
|
|
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<int, int|float> $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<int, int|float> $horseBetAmounts 各马匹的注额数组 [horse_id => amount]
|
|
* @return array<int, float> 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;
|
|
}
|
|
}
|