Educational Objectives: After completing this assignment, the student should be able to accomplish the following:
Operational Objectives: Implement the Partition class and a client program that generates random mazes.
Deliverables: Two files:
partition.h # contains template < typename N > class Partition ranmaze.cpp # client program of Partition < long > generating random mazes
The official development, testing, and assessment environment is g++47 -std=c++11 -Wall -Wextra on the linprog machines. Code should compile without error or warning.
Begin by copying the following files from the course directory into your proj3 directory:
LIB/proj3/fpartition.cpp # test harness for Partition LIB/proj3/makefile # builds most executables LIB/area51/fpartition.x # sample executable LIB/area51/ranmaze.x # sample executable LIB/area51/mazetest.x # maze analyzer
Execute ranmaze.x to generate maze files and mazetest.x to analyze the resulting maze files. You will follow the same process in testing your own ranmaze and its resulting maze files. Also pay careful attention to the detailed I/O behavior of ranmaze, this is how your program should behave.
Warning: Submit scripts do not work on the program and linprog servers. Use shell.cs.fsu.edu to submit projects. If you do not receive the second confirmation with the contents of your project, there has been a malfunction.
Use the API defined by the following:
template < typename N = size_t > class Partition { public: explicit Partition ( N size ); // create singletons {0} .. {size-1} void Reset (); // reverts to singletons void Reset ( N newsize ); void Push () { parent_.PushBack(parent_.Size()); height_.PushBack(0); } void Union ( N x , N y ) { Link(Root(x),Root(y)); } bool Find ( N x , N y ) { return Root(x) == Root(y); } bool Find ( N x , N y ) const { return Root(x) == Root(y); } size_t Size () const { return height_.Size(); } void Dump ( std::ostream& os ) const; size_t Components () const; private: // methods N Root ( N x ); // path compression changes state N Root ( N x ) const; // no path compression void Link ( N root1 , N root2 ); // union assuming arguments are roots private: // objects fsu::Vector <N> parent_; fsu::Vector <N> height_; };
Follow the implementation started here. This is distinct from that of the lecture notes in that
Only a few key implementations are lacking.
Be sure that you implement path compression in the non-const version of Root. Of course, you will be forced to use the non-compression version for the const version of Root.
Use the distributed example executable fpartition.x to see what behavior is expected from Dump, Size, and Components.
Output maze files according to the spec in the Union-Find notes Appendix: Maze Technology. Take care that all maze files are syntactically and semantically correct - mazetest.x is helpful in testing your output.
The maze file should have the start as cell 0 (the upper left corner of the maze) and the goal as cell numrows x numcols - 1 (the lower right corner of the maze).
ranmaze should expect three command line arguments: number of rows, number of columns, and filename.
ranmaze should output two versions of the random maze: (1) the first time goal is reachable from start, output the maze to the file "filename.[components]" where [components] is the actual number of components in the maze; and (2) after all cells are reachable from start, output the maze to the file "filename.1".
Take care that your mazes are corerctly "random" in the sense that all walls have the same probability of being selected for inspection at each step. This is mostly a matter of judicious design, especially at boundary cases. For example: be sure that a cell face on an interior cell has the same chance of selection as a cell face of a boundary cell, where fewer faces are eligible.
The Partition constructor needs to call the correct Vector constructors in order to get the representation initialized to singletons:
Partition::Partition ( N size ) : parent_((size_t)size, 0) , height_((size_t)size, 0) { // loop setting parent values }
Be sure to explicitly cast when mixing the two types N and size_t.
Use the distributed executable area51/fpartition.x to see the expected behavior of Components and Dump.
A very handy way to generate unsigned integers is with the fsu random object generator. Include these files:
#include <xran.h> #include <xran.cpp>
and declare an object and use it like this:
fsu::Random_unsigned_int ran; ... x = ran(a,b); // x is a random unsigned int in the range a <= x < b y = ran(0,4); // y is a random choice of 0,1,2,3 (N,E,S,W) z = ran(0,2); // z is a random choice of 0 or 1 (coin flip)
This generator uses the Marsaglia KISS generator as its underlying engine.
An effective way to organize ranmaze.cpp is with a function
void Connect( size_t beg , size_t end , // start and goal cells size_t numrows, size_t numcols, // maze dimensions fsu::Vector<unsigned char>& maze , // walls codes for cells fsu::Partition<size_t>& p // tracks component structure );
that can be called once for the "first pass" maze and subsequently as often as needed for the "one component" maze:
... size_t numcells = numrows*numcols; start = 0; goal = numcells - 1; fsu::Vector<unsigned char> maze (numcells, 15); // all closed boxes fsu::Partition<size_t> p (numcells); // all singletons // ensure goal is reachable from start: std::cout << " components after 0 passes: " << p.Components() << '\n'; Connect (start, goal, numrows, numcols, maze, p); size_t components = p.Components(); std::cout << " components after 1 pass: " << components << '\n'; if (components > 1) // output intermediate result the first time goal is reachable { // build filename filec ... // output first pass maze out1.open(filec); WriteMaze(out1, start, goal, numrows, numcols, maze); out1.close(); out1.clear(); std::cout << "1-pass maze written to file " << filec << '\n'; } // continue until all cells are reachable if (components > 1) { for (size_t cell = 1; cell < numcells; ++cell) { Connect (0, cell, numrows, numcols, maze, p); } } std::cout << " components after all passes: " << p.Components() << '\n'; out1.open(file1); WriteMaze(out1, start, goal, numrows, numcols, maze); out1.close(); out1.clear(); std::cout << "n-pass maze written to file " << file1 << '\n';
Building the filename is just the metedious [meticulous + tedious] work of creating the correct size null-terminated character array and putting all the characters in the right place. You need two such file names "filec" and "file1" each of which is the user input filename "file" with an extension appended. To get the extension for filec, unfortunately there is no "itoa" in the library, so you need the number of digits = 1 + (size_t)log10(components) and a loop that finds the digits:
for (size_t i = 0; i < digits; ++i) { char digit = '0' + (char)(components % 10); // put this digit in the correct place in filec components /= 10; }This loop calculates the digits. You have to put them in the correct place in the file name array. Don't forget the null terminator.
Here is a way to start the main program, including the handy constants for four atomic walls:
const unsigned char NORTH = 0x01; const unsigned char EAST = 0x02; const unsigned char SOUTH = 0x04; const unsigned char WEST = 0x08; ... int main (int argc, char* argv[]) { if (argc < 4) { std::cout << "** command line arguments required:\n" << " 1: number of rows\n" << " 2: number of cols\n" << " 3: file name for maze\n" return 0; } size_t numrows = atoi(argv[1]); size_t numcols = atoi(argv[2]); ... }
The wall constants are global, hence accessible from Connect()
Note that the supplied makefile has targets fpartition.x, ranmaze.x, and mazetest.x.