Mastermind in Curses
I present yet another curses-based game. This time, it's a "turn-based" one, blocking/waiting for user input, instead of always going forward in an "real-time" oriented manner. It's based on an old popular board game. And more; it's in color!
The rules are simple; try to guess the hidden code in 10 or less attempts. Each time a guess is made, some hints are given that will indicate if a part of the code is correct and in the correct position (X), or part a of the code is correct, but in the wrong position (/).
Here's a screenshot:
Here's the code:
#include <stdlib.h> #include <string.h> #include <time.h> #include <ncurses.h> #define CODE_SLOTS 4 #define GUESS_SLOTS 10 typedef enum { CODE_NONE = 0, CODE_ALPHA = 1, CODE_BRAVO = 2, CODE_CHARLIE = 3, CODE_DELTA = 4, CODE_ECHO = 5, CODE_FOXTROT = 6, } code_t; typedef enum { HINT_NONE = 0, HINT_CODE_AND_POSITION, HINT_CODE_ONLY, } hint_t; typedef struct guess_s { code_t code[CODE_SLOTS]; hint_t hint[CODE_SLOTS]; } guess_t; static const code_t code_to_char[] = {'.', 'A', 'B', 'C', 'D', 'E', 'F'}; static const hint_t hint_to_char[] = {' ', 'X', '/'}; static code_t hidden_code[CODE_SLOTS]; static guess_t guess[GUESS_SLOTS]; static int current_guess, current_slot; static int guess_is_completed() { int i; for (i = 0; i < CODE_SLOTS; i++) { if (guess[current_guess].code[i] == CODE_NONE) return 0; } return 1; } static int guess_is_correct() { int i; for (i = 0; i < CODE_SLOTS; i++) { if (guess[current_guess].hint[i] != HINT_CODE_AND_POSITION) return 0; } return 1; } static void display_init(void) { initscr(); if (has_colors()) { start_color(); init_pair(CODE_ALPHA, COLOR_RED, COLOR_BLACK); init_pair(CODE_BRAVO, COLOR_GREEN, COLOR_BLACK); init_pair(CODE_CHARLIE, COLOR_YELLOW, COLOR_BLACK); init_pair(CODE_DELTA, COLOR_BLUE, COLOR_BLACK); init_pair(CODE_ECHO, COLOR_MAGENTA, COLOR_BLACK); init_pair(CODE_FOXTROT, COLOR_CYAN, COLOR_BLACK); } noecho(); keypad(stdscr, TRUE); } static void display_update(char *end_msg) { int i, j; erase(); /* Draw area with hidden code. */ mvaddstr(0, 0, "+---+---+---+---+"); if (end_msg != NULL) { mvaddstr(1, 0, "| | | | |"); for (i = 0; i < CODE_SLOTS; i++) { move(1, 2 + (i * 4)); if (has_colors() && hidden_code[i] != CODE_NONE) attrset(A_BOLD | COLOR_PAIR(hidden_code[i])); addch(code_to_char[hidden_code[i]]); if (has_colors() && hidden_code[i] != CODE_NONE) attrset(A_NORMAL); } mvaddstr(1, 18, end_msg); } else { mvaddstr(1, 0, "|###|###|###|###|"); } mvaddstr(2, 0, "+---+---+---+---+----+"); /* Draw area with user guesses. */ for (i = 0; i < GUESS_SLOTS; i++) { mvaddstr(3 + (i * 2), 0, "| | | | | |"); for (j = 0; j < CODE_SLOTS; j++) { move(3 + (i * 2), 2 + (j * 4)); if (has_colors() && guess[i].code[j] != CODE_NONE) attrset(A_BOLD | COLOR_PAIR(guess[i].code[j])); addch(code_to_char[guess[i].code[j]]); if (has_colors() && guess[i].code[j] != CODE_NONE) attrset(A_NORMAL); } for (j = 0; j < CODE_SLOTS; j++) { move(3 + (i * 2), 17 + j); if (has_colors() && guess[i].hint[j] != HINT_NONE) attrset(A_BOLD); addch(hint_to_char[guess[i].hint[j]]); if (has_colors() && guess[i].hint[j] != HINT_NONE) attrset(A_NORMAL); } mvaddstr(4 + (i * 2), 0, "+---+---+---+---+----+"); } /* Place cursor on current slot to select. */ move(3 + (current_guess * 2), 2 + (current_slot * 4)); refresh(); } static void evaluate_and_give_hints(void) { int i, j, code_only = 0, code_and_pos = 0; int guess_covered[CODE_SLOTS], hidden_covered[CODE_SLOTS]; /* Check for correct code and position. */ for (i = 0; i < CODE_SLOTS; i++) { if (guess[current_guess].code[i] == hidden_code[i]) { code_and_pos++; guess_covered[i] = 1; hidden_covered[i] = 1; } else { guess_covered[i] = 0; hidden_covered[i] = 0; } } /* Check for any remaining correct codes. */ for (i = 0; i < CODE_SLOTS; i++) { if (guess_covered[i]) continue; for (j = 0; j < CODE_SLOTS; j++) { if (hidden_covered[j]) continue; if (guess[current_guess].code[i] == hidden_code[j]) { hidden_covered[j] = 1; code_only++; break; } } } i = 0; while (code_and_pos-- > 0) guess[current_guess].hint[i++] = HINT_CODE_AND_POSITION; while (code_only-- > 0) guess[current_guess].hint[i++] = HINT_CODE_ONLY; } static void hidden_code_randomize(void) { int i; srandom(time(NULL)); for (i = 0; i < CODE_SLOTS; i++) hidden_code[i] = (random() % CODE_FOXTROT) + 1; } int main(void) { int user_done, user_quit; memset(guess, 0, sizeof(guess_t) * GUESS_SLOTS); display_init(); hidden_code_randomize(); for (current_guess = GUESS_SLOTS - 1; current_guess >= 0; current_guess--) { /* Wait for user to make a valid guess. */ current_slot = user_done = user_quit = 0; while ((! user_done) && (! user_quit)) { display_update(NULL); switch (getch()) { case KEY_LEFT: current_slot--; if (current_slot < 0) current_slot = 0; break; case KEY_RIGHT: current_slot++; if (current_slot >= CODE_SLOTS) current_slot = CODE_SLOTS - 1; break; case KEY_UP: guess[current_guess].code[current_slot]++; if (guess[current_guess].code[current_slot] > CODE_FOXTROT) guess[current_guess].code[current_slot] = CODE_ALPHA; break; case KEY_DOWN: guess[current_guess].code[current_slot]--; if (guess[current_guess].code[current_slot] == CODE_NONE || guess[current_guess].code[current_slot] > CODE_FOXTROT) guess[current_guess].code[current_slot] = CODE_FOXTROT; break; case KEY_ENTER: case '\n': case ' ': if (guess_is_completed()) user_done = 1; break; case 'q': case 'Q': case '\e': user_quit = 1; break; default: break; } } if (user_quit) break; evaluate_and_give_hints(); if (guess_is_correct()) break; } if (guess_is_correct()) { display_update("Congratulations!"); } else { if (user_quit) { display_update("Aborted."); } else { display_update("Too late!"); } } if (! user_quit) getch(); /* Press any key... */ endwin(); return 0; }