#ident "$Id: puzzle.c,v 1.3 2004/11/17 17:29:21 pwh Rel $" /* * Panex puzzle object. */ #include #include #include #include #include #include #include #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 ); }