结对编程作业

结对编程作业

一、开头

博客链接

Github项目地址

具体分工

AI算法 大比拼 原型设计 GUI 博客编写 Github
魏荣峰
卓越

二、原型设计

设计说明

该原型展示了图片华容道小游戏的部分基本功能与页面,采用MockingBot0.7.8进行原型设计。

游戏中会将原始字符图片平均切割成九个部分,并抠掉一张图当空格。此时图片为原始状态。游戏开始时会将小图顺序打乱,用户通过点击鼠标进行移动小图,直到将打乱的图片恢复至原始状态上即可完成游戏。

原型中的主页面即为进入游戏后的初始页面。中间的图片为所提供的所有字符图片的原图。下方有开始游戏、游戏介绍、排行榜、历史得分四个可点击项。用户可以通过点击第一项开始游戏、点击第二项查看该游戏的基本介绍、点击第三项查看排行榜,点击第四项查看历史得分。

点击Introduction,进入到游戏介绍的页面。用户可在该页面查看游戏规则。阅读完毕后,用户可通过点击下方返回项返回到主页面。

回到主页面。用户点击开始游戏项,可以开始游戏,进入到游戏开始后的初始状态,页面中为被切割并打乱的字符图片。用户通过点击空白两侧的小图片进行图片移动。页面上方有退出项,用户可以点击退出返回到主页面。页面右侧的悬浮框会记录当前移动步数,初始为0。移动到一定步数的时候,系统会强制调换此时棋盘上的两个格子,由于此时棋盘不一定有解,玩家有一次自由调换的机会,可选择调换任意两个图片的位置,该权利仅在棋盘无解的情况下使用,且需紧接着强制调换的操作

成功复原图片后,游戏结束。页面上方会出现提示游戏结束的相关字样。用户可以选择点击重新开始项重新开始游戏,或者点击返回项回到主页面。

点击主页Rank,进入排行榜页面,可查看历史玩家得分的排行情况。

点击主页History Score,可查看各玩家历史得分情况。

原型开发工具

采用的原型开发工具为MockingBot0.7.8

结对的过程

结对的过程:在团队项目小组内进行结对。

结对照片:

非摆拍.jpg

遇到的困难

由于之前没有接触原型设计和相关的开发工具,在熟悉Mockingbot工具方面花费了较多的时间。好在Mockingbot是中文版的开发工具,上手难度较低。通过观看网上的一些教程,对一些基本功能有了初步的了解。

在设计页面点击和切换功能的时候,由于对开发工具不够熟悉,不知道如何实现该功能,后来发现只要通过拖拽具体模块旁边的闪电标志,连接到具体页面即可实现该功能。同时也学会了使用其他的一些操作,但是界面的美观度还有待提高,希望在以后的原型设计中能够有所改进。

三、AI与原型设计实现

1、代码实现思路

  • 网络接口的使用

    获取数据:

    通过requests库get到url中的json格式数据,并用eval()转换为列表便于提取

    def interfaceJson(name):
        te = requests.get("http://47.102.118.1:8089/api/problem?stuid=041801420")
        te = eval(te.text)
        img = base64.b64decode(te['img'])
        step = te['step']
        swap = te['swap']
        uuid = te['uuid']
        with open(name + '.png', 'wb') as f:
            f.write(img)
        f.close()
        print("get test image successfully")
        print("saved to : ", './' + name + '.png')
        return step, swap, uuid
    

    提交结果:

    通过requests库的post函数提交结果,并得到返回的测试结果

    def post(uuid, operation, swap):
        postUrl = 'http://47.102.118.1:8089/api/answer'
        swap[0] += 1
        swap[1] += 1
        datas = {
            "uuid": str(uuid),
            "answer": {
                "operations": str(operation),
                "swap": swap
            }
        }
        s = json.dumps(datas)
        print(s)
        headers = {'Content-Type': 'application/json'}
        r = requests.post(url=postUrl, headers=headers, data=s)
        print(r.text)
    
  • 代码组织与内部实现设计(类图)

  • 关键算法流程图

    A star

  • 我认为有价值的代码块

    1️⃣根据图片像素不变的属性对于缺了1/9的图片来说,即使将其打乱,这张图片的像素值仍然是不变的;

    在此前,前提得到了所有36种字母的childs(也就是把每张图片都分别生成9张childs,分别在1-9这九个位置设置空白格),在函数countImgs()中,算出每张图像的平均像素值,由此作为该字母's child的索引,从而定位到某一个child,再通过取余确定原图。

    由以下代码可以得到所有字母的所有缺块情况图片的索引,数据保存在./childIndex.txt,据此我们将实现快速识别出测试图片是哪个字母。

    def countImgs(folderPath):
        """
        注意:由于采用的是img[:, :, 0].mean(),速度较快,仅用于映射识别到原图像
        制作索引映射集合,已通过验证,请不要修改此函数
        :param folderPath: 输入一个文件夹路相对径--'./child/'
        :return: 计算出child文件夹内所有子文件夹下的所有字母(不同形态空白)的像素均值
                采用img[:, :, 0].mean(),目的是标记每一张图片,作为索引
        example:
            countImgs('./child/')
        """
        with open(folderPath + '/childIndex.txt', 'a+', encoding='utf-8')as text:
            root_dir = folderPath
            sub_list = os.listdir(root_dir)
            for folder in sub_list:
                print(folder)
                if folder.split('.')[-1] == 'txt':
                    continue
                img_list = os.listdir(os.path.join(root_dir, folder))
                for img_name in img_list:
                    if img_name.split('.')[-1] != 'png' and img_name.split('.')[-1] != 'jpg':
                        continue
                    img_path = os.path.join(root_dir, folder, img_name)
                    print(img_path)
                    img = cv2.imread(img_path)
                    text.write(str(img[:, :, 0].mean()) + '\n')
                    print("index:", str(img[:, :, 0].mean()))
        text.close()
    

    2️⃣同样根据像素的原理,我们对每一张test的图片进行切割,分成9份,找到的原图进行一一比对并且与之前,将原图按12345678顺序编码,白块固定编码为0,由此映射到test图片,得到test图片对应编码

    根据原图和test图片的编码,我们就可以开始A * 算法进行路径搜索

    def getSequence():
        step, swap, uuid = interfaceJson('test')
        testIndex = countImg('./test.png')
        # origImgPath -> ./child\A_ (2)\_sub7.png
        origImgPath = str(mapped(testIndex))
        # countImg('./child/O_/_sub8.png')
        clip('./test.png')
        # 因为origImgPath是os.path.join生成的,并不全是相对路径的/,所以不能直接用clip函数中自带的split方法
        clip(origImgPath.replace('\\', '/'))
    
        testBlocks = count("./imgsClipped/test")
        origBlocks = count('./imgsClipped/' + str(origImgPath).split('\\')[-1].split('.')[0])
        origT = []
        testT = []
        cnt = 1
        for item in origBlocks:
            if item == 255:
                origT.append(0)
            else:
                origT.append(cnt)
                cnt += 1
        # print(origT)
        for item in testBlocks:
            testT.append(origT[origBlocks.index(item)])
        print("orig: ", origT, '\n', "test:", testT)
        print("swap at steps:", step)
        swap[0] -= 1
        swap[1] -= 1
        print("to be swap:", swap)
        return origT, testT, step, swap, uuid
    

    3️⃣搜索核心:A star算法

    def AStar(init, goal):
        # 边缘队列初始已有源状态节点
        size = int(9 ** 0.5)
        start = tuple(init)
        process = list([])
        target = tuple(goal)
    
        # 优先队列,值越小,优先级越高
        pQueue = PriorityQueue()
        pQueue.put([0 + calDistance(start, size), start, start.index(0), 0, process])
    
        seen = {start}  # 已遍历过的结点
    
        while not pQueue.empty():
            _pri, board, pos0, depth, process = pQueue.get()
    
            if board == target:
                return depth, process
            for d in (-1, 1, -size, size):  # 对应的是左右上下的相邻结点
                nei = pos0 + d
                if abs(nei // size - pos0 // size) + abs(nei % size - pos0 % size) != 1:
                    continue
                if 0 <= nei < size ** 2:  # 符合边界条件的相邻结点
                    newboard = list(board)
                    newboard[pos0], newboard[nei] = newboard[nei], newboard[pos0]
                    newt = tuple(newboard)
                    if newt not in seen:  # 没有被遍历过的状态
                        seen.add(newt)
                        # pQueue.put([depth + 1 + 2 * self.calDistance(newt), newt, nei, depth + 1, process + [d]])
                        pQueue.put(
                            [depth + 1 + 0.9 * calDistance(newt, size), newt, nei, depth + 1,
                             process + [d]])  # 调整启发函数的权重可以缩短时间但增加步数
    
  • 性能分析与改进

    这是原本采用BFS的性能图描述(在A*中设置启发值为0)

    测试耗时长达5s+

  • 描述你改进的思路

    在BFS搜索算法中,如果能在搜索的每一步都利用估价函数f(n)=g(n)+h(n)对Open表(队列)中的节点进行排序,则该搜索算法为A*算法。由于估价函数中带有问题自身的启发性信息,因此,A*算法又称为启发式搜索算法,可以大大减少搜索时间。

  • 性能分析图和程序中消耗最大的函数

2、GitHub签入信息

![](https://img2020.cnblogs.com/blog/2145618/202010/2145618-20201019222321331-1571597132.png) ![](https://img2020.cnblogs.com/blog/2145618/202010/2145618-20201019222334177-92809467.png)

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

  • 问题描述

    说来话长,挑几个最痛苦的异常一吐为快:

    🅰️最早的时候想,c++的速度应该会比python慢很多,假若大家都用python,而我用c++,应该可以在速度上跑出优势。国庆花了一天弄出c++代码,眼看调试出正确路径挺开心,那么如何将python与c++接口连接🔗呢?

    🅱️为什么大比拼的测试总是得到false?明明手动移的方块都是正确的??

  • 解决尝试

    🅰️了解了ctypes库的使用,然而不知道什么原因,在Windows上python总是无法成功调用,只能采用Linux

    🅱️在一遍遍的手动模拟之下,眼睛都看花了。。最后咨询了测试组才知道,规则没有理解正确,所以算法是完全错误的。

  • 是否解决

    🅰️首先,在编译上我觉得是我系统的问题,经常在安装python包的时候如果需要使用到vs16(好像是这个)编译得到whl的我都会报错,即使我安装了全套vs2015和2019。后面越来越觉得用c++实在不现实,幸好提前退水改用python,不然后面这么多规则怎么改的过来。

    🅱️对于我来说,时间实在是有点紧迫,直到AI大比拼的第三天我才知道规则没有理解正确

    个人认为,这里的step表示第几步进行强制交换,那应该是走了step-1步以后到了即将走第step步时交换呢,还是走完step步了在第step步的时候做交换?可能是我语文水平的问题

  • 有何收获

    读懂规则确实重要。虽然说很多的消息都是在群里流水账一般源源不断,但总得静下心来,仔细阅读一番,才能明确方向,而不是一直按照自己的理解。

    最后一天才写出能在大比拼中每submit每true的代码,可能也是不想留遗憾吧,一方面不希望之前的努力付之东流,因为我很清楚其实离成功很近,只是规则上没有完全理透,然而另一方面来说,其实还是很遗憾,最后一天已经改变不了多少分数了,即使我这三天都没有改代码,用之前旧版的代码也能rank20-40,功利上来说确实亏了。

4、评价队友

  • 值得学习的地方

    rfgg牛逼,永远滴神。队友承担了大部分工作量,从算法、AI大比拼、代码,到签入github,每一项工作都做的非常出色。

  • 需要改进的地方

    无。

5、PSP

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 400 400 6 6 熟悉python语言tkinterpyqt特性
2 500 900 12 19 c++,了解了ctype用法
3 400 1300 12 31 完成字母识别
4 800 2100 20 51 学习A star,完成AI算法,基本游戏界面
5 400 2500 8 59 改进AI算法,测试

四、最后的总结

这次结对编程,虽然学习了一些原型设计和相关开发工具的基本知识和使用方法,但是在这方面依然还有很多需要学习的地方。大量在实际开发过程中呈现出来的功能和页面,我没能通过最初的原型设计实现,这也是令我感到很遗憾的地方。

我的队友承担了本次结对编程作业的很大一部分工作量,除了设计原型外,其他大部分内容都由他完成,我常常只是起到了一个帮助他测试测试代码,完善一些小细节、打打杂之类的微乎其微的作用。这是我需要反思和总结的地方。

希望在以后的项目和作业中,不仅能够发挥自己原本的一些优势,也要勇敢跳出舒适圈,尝试更多新的任务和角色,不断突破自我,取得更多的进步吧。

posted @ 2020-10-19 22:32  yuqiao1120  阅读(121)  评论(0编辑  收藏  举报