ex_ski.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. /* EX_SKI.C - EX_SKI is a simple but addictive door game that is written */
  2. /* using OpenDoors. In this action game, the player must control */
  3. /* a skier through a downhill slalom course. The user may turn */
  4. /* the skier left or right, and the game ends as soon as the */
  5. /* player skis outside the marked course. The game begins at */
  6. /* an easy level, but quickly becomes more and more difficult */
  7. /* as the course to be navigated becomes more and more narrow. */
  8. /* The game maintains a list of players with high scores, and */
  9. /* this list may be viewed from the main menu. */
  10. /* */
  11. /* This program shows how to do the following: */
  12. /* */
  13. /* - Maintain a high-score file in a game, in a multi-node */
  14. /* compatible manner. */
  15. /* - How to use your own terminal control sequences. */
  16. /* - How to perform reasonably percise timing under both DOS */
  17. /* and Windows. */
  18. /* Header file for the OpenDoors API */
  19. #include "OpenDoor.h"
  20. /* Other required C header files */
  21. #include <string.h>
  22. #include <stdio.h>
  23. #include <time.h>
  24. #include <errno.h>
  25. #include <stdlib.h>
  26. #include "genwrap.h"
  27. /* Hard-coded configurable constants - change these values to alter game */
  28. #define HIGH_SCORES 15 /* Number of high scores in list */
  29. #define INITIAL_COURSE_WIDTH 30 /* Initial width of ski course */
  30. #define MINIMUM_COURSE_WIDTH 4 /* Minimum width of course */
  31. #define DECREASE_WIDTH_AFTER 100 /* # of ticks before course narrows */
  32. #define CHANGE_DIRECTION 10 /* % of ticks course changes direction */
  33. #define MAX_NAME_SIZE 35 /* Maximum characters in player name */
  34. #define WAIT_FOR_FILE 10 /* Time to wait for access to file */
  35. #define SCORE_FILENAME "skigame.dat" /* Name of high score file */
  36. /* High-score file format structure */
  37. typedef struct
  38. {
  39. char szPlayerName[MAX_NAME_SIZE + 1];
  40. DWORD lnHighScore;
  41. time_t lnPlayDate;
  42. } tHighScoreRecord;
  43. typedef struct
  44. {
  45. tHighScoreRecord aRecord[HIGH_SCORES];
  46. } tHighScoreFile;
  47. /* Prototypes for functions defined and used in this file */
  48. FILE *OpenAndReadHighScores(tHighScoreFile *pFileContents);
  49. void CloseHighScores(FILE *pfHighScoreFile);
  50. void WriteHighScores(FILE *pfHighScoreFile, tHighScoreFile *pFileContents);
  51. FILE *OpenExclusiveFile(char *pszFileName, char *pszAccess, time_t Wait);
  52. int FileExists(char *pszFileName);
  53. void ShowHighScores(void);
  54. void PlayGame(void);
  55. void SpaceRight(int nColumns);
  56. void MoveLeft(int nColumns);
  57. int AddHighScore(tHighScoreFile *pHighScores, tHighScoreRecord *pScoreRecord);
  58. /* The main() or WinMain() function: program execution begins here. */
  59. #ifdef ODPLAT_WIN32
  60. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  61. LPSTR lpszCmdLine, int nCmdShow)
  62. #else
  63. int main(int argc, char *argv[])
  64. #endif
  65. {
  66. char chMenuChoice;
  67. #ifdef ODPLAT_WIN32
  68. /* In Windows, pass in nCmdShow value to OpenDoors. */
  69. od_control.od_cmd_show = nCmdShow;
  70. #endif
  71. /* Set program's name for use by OpenDoors. */
  72. strcpy(od_control.od_prog_name, "Grand Slalom");
  73. strcpy(od_control.od_prog_version, "Version 6.00");
  74. strcpy(od_control.od_prog_copyright, "Copyright 1991-1996 by Brian Pirie");
  75. /* Call the standard command-line parsing function. You will probably */
  76. /* want to do this in most programs that you write using OpenDoors, as it */
  77. /* automatically provides support for many standard command-line options */
  78. /* that will make the use and setup of your program easer. For details, */
  79. /* run the vote program with the /help command line option. */
  80. #ifdef ODPLAT_WIN32
  81. od_parse_cmd_line(lpszCmdLine);
  82. #else
  83. od_parse_cmd_line(argc, argv);
  84. #endif
  85. /* Loop until the user chooses to exit the door */
  86. do
  87. {
  88. /* Clear the screen */
  89. od_clr_scr();
  90. /* Display program title */
  91. od_printf("`bright white` Ûßß ÛßÜ ÛßÛ ÛÜ Û ÛßÜ Ûßß Û ÛßÛ Û ÛßÛ ÛßÛßÛ\n\r");
  92. od_printf("`bright red`ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ`bright white`ÛßÛ`bright red`Ä`bright white`ÛßÜ`bright red`Ä`bright white`ÛßÛ`bright red`Ä`bright white`Û`bright red`Ä`bright white`ßÛ`bright red`Ä`bright white`Û");
  93. od_printf("`bright red`Ä`bright white`Û`bright red`ÄÄÄ`bright white`ßßÛ`bright red`Ä`bright white`Û`bright red`ÄÄÄ`bright white`ÛßÛ`bright red`Ä`bright white`Û`bright red`ÄÄÄ`bright white`Û");
  94. od_printf("`bright red`Ä`bright white`Û`bright red`Ä`bright white`Û`bright red`Ä`bright white`Û`bright red`Ä`bright white`Û`bright red`ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n\r");
  95. od_printf("`bright white` ßßß ß ß ß ß ß ß ßß ßßß ßßß ß ß ßßß ßßß ß ß ß\n\r\n\r");
  96. /* Display instructions */
  97. od_printf("`dark green`Prepare yourself for the challenge of Grand Slalom downhill skiing!\n\r\n\r");
  98. od_printf("When `flashing dark green`playing`dark green` the game, press:\n\r");
  99. od_printf("`dark green` [`bright green`Q`dark green`] key to ski left\n\r");
  100. od_printf(" [`bright green`W`dark green`] key to ski right\n\r\n\r");
  101. od_printf("All that you have to do is ski within the slalom course.\n\r");
  102. od_printf("It may sound easy - but be warned - it gets harder as you go!\n\r");
  103. od_printf("(Each time you hear the beep, the course becomes a bit narrower.)\n\r\n\r");
  104. /* Get menu choice from user. */
  105. od_printf("`bright white`Now, press [ENTER] to begin game, [H] to view High Scores, [E] to Exit: ");
  106. chMenuChoice = od_get_answer("HE\n\r");
  107. /* Perform appropriate action based on user's choice */
  108. switch(chMenuChoice)
  109. {
  110. case '\n':
  111. case '\r':
  112. /* If user chooses to play the game */
  113. PlayGame();
  114. break;
  115. case 'H':
  116. /* If user chose to view high scores */
  117. ShowHighScores();
  118. break;
  119. case 'E':
  120. /* If user chose to return to BBS */
  121. od_printf("\n\rGoodbye from SKIGAME!\n\r");
  122. break;
  123. }
  124. } while(chMenuChoice != 'E');
  125. /* Exit door at errorlevel 10, and do not hang up */
  126. od_exit(10, FALSE);
  127. return(1);
  128. }
  129. /* OpenAndReadHighScores() - Opens high score file and reads contents. If */
  130. /* file does not exist, it is created. File is */
  131. /* locked to serialize access by other nodes on */
  132. /* this system. */
  133. FILE *OpenAndReadHighScores(tHighScoreFile *pFileContents)
  134. {
  135. FILE *pfFile;
  136. int iHighScore;
  137. /* If high score file does not exist */
  138. if(!FileExists(SCORE_FILENAME))
  139. {
  140. /* Open and create it */
  141. pfFile = OpenExclusiveFile(SCORE_FILENAME, "wb", WAIT_FOR_FILE);
  142. /* If open was successful */
  143. if(pfFile != NULL)
  144. {
  145. /* Initialize new high score list */
  146. for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
  147. {
  148. pFileContents->aRecord[iHighScore].lnHighScore = 0L;
  149. }
  150. /* Write high score list to the file */
  151. WriteHighScores(pfFile, pFileContents);
  152. }
  153. }
  154. /* If high score file does exit */
  155. else
  156. {
  157. /* Open the existing file */
  158. pfFile = OpenExclusiveFile(SCORE_FILENAME, "r+b",
  159. WAIT_FOR_FILE);
  160. /* Read the contents of the file */
  161. if(fread(pFileContents, sizeof(tHighScoreFile), 1, pfFile) != 1)
  162. {
  163. /* If unable to read file, then return with an error */
  164. fclose(pfFile);
  165. pfFile = NULL;
  166. }
  167. }
  168. /* Return pointer to high score file, if avilable */
  169. return(pfFile);
  170. }
  171. /* FileExists() - Returns TRUE if file exists, otherwise returns FALSE */
  172. int FileExists(char *pszFileName)
  173. {
  174. /* Attempt to open the specified file for reading. */
  175. FILE *pfFile = OpenExclusiveFile(pszFileName, "rb", WAIT_FOR_FILE);
  176. if(pfFile != NULL)
  177. {
  178. /* If we are able to open the file, then close it and return */
  179. /* indicating that it exists. */
  180. fclose(pfFile);
  181. return(TRUE);
  182. }
  183. else
  184. {
  185. /* If we are unable to open the file, we proceed as if the file */
  186. /* doesn't exist (note that this may not always be a valid assumption) */
  187. return(FALSE);
  188. }
  189. }
  190. /* OpenExclusiveFile() - Opens a file for exclusive access, waiting if the */
  191. /* file is not currently available. */
  192. FILE *OpenExclusiveFile(char *pszFileName, char *pszAccess, time_t Wait)
  193. {
  194. FILE *pfFile;
  195. time_t StartTime = time(NULL);
  196. for(;;)
  197. {
  198. /* Attempt to open file */
  199. pfFile = fopen(pszFileName, pszAccess);
  200. /* If file was opened successfuly, then exit */
  201. if(pfFile != NULL) break;
  202. /* If open failed, but not due to access failure, then exit */
  203. if(errno != EACCES) break;
  204. /* If maximum time has elapsed, then exit */
  205. if(StartTime + Wait < time(NULL)) break;
  206. /* Give the OpenDoors kernel a chance to execute before trying again */
  207. od_kernel();
  208. }
  209. /* Return pointer to file, if opened */
  210. return(pfFile);
  211. }
  212. /* CloseHighScores() - Closes the high score file, allowing other nodes on */
  213. /* system to access it. */
  214. void CloseHighScores(FILE *pfHighScoreFile)
  215. {
  216. if(pfHighScoreFile != NULL)
  217. {
  218. fclose(pfHighScoreFile);
  219. }
  220. }
  221. /* WriteHighScores() - Writes the information from pFileContents to the */
  222. /* high score file. */
  223. void WriteHighScores(FILE *pfHighScoreFile, tHighScoreFile *pFileContents)
  224. {
  225. if(pfHighScoreFile != NULL)
  226. {
  227. fseek(pfHighScoreFile, 0L, SEEK_SET);
  228. fwrite(pFileContents, sizeof(tHighScoreFile), 1, pfHighScoreFile);
  229. }
  230. }
  231. /* ShowHighScores() - Called From DoDoor() to display list of high scores */
  232. void ShowHighScores(void)
  233. {
  234. FILE *pfFile;
  235. tHighScoreFile HighScores;
  236. int iHighScore;
  237. struct tm *pTimeBlock;
  238. char szTimeString[34];
  239. /* Clear the screen */
  240. od_clr_scr();
  241. /* Attempt to read high scores from file */
  242. pfFile = OpenAndReadHighScores(&HighScores);
  243. CloseHighScores(pfFile);
  244. if(pfFile == NULL)
  245. {
  246. /* If unable to open high score file, display an error message */
  247. od_printf("`bright red`Unable to access high score file!\n\r");
  248. }
  249. else
  250. {
  251. /* Display header line */
  252. od_printf("`bright green`Player Score "
  253. "Record Date`dark green`\n\r");
  254. od_printf("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n\r");
  255. /* Display high scores */
  256. for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
  257. {
  258. /* Exit loop when we have reached the end of the high scores */
  259. if(HighScores.aRecord[iHighScore].lnHighScore == 0L) break;
  260. /* Get local time when player set the high score */
  261. pTimeBlock = localtime(&HighScores.aRecord[iHighScore].lnPlayDate);
  262. strftime(szTimeString, sizeof(szTimeString),
  263. "%B %d, %Y at %I:%M%p", pTimeBlock);
  264. /* Display next high score */
  265. od_printf("%-32.32s %-8ld %s\n\r",
  266. HighScores.aRecord[iHighScore].szPlayerName,
  267. HighScores.aRecord[iHighScore].lnHighScore,
  268. szTimeString);
  269. }
  270. }
  271. /* Display footer line */
  272. od_printf("ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\n\r\n\r");
  273. /* Wait for user to press a key */
  274. od_printf("`bright white`Press [ENTER]/[RETURN] to continue: ");
  275. od_get_answer("\n\r");
  276. }
  277. /* PlayGame() - Called from DoDoor() when user chooses to play a game. */
  278. void PlayGame(void)
  279. {
  280. int nLeftEdge = 1;
  281. int nRightEdge = nLeftEdge + 1 + INITIAL_COURSE_WIDTH;
  282. int nPlayerPos = nLeftEdge + 1 + (INITIAL_COURSE_WIDTH / 2);
  283. long lnScore = 0;
  284. int nDistanceSinceShrink = 0;
  285. int bMovingRight = TRUE;
  286. char cKeyPress;
  287. tHighScoreRecord ScoreRecord;
  288. FILE *pfFile;
  289. tHighScoreFile HighScores;
  290. int nBackup=0;
  291. clock_t StartClock;
  292. /* Clear the Screen */
  293. od_set_color(L_WHITE, B_BLACK);
  294. od_clr_scr();
  295. /* Set current display colour to white */
  296. od_set_attrib(L_WHITE);
  297. /* Re-seed random number generator */
  298. srand((unsigned int)time(NULL));
  299. /* Loop until game is over */
  300. for(;;)
  301. {
  302. StartClock = msclock();
  303. /* Display current line */
  304. if(od_control.user_ansi || od_control.user_avatar)
  305. {
  306. SpaceRight(nLeftEdge - 1);
  307. od_set_color(L_WHITE, D_RED);
  308. od_putch((char)223);
  309. od_repeat((unsigned char)219,
  310. (unsigned char)(nPlayerPos - nLeftEdge - 1));
  311. od_putch((char)254);
  312. od_repeat((unsigned char)219,
  313. (unsigned char)(nRightEdge - nPlayerPos - 1));
  314. od_putch((char)223);
  315. nBackup = nRightEdge - nPlayerPos + 1;
  316. }
  317. else
  318. {
  319. /* If neither ANSI nor AVATAR modes are active, then display */
  320. /* course using plain-ASCII. */
  321. SpaceRight(nLeftEdge - 1);
  322. od_putch((char)(bMovingRight ? '\\' : '/'));
  323. SpaceRight(nPlayerPos - nLeftEdge - 1);
  324. od_putch('o');
  325. SpaceRight(nRightEdge - nPlayerPos - 1);
  326. od_putch((char)(bMovingRight ? '\\' : '/'));
  327. }
  328. /* Loop for each key pressed by user */
  329. while((cKeyPress = (char)od_get_key(FALSE)) != '\0')
  330. {
  331. if(cKeyPress == 'q' || cKeyPress == 'Q')
  332. {
  333. /* Move left */
  334. --nPlayerPos;
  335. }
  336. else if(cKeyPress == 'w' || cKeyPress == 'W')
  337. {
  338. /* Move right */
  339. ++nPlayerPos;
  340. }
  341. }
  342. /* Check whether course should turn */
  343. if((rand() % 100) < CHANGE_DIRECTION)
  344. {
  345. bMovingRight = !bMovingRight;
  346. }
  347. else
  348. {
  349. /* If no change in direction, then position moves */
  350. /* Adjust course position appropriately */
  351. if(bMovingRight)
  352. {
  353. ++nLeftEdge;
  354. ++nRightEdge;
  355. }
  356. else
  357. {
  358. --nLeftEdge;
  359. --nRightEdge;
  360. }
  361. }
  362. /* Check whether course size should shink */
  363. if(++nDistanceSinceShrink >= DECREASE_WIDTH_AFTER)
  364. {
  365. /* Reset distance */
  366. nDistanceSinceShrink = 0;
  367. /* Randomly choose a side to shrink */
  368. if((rand() % 100) < 50)
  369. {
  370. ++nLeftEdge;
  371. }
  372. else
  373. {
  374. --nRightEdge;
  375. }
  376. /* Beep when we shrink the size. */
  377. od_printf("\a");
  378. }
  379. /* Change course direction if it collides with edge of screen */
  380. if(nLeftEdge < 1)
  381. {
  382. bMovingRight = TRUE;
  383. ++nLeftEdge;
  384. ++nRightEdge;
  385. }
  386. else if(nRightEdge > 79)
  387. {
  388. bMovingRight = FALSE;
  389. --nLeftEdge;
  390. --nRightEdge;
  391. }
  392. /* Check that player is still within the course */
  393. if(nPlayerPos <= nLeftEdge || nPlayerPos >= nRightEdge)
  394. {
  395. /* Player has left course - game over! */
  396. od_set_color(D_GREY, D_BLACK);
  397. od_clr_scr();
  398. od_printf("`bright red` !!! Game Over !!!\n\r\n\r");
  399. od_printf("`dark green`You have veered off the course!\n\r\n\r");
  400. od_printf("Your Score is: %ld\n\r", lnScore);
  401. /* Create a score record */
  402. ScoreRecord.lnHighScore = lnScore;
  403. strncpy(ScoreRecord.szPlayerName, od_control.user_name, MAX_NAME_SIZE);
  404. ScoreRecord.szPlayerName[MAX_NAME_SIZE] = '\0';
  405. ScoreRecord.lnPlayDate = time(NULL);
  406. /* Attempt to read high scores from file */
  407. pfFile = OpenAndReadHighScores(&HighScores);
  408. if(pfFile == NULL)
  409. {
  410. /* If unable to open high score file, display an error message */
  411. od_printf("`bright red`Unable to access high score file!\n\r");
  412. }
  413. else
  414. {
  415. /* Check whether user made it to high score list */
  416. if(AddHighScore(&HighScores, &ScoreRecord))
  417. {
  418. od_printf("Congratulations! You have made it to the high score list!\n\r");
  419. /* If so, write the new high score list */
  420. WriteHighScores(pfFile, &HighScores);
  421. }
  422. /* Close and unlock file */
  423. CloseHighScores(pfFile);
  424. }
  425. /* Wait for user to press enter */
  426. od_printf("`bright white`\n\rPress [ENTER]/[RETURN] to return to menu: ");
  427. od_get_answer("\n\r");
  428. return;
  429. }
  430. /* Delay for about 1/10th of a second, to add a constant delay after */
  431. /* each line is displayed that does not depend on the connect speed. */
  432. while(msclock() < StartClock + (((clock_t)MSCLOCKS_PER_SEC) / 10))
  433. od_sleep(0);
  434. /* Increase score */
  435. ++lnScore;
  436. /* Replace skiier character with track character */
  437. if(od_control.user_ansi)
  438. {
  439. MoveLeft(nBackup);
  440. od_set_color(L_WHITE, D_GREY);
  441. od_putch((char)178);
  442. od_set_color(L_WHITE, B_BLACK);
  443. }
  444. /* Move to next line */
  445. od_printf("\r\n");
  446. }
  447. }
  448. /* SpaceRight() - Moves right the specified number of columns. In ANSI mode, */
  449. /* uses the move cursor right control sequence. Otherwise, */
  450. /* uses od_repeat(), which is optimized for ASCII and AVATAR */
  451. /* modes. */
  452. void SpaceRight(int nColumns)
  453. {
  454. char szSequence[6];
  455. /* If we don't have a positive column count, then return immediately */
  456. if(nColumns <= 0) return;
  457. /* If operating in ANSI mode */
  458. if(od_control.user_ansi)
  459. {
  460. /* Move cursor right using ESC[nC control sequence */
  461. sprintf(szSequence, "\x1b[%02dC", nColumns);
  462. od_disp_emu(szSequence, TRUE);
  463. }
  464. /* If not operating in ANSI mode */
  465. else
  466. {
  467. od_repeat(' ', (unsigned char)nColumns);
  468. }
  469. }
  470. /* MoveLeft() - Moves the cursor right the specified number of columns. */
  471. /* Intended for use in ANSI mode only. */
  472. void MoveLeft(int nColumns)
  473. {
  474. /* Move cursor left using ESC[nD control sequence */
  475. char szSequence[6];
  476. sprintf(szSequence, "\x1b[%02dD", nColumns);
  477. od_disp_emu(szSequence, TRUE);
  478. }
  479. /* AddHighScore() - Adds a new score to the high score list, if it is high */
  480. /* enough. Returns TRUE if score is added, FALSE otherwise. */
  481. int AddHighScore(tHighScoreFile *pHighScores, tHighScoreRecord *pScoreRecord)
  482. {
  483. int iHighScore;
  484. int iExistingScore;
  485. /* Loop through each existing high score */
  486. for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
  487. {
  488. /* If new score is greater than or equal to this one, then its */
  489. /* position has been found. */
  490. if(pHighScores->aRecord[iHighScore].lnHighScore <=
  491. pScoreRecord->lnHighScore)
  492. {
  493. /* Move remaining scores down one in list */
  494. for(iExistingScore = HIGH_SCORES - 1; iExistingScore >= iHighScore + 1;
  495. --iExistingScore)
  496. {
  497. pHighScores->aRecord[iExistingScore] =
  498. pHighScores->aRecord[iExistingScore - 1];
  499. }
  500. /* Add new score to list */
  501. pHighScores->aRecord[iHighScore] = *pScoreRecord;
  502. /* Return with success */
  503. return(TRUE);
  504. }
  505. }
  506. /* Score did not make it to list */
  507. return(FALSE);
  508. }