今天給大家分享一個由Python3(當然python3.5 3.6 3.7 3.8 ...都行)與pygame模組結合製作的一個經典小遊戲“掃雷”
程式碼是完全可執行的,請大家放心執行。當然了別忘了下載素材(下方程式碼位置處寫明瞭下載地址)
一、執行效果
二、完整程式碼
下面的程式碼用到了一些素材(圖片、字型等),下載地址為:https://www.itprojects.cn/detail.html?example_id=dd0fdfb421f61089547578d235b3fce7
import random import sys import time import pygame # 地雷數量 MINE_COUNT = 99 # 每個方格的大小(寬、高都為20) SIZE = 20 # 方格的行數 BLOCK_ROW_NUM = 16 # 方格的列數 BLOCK_COL_NUM = 30 # 遊戲視窗的寬、高 SCREEN_WIDTH, SCREEN_HEIGHT = BLOCK_COL_NUM * SIZE, (BLOCK_ROW_NUM + 2) * SIZE 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 get_mine_num(row, col, board_list): """ 計算點選的空格周圍的雷的數量 """ # 生成起始位置、終止位置 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): """ 判斷當前位置的周邊位置是否為空,如果是則繼續判斷, 最終能夠實現點選一個空位置後連續的空位置都能夠顯示出來 """ mine_num = get_mine_num(row, col, board_list) 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 <= 15 and 0 <= col + j <= 29: mine_num = get_mine_num(row + i, col + j, board_list) 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 <= 15 and 0 <= col + j <= 29: if not board_list[row + i][col + j].get("opened"): set_nums_blank(row + i, col + j, board_list) else: board_list[row][col]['opened'] = True board_list[row][col]["opened_num"] = mine_num board_list[row][col]["closed_num"] = "空" def left_click_block(row, col, board_list): """ 左擊空格後的處理 """ 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) 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) 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 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 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"] = "雷標記" elif board_list[row][col]["closed_num"] == "雷標記": board_list[row][col]["closed_num"] = "疑問標記" elif board_list[row][col]["closed_num"] == "疑問標記": board_list[row][col]["closed_num"] = "空" 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 run(screen): bgcolor = (225, 225, 225) # 背景色 # 要顯示的棋盤 # board_list = [[0] * BLOCK_COL_NUM for _ in range(BLOCK_ROW_NUM)] board_list = create_random_board(BLOCK_ROW_NUM, BLOCK_COL_NUM, MINE_COUNT) # 16行、30列,有99顆地雷 # 預設的方格圖片 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 # 類的數量圖片 img0 = pygame.image.load('resource/0.bmp').convert() img0 = pygame.transform.smoothscale(img0, (SIZE, SIZE)) img1 = pygame.image.load('resource/1.bmp').convert() img1 = pygame.transform.smoothscale(img1, (SIZE, SIZE)) img2 = pygame.image.load('resource/2.bmp').convert() img2 = pygame.transform.smoothscale(img2, (SIZE, SIZE)) img3 = pygame.image.load('resource/3.bmp').convert() img3 = pygame.transform.smoothscale(img3, (SIZE, SIZE)) img4 = pygame.image.load('resource/4.bmp').convert() img4 = pygame.transform.smoothscale(img4, (SIZE, SIZE)) img5 = pygame.image.load('resource/5.bmp').convert() img5 = pygame.transform.smoothscale(img5, (SIZE, SIZE)) img6 = pygame.image.load('resource/6.bmp').convert() img6 = pygame.transform.smoothscale(img6, (SIZE, SIZE)) img7 = pygame.image.load('resource/7.bmp').convert() img7 = pygame.transform.smoothscale(img7, (SIZE, SIZE)) img8 = pygame.image.load('resource/8.bmp').convert() img8 = pygame.transform.smoothscale(img8, (SIZE, SIZE)) img_dict = { 0: img0, 1: img1, 2: img2, 3: img3, 4: img4, 5: img5, 6: img6, 7: img7, 8: img8, '雷標記': img_mine_flag, '雷': img_mine, '空': img_blank, '疑問標記': img_ask, '踩雷': img_blood, } # 標記是否踩到雷 game_over = False # 遊戲狀態 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": # 如果右擊方格,那麼就更新其狀態 right_click_block(*position, board_list) # 更新標記的雷的數量 flag_count = get_mine_flag_num(board_list) start_record_time = True # 開始記錄耗時 elif mouse_click_type == "left": # 點選空格的處理 game_over = left_click_block(*position, board_list) print("是否踩到雷", game_over) start_record_time = True # 開始記錄耗時 # 更新標記的雷的數量 flag_count = get_mine_flag_num(board_list) if game_over: # 將所有雷的位置,標記出來 open_all_mine(board_list) # 更改遊戲狀態 game_status = "fail" # 停止記錄耗時 start_record_time = False elif face_pos_x <= x <= face_pos_x + face_size and face_pos_y <= y <= face_pos_y + face_size: # 重來一局 print("點選了再來一局...") return # 填充背景色 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, (30, (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 - 30, (SIZE * 2 - f_height) // 2 - 2)) # 重新整理顯示(此時視窗才會真正的顯示) pygame.display.update() # FPS(每秒鐘顯示畫面的次數) clock.tick(60) # 通過一定的延時,實現1秒鐘能夠迴圈60次 def main(): """ 迴圈呼叫run函式,每呼叫一次就重新來一局遊戲 """ pygame.init() pygame.display.set_caption('掃雷') screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) while True: run(screen) if __name__ == '__main__': main()