tt

A terminal-based typing game
git clone git://pcardenasb.com/tt
Log | Files | Refs | README | LICENSE

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, &current);
     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, &current);
    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 }