结对编程作业

结对编程作业

博客链接🔗

姓名 学号 博客地址
柳越 041801413 https://www.cnblogs.com/Roseman/p/13843484.html
张宏铮 061800339 https://www.cnblogs.com/baiweidou/p/13843039.html

Github项目链接🔗

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

具体分工

AI算法 网络接口 原型设计 AI 博客编写 github 图片处理
张宏铮
柳越

1.原型设计

1.1设计说明

1.1.1设计意图

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

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

1.1.3运行环境

在windows平台下运行

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

1.1.4原型设计图片

采用了Axure Rp进行原型设计

1.1.5功能描述

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

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

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

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

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

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

1.1.6原型实现图片

1.2结对过程及照片

1.2.1任务分工描述

在此次的结对编程任务中

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

队友主要负责AI大比拼中核心算法的编写

1.2.2照片

(非摆拍)

1.2.3遇到的困难及解决方法

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

2.原型实现

2.1代码实现思路

2.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)

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

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

2.1.3有价值的代码片段

在图片处理部分,

在原型实现部分,我认为有价值的代码片段主要是在游戏规则类

    def init_load(self):
        self.board = random_first_list()
        self.c = random.choice(cname)
        self.final_list = self.get_final_board()
        self.step = 0
        count = 0
        for x in range(self.shape):
            for y in range(self.shape):
                mark = tuple([x,y])
                #mark是标记现在是哪个块
                #[0,0],[0,1],[0,2]
                #[1,0],[1,1],[1,2]
                #[2,0],[2,1],[2,2]
                self.tiles[mark] = self.board[count]
                #图片编号
                count += 1
        self.init_click_dict()
        #初始化点击转换坐标
    def init_click_dict(self):
        #初始化点击坐标转换下标的数据
        for row in range(self.shape):
            for column in range(self.shape):
                x = left_screen_size + margin * (column + 1) + column * cell_size
                x1 = x + cell_size
                click_x = tuple(range(x,x1))
                #横坐标点击范围为x到x1
                self.click_dict['x'][click_x] = column
                #字典中的字典,第c列的范围是
                y =  margin * (row + 1) + row * cell_size
                y1 = y + cell_size
                click_y = tuple(range(y,y1))
                #纵坐标点击范围为y到y1
                self.click_dict['y'][click_y] = row
    def move(self,mark):
        #移动数据
        for neighbor in self.neighbors:
            #遍历上下左右四个方向
            spot = tuple_add(mark,neighbor)
            #spot等于移动目标向四个方向移动后的坐标
            if spot in self.tiles and self.tiles[spot] == 0:
                #如果移动后在范围内,并且移动后的点数字为0,则交换数据
                self.tiles[spot],self.tiles[mark] = self.tiles[mark],self.tiles[spot]
                self.operations.append(neighbor)
                self.step += 1
                break
    def click_to_move(self,x,y):
        #点击移动
        x1 = None
        for k , v in self.click_dict['x'].items():#items是以元组方式返回键和值
            if x in k :
                #如果点击的x在九宫格范围内,则给x1附现在处于哪个行
                x1 = v
        if x1 is None:
            return

        y1 = None
        for k, v in self.click_dict['y'].items():
            if y in k:
                # 如果点击的y在九宫格范围内,则给y1附现在处于哪个列
                y1 = v
        if y1 is None:
            return
        self.move((y1,x1))
    def key_move(self,direction):
        #键盘移动
        i = list(self.tiles.values()).index(0)
        mark = list(self.tiles.keys())[i]
        (x,y) = tuple_add(mark,direction)
        if (x,y) not in self.tiles.keys():
            return
        self.move((x,y))

在算法部分

 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,操作过程保存为列表里,存入优先队列,并按照启发值从小到大排序,依次取出启发值最小的序列,进行上下左右交换。

2.1.3性能分析与改进

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

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

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

  • 8数码序列在进行遍历的时候,对当前情况未判重,可能会出现相同序列的局面,导致反复进入同一局面,最终步数增加或者无解

2.1.4改进思路

添加一个计数量,在打乱图片分块跟原图分块对比超过三张并且小于等于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)    # 更新空白图的位置

2.1.5性能分析图

2.1.6项目部分单元测试代码

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):
        testNum = 10
        while testNum != 0:
            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))

            testNum = testNum - 1


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

2.2github代码签入记录

2.3遇到的困难及解决方法

在原型实现中遇到的困难有很多

比如在回退过程中与规则中移动方法有冲突,导致程序崩溃;还有步数框利用pygame画出来实在有些不美观等

已解决的部分已经完全体现在原型上,步数框不够美观所以将步数放在了顶上,由于一开始没有想着增加X*X难度的功能,虽然写的规则中可拓展,但在界面上难以适配,程序还不够完善,在后期会加以改进。

2.4评价队友

队友很棒,从一开始进行测试的时候不断false到AI大比拼中不断夺旗以及原型中嵌入了他的核心算法并且成功运行,都让我们成就感满满

队友值得学习的地方很多,写代码的时候很专注很认真,也擅于学习新知识,对时间的安排比我合理得多,我就比较摸鱼

我认为队友需要改进的地方就是晚上太迟睡觉了很吵,其他地方完全OK

2.5psp表格

N 新增代码(行) 累计代码(行) 本周学习耗时(小时****) 累计学习耗时(小时) 重要成长
1 121 121 6 6 定义了规则类和基础的鼠标移动方法,学会了pygame的用法
2 256 377 12 18 完善了界面,更新了图片素材
3 116 493 6 24 完善了功能,嵌入算法,完备了注释
PSP2.1 Personal Software Process Stages 预估耗时(小时) 实际耗时(小时)
Planning 计划 4 4
Development 开发 90 102
Analysis 需求分析 (包括学习新技术) 2 3
Design Spec 生成设计文档 1 2
Design Review 设计复审 5 6
Coding Standard 代码规范 (为目前的开发制定合适的规范) 0 0
Design 具体设计 4 3
Coding 具体编码 30 42
Code Review 代码复审 10 20
Test 测试(自我测试,修改代码,提交修改) 20 20
Reporting 报告 4 4
Test Repor 测试报告 1 1
100Size Measurement 计算工作量 1 1
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 1 2
Total 合计 83 106
posted @ 2020-10-19 21:00  baiweidou(张宏铮)  阅读(169)  评论(0编辑  收藏  举报