commit 09da1f9bea7fb12e327de2399a62ab3370f4f629
parent ef7cd656eb53cd9aa1e20ee29f3fe2bd1c2ce7ea
Author: Emma Weaver <emma@waeaves.com>
Date: Sat, 2 May 2026 12:10:27 -0400
implemented time option, removed statistics option
Diffstat:
8 files changed, 107 insertions(+), 64 deletions(-)
diff --git a/README b/README
@@ -6,8 +6,10 @@ examples for some intended use cases.
Features:
- minimal, only C and standard libs, short and clean code
+ - responsive UI with pretty colors
- skip-to-next-chunk error determination (similar to monkeytype)
- - statistics output
+ - timed mode
+ - output statistics (to stdout or to a file)
--------------------------------------------------------------------------------
# Build/installation
@@ -22,8 +24,6 @@ To install it, edit config.mk to reflect your system's configuration, then run
--------------------------------------------------------------------------------
# TODO
--------------------------------------------------------------------------------
- - multiple-line, auto-wrapped-at-80 input
- - timed mode
- - output file
- - mistakes output
+ - wrap at 80 / support for long prompts
+ - output data about mistakes made
diff --git a/examples/lesson.sh b/examples/lesson.sh
@@ -24,21 +24,21 @@ echo "If it gets too hard, don't be afraid to ctrl+C and redo previous lessons."
echo ""
echo "Introducing the new letters..."
-typie -l "$(./words.sh "$NEW_LETTERS" 6 12)"
-typie -l "$(./words.sh "$NEW_LETTERS" 6 12)"
+typie -lf /dev/null "$(./words.sh "$NEW_LETTERS" 6 12)"
+typie -lf /dev/null "$(./words.sh "$NEW_LETTERS" 6 12)"
echo "Integrating with some old letters..."
-typie "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$(old_letters $EASY)$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$(old_letters $MEDIUM)$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$(old_letters $HARD)$NEW_LETTERS" 5 8)"
echo "All together now!"
-typie "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)"
-typie "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)"
+typie -f /dev/null "$(./words.sh "$OLD_LETTERS$NEW_LETTERS" 5 8)"
echo "All done! :3"
diff --git a/examples/words.sh b/examples/words.sh
@@ -30,6 +30,6 @@ if [ $FEW_WORDS = "true" ]; then
done
echo "$PROMPT"
else
- echo "$WORDS" | shuf -n $LENGTH | tr '\n' ' '
+ echo "$WORDS" | shuf -n $LENGTH | xargs echo
fi
diff --git a/main.c b/main.c
@@ -13,17 +13,24 @@ char prompt_buffer[MAX_BUFFER_LENGTH];
int jumps_buffer[MAX_BUFFER_LENGTH];
const char * usage =\
-"Usage: typie [-ls] [-f FILE] PROMPT" "\n"\
+"Usage: typie [-l] [-f FILE] [-t TIME] PROMPT" "\n"\
"\n"\
" -l no backspacing, incorrect keys not accepted" "\n"\
-" -s output statistics after completion" "\n"\
" -f FILE file to write output to (stdout if unspecified)" "\n"\
+" -t TIME exit after TIME seconds" "\n"\
/* " -m output mistakes after completion" "\n" */\
-/* " -t TIME terminate after TIME milliseconds" "\n" */\
"\n"\
"See the man page for more details." "\n"\
"\n";
+long int
+get_time_millis()
+{
+ struct timespec now;
+ timespec_get(&now, TIME_UTC);
+ return now.tv_sec * 1000 + now.tv_nsec / 1000000;
+}
+
int
type(State * state)
{
@@ -31,7 +38,22 @@ type(State * state)
int started = FALSE;
for (;;) {
- keypress_kind = handle_keypress(state);
+ char c = getchar();
+
+ long int keypress_millis = get_time_millis();
+ long int elapsed_millis = keypress_millis - state->start_millis;
+ if (
+ started &&
+ state->duration_millis != 0 &&
+ elapsed_millis > state->duration_millis
+ ) {
+ state->timed_out = TRUE;
+ return 0;
+ }
+
+ state->end_millis = keypress_millis;
+
+ keypress_kind = handle_keypress(state, c);
fflush(stdout);
switch (keypress_kind) {
@@ -46,7 +68,7 @@ type(State * state)
state->chars_total++;
if (!started) {
started = TRUE;
- state->start_millis = time(NULL);
+ state->start_millis = get_time_millis();
fflush(stdout);
}
break;
@@ -70,27 +92,30 @@ write_output(State state)
FILE * out;
int correct = state.chars_correct,
total = state.chars_total;
- double time_millis = state.end_millis - state.start_millis;
+
+ double time_millis = state.timed_out ?
+ state.duration_millis :
+ state.end_millis - state.start_millis;
double time_seconds = time_millis / 1000;
double time_minutes = time_seconds / 60;
+
int wpm = (correct / 5.0) / time_minutes;
+
int accuracy = ((double) correct / total) * 100;
out = stdout;
if (state.flags & FLAG_FILE)
out = fopen(state.outfile, "w");
- if (state.flags & FLAG_STATISTICS_OUTPUT) {
- fprintf(
- out,
- "time:" "\t\t" "%f" "\n"\
- "wpm:" "\t\t" "%d" "\n"\
- "accuracy:" "\t" "%d%%" "\n",
- time_millis/1000,
- wpm,
- accuracy
- );
- }
+ fprintf(
+ out,
+ "millis %d" "\n"\
+ "wpm %d" "\n"\
+ "accuracy %d%%" "\n",
+ (int) time_millis,
+ wpm,
+ accuracy
+ );
if (state.flags & FLAG_FILE)
fclose(out);
@@ -139,8 +164,6 @@ main(int argc, const char * argv[])
exit(1);
}
- state.end_millis = time(NULL);
-
restore_termios(termios_original);
printf(COLOR_RESET);
diff --git a/opt.c b/opt.c
@@ -1,6 +1,7 @@
#include <stdlib.h>
#include <stdio.h>
+#include <limits.h>
#include "opt.h"
#include "typer.h"
@@ -12,6 +13,18 @@
#define PROCESS_GROUP_SUCCESS 0
#define PROCESS_GROUP_FAILURE 1
#define PROCESS_GROUP_SKIP 2
+#define PARSE_INT_FAILURE -1
+
+int
+parse_int(const char * arg)
+{
+ char * endptr;
+ int ret;
+ ret = strtol(arg, &endptr, 10);
+ if (* endptr != '\0' || ret > INT_MAX || ret < INT_MIN)
+ return PARSE_INT_FAILURE;
+ return ret;
+}
int
get_flag(char c)
@@ -20,31 +33,12 @@ get_flag(char c)
case 'l': return FLAG_LEARN_MODE;
case 'f': return FLAG_FILE;
case 't': return FLAG_TIME;
- case 's': return FLAG_STATISTICS_OUTPUT;
}
return 0;
}
int
-process_file_flag(
- State * state,
- const char * group,
- const char * next,
- int index
-)
-{
- if (next == NULL || group[index+1] != '\0') {
- ERROR("Filename must follow -f." "\n\n");
- return PROCESS_GROUP_FAILURE;
- }
-
- state->outfile = next;
-
- return PROCESS_GROUP_SKIP;
-}
-
-int
process_flag_group(State * state, const char * group, const char * next)
{
int index;
@@ -75,10 +69,36 @@ process_flag_group(State * state, const char * group, const char * next)
return PROCESS_GROUP_FAILURE;
}
- if (flag == FLAG_FILE)
- return process_file_flag(state, group, next, index);
-
state->flags |= flag;
+
+ if (flag == FLAG_FILE) {
+ if (next == NULL || group[index+1] != '\0') {
+ ERROR("Filename must follow -f." "\n\n");
+ return PROCESS_GROUP_FAILURE;
+ }
+
+ state->outfile = next;
+
+ return PROCESS_GROUP_SKIP;
+ }
+
+ if (flag == FLAG_TIME) {
+ long int duration_seconds;
+ if (next == NULL || group[index+1] != '\0') {
+ ERROR("Integer must follow -t." "\n\n");
+ return PROCESS_GROUP_FAILURE;
+ }
+
+ duration_seconds = parse_int(next);
+ if (duration_seconds == PARSE_INT_FAILURE) {
+ ERROR("Integer must follow -t." "\n\n");
+ return PROCESS_GROUP_FAILURE;
+ }
+ state->duration_millis = duration_seconds * 1000;
+
+ return PROCESS_GROUP_SKIP;
+ }
+
index++;
}
diff --git a/opt.h b/opt.h
@@ -4,8 +4,7 @@
typedef enum {
FLAG_LEARN_MODE = 1 << 0,
FLAG_FILE = 1 << 1,
- FLAG_TIME = 1 << 2,
- FLAG_STATISTICS_OUTPUT = 1 << 3
+ FLAG_TIME = 1 << 2
} Flag;
int set_flags(State * state, int argc, const char * argv[]);
diff --git a/typer.c b/typer.c
@@ -225,9 +225,8 @@ start_typing(State * state)
}
int
-handle_keypress(State * state)
+handle_keypress(State * state, char c)
{
- char c = getchar();
char expected = state->prompt[state->index];
if (c == KEY_CTRL_C || c == KEY_CTRL_D || c == KEY_ESCAPE)
diff --git a/typer.h b/typer.h
@@ -55,16 +55,18 @@ typedef struct {
/* options */
int flags;
const char * outfile;
+ long int duration_millis;
/* timing */
long int start_millis;
long int end_millis;
+ int timed_out;
} State;
void setup_termios(struct termios termios0);
void restore_termios(struct termios termios0);
void start_typing(State * state);
-int handle_keypress(State * state);
+int handle_keypress(State * state, char c);
#endif /* TYPERH */