完整项目实战
羽毛球轮转系统详细文档
目录
系统概述
羽毛球轮转系统是一个专为羽毛球活动设计的智能排班和比赛管理系统。系统解决了羽球活动中常见的以下痛点:
- 如何确保所有参与者的出场机会均等?
- 如何避免重复固定的搭档组合?
- 如何记录和统计比赛成绩?
- 如何自动生成合理的轮转排班?
系统采用现代化的Web技术,支持离线使用(数据存储在浏览器本地),即开即用,无需配置服务器。
系统架构
技术架构图
┌─────────────────────────────────────────────────────────────┐
│ 用户界面层 (Presentation) │
│ ┌───────────┬───────────┬───────────┬───────────┬────────┐ │
│ │ 人员管理 │ 分组管理 │ 配置设置 │ 轮转预览 │ 比赛进行│ │
│ └───────────┴───────────┴───────────┴───────────┴────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 业务逻辑层 (Business Logic) │
│ ┌──────────────────┬──────────────────┬──────────────────┐ │
│ │ 数据验证 │ 轮转生成算法 │ 排名计算 │ │
│ │ 页面跳转控制 │ 模拟退火优化 │ 统计分析 │ │
│ └──────────────────┴──────────────────┴──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 数据存储层 (Data Storage) │
│ ┌────────────────────────────────────────────────────────┐│
│ │ LocalStorage (浏览器本地存储) ││
│ │ - 人员数据 - 分组数据 - 配置参数 ││
│ │ - 轮转赛程 - 比赛成绩 - 排名统计 ││
│ └────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
数据模型
1. 人员数据 (People)
{
id: Number, // 人员唯一标识
name: String, // 姓名
gender: String, // 性别 (male/female)
level: String // 技术等级 (可选)
}
2. 分组数据 (Groups)
{
id: String, // 圈子唯一标识 (group_ + 时间戳)
name: String, // 圈子名称
type: String, // 圈子类型
// 'open' - 开放圈子(可与任何人组队)
// 'closed' - 封闭圈子(仅本圈子内组队)
// 'semi-closed' - 半封闭圈子(仅本圈子或自由人)
memberIds: Number[] // 成员ID列表
}
3. 配置数据 (Config)
{
courtCount: Number, // 场地数量 (默认2)
matchDuration: Number, // 每场时长(分钟) (默认10)
totalDuration: Number, // 总时长(分钟) (默认150)
totalRounds: Number, // 总轮次数 (默认15)
allowIntraGroup: Boolean, // 允许同圈对战 (默认true)
maximizeCross: Boolean, // 最大化跨圈比赛 (默认true)
balancePlayTime: Boolean, // 启用模拟退火优化 (默认true)
courtNumbers: String // 场地编号(可选)
}
4. 轮转赛程 (Schedule)
[
{
round: Number, // 轮次编号
courts: [
{
team1: Number[], // 队伍1成员ID列表
team2: Number[], // 队伍2成员ID列表
score1: Number|null, // 队伍1得分
score2: Number|null, // 队伍2得分
confirmed: Boolean // 是否已确认
}
]
}
]
5. 排名数据 (Rankings)
{
personId: Number, // 人员ID
gamesWon: Number, // 胜局数
gamesLost: Number, // 负局数
netGames: Number, // 净胜局 (胜-负)
netScore: Number, // 总净胜分
totalPoints: Number // 总积分
}
核心功能
1. 人员管理
- 添加人员:输入姓名、性别、技术等级
- 删除人员:移除不再参与的人员
- 查看人员列表:显示所有参与人员
- 最低要求:至少需要4名人员才能生成轮转表
2. 分组(圈子)管理
系统支持三种圈子类型,用于控制组队规则:
2.1 开放圈子 (Open)
- 可与任何圈子或自由人组队
- 适合:需要频繁跨圈交流的活动
2.2 封闭圈子 (Closed)
- 仅限本圈子内人员组队
- 适合:固定队伍、团队建设
2.3 半封闭圈子 (Semi-Closed)
- 可与本圈子成员或自由人(未加入任何圈子的人)组队
- 不能与其他半封闭圈子成员组队
- 适合:需要保护部分人员关系的活动
3. 配置设置
- 场地数量:设置可用的羽毛球场数量
- 每场时长:每场比赛预计时间(分钟)
- 总时长:活动总时长(分钟)
- 系统自动计算:建议的轮次数
- 跨圈对战选项:是否允许同圈对战
- 最大化跨圈比赛:优先安排不同圈子的对战
4. 轮转预览
生成后可查看:
- 每轮比赛的场地分配
- 轮转统计概览(总场次、最多/最少场次)
- 个人轮转场次统计
- 总场次:每场比赛都计入
- 参与轮次:实际参与的轮次数
- 轮转表确认后进入比赛阶段
5. 比赛进行
5.1 分数录入
- 按轮次和场地录入比赛分数
- 快捷按钮:10分、16分、21分
- 分数验证:
- 单场最高30分
- 正常21分制需领先2分获胜
- 30:29为最大比分
5.2 比赛确认
- 比赛录入完整后可确认确认
- 确认后可修改(点击修改按钮)
- 确认状态:✅ 已确定 / ⏳ 待录入
5.3 实时排名
| 排名 | 姓名 | 总场次 | 胜局 | 负局 | 净胜局 | 总净胜分 |
|---|
排名规则:
- 胜局数越多,排名越高
- 胜局相同时,负局数越少,排名越高
- 如仍相同,按总净胜分排序
6. 报表统计
完成比赛后提供完整统计:
6.1 最终排名
完整的比赛排名表,包含所有指标
6.2 搭档统计
展示每个人的搭档组合和次数:
- 搭档列表:姓名(次数) 格式
- 搭档人数:总共与多少人组队过
6.3 对阵历史
完整的比赛记录表,包含:
- 轮次 | 场地 | A队 | 比分 | B队 | 胜方
6.4 分享与导出
- 生成分享链接:生成URL分享比赛结果
- 复制Markdown报表:复制格式化的比赛报告
轮转算法介绍
算法总览
轮转算法采用两阶段优化策略:
阶段1: 稳定配对分配 (Stable Pair Assignment)
↓
阶段2: 模拟退火优化 (Simulated Annealing)
阶段1: 稳定配对分配
算法核心思想
采用贪心算法+匈牙利思想的组合,逐轮构建合理的比赛安排。
算法流程
┌─────────────────────────────────────────────────────────┐
│ 初始化 │
│ - 计算每个人目标出场次数 │
│ - 预计算所有合法搭档配对 │
│ - 初始化搭档使用计数 │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 逐轮构建 (for each round) │
│ for round = 1 to totalRounds: │
│ for court = 1 to courtCount: │
│ 1. 筛选候选人: │
│ - 本轮未出场的人 │
│ - 还有配对需求的人 │
│ │
│ 2. 组合搜索 (限制搜索空间): │
│ - 最多30个候选人参与搜索 │
│ - 遍历所有可能的2v2组合 │
│ │
│ 3. 得分计算: │
│ 得分 = 紧迫度×10 │
│ + 跨圈分×(10或1) │
│ - 搭档重复惩罚×3 │
│ │
│ 4. 选择最高得分组合 │
│ 5. 更新状态:出场计数+搭档统计 │
└─────────────────────────────────────────────────────────┘
得分函数详解
| 因子 | 计算 | 权重 | 说明 |
|---|---|---|---|
| 紧迫度 | Σ(目标出场 - 已出场) | ×10 | 优先安排出场少的人 |
| 跨圈分 | 10(跨圈)/5(部分)/0(同圈) | ×10(启用)/×1 | 鼓励跨圈对战 |
| 搭档重复 | -(使用次数之和) | ×3 | 避免重复搭档 |
目标出场次数计算
总位置数 = 轮次数 × 场地数 × 4人
目标出场 = 总位置数 ÷ 人员数
例如:15轮、2场地、20人
总位置数 = 15 × 2 × 4 = 120
目标出场 = 120 ÷ 20 = 6场/人
阶段2: 模拟退火优化
优化目标
进一步优化初始解,降低"能量"(即问题的不合理程度):
- 消除重复对局(同一4人组合多次对战)
- 减少搭档重复
- 保持出场平衡
能量函数
总能量 = 重复对局惩罚 + 搭档重复惩罚 + 出场不平衡惩罚 + 固定搭档约束惩罚
重复对局惩罚 = 1000 × 重复次数
搭档重复惩罚 = Σ (count - 1) × count × 20
出场不平衡 = (max出场 - min出场) × 500
固定搭档约束 = 500 × (未组队次数)
模拟退火参数
| 参数 | 默认值 | 说明 |
|---|---|---|
| 初始温度 T₀ | min(100, 20 + 人数) | 与人数相关 |
| 终止温度 Tmin | 1 | 最低温度 |
| 降温系数 α | 0.92 | 每次温度降低 |
| 最大迭代次数 | min(3000, 轮数×30) | 根据规模调整 |
| 最大无改进次数 | 300 | 提前终止条件 |
邻域生成操作
系统随机选择以下任一操作生成新解:
-
交换不同轮次的两场比赛 (40%概率)
- 从不同轮次随机选择两场
- 交换它们的队伍组合
-
交换同一场的不同队伍 (40%概率)
- 在同一场中随机选择两人
- 交换他们的队伍位置
-
交换同一轮的不同场次的队伍 (20%概率)
- 从同轮不同场地交换队伍
优化流程
┌────────────────────────────────────────────────────┐
│ 初始化 │
│ current = 初始解 │
│ energy = 计算能量(current) │
│ best = current │
│ bestEnergy = energy │
└────────────────────────────────────────────────────┘
↓
┌────────────────────────────────────────────────────┐
│ 退火循环 │
│ while T > Tmin 且 迭代次数 < maxIterations: │
│ neighbor = 生成邻域解(current) │
│ newEnergy = 计算能量(neighbor) │
│ Δ = newEnergy - energy │
│ │
│ if Δ < 0 OR random() < exp(-Δ/T): │
│ 接受新解 │
│ current = neighbor │
│ energy = newEnergy │
│ │
│ if energy < bestEnergy: │
│ best = current │
│ bestEnergy = energy │
│ │
│ if 无改进次数 > 300: break │
│ if bestEnergy == 0: break │
│ │
│ T = T × α (降温) │
└────────────────────────────────────────────────────┘
圈子约束检查
系统根据圈子类型严格验证搭档组合的合法性:
生成合法配对
function canPair(person1, person2) {
const p1Groups = person1所属圈子列表;
const p2Groups = person2所属圈子列表;
// 情况1: 至少有一方是自由人
if (p1Groups.length === 0 || p2Groups.length === 0)
return true;
// 情况2: 双方都有圈子
for (const g1 of p1Groups) {
for (const g2 of p2Groups) {
// open + open → 允许
if (g1.type === 'open' && g2.type === 'open')
return true;
// closed + closed (同圈) → 允许
if (g1.id === g2.id && g1.type === 'closed')
return true;
// semi-closed + semi-closed → 禁止
if (g1.type === 'semi-closed' && g2.type === 'semi-closed')
return false;
// semi-closed + open → 禁止
if ((g1.type === 'semi-closed' && g2.type === 'open') ||
(g1.type === 'open' && g2.type === 'semi-closed'))
return false;
}
}
return true;
}
使用指南
快速开始
1. 打开 badminton.html 文件
2. 进入【人员管理】,添加至少4人
3. 进入【分组管理】,创建圈子并分配人员
4. 进入【配置设置】,设置场地和时长
5. 点击【生成轮转表】
6. 在【轮转预览】中查看结果,确认无误后点击【确认并开始比赛】
7. 进入【比赛进行】,按轮次录入分数
8. 完成后查看【报表统计】
详细操作步骤
步骤1: 添加人员
- 在【人员管理】页面输入:
- 姓名(必填)
- 性别(必填)
- 技术等级(可选)
- 点击"添加人员"按钮
- 重复添加,直到至少4人
- 点击"删除"按钮可移除人员
步骤2: 创建圈子
- 在【分组管理】页面输入:
- 圈子名称(必填,唯一)
- 圈子类型:
- 开放圈子:可与任何人组队
- 封闭圈子:仅本圈子内组队
- 半封闭圈子:仅本圈子或自由人
- 点击"创建圈子"
- 在下方列表中:
- 点击"未分配人员"选择要分配的人
- 点击目标圈子的"+"号添加成员
- 可移除圈子成员(点击×)
步骤3: 配置参数
- 在【配置设置】页面设置:
- 场地数量:2-10个
- 每场时长:5-30分钟
- 总时长:60-300分钟
- 系统自动计算建议的轮次数
- 选择是否启用:
- 跨圈对战设置
- 最大化跨圈比赛
- 点击"生成轮转表"
步骤4: 预览轮转
- 查看每轮比赛的场地分配
- 检查轮转统计(出场是否均衡)
- 查看个人轮转场次统计
- 如不满意,返回配置调整后重新生成
- 满意后点击"确认并开始比赛"
步骤5: 录入比赛
- 按轮次逐一录入:
- 选择比分:可直接输入或点击快捷按钮(10/16/21)
- 两队比分都填入后,"确定"按钮激活
- 点击"确定"完成本场比赛录入
- 已确认的比赛:
- 显示"✅ 已确定"状态
- 如需修改,点击"修改"按钮
- 实时查看排名更新
步骤6: 查看报表
- 完成所有比赛后点击"完成比赛"
- 查看【报表统计】页面:
- 最终排名
- 搭档统计
- 对阵历史
- 可分享或导出报表
分享比赛结果
- 点击"生成分享链接"
- 复制生成的URL
- 发送给其他人员
- 对方打开链接后自动加载数据
重置操作
- 重置分数:清空所有已录入的分数
- 重置全部:清空所有数据,返回初始状态
系统优点
1. 智能算法
- 两阶段优化策略确保轮转表质量
- 模拟退火算法避免局部最优
- 出场均衡,尽量避免一人多次出战
2. 灵活的圈子机制
- 三种圈子类型适应不同活动需求
- 复杂的圈子约束自动处理
- 支持固定搭档、自由组队等场景
3. 跨圈促进
- 可配置跨圈对战策略
- 优先安排不同圈子人员对战
- 促进圈间交流和友谊
4. 即时反馈
- 实时排名更新
- 轮转统计一目了然
- 输入验证即时提示
5. 离线可用
- 数据存储在浏览器本地
- 无需联网即可使用
- 随时保存,下次访问自动加载
6. 数据持久化
- LocalStorage 自动保存
- 页面刷新不丢失数据
- 支持分享链接备份
7. 响应式设计
- 支持桌面和移动设备
- 自适应布局
- 触摸友好的界面
改进点与优化方案
当前存在的改进点
| 问题 | 描述 | 优化方案 |
|---|---|---|
| 算法性能 | 人员较多时(>30人),生成时间较长 | 引入Web Worker多线程处理 |
| 初始解质量 | 贪心算法可能产生次优初始解 | 采用更智能的启发式算法 |
| 圈子约束 | 半封闭圈子限制较多,组合困难 | 放宽部分约束,增加灵活度 |
| 统计维度 | 当前统计基础,缺乏深度分析 | 增加更多统计图表 |
| 数据备份 | LocalStorage有存储限制 | 支持导出JSON文件备份 |
详细优化方案
1. 算法性能优化
问题:
- 当前单线程计算,UI可能卡顿
- 大规模场景(40人+)生成时间超预期
方案:
// 使用 Web Worker 后台计算
worker.postMessage({
people: data.people,
groups: data.groups,
config: data.config
});
worker.onmessage = (e) => {
const { schedule, playCount, partnerCount } = e.data;
// 更新UI
};
预期效果:
- UI保持响应
- 生成时间减少30-50%
2. 初始解质量提升
问题:
- 贪心算法局部最优
- 需要更多退火迭代
方案:
- 采用"圆桌法"生成初始解
- 确保初始解能量较低
- 减少退火迭代次数
// 圆桌法示例
function generateInitialSchedule(people, courtCount) {
const schedule = [];
// 将人员排列在圆桌上
// 每轮按规则选取4人组队
return schedule;
}
3. 圈子约束优化
问题:
- 半封闭圈子限制过严
- 可能导致无法生成有效解
方案:
- 新增"宽松模式"选项
- 在无法生成时自动放宽约束
- 提供约束冲突提示
4. 统计图表增强
新增统计项:
- 分数分布图
- 出场频率热力图
- 搭档关系网络图
- 趋势分析(连续排名变化)
5. 数据备份与恢复
方案:
- 导出JSON文件
- 导入JSON文件恢复
- 云端备份(可选)
// 导出功能
function exportData() {
const blob = new Blob([JSON.stringify(data)], {type: 'application/json'});
const url = URL.createObjectURL(blob);
// 下载文件
}
// 导入功能
function importData(file) {
const reader = new FileReader();
reader.onload = (e) => {
data = JSON.parse(e.target.result);
saveData();
};
reader.readAsText(file);
}
未来改进计划
短期计划(1-3个月)
-
数据导出功能 ⏳
- 支持导出CSV格式
- 支持导出JSON格式
- Excel兼容格式
-
比赛历史记录 ⏳
- 保存多场比赛历史
- 历史对比功能
-
语音播报 ⏳
- 当前轮次语音播报
- 比赛结果播报
中期计划(3-6个月)
-
多人协作模式
- 实时同步比赛分数
- WebSocket 支持
- 多设备同时操作
-
高级统计功能
- 胜率分析
- 搭档成功率
- 个人能力评估
-
模板系统
- 保存常用配置模板
- 一键加载模板
长期计划(6个月以上)
-
移动端APP
- React Native开发
- 离线数据同步
- 推送通知
-
AI辅助
- 智能人员推荐
- 比赛结果预测
- 策略建议
-
社交功能
- 活动组织
- 好友系统
- 成就系统
技术栈
| 技术类别 | 技术选型 |
|---|---|
| 前端框架 | 原生 JavaScript (Vanilla JS) |
| 样式方案 | 原生 CSS + CSS Variables |
| 数据存储 | LocalStorage API |
| 算法 | 模拟退火、贪心算法 |
| 架构模式 | 单页应用 (SPA) |
| 浏览器支持 | Chrome, Firefox, Safari, Edge (现代浏览器) |
| 移动端 | 响应式设计,支持 iOS Safari, Android Chrome |
常见问题
Q1: 如何修改已确认的比赛分数?
A: 在【比赛进行】页面,已确认的比赛会显示"修改"按钮,点击后可重新录入分数。
Q2: 为什么提示"请至少创建一个圈子"?
A: 圈子系统是分组机制的一部分。可以创建一个"全部人员"的开放圈子,将所有人都加入。
Q3: 如何让某些人永远搭档?
A: 在【分组管理】中创建一个2人封闭圈子,将这两个人加入。系统会尽量让他们组队。
Q4: 数据会丢失吗?
A: 数据保存在浏览器 LocalStorage 中,关闭浏览器不会丢失。但清除浏览器数据会丢失,建议定期导出备份。
Q5: 分数录入规则是什么?
A:
- 单场最高30分
- 正常21分制需领先2分获胜
- 30:29为最大比分
- 如果比分不合理(如18:18),会阻止确认
Q6: 排名规则是什么?
A:
- 胜局数越多排名越高
- 胜局相同时,负局数越少排名越高
- 如仍相同,按总净胜分排序
Q7: 可以中途添加人员吗?
A: 目前不支持赛后添加。建议在开始前确保所有人员都已添加。
Q8: 如何删除整场比赛记录?
A: 点击"重置分数"可清空所有分数,重新开始录入。如需生成新的轮转表,点击"重置全部"。
Q9: 系统支持多少人?
A: 理论上无上限,建议4-40人可获得最佳体验。超过40人时生成时间可能较长。
Q10: 分享链接会过期吗?
A: 分享链接不会过期,但数据编码在URL中,如果数据过大可能无法生成。建议在小规模活动(<50人)中使用分享功能。
附录
版本历史
| 版本 | 日期 | 更新内容 |
|---|---|---|
| v1.0 | 2026-03-24 01:00 | 初始版本PC端发布,系统基本功能构建 |
| v1.1 | 2026-03-24 17:00 | 手机浏览器适配版本发布 |
| v1.2 | 2026-03-25 02:00 | 轮转算法优化版本发布,退火算法+谈心兜底 |
| v1.3 | 2026-03-25 16:00 | 功能优化版本发布,增加圈子类别、场地号自定义、个人场次预览 |
联系与反馈
如有问题或建议,请通过以下方式联系:
- 邮箱反馈:205194658@qq.com

浙公网安备 33010602011771号