1.各链接
结对同学博客连接
本作业博客连接
Github项目地址
UI视频百度网盘
UI视频b站
2.具体分工
王嵚:负责前端实现、博客内容指导and部分材料提供
陈荣杰:负责后端算法、博客撰写
3.PSP表格
- 王嵚
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 90 |
·Estimate | ·估计这个任务需要多少时间 | 2600 | 2900 |
Development | 开发 | 3250 | 3960 |
·Analysis | ·需求分析 (包括学习新技术) | 1800 | 2000 |
·Design Spec | ·生成设计文档 | 90 | 60 |
·Design Review | ·设计复审 | 120 | 120 |
·Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 50 |
·Coding | ·具体编码 | 400 | 560 |
·Code Review | ·代码复审 | 450 | 720 |
·Test | ·测试(自我测试,修改代码,提交修改) | 240 | 300 |
Reporting | 报告 | 130 | 115 |
·Test Repor | ·测试报告 | 50 | 60 |
·Size Measurement | · 计算工作量 | 20 | 10 |
·Postmortem & Process Improvement Plan | ·事后总结, 并提出过程改进计划 | 20 | 10 |
合计 | 3440 | 4165 |
- 陈荣杰
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
·Estimate | ·估计这个任务需要多少时间 | 2200 | 2600 |
Development | 开发 | 400 | 460 |
·Analysis | ·需求分析 (包括学习新技术) | 120 | 240 |
·Design Spec | ·生成设计文档 | 40 | 30 |
·Design Review | ·设计复审 | 30 | 20 |
·Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
·Coding | ·具体编码 | 1000 | 1200 |
·Code Review | ·代码复审 | 90 | 90 |
·Test | ·测试(自我测试,修改代码,提交修改) | 300 | 300 |
Reporting | 报告 | 60 | 60 |
·Test Repor | ·测试报告 | 60 | 60 |
·Size Measurement | · 计算工作量 | 20 | 20 |
·Postmortem & Process Improvement Plan | ·事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 2000 | 2400 |
4.解题思路描述与设计实现说明
网络接口的使用
根据样例的提示,调用 requests库中的get/post函数实现get/post请求,把如登录、注册等等操作所需要的请求封装成 不同函数放在一个httpfunctions.py文件下,使用时导入此文件就可以了。
具体代码如下:
def entry(account):#注册+绑定接口 url='http://api.revth.com/auth/login' data={ "username": account["username"], "password": account["password"] } headers={ "Content-Type":"application/json" } response=requests.post(url=url,headers=headers,data=json.dumps(data),verify=False); print(response.text) r = response.json() return r
代码组织与内部实现设计(类图)
- 后端算法代码组织与内部实现设计
如果是特殊牌的话,服务器会自动识别,也就是说只要分堆就好,因此不考虑特殊牌的判别,直接对拿到的手牌进行组合,利用组合的思想对所有牌型进行比较,择取最优选择。 - 前端代码组织与内部实现设计
说明算法的关键与关键实现部分流程图
- 算法关键
- 分墩算法:对手牌进行组合,遍历一副手牌可以组成的所有情况,将当前的组合与当前的最优组合进行比较,按照等级及牌面对应权重矩阵赋权值,三墩相加再比大小,比到最后可以得出一个权值最大的组合,就选中这个组合为最优。
- 因此算法关键应该是对牌型合法的判断和判牌的策略,初始定的赋权值策略是按照牌型等级及牌面直接赋值,可在实测是产生了许多意料之外的bug,debug到自闭,后来了解到有权重矩阵的存在,摸了摸头顶,果断选择改变策略。
关键实现部分流程图
5.关键代码解释
- 后端:
- 先处理传入字符串,将其转化为列表类型数据,每个列表的第一个值为牌面值,第二个值为花色,方便操作,再利用组合的思想对所有牌型进行比较,择取最优牌型
def do(lll):#算法主函数,执行分墩步骤 sda = [#十三水权重矩阵,亲测可用,建议上手 [ # 1 2 3 4 5 6 7 8 9 T J Q K A JUNK [0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 4, 7, 14, 33], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A PAIR [0, 46, 48, 50, 51, 54, 56, 60, 63, 68, 74, 81, 89, 97], [0, 2, 3, 4, 4, 5, 7, 8, 10, 12, 15, 19, 24, 33], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 3]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A TWO_PAIRS [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 36, 37, 38, 40, 44, 46, 49, 54, 57, 62, 64, 0], [0, 0, 2, 3, 4, 4, 6, 7, 8, 10, 11, 13, 13, 0]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A TWO_PAIRS [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 31, 38, 39, 41, 45, 47, 50, 55, 58, 63, 65, 0], [0, 0, 3, 4, 5, 5, 7, 8, 9, 11, 12, 14, 14, 0]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A TRIPLE [0, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [0, 63, 65, 69, 71, 72, 73, 73, 73, 74, 74, 75, 75, 75], [0, 11, 12, 14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A STRAIGHT [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 77, 78, 81, 83, 85, 87, 88, 90, 91, 92], [0, 0, 0, 0, 16, 17, 20, 22, 24, 26, 28, 32, 33, 36]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A FLUSH [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 93, 93, 93, 93, 94, 95, 97, 98], [0, 0, 0, 0, 0, 0, 36, 36, 37, 38, 40, 44, 49, 61]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A FULL_HOUSE [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 98, 98, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100], [0, 64, 67, 70, 71, 73, 75, 77, 80, 82, 85, 88, 91, 94]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A FOUR_OF_A_KIND [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100], [0, 93, 94, 95, 95, 96, 96, 96, 97, 97, 98, 98, 98, 98]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A STRAIGHT_FLUSH [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 100, 100, 100, 100, 100, 100, 100, 100, 100, 0], [0, 0, 0, 0, 98, 98, 99, 99, 99, 99, 99, 99, 100, 0]], [ # 1 2 3 4 5 6 7 8 9 T J Q K A ROYAL_FLUSH [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100]] ]; card=lll card_type=["","",""]#牌型 matrix=[[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,],[0,0,]]#拿到的牌的列表,即为牌库 dun=[[],[],[]]#分墩后存储的位置 j=0; a=0; max=0 for pai in card:#遍历字符串,转换需要的列表数据即拿到的牌 if pai=="*":i=4; elif pai=="$":i=1; elif pai == "&": i = 2; elif pai=="#":i=3; if pai.isdigit(): if pai=="1":matrix[j][0]=10;matrix[j][1]=i;j=j+1; elif pai!="0":matrix[j][0]=int(pai);matrix[j][1]=i;j=j+1; elif pai=="J":matrix[j][0]=11;matrix[j][1]=i;j=j+1; elif pai=="Q":matrix[j][0]=12;matrix[j][1]=i;j=j+1; elif pai=="K":matrix[j][0]=13;matrix[j][1]=i;j=j+1; elif pai=="A":matrix[j][0]=14;matrix[j][1]=i;j=j+1; for i in combinations(matrix, 5):#调用combinations函数得出matrix的组合 sum=0; test= list(i) test_data1 = matrix.copy()#备份牌库 test = sorted(test, key=lambda x: (x[0], -x[1]))#排序,以牌面升序排序 if test[0] in test_data1:#将选出的牌从牌库中去掉以免影响后续操作 test_data1.remove(test[0]) if test[1] in test_data1: test_data1.remove(test[1]) if test[2] in test_data1: test_data1.remove(test[2]) if test[3] in test_data1: test_data1.remove(test[3]) if test[4] in test_data1: test_data1.remove(test[4]) for k in combinations(test_data1, 5):#从牌库剩余的8张牌中,抽出5张得到中墩,余下3张为后墩 test_data2=test_data1.copy()#备份 if k[0] in test_data2:#将抽出的牌从备份牌库中去掉 test_data2.remove(k[0]) if k[1] in test_data2: test_data2.remove(k[1]) if k[2] in test_data2: test_data2.remove(k[2]) if k[3] in test_data2: test_data2.remove(k[3]) if k[4] in test_data1: test_data2.remove(k[4]) pp=list(k) test1=list(sorted(pp, key=lambda x: (x[0], -x[1])))#排序,以牌面升序排序 test2=list(sorted(test_data2,key=lambda x: (x[0], -x[1])))#排序,以牌面升序排序 list1=list(operation(test))#判断各个分墩的牌型以及此牌型中牌面最大的值,如炸弹中炸弹的牌面、两对中最大的对子牌面,返回牌型等级及牌面最大值 list2=list(operation(test1)) list3=list(ope(test2)) if list1[0] < list2[0] or list2[0] < list3[0] or list1[0] == list2[0] and list1[1] < list2[1] or list3[0] ==list2[0] and list2[1] < list3[1]or (list1[0]==list2[0]==7and list1[2]<list2[2]):#根据牌型等级及牌面最大值判断分墩是否合法,若不合法则跳过后续步骤直接下一个循环 continue else: value1 = sda[list1[0] - 1][2][list1[1] - 1]#从权重表中取得对应权值 value2 = sda[list2[0] - 1][1][list2[1] - 1] value3 = sda[list3[0] - 1][0][list3[1] - 1] sum = value1 + value2 + value3 if sum > max:#比较三墩总权值,大的留下 dun = [test2, test1, test] max = sum card_type[2] = get_type(list1[0])#将牌型对应名称赋值进对应墩 card_type[1] = get_type(list2[0]) card_type[0] = get_type(list3[0]) s = jiema(dun)#将处理完后的列表转换为字符串数据 s=s+["三墩牌型:"]+card_type print(s)#输出分墩情况及牌型 return s
- 判断各个分墩的牌型以及此牌型中牌面最大的值,如炸弹中炸弹的牌面、两对中最大的对子牌面,返回牌型等级及牌面最大值
def operation(list):#判别牌型,返回牌型对应数字与此牌型中关键牌值,传入参数升序排列的列表 sum=[0,0,0]; a=[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]]#a[0]代表此位置对应牌的牌面,a[1]代表此牌面的牌有几张,可通过a[1]判断炸弹、葫芦、三条等有多张同牌面的牌型, for list1 in list: for count in range(5): if a[count][0]==list1[0]: a[count][1]=a[count][1]+1 break elif a[count][0]==0: a[count][0] = list1[0] a[count][1] = a[count][1] + 1 break count=0 if(list[0][0]+4==list[1][0]+3==list[2][0]+2==list[3][0]+1==list[4][0]):#按序排列 if list[0][1]==list[1][1]==list[2][1]==list[3][1]==list[4][1]:#同花色 sum=[10,list[4][0]]#同花顺 return sum else: count=0 for jojo in a: if jojo[1]==4: sum= [9,jojo[0]]#炸弹 return sum elif jojo[1]==3: if a[a.index(jojo)+1][1]==2: sum= [8,jojo[0]]#葫芦 return sum elif list[0][1]==list[1][1]==list[2][1]==list[3][1]==list[4][1]: sum=[7,list[4][0],list[3][0]]#同花 return sum elif (list[0][0] + 4 == list[1][0] + 3 == list[2][0] + 2 == list[3][0] + 1 == list[4][0]): sum=[6,list[4][0],list[3][0]]#顺子 return sum else : sum=[5,jojo[0]]#三条 return sum elif jojo[1]==2: if a[1][1]==3: sum= [8,a[1][0]]#葫芦 return sum elif list[0][1] == list[1][1] == list[2][1] == list[3][1] == list[4][1]: sum =[7, list[4][0],list[3][0]] # 同花 return sum elif (list[0][0] + 4 == list[1][0] + 3 == list[2][0] + 2 == list[3][0] + 1 == list[4][0]): sum = [6,list[4][0]] # 顺子 return sum elif a[a.index(jojo)+1][1]==2: if jojo[0]+1==a[a.index(jojo)+1][0]: sum= [4,a[a.index(jojo)+1][0]]#连队 return sum else: sum=[3,a[a.index(jojo)+1][0]] #两对 return sum elif a[a.index(jojo)+2][1]==2: sum =[3 ,a[a.index(jojo)+2][0]] # 两对 return sum else: sum=[2,jojo[0]] #对子 return sum if list[0][1] == list[1][1] == list[2][1] == list[3][1] == list[4][1]: sum =[7 ,list[4][0] ,list[3][0]]# 同花 return sum elif (list[0][0] + 4 == list[1][0] + 3 == list[2][0] + 2 == list[3][0] + 1 == list[4][0]): sum = [6 ,list[4][0]] # 顺子 return sum else: sum = [1,list[4][0]] return sum if list[0][1] == list[1][1] == list[2][1] == list[3][1] == list[4][1]: sum =[7,list[4][0]] # 同花 return sum elif (list[0][0] + 4 == list[1][0] + 3 == list[2][0] + 2 == list[3][0] + 1 == list[4][0]): sum = [6 ,list[4][0]] # 顺子 return sum else: sum = [1,list[4][0]]#散牌 return sum return sum def ope(list3):#求前墩的牌型,返回值与operation()相同 sum=0 if list3[0][0]==list3[1][0]==list3[2][0]:#三条 sum=[5,list3[2][0]] return sum elif list3[0][0]==list3[1][0]:#对子 sum=[2,list3[1][0]] return sum elif list3[2][0]==list3[1][0]:#对子 sum=[2,list3[1][0]] return sum else: sum=[1,list3[2][0]]#散牌 return sum
- 前端:
6.性能分析与改进
改进的思路
- 一开始定的权值策略不佳,导致容易出现葫芦拆成三条和对子,甚至偶尔会出现不合法的情况,后来苦心查阅各方资料
问大佬同学,了解到权值矩阵的存在,一用上就体会到前所未有的顺滑 - 用了权值矩阵之后,偶尔还是会有报错,多次debug后发现是在判断合不合法的条件语句出问题,后面改成判断是否不合法,相对简单一些。
- 一开始憋着一股劲,一股脑把算法敲出来,后来发现敲出来的东西bug实在太多,其实应该再开始前就先查阅资料,了解背景,然后打码的时候也得保持冷静,大脑发热万万不可
性能分析图和程序中消耗最大的函数
本次性能分析是利用cprofile--python性能分析工具
7.单元测试
- 下图是测试用到的函数,代码比较长,限于篇幅不全展开
- 主函数代码展示
class TestDo(TestCase): def test_best_cards_type(): card_dic = [] for i in ['*', '#', '&', '$']: for j in ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']: card_dic.append(i + j) num = 10 while num: cards = "" s = copy(card_dic) for i in range(13): j = numpy.random.randint(0, len(s)) if i != 12: cards += s[j] + ' ' else: cards += s[j] s.pop(j) num -= 1 do(cards) test_best_cards_type()
- 对do函数即分牌函数进行单元测试
测试数据:随机生成,不特意构造
测试思路:利用随机生成的数据对division进行测试,可完成对其实用性和可靠性检测 利用coverage对测试程序进行覆盖率分析
- 分析图
- 分析图
8.Github的代码签入记录
9.遇到的困难及解决方法
- 陈荣杰
困难描述 | 解决尝试 | 是否解决 | 有何收获 |
---|---|---|---|
排行榜、历史战局、登录注册要从服务器得到数据并展示 | 查找资料、根据十三水api上的样例来仿作、求助队友 | 是 | 第一次尝试调用网络接口,requests真的好用 |
分墩赋权值算法不够优 | 查阅资料、询问大佬 | 是 | 原来还有十三水权值矩阵这东西,真好用嘿嘿 |
如何设计算法实现最优牌型的选择 | 第一下想法是判断自己有何牌型,然后从牌型到的到小的剔除,再重新判断,后面用了组合的想法来判牌、分牌 | 是 | 学习了组合数算法,itertools库真好用 |
- 王嵚
困难描述 | 解决尝试 | 是否解决 | 有何收获 |
---|---|---|---|
能够实现前端的工具很多,不知道用哪种比较好比价简单 | 之前做第一次个人编程的时候下载了pycharm,也学了一点py,b站上也有关于pyqt5详细的教程,于是决定用pyqt5做 | 是 | 粗浅地学习了一些pyqt5 |
熟悉的同学中没有和我用相同工具写前端的,代码出现错误时没有腿报 | 看博客,看视频,自己乱试 | 是 | 锻炼了自学习能力 |
10.评价队友
- 值得学习的地方
承当了更多的任务,算法、接口、博客的大部分内容,互相进度配合的较好,算法靠谱上分利器,接口好用拿来就能用,一定程度上监督了我缓慢的进度,让效率低的我可以专心学习并完成前端内容 - 需要改进的地方
没有,超棒的!
11.学习进度条
- 王嵚
第N周 | 新增代码(行) | 累计代码(行 | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 0 | 0 | |||
2 | 500 | 500 | 20 | 20 | pyqt没学会多少python的语法倒是学了不少 |
3 | 1000 | 1500 | 30 | 50 | 实现了前端 |
- 荣杰
第N周 | 新增代码(行) | 累计代码(行 | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
4 | 0 | 0 | 10 | 10 | 对项目的需求分析和原型设计的了解更深 |
6 | 800+ | 800+ | 5 | 15 | 十三水出牌算法的实现,以及py自带堆的学习 |