├── README.md ├── minebash └── pattern /README.md: -------------------------------------------------------------------------------- 1 | # minebash 2 | 3 | minebash is a minesweeper implementation in Python for the Linux command line. 4 | ![minebash screenshot](https://i.imgur.com/dADsjEh.png "it's fun - IGN 10/10") 5 | 6 | ## Features 7 | 8 | 1. Color 9 | 2. User specified field size & minecounts 10 | 3. Difficulty presets 11 | 12 | ## Dependencies 13 | 14 | - Python for Linux (requires Curses) 15 | 16 | ## Setup 17 | 18 | ``` 19 | git clone https://github.com/panki27/minebash.git 20 | cd minebash 21 | chmod +x minebash 22 | ./minebash 23 | ``` 24 | 25 | ## Controls 26 | - Arrow Keys: Move Cursor 27 | - Spacebar: Check field 28 | - F: Place flag 29 | 30 | ## Command line options 31 | See 'minebash help' 32 | -------------------------------------------------------------------------------- /minebash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #TODO: highscore, monochrome, flags red if too many, adventure mode 3 | import random, io, sys, time, os 4 | 5 | import curses 6 | from curses import wrapper 7 | 8 | NOTHING = 0 9 | MINE = -1 10 | FLAG = -2 11 | UNKNOWN = -3 12 | FLAG_MINE = -4 13 | 14 | score_x, score_y = 0, 0 15 | CURSOR_POSITION=[0,0] 16 | 17 | OFFSET = 11 18 | 19 | STARTTIME = 0 20 | 21 | SCREEN = 0 22 | 23 | firstmove = False 24 | FIELD_GENERATED = False 25 | 26 | FIELDS_CLEARED = 0 27 | 28 | width, height = 9, 9 29 | MINECOUNT = 10 30 | FLAGCOUNT = 0 31 | 32 | clWhite, clRed, clCyan, clBlue, clYellow, clGreen, clInverted = None, None, None, None, None, None, None #can only be setup after curses has loaded 33 | 34 | difficulty = 'medium' 35 | 36 | param_error = 'minebash: Invalid parameters. See \'minebash help\' for help ' 37 | helpstr = '''Usage: minebash [easy|medium|hard] | [width height minecount] 38 | 39 | Difficulty presets: 40 | easy: 5x5 4 mines 41 | medium: 9x9 15 mines 42 | hard: 12x12 35 mines 43 | Specify your own: 44 | 4 4 4 4x4 4 mines 45 | 8 8 10 8x8 10 mines 46 | 47 | Controls: 48 | Arrow Keys: Move Cursor 49 | Spacebar: Try field 50 | F: Place flag 51 | ''' 52 | 53 | if len(sys.argv) > 1: 54 | if len(sys.argv) == 2: #param is string like easy, hard 55 | if sys.argv[1] == 'help': 56 | print(helpstr) 57 | sys.exit(0) 58 | if sys.argv[1] == 'easy': 59 | width = 5 60 | height = 5 61 | MINECOUNT = 4 62 | elif sys.argv[1] == 'medium': 63 | width = 9 64 | height = 9 65 | MINECOUNT = 15 66 | elif sys.argv[1] == 'hard': 67 | width = 12 68 | height = 12 69 | MINECOUNT = 35 70 | else: 71 | print(param_error) 72 | sys.exit(1) 73 | difficulty = sys.argv[1] 74 | elif len(sys.argv) == 4: #this means the user has specified width, height and minecount 75 | try: 76 | width = int(sys.argv[1]) 77 | height = int(sys.argv[2]) 78 | except ValueError: 79 | print(param_error) 80 | sys.exit(1) 81 | if int(sys.argv[3]) < width*height: 82 | MINECOUNT = int(sys.argv[3]) 83 | difficulty = 'custom' 84 | else: 85 | print('Minecount muss be less than width x height.') 86 | sys.exit(1) 87 | else: 88 | print(param_error) 89 | sys.exit(1) 90 | playfield = [[UNKNOWN for x in range(width)] for y in range(height)] 91 | 92 | headline = '┌' 93 | midline = '├' 94 | tailline = '└' 95 | 96 | def setup_strings(colcount): 97 | #setup lines to print at the end and start of the matrix, aswell as between rows 98 | global headline, midline, tailline 99 | headline = '┌' + '┬'.join('───' for i in range(width)) + '┐' 100 | midline = '├' + '┼'.join('───' for i in range(width)) + '┤' 101 | tailline = '└' + '┴'.join('───' for i in range(width)) + '┘' 102 | 103 | def endgame(msg=''): 104 | if msg != '': 105 | print(msg) 106 | sys.exit(0) 107 | 108 | def calculate_hint(col, row): 109 | hint = 0 110 | if playfield[row][col] != MINE: 111 | #step through the surrounding 8 (or less) fields 112 | for x in range(col-1, col+2): 113 | if x >= 0 and x < len(playfield): 114 | for y in range(row-1, row+2): 115 | if y >= 0 and y < len(playfield[0]): 116 | if playfield[y][x] == MINE or playfield[y][x] == FLAG_MINE: 117 | #add the mines together 118 | hint+=1 119 | else: 120 | hint = MINE 121 | return hint 122 | 123 | def setup_playfield(w, h, x, y): 124 | global playfield, FIELD_GENERATED, STARTTIME 125 | 126 | minesleft = MINECOUNT 127 | #randomly distribute mines across the field 128 | while minesleft > 0: 129 | randx = random.randint(0, width-1) 130 | randy = random.randint(0, height-1) 131 | #make sure the users first guess isn't a mine 132 | if playfield[randy][randx] != MINE and ([randx, randy] != CURSOR_POSITION): 133 | playfield[randy][randx] = MINE 134 | minesleft -= 1 135 | FIELD_GENERATED = True 136 | STARTTIME = time.time() 137 | 138 | def gameover(win): 139 | SCREEN.clear() 140 | if not win: 141 | SCREEN.addstr(0, 0, r' ________________') 142 | SCREEN.addstr(1, 0, r' ____/ ( ( ) ) \___') 143 | SCREEN.addstr(2, 0, r' /( ( ( ) _ )) ) )\ ') 144 | SCREEN.addstr(3, 0, r' (( ( )( ) ) ( ) )') 145 | SCREEN.addstr(4, 0, r' ((/ ( _( ) ( _) ) ( () ) )') 146 | SCREEN.addstr(5, 0, r' ( ( ( (_) (( ( ) .((_ ) . )_') 147 | SCREEN.addstr(6, 0, r' ( ( ) ( ( ) ) ) . ) ( )') 148 | SCREEN.addstr(7, 0, r' ( ( ( ( ) ( _ ( _) ). ) . ) ) ( )') 149 | SCREEN.addstr(8, 0, r' ( ( ( ) ( ) ( )) ) _)( ) ) )') 150 | SCREEN.addstr(9, 0, r' ( ( ( \ ) ( (_ ( ) ( ) ) ) ) )) ( )') 151 | SCREEN.addstr(10, 0, r' ( ( ( ( (_ ( ) ( _ ) ) ( ) ) )') 152 | SCREEN.addstr(11, 0, r' ( ( ( ( ( ) (_ ) ) ) _) ) _( ( )') 153 | SCREEN.addstr(12, 0, r' (( ( )( ( _ ) _) _(_ ( (_ )') 154 | SCREEN.addstr(13, 0, r' (_((__(_(__(( ( ( | ) ) ) )_))__))_)___)') 155 | SCREEN.addstr(14, 0, r' ((__) \\||lll|l||/// \_))') 156 | SCREEN.addstr(15, 0, r' ( /(/ ( ) ) )\ )') 157 | SCREEN.addstr(16, 0, r' ( ( ( ( | | ) ) )\ )') 158 | SCREEN.addstr(17, 0, r' ( /(| / ( )) ) ) )) )') 159 | SCREEN.addstr(18, 0, r' ( ( ((((_(|)_))))) )') 160 | SCREEN.addstr(19, 0, r' ( ||\(|(|)|/|| )') 161 | SCREEN.addstr(20, 0, r' ( |(||(||)|||| )') 162 | SCREEN.addstr(21, 0, r' ( //|/l|||)|\\ \ )') 163 | SCREEN.addstr(22, 0, r' (/ / // /|//||||\\ \ \ \ _)') 164 | SCREEN.addstr(23, 0, ' You lose! Press Q to quit, or R to restart!') 165 | else: 166 | now = time.time() 167 | elapsed = now - STARTTIME 168 | mins = elapsed / 60 169 | secs = elapsed % 60 170 | secstr = str(round(secs, 2)) 171 | while len(secstr) < 4: secstr += '0' #to avoid a time of 0:001.5 172 | winstr = 'You win! It took you {}:{} to bash the field!'.format(int(mins), secstr.zfill(5)).zfill(7) 173 | SCREEN.addstr(0, 0, ' /$$ /$$ /$$ /$$ /$$$$$$$ /$$') 174 | SCREEN.addstr(1, 0, '| $$ /$ | $$ | $$| $$ | $$__ $$ | $$') 175 | SCREEN.addstr(2, 0, '| $$ /$$$| $$ /$$$$$$ | $$| $$ | $$ \ $$ /$$$$$$ /$$$$$$$ /$$$$$$ | $$') 176 | SCREEN.addstr(3, 0, '| $$/$$ $$ $$ /$$__ $$| $$| $$ | $$ | $$ /$$__ $$| $$__ $$ /$$__ $$| $$') 177 | SCREEN.addstr(4, 0, '| $$$$_ $$$$| $$$$$$$$| $$| $$ | $$ | $$| $$ \ $$| $$ \ $$| $$$$$$$$|__/') 178 | SCREEN.addstr(5, 0, '| $$$/ \ $$$| $$_____/| $$| $$ | $$ | $$| $$ | $$| $$ | $$| $$_____/ ') 179 | SCREEN.addstr(6, 0, '| $$/ \ $$| $$$$$$$| $$| $$ | $$$$$$$/| $$$$$$/| $$ | $$| $$$$$$$ /$$') 180 | SCREEN.addstr(7, 0, '|__/ \__/ \_______/|__/|__/ |_______/ \______/ |__/ |__/ \_______/|__/') 181 | SCREEN.addstr(10, 0, winstr) 182 | SCREEN.addstr(11,0, 'Press Q to quit, or R to restart!') 183 | while True: 184 | key = SCREEN.getch() 185 | if key == ord('q'): return False 186 | elif key == ord('r'): return True 187 | 188 | def getTileLeft(x, y): 189 | cell = playfield[y][x] 190 | s = '' 191 | if [x, y] == CURSOR_POSITION: 192 | s += '[' 193 | color = clGreen 194 | else: 195 | s += ' ' 196 | color = clWhite 197 | return s, color 198 | 199 | def getTileMiddle(x,y): 200 | cell = playfield[y][x] 201 | s = '' 202 | if cell == 0: 203 | s += ' ' 204 | color = clWhite 205 | elif cell == UNKNOWN or cell == MINE: 206 | s += ' ' 207 | color = clInverted 208 | elif cell == FLAG_MINE or cell == FLAG: 209 | s += 'P' 210 | color = clRed 211 | else: 212 | # coloring the hints 213 | if cell == 1: color = clCyan #cyan 214 | elif cell == 2: color = clBlue #blue 215 | else: color = clYellow #yellow 216 | s += str(cell) 217 | 218 | return s, color 219 | 220 | def getTileRight(x, y): 221 | cell = playfield[y][x] 222 | selected = False 223 | s = '' 224 | if [x, y] == CURSOR_POSITION: 225 | s += ']' 226 | color = clGreen 227 | else: 228 | s += ' ' 229 | color = clWhite 230 | return s, color 231 | 232 | def print_playfield(playfield, screen): 233 | global score_x, score_y 234 | #print the matrix 235 | currentline = 0 236 | screen.addstr(currentline, OFFSET-1, headline, clWhite) 237 | currentline +=1 238 | for rowindex, row in enumerate(playfield): 239 | screen.addstr(currentline, OFFSET-1, '│') 240 | pos = OFFSET 241 | for colindex, cell in enumerate(row): 242 | part, color = getTileLeft(colindex, rowindex) 243 | screen.addstr(currentline, pos, part, color) 244 | pos += 1 245 | part, color = getTileMiddle(colindex, rowindex) 246 | screen.addstr(currentline, pos, part, color) 247 | pos += 1 248 | part, color = getTileRight(colindex, rowindex) 249 | screen.addstr(currentline, pos, part, color) 250 | pos += 1 251 | screen.addstr(currentline, pos, '│') 252 | pos += 1 253 | currentline +=1 254 | if(rowindex < len(row)-1): 255 | screen.addstr(currentline, 10, midline) 256 | currentline +=1 257 | screen.addstr(currentline, 10, tailline) 258 | currentline +=1 259 | #get a centered position below the playfield: 260 | score_y = currentline 261 | score_x = int(pos/2) 262 | 263 | def hit(x, y): 264 | global playfield, FIELDS_CLEARED 265 | if playfield[y][x] == UNKNOWN: 266 | #get the number that should be printed in the cell 267 | hint = calculate_hint(x, y) 268 | playfield[y][x] = hint 269 | FIELDS_CLEARED += 1 270 | if hint == NOTHING: 271 | #we hit a 0 or empty field, so we need to hit all the fields around it 272 | #first, step through the 8 or less surrounding fields 273 | for i in range(x-1, x+2): 274 | for j in range(y-1, y+2): 275 | if i >= 0 and i < width: 276 | if j >= 0 and j< height: 277 | if playfield[j][i] == UNKNOWN: 278 | #player has not opened this field yet 279 | hit(i,j) 280 | elif playfield[y][x] == MINE: 281 | return True 282 | 283 | def check_score(): 284 | return FIELDS_CLEARED == (width*height)-MINECOUNT 285 | 286 | 287 | def place_flag(x, y): 288 | global playfield, FLAGCOUNT 289 | if playfield[y][x] == MINE: 290 | playfield[y][x] = FLAG_MINE 291 | FLAGCOUNT += 1 292 | elif playfield[y][x] == UNKNOWN: 293 | playfield[y][x] = FLAG 294 | FLAGCOUNT += 1 295 | elif playfield[y][x] == FLAG_MINE: 296 | playfield[y][x] = MINE 297 | FLAGCOUNT -= 1 298 | elif playfield[y][x] == FLAG: 299 | playfield[y][x] = UNKNOWN 300 | FLAGCOUNT -=1 301 | 302 | def handle_input(k): 303 | global CURSOR_POSITION, firstmove 304 | if k == curses.KEY_LEFT: 305 | if CURSOR_POSITION[0] > 0: 306 | CURSOR_POSITION[0] -=1 307 | else: 308 | CURSOR_POSITION[0] = width-1 309 | elif k == curses.KEY_RIGHT: 310 | if CURSOR_POSITION[0] < width-1: 311 | CURSOR_POSITION[0] +=1 312 | else: 313 | CURSOR_POSITION[0] = 0 314 | elif k == curses.KEY_UP: 315 | if CURSOR_POSITION[1] > 0: 316 | CURSOR_POSITION[1] -=1 317 | else: 318 | CURSOR_POSITION[1] = height-1 319 | elif k == curses.KEY_DOWN: 320 | if CURSOR_POSITION[1] < height-1: 321 | CURSOR_POSITION[1] += 1 322 | else: 323 | CURSOR_POSITION[1] = 0 324 | elif k == ord('f'): 325 | # only place flags after placing mines 326 | if FIELD_GENERATED: 327 | place_flag(CURSOR_POSITION[0], CURSOR_POSITION[1]) 328 | elif k == ord(' '): 329 | if not firstmove: 330 | firstmove = True 331 | else: 332 | return hit(CURSOR_POSITION[0], CURSOR_POSITION[1]) 333 | return False 334 | 335 | def print_footer(screen): 336 | scorestr = 'Mines: {} Flags: {} Difficulty: {}'.format(MINECOUNT, FLAGCOUNT, difficulty) 337 | controlstr = 'Arrow Keys: Move F: Place flag Space: Open field' 338 | score_xpos = int((score_x) - (len(scorestr)/2)) + int(OFFSET/2) 339 | control_xpos = int((score_x) - len(controlstr)/2) + int(OFFSET/2) 340 | if score_xpos < 0: score_xpos = 0 341 | if control_xpos < 0: control_xpos = 0 342 | screen.addstr(score_y, score_xpos, scorestr) 343 | screen.addstr(score_y+1, control_xpos, controlstr) 344 | 345 | def setup_colors(): 346 | global clWhite, clRed, clCyan, clBlue, clYellow, clGreen, clInverted 347 | curses.start_color() 348 | #curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK) 349 | curses.init_pair(2, curses.COLOR_RED, curses.COLOR_BLACK) 350 | curses.init_pair(3, curses.COLOR_CYAN, curses.COLOR_BLACK) 351 | curses.init_pair(4, curses.COLOR_BLUE, curses.COLOR_BLACK) 352 | curses.init_pair(5, curses.COLOR_YELLOW, curses.COLOR_BLACK) 353 | curses.init_pair(6, curses.COLOR_GREEN, curses.COLOR_BLACK) 354 | curses.init_pair(7, curses.COLOR_WHITE, curses.COLOR_WHITE) 355 | clWhite = curses.color_pair(0) #hardwired 356 | clRed = curses.color_pair(2) 357 | clCyan = curses.color_pair(3) 358 | clBlue = curses.color_pair(4) 359 | clYellow = curses.color_pair(5) 360 | clGreen = curses.color_pair(6) 361 | clInverted = curses.color_pair(7) 362 | 363 | def reset(): 364 | global firstmove, playfield, FIELD_GENERATED, FIELDS_CLEARED, CURSOR_POSITION, FLAGCOUNT 365 | firstmove = False 366 | playfield = [[UNKNOWN for x in range(width)] for y in range(height)] 367 | FIELD_GENERATED = False 368 | FLAGCOUNT = 0 369 | FIELDS_CLEARED = 0 370 | CURSOR_POSITION = [0,0] 371 | 372 | def main(stdscr): 373 | global SCREEN, firstmove, FIELD_GENERATED, FIELDS_CLEARED, playfield, CURSOR_POSITION 374 | SCREEN = stdscr 375 | stdscr.clear() 376 | setup_strings(width) 377 | setup_colors() 378 | while(True): 379 | reset() 380 | while(True): #game loop 381 | print_playfield(playfield, stdscr) 382 | key = stdscr.getch() 383 | if handle_input(key): 384 | restart = gameover(False) #user hit a mine 385 | if restart: 386 | stdscr.clear() 387 | break 388 | else: endgame() 389 | if (firstmove) and not (FIELD_GENERATED): 390 | #generate the field 391 | setup_playfield(width, height, CURSOR_POSITION[0], CURSOR_POSITION[1]) 392 | #do this a second time, because we didn't know what the field would be the first time 393 | handle_input(key) 394 | STARTTIME = time.time() 395 | if check_score(): 396 | #player wins! 397 | restart = gameover(True) # does user want restart? 398 | if restart: 399 | stdscr.clear() 400 | break 401 | else: endgame() 402 | print_footer(stdscr) 403 | stdscr.refresh() 404 | if __name__ == "__main__": 405 | try: 406 | wrapper(main) 407 | except KeyboardInterrupt: 408 | sys.exit(0) 409 | -------------------------------------------------------------------------------- /pattern: -------------------------------------------------------------------------------- 1 | ┌───┬───┐ 2 | │[ ]│ │ 3 | ├───┼───┤ 4 | │ 1 │ P │ 5 | └───┴───┘ --------------------------------------------------------------------------------