/********************************************************************** MASTERMIND player. 10 digits (0-9); words of length ln (= 2..6). Makefile is not provided: it's too easy to compile! compile like this: g++ -O -s -o mastermind mastermind.c -lncurses As you can see, it requires ncurses. If you don't have this library - get it, it's free! On the other hand, you might have curses instead. Comment out the #include line, uncomment the next line (#include ) and compile like this: g++ -O -s -o mastermind mastermind.c -lcurses -ltermcap Change Log: 1.3 Dec 20 1994. The word length is not a constant anymore. The players are timed. The code is cleaned up quite a bit. It should be faster now if you have an FPU. 1.2 Dec 12 1994. Added demo game, menus etc. 1.1 Nov 27 1994. Made the guesses to be "random" numbers. Some sort of support for curses added. 1.0 Nov 18 1994. First written. Copyright (C) 1994 by Semion D. Shteingold, Comments, suggestions, bug reports/fixes, flame, praise are welcome! This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with This program; see the file COPYING. If not, write to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. ***********************************************************************/ #include #include #include #include #include #include #include #include //#include // If you do not have ncurses, uncomment this. #define MLN 6 // Maximal Length of the Number #define DEFLN 4 // DEFault Length of the Number #define GL 20 // Maximal Length of the Game #define VERSION "1.3" #define NUM2STR(num, st) sprintf (st, "%.*d", ln, num) /* Can you believe that sprintf returns on Linux and Solaris and on SunOS?! */ #define MINIHELP "\tq,x: quit;\th,?: help;\tb. <- cursor -> f,\t\t" #define LEFTMARGIN 5 #define FIRST 15 // first column #define SECOND 55 // second column #define SPACE 10 // dist from *'s to the replies #define BSPACE 14 // dist from *'s to the beginning of the decsiption #define HELPCOL 22 // the start of help in the first line #define MSGL (LINES-2) // MeSsaGe Line #define ASKL (LINES-2) // ASK Line #define HLPL (LINES-HLPLEN-3) // HeLP Line #define HLPLEN 7 // Number of lines for help #define MHL (LINES-1) // MiniHelp Line #define STARTL 2 // START the game from Line... #define CLN_STR(a) { move (a, 0); clrtoeol (); } #define MOVE_LEFT \ CLN_STR(MSGL); pos = (pos-1+ln)%ln; move (x, y+pos); refresh() #define MOVE_RIGHT \ CLN_STR(MSGL); pos = (pos+1)%ln; move (x, y+pos); refresh() #define MOVE_UP \ CLN_STR(MSGL); pos = (pos-1+num)%num; move(pos+STARTL,LEFTMARGIN); refresh() #define MOVE_DOWN \ CLN_STR(MSGL); pos = (pos+1)%num; move (pos+STARTL, LEFTMARGIN); refresh() #define MOVE_HOME_UP \ CLN_STR(MSGL); pos = 0; move (pos+STARTL, LEFTMARGIN); refresh() #define MOVE_HOME_LEFT \ CLN_STR(MSGL); pos = 0; move (x, y+pos); refresh() #define MOVE_END_DOWN \ CLN_STR(MSGL); pos = num-1; move (pos+STARTL, LEFTMARGIN); refresh() #define MOVE_END_RIGHT \ CLN_STR(MSGL); pos = ln-1; move (x, y+pos); refresh() #define PUT_HELP(text) {move(0,HELPCOL); clrtoeol(); addstr(text); refresh();} #define GET_HELP(a) CLN_STR(MSGL); help (a); refresh(); break #define PUT_STARS(x, y) \ { move(x, y); for(int i=0;i': MOVE_RIGHT; break; case 1: /* ^A */ case 'a': MOVE_HOME_LEFT; break; case 5: /* ^E */ case 'e': MOVE_END_RIGHT; break; case 27: /* ESC */ switch (tolower (getch())) { case 'o': case 91: /* [ */ switch (tolower (getch())) { case 'a': case 'd': MOVE_LEFT; break; case 'b': case 'c': MOVE_RIGHT; break; case '7': /* home */ MOVE_HOME_LEFT; getch(); break; case '8': /* end */ MOVE_END_RIGHT; getch(); break; default: while (getch()!='~'); GET_HELP(1); } break; default: GET_HELP(1); } break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': for (i = 0; (i < ln) && ( (c-'0') != entered[i]) ; i++); if ( ( (c-'0') == entered[i]) && (i != ln) ) message ("You already have this digit!", 1); else { entered[pos] = c-'0'; addch (c); MOVE_RIGHT; } break; case 'h': case '?': GET_HELP(1); case 127: /* ^? */ case 8: /* ^H */ MOVE_LEFT; case 32: addch ('*'); entered[pos] = 10; move (x, y+pos); break; case 10: // Enter for (in_num = i = 0, flag = 1; (i < ln) && (flag) ; i++) if (entered[i] == 10) flag = 0; else in_num = in_num*10+entered[i]; if (flag) { CLN_STR(MSGL); return (in_num); } else { in_num = 0; message ("You have not entered the number yet!", 1); } break; case 'q': case 'x': case 3: /* ^C */ if (ask ("Are you sure you want to quit?", 0, 0)) return (-1); case 12: /* ^L */ case 18: /* ^R */ // redrawln (stdscr, 0, 24); break; message ("sorry, cannot redraw screen yet", 1); break; default: message ("Illegal key. Please use \"h\" or \"?\" for help.", 1); break; } refresh(); } // end of while (1) } // get_number void help (int num) /******************************************* give help according to the context: 0: general; 1: get_number; 2: license; 3: send e-mail to the author; then return to the initial position of the cursor. *******************************************/ { int x, y; getyx (curscr, x, y); clear_str (HLPL, MHL); mvaddstr ( MHL, 0, "G - general help; also: N; L; M." ); switch (num) { case 0: clear_str (HLPL, HLPL+HLPLEN); mvaddstr ( HLPL, 0, "\tHELP: General:\n\t\ This is a game. You and I think of a number, and each tries\n\t\ to guess the number of the other. The answer is displayed\n\t\ to the right in the form: \"b:2 c:1\". This means that\n\t\ the number to the left has 3 common digits with the number\n\t\ being guessed, 2 of them on the right position and one on\n\t\ on the wrong."); message ("Press any key to continue...", 0); getch(); clear_str (HLPL+1, HLPL+HLPLEN); mvaddstr ( HLPL+1, 0 , "\t\ More help: N - how to enter a number; L - see the license;\n\t\ M - send mail to the author.\n\t\ Beware: sending mail will terminate the program!\n\t\ G - see the general help again."); refresh(); CLN_STR(MSGL); break; case 1: clear_str (HLPL, HLPL+HLPLEN); mvaddstr ( HLPL, 0 , "\tHELP: Entering a number:\n\t\ You should enter a \"valid\" number.\n\t\ A number is \"valid\" if it contains digits \'0\' to \'9\'\n\t\ and does not have the same digit twice. The number can\n\t\ start with 0. Use the space bar to erase the curent digit,\n\t\ and the <,b, and >,f keys to move within the allotted space.\n\t\ You can use the BS/DEL keys. The arrows should also work."); refresh(); break; case 2: clear_str (HLPL, HLPL+HLPLEN); mvaddstr ( HLPL, 0, "\tHELP: License information:\n\t\ Copyright (C) 1994 by Sam Shteingold, \n\t\ Comments, suggestions, bug reports/fixes are welcome!\n\t\ This program is free software; you can redistribute it and/or modify\n\t\ it under the terms of the GNU General Public License as published by\n\t\ the Free Software Foundation; either version 2, or (at your option)\n\t\ any later version."); message ("Press any key to continue...", 0); getch(); clear_str (HLPL+1, HLPL+HLPLEN); mvaddstr ( HLPL+1, 0, "\t\ This program is distributed in the hope that it will be useful, but\n\t\ WITHOUT ANY WARRANTY; without even the implied warranty of\n\t\ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n\t\ General Public License for more details.\n\t\ You should have received a copy of the GNU General Public License\n\t\ along with This program; see the file COPYING. If not, write to the\n\t\ Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA."); refresh(); CLN_STR(MSGL); break; case 3: nocbreak(); echo(); clear (); mvaddstr (0,0, "Enter the message. Terminate by a single dot on the line.\n"); refresh(); system ("mail shteingd@math.ucla.edu -s \"I use mastermind...\""); // cbreak(); noecho(); redrawwin (stdscr); break; bye (0); } // of switch (num); switch (tolower (getch())) { case 'g': help (0); break; case 'n': help (1); break; case 'l': help (2); break; case 'm': help (3); break; default: clear_str (HLPL, LINES); mvaddstr (MHL, LEFTMARGIN, MINIHELP); } move (x, y); refresh(); return; } // help int menu (int num, char * items[]) // chose an item in the menu // num items; items[0] = title; others - lables of the items { int pos = 0, c, i; clear_str (1, MSGL); PUT_HELP(items[0]); for (i = 1; i <= num; i++) mvprintw (i + STARTL - 1 , LEFTMARGIN, "%d. %s", i, items[i]); move (STARTL, LEFTMARGIN); refresh(); while (1) { switch (c = tolower (getch())) { case ',': case 16: /* ^P */ case 2: /* ^B */ case 'b': case 'p': case 'k': case 127: /* ^? */ case 8: /* ^H */ case '<': MOVE_UP; break; case '.': case 14: /* ^N */ case 6: /* ^F */ case 'f': case 'n': case 'j': case 32: /* SP */ case '>': MOVE_DOWN; break; case 1: /* ^A */ case 'a': MOVE_HOME_UP; break; case 5: /* ^E */ case 'e': MOVE_END_DOWN; break; case 27: /* ESC */ switch (tolower (getch())) { case 'o': case 91: /* [ */ switch (tolower (getch())) { case 'a': /* ^ */ case 'd': /* < */ MOVE_UP; break; case 'b': /* > */ case 'c': /* v */ MOVE_DOWN; break; case '7': /* home */ MOVE_HOME_UP; getch(); break; case '8': /* end */ MOVE_END_DOWN; getch(); break; default: while (getch() != '~'); GET_HELP(0); } break; default: GET_HELP(0); } break; case 'h': case '?': GET_HELP(0); case 10: // Enter return (pos); case 'q': case 'x': case 3: /* ^C */ bye (0); case 12: /* ^L */ case 18: /* ^R */ //redrawln (stdscr, 0, 24); break; message ("sorry, cannot redraw screen yet", 1); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (c-'1' < num) return (c-'1'); default: message ("Illegal key. Please use \"h\" or \"?\" for help.", 1); break; } refresh(); } } // menu int bye (int i) { CLN_STR(LINES-1); nocbreak(); echo(); move (LINES-1, 0); refresh(); endwin(); exit (i); } // end of interface /******************************************************************* algorithm only. Uses: message(); get_number(). ask(); *******************************************************************/ int play_one_game (int first, int second, double * f_tm, double * s_tm, int max_num) /************** play Mastermind just once. first/second: 0: human; first AND second cannot be 0 BOTH at the same time; 1: computer, incremental; 2: computer, random. returns: 0: tie; 1: first won; -1: second won; 10: error. **************/ { struct timeval tm1, tm2; struct timezone tzp; int frs_num, sec_num, round; reply frs_reps[GL], sec_reps[GL]; int frs_ask[GL], sec_ask[GL]; int (*frs_get) (reply *, int *, int, int, int, int); int (*sec_get) (reply *, int *, int, int, int, int); #define NUMLN 5 static char * lines[NUMLN] = {"Are you still there?! You are very daring!", "No result yet, but somehow I'm sure I'll win.", "You think slowly. I bet you are a mere human!", "Your case is hopeless. Surrender!", "Why don\'t you just give up?!"}; if ( (first == 0) && (second == 0)) { message ("both cannot be humans!", 1); return 10; } clear_str (STARTL, MSGL-1); // the framework: switch (first) { case 0: mvaddstr (STARTL, FIRST-BSPACE, "1:Human - #:"); mvaddstr (STARTL+1, SECOND-BSPACE, " H guesses:"); frs_get = & homo_guess; break; case 1: mvaddstr (STARTL, FIRST-BSPACE, "1:CIncr - #:"); mvaddstr (STARTL+1, SECOND-BSPACE, " CI guesses:"); frs_get = & incr_guess; break; case 2: mvaddstr ( STARTL, FIRST-BSPACE, "1:CRand - #:"); mvaddstr ( STARTL+1, SECOND-BSPACE, " CR guesses:"); frs_get = & rand_guess; break; default: message ("wrong first argument in play_one_game. hit a key\t\t", 1); getch(); return (10); } PUT_STARS(STARTL, FIRST) switch (second) { case 0: mvaddstr (STARTL, SECOND-BSPACE, "2:Human - #:"); mvaddstr (STARTL+1, FIRST-BSPACE, " H guesses:"); sec_get = & homo_guess; break; case 1: mvaddstr (STARTL, SECOND-BSPACE, "2:CIncr - #:"); mvaddstr (STARTL+1, FIRST-BSPACE, " CI guesses:"); sec_get = & incr_guess; break; case 2: mvaddstr (STARTL, SECOND-BSPACE, "2:CRand - #:"); mvaddstr (STARTL+1, FIRST-BSPACE, " CR guesses:"); sec_get = & rand_guess; break; default: message ("wrong second argument in play_one_game. hit a key\t\t", 1); getch(); return (10); } PUT_STARS (STARTL, SECOND) // get the numbers to be guessed. if (first) { do frs_num = RND(max_num); while (not_valid (frs_num)); } else { PUT_HELP("Enter your secret number, then press ."); if ( (frs_num = get_number (STARTL, FIRST)) == -1) return (10); } if (second) { do sec_num = RND(max_num); while (not_valid (sec_num)); } else { PUT_HELP("Enter your secret number, then press ."); if ( (sec_num = get_number (STARTL, SECOND)) == -1) return (10); } round = 0; do { // do the moves: gettimeofday(&tm1, &tzp); if ( ( (*frs_get) (frs_reps, frs_ask, SECOND, round, max_num, sec_num)) == -1) return (10); gettimeofday(&tm2, &tzp); *f_tm += (double) (tm2.tv_sec - tm1.tv_sec) + ( (double) (tm2.tv_usec - tm1.tv_usec) )/1000000; refresh(); gettimeofday(&tm2, &tzp); if ( ( (*sec_get) (sec_reps, sec_ask, FIRST , round, max_num, frs_num)) == -1) return (10); gettimeofday(&tm2, &tzp); *s_tm += (double) (tm2.tv_sec - tm1.tv_sec) + ( (double) (tm2.tv_usec - tm1.tv_usec) ) /1000000; refresh(); // the results of the round: if ( (frs_reps[round].won())&& (sec_reps[round].won())) return (0); //tie else if ( (! (frs_reps[round].won()))&& (sec_reps[round].won())) { mvprintw ( STARTL, SECOND, "%.*d", ln, sec_num); refresh(); return (-1); } else if ( (frs_reps[round].won())&& (! (sec_reps[round].won()))) { mvprintw (STARTL, FIRST, "%.*d", ln, frs_num); refresh(); return (1); } else if (! (first*second)) message (lines[RND(NUMLN)], 0); } while (round++ < GL); } // of play_one_game int rand_guess (reply reps[GL], int old_guess[GL], int pos, int round, int max_num, int secret) /******** make a random guess of SECRET, which is up to MAX_NUM; put at collumn POS. use old guesses OLD_GUESS with replies REPS in ROUND. ********/ { int j, i, ini, num_ok = 0; ini = RND(max_num); PUT_STARS(STARTL+1+round, pos) message ("CRand is thinking. Please wait...", 0); if (drand48() > 0.5) { // go up for (i = ini+1; (i != ini) && (!num_ok) ; i = ( i + 1 ) % max_num ) if (!not_valid (i)) for (j = 0, num_ok = 1 ; (j < round) && (num_ok) ; j++) num_ok *= reps[j].match (i, old_guess[j]); old_guess[round] = ( i - 1 + max_num ) % max_num; } else { // go down for (i = ini-1; (i != ini) && (!num_ok) ; i = ( i - 1 + max_num ) % max_num ) if (!not_valid (i)) for (j = 0, num_ok = 1 ; (j < round) && (num_ok) ; j++) num_ok *= reps[j].match (i, old_guess[j]); old_guess[round] = ( i + 1 ) % max_num; } if (i == ini) { message ("Random: ERROR! hit a key", 1); getch(); } mvprintw (STARTL+1+round, pos, "%.*d", ln, old_guess[round]); reps[round].compare (old_guess[round], secret); reps[round].output (STARTL+1+round, pos+SPACE); return (old_guess[round]); } // rand_guess int incr_guess (reply reps[GL], int old_guess[GL], int pos, int round, int max_num, int secret) /***** make an incremental guess of SECRET, which is up to MAX_NUM; put at collumn POS. use old guesses OLD_GUESS with replies REPS in ROUND. *****/ { int j, i, ini, num_ok = 0; PUT_STARS(STARTL+1+round, pos) message ("CIncr is thinking. Please wait...", 0); (round) ? (i = old_guess[round-1]) : (i = 0); for (i++, num_ok = 0; (i < max_num) && (!num_ok); i++) if (!not_valid (i)) for (j = 0, num_ok = 1; (j < round) && (num_ok) ; j++) num_ok *= reps[j].match (i, old_guess[j]); if (i == max_num) { message ("Incremental: ERROR! hit a key", 1); getch(); } old_guess[round] = --i; mvprintw (STARTL+1+round, pos, "%.*d", ln, old_guess[round]); reps[round].compare (old_guess[round], secret); reps[round].output (STARTL+1+round, pos+SPACE); return (old_guess[round]); } // incr_guess int homo_guess (reply reps[GL], int old_guess[GL], int pos, int round, int max_num, int secret) // get the human player's guess. { PUT_STARS(STARTL+1+round, pos) PUT_HELP("Enter your guess, then press ."); old_guess[round] = get_number (STARTL+1+round, pos); reps[round].compare (old_guess[round], secret); reps[round].output (STARTL+1+round, pos+SPACE); return (old_guess[round]); } void play_many_games (int f, int s) // play several games, keeping the score. { int first = 0, second = 0, tie = 0, i; int max_num = 1; // 10^ln double f_tm = 0, s_tm = 0; // get ln: PUT_HELP ("What will the length of the number be?"); clear_str (STARTL, MSGL); mvaddstr (HLPL, 0, "\tPlease enter the length of the word.\n\t\ You should enter a reasonable number:\n\t\ 2 is too short - it won't be interesting, \n\t\ 6 is too long - it'll take me too long\n\t\ to produce each guess.\n\t\ The reasonable number is, probably, 4, \n\t\ but 3 also might be interesting."); mvprintw ( MSGL, LEFTMARGIN, "Enter number from 2 to %d. Default is %d ...", MLN, DEFLN ); refresh(); ln = getch() - '0'; if ( (ln < 2) || (ln > MLN)) ln = DEFLN; clear_str (HLPL, MSGL); // if not a demo, get_number will say something... PUT_HELP("Demo game..."); // initialization of 'max_num'. for (i = 0; i < ln; i++) max_num *= 10; do { switch (play_one_game (f, s, & f_tm, & s_tm, max_num)) { case 0 : if (f*s == 0) PUT_HELP ("Uphhh... Tie! That was close!"); tie++ ; break; case 1 : if (f == 0) PUT_HELP ("Ouch! You are SMART! You beat me!"); if (s == 0) PUT_HELP ("As one could expect, you lost."); first++; break; case -1: if (s == 0) PUT_HELP ("Ouch! You are SMART! You beat me!"); if (f == 0) PUT_HELP ("As one could expect, you lost."); second++; break; } mvprintw (1, 1, "1:%d; 2:%d; tie:%d; games:%d; time: 1:%.3f sec; 2:%.3f sec.", first, second, tie, first+second+tie, f_tm, s_tm); mvprintw (STARTL, FIRST + SPACE, "(won: %d)", first); mvprintw (STARTL, SECOND + SPACE, "(won: %d)", second); } while (ask ("The game is over. Again?", 0, 1)); clear_str (STARTL-1, MSGL); } // play_many_games inline int not_valid (int x) /*************************************** Check if the number 'x' is valid. Return 0 if valid; or the second part or the first bad spot: for number 4598595 returns 4 - the position of the second "5". To save time does NOT check x < 10^ln. BEWARE! ***************************************/ { int i, j, i10, j10; for (i = 1, i10 = 10 ; i < ln; i++, i10 *= 10) for (j = 0, j10 = 1 ; j < i; j++, j10 *= 10) if ( (x/i10)%10 == (x/j10)%10 ) return (i); return (0); }