Advertisement
Guest User

CS50AI Minesweeper (fails sometimes?)

a guest
Nov 22nd, 2020
702
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.15 KB | None | 0 0
  1. import itertools
  2. import random
  3. from itertools import product
  4.  
  5.  
  6. class Minesweeper:
  7.     """
  8.    Minesweeper game representation
  9.    """
  10.  
  11.     def __init__(self, height=8, width=8, mines=8):
  12.  
  13.         # Set initial width, height, and number of mines
  14.         self.height = height
  15.         self.width = width
  16.         self.mines = set()
  17.  
  18.         # Initialize an empty field with no mines
  19.         self.board = []
  20.         for i in range(self.height):
  21.             row = []
  22.             for j in range(self.width):
  23.                 row.append(False)
  24.             self.board.append(row)
  25.  
  26.         # Add mines randomly
  27.         while len(self.mines) != mines:
  28.             i = random.randrange(height)
  29.             j = random.randrange(width)
  30.             if not self.board[i][j]:
  31.                 self.mines.add((i, j))
  32.                 self.board[i][j] = True
  33.  
  34.         # At first, player has found no mines
  35.         self.mines_found = set()
  36.  
  37.     def print(self):
  38.         """
  39.        Prints a text-based representation
  40.        of where mines are located.
  41.        """
  42.         for i in range(self.height):
  43.             print("--" * self.width + "-")
  44.             for j in range(self.width):
  45.                 if self.board[i][j]:
  46.                     print("|X", end="")
  47.                 else:
  48.                     print("| ", end="")
  49.             print("|")
  50.         print("--" * self.width + "-")
  51.  
  52.     def is_mine(self, cell):
  53.         i, j = cell
  54.         return self.board[i][j]
  55.  
  56.     def nearby_mines(self, cell):
  57.         """
  58.        Returns the number of mines that are
  59.        within one row and column of a given cell,
  60.        not including the cell itself.
  61.        """
  62.  
  63.         # Keep count of nearby mines
  64.         count = 0
  65.  
  66.         # Loop over all cells within one row and column
  67.         for i in range(cell[0] - 1, cell[0] + 2):
  68.             for j in range(cell[1] - 1, cell[1] + 2):
  69.  
  70.                 # Ignore the cell itself
  71.                 if (i, j) == cell:
  72.                     continue
  73.  
  74.                 # Update count if cell in bounds and is mine
  75.                 if 0 <= i < self.height and 0 <= j < self.width:
  76.                     if self.board[i][j]:
  77.                         count += 1
  78.  
  79.         return count
  80.  
  81.     def won(self):
  82.         """
  83.        Checks if all mines have been flagged.
  84.        """
  85.         return self.mines_found == self.mines
  86.  
  87.  
  88. class Sentence:
  89.     """
  90.    Logical statement about a Minesweeper game
  91.    A sentence consists of a set of board cells,
  92.    and a count of the number of those cells which are mines.
  93.    """
  94.  
  95.     _no = 0
  96.  
  97.     def __init__(self, cells, count):
  98.         self.cells = set(cells)
  99.         self.count = count
  100.         self.no = Sentence._no
  101.         Sentence._no += 1
  102.  
  103.     def __eq__(self, other):
  104.         return self.cells == other.cells and self.count == other.count
  105.  
  106.     def __str__(self):
  107.         return f"n.{self.no}: {self.cells} = {self.count}"
  108.  
  109.     def __repr__(self):
  110.         return f"n.{self.no}: {self.cells} = {self.count}"
  111.  
  112.     def known_mines(self):
  113.         """
  114.        Returns the set of all cells in self.cells known to be mines.
  115.        """
  116.         if len(self.cells) == self.count:
  117.             return self.cells
  118.         else:
  119.             return set()
  120.         # raise NotImplementedError
  121.  
  122.     def known_safes(self):
  123.         """
  124.        Returns the set of all cells in self.cells known to be safe.
  125.        """
  126.         # raise NotImplementedError
  127.         if self.count == 0:
  128.             if len(self.cells) != 0:
  129.                 print(f"{self} => SAFE: {self.cells}")
  130.             return self.cells
  131.         else:
  132.             return set()
  133.  
  134.     def mark_mine(self, cell):
  135.         """
  136.        Updates internal knowledge representation given the fact that
  137.        a cell is known to be a mine.
  138.        """
  139.         # print('mark_mine')
  140.         # 1. Check if cell is one of the cells included in the sentence
  141.         # 2. If cell is in the sentence, update the sentence so that
  142.         #    cell is no longer in the sentence, but still
  143.         if cell not in self.cells:
  144.             return
  145.  
  146.         self.cells.remove(cell)
  147.         self.count -= 1
  148.  
  149.     def mark_safe(self, cell):
  150.         """
  151.        Updates internal knowledge representation given the fact that
  152.        a cell is known to be safe.
  153.        """
  154.         if cell not in self.cells:
  155.             return
  156.  
  157.         self.cells.remove(cell)
  158.  
  159.  
  160. class MinesweeperAI:
  161.     """
  162.    Minesweeper game player
  163.    """
  164.  
  165.     def __init__(self, height=8, width=8):
  166.  
  167.         # Set initial height and width
  168.         self.height = height
  169.         self.width = width
  170.  
  171.         # Keep track of which cells have been clicked on
  172.         self.moves_made = set()
  173.  
  174.         # Keep track of cells known to be safe or mines
  175.         self.mines = set()
  176.         self.safes = set()
  177.  
  178.         # List of sentences about the game known to be true
  179.         self.knowledge = []
  180.  
  181.     def mark_mine(self, cell):
  182.         """
  183.        Marks a cell as a mine, and updates all knowledge
  184.        to mark that cell as a mine as well.
  185.        """
  186.         self.mines.add(cell)
  187.         for sentence in self.knowledge:
  188.             sentence.mark_mine(cell)
  189.  
  190.     def mark_safe(self, cell):
  191.         """
  192.        Marks a cell as safe, and updates all knowledge
  193.        to mark that cell as safe as well.
  194.        """
  195.         self.safes.add(cell)
  196.         for sentence in self.knowledge:
  197.             sentence.mark_safe(cell)
  198.  
  199.     def cross_check(self):
  200.         """
  201.        Checks all the sentences using the subset inference
  202.        """
  203.         kn_bef = self.knowledge.copy()
  204.         for i in range(0, len(kn_bef) - 1):
  205.             for j in range(i + 1, len(kn_bef)):
  206.                 sent1 = kn_bef[i]
  207.                 sent2 = kn_bef[j]
  208.                 if (sent1.cells and sent2.cells):
  209.                     if sent1.cells.issubset(sent2.cells):
  210.                         new_sent = Sentence(
  211.                             sent2.cells - sent1.cells,
  212.                             sent2.count - sent1.count
  213.                         )
  214.                         if new_sent not in self.knowledge:
  215.                             self.knowledge.append(new_sent)
  216.  
  217.     def infer(self):
  218.         """
  219.        Updates the stats using updated knowledge
  220.        """
  221.         for sent in self.knowledge:
  222.             for cell in sent.known_safes().copy():
  223.                 self.mark_safe(cell)
  224.             for cell in sent.known_mines().copy():
  225.                 self.mark_mine(cell)
  226.  
  227.     # Algorithm by etuardu from StackOverflow
  228.     def neighbours(self, cell):
  229.         size = 8
  230.         for c in product(*(range(n - 1, n + 2) for n in cell)):
  231.             if c != cell and all(0 <= n < size for n in c):
  232.                 yield c
  233.  
  234.     def add_knowledge(self, cell, count):
  235.         """
  236.        Called when the Minesweeper board tells us, for a given
  237.        safe cell, how many neighboring cells have mines in them.
  238.  
  239.        This function should:
  240.            1) mark the cell as a move that has been made
  241.            2) mark the cell as safe
  242.            3) add a new sentence to the AI's knowledge base
  243.               based on the value of `cell` and `count`
  244.            4) mark any additional cells as safe or as mines
  245.               if it can be concluded based on the AI's knowledge base
  246.            5) add any new sentences to the AI's knowledge base
  247.               if they can be inferred from existing knowledge
  248.        """
  249.         self.moves_made.add(cell)
  250.  
  251.         self.mark_safe(cell)
  252.  
  253.         cells = set()
  254.  
  255.         for c in self.neighbours(cell):
  256.             if c not in self.safes:
  257.                 cells.add(c)
  258.  
  259.         sent = Sentence(cells, count)
  260.  
  261.         self.knowledge.append(sent)
  262.  
  263.         self.infer()
  264.         self.cross_check()
  265.         self.infer()
  266.  
  267.     def make_safe_move(self):
  268.         """
  269.        Returns a safe cell to choose on the Minesweeper board.
  270.        The move must be known to be safe, and not already a move
  271.        that has been made.
  272.  
  273.        This function may use the knowledge in self.mines, self.safes
  274.        and self.moves_made, but should not modify any of those values.
  275.        """
  276.         if len(self.mines) == 8:
  277.             return None
  278.         for cell in self.safes:
  279.             if cell not in self.moves_made and cell not in self.mines:
  280.                 return cell
  281.         return None
  282.  
  283.     def make_random_move(self):
  284.         # print('make_random_move')
  285.         # print(f"Mines: {self.mines}")
  286.         """
  287.        Returns a move to make on the Minesweeper board.
  288.        Should choose randomly among cells that:
  289.            1) have not already been chosen, and
  290.            2) are not known to be mines
  291.        """
  292.         if len(self.mines) == 8:
  293.             return None
  294.         possible_moves = []
  295.         for i in range(self.height):
  296.             for j in range(self.width):
  297.                 if (i, j) not in self.moves_made:
  298.                     possible_moves.append((i, j))
  299.         while len(possible_moves) != 0:
  300.             index = random.randrange(len(possible_moves))
  301.             if possible_moves[index] not in self.mines:
  302.                 return possible_moves[index]
  303.             possible_moves.pop(index)
  304.         return None
  305.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement