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