--- /dev/null
+/*
+ * Copyright (c) 1988 Regents of the University of California.
+ * Copyright (c) 1992 Joe Dellinger, University of Hawaii at Manoa
+ * Copyright (c) 2005 Eric S. Raymond.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * A morse code practice utility. (Contains those characters that can appear
+ * on the FCC ham license exam.)
+ *
+ * Running "morse" without arguments or input gives self-doc.
+ *
+ * It doesn't keep PERFECT time, but it seems reasonably close
+ * for reasonable word speeds on my slow SUN IPC!
+ *
+ * This code is (ridiculously) heavily modified from morse.c from the Reno UNIX
+ * distribution. I (Joe) also used slightly modified versions of a subroutine
+ * from Richard Ottolini at Unocal for Sun workstation tone generation.
+ * Scott Seligman at Stanford added support for other sorts of devices and
+ * made several other changes. John Shalamskas helped test and made comments.
+ *
+ * For a more detailed history, see the HISTORY file
+ */
+
+/*
+ * Useful for seeing what the interleaved reading and writing loops are
+ * really up to.
+ *
+ * #define DEBUG
+ *
+ * If you want to be overwhelmed with information about the probabilities
+ * of each letter being chosen.
+ *
+ * #define DEBUGG
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <ctype.h>
+#include <signal.h>
+#include "beep.h"
+
+#define FREQUENCY 800.0
+#define FREQUENCY2 602.0
+#define VOLUME 0.5
+#define ERROR_VOLUME 0.5
+#define WORDS_PER_MINUTE 20.0
+#define MAX_BEHINDNESS 0
+#define ERROR_FREQUENCY 2000.0
+
+static int whichfrequ = 0;
+static float frequency1 = FREQUENCY;
+static float frequency2 = FREQUENCY2;
+static float frequency;
+static float volume = VOLUME;
+static float dot_time;
+static float dash_time;
+static float intra_char_time;
+static float inter_char_time;
+static float inter_word_time;
+static float catchup_time;
+static int showletters = 0;
+static int showmorse = 0;
+static int wordsbefore = 0;
+static int wordsafter = 0;
+static int fancyending = 1;
+static int noticebad = 0;
+static int testing = 0;
+static int showtesting = 0;
+static int keepquiet = 0;
+static int dynamicspeed = 0;
+static int charbychar = 0;
+static int international = 0;
+static int allprosigns = 0;
+static int allpunctuation = 1;
+static int tryagaincount = 1;
+static float words_per_minute = WORDS_PER_MINUTE;
+static float error_frequency = ERROR_FREQUENCY;
+static float error_volume = ERROR_VOLUME;
+static float fwords_per_minute;
+static int randomletters = 0;
+#define LETMESEE 2
+static int typeaway = 0;
+
+static int totalhitcount = 0;
+static int totalmisscount = 0;
+static int helpmeflag = 0;
+
+#define MAXWORDLEN 20
+#define TESTBUFSZ ((MAXWORDLEN+1)*10)
+static int wordlen = MAXWORDLEN;
+static int wordcount = -1;
+static time_t starttime;
+static int timeout = -1;
+static int testpointer = -1;
+static int testlength = 0;
+static int behindness = 0;
+static int max_behindness = MAX_BEHINDNESS;
+static char teststring[TESTBUFSZ];
+static int yourpointer = -1;
+static int yourlength = 0;
+static char yourstring[TESTBUFSZ];
+static int linepos = 0;
+
+/*
+ * How many times can a given character not be asked before
+ * kicking up the probability of asking that one by one randomfactor unit.
+ */
+#define RIPECOUNT 64
+
+#define TWOFIFTYSIX 256
+static char *(code[TWOFIFTYSIX]);
+static int errorlog[TWOFIFTYSIX];
+static int randomfactor[TWOFIFTYSIX];
+static int randomripe[TWOFIFTYSIX];
+static int testterminal (void);
+static int randomletter (void);
+
+/*
+ * Pointers to termcap/terminfo "smso/so" (enter_standout_mode) and "rmso/se"
+ * (exit_standout_mode) terminal string capabilities (initialized respectively
+ * with ANSI sequences "\E[7m" to turn the inverse video mode on and "\E[0m"
+ * to turn all video attributes off).
+ */
+static const char *enter_standout_mode = "\033[7m";
+static const char *exit_standout_mode = "\033[0m";
+
+/*
+ * Value of (Wrong - Right), which, if exceeded, will cause the program
+ * to start prompting you. Above MAX_ERROR_THRESHOLD it will never prompt.
+ * Don't let the user bank too much credit for past correct answers;
+ * limit it by min(ERROR_FLOOR, error_threshold).
+ */
+#define MAX_ERROR_THRESHOLD 1000
+#define ERROR_FLOOR -3
+static int error_threshold = MAX_ERROR_THRESHOLD;
+static int error_floor = ERROR_FLOOR;
+
+/*
+ * How many characters behind before it decides you're having
+ * trouble keeping up.
+ */
+#define BEHIND 1
+#define WAYBEHIND 3
+#define TOOFARBEHIND 6
+/*
+ * If SLOWPOKE or more wpm ticks go by, then it decides you are having lots
+ * of trouble remembering this character, and need to be asked it more
+ * often.
+ */
+#define SLOWPOKE 10
+/* You aren't slow -- you left and came back! */
+#define SLOWPOKEMAX (50 * SLOWPOKE)
+/*
+ * If FASTPOKE or less wpm ticks go by, then it decides you are good at this
+ * character, and need to be asked it less often.
+ */
+#define FASTPOKE 4
+
+/*
+ * These control how quickly the dynamicspeed option acts when you are
+ * fast or slow. Easier to slow down than speed up!
+ */
+#define ERRORSLOWER 1.04
+#define ALOTSLOWER 1.15
+#define ALITTLESLOWER 1.02
+#define ALITTLEFASTER 1.02
+
+/*
+ * How many inter_char_time's to give you to answer after the end of
+ * a word before considering that you are not keeping up.
+ * Maximum of 2.3, minimum of 0.
+ * The bigger the value, the easier it is to kick in the "automatic
+ * speedup" when using the "-d" option. The maximum means you have (almost)
+ * right up to the beginning of the next word to answer and still have it
+ * count as keeping up.
+ */
+#define SPORTING_RATIO 1.5
+
+/*
+ * The bigger, the more evenly things start out.
+ * (Must be at least 2)
+ */
+#define RANDOMBASELEVEL 7
+/*
+ * RANDOMINCWORSE scales how badly you are punished for being wrong
+ * or taking too long. RANDOMINCBETTER scales how you are rewarded for
+ * answering quickly or being right.
+ */
+#define RANDOMINCWORSE 6
+#define RANDOMINCBETTER 7
+#define RANDOMMAX (30 * RANDOMBASELEVEL)
+/*
+ * The average length of a random word (chosen using exponential distribution).
+ * After implementing this I'm not so sure an exponential distribution
+ * actually models the distribution of real word lengths in English very well.
+ * It's not too bad, though, and the words themselves are all garbage anyway,
+ * so what the heck.
+ */
+#define RANDWORDLEN 3.5
+
+/* Put in a newline instead of a space when past this column */
+#define LINELENGTH 78
+
+/* An EOF without the EOF (%) sound */
+#define SILENTEOF -2
+/* Toggle tone frequency on control-G within input file */
+#define FREQU_TOGGLE ((int)'\007')
+
+/*
+ * If you want the morse code to come out synchronized with the printing
+ * of dots and dashes with the -m option, then define this. The problem
+ * is that then the morse code sounds ratty on slower CPU's.
+ * John Shalamskas (KJ9U) suggested turning the precise morse-code printing
+ * synching off because he didn't like the resulting code quality!
+ */
+#undef FLUSHCODE
+
+static void new_words_per_minute (void);
+static void dowords (int c);
+static void morse (int c);
+static void show (char *s);
+static void testaddchar (char c);
+static void youraddchar (char c);
+static void pollyou (void);
+static void tone (float hertz, float duration, float amplitude);
+static void toneflush (void);
+static void openterminal (void);
+static int readterminal (char **string);
+static void closeterminal (void);
+static void report (void);
+
+static void die (), suspend ();
+static void cleanup ();
+
+int
+main (int argc, char **argv)
+{
+extern char *optarg;
+extern int optind;
+int ch;
+char *p;
+int ii, jj;
+int firsttime, notdoneyet;
+int yourchar;
+float randexp, randnum;
+extern time_t time ();
+
+ if (argc == 1 && isatty (fileno (stdin)))
+ {
+/*
+ * SELF DOC
+ */
+ printf ("Usage:\n");
+ printf ("morse [options] < text_file\n");
+ printf ("morse [options] words words words\n");
+ printf ("morse [options] -r\n");
+ printf ("morse [options] -i\n");
+ printf ("Options:\n");
+ printf ("-i Play what you type.\n");
+ printf ("-I Like -i but don't turn off keyboard echoing.\n");
+ printf ("-r Generate random text. Starts out slanted towards easy\n");
+ printf (" letters, then slants towards ones you get wrong.\n");
+ printf ("-n NUM (default %d means random length)\n", MAXWORDLEN);
+ printf (" Make words (groups) NUM characters long.\n");
+ printf (" Valid values are between %d and %d.\n", 1, MAXWORDLEN);
+ printf ("-R NUM (default 0 means unlimited)\n");
+ printf (" Set the total time (in minutes) to generate text.\n");
+ printf ("-N NUM (default 0 means unlimited)\n");
+ printf (" Set the total number of words (groups) to generate.\n");
+ printf ("-C \'STRING\' (default all available characters)\n");
+ printf (" Select characters to send from this STRING only.\n");
+ printf ("-w words_per_minute (default %g)\n-f frequency_in_hertz (default %g)\n-v volume (zero to one, rather nonlinear, default %g)\n",
+ WORDS_PER_MINUTE, FREQUENCY, VOLUME);
+ printf ("-g alternate_frequency (default %g)\n (toggles via control-G in input FILE at a word break)\n", FREQUENCY2);
+ printf ("-F Farnsworth_character_words_per_minute\n");
+ printf ("-e leave off the <SK> sound at the end\n");
+ printf ("-c complain about illegal characters instead of just ignoring them\n");
+ printf ("-b print each word before doing it\n");
+ printf ("-a print each word after doing it\n");
+ printf ("-l print each letter just before doing it\n");
+ printf ("-m print morse dots and dashes as they sound\n");
+#ifdef FLUSHCODE
+ printf (" (this printing-intensive option slows the wpm down!)\n");
+#endif
+ printf ("-t Type along with the morse, but don't see what\n");
+ printf (" you're typing (unless you make a mistake).\n");
+ printf (" You are allowed to get ahead as much as you want.\n");
+ printf (" If you get too far behind it will stop and resync with you.\n");
+ printf (" You can force it to resync at the next word end by hitting control-H.\n");
+ printf (" Hit ESC to see how you are doing, control-D to end.\n");
+ printf (" (The rightmost space in the printout marks where the average is.\n");
+ printf (" Farther left spaces separate off blocks of letters that are\n");
+ printf (" about twice as probable as the average to occur, three times, etc.)\n");
+ printf ("-T Like -t but see your characters (after they are played).\n");
+ printf ("-s Stop after each character and make sure you get it right. (implies -t)\n");
+ printf ("-q Quietly resyncs with your input (after you make a mistake).\n");
+ printf ("-p NUM (default 0)\n");
+ printf (" Make you get it right NUM times, for penance. (implies -s)\n");
+ printf (" (Yes, NUM = 0 means you can sin all you want.)\n");
+ printf ("-E NUM (default %d)\n", MAX_ERROR_THRESHOLD);
+ printf (" If your count of wrong answers minus right answers for a given character\n");
+ printf (" exceeds this, the program will start prompting you.\n");
+ printf (" If %d or above, it will never prompt. (implies -t)\n", MAX_ERROR_THRESHOLD);
+ printf ("-M NUM (default %d)\n", MAX_BEHINDNESS);
+ printf (" If you get more than this number of characters behind, pause until you\n");
+ printf (" do your next letter. (1 behind is normal, 0 behind means never pause.)\n");
+ printf (" (implies -t)\n");
+ printf ("-d Dynamically speed up or slow down depending on how you are doing.\n");
+ printf (" (if also -s, then -d _only speeds up_!)\n");
+ printf ("-A Add ISO 8850-1 (Latin-1) signs to test set.\n");
+ printf ("-B Add uncommon punctuation to test set.\n");
+ printf ("-S Add uncommon prosigns to test set.\n");
+ printf ("-X Set error volume. Defaults to %g.\n", ERROR_VOLUME);
+ printf (" Error volume 0 means use console speaker.\n");
+ printf ("-x Set frequency of error tone, default 2000.0Hz\n");
+ exit (0);
+ }
+
+ for (ii = 0; ii < TWOFIFTYSIX; ii++)
+ code[ii] = NULL;
+
+/* Load in the morse code code */
+ code[(int) 'a'] = ".-";
+ code[(int) 'b'] = "-...";
+ code[(int) 'c'] = "-.-.";
+ code[(int) 'd'] = "-..";
+ code[(int) 'e'] = ".";
+ code[(int) 'f'] = "..-.";
+ code[(int) 'g'] = "--.";
+ code[(int) 'h'] = "....";
+ code[(int) 'i'] = "..";
+ code[(int) 'j'] = ".---";
+ code[(int) 'k'] = "-.-";
+ code[(int) 'l'] = ".-..";
+ code[(int) 'm'] = "--";
+ code[(int) 'n'] = "-.";
+ code[(int) 'o'] = "---";
+ code[(int) 'p'] = ".--.";
+ code[(int) 'q'] = "--.-";
+ code[(int) 'r'] = ".-.";
+ code[(int) 's'] = "...";
+ code[(int) 't'] = "-";
+ code[(int) 'u'] = "..-";
+ code[(int) 'v'] = "...-";
+ code[(int) 'w'] = ".--";
+ code[(int) 'x'] = "-..-";
+ code[(int) 'y'] = "-.--";
+ code[(int) 'z'] = "--..";
+
+ code[(int) '1'] = ".----";
+ code[(int) '2'] = "..---";
+ code[(int) '3'] = "...--";
+ code[(int) '4'] = "....-";
+ code[(int) '5'] = ".....";
+ code[(int) '6'] = "-....";
+ code[(int) '7'] = "--...";
+ code[(int) '8'] = "---..";
+ code[(int) '9'] = "----.";
+ code[(int) '0'] = "-----";
+
+ /* Punctuation marks */
+ code[(int) '.'] = ".-.-.-";
+ code[(int) ','] = "--..--";
+ code[(int) '?'] = "..--..";
+ code[(int) '/'] = "-..-.";
+ code[(int) '-'] = "-....-";
+
+ if (allpunctuation) {
+ /* Not so commonly used punctuation marks */
+ code[(int) ')'] = "-.--.-";
+ code[(int) '\"'] = ".-..-.";
+ code[(int) '_'] = "..--.-";
+ code[(int) '\''] = ".----.";
+ code[(int) ':'] = "---...";
+ code[(int) ';'] = "-.-.-.";
+ code[(int) '$'] = "...-..-";
+ code[(int) '!'] = "-.-.--";
+ }
+
+ /* Commonly used procedure signs ("prosigns") */
+ code[(int) '+'] = ".-.-."; /* <AR> end of message */
+ code[(int) '*'] = ".-..."; /* <AS> wait, stand-by */
+ code[(int) '='] = "-...-"; /* <BT> (double dash) pause, break for text */
+ code[(int) '('] = "-.--."; /* <KN> over-specified station only */
+ code[(int) '%'] = "...-.-"; /* <SK> end of contact, known also as <VA> */
+
+ if (allprosigns) {
+ /* Not so commonly used procedure signs ("prosigns") */
+ code[(int) '^'] = ".-.-"; /* <AA> new line, the same as :a, ae */
+ code[(int) '#'] = "-...-.-";/* <BK> invite receiving station to transmit */
+ code[(int) '&'] = "-.-.-"; /* <KA> attention */
+ code[(int) '@'] = "...-."; /* <SN> understood */
+ }
+
+ if (international) {
+ /* Not so commonly used international extensions (ISO 8859-1) */
+ code[(int) ((unsigned char)'\344')] = ".-.-"; /* :a (also for ae), the same as <AA> */
+ if (code[(int) '^'] != NULL) code[(int) '^'] = NULL;
+ code[(int) ((unsigned char)'\340')] = ".--.-"; /* `a, oa (danish a with ring over it) */
+ code[(int) ((unsigned char)'\347')] = "----"; /* ch (bar-ch) */
+ code[(int) ((unsigned char)'\360')] = "..--."; /* -d (eth, overstrike d with -) */
+ code[(int) ((unsigned char)'\350')] = "..-.."; /* `e */
+ code[(int) ((unsigned char)'\361')] = "--.--"; /* ~n */
+ code[(int) ((unsigned char)'\366')] = "---."; /* :o (also for oe) */
+ code[(int) ((unsigned char)'\374')] = "..--"; /* :u (also for ue) */
+ code[(int) ((unsigned char)'\376')] = ".--.."; /* ]p (thorn, overstrike ] with p) */
+ code[(int) ((unsigned char)'\247')] = ".-.-.."; /* paragraph */
+ }
+
+ for (ii = 0; ii < TWOFIFTYSIX; ii++)
+ {
+ /* Everything starts equally fresh */
+ randomripe[ii] = 0;
+ /* Start out assuming you know how everything sounds */
+ errorlog[ii] = 0;
+
+ if (code[ii] == NULL)
+ {
+ /* Ensures these will never be chosen */
+ randomfactor[ii] = 0;
+ }
+ else
+ {
+ /* Start out favoring easy ones */
+ randomfactor[ii] = RANDOMBASELEVEL - strlen (code[ii]);
+ if (randomfactor[ii] < 1)
+ randomfactor[ii] = 1;
+ }
+ }
+
+ words_per_minute = WORDS_PER_MINUTE;
+ fwords_per_minute = -1.;
+
+ /* DGHJKLMOPQUVWYZhjkouyz are still available */
+ while ((ch = getopt (argc, argv, "ABC:E:F:IM:N:R:STXabcdef:g:ilmn:p:qrstv:w:x:")) != EOF)
+ switch ((char) ch)
+ {
+ case 'A':
+ international = 1;
+ break;
+ case 'B':
+ allpunctuation = 1;
+ break;
+ case 'C':
+ for (ii = 0; ii < strlen(optarg); ii++)
+ {
+ *(optarg+ii) = tolower(*(optarg+ii));
+ }
+ for (ii = 0; ii < TWOFIFTYSIX; ii++)
+ {
+ if (code[ii] != NULL)
+ {
+ if (strchr(optarg, ii) != NULL)
+ {
+ randomfactor[ii] = RANDOMBASELEVEL - 1;
+ } else {
+ code[ii] = NULL;
+ randomfactor[ii] = 0;
+ }
+ }
+ }
+ break;
+ case 'E':
+ testing = 1;
+ sscanf (optarg, "%d", &error_threshold);
+ if (error_threshold < error_floor)
+ error_floor = error_threshold;
+ break;
+ case 'F':
+ sscanf (optarg, "%f", &fwords_per_minute);
+ break;
+ case 'I':
+ typeaway = LETMESEE;
+ break;
+ case 'M':
+ testing = 1;
+ sscanf (optarg, "%d", &max_behindness);
+ if (max_behindness < 1)
+ max_behindness = 0;
+ break;
+ case 'N':
+ sscanf (optarg, "%d", &wordcount);
+ if (wordcount < 1) wordcount = -1;
+ break;
+ case 'R':
+ sscanf (optarg, "%d", &timeout);
+ timeout *= 60;
+ if (timeout < 1) timeout = -1;
+ break;
+ case 'S':
+ allprosigns = 1;
+ break;
+ case 'T':
+ testing = 1;
+ showtesting = 1;
+ break;
+ case 'X':
+ sscanf (optarg, "%f", &error_volume);
+ break;
+ case 'a':
+ wordsafter = 1;
+ break;
+ case 'b':
+ wordsbefore = 1;
+ break;
+ case 'c':
+ noticebad = 1;
+ break;
+ case 'd':
+ dynamicspeed = 1;
+ break;
+ case 'e':
+ fancyending = 0;
+ break;
+ case 'f':
+ sscanf (optarg, "%f", &frequency1);
+ break;
+ case 'g':
+ sscanf (optarg, "%f", &frequency2);
+ break;
+ case 'i':
+ typeaway = 1;
+ break;
+ case 'l':
+ showletters = 1;
+ break;
+ case 'm':
+ showmorse = 1;
+ break;
+ case 'n':
+ sscanf (optarg, "%d", &wordlen);
+ if (wordlen < 1) wordlen = 1;
+ if (wordlen > MAXWORDLEN) wordlen = MAXWORDLEN;
+ break;
+ case 'p':
+ charbychar = 1;
+ testing = 1;
+ sscanf (optarg, "%d", &tryagaincount);
+ break;
+ case 'q':
+ keepquiet = 1;
+ break;
+ case 'r':
+ randomletters = 1;
+ break;
+ case 's':
+ charbychar = 1;
+ testing = 1;
+ break;
+ case 't':
+ testing = 1;
+ break;
+ case 'v':
+ sscanf (optarg, "%f", &volume);
+ if (volume < 0.)
+ volume = 0.;
+ if (volume > 1.)
+ volume = 1.;
+ break;
+ case 'w':
+ sscanf (optarg, "%f", &words_per_minute);
+ break;
+ case 'x':
+ sscanf (optarg, "%f", &error_frequency);
+ break;
+ default:
+ fprintf (stderr, "Type \"morse\" without arguments to get self-doc!\n");
+ exit (1);
+ break;
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (fwords_per_minute <= 0.)
+ fwords_per_minute = words_per_minute;
+ new_words_per_minute ();
+
+ frequency = frequency1;
+
+ if (BeepInit () != 0)
+ {
+ fprintf (stderr, "Can't access speaker.\n");
+ exit (1);
+ }
+
+ time(&starttime);
+
+ sigset (SIGINT, die);
+ sigset (SIGTERM, die);
+ sigset (SIGQUIT, die);
+ sigset (SIGTSTP, suspend);
+
+ if (testing || typeaway)
+ {
+ openterminal ();
+ }
+
+/*
+ * Do .25 seconds of silence initially to give the workstation time to
+ * get settled after the stress of starting this program and opening
+ * up everything.
+ */
+ tone (frequency, .25, 0.);
+ toneflush ();
+
+ if (typeaway)
+ {
+ testing = 0;
+ showtesting = 0;
+ charbychar = 0;
+ wordsbefore = 0;
+ wordsafter = 0;
+ randomletters = 0;
+
+ notdoneyet = 1;
+
+ while (notdoneyet)
+ {
+ pollyou ();
+
+ for (jj = 0; jj < yourlength; jj++)
+ {
+ yourchar = yourstring[(yourpointer - yourlength + 1 + jj + TESTBUFSZ) % TESTBUFSZ];
+
+ /* Control-D: finished */
+ if (yourchar == (int) '\004')
+ {
+ toneflush ();
+ notdoneyet = 0;
+ break;
+ }
+
+ if (isspace (yourchar))
+ {
+ if (showletters)
+ {
+ toneflush ();
+ printf ("%c", yourchar);
+ fflush (stdout);
+ }
+
+ tone (frequency, inter_word_time, 0.);
+
+ continue;
+ }
+
+ morse (yourchar);
+ }
+ yourlength -= jj;
+
+ if (timeout > 0)
+ if (difftime(time(NULL),starttime) > (double)timeout)
+ break;
+ }
+ }
+ else if (randomletters)
+ {
+ srand48(time (NULL));
+ randexp = 1. / (1. - 1. / (float) (RANDWORDLEN));
+ while (1)
+ {
+ if ((wordcount == 0) || timeout == 0) break;
+
+ dowords (randomletter ());
+
+ if (wordlen == MAXWORDLEN)
+ {
+ /* Knock a few bits off the top so we're sure it won't overflow */
+ /* Shift a few bits because the lower bits stink */
+ /* Add in the time so it doesn't repeat from run to run */
+ randnum = (float) (
+ (((lrand48() >> 9) + (long) (time (NULL))) >> 4)
+ & 0x00FFFFFF);
+ randnum = randnum - randexp * (int) (randnum / randexp);
+ if ((randnum >= 1.) && (linepos != 0)) dowords ((int) ' ');
+ }
+ }
+ }
+ else
+ {
+ if (*argv)
+ {
+ firsttime = 1;
+
+ do
+ {
+ if (!firsttime)
+ {
+ dowords ((int) ' ');
+ }
+ else
+ firsttime = 0;
+
+ if ((wordcount == 0) || timeout == 0) break;
+
+ for (p = *argv; *p; ++p)
+ {
+ dowords ((int) *p);
+ if ((wordcount == 0) || timeout == 0) break;
+ }
+ if ((wordcount == 0) || timeout == 0) break;
+
+ } while (*++argv);
+ }
+ else
+ {
+ while ((ch = getchar ()) != EOF)
+ {
+ dowords (ch);
+ if ((wordcount == 0) || timeout == 0) break;
+ }
+ }
+ }
+
+ if (fancyending)
+ dowords (EOF);
+ else
+ dowords (SILENTEOF);
+
+ fflush (stdout);
+
+ if (testing)
+ {
+ /*
+ * WE'RE completely done, and YOU aren't! Force catch up. (Note if
+ * charbychar = YES we won't get here, since we're always caught up
+ * after each character as it comes out.)
+ */
+ while (testlength > 0)
+ {
+ tone (frequency, catchup_time, 0.);
+ toneflush ();
+ testterminal ();
+ }
+ }
+
+ /* Just to be sure! */
+ toneflush ();
+
+ if (showmorse || wordsbefore || wordsafter || showletters || showtesting)
+ printf ("\n");
+ fflush (stdout);
+
+ if (testing)
+ report ();
+
+/* If you make any mistakes exit with a return code! */
+ cleanup ();
+ return (totalmisscount > 0);
+}
+
+static void
+new_words_per_minute ()
+{
+float wtick, ftick, tick;
+
+ tick = 60. / (words_per_minute * 50);
+
+ /*
+ * In the limit as wpm goes past fwpm, Farnsworth becomes kosher PARIS
+ */
+ if (fwords_per_minute <= words_per_minute)
+ ftick = 60. / (words_per_minute * 50);
+ else
+ ftick = 60. / (fwords_per_minute * 50);
+
+ wtick = (50. * tick - 31. * ftick) / 19.;
+
+ /*
+ * This time is used when the computer is waiting on you to hit a key; it
+ * is useful to scale the granularity with the real overrall words per
+ * minute. This also serves as a measuring rod to see if you are
+ * responding "fast enough". If you are too slow, then obviously you are
+ * having trouble with that character, and should be given it more OFTEN.
+ * Heh heh heh...
+ */
+ catchup_time = tick;
+
+ /*
+ * Things between characters and words go at the "remainder" speed,
+ * whatever space you need to make the sped-up Farnsworth characters come
+ * out with the correct overall words per minute.
+ */
+ inter_char_time = wtick * 3.;
+ inter_word_time = wtick * 7.;
+
+ /* Things within the character go at the Farnsworth speed */
+ intra_char_time = ftick;
+ dot_time = ftick;
+ dash_time = ftick * 3.;
+}
+
+static int tryingagain = 0, slowpoke = 0;
+
+static void
+dowords (int c)
+{
+static int wordc = 0;
+static char word[MAXWORDLEN+1];
+char *wordp;
+int ii;
+int againcount = 0;
+int are_we_repeating;
+
+/*
+ * Increment the line position counter.
+ * If a line gets too long, just cut it off by inserting a new line.
+ */
+ if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE) linepos++;
+ if (isspace (c) && ((linepos + wordlen) >= LINELENGTH)) c = '\n';
+ if (c == '\n') linepos = 0;
+
+ if (isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE)
+ {
+ if (wordc > 0)
+ {
+ if (wordcount > 0) wordcount--;
+ if (timeout > 0)
+ if (difftime(time(NULL),starttime) > (double)timeout)
+ timeout = 0;
+
+ word[wordc] = '\0';
+
+/*
+ * We have just read in a new complete word from the input, (hopefully)
+ * during the time of an inter-word space minus an inter-char space.
+ * Now let's go back and see what happened with the PREVIOUS word,
+ * the one that we had just finished playing. Did the user keep
+ * up with us?
+ */
+#ifdef DEBUG
+ fprintf (stderr, " [%d] ", behindness);
+#endif
+ if (testing && dynamicspeed && !charbychar)
+ {
+ /*
+ * (If charbychar then behindness is ALWAYS 0 at this
+ * point...)
+ */
+ if (behindness == 0)
+ {
+ /* You're a speed demon! Speed up a bit, then! */
+ words_per_minute *= ALITTLEFASTER;
+ new_words_per_minute ();
+ }
+ else if (behindness > WAYBEHIND)
+ {
+ /* You're way behind! Slow way down. */
+ words_per_minute /= ALOTSLOWER;
+ new_words_per_minute ();
+ }
+ else if (behindness > BEHIND)
+ {
+ /* You're behind! Slow down a bit. */
+ words_per_minute /= ALITTLESLOWER;
+ new_words_per_minute ();
+ }
+ }
+
+
+/*
+ * If the user was WAY too far behind stop and catch up with
+ * the "new" word as the first one.
+ */
+ if (testing && (behindness > TOOFARBEHIND || helpmeflag))
+ {
+ if (helpmeflag)
+ printf ("\nOK, let's restart.\n");
+ else
+ printf ("\nYou are too far behind! Let's restart.\n");
+ fflush (stdout);
+
+ toneflush ();
+
+ /* Flush the keyboard buffer */
+ pollyou ();
+
+ /* Forget the past */
+ helpmeflag = 0;
+ behindness = 0;
+ testlength = 0;
+ yourlength = 0;
+
+ /* Give the user a little rest. */
+ sleep (2);
+ printf ("\nWPM now %d\n", (int) (words_per_minute + .5));
+ fflush (stdout);
+ sleep (2);
+ printf ("\nREADY?\n");
+ fflush (stdout);
+ sleep (1);
+ printf ("\nSET\n");
+ fflush (stdout);
+ sleep (1);
+ printf ("\nGO!\n");
+ fflush (stdout);
+ linepos = 0;
+ }
+
+/*
+ * Start treating the new word.
+ */
+ if (wordsbefore)
+ {
+ /* Try to keep your out-of-sync text from getting swirled in */
+ if (showtesting)
+ {
+ printf ("\n");
+ linepos = 0;
+ }
+
+ printf ("%s", word);
+
+ if (showmorse || showletters || wordsafter || showtesting)
+ {
+ printf (" ");
+ for (ii = 0; ii < 16 - (wordc + 2); ii++)
+ {
+ printf (" ");
+ }
+ }
+
+ fflush (stdout);
+ }
+
+ if (testing && charbychar)
+ {
+ againcount = 0;
+ }
+
+ for (wordp = word; *wordp != '\0'; wordp++)
+ {
+ tryingagain = 0;
+
+ tryagain:
+ if (testing && !tryingagain && !showletters &&
+ error_threshold < MAX_ERROR_THRESHOLD &&
+ errorlog[(int) *wordp] > error_threshold)
+ {
+ toneflush ();
+ /* Give them a quick hint */
+ printf ("[%c]", *wordp);
+ fflush (stdout);
+
+ morse (*wordp);
+
+ toneflush ();
+ if (!showmorse)
+ {
+ /* Erase the hint */
+ printf ("\b\b\b \b\b\b");
+ fflush (stdout);
+ }
+ }
+ else
+ {
+ morse (*wordp);
+ }
+
+ if (testing)
+ {
+ if (charbychar)
+ {
+ toneflush ();
+ /* Force catchup */
+ slowpoke = 0;
+ while (behindness > 0)
+ {
+ if (testterminal () && tryagaincount > 0)
+ {
+ /*
+ * OOPS! They got it WRONG! MAKE THEM TRY
+ * AGAIN!
+ */
+ printf ("Try again.\n");
+ linepos = 0;
+ /*
+ * Yeah I know gotos are inelegant but I
+ * don't feel like figuring out the "elegant"
+ * way to do this right now.
+ */
+ againcount = tryagaincount - 1;
+ tryingagain = 1;
+ goto tryagain;
+ }
+ else
+ {
+ /*
+ * They got it right, or they didn't answer
+ * yet.
+ */
+ if (behindness > 0)
+ {
+ /*
+ * They are STILL thinking, the
+ * slowpokes. Wait a bit before trying
+ * again.
+ */
+ tone (frequency, catchup_time, 0.);
+ /*
+ * Keep track of how long they're taking
+ * to answer!
+ */
+ if (slowpoke < SLOWPOKEMAX)
+ slowpoke++;
+ toneflush ();
+ }
+ else if (dynamicspeed && !slowpoke && !tryingagain)
+ {
+ /*
+ * They got it right without errors the
+ * first time and we didn't have to wait
+ * for them! A speed demon! Speed up a
+ * bit, then!
+ */
+ words_per_minute *= ALITTLEFASTER;
+ new_words_per_minute ();
+ }
+ }
+ }
+
+ /* Insufficient penance? */
+ if (againcount > 0)
+ {
+ againcount--;
+ goto tryagain;
+ }
+ }
+ else
+ {
+ testterminal ();
+ /*
+ * Stop if we get more than max_behindness ahead.
+ * max_behindness == 0 means don't worry about them,
+ * they can be as far behind as they want and we
+ * won't stop!
+ */
+ if (max_behindness > 0)
+ {
+ are_we_repeating = 0;
+ while (behindness >= max_behindness)
+ {
+#ifdef DEBUG
+ fprintf (stderr, " (%d) ", behindness);
+#endif
+ if (are_we_repeating)
+ {
+ /*
+ * Pause for a bit so we don't loop too
+ * fast
+ */
+ tone (frequency, catchup_time, 0.);
+ }
+ else
+ {
+ are_we_repeating = 1;
+ }
+ /* Finish playing whatever we're playing */
+ toneflush ();
+ /* And give them another chance */
+ testterminal ();
+ }
+ }
+ }
+ }
+ }
+
+ toneflush ();
+ if (testing)
+ testterminal ();
+
+ if (wordsafter)
+ {
+ printf (" (%s)", word);
+ }
+
+ if (wordsbefore || wordsafter || showmorse)
+ {
+ if (!showmorse && !testing && linepos)
+ {
+ if (wordsafter)
+ linepos += 3;
+ else
+ printf (" ");
+ } else {
+ printf ("\n");
+ linepos = 0;
+ }
+ }
+ else if (showletters || showtesting)
+ {
+ if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE)
+ {
+ if (showletters)
+ printf ("%c", c);
+
+ if (showtesting)
+ testaddchar (c);
+ }
+ }
+
+/*
+ * WHEW! FINISHED QUEUEING THE WORD FOR PLAYING!
+ * Now finish up all the other sundry details...
+ */
+
+ /* Flush the output printing queue... */
+ fflush (stdout);
+ /*
+ * Pause for a bit; this gives the user a sporting chance at
+ * catching up with us.
+ */
+ tone (frequency, SPORTING_RATIO * inter_char_time, 0.);
+ toneflush ();
+ /* Start sounding an inter-word space */
+ tone (frequency, inter_word_time - SPORTING_RATIO * inter_char_time, 0.);
+
+ /* While that silence is playing check if the user has caught up. */
+ if (testing)
+ testterminal ();
+
+ /* We finished this word; reset the word character count */
+ wordc = 0;
+ }
+ else if (!(wordsbefore || wordsafter || showmorse)
+ &&
+ (showletters || showtesting))
+ {
+ if (c != EOF && c != SILENTEOF && c != FREQU_TOGGLE)
+ {
+ if (showletters)
+ printf ("%c", c);
+
+ if (showtesting)
+ testaddchar (c);
+ }
+ }
+
+ if (c == EOF)
+ {
+ morse (EOF);
+ toneflush ();
+ }
+ else if (c == SILENTEOF)
+ {
+ toneflush ();
+ }
+ else if (c == FREQU_TOGGLE)
+ {
+ /* Switch to the other frequency */
+ /* (Won't work from keyboard, only from a file.) */
+ whichfrequ = 1 - whichfrequ;
+ switch (whichfrequ)
+ {
+ case 1:
+ frequency = frequency2;
+ break;
+ case 0:
+ default:
+ frequency = frequency1;
+ break;
+ }
+ }
+ }
+ else
+ {
+ word[wordc++] = c;
+ }
+
+/*
+ * If a word gets too long, just cut it off by inserting a space.
+ * Just call ourselves with the character we wish we'd gotten...
+ */
+ if (wordc == wordlen && !(isspace (c) || c == EOF || c == SILENTEOF || c == FREQU_TOGGLE))
+ dowords ((int) ' ');
+}
+
+/*
+ * Don't try to test the person DURING the call into morse!
+ */
+void
+morse (int c)
+{
+ if ((isalpha (c) && (code[tolower(c)] == NULL)) || ((code[(int) '%'] == NULL) && ((c == EOF) || (c == '\004'))))
+ c = ' ';
+
+ if (showletters)
+ {
+ if ((c == EOF) || (c == '\004'))
+ {
+ if (showmorse)
+ {
+ printf ("<SK>");
+ } else {
+ printf ("%%");
+ }
+ }
+ else if (c == '.' && showmorse)
+ printf ("<DOT>");
+ else if (c == '-' && showmorse)
+ printf ("<DASH>");
+ else if (c == '+' && showmorse)
+ printf ("<AR>");
+ else if (c == '*' && showmorse)
+ printf ("<AS>");
+ else if (c == '=' && showmorse)
+ printf ("<BT>");
+ else if (c == '(' && showmorse)
+ printf ("<KN>");
+ else if (c == '%' && showmorse)
+ printf ("<SK>");
+#ifdef ALLPROSIGNS
+ else if (c == '^' && (code[(int) '^'] != NULL) && showmorse)
+ printf ("<AA>");
+ else if (c == '#' && showmorse)
+ printf ("<BK>");
+ else if (c == '&' && showmorse)
+ printf ("<KA>");
+ else if (c == '@' && showmorse)
+ printf ("<SN>");
+#endif
+ else
+ printf ("%c", c);
+
+ fflush (stdout);
+ }
+
+ if (isalpha (c))
+ {
+ if (testing)
+ testaddchar (c - (isupper (c) ? 'A' : 'a') + 'a');
+ show (code[c - (isupper (c) ? 'A' : 'a') + 'a']);
+ }
+ else if ((c == EOF) || (c == '\004'))
+ {
+ show (code[(int) '%']);
+ }
+ else if (code[c] != NULL)
+ {
+ if (testing)
+ testaddchar (c);
+ show (code[c]);
+ }
+ else
+ {
+ /* Oops! This letter is junk! */
+
+ if (noticebad)
+ {
+ if (showletters)
+ {
+ fflush (stdout);
+ }
+
+ /* Simulate a stumble */
+ tone (frequency, 2. * inter_word_time, 0.);
+ toneflush ();
+ }
+
+ if (showletters)
+ {
+ /* Wipe out what we just printed */
+ fflush (stdout);
+ printf ("\b");
+ printf (" ");
+ printf ("\b");
+ fflush (stdout);
+ }
+
+ if (noticebad)
+ {
+ if (showletters)
+ {
+ /* And replace it with an error message */
+ printf ("<UNKNOWN_CHARACTER>");
+ fflush (stdout);
+ }
+
+ /* Give the error call */
+ show ("........");
+
+ /* Regroup */
+ tone (frequency, inter_word_time, 0.);
+ }
+ }
+
+ if (showmorse)
+ printf (" ");
+ fflush (stdout);
+ toneflush ();
+ tone (frequency, inter_char_time - intra_char_time, 0.);
+}
+
+
+/*
+ * Don't try to test the person WHILE doing dots and dashes!
+ */
+static void
+show (char *s)
+{
+char c;
+
+ while ((c = *s++) != '\0')
+ {
+ tone (frequency, intra_char_time, 0.);
+
+#ifdef FLUSHCODE
+ if (showmorse)
+ toneflush ();
+#endif
+
+ switch (c)
+ {
+ case '.':
+ tone (frequency, dot_time, volume);
+ break;
+ case '-':
+ tone (frequency, dash_time, volume);
+ break;
+ }
+
+ if (showmorse)
+ {
+ printf ("%c", c);
+ fflush (stdout);
+#ifdef FLUSHCODE
+ toneflush ();
+#endif
+ }
+ }
+}
+
+/*
+ * This only gets passed valid characters: ones
+ * that have a morse code associated with them
+ * or ones for which isspace(c) is true.
+ */
+static void
+testaddchar (char c)
+{
+ testpointer = (testpointer + 1) % TESTBUFSZ;
+ teststring[testpointer] = c;
+#ifdef DEBUG
+ fprintf (stderr, " (%c,%d,%d) ", c, testlength, behindness);
+#endif
+ testlength++;
+ if (testlength > TESTBUFSZ)
+ {
+ fprintf (stderr, "\n\nInput buffer queue overflow! Make TESTBUFSZ bigger!\n");
+ fprintf (stderr, "(Or don't fall so far behind)\n");
+
+ die ();
+ }
+
+/*
+ * Since you are never asked to type spaces (you can type them if
+ * you want, but they are ignored) spaces in the input file don't
+ * count against your "behindness".
+ */
+ if (!isspace (c))
+ behindness++;
+}
+
+static void
+youraddchar (char c)
+{
+ yourpointer = (yourpointer + 1) % TESTBUFSZ;
+ yourstring[yourpointer] = c;
+#ifdef DEBUG
+ fprintf (stderr, " <%c,%d> ", c, yourlength);
+#endif
+ yourlength++;
+ if (yourlength > TESTBUFSZ)
+ {
+ fprintf (stderr, "\n\nKeyboard typeahead buffer queue overflow! Make TESTBUFSZ bigger!\n");
+ fprintf (stderr, "(Or don't type so far ahead... how did you expect to get them right anyway?)\n");
+
+ die ();
+ }
+}
+
+static void
+pollyou (void)
+{
+ int ii, num;
+ char *string;
+
+ num = readterminal (&string);
+
+ for (ii = 0; ii < num; ii++)
+ youraddchar (string[ii]);
+}
+
+static int
+testterminal (void)
+{
+int testinc, yourinc;
+int correctchar, yourchar, yourcharnocase;
+int errorcount;
+int resync;
+
+ errorcount = 0;
+
+/*
+ * There is nothing in the input file queue right now,
+ * so we can't process any of your keystrokes.
+ * Defer processing until we can catch up with YOU!
+ */
+ if (testlength == 0)
+ return errorcount;
+
+ /* We're ready for you; but are you ready for us? */
+ pollyou ();
+
+/*
+ * Process your entries and the input queue entries in parallel
+ */
+ if (yourlength > 0 && testlength > 0)
+ {
+ for (testinc = 0, yourinc = 0;
+ testinc < testlength && yourinc < yourlength;
+ testinc++, yourinc++)
+ {
+ correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
+
+ /*
+ * The latter half of this if shouldn't be necessary, but just in
+ * case...
+ */
+ if (isspace (correctchar) || code[correctchar] == NULL)
+ {
+ if (showtesting)
+ {
+ printf ("%c", correctchar);
+ fflush (stdout);
+ }
+
+ /* White space doesn't count for "behindness" */
+ behindness++;
+ /* The _other_ pointer wasn't used; don't increment it. */
+ yourinc--;
+
+ /* Short circuit the loop */
+ continue;
+ }
+
+
+ yourchar = yourstring[(yourpointer - yourlength + 1 + yourinc + TESTBUFSZ) % TESTBUFSZ];
+ if (isalpha (yourchar))
+ yourcharnocase = yourchar - (isupper (yourchar) ? 'A' : 'a') + 'a';
+ else
+ yourcharnocase = yourchar;
+
+ /* Did you type something rude? If so, just ignore it. */
+ if (isspace (yourchar) || code[yourcharnocase] == NULL)
+ {
+ /* ESCAPE: dump status info */
+ /* Control-D: dump status info and then bye bye */
+ /* Control-H: force restart */
+ if (yourchar == '\033' || yourchar == (int) '\004')
+ {
+ report ();
+
+ if (yourchar == (int) '\004')
+ die ();
+ }
+ else if (yourchar == '\b')
+ {
+ helpmeflag = 1;
+ }
+
+ /* The _other_ pointer wasn't used; don't increment it. */
+ testinc--;
+ /* Short circuit the loop */
+ continue;
+ }
+
+ if (keepquiet > 1)
+ {
+ for (resync = testinc; resync < testlength; resync++)
+ if ( yourcharnocase == teststring[(testpointer - testlength + 1 + resync + TESTBUFSZ) % TESTBUFSZ])
+ {
+ for (; testinc < resync; testinc++)
+ {
+ correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
+ if (isspace (correctchar) || code[correctchar] == NULL)
+ {
+ if (showtesting) printf ("%c", correctchar);
+ } else {
+ printf("%s%c%s", enter_standout_mode, correctchar, exit_standout_mode);
+ }
+ }
+ fflush (stdout);
+ correctchar = teststring[(testpointer - testlength + 1 + testinc + TESTBUFSZ) % TESTBUFSZ];
+ keepquiet = 1;
+ break;
+ }
+ }
+
+ if (yourcharnocase != correctchar)
+ {
+ errorcount++;
+ totalmisscount++;
+
+ /*
+ * Record that you are having trouble with these.
+ */
+ errorlog[correctchar]++;
+ if (code[yourcharnocase] != NULL &&
+ errorlog[yourcharnocase] < MAX_ERROR_THRESHOLD)
+ errorlog[yourcharnocase]++;
+
+ if (keepquiet > 0)
+ {
+ printf("%s%c%s", enter_standout_mode, correctchar, exit_standout_mode);
+ fflush (stdout);
+ keepquiet++;
+ } else {
+ /*
+ * Scold them for their mistake.
+ */
+ printf ("\n");
+
+ if (error_volume) {
+ /* Beep using the tone generator */
+ toneflush ();
+ tone (error_frequency, 0.1, error_volume);
+ } else
+ /* Beep using control-G */
+ printf ("\007");
+ printf ("%c (%s) for %c (%s)\n",
+ yourchar, code[yourcharnocase],
+ correctchar, code[correctchar]);
+ fflush (stdout);
+ linepos = 0;
+ }
+
+ if (charbychar)
+ {
+ /* Give them a bit of time to think about their error */
+ tone (frequency, inter_word_time, 0.);
+ toneflush ();
+ }
+ if (dynamicspeed && !charbychar)
+ {
+ /*
+ * Slow down. Doesn't make sense to slow down for errors,
+ * though, if you've got all the time you want to think
+ * about each one.
+ */
+ words_per_minute /= ERRORSLOWER;
+ new_words_per_minute ();
+ }
+ if (randomletters && !tryingagain)
+ {
+ /*
+ * Ask ones that confused you more often!
+ */
+ if (code[yourcharnocase] != NULL)
+ {
+ randomfactor[yourcharnocase] += (3 * RANDOMINCWORSE / 2);
+ if (randomfactor[yourcharnocase] > RANDOMMAX)
+ randomfactor[yourcharnocase] = RANDOMMAX;
+ }
+
+ randomfactor[correctchar] += RANDOMINCWORSE * 2;
+ if (randomfactor[correctchar] > RANDOMMAX)
+ randomfactor[correctchar] = RANDOMMAX;
+ }
+ }
+ else
+ {
+ /*
+ * Record that you got this right.
+ */
+ if (!tryingagain)
+ {
+ totalhitcount++;
+ if (errorlog[correctchar] > error_floor)
+ errorlog[correctchar]--;
+ }
+
+ if (showtesting)
+ {
+ printf ("%c", yourchar);
+ fflush (stdout);
+ }
+
+ if (randomletters && !tryingagain)
+ {
+ if (slowpoke == SLOWPOKEMAX)
+ {
+ printf ("\nNice to have you back again, I was getting bored!\n");
+ linepos = 0;
+ }
+ else if (slowpoke >= SLOWPOKE * 3)
+ {
+ /*
+ * Did you take too long thinking about it? If so,
+ * you probably need to be asked this one more
+ * often...
+ */
+ randomfactor[correctchar] += (3 * RANDOMINCWORSE / 2);
+ if (randomfactor[correctchar] > RANDOMMAX)
+ randomfactor[correctchar] = RANDOMMAX;
+
+ /*
+ * Hits this slow shouldn't count! You were obviously
+ * just guessing! (But it doesn't count as an error
+ * either.)
+ */
+ totalhitcount--;
+ }
+ else if (slowpoke > FASTPOKE)
+ {
+ randomfactor[correctchar] +=
+ (slowpoke * RANDOMINCWORSE) / (2 * SLOWPOKE);
+ if (randomfactor[correctchar] > RANDOMMAX)
+ randomfactor[correctchar] = RANDOMMAX;
+ }
+ else if (slowpoke <= (FASTPOKE / 2))
+ {
+ /*
+ * Ask ones that you quickly answer correctly less
+ * often!
+ */
+ randomfactor[correctchar] -= (3 * RANDOMINCBETTER / 2);
+ /*
+ * Don't let randomfactor hit 0, or you'll NEVER be
+ * asked this one AGAIN!
+ */
+ if (randomfactor[correctchar] < 1)
+ randomfactor[correctchar] = 1;
+ }
+ else if (slowpoke <= FASTPOKE)
+ {
+ randomfactor[correctchar] -= (RANDOMINCBETTER / 2);
+ if (randomfactor[correctchar] < 1)
+ randomfactor[correctchar] = 1;
+ }
+ }
+ }
+
+ }
+ testlength -= testinc;
+ behindness -= testinc;
+ yourlength -= yourinc;
+ }
+
+/*
+ * If there are some extra white space characters in the input queue
+ * it's OK, we'll get to them next time or we'll clean them out at the
+ * end.
+ */
+ return errorcount;
+}
+
+
+/*----------------------------------------*/
+
+static void
+tone (float hertz, float duration, float amplitude)
+{
+ Beep ((int) (duration * 1000), (int) (amplitude * 100), (int) hertz);
+}
+
+
+static void
+toneflush (void)
+{
+ BeepWait ();
+}
+
+
+/*----------------------------------------*/
+
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <termio.h>
+struct termio oldtermgtty;
+struct termio termgtty;
+static char *terminal = "/dev/tty";
+static int termfd;
+static int termopen = 0;
+static int oldflgs, newflgs;
+
+static void
+openterminal (void)
+{
+ /* get parameters and open terminal */
+
+ termfd = open (terminal, O_RDWR | O_NDELAY, 0);
+ ioctl (termfd, TCGETA, &termgtty);
+ oldtermgtty = termgtty;
+ if (typeaway != LETMESEE)
+ termgtty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
+ termgtty.c_lflag &= ~ICANON;
+ termgtty.c_cc[VMIN] = 1;
+ termgtty.c_cc[VTIME] = 0;
+ ioctl (termfd, TCSETAW, &termgtty);
+ termopen = 1;
+}
+
+static int
+readterminal (char **string)
+{
+/* This must be declared static! */
+static char line[TESTBUFSZ];
+int n;
+
+ n = read (termfd, line, sizeof (line) - 1);
+
+ if (n > 0)
+ {
+ line[n] = '\0';
+ *string = line;
+ }
+ else
+ *string = NULL;
+
+ return n;
+}
+
+static void
+closeterminal (void)
+{
+ ioctl (termfd, TCSETAW, &oldtermgtty);
+ close (termfd);
+}
+
+static void
+die (void)
+{
+ cleanup ();
+ exit (1);
+}
+
+static void
+cleanup (void)
+{
+ if (termopen)
+ closeterminal ();
+ BeepCleanup ();
+}
+
+static void
+suspend (int sig)
+{
+ sigset (SIGTSTP, suspend);
+ cleanup ();
+ kill (getpid (), SIGSTOP);
+ if (termopen)
+ openterminal ();
+ BeepResume ();
+}
+
+
+/*----------------------------------------*/
+
+static int
+randomletter (void)
+{
+int ii;
+int sum, sum2;
+long ranspot;
+static int lasttime = -1;
+static long norepeat;
+
+/*
+ * This keeps the not-so-random random number generator from ignoring
+ * certain characters forever!
+ */
+ norepeat = ((long) time (NULL) / 31) % 17291;
+
+/*
+ * All the usable letters get one unit riper.
+ */
+ for (ii = 0; ii < TWOFIFTYSIX; ii++)
+ {
+ if (randomfactor[ii] > 0)
+ {
+#ifdef DEBUGG
+ fprintf (stderr, "%c: %d %d\n",
+ (char) ii, randomfactor[ii], randomripe[ii]);
+#endif
+ randomripe[ii]++;
+ }
+ }
+
+ sum = 0;
+ for (ii = 0; ii < TWOFIFTYSIX; ii++)
+ sum += (randomfactor[ii] + (int) (randomripe[ii] / RIPECOUNT));
+
+/*
+ * The low bits of random aren't very random, I don't care WHAT
+ * the manual claims.
+ */
+ do
+ {
+ ranspot = ((lrand48() >> 4) % sum + norepeat) % sum;
+
+ sum2 = 0;
+ for (ii = 0; ii < TWOFIFTYSIX - 1; ii++)
+ {
+ sum2 += (randomfactor[ii] + (int) (randomripe[ii] / RIPECOUNT));
+
+ if (sum2 > ranspot)
+ break;
+ }
+ /* Do it again if you got the same as last time! */
+ } while (ii == lasttime);
+
+ /* This one is FRESH again. */
+ randomripe[ii] = 0;
+ /* Remember for next time. */
+ lasttime = ii;
+
+ return ii;
+}
+
+static int
+rancomp (const void *elem1, const void *elem2)
+{
+ register int *e1 = (int *)elem1;
+ register int *e2 = (int *)elem2;
+ float a, b;
+
+
+ a = (randomfactor[(*e1)] + (randomripe[(*e1)] / (float) RIPECOUNT));
+ b = (randomfactor[(*e2)] + (randomripe[(*e2)] / (float) RIPECOUNT));
+
+ if (a == b)
+ return 0;
+ else if (a > b)
+ return -1;
+ else
+ return 1;
+}
+
+static void
+report (void)
+{
+ int ii, jj, count;
+ float sum;
+ int randomstr[TWOFIFTYSIX];
+
+ printf ("\nCurrent words per minute: %.1f\n", words_per_minute);
+
+ printf ("Total hits %d, misses %d", totalhitcount, totalmisscount);
+ if (totalmisscount > 0)
+ printf (", hit per miss ratio %.1f\n", (float) totalhitcount / (float) totalmisscount);
+ else
+ printf ("\n");
+
+ if (randomletters)
+ {
+ printf ("Most to least frequent choices:\n");
+ count = 0;
+ sum = 0.;
+ for (ii = 0; ii < TWOFIFTYSIX; ii++)
+ {
+ if (randomfactor[ii] > 0)
+ {
+ sum += (randomfactor[ii] + (randomripe[ii] / (float) RIPECOUNT));
+ randomstr[count] = ii;
+ count++;
+ }
+ }
+
+ qsort ((char *) randomstr, count, sizeof (randomstr[0]), rancomp);
+
+ for (ii = 0; ii < count; ii++)
+ {
+/*
+ * Insert a space for each jump across an integer.
+ * The normalization (count/sum) ensures that if all
+ * letters were equally probable, they would all have value 1.
+ * Since they are not generally equally probable, then 1 is just the average.
+ * Thus the rightmost space in the printout marks where the average is.
+ * Further left spaces separate off blocks of letters that are approximately
+ * twice as probable as the average, three times, etc.
+ */
+ if (ii > 0)
+ {
+ for (jj = 0; jj <
+ (int) (
+ (randomfactor[randomstr[ii - 1]] + (randomripe[randomstr[ii - 1]] / (float) RIPECOUNT))
+ * count / sum) -
+ (int) (
+ (randomfactor[randomstr[ii]] + (randomripe[randomstr[ii]] / (float) RIPECOUNT))
+ * count / sum);
+ jj++)
+ printf (" ");
+ }
+ printf ("%c", (char) randomstr[ii]);
+ }
+ printf ("\n");
+ }
+
+ /*
+ * So you don't get penalized for being "slow" after this.
+ */
+ if (charbychar)
+ slowpoke = SLOWPOKEMAX + 1;
+
+ fflush (stdout);
+}