minesweeper in python with curses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
8.2 KiB

  1. #!/usr/bin/env python3
  2. import curses
  3. from logic import MinesweeperLogic
  4. from ouija import Ouija, Tile
  5. from pathlib import Path
  6. import os
  7. HOME = Path(os.environ["HOME"])
  8. SWEEPY_ROOT = ".sweepy"
  9. MY_SWEEPY_ROOT = HOME / SWEEPY_ROOT
  10. MY_SWEEPY_WON = MY_SWEEPY_ROOT / "won"
  11. MY_SWEEPY_LOST = MY_SWEEPY_ROOT / "lost"
  12. def generate_tiles():
  13. tiles = []
  14. tiles.append(Tile("0", ""))
  15. tiles.append(Tile("1", "1", Ouija.COLORS["DEFAULT"], curses.A_BOLD))
  16. tiles.append(Tile("2", "2", Ouija.COLORS["BLUE"], curses.A_BOLD))
  17. tiles.append(Tile("3", "3", Ouija.COLORS["RED"], curses.A_BOLD))
  18. tiles.append(Tile("4", "4", Ouija.COLORS["GREEN"], curses.A_BOLD))
  19. tiles.append(Tile("5", "5", Ouija.COLORS["ORANGE"], curses.A_BOLD))
  20. tiles.append(Tile("6", "6", Ouija.COLORS["CYAN"], curses.A_BOLD))
  21. tiles.append(Tile("!", "#", Ouija.COLORS["YELLOW"]))
  22. tiles.append(Tile("_", "."))
  23. tiles.append(Tile("x", "x", Ouija.COLORS["BLACK_ON_YELLOW"]))
  24. tiles.append(Tile("-", ".", Ouija.COLORS["BLACK_ON_YELLOW"]))
  25. tiles.append(Tile("+", "#", Ouija.COLORS["BLACK_ON_YELLOW"]))
  26. return tiles
  27. def env_setup():
  28. if not MY_SWEEPY_ROOT.exists():
  29. MY_SWEEPY_ROOT.mkdir()
  30. if not MY_SWEEPY_WON.exists():
  31. MY_SWEEPY_WON.mkdir()
  32. if not MY_SWEEPY_LOST.exists():
  33. MY_SWEEPY_LOST.mkdir()
  34. def game_loop(stdscr, ob, ml):
  35. game_width = ml.WIDTH
  36. game_height = ml.HEIGHT
  37. game_bombs = ml.BOMBS
  38. cur_y, cur_x = ml.get_first_cell()
  39. while(True):
  40. if ml.has_won() or ml.has_lost():
  41. msg = "You won!" if ml.has_won() else "You lost :("
  42. base_fn = MY_SWEEPY_WON if ml.has_won() else MY_SWEEPY_LOST
  43. stdscr.addstr(4, 3, "{} [n]ew game, [r]eplay, or [q]uit?".format(msg))
  44. ob.draw_board(ml.get_board())
  45. fn = "{}.sweepy".format(ml.SEED)
  46. root_fn = MY_SWEEPY_ROOT / fn
  47. fn_fq = MY_SWEEPY_WON / fn
  48. if root_fn.exists():
  49. root_fn.unlink()
  50. ml.save(str(fn_fq))
  51. while(True):
  52. ik = stdscr.getkey()
  53. if ik == 'n':
  54. # clear the line
  55. stdscr.move(4, 3)
  56. stdscr.clrtoeol()
  57. ml = MinesweeperLogic()
  58. ml.new_game(game_width, game_height, game_bombs)
  59. cur_y, cur_x = ml.get_first_cell()
  60. break
  61. elif ik == 'r':
  62. stdscr.move(4, 3)
  63. stdscr.clrtoeol()
  64. ml.new_game(game_width, game_height, game_bombs)
  65. cur_y, cur_x = ml.get_first_cell()
  66. break
  67. elif ik == 'q':
  68. exit()
  69. game_head(stdscr, ml)
  70. game_foot(stdscr)
  71. ob.draw_board(ml.get_board(), cur_y, cur_x)
  72. in_key = stdscr.getkey()
  73. # uncover a cell using the enter key
  74. if in_key == "\n":
  75. ml.do_move(cur_y, cur_x, "u")
  76. if ml.get_first_cell():
  77. cur_y, cur_x = ml.get_closest_cell(cur_y, cur_x)
  78. #cur_y, cur_x = ml.closest(cur_y, cur_x)
  79. # flag a cell using the "f" key
  80. elif in_key == "f":
  81. ml.do_move(cur_y, cur_x, "x")
  82. if ml.get_first_cell():
  83. cur_y, cur_x = ml.get_closest_cell(cur_y, cur_x)
  84. #cur_y, cur_x = ml.closest(cur_y, cur_x)
  85. # save a game (and exit) using the "s" key
  86. elif in_key == "s":
  87. ob.draw_board(ml.get_board())
  88. fn = "{}.sweepy".format(ml.SEED)
  89. root_fn = MY_SWEEPY_ROOT / fn
  90. ml.save(str(root_fn))
  91. stdscr.addstr(4, 3, "Game saved!")
  92. stdscr.getch()
  93. exit()
  94. # start a new game with the "n" key
  95. elif in_key == "n":
  96. ob.draw_board(ml.get_board())
  97. stdscr.addstr(4, 3, "new game? (y/n) ")
  98. choice = stdscr.getkey()
  99. stdscr.move(4, 3)
  100. stdscr.clrtoeol()
  101. if choice == 'y':
  102. ml = MinesweeperLogic()
  103. ml.new_game(game_width, game_height, game_bombs)
  104. cur_y, cur_x = ml.get_first_cell()
  105. # reset the current game with the "r" key
  106. elif in_key == "r":
  107. ob.draw_board(ml.get_board())
  108. stdscr.addstr(4, 3, "reset the current game? (y/n) ")
  109. choice = stdscr.getkey()
  110. stdscr.move(4, 3)
  111. stdscr.clrtoeol()
  112. if choice == 'y':
  113. ml.new_game(game_width, game_height, game_bombs)
  114. cur_y, cur_x = ml.get_first_cell()
  115. # quit without saving using the "q" key
  116. elif in_key == "q":
  117. ob.draw_board(ml.get_board())
  118. stdscr.addstr(4, 3, "Quit without saving? (y/n) ")
  119. choice = stdscr.getkey()
  120. if choice == 'y':
  121. exit()
  122. else:
  123. stdscr.move(4, 3)
  124. stdscr.clrtoeol()
  125. # navigation
  126. elif in_key == "KEY_LEFT":
  127. cur_y, cur_x, cost = ml.get_left(cur_y, cur_x)
  128. elif in_key == "KEY_RIGHT":
  129. cur_y, cur_x, cost = ml.get_right(cur_y, cur_x)
  130. elif in_key == "KEY_UP":
  131. cur_y, cur_x, cost = ml.get_up(cur_y, cur_x)
  132. elif in_key == "KEY_DOWN":
  133. cur_y, cur_x, cost = ml.get_down(cur_y, cur_x)
  134. elif in_key == "\t":
  135. cur_y, cur_x = ml.closest(cur_y, cur_x)
  136. def game_head(stdscr, ml):
  137. stdscr.addstr(1, 3, "~ sweepy ~")
  138. stdscr.addstr(2, 3, "{} bombs, seed: {}".format(str(ml.BOMBS),str(ml.SEED)))
  139. stdscr.addstr(3, 3, "flags: {:>2}".format(str(ml.get_flag_count())))
  140. stdscr.refresh()
  141. def game_foot(stdscr):
  142. stdscr.addstr(24, 3, "u/d/l/r/tab navigation")
  143. stdscr.addstr(25, 3, "enter: uncover cell")
  144. stdscr.addstr(26, 3, "'f': (un)flag cell")
  145. stdscr.addstr(27, 3, "'n': new game")
  146. stdscr.addstr(28, 3, "'r': reset game")
  147. stdscr.addstr(29, 3, "'s': save and quit game")
  148. stdscr.addstr(30, 3, "'q': quit game (without saving)")
  149. def main(stdscr):
  150. stdscr.clear()
  151. curses.curs_set(False)
  152. env_setup()
  153. # blocks are 2 tall and 4 wide
  154. ml = MinesweeperLogic()
  155. board = Ouija(stdscr, 6, 5)
  156. board.style(corner=None, horiz_edge="=")
  157. if not board.setup_tiles(generate_tiles()):
  158. return False
  159. # print some info
  160. stdscr.addstr(1, 3, "~ sweepy ~")
  161. stdscr.addstr(2, 3, "[n]ew game, or [r]esume a game")
  162. # initial loop
  163. while(True):
  164. in_key = stdscr.getkey()
  165. if in_key == "q":
  166. exit()
  167. elif in_key == "n":
  168. stdscr.addstr(3, 3, "choose a size")
  169. stdscr.addstr(4, 3, "[0] 8 x 8 (10 bombs)")
  170. stdscr.addstr(5, 3, "[1] 16 x 8 (25 bombs)")
  171. while True:
  172. choice = stdscr.getkey()
  173. if choice == '0':
  174. ml.new_game(8, 8, 10)
  175. break
  176. elif choice == '1':
  177. ml.new_game(8, 16, 25)
  178. break
  179. else:
  180. stdscr.addstr(6, 3, "invalid choice, choose again")
  181. break
  182. elif in_key == "l":
  183. stdscr.addstr(3, 3, "what game do you want to load? ")
  184. fn = stdscr.getstr()
  185. stdscr.addstr(4, 3, fn)
  186. stdscr.getch()
  187. ml.load(fn.decode('utf-8'))
  188. break
  189. elif in_key == "r":
  190. current_games = sorted(MY_SWEEPY_ROOT.glob("*.sweepy"))
  191. stdscr.addstr(3, 3, "there are " + str(len(current_games)) + " games in progress")
  192. for idx, game in enumerate(current_games):
  193. stdscr.addstr(4 + idx, 3, "[{}] {}".format(str(idx), str(game.name)))
  194. if len(current_games) != 0:
  195. stdscr.addstr(4 + len(current_games), 3, "choose a game")
  196. while True:
  197. r_key = stdscr.getkey()
  198. if r_key.isdigit() and int(r_key) in range(len(current_games)):
  199. r_game = current_games[int(r_key)]
  200. ml.load(str(r_game))
  201. break
  202. else:
  203. stdscr.addstr(4 + len(current_games), 3, "invalid choice, choose a game")
  204. break
  205. stdscr.clear()
  206. game_loop(stdscr, board, ml)
  207. curses.wrapper(main)