Python Telegram Minesweeper Bot https://github.com/isjerryxiao/tgmsbot
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.

191 lines
6.8 KiB

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import numpy as np
  4. from random import shuffle, choice
  5. from copy import deepcopy
  6. # 0 - 8: means 0-8 mines, not opened
  7. # opened block = the value of not opened block + 10
  8. # 9 is mine, not opened
  9. # 19 is flagged mine
  10. # 20 is stepped mine
  11. # default:
  12. # HEIGHT = 8
  13. # WIDTH = 8
  14. # MINES = 9
  15. IS_MINE = 9
  16. DEAD = 20
  17. def check_params(height, width, mines):
  18. if height <= 0 or width <= 0:
  19. return (False, "地图太小!")
  20. elif mines > height * width:
  21. return (False, "放不下这么多雷嘛!")
  22. elif mines == height * width:
  23. return (False, "一点就爆炸,有意思吗?")
  24. elif mines < 0:
  25. return (False, "暂时还不会放负数颗雷呢。")
  26. elif mines <= height * width:
  27. return (True, "")
  28. def get_row_col(width, index):
  29. row = index // width
  30. col = index - width * row
  31. return (row, col)
  32. def get_index(width, row_col):
  33. (row, col) = row_col
  34. index = width * row + col
  35. return index
  36. class Board():
  37. def __init__(self, height, width, mines):
  38. self.height = height
  39. self.width = width
  40. self.mines = mines
  41. self.map = None
  42. self.mmap = None
  43. self.moves = list()
  44. self.state = 0 # 0:not playing, 1:playing, 2:win, 3:dead
  45. # statistics
  46. self.__op = 0
  47. self.__is = 0
  48. self.__3bv = 0
  49. def __gen_map(self, first_move):
  50. height = self.height
  51. width = self.width
  52. mines = self.mines
  53. if mines >= height * width:
  54. return
  55. elif mines < 0:
  56. return
  57. # first_move should't be a mine, and if possible, it should be an open.
  58. self.map = np.zeros((height, width), dtype=np.int8)
  59. map_1d = [IS_MINE] * mines
  60. zero_blocks = list()
  61. fm_index = get_index(width, first_move)
  62. zero_blocks.append(fm_index)
  63. fm_nbrs = [rc for rc in self.__iter_neighbour(*first_move)]
  64. if height * width - mines - 1 >= len(fm_nbrs):
  65. fm_nbrs_index = [get_index(width, fm_nbr) for fm_nbr in fm_nbrs]
  66. zero_blocks += fm_nbrs_index
  67. map_1d += [0] * (height * width - mines - 1 - len(fm_nbrs))
  68. else:
  69. map_1d += [0] * (height * width - mines - 1)
  70. shuffle(map_1d)
  71. for mindex in sorted(zero_blocks):
  72. map_1d.insert(mindex, 0)
  73. for mindex in range(len(map_1d)):
  74. if map_1d[mindex] == IS_MINE:
  75. (row, col) = get_row_col(width, mindex)
  76. self.map[row][col] = IS_MINE
  77. for row in range(height):
  78. for col in range(width):
  79. if self.map[row][col] != IS_MINE:
  80. mine_count = 0
  81. for nbr_value in self.__iter_neighbour(row, col, return_rc=False):
  82. if nbr_value == IS_MINE:
  83. mine_count += 1
  84. self.map[row][col] = mine_count
  85. self.mmap = deepcopy(self.map)
  86. def __iter_neighbour(self, row, col, return_rc=True):
  87. height = self.height
  88. width = self.width
  89. for i in [a - 1 for a in range(3)]:
  90. for j in [b - 1 for b in range(3)]:
  91. if (i != 0 or j != 0) and row + i >= 0 and row + i <= height - 1 and \
  92. col + j >= 0 and col + j <= width - 1:
  93. if return_rc:
  94. yield (row + i, col + j)
  95. else:
  96. yield self.map[row + i][col + j]
  97. def __do_i_win(self):
  98. unopened = 0
  99. mines_opened = 0
  100. for x in np.nditer(self.map):
  101. if x <= 8:
  102. unopened += 1
  103. elif x in (19, DEAD):
  104. mines_opened += 1
  105. if mines_opened == self.mines:
  106. return True
  107. elif unopened == 0:
  108. return True
  109. else:
  110. return False
  111. def __open(self, row, col, automatic=False):
  112. if self.state != 1:
  113. return
  114. if not automatic and self.map[row][col] == 9:
  115. self.map[row][col] = DEAD
  116. self.state = 3
  117. return
  118. elif self.map[row][col] == 0:
  119. self.map[row][col] += 10 # open this block
  120. # open other blocks
  121. for nbr in self.__iter_neighbour(row, col):
  122. self.__open(nbr[0], nbr[1], automatic=True)
  123. elif self.map[row][col] >= 10:
  124. # already opened
  125. if automatic:
  126. return
  127. neighbour_mine_opened = 0
  128. neighbour_unopened = 0
  129. for neighbour in self.__iter_neighbour(row, col, return_rc=False):
  130. if neighbour in (19, DEAD):
  131. neighbour_mine_opened += 1
  132. if neighbour <= 9:
  133. neighbour_unopened += 1
  134. if (neighbour_mine_opened == self.map[row][col] - 10) or \
  135. (neighbour_unopened == self.map[row][col] - 10 - neighbour_mine_opened):
  136. for nbr in self.__iter_neighbour(row, col):
  137. self.__open(nbr[0], nbr[1], automatic=True)
  138. else:
  139. self.map[row][col] += 10
  140. if self.__do_i_win():
  141. self.state = 2
  142. def move(self, row_col):
  143. if self.state == 0:
  144. self.__gen_map(row_col)
  145. self.state = 1
  146. (row, col) = row_col
  147. self.__open(row, col)
  148. def gen_statistics(self):
  149. if self.__3bv != 0:
  150. return (self.__op, self.__is, self.__3bv)
  151. self.__visited = np.zeros((self.height, self.width), dtype=np.int8)
  152. def scan_open(row, col):
  153. self.__visited[row][col] = 1
  154. for nbr_rc in self.__iter_neighbour(row, col):
  155. (nrow, ncol) = nbr_rc
  156. if self.__visited[nrow][ncol] == 0:
  157. nbr = self.mmap[nrow][ncol]
  158. if nbr == 0:
  159. scan_open(nrow, ncol)
  160. elif nbr <= 8:
  161. self.__visited[nrow][ncol] = 1
  162. def scan_island(row, col):
  163. self.__3bv += 1
  164. self.__visited[row, col] = 1
  165. for nbr_rc in self.__iter_neighbour(row, col):
  166. (nrow, ncol) = nbr_rc
  167. if self.__visited[nrow][ncol] == 0:
  168. nbr = self.mmap[nrow][ncol]
  169. if nbr >= 1 and nbr <= 8:
  170. scan_island(nrow, ncol)
  171. for row in range(self.height):
  172. for col in range(self.width):
  173. if self.__visited[row][col] == 0 and self.mmap[row][col] == 0:
  174. self.__op += 1
  175. self.__3bv += 1
  176. scan_open(row, col)
  177. for row in range(self.height):
  178. for col in range(self.width):
  179. cell = self.mmap[row][col]
  180. if self.__visited[row][col] == 0 and cell >= 1 and cell <= 8:
  181. self.__is += 1
  182. scan_island(row, col)
  183. return (self.__op, self.__is, self.__3bv)