起初没看老师要求用Pygame写扫雷,后来发现要用tk写,就简单加了一个tk菜单,设置了三个级别难度,希望有所帮助
其中关于tk和pygame的下载仅对于VS
Ctrl+Alt+L打开解决方案资源管理器+右击Python环境+点击查看所有Python环境+下划线换成PYPI搜索pygame或tkinter,点击运行命令:pip install pygame就可以了
可参照下图:
运行截图:
源代码如下:
import sys
import random
import time
from tkinter import *
import tkinter as tk
import tkinter.messagebox
import pygame
SIZE = 20
def do_job():
s='左击方块为雷游戏结束,否则显示相邻8块地雷数,为空相邻无雷\n'+\
'右击方块标记红旗表示有雷,再右击变?,再右击无标记\n'+\
'方块显示为n,相邻8块有n块为雷'+\
'\n标记红旗排除所有雷即胜利。'
tkinter.messagebox.showinfo(title="帮助",message=s)
def create_random_board(row, col, mine_num):
"""
得到一个随机的棋盘
"""
# 随机布雷
nums = [{"opened": False, "opened_num": 0, 'closed_num': "空"} for _ in range(row * col - mine_num)] # 16x30-99 表示的是生成381个0
nums += [{"opened": False, "opened_num": "雷", 'closed_num': "空"} for _ in range(mine_num)] # 99颗地雷
random.shuffle(nums) # 乱序,此时nums是乱的
return [list(x) for x in zip(*[iter(nums)] * col)]
def click_block(x, y, board_list):
"""
检测点击的是哪个方格(即第x行,第y列)
"""
# 计算出点击的空格的行、列
for row, line in enumerate(board_list):
for col, _ in enumerate(line):
if col * SIZE <= x <= (col + 1) * SIZE and (row + 2) * SIZE <= y <= (row + 2 + 1) * SIZE:
print("点击的空格的位置是:", row, col)
return row, col
def left_click_block(row, col, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM):
"""
左击空格后的处理
"""
if board_list[row][col].get("opened") is False and board_list[row][col].get("opened_num") != "雷":
# 如果不是雷,那么就计算当前位置数字
mine_num = get_mine_num(row, col, board_list,BLOCK_ROW_NUM,BLOCK_COL_NUM)
print("地雷数:", mine_num)
board_list[row][col]["opened_num"] = mine_num
board_list[row][col]["opened"] = True # 标记为"打开"状态
board_list[row][col]["closed_num"] = "空" # 标记为"未打开时的状态为空格",防止显示剩余雷数错误
if mine_num == 0:
# 如果方格周边没有雷此时,判断是否有连续空位置
set_nums_blank(row, col, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM)
elif board_list[row][col].get("opened") is False and board_list[row][col].get("opened_num") == "雷":
board_list[row][col]["opened_num"] = "踩雷" # 标记为"踩雷"图片
board_list[row][col]["opened"] = True # 标记为"打开"状态
board_list[row][col]["closed_num"] = "空" # 标记为"未打开时的状态为空格",防止显示剩余雷数错误
return True
def right_click_block(row, col, board_list):
"""
右击方格后更新其状态(标记为雷、问号?、取消标记)
"""
if board_list[row][col].get("opened") is False:
if board_list[row][col]["closed_num"] == "空":
board_list[row][col]["closed_num"] = "雷标记"
# return 1 # 表示找到了一颗雷,需要+1
elif board_list[row][col]["closed_num"] == "雷标记":
board_list[row][col]["closed_num"] = "疑问标记"
# return -1 # 表示取消标记雷,需要-1
elif board_list[row][col]["closed_num"] == "疑问标记":
board_list[row][col]["closed_num"] = "空"
def get_mine_num(row, col, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM):
"""
计算点击的空格周围的雷的数量
"""
# 生成起始位置、终止位置
row_start = row - 1 if row - 1 >= 0 else row
row_stop = row + 2 if row + 1 <= BLOCK_ROW_NUM - 1 else row + 1
col_start = col - 1 if col - 1 >= 0 else col
col_stop = col + 2 if col + 1 <= BLOCK_COL_NUM - 1 else col + 1
# 循环遍历当前方格周围的雷的数量
mine_num = 0
for i in range(row_start, row_stop):
for j in range(col_start, col_stop):
if board_list[i][j].get("opened_num") == "雷":
mine_num += 1
return mine_num
def set_nums_blank(row, col, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM):
"""
判断当前位置的周边位置是否为空,如果是则继续判断,
最终能够实现点击一个空位置后连续的空位置都能够显示出来
"""
mine_num = get_mine_num(row, col, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM)
print("row=%d, col=%d, mine_num=%d" % (row, col, mine_num))
if mine_num == 0:
board_list[row][col]['opened'] = True
board_list[row][col]["opened_num"] = 0
board_list[row][col]["closed_num"] = "空"
# 判断对角是否是数字
for i, j in [(-1, -1), (1, 1), (1, -1), (-1, 1)]:
if 0 <= row + i <= BLOCK_ROW_NUM-1 and 0 <= col + j <= BLOCK_COL_NUM-1:
mine_num = get_mine_num(row + i, col + j, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM)
if mine_num:
board_list[row + i][col + j]['opened'] = True
board_list[row + i][col + j]["opened_num"] = mine_num
board_list[row + i][col + j]["closed_num"] = "空"
# 判断剩下4个位置是否是也是0,即空
for i, j in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
if 0 <= row + i <= BLOCK_ROW_NUM-1 and 0 <= col + j <= BLOCK_COL_NUM-1:
if not board_list[row + i][col + j].get("opened"):
set_nums_blank(row + i, col + j, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM)
else:
board_list[row][col]['opened'] = True
board_list[row][col]["opened_num"] = mine_num
board_list[row][col]["closed_num"] = "空"
def get_mine_flag_num(board_list):
"""
计算还剩多少颗雷
"""
num = 0
for line in board_list:
for num_dict in line:
if num_dict.get("closed_num") == "雷标记":
num += 1
return num
def open_all_mine(board_list):
"""
显示所有的雷
"""
for row, line in enumerate(board_list):
for col, num_dict in enumerate(line):
if num_dict.get("opened_num") == "雷":
num_dict["opened"] = True
def judge_win(board_list):
"""
判断是否获胜
只要有1个标记为地雷但实际不是地雷的方格,就表示未获胜,否则获胜
"""
for line in board_list:
for num_dict in line:
if num_dict.get("opened_num") == "雷" and num_dict.get("closed_num") != "雷标记":
return False
else:
return True
def run(MINE_COUNT,BLOCK_ROW_NUM,BLOCK_COL_NUM):
SCREEN_WIDTH, SCREEN_HEIGHT = BLOCK_COL_NUM * SIZE, (BLOCK_ROW_NUM + 2) * SIZE
a,b,c=MINE_COUNT,BLOCK_ROW_NUM,BLOCK_COL_NUM
pygame.init()
pygame.display.set_caption('扫雷')
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
bgcolor = (225, 225, 225) # 背景色
# 要显示的棋盘
board_list = create_random_board(BLOCK_ROW_NUM, BLOCK_COL_NUM, MINE_COUNT)
# 默认的方格图片
img_blank = pygame.image.load('resource/blank.bmp').convert()
img_blank = pygame.transform.smoothscale(img_blank, (SIZE, SIZE))
# "雷标记"图片
img_mine_flag = pygame.image.load('resource/flag.bmp').convert()
img_mine_flag = pygame.transform.smoothscale(img_mine_flag, (SIZE, SIZE))
# "雷"图片
img_mine = pygame.image.load('resource/mine.bmp').convert()
img_mine = pygame.transform.smoothscale(img_mine, (SIZE, SIZE))
# "疑问标记"图片
img_ask = pygame.image.load('resource/ask.bmp').convert()
img_ask = pygame.transform.smoothscale(img_ask, (SIZE, SIZE))
# "踩雷"图片
img_blood = pygame.image.load('resource/blood.bmp').convert()
img_blood = pygame.transform.smoothscale(img_blood, (SIZE, SIZE))
# "表情"图片
face_size = int(SIZE * 1.25)
img_face_fail = pygame.image.load('resource/face_fail.bmp').convert()
img_face_fail = pygame.transform.smoothscale(img_face_fail, (face_size, face_size))
img_face_normal = pygame.image.load('resource/face_normal.bmp').convert()
img_face_normal = pygame.transform.smoothscale(img_face_normal, (face_size, face_size))
img_face_success = pygame.image.load('resource/face_success.bmp').convert()
img_face_success = pygame.transform.smoothscale(img_face_success, (face_size, face_size))
# "表情"位置
face_pos_x = (SCREEN_WIDTH - face_size) // 2
face_pos_y = (SIZE * 2 - face_size) // 2
# "雷"的数量图片
img_dict = {
'雷标记': img_mine_flag,
'雷': img_mine,
'空': img_blank,
'疑问标记': img_ask,
'踩雷': img_blood,
}
for i in range(9):
img_temp = pygame.image.load('resource/{}.bmp'.format(i)).convert()
img_temp = pygame.transform.smoothscale(img_temp, (SIZE, SIZE))
img_dict[i] = img_temp
# 游戏状态
game_status = "normal"
# 显示雷的数量、耗时用到的资源
font = pygame.font.Font('resource/a.TTF', SIZE * 2) # 字体
f_width, f_height = font.size('999')
red = (200, 40, 40)
# 标记出雷的个数
flag_count = 0
# 记录耗时
elapsed_time = 0
last_time = time.time()
start_record_time = False
# 创建计时器(防止while循环过快,占用太多CPU的问题)
clock = pygame.time.Clock()
while True:
# 事件检测(鼠标点击、键盘按下等)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN and event.button:
b1, b2, b3 = pygame.mouse.get_pressed()
mouse_click_type = None
if b1 and not b2 and not b3: # 左击
mouse_click_type = "left"
elif not b1 and not b2 and b3: # 右击
mouse_click_type = "right"
print("点击了鼠标的[%s]键" % mouse_click_type)
x, y = pygame.mouse.get_pos()
if game_status == "normal" and 2 * SIZE <= y <= SCREEN_HEIGHT:
# 计算点击的是哪个空
position = click_block(x, y, board_list)
if position:
if mouse_click_type == "right":
# 开始记录耗时
start_record_time = True
# 如果右击方格,那么就更新其状态
right_click_block(*position, board_list)
# 更新标记的雷的数量
flag_count = get_mine_flag_num(board_list)
elif mouse_click_type == "left":
# 开始记录耗时
start_record_time = True
# 点击空格的处理
game_over = left_click_block(*position, board_list,BLOCK_ROW_NUM ,BLOCK_COL_NUM)
# 更新标记的雷的数量
flag_count = get_mine_flag_num(board_list)
if game_over:
# 将所有雷的位置,标记出来
open_all_mine(board_list)
# 更改游戏状态
game_status = "fail"
# 停止记录耗时
start_record_time = False
# 判断是否获胜
win_flag = judge_win(board_list)
if win_flag:
game_status = "win"
# 停止记录耗时
start_record_time = False
elif face_pos_x <= x <= face_pos_x + face_size and face_pos_y <= y <= face_pos_y + face_size:
# 重来一局
run(a,b,c)
# 填充背景色
screen.fill(bgcolor)
# 显示方格
for i, line in enumerate(board_list):
for j, num_dict in enumerate(line):
if num_dict.get("opened"):
screen.blit(img_dict[num_dict.get("opened_num")], (j * SIZE, (i + 2) * SIZE))
else:
screen.blit(img_dict[num_dict.get("closed_num")], (j * SIZE, (i + 2) * SIZE))
# 显示表情
if game_status == "win":
screen.blit(img_face_success, (face_pos_x, face_pos_y))
elif game_status == "fail":
screen.blit(img_face_fail, (face_pos_x, face_pos_y))
else:
screen.blit(img_face_normal, (face_pos_x, face_pos_y))
# 显示剩余雷的数量
mine_text = font.render('%02d' % (MINE_COUNT - flag_count), True, red)
screen.blit(mine_text, (15, (SIZE * 2 - f_height) // 2 - 2))
# 显示耗时
if start_record_time and time.time() - last_time >= 1:
elapsed_time += 1
last_time = time.time()
mine_text = font.render('%03d' % elapsed_time, True, red)
screen.blit(mine_text, (SCREEN_WIDTH - f_width - 10, (SIZE * 2 - f_height) // 2 - 2))
# 刷新显示(此时窗口才会真正的显示)
pygame.display.update()
# FPS(每秒钟显示画面的次数)
clock.tick(60) # 通过一定的延时,实现1秒钟能够循环60次
def main1():
root.destroy()
run(70,16,30)
def main2():
root.destroy()
run(40,16,16)
def main3():
root.destroy()
run(10,9,9)
root = tk.Tk()
root.title('扫雷')
root.geometry('160x180+300+250')
text = "菜单"
text =tk.Label(root,text=text,font=('微软雅黑',14),padx=10).pack()
playButton1 = tk.Button(root, text='初级难度',command=lambda: main3())
playButton1.pack(fill=X,pady='0px')
playButton2 = tk.Button(root, text='中级难度',command=lambda: main2())
playButton2.pack(fill=X,pady='0px')
playButton3 = tk.Button(root, text='高级难度',command=lambda: main1())
playButton3.pack(fill=X,pady='0px')
playButton3 = tk.Button(root, text='帮助',command=do_job)
playButton3.pack(fill=X,pady='0px')
playButton4 = tk.Button(root, text='退出',command=root.destroy)
playButton4.pack(fill=X,pady='0px')
root.mainloop()