Please keep in mind that at this point in the course some of the assignment points will be awarded based on your programs' commenting, formatting, and design -- under the heading of style.
Any readable and well-commented style is OK. These are some of the things to look out for when writing programs for yourself (or others) to read:
First the utterly gratuitous story... You've been hired by Spamsoft, a major software company. Spamsoft would like to implement a "retro" version of the famous Minesweeper game which they call Spamsweeper. (By "retro" we mean that the game will be played on the keyboard and using text-based graphics rather than with a mouse and fancy graphics.)
Spamsoft is a big company, and one division of the company has already developed a few Python classes that will be of useful in your game. However, writing the actual game is your job. (In real software development projects, many people are usually involved and different teams will develop different pieces of the project. Therefore, this part of the assignment exposes you to the idea of using existing classes to write more sophisticated programs.)
The objective of the Spamsweeper game is to safely "defuse" all of the hidden cans of Spam. You will find a compiled version of our game on the top-level assignment page. You can play that game by double-clicking the file after you download it, or running it from the command line using python Spamsweeper.pyc.
At each iteration, you are asked for the row (number) and then column (letter) that you wish to play. Then, you are asked if it is Spam ('s') or free ('f'). If you choose 's' for Spam, there had better be a can of Spam at that location! If so, the cell is exposed and the Spam counter is decremented by one. If not, you lose. If you choose 'f' for free, there had better not be a can of Spam at that location. If there is a can of Spam there, you lose! If not, the cell is exposed. You win when there are no more cans left to find. That is, when you have exposed all non-spam cells OR all the spam cells (or both).
To get you started, download the files Spamsweeper.py from the top of this page. This file contains classes that have been provided for you by "another team" at the company. In a nutshell, the Boardcell class defines one cell of the board of a game. The cell keeps track of not just whether it is "mined" (i.e. contains a can of Spam) but also whether or not this cell should be exposed to the player (initially all cells are unexposed but they become exposed as the player selects them during the course of the game), and the number of surrounding cells that contain mines (Spam). Please read through this code carefully. Read the comments and the code and make sure that you understand what each class provides. Not only will you use these classes in your game, but some of the implementation ideas in this code will be useful to you in later parts of this assignments.
Note that one method (a function defined inside a class is called a "method") of particular utility is printBoard. This method displays the board for the user, hiding the contents of all of the cells that have not yet been exposed by the player and revealing all of the exposed cells. There is also a cheatPrintBoard method that exposes everything! This is useful when debugging your program. It will be handy for you to know where the Spam is located and what the neighor count values are so that you can see if your program is working correctly.
Your job is to write the Spamsweeper game. You need not define it as a class. Simply use the classes that Here are the requirements of the game and the implementation:
In this second part of the assignment, you will compare several implementations for the abstract data type (ADT) Queue. Your goal in this section is to compare the efficiencies of these implementations. You will compare the following three impementations:
You'll also see that there is a lot of other provided code. We'll talk about that in a minute.
Your task is to implement two new classes: RawQ and ListQ. To do so, you need to implement all of the methods above, for each of those two classes.
ListQ will be similar to DequeQ, but it will use a standard Python list to store its contents. (Note that you'll likely find Python's dir command that we have discussed in class useful to figure out what lists can do).
RawQ is a "from scratch" implementation of a queue as a linked list. It should be implemented as a linked list, like the Stack example we saw in class.
Helper class: QCell
Similar to the way in which the Stack class used an inner class named StackCell, your RawQ file might use a QCell class that will be the type of each cell in your RawQ.
Data members for RawQ
The RawQ class itself should have two data members (initialized in __init__)
self.front # the front of the RawQ object - cells are removed from here
self.back # the back of the RawQ object - cells are added to here
Format to use for __repr__
For uniformity of display, please use the following format
when implementing your __repr__
method. Notice that <FRONT>
and <BACK>
always are printed, and <FRONT>
always has a single space after it. When
elements are present, they are printed from front-to-back, each with a
single space
after it, including the final element. <BACK>,
however, does not have a space following it.
Testing your code is important! We have provided a simple tester function. Make sure that it works for your implementation. Also note that it does not test the __repr__ function. You should be sure to test that function too, possibly by adding it to the tester we've provided.
This problem should be done individually. If you worked with a partner on the previous parts, you may use the Queue implementation that you wrote together. Or you may choose to use the built-in Python deque class.
You should start this problem with the Maze.py file provided above. You must run this file from the command line, passing the maze file as a command-line argument. E.g.:
% python Maze.py test1.maze What is already in Maze?
Please read carefully through the provided Maze.py file.
The Maze class has a method for reading a maze
from a file. The format
of the file should be something like this
6 10
**********
*S* *
* * *
* ** *
* *D *
**********
where the first line has the height and width of the maze, and the
subsequent
lines contain the maze itself, as characters, with the asterisk '*'
representing the walls, spaces are free space, and letters are special
free space:
'S' is the start, and 'D' is
the destination for the maze.
The Maze class also has a __repr__ method, a method named findMazeCell that will return a MazeCell that is holding a particular character (S or D, usually), and a main method for testing.
What needs to be implemented?
The method that needs to be implemented is
BFS(self, start, destination)This BFS method does not need to return a value. However, it needs to either
What should BFS return?
Nothing!
Rather, the idea is that once your search has found the destination, it
should have set each cell's parent reference.
Before returning our of the BFS method, run a
loop to change the contents of the MazeCells
on the path from 'D' to 'S'
to the lowercase 'o' character.
Note that this means running through the path in reverse.
Remember that breadth-first search (BFS) uses a queue to keep track of the MazeCells that are waiting to be explored. To support this, you'll need to use your queue implementation from Part 2, or you may use Python's deque class directly. The queue will contain references to MazeCells that are waiting to explore their surrounding cells. Initially, the queue will be populated with just one reference - namely to the MazeCell corresponding to the starting element. You'll just enqueue this MazeCell onto your initially empty queue to get started. Then, while the queue is not empty, repeatedly dequeue the next MazeCell to be processed, mark all its non-wall unvisited neighbors, and enqueue them for later consideration!
Do I need to make new MazeCells to enqueue? You could, but this would be messy! Notice that the maze is already made up of MazeCells! Therefore, the queue need not make new MazeCells - it can just keep references to the MazeCells that are already making up the maze!
Notice that your queue implementation can happily enqueue any kind of Object, so it will be happy to enqueue MazeCells in particular!
Finally, bear in mind that the perimeter of the maze is not necessarily entirely made of walls. Be careful not to fall of the edge of the maze - this will inevitably cause a null pointer exception error.
Other notes and details...
Note that Maze starts automatically using the "main trick" as usual. However, this time it must be called from the COMMAND LINE (e.g., shell prompt), because you need to pass an argument to it:
% python Maze.py test1.maze
The provided file doesn't really do much, since BFS is not yet implemented.
Here is what the Maze shown in the example above might look like when printed after calling BFS:
**********
*S* *
*o* oooo *
*oooo**o *
* *Do *
**********
There certainly may be more than one shortest path (a tie for
shortest). For
example, this is equally valid:
**********
*S* *
*o*ooooo *
*ooo **o *
* *Do *
**********
The difference would be that, in the latter example, the maze-solving
"mouse"
searches North before searching East; in the former example, the
opposite is true.
If you want to have more fun with the BFS program, here's an optional bonus problem. There is more thinking here than coding!
Your objective now is to emulate the kind of service that several direction-finding websites provide. Specifically, your breadth-first search should be modified to find the shortest path with the fewest number of turns. That is, among all shortest paths, we want one that turns the fewest number of times. The reason is that paths with fewer turns are easier for humans to follow and are easier to describe.
Specifically, you should make a copy of your Maze.py file and call it MazeBonus.py. This program should take a maze as input and should display a shortest path with the least number of turns. In addition, it should report the length of the trip, the number of turns required, and then give English instructions on how to get from the start to the destination following this shortest path with fewest turns. For example, instructions might look like:
Trip length: 14 cells
Number of turns: 2
Go north 5 cells.
Turn to the west.
Go west 6 cells.
Turn to the south.
Go south 3 cells.
You have arrived at your destination.
You may wish to modify some of the code that we've provided in Maze.py. In any case, please test your code carefully to make sure that it really is giving the absolute minimum number of turns among all shortest paths. Of course, your program must still be very fast: We'll test your program with large mazes and allow your program only a couple of seconds to find the solution. Thus, using a brute-force approach will not suffice.
Submit this file as MazeBonus.py.