软工结队作业

结队编程作业:图片华容道

1.结队编程的具体分工

白霖(031804101) 游戏代码实现和算法
熊崟(081800228) 原型设计和博客编写

 

1、基本说明与设计思路

2.原型设计

通过HTML5,JavaScript,CSS的编写创建本地网页,通过网页的形式展现游戏内容和相关功能。

  • 游戏中可以查看复原后的原图
  • 游戏中可以查看复原后的原图
  • 在游戏过程中可以重新开始。
  • 游戏记录时间和步数。
  • 可以按照时间和步数分别查询成绩排名以及对应的移动过程。

 

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

原型模型和实际游戏界面在排版上不同,对应的功能键和功能相同

原型模型的构建应用的软件为Axure Rp 8

2.1 主页面(实际游戏页面)

 

 排行榜:按照步数或者时间进行编排成功游戏者和对应的步数和时间

开始游戏:获取分割打乱后的图片,同时启动时间计时器(游戏进行时为“重新开始”,成功时为“再来一次”,都可以开始新一轮的游戏)

 提示:获取并演示解过程

显示图片:可在左边显示原图,以供游戏参考(再点一次则隐藏)

3、困难与解决办法

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

2.1AI与原型设计实现

1、代码实现思路

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、队友互评

 是个大佬般的人,编程设计等方面都很强。

5、学习进度和PSP

第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:59  永-穆  阅读(105)  评论(0编辑  收藏  举报