这次我们基于 pygame 来做一个扫雷,我所有的代码都是基于 python 3.6 的。
下面将一下我的实现逻辑
首先,如何表示雷和非雷,一开始有好多状态,后来一想,干脆就做个类吧。
- class BlockStatus(Enum):
- normal = 1 # 未点
- opened = 2 # 已点
- mine = 3 # 雷
- flag = 4 # 标记雷
- ask = 5 # 标记问号
- bomb = 6 # 踩中雷
- hint = 7 # 被双击周围
- double = 8 # 正在被鼠标左右键双击
-
-
- class Mine:
- def __init__(self, x, y, value=0):
- self._x = x
- self._y = y
- self._value = 0
- self._around_mine_count = -1
- self._status = BlockStatus.normal
- self.set_value(value)
-
- def __repr__(self):
- return str(self._value)
- # return f'({self._x},{self._y})={self._value}, status={self.status}'
-
- 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, doc='0:非地雷 1:雷')
-
- def get_around_mine_count(self):
- return self._around_mine_count
-
- def set_around_mine_count(self, around_mine_count):
- self._around_mine_count = around_mine_count
-
- around_mine_count = property(fget=get_around_mine_count, fset=set_around_mine_count, doc='四周地雷数量')
-
- def get_status(self):
- return self._status
-
- def set_status(self, value):
- self._status = value
-
- status = property(fget=get_status, fset=set_status, doc='BlockStatus')
布雷就很简单了,随机取99个数,从上往下顺序排
- class MineBlock:
- def __init__(self):
- self._block = [[Mine(i, j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]
- # 埋雷
- for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):
- self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1
点击一个格子的时候,只要根据点击的坐标,找到对应的 Mine,看它的值是多少,就知道有没有踩中雷
如果没踩中雷的话,要计算周边8个位置中有几个雷,以便显示对应的数字
如果周边有雷,那么显示数字,这个简单,可是如果周边没有雷,那就要显示一片区域,直到有雷出现
这个计算其实也容易,用递归就可以了,如果计算出周围的雷数为0,则递归计算周边8个位置的四周雷数,直到雷数不为0
class MineBlock:
- class MineBlock:
- import sys
- import time
- from enum import Enum
- import pygame
- from pygame.locals import *
- from mineblock import *
- # 游戏屏幕的宽
- SCREEN_WIDTH = BLOCK_WIDTH * SIZE
- # 游戏屏幕的高
- SCREEN_HEIGHT = (BLOCK_HEIGHT + 2) * SIZE
- class GameStatus(Enum):
- readied = 1,
- started = 2,
- over = 3,
- win = 4
- def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)):
- imgText = font.render(text, True, fcolor)
- screen.blit(imgText, (x, y))
- def main():
- pygame.init()
- screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
- pygame.display.set_caption('扫雷')
- font1 = pygame.font.Font('resources/a.TTF', SIZE * 2) # 得分的字体
- fwidth, fheight = font1.size('999')
- red = (200, 40, 40)
- # 加载资源图片,因为资源文件大小不一,所以做了统一的缩放处理
- img0 = pygame.image.load('resources/0.bmp').convert()
- img0 = pygame.transform.smoothscale(img0, (SIZE, SIZE))
- img1 = pygame.image.load('resources/1.bmp').convert()
- img1 = pygame.transform.smoothscale(img1, (SIZE, SIZE))
- img2 = pygame.image.load('resources/2.bmp').convert()
- img2 = pygame.transform.smoothscale(img2, (SIZE, SIZE))
- img3 = pygame.image.load('resources/3.bmp').convert()
- img3 = pygame.transform.smoothscale(img3, (SIZE, SIZE))
- img4 = pygame.image.load('resources/4.bmp').convert()
- img4 = pygame.transform.smoothscale(img4, (SIZE, SIZE))
- img5 = pygame.image.load('resources/5.bmp').convert()
- img5 = pygame.transform.smoothscale(img5, (SIZE, SIZE))
- img6 = pygame.image.load('resources/6.bmp').convert()
- img6 = pygame.transform.smoothscale(img6, (SIZE, SIZE))
- img7 = pygame.image.load('resources/7.bmp').convert()
- img7 = pygame.transform.smoothscale(img7, (SIZE, SIZE))
- img8 = pygame.image.load('resources/8.bmp').convert()
- img8 = pygame.transform.smoothscale(img8, (SIZE, SIZE))
- img_blank = pygame.image.load('resources/blank.bmp').convert()
- img_blank = pygame.transform.smoothscale(img_blank, (SIZE, SIZE))
- img_flag = pygame.image.load('resources/flag.bmp').convert()
- img_flag = pygame.transform.smoothscale(img_flag, (SIZE, SIZE))
- img_ask = pygame.image.load('resources/ask.bmp').convert()
- img_ask = pygame.transform.smoothscale(img_ask, (SIZE, SIZE))
- img_mine = pygame.image.load('resources/mine.bmp').convert()
- img_mine = pygame.transform.smoothscale(img_mine, (SIZE, SIZE))
- img_blood = pygame.image.load('resources/blood.bmp').convert()
- img_blood = pygame.transform.smoothscale(img_blood, (SIZE, SIZE))
- img_error = pygame.image.load('resources/error.bmp').convert()
- img_error = pygame.transform.smoothscale(img_error, (SIZE, SIZE))
- face_size = int(SIZE * 1.25)
- img_face_fail = pygame.image.load('resources/face_fail.bmp').convert()
- img_face_fail = pygame.transform.smoothscale(img_face_fail, (face_size, face_size))
- img_face_normal = pygame.image.load('resources/face_normal.bmp').convert()
- img_face_normal = pygame.transform.smoothscale(img_face_normal, (face_size, face_size))
- img_face_success = pygame.image.load('resources/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 = {
- 0: img0,
- 1: img1,
- 2: img2,
- 3: img3,
- 4: img4,
- 5: img5,
- 6: img6,
- 7: img7,
- 8: img8
- }
- bgcolor = (225, 225, 225) # 背景色
- block = MineBlock()
- game_status = GameStatus.readied
- start_time = None # 开始时间
- elapsed_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()
- if game_status == GameStatus.started:
- # 鼠标左右键同时按下,如果已经标记了所有雷,则打开周围一圈
- # 如果还未标记完所有雷,则有一个周围一圈被同时按下的效果
- if b1 and b3:
- mine = block.getmine(x, y)
- if mine.status == BlockStatus.opened:
- if not block.double_mouse_button_down(x, y):
- game_status = GameStatus.over
- 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_status = GameStatus.readied
- block = MineBlock()
- start_time = time.time()
- elapsed_time = 0
- continue
- if game_status == GameStatus.readied:
- game_status = GameStatus.started
- start_time = time.time()
- elapsed_time = 0
- if game_status == GameStatus.started:
- mine = block.getmine(x, y)
- if b1 and not b3: # 按鼠标左键
- if mine.status == BlockStatus.normal:
- if not block.open_mine(x, y):
- game_status = GameStatus.over
- elif not b1 and b3: # 按鼠标右键
- if mine.status == BlockStatus.normal:
- mine.status = BlockStatus.flag
- elif mine.status == BlockStatus.flag:
- mine.status = BlockStatus.ask
- elif mine.status == BlockStatus.ask:
- mine.status = BlockStatus.normal
- elif b1 and b3:
- if mine.status == BlockStatus.double:
- block.double_mouse_button_up(x, y)
- flag_count = 0
- opened_count = 0
- for row in block.block:
- for mine in row:
- pos = (mine.x * SIZE, (mine.y + 2) * SIZE)
- if mine.status == BlockStatus.opened:
- screen.blit(img_dict[mine.around_mine_count], pos)
- opened_count += 1
- elif mine.status == BlockStatus.double:
- screen.blit(img_dict[mine.around_mine_count], pos)
- elif mine.status == BlockStatus.bomb:
- screen.blit(img_blood, pos)
- elif mine.status == BlockStatus.flag:
- screen.blit(img_flag, pos)
- flag_count += 1
- elif mine.status == BlockStatus.ask:
- screen.blit(img_ask, pos)
- elif mine.status == BlockStatus.hint:
- screen.blit(img0, pos)
- elif game_status == GameStatus.over and mine.value:
- screen.blit(img_mine, pos)
- elif mine.value == 0 and mine.status == BlockStatus.flag:
- screen.blit(img_error, pos)
- elif mine.status == BlockStatus.normal:
- screen.blit(img_blank, pos)
- print_text(screen, font1, 30, (SIZE * 2 - fheight) // 2 - 2, '%02d' % (MINE_COUNT - flag_count), red)
- if game_status == GameStatus.started:
- elapsed_time = int(time.time() - start_time)
- print_text(screen, font1, SCREEN_WIDTH - fwidth - 30, (SIZE * 2 - fheight) // 2 - 2, '%03d' % elapsed_time, red)
- if flag_count + opened_count == BLOCK_WIDTH * BLOCK_HEIGHT:
- game_status = GameStatus.win
- if game_status == GameStatus.over:
- screen.blit(img_face_fail, (face_pos_x, face_pos_y))
- elif game_status == GameStatus.win:
- screen.blit(img_face_success, (face_pos_x, face_pos_y))
- else:
- screen.blit(img_face_normal, (face_pos_x, face_pos_y))
- pygame.display.update()
- if __name__ == '__main__':
- main()