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