第二次结对编程作业
1.各链接
结对同学博客连接
本作业博客连接
Github项目地址
UI视频百度网盘
UI视频b站
~~建议b站,百度网盘的感觉比较模糊,想更清晰可以下载下噢~
2.具体分工
陈超颖:负责前端实现、博客撰写
林鑫灿:负责后端算法、博客内容指导and部分材料提供
3.PSP表格
- 超颖
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
·Estimate | ·估计这个任务需要多少时间 | 1250 | 1600 |
Development | 开发 | 300 | 360 |
·Analysis | ·需求分析 (包括学习新技术) | 120 | 240 |
·Design Spec | ·生成设计文档 | 60 | 120 |
·Design Review | ·设计复审 | 30 | 40 |
·Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
·Coding | ·具体编码 | 300 | 400 |
·Code Review | ·代码复审 | 90 | 90 |
·Test | ·测试(自我测试,修改代码,提交修改) | 90 | 90 |
Reporting | 报告 | 60 | 60 |
·Test Repor | ·测试报告 | 60 | 60 |
·Size Measurement | · 计算工作量 | 20 | 20 |
·Postmortem & Process Improvement Plan | ·事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1250 | 1600 |
- 鑫灿
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 30 |
·Estimate | ·估计这个任务需要多少时间 | 2000 | 2400 |
Development | 开发 | 300 | 360 |
·Analysis | ·需求分析 (包括学习新技术) | 120 | 240 |
·Design Spec | ·生成设计文档 | 60 | 45 |
·Design Review | ·设计复审 | 30 | 40 |
·Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
·Coding | ·具体编码 | 840 | 1095 |
·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文件下,使用时导入此文件就可以起飞了。
requests基本用法简单方便,易于上手,边学边用,何其快哉。推荐学习教程网址
具体代码如下:
def login(username,password):#登录
global token,user_id
url = "https://api.shisanshui.rtxux.xyz/auth/login"
headers = {'content-type': "application/json"}
data = {
"username": username,
"password": password
}
res = requests.post(url, data=json.dumps(data), headers=headers)
info = res.json()
if 'status' in info:
if info['status'] == 0:
token = info['data']['token']
user_id = info['data']['user_id']
return validate()
else:
print('登录失败,请检查你的密码是否输入正确!')
else:
print(info)
代码组织与内部实现设计(类图)
- 后端算法代码组织与内部实现设计
拿到一副牌,先 判断是不是特殊牌型,如果是特殊牌型,用1-14依次标识特殊牌型,分三堆丢给服务器,否则的话需要对拿到的手牌进行组合,利用组合的思想对所有牌型进行比较,择取最优选择。
- 前端代码组织与内部实现设计
利用qt dersigner 将设计好的界面自动转化为py文件,每张界面都有对应的py文件,再自己编写运行和连接服务器的代码。
说明算法的关键与关键实现部分流程图
- 算法关键
- 普通牌型判断算法:对手牌进行组合,遍历一副手牌可以组成的所有情况,将当前的组合cur_cards与当前的最优组合best_cards进行compare,按照一定的比牌规则(这里用了不通牌型赢的利润作为比牌策略),胜者为王,败者为寇,选取牌型较优者作为当前最佳组合,循环往复,直至遍历完所有的情况,最后得到的best_cards是当前比牌策略下的最优选择。
- 因此算法关键应该是对牌型合法的判断和判牌的策略,经过多次测试验证,我当前所选择的判牌策略在大多情况下能够实现最优,但还是不免有翻车的情况,这说明我这个策略并不是最优的,我猜想如果要跑出一个最优的策略需要大量的测试和重构,工作量一定不小,而且可能最优策略只比我当前策略胜率略高一点,看了看地上越积越多的头发,我果断放弃(
毕竟冬天快到了,头太凉容易感冒,嘤嘤嘤)。
- 关键实现部分流程图
5.关键代码解释
- 后端:
- 利用组合的思想对所有牌型进行比较,择取最优牌型
def best_cards_type(cards):
Cards_s = sort_Cards(cards)
#Cards_s = cards
best_cards = {}
cur_cards = {}
for shangdun in combinations(Cards_s,3):
cur_shangdun = list(shangdun)
cur_cards['shangdun'] = cur_shangdun
Cards_ss = item_delete(cur_shangdun, copy(Cards_s))
for zhongdun in combinations(Cards_ss, 5):
cur_zhongdun = list(zhongdun)
cur_cards['zhongdun'] = cur_zhongdun
cur_xiadun = item_delete(cur_zhongdun, copy(Cards_ss))
cur_cards['xiadun'] = cur_xiadun
cur_cards['type'] = ""
if len(best_cards) == 0:
if isleagle(cur_cards)[0]:
best_cards = copy(cur_cards)
best_cards['type'] = [cards_type(best_cards['shangdun'])[0], cards_type(best_cards['zhongdun'])[0],cards_type(best_cards['xiadun'])[0]]
else:
cur_res = Compare(best_cards,cur_cards)
if cur_res[0]:
best_cards = copy(cur_res[1])
shang = ""
zhong = ""
xia = ""
best_cards_Type = []
shang += best_cards['shangdun'][0] + ' ' + best_cards['shangdun'][1] + ' ' + best_cards['shangdun'][2]
zhong += best_cards['zhongdun'][0] + ' ' + best_cards['zhongdun'][1] + ' ' + best_cards['zhongdun'][2] + ' ' + best_cards['zhongdun'][3] + ' ' + best_cards['zhongdun'][4]
xia += best_cards['xiadun'][0] + ' ' + best_cards['xiadun'][1] + ' ' + best_cards['xiadun'][2] + ' ' + best_cards['xiadun'][3] + ' ' + best_cards['xiadun'][4]
best_cards_Type.append(shang)
best_cards_Type.append(zhong)
best_cards_Type.append(xia)
print(best_cards)
return best_cards_Type
- 判断牌型是否合法
cards_order = {'散牌':0, '一对':1, '两对':2, '三条':3, '顺子':4, '同花':5, '葫芦':6, '炸弹':7, '同花顺':8}
shangdun_type = cards_type(cards['shangdun'])
zhongdun_type = cards_type(cards['zhongdun'])
xiadun_type = cards_type(cards['xiadun'])
if cards_order[shangdun_type[0]] < cards_order[zhongdun_type[0]]:
res = 0
if cards_order[zhongdun_type[0]] < cards_order[xiadun_type[0]]:
return [True,[shangdun_type[0], zhongdun_type[0], xiadun_type[0]]]
elif cards_order[zhongdun_type[0]]> cards_order[xiadun_type[0]]:
return [False, []]
else:
cards1 = []
cards2 = []
if zhongdun_type[0] == "同花顺" or zhongdun_type[0] == "同花" or zhongdun_type[0] == "顺子" or zhongdun_type[0] == "散牌":
cards1 = cards['zhongdun']
cards2 = cards['xiadun']
res = cards_cmp(cards1,cards2)
elif zhongdun_type[0] != '两对':
cards1 = zhongdun_type[2]
for card in zhongdun_type[1]:
cards1.append(card)
cards2 = xiadun_type[2]
for card in xiadun_type[1]:
cards2.append(card)
res = cards_cmp(cards1, cards2)
else:
res = liangdui_cmp(zhongdun_type,xiadun_type)
if res == -1 or res == 0:
return [False,[]]
else:
return [True, [shangdun_type[0], zhongdun_type[0], xiadun_type[0]]]
elif cards_order[shangdun_type[0]] == cards_order[zhongdun_type[0]]:
cards1 = copy(shangdun_type[1])
cards2 = copy(zhongdun_type[1])
if cards_cmp(cards1,cards2) == 1:
if cards_order[zhongdun_type[0]] < cards_order[xiadun_type[0]]:
return [True, [shangdun_type[0], zhongdun_type[0], xiadun_type[0]]]
elif cards_order[zhongdun_type[0]] > cards_order[xiadun_type[0]]:
return [False,[]]
else:
if zhongdun_type[0] == "同花顺" or zhongdun_type[0] == "同花" or zhongdun_type[0] == "顺子" or zhongdun_type[0] == "散牌":
cards1 = cards['zhongdun']
cards2 = cards['xiadun']
res = cards_cmp(cards1, cards2)
elif zhongdun_type[0] != '两对':
cards1 = zhongdun_type[2]
for card in zhongdun_type[1]:
cards1.append(card)
cards2 = xiadun_type[2]
for card in xiadun_type[1]:
cards2.append(card)
res = cards_cmp(cards1, cards2)
else:
res = liangdui_cmp(zhongdun_type, xiadun_type)
if res == -1 or res == 0:
return [False, []]
else:
return [True, [shangdun_type[0], zhongdun_type[0], xiadun_type[0]]]
else:
return [False, []]
return [False,[]]
合法返回True及各墩牌型,否则返回False
- 当前牌型和最优牌型比较
cards_order = {'散牌':0, '一对':1, '两对':2, '三条':3, '顺子':4, '同花':5, '葫芦':6, '炸弹':7, '同花顺':8}
leagle_new = isleagle(new)#判断牌型是否合法
if not leagle_new[0]:#牌型不合法
return [False,[]]
else:
new['type'] = leagle_new[1]
type_old = old['type']
type_new = new['type']
#比牌策略:利用游戏规则对两副牌的大小进行比较,将当前牌型的盈利情况记录在profit中,比完后若profit>0,说明当前牌型更优,返回TRUE,更新best_cards
#计算牌型净利润
profit = 0
# 计算下墩盈利
if cards_order[type_old[2]] < cards_order[type_new[2]]:
if type_new[2] == '同花顺':
profit += 5
elif type_new[2] == "炸弹":
profit += 4
else:
profit += 1
elif cards_order[type_old[2]] > cards_order[type_new[2]]:
if type_new[2] == '同花顺':
profit -= 5
elif type_new[2] == "炸弹":
profit -= 4
else:
profit -= 1
else:
res = 0
card1 = cards_type(old['xiadun'])
card2 = cards_type(new['xiadun'])
if card1[0] == '两对':
if card1[1] in [['2', '3'], ['3', '4'], ['4', '5'], ['5', '6'], ['6', '7'], ['7', '8'], ['8', '9'],
['9', '10'], ['10', 'J'], ['J', 'Q'], ['Q', 'K'], ['K', 'J']]:
if card2[1] in [['2', '3'], ['3', '4'], ['4', '5'], ['5', '6'], ['6', '7'], ['7', '8'], ['8', '9'],
['9', '10'], ['10', 'J'], ['J', 'Q'], ['Q', 'K'], ['K', 'J']]:
res1 = cards_cmp(card1[1], card2[1])
if res1 == 0:
res = cards_cmp(card2[2], card1[2])
else:
res = res1
else:
res = -1
else:
if card1[1] == card2[1]:
res = cards_cmp(card2[2], card1[2])
else:
res = cards_cmp(card1[1], card2[1])
elif card1[0] == '三条' or card1[0] == '葫芦' or card1[0] == '炸弹':
if card1[1] == card2[1]:
res = cards_cmp(card2[2], card1[2])
else:
res = cards_cmp(card1[1], card2[1])
else:
res = cards_cmp(old['xiadun'], new['xiadun'])
#res = cards_cmp(old['xiadun'], new['xiadun'])
if res > 0:
if type_new[1] == '同花顺':
profit += 10
elif type_new[1] == "炸弹":
profit += 8
elif type_new[1] == "葫芦":
profit += 2
else:
profit += 1
elif res < 0:
if type_new[1] == '同花顺':
profit -= 10
elif type_new[1] == "炸弹":
profit -= 8
elif type_new[1] == "葫芦":
profit -= 2
else:
profit -= 1
# 计算中墩盈利
if cards_order[type_old[1]] < cards_order[type_new[1]]:
if type_new[1] == '同花顺':
profit += 10
elif type_new[1] == "炸弹":
profit += 8
elif type_new[1] == "葫芦":
profit += 2
else:
profit += 1
elif cards_order[type_old[1]] > cards_order[type_new[1]]:
if type_new[1] == '同花顺':
profit -= 10
elif type_new[1] == "炸弹":
profit -= 8
elif type_new[1] == "葫芦":
profit -= 2
else:
profit -= 1
else:
res = 0
card1 = cards_type(old['zhongdun'])
card2 = cards_type(new['zhongdun'])
if card1[0] == '两对':
if card1[1] in [['2', '3'], ['3', '4'], ['4', '5'], ['5', '6'], ['6', '7'], ['7', '8'],
['8', '9'],
['9', '10'], ['10', 'J'], ['J', 'Q'], ['Q', 'K'], ['K', 'J']]:
if card2[1] in [['2', '3'], ['3', '4'], ['4', '5'], ['5', '6'], ['6', '7'], ['7', '8'],
['8', '9'],
['9', '10'], ['10', 'J'], ['J', 'Q'], ['Q', 'K'], ['K', 'J']]:
res1 = cards_cmp(card1[1], card2[1])
if res1 == 0:
res = cards_cmp(card2[2], card1[2])
else:
res = res1
else:
res = -1
else:
if card1[1] == card2[1]:
res = cards_cmp(card2[2], card1[2])
else:
res = cards_cmp(card1[1], card2[1])
elif card1[0] == '三条' or card1[0] == '葫芦' or card1[0] == '炸弹':
if card1[1] == card2[1]:
res = cards_cmp(card2[2], card1[2])
else:
res = cards_cmp(card1[1], card2[1])
else:
res = cards_cmp(old['zhongdun'], new['zhongdun'])
#res = cards_cmp(old['zhongdun'],new['zhongdun'])
if res > 0:
if type_new[1] == '同花顺':
profit += 10
elif type_new[1] == "炸弹":
profit += 8
elif type_new[1] == "葫芦":
profit += 2
else:
profit += 1
elif res < 0:
if type_new[1] == '同花顺':
profit -= 10
elif type_new[1] == "炸弹":
profit -= 8
elif type_new[1] == "葫芦":
profit -= 2
else:
profit -= 1
#计算上墩盈利
if cards_order[type_old[0]] < cards_order[type_new[0]]:
profit += 1
elif cards_order[type_old[0]] > cards_order[type_new[0]]:
profit -= 1
else:
profit += cards_cmp(old['shangdun'], new['shangdun'])
if profit > 0:
return [True,new]
else:
return [False,[]]#
- 前端:
- 通过信号与信号槽达到通过按钮跳转页面
def slot1(self):
self.hide()
w = two2(window)
w.resize(775,549)
w.show()
slot1为设置的信号槽,hide函数为隐藏画面,show函数为展示画面,two2为跳转页面封装的类
- 将每一张界面封装成一个类,在总的运行函数中调用,并展示
class three3(QtWidgets.QMainWindow, three.Ui_MainWindow):
def __init__(self, parent=None, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
self.setupUi(self)
three3为自定义界面三的封装类,后面三局为基础的展示
- 从服务器中获得排行榜信息,并展示在设计的界面中
res = httprank(header)
print(res)
print(res[(self.page - 1) * 4]['player_id'])
quece = (self.page - 1) * 4 + 1
self.rank1.setText('No.{}'.format(quece))
self.rank2.setText('No.{}'.format(quece + 1))
self.rank3.setText('No.{}'.format(quece + 2))
self.rank4.setText('No.{}'.format(quece + 3))
self.id1.setText(str(res[(self.page - 1) * 4]['player_id']))
self.id2.setText(str(res[(self.page - 1) * 4 + 1]['player_id']))
self.id3.setText(str(res[(self.page - 1) * 4 + 2]['player_id']))
self.id4.setText(str(res[(self.page - 1) * 4 + 3]['player_id']))
self.name1.setText(str(res[(self.page - 1) * 4]['name']))
self.name2.setText(str(res[(self.page - 1) * 4 + 1]['name']))
self.name3.setText(str(res[(self.page - 1) * 4 + 2]['name']))
self.name4.setText(str(res[(self.page - 1) * 4 + 3]['name']))
self.point1.setText(str(res[(self.page - 1) * 4]['score']))
self.point2.setText(str(res[(self.page - 1) * 4 + 1]['score']))
self.point3.setText(str(res[(self.page - 1) * 4 + 2]['score']))
self.point4.setText(str(res[(self.page - 1) * 4 + 3]['score']))
httprank用于连接服务器,之后由于我每页设置展示四个排名,rank,id,name,point是界面里
6.性能分析与改进
改进的思路
- 一开始做好的牌型判断算法存在很多问题,比如说炸弹牌型被判别为两对,顺子被判断为散牌等等,牌型判断的错误也间接导致了牌型合法性的错误判断,通过构造筞实时数据不断地debug,总算是把判牌函数的bug给填上了。
- isleagle疯狂报错->debug->继续报错->继续debug……在我把键盘的debug按钮快要敲烂的时候,它好了,它终于跑出了正确的结果,我哭了。
- 在测试时发现了选出来的牌型,如果按照人的思想去做,肯定不是最优的,于是我通过改进比牌策略,即调整不同牌型的获胜权值,得到了相对更优牌型
至少一眼看过去是最优的,至于其他策略,限于时间因素,将在在之后在做测试改进。 - 与其说是改进,不如说是改正,一股脑把所有东西都写完甚至写好简直天方夜谭,结果当然是bug无数,无处哭诉。其实主要原因在于自己一开始没有做好合理的规划、设计合适的代码结构,导致在写算法的过程中经常卡壳,效率十分低下。希望借此经历奉劝各位,心急吃不了热豆腐,敲码还得靠一手细腻的心思和清醒的脑子,操之过急敲不得好码
现在就是后悔,非常后悔,
性能分析图和程序中消耗最大的函数
本次性能分析是利用cprofile--python性能分析工具
标志框内为消耗最大的函数Compare(比牌函数)、isleagle(判断牌型合法性)、cards_type(判断每墩牌类型)
ncalls:函数被调用的次数。如果这一列有两个值,就表示有递归调用,第二个值是原生调用次数,第一个值是总调用次数。
tottime:函数内部消耗的总时间。(可以帮助优化)
percall:是tottime除以ncalls,一个函数每次调用平均消耗时间。
cumtime:之前所有子函数消费时间的累计和。
filename:lineno(function):被分析函数所在文件名、行号、函数名。
7.单元测试
- 下图是测试用到的函数,代码比较长,限于篇幅不全展开
- 主函数代码展示
if __name__=='__main__':
card_dic = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']#所有单牌
huase = ['*','#','&','$']#所有花色
for i in range(10): # 构造十组测试数据
i = 13
cards = ""
while i:
card_i = card_dic[numpy.random.randint(0, 13)]
color = huase[numpy.random.randint(0, 4)]
if i != 1:
cards += color + card_i + ' '
else:
cards += color + card_i
i -= 1
sort_cards = sort_Cards(cards) # 构造测试牌型
test_cards = {
'shangdun': [sort_cards[0], sort_cards[1], sort_cards[2]],
'zhongdun': [sort_cards[3], sort_cards[4], sort_cards[5], sort_cards[6], sort_cards[7]],
'xiadun': [sort_cards[8], sort_cards[9], sort_cards[10], sort_cards[11], sort_cards[12]]
}
print(test_cards)
print(isleagle(test_cards)) # 若牌型合法,返回[True,[各墩牌型]],否则返回[False,[]]
- 对isleagle函数进行单元测试
测试数据:随机生成,不特意构造
测试思路:利用随机生成的数据对isleagle进行测试,可完成对其实用性和可靠性检测 - 利用coverage对测试程序进行覆盖率分析
- 覆盖率图
- 分析图
- 覆盖率图
8.Github的代码签入记录
9.遇到的困难及解决方法
困难描述 | 解决尝试 | 是否解决 | 有何收获 |
---|---|---|---|
界面开发工具的选择 | 先是学习了html和css,但是后来由于我队友先用python写好后端,该路pass,听团队的小伙伴说qypt5可以自动转为py文件,就确定用该工具 | 是 | qypt5通过拖拽形成ui文件,并且可以通过PYUIC转为py文件,这个非常方便,不用狂打代码 |
QT dersigner 全英文 | 寻找汉化包 or 自我适应 | 是 | 汉化包很多需要money,下载了一个不用money,但是配置一直出问题。最后就是提高英语水平 |
排行榜、历史战局、登录注册要从服务器得到数据并展示 | 查找资料、询问培荣大佬、求助队友 | 是 | 这部分我感觉是前端部分最吃力的,要自己写,问题很多,但我对如何从服务器获取数据有了更加深入的了解 |
QT dersigner 全英文 | 寻找汉化包 or 自我适应 | 是 | 汉化包很多需要money,下载了一个不用money,但是配置一直出问题。最后就是提高英语水平 |
如何设计算法实现最优牌型的选择 | 第一想法是贪心,但是后来仔细想想贪心并不能保证最优,只能接近最优,后来想到组合数,并尝试解决 | 是 | 学习了组合数算法,但是没有自己造出轮子,有点遗憾,不过不得不说py确实方便,调个itertools库,直接起飞 |
10.评价队友
- 评价人:超颖
- 值得学习的地方
主动承担起后端大梁,也花比我多的时间在这上面,总是熬到很晚,前端登录注册等很多升级的地方都是由队友帮忙,为其努力钻研的精神点赞 - 需要改进的地方
无,真的挺好的了
- 值得学习的地方
- 评价人:鑫灿
- 值得学习的地方
敢于牺牲,对这门课程很上心,在本次作业中主动承担前端的开发设计,花了大量时间地去学习前端开发工具,作出来的效果也还不错(毕竟速成),其乐于探知的精神值得学习 - 需要改进的地方
希望能够更充分地利用空闲时间,提升自己
- 值得学习的地方
11.学习进度条
- 超颖
第N周 | 新增代码(行) | 累计代码(行 | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 0 | 0 | 10 | 10 | 对项目的需求分析和原型设计的了解更深,学习使用原型分析工具 |
2 | 0 | 0 | 5 | 15 | 学习pyqt5 |
3 | 323 | 323 | 10 | 25 | 学习pyqt5,着手设计界面 |
4 | 554 | 877 | 15 | 40 | 学习pyqt5,着手设计界面,尝试从服务器获取数据,编写代码 |
- 鑫灿
第N周 | 新增代码(行) | 累计代码(行 | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 0 | 0 | 10 | 10 | 对项目的需求分析和原型设计的了解更深, 学习使用原型分析工具 |
2 | 0 | 0 | 5 | 15 | 构思算法 |
3 | 382 | 382 | 45 | 60 | 学习py,初步实现特殊牌型的判断算法 |
4 | 733 | 1105 | 45 | 90 | 学习python的itertools库中的组合函数combinations, 通过对牌型的组合比较,实现普通牌型的最优判断算法 |