2020.7.23
- 参考:用Python和Pygame写游戏-从入门到精通(1)Python 游戏:扫雷用pygame.image.load加载时像素化和不完整的图像Pygame详解(四):event 模块参考游戏下载:Windows 7 Games for Windows 10 and 8图片素材 剩下7.8我用ps按样式画了差不多的。虽然所有图标组合起来后我被自己的界面丑到了- -…但是无所谓了主要想练习怎么实现其它推荐:扫雷网
- 运行环境
Win10,anaconda+python 3.8.3 - 代码
# -*- coding: utf-8 -*-
from enum import Enum
import random
import pygame
from pygame.locals import *
from sys import exit
import datetime
import time
class STATE(Enum):
NORMAL = 1 # 未点击
NUMBERED = 2# 有数字(周围有雷)
BLANK = 3 # 空白的(周围无雷)
FLAG = 4 # 标记为地雷
QUESTION = 5# 标记为问号
class GameMinesweeper(object):
def __init__(self):
self.BLOCK_SIZE = 20
self.ROW, self.COL = 16, 30
self.NUM_MINE = 99
self.num_opened = 0
self.block_state = dict()
self.has_mine = dict()
for i in range(self.ROW):
for j in range(self.COL):
self.block_state[(i, j)] = STATE.NORMAL
self.has_mine[(i, j)] = False
pos_list = list(range(self.ROW * self.COL))
random.shuffle(pos_list)
for pos in pos_list[0:self.NUM_MINE]:
i, j = pos // self.COL, pos % self.COL
self.has_mine[(i, j)] = True
def CalNumMine(self, screen, x, y, i, j): # 计算周围8个格中的雷数
sum_mine = 0
need_check = []
DI, DJ = [-1, -1, -1, 0, 0, 1, 1, 1], [-1, 0, 1, -1, 1, -1, 0, 1]
for k in range(8):
newi, newj = i + DI[k], j + DJ[k]
if newi >= 0 and newi < self.ROW and newj >= 0 and newj < self.COL:
if self.has_mine[(newi, newj)]:
sum_mine += 1
if self.block_state[(newi, newj)] == STATE.NORMAL:
need_check.append((newi, newj))
self.num_opened += 1
if sum_mine:
self.block_state[(i, j)] = STATE.NUMBERED
screen.blit(self.img_block, (x, y))
exec("screen.blit(self.img{}, (x, y))".format(sum_mine))
else:
self.block_state[(i, j)] = STATE.BLANK
screen.blit(self.img_block_hover, (x, y))
for newi, newj in need_check:
newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
if self.block_state[(newi, newj)] == STATE.NORMAL: # 因为是递归调用,所以不一定此时此刻在need_check里的仍然是NORMAL,可能在之前的调用中被打开了
self.CalNumMine(screen, newx, newy, newi, newj)
def HandleDoubleClick(self, screen, x, y, i, j):
num_flag = 0
num_mine = 0
block_mine = []
block_unopened = []
DI, DJ = [-1, -1, -1, 0, 0, 1, 1, 1], [-1, 0, 1, -1, 1, -1, 0, 1]
for k in range(8):
newi, newj = i + DI[k], j + DJ[k]
if newi >= 0 and newi < self.ROW and newj >= 0 and newj < self.COL:
if self.block_state[(newi, newj)] == STATE.FLAG:
num_flag += 1
if self.has_mine[(newi, newj)]:
num_mine += 1
block_mine.append((newi, newj))
if self.block_state[(newi, newj)] == STATE.NORMAL:
block_unopened.append((newi, newj))
if num_flag == num_mine:
for newi, newj in block_mine:
if self.block_state[(newi, newj)] != STATE.FLAG: # 玩家标错,游戏结束
newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
screen.blit(self.img_bomb, (newx, newy))
pygame.display.update()
return True
for newi, newj in block_unopened: # 打开周围未打开的格子
newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
self.CalNumMine(screen, newx, newy, newi, newj)
else: # 玩家标多或者标少了,显示周围未打开的格子(因为pygame不能移除blit后的图像,所以暂时想不出更好的提示方法)
for newi, newj in block_unopened:
newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
screen.blit(self.img_block_hover, (newx, newy))
pygame.display.update()
time.sleep(0.2)
for newi, newj in block_unopened:
newx, newy = newj * self.BLOCK_SIZE, newi * self.BLOCK_SIZE
screen.blit(self.img_block, (newx, newy))
return False
def HandleLeftClick(self, screen, x, y, i, j):
if self.has_mine[(i, j)]:
screen.blit(self.img_bomb, (x, y))
pygame.display.update()
return True
elif self.block_state[(i, j)] == STATE.NORMAL or self.block_state[(i, j)] == STATE.QUESTION:
self.CalNumMine(screen, x, y, i, j)
pygame.display.update()
return False
def Play(self):
# 界面初始化
screen = pygame.display.set_mode((self.COL * self.BLOCK_SIZE, (self.ROW + 2) * self.BLOCK_SIZE))
pygame.display.set_caption("扫雷")
font = pygame.font.SysFont("timesroman", 35) #第一个参数是字体,第二个参数是大小。可以用pygame.font.get_fonts()获取可用的所有字体
font_file = font.render("0", False, (255, 0, 0))
screen.blit(font_file, (self.COL // 4 * self.BLOCK_SIZE, self.ROW * self.BLOCK_SIZE)) # 显示时间
font_file = font.render(str(self.NUM_MINE), False, (255, 0, 0))
screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, self.ROW * self.BLOCK_SIZE)) # 显示已标记地雷数
for i in range(1, 9):
exec("self.img{} = pygame.image.load('pictures/{}.png').convert_alpha()".format(i, i))
exec("self.img{} = pygame.transform.smoothscale(self.img{}, (self.BLOCK_SIZE // 4 * 3, self.BLOCK_SIZE // 4 * 3))".format(i, i))
for i in ["block", "block_hover", "flag", "question", "bomb", "restart"]:
exec("self.img_{} = pygame.image.load('pictures/{}.png').convert_alpha()".format(i, i))
exec("self.img_{} = pygame.transform.smoothscale(self.img_{}, (self.BLOCK_SIZE, self.BLOCK_SIZE))".format(i, i))
for i in range(self.ROW):
for j in range(self.COL):
screen.blit(self.img_block, (j * self.BLOCK_SIZE, i * self.BLOCK_SIZE)) # blit参数
screen.blit(self.img_restart, (self.COL // 2 * self.BLOCK_SIZE, int((self.ROW + 0.5) * self.BLOCK_SIZE)))
pygame.display.update()
# 游戏主循环
is_start = False
starttime = 0
nowtime = "0"
num_flag = 0
prex, prey, prei, prej = 0, 0, 0, 0
while True:
event = pygame.event.poll()
if event.type == QUIT:
return False
# 显示经过的时间
if is_start:
endtime = datetime.datetime.now()
font_file = font.render(nowtime, False, (0, 0, 0)) #渲染成一个surface对象。第一个是显示的内容,第二个是否消除锯齿,第三个是RGB颜色
screen.blit(font_file, (self.COL // 4 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
nowtime = str((endtime - starttime).seconds)
font_file = font.render(nowtime, False, (255, 0, 0))
screen.blit(font_file, (self.COL // 4 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
pygame.display.update()
x, y = pygame.mouse.get_pos()
i, j = y // self.BLOCK_SIZE, x // self.BLOCK_SIZE
x, y = j * self.BLOCK_SIZE, i * self.BLOCK_SIZE # 调整为所在块左上角的坐标
# 展示鼠标在每一个块上悬停的效果
if prex != x or prey != y:
if i >= 0 and i < self.ROW and j >= 0 and j < self.COL and self.block_state[(i, j)] == STATE.NORMAL:
screen.blit(self.img_block_hover, (x, y))
if prei >= 0 and prei < self.ROW and prej >= 0 and prej < self.COL and self.block_state[(prei, prej)] == STATE.NORMAL:
screen.blit(self.img_block, (prex, prey))
prex, prey, prei, prej = x, y, i, j
pygame.display.update()
# 如果有鼠标点击事件
if event.type == MOUSEBUTTONDOWN:
if event.button == 1: # 左键按下
# 玩家选择重新开始
if event.pos[0] >= self.COL // 2 * self.BLOCK_SIZE and event.pos[0] < self.COL // 2 * self.BLOCK_SIZE + self.BLOCK_SIZE and \
event.pos[1] >= (self.ROW + 0.5) * self.BLOCK_SIZE and event.pos[1] < int((self.ROW + 0.5) * self.BLOCK_SIZE) + self.BLOCK_SIZE:
return True
# 玩家单击某个块
elif i >= 0 and i < self.ROW and j >= 0 and j < self.COL:
if not is_start: # 点击某个块后游戏才开始计时
is_start = True
starttime = datetime.datetime.now()
# 玩家单击某个已标数字的块
if self.block_state[(i,j)] == STATE.NUMBERED:
time.sleep(0.3)
if pygame.event.peek(MOUSEBUTTONDOWN): # 玩家双击某个已标数字的块
if self.HandleDoubleClick(screen, x, y, i, j): # 玩家标雷错误则返回真
break
# 玩家单击某个未标数字的块
elif self.HandleLeftClick(screen, x, y, i, j): # 玩家踩雷则返回真
break
if self.num_opened == self.ROW * self.COL - self.NUM_MINE: # 获胜判断
break
elif event.button == 3: # 右键按下
if i >= 0 and i < self.ROW and j >= 0 and j < self.COL:
# 玩家单击某个已打开的块
if self.block_state[(i,j)] == STATE.NUMBERED:
while not pygame.event.peek(MOUSEBUTTONUP): # 在右键放开之前
if pygame.event.peek(MOUSEBUTTONDOWN):
next_event = pygame.event.get(MOUSEBUTTONDOWN)
if next_event[0].button == 1: # 玩家同时对某个已标数字的块按下左右键
if self.HandleDoubleClick(screen, x, y, i, j): # 玩家标雷错误则返回真
break
if self.num_opened == self.ROW * self.COL - self.NUM_MINE: # 获胜判断
break
# 玩家单击某个未打开的块
elif self.block_state[(i, j)] == STATE.NORMAL: # 玩家想标旗子
font_file = font.render(str(self.NUM_MINE - num_flag), False, (0, 0, 0))
screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
num_flag += 1
font_file = font.render(str(self.NUM_MINE - num_flag), False, (255, 0, 0))
screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
self.block_state[(i, j)] = STATE.FLAG
screen.blit(self.img_block, (x, y))
screen.blit(self.img_flag, (x, y))
elif self.block_state[(i, j)] == STATE.FLAG: # 玩家想标问号
font_file = font.render(str(self.NUM_MINE - num_flag), False, (0, 0, 0))
screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
num_flag -= 1
font_file = font.render(str(self.NUM_MINE - num_flag), False, (255, 0, 0))
screen.blit(font_file, (self.COL // 4 * 3 * self.BLOCK_SIZE, (self.ROW) * self.BLOCK_SIZE))
self.block_state[(i, j)] = STATE.QUESTION
screen.blit(self.img_block, (x, y))
screen.blit(self.img_question, (x, y))
elif self.block_state[(i, j)] == STATE.QUESTION: # 玩家取消标记
self.block_state[(i, j)] = STATE.NORMAL
screen.blit(self.img_block, (x, y))
pygame.display.update()
if self.num_opened == self.ROW * self.COL - self.NUM_MINE: # 获胜判断
font_file = font.render("! WIN !", False, (255, 0, 0))
screen.blit(font_file, ((self.COL - 3) // 2 * self.BLOCK_SIZE, (self.ROW - 1) // 2 * self.BLOCK_SIZE))
pygame.display.update()
while True: # 玩家获胜或者失败后要么退出,要么重新开始
event = pygame.event.wait()
if event.type == QUIT:
return False
if event.type == MOUSEBUTTONDOWN and event.button == 1 and \
event.pos[0] >= self.COL // 2 * self.BLOCK_SIZE and event.pos[0] < self.COL // 2 * self.BLOCK_SIZE + self.BLOCK_SIZE and \
event.pos[1] >= (self.ROW + 0.5) * self.BLOCK_SIZE and event.pos[1] < (self.ROW + 0.5) * self.BLOCK_SIZE + self.BLOCK_SIZE:
return True
pygame.init()
while True:
game = GameMinesweeper()
if not game.Play(): # 重新开始返回真,退出返回假
break