代码有点多? ?
函数式编程
纯原创手打,详细注释,不容易啊,搞了两天
UI有点low,目前还咩有自学到UI美化方面的内容? 只会画形状填颜色
写到后面,头大了,有很多坐标索引方面的变量都直接用数字算出来代替了,以后改面向对象编程的时候再慢慢改吧?
ps:修改了一个bug? ?之前定义if count_click==1的时候,t_start=time.time()
导致第一次点击不计时,第二次点击的时候才开始计时??
修正之后,应该是定义if count_click==0的时候,t_start=time.time()
然后把这个if 判断放在while True循环内部 ,表示没点击之前,会一直刷新t_start的值,但是不计时,count_click>=1开始计时,且因为此时count_click不等于0,t_start不再刷新,而变成一个固定值 ,用当前时间-t_start,得到的计时显示到窗口上就可以了?
原理:
我定义了一个二维列表list_block,这个数组一共有长*宽个元素,每个元素也是一个列表,这个列表里放了4个元素【格子的x坐标,格子的y坐标,格子是否是雷,格子当前的状态】
格子是否是雷:
定义-1是雷,0是空格(即周围8个格子都不是雷),1-8表示不是雷,也不是空格时,周边雷的数量
格子当前的状态:
0-未标记(初始化时的空方块)
1-标记为雷(没有小红旗的图片,就画了一个红色的圆)
2-标记为?
3-表示鼠标左键点开过
扫雷的难点分析:
1.鼠标左键点击,如果点击的格子是个空格(周围8个格子都没有雷),则需要再调用左键点击这8个格子,然后如果这8个格子里有某些格子也是空格,那就要继续调用点开该格子周围的8个格子,就是一个递归的思想了,边界就是点开的格子都不是空格,递归就结束了?
2.定义鼠标中键点击 扫描点开周围的非雷的格子
(1)前提是点击的这个格子已经被左击打开过,该格子显示周围雷的数量,这个时候中键才能起作用
(2)对比格子上显示的周围雷的数量和该格子周围被右键标记为雷的数量,如果相等,中键才能起作用
(3) 同时满足上面两个条件的情况下,调用鼠标左击的函数点开该格子周围没有被标记的格子
(4)如果标记错误,点开的格子中有雷,触发游戏失败
# -*- coding: utf-8 -*-
# __/author__by:Kevin_F/__
import random
import time
import pygame
num_w = 30 # 横向格子数
num_h = 16 # 竖向格子数
# 界面初始化
def window_init():
pygame.init()
global window
# 格子尺寸30*30,状态栏高度100,上下左右边界20
window = pygame.display.set_mode((30 * num_w + 20 * 2, 100 + 30 * num_h + 2 * 20))
pygame.display.set_caption('扫雷 by:Kevin_X')
window.fill((128, 138, 135)) # 背景色:冷灰
pygame.draw.rect(window, (192, 192, 192), (0, 0, 30 * num_w + 20 * 2, 100)) # 最上面状态框
for j in range(num_h):
for i in range(num_w):
# 格子坐标
block_x = 20 + i * 30
block_y = 100 + 20 + j * 30
list_block.append([block_x, block_y, 0, 0])
list_block_pos.append((block_x, block_y))
"""
list_temp生成存放每一行中格子的坐标信息
block_x:格子的x坐标
block_y:格子的y坐标 (坐标信息是固定不可修改的,所以用元组表示)
0:定义此格子是否是雷 是雷:-1 不是雷:表示周边相邻非雷格子的数量,先写入默认值为0
0:定义格子的标记状态 0-未标记,即初始值 1-标记是雷 2-标记问号 3-左击点开
"""
pygame.draw.rect(window, (192, 192, 192), (block_x, block_y, 30, 30), 0) # 实心格子
pygame.draw.rect(window, (220, 220, 220), (block_x, block_y, 30, 30), 2) # 格子边线
pygame.display.flip()
# 状态栏 雷剩余数
def bomb_les():
global count_bomb
if count_bomb<0:
count_bomb=0
# 不存在标记雷超过99的可能,因为未标记的状态只有先标记成雷-1,再右键取消标记雷+1
pygame.draw.rect(window,(192,192,192),(0,0,150,100),0)
font_bomb_num=pygame.font.SysFont('Microsoft YaHei',60,bold=True)
bomb_num=font_bomb_num.render(str(count_bomb),True,(255,0,0))
window.blit(bomb_num,(20,10))
pygame.display.update()
# 状态栏 状态显示
def game_status(status):
img_working = pygame.image.load('working.png')
img_bad = pygame.image.load('bad.png')
img_win = pygame.image.load('bingo.png')
if status == 'working': # 游戏开始时的图片
window.blit(img_working, (15 * num_w + 20 - 45, 5))
elif status == 'loose': # 游戏失败的图片
window.blit(img_bad, (15 * num_w + 20 - 45, 5))
elif status == 'win': # 游戏胜利的图片
window.blit(img_win, (15 * num_w + 20 - 45, 5))
else:
pass
pygame.display.update()
# 游戏初始化(随机生成99颗雷,初始化每个格子的位置信息,状态信息)
def game_init():
# 随机生成99颗地雷
global list_bomb_pos
list_bomb_pos = random.sample(list_block_pos, 99)
# 从所有格子中取不重复的99个,即得到99个位置坐标(x,y),组成的列表即是所有雷的坐标列表
# 改写list_block中格子的第三个值 从0改成-1 即表示是雷
for i in range(len(list_bomb_pos)):
for j in range(len(list_block)):
if list_block[j][0] == list_bomb_pos[i][0] and list_block[j][1] == list_bomb_pos[i][1]:
list_block[j][2] = -1
# 不是雷的格子,要计算出格子周边的雷数,并写入list_block
for index in range(len(list_block)):
# index为list_block的下标
x = index % 30
y = index // 30
if list_block[index][2] == -1:
continue
else:
list_beside=list_side(x,y)
mark_num = 0 # 定义一个变量,用于存放此格子周边是雷的格子的个数
for element in list_beside:
if element[2] == -1:
mark_num += 1
list_block[index][2] = mark_num # 将周边雷的个数写入list_block中的第三个值
# 初始化一个列表,用于存放不是雷的格子的坐标
for i in list_block_pos:
if i not in list_bomb_pos:
list_not_bomb.append(i)
# 获得一个存放此格子周边相邻格子组成的列表(定义此函数返回值就是这个列表)
def list_side(x,y):
if 1 <= x <= 28 and 1 <= y <= 14:
list_side = [
list_block[x - 1 + (y - 1) * 30],
list_block[x + (y - 1) * 30],
list_block[x + 1 + (y - 1) * 30],
list_block[x - 1 + y * 30],
list_block[x + 1 + y * 30],
list_block[x - 1 + (y + 1) * 30],
list_block[x + (y + 1) * 30],
list_block[x + 1 + (y + 1) * 30]
]
elif x == 0 and 1 <= y <= 14:
list_side = [
list_block[(y - 1) * 30],
list_block[1 + (y - 1) * 30],
list_block[1 + y * 30],
list_block[(y + 1) * 30],
list_block[1 + (y + 1) * 30]
]
elif x == 29 and 1 <= y <= 14:
list_side = [
list_block[28 + (y - 1) * 30],
list_block[29 + (y - 1) * 30],
list_block[28 + y * 30],
list_block[28 + (y + 1) * 30],
list_block[29 + (y + 1) * 30]
]
elif 1 <= x <= 28 and y == 0:
list_side = [
list_block[x - 1 + y * 30],
list_block[x + 1 + y * 30],
list_block[x - 1 + (y + 1) * 30],
list_block[x + (y + 1) * 30],
list_block[x + 1 + (y + 1) * 30]
]
elif 1 <= x <= 28 and y == 15:
list_side = [
list_block[x - 1 + (y - 1) * 30],
list_block[x + (y - 1) * 30],
list_block[x + 1 + (y - 1) * 30],
list_block[x - 1 + y * 30],
list_block[x + 1 + y * 30]
]
elif x == 0 and y == 0:
list_side = [list_block[1], list_block[30], list_block[31]]
elif x == 0 and y == 15:
list_side = [list_block[420], list_block[421], list_block[451]]
elif x == 29 and y == 0:
list_side = [list_block[28], list_block[58], list_block[59]]
else:
list_side = [list_block[448], list_block[449], list_block[478]]
return list_side
# 绘制鼠标点击后的格子
def draw_block(para, x, y):
if para == 0: # 此格子周围格子都不是雷,此格子直接画个实心方块
pygame.draw.rect(window, (180, 180, 180), (x * 30 + 20, y * 30 + 120, 30, 30), 0)
elif para in [1, 2, 3, 4, 5, 6, 7, 8]: # 此格子周围有雷,显示周围雷的数量
colors={
1:(0,0,255),
2:(34,139,34),
3:(25,25,112),
4:(160,32,240),
5:(94,38,18),
6:(199,97,20),
7:(240,230,140),
8:(240,230,140)
}
num_font = pygame.font.SysFont('SimHei', 24,bold=True)
text_num = num_font.render(str(para), True, colors[para])
w1, h1 = text_num.get_size()
window.blit(text_num, (x * 30 + 35 - w1 / 2, y * 30 + 135 - h1 / 2))
elif para == 'bomb': # 左键点击到雷的时候,地雷爆炸(变红色)
pygame.draw.circle(window, (0, 0, 0), (x * 30 + 35, y * 30 + 135), 12)
elif para == 'mark': # 右键标记为雷时,画个红色实心圆表示地雷
pygame.draw.rect(window, (192, 192, 192), (x*30+20, y*30+120, 30, 30), 0) # 实心格子
pygame.draw.rect(window, (220, 220, 220), (x*30+20, y*30+120, 30, 30), 2) # 格子边线
pygame.draw.circle(window, (255, 0, 0), (x * 30 + 35, y * 30 + 135), 11)
elif para=='?': # 右键标记?,格子上显示?
num_font = pygame.font.SysFont('SimHei', 24)
text_num = num_font.render('?', True, (255, 0, 0))
w1, h1 = text_num.get_size()
pygame.draw.rect(window, (192, 192, 192), (x*30+20, y*30+120, 30, 30), 0) # 实心格子
pygame.draw.rect(window, (220, 220, 220), (x*30+20, y*30+120, 30, 30), 2) # 格子边线
window.blit(text_num, (x * 30 + 35 - w1 / 2, y * 30 + 135 - h1 / 2))
elif para=='blank': # 右键切换成未标记状态时,画为初始化时的格子
pygame.draw.rect(window, (192, 192, 192), (x*30+20, y*30+120, 30, 30), 0) # 实心格子
pygame.draw.rect(window, (220, 220, 220), (x*30+20, y*30+120, 30, 30), 2) # 格子边线
else:
pass
pygame.display.update()
# 扫雷区鼠标左击事件
def mouse_click_left(mouse_x, mouse_y):
global list_block,is_loose,count_click
pos_x = (mouse_x - 20) // 30
pos_y = (mouse_y - 120) // 30
index = pos_x + pos_y * 30 # list_block的下标index
# 触发了此位置的左击事件,要先把此位置的左击事件表达出来
if list_block[index][3]==3: # 如果这个格子已经被左击点开过,则不能重复点击
pass
else:
# 先判断点击是否是雷 ,如果是雷,游戏失败
if list_block[index][2]==-1: # 鼠标左击 是雷 游戏失败
is_loose=True
for x, y in list_bomb_pos:
draw_block('bomb', (x - 20) // 30, (y - 120) // 30)
# 把当前点击的这个雷标成红色
pygame.draw.rect(window, (255, 0, 0), (20 + pos_x * 30, 120 + pos_y * 30, 30, 30), 0)
draw_block('bomb', pos_x, pos_y)
game_status('loose')
# 游戏已经失败,这个时候再点击扫雷区,无效
# 计时停止
else:
# 不是雷的情况下,再判断这个格子是否是周边雷数为0 ,如果为0 ,则要继续触发左击点开该格子周围8个格子
# 除了画出当前位置的左击事件,还需要判断当前周边雷数是否为0
# 如果为0,自动触发此格子周边8个相邻格子的左击事件(因为0 代表周边8个格子都不是雷,直接自动左击显示出这8个格子每一个的周边雷数)
# 如果这8个格子中还有周边雷数为0的,则递归调用函数
if list_block[index][2] == 0:
draw_block(list_block[index][2], pos_x, pos_y)
list_block[index][3] = 3
list_beside = list_side(pos_x,pos_y)
for i in list_beside:
x=(i[0]-20)//30
y=(i[1]-120)//30
if list_block[x+30*y][3]==0: # 判断此位置的标记状态(list_block里的最后一个参数),0-未标记
mouse_click_left(x*30+20,y*30+120) # 调用函数
draw_block(list_block[x+30*y][2], x, y) # 画出左击后的图形
list_block[x+30*y][3]=3 # 调用函数,即说明发生左击(即使是电脑自动触发,而非人为操作),更改标记状态为3
else: # 该格子周边雷数不为0,直接显示该格子的周边雷数
draw_block(list_block[index][2], pos_x, pos_y)
list_block[index][3] = 3
count_click=0
for i in list_block:
if i[3]==3:
count_click+=1
# 每次触发鼠标左击事件后,都重新统计已经左击点开的格子个数
# 为了后面判断如果点开的格子个数=非雷的格子总数,则游戏胜利
# 扫雷区鼠标右击事件
def mouse_click_right(mouse_x,mouse_y):
global count_bomb
pos_x = (mouse_x - 20) // 30
pos_y = (mouse_y - 120) // 30
index = pos_x + pos_y * 30 # list_block的下标index
status= list_block[index][3]
# 游戏结束,不能再右击
if is_loose:
pass
else:
# 右击,格子显示状态在0-未标记 1-标记雷 2-标记? 三种状态之间循环切换
if status==0: # 如果此格子当前处于未标记状态
draw_block('mark',pos_x,pos_y)
list_block[index][3]=1
count_bomb-=1
elif status==1:
draw_block('?',pos_x,pos_y)
list_block[index][3]=2
count_bomb+=1
elif status==2:
draw_block('blank',pos_x,pos_y)
list_block[index][3] = 0
else:
# 已经左击点开的位置 不能在右击(即status=3的情况)
pass
# 扫雷区鼠标中键事件
def mouse_click_mid(mouse_x,mouse_y):
# 鼠标中键该格子,表示扫描该格子周边相邻格子
# 该格子只有被左击点开(list_block[index][3]==3),且该格子周边雷数(list_block[index][2])=标记雷的总个数
# 中键触发自动左键点开周边非雷的格子(之前没有左击点开过 ),如果之前左击点开过,则跳过
pos_x = (mouse_x - 20) // 30
pos_y = (mouse_y - 120) // 30
index = pos_x + pos_y * 30 # list_block的下标index
status = list_block[index][3]
list_beside=list_side(pos_x ,pos_y)
mark_bomb_sum=0
for item in list_beside:
if item[3]==1: # item[3]即相当于list_block[index][3] 如果该值为1 表示标记是雷
mark_bomb_sum+=1
if list_block[index][3]==3 and list_block[index][2]==mark_bomb_sum and list_block[index][2]!=0:
# 该格子只有被左击点开(list_block[index][3]==3),且该格子周边雷数(list_block[index][2])=标记雷的总个数,且不等于0
for item in list_beside:
# 中键触发自动左键点开周边非雷的格子(之前没有左击点开过 ),如果之前左击点开过,则跳过
if item[3]==3 or item[3]==1 or item[3]==2:
# 状态是3,表示左击点开过,状态为1 ,表示标记是雷,状态为2,表示疑问,这三种情况都不能触发鼠标左击事件
continue
else:
mouse_click_left(item[0] ,item[1])
def main():
global is_loose,list_block,list_block_pos,list_bomb_pos,list_not_bomb,count_click,count_bomb,num_not_bomb, t_start
list_block_pos = [] # 用于存放每个格子的位置坐标信息(x,y)
list_block = [] # 用于存放每个格子的坐标和状态信息,即每个元素=[x,y,0,0]
list_bomb_pos = [] # 用于存放雷的元素的坐标
list_not_bomb=[] # 用于存放不是雷的格子的坐标
count_click=0 # 用于记录左击点开的格子数量
window_init()
game_status('working')
game_init()
is_loose=False
num_not_bomb=len(list_block_pos)-len(list_bomb_pos)
count_bomb= len(list_bomb_pos)
while True:
bomb_les()
if 0 <= count_click < num_not_bomb and not is_loose:
if count_click == 0:
t_start = int(time.time())
# 在while True循环内,如果count_click=0,表示没有鼠标左击,此时不计时
# 此时,因为while True一直循环,所以会一直刷新t_start的值,不断获取当前时间
# 当count_click从1开始,即一旦发生左击点击,上面if条件 count_click==0不成立,则不会再刷新t_start的值
# t_start的值固定在count_click=1前上一次循环的值,即左击点击之前的最后的一次循环的值,从而实现左击开始计时
t_show = int(time.time()) - t_start
pygame.draw.rect(window, (192, 192, 192), (720, 0, 200, 100), 0)
font_time = pygame.font.SysFont('Microsoft YaHei', 60, bold=True)
text_time = font_time.render(str(t_show), True, (255, 0, 0))
window.blit(text_time, (920 - text_time.get_size()[0], 10)) # 时间靠右显示
pygame.display.update()
else: # 如果游戏成功(count_click = num_not_bomb) 或者游戏失败(is_loose=True) 都停止时间显示
pass
for event in pygame.event.get():
if event.type == pygame.QUIT:
exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
if is_loose:
game_status('loose')
continue
elif count_click<num_not_bomb:
mouse_x, mouse_y = pygame.mouse.get_pos()
click = pygame.mouse.get_pressed()
if 20 <= mouse_x <= 920 and 120 <= mouse_y <= 600:
if click==(True, False, False): # 鼠标左击
mouse_click_left(mouse_x, mouse_y)
elif click==(False,True,False): # 鼠标中键
mouse_click_mid(mouse_x,mouse_y)
elif click==(False,False,True): # 鼠标右键
mouse_click_right(mouse_x,mouse_y)
else: # 当count_click == num_not_bomb,则说明所有非雷的格子全部被点开,游戏胜利
game_status('win')
elif event.type==pygame.MOUSEBUTTONUP:
mouse_x1,mouse_y1=pygame.mouse.get_pos()
if 0 <= mouse_x1 - (15 * num_w + 20 - 45) <= 90 and 10 <= mouse_y1 <= 100:
# 鼠标在状态图标位置左击点击时,表示游戏开始
return main() # 游戏开始或者游戏重新开始
if __name__ == '__main__':
main()
|