第一次结对编程作业
结对编程作业
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算法的部分应该还有改进的空间,但目前我的能力还是远远不够,平时还是应当继续加强算法的练习。