ttcli.c (9916B)
1 #include "tt.h" 2 #include <arpa/inet.h> 3 #include <ctype.h> 4 #include <ncurses.h> 5 #include <netinet/in.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <sys/socket.h> 10 #include <time.h> 11 #include <unistd.h> 12 13 #define AHEAD_SHOW 4 14 #define AHEAD_HIDE 2 15 #define WAIT_TIME 1000000000 16 #define MAX(a, b) ((a) < (b) ? (b) : (a)) 17 #define MIN(a, b) ((a) < (b) ? (a) : (b)) 18 19 void backspace( 20 int *const pos, 21 int *error, 22 struct timespec *last_back, 23 const char *text, 24 int (*func)(int), 25 int flip) 26 { 27 if (*error) { 28 (*pos)++; 29 } 30 while (*pos > 0 && (flip ^ !func(text[*pos - 1]))) { 31 (*pos)--; 32 } 33 while (*pos > 0 && (flip ^ (!!func(text[*pos - 1])))) { 34 (*pos)--; 35 } 36 *error = 0; 37 clock_gettime(CLOCK_REALTIME, last_back); 38 } 39 40 void print_text( 41 WINDOW *win, 42 int pos, 43 int error, 44 int count_errors, 45 int count_slows, 46 struct timespec last_back, 47 struct timespec last_input, 48 struct timespec first_input, 49 struct state *state_players, 50 char num_players, 51 char const *text, 52 unsigned short *pos_to_word, 53 short length, 54 short *spaces, 55 short num_spaces) 56 { 57 werase(win); 58 float elapsed = 59 (last_input.tv_sec - first_input.tv_sec) + 60 (last_input.tv_nsec - first_input.tv_nsec) / 1000000000.0; 61 62 mvwprintw(win, 9, 24, " WPM: %.0f", (pos / 5.0) / (elapsed / 60.0)); 63 mvwprintw(win, 10, 24, "Errors: %d", count_errors); 64 mvwprintw(win, 11, 24, " Slows: %d", count_slows); 65 66 struct timespec current; 67 clock_gettime(CLOCK_REALTIME, ¤t); 68 69 long diff_back = (current.tv_sec - last_back.tv_sec) * 1000000000 + 70 current.tv_nsec - last_back.tv_nsec; 71 long diff_input = (current.tv_sec - last_input.tv_sec) * 1000000000 + 72 current.tv_nsec - last_input.tv_nsec; 73 74 unsigned short current_word = pos_to_word[pos]; 75 76 int space_start = MIN(current_word + AHEAD_HIDE, num_spaces - 1); 77 int space_end = MIN(current_word + AHEAD_SHOW, num_spaces - 1); 78 79 wmove(win, 0, 0); 80 for (int i = 0; i < length; i++) { 81 if (pos >= length) { 82 wprintw(win, "%c", text[i]); 83 } else if (i < spaces[current_word]) { 84 wattron(win, A_DIM); 85 wprintw(win, "%c", text[i]); 86 wattroff(win, A_DIM); 87 } else if (i < spaces[space_start]) { 88 if (error) { 89 wattron(win, COLOR_PAIR(2)); 90 wprintw(win, "%c", text[i]); 91 wattroff(win, COLOR_PAIR(2)); 92 } else if (diff_back < WAIT_TIME) { 93 wattron(win, A_BOLD); 94 wattron(win, COLOR_PAIR(1)); 95 wprintw(win, "%c", text[i]); 96 wattroff(win, COLOR_PAIR(1)); 97 wattroff(win, A_BOLD); 98 } else if (diff_input < WAIT_TIME) { 99 if (isalpha(text[i])) { 100 wprintw(win, " "); 101 } else { 102 wprintw(win, "%c", text[i]); 103 } 104 } else { 105 /* wattron(win, A_BOLD); */ 106 wprintw(win, "%c", text[i]); 107 /* wattroff(win, A_BOLD); */ 108 } 109 } else if (i < spaces[space_end]) { 110 wattron(win, A_BOLD); 111 wprintw(win, "%c", text[i]); 112 wattroff(win, A_BOLD); 113 } else { 114 wprintw(win, "%c", text[i]); 115 } 116 } 117 118 for (int i_player = 0; i_player < num_players; i_player++) { 119 int i = state_players[i_player].pos; 120 121 int y = i / 58; 122 int x = i % 58; 123 124 int c = text[i] == ' ' ? '_' : text[i]; 125 126 wattron(win, COLOR_PAIR(3)); 127 if (i < spaces[current_word]) { 128 wattron(win, A_DIM); 129 mvwprintw(win, y, x, "%c", c); 130 wattroff(win, A_DIM); 131 } else if (i < spaces[space_start]) { 132 if (error) { 133 wattron(win, COLOR_PAIR(4)); 134 mvwprintw(win, y, x, "%c", c); 135 wattroff(win, COLOR_PAIR(4)); 136 } else if (diff_back < WAIT_TIME) { 137 wattron(win, A_BOLD); 138 mvwprintw(win, y, x, "%c", c); 139 wattroff(win, A_BOLD); 140 } else if (diff_input < WAIT_TIME) { 141 mvwprintw(win, y, x, "_"); 142 } else { 143 /* wattron(win, A_BOLD); */ 144 mvwprintw(win, y, x, "%c", c); 145 /* wattroff(win, A_BOLD); */ 146 } 147 } else if (i < spaces[space_end]) { 148 wattron(win, A_BOLD); 149 mvwprintw(win, y, x, "%c", c); 150 wattroff(win, A_BOLD); 151 } else { 152 mvwprintw(win, y, x, "%c", c); 153 } 154 wattroff(win, COLOR_PAIR(3)); 155 } 156 157 if (error) { 158 wmove(win, (pos + 1) / 58, (pos + 1) % 58); 159 } else { 160 wmove(win, pos / 58, pos % 58); 161 } 162 163 wrefresh(win); 164 } 165 166 int main(int argc, const char *argv[]) 167 { 168 if (argc != 3) { 169 fprintf(stderr, "Usage: %s <host> <port>\n", argv[0]); 170 return 1; 171 } 172 173 struct sockaddr_in addr = { 174 .sin_family = AF_INET, 175 }; 176 177 int s; 178 if ((s = inet_pton(AF_INET, argv[1], &addr.sin_addr)) != 1) { 179 if (s == 0) { 180 fprintf(stderr, "Host is not in presentation format\n"); 181 } else { 182 perror("host"); 183 } 184 return 1; 185 } 186 187 char *endptr; 188 long port = strtol(argv[2], &endptr, 10); 189 if (!(*argv[2] != '\0' && *endptr == '\0' && port < 65536)) { 190 fprintf(stderr, "ERROR: '%s' not a valid port.\n", argv[2]); 191 return 1; 192 } 193 addr.sin_port = htons(port); 194 195 int sockfd = socket(AF_INET, SOCK_STREAM, 0); 196 int status = connect(sockfd, (struct sockaddr *)&addr, sizeof addr); 197 if (status == -1) { 198 perror("connect"); 199 endwin(); 200 return 1; 201 } 202 203 initscr(); 204 start_color(); 205 use_default_colors(); 206 noecho(); 207 init_pair(1, COLOR_RED, -1); 208 init_pair(2, -1, COLOR_RED); 209 init_pair(3, COLOR_CYAN, -1); 210 init_pair(4, COLOR_CYAN, COLOR_RED); 211 212 WINDOW *boxwin = newwin(15, 60, (LINES - 15) / 2, (COLS - 60) / 2); 213 WINDOW *win = subwin( 214 boxwin, 13, 58, (LINES - 15) / 2 + 1, (COLS - 60) / 2 + 1); 215 216 wclear(boxwin); 217 box(boxwin, 0, 0); 218 refresh(); 219 wrefresh(boxwin); 220 221 struct state state_players[16]; 222 char num_players = 0; 223 224 /* 225 * if recv_step == 0: read_sock - Waiting text from server. 226 * if recv_step == 1: read sock - Pending to receive num_players. 227 * if recv_step == 2: read sock - Pending to receive state_players. 228 */ 229 char recv_step = 0; 230 char outdated = 0; 231 int new_quote = -1; 232 233 char header_type; 234 short header_length; 235 236 int pos; 237 int error; 238 int count_errors = 0; 239 int count_slows = 0; 240 241 char text[4096]; 242 int length; 243 244 short spaces[1024]; 245 short num_spaces; 246 unsigned short pos_to_word[1024]; 247 248 struct timespec last_back = {0, 0}; 249 struct timespec last_input = {0, 0}; 250 struct timespec first_input = {0, 0}; 251 252 srand(time(NULL)); 253 254 while (1) { 255 fd_set readfds, writefds; 256 FD_ZERO(&readfds); 257 FD_ZERO(&writefds); 258 FD_SET(0, &readfds); 259 FD_SET(sockfd, &readfds); 260 if (outdated || new_quote >= 0) { 261 FD_SET(sockfd, &writefds); 262 } 263 264 struct timeval select_timeout = {1, 200000}; 265 select(FD_SETSIZE, &readfds, &writefds, NULL, &select_timeout); 266 267 if (FD_ISSET(0, &readfds)) { 268 if (first_input.tv_sec == 0 && 269 first_input.tv_nsec == 0) { 270 clock_gettime(CLOCK_REALTIME, &first_input); 271 } 272 273 int c = getch(); 274 275 if (c == ERR) { 276 } else if (c == '\n') { 277 new_quote = rand() % 6357; 278 } else if (c == 0x1b) { 279 c = getch(); 280 if (c == '\b' || c == 0x7f) { 281 backspace( 282 &pos, 283 &error, 284 &last_back, 285 text, 286 isalpha, 287 0); 288 outdated = 1; 289 } 290 } else if (c == 0x17) { 291 backspace( 292 &pos, 293 &error, 294 &last_back, 295 text, 296 isspace, 297 1); 298 outdated = 1; 299 } else { 300 struct timespec current; 301 clock_gettime(CLOCK_REALTIME, ¤t); 302 long diff_back = 303 (current.tv_sec - last_back.tv_sec) * 304 1000000000 + 305 current.tv_nsec - last_back.tv_nsec; 306 long diff_input = 307 (current.tv_sec - last_input.tv_sec) * 308 1000000000 + 309 current.tv_nsec - last_input.tv_nsec; 310 311 if (pos < length && !error && 312 diff_back > WAIT_TIME) { 313 if (c == text[pos]) { 314 if (pos != 0 && 315 diff_input > WAIT_TIME) { 316 count_slows++; 317 } 318 last_input = current; 319 pos++; 320 outdated = 1; 321 } else { 322 error = 1; 323 count_errors++; 324 } 325 } 326 } 327 } 328 329 if (FD_ISSET(sockfd, &readfds)) { 330 int buffer_length; 331 void *buffer; 332 switch (recv_step) { 333 case 0: 334 // TYPE 335 buffer_length = 1; 336 buffer = &header_type; 337 break; 338 case 1: 339 // LENGTH 340 buffer_length = 2; 341 buffer = &header_length; 342 break; 343 case 2: 344 // MESSAGE 345 buffer_length = header_length; 346 if (header_type == 0) { 347 buffer = &text; 348 } else if (header_type == 1) { 349 buffer = &state_players; 350 } 351 break; 352 } 353 354 int recv_length = 355 recv(sockfd, buffer, buffer_length, 0); 356 357 if (recv_length == 0) { 358 endwin(); 359 fprintf(stderr, "Closed by server\n"); 360 close(sockfd); 361 return 0; 362 } 363 364 switch (recv_step) { 365 case 0: 366 // MESSAGE 367 recv_step = 1; 368 break; 369 case 1: 370 // LENGTH 371 if (header_length == 0) { 372 recv_step = 0; 373 } else { 374 recv_step = 2; 375 } 376 break; 377 378 case 2: 379 // MESSAGE 380 recv_step = 0; 381 if (header_type == 0) { 382 length = recv_length; 383 text[length] = '\0'; 384 385 memset(&last_back, 0, sizeof last_back); 386 memset(&last_input, 387 0, 388 sizeof last_input); 389 memset(&first_input, 390 0, 391 sizeof first_input); 392 393 error = 0; 394 count_errors = 0; 395 count_slows = 0; 396 397 spaces[0] = -1; 398 num_spaces = 1; 399 for (int i = 0; i < length; i++) { 400 pos_to_word[i] = num_spaces - 1; 401 if (isspace(text[i])) { 402 spaces[num_spaces] = 403 i + 1; 404 num_spaces++; 405 } 406 } 407 spaces[num_spaces] = length; 408 num_spaces++; 409 410 pos = 0; 411 num_players = 0; 412 } else if (header_type == 1) { 413 num_players = header_length / 414 (sizeof(struct state)); 415 } 416 break; 417 } 418 } 419 420 if (FD_ISSET(sockfd, &writefds)) { 421 if (new_quote >= 0) { 422 struct { 423 char type; 424 unsigned int new_quote; 425 } message = { 426 .type = 1, 427 .new_quote = new_quote, 428 }; 429 send(sockfd, &message, sizeof message, 0); 430 new_quote = -1; 431 outdated = 0; 432 } else if (outdated) { 433 struct { 434 char type; 435 struct state state; 436 } message = { 437 .type = 0, 438 .state.pos = pos, 439 }; 440 send(sockfd, &message, sizeof message, 0); 441 outdated = 0; 442 } 443 } 444 445 print_text( 446 win, 447 pos, 448 error, 449 count_errors, 450 count_slows, 451 last_back, 452 last_input, 453 first_input, 454 state_players, 455 num_players, 456 text, 457 pos_to_word, 458 length, 459 spaces, 460 num_spaces); 461 } 462 463 endwin(); 464 close(sockfd); 465 }