结对编程作业
1.作业信息
1.1博客链接
1.2 Github地址
我的地址:点击这里(内含原型设计工程文件,游戏实现代码)
队友地址:点击这里(内含AI大比拼包括算法在内的代码)
1.3 具体分工
任务 | 我 | 队友 |
---|---|---|
原型设计 | √ | |
AI算法设计 | √ | |
游戏代码 | √ | |
接口与测试 | √ |
2.1原型设计
2.1.1设计说明
既然要设计一个游戏,最基本的肯定要有开始,退出和得分(步数),为了方便还原,将图片的原来样子也展示出来
玩家可以通过查看栏菜单查看游戏规则和排行榜
游戏里添加了强制交换功能,但是因为键盘不方便实现自定义调换,可能会出现无解的情况,于是就注释掉了
因为我们的游戏界面设置的很小,所以如果要添加这么多按钮,就会显得拥挤,所以menu组件就派上了用场
2.1.2原型开发工具:Axure Rp 8
2.1.3结对过程:舍友两两组合
2.1.4困难方法
2.2 AI与原型设计实现
2.2.1代码实现思路:
网络接口的使用
用requests和BeautifulSoup处理,获取某题的Uuid,返回图片的src链接,challengeidd,强制交换的步数和强制交换的操作
getProblem.py
import json
import requests
import base64
url ="http://47.102.118.1:8089/api/challenge/start/e9d5727c-57fa-4182-a1fd-24b43fd392ce"
data = {
"teamid": 53,
"token": "99a01c39-f3ee-4967-8b5e-35cfbcfb9f7a"
}
r = requests.post(url,json=data)
r.raise_for_status()
r.encoding = r.apparent_encoding
dic=json.loads(r.text)
img = base64.b64decode(dic["data"]["img"])
Step=dic["data"]["step"]
Swap=dic["data"]["swap"]
Uuid=dic['uuid']
with open("./photo.jpg", "wb") as fp:
fp.write(img) # 900*900
将获取返回的Uuid和算法解出来的operations和swaplist上传
post.py
import json
import requests
import getproblem as gp
import qipan as Qi
url ="http://47.102.118.1:8089/api/challenge/submit"
data = {
"uuid": gp.Uuid,
"teamid": 53,
"token": "99a01c39-f3ee-4967-8b5e-35cfbcfb9f7a",
"answer": {
"operations": Qi.operations,
"swap": Qi.swaplist
}
}
r = requests.post(url,json=data)
print(r.text)
代码组织与内部实现设计
本部分有getProblem.py,cutImage.py,find_image.py,post.py为工具类函数,用于获取题目,题目图片预处理等,还有generator.py和cutImage.py来生成用于预测代价和题目切图保存到本地文件夹.
Prediction.py,qipan.py两个用于AI求解的函数,Prediction.py通过generator.py生成的quary表中字典代价去预测当前状态下代价最小的下一步走向,qipan.py中实现循环和强制交换,与find_image.py中其他功能性函数链接,包括判断是否无解,无解后自行交换的方法实现
算法关键
识别出挖空的数字,导入对应的q_tab表,每次通过棋盘当前的状态,生成0123的随机序列,0123代表上左下右,对于四个方向的操作,计算出四个方向的代价,之后返回代价列表中最小的代价对应的方向操作
find_image.py 其中的将图片对应关系变成二维数组
def make_qipan(m):
sign=[1,2,3,4,5,6,7,8,9]#存储被挖空的图的位置
blanknumber=[1,2,3,4,5,6,7,8,9]#存储被挖空的图是第几张图
temp=[]#存储棋盘的顺序
qipan=np.arange(1,10).reshape(3,3)#创建2维数组
for pic2 in os.listdir('./QuestionCut'):
for pic1 in os.listdir('./picture/' +m):
with open ('./picture/'+m+'/'+pic1,'rb') as f1:
base64_f1=base64.b64encode(f1.read())
with open ('./QuestionCut/'+pic2,'rb') as f2:
base64_f2=base64.b64encode(f2.read())
if base64_f1==base64_f2:#找到切图和数据库中的对应关系
number=int(pic2.split('.')[0])
col=(number-1)%3#记录每一个问题切图的所在列
row=int((number-1)/3)#记录每一个问题切图的所在行
qipan[row][col]=pic1.split('.')[0]#对应棋盘的位置为这个图所对应数据库的图的数字
temp.append(int(pic1.split('.')[0]))#按顺序存到temp
sign.remove(number)#在存储被挖空的图的位置列表去除对应
blanknumber.remove(int(pic1.split('.')[0]))#在存储被挖空的图的数字列表去除对应
qipan[int((sign[0]-1)/3)][(sign[0]-1)%3]=blanknumber[0]#令二维数组被挖空的图的位置值为0
return temp,qipan,sign[0],blanknumber[0]#返回二维数组的一维形式temp,二维数组qipan,sign[0]为被挖空的数字的题目位置序号,blanknumber[0]为被挖空的数字
判断逆序对的方法judgment,逆序数奇偶性的判断,需要除了空格以外的八个数的有序数列计算
def judgment(temp):
signal=0
for i in range(len(temp)):
for j in range(i+1,len(temp)):
#计算逆序对
if temp[i]>temp[j]:
signal+=1
#保存第一个有逆序对的数字的位置
if signal==1:
position=i
if signal%2!=0:#逆序对为奇数则无解
print('no way')
return signal,position
将二维数组转换为有序的一维数组,和judgment对接
def turnToarray(qipan,bk):
t=[]
for i in range(3):
for j in range(3):
if qipan[i][j]!=bk:
t.append(qipan[i][j])
return t
性能分析
由于强制交换的存在,代价预估预测都对当前状态进行代价分析,不会考虑利用强制交换获取捷径,所以这强制交换之前的操作可能使得交换后更加复杂或者更加简单。
性能分析图
项目展示测试单元
测试接口获取题目gettest.py
import base64
import json
import requests
from PIL import Image
def gethtml(url):
try:
resp = requests.request('get', url)
resp.raise_for_status()
resp.encoding = resp.apparent_encoding
return resp.text
except:
print('err')
def getProblem():
url = "http://47.102.118.1:8089/api/problem?stuid=031804140"
# 每次请求的结果都不一样,动态变化
text = json.loads(gethtml(url))
img_base64 = text["img"]
step = text["step"]
swap = text["swap"]
uuid = text["uuid"]
img = base64.b64decode(img_base64)
# 获取接口的图片并写入本地
with open("question.jpg", "wb") as fp:
fp.write(img) # 900*900
return step,swap
Step,Swap=getProblem()
print(Swap)
print(Step)
测试接口提交答案posttest.py
import json
import requests
import getproblem as gp
import qipan as Qi
url ="http://47.102.118.1:8089/api/answer"
data = {
"uuid": gp.Uuid,
"answer": {
"operations": Qi.operations,
"swap": Qi.swaplist
}
}
r = requests.post(url,json=data)
print(r.text)
2.2.2 GitHub签入记录
2.2.3 代码模块异常
问题描述
强制交换后如果无解,在自行交换过后会出现两个0,即两个数字为挖空的数字,从而导致永远无解;也有交换过后没有改变逆序数的情况也是永远无解
尝试解决
通过对出现问题的题目二维数组进行人工复现代码,找出自行交换的下标处理需要考虑当前空格和交换格子的相对位置
是否解决
def swap(qipan,p,bkp):
if bkp<=p: #空格在第一个逆序数的左边
t = qipan[(p+1) // 3][(p+1) % 3]
qipan[(p+1) // 3][(p+1) % 3] = qipan[(p + 2) // 3][(p + 2) % 3]
qipan[(p + 2) // 3][(p + 2) % 3] = t
print('swap[%d , %d]' % (p+1, p + 2))
return p+1, p + 2
else:
if bkp==p+1:
t=qipan[p//3][p%3]
qipan[p//3][p%3]=qipan[(p+2)//3][(p+2)%3]
qipan[(p + 2) // 3][(p + 2) % 3]=t
print('swap[%d , %d]' % (p, p + 2))
return p, p + 2
else:
t=qipan[p//3][p%3]
qipan[p // 3][p % 3] = qipan[(p + 1) // 3][(p + 1) % 3]
qipan[(p + 1) // 3][(p + 1) % 3] = t
print('swap[%d , %d]' % (p, p + 1))
return p, p + 1
有何收获
能解出答案了www,当时写的太过草率,重新理解以后感觉蛮好的
2.2.4 评价队友
2.2.5 PSP表格/学习进度条
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 100 |
Estimate | 估计这个任务需要多少时间 | 60 | 100 |
Development | 开发 | 1120 | 1940 |
Analysis | 需求分析 (包括学习新技术) | 400 | 720 |
Design Spec | 生成设计文档 | 50 | 80 |
Design Review | 设计复审 | 20 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 50 | 50 |
Design | 具体设计 | 80 | 100 |
Coding | 具体编码 | 360 | 600 |
Code Review | 代码复审 | 120 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 150 | 240 |
Reporting | 报告 | 110 | 110 |
Test Repor | 测试报告 | 60 | 40 |
Size Measurement | 计算工作量 | 30 | 40 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 1290 | 2240 |
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 110 | 110 | 6 | 6 | 学会Axure的基本操作,从测试接口获取答案和上传答案 |
2 | 340 | 450 | 16 | 22 | 掌握tkinter的基本架构,掌握广搜和循环迭代的原理 |
3 | 350 | 800 | 10 | 32 | 游戏功能实现,内嵌AI算法,数据结构剖析,实现判断交换数据格式转换等 |