|
|
- import itertools
- import random
-
-
- class Minesweeper():
- """
- Minesweeper game representation
- """
-
- def __init__(self, height=8, width=8, mines=8):
-
- # Set initial width, height, and number of mines
- self.height = height
- self.width = width
- self.mines = set()
-
- # Initialize an empty field with no mines
- self.board = []
- for i in range(self.height):
- row = []
- for j in range(self.width):
- row.append(False)
- self.board.append(row)
-
- # Add mines randomly
- while len(self.mines) != mines:
- i = random.randrange(height)
- j = random.randrange(width)
- if not self.board[i][j]:
- self.mines.add((i, j))
- self.board[i][j] = True
-
- # At first, player has found no mines
- self.mines_found = set()
-
- def print(self):
- """
- Prints a text-based representation
- of where mines are located.
- """
- for i in range(self.height):
- print("--" * self.width + "-")
- for j in range(self.width):
- if self.board[i][j]:
- print("|X", end="")
- else:
- print("| ", end="")
- print("|")
- print("--" * self.width + "-")
-
- def is_mine(self, cell):
- i, j = cell
- return self.board[i][j]
-
- def nearby_mines(self, cell):
- """
- Returns the number of mines that are
- within one row and column of a given cell,
- not including the cell itself.
- """
-
- # Keep count of nearby mines
- count = 0
-
- # Loop over all cells within one row and column
- for i in range(cell[0] - 1, cell[0] + 2):
- for j in range(cell[1] - 1, cell[1] + 2):
-
- # Ignore the cell itself
- if (i, j) == cell:
- continue
-
- # Update count if cell in bounds and is mine
- if 0 <= i < self.height and 0 <= j < self.width:
- if self.board[i][j]:
- count += 1
-
- return count
-
- def won(self):
- """
- Checks if all mines have been flagged.
- """
- return self.mines_found == self.mines
-
-
- class Sentence():
- """
- Logical statement about a Minesweeper game
- A sentence consists of a set of board cells,
- and a count of the number of those cells which are mines.
- """
-
- def __init__(self, cells, count):
- self.cells = set(cells)
- self.count = count
-
- def __eq__(self, other):
- return self.cells == other.cells and self.count == other.count
-
- def __str__(self):
- return f"{self.cells} = {self.count}"
-
- def known_mines(self):
- if self.cells.__len__() == self.count:
- return self.cells
- return set()
-
- def known_safes(self):
- if self.count == 0:
- return self.cells
- return set()
-
- def mark_mine(self, cell): # Remove cell from set and decrease count by one
- if cell in self.cells:
- self.cells.remove(cell)
- self.count -= 1
-
- def mark_safe(self, cell): # Remove safe cell from cells set
- if cell in self.cells:
- self.cells.remove(cell)
-
-
- class MinesweeperAI():
- """
- Minesweeper game player
- """
-
- def __init__(self, height=8, width=8):
-
- # Set initial height and width
- self.height = height
- self.width = width
-
- # Keep track of which cells have been clicked on
- self.moves_made = set()
-
- # Keep track of cells known to be safe or mines
- self.mines = set()
- self.safes = set()
-
- # List of sentences about the game known to be true
- self.knowledge = []
-
- def mark_mine(self, cell):
- """
- Marks a cell as a mine, and updates all knowledge
- to mark that cell as a mine as well.
- """
- self.mines.add(cell)
- for sentence in self.knowledge:
- sentence.mark_mine(cell)
-
- def mark_safe(self, cell):
- """
- Marks a cell as safe, and updates all knowledge
- to mark that cell as safe as well.
- """
- self.safes.add(cell)
- for sentence in self.knowledge:
- sentence.mark_safe(cell)
-
- def add_knowledge(self, cell, count):
- """
- Called when the Minesweeper board tells us, for a given
- safe cell, how many neighboring cells have mines in them.
-
- This function should:
- 1) mark the cell as a move that has been made
- 2) mark the cell as safe
- 3) add a new sentence to the AI's knowledge base
- based on the value of `cell` and `count`
- 4) mark any additional cells as safe or as mines
- if it can be concluded based on the AI's knowledge base
- 5) add any new sentences to the AI's knowledge base
- if they can be inferred from existing knowledge
- """
- self.moves_made.add(cell) #Store information about the cell
- self.mark_safe(cell)
- surrounding_cells = set()
- for i in range(cell[0]-1 if cell[0] - 1 >= 0 else 0, cell[0]+2 if cell[0] + 2 <= self.width else self.width):
- for j in range(cell[1]-1 if cell[1] - 1 >= 0 else 0, cell[1]+2 if cell[1] + 2 <= self.height else self.height):
- count -= int((i, j) in self.mines)
- if (i, j) not in self.safes.union(self.mines):
- surrounding_cells.add((i, j))
-
- new_knowledge = Sentence(surrounding_cells, count)
- if new_knowledge in self.knowledge:
- return
- inferred_knowledge = []
- knowledge_cpy = self.knowledge.copy()
- popped = 0
- for i, sentence in enumerate(knowledge_cpy):
- if sentence.cells == set():
- self.knowledge.pop(i - popped)
- popped += 1
- continue
- if new_knowledge.cells.issubset(sentence.cells):
- new_cells = sentence.cells - new_knowledge.cells
- new_count = sentence.count - new_knowledge.count
- new_sentence = Sentence(new_cells, new_count)
- inferred_knowledge.append(Sentence(new_cells, new_count))
-
- elif sentence.cells.issubset(new_knowledge.cells):
- new_cells = new_knowledge.cells - sentence.cells
- new_count = new_knowledge.count - sentence.count
- new_sentence = Sentence(new_cells, new_count)
- inferred_knowledge.append(new_sentence)
-
- for i in inferred_knowledge:
- if i.known_safes() != set():
- for j in i.cells:
- self.mark_safe(j)
- elif i.known_mines() != set():
- for j in i.cells:
- self.mark_mine(j)
-
- for i in self.knowledge:
- cells = i.cells.copy()
- if i.known_safes() != set():
- for j in cells:
- self.mark_safe(j)
- elif i.known_mines() != set():
- for j in cells:
- self.mark_mine(j)
-
- inferred_knowledge.append(new_knowledge)
- for i in inferred_knowledge:
- exists = False
- for j in self.knowledge:
- if i == j:
- exists = True
- if not exists:
- self.knowledge.append(i)
-
- def make_safe_move(self):
- available_moves = self.safes - self.moves_made
- for s in self.knowledge:
- available_moves = available_moves.union(s.known_safes())
- if available_moves.__len__() == 0:
- return None
- return available_moves.pop()
-
- def make_random_move(self):
- unavailable_moves = self.moves_made.union(self.mines)
- available_moves = set()
- for i in range(self.width):
- for j in range(self.height):
- if (i, j) not in unavailable_moves:
- available_moves.add((i, j))
- if available_moves.__len__() == 0:
- return None
- return available_moves.pop()
|