zoukankan      html  css  js  c++  java
  • 第二次结对编程作业

    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文件
  • 相关阅读:
    700. Search in a Binary Search Tree
    100. Same Tree
    543. Diameter of Binary Tree
    257. Binary Tree Paths
    572. Subtree of Another Tree
    226. Invert Binary Tree
    104. Maximum Depth of Binary Tree
    1、解决sublime打开文档,出现中文乱码问题
    移植seetafaceengine-master、opencv到ARM板
    ubuntu16.04-交叉编译-SeetaFaceEngine-master
  • 原文地址:https://www.cnblogs.com/WAYNEEZHONG/p/11765716.html
Copyright © 2011-2022 走看看