/***************************************************************************** * ___ __ __ ___ _ __ * * / _ \\ \/ // _ \ '_ \ * * | (_) |> <| __/ | | | * * \___//_/\_\\___|_| |_| * * * * The card game * * * * Copyright (C) 2011, Reiner Herrmann * * Mario Kilies * * * * 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 3 of the License, 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. If not, see . * * * *****************************************************************************/ #include "ui.h" #include #include "card_stack.h" // Definition of ncurses color pair identifiers #define CP_WHITE_ON_BLACK 1 #define CP_YELLOW_ON_BLACK 2 #define CP_BLUE_ON_BLACK 3 #define CP_MAGENTA_ON_BLACK 4 #define CP_RED_ON_BLACK 5 #define KEY_ESCAPE 27 #define KEY_RETURN 10 // Is needed, because the KEY_ENTER defined by ncurses does not work #define KEY_VI_LEFT 'h' #define KEY_VI_RIGHT 'l' #define KEY_VI_UP 'k' #define KEY_VI_DOWN 'j' static bool colors = false; static WINDOW *w_table_cards; static WINDOW *w_stack_points; static WINDOW *w_current_state; static WINDOW *w_messages; static WINDOW *w_hand_cards; static WINDOW *w_final_scores; /** * Draws a card in a given window at a specified position. The card can also be highlighted (this is used for visualization of a selected card). * @param[in] w The window to draw the card in * @param[in] row The row at which the card will be drawn * @param[in] col The column at which the card will be drawn * @param[in] highlight If true, the card will be highlighted * @param[in] c The card, that will be drawn */ static void draw_card(WINDOW *w, const uint8_t row, const uint8_t col, const bool highlight, const card c) { assert(w != NULL); unsigned color_pair = 0; // If card is 0, draw a placeholder (empty space) instead of a card. This is used to remove a card that does not longer exist but has visible remains on the screen. if (0 == c) { mvwhline(w, row, col, ' ', 7); mvwhline(w, row+1, col, ' ', 7); mvwhline(w, row+2, col, ' ', 7); mvwhline(w, row+3, col, ' ', 7); mvwhline(w, row+4, col, ' ', 7); return; } if (highlight) wattron(w, A_BOLD); if (colors) { switch (card_get_points(c)) { case 7: color_pair = CP_MAGENTA_ON_BLACK; break; case 5: color_pair = CP_RED_ON_BLACK; break; case 3: color_pair = CP_YELLOW_ON_BLACK; break; case 2: color_pair = CP_BLUE_ON_BLACK; break; default: color_pair = CP_WHITE_ON_BLACK; } wattron(w, COLOR_PAIR(color_pair)); } mvwaddch(w, row, col, ACS_ULCORNER); // Upper left corner mvwhline(w, row, col+1, ACS_HLINE, 5); // Upper horizontal line mvwaddch(w, row, col+6, ACS_URCORNER); // Upper right corner mvwvline(w, row+1, col, ACS_VLINE, 3); // Left vertical line mvwvline(w, row+1, col+6, ACS_VLINE, 3); // Right vertical line mvwaddch(w, row+4, col, ACS_LLCORNER); // Lower left corner mvwhline(w, row+4, col+1, ACS_HLINE, 5); // Lower horizontal line mvwaddch(w, row+4, col+6, ACS_LRCORNER); // Lower right corner mvwprintw(w, row+1, col+1, " %3d ", c); // Card number, 3 digits wide, filled with spaces, if number has fewer digits // Clear the rest of the card first mvwprintw(w, row+2, col+1, " "); mvwprintw(w, row+3, col+1, " "); switch (card_get_points(c)) { case 7: // "** **" // " *** " mvwhline(w, row+2, col+1, ACS_DIAMOND, 2); mvwhline(w, row+2, col+4, ACS_DIAMOND, 2); mvwhline(w, row+3, col+2, ACS_DIAMOND, 3); break; case 5: // "* * *" // " * * " mvwaddch(w, row+2, col+1, ACS_DIAMOND); mvwaddch(w, row+2, col+3, ACS_DIAMOND); mvwaddch(w, row+2, col+5, ACS_DIAMOND); mvwaddch(w, row+3, col+2, ACS_DIAMOND); mvwaddch(w, row+3, col+4, ACS_DIAMOND); break; case 3: // " * * " // " * " mvwaddch(w, row+2, col+2, ACS_DIAMOND); mvwaddch(w, row+2, col+4, ACS_DIAMOND); mvwaddch(w, row+3, col+3, ACS_DIAMOND); break; case 2: // " * * " // " " mvwaddch(w, row+2, col+2, ACS_DIAMOND); mvwaddch(w, row+2, col+4, ACS_DIAMOND); break; default: // " * " // " " mvwaddch(w, row+2, col+3, ACS_DIAMOND); } if (colors) { wattroff(w, COLOR_PAIR(color_pair)); } if (highlight) wattroff(w, A_BOLD); } /** * Draws a card stack in a given window at a specified position. The card stack can also be highlighted (this is used for visualization of a selected card stack). * @param[in] w The window to draw the card stack in * @param[in] row The row at which the card stack will be drawn * @param[in] col The column at which the card stack will be drawn * @param[in] highlight If true, the card stack will be highlighted * @param[in] cs The card stack, that will be drawn */ static void draw_card_stack(WINDOW *w, const uint8_t row, const uint8_t col, const bool highlight, const card_stack_t *cs) { assert(w != NULL); assert(cs != NULL); // Clear old card stack first mvwhline(w, row, col, ' ', 15); mvwhline(w, row+1, col, ' ', 15); mvwhline(w, row+2, col, ' ', 15); mvwhline(w, row+3, col, ' ', 15); mvwhline(w, row+4, col, ' ', 15); for (uint8_t i = 0; i < MAX_CARD_STACK_SIZE; i++) { if (cs->cards[i] != 0) { draw_card(w, row, i*2, highlight, cs->cards[i]); } } } /** * Displays the table cards window. Draws table stacks and can highlight a stack. * @param[in] ts The table stacks to display * @param[in] highlight If true, a stack will be highlighted * @param[in] highlighted_stack The stack to highlight. Only used, if highlight is true */ void ui_display_wnd_table_cards(const table_stacks_t *ts, const bool highlight, const uint8_t highlighted_stack) { assert(ts != NULL); wattron(w_table_cards, A_BOLD); mvwprintw(w_table_cards, 0, 2, "Table Cards:"); wattroff(w_table_cards, A_BOLD); for (uint8_t i = 0; i < NUM_TABLE_STACKS; i++) { if (highlight && i == highlighted_stack) { draw_card_stack(w_table_cards, 1 + i*5, 0, true, &ts->stacks[i]); continue; } draw_card_stack(w_table_cards, 1 + i*5, 0, false, &ts->stacks[i]); } wnoutrefresh(w_table_cards); } /** * Displays the stack points window. One stack points entry can be highlighted (this is used for visualization of a selected stack). * @param[in] ts The table stacks used for calculation of stack points * @param[in] highlight If true, stack points will be highlighted * @param[in] highlighted_points The stack points to highlight. Only used, if highlight is true */ void ui_display_wnd_stack_points(const table_stacks_t *ts, const bool highlight, const uint8_t highlighted_points) { assert(ts != NULL); wattron(w_stack_points, A_BOLD); mvwprintw(w_stack_points, 0, 0, "Pts:"); wattroff(w_stack_points, A_BOLD); for (uint8_t i = 0; i < NUM_TABLE_STACKS; i++) { if (highlight && i == highlighted_points) { wattron(w_stack_points, A_BOLD); mvwprintw(w_stack_points, 3 + i*5, 1, "%2d", card_stack_get_points(&ts->stacks[i])); wattroff(w_stack_points, A_BOLD); continue; } mvwprintw(w_stack_points, 3 + i*5, 1, "%2d", card_stack_get_points(&ts->stacks[i])); } wnoutrefresh(w_stack_points); } /** * Displays the current state window, showing all player names and their open cards and the client player's score. A player from the player list can be highlighted (used to display the player whose turn it is). * @param[in] pl Player list. List has to be sorted by open card, in ascending order * @param[in] num_players The number of players to show a player name and open card for * @param[in] highlight If true, a player in the player table will be highlighted * @param[in] highlighted_player The player to highlight * @param[in] score The players score */ void ui_display_wnd_current_state(const player_list_t *pl, const uint8_t num_players, const bool highlight, const player_id_t highlighted_player, const uint32_t score) { assert(pl != NULL); uint8_t pos = 0; werase(w_current_state); wattron(w_current_state, A_BOLD); mvwprintw(w_current_state, 0, 0, "Current state:"); mvwprintw(w_current_state, 0, 22, "Your Score: %3d", score); wattroff(w_current_state, A_BOLD); mvwprintw(w_current_state, 1, 1, "Player Card"); mvwprintw(w_current_state, 1, 22, "Player Card"); mvwvline(w_current_state, 1, 19, ACS_VLINE, 6); // Vertical line for (uint8_t i = 0; i < num_players; i++) { if (pl->players[i].player_id == 0) // invalid player continue; if (highlight && i == highlighted_player) wattron(w_current_state, COLOR_PAIR(CP_YELLOW_ON_BLACK)); if (pos < 5) { mvwprintw(w_current_state, 2+pos, 1, "%-s", pl->players[i].player_name); if (0 == pl->players[i].open_card) mvwprintw(w_current_state, 2+pos, 15, "-"); else mvwprintw(w_current_state, 2+pos, 13, "%3d", pl->players[i].open_card); } else { mvwprintw(w_current_state, 2+(pos-5), 22, "%-s", pl->players[i].player_name); if (0 == pl->players[i].open_card) mvwprintw(w_current_state, 2+(pos-5), 36, "-"); else mvwprintw(w_current_state, 2+(pos-5), 34, "%3d", pl->players[i].open_card); } if (highlight && i == highlighted_player) wattroff(w_current_state, COLOR_PAIR(CP_YELLOW_ON_BLACK)); pos++; } wnoutrefresh(w_current_state); } /** * Displays the message window. The displayed message can be highlighted. * @param[in] message The message to display * @param[in] highlight If true, the message will be highlighted */ void ui_display_wnd_messages(const char *message, const bool highlight) { werase(w_messages); if (highlight) wattron(w_messages, COLOR_PAIR(CP_YELLOW_ON_BLACK)); waddstr(w_messages, message); if (highlight) wattroff(w_messages, COLOR_PAIR(CP_YELLOW_ON_BLACK)); wnoutrefresh(w_messages); } /** * Displays the hand cards window. A single card from the hand can be highlighted (used for visualization of a selected card). * @param[in] h The hand that will be displayed. h must be sorted in ascending order * @param[in] highlight If true, a card will be highlighted * @param[in] highlighted_card The card to highlight. Only used, if highlight is true */ void ui_display_wnd_hand_cards(const hand_t *h, const bool highlight, const uint8_t highlighted_card) { assert(h != NULL); uint8_t num_zero_cards = 0; wattron(w_hand_cards, A_BOLD); mvwprintw(w_hand_cards, 0, 0, "Hand Cards:"); wattroff(w_hand_cards, A_BOLD); // Clear old cards from screen first by drawing emtpy card placeholders over them for (uint8_t i = 0; i < MAX_HAND_CARDS; i++) { if (i - num_zero_cards < 5) // Start with the first row of cards draw_card(w_hand_cards, 1, (i - num_zero_cards) * 8, false, 0); else // And then draw the second row draw_card(w_hand_cards, 6, (i - num_zero_cards - 5) * 8, false, 0); } for (uint8_t i = 0; i < MAX_HAND_CARDS; i++) { // Cound all 0 cards and don't draw them if (0 == h->cards[i]) { num_zero_cards++; continue; } if (highlight && i == highlighted_card) { if (i - num_zero_cards < 5) // Start with the first row of cards draw_card(w_hand_cards, 1, (i - num_zero_cards) * 8, true, h->cards[i]); else // And then draw the second row draw_card(w_hand_cards, 6, (i - num_zero_cards - 5) * 8, true, h->cards[i]); continue; } if (i - num_zero_cards < 5) // Start with the first row of cards draw_card(w_hand_cards, 1, (i - num_zero_cards) * 8, false, h->cards[i]); else // And then draw the second row draw_card(w_hand_cards, 6, (i - num_zero_cards - 5) * 8, false, h->cards[i]); } wnoutrefresh(w_hand_cards); } /** * Displays the final scores window and highlights a given player's name and score. The players that finish the game on the first place will be highlighted in a distinct way. * @param[in] pl Player list. List has to be sorted by score, in ascending order * @param[in] num_players The number of players to show a player name and score for * @param[in] highlighted_player The player to highlight */ void ui_display_wnd_final_scores(const player_list_t *pl, const uint8_t num_players, const player_id_t highlighted_player) { uint8_t row = 4; uint8_t place = 1; clear(); refresh(); wattron(w_final_scores, A_BOLD); mvwprintw(w_final_scores, 1, 7, "- Final standings -"); mvwprintw(w_final_scores, 3, 2, "Pos: Player: Score:"); wattroff(w_final_scores, A_BOLD); for (int i = 0; i < num_players; i++) { const player_list_entry_t *ple = &pl->players[i]; if (0 == ple->player_id) // Skip empty player list entries continue; // If the previous player has the same score, we have the same place as he does if (i > 0 && pl->players[i-1].score == ple->score) place = place - 1; // Reward the glorious winners with an outstanding output of their scores if (1 == place) wattron(w_final_scores, A_BOLD); if (ple->player_id == highlighted_player || 1 == place) // Highlight the players score { wattron(w_final_scores, COLOR_PAIR(CP_YELLOW_ON_BLACK)); mvwprintw(w_final_scores, row, 2, "%2d.", place); mvwprintw(w_final_scores, row, 7, "%-s", ple->player_name); mvwprintw(w_final_scores, row++, 25, "%3d", ple->score); wattroff(w_final_scores, COLOR_PAIR(CP_YELLOW_ON_BLACK)); } else { mvwprintw(w_final_scores, row, 2, "%2d.", place); mvwprintw(w_final_scores, row, 7, "%-s", ple->player_name); mvwprintw(w_final_scores, row++, 25, "%3d", ple->score); } if (1 == place) wattroff(w_final_scores, A_BOLD); place++; } wrefresh(w_final_scores); int key; do { key = wgetch(w_final_scores); } while (KEY_RETURN != key && KEY_ESCAPE != key); } /** * Starts the card chooser. The player has to pick a card from a given hand. The hand and a message that the player has to pick a card will be displayed. The function blocks until the player has made his choice. The index of the picked card within the hand will be returned. * @param[in] h The hand to pick a card from * @return The index of the chosen card */ uint8_t ui_choose_card(hand_t *h) { assert(h != NULL); int key; uint8_t chosen_card_idx = 0; int8_t i; // Has to be signed for modulo calculation hand_sort(h); // Select card with lowest index as default if (0 == h->cards[chosen_card_idx]) { i = (chosen_card_idx + 1) % MAX_HAND_CARDS; while(0 == h->cards[i]) i = (i + 1) % MAX_HAND_CARDS; chosen_card_idx = i; } ui_display_wnd_messages("Please choose the card you want to play", true); ui_display_wnd_hand_cards(h, true, chosen_card_idx); ui_update(); while (KEY_RETURN != (key = wgetch(w_hand_cards))) { switch (key) { case KEY_VI_LEFT: // Fall through case KEY_LEFT: // Add MAX_HAND_CARDS to prevent calculating the modulus of negative values i = (chosen_card_idx - 1 + MAX_HAND_CARDS) % MAX_HAND_CARDS; while(0 == h->cards[i]) i = (i - 1 + MAX_HAND_CARDS) % MAX_HAND_CARDS; chosen_card_idx = i; break; case KEY_VI_RIGHT: // Fall through case KEY_RIGHT: i = (chosen_card_idx + 1) % MAX_HAND_CARDS; while(0 == h->cards[i]) i = (i + 1) % MAX_HAND_CARDS; chosen_card_idx = i; break; default: // No default break; } ui_display_wnd_hand_cards(h, true, chosen_card_idx); ui_update(); } return chosen_card_idx; } /** * Starts the card stack chooser. The player has to pick a card stack from given table stacks. The table stacks, stack points and a message that the player has to pick a card stack will be displayed. The function blocks until the player has made his choice. The index of the picked card stack within the table stacks will be returned. * @param[in] ts The table stacks to pick a card stack from * @return The index of the chosen card stack */ uint8_t ui_choose_stack(const table_stacks_t *ts) { assert(ts != NULL); int key; uint8_t chosen_stack_idx = 0; ui_display_wnd_messages("Please choose a stack", true); ui_display_wnd_table_cards(ts, true, chosen_stack_idx); ui_display_wnd_stack_points(ts, true, chosen_stack_idx); ui_update(); while (KEY_RETURN != (key = wgetch(w_table_cards))) { switch (key) { case KEY_VI_UP: // Fall through case KEY_LEFT: // Fall through case KEY_UP: if (0 == chosen_stack_idx) chosen_stack_idx = NUM_TABLE_STACKS - 1; else chosen_stack_idx--; break; case KEY_VI_DOWN: // Fall through case KEY_RIGHT: // Fall through case KEY_DOWN: if (NUM_TABLE_STACKS - 1 == chosen_stack_idx) chosen_stack_idx = 0; else chosen_stack_idx++; break; default: // No default break; } ui_display_wnd_table_cards(ts, true, chosen_stack_idx); ui_display_wnd_stack_points(ts, true, chosen_stack_idx); ui_update(); } return chosen_stack_idx; } /** * Updates the screen. */ void ui_update(void) { doupdate(); } /** * Initializes ncurses. */ void ui_init(void) { initscr(); // Start curses mode if (TRUE == has_colors()) { colors = true; start_color(); init_pair(CP_WHITE_ON_BLACK, COLOR_WHITE, COLOR_BLACK); init_pair(CP_YELLOW_ON_BLACK, COLOR_YELLOW, COLOR_BLACK); init_pair(CP_BLUE_ON_BLACK, COLOR_BLUE, COLOR_BLACK); init_pair(CP_MAGENTA_ON_BLACK, COLOR_MAGENTA, COLOR_BLACK); init_pair(CP_RED_ON_BLACK, COLOR_RED, COLOR_BLACK); bkgd(COLOR_PAIR(0)); } cbreak(); // Line buffering disabled, but interrupt (CTRL-C) possible noecho(); // Don't echo() while we do getch curs_set(0); // Make the cursor invisible clear(); refresh(); // Window positions and sizes w_table_cards = newwin(21, 15, 0, 0); w_stack_points = newwin(19, 4, 0, 17); w_current_state = newwin(7, 38, 0, 24); w_messages = newwin(2, 39, 7, 24); w_hand_cards = newwin(11, 39, 10, 24); w_final_scores = newwin(15, 32, 2, 15); box(w_final_scores, 0, 0); // Set input settings for windows keypad(w_table_cards, true); // We get F1, F2 etc.. keypad(w_hand_cards, true); // We get F1, F2 etc.. // Draw user interface mvvline(0, 22, ACS_VLINE, 21); // Vertical line refresh(); } /** * Finalizes ncurses. */ void ui_fini(void) { delwin(w_table_cards); delwin(w_stack_points); delwin(w_current_state); delwin(w_messages); delwin(w_hand_cards); delwin(w_final_scores); endwin(); // End curses mode }