1.链接与分工
1.1链接与博客
- github链接地址
公共的github仓库地址 - 博客链接
我的博客链接
队友的博客链接
1.2分工合作
AI代码 | 算法代码 | UI设计 | 原型设计 | 博客与Github | |
---|---|---|---|---|---|
王梓鹏 | 80% | 70% | 15% | 10% | 35% |
周天 | 20% | 30% | 85% | 90% | 65% |
分工如表所示,文件等均上传至周天的GitHub
2.原型与代码
2.1-原型设计
2.1.1设计说明:
①进入后展示初始界面,有“排行榜”、“开始游戏”、“打赏”按钮;点击“开始游戏”后进入下一界面——游戏界面;
②游戏界面中有左上角“返回”键——返回初始界面,“计时器”以及“步数”标识,还包括“排行榜”、“助力一步”——AI自动走一步、“重新来过”——重开一局的大按钮,以及“Tips”和“打赏”小按钮。
③查看“排行榜”、“打赏”均会跳转到相应界面,点击周围区域或者“返回键”则返回上一级界面。
④打开“Tips”会跳转到第三方链接。
⑤至于为什么开始界面图用的是“三国华容道”,个人是觉得华容道的思想还是某种意义上的解题,况且对于中国来说三国华容道大家都不陌生,恰好设计想到的是古时的一些用法,比如牌匾的配色等,于是开始界面就是下图所示了。
下图顺序为 : 初始界面 ——> 游戏界面 ——> 排行榜 ——> 打赏 (图片由XD导出)
2.1.2原型工具
本次首先使用的Adobe XD,但是因为导出html文件时无法达到多界面交互,于是后来又转向Axure Rp进行修改。
2.1.3结对过程
主要还是网上交流以及线下跑宿舍,离鹏少宿舍近,这波就已经赚了。
2.1.4困难及解决方法
-
困难主要是想不到ui应该如何设计
-
解决方法就是:数字华容道 ——> 华容道 ——> 三国华容道 ——> 古代;同时想到界面整体类似游戏机,就也搜了一下游戏机的界面
-
问题解决了
-
收获:不会就多查多看
-
一开始考虑到这次原型图的部分,并没有很多的内容,于是我没有想到墨刀和axure,直接使用的XD进行画图,毕竟XD里面也有设计原型操作的效果而且操作方便。但是后面要求导出html故又转向了使用html。
-
原型的部分基本上已经完成,但是预计是没法达到完全实现的目的(毕竟我太垮了)
2.2-AI与原型设计实现
2.2.1代码实现思路
- 网络接口使用
查找相关资料并且请教了同学后使用了pyhon的requests库
'''
请求题目
'''
import requests
import base64
trackId='031802342'
url = "http://47.102.118.1:8089/api/challenge/start/xxxxxxxxxxxxx"
body = {
"teamid":xx,
"token":"xxxxx"
}
headers = {'content-type':"application/json"}
response = requests.request("post",url,headers=headers,json=body)
use_dic=response.json()
data=use_dic['data']
img=data['img']
stepre=data['step']
uuid=use_dic['uuid']
swap=data['swap']
image_data = base64.b64decode(img) # 解码图片
'''
提交答案
'''
headers = {'content-type': "application/json"}
anserurl = "http://47.102.118.1:8089/api/challenge/submit"
answer = {
"uuid": uuid,
"teamid": xx,
"token": "xxxxx",
"answer": {
"operations": arr,
"swap": swap
}
}
response = requests.request("post", anserurl, headers=headers, json=answer)
- 代码组织与内部实现设计,接下来是所使用到的关键算法
'''
将一张图片切为9张图
'''
from PIL import Image
import sys
def Cut_image(image):
width, height = image.size
item_width = int(width / 3)
box_list = []
for i in range(0, 3):
for j in range(0, 3):
box = (j * item_width, i * item_width, (j + 1) * item_width,
(i + 1) * item_width) # Image.crop(left, up, right, below)
box_list.append(box)
image_list = [image.crop(box) for box in box_list]
return image_list
# 保存
def save_images(folder, image_list):
index = 1
for image in image_list:
image.save(folder + str(index) + '.jpg') #folder为存放的文件夹
index += 1
为了复原请求来的打乱的题目,首先要找到对应的原图,一开始的思路是直接对比大图的相似度,考虑到后面AI复原时需要以列表形式存放图片九宫格的对应位置,就将请求来的打乱的题目以及原图进行分割保存。
接着就是对比图片的相似度,鉴于所给的图片只有黑白,所以采用了灰度直方图算法
import os
import cv2
def calculate(image1, image2):
# 灰度直方图算法
# 计算单通道的直方图的相似值
hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0])
hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0])
# 计算直方图的重合度
degree = 0
for i in range(len(hist1)):
if hist1[i] != hist2[i]:
degree = degree +
(1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i]))
else:
degree = degree + 1
degree = degree / len(hist1)
return degree
def runAllImageSimilaryFun(para1, para2):
# 通过imread方法直接读取物理路径
img1 = cv2.imread(para1)
img2 = cv2.imread(para2)
sim = calculate(img1, img2)
return sim
然后先匹配大图找出相似度最高的,在利用小图去一一匹配对应,并标上相应的位置
from similarity import *
def Judge(arr, record,position,target):
for i in range(0, 10):
record.append(0)
for i in range(1, 10):
image1 = 'cutmodify/' + str(i) + '.jpg'
flag = 0 # 是否匹配到图(找白色图片)
for j in range(1, 10):
image2 = 'cutorigin/' + str(j) + '.jpg'
calc_sim = runAllImageSimilaryFun(image1, image2) # 计算相似度
if calc_sim == 1:
flag = 1
arr.append(j)
record[j-1] = 1
break
if flag == 0:
arr.append(0)
# 目标状态
target = []
for i in range(9):
if (record[i] != 0):
target.append(i + 1)
else:
target.append(0)
return arr, record, position, target
AI还原部分主要用到了A*启发式搜索
class EightPuzzle:
def __init__(self):
# 搜索值
self._hval = 0
# 当前实例的搜索深度
self._depth = 0
# 搜索路径中的父节点
self._parent = None
self.adj_matrix = []
for i in range(3):
self.adj_matrix.append(_goal_state[i][:])
def __eq__(self, other):
if self.__class__ != other.__class__:
return False
else:
return self.adj_matrix == other.adj_matrix
def __str__(self):
res = ''
for row in range(3):
res += ' '.join(map(str, self.adj_matrix[row]))
res += '
'
return res
def _clone(self):
p = EightPuzzle()
for i in range(3):
p.adj_matrix[i] = self.adj_matrix[i][:]
return p
def _get_legal_moves(self): # 返回可以交换可用空间的元组列表
# 获取空块的行和列
row, col = self.find(0)
free = []
# 找出哪些位置可以移动到哪里
if row > 0:
free.append((row - 1, col))
if col > 0:
free.append((row, col - 1))
if row < 2:
free.append((row + 1, col))
if col < 2:
free.append((row, col + 1))
return free
def _generate_moves(self):
free = self._get_legal_moves()
zero = self.find(0)
def swap_and_clone(a, b):
p = self._clone()
p.swap(a, b)
p._depth = self._depth + 1
p._parent = self
return p
return map(lambda pair: swap_and_clone(zero, pair), free)
def _generate_solution_path(self, path):
if self._parent == None:
return path
else:
path.append(self)
return self._parent._generate_solution_path(path)
def solve(self, h): # 执行A*搜索目标状态
def is_solved(puzzle):
return puzzle.adj_matrix == _goal_state
openl = [self]
closedl = []
move_count = 0
while len(openl) > 0:
x = openl.pop(0)
move_count += 1
if (is_solved(x)):
if len(closedl) > 0:
return x._generate_solution_path([]), move_count
else:
return [x]
succ = x._generate_moves()
idx_open = idx_closed = -1
for move in succ:
# 是否遍历过该节点
idx_open = index(move, openl)
idx_closed = index(move, closedl)
hval = h(move)
fval = hval + move._depth
if idx_closed == -1 and idx_open == -1:
move._hval = hval
openl.append(move)
elif idx_open > -1:
copy = openl[idx_open]
if fval < copy._hval + copy._depth:
# 将move的值复制到现有的
copy._hval = hval
copy._parent = move._parent
copy._depth = move._depth
elif idx_closed > -1:
copy = closedl[idx_closed]
if fval < copy._hval + copy._depth:
move._hval = hval
closedl.remove(copy)
openl.append(move)
closedl.append(x)
openl = sorted(openl, key=lambda p: p._hval + p._depth)
# 如果未找到完成状态,则返回失败
return [], 0
def heur(puzzle, item_total_calc, total_calc):
t = 0
for row in range(3):
for col in range(3):
val = puzzle.peek(row, col) - 1
target_col = val % 3
target_row = val / 3
# account for 0 as blank
if target_row < 0:
target_row = 2
t += item_total_calc(row, target_row, col, target_col)
return total_calc(t)
# 用来低估的启发式方法
def h_manhattan(puzzle):
return heur(puzzle,
lambda r, tr, c, tc: abs(tr - r) + abs(tc - c),
lambda t: t)
以下是性能的部分
可以看到多个函数都是类似的耗时时间,并没有一个非常突出的
接下来是judge函数,把已经切割过的两张图,9张一一对比,找到相似度为1的块,并标上对应的位置。
2.2.2贴出Github的代码签入记录,合理记录commit信息
2.2.3遇到的问题以及解决
此处遇到了的问题是由于采用的是灰度直方图计算相似度,所以黑色白色的图片相似度显示为1.0。
但是通过增加计算结果的小数位,可以判断出是否为对应图
2.2.4评价队友
- 值得学习的地方
很能肝,很认真,很乐意去学 - 需要改进的地方
调整一下作息,多记录、多用用草稿纸(毕竟一心不能多用)
3.算法和接口
3.1A*算法解释
A*启发式搜索:把起点加入 open list,重复如下过程:a.遍历openlist,查找 F 值最小的节点,把它作为当前要处理的节点。b.把这个节点移到 close list 。c.对当前方格的 8 个相邻方格的每一个方格?
- 如果它是不可抵达的或者它在 close list 中,忽略它。否则,做如下操作。
- 如果它不在 open list 中,把它加入openlist,并且把当前方格设置为它的父亲,记录该方格的 F,G和H值。
- 如果它已经在 open list 中,检查这条路径 ( 即经由当前方格到达它那里 ) 是否更好,用 G 值作参考。更小的 G 值表示这是更好的路径。如果是这样,把它的父亲设置为当前方格,并重新计算它的 G 和F值。如果 openlist是按 F值排序的话,改变后可能需要重新排序。
d.停止,当把终点加入到了 open list 中,此时路径已经找到了,或者查找终点失败,并且 openlist 是空的,此时没有路径。
最后保存路径。从终点开始,每个方格沿着父节点移动直至起点,这就是你的路径。
3.2Json接口部分
- 我们是在postman官网下载的postman,然后按照输入url等等进行GET和POST
- 这里是postman下载链接,接下来是postman对应的使用获取排行榜截图
- 其他的像POST上传题目/答案则类似于下图,改好POST/GET和URL后,就要在body——>raw——>json里面加入代码,这里是具体json代码部分
4.PSP表格和学习进度条
4.1学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|
第1周 | 200 | 200 | 2 | |
第2周 | 0 | 200 | 4 | 初步看了看a*算法和华容道解法 |
第3周 | 300 | 500 | 5 | 了解了a*算法 |
第4周 | 300 | 800 | 16 | 学到了鹏少教的技术 |
4.2PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时 | 实际耗时 |
---|---|---|---|
Plannning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 30 | 50 |
· Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 200 | 180 |
· Design Spec | · 生成设计文档 | 40 | 100 |
· Design Review | · 设计复审 | 60 | 50 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 150 |
· Design | · 具体设计 | 180 | 200 |
· Coding | · 具体编码 | 360 | 400 |
· Code Review | · 代码复审 | 120 | 120 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 120 |
Reporting | 报告 | ||
· Test Repor | · 测试报告 | 60 | 60 |
· Size Measurement | · 计算工作量 | 30 | 30 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 50 |
合计 | 1250 | 1460 |