前言
今天給大家分享是掃雷小游戲,廢話不多說,讓我們愉快地開始吧~
效果展示

開發工具
Python版本: 3.6.4
相關模塊:
pygame模塊;
以及一些python自帶的模塊,
環境搭建
安裝Python并添加到環境變數,pip安裝需要的相關模塊即可,
原理簡介
這這里我簡單介紹一下游戲的實作思路,
相信大家對掃雷這款windows自帶的經典小游戲都不陌生,它的游戲規則很簡單:

游戲界面左上角的數字代表所有方格中埋有雷的數目,右上角是一個計時器,你要做的就是根據提示找出方格中所有的雷,
那么提示是啥呢?就是游戲剛開始的時候你需要隨便點一個方格,就像這樣:

上面的數字代表以該數字為中心的九宮格內埋有的雷的數目,所以如果你人品不好,一開始就點到雷的話,這局游戲就直接結束了,
ok,了解了游戲規則之后,我們就可以開始寫代碼了,首先還是先初始化一下游戲:
# 游戲初始化
pygame.init()
screen = pygame.display.set_mode(cfg.SCREENSIZE)
pygame.display.set_caption('mine sweeper —— ilove-python')
然后把需要用到的字體,圖片,音樂啥的都匯入進來:
# 匯入所有圖片
images = {}
for key, value in cfg.IMAGE_PATHS.items():
if key in ['face_fail', 'face_normal', 'face_success']:
image = pygame.image.load(value)
images[key] = pygame.transform.smoothscale(image, (int(cfg.GRIDSIZE*1.25), int(cfg.GRIDSIZE*1.25)))
else:
image = pygame.image.load(value).convert()
images[key] = pygame.transform.smoothscale(image, (cfg.GRIDSIZE, cfg.GRIDSIZE))
# 載入字體
font = pygame.font.Font(cfg.FONT_PATH, cfg.FONT_SIZE)
# 匯入并播放背景音樂
pygame.mixer.music.load(cfg.BGM_PATH)
pygame.mixer.music.play(-1)
接著,我們來定義一個文字板,用于顯示左上角的埋雷數量和右上角的游戲已進行時間:
'''文字板'''
class TextBoard(pygame.sprite.Sprite):
def __init__(self, text, font, position, color, **kwargs):
pygame.sprite.Sprite.__init__(self)
self.text = text
self.font = font
self.position = position
self.color = color
def draw(self, screen):
text_render = self.font.render(self.text, True, self.color)
screen.blit(text_render, self.position)
def update(self, text):
self.text = text
其實很簡單,只需要把用字體渲染之后的文本物件系結到螢屏上就行,然后設定一個update函式,來實時更新里面的文本內容,
然后,我們再定義一個表情按鈕類:
'''表情按鈕'''
class EmojiButton(pygame.sprite.Sprite):
def __init__(self, images, position, status_code=0, **kwargs):
pygame.sprite.Sprite.__init__(self)
# 匯入圖片
self.images = images
self.image = self.images['face_normal']
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# 表情按鈕的當前狀態
self.status_code = status_code
'''畫到螢屏上'''
def draw(self, screen):
# 狀態碼為0, 代表正常的表情
if self.status_code == 0:
self.image = self.images['face_normal']
# 狀態碼為1, 代表失敗的表情
elif self.status_code == 1:
self.image = self.images['face_fail']
# 狀態碼為2, 代表成功的表情
elif self.status_code == 2:
self.image = self.images['face_success']
# 系結圖片到螢屏
screen.blit(self.image, self.rect)
'''設定當前的按鈕的狀態'''
def setstatus(self, status_code):
self.status_code = status_code
當滑鼠點擊到這個按鈕的時,就重新開始新的游戲(無論當前的游戲狀態如何,都將重新開始新的游戲):

接下來,我們需要定義的就是下面的方格類了:
'''雷'''
class Mine(pygame.sprite.Sprite):
def __init__(self, images, position, status_code=0, **kwargs):
pygame.sprite.Sprite.__init__(self)
# 匯入圖片
self.images = images
self.image = self.images['blank']
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = position
# 雷當前的狀態
self.status_code = status_code
# 真雷還是假雷(默認是假雷)
self.is_mine_flag = False
# 周圍雷的數目
self.num_mines_around = -1
'''設定當前的狀態碼'''
def setstatus(self, status_code):
self.status_code = status_code
'''埋雷'''
def burymine(self):
self.is_mine_flag = True
'''設定周圍雷的數目'''
def setnumminesaround(self, num_mines_around):
self.num_mines_around = num_mines_around
'''畫到螢屏上'''
def draw(self, screen):
# 狀態碼為0, 代表該雷未被點擊
if self.status_code == 0:
self.image = self.images['blank']
# 狀態碼為1, 代表該雷已被點開
elif self.status_code == 1:
self.image = self.images['mine'] if self.is_mine_flag else self.images[str(self.num_mines_around)]
# 狀態碼為2, 代表該雷被玩家標記為雷
elif self.status_code == 2:
self.image = self.images['flag']
# 狀態碼為3, 代表該雷被玩家標記為問號
elif self.status_code == 3:
self.image = self.images['ask']
# 狀態碼為4, 代表該雷正在被滑鼠左右鍵雙擊
elif self.status_code == 4:
assert not self.is_mine_flag
self.image = self.images[str(self.num_mines_around)]
# 狀態碼為5, 代表該雷在被滑鼠左右鍵雙擊的雷的周圍
elif self.status_code == 5:
self.image = self.images['0']
# 狀態碼為6, 代表該雷被踩中
elif self.status_code == 6:
assert self.is_mine_flag
self.image = self.images['blood']
# 狀態碼為7, 代表該雷被誤標
elif self.status_code == 7:
assert not self.is_mine_flag
self.image = self.images['error']
# 系結圖片到螢屏
screen.blit(self.image, self.rect)
@property
def opened(self):
return self.status_code == 1
它的主要作用就是記錄游戲地圖中某個方格的狀態(比如是不是埋了雷呀,有沒有被點開呀,有沒有被標記呀之類的),
最后定義一個游戲地圖類,來把游戲地圖中的所有方格都整合在一起方便在游戲主回圈里呼叫更新:
'''掃雷地圖'''
class MinesweeperMap():
def __init__(self, cfg, images, **kwargs):
self.cfg = cfg
# 雷型矩陣
self.mines_matrix = []
for j in range(cfg.GAME_MATRIX_SIZE[1]):
mines_line = []
for i in range(cfg.GAME_MATRIX_SIZE[0]):
position = i * cfg.GRIDSIZE + cfg.BORDERSIZE, (j + 2) * cfg.GRIDSIZE
mines_line.append(Mine(images=images, position=position))
self.mines_matrix.append(mines_line)
# 隨機埋雷
for i in random.sample(range(cfg.GAME_MATRIX_SIZE[0]*cfg.GAME_MATRIX_SIZE[1]), cfg.NUM_MINES):
self.mines_matrix[i//cfg.GAME_MATRIX_SIZE[0]][i%cfg.GAME_MATRIX_SIZE[0]].burymine()
count = 0
for item in self.mines_matrix:
for i in item:
count += int(i.is_mine_flag)
# 游戲當前的狀態
self.status_code = -1
# 記錄滑鼠按下時的位置和按的鍵
self.mouse_pos = None
self.mouse_pressed = None
'''畫出當前的游戲狀態圖'''
def draw(self, screen):
for row in self.mines_matrix:
for item in row: item.draw(screen)
'''設定當前的游戲狀態'''
def setstatus(self, status_code):
# 0: 正在進行游戲, 1: 游戲結束, -1: 游戲還沒開始
self.status_code = status_code
'''根據玩家的滑鼠操作情況更新當前的游戲狀態地圖'''
def update(self, mouse_pressed=None, mouse_pos=None, type_='down'):
assert type_ in ['down', 'up']
# 記錄滑鼠按下時的位置和按的鍵
if type_ == 'down' and mouse_pos is not None and mouse_pressed is not None:
self.mouse_pos = mouse_pos
self.mouse_pressed = mouse_pressed
# 滑鼠點擊的范圍不在游戲地圖內, 無回應
if self.mouse_pos[0] < self.cfg.BORDERSIZE or self.mouse_pos[0] > self.cfg.SCREENSIZE[0] - self.cfg.BORDERSIZE or \
self.mouse_pos[1] < self.cfg.GRIDSIZE * 2 or self.mouse_pos[1] > self.cfg.SCREENSIZE[1] - self.cfg.BORDERSIZE:
return
# 滑鼠點擊在游戲地圖內, 代表開始游戲(即可以開始計時了)
if self.status_code == -1:
self.status_code = 0
# 如果不是正在游戲中, 按滑鼠是沒有用的
if self.status_code != 0:
return
# 滑鼠位置轉矩陣索引
coord_x = (self.mouse_pos[0] - self.cfg.BORDERSIZE) // self.cfg.GRIDSIZE
coord_y = self.mouse_pos[1] // self.cfg.GRIDSIZE - 2
mine_clicked = self.mines_matrix[coord_y][coord_x]
# 滑鼠按下
if type_ == 'down':
# --滑鼠左右鍵同時按下
if self.mouse_pressed[0] and self.mouse_pressed[2]:
if mine_clicked.opened and mine_clicked.num_mines_around > 0:
mine_clicked.setstatus(status_code=4)
num_flags = 0
coords_around = self.getaround(coord_y, coord_x)
for (j, i) in coords_around:
if self.mines_matrix[j][i].status_code == 2:
num_flags += 1
if num_flags == mine_clicked.num_mines_around:
for (j, i) in coords_around:
if self.mines_matrix[j][i].status_code == 0:
self.openmine(i, j)
else:
for (j, i) in coords_around:
if self.mines_matrix[j][i].status_code == 0:
self.mines_matrix[j][i].setstatus(status_code=5)
# 滑鼠釋放
else:
# --滑鼠左鍵
if self.mouse_pressed[0] and not self.mouse_pressed[2]:
if not (mine_clicked.status_code == 2 or mine_clicked.status_code == 3):
if self.openmine(coord_x, coord_y):
self.setstatus(status_code=1)
# --滑鼠右鍵
elif self.mouse_pressed[2] and not self.mouse_pressed[0]:
if mine_clicked.status_code == 0:
mine_clicked.setstatus(status_code=2)
elif mine_clicked.status_code == 2:
mine_clicked.setstatus(status_code=3)
elif mine_clicked.status_code == 3:
mine_clicked.setstatus(status_code=0)
# --滑鼠左右鍵同時按下
elif self.mouse_pressed[0] and self.mouse_pressed[2]:
mine_clicked.setstatus(status_code=1)
coords_around = self.getaround(coord_y, coord_x)
for (j, i) in coords_around:
if self.mines_matrix[j][i].status_code == 5:
self.mines_matrix[j][i].setstatus(status_code=0)
'''打開雷'''
def openmine(self, x, y):
mine_clicked = self.mines_matrix[y][x]
if mine_clicked.is_mine_flag:
for row in self.mines_matrix:
for item in row:
if not item.is_mine_flag and item.status_code == 2:
item.setstatus(status_code=7)
elif item.is_mine_flag and item.status_code == 0:
item.setstatus(status_code=1)
mine_clicked.setstatus(status_code=6)
return True
mine_clicked.setstatus(status_code=1)
coords_around = self.getaround(y, x)
num_mines = 0
for (j, i) in coords_around:
num_mines += int(self.mines_matrix[j][i].is_mine_flag)
mine_clicked.setnumminesaround(num_mines)
if num_mines == 0:
for (j, i) in coords_around:
if self.mines_matrix[j][i].num_mines_around == -1:
self.openmine(i, j)
return False
'''獲得坐標點的周圍坐標點'''
def getaround(self, row, col):
coords = []
for j in range(max(0, row-1), min(row+1, self.cfg.GAME_MATRIX_SIZE[1]-1)+1):
for i in range(max(0, col-1), min(col+1, self.cfg.GAME_MATRIX_SIZE[0]-1)+1):
if j == row and i == col:
continue
coords.append((j, i))
return coords
'''是否正在游戲中'''
@property
def gaming(self):
return self.status_code == 0
'''被標記為雷的雷數目'''
@property
def flags(self):
num_flags = 0
for row in self.mines_matrix:
for item in row: num_flags += int(item.status_code == 2)
return num_flags
'''已經打開的雷的數目'''
@property
def openeds(self):
num_openeds = 0
for row in self.mines_matrix:
for item in row: num_openeds += int(item.opened)
return num_openeds
這里只解釋幾個可能有小伙伴看不太懂的地方:
打開雷的時候我們用了遞回,作用是當點擊到的方格周圍都沒有雷的時候,系統就自動打開這個方格周圍的方格,以實作有時候點擊一個方格可以打開一大片方格的效果,這里的周圍都特指以目標方格為中心的九宮格內的所有方格;
滑鼠左右鍵一起按在已經打開的方格上的話,如果這個方格周圍的方格已經被標記為雷的數目和這個方格上顯示的數字一致,就把這個方格周圍未被標記為雷的方格都打開(所以如果你標記錯的話,一起打開的時候會顯示你游戲已經GG了),
定義完這些游戲中必要的元素類之后就在游戲主函式里實體化它們:
# 實體化游戲地圖
minesweeper_map = MinesweeperMap(cfg, images)
position = (cfg.SCREENSIZE[0] - int(cfg.GRIDSIZE * 1.25)) // 2, (cfg.GRIDSIZE * 2 - int(cfg.GRIDSIZE * 1.25)) // 2
emoji_button = EmojiButton(images, position=position)
fontsize = font.size(str(cfg.NUM_MINES))
remaining_mine_board = TextBoard(str(cfg.NUM_MINES), font, (30, (cfg.GRIDSIZE*2-fontsize[1])//2-2), cfg.RED)
fontsize = font.size('000')
time_board = TextBoard('000', font, (cfg.SCREENSIZE[0]-30-fontsize[0], (cfg.GRIDSIZE*2-fontsize[1])//2-2), cfg.RED)
time_board.is_start = False
然后寫個游戲主回圈以根據用戶的操作來更新當前的游戲狀態就可以了:
# 游戲主回圈
clock = pygame.time.Clock()
while True:
screen.fill(cfg.BACKGROUND_COLOR)
# --按鍵檢測
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = event.pos
mouse_pressed = pygame.mouse.get_pressed()
minesweeper_map.update(mouse_pressed=mouse_pressed, mouse_pos=mouse_pos, type_='down')
elif event.type == pygame.MOUSEBUTTONUP:
minesweeper_map.update(type_='up')
if emoji_button.rect.collidepoint(pygame.mouse.get_pos()):
minesweeper_map = MinesweeperMap(cfg, images)
time_board.update('000')
time_board.is_start = False
remaining_mine_board.update(str(cfg.NUM_MINES))
emoji_button.setstatus(status_code=0)
# --更新時間顯示
if minesweeper_map.gaming:
if not time_board.is_start:
start_time = time.time()
time_board.is_start = True
time_board.update(str(int(time.time() - start_time)).zfill(3))
# --更新剩余雷的數目顯示
remianing_mines = max(cfg.NUM_MINES - minesweeper_map.flags, 0)
remaining_mine_board.update(str(remianing_mines).zfill(2))
# --更新表情
if minesweeper_map.status_code == 1:
emoji_button.setstatus(status_code=1)
if minesweeper_map.openeds + minesweeper_map.flags == cfg.GAME_MATRIX_SIZE[0] * cfg.GAME_MATRIX_SIZE[1]:
minesweeper_map.status_code = 1
emoji_button.setstatus(status_code=2)
# --顯示當前的游戲狀態地圖
minesweeper_map.draw(screen)
emoji_button.draw(screen)
remaining_mine_board.draw(screen)
time_board.draw(screen)
# --更新螢屏
pygame.display.update()
clock.tick(cfg.FPS)
文章到這里就結束了,感謝你的觀看,Python24個小游戲系列,下篇文章分享消消樂小游戲
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/376840.html
標籤:Python
上一篇:演算法基礎提升學習3
