3D Maze Screenshot (Stefan Haubenthal)
#include <stdio.h> #include <stdlib.h> // random #define random(MAX) rand()%MAX //#include <time.h> // randomize #include <tgi.h> #include <conio.h> // cgetc #if 0 #include <dos.h> // sound, nosound, delay #else #define sound(FREQ) #define nosound() #define delay(TIME) #endif enum {UP,RIGHT,DOWN,LEFT}; // equally NORTH,EAST,SOUTH,WEST #define RIGHT 1 // walls and borders #define BOTTOM 2 typedef struct { unsigned char Walls; unsigned char Border; unsigned short Path; } CELL; struct { int Width, Height, NumCells; int Dir[ 3 ]; CELL *Maze; int Entry; int CenterX; int CenterY; } g; #define Cell( Row, Col ) ((Row) * g.Width + (Col)) #define IsConnected( C1, C2 ) (g.Maze[ (C1) ].Path == g.Maze[ (C2) ].Path) #define GoodDir( Cell, Dir ) ((g.Maze[ (Cell) ].Border & (Dir)) == 0) #define ROTATE_LEFT( Direction ) ((Direction + 3) & 0x3) #define ROTATE_RIGHT( Direction ) ((Direction + 1) & 0x3) enum { CENTER_WALL,LEFT_WALL,RIGHT_WALL,RIGHT_EDGE,LEFT_EDGE}; enum { UNMOVED, MOVED,BLOCKED,EXITED }; // Draws a wall shape void DrawShape(int shape,int Size) { int OldSize=Size*2; // vertical lines shared across all shapes (closes the shapes) tgi_line( g.CenterX - Size, g.CenterY - Size, g.CenterX - Size, g.CenterY + Size); tgi_line( g.CenterX + Size, g.CenterY - Size, g.CenterX + Size, g.CenterY + Size); switch (shape) { case CENTER_WALL: tgi_line( g.CenterX - Size, g.CenterY - Size, g.CenterX + Size, g.CenterY - Size); tgi_line( g.CenterX - Size, g.CenterY + Size, g.CenterX + Size, g.CenterY + Size); break; case LEFT_WALL: tgi_line( g.CenterX - OldSize, g.CenterY - OldSize, g.CenterX - Size, g.CenterY - Size); tgi_line( g.CenterX - OldSize, g.CenterY + OldSize, g.CenterX - Size, g.CenterY + Size); break; case RIGHT_WALL: tgi_line( g.CenterX + OldSize, g.CenterY - OldSize, g.CenterX + Size, g.CenterY - Size); tgi_line( g.CenterX + OldSize, g.CenterY + OldSize, g.CenterX + Size, g.CenterY + Size); break; case RIGHT_EDGE: tgi_line( g.CenterX + OldSize, g.CenterY - Size, g.CenterX + Size, g.CenterY - Size); tgi_line( g.CenterX + OldSize, g.CenterY + Size, g.CenterX + Size, g.CenterY + Size); break; case LEFT_EDGE: tgi_line( g.CenterX - OldSize, g.CenterY - Size, g.CenterX - Size, g.CenterY - Size); tgi_line( g.CenterX - OldSize, g.CenterY + Size, g.CenterX - Size, g.CenterY + Size); break; } } // moves one cell in direction 'Direction' starting from cell *x,*y // assumes *x,*y is within the maze and Direction is a valid direction. int MoveXY(int Direction,int *x,int *y) { switch (Direction) { case (UP): if( *y==0 ) return( BLOCKED ); if( g.Maze[ Cell( (*y)-1, *x ) ].Walls & BOTTOM ) return( BLOCKED ); (*y)--; return( MOVED ); case (RIGHT): if( g.Maze[ Cell( *y, *x ) ].Walls & RIGHT ) return( BLOCKED ); (*x)++; return( MOVED ); case (DOWN): if( g.Maze[ Cell( *y, *x ) ].Walls & BOTTOM ) { if( *x != g.Entry) return( BLOCKED ); else return( EXITED ); } (*y)++; return( MOVED ); case (LEFT): if( *x==0 ) return( BLOCKED ); if( g.Maze[ Cell( *y, (*x)-1 ) ].Walls & RIGHT ) return( BLOCKED ); (*x)--; return( MOVED ); } return( BLOCKED ); } // Calculate which shapes are needed for a given cell void DrawBlock(int Direction, int x, int y,int Size) { int tx, ty; // temporary position for movement tests int movement; // result of movement tests // Look to the left - draw nessary shapes tx = x; ty = y; movement = MoveXY( ROTATE_LEFT( Direction ), &tx, &ty ); if( movement == BLOCKED ) { DrawShape( LEFT_WALL, Size ); } else { if( movement == MOVED ) { if( MoveXY( ROTATE_RIGHT( ROTATE_LEFT( Direction ) ), &tx, &ty) == BLOCKED ) { DrawShape(LEFT_EDGE,Size); } else { } // Draw nothing - there is just empty space to the left. } else { } // Draw nothing - the Exit is to the left } // Look to the right - draw nessary shapes tx = x; ty = y; movement = MoveXY( ROTATE_RIGHT( Direction ), &tx, &ty ); if( movement == BLOCKED ) { DrawShape( RIGHT_WALL, Size ); } else { if( movement == MOVED ) { if( MoveXY( ROTATE_LEFT( ROTATE_RIGHT( Direction ) ), &tx, &ty ) == BLOCKED ) { DrawShape( RIGHT_EDGE, Size ); } else { } // Draw nothing - there is just empty space to the right. } else { } // Draw nothing - the Exit is to the right } // Look straight ahead if( MoveXY( Direction, &x, &y ) == BLOCKED ) { DrawShape( CENTER_WALL, Size ); } } // Draws the maze as seen from cell x,y facing direction Direction void Draw3d( int Direction, int x, int y ) { int Size = g.CenterX / 2; while( 1 ) { DrawBlock( Direction, x, y, Size ); if( MoveXY( Direction, &x, &y ) != MOVED ) break; // reduce shape size Size /= 2; } } // Interactive exploration of the maze in 3d void Explore( void ) { unsigned char GraphicsMode = TGI_MODE_320_200_2; // use VGA 640x480 16 color mode int c; // character input int x, y; // "position" of viewpoint in maze int Direction; // direction of view (enumerated) int Status; // results of attempted move. tgi_load( GraphicsMode ); tgi_init( ); g.CenterX = tgi_getxres()/2; g.CenterY = tgi_getyres()/2; tgi_setcolor( COLOR_WHITE ); // random starting position and direction x = random( g.Width ); y = random( g.Height ); Direction = random( 4 ); Draw3d( Direction, x, y ); c = 0; while( 1 ) { Status = UNMOVED; c = cgetc(); if( c=='q' || c=='Q') break; switch( c ) { case 'J': // Rotate Left case 'j': case '4': Direction = ROTATE_LEFT( Direction ); break; case 'L': // Rotate Right case 'l': case '6': Direction = ROTATE_RIGHT( Direction ); break; case 'I': // Move Forward case 'i': case '8': Status = MoveXY( Direction, &x, &y ); break; case 'K': case 'k': case '2': Status = MoveXY( (Direction + 2 ) % 4 ,&x, &y ); break; default: break; } if( Status==BLOCKED) { // beep the speaker sound(550); delay(10); nosound(); } else { if( Status == EXITED ) { break; } else { tgi_clear(); Draw3d( Direction, x, y ); } } } tgi_done(); if( Status == EXITED ) { printf("Congratulations, you found your way out!\n"); } } // Joins two cells together into the same path. void Join( int CellNum, int Dir ) { int i, OldPath; g.Maze[ CellNum ].Walls &= ~Dir; // clear the wall between them // now merge the paths OldPath = g.Maze[ CellNum + g.Dir[ Dir ] ].Path; for( i = g.NumCells; i >= 0; i-- ) { if( g.Maze[ i ].Path == OldPath ) { g.Maze[ i ].Path = g.Maze[ CellNum ].Path; } } } // Randomly connects a cell with either its bottom or right neighbor. int Connect( int CellNum ) { int Neighbor, Offset, Dir; Dir = random( 2 ) + 1; // random direction, 1 or 2 Neighbor = CellNum + g.Dir[ Dir ]; if( !GoodDir( CellNum, Dir ) || IsConnected( CellNum, Neighbor ) ) { Dir = 3 - Dir; // get the OTHER neighbor Neighbor = CellNum + g.Dir[ Dir ]; if( !GoodDir( CellNum, Dir ) || IsConnected( CellNum, Neighbor ) ) { return( 0 ); } } Join( CellNum, Dir ); return( 1 ); } // Generates the maze. void Generate( void ) { int ACell, Cnt; // Connect a random cell in the maze to another path. // Continue doing this until there is only one path in the maze. do { for( ACell = random( g.NumCells ), Cnt = g.NumCells; Cnt; ACell++, Cnt-- ) { if( ACell == g.NumCells ) ACell = 0; if( Connect( ACell ) ) break; } } while( Cnt ); } // Initialization code. void Init( void ) { int i, Row, Col; _randomize(); g.Dir[ 1 ] = 1; // right neighbor g.Dir[ 2 ] = g.Width; // bottom neighbor if( (g.Maze = calloc( g.NumCells, sizeof( CELL ) )) == NULL ) { puts( "ERROR: Out of memory." ); exit( -1 ); } for( Row = 0; Row < g.Height; Row++ ) { g.Maze[ Cell( Row, g.Width - 1 ) ].Border |= RIGHT; } for( Col = 0; Col < g.Width; Col++ ) { g.Maze[ Cell( g.Height - 1, Col ) ].Border |= BOTTOM; } for( i = 0; i < g.NumCells; i++ ) { g.Maze[ i ].Path = i; g.Maze[ i ].Walls = RIGHT + BOTTOM; } } void main( int Argc, char *Argv[] ) { if( Argc != 3 ) { puts( "Needs dimensions, width and height." ); } else { g.Width = atoi( Argv[ 1 ] ); g.Height = atoi( Argv[ 2 ] ); g.NumCells = g.Width * g.Height; if( g.Width < 2 || g.Width > 50 || g.Height < 2 || g.Height > 50 ) { puts( "Bad dimensions." ); } else { Init(); // open up a random column on the bottom g.Entry = random( g.Width ); Generate(); Explore(); } } }