#ident "$Id: puzzle.c,v 1.3 2004/11/17 17:29:21 pwh Rel $"

/*
 * Panex puzzle object.
 */

#include        <stdio.h>
#include	<stdlib.h>
#include	<sys/stat.h>
#include	<unistd.h>
#include	<errno.h>
#include	<curses.h>
#include	<string.h>

#include	"panex.h"

static const char	gameName[] = PUZZLE_TITLE;
static const char	*pegNames[] = { "left peg", "center peg", "right peg" };
#define		PEG_NAME(a)	pegNames[a]

#define		DISK_CH(a)	((a)->color?(ACS_CKBOARD|A_UNDERLINE|\
				A_REVERSE|A_DIM):(' '|A_UNDERLINE|A_REVERSE))
#define		MOVES_FORMAT	"Moves: %d"
#define		PICKUP_DISK	"^  Pickup disk    s  Solve the puzzle"
#define		DROP_DISK	"v  Drop disk      r  Restart the game"
#define		MOVE_CURSOR	"<> Move cursor    u  Undo last move"
#define		QUIT		"q  Quit"


static const int	solvedPegs [] = { RIGHT_PEG, LEFT_PEG };


static void drawTitle ( PUZZLE *puzzle )

{
   int	titleLen = strlen ( gameName );
   int	x = puzzle->xc - ( ( titleLen + 1 ) >> 1 );

   move ( puzzle->y0, x );

   x = 0;
   while ( x < titleLen ) addch ( gameName [ x++] | A_BOLD | A_UNDERLINE );
   puzzle->yc = puzzle->y0 + 3;
   move ( puzzle->yc, puzzle->x0 );
}


static void drawDisk ( int width, const DISK *disk )

{
   if ( ! disk ) {

	int	i = width + 1;

	while ( i-- > 0 ) addch ( ' ' );
	addch ( ACS_VLINE | A_BOLD );

	i = width;
	while ( i-- > 0 ) addch ( ' ' );

   } else {

	int	i = width - disk->size;

	while ( i-- > 0 ) addch ( ' ' );

	i = ( disk->size << 1 ) + 3;

	while ( i-- > 0 ) addch ( DISK_CH ( disk ) );

	i = width - disk->size - 1;

	while ( i-- > 0 ) addch ( ' ' );
   }
}


static void drawBase ( PUZZLE *puzzle )

{
   int	i = 0;

   while ( i++ < 3 ) {

	int	j = puzzle->height + 1;

	while ( j-- > 0 ) addch ( ACS_HLINE | A_BOLD );

	addch ( ACS_BTEE | A_BOLD );
	j = puzzle->height;

	while ( j-- > 0 ) addch ( ACS_HLINE | A_BOLD );
   }

   addch ( ACS_HLINE | A_BOLD );
}


static void displayMoveCount ( PUZZLE *puzzle )

{
   char	moveBuff [32];
   char	*p = moveBuff;

   sprintf ( moveBuff, MOVES_FORMAT, puzzle->moves );
   move ( puzzle->y0 + puzzle->height + 5,
			puzzle->xc - ( ( sizeof ( MOVES_FORMAT) + 2 ) >> 1 ) );
   while ( *p ) addch ( *p++ );
   addch ( ' ' );
}


static void displayInstructions ( PUZZLE *puzzle )

{
   int	yc = puzzle->y0 + puzzle->height + 7;
   int	xc = puzzle->xc - ( sizeof ( PICKUP_DISK ) >> 1 );
   char	*c;

   c = PICKUP_DISK + 1;
   move ( yc++, xc );
   addch ( ACS_UARROW );
   while ( *c ) addch ( *c++ );

   c = DROP_DISK + 1;
   move ( yc++, xc );
   addch ( ACS_DARROW );
   while ( *c ) addch ( *c++ );

   c = MOVE_CURSOR + 2;
   move ( yc++, xc );
   addch ( ACS_LARROW );
   addch ( ACS_RARROW );
   while ( *c ) addch ( *c++ );

   xc = puzzle->xc - ( sizeof ( QUIT ) >> 1 );
   c = QUIT;
   move ( yc++, xc );
   while ( *c ) addch ( *c++ );
}


int displayPuzzle ( PUZZLE *puzzle )

{
   int	status = 1;

   if ( isInteractive ( puzzle ) ) {

	int	max_y;
	int	max_x;
	int	puzzle_y = puzzle->height + 11;
	int	puzzle_x = 6 * puzzle->height + 7;

	getmaxyx ( stdscr, max_y, max_x );

	if ( puzzle_y > max_y || puzzle_x > max_x ) {

		if ( ! isendwin () ) endwin ();

		fprintf ( stderr,
		"Your screen is not big enough to display a %d disk puzzle.\n",
							puzzle->height );
		status = 0;

	} else {

		int	i;

		puzzle->y0 = ( ( max_y - puzzle_y ) >> 1 );
		puzzle->x0 = ( ( max_x - puzzle_x ) >> 1 );
		puzzle->xc = ( max_x >> 1 );

		clear ();
		drawTitle ( puzzle );

		i = puzzle->height + 1; 

		while ( i-- > 0 ) {

			int	j;


			for ( j = 0; j < 3; ++j ) {

				drawDisk ( puzzle->height,
							puzzle->pegs [j] [i] );
			}
			move ( ++puzzle->yc, puzzle->x0 );
		}

		drawBase ( puzzle );
		displayMoveCount ( puzzle );
		displayInstructions ( puzzle );
		move ( 0, 0 );
		refresh ();
	}

   } else fprintf ( stdout, "%s -- %d disk solution.\n\n", gameName,
							puzzle->height );
   return ( status );
}


static int displayMove ( PUZZLE *puzzle, int src, int dest )

{
   int	status = 1;
   int	c;

   if ( isInteractive ( puzzle ) ) {

	int	distance;
	DISK	*disk = puzzle->pegs [dest] [puzzle->peg_tops [dest] - 1];
	int	min_height = puzzle->height - disk->size - 1;
	int	x0 = puzzle->x0 + ( ( src * ( puzzle->height + 1 ) ) << 1 );
	int	width = ( disk->size << 1 ) + 3;

	timeout ( puzzle->speed );

	if ( puzzle->peg_tops [src] > min_height ) min_height
						= puzzle->peg_tops [src];
	distance = puzzle->height - min_height + 1;

	/* Move disk to top of src peg. */
	puzzle->yc = puzzle->y0 + distance + 2;

	while ( distance-- ) {

		move ( puzzle->yc--, x0 );
		drawDisk ( puzzle->height, NULL );
		move ( puzzle->yc, x0 );
		drawDisk ( puzzle->height, disk );
		move ( 0, 0 );
		refresh ();
		if ( ( c = getch () ) == 'q' || c == ESC ) {

			puzzle->moves = 0;
			status = 0;
			goto abort;
		}
	}

	/* Move disk to dest peg. */
	distance = ( ( puzzle->height + 1 ) * ( dest - src ) ) << 1;
	x0 += puzzle->height - disk->size;

	if ( distance < 0 ) {

		while ( distance < 0 ) {

			distance += puzzle->warpFactor;
			x0 -= puzzle->warpFactor;

			move ( puzzle->yc, x0 );
			addch ( DISK_CH ( disk ) );
			if ( puzzle->warpFactor > 1 )
						addch ( DISK_CH ( disk ) );
			move ( puzzle->yc, x0 + width );
			addch ( ' ' );
			if ( puzzle->warpFactor > 1 ) addch ( ' ' );
			move ( 0, 0 );
			refresh ();
			if ( ( c = getch () ) == 'q' || c == ESC ) {

				puzzle->moves = 0;
				status = 0;
				goto abort;
			}
		}

	} else {

		while ( distance > 0 ) {

			distance -= puzzle->warpFactor;

			move ( puzzle->yc, x0 );
			addch ( ' ' );
			if ( puzzle->warpFactor > 1 ) addch ( ' ' );
			move ( puzzle->yc, x0 + width );
			addch ( DISK_CH ( disk ) );
			if ( puzzle->warpFactor > 1 )
						addch ( DISK_CH ( disk ) );
			move ( 0, 0 );
			refresh ();
			if ( ( c = getch () ) == 'q' || c == ESC ) {

				puzzle->moves = 0;
				status = 0;
				goto abort;
			}
			x0 += puzzle->warpFactor;
		}
	}

	/* Drop disk on detination peg. */
	move ( puzzle->yc, x0 );
	while ( width-- > 0 ) {

		addch ( ' ' );
	}

	x0 -= puzzle->height - disk->size;
	move ( ++puzzle->yc, x0 );
	drawDisk ( puzzle->height, disk );
	move ( 0, 0 );
	refresh ();
	if ( ( c = getch () ) == 'q' || c == ESC ) {

		puzzle->moves = 0;
		status = 0;
		goto abort;
	}

	distance = puzzle->height - puzzle->peg_tops [dest] + 1;
	while ( distance-- ) {

		move ( puzzle->yc, x0 );
		drawDisk ( puzzle->height, NULL );
		move ( ++puzzle->yc, x0 );
		drawDisk ( puzzle->height, disk );
		move ( 0, 0 );
		refresh ();
		if ( ( c = getch () ) == 'q' || c == ESC ) {

			puzzle->moves = 0;
			status = 0;
			goto abort;
		}
	}

	displayMoveCount ( puzzle );
	move ( 0, 0 );
	refresh ();

abort:
	timeout ( -1 );

   } else fprintf ( stdout, "%d.  Move disk from %s to %s.\n", puzzle->moves,
					PEG_NAME ( src ), PEG_NAME ( dest ) );
   return ( status );
}


void closePuzzle ( PUZZLE *puzzle )

{
   if ( puzzle ) {

	if ( isInteractive ( puzzle ) ) {

		if ( puzzle->moves ) {

			getch ();
		}

		if ( ! isendwin () ) endwin ();

	} else putchar ( '\n' );

	free ( puzzle );
   }
}


void resetPuzzle ( PUZZLE *puzzle )

{
   int	i;
   int	height = puzzle->height;

   puzzle->unsolved = 2 * height;
   puzzle->moves = 0;

   puzzle->peg_tops [LEFT_PEG] = height;
   puzzle->peg_tops [CENTER_PEG] = 0;
   puzzle->peg_tops [RIGHT_PEG] = height;

   for ( i = 0; i < height; ++i ) {

	puzzle->disks [0] [i].peg = LEFT_PEG;
	puzzle->disks [1] [i].peg = RIGHT_PEG;
	puzzle->disks [0] [i].height = puzzle->disks [1] [i].height
						= height - 1 - i;
	puzzle->disks [0] [i].solved = puzzle->disks [1] [i].solved = 0;
	puzzle->disks [0] [i].size = puzzle->disks [1] [i].size = i;
	puzzle->disks [0] [i].color = 0;
	puzzle->disks [1] [i].color = 1;
	puzzle->pegs [LEFT_PEG] [i] = & ( puzzle->disks [0] [height - 1 - i] );
	puzzle->pegs [RIGHT_PEG] [i] = & ( puzzle->disks [1] [height - 1 - i] );
	puzzle->pegs [CENTER_PEG] [i] = NULL;
   }

   puzzle->pegs [LEFT_PEG] [height] = puzzle->pegs [CENTER_PEG] [height]
				= puzzle->pegs [RIGHT_PEG] [height] = NULL;
}


PUZZLE *openPuzzle ( int height, int interactive, int speed )

{
   PUZZLE	*puzzle = NULL;
   struct stat	st_buff;

   /* Check if standard out is a terminal. */
   if ( fstat ( 1, &st_buff ) < 0 ) {

	perror ( "Standard out not available" );

   /* Allocate space for a PUZZLE object. */
   } else if ( puzzle = ( PUZZLE * ) malloc ( sizeof ( PUZZLE ) + height
		* ( 2 * sizeof ( DISK ) + 3 * sizeof ( DISK * ) )
						+ 3 * sizeof ( DISK * ) ) ) {
	puzzle->speed = 0;

	/* If interactive flag is set and standard out is a terminal... */
	if ( interactive && S_ISCHR ( st_buff.st_mode ) ) {

		if ( initscr () ) {

			cbreak ();
			noecho ();
			nonl ();
			intrflush ( stdscr, FALSE );
			keypad ( stdscr, TRUE );

			if ( speed == WARP_SPEED ) {

				puzzle->speed = XTREME_SPEED;
				puzzle->warpFactor = 2;

			} else {

				puzzle->speed = speed ? speed : NORMAL_SPEED;
				puzzle->warpFactor = 1;
			}

		} else fprintf ( stderr, "Cannot take control of terminal.\n" );
	}

	puzzle->height = height;

	puzzle->disks [0] = ( DISK * ) ( puzzle + 1 );
	puzzle->disks [1] = puzzle->disks [0] + height;
	puzzle->pegs [LEFT_PEG] = ( DISK ** ) ( puzzle->disks [1] + height );
	puzzle->pegs [CENTER_PEG] = puzzle->pegs [LEFT_PEG] + ( height + 1 );
	puzzle->pegs [RIGHT_PEG] = puzzle->pegs [CENTER_PEG] + ( height + 1);

	resetPuzzle ( puzzle );

	if ( ! displayPuzzle ( puzzle ) ) {

		closePuzzle ( puzzle );
		puzzle = NULL;
	}

	timeout ( 200 );
	getch ();
	timeout ( -1 );
   }

   return ( puzzle );
}


int moveDisk ( PUZZLE *puzzle, int src_peg, int dest_peg )

{
   int	status = 0;
   int	dest_height = puzzle->peg_tops [dest_peg];
   int	src_height = puzzle->peg_tops [src_peg] - 1;

   if ( src_height > -1 && dest_height <= puzzle->height
	&& ( src_peg == CENTER_PEG || dest_peg == CENTER_PEG
	||  puzzle->peg_tops [CENTER_PEG] <= puzzle->height ) ) {

	DISK	*disk = puzzle->pegs [src_peg] [src_height];
	int	min_height = puzzle->height - disk->size - 1;
	int	solvedPeg = solvedPegs [ disk->color ];
	int	solvedHeight = puzzle->height - disk->size - 1;

	if ( min_height > dest_height ) dest_height = min_height;

	if ( puzzle->disks [disk->color] [disk->size].solved ) {

		++puzzle->unsolved;
		puzzle->disks [disk->color] [disk->size].solved = 0;

	} else if ( dest_peg == solvedPeg && dest_height == solvedHeight ) {

		puzzle->disks [disk->color] [disk->size].solved = 1;
		--puzzle->unsolved;
	}

	puzzle->pegs [src_peg] [src_height] = NULL;
	disk->peg = dest_peg;
	disk->height = dest_height;
	puzzle->pegs [dest_peg] [dest_height] = disk;

	while ( src_height-- > 0
			&& puzzle->pegs [src_peg] [src_height] == NULL );

	puzzle->peg_tops [src_peg] = ++src_height;
	puzzle->peg_tops [dest_peg] = ++dest_height;
	puzzle->moves++;
	status = displayMove ( puzzle, src_peg, dest_peg );
   }

   return ( status );
}


Last updated: Saturday, August 01, 2009 11:49:29 PM