1 /*
2 * Dvorak 7min 1.6.1, a Dvorak typing tutor
3 * Copyright (C) 1998-2003 Ragnar Hojland Espinosa
4 * <ragnar@ragnar-hojland.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 */
20
21 /*
22 * It's horrible code. I agree.
23 * I wanted it. I needed it. I now have it.
24 * I hope it ends my frustration learning dvorak.
25 * Otherwise I'm gonna kill someone. Soon.
26 * Do I sound desesperate?
27 *
28 * Try truning on the nastiness option.
29 * You can use the space bar or the enter key interchangeably.
30 *
31 * As far as 1.1, the code doesn't look that bad now thanks to Smoke,
32 * so mental SANity loss is reduced from 1d100 to merely 1d10.
33 *
34 *
35 * Smoke/CRAP here - I just hacked 1.3 up and it's pretty late
36 * a great excuse for messing up the code even more :)
37 *
38 * Hm, 1.5 and I'm pondering on sumbitting it to the Ofuscated C contest...
39 * It'll take a few versions more and some extra "features", and who knows.
40 * This baby is fun :)
41 *
42 */
43
44 #include <string.h>
45 #include <stdlib.h>
46 #include <ctype.h>
47 #include <time.h>
48 #include <curses.h>
49 #include <getopt.h>
50 #include <errno.h>
51 #include <sys/stat.h>
52
53 #include "lessons.h"
54
55 /* these three represent the visualization of the key on screen */
56 #define PRESSED_NOT 0
57 #define PRESSED_REQUESTED 1
58 #define PRESSED_BY_USER 2
59
60 /* Update cp[sm] every LATENCY tenths of second */
61 #define LATENCY 1
62
63 #ifdef NOT_SO_PRETTY
64 int tryToBePretty = 0;
65 #else
66 int tryToBePretty = 1;
67 #endif
68
69 #ifdef BEEPS_ARENT_IRRITATING
70 int beepsArentIrritating = 1;
71 #else
72 int beepsArentIrritating = 0;
73 #endif
74
75 #ifdef FLASHES_ARENT_IRRITATING
76 int flashesArentIrritating = 1;
77 #else
78 int flashesArentIrritating = 0;
79 #endif
80
81 #ifdef NASTY_AS_USUAL
82 int nastiness = 1;
83 #else
84 int nastiness = 0;
85 #endif
86
87 #ifdef NO_COLORS_PLEASE
88 int colorSupport = 0;
89 #else
90 int colorSupport = 1;
91 #endif
92
93 #ifdef NO_KEYS
94 int hideKeys = 1;
95 #else
96 int hideKeys = 0;
97 #endif
98
99 int max_editable_lines = 6;
100 int right_margin = 70;
101
102 const char *postmortem = "undefined postmortem";
103
104 /* all typable characters */
105 char typables[] = "`1234567890[]',.pyfgcrl/=\\aoeuidhtns-;qjkxbmwvz"
106 "~!@#$%^&*(){}\"<>PYFGCRL?+|AOEUIDHTNS_:QJKXBMWVZ\n ";
107
108 char map[] =
109 {
110 '`', '~', '1', '!', '2', '@', '3', '#', '4', '$', '5', '%', '6', '^',
111 '7', '&', '8', '*', '9', '(', '0', ')', '[', '{', ']', '}', 0,
112 3, '\'', '"', ',', '?', '.', '?', 'P', 'Y', 'F', 'G', 'C', 'R', 'L',
113 '/', '?', '=', '+', 0,
114 5, 'A', 'O', 'E', 'U', 'I', 'D', 'H', 'T', 'N', 'S', '-', '_',
115 '\\', '|', 0,
116 2, '<', '>', ';', ':', 'Q', 'J', 'K', 'X', 'B', 'M', 'W', 'V', 'Z', 0,
117 0
118 };
119
120
121 char colormap[] =
122 {
123 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
124 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
125 3, -5, -5, -4, -4, -3, -3, -2, -2, 2, 2, 3, 4, 5,
126 0, 0, 0, 0, 0,
127 5, -5, -4, -3, -2, -2, 2, 2, 3, 4, 5, 0, 0,
128 0, 0, 0,
129 2, 0, 0, -5, -5, -4, -3, -2, -2, 2, 2, 3, 4, 5, 0,
130 0
131 };
132
133
134 int helpInterval;
135 int helpLength;
136 time_t nextHelp;
137
myGetch(char shouldBe)138 int myGetch(char shouldBe)
139 {
140 int deviation;
141 int ch;
142
143 ch = getch();
144
145 if (ch != ERR) {
146 helpInterval = 10000 + (random() % 10000);
147 deviation = random() % 300;
148 nextHelp = time(0) + (helpInterval + deviation) / 1000;
149 helpLength = 1;
150 return ch;
151 }
152
153 if (time(0) < nextHelp) {
154 return ch;
155 }
156
157 if ((helpLength--) == 0) {
158 helpInterval = random() % 1000;
159 helpLength = random() % 8;
160 }
161 deviation = random() % 300;
162 nextHelp = time(0) + (helpInterval + deviation) / 1000;
163
164 if ((random() % 26) == 0) {
165 return KEY_BACKSPACE;
166 } else {
167 return shouldBe;
168 }
169 }
170
171
172 /* calculate typing speed in a given interval */
calcSpeed(time_t timeStart,time_t timeFinish,int hits)173 inline float calcSpeed (time_t timeStart, time_t timeFinish, int hits)
174 {
175 // Use WPS/WPM as better standard, so also divide by 5
176 return (float) hits / 5 / (timeFinish - timeStart);
177 }
178
179
180 /* Import an ASCII text to use as a lesson. Allocates memory for *buf.
181 return length of read text on success, -1 on error
182
183 tabstops are skipped, as are blanks on the beginning and end of
184 lines */
importText(char const * const fileName,char ** buf,unsigned int length)185 int importText (char const * const fileName, char** buf, unsigned int length)
186 {
187 FILE* file;
188 int c;
189 int i = 0;
190 int lines = 0;
191 int column;
192 struct stat st;
193
194 file = fopen (fileName, "r");
195 if (!file) {
196 postmortem = strerror(errno);
197 return -1;
198 }
199
200 fstat (fileno(file), &st);
201 if (st.st_size > length) {
202 length = st.st_size;
203 }
204 #if defined(LIMIT_LENGTH) && (LIMIT_LENGTH > 0)
205 if (length > LIMIT_LENGTH) {
206 length = LIMIT_LENGTH;
207 }
208 #endif
209
210 *buf = (char*) malloc (length+1+1);
211 if (!*buf) {
212 postmortem = "memory allocation failed";
213 fclose (file);
214 return -1;
215 }
216
217 column = 0;
218 (*buf[i++]) = '\x2';
219
220 while (1) {
221 c = fgetc (file);
222 if (errno) {
223 postmortem = strerror(errno);
224 fclose (file);
225 return -1;
226 }
227 if (c == EOF) {
228 fclose (file);
229 (*buf)[i] = '\x1';
230 break;
231 }
232
233 if (index(typables, c) == NULL) {
234 continue;
235 }
236
237 if (c == '\n' || column > right_margin) {
238 if (column > right_margin) {
239 (*buf)[i++] = '\n';
240 }
241
242 while (i > 0) {
243 if ((*buf)[i-1] == ' ') {
244 --i;
245 } else {
246 break;
247 }
248 }
249 column = 0;
250 ++lines;
251
252 if (c == ' ') {
253 continue;
254 }
255 }
256
257 if (lines == max_editable_lines) {
258 lines = 0;
259 (*buf)[i++] = '\x1';
260 } else {
261 (*buf)[i++] = (char) c;
262 ++column;
263 }
264
265 if (i >= length - 1) {
266 fclose (file);
267 (*buf)[i] = '\x1';
268 break;
269 }
270 }
271
272 /* strip unwanted whitespace from the end */
273 while (i > 0) {
274 if ((*buf)[i-1] == ' ' || (*buf)[i-1] == '\n' || (*buf)[i-1] == '\x1') {
275 --i;
276 } else {
277 break;
278 }
279 }
280
281 (*buf)[i] = '\0';
282 return i;
283 }
284
boxed(int x,int y,char u,char d,char pressed,int key)285 void boxed(int x, int y, char u, char d, char pressed, int key)
286 {
287 if (pressed == PRESSED_REQUESTED) {
288 attron (A_REVERSE);
289 }
290 if (pressed == PRESSED_BY_USER) {
291 attron (A_BOLD);
292 }
293 if (colorSupport) {
294 attron (COLOR_PAIR (7-abs(colormap[key])));
295 }
296
297 if (tryToBePretty) {
298 move (y+0, x); addch (ACS_ULCORNER); addch (ACS_HLINE); addch (ACS_HLINE); addch (ACS_HLINE); addch (ACS_URCORNER);
299 move (y+1, x); addch (ACS_VLINE); addch (u); addch (' '); addch (' '); addch (ACS_VLINE);
300 move (y+2, x); addch (ACS_VLINE); addch (' '); addch (' '); addch (d); addch (ACS_VLINE);
301 move (y+3, x); addch (ACS_LLCORNER); addch (ACS_HLINE); addch (ACS_HLINE); addch (ACS_HLINE); addch (ACS_LRCORNER);
302 } else {
303 mvprintw (y+0, x, ".---.");
304 mvprintw (y+1, x, "|%c |", u);
305 mvprintw (y+2, x, "| %c|", d);
306 mvprintw (y+3, x, "`---'");
307 }
308
309 if (colorSupport) {
310 attron (COLOR_PAIR(7));
311 }
312 if (pressed == PRESSED_REQUESTED) {
313 attroff (A_REVERSE);
314 }
315 if (pressed == PRESSED_BY_USER) {
316 attroff (A_BOLD);
317 }
318 }
319
show_layout(char pressedKey,char pressed)320 void show_layout(char pressedKey, char pressed)
321 {
322 int key = 0;
323 int x = 0, y = 0;
324
325 if (hideKeys) {
326 return;
327 }
328
329 while (map[key]) {
330 while (map[key]) {
331 char u = map[key];
332 char d;
333 if (isupper(u)) {
334 /* hack, to fix the keymap i wrote erroneusly.. */
335 d = u;
336 u = tolower (d);
337 } else {
338 ++key;
339 d = map[key];
340 }
341
342 if ((u == pressedKey) || (d == pressedKey)) {
343 boxed (x, y, d, u, pressed, key);
344 } else {
345 if (pressed != PRESSED_REQUESTED) {
346 boxed (x, y, d, u, PRESSED_NOT, key);
347 }
348 }
349
350 x += 6;
351 ++key;
352 }
353 ++key;
354 y += 4;
355 x = map[key];
356 ++key;
357 }
358 refresh();
359 }
360
myaddnstr(char const * text,int lenght)361 void myaddnstr (char const * text, int lenght)
362 {
363 char *buf = malloc (lenght);
364 char *obuf = buf;
365 int i = lenght;
366
367 while (--i) {
368 if (*text != '\x2') {
369 *(buf++) = *(text);
370 }
371 ++text;
372 }
373
374 addnstr (obuf, lenght);
375 free (obuf);
376 }
377
do_text(char * const text)378 void do_text (char * const text)
379 {
380 int hits, misses;
381 int this_page_size;
382
383 time_t timeStart, timeFinish, timeCurrent;
384 float speed;
385
386 char *p = text;
387 char *i;
388 char *this_page_start;
389
390 curs_set (FALSE);
391 show_layout(' ', PRESSED_NOT);
392 mvaddstr (LINES - 1, 0, "[ ] Press a key to start");
393
394 i = (char*) index (text, 0x1);
395 this_page_start = text;
396 this_page_size = i ? i - this_page_start : strlen(this_page_start);
397
398 move ((hideKeys ? 0 : 17), 0);
399
400 myaddnstr (text, this_page_size);
401 hits = 0; misses = 0;
402 refresh();
403 noecho();
404 move (LINES - 1, 1);
405 getch();
406
407 halfdelay (LATENCY);
408
409 move (LINES - 1, 0);
410 clrtoeol();
411 timeStart = 0;
412 nextHelp = time(0) + (helpInterval / 1000);
413
414 while (*p) {
415 int ch = 0;
416
417 if (*p == 0x2) {
418 ++p;
419 if (!timeStart) {
420 timeStart = time(0);
421 hits = 0;
422 }
423 }
424
425 if (*p > 27) {
426 show_layout(*p, PRESSED_REQUESTED);
427 }
428
429 do {
430 ch = myGetch (*p);
431 if (timeStart) {
432 float ratio = hits - misses;
433 ratio = (ratio < 0) ? 0 : (100.0 * ratio / hits);
434 timeCurrent = time(0);
435 speed = calcSpeed (timeStart, timeCurrent, hits + misses);
436 // Use WPS/WPM as better standards
437 mvprintw (LINES - 1, 0, "WPS %.2f WPM %.2f Hits: %d Misses: %d Seconds: %d Ratio: %.2f%%", speed, speed * 60, hits, misses, time(0) - timeStart, ratio);
438 clrtoeol();
439 }
440 } while (ch == ERR);
441
442 show_layout(ch, PRESSED_BY_USER);
443
444 if (ch == *p || (*p == '\n' && ch == ' ')) {
445 ++p;
446 ++hits;
447 } else {
448 /* any other possibilities? */
449 if ( (ch == 8 || ch == KEY_BACKSPACE || ch == KEY_DC || ch == 127)
450 && p > this_page_start) {
451 --p;
452 if (*p == '\x2') {
453 --p;
454 }
455 } else if (ch == 27 || ch == KEY_END) {
456 break;
457 } else {
458 if (beepsArentIrritating) {
459 beep();
460 }
461 if (flashesArentIrritating) {
462 flash();
463 }
464
465 ++misses;
466 if (nastiness && p > this_page_start) {
467 --hits;
468 --p;
469 if (*p == '\x2') {
470 --p;
471 }
472 }
473 }
474 }
475
476 move ((hideKeys ? 0 : 17), 0);
477 i = this_page_start;
478 attron (A_BOLD);
479 while (i < p) {
480 if (*i != 0x2) {
481 addch(*i);
482 }
483 ++i;
484 }
485 if (*i == '\x2') {
486 ++i;
487 }
488 attroff (A_BOLD);
489 attron (A_REVERSE);
490 if (*i != 0x2) {
491 addch(*i);
492 }
493 ++i;
494 attroff (A_REVERSE);
495 while (*i && i < this_page_start + this_page_size) {
496 if (*i != 0x2) {
497 addch(*i);
498 }
499 ++i;
500 }
501
502 if (*p == 0x1) {
503 char *next;
504 ++p;
505 next = (char*) index (p, 0x1);
506 this_page_start = p;
507 this_page_size = next ? next - this_page_start : strlen(this_page_start);
508 move ((hideKeys ? 0 : 17), 0);
509 clrtobot();
510 myaddnstr (this_page_start, this_page_size);
511 }
512 }
513
514 timeFinish = time(0);
515 speed = calcSpeed(timeStart, timeFinish, hits + misses);
516 clear();
517 cbreak();
518 if (!timeStart) {
519 mvprintw (0, 0, "You haven't done enough to get a good benchmark.");
520 } else {
521 float ratio = hits - misses;
522 ratio = (ratio < 0) ? 0 : (100.0 * ratio / hits);
523 mvprintw (0, 0, "Elapsed time: %d seconds", timeFinish - timeStart);
524 mvprintw (1, 0, "Total: %d Misses: %d Ratio: %.2f%%",
525 hits + misses, misses,
526 (float)100*(hits) / (hits + misses));
527 // Use WPM/WPS as better standard
528 mvprintw (2, 0, "WPS: %.2f WPM: %.2f", speed, speed * 60);
529 }
530
531 mvprintw (4, 0, "[ ] Press ESCAPE to continue.");
532 move (4, 1);
533 refresh();
534
535 while (getch() != 27);
536
537 curs_set (TRUE);
538 echo();
539 clear();
540 refresh();
541 }
542
menuInteractive()543 void menuInteractive()
544 {
545 int i, num;
546 char buf[255];
547
548 while (1) {
549 mvprintw (2, 0, "Dvorak7Min, Ragnar Hojland Espinosa, 1998-2003" );
550 mvprintw (3, 0, "enhanced by Smoke of CRAP, 1999 and Nopik, 2003");
551 for (i = 0; lessons[i*2]; ++i) {
552 mvprintw (i/2 + 5, (i%2) * 40, "%2d. %s", i+1, lessons[i*2]);
553 }
554 move (21, 0); clrtobot();
555 mvprintw (21, 0, "Type a lesson number ([N]astiness [H]ide keyboard [Q]uit)? ");
556 refresh();
557 echo();
558 getnstr (buf, sizeof(buf));
559 buf[0] = toupper(buf[0]);
560 if (buf[0] == 'N') {
561 nastiness = !nastiness;
562 if (nastiness)
563 mvprintw (23, 0, "Nastiness is now turned ON. Press any key.");
564 else
565 mvprintw (23, 0, "Nastiness is now turned OFF. Press any key.");
566 getch();
567 continue;
568 }
569
570 if (buf[0] == 'H') {
571 hideKeys = !hideKeys;
572 if (hideKeys)
573 mvprintw (23, 0, "Keyboard layout is now OFF. Press any key.");
574 else
575 mvprintw (23, 0, "Keyboard layout is now ON. Press any key.");
576 getch();
577 continue;
578 }
579
580 if (buf[0] == 'Q' || buf[0] == 27) {
581 clear();
582 move (0,0);
583 refresh();
584 return;
585 }
586 num = atoi (buf);
587 if (!buf[0] || num < 1 || num > i) {
588 mvprintw (23, 0, "Invalid lesson number. Press any key.");
589 getch();
590 continue;
591 }
592
593 clear();
594 refresh();
595 do_text(lessons[(num-1)*2+1]);
596 }
597 }
598
599
initColors()600 int initColors()
601 {
602 start_color();
603
604 if (!has_colors()) {
605 colorSupport = 0;
606 return -1;
607 }
608
609 init_pair( 1, COLOR_BLUE , COLOR_BLACK );
610 init_pair( 2, COLOR_GREEN , COLOR_BLACK );
611 init_pair( 3, COLOR_CYAN , COLOR_BLACK );
612 init_pair( 4, COLOR_RED , COLOR_BLACK );
613 init_pair( 5, COLOR_MAGENTA , COLOR_BLACK );
614 init_pair( 6, COLOR_YELLOW , COLOR_BLACK );
615 init_pair( 7, COLOR_WHITE , COLOR_BLACK );
616
617 return 0;
618 }
619
620
initApp()621 void initApp()
622 {
623 initscr();
624
625 right_margin = COLS - 10;
626 max_editable_lines = LINES - 18 - 1;
627 helpInterval = 10000 + (random() % 10000);
628 helpLength = 1;
629
630 if (colorSupport) {
631 initColors();
632 }
633 }
634
635
636
637
closeApp()638 void closeApp()
639 {
640 move (0, 0);
641 clear();
642 refresh();
643 endwin();
644 }
645
playFile(char * filename)646 int playFile(char *filename)
647 {
648 char *buffer;
649 int r;
650 int len = 0;
651
652 #ifdef LIMIT_LENGTH
653 len = LIMIT_LENGTH;
654 #endif
655
656 r = importText (filename, &buffer, len);
657 if (r <= 0) {
658 return -1;
659 }
660
661 do_text (buffer);
662 return 0;
663 }
664
showHelp()665 void showHelp()
666 {
667 printf("Usage: dvorak7min [OPTION]... [FILE]...\n\n"
668 " -b, --bell beep on error\n"
669 " -f, --flash flash the screen on error\n"
670 " -u, --ugly use low ascii for artwork\n"
671 " -n, --nastiness set nastiness on by default\n"
672 " -k, --hidekeys hides keyboard layout\n"
673 " --help display this help and exit\n"
674 " --version output version information and exit\n"
675 "\n");
676 }
677
showVersionInfo()678 void showVersionInfo()
679 {
680 printf ("dvorak 7min tutor hack 1.6 - FREE Software with NO Warranty\n"
681 "(C) 1998-2001 Ragnar Hojland Espinosa <ragnar@ragnar-hojland.com>\n"
682 "Lessons are (C) 1995 Dan Wood <danwood@karelia.com>\n"
683 "Enhancements in 1999 by Smoke of Crap aka Tijs van Bakel <smoke@casema.net>\n"
684 "and in 2003 by Nopik aka Kamil Burzynski <K.Burzynski@adbglobal.com>\n"
685 "\n");
686 }
687
main(int argc,char * argv[])688 int main ( int argc, char *argv[] )
689 {
690 struct option long_opts[] = {
691 { "help", 0, 0, 'H' },
692 { "version", 0, 0, 'V' },
693 { "nastiness", 0, 0, 'n' },
694 { "bell", 0, 0, 'b' },
695 { "flash", 0, 0, 'f' },
696 { "ugly", 0, 0, 'u' },
697 { "hidekeys", 0, 0, 'k' },
698 { 0, 0, 0, 0 }
699 };
700
701 int opt_index = 0;
702 int c;
703
704 while (1) {
705 c = getopt_long(argc, argv, "HVnbfuk", long_opts, &opt_index);
706 if (c == -1) {
707 break;
708 }
709
710 switch (c) {
711 case 'H':
712 showHelp();
713 return 1;
714 case 'V':
715 showVersionInfo();
716 return 1;
717 case 'b':
718 beepsArentIrritating = 1;
719 break;
720 case 'f':
721 flashesArentIrritating = 1;
722 break;
723 case 'n':
724 nastiness = 1;
725 break;
726 case 'u':
727 tryToBePretty = 0;
728 break;
729 case 'k':
730 hideKeys = 1;
731 break;
732 }
733 }
734
735 initApp();
736
737 if (optind < argc) {
738 while (optind < argc) {
739 if (playFile(argv[optind]) < 0) {
740 closeApp();
741 printf ("%s: %s: %s\n", argv[0], argv[optind], postmortem);
742 return 1;
743 }
744 ++optind;
745 }
746 } else {
747 menuInteractive();
748 }
749
750 closeApp();
751 showVersionInfo();
752
753 return 0;
754 }
755