我打造了一个 Codeforces 辅助工具(开发心得)
在考完 GDKOI 的那天下午,我在机房和同学快乐 generals。众所周知,我在做一件事情的时候可以在脑子里进行另外一件事的思考(?)。得知最近 Codeforces 比赛很多的信息,我开始回想自己打 Codeforces 的经历。
什么是在打 Codeforces 最令人痛苦的事情?无非在 m1/m2/m3 打比赛想看排名和 Predictor,但是只能等镜像站加载完网页才能看到。而且还没有 hack 提示什么的。
一个答案的想法在脑子里浮现。如果可以像 TrafficMonitor(一款可以在任务栏和桌面显示网速、CPU、内存信息的程序,可以免去看任务管理器的时间)一样制作一个可以方便查看 Codeforces 排名数据的东西,那该多好啊……
于是,一切就这么开始了。
首先,我用画图软件做了一个大致的草图,发到了谷群和 LA 群询问如何。在收到大家的积极反应之后,我决定开始着手于这项软件的开发。
开始选择开发语言。我本来打算使用 Qt 一类的编程语言进行编写,但是考虑到自己并不熟悉这些操作,而且当时还不知道怎么用 API,就暂时放弃了。最后,我选择了 HTML/JS/CSS 进行开发。
在第二天,我先将草图中的部件摆了出来,中途临时学了很多 HTML 标签和 CSS 技巧。
然后就遇到了第一个问题:如何使用 API?
Codeforces 提供了 API 文档,直接按照自己的需求调用就行。直接点进去也可以看到对应结果。但是现在需要用 JS 捕获这些结果,还要转化为对象。
由于我本人是一个 JS 的新手,在这方面非常不熟悉,所以我开始百度。一开始是使用了 $.getJSON(url, function(json){}) 方法。这个方法使用起来非常方便,还没有什么需要注意的地方。但是,在我准备提出当前的加载信息的时候,问题出现了。 $.getJSON() 似乎并不能实现这个功能。
在最后更换了 $.ajax(),传入了 xml 才解决问题。
在获取这些信息之后,我们就可以通过很多的数据获取需要的内容并显示出来。实时排名也只需要每 30 秒更新数据就行。
在那之后,我就开始进入另一个问题的思考。在我们开始 Virtual Contest 的时候,可以看到自己的比赛排名是和比赛的人同步的,但是通过 API 获取的信息只有平直的线条(也就是把其他人当作打完了)。获取 Virtual Contest 排名成为了第二个难题。
此时,我找到了一直辅助我管理仓库的 @qinyihao ,拟了一份消息发到了 Mike 的邮箱。过了一两天,那个消息还是 Unread(到现在也还是),于是我准备自己写。
我一开始打算把 Status 和 Hacks 全部获取然后推算,但是光是 Status 的获取就需要 2-3 分钟时间,完全无法承受。于是我换了一种思路,获取每个人最后提交的时间,也就是排行榜(毕竟没有多少人会在 AC 后再交一发),然后在当前时间的基础上取提交时间快于当前时间的记录进行算分,最后把 CF 赛制的 -50 和 ICPC 的 Penalty 连带 hack 记录一起算上,就完成了一个人在一个时刻的大致分数预测。最后只需要进行比较分析有多少个人分数比该用户高,就解决了问题。
代码也很简单。
function getPredictedRank(points,penalty,time){
if(penalty==undefined) penalty = 0;
var returnValue = 1;
for(var i=0;i<StandingsList.length;i++){
var _points = 0, _penalty = 0;
for(var j=0;j<StandingsList[i].problemResults.length;j++){
if(StandingsList[i].problemResults[j].bestSubmissionTimeSeconds!=undefined
&& Math.floor(StandingsList[i].problemResults[j].bestSubmissionTimeSeconds/60)<=Math.floor(time/60)){
_points += StandingsList[i].problemResults[j].points;
var _dalta = StandingsList[i].problemResults[j].penalty;
if(ContestType == "ICPC")
_dalta = Math.floor(StandingsList[i].problemResults[j].bestSubmissionTimeSeconds/60)
+StandingsList[i].problemResults[j].rejectedAttemptCount*10;
_penalty += (_dalta == undefined ? 0 : _dalta);
}
}
if(ContestType == "CF" && hackList[StandingsList[i].party.members[0].handle]!=undefined){
for(var j=0;j<hackList[StandingsList[i].party.members[0].handle].length;j++){
if(hackList[StandingsList[i].party.members[0].handle][j][0]>time) break;
_points += hackList[StandingsList[i].party.members[0].handle][j][1];
}
}
if(points < _points || (points == _points && penalty > _penalty))
++ returnValue;
}
return returnValue;
}
一个人在一场比赛的预测排名曲线只不过是以 30 秒为步长算出排名最后组合在一起罢了。排名预测器就这么完成了。
当然,这个排名预测器还有很多的缺陷,比如前面所说的重复提交 AC 记录,以及一些以分数少的为胜的题目会出现排名问题。但是整体排名预测不会偏差过大,大家可以放心使用。
在这个时间点,我认为自己开发的 Codeforces Contest Helper 已经到达了一定的步骤,可以进行推荐了。于是我请 @qinyihao 将代码压成 exe 文件并更新,我也爆声出了一次讲解(qwq)。在之后,我也推出了网页版,解除了一些 NW.js 的限制。现在大家可以前往 Gitee 网站进行体验。
开发过程中第三个问题是我自己挖的坑。在折线图上显示一条折线,总觉得很孤单,可不可以做到将其他人的数据也拉取进行分析呢?
这个被戏称为“多人运动”的功能在提出的时候,我本人正在回老家的高铁上。于是我开电脑花了一小时写好了表达式匹配规则,然后用两个小时把接口函数什么的进行了大改。主要的修改是把需要获得参数的人放在一起拉取数据,然后将数据分开处理后拿到用户需要的数据,最后把预测接口改成了任意一个人的数据都可以使用的形式。
在三个小时的奋斗后,多人运动的功能也算是写好了。在显示多人运动的时候分数条将不会渲染,避免重叠带来的不良效果。
当然,这个工具还有很多非常使用的设置,比如 show unofficial, 亮暗色切换、提交记录查看等功能,欢迎大家的探索。
使用这个工具的方法在 Github 仓库的中文文档和 wiki 中,可以前往这里进行查看。
这个项目由于长时间没有出现除了 NW.js 特性之外的 issue 所以一直没有更新,但是维护仍然在进行当中。大家在使用中发现了任何问题,也欢迎大家发 issue。