git home / emma home
logo

typie

Minimal typing practice tool.
git clone https://git.y1.nz/archives/typie.tar.gz
README | Files | Log | Refs | LICENSE

main.c


      1 #include <stdlib.h>
      2 #include <stdio.h>
      3 #include <time.h>
      4 
      5 #include "typer.h"
      6 #include "opt.h"
      7 
      8 #define TRUE 1
      9 #define FALSE 0
     10 #define MAX_PROMPT_LENGTH 1024
     11 #define MAX_LINES 100
     12 
     13 char prompt_buffer[MAX_PROMPT_LENGTH];
     14 int jumps_buffer[MAX_PROMPT_LENGTH];
     15 int line_lengths_buffer[MAX_LINES];
     16 char chars_printed_buffer[MAX_PROMPT_LENGTH];
     17 
     18 const char * usage =\
     19 "Usage: typie [-l] [-o OUTFILE] [-t TIME] PROMPT" "\n"\
     20 "\n"\
     21 "        -l          no backspacing, incorrect keys not accepted" "\n"\
     22 "        -o OUTFILE  file to write output to (defaults to stdout)" "\n"\
     23 "        -t TIME     exit after TIME seconds" "\n"\
     24 /* "        -m       output mistakes after completion" "\n" */\
     25 "\n"\
     26 "See the man page for more details." "\n"\
     27 "\n";
     28 
     29 long int
     30 get_time_millis()
     31 {
     32 	struct timespec now;
     33 	timespec_get(&now, TIME_UTC);
     34 	return now.tv_sec * 1000 + now.tv_nsec / 1000000;
     35 }
     36 
     37 int
     38 type(State * state)
     39 {
     40 	int keypress_kind;
     41 	int started = FALSE;
     42 
     43 	for (;;) {
     44 		char c = getchar();
     45 
     46 		long int keypress_millis = get_time_millis();
     47 		long int elapsed_millis = keypress_millis - state->start_millis;
     48 		if (
     49 			started &&
     50 			state->duration_millis != 0 &&
     51 			elapsed_millis > state->duration_millis
     52 		) {
     53 			state->timed_out = TRUE;
     54 			return 0;
     55 		}
     56 
     57 		state->end_millis = keypress_millis;
     58 
     59 		keypress_kind = handle_keypress(state, c);
     60 		fflush(stdout);
     61 
     62 		switch (keypress_kind) {
     63 		case KEYPRESS_EXIT:
     64 			return 1;
     65 		
     66 		case KEYPRESS_CORRECT: /* FALLTHROUGH */
     67 			state->chars_correct++;
     68 			if (state->prompt[state->index] == '\0')
     69 				return 0;
     70 		case KEYPRESS_INCORRECT:
     71 			state->chars_total++;
     72 			if (!started) {
     73 				started = TRUE;
     74 				state->start_millis = get_time_millis();
     75 				fflush(stdout);
     76 			}
     77 			break;
     78 		
     79 		case KEYPRESS_ENTER:
     80 			return 0;
     81 			break;
     82 
     83 		case KEYPRESS_BACKSPACE:
     84 			break;
     85 
     86 		case KEYPRESS_NONE:
     87 			break;
     88 		}
     89 	}
     90 }
     91 
     92 void
     93 write_output(State state)
     94 {
     95 	FILE * out;
     96 	int correct = state.chars_correct,
     97 		total = state.chars_total;
     98 
     99 	double time_millis = state.timed_out ?
    100 		state.duration_millis :
    101 		state.end_millis - state.start_millis;
    102 	double time_seconds = time_millis / 1000;
    103 	double time_minutes = time_seconds / 60;
    104 
    105 	int wpm = (correct / 5.0) / time_minutes;
    106 
    107 	int accuracy = ((double) correct / total) * 100;
    108 
    109 	out = stdout;
    110 	if (state.flags & FLAG_FILE)
    111 		out = fopen(state.outfile, "w");
    112 
    113 	fprintf(
    114 		out,
    115 		"millis    %d" "\n"\
    116 		"wpm       %d" "\n"\
    117 		"accuracy  %d%%",
    118 		(int) time_millis,
    119 		wpm,
    120 		accuracy
    121 	);
    122 	
    123 	if (state.flags & FLAG_FILE) {
    124 		fclose(out);
    125 	} else {
    126 		fprintf(out, "\n");
    127 	}
    128 }
    129 
    130 int
    131 get_length(const char * string)
    132 {
    133 	int index = 0;
    134 	for (;;) {
    135 		if (string[index] == '\0')
    136 			break;
    137 		index++;
    138 	}
    139 	return index;
    140 }
    141 
    142 
    143 void
    144 init_state(State * state)
    145 {
    146 	state->flags = 0;
    147 	state->index = 0;
    148 	state->jumps.buffer = jumps_buffer;
    149 	state->jumps.length = 0;
    150 	state->line_lengths = line_lengths_buffer;
    151 	state->chars_printed = chars_printed_buffer;
    152 	state->chars_printed_index = 0;
    153 	state->index = 0;
    154 	state->row = 0;
    155 	state->max_row = 0;
    156 	state->column = 0;
    157 	state->line_lengths[0] = 0;
    158 }
    159 
    160 int
    161 main(int argc, const char * argv[])
    162 {
    163 	struct termios termios_original;
    164 	State state;
    165 	int result;
    166 	
    167 	init_state(&state);
    168 
    169 	result = set_flags(&state, argc, argv);
    170 	if (result > 0) {
    171 		printf("%s", usage);
    172 		exit(1);
    173 	}
    174 	state.prompt = argv[argc-1];
    175 	state.prompt_length = get_length(state.prompt);
    176 	
    177 	if (state.prompt_length >= MAX_PROMPT_LENGTH) {
    178 		fprintf(stderr, COLOR_RED "Max prompt length exceeded." "\n");
    179 		return 1;
    180 	}
    181 
    182 	tcgetattr(0, &termios_original);
    183 	setup_termios(termios_original);
    184 
    185 	start_typing(&state);
    186 	fflush(stdout);
    187 
    188 	result = type(&state);
    189 	if (result > 0) {
    190 		restore_termios(termios_original);
    191 		exit(1);
    192 	}
    193 
    194 	printf("\033[%dB", state.max_row - state.row - 1);
    195 	restore_termios(termios_original);
    196 	printf(COLOR_RESET "\n");
    197 
    198 	write_output(state);
    199 
    200 	return 0;
    201 }

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.