Advertisement
Guest User

Untitled

a guest
Apr 29th, 2015
211
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.37 KB | None | 0 0
  1. #!/usr/bin/python2
  2. from minesweeper import *
  3. import random
  4.  
  5.  
  6. # Patterns syntax:
  7. # Each pattern is an array of string
  8. # Each string contains pairs <matcher, action> characters (e.g. ?o) separated by whitespace for readability
  9. # match - is a matcher against which element on board will be matched. All elements in patterns must match for action to occur
  10. # possible matchers are:
  11. # ? - match anything, including out of bonds cells
  12. # 0..9 - match against reduced strength
  13. # Reduced strength is (written number in field) - (number of marked mines adjacent to the cell)
  14. # doesn't match out of bonds cells
  15. # K - known cell, impossible for the new mine cell. Matches out-of-bounds, known 0-9, known mines, not unknowns
  16. # . - matches unknown
  17. # action is an action regarding this cell
  18. # o - open it. Action is ignored if cell already marked as mine or open or out of bounds
  19. # * - mark mine
  20. # _ - ignore cell.
  21.  
  22.  
  23. LAST_ACTION=""
  24. def do_print(s):
  25. pass
  26.  
  27. PATTERNS = [
  28. ["?o ?o ?o",
  29. "?o 0_ ?o",
  30. "?o ?o ?o"],
  31.  
  32. [ "K_ K_ K_",
  33. "1_ 2_ K_",
  34. "?_ ?_ .*"],
  35.  
  36. [ "K_ K_ K_ K_",
  37. "K_ 1_ 1_ K_",
  38. "?o ?_ ?_ K_"],
  39.  
  40. ["K_ 1_ 1_ K_",
  41. "K_ ._ ._ K_",
  42. "K_ 1_ .o K_",
  43. "K_ K_ K_ K_"],
  44.  
  45. ]
  46.  
  47. SINGLE_MINE_PATTERNS = [
  48. ["K_ 1_",
  49. "1_ .*"],
  50.  
  51. ["1_ ?_",
  52. "K_ .*",
  53. "K_ 1_"],
  54.  
  55. ["1_ ?_",
  56. "K_ .*",
  57. "1_ ?_"],
  58. ]
  59.  
  60.  
  61. TRANSPOSED_PATTERNS = {}
  62. HFLIPPED_PATTERNS = {}
  63.  
  64. def transpose_pattern(p):
  65. if id(p) in TRANSPOSED_PATTERNS:
  66. return TRANSPOSED_PATTERNS[id(p)]
  67.  
  68. res = []
  69. splitted = [x.split() for x in p]
  70. z = zip(*splitted)
  71. z = [" ".join(x) for x in z]
  72. TRANSPOSED_PATTERNS[id(p)] = z
  73. return z
  74.  
  75. def hflip_pattern(p, cache=True):
  76. if cache and id(p) in HFLIPPED_PATTERNS:
  77. return HFLIPPED_PATTERNS[id(p)]
  78.  
  79. res = []
  80. splitted = [reversed(x.split()) for x in p]
  81. z = [" ".join(x) for x in splitted]
  82. if cache:
  83. HFLIPPED_PATTERNS[id(p)] = z
  84. return z
  85.  
  86. def calculate_pattern_offsets(pattern):
  87. """ Calculate pattern offset. Since ? matcher can match against empty cell, we need to know how far out of bonds we can go
  88. :return: (row_from, row_to, col_from, col_to)
  89. """
  90. row_from = 0
  91. row_to = 0
  92. col_from = 0
  93. col_to = 0
  94.  
  95. pattern_height = len(pattern)
  96. patter_width = len(pattern[0])
  97.  
  98. def impl(p):
  99. xfrom = xto = 0
  100. # First try to find how far we can move pattern out of top bounds
  101. for row in p:
  102. if any(map(lambda p:p[0] not in '?K', row.split())):
  103. break
  104. xfrom -= 1
  105.  
  106. # Now bottom
  107. for row in reversed(p):
  108. if any(map(lambda p:p[0] not in '?K', row.split())):
  109. break
  110. xto += 1
  111. return xfrom, xto
  112.  
  113. row_from, row_to = impl(pattern)
  114. col_from, col_to = impl(transpose_pattern(pattern))
  115.  
  116. return (row_from, row_to, col_from, col_to)
  117.  
  118. PATTERN_OFFSETS = { id(p) : calculate_pattern_offsets(p) for p in PATTERNS }
  119. for pa in SINGLE_MINE_PATTERNS:
  120. PATTERN_OFFSETS[id(pa)] = calculate_pattern_offsets(pa)
  121.  
  122.  
  123.  
  124.  
  125. def print_grid(grid):
  126. for row in xrange(grid.nrows):
  127. s = ""
  128. for col in xrange(grid.ncols):
  129. s += "%2s " % grid[row,col]
  130. print(s)
  131.  
  132. print("Mines left: %d" % TOTAL_MINES_LEFT)
  133.  
  134.  
  135.  
  136. def check_match_at(grid, reduced, pattern, row, col):
  137. cols = reduced.ncols
  138. rows = reduced.nrows
  139. y = row
  140. res = set()
  141.  
  142. for prow in pattern:
  143. x = col - 1
  144. for pair in prow:
  145. x += 1
  146. matcher = pair[0]
  147. in_bounds = 0 <= y < rows and 0 <= x < cols
  148. if in_bounds and reduced[y,x] == '.' and pair[1] != '_':
  149. res.add((y,x, pair[1] == 'o'))
  150.  
  151. if matcher == '.':
  152. if not in_bounds or reduced[y, x] != '.':
  153. return None
  154. continue
  155.  
  156. elif matcher == '?':
  157. continue
  158.  
  159. elif matcher == 'K':
  160. if in_bounds and reduced[y, x] == '.':
  161. return None
  162. continue
  163.  
  164. if not in_bounds:
  165. return None
  166.  
  167. if int(matcher) != reduced[y,x]:
  168. return None
  169. y += 1
  170. return res
  171.  
  172.  
  173.  
  174. def do_check_match(grid, reduced, pattern, pattern_offset, bounds = None):
  175. if bounds == None or bounds[0] == None:
  176. bounds = (0, reduced.nrows-1, 0, reduced.ncols-1)
  177. pattern_height = len(pattern)
  178. patter_width = len(pattern[0])
  179. row_from, row_to, col_from, col_to = pattern_offset
  180.  
  181. col_to = min(reduced.ncols, bounds[3]) + col_to
  182. row_to = min(reduced.nrows, bounds[1]) + row_to
  183. row_from += bounds[0]
  184. col_from += bounds[2]
  185. pairs = [p.split() for p in pattern]
  186.  
  187. for row in xrange(row_from, row_to):
  188. for col in xrange(col_from, col_to):
  189. res = check_match_at(grid, reduced, pairs, row, col)
  190. if res:
  191. do_print("Checked %s at %d %d=%s [TM=%d]" % (pattern, row, col, res, TOTAL_MINES_LEFT))
  192. return res
  193. return None
  194.  
  195. def set_add(the_set, elt):
  196. if elt in the_set:
  197. return False
  198. the_set.add(elt)
  199. return True
  200.  
  201. def check_match(grid, reduced, pattern):
  202. processed = set()
  203.  
  204. def check_flips(p, ofs, bounds):
  205. # Vertically flipped pattern
  206. # E R N _
  207. # P A T T
  208. vpattern = list(reversed(p))
  209. voffset = (-ofs[1], -ofs[0], ofs[2], ofs[3])
  210. if set_add(processed, "_".join(vpattern)):
  211. res = do_check_match(grid, reduced, vpattern, voffset, bounds)
  212. if res:
  213. return res
  214.  
  215. # Horizontally flipped pattern
  216. # T T A P
  217. # _ N R E
  218. hoffset = (ofs[0], ofs[1], -ofs[3], -ofs[2])
  219. hpattern = hflip_pattern(p)
  220. if set_add(processed, "_".join(hpattern)):
  221. res = do_check_match(grid, reduced, hpattern, hoffset, bounds)
  222. if res:
  223. return res
  224.  
  225. # Flipped both way
  226. # _ N R E
  227. # T T A P
  228. vhoffset = (-ofs[1], -ofs[0], -ofs[3], -ofs[2])
  229. vhpattern = hflip_pattern(vpattern, False)
  230. if set_add(processed, "_".join(vhpattern)):
  231. res = do_check_match(grid, reduced, vhpattern, vhoffset, bounds)
  232. return res
  233. return None
  234.  
  235. # normal pattern
  236. # P A T T
  237. # E R N _
  238. do_print("pattern : %s" % pattern)
  239. offset = PATTERN_OFFSETS[id(pattern)]
  240. res = do_check_match(grid, reduced, pattern, offset, BOUNDS)
  241. if res:
  242. return res
  243.  
  244. res = check_flips(pattern, offset, BOUNDS)
  245. if res:
  246. return res
  247.  
  248.  
  249.  
  250. # transposes pattern
  251. # P E
  252. # A R
  253. # T N
  254. # T _
  255.  
  256. tpattern = transpose_pattern(pattern)
  257. toffset = (offset[2], offset[3], offset[0], offset[1])
  258. res = do_check_match(grid, reduced, tpattern, toffset, BOUNDS)
  259. if res:
  260. return res
  261.  
  262. res = check_flips(tpattern, toffset, BOUNDS)
  263. return res
  264.  
  265. def iter_neigbours(arr2d, row, col):
  266. for dx in (-1,0,1):
  267. for dy in (-1,0,1):
  268. if dx == dy == 0:
  269. continue
  270. cx, cy = col + dx, row + dy
  271. if 0 <= cx < arr2d.ncols and 0 <= cy < arr2d.nrows:
  272. yield cy, cx
  273.  
  274. def guess_mine_at(reduced, y, x):
  275. global TOTAL_MINES_LEFT
  276.  
  277. if reduced[y, x] == '*':
  278. return False
  279.  
  280. do_print("found mine at %d, %d" % (y,x))
  281.  
  282. reduced[y, x] = '*'
  283. TOTAL_MINES_LEFT -= 1
  284. for row, col in iter_neigbours(reduced, y, x):
  285. r = reduced[row, col]
  286. if type(r) == int and r > 0:
  287. r -= 1
  288. reduced[row, col] = r
  289. expand_bounds(reduced, row, col)
  290.  
  291. expand_bounds(reduced, y, x)
  292. return True
  293.  
  294. def try_to_fill(grid, reduced):
  295. res = False
  296. for y in xrange(reduced.nrows):
  297. for x in xrange(reduced.ncols):
  298. if not type(reduced[y,x]) == int:
  299. continue
  300. if reduced[y,x] == 0:
  301. continue
  302.  
  303.  
  304. total_neighbours = 0
  305. unknowns = 0
  306.  
  307. for cy, cx in iter_neigbours(reduced, y, x):
  308. total_neighbours += 1
  309. if reduced[cy,cx] == ".":
  310. unknowns += 1
  311.  
  312. if reduced[y,x] == unknowns:
  313. do_print("unknowns=val %d at %d %d" % (unknowns, y,x))
  314. for cy, cx in iter_neigbours(reduced, y, x):
  315. if reduced[cy,cx] == ".":
  316. res = guess_mine_at(reduced, cy, cx) or res
  317.  
  318. if TOTAL_MINES_LEFT == 0:
  319. res = set([])
  320. for y in xrange(reduced.nrows):
  321. for x in xrange(reduced.ncols):
  322. if reduced[y,x] == '.':
  323. guess_empty_at(grid, reduced, y, x)
  324. res.add((y, x, True))
  325.  
  326.  
  327. return res
  328.  
  329.  
  330. TOTAL_MINES_LEFT = 0
  331.  
  332.  
  333. def guess_empty_at(grid, reduced, row, col):
  334. if reduced[row, col] == '.':
  335. grid.guess(row, col)
  336. m = grid.nMinesAround(row, col)
  337. for cy, cx in iter_neigbours(reduced, row, col):
  338. if reduced[cy, cx] == '*':
  339. m -= 1
  340. reduced[row,col] = m
  341. expand_bounds(reduced, row, col)
  342.  
  343. # row_start, row_end, col_start, col_end
  344. BOUNDS=[None, None, None, None]
  345.  
  346. def reset_bounds():
  347. BOUNDS[0]=BOUNDS[1]=BOUNDS[2]=BOUNDS[3] = None
  348.  
  349. def expand_bounds(reduced, row, col):
  350. DELTA = 4
  351. if BOUNDS[0] == None:
  352. BOUNDS[0] = row - DELTA
  353. BOUNDS[1] = row + DELTA
  354. BOUNDS[2] = col - DELTA
  355. BOUNDS[3] = col + DELTA
  356. else:
  357. BOUNDS[0] = min(row - DELTA, BOUNDS[0])
  358. BOUNDS[1] = max(row + DELTA, BOUNDS[1])
  359. BOUNDS[2] = min(col - DELTA, BOUNDS[2])
  360. BOUNDS[3] = max(col + DELTA, BOUNDS[3])
  361.  
  362.  
  363. STEP = 0
  364. def exampleSolveGame0(grid):
  365. global LAST_R
  366. global TOTAL_MINES_LEFT
  367. global STEP
  368. STEP += 1
  369. TOTAL_MINES_LEFT = grid.nMines()
  370. reduced = Array2D(grid.nRows(), grid.nCols(), ".")
  371. LAST_R = reduced
  372.  
  373. reset_bounds()
  374. guess_empty_at(grid, reduced, reduced.nrows//2, reduced.ncols//2)
  375.  
  376. several_mines_patterns = PATTERNS
  377. single_mine_patterns = PATTERNS + SINGLE_MINE_PATTERNS
  378. assert SINGLE_MINE_PATTERNS[2] in single_mine_patterns
  379. assert SINGLE_MINE_PATTERNS[2] not in several_mines_patterns
  380.  
  381. probabilities = Array2D(grid.nRows(), grid.nCols(), 0.0)
  382.  
  383. while not grid.isSolved():
  384. old=True
  385.  
  386. l = several_mines_patterns
  387. if TOTAL_MINES_LEFT == 1:
  388. l = single_mine_patterns
  389.  
  390. for p in l:
  391. res = check_match(grid, reduced, p)
  392. if res:
  393. reset_bounds()
  394. do_print("Found pattern(@ %s)=%s," % (res, p))
  395. for row, col, is_empty in res:
  396. if is_empty:
  397. guess_empty_at(grid, reduced, row, col)
  398. else:
  399. guess_mine_at(reduced, row, col)
  400. old = False
  401. break
  402. if not old:
  403. continue
  404.  
  405. if BOUNDS[0] != None:
  406. do_print("Nothing found in %s, resetting" % BOUNDS)
  407. reset_bounds()
  408. continue
  409.  
  410. do_print("Nothing found in whole field. Filling")
  411. res = try_to_fill(grid, reduced)
  412.  
  413. if res:
  414. do_print("Filled: OK")
  415. continue
  416.  
  417. do_print("Filled: nothing filled")
  418.  
  419. probabilities = Array2D(reduced.nrows, reduced.ncols, 0.0)
  420.  
  421. for y in xrange(0, reduced.nrows):
  422. for x in xrange(0, reduced.ncols):
  423. r = reduced[y,x]
  424. if type(r) != int or r == 0:
  425. continue
  426.  
  427. unknowns = 0
  428. for cy, cx in iter_neigbours(reduced, y, x):
  429. if reduced[cy,cx] == '.':
  430. unknowns += 1
  431.  
  432. probabilty = float(r) / float(unknowns)
  433.  
  434. for cy, cx in iter_neigbours(reduced, y, x):
  435. probabilities[cy, cx] = max(probabilities[cy, cx], probabilty)
  436.  
  437.  
  438. ey, ex, best_probability = 0, 0, 1.0
  439. unopened_squares = 0
  440. for y in xrange(0, reduced.nrows):
  441. for x in xrange(0, reduced.ncols):
  442. if reduced[y, x] == '.':
  443. unopened_squares += 1
  444. if probabilities[y,x] != 0.0 and probabilities[y, x] < best_probability:
  445. best_probability = probabilities[y, x]
  446. ey, ex = y, x
  447.  
  448.  
  449. while True:
  450. cy = random.randint(0, reduced.nrows-1)
  451. cx = random.randint(0, reduced.ncols-1)
  452.  
  453. if reduced[cy,cx] == '.':
  454. break
  455.  
  456. if best_probability < 1.0:
  457. do_print("Educational guess at %d %d with prob %.4f%% against %f" % (cy, cx, 100.0 * best_probability,
  458. probabilities[cy,cx]))
  459. guess_empty_at(grid, reduced, ey, ex)
  460. continue
  461.  
  462. do_print("Non-educational guess at %d %d" % (cy, cx))
  463. guess_empty_at(grid, reduced, cy, cx)
  464.  
  465. def exampleSolveGame(grid):
  466. try:
  467. exampleSolveGame0(grid)
  468. except LostGameError, ex:
  469. if STEP == -43:
  470. print("LOST %s" % LAST_ACTION)
  471. print_grid(LAST_R)
  472. print("ACTUAL FIELD:")
  473. #grid.printMines()
  474. print("\n")
  475. raise ex
  476.  
  477. def main():
  478. # Set this to True if you want to play a game of Minesweeper with a
  479. # crappy text interface. Set this to False if you want to test
  480. # how good the example AI is (spoiler: not very).
  481. doYouWantToPlayAGame = False
  482.  
  483. if doYouWantToPlayAGame:
  484. playGame(9, 9, 10)
  485. else:
  486. testSolver(exampleSolveGame, 10000, True,
  487. beginner="mines_beginner.txt",
  488. intermediate="mines_intermediate.txt",
  489. expert="mines_expert.txt")
  490. return
  491. try:
  492. mf = Minefield(16, 30, 99)
  493. mf.load("test.mf")
  494. exampleSolveGame(mf)
  495. print(mf.isSolved())
  496. except LostGameError:
  497. print ("lost")
  498. pass
  499.  
  500. if __name__ == "__main__":
  501. random.seed(12475)
  502. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement