作业要求:https://edu.cnblogs.com/campus/ustc/InnovatingLeadersClass/homework/2231
项目源码:https://github.com/jackroos/golden_number
黄金点游戏简介
N个同学(N通常大于10),每人写一个0~100之间的有理数(不包括0或100),交给裁判,裁判算出所有数字的平均值,然后乘以0.618(所谓黄金分割常数),得到黄金点G。提交的数字最靠近G(取绝对值)的同学得到N分,离G最远的同学得到-2分,其他同学得0分。
项目简介
本次结对编程的项目是每个两人小组编写一个黄金点游戏的bot,然后进行上下半场各400轮的黄金点比赛。
比赛规则
-
假设有M个玩家, (P_1, P_2, P_3, ..., P_M)
-
在 (0-100) 开区间内,所有玩家自由选择两个正有理数数字提交(可以相同或者不同)给服务器,假设(N_{11}, N_{12},N_{21},N_{22}, ..., N_{M1},N_{M2})
-
等(Mcdot2)个数字都提交后,服务器做如下计算:
[(N_{11}+N_{12}+N_{21}+N_{22}+…+N_{M1}+N_{M2})/(Mcdot2)cdot0.618 = GN ] -
由此得到黄金点数字(GN)
-
查看所有玩家提交的数字与(GN)的算术差的绝对值,值最小者得分,值最大者扣分。其它玩家不得分
-
此回合结束,进行下一回合,多回合后,累计得分高者获胜
计分规则
-
(M)个玩家比赛时,每轮离(GN)最近的玩家得(M)分,最远的扣2分,其它玩家不得分
-
如果一个玩家在一轮内提交两个相同的数字并得分时,只计一次分
-
多个玩家在一轮内同时离(GN)最近时,每个玩家都得(M)分
接口设计与实现
项目包含的内容
- 黄金点游戏的bot
- 为了测试我们的bot的表现,除了助教提供的复盘程序和以往比赛的数据可以用来测试外,我们小组还为此自己写了一个简单的黄金点游戏服务器,然后通过写一些简单bot,和我们的bot一起进行模拟比赛。
源代码文件结构
-
golden_number/
- bots/
- bot0.py
- bot1.py, bot2.py, ..., bot6.py
- env/
- golden_number_server.py
- run_golden_number_game.py
- student/ (课程助教提供的复盘程序,这里不做展开介绍)
源代码中主要包括三个源文件bot0.py, golden_number_server.py, run_golden_number_game.py:
- bots/
-
bot0.py 我们组的黄金点游戏bot源代码
-
golden_number_server.py 一个可以多进程调用bots的黄金点游戏server
-
run_golden_number_game.py 示例程序,调用我们自己的黄金点游戏server进行黄金点比赛
注:bots文件夹中的bot1.py ~ bot6.py是一些简单策略的bot,可以用来模拟比赛。
接口设计与实现
-
bot0.py源代码接口及函数结构
-
通过标准输入得到历史数据
-
通过标准输出输出提交的两个数,中间以' '分隔
-
bot核心思想:
- 假设所有玩家的策略是与上一轮黄金点相关的,那么可以基于历史数据统计出所有玩家的每一轮提交的两个数的平均值与上一轮黄金点的比例的概率分布,通过概率分布预测他们下一轮所提交的两个数的平均值,进而可以求得自己应该提交的数。
- 另外可能有些玩家基于“捣乱”策略,所以对于经常提交大数或小数的玩家,我们会计算他们提交大数或小数的概率以及大数/小数的均值,以此来预测他们下一轮的提交值。
-
bot0.py 中的核心函数:
-
get_stat
- 功能:通过历史数据,统计出上面所说的概率分布
-
pred
- 功能:通过概率分布,求出别的玩家下一轮提交的数的平均值
-
bot
- 通过历史数据,调用get_stat和pred后,计算我们应该提交的数,并进行标准输出
-
其他:处理标准输入数据的部分这里不做展开介绍。
-
-
-
**GoldenNumberServer **类接口
- 构造函数:
- 输入:
- players_bot_list, 即所有玩家的bot的源代码路径的列表
- 输入:
- 成员函数:
- run
- 功能:从所有bot得到该轮提交的数字,并更新他们的得分
- 实现:实现时利用了multiprocessing库多进程获取bot提交的数
- reset
- 功能:重置比赛数据,开始新的比赛
- _get_number
- 功能:调用bot指定的路径的bot源代码,得到该bot提交的数
- _update_history_str
- 功能:更新历史数据的字符串(history_str_head, history_str_body)
- run
- 成员变量:
- players_bot_list: 玩家的bot路径列表
- scores:玩家的得分情况
- history_str_head:要提供给bot的标准输入的第一行,包括历史数据轮数以及每轮的数据的个数
- history_str_body:要提供给bot的标准输入中的主体部分,即历史数据
- pool: 多进程pool
- 构造函数:
Design by Contract(契约式设计)
-
我觉得契约式设计的好处是可以在编程的时候就知道输入数据的合法类型和范围,这样在编写代码的时候能够减少很多边界条件/异常情况的处理,而将注意力集中于主要算法的设计。他的缺点就是不适用于很多只在内部调用的模块,内部调用的库和函数,没必要进行这样的契约式设计,在内部调用的时候一般不会出现类型错误或者不合法值。
-
我们项目中的契约主要就是bot主程序代码读取历史数据和输出提交的数都是通过标准输入/输出,然后输入/输出也符合提前约定好的格式,比如数与数之间用制表符分隔等。
代码规范及异常处理
我们没有显式的去定义我们的代码规范,只是根据Python语言中比较公认的一些规范进行编码,然后尽量把每个独立的部分单独成函数,这样便于测试和改进代码。然后关于异常处理,由于假设输入输出都是标准输入输出,格式是预先定好的格式,所以我们的代码也没有进行什么特别的异常处理。
结对编程
-
结对过程:
- 结对后,刚开始我们进行了讨论,然后一开始我们的想法是利用强化学习的方法,然后通过设计一些对手,来训练我们的智能体,但可能由于我们设计的对手太局限,这一想法在实验时发现智能体选择很简单的策略(上一轮黄金点乘以某一固定常数)就能赢下对手,但这显然并不能在真实中获胜;后来,我们又进行了讨论后,由于时间紧迫,我们决定换一种策略,基于统计对手的提交历史数据的方法来预测对手的下一轮提交值,然后编写了最后提交的bot。
-
我们在结对编程的前半阶段采取的是分工的方式,后半阶段采取的是驾驶员和领航员的合作方式。
-
结对编程的优点和缺点:
- 优点:结对编程可以互相学习,互相借鉴对方的编程技巧;结对编程时能迫使编码者更加集中注意力进行编程,提高代码质量;结对编程的过程中代码可以立即被复审,可以减少代码中的bug。
- 缺点:在结对编程时,会增加编程时的压力,因为写代码时需要向对方展示自己的思路,这可能会使得有些人没法自由发挥,在代码选择中偏向于选择易于理解的代码,而不是效率较高的代码。
-
我们各自的优缺点:
- 我的优点:
- 熟悉numpy等科学计算库,能较快利用numpy的向量化操作实现函数
- 熟悉封装函数类
- 善于对每个模块进行测试
- 我的缺点:
- 不够细心,有时会出现一些例如没有进行深拷贝的错误
- 同伴的优点:
- 复审代码时非常细致,经常在早期就能发现我编码上的错误
- 善于通过查找资料快速解决问题
- 代码较简洁,可读性强
- 同伴的缺点:
- 对python中numpy等的向量化操作不是很熟悉
- 我的优点:
其他收获
- 在这次黄金点游戏的比赛中,我体会到的一点就是在刚开始做项目的时候,应该先完成一个初步版本,然后再通过实验不断改进,而不要所有事情都想一步到位,反而会什么都没做成。
附录
- 作业要求中关于7、8、9 界面模块的方面,由于我们的结对编程项目侧重于写黄金点游戏的bot,界面只有简单的命令行输出,所以没有在博客中叙述界面模块的内容。
- 作业要求中的PSP表格由于我们当时并没有进行这一步骤,所以无法在博客中给出。
- 结对编程时,我们忘了进行拍照,所以没有讨论时的照片。
- 关于UML图,由于我们的代码大部分都是调用numpy等库,自己的代码中主要是一个函数类和一个bot的主程序,所以没有画UML类图。