羽毛球轮转系统详细文档
目录
- 系统概述
- 系统架构
- 核心功能
- 轮转算法介绍
- 使用指南
- 系统优点
- 改进点与优化方案
- 未来改进计划
- 技术栈
- 常见问题
系统概述
羽毛球轮转系统是一个专为羽毛球活动设计的智能排班和比赛管理系统。系统解决了羽球活动中常见的以下痛点:
- 如何确保所有参与者的出场机会均等?
- 如何避免重复固定的搭档组合?
- 如何记录和统计比赛成绩?
- 如何自动生成合理的轮转排班?
系统采用现代化的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 + 人数)与人数相关终止温度 Tmin1最低温度降温系数 α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
- };
复制代码 预期效果:
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中,如果数据过大可能无法生成。建议在小规模活动( |