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):
"""
Returns the set of all cells in self.cells known to be mines.
"""
if len(self.cells) == self.count and self.count != 0:
return self.cells
else:
return set()
def known_safes(self):
"""
Returns the set of all cells in self.cells known to be safe.
"""
if self.count == 0:
return self.cells
else:
return set()
def mark_mine(self, cell):
"""
Updates internal knowledge representation given the fact that
a cell is known to be a mine.
"""
if cell in self.cells:
self.cells.remove(cell)
self.count -= 1
def mark_safe(self, cell):
"""
Updates internal knowledge representation given the fact that
a cell is known to be safe.
"""
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
self.moves_made.add(cell)
# 2) mark the cell as safe
self.mark_safe(cell)
# 3) add a new sentence to the AI's knowledge base based
# on the value of `cell` and `count`
up = cell[1] - 1
down = cell[1] + 1
left = cell[0] - 1
right = cell[0] + 1
undetermined = set()
mine_counter = 0
horizontal_boundary = self.width
vertical_boundary = self.height
for i in range(left, right + 1):
for j in range(up, down + 1):
# Ignore cell if it is out of boundaries or if is the cell itself
if j < 0 or j > vertical_boundary or i < 0 or i > horizontal_boundary or (i,j) == cell:
continue
# Augment mine counter if cell is in mines list
if (i,j) in self.mines:
mine_counter += 1
# Add the mine to the undetermined list if cell is not in safes
if (i,j) not in self.safes:
undetermined.add((i,j))
new_counter = count - mine_counter
new_sentence = Sentence(undetermined, new_counter)
print(f'Move on cell: {cell} has added sentence to knowledge {undetermined} = {new_counter}' )
self.knowledge.append(new_sentence)
# 4) mark any additional cells as safe or as mines if it
# can be concluded based on the AI's knowledge base
for sen in self.knowledge:
if sen.known_mines():
for cell in sen.known_mines().copy():
self.mark_mine(cell)
if sen.known_safes():
for cell in sen.known_safes().copy():
self.mark_safe(cell)
#5) add any new sentences to the AI's knowledge base if they can
# be inferred from existing knowledge
knowledge_copy = self.knowledge.copy()
for sentence1 in knowledge_copy:
for sentence2 in knowledge_copy:
sen1 = sentence1.cells
sen2 = sentence2.cells
count1 = sentence1.count
count2 = sentence2.count
if sen2.issubset(sen1) and knowledge_copy.index(sentence2) != knowledge_copy.index(sentence1):
for rem in sen2:
if rem in sen1:
sen1.remove(rem)
new_count = count1 - count2
another_sentence = Sentence(sen1, new_count)
self.knowledge.append(another_sentence)
def make_safe_move(self):
"""
Returns a safe cell to choose on the Minesweeper board.
The move must be known to be safe, and not already a move
that has been made.
This function may use the knowledge in self.mines, self.safes
and self.moves_made, but should not modify any of those values.
"""
for move in self.safes:
if move not in self.moves_made and move not in self.mines:
return move
else:
return None
def make_random_move(self):
"""
Returns a move to make on the Minesweeper board.
Should choose randomly among cells that:
1) have not already been chosen, and
2) are not known to be mines
"""
for i in range(self.height):
for j in range(self.width):
move = (i, j)
if move not in self.moves_made and move not in self.mines:
return move
return None
When I try to run the AI to play this minesweeper code, it runs ok in some moves, but after 6 or more moves, it just crashes and the program exits. I think it is a problem with my PC resources. How can I fix it?