Fork me on GitHub

第一次结对编程作业

结对编程作业

PATR 1 博客信息

1.1博客链接🔗

姓名 学号 博客地址
柳越 041801413 点这里🔗
张宏铮 061800339 点这里🔗

1.2 Github项目链接🔗

姓名 学号 项目地址
柳越 041801413 点这里🔗
张宏铮 061800339 点这里🔗

1.3具体分工

AI算法 大比拼 原型设计 博客编写 Github
柳越
张宏铮

PATR 2 原型设计

2.1设计说明

2.1.1设计意图

一方面为了是为了结对编程的作业

另一方面参考了许多市面上存在的数字、图片华容道游戏,想对这些已存在的游戏加以改进,增加一些更为完善,便利的功能

2.1.3运行环境

在windows平台下运行

提供了打包好的可执行文件

2.1.4原型设计图片

采用了Axure Rp进行原型设计

2.1.5功能描述

(1)基本华容道功能:默认为3*3版华容道,可以用鼠标或者键盘的WASD及上下左右键来操作,以将打乱的图片拼回原图为目标

(2)重置功能:重置图片及打乱的顺序

(3)单步返回:返回上一步

(4)单步运行:程序AI自动选择最可能的下一步

(5)自动运行:以0.3s/步的速度让程序自动拼完图片

(6)断点返回:返回至自动运行前的顺序

2.1.6原型实现图片



2.2结对过程及照片

2.2.1任务分工描述

在此次的结对编程任务中

队友主要负责原型的设计及实现,网络接口的调用,AI大比拼中图片获取后的初步处理。

我主要负责AI大比拼中核心算法的编写以及后期博客的编写。

2.2.2照片

2.2.3遇到的困难及解决方法

  • 困难描述
    • 一开始都不知道该怎么使用原型设计工具,并且想着设计太难可能会做不出来
  • 解决尝试
    • 尝试去Github找类似项目参考,对于Axure工具的使用也是在b站上的教程上学习的
  • 是否解决
    • 已解决
  • 有何收获
    • 对原型的设计有了进一步的了解,也对本次的项目有了清晰的认识,更好的进行原型的实现

PATR 3 AI与原型设计实现

3.1 代码实现思路

3.1.1 网络接口的使用

对于AI大比拼中网络接口的调用

使用POSTman进行接口的测试

利用python中的request和json库实现自动提交

request库提供了get和post方法

json库提供了json解析

get_undo函数将所有未做的题目的uuid保存在list.txt文件中

get_start函数将list.txt文件中的uuid逐行提出,获取题目

submit函数将题目提交

import requests,json
def get_undo():
    r = requests.get('http://47.102.118.1:8089/api/team/problem/49')
    uuid = []
    for i in r.json():
        uuid.append(i['uuid'])
    print(uuid)
    with open('get_list_file.txt','w') as f:
        for i in uuid:
            f.write(i)
            f.write('\n')
def get_rank():
    r = requests.get('http://47.102.118.1:8089/api/rank')
    print(r.json())
def get_start(uuid):
    url_json = 'http://47.102.118.1:8089/api/challenge/start/'+uuid
    data = {
        "teamid": 49,
        "token": "4357e1a5-d93a-4fc4-8b3b-008d2c9dcfba"
    }
    headers = {"Content-Type":"application/json"}
    r_json = requests.post(url_json.strip(),headers = headers,data = json.dumps(data))
    img = r_json.json()['data']['img']
    step = r_json.json()['data']['step']
    swap = r_json.json()['data']['swap']
    uuid = r_json.json()['uuid']
    chance = r_json.json()['chanceleft']
    print("剩余机会为 %s"%chance)
    with open('1.txt','w') as f:
        f.write(img)
    return (step , swap , uuid )
def submit(data):
    url_json = 'http://47.102.118.1:8089/api/challenge/submit'
    headers = {"Content-Type": "application/json"}
    r_json = requests.post(url_json.strip(), headers=headers, data=json.dumps(data))
    print(r_json.json())
if __name__ == '__main__':
    get_undo()
    #get_rank()
    with open('get_list_file.txt','r') as f:
        uuid = f.readline()
    step, swap, return_uuid = get_start(uuid)
    #操作
    data ={
        "uuid": return_uuid ,
        "teamid": 49,
        "token": "4357e1a5-d93a-4fc4-8b3b-008d2c9dcfba",
        "answer": {
            "operations": operations,
            "swap": return_swap
        }
    }
    submit(data)

3.1.2 代码组织与内部实现设计(类图)


3.1.3 算法的关键与关键实现部分流程图

​ 该算法关键部分的实现主要是通过BFS+A *搜索算法共同完成的。通过A *的思想,每次计算出从当前8数码序列的所有节点走回到最终的目标位置的距离(启发值)与移动的步数之和,记录为一个权重值,并将此时的8数码序列,空白图存放的位置,移动步数,无解时执行交换的图片1和图片2,以及总体的操作过程共同保存在一个列表中。然后通过BFS的思想,将该列表存入一个优先队列中,此时优先队列按照列表中权重值的从小到大存放。在操作过程中,每次选出权重最小的的列表,并将该列表的8数码序列中的数字0分别进行上,下,左,右的交换,并将该操作以数字形式(-3,3,-1,1)保存在操作过程中,并计算出此时的启发值与移动步数的和,作为新的权重,存放在优先队列中。如此反复循环,直到第一次出现列表中的8数码序列等于设定的最终序列后,退出循环,返回此时的操作过程。

3.1.4 有价值的代码片段

    def goBackDistance(num):
        dist = 0
        row = 0
        col = 0
        for i in range(len(num)):
            if num[i] == 0:
                continue
            else:
                row = abs((num[i] - 1) // 3 - i // 3)  # 竖直距离之差
                col = abs((num[i] - 1) % 3 - i % 3)    # 水平距离之差
                dist = dist + row + col  			   # 当前位置到最终位置之差
        return dist

    def search(self, board):
        operation = []
        begin = tuple(board)

        Queue = PriorityQueue()
        # 启发值,开始序列,空白图的位置,步数,交换图片1,交换图片2,操作过程
        Queue.put([self.goBackDistance(begin), begin, begin.index(0), 0, 0, 0, operation])
        vis = {begin}        # 遍历过的序列

        while Queue.not_empty:
            prioritynum, board, pos, steps, change_pos1, change_pos2, operation = Queue.get()
            # 优先级,序列,空白图位置,交换图片1,交换图片2,操作过程
            if board == final:  # 得到最终解时
                num1 = change_pos1
                num2 = change_pos2
                for i in range(len(operation)):
                    if operation[i] == -1:
                        operation[i] = 'a'
                    elif operation[i] == 1:
                        operation[i] = 'd'
                    elif operation[i] == 3:
                        operation[i] = 's'
                    elif operation[i] == -3:
                        operation[i] = 'w'

                return steps, operation, change_pos1, change_pos2

            if request_steps == steps: # 步数和指定步数相同时
                board = list(board)
                board[pic_num1 - 1], board[pic_num2 - 1] = board[pic_num2 - 1], board[pic_num1 - 1]  # 交换图片
                count = 0  # 统计逆序数个数的奇偶性
                for i in range(len(board)):
                    for j in range(i + 1, len(board)):
                        if board[j] == 0:
                            continue
                        if board[i] > board[j]:
                            count = count + 1

                if (count % 2) == 1:  # 奇数 — 无解
                    pos1, pos2 = self.quickExchange(board) # 寻找交换的最优情况
                    change_pos1 = pos1
                    change_pos2 = pos2
                    board[pos1], board[pos2] = board[pos2], board[pos1] # 交换情况

            pos = board.index(0)      # 更新空白图的位置
            for i in (-1, 1, -3, 3):  # 左右上下
                pos0 = pos + i
                row = abs(pos0 // 3 - pos // 3)  # 竖直距离之差
                col = abs(pos0 % 3 - pos % 3)    # 水平距离之差

                if row + col != 1:
                    continue
                if pos0 < 0:
                    continue
                if pos0 > 8:
                    continue

                newboard = list(board)
                newboard[pos0], newboard[pos] = newboard[pos], newboard[pos0]
                visboard = tuple(newboard)
                if visboard not in vis:
                    vis.add(visboard)   # 加入遍历过的序列
                    tup1 = operation
                    tup2 = [i]
                    tup1 = tup1 + tup2
                    Queue.put([steps + 1 + self.goBackDistance(newboard), newboard, pos0, steps + 1, change_pos1, change_pos2, tup1])
                    
   def quickExchange(self,board):
        min = 0xfffff
        dist = 0
        pic1 = 0
        pic2 = 0
        for i in range(len(board)):
            if board[i] == 0:
                continue
            for j in range(i+1, len(board)):
                if board[j] == 0:
                    continue
                board[i], board[j] = board[j], board[i]
                dist = self.goBackDistance(board)
                if dist < min:
                    min = dist
                    pic1 = i
                    pic2 = j

                board[i], board[j] = board[j], board[i]

        return pic1, pic2

goBackDistance()函数主要计算的是当前节点到最终节点之间的曼哈顿距离,然后逐个相加,作为启发函数的估计值。

quickExchange()函数主要是当交换图片无解后,依次选择非空白图片的两张图片,将其位置交换,计算出此时的估计值,并记录下来,最终选择对应估计值最少的两张图片,将其位置进行交换。

search()函数是解决该问题的核心,具体思路便是将BFS和A*算法结合起来,计算出启发值,并将启发值,开始序列,空白图的位置,步数,交换图片1,交换图片2,操作过程保存为列表里,存入优先队列,并按照启发值从小到大排序,依次取出启发值最小的序列,进行上下左右交换。具体思路上面有详细的介绍。

3.1.5 性能分析与改进

在程序的最终版本中,主要的时间损耗在图像处理中

图像处理的方法是将获取的打乱图片分块,利用python中的PIL库与所有图片的分块对比,先确定是哪个字母,再对比编号

  • 由于打开文件和对比文件比较耗时,并且每次都要对比9*9*36张图,数据量过于庞大,通常在5-6秒才能完成

  • 当初调换完图片后无解的时候,选择调换任意的两张图片,由于当初空白图片的位置在调换的时候可能会被替换,导致最终结果错乱

3.1.6 改进的思路

  • 添加一个计数量,在打乱图片分块跟原图分块对比超过三张并且小于等于1张相等则跳出。否则就确定是这张图。(并没有想到更好的优化方案)

    def Pics_Compare(num):  #九张分块图片和九张原图分块对比并编号
        #创建编号数组
        code_list = [0 for i in range(9)]
        #创建相似度二维数组
        sim = [[0 for i in range(9)] for j in range(9)]
        #计数,看一组图片中有多少块相似的
        count = 0
    
        #每张分块和原图片的九张分块对比
        for i in range(1,10):
            path_one = '%d.jpg'%i
            #如果对比三次以上,还没有发现有两张图片相似,则跳出循环
            if i > 3 and count <= 1:
                break
            #每张分块和九张分块对比
            for j in range(1,10):
                path_two = r'.\无框字符分块\%d_%d.jpg'%(num,j)
                sim[i-1][j-1] = int(Pic_Compare(path_one,path_two))
                if sim[i-1][j-1] == 0:
                    count += 1
                    #编号
                    code_list[i-1] = j
        #如果相似的块大于4,就认为是同一组图片,返回确认值
        if count > 4:
            flag = 1
        else:
            flag = 0
        return (code_list,flag)
    
  • 对于交换完图片后无解的情况,理论上来说,只要调换除了空白图片的任意两张图片,都可以将其转换为有解的状态。但交换任意的两张图片,可能会破坏当前的8数码序列的状态,导致增加了最终步数。因此,改进的思路是从第一张图片开始,遇到空白图片就跳过,然后依次交换图片,计算出每次交换后的启发值,并记录下该启发值和交换的两张图片。最后选出对应启发值最小的两张图片,将其位置调换,将此时的8数码序列转换有解情况。

if request_steps == steps:  # 步数和指定步数相同时
    board = list(board)
    board[pic_num1 - 1], board[pic_num2 - 1] = board[pic_num2 - 1], board[pic_num1 - 1]  # 交换图片
    count = 0  # 统计逆序数个数的奇偶性
    for i in range(len(board)):
        for j in range(i + 1, len(board)):
            if board[j] == 0:
                continue
            if board[i] > board[j]:
                count = count + 1

    if (count % 2) == 1:  # 奇数 — 无解
        pos1, pos2 = self.quickExchange(board)  # 寻找交换的最优情况
        change_pos1 = pos1
        change_pos2 = pos2
        board[pos1], board[pos2] = board[pos2], board[pos1]  # 交换情况

pos = board.index(0)  # 更新空白图的位置

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


由图可见,在图片的对比和识别部分(Pics_Compare)花费了较长的时间(约占总程序的79.3%),在计算最终的结果这方面反而倒很快

3.1.8 项目部分单元测试代码

import unittest
from 最终版本 import *
request_steps = -1
pic_num1 = 0
pic_num2 = 0
swap_num1 = 0
swap_num2 = 0
final = [0, 0, 0, 0, 0, 0, 0, 0, 0]
class MyTestCase(unittest.TestCase):
    def startPrint(self):
        print("开始测试")

    def test(self):
        request_steps, swap, uuid = get_api()
        pic_num1 = swap[0]
        pic_num2 = swap[1]
        pic_decode()
        image = Image.open('10.jpg')
        image_list = cut_image(image)
        save_images(image_list)
        board, string = All_compare()
        count = [0, 0, 0, 0, 0, 0, 0, 0, 0]
        for i in range(len(board)):
            if (board[i] == 0):
                continue
            count[board[i] - 1] = 1
    
        for i in range(len(board)):
            if count[i] == 1:
                final[i] = i + 1
            else:
                final[i] = 0
    
        steps, process, swap_num1, swap_num2 = Search().search(board)
    
        swap_num1 = swap_num1 + 1
        swap_num2 = swap_num2 + 1
        strOperation = "".join(process)
        swapPic = []
        swapPic.append(swap_num1)
        swapPic.append(swap_num2)
        data1 = {"operations": strOperation, "swap": swapPic}
        data = {"uuid": uuid, "answer": data1}
        post_api(data)
        print('步数: {}'.format(steps))
        print('识别字母: {}'.format(string))

if __name__ == '__main__':
    unittest.main()

3.2 Github的代码签入记录

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

  • 8数码序列在进行遍历的时候,对当前情况未判重可能会出现相同序列的局面,导致反复进入同一局面,最终步数增加或者无解。这应该算是低级错误吧,思考了很久后才想到,创建一个集合保存已经遍历的序列,只要查看该序列是否集合中,若没有的话,将该序列保存在集合就可以了。还是因为算法能力不够,浪费很多时间,吾当自罚。

  • 当数码序列步数等于题目给定交换步数时,将给定序号的两张图片进行调换,碰巧将空白图片的位置进行调换,导致此时算法中空白图片的位置没有及时更新,此后的操作过程就完全出错。因此在交换完图片或者每步,时刻更新算法中空白图片的位置,确保过程执行过程的正确性。这个问题看似很简单,但当初那几天完全被这个小问题搞得焦头烂额,完全找不到bug在哪,无奈只能在选择在输出的时候一步步查看此时的序列状态,最终才恍然大悟。因此,在实现算法的时候,要将各个可能出现的小细节都需要考虑。

3.4 评价队友

  • 值得学习的地方

    队友真的很认真也很负责,虽然一开始什么两个人都不会,但通过不断学习以及不断地修改,最后还是一步一步慢慢实现了,是合格的程序员,赞~

  • 需要改进的地方

    我对我的队友很满意,没什么需要改的!

3.5 学习进度条

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 120 120 7 7 了解题目需求,学习Pygame库和postman的使用
2 230 370 18 25 学习A*启发式算法实现拼图过程,并改进算法,编写A *算法
3 100 470 18 43 修改A *算法,增加交换规则,修改无解时的交换策略
4 350 820 20 63 增加了输出函数以实现输出查看每步的变化,编写AI大比拼,修改原有的算法,编写博客

3.5 PSP表格

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

PATR 4 学习总结

1.对于这次结对编程真的花了很多功夫,虽说最终的程序没有其他同学的功能丰富,不过最后能完成真的还是很有成就感的,很感激我的队友,两个人平时交流很多,相互配合的也很默契~~但对于AI大比拼,由于我个人的失误,导致落后了一名,有点自责QAQ

2.队友的那部分已经完成的非常好了,倒是我的AI算法的部分应该还有改进的空间,但目前我的能力还是远远不够,平时还是应当继续加强算法的练习。

posted @ 2020-10-19 22:13  Roseman  阅读(145)  评论(0编辑  收藏  举报