软件工程06—亚瑟王の十三水2.0

1、相关链接


小伙伴的博客
本作业博客
GitHub地址

2、具体分工


钟伟颀:前端,交互
陈锦鸿:算法,博客

3、PSP表格


PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 30
· Estimate · 估计这个任务需要多少时间 30 30
Development 开发 1720 1935
· Analysis · 需求分析 (包括学习新技术) 600 720
· Design Spec · 生成设计文档 20 10
· Design Review · 设计复审 10 30
· Coding Standard · 代码规范(为开发制定合适的规范) 10 10
· Design · 具体设计 240 350
· Coding · 具体编码 600 500
· Code Review · 代码复审 60 75
· Test · 测试(自我测试,修改代码,提交修改) 180 240
Reporting 报告 150 125
· Test Repor · 测试报告 60 45
· Size Measurement · 计算工作量 30 20
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 60 60
合计 1900 2090

4、解题思路描述与设计实现说明


  • 网络接口的使用

使用python的reequests库的post或者get方法。出牌部分需要通过开启战局API获取的card数据发送至后端,返回排列好的前中后墩,然后通过出牌API发送。API文档将使用方法都写得很清楚。

  • 以注册为例,接口的使用大同小异:
account = self.zhang_hao.text()
password = self.mi_ma.text()
jwc_account = self.xue_hao.text()
jwc_password = self.jwc_mima.text()

url = 'http://www.revth.com:12300/auth/register2'
form_data = {
    "username": account,
    "password": password,
    "student_number": jwc_account,
    "student_password": jwc_password
}
headers = {
    "Content-Type": 'application/json',
}
response = requests.post(url=url, headers=headers, data=json.dumps(form_data), verify=False)
  • 代码组织与内部实现设计

    • 前端

    基本上是一个窗口一个类。()主要包括以下几个类:

    • 初始界面:摆设。
    • 注册界面:提供绑定学号注册功能。
    • 登录界面:已注册过的用户可输入账号密码直接登录。
    • 游戏大厅:选择界面。有开始游戏、游戏规则、个人中心、排行榜四个按钮供选择。
    • 开始游戏、分墩、出牌:打牌流程。
    • 游戏规则:查看游戏规则,包含2个页面。
    • 个人中心:可查看个人最近的几场战局的ID、得分、出牌情况。
    • 排行榜:查看服务器排行榜。
    • 各种弹窗;用于在各个界面中的提示信息。
      由于是使用QtDesigner设计的前端,所以在转成代码的时候代码会显得较为冗长。
    • 算法

    两个类:Poker和Judge。Poker类用于存储从API获得的手牌,并分析手牌返回分墩后的手牌。Judge类用于判断每一墩的牌型、得出该种分墩方案的分值以及判断是否倒水所用的分值。

  • 算法关键

    • 算法思路

由于特殊牌由系统自行判断,因此不判断特殊牌型,直接考虑普通牌型的分墩。如果只凑出某一墩最大,可能会出现其他两墩过小而输掉牌局的情况。要使己方迎面最大,不能仅仅考虑只凑出令某一墩最大,而应综合三墩考虑。最终选择使用最暴力的办法,遍历所有可能的分墩结果,依次判断三墩牌型、是否倒水,计算分值,比较符合规则的每一种分墩的分值并返回最大分值的分墩结果。

  • 牌的储存

    牌的储存直接关系到后面牌型的判断以及分墩和输出。起初打算使用类似于桶排序的方法使用13*4的cards列表,后来考虑到取牌、分墩的不方便,改由使用cards列表分别储存每张牌的大小和花色。

  • 牌型的判断

    按每一种普通牌型由大至小逐级判断,根据每一种牌型特点判断即可。

  • 分墩分值的计算

    以每一墩牌能够获胜的概率作为该墩分值,即该墩牌所能胜过的牌型种类/总的牌型种类,三墩分值之和作为总分值。

    • 每一墩牌能胜过的牌型总数=该墩牌能胜过的不同种牌型总数+同种牌型能胜过的总数。
    • 某种牌能胜过的不同种牌型总数=小于该种牌型的牌型总数
    • 各种牌型总数
      同花顺:C19C14 = 36
      炸弹:C113C148 = 624
      葫芦:C113C34C112C24 = 3744
      同花:C513C14 = 5148
      顺子:C19(C14)5 = 9216
      三条:C313C13C34*C14C14 = 54912
      二对:C313C13C14*(C14)3 = 123552
      对子:C313C14C24*(C14)3 = 1098240
      散牌:C513*(C14)5 = 131788813
    • 某种牌能胜过的同种牌型总数=(该种牌型牌值大小-最小牌值)/(最大牌值-最小牌值+1)*该种牌型总数。以同花顺56789为例,牌值为9,最小牌值为6(23456),最大牌值为14(10JQKA)。因此,该种牌能胜过的同种牌型总数=3/9*36=12。
  • 倒水的判断

    给每一种牌型一个基础分值,每墩牌大小=该种牌型基础分+牌值大小。基础分值必须大于13以确保高等级牌的分值大于低等级牌。起初直接使用每一墩的分值作为比较依据,但是在测试的时候发现由于前中墩的牌数不一样,会出现将正常的牌型判定为倒水。

5、关键代码解释


  • 服务器请求
    详见上文。

  • 分墩
    最暴力的办法。从13张中任选5张牌作为后墩,再从剩余8张中任选5张作为中墩,余下3张为前墩。再依次判断三墩牌型、是否倒水,计算分值。比较合法的每一种分墩的分值并返回。

    def solve(self):
        card_1 = list(itertools.combinations(self.cards, 5))  # 排列组合:13选5
        for card_a in card_1:  # card_a为后墩
            card_2 = self.del_5(card_a, self.cards.copy())  # card_2为除去后墩所余牌
            score_a, val_a = Judge(card_a).score_BM()   # 后墩的计算
    
            card_3 = list(itertools.combinations(card_2, 5))  # 8选5
            for card_b in card_3:  # card_b为中墩
                score_b, val_b = Judge(card_b).score_BM()
                if val_b > val_a:  # 中后倒水?
                    continue
                card_c = self.del_5(card_b, card_2.copy())  # card_c为前墩
                score_c, val_c = Judge(card_c).score_F()
                if val_c > val_b:  # 前中倒水?
                    continue
                score = score_c + score_a + score_b
                #  更新最大分值牌型
                if score > self.max[0]:
                    self.max[0] = score
                    self.max[1] = [card_c, card_b, card_a]
    
  • 中后墩分值的判断、计算
    牌型由大至小逐级判断,并计算相应牌型的分值,分值计算方法具体见上文。函数返回2个参数:获胜概率,即用于得出该墩牌的分值score,以及用于判断是否倒水的分值val。由于同花顺、炸弹、葫芦有得分加成,优先选择,因此这三种牌型的score乘以更高的比例。

    def score_BM(self):  # 中后墩
        x = self.tonghuashun()
        if x != 0:  # 同花顺
            return ((x - 6) * 4 + 2613324) / 2613360 * 2, x + 160
        x = self.zhadan()
        if x != 0:  # 炸弹
            return ((x - 2) * 48 + 2612700) / 2613360 * 1.5, x + 140
        x = self.hulu()
        if x != 0:  # 葫芦
            return ((x - 2) * 288 + 2608956) / 2613360 * 1.2, x + 120
        x = self.tonghua()
        if x != 0:  # 同花
            return ((x - 2) * 396 + 2603808) / 2613360, x + 100
        x = self.shunzi()
        if x != 0:  # 顺子
            return ((x - 6) * 1024 + 2594592) / 2613360, x + 80
        x = self.santiao_5()
        if x != 0:  # 三条
            return ((x - 2) * 4224 + 2539680) / 2613360, x + 60
        x = self.erdui()
        if x != 0:  # 二对
            return ((x - 2) * 9504 + 2416128) / 2613360, x + 40
        x = self.duizi_5()
        if x != 0:  # 对子
            return ((x - 2) * 84480 + 1317888) / 2613360, x + 20
        else:  # 散牌
            return ((self.card[0][0] - 2) + (self.card[1][0] - 2) * 0.1 + (self.card[2][0] - 2) * 0.01 + (self.card[3][0] - 2) * 0.001 + (self.card[4][0] - 2) * 0.0001) * 101376 / 2613360, 
                self.card[0][0] + self.card[1][0] * 0.1 + self.card[2][0] * 0.01 + self.card[3][0] * 0.001 + self.card[4][0] * 0.0001
    

6、性能分析与改进


  • 性能分析图(戳):程序时间消耗最大的是API的请求上,其次是solve函数。由于solve()要遍历所有分墩的可能并进行判断和计算分值,即有C51358 = 72072种可能。

  • 改进思路:API的访问基本没什么好改进的,因此主要针对算法方面。

    • 增加特殊牌型的判断,如果为特殊牌型就不必遍历所有可能组合,直接分墩输出,减少耗时。
    • 在所有可能中有一半是会出现倒水的情况,即前墩相同的情况下,中后墩的牌对调。两种情况中必有一种是不合规则的,可以考虑只判断其中一种情况,如果合法,另一种情况直接忽略;如果不合法,返回所得的分值,将牌的中后墩对调,同时删除另一种情况。可以减少很多计算量,不过实现起来可能会很困难。

7、单元测试


  • API连接:直接调用API,解析返回的json并输出,查看返回参数的status,若为0即为成功。

    # 登录
    url = 'http://api.revth.com/auth/login'
    form_data = { "username": 'czh', "password": '123456' }
    headers = { "Content-Type": 'application/json', }
    response = requests.post(url=url, headers=headers, data=json.dumps(form_data), verify=False)
    dicts = dict(json.loads(response.text))
    print(dicts)
    # 返回结果
    {'status': 0, 'data': {'user_id': 65, 'token': '90fdabbc-83cf-447a-a7b2-ddce99dcd429'}}
    
    # 开启战局
    response = requests.post(url='http://www.revth.com:12300/game/open', headers={"X-Auth-Token": token})
    dicts = dict(json.loads(response.text))
    print(dicts)
    # 返回结果
    {'status': 0, 'data': {'id': 63272, 'card': '*4 #2 $7 #J #6 &J *7 *10 &2 &K &4 &10 &8'}}
    
    # 出牌
    response = requests.post(url='http://api.revth.com/game/submit', data=outp, headers={"X-Auth-Token": token, "Content-Type": "application/json"})
    dicts = dict(json.loads(response.text))
    print(dicts)
    # 返回结果
    {'status': 0, 'data': {'msg': 'Success'}}
    
  • 算法测试

    • 构造数据:检测算法在各种牌型之间的取舍。
    • ['$A #10 #9', '*J $J $5 &2 *2', '&7 &6 #6 *6 $6'] ,将散牌中最小的$5和&7放于中后墩,与炸弹、连队结合,保证前墩牌能尽量大。
    • ['$K *Q #10', '#A *A #5 &3 *2', '&9 #9 &8 *8 &7'],检测二对中对于连对的选择。
    • ['#A *8 $2', '#J $J #9 *5 *5', '#K &K $7 &3 $3'],有对子3、5、J、K四个对子,将K分配给后墩,J分配给中墩,保证中后墩的牌尽量大。
    • 实战测试:直接扔服务器上跑,查看服务器所给的牌以及自己返回的分墩的牌。检测对随机产生的数据的应对。这里贴出随机的几组出牌情况。

    ['&7 *7 $2', '&K $K *6 &4 *3', '&A $A &Q *10 #8']
    ['&10 &4 #4', '$A $Q $6 $5 $3', '&K &7 #7 *7 $7']
    ['*A #J #8', '*Q $Q &9 #9 &2', '*7 $6 &5 *4 *3']
    ['&8 $8 *3', '&K $K *Q *J *6', '#A #8 #7 #4 #2']
    ['#K *6 *2', '&6 *5 *4 $3 &2', '&Q &J *10 $9 *8']

8、GitHub代码签入记录


9、遇到的代码模块异常或结对困难及解决方法


遇到的困难:没有接触过前端界面代码的编写、不会使用接口。
解决尝试:百度、看视频教程、向同学请教学习。
是否解决:
收获:学会了PyQt5的一些基本语法以及接口的使用。

遇到的困难:分工不够明确,双方做出来的作品不能兼容。
解决尝试:加强交流,双方多做尝试和调整。
是否解决:
收获:默契度UP!做事情还是要事先沟通好,可以减少许多不必要的麻烦。

遇到的困难:生成的exe闪退,在cmd中提示信息:unable to find Qt5Core.dll on PATH。
解决尝试:根据报错的提示信息在网上查找解决方法,新建一个fix_qt_import_error.py并导入
是否解决:
收获:新技能get✔(其实还是没怎么搞懂为啥)

10、队友评价


  • 陈锦鸿 To 钟伟颀

    值得学习的地方:颀哥的学习积极性很强,很早就在研究API的使用了。效率也是杠杠的,UI的编码很快就写的基本差不多了,紧接着又去学习窗口跳转方法,将一个个单独的界面串联起来。很有想法,同时也考虑实际,在适当的时候能够提出修改意见,是项目更加完善。
    需要改进的地方:没有,太完美了,非要挑刺的话,可能就是代码的规范性稍稍差了一点点吧。颀哥带飞!

  • 钟伟颀 To 陈锦鸿

    值得学习的地方:锦鸿哥的写算法能力真的强,这次十三水出牌算法就是由他来写的。并且效率也很高,算法牛批,快速上分,学习能力也很强,很快就完成了算法的编写然后来帮我一起做了一部分的UI界面,这个大腿抱紧就完事了嗷。
    需要改进的地方:没啥问题,这是一次很愉快的合作。

11、学习进度条


第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 0 0 9 9 学习使用Axure Rp 8制作原型
2 1542 1542 14 23 学会PyQt5、接口的使用
3 2052 3594 21 44 学习前后端交互方法、生成exe文件
原文地址:https://www.cnblogs.com/honger125/p/11735973.html