1 /*
2  *  typespeed - measures your typing speed
3  *  Copyright (C) 1999-2003   Jani Ollikainen  <bestis@iki.fi>
4  *                          & Jaakko Manelius  <jman@iki.fi>
5  *  Copyright (C) 2006-2008   Tobias Stoeckmann  <tobias@bugol.de>
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21 
22 /*
23  * typespeed.c - the main code
24  */
25 
26 #ifdef HAVE_CONFIG_H
27 	#include "config.h"
28 #endif /* HAVE_CONFIG_H */
29 
30 #ifdef HAVE_SYS_PARAM_H
31 	#include <sys/param.h>
32 #endif /* HAVE_SYS_PARAM_H */
33 
34 #ifdef HAVE_SYS_SOCKET_H
35 	#include <sys/socket.h>
36 #endif /* HAVE_SYS_SOCKET_H */
37 
38 #ifdef HAVE_SYS_TYPES_H
39 	#include <sys/types.h>
40 #endif /* HAVE_SYS_TYPES_H */
41 
42 #ifdef HAVE_BITS_POSIX1_LIM_H
43 	#include <bits/posix1_lim.h>
44 #endif /* HAVE_BITS_POSIX1_LIM_H */
45 
46 #include <ctype.h>
47 #include <curses.h>
48 #include <errno.h>
49 
50 #ifdef HAVE_FCNTL_H
51 	#include <fcntl.h>
52 #endif /* HAVE_FCNTL_H */
53 
54 #include <getopt.h>
55 
56 #ifdef HAVE_LOCALE_H
57 	#include <locale.h>
58 #endif /* HAVE_LOCALE_H */
59 
60 #include <signal.h>
61 #include <stdio.h>
62 
63 #ifdef HAVE_STDLIB_H
64 	#include <stdlib.h>
65 #endif /* HAVE_STDLIB_H */
66 
67 #ifdef HAVE_STRING_H
68 	#include <string.h>
69 #endif /* HAVE_STRING_H */
70 
71 #include <time.h>
72 
73 #ifdef HAVE_UNISTD_H
74 	#include <unistd.h>
75 #endif /* HAVE_UNISTD_H */
76 
77 #include "gettext.h"
78 #include "pathnames.h"
79 #include "typespeed.h"
80 
81 #define _(string)	gettext(string)
82 
83 #define DEFAULT_CHEAT		0
84 #define DEFAULT_COLORS		1
85 #define DEFAULT_PORT		6025
86 
87 extern int		addscore(char *, struct stats *);
88 extern int		choosewordfile(int);
89 extern void		closenetwork(void);
90 extern unsigned long	cstrl(char *);
91 extern void		drawmenu(void);
92 extern void		drawscreen(void);
93 extern void		drawstatus(unsigned);
94 extern void		endcursestuff(void);
95 extern void		freewords(void);
96 extern void		initcursestuff(void);
97 extern int		initnetwork(char *, int);
98 extern void		initstatus(char *);
99 extern unsigned short	level(int);
100 extern int		loadscores(char *);
101 extern void		makescorefiles(char *);
102 extern void		multipmenu(void);
103 extern int		netrecv(int, int, int, int, char *, size_t);
104 extern int		netsend(char *);
105 extern int		netswapscore(struct stats *, struct stats *);
106 extern void		optionmenu(void);
107 extern void		pressanykey(int, int);
108 extern int		r(int);
109 extern void		readconfig(void);
110 extern void		rulesmenu(void);
111 extern void		showhighscores(void);
112 extern void		setnoblock(void);
113 extern void		setruleset(char *);
114 extern void		tellstory(void);
115 extern clock_t		timenow(void);
116 extern int		typorankkaus(float);
117 extern void		xcolor_set(short);
118 extern void		xerr(int, const char *, ...);
119 extern void		xerrx(int, const char *, ...);
120 extern void		xstrncpy(char *, char *, size_t);
121 
122 int			addnewword(char *);
123 void			defrule(void);
124 
125 #ifdef WIN32
126 void			win32_sighandler(int);
127 #endif /* WIN32 */
128 
129 #ifndef TEST
130 static void		clearword(int, int, size_t);
131 static void		cwords(void);
132 static void		movewords(int *);
133 static void		parseinput(int *, char *, clock_t *, unsigned *, int *, int*);
134 static int		play(void);
135 static void		usage(void);
136 #endif /* TEST */
137 
138 /* globals */
139 struct finfo finfo;
140 struct stats now;
141 struct opt opt;
142 struct rules rules;
143 struct rawdata words;
144 
145 char *rankki[11] =
146 {"None", "Beginner", "Learner", "NoGood", "Average",
147  "Good", "VeryGood", "Pro", "3l33t", "*(GOD)*", "Computer"};
148 
149 char *typorank[12] =
150 {"None", "Alien", "Secretary", "Human", "IT Person", "Handicap",
151  "Monkey", "Pencil", "T-Bone", "E-Typo", "TypOmatiC", "TypoKing"};
152 
153 FILE		*netlogfile;
154 struct stats	 best;
155 int		 hfd;
156 int		 misses;
157 struct stats	 other;
158 char		*progname;
159 float		 rate;
160 char		*usedwordfile;
161 int		 wordcount = 0;
162 int		 wordpos[22];
163 char		 wordstring[22][20];
164 
165 struct option options[] = {
166 	{"cheat", no_argument, &opt.cheat, 1},
167 	{"client", required_argument, NULL, 'o'},
168 	{"help", no_argument, NULL, 'h'},
169 	{"nocolors", no_argument, &opt.usecolors, 0},
170 	{"netlog", required_argument, NULL, 'n'},
171 	{"port", required_argument, NULL, 'p'},
172 	{"seed", required_argument, NULL, 'r'},
173 	{"server", no_argument, &opt.net, H2H},
174 	{0, 0, 0, 0}
175 };
176 
177 /*
178  * Insert a new word into wordstring and wordpos, i.e. one more word
179  * on the screen.
180  *
181  * If newword points to a string, newword will be inserted.
182  * If newword is NULL, a random word out of word will be inserted.
183  * In both cases, duplicates are not allowed. Should newword point
184  * to duplicated string or string that cannot be typed with current
185  * locale, no string will be inserted.
186  *
187  * Returns TRUE on success or FALSE on failure.
188  *
189  * XXX: Can result in a veeery long loop when there are many words on
190  *      field and only a few words in word list (no duplicates allowed).
191  */
192 int
addnewword(char * newword)193 addnewword(char *newword)
194 {
195 	int count, dup, slot;
196 	int freeslot[22];
197 	size_t i;
198 	char *myword, *p;
199 
200 	if (newword != NULL) {
201 		if (strlen(newword) > (size_t)rules.maxlen)
202 			return FALSE;
203 		for (p = newword; *p != '\0'; p++)
204 			if (!isprint(*p) || isspace(*p))
205 				return FALSE;
206 	}
207 
208 	/* take a random free slot */
209 	for (count = 0, i = 0; i < 22; i++)
210 		if (wordpos[i] == -2)
211 			freeslot[count++] = i;
212 
213 	if (!count || count < 23 - rules.maxwords)
214 		/* the player has enough trouble... */
215 		return FALSE;
216 
217 	slot = freeslot[r(count)];
218 	myword = (newword == NULL) ? words.word[r(words.n)] : newword;
219 
220 	do {
221 		for (dup = 0, i = 0; i < 22; i++)
222 			if (!strcmp(wordstring[i], myword)) {
223 				if (newword != NULL)
224 					return FALSE;
225 				dup = 1;
226 				myword = words.word[r(words.n)];
227 				break;
228 			}
229 	} while (dup);
230 
231 	xstrncpy(wordstring[slot], myword, sizeof(wordstring[slot]) - 1);
232 	wordpos[slot] = -1;
233 
234 	return TRUE;
235 }
236 
237 #ifndef TEST
238 /* Writes "length" blanks at position x y. */
239 static void
clearword(int y,int x,size_t length)240 clearword(int y, int x, size_t length)
241 {
242 	move(y, x);
243 	while (length--)
244 		addch(' ');
245 }
246 #endif /* TEST */
247 
248 #ifndef TEST
249 /*
250  * Sets rate according to current length of all visible words.
251  * If words with too few chars are on screen, throw in another word.
252  */
253 static void
cwords(void)254 cwords(void)
255 {
256 	int i, wc;
257 	unsigned long length;
258 
259 	for (wc = 0, length = 0, i = 0; i < 22; i++)
260 		if (wordpos[i] > -2) {
261 			wc++;
262 			length += strlen(wordstring[i]) + 1;
263 		}
264 
265 	if (length < now.score / 4 + 1 || wc < rules.minwords)
266 		addnewword(NULL);
267 
268 	if (rules.smooth)
269 		rate = (float)now.score / rules.step + rules.minspeed;
270 	else
271 		rate = now.score / rules.step + rules.minspeed;
272 
273 	if (rules.maxspeed && rate > rules.maxspeed)
274 		rate = rules.maxspeed;
275 }
276 #endif /* TEST */
277 
278 /*
279  * Resets rules to typespeed's default.
280  */
281 void
defrule(void)282 defrule(void)
283 {
284 	rules.misses = 10;
285 	rules.minlen = 1;
286 	rules.maxlen = 19;
287 	rules.minwords = 1;
288 	rules.maxwords = 22;
289 	rules.hightype = 1;
290 	rules.minscore = 0;
291 	rules.minspeed = 3;
292 	rules.maxspeed = 0;
293 	rules.step = 175;
294 	rules.smooth = 1;
295 	xstrncpy(rules.fname, "default", sizeof(rules.fname) - 1);
296 	xstrncpy(rules.name, _("default"), sizeof(rules.name) - 1);
297 }
298 
299 #ifdef WIN32
300 void
sleep(clock_t wait)301 sleep(clock_t wait)
302 {
303 	clock_t goal;
304 
305 	goal = wait * 1000 + clock();
306 	while (goal > clock())
307 		;
308 }
309 #endif /* WIN32 */
310 
311 #ifndef TEST
312 int
main(int argc,char ** argv)313 main(int argc, char **argv)
314 {
315 	int i, wheretogo;
316 #ifndef WIN32
317 	gid_t mygid;
318 #endif /* WIN32 */
319 	char serv[MAXHOSTNAMELEN];
320 	unsigned long val;
321 
322 	if ((progname = strrchr(argv[0], '/')) == NULL)
323 		progname = argv[0];
324 	else
325 		progname++;
326 
327 	/* just open high score file while being setgid games */
328 	if ((hfd = open(HIGHFILE, O_RDWR, 0)) == -1)
329 		xerr(1, "main: open: %s", HIGHFILE);
330 
331 #ifndef WIN32
332 	/* drop privileges */
333 	mygid = getgid();
334 #if defined(HAVE_SETRESGID)
335 	if (setresgid(mygid, mygid, mygid) == -1) {
336 		fputs("Cannot drop privilege!\n", stderr);
337 		exit(1);
338 	}
339 #elif defined(HAVE_RESREGID)
340 	if (setregid(mygid, mygid) == -1) {
341 		fputs("Cannot drop privilege!\n", stderr);
342 		exit(1);
343 	}
344 #else
345 	if (setegid(mygid) == -1) {
346 		fputs("Cannot drop privilege!\n", stderr);
347 		exit(1);
348 	}
349 	if (setgid(mygid) == -1) {
350 		fputs("Cannot drop privilege!\n", stderr);
351 		exit(1);
352 	}
353 #endif /* HAVE_SETRESGID */
354 #endif /* WIN32 */
355 
356 	/* check file descriptors for consistency */
357 	if (hfd == STDIN_FILENO || hfd == STDOUT_FILENO ||
358 	    hfd == STDERR_FILENO)
359 		exit(1);
360 	if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) ||
361 	    !isatty(STDERR_FILENO))
362 		xerrx(1, "not fully connected to a terminal");
363 
364 	if (setlocale(LC_ALL, "") == NULL)
365 		xerrx(1, "main: setlocale");
366 
367 	if (bindtextdomain(PACKAGE, LOCALEDIR) == NULL)
368 		xerr(1, "main: bindtextdomain");
369 	if (textdomain(PACKAGE) == NULL)
370 		xerr(1, "main: textdomain");
371 
372 #ifdef WIN32
373 	/* properly exit if someone presses close button */
374 	(void)signal(SIGINT, &win32_sighandler);
375 #else
376 	/* ignore SIGPIPE (for network code) */
377 	if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
378 		xerrx(1, "main: cannot drop SIGPIPE");
379 #endif /* WIN32 */
380 
381 	opt.cheat = DEFAULT_CHEAT;
382 	opt.net = 0;
383 	opt.port = DEFAULT_PORT;
384 	opt.usecolors = DEFAULT_COLORS;
385 	strncpy(opt.name, "default", sizeof(opt.name));
386 	opt.name[sizeof(opt.name) - 1] = '\0';
387 
388 	usedwordfile = NULL;
389 
390 	defrule();
391 
392 	memset(&best, 0, sizeof(best));
393 	memset(&now, 0, sizeof(now));
394 
395 	readconfig();
396 	if (strcmp(rules.fname, "default"))
397 		setruleset(rules.fname);
398 
399 	printf(_(
400 "Typespeed %s, Copyright (C) Jani Ollikainen & Jaakko Manelius\n\
401                   Copyright (C) Tobias Stoeckmann\n\
402 Typespeed comes with ABSOLUTELY NO WARRANTY; for details read COPYING.\n"),
403 	    TVERSION);
404 
405 	/* Prepare status bar, depends on locale */
406 	initstatus(
407 	    _("Rank: %------- Score: %--- WPM: %-- CPS: %---- Misses: %-"));
408 
409 	while ((i = getopt_long(argc, argv, "to:h", options, NULL)) != -1) {
410 		switch (i) {
411 		case 0:
412 			/* flag already set by getopt */
413 			break;
414 		case 'n':
415 			if (netlogfile != NULL)
416 				if (fclose(netlogfile))
417 					xerr(1, "main: fclose netlogfile");
418 			if ((netlogfile = fopen(optarg, "w")) == NULL)
419 				xerr(1, "main: fopen netlogfile");
420 			break;
421 		case 'o':
422 			opt.net = H2H;
423 			if (optarg == NULL)
424 				/* getopt should handle this */
425 				abort();
426 			xstrncpy(serv, optarg, sizeof(serv) - 1);
427 			break;
428 		case 'p':
429 			if (optarg == NULL)
430 				/* getopt should handle this */
431 				abort();
432 			if ((!(val = cstrl(optarg)) && errno) ||
433 			    val > 65535)
434 				xerrx(1, "Cannot convert %s to port number",
435 				    optarg);
436 			if (val < 1025)
437 				xerrx(1, "Do not use privileged port!");
438 			opt.port = val;
439 			break;
440 		case 'r':
441 			if (optarg == NULL)
442 				/* getopt should handle this */
443 				abort();
444 			if ((!(val = cstrl(optarg)) && errno) ||
445 			    val > UINT_MAX)
446 				xerrx(1, "Cannot convert %s to random seed",
447 				    optarg);
448 			opt.seed = val;
449 			break;
450 		case 't':
451 			opt.net = NET;
452 			if (optarg == NULL)
453 				/* getopt should handle this */
454 				abort();
455 			xstrncpy(serv, optarg, sizeof(serv) - 1);
456 			break;
457 		case ':':
458 		case '?':
459 			/* getopt already printed error */
460 		case 'h':
461 		default:
462 			usage();
463 			/* NOTREACHED */
464 			break;
465 		}
466 	}
467 	argc -= optind;
468 	argv += optind;
469 
470 	if (argc)
471 		usage();
472 		/* NOTREACHED */
473 
474 	if (opt.net) {
475 		if (initnetwork(serv, 0)) {
476 			fflush(stdout);
477 			xerrx(1, "main: initnetwork");
478 		}
479 		fflush(stdout);
480 		setnoblock();
481 	}
482 
483 	sleep(1);
484 	initcursestuff();
485 
486 	if (opt.net == H2H)
487 		play();
488 
489 	drawmenu();
490 	/*
491 	 * THIS IS OLD NEWS - LEFT HERE TO PUT SOME FUTURE NEWS IN
492 	 * mvaddstr(15, 20, "...");
493 	 * mvaddstr(16, 26, "...");
494 	 */
495 
496 	wheretogo = 0;
497 	do {
498 		switch (wheretogo = getch()) {
499 		case '1':
500 			play();
501 			break;
502 		case '2':
503 			if (opt.net == NET) {
504 				closenetwork();
505 				break;
506 			}
507 			multipmenu();
508 			if (opt.net == H2H)
509 				play();
510 			break;
511 		case '3':
512 			tellstory();
513 			break;
514 		case '4':
515 			showhighscores();
516 			break;
517 		case '5':
518 			optionmenu();
519 			break;
520 		case '6':
521 			rulesmenu();
522 			break;
523 		default:
524 			break;
525 		}
526 		if (wheretogo > '0' && wheretogo < '7')
527 			drawmenu();
528 	} while (wheretogo != '7');
529 
530 	endcursestuff();
531 
532 	puts("\033[H\033[J");
533 	printf(_(
534 "Typespeed %s\n\n\
535 Best score was:\n\n\
536 Rank:\t\t%s\n\
537 Score:\t\t%u\n\
538 WPM:\t\t%u\n\
539 CPS:\t\t%2.3f\n\
540 Typo ratio:\t%2.1f%%\n\
541 Typorank:\t"),
542 	    TVERSION, rankki[best.level], best.score, best.wpm,
543 	    best.speed, best.ratio);
544 	if (best.tcount)
545 		puts(typorank[typorankkaus(best.ratio)]);
546 	else
547 		puts(typorank[0]);
548 	fputc('\n', stdout);
549 	return 0;
550 }
551 #endif /* TEST */
552 
553 #ifndef TEST
554 /*
555  * Move words across the screen.
556  * Removes them if they reach the right side and returns the amount of
557  * words which left the screen.
558  */
559 static void
movewords(int * misses)560 movewords(int *misses)
561 {
562 	int buf[4][22];
563 	int count[4];
564 	int i, j, length;
565 
566 	memset(count, 0, sizeof(count));
567 
568 	xcolor_set(0);
569 	for (i = 0; i < 22; i++) {
570 		if (wordpos[i] == -2)
571 			continue;
572 
573 		length = (int)strlen(wordstring[i]);
574 
575 		if (wordpos[i] > (79 - length)) {
576 			clearword(i, wordpos[i], length);
577 			buf[3][count[3]++] = i;
578 			wordpos[i] = -2;
579 			continue;
580 		}
581 
582 		if (wordpos[i] > -1)
583 			mvaddch(i, wordpos[i], ' ');
584 
585 		if (wordpos[i] > (65 - length))
586 			buf[2][count[2]++] = i;
587 		else if (wordpos[i] > (50 - length))
588 			buf[1][count[1]++] = i;
589 		else
590 			buf[0][count[0]++] = i;
591 	}
592 
593 	*misses += count[3];
594 
595 	/*
596 	 * Get words that are missed back in buffer, maybe the player
597 	 * was about to type them.
598 	 */
599 	if (*misses >= rules.misses) {
600 		for (i = 0; i < count[3]; i++)
601 			wordpos[buf[3][i]] = 0;
602 		return;
603 	}
604 
605 	for (j = 0; j < 3; j++) {
606 		if (!count[j])
607 			continue;
608 
609 		switch (j) {
610 		case 0:
611 			xcolor_set(1);
612 			break;
613 		case 1:
614 			xcolor_set(7);
615 			break;
616 		case 2:
617 			xcolor_set(3);
618 			break;
619 		default:
620 			break;
621 		}
622 
623 		for (i = 0; i < count[j]; i++)
624 			mvaddstr(buf[j][i], ++wordpos[buf[j][i]],
625 			    wordstring[buf[j][i]]);
626 	}
627 }
628 #endif /* TEST */
629 
630 #ifndef TEST
631 /*
632  * Parses user input during game.
633  */
634 static void
parseinput(int * escend,char * input,clock_t * starttime,unsigned * inputpos,int * nappaykset,int * wrngret)635 parseinput(int *escend, char *input, clock_t *starttime, unsigned *inputpos,
636     int *nappaykset, int *wrngret)
637 {
638 	int i, nappi, foundword;
639 	clock_t pausetime;
640 
641 	if ((nappi = getch()) == ERR)
642 		return;
643 
644 	switch (tolower(nappi)) {
645 	case 8:
646 	case 127:
647 	case KEY_BACKSPACE:
648 		if (*inputpos) {
649 			mvaddch(23, 1 + *inputpos, ' ');
650 			input[--(*inputpos)] = '\0';
651 		}
652 		break;
653 	case 27: /* ESC */
654 		*escend = 1;
655 		break;
656 	case KEY_UP:
657 		if (opt.net) {
658 			*escend = 1;
659 			break;
660 		}
661 
662 		pausetime = timenow();
663 		mvaddstr(23, 2, _("       PAUSED      "));
664 		move(23, 2);
665 		while (getch() == ERR)
666 			;
667 		*starttime += timenow() - pausetime;
668 		mvaddstr(23, 2, "                   ");
669 		flushinp();
670 		/* FALLTHROUGH */
671 	case 21: /* ^U */
672 		if (*inputpos) {
673 			clearword(23, 2, *inputpos);
674 			*inputpos = 0;
675 			input[0] = '\0';
676 		}
677 		break;
678 	case 32: /* SPACE */
679 	case 10: /* ENTER */
680 		foundword = 0;
681 		for (i = 0; i < 22; i++) {
682 			if (strcmp(input, wordstring[i]))
683 				continue;
684 			foundword = 1;
685 			now.wordswritten++;
686 			now.score += *inputpos;
687 			clearword(i, wordpos[i], strlen(wordstring[i]));
688 			wordpos[i] = -2;
689 			switch (opt.net) {
690 			case H2H:
691 				if (r(2))
692 					break;
693 				/* FALLTHROUGH */
694 			case NET:
695 				if (netsend(wordstring[i]))
696 					*escend = 2;
697 				break;
698 			default:
699 				break;
700 			}
701 			xstrncpy(wordstring[i], " ", sizeof(wordstring[i]) - 1);
702 		}
703 		if (!foundword) {
704 			(*nappaykset)++;
705 			(*wrngret)++;
706 		}
707 		clearword(23, 2, 19);
708 		*inputpos = 0;
709 		input[0] = '\0';
710 		break;
711 	case 11: /* ^K */
712 		if ((size_t)*inputpos < strlen(input)) {
713 			input[*inputpos] = '\0';
714 			clearword(23, 2 + *inputpos, 19 - *inputpos);
715 		}
716 		break;
717 	case KEY_RIGHT:
718 	case 6: /* ^F */
719 		if (*inputpos < 19 && (size_t)*inputpos < strlen(input))
720 			(*inputpos)++;
721 		break;
722 	case KEY_END:
723 	case 5: /* ^E */
724 		*inputpos = strlen(input);
725 		break;
726 	case KEY_LEFT:
727 	case 2: /* ^B */
728 		if (*inputpos)
729 			(*inputpos)--;
730 		break;
731 	case KEY_HOME:
732 	case 1: /* ^A */
733 		*inputpos = 0;
734 		break;
735 	default:
736 		if (nappi > 255)
737 			break;
738 		(*nappaykset)++;
739 		if (*inputpos > 18 || iscntrl(nappi))
740 			break;
741 		mvaddch(23, 2 + *inputpos, nappi);
742 		if (*inputpos == strlen(input))
743 			input[*inputpos + 1] = '\0';
744 		input[(*inputpos)++] = nappi;
745 		if (!opt.cheat)
746 			break;
747 		for (i = 0; i < 22; i++) {
748 			if (strcmp(input, wordstring[i]))
749 				continue;
750 			now.score += *inputpos;
751 			clearword(i, wordpos[i], strlen(wordstring[i]));
752 			wordpos[i] = -2;
753 			clearword(23, 2, 19);
754 			*inputpos = 0;
755 			input[0]= '\0';
756 			xstrncpy(wordstring[i], " ", sizeof(wordstring[i]) - 1);
757 		}
758 		break;
759 	}
760 	move(23, 2 + *inputpos);
761 }
762 #endif /* TEST */
763 
764 #ifndef TEST
765 /*
766  * Contains core game logic.
767  * This function runs as long as player hasn't reached main screen again. :)
768  */
769 static int
play(void)770 play(void)
771 {
772 	int escend, i, nappaykset, wordcount, wrngret;
773 	unsigned inputpos, wordlim;
774 	clock_t oldtimes, starttime;
775 	char input[20];
776 
777 	memset(&now, 0, sizeof(now));
778 	now.sinit = opt.seed ? opt.seed : (uint32_t)time(NULL);
779 
780 #ifdef WIN32
781 	srand(now.sinit);
782 #else
783 	srandom(now.sinit);
784 #endif /* WIN32 */
785 
786 	if (opt.net == H2H)
787 		defrule();
788 
789 	if (choosewordfile(0)) {
790 		if (opt.net == H2H) {
791 			(void)netsend(" ERROR");
792 			closenetwork();
793 		}
794 		return 1;
795 	}
796 
797 	misses = 0;
798 	escend = inputpos = nappaykset = 0;
799 	rate = 1;
800 
801 	wordlim = (rules.maxwords == 1) ? 0 : 1;
802 
803 	drawscreen();
804 	halfdelay(1);
805 
806 	if (opt.net) {
807 		xcolor_set(2);
808 		mvaddstr(12, 20,
809 		    _("Waiting for the other party to join in..."));
810 		refresh();
811 		if (netsend(" GAME START") || netrecv(1, 0, 1, 1, NULL, 0)
812 		    != 5) {
813 			nocbreak();
814 			cbreak();
815 			closenetwork();
816 			return 1;
817 		}
818 		flushinp();
819 		drawscreen();
820 	}
821 
822 	oldtimes = starttime = timenow();
823 
824 	/* Prevent an (almost) impossible division by zero later on */
825 	while (starttime == timenow())
826 		;
827 
828 	input[0] = '\0';
829 
830 	for (i = 0; i < 22; i++) {
831 		wordstring[i][0] = ' ';
832 		wordstring[i][1] = '\0';
833 		wordpos[i] = -2;
834 	}
835 
836 	/* move cursor to correct position */
837 	drawstatus(0);
838 
839 	while (!escend && misses < rules.misses) {
840 		wordcount = now.wordswritten;
841 		wrngret = 0;
842 		while (timenow() < (oldtimes + (100.0 / rate))) {
843 			parseinput(&escend, input, &starttime, &inputpos,
844 			    &nappaykset, &wrngret);
845 			if (now.wordswritten - wordcount > wordlim &&
846 			    wrngret > 10) {
847 				escend = 1;
848 				rules.hightype = 0;
849 				memset(&now, 0, sizeof(now));
850 				nappaykset = 0;
851 			}
852 		}
853 		oldtimes = timenow();
854 		now.duration = oldtimes - starttime;
855 
856 		movewords(&misses);
857 
858 		/*
859 		 * Be nice and check if a player was to typo something while
860 		 * the last word slipped away... Every other situation is a
861 		 * typo though.
862 		 */
863 		if (misses >= rules.misses)
864 			for (i = 0; i < 22; i++)
865 				if (wordpos[i] != -2 &&
866 				    !strncmp(wordstring[i], input, inputpos)) {
867 					now.score += inputpos;
868 					break;
869 				}
870 
871 		cwords();
872 
873 		now.speed = (now.score + now.wordswritten) * 100.0 /
874 		    now.duration;
875 
876 		now.wpm = now.speed * 12;
877 
878 		drawstatus(inputpos);
879 
880 		/* last, we check if the opponent has new words for us... */
881 		if (opt.net && !escend) {
882 			switch(netrecv(0, 0, 1, 1, NULL, 0)) {
883 			case -1:
884 			case 2:
885 				escend = 2;
886 				break;
887 			default:
888 				break;
889 			}
890 		}
891 
892 		refresh();
893 	}
894 
895 	nocbreak();
896 	cbreak();
897 
898 	if (opt.net && netsend(" GAME END")) {
899 		closenetwork();
900 		return -1;
901 	}
902 
903 	drawscreen();
904 	xcolor_set(5);
905 	mvaddstr(12, 30, _("GAME OVER!"));
906 	refresh();
907 
908 	now.duration = oldtimes - starttime;
909 	now.tcount = nappaykset;
910 	now.level = level(now.score);
911 
912 	if (now.tcount)
913 		now.ratio = (1 - (float)(now.score + now.wordswritten) /
914 		(now.tcount + now.wordswritten)) * 100;
915 
916 	if (now.score >= best.score)
917 		memcpy((void *)&best, (void *)&now, sizeof(best));
918 
919 	if (opt.net == H2H) {
920 		mvaddstr(14, 23, _("Retrieving opponent's high score ..."));
921 		refresh();
922 		if (netswapscore(&now, &other))
923 			memset(&other, 0, sizeof(other));
924 	}
925 
926 	sleep(3);
927 	drawscreen();
928 	xcolor_set(5);
929 	refresh();
930 
931 	clearword(12, 30, strlen(_("GAME OVER!")));
932 
933 	mvaddstr(3, 20, "Typespeed ");
934 	addstr(TVERSION);
935 
936 	mvaddstr(5, 20, _("You achieved:"));
937 
938 	mvaddstr(7, 20, _("Rank:"));
939 	mvprintw(8, 20, _("Score:\t\t%d"), now.score);
940 	mvprintw(9, 20, _("WPM:\t\t%lu"), now.wpm);
941 	mvprintw(10, 20, _("CPS:\t\t%2.3f"), now.speed);
942 	mvprintw(11, 20, _("Typo ratio:\t\t%2.1f%%"), now.ratio);
943 	mvaddstr(12, 20, _("Typorank:"));
944 
945 	/* these functions change color */
946 	mvaddstr(7, 40, rankki[level(now.score)]);
947 	mvaddstr(12, 40, typorank[now.tcount ? typorankkaus(now.ratio) : 0]);
948 
949 	if (opt.net == H2H) {
950 		xcolor_set(5);
951 		if (other.name[0] != '\0' && strcmp(other.name, "default"))
952 			mvprintw(5, 60, "%s:", other.name);
953 		else
954 			mvaddstr(5, 60, _("Opponent:"));
955 		mvprintw(8, 60, "%d", other.score);
956 		mvprintw(9, 60, "%lu", other.wpm);
957 		mvprintw(10, 60, "%2.3f", other.speed);
958 		if (other.tcount)
959 			other.ratio = (1 - (float)(other.score +
960 			    other.wordswritten) / (other.tcount +
961 			    other.wordswritten)) * 100;
962 		else
963 			other.ratio = 0;
964 		mvprintw(11, 60, _("%2.1f%%"), other.ratio);
965 
966 		/* these functions change color */
967 		mvaddstr(7, 60, rankki[level(other.score)]);
968 		mvaddstr(12, 60, typorank[other.tcount ?
969 		    typorankkaus(other.ratio) : 0]);
970 	}
971 
972 	xcolor_set(2);
973 	if (opt.net == H2H)
974 		mvaddstr(17, 15, _("NETWORK MODE - YOU CANNOT GET TO TOP10"));
975 	else if (!rules.hightype)
976 		mvaddstr(17, 15,
977 		    _("RANKING DISABLED - YOU CANNOT GET TO TOP10"));
978 
979 	pressanykey(15, 20);
980 	if ((!opt.net || opt.net == NET) && now.score > rules.minscore
981 	    && rules.hightype) {
982 		addscore(usedwordfile, &now);
983 		loadscores(usedwordfile);
984 	}
985 	free(usedwordfile);
986 	usedwordfile = NULL;
987 	freewords();
988 
989 	if (opt.net == H2H)
990 		closenetwork();
991 
992 	return 0;
993 }
994 #endif /* TEST */
995 
996 #ifndef TEST
997 /* Prints usage information on stderr and exits with return value 1. */
998 static void
usage()999 usage()
1000 {
1001 	xerrx(1 , _("\n\
1002 command line options:\n\
1003   --cheat       = multimedia cheat(?)\n\
1004   --client=addr = ip address of server\n\
1005   --netlog=file = log network traffic into file\n\
1006   --nocolors    = do not use colors\n\
1007   --port=port   = port number in the network play\n\
1008   --server      = start typespeed in server mode\n\
1009   --help        = this help!\n\
1010 \n\
1011 usage: %s options\n\
1012 \n\
1013 typespeed %s - http://tobias.eyedacor.org/typespeed/\n"),
1014 	    progname, TVERSION);
1015 }
1016 #endif /* TEST */
1017 
1018 #ifdef WIN32
1019 void
win32_sighandler(int sig)1020 win32_sighandler(int sig)
1021 {
1022 	exit(0);
1023 }
1024 #endif /* WIN32 */
1025 
1026