tictactoe

Tic Tac Toe on the terminal using ANSI Escape Codes
Index Commits Files Refs README LICENSE
tictactoe.cpp (7775B)
   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 }