1.相关链接
结对同学的博客链接
Github项目地址
UI测试视频百度网盘链接 提取码:rq1u
b站演示视频
PS:1.UI测试视频github上也有 2.也可以在github下载html5文件用火狐浏览器或谷歌浏览器打开 3.也可以联系QQ1067414607获取exe文件
2.具体分工
郑裕恒
- Web端UI的全部实现
- 所有网络接口调用
余廷龙
- AI的全部实现
- 提供AI的网络接口
- 撰写博客
3.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) | |||||||||
Planning | · 计划 | 90 | 120 | |||||||||
· Estimate | · 估计这个任务需要多少时间 | 90 | 120 | |||||||||
Development | · 开发 | 1500 | 3000 | |||||||||
· Analysis | · 需求分析 (包括学习新技术) | 370 | 640 | |||||||||
· Design Spec | · 生成设计文档 | 30 | 60 | |||||||||
· Design Review | · 设计复审 | 10 | 20 | |||||||||
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 40 | |||||||||
· Design | · 具体设计 | 20 | 40 | |||||||||
· Coding | · 具体编码 | 1000 | 2100 | |||||||||
· Code Review | · 代码复审 | 20 | 40 | |||||||||
· Test | · 测试(自我测试,修改代码,提交修改) | 40 | 80 | |||||||||
Reporting | · 报告 | 30 | 30 | |||||||||
· Test Report | · 测试报告 | 10 | 10 | |||||||||
· Size Measurement | · 计算工作量 | 10 | 10 | |||||||||
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 10 | 10 | |||||||||
· 合计 | 1620 | 3150 |
4.解题思路描述与设计实现说明
网络接口的使用
使用ajax实现以服务器的连接,分别展示不需要token的注册接口使用和需要token的开启战局接口使用
代码组织与内部实现设计(类图)
算法的关键与关键实现部分流程图
说明:1.出牌算法采用三重循环遍历各种牌型组合取最大权值组合的方法,获取后墩所有牌型组合集合是从原始13张牌中获取所有对子牌型以上的组合,获取中墩所有牌型组合集合是在遍历过程中从去除后墩牌后剩余的牌中查找,前墩所有牌型组合集合亦是如此。
2.为了减少循环次数,在获取各墩牌的所有牌型组合时做了一些优化,例如对子牌型的大小只由两张牌决定,三条牌型的大小只有三张牌决定,因此在获取牌型集合的时候只用决定性的几张牌表示一墩牌,缺少的牌在一趟循环结束后用剩下的牌填充。
3.算法的实际流程比上面的流程图更加繁琐复杂,为了便于读者阅读,上述流程图已经去除部分复杂逻辑,只保留核心部分。
4.经本地测试,上述算法平均每秒可响应60+次。
5.关键代码解释
给定一组牌,找出所有对子以上的组合的函数
说明:这个函数可以找出所有的对子以上牌型的组合并存入列表中返回。在有些牌型中,一墩牌的大小会被一部分牌决定,比如在中墩中,三条牌型的大小只被三张相同牌的大小所决定,而与剩余两张牌的大小无关,所以为了避免冗余的遍历,在表示某一种组合的时候只取其决定性的部分。
def FindAllCardsCombination(Cardlist=[]):
temp_Cardlist = list.copy(Cardlist)
AllCardsCombination = []
temp_list = CommonCardsType.FindTonghuashun(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindZhadan(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindHulu(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindTonghua(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindShunzi(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindSantiao(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindLiandui(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindErdui(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
temp_list = CommonCardsType.FindDuizi(temp_Cardlist)
if (temp_list != []):
for item in temp_list:
AllCardsCombination.append(item)
return AllCardsCombination
比较牌型大小函数
说明:为了避免不合法墩牌,需要比较三墩牌的大小是否符合规定,这个函数可以比较任意两墩牌的大小。
def Compare(Cardlist1=[], Cardlist2=[]):
flag1 = Judge(Cardlist1)
flag2 = Judge(Cardlist2)
list_count1 = CommonCardsType.GetList_count(Cardlist1)
list_count2 = CommonCardsType.GetList_count(Cardlist2)
bigCard1 = -1
bigCard2 = -2
if (flag2 > flag1):
return True
elif (flag1 == flag2):
if (flag1 == flag2 == 7):
for i in range(15):
if (list_count1[i] == 3):
bigCard1 = i
if (list_count2[i] == 3):
bigCard2 = i
if (bigCard2 >= bigCard1):
return True
else:
return False
else:
bigCard1 = FindBiggestCard(Cardlist1)
bigCard2 = FindBiggestCard(Cardlist2)
if (bigCard2 >= bigCard1):
return True
else:
return False
else:
return False
出牌函数
说明:这个函数原代码很长,这里省略了很多原代码,只展示最关键部分,找出最优出牌组合的思路主要就是通过三重循环遍历各种可能(遍历过程有做优化,并不是单纯的暴力遍历),然后给前中后墩赋权值并判断牌面大小是否满足递增顺序,如果权值比之前最大的还大,就更新最优出牌组合以及最大权值,然后进入下一次循环。
def PostCards(str_data):
#省略
for item1 in AllCardsCombination_FromAllcards: # 找出一种组合作为后墩
#省略
for item2 in AllCardsCombination_AfterTakeHoudun: #从剩下的牌中选出一个组合作为中墩
for item3 in AllCardsCombination_AfterTakeZhongdun:#拿出中墩后,再从剩下的牌中找出一种组合作为前墩
#省略
#判断新的墩牌组合是否比上一次更优
if (Weight_Qiandun + Weight_Zhongdun + Weight_Houdun > Weight_All
and Compare(Cards_Qiandun, Cards_Zhongdun) and Compare(Cards_Zhongdun, Cards_Houdun)):
#更新最高的权值
Weight_All = Weight_Qiandun + Weight_Zhongdun + Weight_Houdun
#省略
else:
#省略
else:
#省略
return Post_Cards
6.性能分析与改进
改进思路
算法时间效率的优化
最开始想到的就是通过暴力遍历的方法找出所有墩牌组合中最优的一种,但是这种方法过于暴力,每次出牌都要经过1287*56=72072次遍历,每次循环的内容都非常复杂,所以这种做法很可能出现响应时间长的问题。改进思路就是减少冗余的遍历,比如在后墩中,对子的大小只由两张对子所决定,而与其他三张牌无关,这样我们在遍历的过程中就可以用这两张牌来表示后墩,对于中墩、前墩以及其他牌型也可以用此方法,在选出不完整但牌型大小已经被确定的三墩牌后,再用剩余的牌补全这三墩牌,经过这样的处理,每次遍历只要经过数千次的循环便可完成。
赋权优化
给每一种牌型及其牌面赋值,能否找到一种好的赋权方式决定最后能不能打出优秀的牌。我最开始的赋权方法是在相同墩里,牌型大的权值比牌型小的权值大,相同牌型,牌面大的权值比牌面小的权值大,在不同墩里,相同牌面相同牌型的牌,后墩的权值比中墩的大,中墩的权值比前墩的大,按照这种赋权方法赋值,最后的实现效果其实非常接近贪心算法,显然贪心算法在这道题中不是一种好的算法。
优化后的赋权方法是按照某种牌组的胜率赋值,比如,在前墩中,一共可能出现22100中组合,假如其中一种组合比其他10000中组合都大,那么它的胜率x=10000/22100X100%。因此,在某一墩中,假设某种牌组的胜率为x,那么它的权值weight=100000*x。
性能分析图
7.单元测试
测试不合法墩牌
def testForIllegalDunpai()
count = 0
error1 = 0
error2 = 0
while (True):
list = [x for x in range(52)]
cards = ["*A", "$A", "&A", "#A",
"*2", "$2", "&2", "#2",
"*3", "$3", "&3", "#3",
"*4", "$4", "&4", "#4",
"*5", "$5", "&5", "#5",
"*6", "$6", "&6", "#6",
"*7", "$7", "&7", "#7",
"*8", "$8", "&8", "#8",
"*9", "$9", "&9", "#9",
"*10", "$10", "&10", "#10",
"*J", "$J", "&J", "#J",
"*Q", "$Q", "&Q", "#Q",
"*K", "$K", "&K", "#K", ]
numbers = random.sample(list, 13)
str_data = ""
for i in numbers:
str_data = str_data + cards[i] + " "
str_data = str_data[0:len(str_data) - 1]
print(str_data)
output = PlayCards.PostCards(str_data)
print(output)
count = count + 1
if (len(output) != 3 and len(output) != 13):
error1 = error1 + 1
if (len(output[0]) != 3 or len(output[1]) != 5 or len(output[2]) != 5):
if (len(output) != 13):
error2 = error2 + 1
print(count)
print(error1)
print(error2)
测试出千
def testForChuqian():
count = 0
error = 0
while (True):
list = [x for x in range(52)]
cards = ["*A", "$A", "&A", "#A",
"*2", "$2", "&2", "#2",
"*3", "$3", "&3", "#3",
"*4", "$4", "&4", "#4",
"*5", "$5", "&5", "#5",
"*6", "$6", "&6", "#6",
"*7", "$7", "&7", "#7",
"*8", "$8", "&8", "#8",
"*9", "$9", "&9", "#9",
"*10", "$10", "&10", "#10",
"*J", "$J", "&J", "#J",
"*Q", "$Q", "&Q", "#Q",
"*K", "$K", "&K", "#K", ]
numbers = random.sample(list, 13)
str_data = ""
for i in numbers:
str_data = str_data + cards[i] + " "
str_data = str_data[0:len(str_data) - 1]
originalcards = PlayCards.GetCardlist(str_data)
print(str_data)
output = PlayCards.PostCards(str_data)
temp_output = []
for item in output:
if (len(output) != 13):
temp_output = temp_output + item
else:
temp_output.append(item)
temp_output.sort()
originalcards.sort()
if (temp_output != originalcards):
error = error + 1
print(output)
count = count + 1
print(count)
print(error)
说明:在拿到一副牌后,不同的人有不同的出牌策略,而且在许多情况下,并不能找到一种绝对最优的出牌方法,因此在代码测试阶段,我们无法利用测试函数测试出牌结果是不是最优,而是通过肉眼观察出牌结果以确定出牌是否与预期相符,从而进行相应的改进。但是,对于不合法墩牌和出千的情况就需要在大量的测试中发现,如果因为这种情况而失去战局实属可惜。
在不合法墩牌测试中,主要测试出牌结果是否有且只有三墩并且数量分别为3、5、5。在while(True)无限循环中,利用生成随机数的方法随机抽取13张牌,调用出牌函数后检查结果是否符合标准,变量error1记录出牌不刚好为三墩的次数,变量error2记录三墩牌不为3、5、5的次数,变量count记录循环次数(即测试次数)。每次循环都打印发牌、出牌、error1、error2、count以便观察结果。
在出千测试中,主要测试出牌与发牌是否一致。在while(True)无限循环中,利用生成随机数的方法随机抽取13张牌,调用出牌函数后检查出牌是否与发牌一致,变量error记录出牌与发牌不一致的次数,变量count记录循环次数。每次循环都打印发牌、出牌、error、count以便观察结果。
8.Github的代码签入记录
9.遇到的代码模块异常或结对困难及解决方法
问题描述
- 出现程序把所有牌都判定为三顺子的bug
- 出现程序出牌不止三墩或者后墩出牌数量大于5的bug
- 流程图和类图的画法困难
- 从零开始的前端生活让人束手无策,调整元素的位置十分费劲
- 调用网络接口十分费解
- 初次使用git,觉得不好上手
- web端无法调用AI接口
做过哪些尝试
- 手动Debug+查询资料
- 手动Debug+查询资料
- 查找绘制流程图和类图的相关软件,最终选择使用亿图图示绘制流程图,类图使用Pycharm自带的功能导出
- 图书馆借书+网络学习+不断尝试
- 疯狂求助百度和身边大佬
- 求助百度
- 两人先各自检查自己的代码是否有问题,然后百度查询解决方法无果后,在海东大佬的帮助下顺利解决问题
是否解决
- 解决
- 解决
- 解决
- 解决
- 解决
- 解决
- 解决
有何收获
- 发现是没有处理好全局变量的问题
- 发现python的数据类型分为可变类型和不可变类型,列表属于可变类型,在函数传参的时候是传引用,我误以为列表是以传值的方式传递给函数,故导致bug产生
- 提高了绘制流程图的能力,学会了类图的方便导出方法
- 前端水平突飞猛进,对html、css、js有了一定的了解,迈出了最困难的第一步
- 了解了网络接口的使用,对服务器与用户端的交互理解更加深刻
- 现在可以较熟练地使用git来进行文件的上传与更新
- 了解到网络接口调用的跨域问题和解决方法,以及网络接口调用者和网络接口提供者之间的数据传递要保持类型一致
10.评价你的队友
值得学习的地方
我的好朋友余廷龙是我这次结对作业的队友。在接收到廷龙的结对请求的时候我非常开心,那感觉就像在享用德克士的双层鸡腿堡时,发现里面有三层肉一样。我们一拍既合,福建十三水在等着我们。从9月6号到10月15号,不长不短的40天过去了,这趟奇妙的旅程终于走到了尽头。很感谢柯老师为我提供了这样的机会,让我学会了很多新的知识,让我和曾经一起看新垣结衣的老朋友又有了交集。抛开好朋友这一身份,单从结对作业的队友这个角度,廷龙亦让我很是满意。早在第二次结对作业开始以前,他就开始研究我们到底要做出什么样的作品,怎样与接口进行交互。由于他开始研究的时间较早,我们在结束了原型设计以后马上就开始了对编程部分的讨论,这让我们早早的定下了方向,在接下来的三周时间可以合理地完成作业。廷龙对自己的优缺点都很了解,在我提出前后端分工较合理的时候,他马上选择了自己的方向。是的,他算法的实现能力很强,如果让我用一个成语来形容写算法时候的他,那我只能给出“滴水不漏”这个词,简单阅读了他的代码以后,对他的敬佩又深了一层。廷龙的编码效率高,正当我国庆在边看假面骑士边读前端的时候,他就已经完成了算法的一大本了。和今天才写完前端的我不同,廷龙绝不拖沓。有时我想放弃这次结对作业,但是一想到他对我的信任,我觉得我又能熬几次夜。
需要改进的地方
我觉得廷龙在结对作业的队友这个层面,可以说是很难让人挑出毛病。如果要说的话,就是过分隐藏自己实力。在对我发出结对请求的时候,骗我很久没写代码了。这让我错误预估了他的实力,使得我在知道他算法写那么快的时候被吓了一跳。国庆的时候我正在看假面骑士build,得知他快写完算法了我被吓坏了,这勉强算是一个他需要改进的地方的,希望他可以更加自信的承认自己的强大。
11.学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 | |||||||
1 | 0 | 0 | 5 | 5 | 研究如何实现十三水的编码部分,开始学习前端知识 | |||||||
2 | 150 | 150 | 17 | 22 | 明确本次作业的代码实现,继续学习前端知识 | |||||||
3 | 1500 | 1650 | 30 | 52 | 开始实现具体编码,在实践中加深对前端语言的认识 |