1 #include <iostream> 2 #include <cstdlib> 3 #include <csignal> 4 #include <termios.h> 5 #include <string> 6 #include <sstream> 7 8 #define TUI_TOTAL_HEIGHT 7 9 10 typedef enum { 11 PLAYER_1 = 1, 12 PLAYER_2 13 } player_t; 14 15 typedef enum { 16 PLAYING, 17 WIN, 18 DRAFT, 19 RESET, 20 HALT, 21 EXIT 22 } game_status_t; 23 24 static struct termios default_attributes; 25 static player_t current_player; 26 static game_status_t game_status; 27 static int cursor_col, cursor_row, cursor_row_pre; 28 std::string player_status, key_string; 29 char game_board[3][3]; 30 31 void tui_set_input_mode(void) { 32 struct termios tattr; 33 34 if (!isatty(STDIN_FILENO)) { 35 std::cerr << "Error: not a terminal" << std::endl; 36 exit (EXIT_FAILURE); 37 } 38 39 tcgetattr(STDIN_FILENO, &default_attributes); 40 tcgetattr(STDIN_FILENO, &tattr); 41 tattr.c_lflag &= ~(ICANON|ECHO); 42 tattr.c_cc[VMIN] = 1; 43 tattr.c_cc[VTIME] = 0; 44 tcsetattr(STDIN_FILENO, TCSAFLUSH, &tattr); 45 } 46 47 void tui_reset_input_mode(void) { 48 tcsetattr(STDIN_FILENO, TCSANOW, &default_attributes); 49 } 50 51 void game_sig_handler(int signo) { 52 signal(signo, SIG_IGN); 53 if ((signo == SIGINT) || (signo == SIGQUIT)) { 54 game_status = EXIT; 55 exit(0); 56 } 57 } 58 59 void game_clear_board(void) { 60 std::cout << "\033[" << (cursor_row_pre*2)+1 << "F\033[0K"; 61 cursor_row_pre = cursor_row; 62 } 63 64 void game_player_status_msg(void) { 65 key_string.assign("\033[0J"); 66 67 switch(game_status) { 68 case PLAYING: 69 player_status.assign("Player "); 70 player_status += (std::ostringstream() << current_player).str(); 71 player_status += "\033[0J"; 72 break; 73 case WIN: 74 player_status.assign("Player "); 75 player_status += (std::ostringstream() << (current_player^3)).str(); 76 player_status += " Wins"; 77 key_string.assign("Press `r` to restart\033[0J"); 78 game_status = HALT; 79 break; 80 case DRAFT: 81 player_status.assign("Draft\033[0J"); 82 game_status = HALT; 83 case HALT: 84 key_string.assign("Press `r` to restart\033[0J"); 85 break; 86 default: 87 break; 88 } 89 } 90 91 void game_print_board(void) { 92 game_player_status_msg(); 93 std::cout << "┌───┬───┬───┐\n│ " 94 << game_board[0][0] << " │ " 95 << game_board[0][1] << " │ " 96 << game_board[0][2] << " │ " << player_status << "\n"; 97 98 std::cout << "├───┼───┼───┤ " << key_string << "\n│ " 99 << game_board[1][0] << " │ " 100 << game_board[1][1] << " │ " 101 << game_board[1][2] << " │\n"; 102 103 std::cout << "├───┼───┼───┤\n│ " 104 << game_board[2][0] << " │ " 105 << game_board[2][1] << " │ " 106 << game_board[2][2] << " │\n" 107 << "└───┴───┴───┘\n"; 108 109 std::cout << "\033[" << TUI_TOTAL_HEIGHT-1 << "A"; 110 } 111 112 void game_board_set_cursor(void) { 113 if(cursor_row) 114 std::cout << "\033[" << cursor_row*2 << "B"; 115 116 std::cout << "\033[0G\033[" << (cursor_col*4)+2 << "C"; 117 } 118 119 void game_move_cursor_right(void) { 120 cursor_col = (cursor_col == 2) ? 0 : cursor_col + 1; 121 } 122 123 void game_move_cursor_left(void) { 124 cursor_col = (cursor_col > 0) ? cursor_col-1 : 2; 125 } 126 127 void game_move_cursor_up(void) { 128 cursor_row_pre = cursor_row; 129 cursor_row = (cursor_row > 0) ? cursor_row-1 : 2; 130 } 131 132 void game_move_cursor_down(void) { 133 cursor_row_pre = cursor_row; 134 cursor_row = ((cursor_row + 1) > 2) ? 0 : cursor_row+1; 135 } 136 137 void game_set_at_cursor_pos(void) { 138 if(game_board[cursor_row][cursor_col] == ' ') { 139 game_board[cursor_row][cursor_col] = ((current_player == PLAYER_1) ? 'X' : 'O'); 140 current_player = ((current_player == PLAYER_1) ? PLAYER_2 : PLAYER_1); 141 } 142 } 143 144 void game_redraw_board(void) { 145 game_clear_board(); 146 game_print_board(); 147 game_board_set_cursor(); 148 } 149 150 void game_tui_cleanup(void) { 151 tui_reset_input_mode(); 152 std::cout << "\033[" << TUI_TOTAL_HEIGHT-(cursor_row*2)-1 << "E"; 153 std::cout << "\033[0 q"; // block cursor shape 154 } 155 156 void game_tui_setup(void) { 157 tui_set_input_mode(); 158 signal(SIGINT, game_sig_handler); 159 signal(SIGQUIT, game_sig_handler); 160 signal(SIGTSTP, SIG_IGN); 161 atexit(game_tui_cleanup); 162 std::cout << "\033[4 q"; // underline cursor shape 163 } 164 165 void game_initialize_board(void) { 166 game_status = PLAYING; 167 cursor_col = cursor_row = 1; 168 current_player = PLAYER_1; 169 for(size_t i = 0; i < 3; ++i) { 170 for(size_t j = 0; j < 3; ++j) { 171 game_board[i][j] = ' '; 172 } 173 } 174 } 175 176 void game_check_status(void) { 177 char first; 178 size_t i, j, h, v, d, rd, white_space; 179 180 if(game_status != PLAYING) 181 return; 182 183 for(i = white_space = 0; i < 3; ++i) { 184 for(j = h = v = d = rd = 0; j < 3; ++j) { 185 if((first = game_board[i][0]) != ' ') { // horizontal (-) 186 if(game_board[i][j] == first) { 187 if((h++) == 2) { 188 game_status = WIN; 189 return; 190 } 191 } 192 } 193 if((first = game_board[0][i]) != ' ') { // vertical (|) 194 if(game_board[j][i] == first) { 195 if((v++) == 2) { 196 game_status = WIN; 197 return; 198 } 199 } 200 } 201 if((first = game_board[0][0]) != ' ') { // diagonal (/) 202 if(game_board[j][j] == first) { 203 if((d++) == 2) { 204 game_status = WIN; 205 return; 206 } 207 } 208 } 209 if((first = game_board[0][2]) != ' ') { // reverse diagonal (\) 210 if(game_board[2-j][j] == first) { 211 if((rd++) == 2) { 212 game_status = WIN; 213 return; 214 } 215 } 216 } 217 if(game_board[i][j] == ' ') { 218 white_space++; 219 } 220 } 221 } 222 if((i == 3) && (j == 3) && (white_space == 0)) 223 game_status = DRAFT; 224 } 225 226 void game_key_handler(void) { 227 char cmd; 228 229 std::cin.get(cmd); 230 switch(cmd) { 231 case 'w': 232 case 'k': 233 game_move_cursor_up(); 234 break; 235 case 's': 236 case 'j': 237 game_move_cursor_down(); 238 break; 239 case 10: // Enter 240 case ' ': 241 if(game_status == PLAYING) 242 game_set_at_cursor_pos(); 243 break; 244 case 'l': 245 case 'd': 246 game_move_cursor_right(); 247 break; 248 case 'h': 249 case 'a': 250 game_move_cursor_left(); 251 break; 252 case 'r': 253 if(game_status == HALT) 254 game_status = RESET; 255 break; 256 case 'q': 257 game_status = EXIT; 258 break; 259 case 27: { // Escape codes (i.e. arrow keys) 260 std::cin.get(cmd); // skip '[' 261 std::cin.get(cmd); 262 switch (cmd) { 263 case 'A': // up 264 game_move_cursor_up(); 265 break; 266 case 'B': // down 267 game_move_cursor_down(); 268 break; 269 case 'C': // right 270 game_move_cursor_right(); 271 break; 272 case 'D': // left 273 game_move_cursor_left(); 274 break; 275 } 276 break; 277 } 278 } 279 } 280 281 int main(void) { 282 cursor_row = cursor_col = cursor_row_pre = 0; 283 284 game_tui_setup(); 285 game_initialize_board(); 286 game_print_board(); 287 std::cout << "\033[" << ((cursor_col*4)+2) << "C"; 288 game_redraw_board(); 289 290 while(game_status != EXIT) { 291 game_key_handler(); 292 game_check_status(); 293 if(game_status == RESET) 294 game_initialize_board(); 295 game_redraw_board(); 296 } 297 298 return 0; 299 }
