[alt.sources] Touch typing game

spear@druco.ATT.COM (Steve Spearman) (02/01/89)

Here's a little screen-oriented typing game I wrote
a while back.  It's curses-based and known to run
on System V.  No man page but has help.

---cut here---
#To extract the files contained herein, just execute
#this file via the command:
#  sh thisfile
#where 'thisfile' is the name of this file
echo extracting game.c
cat >game.c <<'!FuNkYsTuFf'
/*  
 *  TYPEFAST 
 *  Improve your typing the fun way 
 *	by druco!spear  11/18/86 
 */
/* Fixes/Enhancements: */

#include <stdio.h>
#include <curses.h>

/* non-int external functions used */
extern long time();

/*Declarations follow for non-int functions */
extern void screeninit(),getsetup(),checkhit(),showscore(),cleartarget();

#define MAXLEN		20	/*MAX word length */
#define MAXSIZE		30	/*MAX word length + some fudge */
#define	MAXTARGET	10	/*MAX target words*/
#define	MAXMISS		10	/*Number of missed words to end game */
#define MAXROW		23	/*Maximum row allowed */

#define	boolean	int	

extern char	*words[];	/* word list from other file */
extern int	initwords();	/* word list initializer from other file */

struct word {			/* the structure of each word on the screen*/
	char text[MAXLEN];	/* word text */
	int row;		/* screen row position */
	int col;		/* screen column position */
	int len;		/* length of word */
	boolean active;		/* indicates if word is active or not */
};
struct word target[MAXTARGET];

/* the following are set in the 'getsetup' function */
int addtarget,	/* Add a new target every x hits*/
    movedown,	/* move top row down every x hits */
    movebottom,	/* move botom row down every x hits */
    startwords;	/* Starting number of target words*/
int lowrow;	/* lowest row number for a word */
int highrow;	/* highest row number for a word */

/* internal variables */
int maxword;		/* number of words in list */
int targets=0;		/* total active targets */
int missed=0;		/* total misses */
int total=0;		/* total hits */
long strokes=0;		/* total key stroke hits on words gotten*/
long allstrokes=0;	/* total key stroke hits including bad*/
long starttime;		/* time we started typing */

main(argc, argv)
int argc;
char *argv[];
{
        extern char *optarg;
        extern int   opterr;
        int      c;

        opterr = 0;     /* suppress the 'getopt' error messages */

        while ((c = getopt(argc, argv, "?")) != EOF)
          switch (c) {
            case '?' : 
            default  : 	printf("Usage: %s \n", argv[0]);
			exit(1);
          }

	initialize();		/* initialize curses */
	getsetup();		/* set difficulty variables */
	screeninit();		/* put up initial display */
	maxword = initwords();	/* get the word list */

	for (targets=0;targets < startwords; targets++) {
		newtarget(targets);	/* set up a target word */
	}
	starttime = time ((long *) 0);

	while (missed < MAXMISS) {

		targetmove();
		refresh();
		napms(600);	/* suspend for so many milliseconds */
		while ((c=getch()) >= 0) {
			if (c == '\033') {	/* ESCAPE */
				doend();	/* end the game with states*/
			}
			checkhit(c);
		}
		  
	}
	move(21,0);
	clrtoeol();
	mvprintw(21,0,"END of Game: you missed the maximum %d words",missed);
	doend();
}

/* DOEND
 *
 * Print out the ending statistics and exit gracefully
 */
int 
doend()
{
	mvprintw(22,0,"WPM on words typed correctly: %d     WPM on all words typed: %d",wpm(strokes),wpm(allstrokes));
	goodbye();
}

/* TARGETMOVE
 *
 * Moves all targets down a row and checks for any that have reached
 * the bottom.  If they have, it is counted and a new word is created.
 */
int
targetmove()
{
register int num;
for (num = 0; num < MAXTARGET; num++) {
	if ( ! target[num].active) 
		continue;
	if (movetarget(num)) {
		newtarget(num);
		missed++;
		flash();
		showscore();
	}
}
}

/* CLEARWORD
 *
 * This function clears out the screen position of the word
 * whose structure index is passed.
 */
void
clearword(num)
int num;
{
register int i;
	move(target[num].row,target[num].col);
	for (i=0; i < target[num].len; i++) {
		addch(' ');
	}
}

/* MOVETARGET
 *
 * This move the word structure whose index is passed down one row.
 * If this move brings it to the edge of the screen, 1 is returned,
 * otherwise, 0 is returned.
 */
int
movetarget(num)
int num;
{
	clearword(num);		/* clear the old word on screen */
	target[num].row++;	/* move down a row */
	if(target[num].row >= MAXROW)
		return(1);
	mvprintw(target[num].row,target[num].col,target[num].text);
	return(0);
}


/* NEWTARGET
 *
 * This function produces a new word structure in the structure whose
 * index is passed.   The word is chosen randomly and is placed randomly
 * subject to constraints of screen placement and word proximity.
 */
int
newtarget(num)
int num;
{
register int i;
int	wordno;
boolean	bad;
int	safety;
int	samecol;

        wordno = (rand() % maxword);		/* pick a word number */
	strcpy(target[num].text,words[wordno]);	/* copy the word into struct */
	target[num].len=strlen(target[num].text);	/* calculate length */
	bad = 1;	/* flag that is cleared when we get a good word */
	safety = 0;
	while (bad) {
		if (++safety > 5000)
			error("New word not placable");
		/* try to get a good row and column */
		target[num].row= (rand() % (highrow - lowrow + 1)) + lowrow;
		target[num].col= (rand() % (80-target[num].len));
		bad = 0;
		/* check all other words to make sure no overlaps */
		for (i=0; i< MAXTARGET; i++) {
			if ( (! target[i].active) || (i == num) )
				continue;	/* don't check against self */
			samecol = 0;
			if ( (target[num].col <= target[i].col) &&
			 ((target[num].col + target[num].len + 1) >= target[i].col))
				samecol = 1;	/* columns touch/overlap */
			if ( (target[i].col <= target[num].col) &&
			  ((target[i].col + target[i].len + 1) >= target[num].col))
				samecol = 1;	/* columns touch/overlap */
			/* if columns overlap, check that the rows are */
			/* separated by at least one blank to avoid problems */
			/* with erasing or overwriting the other word during */
			/* movement */
			if ( samecol && 
			  (abs(target[num].row - target[i].row) < 2)) {
				bad = 1;	
				break;
			}
		}
	}
	target[num].active = 1;
}

/* CHECKHIT
 *
 * This routine is passed a char each time one is typed.  It collects
 * words and compares the word typed with all words on the screen.  If
 * a match is found, the match lowest on the screen is considered hit
 * and the routine gotit() is called to handle the hit.
 */
void
checkhit(c)
char c;
{
static int count = 0;		/* character counter */
static char line[MAXSIZE];	/* the line you are typing */
int couldhit[MAXTARGET];	/* some booleans */
register int num;
register int row;		/* holds row of number we shot */
int shot;			/* holds the number of word we shot */
/* check for a word separator to end the word */
if ( (c != ' ') && (c != '\n') && ( c != '\r')) {
	allstrokes++;	/* count all key strokes */
	/* character is not a separator, so just collect it */
	if (count >= (MAXSIZE - 2))	/* if line too long, */
		return;	/* wait for next separator to begin again */
	line[count++] = c;
	return;
}
/* if we got here, we got a separator and have a word */
if ( count == 0 )	/* null word */
	return;
line[count] = '\0';
count = 0;	/*reset for next word */
shot = -1;
row = 0;	/* keep track of largest row of word shot */
for (num=0;num < MAXTARGET; num++) {
	if ( ! target[num].active )	/* only check active words */
		continue;
	/* check to see if the word matches */
	if (strncmp(target[num].text,line,MAXSIZE) == 0) {
			/* got a word! */
			/* check if this is lower on screen than any others*/
			if (target[num].row > row) {
				row = target[num].row;
				shot = num;
			}
	} 
}
if ( shot == -1 )
	return;
gotit(shot);	/* handle the word we shot */
}

/* GOTIT
 *
 * We have gotten the word whose number is passed.  Now produce any
 * appropriate effects and clear out the word.  Also, update score
 * and create a new replacement word.
 */
int 
gotit(num)
int num;
{
register int i,x,y;
char oldc = '\0';
int incmove;
	total++;			/* keep track of words gotten */
	x = target[num].col + (target[num].len / 2);	/* center of word */
	y = target[num].row;		/* row of word */
	strokes += target[num].len;	/* count letters */
	incmove = 1;
	if (baudrate() < 9600)
		incmove = 2;
	if (baudrate() <= 1200)
		incmove = 4;
	if (baudrate() <= 300)
		incmove = 8;
	for (i = MAXROW ; i >= y; i -= incmove) {	/* from the screen bottom to word */
		if (i < MAXROW)		/* restore previous position */
			mvaddch(i + incmove,x,oldc);
		oldc = mvinch(i,x);	/* store old char for restore */
		mvaddch(i,x,'^');	/* put out 'missile' */
		refresh();
	}
	if (oldc)
		mvaddch(i + incmove,x,oldc);	/* clear last 'missile' */
	if (((total % addtarget) == 0 ) && ((targets + 1) < MAXTARGET))
		newtarget(targets++);
	if (((total % movedown) == 0 ) && (highrow > (lowrow + 1)) )
		lowrow++;
	if (((total % movebottom) == 0 ) && (highrow < (MAXROW - 2)) )
		highrow++;
	showscore();
	cleartarget(num);
	newtarget(num);
}

/* CLEARTARGET
 *
 * Make an explosion where the word was if baudrate allows, and clear
 * the screen at the word location
 */
void
cleartarget(num)
int num;
{
register int i;
int j;
char c;
	c = '*';	/* char to produce an 'explosion' */
	for (j = 0; j <= 2; j++) {
		if ((j < 2) && (baudrate() < 4800))
			continue;
		if (j == 1)
			c = '#';	
		if (j == 2)
			c = ' ';	/* last time we clear the spot */
		move(target[num].row,target[num].col);
		for (i=0;i< target[num].len; i++) {
			addch(c);
		}
		refresh();
	}
}

/* SHOWSCORE
 *
 * Show the total words gotten and words missed
 */
void
showscore()
{
	mvprintw(2,30,"Hits: %d",total);
	mvprintw(2,42,"Misses: %d",missed);
}

/* SCREENINIT
 *
 * Initialize the screen for the game
 */
void
screeninit()
{
	clear();
        srand(time(0));	/* set up random numbers */
	standout();
	mvprintw(1,35,"**TYPEFAST**");
	mvprintw(MAXROW,1,"<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>");
	standend();
	showscore();
	refresh();
}

/* WPM
 *
 * Calculate total wpm by taking the number of key strokes passed,
 * calculating elapsed time, and figuring 5 chars per word.
 */
int
wpm(tstrokes)
long tstrokes;
{
long totaltime;
float result;
int iresult;
	totaltime = time((long *) 0);
	totaltime -= starttime;	/* figure total time in seconds */
	if (totaltime < 1)
		return(0);
	result = (tstrokes * 60.0)/ (5.0 * totaltime);
	iresult = (int) result;
	return(iresult);
}

/* GETSETUP
 *
 * This routine sets up the initial screen and gives instructions,
 * and prompts for game level.  It then sets several game variables
 * for number of targets, locations, and how often variables are
 * changed to increase difficulty.
 */
void
getsetup()
{
char c;
	clear();
	standout();
	mvprintw(0,0,"<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>");
	mvprintw(MAXROW-1,0,"<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>");
	mvprintw(5,30,"The TYPEFAST Game");
	standend();
	mvprintw(12,10,"Do you need instructions?  Press y or n (no RETURN)");
	refresh();
	while (((c = getch()) != 'y') && (c != 'n')) ;
	move(12,10);
	clrtoeol();
	if (c == 'y') {
		move(8,0);
		printw("Words will move down the screen toward the bottom.  You must type\n");
		printw("each word before it reaches the bottom.  End each word you type with\n");
		printw("a SPACE or a RETURN.  When you successfully type a word in time a\n");
		printw("new one will take its place somewhere on the screen.  The number\n");
		printw("of words will increase as you go along and the words will get closer\n");
		printw("to the bottom of the screen.  At the end your Words Per Minute is shown.\n");
	}
	standout();
	mvprintw(16,10,"Press the letter of the level of difficult you wish (no RETURN)");
	standend();
	mvprintw(18,33,"1-Easy");
	mvprintw(19,33,"2-Normal");
	mvprintw(20,33,"3-Hard");
	mvprintw(21,33,"q-Quit");
	refresh();
	while (1) {
		c = getch();
		if ((c >= '1') && (c <= '3'))
			break;
		if (c == 'q')
			error("Game aborted as requested");
	}
	switch (c) {
	 case '1':
		addtarget=  60;	/*Add a new target every x hits*/
		movedown=   50;	/*move top row down every x hits */
		movebottom=130;	/*move botom row down every x hits */
		startwords=  2;	/*Starting target words*/
		lowrow =     4;	/* lowest row number for a word */
		highrow =   15;	/* highest row number for a word */
		break;
	 case '2':
		addtarget=  50;	/*Add a new target every x hits*/
		movedown=   50;	/*move top row down every x hits */
		movebottom=100;	/*move botom row down every x hits */
		startwords=  3;	/*Starting target words*/
		lowrow =     6;	/* lowest row number for a word */
		highrow =   17;	/* highest row number for a word */
		break;
	 case '3':
		addtarget=  50;	/*Add a new target every x hits*/
		movedown=   30;	/*move top row down every x hits */
		movebottom= 60;	/*move botom row down every x hits */
		startwords=  5;	/*Starting target words*/
		lowrow =    10;	/* lowest row number for a word */
		highrow =   18;	/* highest row number for a word */
		break;
	 default:
		error("Internal game switch error");
	}
	clear();
}
!FuNkYsTuFf
echo extracting makefile
cat >makefile <<'!FuNkYsTuFf'
# Makefile for typefast
OBJ=game.o utils.o words.o

all:	$(OBJ)
	cc -o typefast $(OBJ) -lcurses

game.o:	game.c
	cc -c -O game.c

!FuNkYsTuFf
echo extracting utils.c
cat >utils.c <<'!FuNkYsTuFf'
#include <stdio.h>
#include <curses.h>
#include <sys/signal.h>

/* game - utilities
 *
 * by Steve Spearman
 */

/********************************************************
 *
 *  error ()
 * 
 *  Error() halts with an error message which is passed
 *  as an argument
 ********************************************************/

int
error(reason)
char reason[];
{
	move(22,0);
	clrtoeol();
	printw("Fatal Game Error - %s\n",reason);
	refresh();
	nodelay(stdscr,FALSE); 
	endwin();
	exit(1);
}

int
goodbye()
{
	move(23,0);
	refresh();
	nodelay(stdscr,FALSE); 
	endwin();
	exit(0);
}

int
sighandle()
{
	error("Unexpected signal received");
}

int
initialize()
{
	initscr();	/* initialize screen*/
	cbreak();	/* get characters in immediate mode*/
	nodelay(stdscr,TRUE); 
	noecho(); 	/* don't echo input */
	if (clear()==ERR)
		error("Your terminal to too dumb");	/* clear the screen*/
	if (clrtoeol()==ERR)
		error("Your terminal to too dumb");	/* check for function*/
	if (move(23,79)==ERR)
		error("Your terminal to too small");	/* check for size*/
	signal(SIGINT,sighandle);
	signal(SIGQUIT,sighandle);
	signal(SIGILL,sighandle);
	signal(SIGBUS,sighandle);
	signal(SIGTRAP,sighandle);
	signal(SIGIOT,sighandle);
	signal(SIGEMT,sighandle);
	signal(SIGSEGV,sighandle);
	signal(SIGSYS,sighandle);
	signal(SIGTERM,sighandle);
}


int
mygetchar(c)
int *c;
{
	if (read(0,c,1) <= 0)
		return(0);
	*c &= 0177;
	return(1);
}
!FuNkYsTuFf
echo extracting words.c
cat >words.c <<'!FuNkYsTuFf'
/* FASTTYPE file words.c
 *
 * This file contains the word read-in and initialization functions
 */

/* WORDS
 * The default list of words to type.
 * Must terminate with a "\0" 
 */
char *words[]={"now","how","are","there","sew","tough",
	"and", "and", "and", "all", "are",
	"the", "the", "the", "we","us", "will", "did",
	"why", "when", "thus", "hard", "easy", "small",
	"move", "say", "tell", "try", "keep", "do",
	"concrete","abstract","real","ideal",
	"accept","reject", "grow","new","renew","fresh",
	"rapid","change","careful","broad",
	"massive","huge", "doubt","dubious","certain",
	"past","present","future", "obvious","consider","finish","complete",
	"actual","add","adequate","afford",
	"worse","bad","basic","fun","brief",
	"uniform", "vital","viable", "worth", "clear","lucid","simple",
	"stud","fox","quick","brown","dog","lazy","lament",
	"command","entire","earth","purple","his","coward",
	"wonder","where","why","and","blue","book","letter",
	"girl","boy","stood","read","ponder","summary","escape",
	"terminal","game","type","show","status","state","line",
	"print","bell","lock","keyboard","shift","reset","normal",
	"video","reverse","follow","number","speaker","always",
	"able","ability", "about", "above", "absolute",
	"abstract", "accept", "accomplish", "accord","as",
	"accurate", "across", "active", "at", "activity",
	"actual", "adapt", "add", "adequate", "adjust",
	"advent", "affirm", "afford", "after", "agenda",
	"aggregate", "agree", "allocate", "alternate", "although",
	"ambiguous", "amount", "amply", "analogous", "analysis",
	"anomaly", "apparent", "appear", "apply", "approval",
	"approximate", "arbitrary", "argument", "as", "assign",
	"associate", "assume", "at", "attach", "attend",
	"attendant", "attitude", "attractive", "authorize", "automatic",
	"awfully", "bad", "badly", "barely", "bare",
	"basis", "because", "beginning", "behind", "below",
	"beneficial", "benefit", "better", "blatant", "brief",
	"broad", "broadly", "by", "candidate", "less",
	"capable", "careful", "carefully", "care", "casual",
	"category", "central", "certain", "certainly", "change",
	"chart", "charter", "cleanly", "clear", "close",
	"coarse", "collateral", "collect", "command", "commence",
	"commit", "comparably", "competent", "complaint", "complete",
	"complicate", "comprehend", "concern", "concise", "conclusion",
	"confess", "confide", "consider", "constant", "constraint",
	"construct", "contact", "contrary", "convenient", "correct",
	"criteria", "crude", "curious", "current", "date",
	"deadline", "debate", "decide", "decision", "deeply",
	"deficient", "defined", "delivery", "demand", "depend",
	"development", "differ", "direct", "disposal", "diverse",
	"doubt", "doubtful", "dubious", "durable", "dynamic",
	"early", "effect", "effective", "efficient", "elegant",
	"employ", "enclosed", "encourage", "endeavor", "end",
	"enhance", "entertain", "entire", "entirely", "environment",
	"epistomological", "equally", "equipment", "equitable", "erroneous",
	"essence", "essential", "eternal", "evaluate", "evenly",
	"event", "evidence", "evident", "evidently", "exact",
	"exactly", "excellent", "excess", "existence", "experience",
	"experiment", "explicit", "expression", "extreme", "failure",
	"fair", "faith", "fault", "fear", "field", "full",
	"file", "final", "finance", "finish", "finite",
	"firmly", "follow", "for", "force", "forecast",
	"foresee", "formal", "frank", "free", "fresh",
	"from", "full", "fully", "fundamental", "further",
	"future", "general", "global", "goal", "good",
	"gradual", "great", "ground", "grow", "guidance",
	"guide", "harm", "help", "hierarchy", "high",
	"hopeful", "huge", "idea", "ideal", "ideally",
	"identical", "idly", "if", "imaginable", "immediate",
	"immensely", "impact", "impediment", "important", "in",
	"incomplete", "increment", "independent", "infallibly", "inform",
	"initial", "input", "instant", "insurance", "intelligent",
	"interest", "interface", "intuitive", "inverse", "issuance",
	"issue", "judgment", "just", "large", "lastly",
	"likely", "literal", "local", "loosely", "lucid",
	"major", "manage", "mark", "massive", "material",
	"material", "matter", "meaningful", "measurable", "measurement",
	"men", "method", "mildly", "mile", "mind",
	"minimal", "minor", "model", "moderate", "module",
	"moment", "move", "natural", "nature", "near",
	"necessary", "necessity", "need", "new", "next",
	"normal", "note", "objective", "obvious", "occasion",
	"of", "official", "only", "opinion", "opportunity",
	"opposite", "ordinary", "organize", "outcome", "output",
	"over", "owner", "partial", "past", "perceive",
	"permission", "person", "personal", "plain", "plan",
	"please", "policy", "political", "ponder", "portal",
	"power", "practice", "prefer", "premise", "present",
	"primary", "principal", "priority", "probably", "procedure",
	"process", "progress", "project", "prolong", "promptly",
	"proposal", "protocol", "purchase", "pure", "purpose",
	"quality", "quantity", "quickly", "quite", "quota",
	"radical", "random", "rapid", "rather", "real",
	"reality", "reason", "recent", "recruit", "redundant",
	"reference", "refine", "register", "regret", "reject",
	"relative", "reliably", "renew", "repair", "report",
	"request", "resource", "result", "robust", "routine",
	"schedule", "separate", "service", "short", "simple",
	"since", "size", "sponsor", "standard", "statement",
	"steady", "strike", "strong", "success", "suggest",
	"super", "system", "task", "team", "temporal",
	"time", "to", "to", "to", "topical", "total", "trade",
	"travel", "tremendous", "trivial", "true", "truth",
	"typical", "ultimate", "under", "unfortunate", "unify",
	"unique", "useful", "usual", "vague", "valuable",
	"very", "viable", "view", "vital", "weak",
	"well", "with", "work", "worse", "worth",
	"\0"};	/* leave this here */

/* INITWORDS
 *
 * This routine gets the word list and sets the maximum
 * word number in its return value
 */
int
initwords()
{
register int maxwd;
	maxwd = 0;
	while (*words[maxwd] != '\0') {
		maxwd++;
		if (maxwd > 10000)	/* safety check */
			error("Could not find word list end");
	}
	return(maxwd);
}
!FuNkYsTuFf