2022年 11月 9日

Python游戏:扫雷 (附源码)

这次我们基于 pygame 来做一个扫雷,我所有的代码都是基于 python 3.6 的。

下面将一下我的实现逻辑

首先,如何表示雷和非雷,一开始有好多状态,后来一想,干脆就做个类吧。

  1. class BlockStatus(Enum):
  2. normal = 1 # 未点
  3. opened = 2 # 已点
  4. mine = 3 # 雷
  5. flag = 4 # 标记雷
  6. ask = 5 # 标记问号
  7. bomb = 6 # 踩中雷
  8. hint = 7 # 被双击周围
  9. double = 8 # 正在被鼠标左右键双击
  10. class Mine:
  11. def __init__(self, x, y, value=0):
  12. self._x = x
  13. self._y = y
  14. self._value = 0
  15. self._around_mine_count = -1
  16. self._status = BlockStatus.normal
  17. self.set_value(value)
  18. def __repr__(self):
  19. return str(self._value)
  20. # return f'({self._x},{self._y})={self._value}, status={self.status}'
  21. def get_x(self):
  22. return self._x
  23. def set_x(self, x):
  24. self._x = x
  25. x = property(fget=get_x, fset=set_x)
  26. def get_y(self):
  27. return self._y
  28. def set_y(self, y):
  29. self._y = y
  30. y = property(fget=get_y, fset=set_y)
  31. def get_value(self):
  32. return self._value
  33. def set_value(self, value):
  34. if value:
  35. self._value = 1
  36. else:
  37. self._value = 0
  38. value = property(fget=get_value, fset=set_value, doc='0:非地雷 1:雷')
  39. def get_around_mine_count(self):
  40. return self._around_mine_count
  41. def set_around_mine_count(self, around_mine_count):
  42. self._around_mine_count = around_mine_count
  43. around_mine_count = property(fget=get_around_mine_count, fset=set_around_mine_count, doc='四周地雷数量')
  44. def get_status(self):
  45. return self._status
  46. def set_status(self, value):
  47. self._status = value
  48. status = property(fget=get_status, fset=set_status, doc='BlockStatus')

布雷就很简单了,随机取99个数,从上往下顺序排

  1. class MineBlock:
  2. def __init__(self):
  3. self._block = [[Mine(i, j) for i in range(BLOCK_WIDTH)] for j in range(BLOCK_HEIGHT)]
  4. # 埋雷
  5. for i in random.sample(range(BLOCK_WIDTH * BLOCK_HEIGHT), MINE_COUNT):
  6. self._block[i // BLOCK_WIDTH][i % BLOCK_WIDTH].value = 1

 

点击一个格子的时候,只要根据点击的坐标,找到对应的 Mine,看它的值是多少,就知道有没有踩中雷

如果没踩中雷的话,要计算周边8个位置中有几个雷,以便显示对应的数字

如果周边有雷,那么显示数字,这个简单,可是如果周边没有雷,那就要显示一片区域,直到有雷出现

这个计算其实也容易,用递归就可以了,如果计算出周围的雷数为0,则递归计算周边8个位置的四周雷数,直到雷数不为0

class MineBlock:
  1. class MineBlock:
  2. import sys
  3. import time
  4. from enum import Enum
  5. import pygame
  6. from pygame.locals import *
  7. from mineblock import *
  8. # 游戏屏幕的宽
  9. SCREEN_WIDTH = BLOCK_WIDTH * SIZE
  10. # 游戏屏幕的高
  11. SCREEN_HEIGHT = (BLOCK_HEIGHT + 2) * SIZE
  12. class GameStatus(Enum):
  13. readied = 1,
  14. started = 2,
  15. over = 3,
  16. win = 4
  17. def print_text(screen, font, x, y, text, fcolor=(255, 255, 255)):
  18. imgText = font.render(text, True, fcolor)
  19. screen.blit(imgText, (x, y))
  20. def main():
  21. pygame.init()
  22. screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
  23. pygame.display.set_caption('扫雷')
  24. font1 = pygame.font.Font('resources/a.TTF', SIZE * 2) # 得分的字体
  25. fwidth, fheight = font1.size('999')
  26. red = (200, 40, 40)
  27. # 加载资源图片,因为资源文件大小不一,所以做了统一的缩放处理
  28. img0 = pygame.image.load('resources/0.bmp').convert()
  29. img0 = pygame.transform.smoothscale(img0, (SIZE, SIZE))
  30. img1 = pygame.image.load('resources/1.bmp').convert()
  31. img1 = pygame.transform.smoothscale(img1, (SIZE, SIZE))
  32. img2 = pygame.image.load('resources/2.bmp').convert()
  33. img2 = pygame.transform.smoothscale(img2, (SIZE, SIZE))
  34. img3 = pygame.image.load('resources/3.bmp').convert()
  35. img3 = pygame.transform.smoothscale(img3, (SIZE, SIZE))
  36. img4 = pygame.image.load('resources/4.bmp').convert()
  37. img4 = pygame.transform.smoothscale(img4, (SIZE, SIZE))
  38. img5 = pygame.image.load('resources/5.bmp').convert()
  39. img5 = pygame.transform.smoothscale(img5, (SIZE, SIZE))
  40. img6 = pygame.image.load('resources/6.bmp').convert()
  41. img6 = pygame.transform.smoothscale(img6, (SIZE, SIZE))
  42. img7 = pygame.image.load('resources/7.bmp').convert()
  43. img7 = pygame.transform.smoothscale(img7, (SIZE, SIZE))
  44. img8 = pygame.image.load('resources/8.bmp').convert()
  45. img8 = pygame.transform.smoothscale(img8, (SIZE, SIZE))
  46. img_blank = pygame.image.load('resources/blank.bmp').convert()
  47. img_blank = pygame.transform.smoothscale(img_blank, (SIZE, SIZE))
  48. img_flag = pygame.image.load('resources/flag.bmp').convert()
  49. img_flag = pygame.transform.smoothscale(img_flag, (SIZE, SIZE))
  50. img_ask = pygame.image.load('resources/ask.bmp').convert()
  51. img_ask = pygame.transform.smoothscale(img_ask, (SIZE, SIZE))
  52. img_mine = pygame.image.load('resources/mine.bmp').convert()
  53. img_mine = pygame.transform.smoothscale(img_mine, (SIZE, SIZE))
  54. img_blood = pygame.image.load('resources/blood.bmp').convert()
  55. img_blood = pygame.transform.smoothscale(img_blood, (SIZE, SIZE))
  56. img_error = pygame.image.load('resources/error.bmp').convert()
  57. img_error = pygame.transform.smoothscale(img_error, (SIZE, SIZE))
  58. face_size = int(SIZE * 1.25)
  59. img_face_fail = pygame.image.load('resources/face_fail.bmp').convert()
  60. img_face_fail = pygame.transform.smoothscale(img_face_fail, (face_size, face_size))
  61. img_face_normal = pygame.image.load('resources/face_normal.bmp').convert()
  62. img_face_normal = pygame.transform.smoothscale(img_face_normal, (face_size, face_size))
  63. img_face_success = pygame.image.load('resources/face_success.bmp').convert()
  64. img_face_success = pygame.transform.smoothscale(img_face_success, (face_size, face_size))
  65. face_pos_x = (SCREEN_WIDTH - face_size) // 2
  66. face_pos_y = (SIZE * 2 - face_size) // 2
  67. img_dict = {
  68. 0: img0,
  69. 1: img1,
  70. 2: img2,
  71. 3: img3,
  72. 4: img4,
  73. 5: img5,
  74. 6: img6,
  75. 7: img7,
  76. 8: img8
  77. }
  78. bgcolor = (225, 225, 225) # 背景色
  79. block = MineBlock()
  80. game_status = GameStatus.readied
  81. start_time = None # 开始时间
  82. elapsed_time = 0 # 耗时
  83. while True:
  84. # 填充背景色
  85. screen.fill(bgcolor)
  86. for event in pygame.event.get():
  87. if event.type == QUIT:
  88. sys.exit()
  89. elif event.type == MOUSEBUTTONDOWN:
  90. mouse_x, mouse_y = event.pos
  91. x = mouse_x // SIZE
  92. y = mouse_y // SIZE - 2
  93. b1, b2, b3 = pygame.mouse.get_pressed()
  94. if game_status == GameStatus.started:
  95. # 鼠标左右键同时按下,如果已经标记了所有雷,则打开周围一圈
  96. # 如果还未标记完所有雷,则有一个周围一圈被同时按下的效果
  97. if b1 and b3:
  98. mine = block.getmine(x, y)
  99. if mine.status == BlockStatus.opened:
  100. if not block.double_mouse_button_down(x, y):
  101. game_status = GameStatus.over
  102. elif event.type == MOUSEBUTTONUP:
  103. if y < 0:
  104. if face_pos_x <= mouse_x <= face_pos_x + face_size \
  105. and face_pos_y <= mouse_y <= face_pos_y + face_size:
  106. game_status = GameStatus.readied
  107. block = MineBlock()
  108. start_time = time.time()
  109. elapsed_time = 0
  110. continue
  111. if game_status == GameStatus.readied:
  112. game_status = GameStatus.started
  113. start_time = time.time()
  114. elapsed_time = 0
  115. if game_status == GameStatus.started:
  116. mine = block.getmine(x, y)
  117. if b1 and not b3: # 按鼠标左键
  118. if mine.status == BlockStatus.normal:
  119. if not block.open_mine(x, y):
  120. game_status = GameStatus.over
  121. elif not b1 and b3: # 按鼠标右键
  122. if mine.status == BlockStatus.normal:
  123. mine.status = BlockStatus.flag
  124. elif mine.status == BlockStatus.flag:
  125. mine.status = BlockStatus.ask
  126. elif mine.status == BlockStatus.ask:
  127. mine.status = BlockStatus.normal
  128. elif b1 and b3:
  129. if mine.status == BlockStatus.double:
  130. block.double_mouse_button_up(x, y)
  131. flag_count = 0
  132. opened_count = 0
  133. for row in block.block:
  134. for mine in row:
  135. pos = (mine.x * SIZE, (mine.y + 2) * SIZE)
  136. if mine.status == BlockStatus.opened:
  137. screen.blit(img_dict[mine.around_mine_count], pos)
  138. opened_count += 1
  139. elif mine.status == BlockStatus.double:
  140. screen.blit(img_dict[mine.around_mine_count], pos)
  141. elif mine.status == BlockStatus.bomb:
  142. screen.blit(img_blood, pos)
  143. elif mine.status == BlockStatus.flag:
  144. screen.blit(img_flag, pos)
  145. flag_count += 1
  146. elif mine.status == BlockStatus.ask:
  147. screen.blit(img_ask, pos)
  148. elif mine.status == BlockStatus.hint:
  149. screen.blit(img0, pos)
  150. elif game_status == GameStatus.over and mine.value:
  151. screen.blit(img_mine, pos)
  152. elif mine.value == 0 and mine.status == BlockStatus.flag:
  153. screen.blit(img_error, pos)
  154. elif mine.status == BlockStatus.normal:
  155. screen.blit(img_blank, pos)
  156. print_text(screen, font1, 30, (SIZE * 2 - fheight) // 2 - 2, '%02d' % (MINE_COUNT - flag_count), red)
  157. if game_status == GameStatus.started:
  158. elapsed_time = int(time.time() - start_time)
  159. print_text(screen, font1, SCREEN_WIDTH - fwidth - 30, (SIZE * 2 - fheight) // 2 - 2, '%03d' % elapsed_time, red)
  160. if flag_count + opened_count == BLOCK_WIDTH * BLOCK_HEIGHT:
  161. game_status = GameStatus.win
  162. if game_status == GameStatus.over:
  163. screen.blit(img_face_fail, (face_pos_x, face_pos_y))
  164. elif game_status == GameStatus.win:
  165. screen.blit(img_face_success, (face_pos_x, face_pos_y))
  166. else:
  167. screen.blit(img_face_normal, (face_pos_x, face_pos_y))
  168. pygame.display.update()
  169. if __name__ == '__main__':
  170. main()