diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..589b783 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CC=g++ +CFLAGS=-Wall +CURSES_LIBS=-lncurses + +ncurses: windows/windows.o braincurses.o + ${CC} ${CFLAGS} windows/windows.o braincurses.o ${CURSES_LIBS} -o braincurses + +windows.o: windows.cpp + ${CC} ${CFLAGS} -c windows/windows.cpp + +braincurses.o: braincurses.cpp + ${CC} ${CFLAGS} -c braincurses.cpp + +install: + install -o root -g root braincurses /usr/local/bin/ + +uninstall: + rm /usr/local/bin/braincurses + +clean: + rm -f braincurses + rm -f *.o + rm -f curses/*.o diff --git a/README.md b/README.md new file mode 100644 index 0000000..c80a7ff --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +A simple code-breaking game. + +The object of the game is to guess the correct colors in the correct order. +[WikiPedia][] has more details about game play, the history of the game, and +Donald Knuth's algorithm. + +[WikiPedia]: https://en.wikipedia.org/wiki/Mastermind_(board_game) + "Mastermind" diff --git a/braincurses.cpp b/braincurses.cpp new file mode 100644 index 0000000..2b945d9 --- /dev/null +++ b/braincurses.cpp @@ -0,0 +1,207 @@ +/* braincurses.cpp + * Copyright © 2002, Brian Derr + */ + +#include "braincurses.h" + +using namespace std; + + Answer:: +Answer() +{ + getRdmNums(); +} + + Answer:: +~Answer() +{ } + + void Answer:: +getRdmNums() +{ + static bool ran = false; + int i; + if( ran == false ) { + srand( time( 0 ) * getpid() ); + ran = true; + } + + for( i = 0; i < 4; i++ ) + answer[i] = 1 + (int) ( 6.0 * rand() / ( RAND_MAX + 1.0 ) ); +} + + int Answer:: +grabAnswer( int x ) +{ + return( answer[x] ); +} + + Guess:: +Guess() +{ + // Initialize the variables + int i; + for( i = 0; i < 4; i++ ) { + guess[i] = 0; + markers[i] = 0; + } +} + + Guess:: +~Guess() +{ } + + bool Guess:: +isValid( string tmp ) +{ + bool done = false; + static int tracker = 0; + if( tracker == 4 ) + tracker = 0; + if( tmp == "red" || tmp == "r" ) { + setInput( "red", tracker ); + tracker++; + done = true; + } else if( tmp == "white" || tmp == "w" ) { + setInput( "white", tracker ); + tracker++; + done = true; + } else if( tmp == "yellow" || tmp == "y" ) { + setInput( "yellow", tracker ); + tracker++; + done = true; + } else if( tmp == "green" || tmp == "g" ) { + setInput( "green", tracker ); + tracker++; + done = true; + } else if( tmp == "blue" || tmp == "b") { + setInput( "blue", tracker ); + tracker++; + done = true; + } else if( tmp == "purple" || tmp == "p" ) { + setInput( "purple", tracker ); + tracker++; + done = true; + } else if( tmp == "quit" ) { + quitGame(); + } else + done = false; + + return( done ); +} + + void Guess:: +setInput( string str, int tracker ) +{ + if( str == "red" ) + guess[tracker] = RED; + else if( str == "white" ) + guess[tracker] = WHITE; + else if( str == "yellow" ) + guess[tracker] = YELLOW; + else if( str == "green" ) + guess[tracker] = GREEN; + else if( str == "blue" ) + guess[tracker] = BLUE; + else if( str == "purple" ) + guess[tracker] = PURPLE; + else { + cerr << "braincurses: incorrect input" << endl; + exit( 1 ); + } +} + + void Guess:: +compareWithAnswer( Answer ans ) +{ + // Assertion: You will call this function with your current Answer object + // for it to correctly compare against user inputed guesses + + int bMarker = 0, wMarker = 0; + // bMarker indicates that the guess is the correct color and placement + // wMarker indicates that a guess is correct color but not placement + + int i; + + for( i = 0 ; i < 4 ; i++ ) { + if( guess[i] == ans.grabAnswer(i) ){ + bMarker++; + } + } + + int num_len=10; + int guess_num[num_len]; + int ans_num[num_len]; + + for( i = 0; i < num_len; i++ ) { + guess_num[i] = 0; + ans_num[i] = 0; + } + + for( i = 0; i < 4; i++ ) { + guess_num[guess[i]]++; + ans_num[ans.grabAnswer(i)]++; + } + + for( i = 0; i < num_len; i++ ) { + if( ans_num[i] <= guess_num[i] ) + wMarker += ans_num[i]; + else + wMarker += guess_num[i]; + } + + wMarker -= bMarker; + if( wMarker < 0 ) + wMarker = 0; + // my thinking here is that there will be bMarker more wMarkers than + // needed since a bMarker is inherently a wMarker + + setMarkers( bMarker, wMarker ); +} + + void Guess:: +setMarkers( int bMarker, int wMarker ) +{ + int i; + + if( bMarker > 0 ) + for( i = 0; i < bMarker; i++ ) + markers[i] = 0; + + if( wMarker > 0 ) + for(i = bMarker; i < wMarker + bMarker; i++ ) + markers[i] = 1; + + int count = bMarker + wMarker; + + if( count == 0 ) + for( i = count; i < 4; i++ ) + markers[i] = 3; + else + for( i = count; i < 4; i++ ) + markers[i] = 3; + } + + void Guess:: +showMarkers( int array[] ) +{ + int i; + for(i = 0; i < 4; i++ ) + array[i] = markers[i]; +} + + void Guess:: +showGuesses( int array[] ) +{ + int i; + for( i = 0; i < 4; i++ ) + array[i] = guess[i]; +} + + void Guess:: +quitGame() +{ + system( "clear" ); + cout << "Bye, thanks for playing." << endl; + exit( 0 ); +} diff --git a/braincurses.h b/braincurses.h new file mode 100644 index 0000000..4debff4 --- /dev/null +++ b/braincurses.h @@ -0,0 +1,62 @@ +/* braincurses.h + * Copyright © 2002, Brian Derr + */ + +#ifndef BRAINCURSES_H +#define BRAINCURSES_H + +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +enum Colors { NONE, RED, WHITE, YELLOW, GREEN, BLUE, PURPLE }; + +class Answer +{ + private: + int answer[4]; + void getRdmNums(); + + protected: + + public: + Answer(); + ~Answer(); + + int grabAnswer( int x ); +}; + +class Guess +{ + private: + int guess[4]; + int markers[4]; // used to keep track if a guess is in the correct place + // 0 = black, 1 = white, 3 = none + typedef map mGuess; + void setInput( string str, int tracker ); + void setMarkers( int bMarker, int wMarker ); + + protected: + + public: + Guess(); + Guess( int guess1, int guess2, int guess3, int guess4 ); + ~Guess(); + + bool isValid( string tmp ); + void compareWithAnswer( Answer ans ); + void showMarkers( int array[] ); + void showGuesses( int array[] ); + void quitGame(); + + friend class Answer; +}; + + +#endif // BRAINCURSES_H diff --git a/windows/windows.cpp b/windows/windows.cpp new file mode 100644 index 0000000..2e0aaec --- /dev/null +++ b/windows/windows.cpp @@ -0,0 +1,365 @@ +/* windows.cpp + * Copyright © 2002, Brian Derr + */ + +#include "windows.h" + +#define DEFAULT_NUM_GUESSES 10 + +using namespace std; + +int main( int argv, char *argc[] ) +{ + /* Thanks to Peter Peterson for adding the ability to set the + * number of guesses from the command line. An option I never + * thought about but is a good one. */ + int maxGuesses = DEFAULT_NUM_GUESSES; + if( argv == 2 ) + maxGuesses = atoi( argc[1] ); + if( maxGuesses <= 0 ) + maxGuesses = DEFAULT_NUM_GUESSES; + if( maxGuesses > 15 ) + maxGuesses = 15; + initscr(); + + if( has_colors() ) { + start_color(); + + init_pair( 1, COLOR_RED, COLOR_BLACK ); + init_pair( 2, COLOR_WHITE, COLOR_BLACK ); + init_pair( 3, COLOR_YELLOW, COLOR_BLACK ); + init_pair( 4, COLOR_GREEN, COLOR_BLACK ); + init_pair( 5, COLOR_BLUE, COLOR_BLACK ); + init_pair( 6, COLOR_MAGENTA, COLOR_BLACK ); + } else { + cerr << "braincurses: Your terminal cannot display colors.\n" << + "Gameplay is not possible without colors." << endl; + exit( 99 ); + } + + cbreak(); + curs_set( 1 ); + + Winders winders; + + winders.top_left = create_newwin( 3, 18, 0, 0 ); + winders.top_right = create_newwin( 3, 18, 0, 18 ); + winders.left = create_newwin( 17, 17, 3, 0 ); + winders.middle = create_newwin( 17, 17, 3, 19 ); + winders.right = create_newwin( 20, 24, 0, 36 ); + winders.bottom = create_newwin( 3, 60, 20, 0 ); + + keypad( winders.bottom, TRUE ); + + // --- make a small window for the ruler --- // + WINDOW *slit; + slit = newwin( 17, 2, 3, 17 ); + wrefresh( slit ); + // --- all done with that --- // + + // ----------------------------- // + // set up unchanging texts // + char game_name[12] = "BrainCurses"; + char author[11] = "Brian Derr"; + char copyright[9] = "(c) 2002"; + char hidden = 'X'; + mvwaddstr( winders.top_left, 1, 4, game_name ); + mvwaddstr( winders.right, 18, 2, copyright ); + mvwaddstr( winders.right, 18, 11, author ); + mvwaddstr( winders.right, 1, 2, "Colors: " ); + mvwaddstr( winders.right, 2, 2, "RED, BLUE, YELLOW" ); + mvwaddstr( winders.right, 3, 2, "WHITE, GREEN, PURPLE" ); + mvwaddstr( winders.right, 5, 2, "Type \"quit\" to end" ); + mvwaddstr( winders.right, 6, 2, "the game." ); + mvwaddstr( winders.right, 8, 2, "Remember to hit the" ); + mvwaddstr( winders.right, 9, 2, "enter key after each" ); + mvwaddstr( winders.right, 10, 2, "guess!" ); + mvwaddch( winders.top_right, 1, 3, hidden ); + mvwaddch( winders.top_right, 1, 7, hidden ); + mvwaddch( winders.top_right, 1, 11, hidden ); + mvwaddch( winders.top_right, 1, 15, hidden ); + + char guessLabel[2]; + for( int i = 1; i <= maxGuesses; i++ ) { + sprintf( guessLabel, "%2d", i ); + mvwaddstr( slit, 16-i, 0, guessLabel ); + } + wmove( winders.bottom, 1, 15 ); + wnoutrefresh( winders.top_left ); + wnoutrefresh( winders.top_right ); + wnoutrefresh( winders.right ); + wnoutrefresh( slit ); + doupdate(); + // ----------------------------- // + + Answer ans; // setup random numbers for first game + + int marker_arr[4]; + int guess_arr[4]; + + int outer = 0, inner = 0; + bool winner = false; + while( outer < maxGuesses ) { + Guess guess; + for( inner = 0; inner < 4; inner++ ) { + if( ! getInput( guess, inner, winders ) ) { + wrongInput( winders.bottom, guess, inner ); + inner -= 1; + } + } // end "inner" loop + + cleanUpWindow( winders.bottom ); + guess.compareWithAnswer( ans ); + guess.showGuesses( guess_arr ); + dispGuesses( winders.middle, guess, guess_arr, outer ); + guess.showMarkers( marker_arr ); + dispMarkers( winders.left, marker_arr, outer ); + + if( isWinner( marker_arr ) ) { + winner = true; + break; + } + + outer++; + } // end "outer" while loop + + if( winner == true ) { + youWin( winders.bottom ); + dispAnswers( winders.top_right, ans ); + // writeScores( outer ); + } else { + youLose( winders.bottom ); + dispAnswers( winders.top_right, ans ); + } + + wgetch( winders.bottom ); + if( playAgain( winders.bottom ) ) + main( argv, argc ); + + // wrap the show up + closeCurses( winders ); + Guess g; + g.quitGame(); + + return( 0 ); +} + +WINDOW *create_newwin( int height, int width, int starty, int startx ) +{ + WINDOW *local_win; + + local_win = newwin( height, width, starty, startx ); + box( local_win, 0, 0 ); + wrefresh( local_win ); + + return( local_win ); +} + +void destroy_win( WINDOW *local_win ) +{ + wborder( local_win, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' ); + wrefresh( local_win ); + delwin( local_win ); +} + +void cleanUpWindow( WINDOW *local_win ) +{ + werase( local_win ); + box( local_win, 0, 0 ); + wrefresh( local_win ); +} + +bool getInput( Guess &g, int x, Winders winders ) +{ + bool done = false; + char input[INPUT_LENGTH]; + mvwgetnstr( winders.bottom, 1, 14 * (x + 1) - INPUT_LENGTH, input, + INPUT_LENGTH ); + + string guess = input; + if( strcmp( input, "quit" ) == 0 ) { + closeCurses( winders ); + g.quitGame(); + } else if( strcmp( input, "undo" ) == 0 ) { + ; // still need to implement this + } else if( g.isValid( guess ) ) + done = true; + + return( done ); +} + +void wrongInput( WINDOW *local_win, Guess &g, int x ) +{ + // first lets clear the window + int places[4] = { 7, 21, 35, 49 }; + if( x == 0 ) { + wmove( local_win, 1, places[0] ); + waddstr( local_win, " " ); + } else if( x == 1 ) { + wmove( local_win, 1, places[1] ); + waddstr( local_win, " " ); + } else if( x == 2 ) { + wmove( local_win, 1, places[2] ); + waddstr( local_win, " " ); + } else { + wmove( local_win, 1, places[3] ); + waddstr( local_win, " " ); + } + wrefresh( local_win ); +} + +void dispGuesses( WINDOW *local_win, const Guess &g, int guess_arr[], + int outer ) +{ + int i, y, x; + y = 15 - outer; + for( i = 0; i < 4; i++ ) { + if( i == 0 ) + x = 2; + else if( i == 1 ) + x = 6; + else if( i == 2) + x = 10; + else if( i ==3 ) + x = 14; + switch( guess_arr[i] ) { + case RED: + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(1) | A_BOLD ); + break; + case WHITE: + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(2) | A_BOLD ); + break; + case YELLOW: + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(3) | A_BOLD ); + break; + case GREEN: + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(4) | A_BOLD ); + break; + case BLUE: + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(5) | A_BOLD ); + break; + case PURPLE: + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(6) | A_BOLD ); + break; + } + wrefresh( local_win ); + } +} + +void dispMarkers( WINDOW *local_win, int marker_arr[], int outer ) +{ + int i, y, x; + y = 15 - outer; + for( i = 0; i < 4; i++ ) { + if( i == 0 ) + x = 2; + else if( i == 1 ) + x = 6; + else if( i == 2 ) + x = 10; + else if( i == 3 ) + x = 14; + + if( marker_arr[i] == 0 ) + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(1) | A_BOLD ); + else if( marker_arr[i] == 1 ) + mvwaddch( local_win, y, x, 'X' | COLOR_PAIR(2) | A_BOLD ); + } // end for + wrefresh( local_win ); +} + +bool isWinner( int marker_arr[] ) +{ + int i; + bool winner = true; + + for( i = 0; i < 4; i++ ) { + if( marker_arr[i] == 0 ) + continue; + else { + winner = false; + break; + } + } // end for + return( winner ); +} + +void youWin( WINDOW *local_win ) +{ + cleanUpWindow( local_win ); + mvwaddstr( local_win, 1, 1, "You win the game! Congratulations!" ); + wrefresh( local_win ); +} + +void youLose( WINDOW *local_win ) +{ + cleanUpWindow( local_win ); + mvwaddstr( local_win, 1, 1, + "You ran out of turns! Better luck next time." ); + wrefresh( local_win ); +} + +void dispAnswers( WINDOW *local_win, Answer ans ) +{ + int i, x; + for( i = 0; i < 4; i++ ) { + if( i == 0 ) + x = 3; + else if( i == 1 ) + x = 7; + else if( i == 2 ) + x = 11; + else if( i == 3 ) + x = 15; + + switch( ans.grabAnswer( i ) ) { + case RED: + mvwaddch( local_win, 1, x, 'X' | COLOR_PAIR(1) | A_BOLD); + break; + case WHITE: + mvwaddch( local_win, 1, x, 'X' | COLOR_PAIR(2) | A_BOLD ); + break; + case YELLOW: + mvwaddch( local_win, 1, x, 'X' | COLOR_PAIR(3) | A_BOLD ); + break; + case GREEN: + mvwaddch( local_win, 1, x, 'X' | COLOR_PAIR(4) | A_BOLD ); + break; + case BLUE: + mvwaddch( local_win, 1, x, 'X' | COLOR_PAIR(5) | A_BOLD ); + break; + case PURPLE: + mvwaddch( local_win, 1, x, 'X' | COLOR_PAIR(6) | A_BOLD ); + break; } + + wrefresh( local_win ); + curs_set( 0 ); + } +} + +bool playAgain( WINDOW *local_win ) +{ + noecho(); + cleanUpWindow( local_win ); + mvwaddstr( local_win, 1, 1, "Would you like to play again? (y/n)" ); + wrefresh( local_win ); + + char again; + again = wgetch( local_win ); + + echo(); + + return( again == 'y' ? true : false ); +} + +void closeCurses( Winders winders ) +{ + destroy_win( winders.top_left ); + destroy_win( winders.top_right ); + destroy_win( winders.left ); + destroy_win( winders.middle ); + destroy_win( winders.right ); + destroy_win( winders.bottom ); + + endwin(); +} diff --git a/windows/windows.h b/windows/windows.h new file mode 100644 index 0000000..440f9db --- /dev/null +++ b/windows/windows.h @@ -0,0 +1,46 @@ +/* windows.h + * Copyright © 2002, Brian Derr + */ + +#ifndef WINDOWS_H +#define WINDOWS_H + +#include + +#include +#include + +#include "../braincurses.h" + +const int INPUT_LENGTH = 7; + +struct Winders { + WINDOW *top_left; + WINDOW *top_right; + WINDOW *left; + WINDOW *middle; + WINDOW *right; + WINDOW *bottom; +}; + +WINDOW *create_newwin( int height, int width, int starty, int startx ); +void destroy_win( WINDOW *local_win ); + +void cleanUpWindow( WINDOW *local_win ); +bool getInput( Guess &g, int x, Winders winders ); +void wrongInput( WINDOW *local_win, Guess &g, int inner ); +void dispGuesses( WINDOW *local_win, const Guess &g, int guess_arr[], + int outer ); +void dispMarkers( WINDOW *local_win, int marker_arr[], int outer ); +bool isWinner( int marker_arr[] ); +void youWin( WINDOW *local_win ); +void youLose( WINDOW *local_win ); +void dispAnswers( WINDOW *local_win, Answer ans ); +bool playAgain( WINDOW *local_win ); +void closeCurses( Winders winders ); +void printTopScores( WINDOW *local_win ); + +extern void getScores( int scoreNum, char *name, int &score ); +extern int writeScores( int turns ); + +#endif // WINDOWS_H