python实现扫雷小游戏
- 扫雷
-
- 准备工作
-
- 设计原理
-
- 实施步骤
-
-
- 1.定义一个类用来表示方块的状态
-
-
- -2.再定义一个类用来获取坐标,周边地雷的数量,方块的状态
-
- -3.随机生成地雷,递归打开周边无雷的方块
-
- -4.主函数:窗口绘制,图形加载,事件处理
- -游戏效果
扫雷
新人博主,学习python一个多月,请大家多多关照,不足之处,敬请雅正。
准备工作
下载制作扫雷游戏所需要的一些图片,如数字0-8,雷等。 链接: link.
设计原理
定义一个类block_condition用来判断方块的状态。再用个类Mine返回方块的性质:坐标,是否为雷,周边雷的数量。类mine_block用来埋地雷和点击的效果,点到雷,返回False,没点到雷就点开这个方块,如果点击的方块周边无雷,则向周围递归打开,可以实现一点就是一片的效果。判断游戏胜利的标志为:点开的方块加地雷数等于总数,失败的标志为:踩到地雷。
实施步骤
1.定义一个类用来表示方块的状态
代码如下
class Block_Condition():
unclicked = 1 # 未点击
clicked = 2 # 已点击
mine = 3 # 地雷
flag = 4 # 标记为地雷
ask = 5 # 标记为问号
bomb = 6 # 踩中地雷
2.再定义一个类用来获取坐标,周边地雷的数量,方块的状态
代码如下
from block_condition import *
class Mine:
def __init__(self, x, y, value=0):
self._x = x
self._y = y
self._value = 0
self._surround_count = -1 #周边雷的数量
self._condition = Block_Condition.unclicked #初始设置为未点击
self.set_value(value)
def get_x(self):
return self._x #直接传入
def set_x(self, x):
self._x = x #输入
x = property(fget=get_x, fset=set_x)
def get_y(self):
return self._y
def set_y(self, y):
self._y = y
y = property(fget=get_y, fset=set_y)
def get_value(self):
return self._value
def set_value(self, value):
if value:
self._value = 1 #是地雷
else:
self._value = 0 #不是雷
value = property(fget=get_value, fset=set_value)
def get_surround_count(self): #获取周边雷的数量
return self._surround_count
def set_surround_count(self, surround_count):
self._surround_count = surround_count
surround_count = property(fget=get_surround_count, fset=set_surround_count)
def get_condition(self): #方块的状态
return self._condition
def set_condition(self, value):
self._condition = value
condition = property(fget=get_condition, fset=set_condition)
3.随机生成地雷,递归打开周边无雷的方块
要想实现扫雷,每个方块都得是一个类,包含其坐标,自身的状态(是否是雷或是周边有多少地雷),所以需要定义一个二维数组,每个元素都是Mine类。而要想实现一点开就是一片的效果,则需要函数返回某个方块周边的方块,向周边打开(只打开周边没有地雷的方块),形成一点就是一片的效果。游戏可以从代码里调整方块和地雷的数量来改变难度。
import random
import time
from Mine import *
from block_condition import *
BLOCK_WIDTH=30
BLOCK_HEIGHT=20
MINE_COUNT=99 #地雷数量
list_mine=[[]]
list_mine=[[Mine(i,j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]
def get_around(x, y):
#返回(x, y)周围一圈的点
return [(i, j) for i in range(max(0, x - 1), min(BLOCK_WIDTH - 1, x + 1) + 1) #range是左闭右开
for j in range(max(0, y - 1), min(BLOCK_HEIGHT - 1, y + 1) + 1) ]
class Mine_Block:
def __init__(self):
self._block = list_mine
# 埋下地雷的种子
for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):
self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1
def getblock(self, x, y):
return self._block[y][x]
def open_block(self, x, y):
# 真不幸,这是颗地雷
if self._block[y][x].value:
self._block[y][x].condition = Block_Condition.bomb
return False
self._block[y][x].condition = Block_Condition.clicked #没踩雷
around = get_around(x, y)
sum1 = 0
for i, j in around:
if self._block[j][i].value:
sum1 += 1
self._block[y][x].surround_count = sum1
if sum1 == 0: #周边无雷,向旁边递归,一点打开一片
for i, j in around:
if self._block[j][i].surround_count == -1:
self.open_block(i, j)
return True
4.主函数:窗口绘制,图形加载,事件处理
先做一些准备工作,引入一些库,其中sys库用sys.exit()来结束程序,time库则是用来加载时间,pygame库用来绘制窗口,加载图片,处理事件等,是我们这次扫雷游戏的核心库函数。
import sys
import time
import pygame
from pygame.locals import *
from mine_block import *
from Mine import *
import block_condition
BLOCK_WIDTH = 30 #横向30块
BLOCK_HEIGHT = 20 #纵向20块
SIZE=30 #一个小方块的边长
SCREEN_WIDTH = BLOCK_WIDTH * SIZE # 游戏屏幕的宽
SCREEN_HEIGHT = (BLOCK_HEIGHT + 2) * SIZE # 游戏屏幕的高
ready=0
start=1
win=2
lose=3
game_dict=[ready, start, win, lose]#四个标志用来判断游戏的进行与否
#ready:准备就绪 start:游戏开始 win:胜利 lose:失败
下面是主函数,设置屏幕标题,加载图片(加载图片都是同样的代码,此处我就放一个)其他图片以同样的方式加载就行
def main():
pygame.init()
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) #设置窗口大小
pygame.display.set_caption('欢迎来到艾欧尼亚->扫雷') #设置标题
png0 = pygame.image.load('./number_mine/0.png').convert() #png支持高级别无损耗压缩
png0 = pygame.transform.smoothscale(png0, (SIZE, SIZE)) #将曲面缩放到指定大小,使得图片一样大
下面是加载笑脸,笑脸用来判断游戏的输赢状态,点击笑脸可以重新开始游戏
face_size = int(SIZE * 1.5)#设置笑脸的大小
png_face_lose = pygame.image.load('./face/3.png').convert()
png_face_lose = pygame.transform.smoothscale(png_face_lose, (face_size, face_size))
png_face_normal = pygame.image.load('./face/1.png').convert()
png_face_normal = pygame.transform.smoothscale(png_face_normal, (face_size, face_size))
png_face_win = pygame.image.load('./face/4.png').convert()
png_face_win = pygame.transform.smoothscale(png_face_win, (face_size, face_size))
face_pos_x = (SCREEN_WIDTH - face_size) // 2
face_pos_y = (SIZE * 2 - face_size) // 2 #笑脸的坐标
此处用一个字典对应雷的数量以及相应的图片
png_dict = {0: png0, 1: png1, 2: png2, 3: png3, 4: png4, 5: png5, 6: png6, 7: png7, 8: png8} #依据雷的数目加载相应图片
bgcolor = (240, 200, 200) #背景色
block = Mine_Block()
game_dict1=game_dict[0] #准备就绪
start_time = None #开始时间
used_time=0 #声明变量
下面是事件处理函数,包含处理鼠标点击,点击笑脸重新开始游戏,点击方块则开始游戏,鼠标移动则用图片显示鼠标的位置,话不多说,上代码。
while True:
screen.fill(bgcolor) #填充背景色
for event in pygame.event.get(): #获取操作
if event.type == QUIT: #点击右上角的'×'
sys.exit() #退出程序
elif event.type == MOUSEBUTTONDOWN:#鼠标按下
mouse_x, mouse_y = event.pos #获取鼠标位置
x = mouse_x // SIZE
y = mouse_y // SIZE - 2
b1, b2, b3 = pygame.mouse.get_pressed()#鼠标点击的位置,b1对应左键,b3对应右键
elif event.type == MOUSEBUTTONUP: #松开鼠标
if y < 0: #点击方块上方
if face_pos_x <= mouse_x <= face_pos_x + face_size and face_pos_y <= mouse_y <= face_pos_y + face_size: #判断是否点击笑脸
game_dict1=game_dict[0] #重置数据,开始新一局游戏
start_time = time.time()
used_time = 0
for i in list_mine:
for j in i:
pos = (j.x * SIZE, (j.y + 2) * SIZE)
j.surround_count = -1 #把周边雷的数目回归到原始状态
j.condition = Block_Condition.unclicked #所有方块回归为未点击状态
screen.blit(png_blank, pos)
pygame.display.flip()
for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), BLOCK_WIDTH * BLOCK_HEIGHT):
list_mine[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 0 #把上一轮雷的定义全部清除
block=Mine_Block() #重新定义雷
continue
else:
continue
if game_dict1 == game_dict[0]: #状态为准备就绪
game_dict1 = game_dict[1] #开始
start_time = time.time() #开始计时
if game_dict1 == game_dict[1]:
mine = block.getblock(x, y)
if b1 and not b3: #按鼠标左键
if mine.condition == Block_Condition.unclicked: #未点击的会点开
if not block.open_block(x, y): #中奖了啊,一颗雷
game_dict1 = game_dict[3] #游戏状态为失败
elif not b1 and b3: #按鼠标右键
if mine.condition == Block_Condition.unclicked: #如果未点击,则标记为旗帜(雷)
mine.condition = Block_Condition.flag
elif mine.condition == Block_Condition.flag: #如果之前的标记是旗帜,则改标记为问号
mine.condition = Block_Condition.ask
elif mine.condition == Block_Condition.ask: #如果之前的标记是问号,则改为未点击的状态
mine.condition = Block_Condition.unclicked
flag_count = 0 #旗帜为0
opened_count = 0 #点开的方块为0
for row in block._block:
for mine in row:
pos = (mine.x * SIZE, (mine.y + 2) * SIZE)
if mine.condition == Block_Condition.clicked: #点开了加载图片(数字),点开的方块加一
screen.blit(png_dict[mine.surround_count], pos)
opened_count += 1
elif mine.condition == Block_Condition.bomb: #状态是炸弹
screen.blit(png_blood, pos)
elif mine.condition == Block_Condition.flag: #是旗帜
screen.blit(png_flag, pos)
flag_count += 1
elif mine.condition == Block_Condition.ask: #是问号
screen.blit(png_ask, pos)
elif game_dict1 == game_dict[3] and mine.value:
screen.blit(png_mine, pos) #失败了显示所有地雷
elif mine.condition == Block_Condition.unclicked: #是未打开
screen.blit(png_blank, pos)
if event.type == MOUSEMOTION:#用特定的图像显示鼠标的位置
mouse_x, mouse_y = event.pos
x = mouse_x // SIZE
y = mouse_y // SIZE - 2
mine = block.getblock(x, y)
pos = (mine.x * SIZE, (mine.y + 2) * SIZE)
if y < 0:
continue
else:
if mine.condition == Block_Condition.unclicked:
screen.blit(png_dict[0],pos)
font1 = pygame.font.Font(None, SIZE * 2)
width, height = font1.size('100')
text1=font1.render('%d'%(MINE_COUNT-flag_count),True,(255,0,0))
screen.blit(text1,(30,(SIZE*2-height)//2-2)) #打印剩余雷的数量
if game_dict1 == game_dict[1]:
used_time = int(time.time() - start_time)
text2=font1.render('%d'%used_time,True,(255,0,0))
screen.blit(text2,(SCREEN_WIDTH-width-30,(SIZE*2-height)//2-2)) #打印已用时间
if opened_count + MINE_COUNT == BLOCK_WIDTH * BLOCK_HEIGHT: #点开方块的加上地雷数等于总数
game_dict1 = game_dict[2] #则视为胜利
if game_dict1 == game_dict[2]: #游戏的状态为胜利
screen.blit(png_face_win, (face_pos_x, face_pos_y))
elif game_dict1 == game_dict[3]: #游戏的状态为失败
screen.blit(png_face_lose, (face_pos_x, face_pos_y))
else:
screen.blit(png_face_normal, (face_pos_x, face_pos_y))
pygame.display.update()
if __name__ == '__main__':
main()
游戏效果
1.游戏开始画面
2.鼠标显示
3.游戏失败画面(有可能第一次点击就会点到雷哦)
4.游戏胜利画面
有兴趣的小伙伴可以自己写一写,欢迎前来交流~后续可能会添加新的功能,希望大家多多支持!
本文转自SDK社区:https://www.sdk.cn/