This repository has been archived on 2024-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
magicka/deps/odoors/ex_ski.c
2017-03-19 07:49:46 +10:00

603 lines
20 KiB
C
Raw Blame History

/* EX_SKI.C - EX_SKI is a simple but addictive door game that is written */
/* using OpenDoors. In this action game, the player must control */
/* a skier through a downhill slalom course. The user may turn */
/* the skier left or right, and the game ends as soon as the */
/* player skis outside the marked course. The game begins at */
/* an easy level, but quickly becomes more and more difficult */
/* as the course to be navigated becomes more and more narrow. */
/* The game maintains a list of players with high scores, and */
/* this list may be viewed from the main menu. */
/* */
/* This program shows how to do the following: */
/* */
/* - Maintain a high-score file in a game, in a multi-node */
/* compatible manner. */
/* - How to use your own terminal control sequences. */
/* - How to perform reasonably percise timing under both DOS */
/* and Windows. */
/* Header file for the OpenDoors API */
#include "OpenDoor.h"
/* Other required C header files */
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <errno.h>
#include <stdlib.h>
#include "genwrap.h"
/* Hard-coded configurable constants - change these values to alter game */
#define HIGH_SCORES 15 /* Number of high scores in list */
#define INITIAL_COURSE_WIDTH 30 /* Initial width of ski course */
#define MINIMUM_COURSE_WIDTH 4 /* Minimum width of course */
#define DECREASE_WIDTH_AFTER 100 /* # of ticks before course narrows */
#define CHANGE_DIRECTION 10 /* % of ticks course changes direction */
#define MAX_NAME_SIZE 35 /* Maximum characters in player name */
#define WAIT_FOR_FILE 10 /* Time to wait for access to file */
#define SCORE_FILENAME "skigame.dat" /* Name of high score file */
/* High-score file format structure */
typedef struct
{
char szPlayerName[MAX_NAME_SIZE + 1];
DWORD lnHighScore;
time_t lnPlayDate;
} tHighScoreRecord;
typedef struct
{
tHighScoreRecord aRecord[HIGH_SCORES];
} tHighScoreFile;
/* Prototypes for functions defined and used in this file */
FILE *OpenAndReadHighScores(tHighScoreFile *pFileContents);
void CloseHighScores(FILE *pfHighScoreFile);
void WriteHighScores(FILE *pfHighScoreFile, tHighScoreFile *pFileContents);
FILE *OpenExclusiveFile(char *pszFileName, char *pszAccess, time_t Wait);
int FileExists(char *pszFileName);
void ShowHighScores(void);
void PlayGame(void);
void SpaceRight(int nColumns);
void MoveLeft(int nColumns);
int AddHighScore(tHighScoreFile *pHighScores, tHighScoreRecord *pScoreRecord);
/* The main() or WinMain() function: program execution begins here. */
#ifdef ODPLAT_WIN32
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
#else
int main(int argc, char *argv[])
#endif
{
char chMenuChoice;
#ifdef ODPLAT_WIN32
/* In Windows, pass in nCmdShow value to OpenDoors. */
od_control.od_cmd_show = nCmdShow;
#endif
/* Set program's name for use by OpenDoors. */
strcpy(od_control.od_prog_name, "Grand Slalom");
strcpy(od_control.od_prog_version, "Version 6.00");
strcpy(od_control.od_prog_copyright, "Copyright 1991-1996 by Brian Pirie");
/* Call the standard command-line parsing function. You will probably */
/* want to do this in most programs that you write using OpenDoors, as it */
/* automatically provides support for many standard command-line options */
/* that will make the use and setup of your program easer. For details, */
/* run the vote program with the /help command line option. */
#ifdef ODPLAT_WIN32
od_parse_cmd_line(lpszCmdLine);
#else
od_parse_cmd_line(argc, argv);
#endif
/* Loop until the user chooses to exit the door */
do
{
/* Clear the screen */
od_clr_scr();
/* Display program title */
od_printf("`bright white` <20><><EFBFBD> <20><><EFBFBD> <20><><EFBFBD> <20><> <20> <20><><EFBFBD> <20><><EFBFBD> <20> <20><><EFBFBD> <20> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>\n\r");
od_printf("`bright red`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>`bright white`<60><><EFBFBD>`bright red`<60>`bright white`<60><><EFBFBD>`bright red`<60>`bright white`<60><><EFBFBD>`bright red`<60>`bright white`<60>`bright red`<60>`bright white`<60><>`bright red`<60>`bright white`<60>");
od_printf("`bright red`<60>`bright white`<60>`bright red`<60><><EFBFBD>`bright white`<60><><EFBFBD>`bright red`<60>`bright white`<60>`bright red`<60><><EFBFBD>`bright white`<60><><EFBFBD>`bright red`<60>`bright white`<60>`bright red`<60><><EFBFBD>`bright white`<60>");
od_printf("`bright red`<60>`bright white`<60>`bright red`<60>`bright white`<60>`bright red`<60>`bright white`<60>`bright red`<60>`bright white`<60>`bright red`<60><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>\n\r");
od_printf("`bright white` <20><><EFBFBD> <20> <20> <20> <20> <20> <20> <20><> <20><><EFBFBD> <20><><EFBFBD> <20> <20> <20><><EFBFBD> <20><><EFBFBD> <20> <20> <20>\n\r\n\r");
/* Display instructions */
od_printf("`dark green`Prepare yourself for the challenge of Grand Slalom downhill skiing!\n\r\n\r");
od_printf("When `flashing dark green`playing`dark green` the game, press:\n\r");
od_printf("`dark green` [`bright green`Q`dark green`] key to ski left\n\r");
od_printf(" [`bright green`W`dark green`] key to ski right\n\r\n\r");
od_printf("All that you have to do is ski within the slalom course.\n\r");
od_printf("It may sound easy - but be warned - it gets harder as you go!\n\r");
od_printf("(Each time you hear the beep, the course becomes a bit narrower.)\n\r\n\r");
/* Get menu choice from user. */
od_printf("`bright white`Now, press [ENTER] to begin game, [H] to view High Scores, [E] to Exit: ");
chMenuChoice = od_get_answer("HE\n\r");
/* Perform appropriate action based on user's choice */
switch(chMenuChoice)
{
case '\n':
case '\r':
/* If user chooses to play the game */
PlayGame();
break;
case 'H':
/* If user chose to view high scores */
ShowHighScores();
break;
case 'E':
/* If user chose to return to BBS */
od_printf("\n\rGoodbye from SKIGAME!\n\r");
break;
}
} while(chMenuChoice != 'E');
/* Exit door at errorlevel 10, and do not hang up */
od_exit(10, FALSE);
return(1);
}
/* OpenAndReadHighScores() - Opens high score file and reads contents. If */
/* file does not exist, it is created. File is */
/* locked to serialize access by other nodes on */
/* this system. */
FILE *OpenAndReadHighScores(tHighScoreFile *pFileContents)
{
FILE *pfFile;
int iHighScore;
/* If high score file does not exist */
if(!FileExists(SCORE_FILENAME))
{
/* Open and create it */
pfFile = OpenExclusiveFile(SCORE_FILENAME, "wb", WAIT_FOR_FILE);
/* If open was successful */
if(pfFile != NULL)
{
/* Initialize new high score list */
for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
{
pFileContents->aRecord[iHighScore].lnHighScore = 0L;
}
/* Write high score list to the file */
WriteHighScores(pfFile, pFileContents);
}
}
/* If high score file does exit */
else
{
/* Open the existing file */
pfFile = OpenExclusiveFile(SCORE_FILENAME, "r+b",
WAIT_FOR_FILE);
/* Read the contents of the file */
if(fread(pFileContents, sizeof(tHighScoreFile), 1, pfFile) != 1)
{
/* If unable to read file, then return with an error */
fclose(pfFile);
pfFile = NULL;
}
}
/* Return pointer to high score file, if avilable */
return(pfFile);
}
/* FileExists() - Returns TRUE if file exists, otherwise returns FALSE */
int FileExists(char *pszFileName)
{
/* Attempt to open the specified file for reading. */
FILE *pfFile = OpenExclusiveFile(pszFileName, "rb", WAIT_FOR_FILE);
if(pfFile != NULL)
{
/* If we are able to open the file, then close it and return */
/* indicating that it exists. */
fclose(pfFile);
return(TRUE);
}
else
{
/* If we are unable to open the file, we proceed as if the file */
/* doesn't exist (note that this may not always be a valid assumption) */
return(FALSE);
}
}
/* OpenExclusiveFile() - Opens a file for exclusive access, waiting if the */
/* file is not currently available. */
FILE *OpenExclusiveFile(char *pszFileName, char *pszAccess, time_t Wait)
{
FILE *pfFile;
time_t StartTime = time(NULL);
for(;;)
{
/* Attempt to open file */
pfFile = fopen(pszFileName, pszAccess);
/* If file was opened successfuly, then exit */
if(pfFile != NULL) break;
/* If open failed, but not due to access failure, then exit */
if(errno != EACCES) break;
/* If maximum time has elapsed, then exit */
if(StartTime + Wait < time(NULL)) break;
/* Give the OpenDoors kernel a chance to execute before trying again */
od_kernel();
}
/* Return pointer to file, if opened */
return(pfFile);
}
/* CloseHighScores() - Closes the high score file, allowing other nodes on */
/* system to access it. */
void CloseHighScores(FILE *pfHighScoreFile)
{
if(pfHighScoreFile != NULL)
{
fclose(pfHighScoreFile);
}
}
/* WriteHighScores() - Writes the information from pFileContents to the */
/* high score file. */
void WriteHighScores(FILE *pfHighScoreFile, tHighScoreFile *pFileContents)
{
if(pfHighScoreFile != NULL)
{
fseek(pfHighScoreFile, 0L, SEEK_SET);
fwrite(pFileContents, sizeof(tHighScoreFile), 1, pfHighScoreFile);
}
}
/* ShowHighScores() - Called From DoDoor() to display list of high scores */
void ShowHighScores(void)
{
FILE *pfFile;
tHighScoreFile HighScores;
int iHighScore;
struct tm *pTimeBlock;
char szTimeString[34];
/* Clear the screen */
od_clr_scr();
/* Attempt to read high scores from file */
pfFile = OpenAndReadHighScores(&HighScores);
CloseHighScores(pfFile);
if(pfFile == NULL)
{
/* If unable to open high score file, display an error message */
od_printf("`bright red`Unable to access high score file!\n\r");
}
else
{
/* Display header line */
od_printf("`bright green`Player Score "
"Record Date`dark green`\n\r");
od_printf("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>\n\r");
/* Display high scores */
for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
{
/* Exit loop when we have reached the end of the high scores */
if(HighScores.aRecord[iHighScore].lnHighScore == 0L) break;
/* Get local time when player set the high score */
pTimeBlock = localtime(&HighScores.aRecord[iHighScore].lnPlayDate);
strftime(szTimeString, sizeof(szTimeString),
"%B %d, %Y at %I:%M%p", pTimeBlock);
/* Display next high score */
od_printf("%-32.32s %-8ld %s\n\r",
HighScores.aRecord[iHighScore].szPlayerName,
HighScores.aRecord[iHighScore].lnHighScore,
szTimeString);
}
}
/* Display footer line */
od_printf("<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>\n\r\n\r");
/* Wait for user to press a key */
od_printf("`bright white`Press [ENTER]/[RETURN] to continue: ");
od_get_answer("\n\r");
}
/* PlayGame() - Called from DoDoor() when user chooses to play a game. */
void PlayGame(void)
{
int nLeftEdge = 1;
int nRightEdge = nLeftEdge + 1 + INITIAL_COURSE_WIDTH;
int nPlayerPos = nLeftEdge + 1 + (INITIAL_COURSE_WIDTH / 2);
long lnScore = 0;
int nDistanceSinceShrink = 0;
int bMovingRight = TRUE;
char cKeyPress;
tHighScoreRecord ScoreRecord;
FILE *pfFile;
tHighScoreFile HighScores;
int nBackup=0;
clock_t StartClock;
/* Clear the Screen */
od_set_color(L_WHITE, B_BLACK);
od_clr_scr();
/* Set current display colour to white */
od_set_attrib(L_WHITE);
/* Re-seed random number generator */
srand((unsigned int)time(NULL));
/* Loop until game is over */
for(;;)
{
StartClock = msclock();
/* Display current line */
if(od_control.user_ansi || od_control.user_avatar)
{
SpaceRight(nLeftEdge - 1);
od_set_color(L_WHITE, D_RED);
od_putch((char)223);
od_repeat((unsigned char)219,
(unsigned char)(nPlayerPos - nLeftEdge - 1));
od_putch((char)254);
od_repeat((unsigned char)219,
(unsigned char)(nRightEdge - nPlayerPos - 1));
od_putch((char)223);
nBackup = nRightEdge - nPlayerPos + 1;
}
else
{
/* If neither ANSI nor AVATAR modes are active, then display */
/* course using plain-ASCII. */
SpaceRight(nLeftEdge - 1);
od_putch((char)(bMovingRight ? '\\' : '/'));
SpaceRight(nPlayerPos - nLeftEdge - 1);
od_putch('o');
SpaceRight(nRightEdge - nPlayerPos - 1);
od_putch((char)(bMovingRight ? '\\' : '/'));
}
/* Loop for each key pressed by user */
while((cKeyPress = (char)od_get_key(FALSE)) != '\0')
{
if(cKeyPress == 'q' || cKeyPress == 'Q')
{
/* Move left */
--nPlayerPos;
}
else if(cKeyPress == 'w' || cKeyPress == 'W')
{
/* Move right */
++nPlayerPos;
}
}
/* Check whether course should turn */
if((rand() % 100) < CHANGE_DIRECTION)
{
bMovingRight = !bMovingRight;
}
else
{
/* If no change in direction, then position moves */
/* Adjust course position appropriately */
if(bMovingRight)
{
++nLeftEdge;
++nRightEdge;
}
else
{
--nLeftEdge;
--nRightEdge;
}
}
/* Check whether course size should shink */
if(++nDistanceSinceShrink >= DECREASE_WIDTH_AFTER)
{
/* Reset distance */
nDistanceSinceShrink = 0;
/* Randomly choose a side to shrink */
if((rand() % 100) < 50)
{
++nLeftEdge;
}
else
{
--nRightEdge;
}
/* Beep when we shrink the size. */
od_printf("\a");
}
/* Change course direction if it collides with edge of screen */
if(nLeftEdge < 1)
{
bMovingRight = TRUE;
++nLeftEdge;
++nRightEdge;
}
else if(nRightEdge > 79)
{
bMovingRight = FALSE;
--nLeftEdge;
--nRightEdge;
}
/* Check that player is still within the course */
if(nPlayerPos <= nLeftEdge || nPlayerPos >= nRightEdge)
{
/* Player has left course - game over! */
od_set_color(D_GREY, D_BLACK);
od_clr_scr();
od_printf("`bright red` !!! Game Over !!!\n\r\n\r");
od_printf("`dark green`You have veered off the course!\n\r\n\r");
od_printf("Your Score is: %ld\n\r", lnScore);
/* Create a score record */
ScoreRecord.lnHighScore = lnScore;
strncpy(ScoreRecord.szPlayerName, od_control.user_name, MAX_NAME_SIZE);
ScoreRecord.szPlayerName[MAX_NAME_SIZE] = '\0';
ScoreRecord.lnPlayDate = time(NULL);
/* Attempt to read high scores from file */
pfFile = OpenAndReadHighScores(&HighScores);
if(pfFile == NULL)
{
/* If unable to open high score file, display an error message */
od_printf("`bright red`Unable to access high score file!\n\r");
}
else
{
/* Check whether user made it to high score list */
if(AddHighScore(&HighScores, &ScoreRecord))
{
od_printf("Congratulations! You have made it to the high score list!\n\r");
/* If so, write the new high score list */
WriteHighScores(pfFile, &HighScores);
}
/* Close and unlock file */
CloseHighScores(pfFile);
}
/* Wait for user to press enter */
od_printf("`bright white`\n\rPress [ENTER]/[RETURN] to return to menu: ");
od_get_answer("\n\r");
return;
}
/* Delay for about 1/10th of a second, to add a constant delay after */
/* each line is displayed that does not depend on the connect speed. */
while(msclock() < StartClock + (((clock_t)MSCLOCKS_PER_SEC) / 10))
od_sleep(0);
/* Increase score */
++lnScore;
/* Replace skiier character with track character */
if(od_control.user_ansi)
{
MoveLeft(nBackup);
od_set_color(L_WHITE, D_GREY);
od_putch((char)178);
od_set_color(L_WHITE, B_BLACK);
}
/* Move to next line */
od_printf("\r\n");
}
}
/* SpaceRight() - Moves right the specified number of columns. In ANSI mode, */
/* uses the move cursor right control sequence. Otherwise, */
/* uses od_repeat(), which is optimized for ASCII and AVATAR */
/* modes. */
void SpaceRight(int nColumns)
{
char szSequence[6];
/* If we don't have a positive column count, then return immediately */
if(nColumns <= 0) return;
/* If operating in ANSI mode */
if(od_control.user_ansi)
{
/* Move cursor right using ESC[nC control sequence */
sprintf(szSequence, "\x1b[%02dC", nColumns);
od_disp_emu(szSequence, TRUE);
}
/* If not operating in ANSI mode */
else
{
od_repeat(' ', (unsigned char)nColumns);
}
}
/* MoveLeft() - Moves the cursor right the specified number of columns. */
/* Intended for use in ANSI mode only. */
void MoveLeft(int nColumns)
{
/* Move cursor left using ESC[nD control sequence */
char szSequence[6];
sprintf(szSequence, "\x1b[%02dD", nColumns);
od_disp_emu(szSequence, TRUE);
}
/* AddHighScore() - Adds a new score to the high score list, if it is high */
/* enough. Returns TRUE if score is added, FALSE otherwise. */
int AddHighScore(tHighScoreFile *pHighScores, tHighScoreRecord *pScoreRecord)
{
int iHighScore;
int iExistingScore;
/* Loop through each existing high score */
for(iHighScore = 0; iHighScore < HIGH_SCORES; ++iHighScore)
{
/* If new score is greater than or equal to this one, then its */
/* position has been found. */
if(pHighScores->aRecord[iHighScore].lnHighScore <=
pScoreRecord->lnHighScore)
{
/* Move remaining scores down one in list */
for(iExistingScore = HIGH_SCORES - 1; iExistingScore >= iHighScore + 1;
--iExistingScore)
{
pHighScores->aRecord[iExistingScore] =
pHighScores->aRecord[iExistingScore - 1];
}
/* Add new score to list */
pHighScores->aRecord[iHighScore] = *pScoreRecord;
/* Return with success */
return(TRUE);
}
}
/* Score did not make it to list */
return(FALSE);
}