结对编程作业


写在前面
1.我的博客地址
2.队友的博客地址
3.github项目地址
4.具体分工

姓名\分工 AI算法 原型设计 游戏实现 博客
    白霖    √       √
    熊崟     √   √
一.原型设计
1.设计说明
通过HTML5,JavaScript,CSS的编写创建本地网页,通过网页的形式展现游戏内容和相关功能:
1)可以查看原图,或者不看(记忆大佬)。
2)在游戏过程中可以重新开始。
3)记录时间和步数。
4)根据时间和步数分别查看排名。
5)自动演示走法。

2.原型模型设计(Axure Rp)

原型模型的构建应用的软件为Axure Rp 8
主页面(实际游戏页面)

排行榜:按照步数或者时间进行编排成功游戏者和对应的步数和时间
开始游戏:获取分割打乱后的图片,同时启动时间计时器(游戏进行时为“重新开始”,成功时为“再来一次”,都可以开始新一轮的游戏)
提示:获取并演示解过程
显示图片:可在左边显示原图,以供游戏参考(再点一次则隐藏)

3.结对交流过程

4.困难与解决办法
在原型实现的过程中,运用动态面板进行操作和记录困难,多次出错。通过多次的查询和实例理解最终实现。

二、AI与原型设计实现
1.AI的原型实现
1)代码实现思路
网络接口的使用:
使用python的requests库来进行get和post请求

def getData():
    url = "http://47.102.118.1:8089/api/problem?stuid=031804101"
    r = requests.get(url)
    data = json.loads(r.text)
    imgdata = base64.b64decode(data['img'])
    step = data['step']
    swap = data['swap']
    uuid = data['uuid']
    # 将图片写到本地
    path = "C:\\software\\char.jpg"
    file = open(path,'wb')
    file.write(imgdata)
    file.close() 
    return step,swap,uuid

def postData(uuid,ope,swap):
    url = "http://47.102.118.1:8089/api/answer"
    data = {
        "uuid":uuid,
        "answer":{
            "operations": ope,
            "swap": swap
        }
    }
    r = requests.post(url,json=data)
    return r.text

代码组织与内部实现设计(类图)
算法流程图


我认为算法的关键是图片识别,如果图片都识别不了,后面就不用做了
采用KNN算法实现

# 训练模型,我练我自己?
import os
import numpy as np
from PIL import Image
from sklearn.neighbors import KNeighborsClassifier
from sklearn.externals import joblib

# subimg中每个文件夹都包含9张切割完的子图
path = 'C:\\software\\subimg\\'
dirs = os.listdir(path)
length = len(dirs)

X_train=[]
y_train=[]
for j in range(length):
    dirpath = path + dirs[j] + "\\"
    names = os.listdir(dirpath)
    for i in range(len(names)):
        f = Image.open(dirpath+names[i],"r")
        # 判断是不是纯黑,纯黑就跳过
        extrema = f.convert("L").getextrema()
        if extrema == (0, 0):
            continue
        
        # 图片有三个通道,通常是转为灰度图,在这我取其中一个通道
        image = np.array(f)
        image = image[:,:,0]
        # 转换为2维的numpy数组
        image = image.reshape(1,-1)
        # 有监督学习,还需要y标签
        X_train.append(image)
        y_train.append(j*10+i)

X_train = np.array(X_train)
y_train = np.array(y_train)
# 还需要再将X_train转为2维
X_train = X_train.reshape(X_train.shape[0],-1)

# 预测的时候找和它最像的就行了
clf = KNeighborsClassifier(1)
clf.fit(X_train,y_train)

#保存模型,需要时直接加载使用
joblib.dump(clf, 'C:\\software\\clf.pkl')
# 识别图片,返回相应的序列及白块位置
# 将获取的图片转为np数组,并添加到列表
def imgToArr():
    arrlist = []
    f = Image.open('C:\\software\\char.jpg',"r")
    array = np.array(f)
    for i in range(3):
        for j in range(3):
            subarray = array[i*300:(i+1)*300,j*300:(j+1)*300]
            arrlist.append(subarray)
    return arrlist

def getNum():
    arrs = imgToArr()
    # load model
    clf = joblib.load('C:\\software\\clf.pkl')
    ans = []
    dirindex = 0

    # 对arrs中的每个np数组进行预测
    for arr in arrs:
        # s1==0 白  s2==0 黑
        s1 = np.count_nonzero(arr==0)
        s2 = np.count_nonzero(arr==255)
        if s1==0:
            ans.append("白")
        elif s2==0:
            ans.append("黑")
        else:
            arr = arr[:,:,0]
            arr = arr.reshape(1,-1)
            pre = int(clf.predict(arr))
            dirindex=pre//10
            ans.append(pre)

    path = 'C:\\software\\subimg\\'
    dirs = os.listdir(path)
    # 白块的位置
    white = ans.index("白")

    # matchNums是已经确定的子图,noMatchNums还没确定的子图,有一白(白块恰好就是黑色的)和一黑一白两种情况
    matchNums = [i%10 for i in ans if isinstance(i,int)]
    noMatchNums = [i for i in range(9) if i not in matchNums]

    # 对还未确定的子图进行判断
    if "黑" not in ans:
        ans[ans.index("白")] = noMatchNums[0]
    else:
        img1path = path + dirs[dirindex] + "\\"
        files = os.listdir(img1path)
        img1path += files[noMatchNums[0]]
        arr = np.array(Image.open(img1path,"r"))
        if np.count_nonzero(arr==255) == 0:
            ans[ans.index("黑")] = noMatchNums[0]
            ans[ans.index("白")] = noMatchNums[1]
        else:
            ans[ans.index("黑")] = noMatchNums[1]
            ans[ans.index("白")] = noMatchNums[0]
    # ans是获取的图片,对应原图的序列(0-8)
    ans = [i%10 for i in ans]
    s = ""
    for l in ans:
        s+=str(l)
    return s,str(ans[white])

由序列,白块位置,step,swap得到结果,最开始使用遍历,遇到step=15+的20分钟还出不来
于是就对原来的方法稍微优化一下,不过只能得到大部分最优解
改进思路

# 交换字符串两个位置
def change(s,i,j):
    if i==j:
        return s
    a = min(i,j)
    b = max(i,j)
    return s[:a]+s[b]+s[a+1:b]+s[a]+s[b+1:]

def getMethod(nums,white,step,swap):
    '''
    1.将部分数据写在文件里
    white是指白块对应的数字
    flag中的white.txt是判断当前序列在不在文件中
    在的话有解,获取索引,去ans中的white.txt获得序列;否则无解
    '''
    ww = open("C:\\software\\flag\\"+white+".txt")
    qq = ww.read()
    pflag = qq.split("\n")
    ww.close()

    ww = open("C:\\software\\ans\\"+white+".txt")
    qq = ww.read()
    pans = qq.split("\n")
    ww.close()

    # 记录要交换的序列,交换的位置,以及最少步数
    post_swap = None
    post_ope = None
    minsteps = 100

    queue = []
    flag = {}
    # 2.对强制交换后的序列也进行判重
    flag2 = {}
    flag3 = {}
    lis = [[i,j] for i in range(9) for j in range(9) if i<j]
    flag[nums]=1
    queue.append(nums+"0 ")
    while queue:
        p = queue[0]
        queue.remove(p)
        space = p.index(" ")
        # 步数
        step2 = int(p[9:space])
        # 空格+序列
        me = p[space:]
        # 字符串
        p=p[:9]
        if p=="012345678":
            if step2<=step:
                post_swap = []
                post_ope = me[1:]
            break
        if step2 == step:
            s = change(p,swap[0]-1,swap[1]-1)
            if flag2.__contains__(s):
                continue
            else:
                flag2[s] = 1
            if s in pflag:
                ins = pflag.index(s)
                method = pans[ins]
                if len(me[1:]+method)<minsteps:
                    post_swap = []
                    post_ope = me[1:]+method
                    minsteps = len(post_ope)
                    # print("[]",post_ope,str(minsteps))
            else:
                for li in lis:
                    s2 = change(s,li[0],li[1])
                    if flag3.__contains__(s2):
                        continue
                    else:
                        flag3[s2] = 1
                    if s2 in pflag:
                        ins = pflag.index(s2)
                        method = pans[ins]
                        if len(me[1:]+method)<minsteps:
                            post_swap = [li[0]+1,li[1]+1]
                            post_ope = me[1:]+method
                            minsteps = len(post_ope)
                            # print(post_swap,post_ope,str(minsteps))  
            # 3.当前最短步数已经等于其它大佬的最短步数
            #if minsteps == 20:
            #    break
        if step2<step:
            pos = p.index(white)
            if(pos>=3):# 上
                s = change(p,pos-3,pos)
                if(not flag.__contains__(s)):
                    flag[s]=1
                    queue.append(s+str(step2+1)+me+"w")
            if(pos<=5):# 下
                s = change(p,pos,pos+3)
                if(not flag.__contains__(s)):
                    flag[s]=1
                    queue.append(s+str(step2+1)+me+"s")
            if(pos%3!=0):# 左
                s = change(p,pos-1,pos)
                if(not flag.__contains__(s)):
                    flag[s]=1
                    queue.append(s+str(step2+1)+me+"a")
            if(pos%3!=2):# 右
                s = change(p,pos,pos+1)
                if(not flag.__contains__(s)):
                    flag[s]=1
                    queue.append(s+str(step2+1)+me+"d")
    return post_ope,post_swap

性能分析与改进

因为涉及到图片的读写操作,以及文件的读取,所以io操作占了较大的比例

单元测试

from AI import getMethod
import unittest
from BeautifulReport import BeautifulReport as br

class Test(unittest.TestCase):
    # 测试3组:强制交换完正好还原,一开始就是还原后的序列,正常情况
    def test1(self):
        nums = "017345628"
        white = "2"
        step = 2
        swap = [3,8]
        print(getMethod(nums,white,step,swap))
    
    def test2(self):
        nums = "012345678"
        white = "2"
        step = 2
        swap = [3,8]
        print(getMethod(nums,white,step,swap))
    
    def test3(self):
        nums = "876543210"
        white = "2"
        step = 5
        swap = [3,4]
        print(getMethod(nums,white,step,swap))

if __name__ == "__main__":
    ts = unittest.TestSuite()
    test = [Test('test1'),Test('test2'),Test('test3'),]
    ts.addTests(test)
    br(suite).report('result.html','report','.')

2.游戏实现
游戏分为前端和后端
前端采用了html、js、bootstrap、jquery、ajax等技术,后端用python flask库搭建了个本地服务器
1)生成几百种有解的序列,写入到文件
2)游戏页面点击开始游戏/重新开始,会通过ajax的get方法向后端获取数据,包括原图(随机),由原图生成的子图,白块,位置排列,白块位置,
前端根据获得的数据进行排列数据
3)交换相邻的块,js实现.点击白块周边的块,则两个img标签的src、value属性互换,标记白块位置的变量pos变为点击的块的id

function turn(btn) {
	white_btn = document.getElementById(pos);
	btn_id = btn.id;
	if (pos == btn_id) return;
	if ((btn_id == pos - 3) || (btn_id == parseInt(pos) + parseInt(3)) || (btn_id == pos - 1 && pos != 3 && pos != 6) ||
		(btn_id == parseInt(pos) + parseInt(1) && pos != 2 && pos != 5)) {
                //两块交换,就交换他们的图片路径和img标签的value值,
		src = white_btn.src;
		white_btn.src = btn.src;
		btn.src = src;

		var tmp = $("#" + pos).attr("value");
		$("#" + pos).attr("value", $("#" + btn_id).attr("value"));
		$("#" + btn_id).attr("value", tmp);
                //改变步数
		pos = btn_id;
		k = document.getElementById("cnt").innerHTML;
		document.getElementById("cnt").innerHTML = parseInt(k) + 1;

		// 判断成功
		var flag = 1;
		for (i = 0; i < 9; i++) {
			if ($("#" + i).attr("value") != i) {
				flag = 0;
				break;
			}
		}
		if (flag) {
			$("#succ").attr("class", "alert alert-success");
			$("#succ").attr("role", "alert");
			$("#succ").text("成功解出!");
			//time stop
			clearTimeout(timer);
			//can't move
			$("img").attr("onClick", "");
			document.getElementById("start").innerHTML = "再来一局";
			// 有帮助 不记录
			if(!isHelp) record();
		}
	}
}

4)自动演示还原.通过jquery获得每个img标签的value,得到一串序列,加上白块的位置,通过ajax的post方法提交给后端,后端调用解法函数返回走的序列,for循环+定时器,白块逐一移到序列的每一个位置

function help() {
	// 如果已经还原了
	var arr = new Array(9);
	flag = 1;
	for (i = 0; i < 9; i++){
		arr[i] = $("#" + i).attr("value");
		if(arr[i]!=i) flag=0;
	}
	if(flag) document.getElementById("succ").innerHTML = '您已经成功啦';
	else {
	isHelp = true;
	var jsonString = JSON.stringify(arr);
	$.ajax({
		type: "POST",
		url: "http://127.0.0.1:5000/h2",
		data: {
			"pos": jsonString,
			"white": arr[pos]
		},
		dataType: "json",
		contentType: "application/x-www-form-urlencoded;charset=UTF-8",
		async: false,
		success: function(data) {
                        // 从后端返回的data是一串序列,第n步移动到哪一个位置,然后设个定时器不断调用移动函数即可
			arr = data;
			var j = 0;
			function fn(){ turn(document.getElementById(arr[j])); j++; }
			for(var i = 0; i < arr.length; i++ ){
		    	setTimeout(fn,i*700)//还原速度
		    }
		},
		error: function(XMLHttpRequest, textStatus, errorThrown) {
			alert("请先开始游戏");
		}
	});
	}
}

5)排行榜。分为时间榜和步数榜,使用bootstrap的按钮组件实现。
2.Github代码签入记录

3.遇到的困难及解决方法。

  • 问题描述
    对新知识不了解,经常出错,而且错误了没提示,只是该功能失效,不好检查bug
    例如:jquery改不了标签的内容,只能用dom获取标签进行修改
    本机作为服务器还得处理跨域问题...
  • 解决尝试
    网上查阅相关资料
  • 是否解决
  • 有何收获
    以前有看过相关视频,但只是纸上谈兵,自己从0到1实现一个小项目还是蛮有成就感。

4.评价你的队友。

  • 值得学习的地方
    实力很强,提出的idea很好,画图很漂亮
  • 需要改进的地方
    得早点开始准备

5.学习进度条

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 200 200 14 14 解决图像识别问题
2 150 350 10 24 学习html,css,js,做出游戏雏形,AI算法初步
3 200 550 16 40 学习jquery,游戏功能添加
学习ajax,实现前后端交互
4 350 900 20 60 使用bootstrap美化页面,
添加排行榜功能,改进AI算法
PSP表格
PSP2.1 Personal Software
Process Stages
预估耗时
(分钟)
实际耗时
(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少
时间
180 200
Development 开发
· Analysis · 需求分析 (包括学习新
技术)
450 480
· Design Spec · 生成设计文档 200 180
· Design Review · 设计复审 60 80
· Coding Standard · 代码规范 (为目前的开
发制定合适的规范)
60 60
· Design · 具体设计 100 120
· Coding · 具体编码 1500 1600
· Code Review · 代码复审 90 90
· Test · 测试(自我测试,修改
代码,提交修改)
50 60
Reporting 报告
· Test Repor · 测试报告 60 70
· Size Measurement · 计算工作量 50 75
· Postmortem & Process
Improvement Plan
· 事后总结, 并提出过程
改进计划
100 90
· 合计 2900 3105
posted @ 2020-10-19 22:46  家住海边所以浪  阅读(114)  评论(0编辑  收藏  举报