home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 19 / CD_ASCQ_19_010295.iso / vrac / odoors50.zip / EX_VOTE4.C < prev    next >
C/C++ Source or Header  |  1994-09-24  |  45KB  |  1,233 lines

  1. /* EX_VOTE4.C - The files ex_vote1.c thru ex_vote5.c demonstrate the         */
  2. /*              possible steps in the development of a door program using    */
  3. /*              OpenDoors. The EZVote door program allows users to create    */
  4. /*              questions or surveys for other users to respond to. Users    */
  5. /*              are also able to view the results of voting on each topic    */
  6. /*              after having voted on the topic themselves.                  */
  7. /*                                                                           */
  8. /*              To recompile this program, follow the instructions in the    */
  9. /*              OpenDoors manual. Be sure to set your compiler to use the    */
  10. /*              large memory model, and add the ODOORL.LIB file to your      */
  11. /*              project/makefile.                                            */
  12. /*                                                                           */
  13. /*              Each of the ex_vote?.c files shows a further incremental     */
  14. /*              step in the construction of the EZVote door program, as      */
  15. /*              follows:                                                     */
  16. /*                                                                           */
  17. /*              EX_VOTE1.C - Demonstrates the basic elements of any door     */
  18. /*                           program. Contains the code to handle display    */
  19. /*                           of main menu, responding to the user's choice   */
  20. /*                           from the main menu, and common commands such    */
  21. /*                           as returning to the BBS and paging the system   */
  22. /*                           operator. Demonstrates basics of displaying     */
  23. /*                           text and retrieving input from user.            */
  24. /*                                                                           */
  25. /*              EX_VOTE2.C - Adds the user interface code to handle the main */
  26. /*                           menu commands specific to EZVote, such as       */
  27. /*                           answering questions, viewing the results of     */
  28. /*                           questions, and adding new questions.            */
  29. /*                           Demonstrates the use of OpenDoors functions     */
  30. /*                           such as od_input_str() for allowing the user to */
  31. /*                           input a sring of characters, and od_get_key()   */
  32. /*                           for inputting any character from the user. Also */
  33. /*                           introduces the od_control structure for         */
  34. /*                           obtaining information about the user and the    */
  35. /*                           system that the door program is running on.     */
  36. /*                                                                           */
  37. /*              EX_VOTE3.C - Adds the underlying file access functionality   */
  38. /*                           that is specific to EZVote. EZVote uses a       */
  39. /*                           relatively complex file structure in order to   */
  40. /*                           track which questions each user has voted on,   */
  41. /*                           and in order to allow a large number (200)      */
  42. /*                           question records to be stored in the file.      */
  43. /*                                                                           */
  44. /*              EX_VOTE4.C - Adds color to display and demonstrates the use  */
  45. /*                           of ANSI/AVATAR/RIP specific features.           */
  46. /*                                                                           */
  47. /*              EX_VOTE5.C - Adds support for the OpenDoors configuration    */
  48. /*                           file system, which provides automatic support   */
  49. /*                           for a wide variety of configurable options.     */
  50. /*                           EZVote adds its own configuration options to    */
  51. /*                           control program characteristics such as whether */
  52. /*                           or not the user is premitted to create their    */
  53. /*                           own questions. Also adds support for the        */
  54. /*                           OpenDoors log file system which records major   */
  55. /*                           actions taken by the user. In addition, this    */
  56. /*                           step enables the OpenDoors multiple-personality */
  57. /*                           system and adds other finishing touches.        */
  58.  
  59.  
  60. /* Include standard C header files required by EZVote. */
  61. #include <string.h>
  62. #include <stdio.h>
  63. #include <stdlib.h>
  64. #include <time.h>
  65. #include <errno.h>
  66. #include <ctype.h>
  67.  
  68. /* Include the OpenDoors header file. This line must be done in any program */
  69. /* using OpenDoors.                                                         */
  70. #include "opendoor.h"
  71.  
  72.  
  73. /* Manifest constants used by EZVote */
  74. #define NO_QUESTION              -1
  75. #define NEW_ANSWER               -1
  76.  
  77. #define QUESTIONS_VOTED_ON        0x0001
  78. #define QUESTIONS_NOT_VOTED_ON    0x0002
  79.  
  80. #define MAX_QUESTIONS             200
  81. #define MAX_USERS                 30000
  82. #define MAX_ANSWERS               15
  83. #define QUESTION_STR_SIZE         71
  84. #define ANSWER_STR_SIZE           31
  85.  
  86. #define USER_FILENAME             "EZVOTE.USR"
  87. #define QUESTION_FILENAME         "EZVOTE.QST"
  88.  
  89. #define FILE_ACCESS_MAX_WAIT      20
  90.  
  91. #define QUESTION_PAGE_SIZE        17
  92.  
  93.  
  94. /* Structure of records stored in the EZVOTE.USR file */
  95. typedef struct
  96. {
  97.    char szUserName[36];
  98.    char bVotedOnQuestion[MAX_QUESTIONS];
  99. } tUserRecord;
  100.               
  101. tUserRecord CurrentUserRecord;
  102. int nCurrentUserNumber;
  103.  
  104.  
  105. /* Structure of records stored in the EZVOTE.QST file */
  106. typedef struct
  107. {
  108.    char szQuestion[QUESTION_STR_SIZE];
  109.    char aszAnswer[MAX_ANSWERS][ANSWER_STR_SIZE];
  110.    int nTotalAnswers;
  111.    unsigned int auVotesForAnswer[MAX_ANSWERS];
  112.    unsigned int uTotalVotes;
  113.    char bCanAddAnswers;
  114.    char bDeleted;
  115.    char szCreatorName[36];
  116.    time_t lCreationTime;
  117. } tQuestionRecord;
  118.  
  119.  
  120. /* Prototypes for functions that form EZVote */
  121. void VoteOnQuestion(void);
  122. void ViewResults(void);
  123. int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord);
  124. void AddQuestion(void);
  125. void DeleteQuestion(void);
  126. int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation);
  127. void DisplayQuestionResult(tQuestionRecord *pQuestionRecord);
  128. int ReadOrAddCurrentUser(void);
  129. void WriteCurrentUser(void);
  130. FILE *ExculsiveFileOpen(char *pszFileName, char *pszMode);
  131. void WaitForEnter(void);
  132.  
  133.  
  134. /* main() function - Program execution begins here. */
  135. main()
  136. {
  137.    /* Variable to store user's choice from the menu */
  138.    char chMenuChoice;
  139.    char chYesOrNo;
  140.  
  141.    /* Initialize OpenDoors. This function call is optional, and can be used */
  142.    /* to force OpenDoors to read the door informtion file and begin door    */
  143.    /* operations. If a call to od_init() is not included in your program,   */
  144.    /* OpenDoors initialization will be performed at the time of your first  */
  145.    /* call to any OpenDoors function. */
  146.    od_init();
  147.  
  148.    /* Call the EZVote function ReadOrAddCurrentUser() to read the current */
  149.    /* user's record from the EZVote user file, or to add the user to the  */
  150.    /* file if this is the first time that they have used EZVote.          */
  151.    if(!ReadOrAddCurrentUser())
  152.    {
  153.       /* If unable to obtain a user record for the current user, then exit */
  154.       /* the door after displaying an error message.                       */
  155.       od_printf("Unable to access user file. File may be locked or full.\n\r");
  156.       WaitForEnter();
  157.       od_exit(1, FALSE);
  158.    }   
  159.  
  160.    /* Loop until the user choses to exit the door. For each iteration of  */
  161.    /* this loop, we display the main menu, get the user's choice from the */
  162.    /* menu, and perform the appropriate action for their choice.          */
  163.  
  164.    for(;;)
  165.    {
  166.       /* Clear the screen */
  167.       od_clr_scr();
  168.  
  169.       /* Display main menu */
  170.       od_printf("`bright red`                    EZVote - OpenDoors 5.00 demonstration Door\n\r");
  171.       od_printf("`dark red`");
  172.       if(od_control.user_ansi || od_control.user_avatar)
  173.       {
  174.          od_repeat((unsigned char)196, 79);
  175.       }
  176.       else
  177.       {
  178.          od_repeat('-', 79);
  179.       }
  180.       od_printf("\n\r\n\r\n\r`dark green`");
  181.       od_printf("                        [`bright green`V`dark green`] Vote on a question\n\r\n\r");
  182.       od_printf("                        [`bright green`R`dark green`] View the results of question\n\r\n\r");
  183.       od_printf("                        [`bright green`A`dark green`] Add a new question\n\r\n\r");
  184.       
  185.       /* If current user is the system operator, add a D function to permit */
  186.       /* deletion of unwanted questions.                                    */
  187.       if(strcmp(od_control.sysop_name, od_control.user_name) == 0)
  188.       {
  189.          od_printf("                        [`bright green`D`dark green`] Delete a question\n\r\n\r");
  190.       }
  191.       od_printf("                        [`bright green`P`dark green`] Page system operator for chat\n\r\n\r");
  192.       od_printf("                        [`bright green`E`dark green`] Exit door and return to the BBS\n\r\n\r");
  193.       od_printf("                        [`bright green`H`dark green`] End call (hangup)\n\r\n\r\n\r");
  194.       od_printf("`bright white`Press the key corresponding to the option of your choice.\n\r`dark green`");
  195.  
  196.       /* Get the user's choice from the main menu. This choice may only be */
  197.       /* V, R, A, D, P, E or H.                                            */
  198.       chMenuChoice = od_get_answer("VRADPEH");
  199.  
  200.       /* Perform the appropriate action based on the user's choice */
  201.       switch(chMenuChoice)
  202.       {
  203.          case 'V':
  204.             /* Call EZVote's function to vote on question */
  205.             VoteOnQuestion();
  206.             break;
  207.             
  208.          case 'R':
  209.             /* Call EZVote's function to view the results of voting */
  210.             ViewResults();
  211.             break;
  212.             
  213.          case 'A':
  214.             /* Call EZVote's function to add a new question */
  215.             AddQuestion();
  216.             break;
  217.  
  218.          case 'D':
  219.             /* Call EZVote's funciton to delete an existing question */
  220.             DeleteQuestion();
  221.             break;
  222.             
  223.          case 'P':
  224.             /* If the user pressed P, allow them page the system operator. */
  225.             od_page();
  226.             break;
  227.  
  228.          case 'E':
  229.             /* If the user pressed E, exit door and return to BBS. */
  230.             od_exit(0, FALSE);
  231.             break;
  232.  
  233.          case 'H':
  234.             /* If the user pressed H, ask whether they wish to hangup. */
  235.             od_printf("\n\rAre you sure you wish to hangup? (Y/N) ");
  236.  
  237.             /* Get user's response */
  238.             chYesOrNo = od_get_answer("YN");
  239.  
  240.             /* If user answered yes, exit door and hangup */
  241.             if(chYesOrNo == 'Y')
  242.             {
  243.                od_exit(0, TRUE);
  244.             }
  245.             break;
  246.       }
  247.    }
  248.  
  249.    return(0);
  250. }
  251.  
  252.  
  253. /* EZVote calls the VoteOnQuestion() function when the user chooses the   */
  254. /* vote command from the main menu. This function displays a list of      */
  255. /* available topics, asks for the user's answer to the topic they select, */
  256. /* and display's the results of voting on that topic.                     */
  257. void VoteOnQuestion(void)
  258. {
  259.    int nQuestion;
  260.    int nAnswer;
  261.    tQuestionRecord QuestionRecord;
  262.    char szNewAnswer[ANSWER_STR_SIZE];
  263.    char szUserInput[3];
  264.    FILE *fpFile;
  265.    int nPageLocation = 0;
  266.  
  267.    /* Loop until the user chooses to return to the main menu, or until */
  268.    /* there are no more questions to vote on.                          */
  269.    for(;;)   
  270.    {
  271.       /* Allow the user to choose a question from the list of questions */
  272.       /* that they have not voted on.                                   */
  273.       nQuestion = ChooseQuestion(QUESTIONS_NOT_VOTED_ON,
  274.          "                              Vote On A Question\n\r",
  275.          &nPageLocation);
  276.    
  277.  
  278.       /* If the user did not choose a question, return to main menu. */   
  279.       if(nQuestion == NO_QUESTION)
  280.       {
  281.          return;
  282.       }
  283.  
  284.       /* Read the question chosen by the user. */
  285.       if(!GetQuestion(nQuestion, &QuestionRecord))
  286.       {
  287.          /* If unable to access file, return to main menu. */
  288.          return;
  289.       }
  290.    
  291.       /* Don't allow addition of new answers if maximum number of answers */
  292.       /* have already been added.                                         */
  293.       if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
  294.       {
  295.          QuestionRecord.bCanAddAnswers = FALSE;
  296.       }
  297.    
  298.       /* Loop until user makes a valid respose. */
  299.       for(;;)
  300.       {
  301.          /* Display question to user. */
  302.  
  303.          /* Clear the screen. */
  304.          od_clr_scr();
  305.  
  306.          /* Display question itself. */
  307.          od_printf("`bright red`%s\n\r\n\r", QuestionRecord.szQuestion);
  308.  
  309.          /* Loop for each answer to the question. */   
  310.          for(nAnswer = 0; nAnswer < QuestionRecord.nTotalAnswers; ++nAnswer)
  311.          {
  312.             /* Display answer number and answer. */
  313.             od_printf("`bright green`%d. `dark green`%s\n\r",
  314.                nAnswer + 1,
  315.                QuestionRecord.aszAnswer[nAnswer]);
  316.          }
  317.  
  318.          /* Display prompt to user. */
  319.          od_printf("\n\r`bright white`Enter answer number, ");
  320.          if(QuestionRecord.bCanAddAnswers)
  321.          {
  322.             od_printf("[A] to add your own response, ");
  323.          }
  324.          od_printf("[Q] to quit: `dark green`");
  325.    
  326.          /* Get response from user. */
  327.          od_input_str(szUserInput, 2, ' ', 255);
  328.          /* Add a blank line. */      
  329.          od_printf("\n\r");
  330.    
  331.          /* If user entered Q, return to main menu. */
  332.          if(stricmp(szUserInput, "Q") == 0)
  333.          {
  334.             return;
  335.          }
  336.  
  337.          /* If user enetered A, and adding answers is premitted ... */
  338.          else if (stricmp(szUserInput, "A") == 0
  339.             && QuestionRecord.bCanAddAnswers)
  340.          {
  341.             /* ... Prompt for answer from user. */
  342.             od_printf("`bright green`Please enter your new answer:\n\r");
  343.             od_printf("`dark green`[------------------------------]\n\r ");
  344.          
  345.             /* Get string from user. */
  346.             od_input_str(szNewAnswer, ANSWER_STR_SIZE - 1, ' ', 255);
  347.          
  348.             /* Record that user entered a new answer answer. */
  349.             nAnswer = NEW_ANSWER;
  350.  
  351.             /* If user entered a valid answer, then exit loop. */
  352.             if(strlen(szNewAnswer) > 0)
  353.             {
  354.                break;
  355.             }         
  356.          }
  357.  
  358.          /* Otherwise, attempt to get answer number from user. */      
  359.          nAnswer = atoi(szUserInput) - 1;
  360.  
  361.          /* If user input is not a valid answer. */      
  362.          if(nAnswer < 0 || nAnswer >= QuestionRecord.nTotalAnswers)
  363.          {
  364.             /* Display message. */
  365.             od_printf("That is not a valid response.\n\r");
  366.             WaitForEnter();
  367.          }
  368.          else
  369.          {
  370.             /* Otherwise, exit loop. */
  371.             break;
  372.          }
  373.       }
  374.  
  375.       /* Add user's vote to question. */
  376.    
  377.       /* Open question file for exclusive access by this node. */
  378.       fpFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  379.       if(fpFile == NULL)
  380.       {
  381.          /* If unable to access file, display error and return. */
  382.          od_printf("Unable to access the question file.\n\r");
  383.          WaitForEnter();
  384.          return;
  385.       }
  386.    
  387.       /* Read the answer record from disk, because it may have been changed. */
  388.       /* by another node. */
  389.       fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  390.       if(fread(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  391.       {
  392.          /* If unable to access file, display error and return. */
  393.          fclose(fpFile);
  394.          od_printf("Umable to read from question file.\n\r");
  395.          WaitForEnter();
  396.          return;
  397.       }
  398.    
  399.       /* If user entered their own answer, try to add it to the question. */
  400.       if(nAnswer == NEW_ANSWER)
  401.       {
  402.          /* Check that there is still room for another answer. */
  403.          if(QuestionRecord.nTotalAnswers >= MAX_ANSWERS)
  404.          {
  405.             fclose(fpFile);
  406.             od_printf("Sorry, this question already has the maximum number of answers.\n\r");
  407.             WaitForEnter();
  408.             return;
  409.          }
  410.       
  411.          /* Set answer number to number of new answer. */
  412.          nAnswer = QuestionRecord.nTotalAnswers;
  413.       
  414.          /* Add 1 to total number of answers. */
  415.          ++QuestionRecord.nTotalAnswers;
  416.       
  417.          /* Initialize new answer string and count. */
  418.          strcpy(QuestionRecord.aszAnswer[nAnswer], szNewAnswer);
  419.          QuestionRecord.auVotesForAnswer[nAnswer] = 0;
  420.       }
  421.    
  422.       /* Add user's vote to question. */
  423.       ++QuestionRecord.auVotesForAnswer[nAnswer];
  424.       ++QuestionRecord.uTotalVotes;
  425.    
  426.       /* Write the question record back to the file. */
  427.       fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  428.       if(fwrite(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  429.       {
  430.          /* If unable to access file, display error and return. */
  431.          fclose(fpFile);
  432.          od_printf("Umable to write question to file.\n\r");
  433.          WaitForEnter();
  434.          return;
  435.       }
  436.    
  437.       /* Close the question file to allow access by other nodes. */
  438.       fclose(fpFile);
  439.    
  440.       /* Record that user has voted on this question. */
  441.       CurrentUserRecord.bVotedOnQuestion[nQuestion] = TRUE;
  442.    
  443.       /* Open user file for exclusive access by this node. */
  444.       fpFile = ExculsiveFileOpen(USER_FILENAME, "r+b");
  445.       if(fpFile == NULL)
  446.       {
  447.          /* If unable to access file, display error and return. */
  448.          od_printf("Unable to access the user file.\n\r");
  449.          WaitForEnter();
  450.          return;
  451.       }
  452.  
  453.       /* Update the user's record in the user file. */
  454.       fseek(fpFile, nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
  455.       if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpFile) != 1)
  456.       {
  457.          /* If unable to access file, display error and return. */
  458.          fclose(fpFile);
  459.          od_printf("Unable to write to user file.\n\r");
  460.          WaitForEnter();
  461.          return;
  462.       }
  463.    
  464.       /* Close the user file to allow access by other nodes. */
  465.       fclose(fpFile);
  466.    
  467.       /* Display the result of voting on this question to the user. */
  468.       DisplayQuestionResult(&QuestionRecord);
  469.    }
  470. }
  471.  
  472.  
  473. /* The ViewResults function is called when the user chooses the "view    */
  474. /* results" command from the main menu. This function alows the user to  */
  475. /* choose a question from the list of questions, and then displays the   */
  476. /* results of voting on that question.                                   */
  477. void ViewResults(void)
  478. {
  479.    int nChoice;
  480.    tQuestionRecord QuestionRecord;
  481.    int nPageLocation = 0;
  482.  
  483.    /* Loop until user chooses to return to main menu. */
  484.    for(;;)
  485.    {   
  486.       /* Allow the user to choose a question from the list of questions that */
  487.       /* they have already voted on.                                         */
  488.       nChoice = ChooseQuestion(QUESTIONS_VOTED_ON,
  489.          "                                 View Results\n\r", &nPageLocation);
  490.  
  491.       /* If the user did not choose a question, return to main menu. */   
  492.       if(nChoice == NO_QUESTION)
  493.       {
  494.          return;
  495.       }
  496.    
  497.       /* Read the specified question number from the question file. */
  498.       if(!GetQuestion(nChoice, &QuestionRecord))
  499.       {
  500.          return;
  501.       }
  502.    
  503.       /* Display the results for the selected question. */
  504.       DisplayQuestionResult(&QuestionRecord);
  505.    }
  506. }
  507.  
  508.  
  509. /* The GetQuestion function read the record for the specified question */
  510. /* number from the question file.                                      */
  511. int GetQuestion(int nQuestion, tQuestionRecord *pQuestionRecord)
  512. {
  513.    FILE *fpQuestionFile;
  514.  
  515.    /* Open the question file for exculsive access by this node. */
  516.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  517.    if(fpQuestionFile == NULL)
  518.    {
  519.       /* If unable to access file, display error and return. */
  520.       od_printf("Unable to access the question file.\n\r");
  521.       WaitForEnter();
  522.       return(FALSE);
  523.    }
  524.    
  525.    /* Move to location of question in file. */
  526.    fseek(fpQuestionFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  527.    
  528.    /* Read the question from the file. */
  529.    if(fread(pQuestionRecord, sizeof(tQuestionRecord), 1, fpQuestionFile) != 1)
  530.    {
  531.       /* If unable to access file, display error and return. */
  532.       fclose(fpQuestionFile);
  533.       od_printf("Umable to read from question file.\n\r");
  534.       WaitForEnter();
  535.       return(FALSE);;
  536.    }
  537.    
  538.    /* Close the question file to allow access by other nodes. */
  539.    fclose(fpQuestionFile);
  540.  
  541.    /* Return with success. */
  542.    return(TRUE);
  543. }
  544.  
  545.  
  546. /* The AddQuestion() function is called when the user chooses the "add    */
  547. /* question" option from the main menu. This function allows the user     */
  548. /* to enter a new question, possible responses, and save the question for */
  549. /* other users to vote on.                                                */
  550. void AddQuestion(void)
  551. {
  552.    tQuestionRecord QuestionRecord;
  553.    FILE *fpQuestionFile;
  554.  
  555.    /* Clear the screen. */
  556.    od_clr_scr();
  557.    
  558.    /* Display screen header. */
  559.    od_printf("`bright red`                                Add A Question\n\r");
  560.    od_printf("`dark red`");
  561.    if(od_control.user_ansi || od_control.user_avatar)
  562.    {
  563.       od_repeat((unsigned char)196, 79);
  564.    }
  565.    else
  566.    {
  567.       od_repeat('-', 79);
  568.    }
  569.    od_printf("\n\r\n\r");
  570.    
  571.    /* Obtain quesiton text from the user. */
  572.    od_printf("`bright green`Enter Your Question (blank line cancels)\n\r");
  573.    od_printf("`dark green`[----------------------------------------------------------------------]\n\r ");
  574.    od_input_str(QuestionRecord.szQuestion, QUESTION_STR_SIZE - 1, ' ', 255);
  575.    
  576.    /* If question was empty, then return to main menu. */
  577.    if(strlen(QuestionRecord.szQuestion) == 0)
  578.    {
  579.       return;
  580.    }
  581.    
  582.    /* Display prompt for answers. */
  583.    od_printf("\n\r`bright green`Enter Possible Answers (blank line when done)\n\r");
  584.    od_printf("`dark green`   [------------------------------]\n\r");
  585.    
  586.    /* Loop, getting answers from user. */
  587.    for(QuestionRecord.nTotalAnswers = 0;
  588.        QuestionRecord.nTotalAnswers < MAX_ANSWERS;
  589.        QuestionRecord.nTotalAnswers++)
  590.    {
  591.       /* Display prompt with answer number. */
  592.       od_printf("`bright green`%2d: `dark green`", QuestionRecord.nTotalAnswers + 1);
  593.       
  594.       /* Get string from user. */
  595.       od_input_str(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers],
  596.          ANSWER_STR_SIZE - 1, ' ', 255);
  597.          
  598.       /* If string was empty, then exit loop. */
  599.       if(strlen(QuestionRecord.aszAnswer[QuestionRecord.nTotalAnswers]) == 0)
  600.       {
  601.          break;
  602.       }
  603.       
  604.       /* Reset count of votes for this answer to zero. */
  605.       QuestionRecord.auVotesForAnswer[QuestionRecord.nTotalAnswers] = 0;
  606.    }
  607.    
  608.    /* If no answers were supplied, then cancel, returning to main menu. */
  609.    if(QuestionRecord.nTotalAnswers == 0)
  610.    {
  611.       return;
  612.    }
  613.  
  614.    /* Ask whether users should be able to add their own answers. */
  615.    od_printf("\n\r`bright green`Should voters be able to add their own options? (Y/N) `dark green`");
  616.    
  617.    /* Get answer from user. */
  618.    if(od_get_answer("YN") == 'Y')
  619.    {
  620.       /* If user pressed the 'Y' key. */
  621.       od_printf("Yes\n\r\n\r");
  622.       
  623.       /* Record user's response. */
  624.       QuestionRecord.bCanAddAnswers = TRUE;
  625.    }
  626.    else
  627.    {
  628.       /* If user pressed the 'N' key. */
  629.       od_printf("No\n\r\n\r");
  630.  
  631.       /* Record user's response. */
  632.       QuestionRecord.bCanAddAnswers = FALSE;
  633.    }
  634.    
  635.    /* Confirm save of new question. */
  636.    od_printf("`bright green`Do you wish to save this new question? (Y/N) `dark green`");
  637.    
  638.    /* If user does not want to save the question, return to main menu now. */
  639.    if(od_get_answer("YN") == 'N')
  640.    {
  641.       return;
  642.    }
  643.  
  644.    /* Set total number of votes for this question to 0. */   
  645.    QuestionRecord.uTotalVotes = 0;
  646.    
  647.    /* Set creator name and creation time for this question. */
  648.    strcpy(QuestionRecord.szCreatorName, od_control.user_name);
  649.    QuestionRecord.lCreationTime = time(NULL);
  650.    QuestionRecord.bDeleted = FALSE;
  651.    
  652.    /* Open question file for exclusive access by this node. */
  653.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "a+b");
  654.    if(fpQuestionFile == NULL)
  655.    {
  656.       od_printf("Unable to access the question file.\n\r");
  657.       WaitForEnter();
  658.       return;
  659.    }
  660.    
  661.    /* Determine number of records in question file. */
  662.    fseek(fpQuestionFile, 0, SEEK_END);
  663.    
  664.    /* If question file is full, display message and return to main menu */
  665.    /* after closing file.                                               */
  666.    if(ftell(fpQuestionFile) / sizeof(tQuestionRecord) >= MAX_QUESTIONS)
  667.    {
  668.       fclose(fpQuestionFile);
  669.       od_printf("Cannot add another question, EZVote is limisted to %d questions.\n\r", MAX_QUESTIONS);
  670.       WaitForEnter();
  671.       return;
  672.    }
  673.    
  674.    /* Add new question to file. */
  675.    if(fwrite(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) != 1)
  676.    {
  677.       fclose(fpQuestionFile);
  678.       od_printf("Umable to write to question file.\n\r");
  679.       WaitForEnter();
  680.       return;
  681.    }
  682.    
  683.    /* Close question file, allowing other nodes to access file. */
  684.    fclose(fpQuestionFile);
  685. }
  686.  
  687.  
  688. /* The DeleteQuestion() function is called when the sysop chooses the   */
  689. /* "delete question" option from the main menu. This function displays  */
  690. /* a list of all questions, allowing the sysop to choose a question for */
  691. /* deletion.                                                            */
  692. void DeleteQuestion(void)
  693. {
  694.    int nQuestion;
  695.    tQuestionRecord QuestionRecord;
  696.    FILE *fpFile;
  697.    int nPageLocation = 0;
  698.  
  699.    /* Check that user is system operator. */
  700.    if(strcmp(od_control.user_name, od_control.sysop_name) != 0)
  701.    {
  702.       return;
  703.    }
  704.  
  705.    /* Allow the user to choose a question from the list of all questions. */
  706.    nQuestion = ChooseQuestion(QUESTIONS_NOT_VOTED_ON | QUESTIONS_VOTED_ON,
  707.       "                              Delete A Question\n\r", &nPageLocation);
  708.  
  709.    /* If the user did not choose a question, return to main menu. */   
  710.    if(nQuestion == NO_QUESTION)
  711.    {
  712.       return;
  713.    }
  714.  
  715.    /* Read the question chosen by the user. */
  716.    if(!GetQuestion(nQuestion, &QuestionRecord))
  717.    {
  718.       /* If unable to access file, return to main menu. */
  719.       return;
  720.    }
  721.  
  722.    /* Confirm deletion of this question. */
  723.    od_printf("\n\r`bright green`Are you sure you want to delete the question:\n\r   `dark green`%s\n\r",
  724.       QuestionRecord.szQuestion);
  725.    od_printf("`bright green`[Y]es or [N]o?\n\r`dark green`");
  726.  
  727.    /* If user canceled deletion, return now. */   
  728.    if(od_get_answer("YN") == 'N')
  729.    {
  730.       return;
  731.    }
  732.  
  733.    /* Mark the question as being deleted. */
  734.    QuestionRecord.bDeleted = TRUE;
  735.    
  736.    /* Open question file for exclusive access by this node. */
  737.    fpFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  738.    if(fpFile == NULL)
  739.    {
  740.       /* If unable to access file, display error and return. */
  741.       od_printf("Unable to access the question file.\n\r");
  742.       WaitForEnter();
  743.       return;
  744.    }
  745.  
  746.    /* Write the question record back to the file. */
  747.    fseek(fpFile, (long)nQuestion * sizeof(tQuestionRecord), SEEK_SET);
  748.    if(fwrite(&QuestionRecord, sizeof(tQuestionRecord), 1, fpFile) != 1)
  749.    {
  750.       /* If unable to access file, display error and return. */
  751.       fclose(fpFile);
  752.       od_printf("Umable to write question to file.\n\r");
  753.       WaitForEnter();
  754.       return;
  755.    }
  756.    
  757.    /* Close the question file to allow access by other nodes. */
  758.    fclose(fpFile);
  759. }
  760.  
  761.  
  762. /* The ChooseQuestion() function provides a list of questions and allows   */
  763. /* the user to choose a particular question, cancel back to the main menu, */ 
  764. /* and page up and down in the list of questions. Depending upon the value */
  765. /* of the nFromWhichQuestions parameter, this function will present a list */
  766. /* of questions that the user has voted on, a list of questions that the   */
  767. /* user has not voted on, or a list of all questions.                      */
  768. int ChooseQuestion(int nFromWhichQuestions, char *pszTitle, int *nLocation)
  769. {
  770.    int nCurrent;
  771.    int nFileQuestion = 0;
  772.    int nPagedToQuestion = *nLocation;
  773.    int nDisplayedQuestion = 0;
  774.    char bVotedOnQuestion;
  775.    char chCurrent;
  776.    tQuestionRecord QuestionRecord;
  777.    FILE *fpQuestionFile;
  778.    static char szQuestionName[MAX_QUESTIONS][QUESTION_STR_SIZE];
  779.    static int nQuestionNumber[MAX_QUESTIONS];
  780.    
  781.    /* Attempt to open question file. */
  782.    fpQuestionFile = ExculsiveFileOpen(QUESTION_FILENAME, "r+b");
  783.  
  784.    /* If unable to open question file, assume that no questions have been */
  785.    /* created.                                                            */
  786.    if(fpQuestionFile == NULL)
  787.    {
  788.       /* Display "no questions yet" message. */
  789.       od_printf("\n\rNo questions have been created so far.\n\r");
  790.       
  791.       /* Wait for user to press enter. */
  792.       WaitForEnter();
  793.       
  794.       /* Indicate that no question has been chosen. */
  795.       return(NO_QUESTION);
  796.    }
  797.    
  798.    /* Loop for every question record in the file. */
  799.    while(fread(&QuestionRecord, sizeof(QuestionRecord), 1, fpQuestionFile) == 1)
  800.    {
  801.       /* Determine whether or not the user has voted on this question. */
  802.       bVotedOnQuestion = CurrentUserRecord.bVotedOnQuestion[nFileQuestion];
  803.       
  804.       /* If this is the kind of question that the user is choosing from */
  805.       /* right now.                                                     */
  806.       if((bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_VOTED_ON)) ||
  807.          (!bVotedOnQuestion && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON)))
  808.       {
  809.          /* If question is not deleted. */
  810.          if(!QuestionRecord.bDeleted)
  811.          {
  812.             /* Add this question to list to be displayed. */
  813.             strcpy(szQuestionName[nDisplayedQuestion],
  814.                QuestionRecord.szQuestion);
  815.             nQuestionNumber[nDisplayedQuestion] = nFileQuestion;
  816.          
  817.             /* Add one to number of questions to be displayed in list. */
  818.             nDisplayedQuestion++;
  819.          }
  820.       }
  821.       
  822.       /* Move to next question in file. */
  823.       ++nFileQuestion;
  824.    }   
  825.    
  826.    /* Close question file to allow other nodes to access the file. */
  827.    fclose(fpQuestionFile);
  828.  
  829.    /* If there are no questions for the user to choose, display an */
  830.    /* appropriate message and return. */
  831.    if(nDisplayedQuestion == 0)
  832.    {
  833.       /* If we were to list all questions. */
  834.       if((nFromWhichQuestions & QUESTIONS_VOTED_ON)
  835.          && (nFromWhichQuestions & QUESTIONS_NOT_VOTED_ON))
  836.       {
  837.          od_printf("\n\rThere are no questions.\n\r");
  838.       }
  839.       /* If we were to list questions that the user has voted on. */
  840.       else if(nFromWhichQuestions & QUESTIONS_VOTED_ON)
  841.       {
  842.          od_printf("\n\rThere are no questions that you have voted on.\n\r");
  843.       }
  844.       /* Otherwise, we were to list questions that use has not voted on. */
  845.       else
  846.       {
  847.          od_printf("\n\rYou have voted on all the questions.\n\r");
  848.       }
  849.       
  850.       /* Wait for user to press enter key. */
  851.       WaitForEnter();
  852.       
  853.       /* Return, indicating that no question was chosen. */
  854.       return(NO_QUESTION);
  855.    }
  856.  
  857.    /* Ensure that initial paged to location is within range. */
  858.    while(nPagedToQuestion >= nDisplayedQuestion)
  859.    {
  860.       nPagedToQuestion -= QUESTION_PAGE_SIZE;
  861.    }
  862.  
  863.    /* Loop, displaying current page of questions, until the user makes a */
  864.    /* choice.                                                            */
  865.    for(;;)
  866.    {
  867.       /* Clear the screen. */
  868.       od_clr_scr();
  869.  
  870.       /* Display header. */
  871.       od_printf("`bright red`");
  872.       od_printf(pszTitle);
  873.       od_printf("`dark red`");
  874.       if(od_control.user_ansi || od_control.user_avatar)
  875.       {
  876.          od_repeat((unsigned char)196, 79);
  877.       }
  878.       else
  879.       {
  880.          od_repeat('-', 79);
  881.       }
  882.       od_printf("\n\r");
  883.    
  884.       /* Display list of questions on this page. */
  885.       for(nCurrent = 0;
  886.          nCurrent < QUESTION_PAGE_SIZE
  887.          && nCurrent < (nDisplayedQuestion - nPagedToQuestion);
  888.          ++nCurrent)
  889.       {
  890.          /* Determine character to display for current line. */
  891.          if(nCurrent < 9)
  892.          {
  893.             chCurrent = (char)('1' + nCurrent);
  894.          }
  895.          else
  896.          {
  897.             chCurrent = (char)('A' + (nCurrent - 9));
  898.          }
  899.       
  900.          /* Display this question's title. */
  901.          od_printf("`bright green`%c.`dark green`", chCurrent);
  902.          od_printf(" %s\n\r", szQuestionName[nCurrent + nPagedToQuestion]);
  903.       }
  904.  
  905.       /* Display prompt for input. */
  906.       od_printf("\n\r`bright white`[Page %d]  Choose a question or",
  907.          (nPagedToQuestion / QUESTION_PAGE_SIZE) + 1);
  908.       if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
  909.       {
  910.          od_printf(" [N]ext page,");
  911.       }
  912.       if(nPagedToQuestion > 0)
  913.       {
  914.          od_printf(" [P]revious page,");
  915.       }
  916.       od_printf(" [Q]uit.\n\r");
  917.       
  918.       /* Loop until the user makes a valid choice. */
  919.       for(;;)
  920.       {      
  921.          /* Get input from user */
  922.          chCurrent = (char)od_get_key(TRUE);
  923.          chCurrent = (char)toupper(chCurrent);
  924.       
  925.          /* Respond to user's input. */
  926.       
  927.          /* If user pressed Q key. */
  928.          if(chCurrent == 'Q')
  929.          {
  930.             /* Return without a choosing a question. */
  931.             return(NO_QUESTION);
  932.          }
  933.       
  934.          /* If user pressed P key. */
  935.          else if(chCurrent == 'P')
  936.          {
  937.             /* If we are not at the first page. */
  938.             if(nPagedToQuestion > 0)
  939.             {
  940.                /* Move paged to location up one page. */
  941.                nPagedToQuestion -= QUESTION_PAGE_SIZE;
  942.                
  943.                /* Exit user input loop to display next page. */
  944.                break;
  945.             }
  946.          }
  947.       
  948.          /* If user pressed N key. */
  949.          else if(chCurrent == 'N')
  950.          {
  951.             /* If there is more questions after this page. */
  952.             if(nPagedToQuestion < nDisplayedQuestion - QUESTION_PAGE_SIZE)
  953.             {
  954.                /* Move paged.to location down one page. */
  955.                nPagedToQuestion += QUESTION_PAGE_SIZE;
  956.  
  957.                /* Exit user input loop to display next page. */
  958.                break;
  959.             }
  960.          }
  961.       
  962.          /* Otherwise, check whether the user chose a valid question. */
  963.          else if ((chCurrent >= '1' && chCurrent <= '9')
  964.             || (chCurrent >= 'A' && chCurrent <= 'H'))
  965.          {
  966.             /* Get question number from key pressed. */
  967.             if(chCurrent >= '1' && chCurrent <= '9')
  968.             {
  969.                nCurrent = chCurrent - '1';
  970.             }
  971.             else
  972.             {
  973.                nCurrent = (chCurrent - 'A') + 9;
  974.             }
  975.          
  976.             /* Add current paged to position to user's choice. */
  977.             nCurrent += nPagedToQuestion;
  978.  
  979.             /* If this is valid question number. */            
  980.             if(nCurrent < nDisplayedQuestion)
  981.             {
  982.                /* Set caller's current question number. */
  983.                *nLocation = nPagedToQuestion;
  984.             
  985.                /* Return actual question number in file. */
  986.                return(nQuestionNumber[nCurrent]);
  987.             }
  988.          }
  989.       }
  990.    }
  991. }
  992.  
  993.  
  994. /* The DisplayQuestionResult() function is called to display the results */
  995. /* of voting on a paricular question, and is passed the question record  */
  996. /* of the question. This function is called when the user selects a      */
  997. /* question using the "view results" option, and is also called after    */
  998. /* the user has voted on a question, to display the results of voting on */
  999. /* that question.                                                        */
  1000. void DisplayQuestionResult(tQuestionRecord *pQuestionRecord)
  1001. {
  1002.    int nAnswer;
  1003.    int uPercent;
  1004.  
  1005.    /* Clear the screen. */
  1006.    od_clr_scr();
  1007.  
  1008.    /* Check that there have been votes on this question. */
  1009.    if(pQuestionRecord->uTotalVotes == 0)
  1010.    {
  1011.       /* If there have been no votes for this question, display a message */
  1012.       /* and return.                                                      */
  1013.       od_printf("Nobody has voted on this question yet.\n\r");
  1014.       WaitForEnter();
  1015.       return;
  1016.    }
  1017.  
  1018.    /* Display question itself. */
  1019.    od_printf("`bright red`%s\n\r", pQuestionRecord->szQuestion);
  1020.  
  1021.    /* Display author's name. */
  1022.    od_printf("`dark red`Question created by %s on %s\n\r",
  1023.       pQuestionRecord->szCreatorName,
  1024.       ctime(&pQuestionRecord->lCreationTime));
  1025.    
  1026.    /* Display heading for responses. */
  1027.    od_printf("`bright green`Response                        Votes  Percent  Graph\n\r`dark green`");
  1028.    if(od_control.user_ansi || od_control.user_avatar)
  1029.    {
  1030.       od_repeat((unsigned char)196, 79);
  1031.    }
  1032.    else
  1033.    {
  1034.       od_repeat('-', 79);
  1035.    }
  1036.    od_printf("\n\r");
  1037.  
  1038.    /* Loop for each answer to the question. */   
  1039.    for(nAnswer = 0; nAnswer < pQuestionRecord->nTotalAnswers; ++nAnswer)
  1040.    {
  1041.       /* Determine percent of users who voted for this answer. */
  1042.       uPercent = (pQuestionRecord->auVotesForAnswer[nAnswer] * 100)
  1043.          / pQuestionRecord->uTotalVotes;
  1044.       
  1045.       /* Display answer, total votes and percentage of votes. */
  1046.       od_printf("`dark green`%-30.30s  %-5u  %3u%%     `bright white`",
  1047.          pQuestionRecord->aszAnswer[nAnswer],
  1048.          pQuestionRecord->auVotesForAnswer[nAnswer],
  1049.          uPercent);
  1050.  
  1051.       /* Display a bar graph corresponding to percent of users who voted */
  1052.       /* for this answer.                                                */
  1053.       if(od_control.user_ansi || od_control.user_avatar)
  1054.       {
  1055.          od_repeat((unsigned char)220, (unsigned char)((uPercent * 31) / 100));
  1056.       }
  1057.       else
  1058.       {
  1059.          od_repeat('=', (unsigned char)((uPercent * 31) / 100));
  1060.       }
  1061.  
  1062.       /* Move to next line. */
  1063.       od_printf("\n\r");
  1064.    }
  1065.    
  1066.    /* Display footer. */
  1067.    od_printf("`dark green`");
  1068.    if(od_control.user_ansi || od_control.user_avatar)
  1069.    {
  1070.       od_repeat((unsigned char)196, 79);
  1071.    }
  1072.    else
  1073.    {
  1074.       od_repeat('-', 79);
  1075.    }
  1076.    od_printf("\n\r");
  1077.    od_printf("`dark green`                         TOTAL: %u\n\r\n\r",
  1078.       pQuestionRecord->uTotalVotes);
  1079.    
  1080.    /* Wait for user to press enter. */
  1081.    WaitForEnter();
  1082. }
  1083.  
  1084.  
  1085. /* The ReadOrAddCurrentUser() function is used by EZVote to search the    */
  1086. /* EZVote user file for the record containing information on the user who */
  1087. /* is currently using the door. If this is the first time that the user   */
  1088. /* has used this door, then their record will not exist in the user file. */
  1089. /* In this case, this function will add a new record for the current      */
  1090. /* user. This function returns TRUE on success, or FALSE on failure.      */
  1091. int ReadOrAddCurrentUser(void)
  1092. {
  1093.    FILE *fpUserFile;
  1094.    int bGotUser = FALSE;
  1095.    int nQuestion;
  1096.  
  1097.    /* Attempt to open the user file for exclusize access by this node.     */
  1098.    /* This function will wait up to the pre-set amount of time (as defined */   
  1099.    /* near the beginning of this file) for access to the user file.        */
  1100.    fpUserFile = ExculsiveFileOpen(USER_FILENAME, "a+b");
  1101.  
  1102.    /* If unable to open user file, return with failure. */   
  1103.    if(fpUserFile == NULL)
  1104.    {
  1105.       return(FALSE);
  1106.    }
  1107.  
  1108.    /* Begin with the current user record number set to 0. */
  1109.    nCurrentUserNumber = 0;
  1110.  
  1111.    /* Loop for each record in the file */
  1112.    while(fread(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1113.    {
  1114.       /* If name in record matches the current user name ... */
  1115.       if(strcmp(CurrentUserRecord.szUserName, od_control.user_name) == 0)
  1116.       {
  1117.          /* ... then record that we have found the user's record, */
  1118.          bGotUser = TRUE;
  1119.          
  1120.          /* and exit the loop. */
  1121.          break;
  1122.       }
  1123.  
  1124.       /* Move user record number to next user record. */      
  1125.       nCurrentUserNumber++;
  1126.    }
  1127.  
  1128.    /* If the user was not found in the file, attempt to add them as a */
  1129.    /* new user if the user file is not already full.                  */
  1130.    if(!bGotUser && nCurrentUserNumber < MAX_USERS)
  1131.    {
  1132.       /* Place the user's name in the current user record. */
  1133.       strcpy(CurrentUserRecord.szUserName, od_control.user_name);
  1134.       
  1135.       /* Record that user hasn't voted on any of the questions. */
  1136.       for(nQuestion = 0; nQuestion < MAX_QUESTIONS; ++nQuestion)
  1137.       {
  1138.          CurrentUserRecord.bVotedOnQuestion[nQuestion] = FALSE;
  1139.       }
  1140.       
  1141.       /* Write the new record to the file. */
  1142.       if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1143.       {
  1144.          /* If write succeeded, record that we now have a valid user record. */
  1145.          bGotUser = TRUE;
  1146.       }
  1147.    }
  1148.  
  1149.    /* Close the user file to allow other nodes to access it. */
  1150.    fclose(fpUserFile);
  1151.  
  1152.    /* Return, indciating whether or not a valid user record now exists for */
  1153.    /* the user that is currently online.                                   */   
  1154.    return(bGotUser);
  1155. }
  1156.  
  1157.  
  1158. /* The WriteCurrentUser() function is called to save the information on the */
  1159. /* user who is currently using the door, to the EZVOTE.USR file.            */
  1160. void WriteCurrentUser(void)
  1161. {
  1162.    FILE *fpUserFile;
  1163.  
  1164.    /* Attempt to open the user file for exclusize access by this node.     */
  1165.    /* This function will wait up to the pre-set amount of time (as defined */   
  1166.    /* near the beginning of this file) for access to the user file.        */
  1167.    fpUserFile = ExculsiveFileOpen(USER_FILENAME, "r+b");
  1168.  
  1169.    /* If unable to access the user file, display an error message and */
  1170.    /* return.                                                         */
  1171.    if(fpUserFile == NULL)
  1172.    {
  1173.       od_printf("Unable to access the user file.\n\r");
  1174.       WaitForEnter();
  1175.       return;
  1176.    }
  1177.    
  1178.    /* Move to appropriate location in user file for the current user's */
  1179.    /* record. */
  1180.    fseek(fpUserFile, (long)nCurrentUserNumber * sizeof(tUserRecord), SEEK_SET);
  1181.  
  1182.    /* Write the new record to the file. */
  1183.    if(fwrite(&CurrentUserRecord, sizeof(tUserRecord), 1, fpUserFile) == 1)
  1184.    {
  1185.       /* If unable to write the record, display an error message. */
  1186.       fclose(fpUserFile);
  1187.       od_printf("Unable to update your user record file.\n\r");
  1188.       WaitForEnter();
  1189.       return;
  1190.    }
  1191.    
  1192.    /* Close the user file to allow other nodes to access it again. */
  1193.    fclose(fpUserFile);
  1194. }
  1195.  
  1196.  
  1197. /* This function opens the specified file in the specified mode for         */
  1198. /* exculsive access by this node; while the file is open, other nodes will  */
  1199. /* be unable to open the file. This function will wait for up to the number */
  1200. /* of seconds set by FILE_ACCESS_MAX_WAIT, which is defined near the        */
  1201. /* beginning of this file.                                                  */
  1202. FILE *ExculsiveFileOpen(char *pszFileName, char *pszMode)
  1203. {
  1204.    FILE *fpFile = NULL;
  1205.    time_t StartTime = time(NULL);
  1206.  
  1207.    /* Attempt to open the file while there is still time remaining. */    
  1208.    while((fpFile = fopen(pszFileName, pszMode)) == NULL
  1209.       && errno == EACCES
  1210.       && difftime(time(NULL), StartTime) < FILE_ACCESS_MAX_WAIT)
  1211.    {
  1212.       /* If we were unable to open the file, call od_kernal, so that    */
  1213.       /* OpenDoors can continue to respond to sysop function keys, loss */
  1214.       /* of connection, etc.                                            */
  1215.       od_kernal();
  1216.    }
  1217.  
  1218.    /* Return FILE pointer for opened file, if any. */   
  1219.    return(fpFile);
  1220. }
  1221.  
  1222.  
  1223. /* The WaitForEnter() function is used by EZVote to create its custom */
  1224. /* "Press [ENTER] to continue." prompt.                               */
  1225. void WaitForEnter(void)
  1226. {
  1227.    /* Display prompt. */
  1228.    od_printf("`bright white`Press [ENTER] to continue.\n\r");
  1229.    
  1230.    /* Wait for a Carriage Return or Line Feed character from the user. */
  1231.    od_get_answer("\n\r");
  1232. }
  1233.