paulh@copper.UUCP (Paul Hubbard) (12/13/85)
--------- Several people have requested my modified ttype source, so here it is. --------8<-----------8<------------8<-------------8<------------8<-------- /* * ttype By Chris Bertin, 1983 * Modified by Paul Hubbard, 1985 * * Description: TTYPE allows you to practice terminal or typewriter * typing. TTYPE is self-explanatory if you request * instructions. * * Compilation: cc -o ttype ttype.c -lcurses -ltermlib * * Invocation: ttype */ static char cpright[] = "(C) Chris Bertin, 1983"; static char cpleft[] = "Paul Hubbard, 1985"; #include <sys/types.h> #include <sys/stat.h> #include <sys/timeb.h> #include <sys/time.h> #include <sys/ioctl.h> #include <stdio.h> #include <ctype.h> #include <signal.h> #include <curses.h> #define WORDLIST "/usr/dict/words" #define LFCR "\n\r" #define CHAR 0 /* answer types */ #define NUM 1 /* ... */ #define PRACTICE 0 /* run types */ #define DICT 1 /* ... */ #define FILEIN 2 /* ... */ #define MAX 128 /* std array size as well as max ASCII */ #define SPEED 0 /* screen locations */ #define SCORE 2 /* ... */ #define TYPE LINES -12 /* ... */ #define ASK LINES - 3 /* ... */ #define BOTTOM LINES - 2 /* ... */ #define ALLCHARS "&*()_+|\\{}:\"~<>?-=[];'`,./!#$%^ ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 abcdefghijklmnopqrstuvwxyz" #define moveto(x,y,str) if(CA)move(x,y),clrtoeol();else pr(0,str); #define dorefresh() if(CA){clearok(stdscr,TRUE);refresh();clearok(stdscr,FALSE);} #define sethelp() wclear(helpscr);wrefresh(helpscr);moveto(TYPE,0,LFCR); char what_chars[MAX*2]; /* the characters to practice on */ char try_str[MAX*2]; /* the string to be retyped */ char typed_str[MAX*2]; /* the chars being typed by the user */ char work_str[MAX*2]; /* work space (temp string) */ char badchar[MAX]; /* errors; the mistyped chars are pointers into them */ char gbadchar[MAX]; /* same but session total */ int xcoor,ycoor; /* temp coordinates */ int isafastscreen; /* is a fast screen? */ int trials; /* number of trials */ int npractice; /* number of characters to practice on (practice opt.)*/ char lasterr[500]; /* last error, the next string will start with it */ int err_num; /* last error pointer */ int linerrors, toterrors, gtoterrors; /* errors per line / run / session */ float totlngth, gtotlngth; /* chars typed per run / session */ float tottime, gtottime; /* time spent per run / session */ struct timeb tb, ta; struct stat sbuf; FILE *fp, *dictfp, *fopen(); WINDOW *helpscr, *subwin(); main() { register option, screenloc, lngth, more = 1; int out_size, nwords, instruct; char fname[MAX]; float chkstr(); setbuf(stdin, (char *)NULL); ftime(&ta); srandom(getpid() + ta.millitm - getuid()); /* should be unique... */ initscr(); scrollok(stdscr, FALSE); (void) signal(SIGFPE, SIG_IGN); if(CA) /* Do we have cursor control?? */ isafastscreen = highspeed(); else pr(1, "[No cursor addressing, using hard copy mode]\n"); raw(), noecho(); moveto(TYPE - 6, 0, ""); pr(2, "\t\t\tP R A C T I C E T Y P I N G"); pr(2, "\t\t\t~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); if(instruct = answer(work_str, "Instructions? (y or n)", CHAR) == 1) helpscr = subwin(stdscr, 9, COLS, TYPE, 0); gtoterrors = gtotlngth = gtottime = 0.; zero(gbadchar, sizeof gbadchar); while(more) { screenloc = TYPE; if(instruct) help1(); (void) answer(work_str, "What text type do you want?", CHAR); if(! strcmp(work_str, "words")) { option = DICT; if(dictfp == NULL) /* don't redo it */ if(((stat(WORDLIST, &sbuf)) < 0) || ((dictfp = fopen(WORDLIST,"r")) == NULL)){ note("Can't open %s", WORDLIST); continue; } if(instruct) help3(); nwords = answer(work_str, "How many words in each line?", NUM); if(nwords > COLS/11) note("Set to maximum allowed (%d)", (nwords = COLS/11)); } else if(! strcmp(work_str, "file")) { option = FILEIN; if(instruct) help2(); (void) answer(fname, "Enter file name:", CHAR); if(! *fname) continue; if((fp = fopen(fname, "r")) == NULL) { note("Can't open %s", fname); continue; } } else { option = PRACTICE; strcpy(what_chars, work_str[0] ? work_str : ALLCHARS); npractice = strlen(what_chars); if(instruct) help4(); out_size = answer(work_str, "How many characters in each string?", NUM); } if(option != FILEIN) { if(instruct) help5(); trials = answer(work_str, "How many trials?", NUM); } if(instruct) help7(); zero(badchar, sizeof badchar); err_num = toterrors = 0; totlngth = tottime = 0.; if(CA) clear(); moveto(SCORE, 0, LFCR); pr(2, "\tNext line: ^C, Quit: ^D, Redraw: ^L"); while(makestr(option, out_size, nwords) >= 0) { ioctl(0, TIOCFLUSH, 0); if(CA) note("", ""); if((lngth = chkstr(screenloc)) == 0) continue; totlngth += lngth; toterrors += linerrors; if(isafastscreen) score(badchar, toterrors, totlngth, tottime); if((screenloc += 3) > (ASK - 2)) screenloc = TYPE; } gtotlngth += totlngth; gtoterrors += toterrors; if( ! CA || ! isafastscreen) score(badchar, toterrors, totlngth, tottime); more = answer(work_str, "Try again?", CHAR); } leave(); } answer(str, msg, type) register char *str; char *msg; { register c, i, ret = 0; for(;;) { moveto(ASK, 10, "\n\r> "); pr(1, "%s: ", msg); for(i=0; ((c = getch()) != '\n' && c != '\r') ;) switch(c) { case '\f': dorefresh(); break; case '\10': if(i>0) --i, pr(1, "\010 \010"); break; case '\04': case '\03': case 0177: leave(); default: pr(1, "%c", (str[i++] = noctrl(c))); } if(CA) note("", ""); while(str[--i] == ' ') ; str[++i] = 0; if(type == CHAR) if(*str == 'y') return(1); else return(0); if((ret = atoi(str)) <= 0) { note("Numeric value (> 0) required", ""); continue; } return(ret); } } makestr(opt, lng, n_of_wrds) /* builds the string to be typed */ { register ind; if(opt != FILEIN && trials <= 0) return(-1); zero(try_str, sizeof try_str); switch(opt) { case FILEIN: if(fgets(try_str, sizeof try_str, fp) == NULL) return(fclose(fp)); fixstr(try_str); return(1); case DICT: for(ind = 0; ind < n_of_wrds ; ++ind, strcat(try_str, " ")) { if(fseek(dictfp, random()%(long)sbuf.st_size, 0) == -1) strcpy(work_str, "fseek failed!!!"); fgets(work_str, sizeof work_str, dictfp); if(fgets(work_str, sizeof work_str, dictfp) == NULL) strcpy(work_str, "End of file!!!"); work_str[strlen(work_str) - 1] = 0; strcat(try_str, work_str); } return(--trials); case PRACTICE: for(ind = 0 ; ind < lng ; ++ind) try_str[ind] = what_chars[(int)random() % npractice]; try_str[0] = (lasterr[err_num] ? lasterr[err_num] : try_str[0]); err_num = 0; return(--trials); } /* NOTREACHED */ } fixstr(str) char *str; { register char *in, *out; short space = 0; for(in = str ; isspace(*in) ; ++in) ; for(out = str ; *in ; ++in) { if(*in == '\010') { if(out > str) out--; continue; } if(isspace(*in) && space) continue; space = isspace(*in); *out++ = noctrl(*in); } *out = 0; } float chkstr(location) /* checks the typing */ { register ind, c = 0; register char *typed_pnt; register float n_of_chars; float f, elapsed, starttime, gettime(); zero(typed_str, sizeof typed_str); typed_pnt = typed_str; if(strlen(try_str) >= COLS-1) { note("Line too long, truncated", ""); try_str[COLS-1] = 0; } if((n_of_chars = (float) putline(try_str, location)) == 0.) return(0.); moveto(location+1, 0, LFCR); refresh(); for(linerrors = ind = 0; ind < n_of_chars ; ++ind) { switch(c = getch()) { case '\n': case '\r': --ind; break; case 0177: case '\b': getyx(stdscr,ycoor,xcoor); move(ycoor,xcoor-1); delch(); if (lasterr[err_num-1] == try_str[ind-1]) { --linerrors; --badchar[lasterr[err_num-1]]; --gbadchar[lasterr[err_num-1]]; --err_num; } clrtoeol(); refresh(); ind -= 2; break; case '\f': dorefresh(); --ind; break; case '\03': if((n_of_chars = ind) == 0) /* force out */ return(0.); break; case '\04': gtotlngth += totlngth + ind; gtoterrors += toterrors + linerrors; leave(); default: /* a character... */ if(ind == 0) starttime = gettime(0, ""); if(c != try_str[ind]) { /* error */ pr(0, "%c", ch_case(c, try_str[ind])); warnerror(location+1, ind+1); lasterr[err_num] = try_str[ind]; ++err_num; ++linerrors; ++badchar[lasterr[err_num-1]]; ++gbadchar[lasterr[err_num-1]]; } else { pr(0, "%c", noctrl(c)); clrtoeol(); } refresh(); break; } } *typed_pnt = 0; standend(); clrtoeol(); elapsed = gettime(12, "/ ") - starttime; tottime += elapsed; gtottime += elapsed; moveto(SPEED, COLS-40, LFCR); standout(); if(! CA) printf("\t\t\t\t"); pr(2, "%.1f secs, %d error(s), %.2f w/min", elapsed, linerrors, (f=((n_of_chars / 5.) - linerrors) / (elapsed / 60.))>0.?f:0.0); standend(); refresh(); return(n_of_chars); } float gettime(where, str) register char *str; { struct tm *localtime(); register struct tm *tm; register x, y; ftime(&tb); if(isafastscreen) { tm = localtime(&tb.time); getyx(stdscr, y, x); move(SPEED, where); pr(1, "%s%02d:%02d:%02d.%02d", str, tm->tm_hour, tm->tm_min, tm->tm_sec, (tb.millitm / 10)); clrtoeol(); move(y, x); refresh(); } return((float) (tb.time - ta.time) + ((float)tb.millitm / 1000.)); } putline(outstr, where) register char *outstr; { register n; char *rindex(); register char *back = rindex(outstr, '\0') - 1; while(isspace(*back)) *back-- = 0; moveto(where, 0, "\r"); for(n = 0 ; *outstr ; ++n) pr(0, "%c", noctrl(*outstr++)); if(n) refresh(); return(n); } warnerror(x, y) { if(! isafastscreen || y > COLS-15) { putchar('\07'); return; } clrtoeol(); move(x, y+3); standout(); pr(0, " Error"); standend(); move(x, y); refresh(); } /* NOSTRICT */ /* VARARGS */ note(str1, str2) char *str1; long str2; { moveto(BOTTOM+1, COLS-40, LFCR); clrtoeol(); standout(); pr(2, str1, str2); standend(); } ch_case(bad, good) /* converts to upper or lower case */ { register ret; if(isupper(bad)) return((good - (ret=tolower(bad))) ? ret : bad); if(islower(bad)) return((good - (ret=toupper(bad))) ? ret : bad); return(noctrl(bad)); } noctrl(ch) register ch; { if(ch == '\t') return(' '); return(iscntrl(ch) ? (ch - '\01' + 'A') : ch); } score(errstr, err, lng, ttime) /* prints the score */ char *errstr; register float lng, ttime; { register s, i, n=0; float f; if(lng == 0.) return; moveto(SCORE, 0, "\n\n\r"); s = (lng - (float) err) * 100. / lng; pr(0,"%d errors on %.0f characters (%d %% error), ", err, lng, (100-s)); pr(1, "average speed: %.2f words/minute", ((f = ((lng / 5.) - err) / (ttime / 60.)) >0. ? f : 0.), err); if(err) { moveto(SCORE + 2, 0, LFCR); for(n = i = 0 ; i < MAX ; ++i) if(errstr[i] != 0) { pr(0, "%4d errors on '%c'", errstr[i], i); if(++n > 3) { n = 0; pr(1, LFCR); } } pr(1, LFCR); } return; } /* NOSTRICT */ /* VARARGS */ pr(mode, fmt, str1, str2, str3, str4, str5) /* mode 2: add LFCR, 1: refresh() */ char *fmt; long str1, str2, str3, str4, str5; { if( CA) { printw(fmt, str1, str2, str3, str4, str5); if(mode == 2) printw(LFCR); if(mode > 0) refresh(); } else { printf(fmt, str1, str2, str3, str4, str5); if(mode == 2) printf(LFCR); fflush(stdout); } } highspeed() /*return 1 if 1200 baud or more */ { struct sgttyb mod; gtty(0, &mod); if(mod.sg_ispeed >= B1200) return(1); move(ASK, 10); pr(1, "[Using slow terminal mode]"); return(0); } help1() { sethelp(); pr(2,"\tIf you hit <RETURN>, the system will give you a random string"); pr(2,"\tof characters to practice on. If you want to limit the range"); pr(2,"\tof characters, enter a character list. To practice on random"); pr(2,"\twords from the dictionary type 'words'. To practice on the"); pr(2,"\ttext in a file, type 'file'."); } help2() { sethelp(); pr(2, "\tThe system will print one by one all the lines from the file"); pr(2, "\tand wait for you to retype them. If the file is not in the"); pr(2, "\tcurrent directory, enter the full path name (Ex:/home/test)."); pr(2, "\tNote: trailing spaces and tabs are truncated, tabs in the"); pr(2, "\tlines are replaced with spaces and empty lines are skipped."); } help3() { sethelp(); pr(2, "\tThe program will pick random words from the dictionary."); pr(2, "\tEnter the number of words you want the system to give you"); pr(2, "\ton each line. (More words produce a more accurate speed"); pr(2, "\trating.)"); } help4() { sethelp(); pr(2, "\tThe program will make strings of characters for you to"); pr(2, "\tenter. Enter the length of string. (More letters produce"); pr(2, "\ta more accurate speed rating.)"); } help5() { sethelp(); pr(2, "\tHow many lines do you want the system to give you in this"); pr(2, "\tsession?"); } help6() { sethelp(); pr(2,"\t Do you want to see the characters you are typing as you type"); pr(2,"\tthem? If you type 'no', only the errors will be printed."); } help7() { sethelp(); pr(2, "\tMiscellaneous Notes: <RETURN> and <LINE-FEED> are ignored."); pr(2, "\tEach error subtracts one word from the speed. You can use "); pr(2, "\tbackspaces to recover from errors."); if(CA) (void) answer(work_str, "Hit <RETURN> to continue", CHAR); } leave() { if(gtotlngth) { moveto(SPEED, 0, LFCR); pr(2, "\t\t\tS E S S I O N T O T A L"); score(gbadchar, gtoterrors, gtotlngth, gtottime); } clrtobot(); moveto(BOTTOM, 0, LFCR); refresh(); endwin(); exit(0); } zero(str, n) register char *str; register n; { while(n--) *str++ = 0; }