第二次结对编程作业

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,通过对牌型的组合比较,实现普通牌型的最优判断算法
posted @ 2019-10-15 22:18  Joyinginging  阅读(248)  评论(2编辑  收藏  举报