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();
      }
    }
  }


}}}