home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume25 / letters / letters.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-01-02  |  12.6 KB  |  667 lines

  1. /*
  2.  * letters.c: A simple game to help improve typing skills.
  3.  *
  4.  * copyright 1991 Larry Moss (lm03_cif@uhura.cc.rochester.edu)
  5.  */
  6.  
  7. #define CTRL(c)        (c & 037)
  8. #define TRUE    1
  9. #define FALSE    0
  10.  
  11. #include <stdio.h>
  12. #include <string.h>
  13. #include <time.h>
  14. #ifdef AMIGA
  15. # include <math.h>
  16. #endif
  17. #include "config.h"
  18. #include "kinput.h"
  19. #include "terms.h"
  20. #include "term.h"
  21.  
  22. #if defined(SYSV)
  23. # define srandom srand48
  24. # define random lrand48
  25. #endif
  26.  
  27. struct s_word {
  28.     struct s_word *nextword;
  29.     int    posx;
  30.     int    posy;
  31.     int    length;
  32.     int    drop;
  33.     int    matches;
  34.     char    word[1];    /* extensible */
  35. };
  36.     
  37. int  move();
  38. void putword();
  39. int  game();
  40. void redraw();
  41. void erase();
  42. void status();
  43. void new_level();
  44. void banner();
  45. struct s_word *newword();
  46. struct s_word *searchstr(), *searchchar();
  47. void kill_word();
  48. int (*ding)();
  49.  
  50. char *malloc();
  51. void free();
  52.  
  53. /*
  54.  * There are too many globals for my taste, but I took the easy way out in
  55.  * a few places
  56.  */
  57. unsigned int score = 0;
  58. unsigned int level = 0;
  59. int levels_played = -1;
  60. unsigned int word_count = 0;
  61. static int lives = 2;
  62. static long delay;
  63. struct s_word    *words, *lastword, *prev_word;
  64. int    bonus = FALSE;    /* to determine if we're in a bonus round */
  65. int    wpm = 0;
  66. int    letters = 0;
  67.  
  68. void usage(progname)
  69. char *progname;
  70. {
  71.     fprintf(stderr, "Usage: %s [-q | -b] [-l#]\n", progname);
  72.     fprintf(stderr, "       %s [-h]\n", progname);
  73.     exit(0);
  74. }
  75.  
  76. void main(argc, argv)
  77. int argc;
  78. char *argv[];
  79. {
  80.     char *progname;
  81.     int    foo;
  82.  
  83.     /*
  84.      * make sure the person is on a tty
  85.      */
  86.     if(!isatty(0)) {
  87.         fputs("where are you?\n", stderr);
  88.         exit(1);
  89.     }
  90.  
  91.     /*
  92.      * this should also really check to make sure it's being played on
  93.      * the terminal of the person running it.  People around here have
  94.      * the habit of redirecting random programs to other screens.
  95.      */
  96.      if(!isatty(1)) {
  97.          fputs("This game can only be played on a terminal!\n", stderr);
  98.          exit(0);
  99.      }
  100.      
  101.     /*
  102.      * default bell sound
  103.      */
  104.     ding = quiet;
  105.  
  106.     /*
  107.      * initialize display stuff
  108.      */
  109.     init_term();        /* termcap stuff */
  110.  
  111.     /*
  112.      * get name of program
  113.      */
  114. #ifdef AMIGA
  115.     if(argc==0) {  /* called from Workbench */
  116.         progname="letters";
  117.         argv[1]=NULL;
  118.     } else
  119. #endif
  120.         progname = argv[0];
  121.  
  122.     /*
  123.      * check for options
  124.      */
  125.     while(*++argv) {
  126.         if((*argv)[0] == '-') {
  127.             switch((*argv)[1]) {
  128.                 case 'b':
  129.                     ding = bell;
  130.                     break;
  131.                 case 'q':
  132.                     ding = quiet;
  133.                     break;
  134.                 case 'h':
  135.                     read_scores();
  136.                     show_scores();
  137.                     exit(0);
  138.                     break;
  139.                 case 'l':
  140.                     sscanf(&argv[0][2], "%d", &level);
  141.                     if(DELAY(level) < PAUSE) {
  142.                         fprintf(stderr,    "You may not start at level %d\n", level);
  143.                         exit(0);
  144.                     }
  145.                     break;
  146.                 default:
  147.                     usage(progname);
  148.             }
  149.         } else {
  150.             usage(progname);
  151.         }
  152.     }
  153.     
  154.     /*
  155.      * get stuff initialized
  156.      */
  157. #ifdef AMIGA
  158.     fixedwin(argc,progname);
  159. #endif
  160.     setterm(NEW);        /* signal stuff, keyboard stuff */
  161.     srandom(getpid());
  162.     clrdisp();
  163.     new_level();
  164.     status();
  165.     words = NULL;
  166.     
  167.     for(;;) {
  168.         /*
  169.          * allocate memory for the first word and then find a word
  170.          * if there are no others on the screen.  There must always
  171.          * be at least one word active.
  172.          */
  173.         if(words == NULL) {
  174.             lastword = words = newword((struct s_word *)NULL);
  175.             prev_word = NULL;
  176.             putword(lastword);
  177.         }
  178.         
  179.         if(game() == 0) {
  180.             /*
  181.              * all finished.  print score and clean up.
  182.              */
  183.             gotoxy(0, SCREENLENGTH);
  184.             fflush(stdout);
  185.             putchar('\n');
  186.             break;
  187.         }
  188.     }
  189.  
  190.     read_scores();
  191.     update_scores();
  192.     sleep(2);
  193.     show_scores();
  194.     printf("\n\nfinal: score = %u\twords = %u\t level = %d\n",
  195.            score, word_count, level);
  196.  
  197.     /*
  198.      * flush keyboard and quit.
  199.      */
  200.     while(key_pressed() != -1);
  201.     setterm(ORIG);
  202.     exit(0);
  203. }
  204.  
  205. /*
  206.  * move all words down 1 or more lines.
  207.  * return the number of words that have fallen off the bottom of the screen
  208.  */
  209. int move()
  210. {
  211.     struct s_word  *wordp, *next;
  212.     int        died;
  213.     
  214.     died = 0;
  215.     for(wordp = words; wordp != NULL; wordp = next) {
  216.         next = wordp->nextword;
  217.         erase(wordp);
  218.         wordp->posy += wordp->drop;
  219.  
  220.         if(wordp->posy >= SCREENLENGTH) {
  221.             kill_word(wordp);
  222.             died++;
  223.         }
  224.         else {
  225.             putword(wordp);
  226.         }
  227.     }
  228.     return died;
  229. }
  230.  
  231. /*
  232.  * erase a word on the screen by printing the correct number of blanks
  233.  */
  234. void erase(wordp)
  235. struct s_word *wordp;
  236. {
  237.     int    i;
  238.  
  239.     gotoxy(wordp->posx, wordp->posy);
  240.     for(i = 0; i < wordp->length; i++)
  241.         putchar(' ');
  242. }
  243.  
  244. /*
  245.  * write the word to the screen with already typed letters highlighted
  246.  */
  247. void putword(wordp)
  248. struct s_word *wordp;
  249. {
  250.     int    i;
  251.  
  252.     gotoxy(wordp->posx, wordp->posy);
  253.  
  254.     /*
  255.      * print the letters in the word that have so far been matched
  256.      * correctly.
  257.      */
  258.     highlight(1);
  259.     for(i = 0; i < wordp->matches; i++)
  260.         putchar(wordp->word[i]);
  261.     highlight(0);
  262.  
  263.     /*
  264.      * print the rest of the word.
  265.      */
  266.     for(i = wordp->matches; i < wordp->length; i++)
  267.         putchar(wordp->word[i]);
  268. }
  269.  
  270. /*
  271.  * Here's the main routine of the actual game.
  272.  */
  273. int game()
  274. {
  275.     int        key;
  276.     unsigned    i;
  277.     int        died;
  278.     struct s_word    *curr_word, *temp_word;
  279.  
  280.     /*
  281.      * look to see if we already have a partial match, if not
  282.      * set the current word pointer to the first word
  283.      */
  284.     for(curr_word = words; curr_word; curr_word = curr_word->nextword)
  285.         if (curr_word->matches > 0)
  286.             break;
  287.     if (!curr_word)
  288.         curr_word = words;
  289.     
  290.     while(curr_word->matches < curr_word->length) {
  291.         for(i = 0; i < delay; i+= PAUSE) {
  292.             while((curr_word->matches != curr_word->length) &&
  293.                   ((key = key_pressed()) != -1)) {
  294.                 if(key == CTRL('L')) {
  295.                     redraw();
  296.                     continue;
  297.                 }
  298.                 if(key == CTRL('N')) {
  299.                     level++;
  300.                     delay = DELAY(level);
  301.                     if(delay < PAUSE)
  302.                         delay = PAUSE;
  303.                     status();
  304.                     continue;
  305.                 }
  306.                 /*
  307.                  * This stuff deals with collecting letters
  308.                  * for a word that has already been
  309.                  * started.  It's kind of clumsy the way
  310.                  * it's being done now and should be
  311.                  * cleaned up, but the obvious combination
  312.                  * of erase() and putword() generate too
  313.                  * much output to be used at 2400 baud.  (I
  314.                  * can't play too often at work)
  315.                  */
  316.                 if(curr_word->matches > 0 &&
  317.                    key == curr_word->word[curr_word->matches]) {
  318.                     gotoxy(curr_word->posx + curr_word->matches, curr_word->posy);
  319.                     highlight(1);
  320.                     putchar(curr_word->word[curr_word->matches]);
  321.                     highlight(0);
  322.                     gotoxy(SCREENWIDTH,SCREENLENGTH);
  323.                     fflush(stdout);
  324.                     curr_word->matches++;
  325.                     /*
  326.                      * fill the word with characters to
  327.                      * "explode" it.
  328.                      */
  329.                     if(curr_word->matches >= curr_word->length)
  330.                         for (i = 0; i<curr_word->length; i++)
  331.                             curr_word->word[i] = '-';
  332.                     continue;
  333.  
  334.                 } else if(temp_word = searchstr(key,
  335.                             curr_word->word,
  336.                             curr_word->matches)) {
  337.                     erase(temp_word);
  338.                     temp_word->matches = curr_word->matches;
  339.                     curr_word->matches = 0;
  340.                     putword(curr_word);
  341.                     curr_word = temp_word;
  342.                     curr_word->matches++;
  343.                 } else if(temp_word = searchchar(key)) {
  344.                     erase(temp_word);
  345.                     curr_word->matches = 0;
  346.                     putword(curr_word);
  347.                     curr_word = temp_word;
  348.                     curr_word->matches++;
  349.                 } else {
  350.                     ding();
  351.                     curr_word->matches = 0;
  352.                 }
  353.                 erase(curr_word);
  354.                 putword(curr_word);
  355.                 gotoxy(SCREENWIDTH,SCREENLENGTH);
  356.  
  357.                 fflush(stdout);
  358.             }
  359.             usleep(PAUSE);
  360.         }
  361.  
  362.         died = move();        /* NB: move may invalidate curr_word */
  363.         if (died > 0)
  364.         {
  365.             /*
  366.              * we only subtract lives if a word reaches the
  367.              * bottom in a normal round.  If a word reaches
  368.              * bottom during bonus play, just end the bonus
  369.              * round.
  370.              */
  371.             if(bonus == FALSE)
  372.                 lives -= died;
  373.             else if(died > 0)
  374.                 new_level();
  375.                 
  376.             if (lives < 0)
  377.                 lives = 0;
  378.             status();
  379.             gotoxy(SCREENWIDTH,SCREENLENGTH);
  380.             fflush(stdout);
  381.             return (lives != 0);
  382.         } else {
  383.             gotoxy(SCREENWIDTH,SCREENLENGTH);
  384.             fflush(stdout);
  385.         }
  386.         if((random() % ADDWORD) == 0) {
  387.             lastword = newword(lastword);
  388.             putword(lastword);
  389.         }
  390.     }
  391.  
  392.     /*
  393.      * all letters in the word have been correctly typed.
  394.      */
  395.  
  396.     /*
  397.      * erase the word
  398.      */
  399.     if(curr_word->length == curr_word->matches) {
  400.         ding(); ding();
  401.         erase(curr_word);
  402.     }
  403.  
  404.     /*
  405.      * add on an appropriate score.
  406.      */
  407.     score += curr_word->length + (2 * level);
  408.     letters+= curr_word->length;
  409.     word_count++;
  410.     status();
  411.  
  412.     /*
  413.      * delete the completed word and revise pointers.
  414.      */
  415.     kill_word(curr_word);
  416.  
  417.     /*
  418.      * increment the level if it's time.
  419.      */
  420.     if(word_count % LEVEL_CHANGE == 0)
  421.         new_level();
  422.  
  423.     return 1;
  424. }
  425.  
  426.  
  427. /*
  428.  * clear the screen and redraw it
  429.  */
  430. void redraw() {
  431.     clrdisp();
  432.     status();
  433.     fflush(stdout);
  434. }
  435.  
  436. /*
  437.  * display the status line in inverse video
  438.  */
  439. void status() {
  440.     static char    line[SCREENWIDTH];
  441.     int        i;
  442.  
  443.     sprintf(line, "Score: %-7uLevel: %-3uWords: %-6uLives: %-3dWPM: %-4d",
  444.         score, level, word_count, lives, wpm);
  445.  
  446.     /*
  447.      * fill the line with spaces
  448.      */
  449.     for(i = strlen(line); i < SCREENWIDTH - 2; i++)
  450.         line[i] = ' ';
  451.  
  452.     highlight(1);
  453.     gotoxy(0, SCREENLENGTH);
  454.     fputs(line, stdout);
  455.     highlight(0);
  456. }
  457.  
  458. /*
  459.  * do stuff to change levels.  This is where special rounds can be stuck in.
  460.  */
  461. void new_level()
  462. {
  463.     struct s_word    *next, *wordp;
  464.     static time_t    last_time = 0L;
  465.     time_t        curr_time;
  466.  
  467.     /*
  468.      * update the words per minute
  469.      */
  470.     time(&curr_time);
  471.     wpm = (letters / 5) / ((curr_time - last_time) / 60.0);
  472.     last_time = curr_time;
  473.     letters = 0;
  474.  
  475.     /*
  476.      * if we're inside a bonus round we don't need to change anything
  477.      * else so just take us out of the bonus round and exit this routine
  478.      */
  479.     if(bonus == TRUE) {
  480.         bonus = FALSE;
  481.         banner("Bonus round finished");
  482.  
  483.         /*
  484.          * erase all existing words so we can go back to a normal
  485.          * round
  486.          */
  487.         for(wordp = words; wordp != NULL; wordp = next) {
  488.             next = wordp->nextword;
  489.             kill_word(wordp);
  490.         }
  491.  
  492.         status();
  493.         return;
  494.     }
  495.  
  496.     levels_played++;
  497.  
  498.     /*
  499.      * If you start at a level other than 1, the level does not
  500.      * actually change until you've completed a number of levels equal
  501.      * to the starting level.
  502.      */
  503.     if(level <= levels_played)
  504.         level++;
  505.         
  506.     delay = DELAY(level);
  507.  
  508.     /*
  509.      * no one should ever reach a level where there is no delay, but
  510.      * just to be safe ...
  511.      */
  512.     if(delay < PAUSE)
  513.         delay = PAUSE;
  514.  
  515.     if((levels_played % 3 == 0) && (levels_played != 0)) {
  516.         bonus = TRUE;
  517.  
  518.         /*
  519.          * erase all existing words so we can have a bonus round
  520.          */
  521.         for(wordp = words; wordp != NULL; wordp = next) {
  522.             next = wordp->nextword;
  523.             kill_word(wordp);
  524.         }
  525.  
  526.         banner("Prepare for bonus words");
  527.         lives++;
  528.     }
  529.     
  530.     status();
  531. }
  532.  
  533. /*
  534.  * allocate memory for a new word and get it all set up to use.
  535.  */
  536. struct s_word *newword(wordp)
  537. struct s_word *wordp;
  538. {
  539.     struct s_word    *nword;
  540.     char        *word, *getword(), *bonusword();
  541.     int        length;
  542.  
  543.     if(bonus == TRUE)
  544.         word = bonusword();
  545.     else
  546.         word = getword();
  547.         
  548.     length = strlen(word);
  549.  
  550.     nword = (struct s_word *)malloc(sizeof(struct s_word) + length);
  551.     if(nword == (struct s_word *)0) {
  552.         perror("\nmalloc");
  553.         setterm(ORIG);
  554.         exit(1);
  555.     }
  556.  
  557.     strncpy(nword->word, word, length);
  558.     nword->length = length;
  559.     nword->drop = length > 6 ? 1 : length > 3 ? 2 : 3;
  560.     nword->matches = 0;
  561.     nword->posx = random() % ((SCREENWIDTH - 1) - nword->length);
  562.     nword->posy = 0;
  563.     nword->nextword = NULL;
  564.  
  565.     if(wordp != NULL)
  566.         wordp->nextword = nword;
  567.         
  568.     return nword;
  569. }
  570.  
  571. /*
  572.  * look at the first characters in each of the words to find one which
  573.  * one matches the amount of stuff typed so far
  574.  */
  575. struct s_word *searchstr(key, str, len)
  576. char *str;
  577. int  len, key;
  578. {
  579.     struct s_word    *wordp, *best;
  580.  
  581.     for(best = NULL, prev_word = NULL, wordp = words;
  582.         wordp != NULL;
  583.         prev_word = wordp,  wordp = wordp->nextword) {
  584.         if(wordp->length > len
  585.         && strncmp(wordp->word, str, len) == 0
  586.         && wordp->word[len] == key
  587.         && (!best || best->posy < wordp->posy))
  588.             best = wordp;
  589.     }
  590.  
  591.     return best;
  592. }
  593.  
  594.  
  595. /*
  596.  * look at the first character in each of the words to see if any match the
  597.  * one that was typed.
  598.  */
  599. struct s_word *searchchar(key)
  600. char key;
  601. {
  602.     struct s_word    *wordp, *best;
  603.     
  604.     for(best = NULL, prev_word = NULL, wordp = words; wordp != NULL;
  605.         prev_word = wordp,  wordp = wordp->nextword) {
  606.         if(wordp->word[0] == key
  607.          && (!best || best->posy < wordp->posy))
  608.             best = wordp;
  609.     }
  610.  
  611.     return best;
  612. }
  613.  
  614. void kill_word(wordp)
  615. struct s_word *wordp;
  616. {
  617.     struct s_word *temp, *prev = NULL;
  618.  
  619.     /*
  620.      * check to see if the current word is the first one on our list
  621.      */
  622.     if(wordp != words)
  623.         for(prev = words, temp = words->nextword; temp != wordp;) {
  624.             prev = temp;
  625.             temp = temp->nextword;
  626.         }
  627.             
  628.     if(prev != NULL) {
  629.         prev->nextword = wordp->nextword;
  630.     } else
  631.         words = wordp->nextword;
  632.  
  633.     if(wordp->nextword != NULL)
  634.         wordp->nextword = wordp->nextword->nextword;
  635.  
  636.     if(wordp == lastword)
  637.         lastword = prev;
  638.  
  639.     free((char *)wordp);
  640. }
  641.  
  642.  
  643. /*
  644.  * momentarily display a banner message across the screen and eliminate any
  645.  * random keystrokes form the last round
  646.  */
  647. void banner(text)
  648. char *text;
  649. {
  650.     /*
  651.      * display banner message
  652.      */
  653.     clrdisp();
  654.     gotoxy((SCREENWIDTH - strlen(text))/2, 10);
  655.     sleep(3);
  656.     puts(text);
  657.     gotoxy(SCREENWIDTH,SCREENLENGTH);
  658.     fflush(stdout);
  659.     sleep(2);
  660.     clrdisp();
  661.  
  662.     /*
  663.      * flush keyboard
  664.      */
  665.     while(key_pressed() != -1);
  666. }
  667.