• 第二次结对编程作业


    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']

    陈锦鸿 2019/10/30 17:38:50

    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文件
  • 相关阅读:
    笑话(真人真事)一则
    Object Builder中的Locator究竟是不是采用Composite的模式之我见
    C++AndC#我的程序员之路
    C#中各种十进制数的转换
    使用GotDotnet workSpace手记
    检索 COM 类工厂中 CLSID 为 {0002450000000000C000000000000046} 的组件失败
    CSS如何让同一行的图片和文字垂直居中对齐(FF,Safari,IE都通过)
    怎样练习一万小时成为顶级高手?
    CSS控制大小写
    做SEO权重计算公式
  • 原文地址:https://www.cnblogs.com/WAYNEEZHONG/p/11765716.html
Copyright © 2020-2023  润新知