|null */ private static ?array $cache = null; public static function clearCache(): void { self::$cache = null; } /** * @return list */ public static function defaultRows(): array { $out = []; for ($s = 1; $s <= 10; $s++) { $out[] = [ 'streak' => $s, 'odds_factor' => $s, 'is_jackpot' => $s === 10, ]; } return $out; } /** * @param mixed $raw * * @return list */ public static function parseFromConfigValue($raw): array { if (!is_string($raw) || trim($raw) === '') { return self::defaultRows(); } $decoded = json_decode($raw, true); if (!is_array($decoded)) { return self::defaultRows(); } $list = $decoded['rows'] ?? $decoded; if (!is_array($list)) { return self::defaultRows(); } $byStreak = []; foreach ($list as $row) { if (!is_array($row)) { continue; } $streak = isset($row['streak']) && is_numeric($row['streak']) ? (int) $row['streak'] : 0; if ($streak < 1 || $streak > 10) { continue; } $factor = isset($row['odds_factor']) && is_numeric($row['odds_factor']) ? (int) $row['odds_factor'] : $streak; if ($factor < 1) { $factor = 1; } $jack = !empty($row['is_jackpot']); $byStreak[$streak] = [ 'streak' => $streak, 'odds_factor' => $factor, 'is_jackpot' => $jack, ]; } $out = []; for ($s = 1; $s <= 10; $s++) { $out[] = $byStreak[$s] ?? [ 'streak' => $s, 'odds_factor' => $s, 'is_jackpot' => $s === 10, ]; } return $out; } /** * 从库加载并缓存 * * @return list */ public static function loadRows(): array { if (self::$cache !== null) { return self::$cache; } $row = GameHotDataRedis::gameConfigRow(self::CONFIG_KEY); self::$cache = self::parseFromConfigValue($row['config_value'] ?? null); return self::$cache; } /** * 当前玩家本局适用赔率(非全表):按 current_streak 解析下一注中奖将使用的档位。 * * @return array{current_streak: int, streak_level: int, odds_factor: int, is_jackpot: bool} */ public static function playerBetOddsForCurrentStreak(int $currentStreak): array { if ($currentStreak < 0) { $currentStreak = 0; } $row = self::rowForStreakAtBet($currentStreak); return [ 'current_streak' => $currentStreak, 'streak_level' => self::levelFromStreakAtBet($currentStreak), 'odds_factor' => (int) ($row['odds_factor'] ?? 1), 'is_jackpot' => ($row['is_jackpot'] ?? false) === true, ]; } /** * streak_at_bet 为下注时快照(0 表示尚未连胜);档位取 min(streak_at_bet+1, 10)。 */ public static function levelFromStreakAtBet(int $streakAtBet): int { $level = $streakAtBet + 1; if ($level < 1) { $level = 1; } if ($level > 10) { $level = 10; } return $level; } /** * @return array{streak: int, odds_factor: int, is_jackpot: bool} */ public static function rowForStreakAtBet(int $streakAtBet): array { $level = self::levelFromStreakAtBet($streakAtBet); foreach (self::loadRows() as $row) { if ((int) ($row['streak'] ?? 0) === $level) { return $row; } } return [ 'streak' => $level, 'odds_factor' => $level, 'is_jackpot' => $level === 10, ]; } public static function isJackpotForStreakAtBet(int $streakAtBet): bool { return self::rowForStreakAtBet($streakAtBet)['is_jackpot'] === true; } /** * 返回该注单适用的「赔率乘数」字符串(= 配置档位的 odds_factor),供 bcmul(total_amount, ..., 2)。 */ public static function totalOddsMultiplierForStreakAtBet(int $streakAtBet): string { $factor = (int) self::rowForStreakAtBet($streakAtBet)['odds_factor']; if ($factor < 1) { $factor = 1; } return bcadd((string) $factor, '0', 2); } /** * @param list $rows */ public static function encodeForDb(array $rows): string { $normalized = []; for ($s = 1; $s <= 10; $s++) { $found = null; foreach ($rows as $r) { if (!is_array($r)) { continue; } $st = isset($r['streak']) && is_numeric($r['streak']) ? (int) $r['streak'] : 0; if ($st === $s) { $found = $r; break; } } if ($found === null) { $normalized[] = [ 'streak' => $s, 'odds_factor' => $s, 'is_jackpot' => $s === 10, ]; } else { $f = isset($found['odds_factor']) && is_numeric($found['odds_factor']) ? (int) $found['odds_factor'] : $s; if ($f < 1) { $f = 1; } $normalized[] = [ 'streak' => $s, 'odds_factor' => $f, 'is_jackpot' => !empty($found['is_jackpot']), ]; } } $json = json_encode(['rows' => $normalized], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); if ($json === false) { return '{"rows":[]}'; } return $json; } }