本文目录导读:

- 第一步:数据模型设计(MySQL表结构)
- 第二步:核心算法——默契度加权轮转
- 第三步:默契度的“动态更新”机制
- 第四步:加入“轮转周期递减算法”(防疲劳度)
- 第五步:可视化与实时反馈(WebSocket / SSE)
- 总结(给项目负责人的建议)
“轮转换位防守默契度”在PHP项目中的体现,通常不是一个单一的算法,而是一个数据模型 + 业务逻辑 + 洗牌策略的综合体。
它常见于体育类管理系统(篮球、足球)、电竞BP系统,或者是位置权限动态调度系统。
由于你是“综合PHP项目”,我无法看到你的具体代码,所以我从架构设计和核心算法两个维度,给你一套完整的落地方案,假设你的场景是:有N个球员(或模块),要在M个位置(或守护区域)上进行轮转,且需要评估/提升轮转时的默契度(默契度低=冲突高)。
第一步:数据模型设计(MySQL表结构)
默契度是动态变化的,需要持久化存储,建议设计以下核心表:
-- 1. 球员/成员表 CREATE TABLE `players` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(50) NOT NULL, `position` VARCHAR(20), -- 主位置:前锋/后卫/核心位 `skill_level` TINYINT DEFAULT 3 -- 1-5 能力值 ); -- 2. 位置/点位表 CREATE TABLE `positions` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `code` VARCHAR(20) NOT NULL, -- 如 A区、B区、盯人位、协防位 `required_skill` TINYINT DEFAULT 3 -- 该位置需要的技能下限 ); -- 3. 默契度关系表(核心) -- 存的不是“谁和谁好”,而是“A在和B搭配时,在特定位置的得分” CREATE TABLE `chemistry` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `player_a_id` INT NOT NULL, `player_b_id` INT NOT NULL, `position_code` VARCHAR(20) NOT NULL, -- 在这个位置上搭配 `score` TINYINT DEFAULT 0, -- 0-100,越高越默契 `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `unique_pair_pos` (`player_a_id`, `player_b_id`, `position_code`) ); -- 4. 轮换历史表(用于记录并反馈默契度权重) CREATE TABLE `rotation_history` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `turn_id` INT NOT NULL, -- 第几轮 `player_id` INT NOT NULL, `position_code` VARCHAR(20) NOT NULL, `performance_score` TINYINT, -- 该轮次该位置的表现得分,用于回写chemistry表 `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
第二步:核心算法——默契度加权轮转
这是最关键的部分,我们不能随机轮转,要根据团队默契度做“最小化冲突”的贪心匹配。
逻辑思路(PHP实现):
- 获取当前轮次的空位。
- 计算每个球员去每个空位的“冲突成本”(即默契度低=成本高)。
- 优先分配冲突成本最低的组合(即默契度最高的组合)。
- 保证每个位置不重复,每个球员不重复。
<?php
class RotationOptimizer {
private $db; // PDO 实例
public function __construct(\PDO $pdo) {
$this->db = $pdo;
}
/**
* 计算某球员在特定位置的“冲突成本”
* 成本越低,默契度越高,成本 = 100 - 默契度评分
*/
private function getConflictCost(int $playerId, string $positionCode, array $alreadyAssignedPlayers): int {
// 基础成本
$baseCost = 80; // 默认中等等级
// 如果该位置需要高技能,而球员技能低,则提高成本
// 这里简化处理:查数据库进行技能匹配...
// $skillCost = ...
// 核心:计算与已分配球员的平均默契度
if (empty($alreadyAssignedPlayers)) {
return 50; //第一个上场,默契度不影响
}
// 构建IN查询
$placeholders = implode(',', array_fill(0, count($alreadyAssignedPlayers), '?'));
$sql = "SELECT AVG(score) AS avg_score
FROM chemistry
WHERE (player_a_id = ? AND player_b_id IN ($placeholders))
OR (player_b_id = ? AND player_a_id IN ($placeholders))";
$params = array_merge([$playerId], $alreadyAssignedPlayers, [$playerId], $alreadyAssignedPlayers);
// 注意:如果只有1个已分配球员,上面的SQL可能重复,需优化,这里仅示意
$stmt = $this->db->prepare($sql);
$stmt->execute($params);
$avgScore = (int) $stmt->fetchColumn() ?: 0; // 无历史数据默认为0
// 默契度越低(score低),成本越高
$chemistryCost = 100 - $avgScore;
// 加权:技能匹配占40%,默契度占60%
$finalCost = (int) round($baseCost * 0.4 + $chemistryCost * 0.6);
return $finalCost;
}
/**
* 生成最优轮换阵容
*/
public function generateRotation(array $availablePlayerIds, array $positionCodes): array {
$unassignedPlayers = $availablePlayerIds;
$assignedPositions = []; // 位置 => 球员
$usedPlayers = []; // 已用的球员ID
// 排序:先从“难”的位置开始分配(位置需求技能最高的),保证核心位置有最默契的人
// 这里假设你有个方法获取位置难度,略...
foreach ($positionCodes as $posCode) {
$bestPlayerId = null;
$lowestCost = PHP_INT_MAX;
foreach ($unassignedPlayers as $playerId) {
// 检查该球员是否已分配
if (in_array($playerId, $usedPlayers)) {
continue;
}
$cost = $this->getConflictCost($playerId, $posCode, $usedPlayers);
if ($cost < $lowestCost) {
$lowestCost = $cost;
$bestPlayerId = $playerId;
}
}
if ($bestPlayerId !== null) {
$assignedPositions[$posCode] = $bestPlayerId;
$usedPlayers[] = $bestPlayerId;
// 移除已分配球员
$key = array_search($bestPlayerId, $unassignedPlayers);
unset($unassignedPlayers[$key]);
$unassignedPlayers = array_values($unassignedPlayers);
}
}
return $assignedPositions;
}
}
第三步:默契度的“动态更新”机制
默契度不能是一成不变的死数据,实战中表现好,这个配对要加分,表现差要减分,这是“综合项目”中最体现专业度的环节。
PHP 回调函数设计:
<?php
/**
* 比赛结束后,更新轮转默契度
*/
function updateChemistryAfterMatch(\PDO $db, int $matchId): void {
// 1. 读取这场比赛的历史数据
$stmt = $db->prepare("SELECT player_id, position_code, performance_score FROM rotation_history WHERE turn_id = :match_id");
$stmt->execute([':match_id' => $matchId]);
$history = $stmt->fetchAll();
// 2. 两两配对,更新chemistry表
for ($i = 0; $i < count($history); $i++) {
for ($j = $i + 1; $j < count($history); $j++) {
$p1 = $history[$i];
$p2 = $history[$j];
// 计算这个比赛的“同队表现分” = 平均分
$combinedScore = ($p1['performance_score'] + $p2['performance_score']) / 2;
// 更新历史默契度(权重:历史70% + 本场30%)
$upsertSql = "
INSERT INTO chemistry (player_a_id, player_b_id, position_code, score)
VALUES (:a, :b, :pos, :new_score)
ON DUPLICATE KEY UPDATE
score = ROUND(score * 0.7 + VALUES(score) * 0.3)
";
$stmt = $db->prepare($upsertSql);
$stmt->execute([
':a' => min($p1['player_id'], $p2['player_id']),
':b' => max($p1['player_id'], $p2['player_id']),
':pos' => $p1['position_code'], // 位置要对应
':new_score' => $combinedScore
]);
}
}
// 3. 清空该比赛的历史记录(防止重复更新)
$db->prepare("DELETE FROM rotation_history WHERE turn_id = :match_id")->execute([':match_id' => $matchId]);
}
第四步:加入“轮转周期递减算法”(防疲劳度)
这是“综合项目”里很容易漏掉的一点,如果只讲默契度,AI会一直让两个关系最好的球员永远绑在一起,导致体力失衡。
解决方案:引入“疲劳值/冷却期”
- 条件: 如果球员A和B在上一轮已经搭档过了,本轮成本强制增加30%。
- 目的: 强制系统尝试不同的组合,从而让替补队员也能涨默契度。
// 在 getConflictCost 函数末尾加: $sql = "SELECT COUNT(*) FROM rotation_history WHERE turn_id = :last_turn AND player_id IN (:a, :b)"; // 如果上一轮他们同时在场,则 $cost += 30;
第五步:可视化与实时反馈(WebSocket / SSE)
作为综合项目,前端展示很重要。
- 轮转图: 用PHP生成JSON数据,前端用
Canvas或D3.js绘制热力图,哪里默契度高(绿色),哪里红色(冲突)。 - 实时推演: 使用
Redis存储当前轮次的临时状态,配合Swoole或Workerman推送实时轮换建议。
给项目负责人的建议)
如果你的“综合PHP项目”是一个管理系统,建议架构分层如下:
- Model层: 构建
ChemistryModel(计算默契度)、RotationModel(轮转算法)。 - Service层:
MatchService(处理比赛逻辑)、RotationService(调度轮换)。 - 设计模式: 使用策略模式,因为“默契度算法”可能不止一种。
- 策略A:基于历史得分的加权。
- 策略B:基于最近状态的动态衰减(最近两轮不重复搭档)。
- 策略C:考虑对手强度的预判(对手是右路进攻强队,则我方左路协防位必须上防守默契度最高的大个子)。
如果你能提供具体的项目框架(如ThinkPHP/Laravel)以及位置的设定(比如5人轮换还是11人轮换),我可以给你直接改写成适合你框架的代码片段。