Advertisement
Guest User

Untitled

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