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