结对编程作业

题目:图片华容道

我的GitHub项目链接:https://github.com/suuuhope/PinTu
队友的GitHub项目链接:https://github.com/rhd123/PinTu
我的博客链接:https://www.cnblogs.com/suuuhope/p/13837893.html
队友的博客链接:https://www.cnblogs.com/dishao/p/13816452.html

p-主要工作;s-协助工作;0-无工作

分工内容 队友
原型设计 p 0
基本UI界面 s p
UI细节优化 p s
AI p 0
接口 0 p
题目设计 0 p

二、原型设计

2.1设计说明

首先,是关于模型的设计,我们的想法是:先做个好看点的游戏开始界面,两个按钮“开始游戏”、“结束游戏”。点开始游戏之后,进入可以玩的游戏窗口。左边是拼图区块,右边加些功能型按钮,比如“重新开始”、“退出游戏”、“选择拼图”等,再显示步数、最快步数等内容。

拿到题目的时候我就想用用pycharm实现,因为pycharm有个pygame的库,我觉得做这游戏效果会好些。

最开始就是用pygame设计出游戏窗口,写一些颜色的RGB值,用纯色填充游戏背景,导入图片。对九个拼图进行编号,利用random库的shuffle函数进行随机排序,从图片中挖出一个空白格,用空白格做文章,监听键盘进行移动,当序号还原到未被打乱的状态时,即完成拼图,然后会在界面上显示完整的拼图。
之后优化了进入的界面,做了个稍帅一点的背景图,放了几个按钮,点击“开始游戏”进入游戏界面,游戏界面增加了几个功能按钮:“重新开始”,“退出游戏”,“AI解题”,“上次步数显示”,“当前步数显示”。点击AI解题后会在pycharm的运行面板里显示出后续的打发,根据“0”的移动来确定空白块的移动。

2.2使用的原型模型设计工具:

Mockplus

2.3共同面对

2.4遭遇的困难及解决方法

  • 困难描述:①不知道怎么和队友交接代码 ②一开始不知道怎么起步,迷茫了一个星期 ③设想着两个窗口,点击第一个窗口的选项然后切换到第二个游玩窗口,但是不知道两个窗口之间要怎么切换。
  • 解决尝试:两个人在同一个宿舍搞代码还是比较方便的,讨论也比较方便,然后就py文件发来发去,就安排上了。迷茫完了还是得面对,第二个星期就开始慢慢着手做事了,从pygame的学习开始,然后就递归递归递归学习······
  • 问题基本上解决了。
  • 收获:团队作业应该多分函数写在不同的python file上,调用起来方便,彼此沟通也快,找代码段落也快,对方修改了某个函数、某个类,也不需要大动干戈的修改。

三、AI与原型设计实现

3.1代码实现思路:

1.网络接口的使用:

# 从Postman获取题目,将图片保存至文件夹
pic_url = requests.get('http://47.102.118.1:8089/api/problem?stuid=031802423')
image_code = base64.b64decode(pic_url.json()['img'])
file_like = BytesIO(image_code)
image = Image.open(file_like)
image.save('image.JPG')

2.代码组织与内部实现设计:

3.关键的算法与关键实现部分流程图

比较关键的算法有:从文件夹中寻找与切块后的拼图拥有相同部分的图片的算法

4.比较重要的代码片段

  • 将base64编码转为图片,并将图片保存到文件夹的代码:
pic_url = requests.get('http://47.102.118.1:8089/api/problem?stuid=031802423')
image_code = base64.b64decode(pic_url.json()['img'])
file_like = BytesIO(image_code)
image = Image.open(file_like)
image.save('image.JPG')
  • 将图片切块、保存的代码:
#切图
def cut_image(image) :
    item_width = 300
    box_list = []
    for i in range(0, 3) :  # 两重循环,生成9张图片基于原图的位置
        for j in range(0, 3) :
            box = (j*item_width, i*item_width, (j+1)*item_width, (i+1)*item_width)
            box_list.append(box)
    image_list = [image.crop(box) for box in box_list]
    return image_list
# 保存拼图
def save_images(image_list) :
    index = 0
    for image in image_list :
        image.save(str(index) + '.jpg')
        index += 1
gameImage2 = Image.open('./zifu/H_.jpg')
image_list = cut_image(gameImage2)
save_images(image_list)
  • 比较两章图片是否相同的代码:
#对比是否为相同图片,如果相同,则返回True
def compare_image_with_hash(image_file_name_1, image_file_name_2, max_dif=0) :
    ImageFile.LOAD_TRUNCATED_IMAGES = True
    hash_1 = None
    hash_2 = None
    with open(image_file_name_1, 'rb') as fp :
        hash_1 = imagehash.average_hash(Image.open(fp))
    with open(image_file_name_2, 'rb') as fp :
        hash_2 = imagehash.average_hash(Image.open(fp))
    dif = hash_1 - hash_2
    if dif < 0 :
        dif = -dif
    if dif <= max_dif :
        return True
    else :
        return False
  • 判断文件夹中的图片切块是否与选出的拼图块相同的代码:
for pic_name in os.listdir("D://python_work//pycharmWork//huarong//zifu"):
    img = Image.open("D://python_work//pycharmWork//huarong//zifu" + "/" +pic_name)
    image_list1 = cut_image(img)
    for image in image_list1 :
            image.save('cankao' + '.jpg')
            if compare_image_with_hash('cankao.jpg', 'tu.jpg', 0):
                img.save('cankao1.jpg')
                break
  • 绘制每次移动后的拼图的代码:
for i in range(blocks):
    row = int(i/rows)
    col = int(i%rows)
    rectDst = pygame.Rect(col*width, row*height, width, height)
    if gameBoard[i] == -1:
        continue
    rowArea = int(gameBoard[i]/rows)
    colArea = int(gameBoard[i]%rows)
    rectArea = pygame.Rect(colArea*width, rowArea*height, width, height)
    windowSurface.blit(gameImage, rectDst, rectArea)

5.性能分析与改进:

布置完题目后,在一节人工智能课上听到了老师讲解使用Astar算法解决八数码问题便反应过来可以运用到此次的结对编程作业上。Astar算法其实是Dijkstra最短路径算法的一种扩展,由于效率高,被广泛应用于电脑游戏中的路径规划问题中。此算法中最核心的就是启发是函数与对open表和close表的维护。
启发式函数 f(n) = g(n) + h(n) 可以理解为从原点到目标点的所需要消耗的总代价f(n),这个总代价可以分成两个部分,从原点到中间节点(搜索的中间状态)已经消耗的实际代价g(n),和从中间节点到目标点的预测h(n)。
open表:可以认为是一个未搜索节点的表
close表:可以认为是一个已完成搜索的节点的表
在每一次搜索中两个表中的元素可以互相转换。
在此次作业中,我把启发式函数含义定为:当前格局与目的格局相比,其位置不符的将牌数目。

def A_star(s):
        global openlist  # 全局变量可以让open表进行时时更新
        openlist = [s]
        while (openlist):  # 当open表不为空
            get = openlist[0]  # 取出open表的首节点
            if (get.node == goal.node).all():  # 判断是否与目标节点一致
                return get
            openlist.remove(get)  # 将get移出open表
            # 判断此时状态的空格位置
            for a in range(len(get.node)):
                for b in range(len(get.node[a])):
                    if get.node[a][b] == 0:
                        break
                if get.node[a][b] == 0:
                    break
            # 开始移动
            for i in range(len(get.node)):
                for j in range(len(get.node[i])):
                    c = get.node.copy()
                    if (i + j - a - b) ** 2 == 1:
                        c[a][b] = c[i][j]
                        c[i][j] = 0
                        new = State(c)
                        new.father = get  # 此时取出的get节点成为新节点的父亲节点
                        new.g = get.g + 1  # 新节点与父亲节点的距离
                        new.h = h(new)  # 新节点的启发函数值
                        new.f = new.g + new.h  # 新节点的估价函数值
                        openlist.append(new)  # 加入open表中
            list_sort(openlist)  # 排序

6.改进的思路:

对于启发式函数的定义可以再进行改进,比如将其定义为:各将牌移到目的位置所需移动的距离的总和。

7.性能分析展示

  • 性能分析图


  • 程序中消耗最大的函数

8.展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路

  • (1)对空白块上下左右移动的测试,确保移动时不会跑到界外去,在完成拼图时能够显示出原来完整的图片。对空白块移动的单元测试代码如下:
import pygame, sys, random
from pygame.locals import *

# 一些常量
window_length = 500
background = (255, 255, 255)
BLUE = (0, 0, 255)
BLACK = (0, 0, 0)

rows = 3
blocks = rows * rows


# 随机生成游戏盘面
def newGameBoard() :
    board = []
    for i in range(blocks) :
        board.append(i)
    blackCell = blocks - 1
# 将图片的一块挖空
    board[blackCell] = -1
    # 将空白块进行随机移动,确保每次打乱都能够有解
    for i in range(50) :
        direction = random.randint(0, 3)
        if (direction == 0) :
            blackCell = moveLeft(board, blackCell)
        elif (direction == 1) :
            blackCell = moveRight(board, blackCell)
        elif (direction == 2) :
            blackCell = moveUp(board, blackCell)
        elif (direction == 3) :
            blackCell = moveDown(board, blackCell)
    return board, blackCell
# 若空白图像块不在最左边,则将空白块左边的块移动到空白块位置
def moveRight(board, blackCell) :
    if blackCell % rows == 0 :
        return blackCell
    board[blackCell - 1], board[blackCell] = board[blackCell], board[blackCell - 1]
    return blackCell - 1


# 若空白图像块不在最右边,则将空白块右边的块移动到空白块位置
def moveLeft(board, blackCell) :
    if blackCell % rows == rows - 1 :
        return blackCell
    board[blackCell + 1], board[blackCell] = board[blackCell], board[blackCell + 1]
    return blackCell + 1


# 若空白图像块不在最上边,则将空白块上边的块移动到空白块位置
def moveDown(board, blackCell) :
    if blackCell < rows :
        return blackCell
    board[blackCell - rows], board[blackCell] = board[blackCell], board[blackCell - rows]
    return blackCell - rows


# 若空白图像块不在最下边,则将空白块下边的块移动到空白块位置
def moveUp(board, blackCell) :
    if blackCell >= blocks - rows :
        return blackCell
    board[blackCell + rows], board[blackCell] = board[blackCell], board[blackCell + rows]
    return blackCell + rows


# 是否完成
def isFinished(board, blackCell) :
    for i in range(blocks - 1) :
        if board[i] != i :
            return False
    return True


# 初始化
pygame.init()
# 加载图片
gameImage = pygame.image.load('wu.jpg')
gameRect = gameImage.get_rect()

# 设置窗口
screen = pygame.display.set_mode((gameRect.width, gameRect.height))
pygame.display.set_caption('拼图')
# 每块拼图的大小
cellWidth = int(gameRect.width / rows)
cellHeight = int(gameRect.height / rows)

finish = False

gameBoard, blackCell = newGameBoard()

# 游戏主循环
while True :
    for event in pygame.event.get() :
        if event.type == QUIT :
            pygame.quit()
            sys.exit()
        if finish :
            continue
        if event.type == KEYDOWN :
            if event.key == K_d :
                blackCell = moveLeft(gameBoard, blackCell)

            if event.key == K_a :
                blackCell = moveRight(gameBoard, blackCell)

            if event.key == K_s :
                blackCell = moveUp(gameBoard, blackCell)

            if event.key == K_w :
                blackCell = moveDown(gameBoard, blackCell)


    if (isFinished(gameBoard, blackCell)) :
        gameBoard[blackCell] = blocks - 1
        finish = True

    screen.fill(background)

#绘制拼图
    for i in range(blocks) :
        rowDst = int(i / rows)
        colDst = int(i % rows)
        rectDst = pygame.Rect(colDst * cellWidth, rowDst * cellHeight, cellWidth, cellHeight)

        if gameBoard[i] == -1 :
            continue

        rowArea = int(gameBoard[i] / rows)
        colArea = int(gameBoard[i] % rows)
        rectArea = pygame.Rect(colArea * cellWidth, rowArea * cellHeight, cellWidth, cellHeight)
        screen.blit(gameImage, rectDst, rectArea)


    pygame.display.update()

构造思路:刚起步的时候,取出main.py中的部分代码,进行测试,再进行改进。

3.2 GitHub的代码签入记录:


3.3 遇到的代码模块异常或结对困难及解决方法

  • 困难描述:①如何从Postman中导入图片的base64编码,将其转换成对应的图片。②如何识别出原图作为参考图片进行后续拼图。 ③打开图片的时候总是会出现一些错误。 ④AI结题有的时候会崩溃
  • 解决尝试:问题①通过网上资料查询比较容易找到代码并进行修改;问题②我们先将打乱的拼图切成九块,取其中一块,判断它是不是空白块或是全黑块,若都不是,则顺序打开文件夹中的图片,进行切块,并依次对比,得到相同的,则是对应的未打乱的图片。 问题③我们选择保存这些后面需要用到的图片,再从文件夹中调用这些图片。
  • 问题①③完美解决;问题②判断C和e会出现冲突;问题④的崩溃偶尔还是会发生。
  • 收获:问题②的解决思路是从“拼图的每一块都是独一无二的”得到的,将未打乱的图片和打乱的图片都切块,对比得到。学会了怎么从文件夹打开图片,学会了怎么使用Postman的链接。pygame的图片调用跟TK的画布一起使用的话会出现两个窗口,然后会神奇的自动关闭掉一个。。也许是代码打错了(逃)

3.4评价队友

感谢迪哥不抛弃不放弃,UI界面设计和增添细节有一手,最后的收尾工作也是我迪哥carry。
这次的结对编程其实很早就发布了题目,一开始确实没什么思路,后面有思路了但工作开始的太晚了,导致时间大部分花在基础功能的实现上很多方面没来得及去优化,拖延症真的使不得。果然ddl才是最终生产力

3.5 PSP表

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
· Estimate · 估计这个任务需要多少时间 60 60
Development 开发
· Analysis · 需求分析 (包括学习新技术) 500 720
· Design Spec · 生成设计文档 30 60
· Design Review · 设计复审 40 50
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 20
· Design · 具体设计 40 60
· Coding · 具体编码 360 1440
· Code Review · 代码复审 30 60
· Test · 测试(自我测试,修改代码,提交修改) 180 360
Reporting 报告
· Test Repor · 测试报告 60 60
· Size Measurement · 计算工作量 30 30
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
· 合计 1380 2980

学习进度条

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
1 0 0 0 0 迷茫的一周
2 0 0 15 15 学习了pygame和simplegui的基础用法
3 204 204 20 35 选择了pygame开始写,用Mockplus做了原型
4 675 879 24 59 学了A*算法,设计游戏窗口,设计游戏机制
posted @ 2020-10-19 18:26  suuuhope  阅读(164)  评论(0编辑  收藏  举报