2024-10-10

This commit is contained in:
2024-10-10 12:56:56 +08:00
commit 7b347c848a
2648 changed files with 643965 additions and 0 deletions

View File

@@ -0,0 +1,707 @@
/**
* scrollboard.js
* ACM竞赛滚榜展示插件基于JQuery、Bootstrap
*
* Version 1.0.0
* Author: qinshaoxuan qsxuan.com
* Github: https://github.com/qinshaoxuan/ScrollBoard
* Demo: https://qinshaoxuan.github.io/ScrollBoard/
*
*/
/**
* 从服务器获取提交列表可按后台json格式修改
* @return {Array<Submit>} 初始化后的Submit对象数组
*/
/*function getSubmitList() {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "data/submitData.json",
dataType: "json",
data: {},
async: false,
success: function(result) {
for (var key in result.data) {
var sub = result.data[key];
// 提交ID用户ID题号提交时间结果
data.push(new Submit(sub.submitId, sub.userId, sub.alphabetId, sub.subTime, sub.resultId));
}
},
error: function() {
alert("获取Submit数据失败");
}
});
return data;
}*/
function getSubmitList(cid) {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "contestrank3.php?cid="+cid+"&type=json&list=submit",
dataType: "json",
data: {},
async: false,
success: function(result) {
for (var key in result) {
var sub = result[key];
// 提交ID用户ID题号提交时间结果
data.push(new Submit(sub.submitID, sub.userID, sub.alphabetID, sub.subTime, sub.resultID));
}
},
error: function() {
alert("获取Submit数据失败");
}
});
return data;
}
/**
* 从服务器获取队伍列表可按后台json格式修改
* @return {Array<Team>} 初始化后的Team对象数组
*/
/*function getTeamList() {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "data/teamData.json",
dataType: "json",
async: false,
data: {},
success: function(result) {
for (var key in result.data) {
var team = result.data[key];
// 队伍ID 队伍名称真名offical
data[team.teamId] = new Team(team.teamId, team.nickname, team.realname, team.official);
}
},
error: function() {
alert("获取Team数据失败");
}
});
return data;
}*/
function getTeamList(cid) {
var data = new Array();
$.ajax({
type: "GET",
content: "application/x-www-form-urlencoded",
url: "contestrank3.php?cid="+cid+"&type=json&list=team",
dataType: "json",
async: false,
data: {},
success: function(result) {
for (var key in result) {
var team = result[key];
// 队伍ID 队伍名称真名offical
data[team.userID] = new Team(team.userID, team.nickName, team.realName, team.official);
}
},
error: function() {
alert("获取Team数据失败");
}
});
return data;
}
/**
* yyyy-mm-dd hh:mm:ss格式转Date
* @param {Date} s 字符串对应的日期
*/
function StringToDate(s) {
var d = new Date();
d.setYear(parseInt(s.substring(0, 4), 10));
d.setMonth(parseInt(s.substring(5, 7) - 1, 10));
d.setDate(parseInt(s.substring(8, 10), 10));
d.setHours(parseInt(s.substring(11, 13), 10));
d.setMinutes(parseInt(s.substring(14, 16), 10));
d.setSeconds(parseInt(s.substring(17, 19), 10));
return d;
}
/**
* Submit对象
* @param {int} submitId 全局runID
* @param {int} teamId 队伍ID
* @param {String} alphabetId 比赛中的题目IDA,B,C...
* @param {int} subTime 提交时间
* @param {int} resultId 判题结果ID
*/
function Submit(submitId, teamId, alphabetId, subTime, resultId) {
this.submitId = submitId; //全局runID
this.teamId = teamId; //队伍ID
this.alphabetId = alphabetId; //比赛中的题目IDA,B,C,D...
//this.subTime = new Date(subTime);
this.subTime = StringToDate(subTime);
/**
* 判题结果ID
* @type {int}
* @value 0 Accepted
* @value 1 Presentation Error
* @value 2 Time Limit Exceeded
* @value 3 Memory Limit Exceeded
* @value 4 Wrong Answer
* @value 5 Runtime Error
* @value 6 Output Limit Exceeded
* @value 7 Compile Error
* @value 8 System Error
* @value 9 Security Error
* @value -1 Waiting
*/
this.resultId = resultId;
}
/**
* TeamProblem对象用来存放每个队伍的每道题的提交情况
*/
function TeamProblem() {
this.alphabetId = "";
this.isAccepted = false;
this.penalty = 0; //罚时毫秒数
this.acceptedTime = new Date(); //AC时间
this.submitCount = 0; //AC前提交次数如果AC了值加1
this.isUnkonwn = false; //是否为封榜后提交如果封榜前已AC也为false
}
/**
* Team对象
* @param {int} teamId 队伍ID
* @param {String} teamName 队伍名
* @param {String} teamMember 队员
* @param {boolean} official 是否计入排名
*/
function Team(teamId, teamName, teamMember, official) {
this.teamId = teamId; //队伍ID
this.teamName = teamName; //队伍名
this.teamMember = teamMember; //队员
this.official = true; //计入排名
this.solved = 0; //通过数
this.penalty = 0; //罚时,单位为毫秒
this.gender = false; //女队,默认否
this.submitProblemList = []; //提交题目列表
this.unkonwnAlphabetIdMap = new Array(); //未知的题目AlphabetId列表
this.submitList = []; //提交列表
this.lastRank = 0; //最终排名
this.nowRank = 0; //当前排名
}
/**
* Team对象初始化函数更新到封榜时的状态
* @param {Date} startTime 比赛开始时间
* @param {Date} freezeBoardTime 封榜时间
*/
Team.prototype.init = function(startTime, freezeBoardTime) {
//按提交顺序排序
this.submitList.sort(function(a, b) {
return a.submitId - b.submitId;
});
for (var key in this.submitList) {
var sub = this.submitList[key];
//创建对象
var p = this.submitProblemList[sub.alphabetId];
if (!p) p = new TeamProblem();
//设置alphabetId
p.alphabetId = sub.alphabetId;
//已经AC的题目不再计算
if (p.isAccepted) continue;
//封榜后的提交设置isUnkonwn为true
if (sub.subTime > freezeBoardTime) {
p.isUnkonwn = true;
this.unkonwnAlphabetIdMap[p.alphabetId] = true;
}
//增加提交次数
p.submitCount++;
//更新AC状态
p.isAccepted = (sub.resultId == 0);
//如果当前提交AC
if (p.isAccepted) {
//则保存AC时间
p.acceptedTime = sub.subTime.getTime() - startTime.getTime();
//如果为封榜前AC则计算罚时,且队伍通过题数加1
if (p.acceptedTime < freezeBoardTime - startTime) {
p.penalty += p.acceptedTime + (p.submitCount - 1) * 20 * 60 * 1000;
this.solved++;
this.penalty += p.penalty;
}
}
//更新submitProblemList
this.submitProblemList[p.alphabetId] = p;
}
}
/**
* 计算Team中有多少道题状态未知
* @return {int} 未知状态题目的数量
*/
Team.prototype.countUnkonwnProblme = function() {
var count = 0;
for (var key in this.unkonwnAlphabetIdMap) {
count++;
}
return count;
}
/**
* 滚榜时,更新一个队伍的一个题的状态
* @return {boolean} true:当前队伍排名上升,false:排名无变化
*/
Team.prototype.updateOneProblem = function() {
for (var key in this.submitProblemList) {
var subProblem = this.submitProblemList[key];
//如果题目结果未知
if (subProblem.isUnkonwn) {
//更新题目状态
subProblem.isUnkonwn = false;
delete this.unkonwnAlphabetIdMap[subProblem.alphabetId];
//如果AC则更新题目状态
if (subProblem.isAccepted) {
subProblem.penalty += subProblem.acceptedTime + (subProblem.submitCount - 1) * 20 * 60 * 1000;
this.solved++;
this.penalty += subProblem.penalty;
return true;
}
return false;
}
}
}
/**
* 队伍排位函数
* @param {Team} a Team a
* @param {Team} b Team b
* @return {int} 负数a排位高正数b排位高
*/
function TeamCompare(a, b) {
if (a.solved != b.solved) //第一关键字,通过题数高者排位高
return a.solved > b.solved ? -1 : 1;
if (a.penalty != b.penalty) //第二关键字,罚时少者排位高
return a.penalty < b.penalty ? -1 : 1;
//return a.teamId < b.teamId ? -1 : 1; //第三关键字队伍ID小者排位高
return a.teamId.localeCompare(b.teamId);
}
/**
* Board对象
* @param {int} problemCount 题目数量
* @param {Array<int>} medalCounts 奖牌数,无特等奖则为3个数,有特等奖则为4个数,第一个为特等奖
* @param {Date} startTime 比赛开始时间
* @param {Date} freezeBoardTime 封榜时间
*/
function Board(problemCount, medalCounts, startTime, freezeBoardTime, cid) {
this.problemCount = problemCount; //题目数量
this.medalCounts = medalCounts; //奖牌数数组,无特等奖则为3个数,有特等奖则为4个数第一个为特等奖
this.medalRanks = []; //每个奖牌的最后一名的RANK值
this.medalStr = ["gold", "silver", "bronze"];
this.problemList = []; //题目alphabetId编号列表
this.startTime = startTime;
this.freezeBoardTime = freezeBoardTime;
this.teamList = []; //从服务器获取的teamList为teamId与Team对象的映射
this.submitList = []; //从服务器获取的所有的submitList,Submit对象数组
this.teamNowSequence = []; //当前队伍排名存队伍ID
this.teamNextSequence = []; //下一步队伍排名存队伍ID
this.teamCount = 0; //队伍数量
this.displayTeamPos = 0; //当前展示的队伍位置
this.noAnimate = true; //当前无动画进行
//根据题目数量设置alphabetId
var ACode = 65;
for (var i = 0; i < problemCount; i++)
this.problemList.push(String.fromCharCode(ACode + i));
//计算medalRanks
this.medalRanks[0] = medalCounts[0];
for (var i = 1; i < this.medalCounts.length; ++i) {
this.medalRanks[i] = this.medalCounts[i] + this.medalRanks[i - 1];
}
//从服务器得到submitList和teamList
this.submitList = getSubmitList(cid);
this.teamList = getTeamList(cid);
//将submit存到对应的Team对象里
for (var key in this.submitList) {
var sub = this.submitList[key];
this.teamList[sub.teamId].submitList.push(sub);
}
//初始化Team对象同时将队伍ID放入序列
for (var key in this.teamList) {
var team = this.teamList[key];
team.init(this.startTime, this.freezeBoardTime);
this.teamNowSequence.push(team);
this.teamCount++;
}
this.displayTeamPos = this.teamCount - 1;
//队伍排序
this.teamNowSequence.sort(function(a, b) {
return TeamCompare(a, b);
});
this.teamNextSequence = this.teamNowSequence.slice(0);
}
/**
* 更新队伍排序,得到下一个队伍移动后的序列
* @return {int} 排名上升的队伍要插入的位置,如果无变化返回-1
*/
Board.prototype.updateTeamSequence = function() {
var teamSequence = this.teamNextSequence.slice(0); //复制数组js为引用传递
teamSequence.sort(function(a, b) {
return TeamCompare(a, b);
});
//找到第一个改变的位置,即为排名上升的队伍要插入的位置
var toPos = -1;
for (var i = 0; i < this.teamCount; i++) {
if (this.teamNextSequence[i].teamId != teamSequence[i].teamId) {
toPos = i;
break;
}
}
//更新队列
this.teamNowSequence = this.teamNextSequence.slice(0);
this.teamNextSequence = teamSequence.slice(0);
return toPos;
}
/**
* 不断更新最后一个unkonwn队伍的题目状态直到排名发生变化或者无题目可更新
* @return {Team} 返回正在更新的Team对象没有则返回null
*/
Board.prototype.UpdateOneTeam = function() {
//得到需要更新的队伍在当前排名中的的位置
var updateTeamPos = this.teamCount - 1;
while (updateTeamPos >= 0 && this.teamNextSequence[updateTeamPos].countUnkonwnProblme() < 1)
updateTeamPos--;
//如果有队伍可更新
if (updateTeamPos >= 0) {
//不断更新队伍题目状态,直到排名发生变化或者无题目可更新
while (this.teamNextSequence[updateTeamPos].countUnkonwnProblme() > 0) {
//更新一个题目状态
var result = this.teamNextSequence[updateTeamPos].updateOneProblem();
return this.teamNextSequence[updateTeamPos];
}
}
return null;
}
/**
* 显示封榜时的状态
*/
Board.prototype.showInitBoard = function() {
//设置表头宽度百分比
var rankPer = 5; //Rank列宽度百分比
var teamPer = 25; //Team列宽度百分比
var solvedPer = 4; //Solved列宽度百分比
var penaltyPer = 7; //Penalty列宽度百分比
var problemStatusPer = (100.0 - rankPer - teamPer - solvedPer - penaltyPer) / this.problemCount; //Problem列宽度百分比
//表头
var headHTML =
"<div id=\"timer\"></div>\
<div class=\"ranktable-head\">\
<table class=\"table\">\
<tr>\
<th width=\"" + rankPer + "%\">Rank</th>\
<th width=\"" + teamPer + "%\">Team</th>\
<th width=\"" + solvedPer + "%\">Solved</th>\
<th width=\"" + penaltyPer + "%\">Penalty</th>";
var footHTML =
"</tr>\
</table>\
</div>";
$('body').append(headHTML + footHTML);
//题目列
for (var i = 0; i < this.problemList.length; i++) {
var alphabetId = this.problemList[i];
var bodyHTML = "<th width=\"" + problemStatusPer + "%\">" + alphabetId + "</th>";
$('.ranktable-head tr').append(bodyHTML);
}
var maxRank = 0;
//队伍
for (var i = 0; i < this.teamCount; i++) {
var team = this.teamNowSequence[i];
//计算每支队伍的排名和奖牌情况
var rank = 0;
var medal = -1;
if (team.solved != 0) {
rank = i + 1;
maxRank = rank + 1;
for (var j = this.medalRanks.length - 1; j >= 0; j--) {
if (rank <= this.medalRanks[j])
medal = j;
}
} else {
rank = maxRank;
medal = -1;
}
//构造HTML
var headHTML =
"<div id=\"team_" + team.teamId + "\" class=\"team-item\" team-id=\"" + team.teamId + "\"> \
<table class=\"table\"> \
<tr>";
var rankHTML = "<th class=\"rank\" width=\"" + rankPer + "%\">" + rank + "</th>";
var teamHTML = "<td class=\"team-name\" width=\"" + teamPer + "%\"><span>" + team.teamName + /*"<br/>" + team.teamMember +*/ "</span></td>";
var solvedHTML = "<td class=\"solved\" width=\"" + solvedPer + "%\">" + team.solved + "</td>";
var penaltyHTML = "<td class=\"penalty\" width=\"" + penaltyPer + "%\">" + parseInt(team.penalty / 1000.0 / 60.0) + "</td>";
var problemHTML = "";
for (var key in this.problemList) {
problemHTML += "<td class=\"problem-status\" width=\"" + problemStatusPer + "%\" alphabet-id=\"" + this.problemList[key] + "\">";
var tProblem = team.submitProblemList[this.problemList[key]];
if (tProblem) {
if (tProblem.isUnkonwn)
problemHTML += "<span class=\"label label-warning\">" + tProblem.submitCount + "</span></td>";
else {
if (tProblem.isAccepted) {
problemHTML += "<span class=\"label label-success\">" + tProblem.submitCount + "/" + parseInt(tProblem.acceptedTime / 1000.0 / 60.0) + "</span></td>";
} else {
problemHTML += "<span class=\"label label-danger\">" + tProblem.submitCount + "</span></td>";
}
}
}
}
var footHTML =
"</tr> \
</table> \
</div>";
var HTML = headHTML + rankHTML + teamHTML + solvedHTML + penaltyHTML + problemHTML + footHTML;
//填充HTML
$('body').append(HTML);
//设置奖牌对应的CSS样式
if (medal != -1)
$("#team_" + team.teamId).addClass(this.medalStr[medal]);
}
//构造一个空的队伍,填充底部
var headHTML =
"<div id=\"team-void\" class=\"team-item\"> \
<table class=\"table\"> \
<tr>";
var rankHTML = "<th class=\"rank\" width=\"" + rankPer + "%\"></th>";
var teamHTML = "<td class=\"team-name\" width=\"" + teamPer + "%\"></td>";
var solvedHTML = "<td class=\"solved\" width=\"" + solvedPer + "%\"></td>";
var penaltyHTML = "<td class=\"penalty\" width=\"" + penaltyPer + "%\"></td>";
var problemHTML = "";
for (var key in this.problemList) {
problemHTML += "<td class=\"problem-status\" width=\"" + problemStatusPer + "%\" alphabet-id=\"" + this.problemList[key] + "\"></td>";
}
var footHTML =
"</tr> \
</table> \
</div>";
var HTML = headHTML + rankHTML + teamHTML + solvedHTML + penaltyHTML + problemHTML + footHTML;
//填充HTML
$('body').append(HTML);
//按排名对队伍的div进行排序
var headerHeight = 44; //表头的高度
var teamHeight = 68; //队伍行的高度
for (var i = 0; i < this.teamCount; ++i) {
//var teamId = this.teamList[this.teamNowSequence[i]].teamId;
var teamId = this.teamNowSequence[i].teamId;
$("div[team-id=\"" + teamId + "\"]").stop().animate({ top: i * teamHeight + headerHeight }, 800);
}
//移到底部
$("#team-void").stop().animate({ top: this.teamCount * teamHeight + headerHeight }, 800);
}
/**
* 更新队伍的表现状态即改变HTML样式
* @param {Team} team 要改变的Team对象
* @return {boolean} 要更新的题目是否AC
*/
Board.prototype.updateTeamStatus = function(team) {
var thisBoard = this;
//更新ProblemStatus
for (var key in team.submitProblemList) {
var tProblem = team.submitProblemList[key];
//构造题目状态HTML
problemHTML = "";
if (tProblem.isUnkonwn)
problemHTML = "<span class=\"label label-warning\">" + tProblem.submitCount + "</td>";
else {
if (tProblem.isAccepted) {
problemHTML = "<span class=\"label label-success\">" + tProblem.submitCount + "/" + parseInt(tProblem.acceptedTime / 1000.0 / 60.0) + "</td>";
} else {
problemHTML = "<span class=\"label label-danger\">" + tProblem.submitCount + "</td>";
}
}
var $problemStatus = $("#team_" + team.teamId + " .problem-status[alphabet-id=\"" + key + "\"]");
var $statusSpan = $problemStatus.children('span[class="label label-warning"]');
//让题目状态闪烁,并更新状态
if (tProblem.isUnkonwn == false) {
//加高亮边框前去掉所有高亮边框
$('.team-item.hold').removeClass("hold");
var $team = $("div[team-id=\"" + team.teamId + "\"]");
//加高亮边框
$team.addClass("hold");
//得到TeamDiv距顶部的高度
var clientHeight = document.documentElement.clientHeight || document.body.clientHeight || 0;
var teamTopHeight = $team.offset().top - clientHeight + 100;
//移动视点
$('body,html').stop().animate({
scrollTop: teamTopHeight
},
500);
//传参,不懂原理,用此可以在动画的回调函数使用参数
(function(thisBoard, tProblem, problemHTML) {
//闪烁两次后显示未知题目的结果
var speed = 100; //闪烁速度
$statusSpan.fadeOut(speed).fadeIn(speed, function() {
//更新题目表现状态
$(this).parent().html(problemHTML);
});
})(thisBoard, tProblem, problemHTML);
}
}
//延时更新榜单
//传参,不懂原理,用此可以在动画的回调函数使用参数
(function(thisBoard, team) {
//延时0.2s
$('#timer').animate({ margin: 0 }, 200, function() {
/*
更新Rank
*/
var maxRank = 0;
//移除div中的奖牌样式
for (var i in thisBoard.medalStr) {
$(".team-item").removeClass(thisBoard.medalStr[i]);
}
//对于每个队计算排名和奖牌情况
for (var i = 0; i < thisBoard.teamCount; i++) {
var t = thisBoard.teamNextSequence[i];
var medal = -1;
var rankValue = 0;
if (t.solved != 0) {
rankValue = i + 1;
maxRank = rankValue + 1;
for (var j = thisBoard.medalRanks.length - 1; j >= 0; j--) {
if (rankValue <= thisBoard.medalRanks[j])
medal = j;
}
} else {
rankValue = maxRank;
medal = -1;
}
$team = $("div[team-id=\"" + t.teamId + "\"]");
if (medal != -1)
$team.addClass(thisBoard.medalStr[medal]);
$("#team_" + t.teamId + " .rank").html(rankValue);
}
//更新Solved
$("#team_" + team.teamId + " .solved").html(team.solved);
//更新Penaly
$("#team_" + team.teamId + " .penalty").html(parseInt(team.penalty / 1000.0 / 60.0));
}, false);
})(thisBoard, team);
}
/**
* 更新队伍div的位置
* @param {int} toPos 当前关注队伍在序列中的终点位置,-1为不移动
*/
Board.prototype.moveTeam = function(toPos) {
var thisBoard = this;
//传参,不懂原理,用此可以在动画的回调函数使用参数
(function(thisBoard) {
var headerHeight = 44;
var teamHeight = 68;
for (var i = 0; i < thisBoard.teamCount; ++i) {
var teamId = thisBoard.teamNextSequence[i].teamId;
//延时1.2s更新位置,为了等待题目状态更新完成
if(toPos != -1)
$("div[team-id=\"" + teamId + "\"]").animate({ margin: 0 }, 200).animate({ top: i * teamHeight + headerHeight }, 1000, function() {
thisBoard.noAnimate = true;
});
else
$("div[team-id=\"" + teamId + "\"]").animate({ margin: 0 }, 200 ,function() {
thisBoard.noAnimate = true;
});
}
})(thisBoard);
}
/**
* 按下按键时调用的函数,包括榜更新一步的过程
*/
Board.prototype.keydown = function() {
//等动画结束后再进行下一步
if (this.noAnimate) {
this.noAnimate = false;
//更新一支队伍的状态,没有则team==null
var team = this.UpdateOneTeam();
if (team) {
//根据现在的状态更新序列
var toPos = this.updateTeamSequence();
//更新队伍HTML内容
this.updateTeamStatus(team);
//移动队伍
this.moveTeam(toPos);
} else {
//无队伍可更新时取消高亮边框
$('.team-item.hold').removeClass("hold");
}
}
}