前段时间用C语言做了个字符版的推箱子,着实是比较简陋。正好最近用到了Python,然后想着用Python做一个图形界面的推箱子。这回可没有C那么简单,首先Python的图形界面我是没怎么用过,在网上找了一大堆教材,最后选择了tkinter,没什么特别的原因,只是因为网上说的多。

接下来就来和大家分享一下,主要分享两点,第一就是这个程序的实现过程,第二点就是我在编写过程中的一些思考。

一、介绍

开发语言:Python   3.7
开发工具:PyCharm 2019.2.4
日期:2019年10月2日
作者:ZackSock

这次的推箱子不同与C语言版的,首先是使用了图形界面,然后添加了背景音乐,还有就是可以应对多种不同的地图。我内置了三张地图,效果图如下:



比上次的高级多了,哈哈。

二、开发环境

我也不知道这么取名对不对,这里主要讲的就是使用到的模块。因为Python不是我的强项,所以我只能简单说一下。

首先我使用的是Python3.7,主要用了两个模块,tkinterpygame。其中主要使用的还是tkinter,而pygame是用来播放音乐的。(因为没去了解pygame,所有界面我是用tkinter写的)。库的导入我使用的是pycharm,导入非常方便。如果使用其它软件可以考虑用pip安装模块,具体操作见博客:https://www.cnblogs.com/banzhen/p/isimulink.html。

pip install tkinter
pip install pygame

三、原理分析

1、地图

地图在思想方面没有太大改变,还是和以前一样使用二维数组表示。不过我认为这样确实不是非常高效的做法,不过这个想法也是在我写完之后才有的

2、移动

在移动方面我修改了很多遍,先是完全按照原先的算法。这个确实也实现了,不过只能在第一关有效,在我修改地图之后发现了一系列问题,然后根据问题发现实际遇到的情况要复杂很多。因为Python是用强制缩进替代了{},所以代码在观看中会有些难度,希望大家见谅。

移动的思想大致如下:

/**
*   0表示空白
*   1表示墙
*   2表示人
*   3表示箱子
*   4表示终点
*   5表示已完成的箱子
*   6表示在终点上的人
*/
一、人1、移动方向为空白前方设置为2当前位置为02、移动方向为墙直接return3、移动方向为终点   前面设置为6当前位置设置为04、移动方向为已完成的箱子4.1、已完成箱子前面是箱子return4.2、已完成箱子前面是已完成的箱子return4.3、已完成箱子前面是墙return4.4、已完成箱子前面为空白已完成箱子前面设置3前方位置设置为6当前位置设置为04.5、已完成箱子前面为终点已完成箱子前面设置为5前方位置设置为6当前位置设置为05、前方为箱子5.1、箱子前方为空白箱子前方位置设置为3前方位置设置为2当前位置设置为05.2、箱子前方为墙return5.3、箱子前方为箱子return5.4、箱子前方为已完成的箱子return5.5、箱子前方为终点箱子前方位置设置为5前方位置设置为2当前位置设置为0
二、在终点上的人1、移动方向为空白前方设置为2当前位置设置为42、移动方向为墙直接return3、移动方向为终点前面设置为6当前位置设置为44、移动方向为已完成的箱子4.1、已完成箱子前面是箱子return4.2、已完成箱子前面是已完成的箱子return4.3、已完成箱子前面是墙return4.4、已完成箱子前面为空白已完成箱子前面设置3前方位置设置为6当前位置设置为44.5、已完成箱子前面为终点已完成箱子前面设置为5前方位置设置为6当前位置设置为45、前方为箱子5.1、箱子前方为空白箱子前方位置设置为3前方位置设置为2当前位置设置为45.2、箱子前方为墙return5.3、箱子前方为箱子return5.4、箱子前方为已完成的箱子return5.5、箱子前方为终点箱子前方位置设置为5前方位置设置为2当前位置设置为4

首先,人有两种状态,人可以站在空白处,也可以站在终点处。后面我发现,人在空白处和人在终点唯一的区别是,人移动后,人原先的位置一个设置为0,即空白,一个设置为4,即终点。所以我在移动前判断人背后的东西,就可以省去一般的代码了。上面的逻辑可以改为如下:

/**
*   0表示空白
*   1表示墙
*   2表示人
*   3表示箱子
*   4表示终点
*   5表示已完成的箱子
*   6表示在终点上的人
*/
if(当前位置为2):#即人在空白处back = 0
elif(当前位置为6):#即人在终点处back = 41、移动方向为空白  (可移动)前方设置为2当前位置为back
2、移动方向为墙直接return
3、移动方向为终点   (可移动)前面设置为6当前位置设置为back
4、移动方向为已完成的箱子4.1、已完成箱子前面是箱子return4.2、已完成箱子前面是已完成的箱子return4.3、已完成箱子前面是墙return4.4、已完成箱子前面为空白  (可移动)已完成箱子前面设置3前方位置设置为6当前位置设置为back4.5、已完成箱子前面为终点  (可移动)已完成箱子前面设置为5前方位置设置为6当前位置设置为back
5、前方为箱子5.1、箱子前方为空白  (可移动)箱子前方位置设置为3前方位置设置为2当前位置设置为back5.2、箱子前方为墙return5.3、箱子前方为箱子return5.4、箱子前方为已完成的箱子return5.5、箱子前方为终点   (可移动)箱子前方位置设置为5前方位置设置为2当前位置设置为back

四、文件分析


目录结构如下,主要有三个文件BoxGame、initGame和Painter。test文件的话就是测试用的,没有实际用处。然后讲一下各个文件的功能:

  1. BoxGame:作为游戏的主入口,游戏的主要流程就在里面。老实说我Python学习的内容比较少,对Python的面向对象不是很熟悉,所有这个流程更偏向于面向过程的思想。
  2. initGame:初始化或存储一些数据,如地图数据,人的位置,地图的大小,关卡等
  3. Painter:我在该文件里定义了一个Painter对象,主要就是用来绘制地图

除此之外就是图片资源和音乐资源了。

五、代码分析

1、BoxGame
from tkinter import *
from initGame import *
from Painter import Painter
from pygame import mixer#创建界面并设置属性
#创建一个窗口
root = Tk()
#设置窗口标题
root.title("推箱子")
#设置窗口大小,当括号中为"widhtxheight"形式时,会判断为设置宽高这里注意“x”是重要标识
root.geometry(str(width*step) + "x" + str(height*step))
#设置边距, 当括号中为"+left+top"形式,会判断为设置边距
root.geometry("+400+200")
#这句话的意思是width可以改变0,height可以改变0,禁止改变也可以写成resizable(False, False)
root.resizable(0, 0)#播放背景音乐
mixer.init()
mixer.music.load('bgm.mp3')   #加载音乐
mixer.music.play()      #播放音乐,歌曲播放完会自动停止#创建一个白色的画板,参数分别是:父窗口、背景、高、宽
cv = Canvas(root, bg='white', height=height*step, width=width*step)#绘制地图
painter = Painter(cv, map, step)
painter.drawMap()#关联Canvas
cv.pack()#定义监听方法
def move(event):pass#绑定监听事件,键盘事件第一个参数固定为"<Key>",第二个参数为方法名(不能加括号)
root.bind("<Key>", move)
#进入循环
root.mainloop()

因为move的代码比较长,就先不写出来,后面讲解。BoxGame主要流程如下:

  1. 导入模块
  2. 创建窗口并设置属性
  3. 播放背景音乐
  4. 创建画板
  5. 在画板上绘制地图
  6. 将画板铺到窗口上
  7. 让窗口关联监听事件
  8. 游戏循环了
2、initGame
#游戏需要的一些参数
mission = 0
mapList = [[[0, 0, 1, 1, 1, 0, 0, 0],[0, 0, 1, 4, 1, 0, 0, 0],[0, 0, 1, 0, 1, 1, 1, 1],[1, 1, 1, 3, 0, 3, 4, 1],[1, 4, 0, 3, 2, 1, 1, 1],[1, 1, 1, 1, 3, 1, 0, 0],[0, 0, 0, 1, 4, 1, 0, 0],[0, 0, 0, 1, 1, 1, 0, 0]],[[0, 0, 0, 1, 1, 1, 1, 1, 1, 0],[0, 1, 1, 1, 0, 0, 0, 0, 1, 0],[1, 1, 4, 0, 3, 1, 1, 0, 1, 1],[1, 4, 4, 3, 0, 3, 0, 0, 2, 1],[1, 4, 4, 0, 3, 0, 3, 0, 1, 1],[1, 1, 1, 1, 1, 1, 0, 0, 1, 0],[0, 0, 0, 0, 0, 1, 1, 1, 1, 0]],[[0, 0, 1, 1, 1, 1, 0, 0],[0, 0, 1, 4, 4, 1, 0, 0],[0, 1, 1, 0, 4, 1, 1, 0],[0, 1, 0, 0, 3, 4, 1, 0],[1, 1, 0, 3, 0, 0, 1, 1],[1, 0, 0, 1, 3, 3, 0, 1],[1, 0, 0, 2, 0, 0, 0, 1],[1, 1, 1, 1, 1, 1, 1, 1]],[[1, 1, 1, 1, 1, 1, 1, 1],[1, 0, 0, 1, 0, 0, 0, 1],[1, 0, 3, 4, 4, 3, 0, 1],[1, 2, 3, 4, 5, 0, 1, 1],[1, 0, 3, 4, 4, 3, 0, 1],[1, 0, 0, 1, 0, 0, 0, 1],[1, 1, 1, 1, 1, 1, 1, 1]]
]
map = mapList[3]#人背后的东西
back = 0
#地图的宽高
width, height = 0, 0
#地图中箱子的个数
boxs = 0
#地图中人的坐标
x = 0
y = 0
#画面大小
step = 30def start():global width, height, boxs, x, y, map# 做循环变量m, n = 0, 0for i in map:for j in i:# 获取宽,每次内循环的次数都是一样的,只需要第一次记录width就可以了if (n == 0):width += 1#遍历到箱子时箱子数量+1if (j == 3):boxs += 1#当为2或者6时,为遍历到人if (j == 2 or j == 6):x, y = m, nm += 1m = 0n += 1height = n
start()

因为我还没有实现关卡切换,所以这里的mapList和mission没有太大用处,主要参数有一下几个:

  1. back:人背后的东西(前面分析过了)
  2. width、height:宽高
  3. boxs:箱子的个数
  4. x、y:人的坐标
  5. step:每个正方形格子的边长,因为我对Canvas绘制图片不熟悉,所以固定图片为30px

因为initGame中没有定义类,所以在引用时就相当于执行了其中的代码。

3、Painter
from tkinter import PhotoImage, NW#在用Canvas绘制图片时,图片必须是全局变量
img = []
class Painter():def __init__(self, cv, map, step):"""Painter的构造函数,在cv画板上,根据map画出大小为step的地图"""#传入要拿来画的画板self.cv = cv#传入地图数据self.map = map#传入地图大小self.step = stepdef drawMap(self):"""用来根据map列表绘制地图"""#img列表的长度imgLen = 0global img#循环变量x, y = 0, 0for i in self.map:for j in list(i):#记录实际位置lx = x * self.steply = y * self.step# 画空白处if (j == 0):self.cv.create_rectangle(lx, ly, lx + self.step, ly+self.step,fill="white", width=0)# 画墙elif (j == 1):img.append(PhotoImage(file="imgs/wall.png"))self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])elif (j == 2):img.append(PhotoImage(file="imgs/human.png"))self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])# 画箱子elif (j == 3):img.append(PhotoImage(file="imgs/box.png"))self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])elif (j == 4):img.append(PhotoImage(file="imgs/terminal.png"))self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])elif (j == 5):img.append(PhotoImage(file="imgs/star.png"))self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])elif (j == 6):img.append(PhotoImage(file="imgs/t_man.png"))self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])x += 1x = 0y += 1

这里说一下,cv的方法,这里用到了两个,一个是create_image一个是create_rectangle:

#绘画矩形
cv.create_rectangle(sx, sy, ex, ey, key=value...)
1、前两个参数sx、sy(s代表start)为左上角坐标
2、后两个参数ex、ey(e代表end)表示右下角坐标
3、而后面的key=value...表示多个key=value形式的参数(顺序不固定)
如:
#填充色为红色
fill = "red"
#边框色为黑色
outline = "black"
#边框宽度为5
width = 5具体使用例如:
#在左上角画一个边长为30,的黑色矩形
cv.create_rectangle(0, 0, 30, 30, fill="black", width=0)

然后是绘制图片:

#这里要注意img必须是全局对象
self.cv.create_image(x, y, anchor=NW, img)
1、前两个参数依旧是坐标,但是这里不一定是左上角坐标,x,y默认是图片中心坐标
2、anchor=NW,设置anchor后,x,y为图片左上角坐标
3、img是一个PhotoImage对象(PhotoImage对象为tkinter中的对象),PhotoImage对象的创建如下#通过文件路径创建PhotoImage对象
img = PhotoImage(file="img/img1.png")

因为我自己也不是非常了解,所以更细节的东西我也说不出来了。

然后是实际坐标的问题,上面说的坐标都是以数组为参考。而实际绘图时,需要用具体的像素。在绘制过程中,需要绘制两种,矩形、图片。

  1. 矩形:矩形需要两个坐标。当数组坐标为(1,1)时,因为单元的间隔为step(30),所以对应的像素坐标为(30, 30)。(2,2)对应(60,60),即(x*step,y*step),而终点位置为(x*step+step,y*step+step)。
  2. 图片:绘制图片只需要一个坐标,左上角坐标,这个是前面一样为(x*step, y*step)。

上面还有一个重要的点,我在最开始定义了img列表,用于装图片对象。开始我尝试用单个图片对象,但是在绘制图片的时候只会显示一个,后面想到用img列表代替,然后成功了。(因为我学的不是非常扎实,也解释不清楚)。

在绘制图片时有以下两个步骤:

#根据数组元素,创建相应的图片对象,添加到列表末尾
img.append(PhotoImage(file="imgs/wall.png"))#在传入图片对象参数时,使用img[imgLen - 1],imgLen为列表当前长度,而imgLen-1就是最后一个元素,即刚刚创建的图片对象
self.cv.create_image(lx, ly, anchor=NW, image=img[imgLen - 1])
4、move
def move(event):global x, y, boxs, back, mission,mapList, mapdirection = event.char#判断人背后的东西# 在空白处的人if (map[y][x] == 2):back = 0 #讲back设置为空白# 在终点上的人elif (map[y][x] == 6):back = 4    #将back设置为终点#如果按的是wif(direction == 'w'):#获取移动方向前方的坐标ux, uy = x, y-1#如果前方为墙,直接returnif(map[uy][ux] == 1):return# 前方为空白(可移动)if (map[uy][ux] == 0):map[uy][ux] = 2       #将前方设置为人# 前方为终点elif (map[uy][ux] == 4):map[uy][ux] = 6       #将前方设置为终点# 前方为已完成的箱子elif (map[uy][ux] == 5):#已完成箱子前面为箱子已完成箱子或者墙都不能移动if (map[uy - 1][ux] == 3 or map[uy - 1][ux] == 5 or map[uy - 1][ux] == 1):return# 已完成前面为空白(可移动)elif (map[uy - 1][ux] == 0):map[uy - 1][ux] = 3     #箱子向前移动map[uy][ux] = 6         #已完成箱子处原本是终点,人移动上去之后就是6了boxs += 1              #箱子移出,箱子数量要+1#已完成箱子前面为终点(可移动)elif (map[uy - 1][ux] == 4):map[uy - 1][ux] = 5       #前方的前方设置为已完成箱子map[uy][ux] = 6          #前方的箱子处原本是终点,人移动上去后是6# 前方为箱子elif (map[uy][ux] == 3):# 箱子不能移动if (map[uy - 1][ux] == 1 or map[uy - 1][ux] == 3 or map[uy - 1][ux] == 5):return# 箱子前方为空白elif (map[uy - 1][ux] == 0):map[uy - 1][ux] = 3map[uy][ux] = 2# 箱子前方为终点elif (map[uy - 1][ux] == 4):map[uy - 1][ux] = 5map[uy][ux] = 2boxs -= 1#前面只是改变了移动方向的数据,当前位置还是2或6,此时把当前位置设置为backmap[y][x] = back#记录移动后的位置y = uy# 清除屏幕,并绘制地图cv.delete("all")painter.drawMap()if(boxs == 0):print("游戏结束")

这里只讲了一个方向的,因为其它方向代码非常类似也就列出来了。唯一的区别就是前方的坐标和前方的前方的坐标具体如下:

  • 向前:前方ux,uy=x,y-1,前方的前方ux,uy-1
  • 向下:前方ux,uy=x,y+1,前方的前方ux,yu+1
  • 向左:前方ux,uy=x-1,y,前方的前方ux-1,uy
  • 向右:前方ux,uy=x+1,y,前方的前方ux+1,uy

六、总结

因为本身对Python语言的不了解,在写博客中难免会有解释不清楚或者错误的地方,非常抱歉,希望大家见谅。

这个游戏用的更多的是面向过程的思想,而可以改进的地方也非常多。对于改进工作我也让Python大佬Clever_Hui来帮忙完成了,因为修改后的代码不是非常了解,所有我分享的是我原本的代码。源码两份我都会上传,感谢大家支持。

  • 原版:链接:https://pan.baidu.com/s/1NSmOeSlpHqU1OFFTcS_kcA 提取码:r0ba

  • 改进版:链接:https://pan.baidu.com/s/1-_uiZKRdH-OBVq1-ES9JaA 提取码:53yt

Python使用tkinter模块实现推箱子游戏相关推荐

  1. python推箱子代码详细讲解_Python使用tkinter模块实现推箱子游戏

    前段时间用C语言做了个字符版的推箱子,着实是比较简陋.正好最近用到了Python,然后想着用Python做一个图形界面的推箱子.这回可没有C那么简单,首先Python的图形界面我是没怎么用过,在网上找 ...

  2. python推箱子游戏代码_用python入门知识做推箱子游戏,若能打过第三关,则可以学会编程...

    不得不说,Python小游戏是最适合入门编程的项目,因为太简单! 无论懂或完全不懂python,这样的小游戏,你都可以直接开发出来,原因很简单: 在详细教程里,会有从零基础开始,一步一步的教你完成这个 ...

  3. python推箱子游戏顶层设计子层设计_python实现推箱子游戏

    本文实例为大家分享了python实现推箱子游戏的具体代码,供大家参考,具体内容如下 题目描述: 最短路径为: uurrDDDDuuuulldRurDDDrddLLrruLuuulldRurDDDrdL ...

  4. python 推箱子实验开发报告,python实现推箱子游戏

    本文实例为大家分享了python实现推箱子游戏的具体代码,供大家参考,具体内容如下 题目描述: 最短路径为: uurrDDDDuuuulldRurDDDrddLLrruLuuulldRurDDDrdL ...

  5. 用python写一个推箱子游戏

    好的,我会用中文来帮助您编写一个推箱子游戏. 首先,我们需要定义游戏的地图.地图可以用一个二维数组来表示,其中 0 表示可以走的路,1 表示墙壁,2 表示箱子,3 表示目标位置.例如,我们可以定义一个 ...

  6. python推箱子文档_python实现推箱子游戏

    本文实例为大家分享了python实现推箱子游戏的具体代码,供大家参考,具体内容如下 题目描述: 最短路径为: uurrDDDDuuuulldRurDDDrddLLrruLuuulldRurDDDrdL ...

  7. 一个基于Python的tkinter模块实现的游戏活动日历模拟器

    一个基于Python的tkinter模块实现的游戏活动日历模拟器 1.Python环境准备 2.简单介绍 3.源代码 4.源代码及活动配置表下载 1.Python环境准备 运行该项目需要Python3 ...

  8. 用python开发一个推箱子游戏

    好的,为了开发一个推箱子游戏,你需要了解一些基本的编程概念,如变量,循环,条件语句和函数. 首先,你需要定义游戏场景,即箱子和人物所在的空间.你可以使用二维数组表示游戏场景,每个元素都代表一个格子. ...

  9. 10 益智游戏——推箱子游戏

    推箱子游戏 11.3 关键技术 1.前方的几种情况: 2.列表复制--深复制 11.4 源代码 1.设计游戏地图 2.绘制整个游戏区域图形 3.按键事件处理 4.主程序 #!/usr/bin/env ...

最新文章

  1. CycleGAN非配对图像生成,定制你的卡通照
  2. Java FutureTask示例程序
  3. 软件项目周报_有关细节、效率、周报和扯淡
  4. 自定义元素探秘及构建可复用组件最佳实践 1
  5. cnn在nlp应用中的调参建议
  6. Widnows开启休眠
  7. 通信电子线路高如云pdf_通信电子线路 第三版 教学课件 高如云 封面及目录.pdf...
  8. matlab信道编码程序,信道编码作业matlab.doc
  9. MATLAB-alphaShape三维使用
  10. 阴阳师服务器维护3月25日,阴阳师3月25日更新内容-阴阳师3月25日更新内容介绍_牛游戏网...
  11. 农村信用贷款要具备什么条件,有哪些要求
  12. pboot 将编码转换为实体html_java转换 HTML字符实体,java特殊字符转义字符串
  13. 人口普查分析:利用python+百度文字识别提取图片中的表格数据
  14. Acer 4750 安装黑苹果_黑苹果的安装(干货)
  15. 第二届“强网”拟态防御国际精英挑战赛落幕,29支国内外精英队伍未能突破拟态防御,赛宁网安靶场平台完美支撑BWM新赛制.
  16. Q2营收利润双攀升,陆金所的市值却仍被“束缚”
  17. linux中文化,linux中文化-语系
  18. 记忆的助记器|如何使用联想记忆法
  19. Java 正则表达式工具类大全
  20. 基于置信传播(BP,Belief Propagation)的立体匹配算法

热门文章

  1. 固态硬盘启动蓝屏解决方法
  2. XMPP——Smack[2]会话、消息监听、字体表情和聊天窗口控制
  3. 中国科学院大学(国科大)毕业生申办上海户籍的手续
  4. R语言 关于h2o深度学习的一些心得
  5. 品•文案——聊聊产品文案优化设计
  6. linux获取文件后缀名,linux shell获取文件名和路径,basename/dirname/${}运用
  7. pageCache和bufferCache
  8. Couldn‘t flush user prefs: java.util.prefs.BackingStoreException: Couldn‘t get file lock
  9. MQ命令学习总结大全MQ常用命令
  10. 活用lambda之list函数处理