diff --git a/.clang-format b/.clang-format index c897662cfaa543a38998a83da4a4ebf1ddd124f7..d8a2c06a7cba23b658269c9819f60f81ea38d3fb 100644 --- a/.clang-format +++ b/.clang-format @@ -58,7 +58,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 100 +ColumnLimit: 140 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false diff --git a/main.c b/main.c index 081068ebbd7b0cf0c6c6ee21b365f2aaf1e51276..8d3eb8401b8eb782f6d502b8a67687f0f869e5be 100644 --- a/main.c +++ b/main.c @@ -20,8 +20,7 @@ int main(void) { clear(); set_gamemode(menu_choice); - - getch(); + launch_game(); cleanup_term(); diff --git a/ui/ui.c b/ui/ui.c index 034c6e5a11342048150d5dee8a30e71ffe840a28..233c24737acecd8af746b3402a13d8dc6b5fae30 100644 --- a/ui/ui.c +++ b/ui/ui.c @@ -1,30 +1,16 @@ #include "ui.h" -#define MENU_CHOICE_COUNT 4 +#define MENU_MAX 4 #define TITLE_Y_OFFSET 5 +#define MENU_CHOICE_Y_OFFSET 23 +#define GAMEBOARD_Y_OFFSET 10 //========================== // PRIVATE //========================== +// Windows WINDOW *ui_window = NULL; -char menu_choices[MENU_CHOICE_COUNT][10] = {"Solo", "1 Vs 1", "Computer", "Quit"}; - -char title[8][64] = {"$$\\ $$\\ $$$$$$\\ $$$$$$$\\ $$$$$$$\\ $$\\ $$$$$$$$\\ ", - "$$ | $\\ $$ |$$ __$$\\ $$ __$$\\ $$ __$$\\ $$ | $$ _____|", - "$$ |$$$\\ $$ |$$ / $$ |$$ | $$ |$$ | $$ |$$ | $$ | ", - "$$ $$ $$\\$$ |$$ | $$ |$$$$$$$ |$$ | $$ |$$ | $$$$$\\ ", - "$$$$ _$$$$ |$$ | $$ |$$ __$$< $$ | $$ |$$ | $$ __| ", - "$$$ / \\$$$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ | ", - "$$ / \\$$ | $$$$$$ |$$ | $$ |$$$$$$$ |$$$$$$$$\\ $$$$$$$$\\ ", - "\\__/ \\__| \\______/ \\__| \\__|\\_______/ \\________|\\________|"}; - -/** - * @brief Print a text at the center of the given line - * - * @param foo - * @param line - */ -void printw_center(char *foo, int line) { mvaddstr(line, TERM_MID_COLS - (strlen(foo) / 2), foo); } +WINDOW *help = NULL; /** * @brief Set the color scheme of the programm @@ -34,6 +20,7 @@ void set_colors() { init_pair(GREEN_PAIR, COLOR_GREEN, COLOR_BLACK); init_pair(ORANGE_PAIR, COLOR_YELLOW, COLOR_BLACK); init_pair(WHITE_PAIR, COLOR_WHITE, COLOR_BLACK); + init_pair(RED_PAIR, COLOR_RED, COLOR_BLACK); } /** @@ -41,48 +28,30 @@ void set_colors() { * * @return int */ -int select_menu_choice() { - int current_choice = 0; - int selection_ctrl = 0; - - do { - // Keyboard controls - switch (selection_ctrl) { - case KEY_DOWN: - current_choice += 1; - current_choice %= MENU_CHOICE_COUNT; - break; - - case KEY_UP: - current_choice -= 1; - current_choice = current_choice < 0 ? MENU_CHOICE_COUNT - 1 : current_choice; - break; - } - - // Print of the menu choices - for (int i = 0; i < MENU_CHOICE_COUNT; i++) { - int menu_selector_x = TERM_MID_COLS - (strlen(menu_choices[i]) / 2) - 2; - - if (i == current_choice) { - attron(A_BOLD); - mvaddch(23 + i, menu_selector_x, '>'); // Menu selector ON - printw_center(menu_choices[i], 23 + i); - attroff(A_BOLD); - } else { - mvaddch(23 + i, menu_selector_x, ' '); // Menu selector OFF - printw_center(menu_choices[i], 23 + i); - } - } - } while ((selection_ctrl = getch()) != '\n'); - - return current_choice; +void draw_menu(int menu_item) { + char menu_choices[MENU_MAX][20] = {" Solo ", " 1 Vs 1 ", " Computer ", " Quit "}; + + // Print of the menu choices + for (int i = 0; i < MENU_MAX; i++) { + if (i == menu_item) + attron(A_STANDOUT); + if (i == MENU_QUIT) + attron(COLOR_PAIR(RED_PAIR)); + + printw_center(menu_choices[i], MENU_CHOICE_Y_OFFSET + (i * 2)); + attroff(COLOR_PAIR(WHITE_PAIR)); + attroff(A_STANDOUT); + } } //========================== // PUBLIC //========================== +void printw_center(char *foo, int line) { mvaddstr(line, TERM_MID_COLS - (strlen(foo) / 2), foo); } + bool init_term() { ui_window = initscr(); + cbreak(); noecho(); curs_set(0); keypad(stdscr, true); @@ -103,12 +72,23 @@ bool init_term() { void cleanup_term() { curs_set(1); + nocbreak(); + delwin(help); delwin(ui_window); endwin(); refresh(); } int show_menu() { + char title[8][64] = {"$$\\ $$\\ $$$$$$\\ $$$$$$$\\ $$$$$$$\\ $$\\ $$$$$$$$\\ ", + "$$ | $\\ $$ |$$ __$$\\ $$ __$$\\ $$ __$$\\ $$ | $$ _____|", + "$$ |$$$\\ $$ |$$ / $$ |$$ | $$ |$$ | $$ |$$ | $$ | ", + "$$ $$ $$\\$$ |$$ | $$ |$$$$$$$ |$$ | $$ |$$ | $$$$$\\ ", + "$$$$ _$$$$ |$$ | $$ |$$ __$$< $$ | $$ |$$ | $$ __| ", + "$$$ / \\$$$ |$$ | $$ |$$ | $$ |$$ | $$ |$$ | $$ | ", + "$$ / \\$$ | $$$$$$ |$$ | $$ |$$$$$$$ |$$$$$$$$\\ $$$$$$$$\\ ", + "\\__/ \\__| \\______/ \\__| \\__|\\_______/ \\________|\\________|"}; + // Title print for (int i = 0; i < 8; i++) printw_center(title[i], i + TITLE_Y_OFFSET); @@ -116,14 +96,97 @@ int show_menu() { // Menu print attron(COLOR_PAIR(GREEN_PAIR)); attron(A_BOLD); - printw_center("MENU", 20); + printw_center("====== MENU ======", MENU_CHOICE_Y_OFFSET - 3); attroff(A_BOLD); + attron(COLOR_PAIR(WHITE_PAIR)); + + attron(A_DIM); + printw_center("Use arrow keys to move; Enter to chose", MENU_CHOICE_Y_OFFSET + 10); + attroff(A_DIM); // Menu choices handler - attron(COLOR_PAIR(WHITE_PAIR)); - int menu_choice = select_menu_choice(); + int key = 0, menu_item = 0; + do { + switch (key) { + case KEY_DOWN: + menu_item += 1; + if (menu_item > MENU_MAX - 1) + menu_item = 0; + break; + + case KEY_UP: + menu_item -= 1; + if (menu_item < 0) + menu_item = MENU_MAX - 1; + break; + } + + draw_menu(menu_item); + key = getch(); + } while (key != '\n'); + + refresh(); + + return menu_item; +} + +void toggle_help() { + // Delete existing instance of the subwindow + if (help != NULL) { + wclear(ui_window); + delwin(help); + refresh(); + + help = NULL; + return; + } + // Create new instance of the subwindow + help = subwin(ui_window, 7, COLS - 2, LINES - 7, 1); refresh(); - return menu_choice; + if (help == NULL) { + cleanup_term(); + exit(EXIT_FAILURE); + } + + box(help, 0, 0); + mvwprintw(help, 0, 1, " Help "); // Title + + // Content + // Keybindings + wattron(help, A_BOLD); + mvwprintw(help, 2, 2, "Ctrl+h"); + mvwprintw(help, 3, 2, "Ctrl+t"); + mvwprintw(help, 4, 2, "Ctrl+w"); + wattroff(help, A_BOLD); + + // Description + mvwprintw(help, 2, 10, "Show this page"); + mvwprintw(help, 3, 10, "Toggle the assistance tool (only in Computer mode)"); + mvwprintw(help, 4, 10, "Quit the game"); + + refresh(); + wrefresh(help); +} + +void show_gameboard(int tries_count, int word_length, char **tries, int **validations) { + for (int i = 0; i < tries_count; i++) { + // Draw current try letters + for (size_t j = 0; j < strlen(tries[i]); j++) { + if (validations[i][j] == 1) + attron(COLOR_PAIR(ORANGE_PAIR)); + if (validations[i][j] == 2) + attron(COLOR_PAIR(GREEN_PAIR)); + + mvaddch(GAMEBOARD_Y_OFFSET + i, TERM_MID_COLS - word_length + (j * 2), tries[i][j]); + attroff(COLOR_PAIR(GREEN_PAIR)); + attroff(COLOR_PAIR(ORANGE_PAIR)); + } + + // Draw remaning try letters placeholder + for (size_t j = 0; j < word_length - strlen(tries[i]); j++) + mvaddstr(GAMEBOARD_Y_OFFSET + i, TERM_MID_COLS + word_length - 2 - (j * 2), "_ "); + } + refresh(); } \ No newline at end of file diff --git a/ui/ui.h b/ui/ui.h index 13eb9eb19dcf7921ace7a43b1a08bd684fa6da20..159238442c4f502012845c269c53ef36f6ee64e2 100644 --- a/ui/ui.h +++ b/ui/ui.h @@ -15,6 +15,7 @@ #define GREEN_PAIR 1 #define ORANGE_PAIR 2 #define WHITE_PAIR 3 +#define RED_PAIR 4 // Menu choices #define MENU_SOLO 0 @@ -22,6 +23,23 @@ #define MENU_COMPUTER 2 #define MENU_QUIT 3 +// Control key handler +#define ctrl(x) ((x)&0x1f) + +// Game controls +#define KEYBIND_HELP (ctrl('h')) +#define KEYBIND_TAG (ctrl('t')) // Tool Assisted Game +#define KEYBIND_QUIT (ctrl('w')) +#define KEYBIND_GUESS '\n' + +/** + * @brief Print a text at the center of the given line + * + * @param foo + * @param line + */ +void printw_center(char *foo, int line); + /** * @brief Initialize the terminal * @@ -43,4 +61,20 @@ void cleanup_term(); */ int show_menu(); +/** + * @brief Toggle the help window + * + */ +void toggle_help(); + +/** + * @brief Show the gameboard + * + * @param tries_count + * @param word_length + * @param tries + * @param validations + */ +void show_gameboard(int tries_count, int word_length, char **tries, int **validations); + #endif \ No newline at end of file diff --git a/wordle/wordle.c b/wordle/wordle.c index e477c02de39c5184a74eaa5b236be8480b3a0cb6..4a03714e31a8d75f7a4cb6a5b08f0366730b0926 100644 --- a/wordle/wordle.c +++ b/wordle/wordle.c @@ -4,8 +4,139 @@ // PRIVATE //========================== int gamemode = -1; +bool _game_finished = false; +char **tries; +char *chosen_word; +int **validations; +int current_try_id = 0; +int current_try_letter_id = 0; + +/** + * @brief Terminate a game + */ +void terminate_game() { _game_finished = true; } + +/** + * @brief Add a letter to the current try + * + * @param letter + */ +void add_letter_to_try(char letter) { + tries[current_try_id][current_try_letter_id] = letter; + current_try_letter_id += 1; +} + +/** + * @brief Check if the given letter is a lowercase letter only + * + * @param key + * @return true + * @return false + */ +bool validate_letter(int key) { return (key >= 97 && key <= 122); } + +/** + * @brief Validate the last guess + * + */ +int *validate_last_guess(char answer[WORD_LENGHT], char try[WORD_LENGHT]) { + int a[WORD_LENGHT] = {0, 2, 0, 1, 0}; + int *validation = calloc(WORD_LENGHT, sizeof(int)); + + for (int i = 0; i < WORD_LENGHT; i++) + validation[i] = a[i]; + + return validation; +} + +/** + * @brief Handle controls + * + * @param key + */ +void handle_controls(int key) { + switch (key) { + case KEYBIND_HELP: + toggle_help(); + break; + + case KEYBIND_QUIT: + terminate_game(); + break; + + case KEYBIND_GUESS: + if (strlen(tries[current_try_id]) < WORD_LENGHT) + break; + + int *tmp = validate_last_guess(chosen_word, tries[current_try_id]); + for (int i = 0; i < WORD_LENGHT; i++) + validations[current_try_id][i] = tmp[i]; + free(tmp); + + current_try_id += 1; + current_try_letter_id = 0; + break; + } +} //========================== // PUBLIC //========================== -void set_gamemode(int menu_gamemode) { gamemode = menu_gamemode; } \ No newline at end of file +void set_gamemode(int menu_gamemode) { gamemode = menu_gamemode; } + +void launch_game() { + chosen_word = "SALUT"; + + tries = calloc(TRIES_COUNT, sizeof(char *)); + validations = malloc(sizeof(int *) * TRIES_COUNT); + + for (int i = 0; i < TRIES_COUNT; i++) { + validations[i] = calloc(WORD_LENGHT + 1, sizeof(int)); + tries[i] = calloc(WORD_LENGHT + 1, sizeof(char)); // +1 because of \0 + } + + while (!game_finished()) { + // Guard clause + if (current_try_id > TRIES_COUNT - 1) { + terminate_game(); + continue; + } + + // Render + switch (gamemode) { + case GAMEMODE_SOLO: + printw_center("Solo", 5); + break; + + case GAMEMODE_1_V_1: + printw_center("Player 1", 5); + break; + + case GAMEMODE_COMPUTER: + printw_center("Computer assisted", 5); + break; + } + + show_gameboard(TRIES_COUNT, WORD_LENGHT, tries, validations); + + // Next letter + int key = getch(); + + if (validate_letter(key)) { + if (current_try_letter_id < WORD_LENGHT) + add_letter_to_try((char)key - 32); // Offset letter to only put upercase + } + + handle_controls(key); + refresh(); + } + + for (int i = 0; i < TRIES_COUNT; i++) { + free(tries[i]); + free(validations[i]); + } + free(tries); + free(validations); +} + +bool game_finished() { return _game_finished; } \ No newline at end of file diff --git a/wordle/wordle.h b/wordle/wordle.h index b0ac85f2ee9279b65d2a44737da7bc95058e4721..01882a2c9052ed2de6dc84fe8e298b766b00e629 100644 --- a/wordle/wordle.h +++ b/wordle/wordle.h @@ -1,11 +1,18 @@ #ifndef WORDLE_H_ #define WORDLE_H_ +#include "../ui/ui.h" +#include <stdbool.h> + // Gamemodes #define GAMEMODE_SOLO 0 #define GAMEMODE_1_V_1 1 #define GAMEMODE_COMPUTER 2 +// Game setup +#define TRIES_COUNT 6 +#define WORD_LENGHT 5 + /** * @brief Set the gamemode * @@ -13,4 +20,18 @@ */ void set_gamemode(int menu_gamemode); +/** + * @brief Launch the game + * + */ +void launch_game(); + +/** + * @brief Is the game finished + * + * @return true + * @return false + */ +bool game_finished(); + #endif \ No newline at end of file