Touch typing game
Steve Spearman
spear at druco.ATT.COM
Wed Feb 1 07:30:35 AEST 1989
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
More information about the Alt.sources
mailing list