我的github项目地址 博客链接
队友的github项目地址 博客链接
具体分工情况表格
任务 | 我 | 队友 |
---|---|---|
原型设计 | √ | |
算法 | √ | |
界面 | √ | |
接口 | √ |
一、原型设计
1.提供此次结对作业的设计说明,要求文字准确、样式清晰、图文并茂(多贴原型图)
此次作业设计了4个页面,分别是主界面,开始游戏界面,游戏规则界面,联网解题界面,历史得分界面(具体内容见下图),并实现了各个界面之间的跳转。
主界面: 是整个程序(游戏)的入口,用于链接和跳转各个界面。
开始游戏: 是单机游戏,随机生成一个白块,并有提示功能,可玩性还行。
游戏规则: 这个游戏的游戏规则还挺复杂,所以这个界面说明了游戏规则。
联网解题模式: 链接postman接口,获取随机的题目,并通过AI解法解题,提交答案。
历史得分: 针对开始游戏的那个单机游戏按步数算分,并把得分的前15个返回,简而言之,就是一个排行版。
- 所有页面的跳转方式如下
2.原型设计工具
Axure Rp
3.结对图片描述结对的过程,提供非摆拍的两人在讨论、细化和使用专用原型模型工具时的结对照片。
题目出来的时候刚好在上课,两人对视一眼,一拍即合,默契满分,组队成功。
4.遇到的困难及解决方法
- 困难描述
1.不懂原型设计是什么?
2.只能做简单的界面,不能实现页面的跳转,其实就是不会使用Axure Rp。
- 解决尝试
一开始不知道原型设计的概念,后来通过百度,B站等途径了解了原型设计是产品经理的一门必修课,然后在B站两小时速成Axure Rp的使用,就可以做一个简单的还算可以的原型。
- 是否解决
是
- 有何收获
1.了解了原型设计的重要性,扩展了知识面
2.学会了Axure Rp的使用,可以独立做一些简单的、工程量小的原型设计
二、AI与原型设计实现
1.代码实现思路
-
网络接口的使用
-
请求接口
import base64
import json
import requests
def gethtml(url):
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3941.4 Safari/537.36'
}
r=requests.get(url,headers = headers)
r.raise_for_status()
r.encoding=r.apparent_encoding
return r.text
except:
return ""
if __name__ == "__main__":
url = "http://47.102.118.1:8089/api/problem?stuid=031802433"
# 每次请求的结果都不一样,动态变化
text = json.loads(gethtml(url))
img_base64 = text["img"]
step = text["step"]
swap = text["swap"]
uuid = text["uuid"]
img = base64.b64decode(img_base64)
- 提交接口
import json
import requests
def getAnswer(url):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3941.4 Safari/537.36',
'Content-Type': 'application/json'
}
r=requests.post(url,headers = headers,data = data_json)
return r.text
if __name__ == "__main__":
url = " http://47.102.118.1:8089/api/answer"
data = {
"uuid":"20c8d3e27d6e4d638a1fed4218737e41",#本道题目标识
"answer":{
"operations": "wsaaadasdadadaws",
"swap": [1,2]
}
}
data_json = json.dumps(data)#转为json提交
ret = getAnswer(url)
-
代码组织与内部实现设计(类图)
-
说明算法的关键与关键实现部分流程图
-
AStart算法
-
确定序列算法
-
贴出你认为重要的/有价值的代码片段,并解释
最有价值的代码片段肯定是A*算法进行自动求解,但是以因为这一部分不是我写的,所以就换成历史记录得分界面的编写,通过文件存储历史得分并取出前15名进行显示输出。
class ThirdUi(QWidget):
def __init__(self):
super(ThirdUi, self).__init__()
self.init_ui()
def init_ui(self):
self.resize(950, 950)
self.setWindowTitle('历史得分')
#设置背景
window_pale = QtGui.QPalette()
window_pale.setBrush(self.backgroundRole(), QtGui.QBrush(QtGui.QPixmap("back.jpg")))
self.setPalette(window_pale)
self.label1 = QLabel(self)
self.label1.setGeometry(350, 50, 255, 150)
self.label1.setText("历史得分")
self.label1.setStyleSheet("QLabel{color:rgb(0,0,0,255);font-size:60px;font-weight:bold;font-family:楷体;}")
self.label = QLabel(self)
self.label.setGeometry(400, 150, 600, 800)
with open("score.txt","r",encoding="utf-8") as fp:
scores = fp.readlines()
ls = []#只保留前15个记录
for score in scores:
ls.append(int(score.replace('
', '')))
ls = sorted(ls)
finalstr = ''
if len(ls)>=15:
for i in range(15):
if i<9:
finalstr += (str(i + 1) + '.' +" "+ str(ls[i]) + '
')
else:
finalstr += (str(i+1)+'.'+" " +str(ls[i])+'
')
else:
for i in range(len(ls)):
if i<9:
finalstr += (str(i + 1) + '.' +" "+ str(ls[i]) + '
')
else:
finalstr += (str(i+1)+'.'+" " +str(ls[i])+'
')
self.label.setText(finalstr)
self.label.setStyleSheet("QLabel{color:rgb(0,0,0,255);font-size:40px;font-weight:bold;font-family:楷体;}")
# self.label.setStyleSheet("QLabel{color:rgb(33,215,217,255)}")
self.btn = QPushButton('返回主页面', self)
self.btn.setGeometry(0,0, 150, 50)
self.btn.setStyleSheet("QPushButton{color:black;font-size:20px}"
"QPushButton:hover{background-color:lightgreen}"
"QPushButton{background-color:lightblue}"
"QPushButton{border:2px}"
"QPushButton{border-radius:10px}"
"QPushButton{padding:2px 4px}"
)
self.btn.clicked.connect(self.slot_btn_function)
def slot_btn_function(self):
self.hide()
self.f = FirstUi()
self.f.show()
- 性能分析与改进
从图可以看出本程序的时间消耗最大是pyqt5内置方法,键盘等待时间,AStart算法,pyqt5内置方法无法优化,键盘等待时间受人为因素影响不确定,所以唯一可以优化的就是AStart算法。
- 描述你改进的思路
通过优化AStart算法来提高程序的性能。 - 改进前
def solve(self):
# self.prt2(self.mylist)
self.goal = []
for row in range(3):
self.goal.append([])
for column in range(3):
temp = self.numbers[row * 3 + column]
self.goal[row].append(temp)
# self.prt2(self.goal)
self.mylist = self.blocks
if not checkNoAns(self.mylist):
print("No Ans!")
exit(1)
startNum = StruNum(self.mylist, [], 0, self.goal)
g_ListArr.append(startNum)
cichu = 0
while g_ListArr:
nowNum = g_ListArr[0]
g_ListArr.remove(nowNum) # 从队列头移除
g_readArr.append(nowNum) # 添加到已读队列
cichu +=1;
if nowNum.myList == self.goal:
print("Find The Answer!")
self.ans = ""
while nowNum.myPreList:
x1, y1 = find0(nowNum.myList)
nowNum = nowNum.GetItsPre()
x2, y2 = find0(nowNum.myList)
if x2 > x1:
self.ans += "w"
elif x2 < x1:
self.ans += "s"
elif x2 == x1:
if y2 > y1:
self.ans += "a"
elif y2 < y1:
self.ans += "d"
listans = list(self.ans)
listans.reverse()
self.ans = "".join(listans)
print(self.ans)
print(cichu)
break
else:
[idx, jdx] = nowNum.GetZeroIndex()
if idx - 1 >= 0:
upList = copy.deepcopy(nowNum.myList)
upList[idx][jdx] = upList[idx - 1][jdx]
upList[idx - 1][jdx] = 0
upNum = StruNum(upList, nowNum.myList, nowNum.myCost + 1, self.goal)
# A*算法:代价 = 到达此状态代价 + 期望到达目标节点代价
upNum.myCost += upNum.GetGoalCost()
# 如果新节点没有被走过
if upNum.GetListIndex(g_readArr) == -1:
tmpIndex = upNum.GetListIndex(g_ListArr)
if tmpIndex != -1:
# 当新节点已经出现在未读队列中,如果新节点的代价更小,则更新,否则不更新
if upNum.myCost < g_ListArr[tmpIndex].myCost:
g_ListArr.remove(g_ListArr[tmpIndex])
g_ListArr.append(upNum)
else:
g_ListArr.append(upNum)
if idx + 1 < 3:
downList = copy.deepcopy(nowNum.myList)
downList[idx][jdx] = downList[idx + 1][jdx]
downList[idx + 1][jdx] = 0
downNum = StruNum(downList, nowNum.myList, nowNum.myCost + 1, self.goal)
downNum.myCost += downNum.GetGoalCost()
# 如果新节点没有被走过
if downNum.GetListIndex(g_readArr) == -1:
tmpIndex = downNum.GetListIndex(g_ListArr)
if tmpIndex != -1:
# 当新节点已经出现在未读队列中,如果新节点的代价更小,则更新,否则不更新
if downNum.myCost < g_ListArr[tmpIndex].myCost:
g_ListArr.remove(g_ListArr[tmpIndex])
g_ListArr.append(downNum)
else:
g_ListArr.append(downNum)
if jdx - 1 >= 0:
leftList = copy.deepcopy(nowNum.myList)
leftList[idx][jdx] = leftList[idx][jdx - 1]
leftList[idx][jdx - 1] = 0
leftNum = StruNum(leftList, nowNum.myList, nowNum.myCost + 1, self.goal)
leftNum.myCost += leftNum.GetGoalCost()
# 如果新节点没有被走过
if leftNum.GetListIndex(g_readArr) == -1:
tmpIndex = leftNum.GetListIndex(g_ListArr)
if tmpIndex != -1:
# 当新节点已经出现在未读队列中,如果新节点的代价更小,则更新,否则不更新
if leftNum.myCost < g_ListArr[tmpIndex].myCost:
g_ListArr.remove(g_ListArr[tmpIndex])
g_ListArr.append(leftNum)
else:
g_ListArr.append(leftNum)
if jdx + 1 < 3:
rightList = copy.deepcopy(nowNum.myList)
rightList[idx][jdx] = rightList[idx][jdx + 1]
rightList[idx][jdx + 1] = 0
rightNum = StruNum(rightList, nowNum.myList, nowNum.myCost + 1, self.goal)
rightNum.myCost += rightNum.GetGoalCost()
# 如果新节点没有被走过
if rightNum.GetListIndex(g_readArr) == -1:
tmpIndex = rightNum.GetListIndex(g_ListArr)
if tmpIndex != -1:
# 当新节点已经出现在未读队列中,如果新节点的代价更小,则更新,否则不更新
if rightNum.myCost < g_ListArr[tmpIndex].myCost:
g_ListArr.remove(g_ListArr[tmpIndex])
g_ListArr.append(rightNum)
else:
g_ListArr.append(rightNum)
# 按照COST排序
g_ListArr.sort(key=takeThr)
- 改进后
def Astar(startStat):
# open和closed存的是grid对象
openlist = []
closed = []
# 初始化状态
g = grid(startStat)
# 检查是否有解
if not judge(startStat, target):
print("无解")
exit(1)
openlist.append(g)
# time变量用于记录遍历次数
time = 0
# 当open表非空时进行遍历
while openlist:
# 根据启发函数值对open进行排序,默认升序
openlist.sort(key=lambda G: G.F)
# 找出启发函数值最小的进行扩展
minFStat = openlist[0]
# 检查是否找到解,如果找到则从头输出移动步骤
if minFStat.H == 0:
print("found and times:", time, "moves:", minFStat.G)
minFStat.seeAns()
break
# 走到这里证明还没有找到解,对启发函数值最小的进行扩展
openlist.pop(0)
closed.append(minFStat)
expandStats = minFStat.expand()
# 遍历扩展出来的状态
for stat in expandStats:
# 将扩展出来的状态(二维列表)实例化为grid对象
tmpG = grid(stat)
# 指针指向父节点
tmpG.pre = minFStat
# 初始化时没有pre,所以G初始化时都是0
# 在设置pre之后应该更新G和F
tmpG.update()
# 查看扩展出的状态是否已经存在与open或closed中
findstat = isin(tmpG, openlist)
findstat2 = isin(tmpG, closed)
# 在closed中,判断是否更新
if findstat2[0] == True and tmpG.F < closed[findstat2[1]].F:
closed[findstat2[1]] = tmpG
openlist.append(tmpG)
time += 1
# 在open中,判断是否更新
if findstat[0] == True and tmpG.F < openlist[findstat[1]].F:
openlist[findstat[1]] = tmpG
time += 1
# tmpG状态不在open中,也不在closed中
if findstat[0] == False and findstat2[0] == False:
openlist.append(tmpG)
time += 1
从这几张性能分析图的对比可以看出,改进后的算法性能提升了将近三倍。
-
展示性能分析图和程序中消耗最大的函数
-
性能分析图
程序中消耗最大的函数:pyqt5内置方法(大约占了程序的74.6%)
-
展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路
单元测试采用AI大比拼的接口,通过返回的"success"值与True比较,如果相同则说明本题求解成功,通过单元测试,反之则未通过。具体实现代码如下:
#coding:utf-8
import unittest
from AI大比拼 import *
class MyTestCase(unittest.TestCase):
def test1(self):
uuid = "7aa3a255-a8a9-4314-91d8-57772d068087"
global answer
answer = False
step, swap, uuid, zuhao, listproblem, dis = jiekou.challenge(uuid)
set(step, swap, uuid, zuhao, listproblem, dis)
NumberHuaRong()
jiekou.submit(uuid, operations, myswap)
self.assertEqual(answer, True)
def test2(self):
uuid = "ec0dd026-3a78-4b18-971f-2b651aaa7b5f"
global answer
answer = False
step, swap, uuid, zuhao, listproblem, dis = jiekou.challenge(uuid)
set(step, swap, uuid, zuhao, listproblem, dis)
NumberHuaRong()
jiekou.submit(uuid, operations, myswap)
self.assertEqual(answer, True)
def test3(self):
uuid = "cac66a4d-956b-4abe-9baf-f45087a4290a"
global answer
answer = False
step, swap, uuid, zuhao, listproblem, dis = jiekou.challenge(uuid)
set(step, swap, uuid, zuhao, listproblem, dis)
NumberHuaRong()
jiekou.submit(uuid, operations, myswap)
self.assertEqual(answer, True)
if __name__ == '__main__':
unittest.main()
2.贴出Github的代码签入记录,合理记录commit信息。
3.遇到的代码模块异常或结对困难及解决方法。
- 问题描述
在编写UI界面的过程中,华容道的界面和主界面无法进行切换,开始是想通过一个按钮来实现界面的切换,但是添加了按钮后整个游戏的九宫格布局就被毁了,而且还有可能闪退.
- 解决尝试
队友灵机一动,想出了通过键盘的按钮来触发界面的切换,既不会破坏布局,也可以实现切换功能,而且效果还更好。问题完美解决~~~nice
- 是否解决
是
- 有何收获
通过这次华容道的学习,学习到了非常多的知识。比如pyqt5的以及qtDesigner的使用,在这之前,我只会用tkinter进行一些简单的GUI界面编程,这次通过pyqt5的学习,可以用之开发一些较为复杂的界面,qt不愧是GUI编程的神器。还有接口的使用,学习了使用Python提交url请求。
4.评价你的队友。(2分)
- 值得学习的地方
十分感谢我无敌的队友,我生病了一段时间回来就把大部分东西都搞完了,超强的学习和实践能力是十分值得我学习的。
- 需要改进的地方
太优秀了,没有缺点,不用改进。
5.提供此次结对作业的PSP和学习进度条(每周追加)
- 学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
第一周 | 300 | 300 | 10 | 10 | 学会设计原型和Axure的使用,初步了解qt |
第二周 | 500 | 800 | 5 | 15 | 学习了一些八数码的算法 |
第三周 | 300 | 1100 | 12 | 22 | 学习了pyqt5编写GUI界面 |
第四周 | 300 | 1400 | 10 | 32 | 学习了接口的编写 |
- PSP表格
PSP2.1
Personal Software Process Stages
预估耗时(分钟)
实际耗时(分钟)
Planning
计划
70
70
· Estimate
· 估计这个任务需要多少时间
70
70
Development
开发
1030
1750
· Analysis
· 需求分析 (包括学习新技术)
300
870
· Design Spec
· 生成设计文档
200
210
· Design Review
· 设计复审
50
40
· Coding Standard
· 代码规范 (为目前的开发制定合适的规范)
20
30
· Design
· 具体设计
30
30
· Coding
· 具体编码
300
430
· Code Review
· 代码复审
30
20
· Test
· 测试(自我测试,修改代码,提交修改)
100
120
Reporting
报告
400
440
· Test Repor
· 测试报告
300
320
· Size Measurement
· 计算工作量
50
50
· Postmortem & Process Improvement Plan
· 事后总结, 并提出过程改进计划
50
70
· 合计
1500
2260