├── .github └── FUNDING.yml ├── README.md ├── minesweeper.py └── minesweeper_empty.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [kying18] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minesweeper 2 | 3 | This is a Python implementation of 2-D Minesweeper! 4 | 5 | Check out the tutorial here: https://youtu.be/Fjw7Lc9zlyU 6 | 7 | You start a game by running the script: 8 | ``` 9 | python3 minesweeper.py 10 | ``` 11 | (If you do not edit the parameters in the script, the script will automatically initialize it to a 10x10 board, with 10 bombs) 12 | 13 | Note that the inputs must be that the number of bombs is less than the total number of spaces (n^2). 14 | 15 | For now, this script does not have a GUI and you can use terminal :D (If you want to make a GUI, feel free to make a pull request) 16 | 17 | In order to "dig" at a certain location, you type in the index of the row, then the column, separated by a comma (whitespace optional). The game "digs" recursively around that location if there are no bombs nearby. 18 | 19 | You can continue digging until either you hit a bomb (which is game over) or you've successfully dug up all n-b non-bomb locations (which is victory)! 20 | 21 | This repo contains two files: 22 | - minesweeper.py: implementation of minesweeper 23 | - minesweeper_empty.py: empty code template for you to start somewhere :) 24 | 25 | 26 | YouTube Kylie Ying: https://www.youtube.com/ycubed 27 | Twitch KylieYing: https://www.twitch.tv/kylieying 28 | Twitter @kylieyying: https://twitter.com/kylieyying 29 | Instagram @kylieyying: https://www.instagram.com/kylieyying/ 30 | Website: https://www.kylieying.com 31 | Github: https://www.github.com/kying18 32 | Programmer Beast Mode Spotify playlist: https://open.spotify.com/playlist/4Akns5EUb3gzmlXIdsJkPs?si=qGc4ubKRRYmPHAJAIrCxVQ 33 | -------------------------------------------------------------------------------- /minesweeper.py: -------------------------------------------------------------------------------- 1 | """ 2 | Implementation of command-line minesweeper by Kylie Ying 3 | 4 | YouTube Kylie Ying: https://www.youtube.com/ycubed 5 | Twitch KylieYing: https://www.twitch.tv/kylieying 6 | Twitter @kylieyying: https://twitter.com/kylieyying 7 | Instagram @kylieyying: https://www.instagram.com/kylieyying/ 8 | Website: https://www.kylieying.com 9 | Github: https://www.github.com/kying18 10 | Programmer Beast Mode Spotify playlist: https://open.spotify.com/playlist/4Akns5EUb3gzmlXIdsJkPs?si=qGc4ubKRRYmPHAJAIrCxVQ 11 | 12 | Project specs, files, code all over the place? Start using Backlog for efficient management!! There is a free tier: https://cutt.ly/ehxImv5 13 | """ 14 | 15 | import random 16 | import re 17 | 18 | # lets create a board object to represent the minesweeper game 19 | # this is so that we can just say "create a new board object", or 20 | # "dig here", or "render this game for this object" 21 | class Board: 22 | def __init__(self, dim_size, num_bombs): 23 | # let's keep track of these parameters. they'll be helpful later 24 | self.dim_size = dim_size 25 | self.num_bombs = num_bombs 26 | 27 | # let's create the board 28 | # helper function! 29 | self.board = self.make_new_board() # plant the bombs 30 | self.assign_values_to_board() 31 | 32 | # initialize a set to keep track of which locations we've uncovered 33 | # we'll save (row,col) tuples into this set 34 | self.dug = set() # if we dig at 0, 0, then self.dug = {(0,0)} 35 | 36 | def make_new_board(self): 37 | # construct a new board based on the dim size and num bombs 38 | # we should construct the list of lists here (or whatever representation you prefer, 39 | # but since we have a 2-D board, list of lists is most natural) 40 | 41 | # generate a new board 42 | board = [[None for _ in range(self.dim_size)] for _ in range(self.dim_size)] 43 | # this creates an array like this: 44 | # [[None, None, ..., None], 45 | # [None, None, ..., None], 46 | # [... ], 47 | # [None, None, ..., None]] 48 | # we can see how this represents a board! 49 | 50 | # plant the bombs 51 | bombs_planted = 0 52 | while bombs_planted < self.num_bombs: 53 | loc = random.randint(0, self.dim_size**2 - 1) # return a random integer N such that a <= N <= b 54 | row = loc // self.dim_size # we want the number of times dim_size goes into loc to tell us what row to look at 55 | col = loc % self.dim_size # we want the remainder to tell us what index in that row to look at 56 | 57 | if board[row][col] == '*': 58 | # this means we've actually planted a bomb there already so keep going 59 | continue 60 | 61 | board[row][col] = '*' # plant the bomb 62 | bombs_planted += 1 63 | 64 | return board 65 | 66 | def assign_values_to_board(self): 67 | # now that we have the bombs planted, let's assign a number 0-8 for all the empty spaces, which 68 | # represents how many neighboring bombs there are. we can precompute these and it'll save us some 69 | # effort checking what's around the board later on :) 70 | for r in range(self.dim_size): 71 | for c in range(self.dim_size): 72 | if self.board[r][c] == '*': 73 | # if this is already a bomb, we don't want to calculate anything 74 | continue 75 | self.board[r][c] = self.get_num_neighboring_bombs(r, c) 76 | 77 | def get_num_neighboring_bombs(self, row, col): 78 | # let's iterate through each of the neighboring positions and sum number of bombs 79 | # top left: (row-1, col-1) 80 | # top middle: (row-1, col) 81 | # top right: (row-1, col+1) 82 | # left: (row, col-1) 83 | # right: (row, col+1) 84 | # bottom left: (row+1, col-1) 85 | # bottom middle: (row+1, col) 86 | # bottom right: (row+1, col+1) 87 | 88 | # make sure to not go out of bounds! 89 | 90 | num_neighboring_bombs = 0 91 | for r in range(max(0, row-1), min(self.dim_size-1, row+1)+1): 92 | for c in range(max(0, col-1), min(self.dim_size-1, col+1)+1): 93 | if r == row and c == col: 94 | # our original location, don't check 95 | continue 96 | if self.board[r][c] == '*': 97 | num_neighboring_bombs += 1 98 | 99 | return num_neighboring_bombs 100 | 101 | def dig(self, row, col): 102 | # dig at that location! 103 | # return True if successful dig, False if bomb dug 104 | 105 | # a few scenarios: 106 | # hit a bomb -> game over 107 | # dig at location with neighboring bombs -> finish dig 108 | # dig at location with no neighboring bombs -> recursively dig neighbors! 109 | 110 | self.dug.add((row, col)) # keep track that we dug here 111 | 112 | if self.board[row][col] == '*': 113 | return False 114 | elif self.board[row][col] > 0: 115 | return True 116 | 117 | # self.board[row][col] == 0 118 | for r in range(max(0, row-1), min(self.dim_size-1, row+1)+1): 119 | for c in range(max(0, col-1), min(self.dim_size-1, col+1)+1): 120 | if (r, c) in self.dug: 121 | continue # don't dig where you've already dug 122 | self.dig(r, c) 123 | 124 | # if our initial dig didn't hit a bomb, we *shouldn't* hit a bomb here 125 | return True 126 | 127 | def __str__(self): 128 | # this is a magic function where if you call print on this object, 129 | # it'll print out what this function returns! 130 | # return a string that shows the board to the player 131 | 132 | # first let's create a new array that represents what the user would see 133 | visible_board = [[None for _ in range(self.dim_size)] for _ in range(self.dim_size)] 134 | for row in range(self.dim_size): 135 | for col in range(self.dim_size): 136 | if (row,col) in self.dug: 137 | visible_board[row][col] = str(self.board[row][col]) 138 | else: 139 | visible_board[row][col] = ' ' 140 | 141 | # put this together in a string 142 | string_rep = '' 143 | # get max column widths for printing 144 | widths = [] 145 | for idx in range(self.dim_size): 146 | columns = map(lambda x: x[idx], visible_board) 147 | widths.append( 148 | len( 149 | max(columns, key = len) 150 | ) 151 | ) 152 | 153 | # print the csv strings 154 | indices = [i for i in range(self.dim_size)] 155 | indices_row = ' ' 156 | cells = [] 157 | for idx, col in enumerate(indices): 158 | format = '%-' + str(widths[idx]) + "s" 159 | cells.append(format % (col)) 160 | indices_row += ' '.join(cells) 161 | indices_row += ' \n' 162 | 163 | for i in range(len(visible_board)): 164 | row = visible_board[i] 165 | string_rep += f'{i} |' 166 | cells = [] 167 | for idx, col in enumerate(row): 168 | format = '%-' + str(widths[idx]) + "s" 169 | cells.append(format % (col)) 170 | string_rep += ' |'.join(cells) 171 | string_rep += ' |\n' 172 | 173 | str_len = int(len(string_rep) / self.dim_size) 174 | string_rep = indices_row + '-'*str_len + '\n' + string_rep + '-'*str_len 175 | 176 | return string_rep 177 | 178 | # play the game 179 | def play(dim_size=10, num_bombs=10): 180 | # Step 1: create the board and plant the bombs 181 | board = Board(dim_size, num_bombs) 182 | 183 | # Step 2: show the user the board and ask for where they want to dig 184 | # Step 3a: if location is a bomb, show game over message 185 | # Step 3b: if location is not a bomb, dig recursively until each square is at least 186 | # next to a bomb 187 | # Step 4: repeat steps 2 and 3a/b until there are no more places to dig -> VICTORY! 188 | safe = True 189 | 190 | while len(board.dug) < board.dim_size ** 2 - num_bombs: 191 | print(board) 192 | # 0,0 or 0, 0 or 0, 0 193 | user_input = re.split(',(\\s)*', input("Where would you like to dig? Input as row,col: ")) # '0, 3' 194 | row, col = int(user_input[0]), int(user_input[-1]) 195 | if row < 0 or row >= board.dim_size or col < 0 or col >= dim_size: 196 | print("Invalid location. Try again.") 197 | continue 198 | 199 | # if it's valid, we dig 200 | safe = board.dig(row, col) 201 | if not safe: 202 | # dug a bomb ahhhhhhh 203 | break # (game over rip) 204 | 205 | # 2 ways to end loop, lets check which one 206 | if safe: 207 | print("CONGRATULATIONS!!!! YOU ARE VICTORIOUS!") 208 | else: 209 | print("SORRY GAME OVER :(") 210 | # let's reveal the whole board! 211 | board.dug = [(r,c) for r in range(board.dim_size) for c in range(board.dim_size)] 212 | print(board) 213 | 214 | if __name__ == '__main__': # good practice :) 215 | play() 216 | -------------------------------------------------------------------------------- /minesweeper_empty.py: -------------------------------------------------------------------------------- 1 | """ 2 | Empty minesweeper template by Kylie Ying 3 | 4 | YouTube Kylie Ying: https://www.youtube.com/ycubed 5 | Twitch KylieYing: https://www.twitch.tv/kylieying 6 | Twitter @kylieyying: https://twitter.com/kylieyying 7 | Instagram @kylieyying: https://www.instagram.com/kylieyying/ 8 | Website: https://www.kylieying.com 9 | Github: https://www.github.com/kying18 10 | Programmer Beast Mode Spotify playlist: https://open.spotify.com/playlist/4Akns5EUb3gzmlXIdsJkPs?si=qGc4ubKRRYmPHAJAIrCxVQ 11 | 12 | Project specs, files, code all over the place? Start using Backlog for efficient management!! There is a free tier: https://cutt.ly/ehxImv5 13 | """ 14 | 15 | import random 16 | import re 17 | 18 | # Creating a board object to represent the minesweeper board 19 | # This is so that when we code up the game, we can just say "create a new board object" 20 | # and dig on that board, etc. 21 | class Board: 22 | def __init__(self, dim_size, num_bombs): 23 | # keep track of these parameters because we might find them helpful later on 24 | self.dim_size = dim_size 25 | self.num_bombs = num_bombs 26 | 27 | # get the board 28 | self.board = self.make_new_board() 29 | self.assign_values_to_board() 30 | 31 | # initialize a set to keep track of which locations we've uncovered 32 | # we will put (row,col) tuples into these sets 33 | self.dug = set() 34 | 35 | def make_new_board(self): 36 | # construct a new board based on the dim size and num bombs 37 | # we should construct the list of lists here (or whatever representation you prefer, 38 | # but since we have a 2-D board, list of lists is most natural) 39 | return [] # change this 40 | 41 | def assign_values_to_board(self): 42 | # now that we have the bombs planted, let's assign a number 0-8 for all the empty spaces, which 43 | # represents how many neighboring bombs there are. we can precompute these and it'll save us some 44 | # effort checking what's around the board later on :) 45 | pass 46 | 47 | def get_num_neighboring_bombs(self, row, col): 48 | # let's iterate through each of the neighboring positions and sum number of bombs 49 | # top left: (row-1, col-1) 50 | # top middle: (row-1, col) 51 | # top right: (row-1, col+1) 52 | # left: (row, col-1) 53 | # right: (row, col+1) 54 | # bottom left: (row+1, col-1) 55 | # bottom middle: (row+1, col) 56 | # bottom right: (row+1, col+1) 57 | 58 | # ps we need to make sure we don't go out of bounds!! 59 | pass 60 | 61 | def dig(self, row, col): 62 | # dig at that location! 63 | # return True if successful dig, False if bomb dug 64 | 65 | # a couple of scenarios to consider: 66 | # hit a bomb -> game over 67 | # dig at a location with neighboring bombs -> finish dig 68 | # dig at a location with no neighboring bombs -> recursively dig neighbors! 69 | pass 70 | 71 | def __str__(self): 72 | # return a string that shows the board to the player 73 | # note: this part is kinda hard to get the formatting right, you don't have to do it the same way 74 | # i did 75 | # you can also just copy and paste from the implementation 76 | # this part is not that important to understanding the logic of the code :) 77 | return '' 78 | 79 | def play(dim_size=10, num_bombs=10): 80 | # Step 1: create the board and plant the bombs 81 | # Step 2: show the user the board and ask for where they want to dig 82 | # Step 3a: if the location is a bomb, then show game over message 83 | # Step 3b: if the location is not a bomb, dig recursively until one of the squares is next to a bomb 84 | # Step 4: repeat steps 2 and 3a/b until there are no more places to dig, then show victory 85 | pass 86 | 87 | if __name__=='__main__': 88 | play() 89 | 90 | --------------------------------------------------------------------------------