PHP 实时项目:显示场上谁更占优势
这是一个很常见的需求,比如体育比赛、电竞对战、辩论赛等场景,下面给你一套完整的实现思路和代码。

整体架构
前端(JS定时轮询/WebSocket) ←→ PHP后端(API) ←→ 数据库(比分/状态)
↓
优势算法
数据表设计
CREATE TABLE matches (
id INT PRIMARY KEY AUTO_INCREMENT,
team_a VARCHAR(50),
team_b VARCHAR(50),
score_a INT DEFAULT 0,
score_b INT DEFAULT 0,
-- 进阶统计
possession_a DECIMAL(5,2) DEFAULT 50, -- 控球率
shots_a INT DEFAULT 0,
shots_b INT DEFAULT 0,
status TINYINT DEFAULT 0, -- 0未开始 1进行中 2结束
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
优势算法(核心)
不要只看比分,要综合多维度加权计算:
<?php
/**
* 计算双方优势指数
* 返回 -100 ~ +100,正数A占优,负数B占优
*/
function calcAdvantage(array $m): float
{
// 1. 比分差(权重最高,且随时间放大)
$scoreDiff = $m['score_a'] - $m['score_b'];
$scoreScore = $scoreDiff * 25;
// 2. 控球率差
$possDiff = ($m['possession_a'] - 50) * 0.4;
// 3. 射门差
$shotDiff = ($m['shots_a'] - $m['shots_b']) * 1.5;
// 4. 时间因子:比分领先在后期更"稳"
$timeWeight = 1.0;
if (isset($m['elapsed_min'])) {
$timeWeight = 1 + ($m['elapsed_min'] / 90) * 0.5;
}
$advantage = $scoreScore * $timeWeight + $possDiff + $shotDiff;
// 归一化到 -100 ~ 100
return max(-100, min(100, round($advantage, 1)));
}
/**
* 转换为文字描述
*/
function advantageText(float $adv, string $a, string $b): string
{
$abs = abs($adv);
if ($abs < 10) return "势均力敌";
if ($abs < 30) return $adv > 0 ? "{$a}略占上风" : "{$b}略占上风";
if ($abs < 60) return $adv > 0 ? "{$a}明显占优" : "{$b}明显占优";
return $adv > 0 ? "{$a}全面压制" : "{$b}全面压制";
}
API 接口
<?php
// api/advantage.php
header('Content-Type: application/json; charset=utf-8');
$pdo = new PDO('mysql:host=localhost;dbname=game;charset=utf8mb4', 'user', 'pass');
$id = (int)($_GET['id'] ?? 0);
$stmt = $pdo->prepare("SELECT * FROM matches WHERE id = ?");
$stmt->execute([$id]);
$m = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$m) {
echo json_encode(['error' => 'not found']);
exit;
}
$adv = calcAdvantage($m);
echo json_encode([
'team_a' => $m['team_a'],
'team_b' => $m['team_b'],
'score' => "{$m['score_a']} : {$m['score_b']}",
'advantage'=> $adv, // -100~100
'percent_a'=> round((100 + $adv) / 2), // 0~100,方便画进度条
'text' => advantageText($adv, $m['team_a'], $m['team_b']),
'updated' => $m['updated_at'],
], JSON_UNESCAPED_UNICODE);
前端实时展示
方案 A:轮询(简单)
<div id="box">
<span id="a">A</span>
<div class="bar">
<div id="barA" style="width:50%"></div>
<div id="barB" style="width:50%"></div>
</div>
<span id="b">B</span>
<p id="text"></p>
</div>
<style>
.bar{display:flex;height:20px;background:#eee;border-radius:10px;overflow:hidden}
#barA{background:#2196f3;transition:width .6s}
#barB{background:#f44336;transition:width .6s}
</style>
<script>
async function refresh() {
const r = await fetch('api/advantage.php?id=1');
const d = await r.json();
document.getElementById('a').textContent = d.team_a;
document.getElementById('b').textContent = d.team_b;
document.getElementById('barA').style.width = d.percent_a + '%';
document.getElementById('barB').style.width = (100 - d.percent_a) + '%';
document.getElementById('text').textContent =
`${d.score} — ${d.text}`;
}
refresh();
setInterval(refresh, 3000); // 每3秒刷新
</script>
方案 B:WebSocket(更高实时性)
推荐用 Workerman 或 Swoole,比分一有变动就 push 给所有客户端,无需轮询。
// Workerman 示例
use Workerman\Worker;
require 'vendor/autoload.php';
$ws = new Worker('websocket://0.0.0.0:2346');
$ws->onConnect = function($conn) {
$conn->send(json_encode(['msg' => 'connected']));
};
$ws->onMessage = function($conn, $data) use ($ws) {
// 收到比分更新,广播给所有人
foreach ($ws->connections as $c) $c->send($data);
};
Worker::runAll();
进阶提升建议
| 提升点 | 做法 |
|---|---|
| 算法更准 | 引入 Elo / 泊松模型 / 历史胜率 |
| 性能 | 优势结果缓存到 Redis,30秒过期 |
| 实时性 | WebSocket 或 SSE 替代轮询 |
| 可视化 | ECharts 半圆仪表盘展示优势值 |
| 多场次 | 前端用 setInterval 只拉当前页可见的比赛 |
告诉我你的具体场景(体育?电竞?辩论?),我可以帮你把算法权重和字段调整得更贴合,另外你想用轮询还是 WebSocket?我可以给完整可运行的代码。