121 lines
3.9 KiB
PHP
121 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace app\common\service;
|
|
|
|
use support\think\Db;
|
|
use Throwable;
|
|
use Webman\Push\Api;
|
|
|
|
final class GameLiveService
|
|
{
|
|
private const BASE_ODDS = 33;
|
|
private const CHANNEL = 'game-live';
|
|
private const EVENT = 'bet-updated';
|
|
|
|
public static function buildSnapshot(?int $recordId = null): array
|
|
{
|
|
$record = self::resolveRecord($recordId);
|
|
if (!$record) {
|
|
return [
|
|
'record' => null,
|
|
'bets' => [],
|
|
'candidate_numbers' => [],
|
|
'ai_default_number' => null,
|
|
'server_time' => time(),
|
|
];
|
|
}
|
|
|
|
$bets = Db::name('bet_order')
|
|
->where('period_id', (int) $record['id'])
|
|
->order('id', 'desc')
|
|
->limit(200)
|
|
->select()
|
|
->toArray();
|
|
|
|
$candidates = [];
|
|
$bestNumber = null;
|
|
$bestLoss = null;
|
|
for ($n = 1; $n <= 36; $n++) {
|
|
$loss = self::estimateLossForNumber($bets, $n);
|
|
$candidates[] = [
|
|
'number' => $n,
|
|
'estimated_loss' => $loss,
|
|
];
|
|
if ($bestLoss === null || bccomp((string) $loss, (string) $bestLoss, 4) < 0) {
|
|
$bestLoss = $loss;
|
|
$bestNumber = $n;
|
|
}
|
|
}
|
|
|
|
return [
|
|
'record' => $record,
|
|
'bets' => array_map(static function (array $row): array {
|
|
return [
|
|
'id' => (int) $row['id'],
|
|
'user_id' => (int) $row['user_id'],
|
|
'period_no' => (string) $row['period_no'],
|
|
'pick_numbers' => $row['pick_numbers'],
|
|
'unit_amount' => (string) $row['unit_amount'],
|
|
'total_amount' => (string) $row['total_amount'],
|
|
'streak_at_bet' => (int) $row['streak_at_bet'],
|
|
'create_time' => (int) $row['create_time'],
|
|
];
|
|
}, $bets),
|
|
'candidate_numbers' => $candidates,
|
|
'ai_default_number' => $bestNumber,
|
|
'server_time' => time(),
|
|
];
|
|
}
|
|
|
|
public static function publishSnapshot(?int $recordId = null): void
|
|
{
|
|
try {
|
|
$payload = self::buildSnapshot($recordId);
|
|
$api = new Api(
|
|
str_replace('0.0.0.0', '127.0.0.1', (string) config('plugin.webman.push.app.api')),
|
|
(string) config('plugin.webman.push.app.app_key'),
|
|
(string) config('plugin.webman.push.app.app_secret')
|
|
);
|
|
$api->trigger(self::CHANNEL, self::EVENT, $payload);
|
|
} catch (Throwable) {
|
|
}
|
|
}
|
|
|
|
private static function resolveRecord(?int $recordId): ?array
|
|
{
|
|
if ($recordId !== null && $recordId > 0) {
|
|
$row = Db::name('game_record')->where('id', $recordId)->find();
|
|
if ($row) {
|
|
return $row;
|
|
}
|
|
}
|
|
return Db::name('game_record')->whereIn('status', [0, 1, 2, 3])->order('id', 'desc')->find();
|
|
}
|
|
|
|
private static function estimateLossForNumber(array $bets, int $number): string
|
|
{
|
|
$payout = '0.0000';
|
|
foreach ($bets as $bet) {
|
|
$pickNumbers = $bet['pick_numbers'];
|
|
if (is_string($pickNumbers)) {
|
|
$decoded = json_decode($pickNumbers, true);
|
|
$pickNumbers = is_array($decoded) ? $decoded : [];
|
|
}
|
|
if (!is_array($pickNumbers)) {
|
|
$pickNumbers = [];
|
|
}
|
|
if (!in_array($number, array_map('intval', $pickNumbers), true)) {
|
|
continue;
|
|
}
|
|
$unit = (string) ($bet['unit_amount'] ?? '0');
|
|
$streak = (int) ($bet['streak_at_bet'] ?? 0);
|
|
$odds = (string) (($streak + 1) * self::BASE_ODDS);
|
|
$orderPayout = bcmul($unit, $odds, 4);
|
|
$payout = bcadd($payout, $orderPayout, 4);
|
|
}
|
|
return $payout;
|
|
}
|
|
}
|