preface
Today I'll share with you a minesweeping game. There's no more nonsense. Let's start happily~
Effect display
development tool
Python version: 3.6.4
Related modules:
pygame module;
And some python built-in modules.
Environment construction
Install Python and add it to the environment variable. pip can install the relevant modules required.
Principle introduction
Here I briefly introduce the implementation idea of the game.
I believe you are no stranger to minesweeping, a classic little game of windows. Its rules are very simple:
The number in the upper left corner of the game interface represents the number of Mines buried in all squares, and the upper right corner is a timer. All you have to do is find all the thunder in the square according to the prompt.
So what's the hint? At the beginning of the game, you need to click a square, like this:
The number above represents the number of Mines buried in the Jiugong grid centered on this number. So if you have a bad character and point to ray at the beginning, the game will end directly.
ok, after understanding the rules of the game, we can start writing code. First, initialize the game:
# Game initialization pygame.init() screen = pygame.display.set_mode(cfg.SCREENSIZE) pygame.display.set_caption('mine sweeper - ilove-python') Copy code
Then import the required fonts, pictures and music:
# Import all pictures 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)) # Load font font = pygame.font.Font(cfg.FONT_PATH, cfg.FONT_SIZE) # Import and play background music pygame.mixer.music.load(cfg.BGM_PATH) pygame.mixer.music.play(-1) Copy code
Next, let's define a text board to display the number of Mines buried in the upper left corner and the game elapsed time in the upper right corner:
'''Text board''' 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 Copy code
In fact, it's very simple. Just bind the text object rendered with font to the screen, and then set an update function to update the text content in real time.
Then, we define an emoticon button class: '''Emoticon button''' class EmojiButton(pygame.sprite.Sprite): def __init__(self, images, position, status_code=0, **kwargs): pygame.sprite.Sprite.__init__(self) # import picture self.images = images self.image = self.images['face_normal'] self.rect = self.image.get_rect() self.rect.left, self.rect.top = position # The current state of the emoticon button self.status_code = status_code '''Draw to screen''' def draw(self, screen): # The status code is 0, which represents a normal expression if self.status_code == 0: self.image = self.images['face_normal'] # The status code is 1, which represents the expression of failure elif self.status_code == 1: self.image = self.images['face_fail'] # The status code is 2, which represents the expression of success elif self.status_code == 2: self.image = self.images['face_success'] # Bind picture to screen screen.blit(self.image, self.rect) '''Sets the status of the current button''' def setstatus(self, status_code): self.status_code = status_code Copy code
When the mouse clicks this button, the new game will be restarted (regardless of the current game state, the new game will be restarted):
Next, we need to define the following grid class:
'''thunder''' class Mine(pygame.sprite.Sprite): def __init__(self, images, position, status_code=0, **kwargs): pygame.sprite.Sprite.__init__(self) # import picture self.images = images self.image = self.images['blank'] self.rect = self.image.get_rect() self.rect.left, self.rect.top = position # Ray's current state self.status_code = status_code # True thunder or false thunder (false thunder by default) self.is_mine_flag = False # Number of surrounding mines self.num_mines_around = -1 '''Set current status code''' def setstatus(self, status_code): self.status_code = status_code '''Buried mine''' def burymine(self): self.is_mine_flag = True '''Set the number of surrounding mines''' def setnumminesaround(self, num_mines_around): self.num_mines_around = num_mines_around '''Draw to screen''' def draw(self, screen): # The status code is 0, which means the mine is not clicked if self.status_code == 0: self.image = self.images['blank'] # The status code is 1, indicating that the mine has been triggered elif self.status_code == 1: self.image = self.images['mine'] if self.is_mine_flag else self.images[str(self.num_mines_around)] # The status code is 2, which means that the mine is marked as mine by the player elif self.status_code == 2: self.image = self.images['flag'] # The status code is 3, which means that the mine is marked as a question mark by the player elif self.status_code == 3: self.image = self.images['ask'] # The status code is 4, which means that the mine is being double clicked by the left and right mouse buttons elif self.status_code == 4: assert not self.is_mine_flag self.image = self.images[str(self.num_mines_around)] # The status code is 5, which means that the mine is around the mine double clicked by the left and right mouse buttons elif self.status_code == 5: self.image = self.images['0'] # The status code is 6, which means the mine is stepped on elif self.status_code == 6: assert self.is_mine_flag self.image = self.images['blood'] # The status code is 7, which means that the mine is mismarked elif self.status_code == 7: assert not self.is_mine_flag self.image = self.images['error'] # Bind picture to screen screen.blit(self.image, self.rect) @property def opened(self): return self.status_code == 1 Copy code
Its main function is to record the state of a square in the game map (such as whether it is buried with thunder, whether it is clicked, whether it is marked, etc.).
Finally, define a game map class to integrate all squares in the game map, so as to call and update in the main cycle of the game:
'''Minesweeping map''' class MinesweeperMap(): def __init__(self, cfg, images, **kwargs): self.cfg = cfg # Ray type matrix 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) # Random buried mine 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) # Current state of the game self.status_code = -1 # Record the position when the mouse is pressed and the key pressed self.mouse_pos = None self.mouse_pressed = None '''Draw the current game state diagram''' def draw(self, screen): for row in self.mines_matrix: for item in row: item.draw(screen) '''Set current game status''' def setstatus(self, status_code): # 0: the game is in progress, 1: the game is over, - 1: the game has not started yet self.status_code = status_code '''Update the current game status map according to the player's mouse operation''' def update(self, mouse_pressed=None, mouse_pos=None, type_='down'): assert type_ in ['down', 'up'] # Record the position when the mouse is pressed and the key pressed 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 # The range of mouse click is not in the game map and there is no response 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 # Click the mouse in the game map to start the game (that is, you can start timing) if self.status_code == -1: self.status_code = 0 # It's no use pressing the mouse if it's not in the game if self.status_code != 0: return # Mouse position rotation matrix index 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] # Mouse down if type_ == 'down': # --Press the left and right mouse buttons at the same time 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) # Mouse release else: # --Left mouse button 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) # --Right mouse button 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) # --Press the left and right mouse buttons at the same time 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) '''Open ray''' 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 '''Obtain the surrounding coordinate points of the coordinate points''' 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 '''Are you in the game''' @property def gaming(self): return self.status_code == 0 '''Number of Mines marked as mines''' @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 '''Number of mines that have been opened''' @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 Copy code
Here we will explain only a few things that may be incomprehensible to our friends:
When opening the thunder, we use recursion. The function is that when there is no thunder around the clicked square, the system will automatically open the square around the square, so as to realize the effect that sometimes clicking a square can open a large square. The surroundings here refer to all squares in the Jiugong square centered on the target square;
Press the left and right mouse buttons together on the open grid. If the number of squares around the grid that have been marked as thunder is consistent with the number displayed on the grid, open all squares around the grid that have not been marked as thunder (so if you mark wrong, it will show that your game has been GG when you open it together).
After defining the necessary element classes in the game, instantiate them in the main function of the game:
# Instantiate game map 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 Copy code
Then write a game main loop to update the current game state according to the user's operation:
# Game main loop clock = pygame.time.Clock() while True: screen.fill(cfg.BACKGROUND_COLOR) # --Key detection 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) # --Update time display 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)) # --Update the display of the number of remaining mines remianing_mines = max(cfg.NUM_MINES - minesweeper_map.flags, 0) remaining_mine_board.update(str(remianing_mines).zfill(2)) # --Update expression 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) # --Displays the current game status map minesweeper_map.draw(screen) emoji_button.draw(screen) remaining_mine_board.draw(screen) time_board.draw(screen) # --Update screen pygame.display.update() clock.tick(cfg.FPS) Copy code
This is the end of the article. Thank you for watching. The next article shares Xiaole games
In order to thank readers, I would like to share some of my recent collection of programming dry goods with you and give back to every reader. I hope I can help you.
Dry goods mainly include: Backstage private letters I need 666
① More than 2000 Python e-books (both mainstream and classic books should be available)
② Python standard library materials (the most complete Chinese version)
③ Project source code (forty or fifty interesting and classic hand training projects and source code)
④ Videos on basic introduction to Python, crawler, web development and big data analysis (suitable for Xiaobai)
⑤ Python learning roadmap (bid farewell to non stream learning)
⑥ Two days of Python crawler training camp live rights