#ident "$Id: puzzle.c,v 1.6 2006/06/27 02:47:46 pwh Rel $" /* * Tower of Hanoi puzzle object. */ #include #include #include #include #include #include #include "hanoi.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)->size&1)?(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 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 ); move ( puzzle->y0 + 3, puzzle->x0 ); } static void drawDisk ( const DISK *disk ) { if ( disk ) { int diskCh = DISK_CH ( disk ); int i = ( disk->size << 1 ) + 3; move ( disk->y, disk->x - disk->size - 1 ); while ( i-- > 0 ) addch ( diskCh ); } } static void eraseDisk ( const DISK *disk ) { if ( disk ) { int i = disk->size + 1; move ( disk->y, disk->x - disk->size - 1 ); while ( i-- > 0 ) addch ( ' ' ); addch ( ACS_VLINE | A_BOLD ); i = disk->size + 1; while ( i-- > 0 ) addch ( ' ' ); } } static void drawBase ( PUZZLE *puzzle ) { int i = 0; while ( i < 3 ) { int j = puzzle->height + 1; int y = puzzle->y0 + 3; while ( j-- > 0 ) { move ( y++, puzzle->xp [i] ); addch ( ACS_VLINE | A_BOLD ); } ++i; } i = 0; move ( puzzle->y0 + puzzle->height + 4, puzzle->x0 ); 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 ); ++i; } 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++ ); } static int displayPuzzle ( PUZZLE *puzzle ) { int status = 1; if ( isAnimated ( 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 ); puzzle->xp [0] = puzzle->x0 + puzzle->height + 1; puzzle->xp [1] = puzzle->xp [0] + ( ( puzzle->height + 1 ) << 1 ); puzzle->xp [2] = puzzle->xp [1] + ( ( puzzle->height + 1 ) << 1 ); clear (); drawTitle ( puzzle ); drawBase ( puzzle ); i = puzzle->height; while ( i-- > 0 ) { puzzle->disks [i].x = puzzle->xp [LEFT_PEG]; puzzle->disks [i].y = puzzle->y0 + i + 4; drawDisk ( & ( puzzle->disks [i] ) ); } displayMoveCount ( puzzle ); displayInstructions ( puzzle ); move ( 0, 0 ); refresh (); } } else fprintf ( stdout, "%s -- %d disk solution.\n\n", PUZZLE_TITLE, puzzle->height ); return ( status ); } static int diskUp ( PUZZLE *puzzle ) { int status = 1; if ( isAnimated ( puzzle ) ) { int top = puzzle->y0 + 2; DISK *disk = puzzle->xDisk; while ( disk->y > top ) { int c; eraseDisk ( disk ); --disk->y; drawDisk ( disk ); move ( 0, 0 ); refresh (); if ( ( c = getch () ) != ERR && ( puzzle->interactive || c == 'q' || c == ESC ) ) { errno = EINTR; ungetch ( c ); status = 0; break; } } } else fprintf ( stdout, "%d. Move disk from %s", puzzle->moves + 1, PEG_NAME ( puzzle->xDisk->peg ) ); return ( status ); } int slideDisk ( PUZZLE *puzzle, int dest ) { int status = 1; if ( isAnimated ( puzzle ) ) { DISK *disk = puzzle->xDisk; errno = 0; if ( disk ) { int warpFactor = puzzle->warpFactor; int peg = puzzle->xp [dest]; if ( disk->x != peg && ( status = diskUp ( puzzle ) ) ) { int ch = DISK_CH ( disk ); int ch2; int increment; if ( peg < disk->x ) { increment = -warpFactor; ch2 = ' '; } else { increment = warpFactor; ch2 = ch; ch = ' '; } while ( disk->x != peg ) { int i = warpFactor; if ( puzzle->newSpeed ) setSpeed ( puzzle, puzzle->newSpeed ); if ( increment < 0 ) disk->x += increment; move ( disk->y, disk->x - disk->size - 1 ); while ( i-- > 0 ) addch ( ch ); move ( disk->y, disk->x + disk->size + 2 ); i = warpFactor; while ( i-- > 0 ) addch ( ch2 ); if ( increment > 0 ) disk->x += increment; move ( 0, 0 ); refresh (); if ( ( i = getch () ) != ERR && ( puzzle->interactive || i == 'q' || i == ESC ) ) { errno = EINTR; ungetch ( i ); status = 0; break; } } } } else status = 0; } return ( status ); } static int diskDown ( PUZZLE *puzzle, int dest ) { int status = 1; if ( isAnimated ( puzzle ) ) { DISK *disk = puzzle->xDisk; if ( disk->x == puzzle->xp [dest] || ( status = slideDisk ( puzzle, dest ) ) ) { int bottom = puzzle->y0 + puzzle->height - puzzle->peg_tops [dest] + 3; if ( disk->y == puzzle->y0 + 2 ) { int i = ( disk->size << 1 ) + 3; move ( disk->y, disk->x - disk->size - 1 ); while ( i-- > 0 ) addch ( ' ' ); ++disk->y; drawDisk ( disk ); move ( 0, 0 ); refresh (); if ( ( i = getch () ) != ERR && ( puzzle->interactive || i == 'q' || i == ESC ) ) { errno = EINTR; ungetch ( i ); status = 0; } } if ( status ) { while ( disk->y < bottom ) { int c; eraseDisk ( disk ); ++disk->y; drawDisk ( disk ); move ( 0, 0 ); refresh (); if ( ( c = getch () ) != ERR && ( puzzle->interactive || c == 'q' || c == ESC ) ) { errno = EINTR; ungetch ( c ); status = 0; break; } } } } } else fprintf ( stdout, " to %s.\n", PEG_NAME ( dest ) ); return ( status ); } int dropDisk ( PUZZLE *puzzle, int dest ) { int status = 0; DISK *disk = puzzle->xDisk; errno = 0; /* Is this a legal move? */ if ( disk && ( puzzle->peg_tops [dest] == 0 || disk->size < puzzle->pegs [dest] [puzzle->peg_tops [dest] - 1]->size ) ) { if ( status = diskDown ( puzzle, dest ) ) { if ( disk->peg != dest ) { ++puzzle->moves; disk->peg = dest; disk->height = puzzle->peg_tops [dest]; } displayMoveCount ( puzzle ); puzzle->pegs [dest] [puzzle->peg_tops [dest]++] = disk; puzzle->xDisk = NULL; } } return ( status ); } int pickupDisk ( PUZZLE *puzzle, int src ) { int status = 0; errno = 0; if ( ( puzzle->peg_tops [src] > 0 || ( puzzle->xDisk && puzzle->xDisk->peg == src ) ) && ( ! puzzle->xDisk || puzzle->xDisk->peg == src || ( status = dropDisk ( puzzle, puzzle->xDisk->peg ) ) ) ) { if ( ! puzzle->xDisk ) { puzzle->xDisk = puzzle->pegs [src] [--puzzle->peg_tops [src]]; puzzle->pegs [src] [puzzle->peg_tops [src]] = NULL; } status = diskUp ( puzzle ); } return ( status ); } void closePuzzle ( PUZZLE *puzzle ) { if ( puzzle ) { if ( isAnimated ( puzzle ) ) { timeout ( -1 ); if ( puzzle->moves ) getch (); if ( ! isendwin () ) endwin (); } else putchar ( '\n' ); free ( puzzle ); } } int resetPuzzle ( PUZZLE *puzzle ) { int height = puzzle->height; int i; puzzle->interactive = 1; puzzle->moves = 0; puzzle->peg_tops [LEFT_PEG] = height; puzzle->peg_tops [CENTER_PEG] = 0; puzzle->peg_tops [RIGHT_PEG] = 0; puzzle->xDisk = NULL; for ( i = 0; i < height; ++i ) { puzzle->disks [i].peg = LEFT_PEG; puzzle->disks [i].height = height - 1 - i; puzzle->pegs [LEFT_PEG] [i] = & ( puzzle->disks[height - 1 - i] ); puzzle->pegs [CENTER_PEG] [i] = NULL; puzzle->pegs [RIGHT_PEG] [i] = NULL; } return ( displayPuzzle ( puzzle ) ); } void setSpeed ( PUZZLE *puzzle, int speed ) { puzzle->newSpeed = speed ? speed : NORMAL_SPEED; if ( isAnimated ( puzzle ) && ( ! puzzle->xDisk || puzzle->xDisk->y != puzzle->y0 + 2 || ! ( ( puzzle->xDisk->x - puzzle->xp[0] ) & 1 ) ) ) { if ( puzzle->newSpeed == WARP_SPEED ) { puzzle->speed = XTREME_SPEED; puzzle->warpFactor = 2; } else { puzzle->speed = puzzle->newSpeed; puzzle->warpFactor = 1; } puzzle->newSpeed = 0; timeout ( puzzle->speed ); } } PUZZLE *openPuzzle ( int height, int animated, 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 * ( sizeof ( DISK ) + 3 * sizeof ( DISK * ) ) ) ) { puzzle->speed = 0; /* If animated flag is set and standard out is a terminal... */ if ( animated && S_ISCHR ( st_buff.st_mode ) ) { if ( initscr () ) { cbreak (); noecho (); nonl (); intrflush ( stdscr, FALSE ); keypad ( stdscr, TRUE ); puzzle->speed = 1; /* Kludge. */ setSpeed ( puzzle, speed ); } else fprintf ( stderr, "Cannot take control of terminal.\n" ); } puzzle->height = height; puzzle->disks = ( DISK * ) ( puzzle + 1 ); puzzle->pegs [LEFT_PEG] = ( DISK ** ) ( puzzle->disks + height ); puzzle->pegs [CENTER_PEG] = puzzle->pegs [LEFT_PEG] + height; puzzle->pegs [RIGHT_PEG] = puzzle->pegs [CENTER_PEG] + height; while ( height-- > 0 ) puzzle->disks [height].size = height; if ( ! resetPuzzle ( puzzle ) ) { closePuzzle ( puzzle ); puzzle = NULL; } else if ( isAnimated ( puzzle ) ) { timeout ( 200 ); getch (); timeout ( puzzle->speed ); } } return ( puzzle ); } int solvePuzzle ( PUZZLE *puzzle, int speed ) { int status = 1; puzzle->interactive = 0; setSpeed ( puzzle, speed ); status = solve ( puzzle ); return ( status ); } int undoMove ( PUZZLE *puzzle, int src, int dest ) { int status = 0; errno = 0; if ( puzzle->moves > 0 && ! puzzle->xDisk && puzzle->peg_tops [dest] > 0 ) { puzzle->xDisk = puzzle->pegs [dest] [--puzzle->peg_tops [dest]]; puzzle->pegs [dest] [puzzle->peg_tops [dest]] = NULL; puzzle->xDisk->peg = src; puzzle->moves -= 1; status = 1; } return ( status ); } int moveDisk ( PUZZLE *puzzle, int src_peg, int dest_peg ) { return ( ( ( puzzle->xDisk && puzzle->xDisk->peg == src_peg ) || pickupDisk ( puzzle, src_peg ) ) && dropDisk ( puzzle, dest_peg ) ); }