commit 877ea522a05b6443d8168bfb5f4abf22c4cfbef2 Author: filifa Date: Tue Mar 19 20:38:20 2024 -0500 Initial commit diff --git a/board.py b/board.py new file mode 100644 index 0000000..acfc06a --- /dev/null +++ b/board.py @@ -0,0 +1,157 @@ +import pygame +import random +from square import Square + +class Board: + def __init__(self, size, square_size, surface): + self.surface = surface + self.size = size + self.square_size = square_size + self.game_lost = False + self.game_won = False + self.total_flags = 0 + self.coords = (0,50) + + self.unclicked_squares = size[0] * size[1] + self.total_mines = round(.2*self.unclicked_squares) + + # Makes a 2D list of Square objects + start_x = self.coords[0] + start_y = self.coords[1] + end_x = start_x + self.square_size*size[1] + end_y = start_y + self.square_size*size[0] + self.squares = [[Square(self, i, j) + for i in range(start_x, end_x, self.square_size)] + for j in range(start_y, end_y, self.square_size)] + + # Font object for mine counter and timer + self.game_font = pygame.font.Font('freesansbold.ttf', 48) + + def update_mine_counter(self): + # Makes the mine counter the right size + self.mine_counter = self.game_font.render("99", True, (255,0,0)) + self.counter_loc = self.mine_counter.get_rect() + self.counter_loc.bottomleft = self.coords + + # Makes the background of the counter black + self.mine_counter.fill((0,0,0)) + self.surface.blit(self.mine_counter, self.counter_loc) + + mines_left = str(self.total_mines - self.total_flags) + if len(mines_left) == 1: + mines_left = "0" + mines_left + self.mine_counter = self.game_font.render(mines_left, True, (255,0,0)) + self.counter_loc = self.mine_counter.get_rect() + self.counter_loc.bottomleft = self.coords + self.surface.blit(self.mine_counter, self.counter_loc) + + def check_for_win(self): + only_mines_left = self.total_mines == self.unclicked_squares + if not self.game_lost and only_mines_left: + self.game_won = True + self.flag_remaining_mines() + + def show_unflagged_mines(self): + for row in self.squares: + for s in row: + if s.is_mine and not s.is_flagged: + s.reveal() + elif not s.is_mine and s.is_flagged: + s.draw_line() + + def flag_remaining_mines(self): + for row in self.squares: + for s in row: + if s.is_mine and s.is_questioned: + s.cycle_flag() + if s.is_mine and not s.is_flagged: + s.cycle_flag() + + def get_clicked_square(self, mousepos): + mousex, mousey = mousepos + for row in self.squares: + for s in row: + sx, sy = s.coords + too_far_right = sx+self.square_size < mousex + too_far_up = sy+self.square_size < mousey + too_far_down = sy > mousey + if not (too_far_right or too_far_up or too_far_down): + return s + return None + + def get_index(self, s): + for row in self.squares: + if s in row: + return (self.squares.index(row), row.index(s)) + + # Places mines in such a way that the first space clicked will be a 0 space + # Returns False if a mine was clicked, returns True otherwise + def place_mines(self, mousepos): + square = self.get_clicked_square(mousepos) + if square is None: + return True + adj_squares = self.squares_adjacent_to(square) + free_spaces = self.unclicked_squares-self.total_mines-1-len(adj_squares) + is_mine = ([True]*self.total_mines + [False]*free_spaces) + random.shuffle(is_mine) + for row in self.squares: + for s in row: + if s is not square and s not in adj_squares: + s.is_mine = is_mine.pop() + for row in self.squares: + for s in row: + adj_squares = self.squares_adjacent_to(s) + total_adj_mines = sum([i.is_mine for i in adj_squares]) + s.mines_touching = total_adj_mines + return False + + def squares_adjacent_to(self, s): + x, y = self.get_index(s) + adj_indices = [(x-1,y-1), (x,y-1), (x+1,y-1), + (x-1,y), (x+1,y), + (x+1,y+1), (x,y+1), (x-1,y+1)] + adj_squares = [self.squares[i[0]][i[1]] for i in adj_indices + if 0 <= i[0] < self.size[0] and 0 <= i[1] < self.size[1]] + return adj_squares + + def open(self, s): + if s.is_clicked or s.is_flagged: + return + s.reveal() + if s.is_clicked: + self.unclicked_squares -= 1 + if s.is_mine and s.is_clicked: + self.game_lost = True + self.show_unflagged_mines() + elif not s.is_mine and s.is_clicked and s.mines_touching == 0: + self.open_squares_adjacent_to(s) + + def open_squares_adjacent_to(self, square): + adj_squares = self.squares_adjacent_to(square) + counter = 0 + for s in adj_squares: + if s.is_flagged: + counter += 1 + if counter != square.mines_touching: + return + for s in adj_squares: + self.open(s) + + def left_click(self, mousepos): + square = self.get_clicked_square(mousepos) + if square is None: + return + elif square.is_clicked: + self.open_squares_adjacent_to(square) + return + self.open(square) + + def right_click(self, mousepos): + square = self.get_clicked_square(mousepos) + if square is None: + return + square.cycle_flag() + if square.is_flagged: + self.total_flags += 1 + elif square.is_questioned: + self.total_flags -= 1 diff --git a/main.py b/main.py new file mode 100644 index 0000000..9d54105 --- /dev/null +++ b/main.py @@ -0,0 +1,48 @@ +import pygame +import sys +from pygame.locals import * +from square import Square +from board import Board + +pygame.init() +# Pygame/ALSA has a bug that results in high CPU usage. This line reduces the +# CPU usage +pygame.mixer.quit() + +DISPLAYSURF = pygame.display.set_mode((1280, 720)) +DISPLAYSURF.fill((185,185,185)) +pygame.display.set_caption("Minesweeper") + +board = Board((16, 31), 40, DISPLAYSURF) +# board = Board((9, 10), 48, DISPLAYSURF) + +# Prevents mouse movement from counting as an event, reducing CPU usage +pygame.event.set_blocked(MOUSEMOTION) + +first_left_click = True +while True: + board.update_mine_counter() + board.check_for_win() + pygame.display.update() + + # TODO: Add options to play again + if board.game_lost: + print("You lose") + break + elif board.game_won: + print("You win") + break + + # Program waits here until a non-mouse movement event is read + event = pygame.event.wait() + + if event.type == QUIT: + pygame.quit() + sys.exit() + elif event.type == MOUSEBUTTONUP and event.button == 1: + # FIXME: Need a check to make sure a square was actually clicked + if first_left_click: + first_left_click = board.place_mines(event.pos) + board.left_click(event.pos) + elif event.type == MOUSEBUTTONUP and event.button == 3: + board.right_click(event.pos) diff --git a/png/Minesweeper_0.png b/png/Minesweeper_0.png new file mode 100644 index 0000000..9481b3d Binary files /dev/null and b/png/Minesweeper_0.png differ diff --git a/png/Minesweeper_1.png b/png/Minesweeper_1.png new file mode 100644 index 0000000..7ab7880 Binary files /dev/null and b/png/Minesweeper_1.png differ diff --git a/png/Minesweeper_2.png b/png/Minesweeper_2.png new file mode 100644 index 0000000..418748c Binary files /dev/null and b/png/Minesweeper_2.png differ diff --git a/png/Minesweeper_3.png b/png/Minesweeper_3.png new file mode 100644 index 0000000..04c03e9 Binary files /dev/null and b/png/Minesweeper_3.png differ diff --git a/png/Minesweeper_4.png b/png/Minesweeper_4.png new file mode 100644 index 0000000..7df1367 Binary files /dev/null and b/png/Minesweeper_4.png differ diff --git a/png/Minesweeper_5.png b/png/Minesweeper_5.png new file mode 100644 index 0000000..aec0d37 Binary files /dev/null and b/png/Minesweeper_5.png differ diff --git a/png/Minesweeper_6.png b/png/Minesweeper_6.png new file mode 100644 index 0000000..7f333f8 Binary files /dev/null and b/png/Minesweeper_6.png differ diff --git a/png/Minesweeper_7.png b/png/Minesweeper_7.png new file mode 100644 index 0000000..90a368e Binary files /dev/null and b/png/Minesweeper_7.png differ diff --git a/png/Minesweeper_8.png b/png/Minesweeper_8.png new file mode 100644 index 0000000..72f0a4a Binary files /dev/null and b/png/Minesweeper_8.png differ diff --git a/png/Minesweeper_flag.png b/png/Minesweeper_flag.png new file mode 100644 index 0000000..1f732fd Binary files /dev/null and b/png/Minesweeper_flag.png differ diff --git a/png/Minesweeper_questionmark.png b/png/Minesweeper_questionmark.png new file mode 100644 index 0000000..aa2be79 Binary files /dev/null and b/png/Minesweeper_questionmark.png differ diff --git a/png/Minesweeper_unopened_square.png b/png/Minesweeper_unopened_square.png new file mode 100644 index 0000000..c400f03 Binary files /dev/null and b/png/Minesweeper_unopened_square.png differ diff --git a/png/bomb.png b/png/bomb.png new file mode 100644 index 0000000..3f73cb5 Binary files /dev/null and b/png/bomb.png differ diff --git a/square.py b/square.py new file mode 100644 index 0000000..f1eed6f --- /dev/null +++ b/square.py @@ -0,0 +1,69 @@ +import pygame + +class Square: + def __init__(self, board, x, y): + self.board = board + self.surface = board.surface + self.size = board.square_size + + self.is_clicked = False + self.is_mine = False + self.is_flagged = False + self.is_questioned = False + + self.mines_touching = 0 + self.coords = (x, y) + self.set_img("png/Minesweeper_unopened_square.png") + + def draw_line(self): + RED = (255,0,0) + top_left = self.coords + bottom_right = (self.coords[0]+self.size, self.coords[1]+self.size) + pygame.draw.line(self.surface, RED, top_left, bottom_right, 10) + + def update_img(self): + if self.is_mine: + self.set_img("png/bomb.png") + elif self.mines_touching == 0: + self.set_img("png/Minesweeper_0.png") + elif self.mines_touching == 1: + self.set_img("png/Minesweeper_1.png") + elif self.mines_touching == 2: + self.set_img("png/Minesweeper_2.png") + elif self.mines_touching == 3: + self.set_img("png/Minesweeper_3.png") + elif self.mines_touching == 4: + self.set_img("png/Minesweeper_4.png") + elif self.mines_touching == 5: + self.set_img("png/Minesweeper_5.png") + elif self.mines_touching == 6: + self.set_img("png/Minesweeper_6.png") + elif self.mines_touching == 7: + self.set_img("png/Minesweeper_7.png") + elif self.mines_touching == 8: + self.set_img("png/Minesweeper_8.png") + + def set_img(self, img): + self.img = pygame.image.load(img) + self.img = pygame.transform.scale(self.img, (self.size, self.size)) + self.surface.blit(self.img, self.coords) + + def reveal(self): + if not self.is_flagged and not self.is_clicked: + self.is_clicked = True + self.is_questioned = False + self.update_img() + + def cycle_flag(self): + if self.is_clicked: + return + elif self.is_flagged: + self.set_img("png/Minesweeper_questionmark.png") + self.is_flagged = False + self.is_questioned = True + elif self.is_questioned: + self.set_img("png/Minesweeper_unopened_square.png") + self.is_questioned = False + else: + self.set_img("png/Minesweeper_flag.png") + self.is_flagged = True