You are on page 1of 7

Project 9: Maze Generator

CS 200 20+10 Points Total Due in class Friday, April 22, 2011
Objectives

Write a recursive maze generator in assembly. Practice using stack frames, general assembly programming, and using a high-level language implementation as a reference for writing assembly.

Overview Recursion can be used to easily create simple tile-based mazes. The idea is you start somewhere in "solid rock" and tunnel your way two steps in a random direction, then recursively repeat. When your twisty little passage runs out of room to grow (when it's about to cross itself), you fall back to the previous level of recursion and see if you can go in another direction. Here is a C++ implementation of such a maze generator:
//======================================================================= ====== // maze.cpp // // C++ implementation of a recursive maze-generating program. // // History: // 2006.03.30 / Abe Pralle - Created // 2010.04.02 / Abe Pralle - Converted to C++ //======================================================================= ====== #include <iostream> using namespace std; //----CONSTANTS------------------------------------------------------#define GRID_WIDTH 79 #define GRID_HEIGHT 23 #define #define #define #define NORTH EAST SOUTH WEST 0 1 2 3

//----GLOBAL VARIABLES-----------------------------------------------char grid[GRID_WIDTH*GRID_HEIGHT];

//----FUNCTION PROTOTYPES--------------------------------------------void ResetGrid(); int XYToIndex( int x, int y ); int IsInBounds( int x, int y ); void Visit( int x, int y ); void PrintGrid(); //----FUNCTIONS------------------------------------------------------int main() { // Starting point and top-level control. srand( time(0) ); ResetGrid(); Visit(1,1); PrintGrid(); return 0; } void ResetGrid() { // Fills the grid with walls ('#' characters). for (int i=0; i<GRID_WIDTH*GRID_HEIGHT; ++i) { grid[i] = '#'; } } int XYToIndex( int x, int y ) { // Converts the two-dimensional index pair (x,y) into a // single-dimensional index. The result is y * ROW_WIDTH + x. return y * GRID_WIDTH + x; } int IsInBounds( int { // Returns "true" if (x < 0 || x >= if (y < 0 || y >= return true; } x, int y ) if x and y are both in-bounds. GRID_WIDTH) return false; GRID_HEIGHT) return false; // seed random number generator.

void Visit( int x, int y ) { // Starting at the given index, recursively visits every direction in a // randomized order.

// Set my current location to be an empty passage. grid[ XYToIndex(x,y) ] = ' '; // Create an local array containing the 4 directions and shuffle their order. int dirs[4]; dirs[0] = NORTH; dirs[1] = EAST; dirs[2] = SOUTH; dirs[3] = WEST; for (int i=0; i<4; ++i) { int r = rand() & 3; int temp = dirs[r]; dirs[r] = dirs[i]; dirs[i] = temp; } // Loop through every direction and attempt to Visit that direction. for (int i=0; i<4; ++i) { // dx,dy are offsets from current location. Set them based // on the next direction I wish to try. int dx=0, dy=0; switch (dirs[i]) { case NORTH: dy = -1; break; case SOUTH: dy = 1; break; case EAST: dx = 1; break; case WEST: dx = -1; break; } // Find the (x,y) coordinates of the grid cell 2 spots // away in the given direction. int x2 = x + (dx<<1); int y2 = y + (dy<<1); if (IsInBounds(x2,y2)) { if (grid[ XYToIndex(x2,y2) ] == '#') { // (x2,y2) has not been visited yet... knock down the // wall between my current position and that position grid[ XYToIndex(x2-dx,y2-dy) ] = ' '; // Recursively Visit (x2,y2) Visit(x2,y2); } } } } void PrintGrid() { // Displays the finished maze to the screen.

for (int y=0; y<GRID_HEIGHT; ++y) { for (int x=0; x<GRID_WIDTH; ++x) { cout << grid[XYToIndex(x,y)]; } cout << endl; } }

Here is an example of a maze generated by the program:


######################################################################### ###### # # # # # # # # # # ######### ####### ### # ### # ### # ####### ### # ### # ####### ### ### ##### # # # # # # # # # # # # # # # # # # # # # # # # # # ####### ### # # # # # ##### # ### ### ### # ######### # # # # ### ##### # # # # # # # # # # # # # # # # # # # # # # # # # # # ### ##### ############### ### # ### ####### ####### # # # # ### # # # # # # # # # # # # # # # # # # # # # # # # # # # ### # # # ### ########### # # # # # # # ##### ### # ##### # # # ### ####### # # # # # # # # # # # # # # # # # # # # # # # ### # ########### # ### # ### # ### # # ### ##### ### # # ####### # # ### # ### # # # # # # # # # # # # # # # # # # # # # # # # # ##### ######### # # # # ### # ####### # # ##### # ### ### # # ##### ### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##### ######### # ### # # # # ### ### ##### # # # ### ######### ### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ### # ##### ### # # # ### # # ### ### # ### ### # # # ### # ### ### # ### # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ### # # # # ##### # ##### # ##### # # ##### ### # ### # ### # # ### ##### # # # # # # # # # # # # # # # # # # # # # # # # # # # # ##### # # # # # # # ### # ##### ### # # ##### ######### # ### ####### # # #

# # # # # # # # # # # ######################################################################### ######

Assignment Convert the C++ program into an x86 assembly language program using the following guidelines:

Use stack-based parameter passing and stack frames for local variables. For example, to call the "Visit" procedure with (1,1) the following code would be used:
push 1 push 1 call Visit ... Visit proc push ebp mov ebp,esp sub esp,VISIT_LOCAL_SIZE ... mov eax,[ebp+12] ;x parameter mov ebx,[ebp+8] ;y parameter ... ret 8 ;automatically pop off 8 bytes of parameters Visit endp ;x ;y

You might want to set up a defined constant for each of the parameters and local variables to minimize confusion and the potential for bugs. For example:
VISIT_PARAM_X EQU DWORD PTR [ebp+12] VISIT_X2 EQU DWORD PTR [ebp-4] ... mov eax, VISIT_PARAM_X ... mov VISIT_X2, 0 ...

If you use Irvine32's "Random32" procedure, call "Randomize" once at the beginning of the program so that the maze is different every time. Implement and test your program in stages to avoid getting overwhelmed by having to debug too much assembly at once. For example, start by defining the ResetGrid and PrintGrid procedures and make sure you can print out a "maze" that's completely full of pound signs. A common mistake is to use a global array for "dirs". To work right, each recursion must have its own separate "dirs" array, so create it on the stack as a set of 4 local variables.

Don't use any high-level macro commands like ".if" or ".while". Your program should be pure assembly.

Extra Credit: Detailed Walls MS-DOS console mode has a number of special characters that are suitable for drawing somewhat-detailed text-based graphics - this character set is called "Code Page 437" or CP437 (see inside back cover of the assembly book). For up to +10 points of extra credit, adjust your maze drawing procedure to utilize these special characters to connect adjoining walls, like this:

Here are some hints on how to accomplish this:


Make a separate routine to draw the maze at a certain (x,y) location. Whenever the maze contains a space, draw a space. If it contains a wall ("#"), determine which adjacent locations also contain a wall and use that to decide which character to draw. In each of 4 directions there can be a wall or no wall. This leads to 24 or 16 possible combinations. You can use 4 bits of a register to indicate the presence of a wall in each of four directions. Set a given bit if there's a wall in that direction, or clear it if there's not. Once you set your bits you'll have a number between 0 and 15 - plug that into a lookup table to retrieve the appropriate character.

You may wish to implement the extra credit in C++ or Java first so that you're sure of your technique.

If you do the extra credit, have your sample program output consist of the extra credit output only and write "XC Included" on the top-right of your cover page.

Project Report Create a report consisting of a cover page, an overview of the project, the assembly source code, and output results. The cover page should contain the following information. Correct the date to be the day you actually turn in the project. If you have a web-based solution, please note the URL. Project 9: Maze Generator Your Name April 22, 2011 CS 200 The project overview should contain at least these three parts: 1. Purpose Describe the purpose of your program. 2. Approach Describe how you solved the problem in a broad sense. Briefly recap your algorithm and, if you did the extra credit, the details about the CP437 character set that you used. 3. Problems Describe any significant problems you encountered and how you solved them. The assembly source should be a printout of your ".asm" file. For the output results, run your program twice to generate two different mazes. Include both mazes in your report. Turn in your project report by the end of class on the due date.

You might also like