结对编程作业
结对编程作业
博客链接🔗
姓名 | 学号 | 博客地址 |
---|---|---|
柳越 | 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 |