|
| 1 | +""" |
| 2 | +Implementation of command-line minesweeper by Sai Rahul |
| 3 | +""" |
| 4 | + |
| 5 | +import random |
| 6 | +import re |
| 7 | + |
| 8 | +# lets create a board object to represent the minesweeper game |
| 9 | +# this is so that we can just say "create a new board object", or |
| 10 | +# "dig here", or "render this game for this object" |
| 11 | +class Board: |
| 12 | + def __init__(self, dim_size, num_bombs): |
| 13 | + # let's keep track of these parameters. they'll be helpful later |
| 14 | + self.dim_size = dim_size |
| 15 | + self.num_bombs = num_bombs |
| 16 | + |
| 17 | + # let's create the board |
| 18 | + # helper function! |
| 19 | + self.board = self.make_new_board() # plant the bombs |
| 20 | + self.assign_values_to_board() |
| 21 | + |
| 22 | + # initialize a set to keep track of which locations we've uncovered |
| 23 | + # we'll save (row,col) tuples into this set |
| 24 | + self.dug = set() # if we dig at 0, 0, then self.dug = {(0,0)} |
| 25 | + |
| 26 | + def make_new_board(self): |
| 27 | + # construct a new board based on the dim size and num bombs |
| 28 | + # we should construct the list of lists here (or whatever representation you prefer, |
| 29 | + # but since we have a 2-D board, list of lists is most natural) |
| 30 | + |
| 31 | + # generate a new board |
| 32 | + board = [[None for _ in range(self.dim_size)] for _ in range(self.dim_size)] |
| 33 | + # this creates an array like this: |
| 34 | + # [[None, None, ..., None], |
| 35 | + # [None, None, ..., None], |
| 36 | + # [... ], |
| 37 | + # [None, None, ..., None]] |
| 38 | + # we can see how this represents a board! |
| 39 | + |
| 40 | + # plant the bombs |
| 41 | + bombs_planted = 0 |
| 42 | + while bombs_planted < self.num_bombs: |
| 43 | + loc = random.randint(0, self.dim_size**2 - 1) # return a random integer N such that a <= N <= b |
| 44 | + row = loc // self.dim_size # we want the number of times dim_size goes into loc to tell us what row to look at |
| 45 | + col = loc % self.dim_size # we want the remainder to tell us what index in that row to look at |
| 46 | + |
| 47 | + if board[row][col] == '*': |
| 48 | + # this means we've actually planted a bomb there already so keep going |
| 49 | + continue |
| 50 | + |
| 51 | + board[row][col] = '*' # plant the bomb |
| 52 | + bombs_planted += 1 |
| 53 | + |
| 54 | + return board |
| 55 | + |
| 56 | + def assign_values_to_board(self): |
| 57 | + # now that we have the bombs planted, let's assign a number 0-8 for all the empty spaces, which |
| 58 | + # represents how many neighboring bombs there are. we can precompute these and it'll save us some |
| 59 | + # effort checking what's around the board later on :) |
| 60 | + for r in range(self.dim_size): |
| 61 | + for c in range(self.dim_size): |
| 62 | + if self.board[r][c] == '*': |
| 63 | + # if this is already a bomb, we don't want to calculate anything |
| 64 | + continue |
| 65 | + self.board[r][c] = self.get_num_neighboring_bombs(r, c) |
| 66 | + |
| 67 | + def get_num_neighboring_bombs(self, row, col): |
| 68 | + # let's iterate through each of the neighboring positions and sum number of bombs |
| 69 | + # top left: (row-1, col-1) |
| 70 | + # top middle: (row-1, col) |
| 71 | + # top right: (row-1, col+1) |
| 72 | + # left: (row, col-1) |
| 73 | + # right: (row, col+1) |
| 74 | + # bottom left: (row+1, col-1) |
| 75 | + # bottom middle: (row+1, col) |
| 76 | + # bottom right: (row+1, col+1) |
| 77 | + |
| 78 | + # make sure to not go out of bounds! |
| 79 | + |
| 80 | + num_neighboring_bombs = 0 |
| 81 | + for r in range(max(0, row-1), min(self.dim_size-1, row+1)+1): |
| 82 | + for c in range(max(0, col-1), min(self.dim_size-1, col+1)+1): |
| 83 | + if r == row and c == col: |
| 84 | + # our original location, don't check |
| 85 | + continue |
| 86 | + if self.board[r][c] == '*': |
| 87 | + num_neighboring_bombs += 1 |
| 88 | + |
| 89 | + return num_neighboring_bombs |
| 90 | + |
| 91 | + def dig(self, row, col): |
| 92 | + # dig at that location! |
| 93 | + # return True if successful dig, False if bomb dug |
| 94 | + |
| 95 | + # a few scenarios: |
| 96 | + # hit a bomb -> game over |
| 97 | + # dig at location with neighboring bombs -> finish dig |
| 98 | + # dig at location with no neighboring bombs -> recursively dig neighbors! |
| 99 | + |
| 100 | + self.dug.add((row, col)) # keep track that we dug here |
| 101 | + |
| 102 | + if self.board[row][col] == '*': |
| 103 | + return False |
| 104 | + elif self.board[row][col] > 0: |
| 105 | + return True |
| 106 | + |
| 107 | + # self.board[row][col] == 0 |
| 108 | + for r in range(max(0, row-1), min(self.dim_size-1, row+1)+1): |
| 109 | + for c in range(max(0, col-1), min(self.dim_size-1, col+1)+1): |
| 110 | + if (r, c) in self.dug: |
| 111 | + continue # don't dig where you've already dug |
| 112 | + self.dig(r, c) |
| 113 | + |
| 114 | + # if our initial dig didn't hit a bomb, we *shouldn't* hit a bomb here |
| 115 | + return True |
| 116 | + |
| 117 | + def __str__(self): |
| 118 | + # this is a magic function where if you call print on this object, |
| 119 | + # it'll print out what this function returns! |
| 120 | + # return a string that shows the board to the player |
| 121 | + |
| 122 | + # first let's create a new array that represents what the user would see |
| 123 | + visible_board = [[None for _ in range(self.dim_size)] for _ in range(self.dim_size)] |
| 124 | + for row in range(self.dim_size): |
| 125 | + for col in range(self.dim_size): |
| 126 | + if (row,col) in self.dug: |
| 127 | + visible_board[row][col] = str(self.board[row][col]) |
| 128 | + else: |
| 129 | + visible_board[row][col] = ' ' |
| 130 | + |
| 131 | + # put this together in a string |
| 132 | + string_rep = '' |
| 133 | + # get max column widths for printing |
| 134 | + widths = [] |
| 135 | + for idx in range(self.dim_size): |
| 136 | + columns = map(lambda x: x[idx], visible_board) |
| 137 | + widths.append( |
| 138 | + len( |
| 139 | + max(columns, key = len) |
| 140 | + ) |
| 141 | + ) |
| 142 | + |
| 143 | + # print the csv strings |
| 144 | + indices = [i for i in range(self.dim_size)] |
| 145 | + indices_row = ' ' |
| 146 | + cells = [] |
| 147 | + for idx, col in enumerate(indices): |
| 148 | + format = '%-' + str(widths[idx]) + "s" |
| 149 | + cells.append(format % (col)) |
| 150 | + indices_row += ' '.join(cells) |
| 151 | + indices_row += ' \n' |
| 152 | + |
| 153 | + for i in range(len(visible_board)): |
| 154 | + row = visible_board[i] |
| 155 | + string_rep += f'{i} |' |
| 156 | + cells = [] |
| 157 | + for idx, col in enumerate(row): |
| 158 | + format = '%-' + str(widths[idx]) + "s" |
| 159 | + cells.append(format % (col)) |
| 160 | + string_rep += ' |'.join(cells) |
| 161 | + string_rep += ' |\n' |
| 162 | + |
| 163 | + str_len = int(len(string_rep) / self.dim_size) |
| 164 | + string_rep = indices_row + '-'*str_len + '\n' + string_rep + '-'*str_len |
| 165 | + |
| 166 | + return string_rep |
| 167 | + |
| 168 | +# play the game |
| 169 | +def play(dim_size=10, num_bombs=10): |
| 170 | + # Step 1: create the board and plant the bombs |
| 171 | + board = Board(dim_size, num_bombs) |
| 172 | + |
| 173 | + # Step 2: show the user the board and ask for where they want to dig |
| 174 | + # Step 3a: if location is a bomb, show game over message |
| 175 | + # Step 3b: if location is not a bomb, dig recursively until each square is at least |
| 176 | + # next to a bomb |
| 177 | + # Step 4: repeat steps 2 and 3a/b until there are no more places to dig -> VICTORY! |
| 178 | + safe = True |
| 179 | + |
| 180 | + while len(board.dug) < board.dim_size ** 2 - num_bombs: |
| 181 | + print(board) |
| 182 | + # 0,0 or 0, 0 or 0, 0 |
| 183 | + user_input = re.split(',(\\s)*', input("Where would you like to dig? Input as row,col: ")) # '0, 3' |
| 184 | + row, col = int(user_input[0]), int(user_input[-1]) |
| 185 | + if row < 0 or row >= board.dim_size or col < 0 or col >= dim_size: |
| 186 | + print("Invalid location. Try again.") |
| 187 | + continue |
| 188 | + |
| 189 | + # if it's valid, we dig |
| 190 | + safe = board.dig(row, col) |
| 191 | + if not safe: |
| 192 | + # dug a bomb ahhhhhhh |
| 193 | + break # (game over rip) |
| 194 | + |
| 195 | + # 2 ways to end loop, lets check which one |
| 196 | + if safe: |
| 197 | + print("CONGRATULATIONS!!!! YOU ARE VICTORIOUS!") |
| 198 | + else: |
| 199 | + print("SORRY GAME OVER :(") |
| 200 | + # let's reveal the whole board! |
| 201 | + board.dug = [(r,c) for r in range(board.dim_size) for c in range(board.dim_size)] |
| 202 | + print(board) |
| 203 | + |
| 204 | +if __name__ == '__main__': # good practice :) |
| 205 | + play() |
0 commit comments