1.各链接
结对同学博客连接
本作业博客连接
Github项目地址(项目仍在建设中~~~~~)
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基本用法简单方便,易于上手,边学边用,何其快哉。推荐学习教程网址
代码组织与内部实现设计(类图)
- 后端算法代码组织与内部实现设计
拿到一副牌,先 判断是不是特殊牌型,如果是特殊牌型,用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.单元测试
8.Github的代码签入记录
9.遇到的困难及解决方法
- 前端
困难描述 | 解决尝试 | 是否解决 | 有何收获 |
---|---|---|---|
界面开发工具的选择 | 先是学习了html和css,但是后来由于我队友先用python写好后端,该路pass,听团队的小伙伴说qypt5可以自动转为py文件,就确定用该工具 | 是 | qypt5通过拖拽形成ui文件,并且可以通过PYUIC转为py文件,这个非常方便,不用狂打代码 |
QT dersigner 全英文 | 寻找汉化包 or 自我适应 | 是 | 汉化包很多需要money,下载了一个不用money,但是配置一直出问题。最后就是提高英语水平 |
排行榜、历史战局、登录注册要从服务器得到数据并展示 | 查找资料、询问培荣大佬、求助队友 | 是 | 这部分我感觉是前端部分最吃力的,要自己写,问题很多,但我对如何从服务器获取数据有了更加深入的了解 |
QT dersigner 全英文 | 寻找汉化包 or 自我适应 | 是 | 汉化包很多需要money,下载了一个不用money,但是配置一直出问题。最后就是提高英语水平 |
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,利用组合实现普通牌型的最优选择算法 |