Robocup2D 的国赛终于结束了,虽然只拿到了第五,但对于我来说,成绩好像从来都不是什么很重要的事情。作为我整个大学生涯接触的第二长的项目,我想着是时候,也有必要写点什么,这样才不至于感到遗憾。

序章

故事得从某个疯狂的学期末开始,大一结束的那个暑假,我留在学校参加自动化院的夏令营。我花了 300 买了台古老的 IBM 服务器,在宿舍瞎折腾。那时还是 Apollo3D 组成员的室友看到了,问我要不要去他们 2D 组试试,说是那边人比较少,而且水平不太够。在我以菜为名,推脱了一下之后,好奇心终于战胜了面子,我决定去 2D 组试试运气,顺便弥补下大一没参加社团的遗憾。然后我就认识了某位李姓帅哥。

李总,Apollo 上任 2D 组组长,帅。他告诉了我这大概是个什么项目,要比什么,顺便发来了一份源码的压缩包。我记得我花了不少时间去装比赛环境,然后顺着 rcsc 项目的官网找更多的资料。但当我拿着官网资料找李总的时候,李总开始惊喜:“你从哪找到这个资料?”,我说:“官网呀”,“原来还有官网”。李总的英语并不是很好,他的资料大多数来自中文互联网——或者更准确一点:CSDN。我的资料搜索能力这似乎成为了一个不错的优势,李总在接下来的全组组会上把没到场的我推成了下任组长。就这样,一个最晚来的人,反倒成为了这支迷茫队伍的组长。

新人与队友

虽然感觉很怪,但是这组长又不是不能当。我的第一个活就是给这支队伍招新。我跑到学校表白墙大肆宣传了一波,然后果然收获了不少年轻人的申请。可能是我刚经历过青柚的面试,我决定在 apollo 也搞一搞面试。我记得有个懂球的小伙子很我聊了很久,从技术到足球,再从足球到技术,我也从一开始的严肃到后面的随意,感觉这压根不是面试,就是纯粹聊天。也有个小伙子说是自己学的特别好,然后一考就一问三不知了。总之这就像是很随意的聊天,不过终归是招到了不少人。我的原则也很简单,先把人招进来再说。不过在不久的后来,我会为这个选择后悔的。

当大二生活开启的时候,我那 3D 组成员的室友已经成了整个 Apollo 的队长,他拉着我去跟他培养新生,然后在 2D 和 3D 联合培养的课堂上:会的已经会了,不用来也学的会,不会的还是不会,也压根没打算学会。这个时候我开始好好反思自己,是不是对他们的期望有点过高了。在我反省的同时,我终于见到了我的队友们,他们此前有的打过智能车比赛,有的是电子部大佬,当然也有的比较沉默。他们或多或少参与过这个项目的授课了——虽然只进行过一次,这是李总自己搞的,但总比哪都没去的“宿舍蹲”的我好。我很期待什么时候推进一下这些工作,但我想等一等林总,他在打电赛,我感觉他是这个项目的关键人物之一,后来也确实如此。

在和这些新人与队友交流的同时,我也尝试去看看能不能为整个项目做些什么,不过在此之前,我想是不是该做点准备工作。我首先把 Apollo 的源码丢到了 gitlab 上面,然后教会了其他人怎么用 git,我还记得大我一届的李总看到 git 之后的惊喜表情。然后我把所有资料收到了一份表格中,这个时候林总也回来了,我们把项目理了一遍,其实到这里,整个项目的架构在我心中已经很清晰了,只是细节不太清晰,导致不知道从哪开始修改。不过感觉等等队友也不是什么糟糕的选择,结果这一等,就等到了期末,大二的第一个学期很快就结束了。

DIY

经历完痛苦的期末考试之后,我决心一定要在这个寒假里做点什么。我们的目标定的很高,打算让所有新人看一遍代码,然后把整个底层从 10 年来没变过的 agent2d 迁移到 helios-base,其中对 librcsc 的适配更是重中之重。我精细地划分了任务,制定了阶段计划并指定了责任人,然后在中期发现根本没有人在看代码!至于我的那部分,我在第一天就看完了,顺便还帮某位队友看完了他的部分。当这个寒假快结束的时候,我已经为整个项目的升级做好了准备,我研究了 CMake 与 Makefile,了解了 rcssserver、monitor 和其他的比赛周边环境,学习了 librcsc 的架构。但就是没有去做真正的迁移,所以在还有一周就开学的时候,我想,为啥不干脆我自己来呢?

我找了一份 helios-base,然后写了一套 CMakeList,替换掉原来的 autoconf。然后把原来的源码文件一个文件一个文件复制粘贴过来,我还写了个脚本,用于在 vscode 中快速对比原来的文件与 helios-base 中的文件的区别,如果新的 更好,我会选择直接用新的,在所有 100 多个文件都复制过来之后,我尝试编译整个项目,当出现报错的时候,我就去看看这个函数或是变量是否被更名或是被更改了类的结构。这里还得感谢 helios-base 的作者秋山教授,在 git 还没有流行开来的那几年,他用 CHANGELOG 记录了他的所有变更。vscode 的全局替换功能能帮我解决不小的麻烦,为了更精准地替换,我还补了不少正则表达式的知识。我一次又一次地编译这个工程,一次又一次替换那些出错的函数,一次又一次修改写错的 CMakeLists。在我做出自己来这个决定的第二天晚上,这个使用新 librcsc 库的 Apollo 源码成功通过了编译。

我记得那天我非常激动地向群里的队友们说我一个人就把迁移完成了,然后好像没有人理我哈哈哈。其实事情到这里还没完全结束,接下来我尝试了运行新的球队,并和老的球队进行比赛,看看更换底层是否会影响到球队的表现。然后就发现一方面球队的阵型文件已经过时了,另一方面球队的罚球(因为没有点球,用的是那种老式的点球方式)会出现跑不起来之类的 bug。于是我又花了不少时间去重新看看 librcsc 的 CHANGELOG,发现了 librcsc 自带的几个小工具,然后还问了问秋山教授他当年怎么处理数据转换的,他说这玩意很简单,让我自己手写,所以我就手写了一个,这样就解决了阵型格式文件不一致的问题。而罚球也是一样,我先是找到了一个合理方案,然后请教了秋山教授,确认这个方案没得问题后写了上去。

在出发去学校前的倒数第二天,我终于完成了所有的升级任务,新的球队似乎和老的并没有太大差距。至此,一项伟大的任务完成了。

选拔与 HFO

当大二的第二个学期开始的时候,我想做的第一件事就是开启选拔。我当然不能期望每一个接触过这个项目的人最终都能成为一个合格的队员,毕竟每个人都有自己的生活,哪怕是我,也没有把最多的精力放在这个项目上,不过当我们需要推进一些工作的时候,我还是希望能有一些靠谱的队友来一起完成一些工作。

恰好之前接触过 HFO 这个项目。这个项目把半场攻防作为主要目的,研究在半场攻防情况下双方的攻防情况。一开始我是想学习隔壁 3D 项目,让新人们自己写一只球队然后互相进行半场攻防,然后根据结果选拔的,结果发现没有哪个人能自己写球队。恰好 HFO 这个项目也有一堆缺陷,首先是 HFO 与某个 rcssserver 版本绑定了,当 rcssserver 升级后,HFO 并没有跟着升级。另一方面 HFO 所需要的环境和新版本的 rcssserver 版本是冲突的,对于我来说这倒不是什么问题,但是对于一些新人来说,光是解决这些环境问题就已经够麻烦的了。所以,我做了一些突破性的尝试,我把 HFO 中魔改的 rcssserver 中的 HFO referee 部分改写成了我们球队中的 trainer,我对这个全新的 trainer 做了大量的设计,这很考验我的 Cpp 功底,不过好在对 rcssserver 和 librcsc 的代码都足够了解,这个 trainer 很快写成了,一开始,这个 trainer 被用于模拟 HFO 场景,后来我突发奇想,不如让新队员写一个最简单的追球动作吧。于是我改出了一份专门用于“发球”的 trainer,这个 trainer 会按照设置把球往某个角度以某个速度发射出去,然后会自动计算球员追到球所需花费的时间。接下来我把这个 trainer 打包到 helios-base 中,并设置了一系列角度和速度的情况,然后我写了几个脚本方便编译和测试并附上了长长的 README 来说明选拔的要求和规则。最后我还找到了隔壁 3D 组要了 5 台老旧的服务器,将其中 4 台用于比赛,另一台用于收集和实时展示数据。

这次选拔只进行了半个月多一点的时间,我在前面几天说明了情况,然后各位同学就开始各显神通了。其实追球是一个相当简单的动作,一方面你可以手写一下重新实现追球这个动作,另一方面你可以直接调用底层库 librcsc 中的内容。最终拿下前二的两位同学就是这两个思路的代表。鸡米花同学发挥了他扎实的算法功底,把追球的时间压缩到了最低,拿到了绝大多数情况下的第一,但是在剩下的一些情况中,他的写法会导致追不到球。而刘总在选拔开始的第二天就写完了代码,而且再也没改过。他在几乎所有情况下都拿下了第二,而他的代码只有短短 5 行,因为他直接调用了某个底层库提供的函数。剩下的同学有的也有不错的优化,被我中途拉进来的小小于依靠不赖的算法能力拿到了第三,少数情况下还拿了几个第一。这些同学后来真正成为了能改变这支球队的力量。

值得一提的是,还有一些同学选择了放弃比赛。这其实是一件在正常不过的事情,光靠这样一个比赛去说明一个人的能力或是做出一些评价并不是一件公平的事情。但对于我自己来说,我学到的很重要的一件事情就是,并不是每个人都和你自己一样会甘心为了达成同一个目的而努力的,这和你为这些目的或这些人付出了什么是完全没有关系的。每个人都活在自己的世界,因此永远不要以己度人,拿着自己的条件去评价别人的是非。这并不是什么牢骚,仅仅是一些举办这样一次选拔这样之后的感慨。

比赛

我还记得新队员选拔出来之后,我们雄心勃勃地开始安排接下来的工作。在一开始我们也推进了一些工作,林总和刘总苦读了好几篇论文,鸡米花开始研究足球的评分系统,后来还提出了 XG 等一些列我看不懂的概念,我也写出了用于进行大量测试球队表现的 autogame,接总也改好了 loganalyzer,接入了我的 autogame。但随着时间的推进,大家都各忙各的去了,林总去打电赛了,鸡米花继续他的 icpc 生涯,接总也开始了他的无人机比赛,我也开始了自己的青柚生涯。似乎过了很长时间,我们没有管 apollo 了,这个时候大家的心态似乎都发生了一些变化,没有人在乎这场比赛了。巴西赛我们也不怎么重视,本打算拿老的代码上去随便跑跑,然后提交代码的时候无论怎么搞,巴西人那边就是跑不起来,最后只能垫底收场。然后很快又开始国赛了,这次我们提前了二十多天开始准备,刘总写好了一份守门员优化的代码,林总和后面加入的薛总写了个计算 XG 的函数。哪怕好好打了个包交上去。然后开始了等死之旅。

因为基本没啥准备,说是等死也不为过,一开始我的心态是很好的,毕竟知道自己确实没有什么特别大的进步,小组赛出线就是胜利了。但真的看起比赛直播了,还是感觉我们这比赛比隔壁的卡塔尔世界杯刺激多了。很多时候我们只能眼睁睁地看着对面踢球,只能对着看到球到处飞却没有作为的守门员无能狂怒——当然,这也怪不了别人,毕竟代码确实是自己的,没有改好是我们自己的问题。组里面就中南大学和南京大学略有竞争力,一开始以为南大很强,结果踢平了,后面以为中南也能平,结果 2:3 小败了。比较意外的是,被我们暴打的南华大学居然能和南京大学踢平。虽然很魔幻,但总归是小组第二出线了。在第一天比赛的晚上,还有一场技术挑战赛,比赛内容就是每个队上去讲讲过去一年里有什么改进。我们上去讲了讲 HFO 和 autogame,最后的提问环节中南的同学还问了一下我们 librcsc 升级的问题,这个时候我略带嚣张地回答:“我在 github 上回答过这个问题了,你们可以自己去翻”。虽然介绍的时候自己的表现一塌糊涂,但总归是段不错的经历。

第二天的比赛是排位赛,小组赛表现勇猛的中南突然就谁都踢不过了,甚至我们逼平了去年的世界第四合肥师范,比较令人惊奇的是合肥学院 MT 表现亮眼,我记得这个队是因为赛前 apollo 队长对我们的要求是:“你们起码得踢赢 MT 吧”。结果在第三天的最终天梯赛,我们打败了中南的云麓,守住了前一天拿下的第五的排名,然后 MT 在和连续三年的世界关于 Yushan 的一二名排名赛中,依靠 7500 回合(相当于足球比赛中的加时还有 15 分钟结束)打入的一粒进球绝杀了 Yushan,拿下了这届世界杯的冠军。这个时候,我倒是心态平稳了,毕竟确实小组赛出现了,想要重现去年第二的奇迹也确实没有那么好的运气和实力。在赛后的 apollo 大合照环节,我因为差点忘记这回事而错过。

回顾

故事写到这里,流水账写的也差不多了。很多时候,我觉得自己是不是对这个项目太不上心了。因为我的敷衍,巴西赛没有打成,有的新人没有留下来,甚至 apollo 的合影也差点错过,包括最后看比赛的时候,偶尔也会想一想,如果早先优化了某个技术点,刚刚这个球说不定能进/能守住呢?在我写这篇文章的时候,我想象了一下,如果当年我拒绝了室友的邀请,这个项目会怎么样呢?

首先鸡米花和刘总也许不会加入团队,因为他俩是我在表白墙上发招新拉进来的,林总也许会当上 2D 的组长,也许会从他的电子部招几个人过来。这样的话,这份代码还是会以“u 盘传 u 盘”的方式传递下去,不会上 gitlab。也不会有 HFO trainer,不会有 autogame。也许没人知道 rcssserver 的版本升级和 librcsc 的通信协议格式升级。这份代码仍然会使用 10 年未变的古老 agent2D 格式。我们还是打不上世界杯,因为我们既没有成绩也没有钱交报名费。我甚至想过,2D 组也许会死掉,3D 的同学会过来几个人,然后报名拿几个奖。当然,比赛最终的成绩好像也不会有太大变化,毕竟确实也没啥高效的修改。

这样想想的话,感觉好像一切都不算太糟,毕竟 2D 现在还活着,至少也能招到几个大一的同学,鸡米花、刘总和薛总会继续带着这个团队走下去,陷入考研泥潭的李总不久也会回来帮帮忙。但对于我来说,我的故事应该就到这里了。我曾经很迷恋 cpp 这门语言,因为似乎没有 cpp 写不出来的东西,哪怕花很多时间似乎也是值得的。但在见识了更为广阔的世界之后,我选择了不拘泥于 cpp 去探索更广阔的世界,这也是为什么从某个时期开始,我没有再推进 apollo 的相关工作了。我已经从这个项目中学到了足够多的知识,我的奉献虽然帮不上什么大忙,但也不至于一点没有。作为一个大三的当代老年人,我只能祝福下一代 2D 团队用自己的力量和我们这些老年人留下的东西去写出更好的球队。至于我自己,也要走到未来的道路上了。

The life cycle of NewApolloBase v0.0.1 has reached the end of life.