PyGame做了一个扫雷
效果
- 绿色的F表示右键点击插旗的地方
- 红色的格子表示点击到了地雷
原理
主时间循环
基本上GUI应用都会有一个主循环,用来接收各种事件,并按照时间类型进行不同的处理。
while True: for event in pygame.event.get(): if event.type == pygame.MOUSEBUTTONUP: if game.is_over(): init_game(30) continue pos = event.pos if event.button == 1: game.clicked(pos) elif event.button == 3: game.right_clicked(pos) # 获取当前那个格子被点击了 if event.type == pygame.QUIT: sys.exit(0) pygame.display.update()
主要自定义处理类
- class Game
- class Square
Game
表示一次游戏。当前游戏中所有的地雷分布;保存所有的格子信息;以及每个格子周围的地雷数量。Game点击后需要判断是否是否游戏结束。
如何获胜:
- 所有的雷都被标注
- 已经没有空白的格子可以点了
Square
表示一个格子,初始化的时候会传入是否包含地雷的信息,格子作为一圈的地雷数量。
Square本身含有一个状态机,根据点击状态和点击方式画出各种样式(空白,带数字,F,红色)等。
主循环中的事件会传给Game,最终根据位置传个某个Square。
PyGame
pygame.Screen 使用pygame.display.set_mode返回一个画布,然后可以将各种Rect,font之类的放到这画布上。
pygame.display.set_mode((total_width, total_height))
pygame.Rect 输出格子
pygame.font 输出字体
重点方法
- pygame.Screen.blit 将绘制的内容叠放到屏幕上。
self.face = pygame.Surface((self.rect.width, self.rect.height)) self.face.fill('white') game.get_screen().blit(self.face, (self.rect.left, self.rect.top)) pygame.draw.rect(self.surface, 'gray', self.rect, 1)
- pygame.display.update 刷新页面
参考
- https://www.pygame.org/news
代码
# 这是一个示例 Python 脚本。 # 按 ⌃R 执行或将其替换为您的代码。 # 按 双击 ⇧ 在所有地方搜索类、文件、工具窗口、操作和设置。 import sys import pygame import random game = None BOMB_COUNT = 80 class Game(object): screen = None row_count = 0 bomb_location = [] squares_list = [] squares_val_dict = {} win = False def __init__(self, count): pygame.init() total_width = count * 18 + 100 * 2 total_height = count * 18 + 100 * 2 self.row_count = count self.screen = pygame.display.set_mode((total_width, total_height)) self.win = False self.bomb_location = [] self.squares_list = [] self.squares_val_dict = {} def __del__(self): print('del game') pygame.display.quit() def is_over(self): return self.win def get_screen(self): return self.screen # 初始化随机产生炸弹 def random_bomb(self): # 随机产生炸弹 for i in range(BOMB_COUNT): while 1: x = random.randint(0, 29) y = random.randint(0, 29) if (x, y) in self.bomb_location: continue else: self.bomb_location.append((x, y)) break # 计算某个位置范围的数字 for x in range(self.row_count): for y in range(self.row_count): count = 0 if (x - 1, y) in self.bomb_location: count += 1 if (x - 1, y - 1) in self.bomb_location: count += 1 if (x - 1, y + 1) in self.bomb_location: count += 1 if (x , y - 1) in self.bomb_location: count += 1 if (x , y) in self.bomb_location: count += 1 if (x , y + 1) in self.bomb_location: count += 1 if (x + 1 , y - 1) in self.bomb_location: count += 1 if (x + 1 , y) in self.bomb_location: count += 1 if (x + 1, y + 1) in self.bomb_location: count += 1 # print('%s,%s %d' % (x, y, count)) self.squares_val_dict[(x, y)] = count def init_square(self): for i in range(self.row_count): self.squares_list.append([]) top = 100 + i * 18 for j in range(self.row_count): left = 100 + j * 18 width = 18 height = 18 exist = False if (i, j) in self.bomb_location: # print('%s,%s exists' % (j, i)) exist = True # 周围的炸弹数量 around_count = self.squares_val_dict.get((i, j), 0) # print('init square %s,%s %d' % (i, j, around_count)) self.squares_list[i].append(Square(exist, around_count, self.screen, left, top, width, height)) self.squares_list[i][j].draw() pygame.display.update() def start_game(self): pass # 胜利后显示的文字 def display_win(self): font = pygame.font.SysFont("Andale Mono", 32) txt = font.render("Winner Winner Winner", True, 'Red') self.get_screen().blit(txt, (200, 0)) font = pygame.font.SysFont("Andale Mono", 16) txt = font.render("uploading to dashboard...", True, 'green') self.get_screen().blit(txt, (260, 40)) font = pygame.font.SysFont("Andale Mono", 16) txt = font.render("click to continue...", True, 'gray') self.get_screen().blit(txt, (280, 60)) self.win = True def display_flag(self): for (x, y) in self.bomb_location: square = self.squares_list[x][y] square.right_click() square.draw() # 根据所有的旗帜来判断胜利 def check_win_by_flag(self): for (x, y) in self.bomb_location: square = self.squares_list[x][y] if square.state == 'flag' and square.exist: continue return False self.display_win() return True # 根据已经没有格子点击了来判断胜利 def check_win_by_click(self): # print('check by click') for x in range(self.row_count): for y in range(self.row_count): square = self.squares_list[x][y] if square.state == 'blank' or square.exist: # print(1) continue return False self.display_flag() self.display_win() return True def right_clicked(self, pos): left = pos[0] top = pos[1] x = int((top - 100) / 18) y = int((left - 100) / 18) # print('right clicked %s, %s' % (x, y)) if x in range(0, self.row_count) and y in range(0, self.row_count): square = self.squares_list[x][y] if not square.right_click(): return # 表示右键生效 square.draw() if square.state == 'flag' and square.exist: # 只有当前标记是正确的时候才判断 # 判断是否已经将所有的炸弹标记出来 self.check_win_by_flag() pygame.display.update() def clicked(self, pos): left = pos[0] top = pos[1] x = int((top - 100) / 18) y = int((left - 100) / 18) def click_square(self, x, y): if x not in range(0, self.row_count) or y not in range(0, self.row_count): return False square = self.squares_list[x][y] if square.state != 'new': return False if not square.click(): return False square.draw() if square.around_count == 0: # print('around is 0') for (x1, y1) in [ (x - 1, y), (x - 1, y - 1), (x - 1, y + 1), (x, y - 1), (x, y), (x, y + 1), (x + 1, y - 1), (x + 1, y), (x + 1, y + 1), ]: click_square(self, x1, y1) return True if x in range(0, self.row_count) and y in range(0, self.row_count): if click_square(self, x, y): # 判断是否成功 self.check_win_by_click() pygame.display.update() def refresh(self): pygame.display.update() # 扫雷中的一个格子 class Square(object): exist = False surface = None rect = None state = '' # new, blank, flag, bomed face = None around_count = 0 def __init__(self, exist, around_count, surface, left, top, width, height): self.rect = pygame.Rect(left, top, width, height) self.exist = exist self.surface = surface self.state = 'new' self.around_count = around_count # print('%s' % (self.around_count)) def exists(self): return self.exist def draw(self): global game if self.state == 'new': self.face = pygame.Surface((self.rect.width, self.rect.height)) self.face.fill('white') game.get_screen().blit(self.face, (self.rect.left, self.rect.top)) pygame.draw.rect(self.surface, 'gray', self.rect, 1) elif self.state == 'blank': self.face.fill('gray') game.get_screen().blit(self.face, (self.rect.left, self.rect.top)) pygame.draw.rect(self.surface, 'white', self.rect, 1) # 在格子中间画上数字 font = pygame.font.SysFont("Andale Mono", 16) txt = font.render("%s" % (self.around_count if self.around_count > 0 else ''), True, 'blue') # print('%s, %s' % (txt.get_rect(), self.around_count)) game.get_screen().blit(txt, (self.rect.left + 4, self.rect.top)) pass elif self.state == 'flag': # 在格子中间画上 F font = pygame.font.SysFont("Andale Mono", 16) txt = font.render("F", True, 'green') # print('%s, %s' % (txt.get_rect(), self.around_count)) game.get_screen().blit(txt, (self.rect.left + 4, self.rect.top)) elif self.state == 'boom': self.face.fill('red') game.get_screen().blit(self.face, (self.rect.left, self.rect.top)) pygame.draw.rect(self.surface, 'white', self.rect, 1) pass def click(self): need_update = False if self.state == 'new': if self.exist: self.state = 'boom' need_update = True else: self.state = 'blank' need_update = True return need_update def right_click(self): need_update = False if self.state == 'new': self.state = 'flag' need_update = True elif self.state == 'flag': self.state = 'new' need_update = True return need_update def init_game(count, x=18, y=18): global game if game: del game game = Game(count) game.random_bomb() game.init_square() # 按间距中的绿色按钮以运行脚本。 if __name__ == '__main__': init_game(30) # 主事件循环 while True: for event in pygame.event.get(): if event.type == pygame.MOUSEBUTTONUP: if game.is_over(): init_game(30) continue pos = event.pos if event.button == 1: game.clicked(pos) elif event.button == 3: game.right_clicked(pos) # 获取当前那个格子被点击了 if event.type == pygame.QUIT: sys.exit(0) pygame.display.update()