Relay-Version: version B 2.10.3 4.3bsd-beta 6/6/85; site seismo.CSS.GOV Posting-Version: version B 2.10.2 9/3/84; site panda.UUCP Path: seismo!harvard!talcott!panda!sources-request From: sources-request@panda.UUCP Newsgroups: mod.sources Subject: ttype: typing tutor Message-ID: <1100@panda.UUCP> Date: 12 Nov 85 22:31:31 GMT Sender: jpn@panda.UUCP Lines: 611 Approved: jpn@panda.UUCP Mod.sources: Volume 3, Issue 42 Submitted by: ??? (Chris Bertin) Several people have been asking for a touch-type program. I wrote this a long time ago (and posted it back then). It used to be called 'type' but should be renamed 'ttype' to avoid conflict with the shell's builtin. The instructions are on line and it needs -lcurses and -ltermcap. It should work on any BSD system with very few modifications. -------------------------- cut here ------------------------------- static char cpright[] = "(C) Chris Bertin, 1983"; /* * Learn how to type ( using cursor control if run on a CRT ) */ #include #include #include #include #include #include #include #include #include #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 echo_it; /* echo flag */ int isafastscreen; /* is a fast screen? */ int trials; /* number of trials */ int npractice; /* number of characters to practice on (practice opt.)*/ int lasterr; /* last error, the next string will start with it */ 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\tL E A R N H O W T O T Y P E"); 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 characters 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) help6(); echo_it = answer(work_str, "Do you want echo?", CHAR); if(instruct) help7(); zero(badchar, sizeof badchar); lasterr = toterrors = 0; totlngth = tottime = 0.; if(CA) clear(); moveto(SCORE, 0, LFCR); pr(2, "\tNext line: ^C, Quit: ^D, Move Cursor: ^A, 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 ? lasterr : try_str[0]); lasterr = 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 '\01': *typed_pnt = '^'; *(typed_pnt+1) = 0; (void) putline(typed_str, location+1); printw("%c", '\10'); refresh(); /* fall through */ case '\n': case '\r': --ind; break; case '\f': dorefresh(); --ind; break; case '\03': if((n_of_chars = ind) == 0) /* force out */ return(0.); break; case '\04': case 0177: gtotlngth += totlngth + ind; gtoterrors += toterrors + linerrors; leave(); default: /* a character... */ if(ind == 0) starttime = gettime(0, ""); if(c != try_str[ind]) { /* error */ if(echo_it) pr(0, "%c", ch_case(c, try_str[ind])); else { #ifdef SHOWALL *typed_pnt++ = ch_case(c, try_str[ind); #else *typed_pnt++ = noctrl(c); #endif *typed_pnt = 0; (void) putline(typed_str, location+1); } warnerror(location+1, ind+1, c, try_str[ind]); lasterr = try_str[ind]; ++linerrors; ++badchar[lasterr]; ++gbadchar[lasterr]; } else { if(echo_it) pr(0, "%c", noctrl(c)); else #ifdef SHOWALL *typed_pnt++ = noctrl(c); #else *typed_pnt++ = ' '; #endif clrtoeol(); } refresh(); break; } } *typed_pnt = 0; #ifdef SHOWALL if(! echo_it) (void) putline(typed_str, location+1); #endif 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, goodc, badc) { if(! isafastscreen || y > COLS-15) { putchar('\07'); return; } clrtoeol(); move(x, y+3); standout(); pr(0, "Error (%c/%c)", goodc, badc); 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,"\t If you just hit , the system will give you random"); pr(2,"\tcharacters picked from all the characters of your keyboard"); pr(2,"\tto practice on. If you want to limit the range of characters,"); pr(2,"\tenter the list followed by a ."); pr(2,"\t If you want to practice on random words from the dictionnary"); pr(2,"\ttype 'words', and if you want to practice on an existing file"); pr(2,"\ttype '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, "\t Note: 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, "\t The system will now 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."); } help4() { sethelp(); pr(2, "\t The system will now make strings of characters for you to"); pr(2, "\tpractice on. Enter the length that you want each string to"); pr(2, "\tbe."); } help5() { sethelp(); pr(2, "\t How many lines do you want the system to give you before"); pr(2, "\tit resets the errors to zero?"); } help6() { sethelp(); pr(2,"\t Do you want to see the characters you are typing as you type"); #ifdef SHOWALL pr(2,"\tthem? If you type 'no', the line will be printed only if you"); pr(2,"\tmake a mistake. In any case, it is printed in full when the"); pr(2,"\tline is complete."); #else pr(2,"\tthem? If you type 'no', only the errors will be printed."); #endif } help7() { sethelp(); pr(2, "\tMiscellaneous Notes: and are ignored."); pr(2, "\tCtrl_A moves the cursor to the next character to be typed."); pr(2, "\tEach error subtracts one word from the speed. (The longer"); pr(2, "\tthe strings to be typed are, the more accurate the speed is)"); pr(2, "\n\t ---- Written by Chris Bertin, May 1983 ----"); if(CA) (void) answer(work_str, "Hit 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; } -------------------------- cut here ---------------------------------- Chris