Files
pk10/App/Core/PK10Algorithm.php
T
li 4a94fe36cf feat: 重构代码架构 + 新增报表/跟单/输赢统计功能
- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController
- 新增 Service 层: TransactionService, ReportService, FollowPlanService
- pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll)
- 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan)
- 后台新增跟单计划管理 + 用户输赢明细统计
- 封盘状态显示倒计时 (x:xx)
- 音效仅在开奖弹窗打开时播放
- 路由按模块分组整理
- autoload 支持 App\Services 命名空间
2026-03-27 18:41:06 +08:00

212 lines
8.1 KiB
PHP

<?php
namespace App\Core;
class PK10Algorithm implements GameAlgorithmInterface {
/**
* 生成随机开奖结果 (1-10的排列)
*/
public static function generateResult(): array {
$cars = range(1, 10);
shuffle($cars);
return $cars; // index 0=冠军, 1=亚军, ..., 9=第十名
}
/**
* 带放水机制的开奖结果生成
*
* 算法策略:
* - targetProfitRate > 0 时:选择利润最接近「总投注额 × 目标盈利率」的结果
* - targetProfitRate == 0 时:选择平台利润最高的结果(原逻辑)
*
* @param array $bets 当期所有投注 [{bet_type, bet_target, amount, odds}, ...]
* @param array $waterConfig [{bet_type => win_rate_pct}, ...]
* 当前版本中 waterConfig 的 win_rate_pct 未直接参与结果选择,
* 因为开奖结果是全局排列,无法单独控制某个投注类型的胜率。
* 保留此参数供未来扩展(如按类型加权评分、分类型概率偏移等)。
* @param int $attempts 最大尝试次数
* @param float $targetProfitRate 目标盈利率(百分比,如 5.0 表示 5%)
*/
public static function generateControlledResult(array $bets, array $waterConfig, int $attempts = 100, float $targetProfitRate = 0): array {
if (empty($bets)) {
return self::generateResult();
}
// 计算目标利润(仅当 targetProfitRate > 0 时生效)
$targetProfit = 0;
if ($targetProfitRate > 0) {
$totalBet = 0;
foreach ($bets as $bet) {
$totalBet += (float)$bet['amount'];
}
$targetProfit = $totalBet * ($targetProfitRate / 100);
}
$bestResult = null;
$bestProfit = PHP_INT_MIN;
$bestDistance = PHP_FLOAT_MAX;
for ($i = 0; $i < $attempts; $i++) {
$result = self::generateResult();
$profit = self::calculatePlatformProfit($result, $bets);
if ($targetProfitRate > 0) {
// 目标盈利率模式:选择利润最接近目标值的结果
$distance = abs($profit - $targetProfit);
if ($distance < $bestDistance) {
$bestDistance = $distance;
$bestProfit = $profit;
$bestResult = $result;
}
} else {
// 原逻辑:选择平台利润最高的结果
if ($profit > $bestProfit) {
$bestProfit = $profit;
$bestResult = $result;
}
}
}
return $bestResult;
}
/**
* 计算某个开奖结果下平台的利润
*/
public static function calculatePlatformProfit(array $result, array $bets): float {
$totalBet = 0;
$totalPayout = 0;
foreach ($bets as $bet) {
$amount = (float)$bet['amount'];
$totalBet += $amount;
if (self::checkWin($result, $bet['bet_type'], $bet['bet_target'])) {
$totalPayout += $amount + $amount * (float)$bet['odds'];
}
}
return $totalBet - $totalPayout;
}
/**
* 核心中奖判定
* @param array $result 开奖排名 [冠军车号, 亚军车号, ..., 第十名车号]
* @param string $betType 投注类型: rank/bs/oe/dt/sum/sum_bs
* @param string $betTarget 投注目标: rank1_5/rank1_big/dt1_dragon/sum_11/sum_big...
*/
public static function checkWin(array $result, string $betType, string $betTarget): bool {
switch ($betType) {
case 'rank': return self::checkRank($result, $betTarget);
case 'bs': return self::checkBigSmall($result, $betTarget);
case 'oe': return self::checkOddEven($result, $betTarget);
case 'dt': return self::checkDragonTiger($result, $betTarget);
case 'sum': return self::checkSum($result, $betTarget);
case 'sum_bs': return self::checkSumBigSmall($result, $betTarget);
default: return false;
}
}
// 名次投注: rank{N}_{carNo} 如 rank1_5 = 冠军是5号车
private static function checkRank(array $r, string $target): bool {
if (!preg_match('/^rank(\d+)_(\d+)$/', $target, $m)) return false;
$pos = (int)$m[1] - 1; // 0-indexed
$car = (int)$m[2];
return isset($r[$pos]) && $r[$pos] === $car;
}
// 大小: rank{N}_big/small, 车号>=6为大, <=5为小
private static function checkBigSmall(array $r, string $target): bool {
if (!preg_match('/^rank(\d+)_(big|small)$/', $target, $m)) return false;
$pos = (int)$m[1] - 1;
if (!isset($r[$pos])) return false;
$car = $r[$pos];
return $m[2] === 'big' ? $car >= 6 : $car <= 5;
}
// 单双: rank{N}_odd/even
private static function checkOddEven(array $r, string $target): bool {
if (!preg_match('/^rank(\d+)_(odd|even)$/', $target, $m)) return false;
$pos = (int)$m[1] - 1;
if (!isset($r[$pos])) return false;
$car = $r[$pos];
return $m[2] === 'odd' ? ($car % 2 === 1) : ($car % 2 === 0);
}
// 龙虎: dt{N}_dragon/tiger, N=1-5, 对应 1vs10, 2vs9, 3vs8, 4vs7, 5vs6
private static function checkDragonTiger(array $r, string $target): bool {
if (!preg_match('/^dt(\d)_(dragon|tiger)$/', $target, $m)) return false;
$pair = (int)$m[1]; // 1-5
$frontPos = $pair - 1; // 0,1,2,3,4
$backPos = 10 - $pair; // 9,8,7,6,5
if (!isset($r[$frontPos], $r[$backPos])) return false;
$front = $r[$frontPos];
$back = $r[$backPos];
if ($front === $back) return false; // 和局(PK10不会出现)
return $m[2] === 'dragon' ? $front > $back : $front < $back;
}
// 冠亚和值: sum_{3-19}
private static function checkSum(array $r, string $target): bool {
if (!preg_match('/^sum_(\d+)$/', $target, $m)) return false;
$sumVal = $r[0] + $r[1]; // 冠军+亚军
return $sumVal === (int)$m[1];
}
// 冠亚和大小单双: sum_big/small/odd/even, 和>=12为大, <=11为小
private static function checkSumBigSmall(array $r, string $target): bool {
$sumVal = $r[0] + $r[1];
switch ($target) {
case 'sum_big': return $sumVal >= 12;
case 'sum_small': return $sumVal <= 11;
case 'sum_odd': return $sumVal % 2 === 1;
case 'sum_even': return $sumVal % 2 === 0;
default: return false;
}
}
/**
* 获取名次中文/英文名称
*/
public static function getRankName(int $pos): string {
$names = [1=>'champion',2=>'runner_up',3=>'rank_n',4=>'rank_n',5=>'rank_n',
6=>'rank_n',7=>'rank_n',8=>'rank_n',9=>'rank_n',10=>'rank_n'];
return $names[$pos] ?? 'rank_n';
}
/**
* 龙虎对应关系
*/
public static function getDragonTigerPairs(): array {
return [
1 => [1, 10], // 第1名 vs 第10名
2 => [2, 9],
3 => [3, 8],
4 => [4, 7],
5 => [5, 6],
];
}
/**
* 期号生成
*/
public static function generatePeriodNumber(int $gameId): string {
return 'PK' . $gameId . date('Ymd') . str_pad(mt_rand(1, 9999), 4, '0', STR_PAD_LEFT);
}
public static function getResultTable(): ?string { return 'pk10_results'; }
public static function formatResultForStorage(array $result, int $periodId): array {
return [
'period_id' => $periodId,
'rank_1' => $result[0], 'rank_2' => $result[1], 'rank_3' => $result[2],
'rank_4' => $result[3], 'rank_5' => $result[4], 'rank_6' => $result[5],
'rank_7' => $result[6], 'rank_8' => $result[7], 'rank_9' => $result[8],
'rank_10' => $result[9], 'champion_sum' => $result[0] + $result[1],
];
}
public static function parseResultFromDb(array $row): array {
$r = [];
for ($i = 1; $i <= 10; $i++) $r[] = (int)($row['rank_' . $i] ?? 0);
return $r;
}
}