Python//2048小游戏
这是一篇关于2048小游戏自制记录,用于个人总结与回看。如果能对你有帮助,我很荣幸。
文章目录
- Python//2048小游戏
- 成品效果图
- 0 仍存在的未解决问题
- 一、写在前面
- 二、代码思路与分析
- 三、代码总览
- 四、写代码过程中遇到的坑汇总
- 五、收获总结
成品效果图
伴随音乐,音量可调。
0 仍存在的未解决问题
如果有大佬看见了这篇文章,能帮助我解决以下的一些问题,我十分感激。
1、在我电脑上,pygame(1.9.6)只能加载bmp图片格式,但我从查找的资料来看,pygame应该可以读取png jpg等图片格式的。
2、在我用pyinstaller将代码打包为exe文件时,添加-w(使用Windows子系统执行.当程序启动时侯不会打开命令行) 参数会出现错误,出现了我不太懂的一个名词bootloader,报错如下:
3、在我用pyinstaller将代码打包为exe文件时,添加-i(修改exe默认图片)参数,图片十分模糊,且在打包好的文件夹中是正常的,移动到桌面(或者其他目录下)exe图标还是默认图标,十分疑惑。
4、我希望可以监测鼠标的点击位置(我希望是一个图片),但是我在pygame中发现只能(实时监测鼠标位置+鼠标点击事件)同时使用,以到达目的,但效果不好(因为要涉及到整个图片),我不太会操作。
一、写在前面
身为非计算机专业的大一小白,自知代码风格不佳,函数封装冗余,但会努力学习,如果你看我的代码觉得混乱,我十分抱歉。
1、出于Python课程大作业原因,借此机会,完成了我一直想写一个游戏的想法。
2、代码运用到了 numpy,random,pygame,sys库
import numpy as np
import random
import pygame
import sys
二、代码思路与分析
上一张粗略的思维导图
1、初始化部分,就是简单的生成两个4*4矩阵,record1记录上一步操作(希望添加返回上一步的功能,但设计不够完善,有一点小bug,后面会提到),record2记录当前操作,以0表示没有数字。用random.randint(0,9)来生成随机数,用以表示刷新2还是4(比例我设置为9:1)。然后将该数填入矩阵内随机位置即可,这里依然采用randint方式。
2、当发生移动时(上下左右),矩阵应该如何变化?
以向上移动为例说明。首先,排列数字(即不考虑合并),按顺序遍历矩阵,当出现数字时,放在该列最上面,此处顺序指的是:向上移动就是由小到大遍历,向下就是由大到小遍历,核心思想就是,从移动方向开始,寻找数字,并记录次数。排列完成后开始,合并相同的数字(如:4+4 = 8)这里只合并一次(做了适当简化,正常可玩一下2048,感受一下),合并过程为,遍历格子,向四周寻找相同的数字,当找到后,向移动方向合并,并把后续数字想该方向推进,然后跳出循环(这里对后续数字的判断有些冗余,我认为可以更简单,这里先不修改了),其余方向思想相同。
3、刷新矩阵,即产生一个新的数字(2或4)填入矩阵为0部分,因为此处希望可以满足按其他键(非方向键)不刷新的要求,先判断矩阵是否相同,
if not np.array_equal(record2, record1):
如果相同,则不进行刷新。此处刷新大体与初始化的刷新相同,其中一步变化就是需要判断矩阵元素是否为0,这里没有采用遍历方式,具体操作如下:
N = random.choice(np.where(record2.reshape(1, 16) == 0)[1])
Next_cell1_location_raw = N // 4
Next_cell1_location_clo = N
while Next_cell1_location_clo > 3:
Next_cell1_location_clo = Next_cell1_location_clo - 4
record2[Next_cell1_location_raw][Next_cell1_location_clo] = Next_cell1
5、判断游戏结束,当矩阵没有元素为0时,遍历每个格子,向四周寻找相同数字,这里与合并判断一样,不懂赘述。(我写的确实是复杂了/菜)
6、最后一步就是运用pygame的界面设计,具体设计不讲,只说pygame中一些函数的运用。
pygame.init()#初始化各模块部件
size = width, height = 500, 600
screen = pygame.display.set_mode(size,flags = pygame.RESIZEABLE )#设置界面大小,可调模式(屏幕大小可调RESIZEABLE,屏幕没有边框NOFRAME,屏幕全屏显示FULLSCREEN)
icon1 = pygame.image.load('2048.bmp')#加载图片
pygame.display.set_caption('2048小游戏')#更改标题
screen.fill((238 ,220 ,130))#屏幕背景色
map_font = pygame.font.Font('msyh.ttc', 40)#生成一个字体对象,这里是微软雅黑
font_surf1 = map_font_start.render('2048 小游戏', True, (105 ,112 ,225))#写文字
font_rect1 = font_surf1.get_rect()#生成rect(相当于一个矩阵框住文字形成图片,方便移动)
font_rect1.center = (100,20)#文本位置设置,这里采用center居中操作
screen.blit(font_surf1, font_rect1)#显示文字,对所有文字,图片,模块的设计都要执行这一步
screen.blit(block_scord, (350, 80))#这是对一个模块的设计
for i in range(4):
for j in range(4):
if record2[i][j] == 0:
font_surf = map_font.render('', True, (0, 0, 0))
else:
font_surf = map_font.render(str(record2[i][j]), True, (0, 0, 0))
font_rect = font_surf.get_rect()
font_rect.center = (80 + 110 * j, 190 + 110 * i)
block = pygame.Surface((100, 100))
block.fill(eval(color[i][j]))
screen.blit(block, (30 + 110 * j, 140 + 110 * i))
screen.blit(font_surf, font_rect)
'''
这里是循环生成16个模块分别填充上面矩阵record2的数字
'''
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()#这里是事件判断,包括按键事件,鼠标事件等所有事件均采取这种方式(从事件队列中读取)读入。
pygame.mixer.music.load(flag)#读入音乐
pygame.mixer.music.play()#播放音乐 pygame.mixer.music.set_volume(sound)#调节音乐音量
下面是判断矩阵不刷新条件,error是为了区分方向键,否则就会出现无法返回上一步(当按下这些键时)
if not np.array_equal(record2,record1) and error == 0:
Flash()
6、我还加了一步最好成绩记录,用txt保存,这一步应该没有难度,但open中的参数(r w w+ r+ a a+)很有研究的意义,可以查找资料看看。
三、代码总览
import numpy as np
import random
import pygame
import sys
#初始化游戏,建立两个矩阵,record1记录上一步,record2实时变化。
#random随机刷新新的格子
def New_Game():
global record1, record2
Gameover = False
record1 = np.array(
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).reshape(4, 4)
record2 = np.array(
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]).reshape(4, 4)
if random.randint(0, 10) in range(9):
rand = 1
else:
rand = 2
First_cell = 2 ** rand
First_cell_location_raw, First_cell_location_clo = random.randint(0, 3), random.randint(0, 3)
record2[First_cell_location_clo, First_cell_location_raw] = First_cell
#每次刷新一个格子
def Flash():
if not np.array_equal(record2, record1):
try:
if random.randint(0, 10) in range(9):
rand = 1
else:
rand = 2
Next_cell1 = 2 ** rand
N = random.choice(np.where(record2.reshape(1, 16) == 0)[1])
Next_cell1_location_raw = N // 4
Next_cell1_location_clo = N
while Next_cell1_location_clo > 3:
Next_cell1_location_clo = Next_cell1_location_clo - 4
record2[Next_cell1_location_raw][Next_cell1_location_clo] = Next_cell1
except:
pass
#如果16个格子被占满,此时如果一个格子向四周拓展均没有相同的则判断此时游戏结束
def Gameover():
global gameover
gameover = 0
_ = 0
if len(record2[record2 == 0]) == 0:
for raw in range(4):
for clo in range(4):
if raw == 0:
if record2[raw][clo] == record2[raw + 1][clo]:
_ = 1
break
elif raw == 3:
if record2[raw][clo] == record2[raw - 1][clo]:
_ = 1
break
else:
if record2[raw][clo] == record2[raw + 1][clo] or record2[raw][clo] == record2[raw - 1][clo]:
_ = 1
break
if clo == 0:
if record2[raw][clo] == record2[raw][clo + 1]:
_ = 1
break
elif clo == 3:
if record2[raw][clo] == record2[raw][clo - 1]:
_ = 1
break
else:
if record2[raw][clo] == record2[raw][clo + 1] or record2[raw][clo] == record2[raw][clo - 1]:
_ = 1
break
if _ == 1:
break
else:
gameover = 1
#以下四个函数为向指定方向移动的变化,包括相同格子的合并,不同格子的移动
def point_up():
for clo in range(4):
count = 0
for raw in range(4):
if record2[raw][clo] != 0:
record2[count][clo] = record2[raw][clo]
if count != raw:
record2[raw][clo] = 0
count += 1
for raw in range(3):
if record2[raw][clo] == record2[raw + 1][clo] and record2[raw][clo] != 0:
record2[raw][clo] = record2[raw][clo] * 2
if raw == 2:
lenth = 0
elif raw == 1:
lenth = 1
else:
lenth = 2
for i in range(lenth):
record2[raw + i + 1][clo] = record2[raw + i + 2][clo]
record2[3][clo] = 0
break
def point_left():
for raw in range(4):
count = 0
for clo in range(4):
if record2[raw][clo] != 0:
record2[raw][count] = record2[raw][clo]
if count != clo:
record2[raw][clo] = 0
count += 1
for clo in range(3):
if record2[raw][clo] == record2[raw][clo + 1] and record2[raw][clo] != 0:
record2[raw][clo] = record2[raw][clo] * 2
if clo == 2:
lenth = 0
elif clo == 1:
lenth = 1
else:
lenth = 2
for i in range(lenth):
record2[raw][clo + i + 1] = record2[raw][clo + i + 2]
record2[raw][3] = 0
break
def point_down():
for clo in range(4):
count = 0
for raw in list(range(4))[::-1]:
if record2[raw][clo] != 0:
record2[3 - count][clo] = record2[raw][clo]
if 3 - count != raw:
record2[raw][clo] = 0
count += 1
for raw in list(range(1, 4))[::-1]:
if record2[raw][clo] == record2[raw - 1][clo] and record2[raw][clo] != 0:
record2[raw][clo] = record2[raw][clo] * 2
if raw == 1:
lenth = 0
elif raw == 2:
lenth = 1
else:
lenth = 2
for i in range(lenth):
record2[raw - i - 1][clo] = record2[raw - i - 2][clo]
record2[0][clo] = 0
break
def point_right():
for raw in range(4):
count = 0
for clo in list(range(4))[::-1]:
if record2[raw][clo] != 0:
record2[raw][3 - count] = record2[raw][clo]
if 3 - count != clo:
record2[raw][clo] = 0
count += 1
for clo in list(range(1, 4))[::-1]:
if record2[raw][clo] == record2[raw][clo - 1] and record2[raw][clo] != 0:
record2[raw][clo] = record2[raw][clo] * 2
if clo == 1:
lenth = 0
elif clo == 2:
lenth = 1
else:
lenth = 2
for i in range(lenth):
record2[raw][clo - i - 1] = record2[raw][clo - i - 2]
record2[raw][0] = 0
break
#利用pygame设计游戏画面
def gameframe():
global map_font ,screen
pygame.init()
icon1 = pygame.image.load('2048.bmp')
size = width, height = 500, 600
screen = pygame.display.set_mode(size)
pygame.display.set_caption('2048小游戏')
screen.fill((238 ,220 ,130))
map_font = pygame.font.Font('msyh.ttc', 40)
map_font_start = pygame.font.Font('msyh.ttc', 20)
font_surf1 = map_font_start.render('2048 小游戏', True, (105 ,112 ,225))
font_rect1 = font_surf1.get_rect()
font_rect1.center = (100,20)
font_surf2 = map_font_start.render('***祝您游戏愉快***', True, (150, 0, 221))
font_rect2 = font_surf2.get_rect()
font_rect2.center = (100,60)
font_surf3 = map_font_start.render('白云苍狗 制作', True, (152, 52, 12))
font_rect3 = font_surf3.get_rect()
font_rect3.center = (100,100)
font_surf4 = map_font_start.render('历史最高:{}'.format(eval(maxscord)), True, (0, 0, 0))
font_rect4 = font_surf3.get_rect()
font_rect4.center = (395,50)
screen.blit(font_surf1, font_rect1)
screen.blit(font_surf2, font_rect2)
screen.blit(font_surf3, font_rect3)
screen.blit(font_surf4, font_rect4)
# fps = 30
# flock = pygame.time.Clock()
#动态刷新16个格子的值
def show_config():
color = np.array(['(255 ,246 ,143)','(255 ,226 ,133)','(255 ,216 ,163)','(255 ,216 ,143)','(255 ,236 ,123)','(255 ,246 ,123)','(255 ,255 ,123)','(255 ,255 ,143)','(245 ,246 ,133)','(255 ,216 ,173)','(255 ,226 ,173)','(255 ,226 ,143)','(255 ,246 ,173)','(255 ,246 ,103)',"(255 ,206 ,143)",'(245 ,226 ,163)']).reshape(4,4)
for i in range(4):
for j in range(4):
if record2[i][j] == 0:
font_surf = map_font.render('', True, (0, 0, 0))
else:
font_surf = map_font.render(str(record2[i][j]), True, (0, 0, 0))
font_rect = font_surf.get_rect()
font_rect.center = (80 + 110 * j, 190 + 110 * i)
block = pygame.Surface((100, 100))
block.fill(eval(color[i][j]))
screen.blit(block, (30 + 110 * j, 140 + 110 * i))
screen.blit(font_surf, font_rect)
map_font_scord = pygame.font.Font('msyh.ttc', 20)
font_surf4 = map_font_scord.render('总分:{:<}分'.format(np.sum(record2)), True, (0, 0, 0))
font_rect4 = font_surf4.get_rect()
font_rect4.center = (400,95)
block_scord = pygame.Surface((150, 35))
block_scord.fill((238 ,220 ,130))
screen.blit(block_scord, (350, 80))
screen.blit(font_surf4, font_rect4)
#pygame 主循环,键盘事件的监测
def main():
global record1 ,record2,maxscord
with open('最好成绩.txt', 'r') as f:
maxscord = f.read()
gameframe()
New_Game()
path1 = '古筝.wav'
path2 = 'River Fflows In You.wav'
flag = path2
sound = 0.5
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif pygame.mixer.music.get_busy()==False:
if flag == path1:
flag = path2
else:
flag = path1
pygame.mixer.music.load(flag)
pygame.mixer.music.play()
elif event.type == pygame.KEYUP:
error = 0
if event.key == pygame.K_LEFT:
record1 = record2 * 1
point_left()
elif event.key == pygame.K_RIGHT:
record1 = record2 * 1
point_right()
elif event.key == pygame.K_UP:
record1 = record2 * 1
point_up()
elif event.key == pygame.K_DOWN:
record1 = record2 * 1
point_down()
elif event.key == pygame.K_BACKSPACE:
record2 = record1
elif event.key == pygame.K_u:
sound = sound + 0.05
pygame.mixer.music.set_volume(sound)
error = 1
elif event.key == pygame.K_d:
sound = sound - 0.05
pygame.mixer.music.set_volume(sound)
error = 1
else:
error = 1
if not np.array_equal(record2,record1) and error == 0:
Flash()
show_config()
#flock.tick(fps)
Gameover()
if gameover == 1:
block_over = pygame.Surface((400,150))
block_over.fill((238 ,220 ,130))
map_font_over = pygame.font.Font('msyh.ttc', 60)
font_surf = map_font_over.render('GAME OVER', True, (0, 0, 0))
font_rect = font_surf.get_rect()
font_rect.center = (250, 350)
screen.blit(block_over, (50,275))
screen.blit(font_surf, font_rect)
with open('最好成绩.txt', 'w') as f:
f.write(str(np.sum(record2)))
if np.sum(record2) > eval(maxscord):
with open('最好成绩.txt', 'w') as f:
f.write(str(np.sum(record2)))
pygame.display.update()
pygame.display.update()
if __name__ == "__main__":
main()
四、写代码过程中遇到的坑汇总
1、numpy提供的array不能直接判断是否相等,需要用到函数array_equal(array1,array2)
2、pygame的界面刷新实行的是叠加,而不是覆盖,且其中存在先后顺序。
3、pygame音乐模块,如果希望打包exe,只能使用wav文件,否则报错。
4、这是一个推测,如果有大佬懂,欢迎指正。在函数中global一个array,然后给这个array赋值为另一个array时,如果这样写:array1 = array2 那么接下来array1 与array2将会保持一致,也就是说array2变化后,执行另一个函数时,array1仍是变化后的array2,而不是我想要的之前保存的array1,我是这么做的,array1 = array2 * 1(感觉有点傻,具体原理我不太懂,隐约感觉是global的原因)
五、收获总结
通过这次2048的制作,我不仅了解到pygame的使用(第一次接触),同样对较长代码(对我来说)有了一定的编写经验,花了两个晚上做的感觉比较满意,对现阶段我的水平,确实发挥出来了,过程也比较顺利。再加上第一次写csdn,总结下来同样有一些收获。