• 2019软工实践_作业4_1(结对编程实现博客)


    0、UI设计

    1、队友链接

    2、分工

    • 我负责出牌算法的编写,xr负责了其中特殊牌型的判断
    • UI部分由xr完成,以及在前端加入api接口与界面交互相连接

    3、PSP表格

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

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

    网络接口的使用

    def login ():#登陆
            conn = HTTPSConnection("api.shisanshui.rtxux.xyz")
            payload = "{"username":""+ login_user +"","password":""+login_password+""}"
            headers = {'content-type': "application/json"}
            conn.request("POST", "/auth/login", payload, headers)
            res = conn.getresponse()
            data = res.read()
            flag =loads(data.decode("utf-8"))
            if flag["status"]== 0:
                global token
                global player_id
                token = flag["data"]["token"]
                player_id = flag["data"]["user_id"]
    
    def regs():#注册
            login_user = self.lineEdit_2.text()
            login_password = self.lineEdit_3.text()
            conn = HTTPSConnection("api.shisanshui.rtxux.xyz")
            payload = "{"username":"" + login_user + "","password":"" + login_password + ""}"
            headers = {'content-type': "application/json"}
            conn.request("POST", "/auth/register", payload, headers)
            res = conn.getresponse()
            data = res.read()
            flag = loads(data.decode("utf-8"))
            print(flag)
            if flag["status"] == 0:
                self.box = QMessageBox(QMessageBox.Question, '提示', '注册成功,请登录!')
                yes = self.box.addButton('确定', QMessageBox.YesRole)
                self.box.show()
            else:
                self.box = QMessageBox(QMessageBox.Question, '提示', '该账号已被注册,请重新输入!')
                yes = self.box.addButton('确定', QMessageBox.YesRole)
                self.box.show()
                self.lineEdit_3.clear()
                self.lineEdit_2.clear()
        
    
    def get_battle():#开启牌局
        import requests
        url = "https://api.shisanshui.rtxux.xyz/game/open"
        headers = {'x-auth-token': token}
        response = requests.request("POST", url, headers=headers)
        return response.text
    
    
    def decode_data(data):#解码
        data_dict = json.loads(data)
        #print(data_dict)
        system_cards = data_dict["data"]["card"]
        nw_id = data_dict["data"]["id"]
        print(system_cards)
        return (nw_id, system_cards)
    
    
    

    思路描述

    • 首先,数据规模为$n=13$很容易想到枚举算法(大概是$O(13^{5} * 8^{5}$)?貌似也没有限时),只要暴力枚举前中墩的情况即可,因 - 为前中墩枚举结束后,剩余牌自动归为前墩。
    • 其次,对于枚举结束之后的三墩,先进行合法性检测,即三墩需满足:前墩$lt$中墩$lt$后墩等号取不到
    • 再者,对于合法的一组手牌,我们算出这组牌赢的概率的估计值。对,是概率的估计值,那么如何计算?因为我们偷到了一个权重数组,同时,我们维护一个大小为25的大根堆队友说25吉利,显然最后堆顶的牌组就是我们考虑的最优组合。
    • 最后,发到服务器,等待出分。

    设计实现说明

    • 爆搜或者10重for的嵌套。简单粗暴,除了久一点,没什么问题。
    • 爆搜或者5重for,先选出后墩,再选出中墩。即后中墩是分开选择的。显然可以出解,而且比上一种方法快很多。但是,会出现倒水的情况,即无法保证三墩的大小关系。
    • 先搜模式,匹配原始牌组,最后出牌。
    • AI算法,笔者没有头绪。
    • 最后选择了方法3因为一开始写的是方法2,发现倒水了,就被队友说服去写方法3了,而至于为什么不写方法1,因为方法1时间复杂度太高了$O(!)$,关于方法3的实现,请看官移步开头链接查看。

    类图




    流程图

    5、关键代码解释

    • 最初一个简(T)单(到)粗(无)暴(边)的爆搜,直接否掉,这就不能算是个解决方案,实在是太缓慢了。而接下来是一个暴力+贪心的版本。
    def dfs(card_list, nw, state):
        if ( nw == s ):
            ct = 0
            for i in range(len(state)):
                if ( state[i] == 0 ):
                    state[i] = 1
                    hands.append(card_list[i])
                    state[i] = 0
                    ct += 1
            print(hands)
            header = hands[:f]
            middle = hands[f:s]
            tailer = hands[s:t]
            #print(hands)
            #print(header); print(middle); print(tailer)
            ret = chk(header, middle, tailer)
            if ( ret[0] == 1 ):
                if ( len(q) >= hyper_n ):
                    if ( q[0].weight < ret[1] ):
                        heapq.heappushpop(q, HandCard(hands, ret[1]))
                else:
                    heapq.heappush(q, HandCard(hands, ret[1]))
            while (ct>0):
                hands.pop()
                ct -= 1
            return
        for i in range(len(state)):
            if ( state[i] == 0 ):
                state[i] = 1
                hands.append(card_list[i])
                dfs(card_list, nw+1, state)
                hands.pop()
                state[i] = 0
    
    hyper_n = 10
    f = 3
    s = 8
    t = 13
    q = []
    hands = []
    heapq.heapify(q)
    dfs(cards, 0, np.zeros(len(cards)))
    
    • 然后是我们真正采用的算法。暴力枚举13张牌可能组成的牌型,从单张(junks)到顺子(straights),每种牌型从大到小排序,再从中枚举组合出后中前墩的可能出牌模式,最后枚举判断是否合法即可,如若合法则加入丢入小根堆中维护,最后小根堆弹出至空的最后一组出牌即为所求。因为每部分都不短,还请看官移步文首的仓库品鉴(文件名为:_AutoRecommend.py)
    def RecommendHands( card_list ):
        sz = len(card_list)
        q = []
        heapq.heapify(q)
        nw_hands = []
        #last O(n^5) using the brute force to enumerate the combination of the last hands
        for i in range(0,sz,1):
            nw_hands.append(card_list[i])
            for j in range(i+1,sz,1):
                nw_hands.append(card_list[j])
                for k in range(j+1,sz,1):
                    nw_hands.append(card_list[k])
                    for g in range(k+1,sz,1):
                        nw_hands.append(card_list[g])
                        for t in range(g+1,sz,1):
                            nw_hands.append(card_list[t])
                            _c = nw_hands; _w = get_weight(_c, 2)
                            heapq.heappush(q, Hands(_c, -_w))
                            if len(q) > hyper_n:
                                _ = heapq.heappop(q)
                            nw_hands.pop()
                        nw_hands.pop()
                    nw_hands.pop()
                nw_hands.pop()
            nw_hands.pop()
        
        last_hands = []; middle_hands = []; header_hands = []
        while len(q)>0:
            last_hands.append(heapq.heappop(q))
    
        #last_hands = [ ([(1,1),(1,2),(1,3),(1,4),(1,5)],weight), (), (), ... () ].dtype = Hands([(),()],w)
        for _ in last_hands:
            #fir every last_hands choose the middle_hands and header_hands
            tp_card_list = card_list.copy()
            for i in _.list:
                for j in range(len(tp_card_list)):
                    if tp_card_list[j] == i:
                        tp_card_list.pop(j)
                        break
            
            #now tp_card_list contain only 8 cards for middle and header
            sz = len(tp_card_list)
            for i in range(0,sz,1):
                nw_hands.append(tp_card_list[i])
                for j in range(i+1,sz,1):
                    nw_hands.append(tp_card_list[j])
                    for k in range(j+1,sz,1):
                        nw_hands.append(tp_card_list[k])
                        for g in range(k+1,sz,1):
                            nw_hands.append(tp_card_list[g])
                            for t in range(g+1,sz,1):
                                nw_hands.append(tp_card_list[t])
                                _c = nw_hands; _w = get_weight(_c, 1)
                                heapq.heappush(q, Hands(_c, -_w))
                                if len(q) > hyper_n:
                                    _ = heapq.heappop(q)
                                nw_hands.pop()
                            nw_hands.pop()
                        nw_hands.pop()
                    nw_hands.pop()
                nw_hands.pop()
            
            while len(q)>0:
                X = heapq.heappop(q)
                middle_hands.append(X)
                tp2_card_list = tp_card_list.copy()
                szz = len(middle_hands)
                for i in middle_hands[szz-1].list:
                    for j in range(len(tp2_card_list)):
                        if i == tp2_card_list[j]:
                            tp2_card_list.pop(j)
                            break
                _c = tp2_card_list; _w = get_weight(tp2_card_list, 0)
                header_hands.append(Hands(_c, -_w))
        
        my_hands = []
        for lst in last_hands:
            for j in range(hyper_n):
                my_hands.append(  [(header_hands[j].list, header_hands[j].weight),
                                   (middle_hands[j].list, middle_hands[j].weight),
                                   (lst.list, lst.weight)] ) 
        #for i in my_hands:
        #   print(i)
        
        return my_hands
        
        #middle O(n^5) using the brute force to enumrate the combination of the middle hands 
        #first the rest, no choise to choose
    
    
    • 下面是做UI实现时将json转化成表格的代码
    #从api请求返回的排行榜数据
    # 将json格式转换成单元格内容
    item = [(j, c, data[c].values()) for j in range(3) for c in range(len(data))]
    for v in item:
        #print('行下标%s,列下标%s,值:%s' % (v[1], v[0], list(v[2])[v[0]]))
        newitem = QTableWidgetItem(str(list(v[2])[v[0]]))
        newitem.setTextAlignment(Qt.AlignHCenter | Qt.AlignBottom)
        if v[0]==1 or v[0]==0:
            self.tableWidget.setItem(v[1], v[0]+1, newitem)  
        else:
            self.tableWidget.setItem(v[1], v[0]-2, newitem)
    
    

    6、性能分析与改进

        ncalls  tottime  percall  cumtime  percall
           64    0.001    0.000   15.890    0.248 Algorithm_fight.py:142(get_battle)
           32    0.030    0.001    8.021    0.251 Algorithm_fight.py:155(decode_data)
           32    0.001    0.000    3.058    0.096 Algorithm_fight.py:168(my_choose)
          800    0.000    0.000    0.000    0.000 Algorithm_fight.py:180(<lambda>)
           32    0.001    0.000    7.483    0.234 Algorithm_fight.py:204(send_2_system)
           32    0.055    0.002   26.529    0.829 Algorithm_fight.py:221(_start)
            1    0.000    0.000   26.530   26.530 Algorithm_fight.py:239(main)
          416    0.000    0.000    0.000    0.000 Algorithm_fight.py:29(chg)
            1    0.000    0.000    0.000    0.000 Algorithm_fight.py:37(Hands)
        50944    0.019    0.000    0.025    0.000 Algorithm_fight.py:38(__init__)
       292872    0.034    0.000    0.034    0.000 Algorithm_fight.py:42(__lt__)
           32    0.125    0.004    3.056    0.096 Algorithm_fight.py:49(RecommendHands)
            1    0.000    0.000   26.767   26.767 Algorithm_fight.py:7(<module>)
            1    0.000    0.000    0.000    0.000 GetWeight.py:7(<module>)
        50944    0.240    0.000    2.790    0.000 GetWeight.py:99(get_weight)
    

    参数解释:

    • ncalls:表示函数调用的次数。
    • tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间。
    • percall:(第一个 percall)等于 tottime/ncalls。
    • cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间。
    • percall:(第二个 percall)即函数运行一次的平均时间,等于 cumtime/ncalls。
    • filename:lineno(function):每个函数调用的具体信息

    消耗最大的函数:

    def AutoRecommend(tp_cards, junks, pairs, triples, booms, straights, flushs, _2_pairs, _32_tps):
        _third  = booms + _32_tps + flushs + straights + triples + _2_pairs + pairs + junks
        _second = booms + _32_tps + flushs + straights + triples + _2_pairs + pairs + junks
        _first  = triples + pairs + junks
        nw_cards = tp_cards.copy()
        nw_cards.sort(key=lambda x:-x[1])
        #print("nw_cards = ", nw_cards)
        q = []
        hyper_n = 20
        my_weight = [1/5, 2/5, 2/5]
        heapq.heapify(q)
    
        for i in _third:
        
            #print("i = ",i)
            nwcs = nw_cards.copy()
            #print("nwcs0 ", nwcs)
        
            tail = i.copy()
            for ii in tail:
                nwcs.remove(ii)
        
            tp_nwcs0 = nwcs.copy()
            #print("nwcs after ii ", nwcs)
            tp_tail = tail.copy()
            for j in _second:
                nwcs = tp_nwcs0.copy()
                #print("j = ",j)
                tail = tp_tail.copy()
                mid = j.copy()
                flg = 1
                for jj in mid:
                    if ( jj in nwcs ):
                        nwcs.remove(jj)
                    else:
                        flg = 0
                        break
                if ( flg == 0 ):
                    continue
                
                tp_nwcs1 = nwcs.copy()
                #print("nwcs = ", nwcs)
                #print("nwcs after jj ", nwcs)
                tp_mid = mid.copy()
                for k in _first:
                    nwcs = tp_nwcs1.copy()
                    #print("k = ",k)
                    tail = tp_tail.copy()
                    mid = tp_mid.copy()
                    head = k.copy()
                    flg = 1
                    for kk in head:
                        if ( kk in nwcs):
                            nwcs.remove(kk)
                        else:
                            flg = 0
                            break
                    if ( flg == 0 ):
                        continue
                    
                    #print("nwcs the rest ", nwcs)
                    #print("nw_cards_0 = ", head, mid, tail)
                    #complete the head
    
                    #print(head); print(mid); print(tail)
                    
                    tpp = nw_cards.copy()
                    #print("tpp = ", tpp)
                    for hd in head:
                        tpp.remove(hd)
                    for mi in mid:
                        tpp.remove(mi)
                    for tl in tail:
                        tpp.remove(tl)
                    
                    pos = 0
                    while ( pos < len(tpp) ):
                        if ( len(head) < 3 and pos < len(tpp) ):
                            head.append(tpp[pos])
                            pos += 1
                        if ( len(mid) < 5 and pos < len(tpp) ):
                            mid.append(tpp[pos])
                            pos += 1
                        if ( len(tail) < 5 and pos < len(tpp) ):
                            tail.append(tpp[pos])
                            pos += 1
                
                    w_h, w_m, w_t = get_weight(head,0), get_weight(mid,1), get_weight(tail,2)
                    nw_w = (np.array([w_h,w_m,w_t])*np.array(my_weight)).sum()
                    #print("nw_cards_1 = ", head, mid, tail)
                    chk_val = chk_ordered(head, mid, tail)
                    if ( chk_val[0] == 1 ):
                        if ( len(q) < hyper_n ):
                            heapq.heappush(q,HandCard(head+mid+tail, nw_w))
                        else:
                            if ( nw_w > q[0].weight ):
                                heapq.heappushpop(q, HandCard(head+mid+tail, nw_w))
    
        result_cards = []
        while ( len(q) > 0 ):
            result_cards.append(heapq.heappop(q))
        ret = result_cards[len(result_cards)-1].list
        return ret
    

    改进思路:

    • 其实,撇开网络请求,自我感觉完成得还是不错的。
    • 改进方面,不断推翻前面的方案,最终在临近ddl的时候灵光一现,找到了省时方便又合理的贪心组合解决方法。
    • 改进的时候就是看时间是否满足调节,通过算法的优化减小时间的耗费。
    • 从暴搜到简单搜索到牌型贪心组合。枚举13张牌可能组成的牌型,从单张(junks)到顺子(straights),每种牌型从大到小排序,再从中枚举组合出后中前墩的可能出牌模式,最后枚举判断是否合法即可。

    7、单元测试

    构造思路

    • 使用unittest.TestSuite()
    • 按牌型顺序进行判牌单元测试
    • pair 2pairs triple boom straight flush fullhouse分别判断
    • 输出判断的值
    #按牌型顺序进行判牌单元测试
    #pair 2pairs triple boom straight flush fullhouse分别判断
    class UnitTest(unittest.TestCase):
        
        @classmethod
        def setUpClass(self):
            pass
    
        @classmethod
        def tearDownClass(self):
            pass
    
        def tst_jdg_pair(self):
            ct = 0
            for i in open('./UnitTest-in.txt').readlines():
                ct += 1
                nw = []; cards = i.split()
                for j in cards:
                    nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
                #print(nw)
                print("card %d " % (ct), end="")
                print(jdg_pair(nw))
    
        def tst_jdg_2pairs(self):
            ct = 0
            for i in open('./UnitTest-in.txt').readlines():
                nw = []; cards = i.split()
                for j in cards:
                    nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
                #print(nw)
                ct += 1
                print("card %d " % (ct), end="")
                print(jdg_2pairs(nw))
    
        def tst_jdg_triple(self):
            ct = 0
            for i in open('./UnitTest-in.txt').readlines():
                nw = []; cards = i.split()
                for j in cards:
                    nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
                #print(nw)
                ct += 1
                print("card %d " % (ct), end="")
                print(jdg_triple(nw))
    
        def tst_jdg_boom(self):
            ct = 0
            for i in open('./UnitTest-in.txt').readlines():
                nw = []; cards = i.split()
                for j in cards:
                    nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
                #print(nw)
                ct += 1
                print("card %d " % (ct), end="")
                print(jdg_boom(nw))
    
        def tst_jdg_straight(self):
            ct = 0
            for i in open('./UnitTest-in.txt').readlines():
                nw = []; cards = i.split()
                for j in cards:
                    nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
                #print(nw)
                ct += 1
                print("card %d " % (ct), end="")
                print(jdg_boom(nw))
    
        def tst_jdg_flush(self):
            ct = 0
            for i in open('./UnitTest-in.txt').readlines():
                nw = []; cards = i.split()
                for j in cards:
                    nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
                #print(nw)
                ct += 1
                print("card %d " % (ct), end="")
                print(jdg_flush(nw))
    
        def tst_jdg_fullhouse(self):
            ct = 0
            for i in open('./UnitTest-in.txt').readlines():
                nw = []; cards = i.split()
                for j in cards:
                    nw.append((suit_sa[j[0]],number_sa[j[1:len(j)]]))
                #print(nw)
                ct += 1
                print("card %d " % (ct), end="")
                print(jdg_fullhouse(nw))
    
    test_lists = ["tst_jdg_pair","tst_jdg_2pairs","tst_jdg_triple","tst_jdg_boom","tst_jdg_straight","tst_jdg_flush","tst_jdg_fullhouse"]
    if __name__ == "__main__":
        for i in test_lists:
            suit = unittest.TestSuite()
            suit.addTest(UnitTest(i))
            runner = unittest.TextTestRunner()
            runner.run(suit)
    

    8、贴出Github的代码签入记录


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

    问题描述

    曾世缘:

    • 结对没有尽早开始写代码,拖延得比较后面。国庆当然是快乐
    • 关于算法结果有冲突。倒水我是不想重写的

    庄锡荣:

    • PyDesinger每个页面制作完成后,在页面的切换出现问题,无法通过按钮相互切换
    • Mainwindow弹窗功能使用不了

    做过哪些尝试

    曾世缘:

    • 意识到快来不及了,马上动工233
    • 队友说得对,就重写吧。

    庄锡荣:

    • 查阅别人实现的代码,阅读Pyqt5接口文档
    • 根据别人的用法基础上修改,找到能使自己的Mainwindow可以弹窗的用法

    是否解决

    曾世缘:

    +较好解决。显然我们完成了作业,我也解决了倒水。

    庄锡荣:

    • 解决且实现了弹窗问题和窗口页面相互切换调用

    有何收获

    曾世缘:

    • 队友是个好队友,我就不知道了。
    • 学了一下python自带堆heapq的使用

    庄锡荣:

    • 依旧是觉得做前端很累
    • 学习新语言的使用还是要多尝试,在实践中不断踩坑,在踩坑中不断成长

    10、评价你的队友

    值得学习的地方

    • 儒雅随和,不紧不慢。

    心得:

    曾世缘:

    1、感谢我的队友,考虑到我的各种原因,包揽了前端,还写了特殊牌型得判断,最后放我来写我自己最熟悉的算法部分,同时队友也非常体谅我的进度。平心而论,如果这次是个人作业,我可能就完成不了了,或者说只会做出一个非常丑陋的UI界面。
    2、一开始想写爆搜,怕爆栈就丢了for上去,确实除了慢点没什么问题,剪了剪枝也没有非常出乎意料的提升。暴力出奇迹,这是我写过最暴力的工程代码,但是却有着极低的编程复杂度。

    庄锡荣:

    1、这次的结对作业也算为之后的团队作业打下基础吧,自己应该学会如何去管理调整整个项目的进度,合理的发挥每个人的长处,而不是都堆积在一起实现一两个功能。
    2、应该尽早开始做提前准备工作,而不是临时开始学习要用的东西,赶工出来的结果一定不会是最优秀的作品。

    学习进度条

    周数 新增代码(行) 累计代码(行) 本周学习消耗(小时) 累计学习消耗(小时) 重要成长
    4 0 0 21 21 AxureRp的学习与制作
    6 800+ 800+ 0.2 21.2 十三水出牌算法的实现,以及py自带堆的学习
  • 相关阅读:
    排序算法之快速排序
    设计模式之原型模式
    设计模式之门面模式
    第五十四课 树中节点的插入操作
    第五十三课 树中节点的查找操作
    第五十二课 树的存储结构与实现
    第五十一课 树的定义与操作
    第五十课 排序的工程应用示例
    第四十九课 归并排序和快速排序
    第四十八课 冒泡排序和希尔排序
  • 原文地址:https://www.cnblogs.com/FormerAutumn/p/11667364.html
Copyright © 2020-2023  润新知