Have you ever played a game of minesweeper1? It is provided in an operating system the name of which escapes us. Oh well, the intention of the game is to discover where all mines are hidden in a rectangular $$m\times n$$ field with $$m$$ rows and $$n$$ columns. To make things a little more easy, the numbers are given in the boxes of the field. These indicate the number of mines that are hidden in adjacent boxes. Here, both horizontally, vertically and diagonally adjacent boxes count. Suppose, for example, that two mines are hidden in a $$4\times 5$$ field (their location is indicated with an asterisk):
*.... ..*.. ..... .....
If we were to indicate the amount of mines in adjacent boxes in the empty boxes (indicated with a full stop), we would obtain the following representation of the field:
*2110 12*10 01110 00000
As you can see, every box has a maximum of 8 adjacent boxes.
Define a class Minesweeper which can be used to represent a field of a game of minesweeper. This class must support the following methods:
An initializing method __init__ that sees to an initial filling-in of the field as a rectangular $$m \times n$$ grid with $$m$$ rows and $$n$$ columns. The standard field is a rectangular grid that consists of 8 rows and 8 columns and contains no mines. However, three optional parameters can be given to the initializing method: a parameter rows that indicates the number of rows of the field (standard value 8), a parameter columns that indicates the numbers of columns in the field (standard value 8) and a parameter mines: a list of tuples that indicates where the mines of the field are hidden. Here, every tuple $$(x, y)$$ indicates that a mine is hidden in the grid on row $$x$$ and column $$y$$. Rows and columns are always numbered from 0. The given field must always consist of at least 2 rows and 2 columns. Look at the example below to verify how the initializing method must react if one of these conditions is not met. Also, the action the initializing method should undertake if a given position for the mine is not within the field, is given in the example.
Methods rows and columns that respectively print the number of rows and the number of columns in the field.
A method isMine that prints a Boolean value as a result. This value indicates whether or not a mine was placed on a given position in the field. The co-ordinates of the given position must be given to the method as an argument.
Methods addMine and eraseMine that respectively add or erase a mine on a given position in the field. The co-ordinates of the given position must be given to the method as arguments.
A method nearbyMines that indicates the number of mines on nearby positions for a given position of the field. The co-ordinates of the given position must be given to the method as arguments.
A method __repr__ that prints a string representation of the field in the format of a valid Python expression that can be used to construct a new field for the game minesweeper that has the same state as the current object. This string representation has the format Minesweeper(r, c, list), where r and c represent the rows and columns of the field. The list of tuples indicates the positions of the mines on the field, listed from left to right and from top to bottom.
A method __str__ that prints a string representation of the field, where every row of the grid is represented as an independent line of text. Positions of the field on which mines are situated are indicated with an asterisk (*) and positions where no mine is situated are indicated with a digit that indicates the number of adjacent boxes that have a mine.
Methods to which a position on the field must be given (isMine, addMine, eraseMine and nearbyMines), must always verify whether this position is situated within the boundaries of the field. If this should not be the case, an AssertionError must be raised with an appropriate message as indicated in the example below.
>>> field = Minesweeper(4, 5, [(0, 0), (1, 2)])
>>> print(field)
*2110
12*10
01110
00000
>>> field.rows()
4
>>> field.columns()
5
>>> field.isMine(0, 0)
True
>>> field.nearbyMines(1, 0)
1
>>> field.nearbyMines(1, 1)
2
>>> field.addMine(3, 3)
>>> print(field)
*2110
12*10
01221
001*1
>>> field.eraseMine(0, 0)
>>> print(field.isMine(0, 0))
False
>>> print(field)
01110
01*10
01221
001*1
>>> field
Minesweeper(4, 5, [(1, 2), (3, 3)])
>>> field.isMine(4, 4)
Traceback (most recent call last):
AssertionError: invalid position (4, 4)
>>> speelveld = Minesweeper(1, 1)
Traceback (most recent call last):
AssertionError: field must contain at least two rows
>>> speelveld = Minesweeper(2, 1)
Traceback (most recent call last):
AssertionError: field must contain at least two columns