Okay, as I said earlier, I have made some changes to the Deal or No Deal program. I have:
1. Broken down that huge main function into several smaller functions, often removing duplication in the process.
2. Flushed all output prior to input. This should aid in portability.
3. Rewrote several items to eliminate warnings generated by the two compilers I tested with: gcc 4.6.3 on Ubuntu 12.04, and Visual Studio 2008 C compiler on Windows Vista.
3. Created a new print_money function, replacing the old print_double function, that prints the monetary amounts in a locale-aware way, with the aid of a recursive worker function. I have actually never used this locale stuff before, so it was kind of fun learning this feature and implementing the monetary printing routine. This was tested using English(CA, GB, NZ, US) and French(CA, FR) locales.
4. Updated several comments and added more comments to make the logic easier to follow.
This is still a big program (533 lines) but I can not upload C program files. So here it is:
/* dond.c
* Deal or No Deal
* Simulation of the game show.
* by David Nuttall January 29, 2013 - Feb 6, 2013
*/
#define _CRT_SECURE_NO_WARNINGS 1 /* Used to suppress warnings to use sscanf_s() and sprintf_s() instead of insecure versions in MS-VS */
#include <assert.h>
#include <ctype.h>
#include <locale.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define CASE_COUNT 26
/* C does not know about bool type */
typedef enum _boolean
{
false = 0, true = 1
} boolean;
char get_char(const char *sPrompt, const char *sValidChar)
{
int iTmp;
char sInput[80];
while (true)
{
fputs(sPrompt, stdout);
fflush(stdout);
if (fgets(sInput, 80, stdin)==NULL)
return -2;
else if (feof(stdin))
return -2;
iTmp=0;
while (iTmp < 80 && sInput[iTmp] != '\0')
{
if (isspace(sInput[iTmp]))
/* skip spaces */
++iTmp;
else if (strchr(sValidChar, sInput[iTmp])!=NULL)
{
return sInput[iTmp];
}
else
{
printf("'%c' is invalid. Please enter ", sInput[iTmp]);
for (iTmp = 0; sValidChar[iTmp] != '\0'; ++iTmp)
{
if (iTmp > 0)
{
if (sValidChar[iTmp+1] == '\0')
fputs(" or ", stdout);
else
fputs(", ", stdout);
} /* if iTmp > 0 */
putchar(sValidChar[iTmp]);
} /* for iTmp */
puts(".");
break;
} /* else (invalid entry) */
} /* while */
} /* while (infinite loop) */
} /* get_char */
int get_integer(const char *sPrompt, const int iUpperLimit)
{
int iReturn = 0;
char sInput[80];
do
{
fputs(sPrompt, stdout);
fflush(stdout);
if (fgets(sInput, 80, stdin) == NULL || feof(stdin))
/* unrecoverable error, like end-of-file on input. */
return -1;
switch (sscanf(sInput, "%i", &iReturn))
{
case EOF:
/* Blank line.
* Go back and ask again. */
continue;
case 0:
/* Could not read a number. */
puts("I could not understand that number. Please try again.");
continue;
case 1:
/* Got a number */
if (iReturn < 1)
{
printf("%d is too small. Choose a number at least 1.\n", iReturn);
iReturn = 0;
continue;
}
else if (iReturn > iUpperLimit)
{
printf("%d is too large. Choose a number no more than %d.\n", iReturn, iUpperLimit);
iReturn = 0;
continue;
}
else
/* If it made it so far, then it is a good number. */
break;
default:
/* Something else happened, not sure what. */
puts("Something weird happened. Please try again.");
continue;
} /* switch */
} while (iReturn == 0);
return iReturn;
} /* get_integer() */
boolean print_groups(double dValue, unsigned int iGroup)
{
int iGroupSize;
double dScalingFactor, dTmp;
const char *sGroups = localeconv()->grouping;
/* Recursively prints the given value, using monetary separation symbols */
/* Prints the more significant digits first, then prints the less significant digits. */
/* If passed a value of 0, no more recursion. */
if (floor(dValue) == 0.0)
return false;
/* How big is this group? This allows for different sized groups, as stated in the standard. */
if (iGroup < strlen(sGroups))
iGroupSize = sGroups[strlen(sGroups) - iGroup - 1];
else
iGroupSize = sGroups[0];
dScalingFactor = pow(10.0, iGroupSize);
/* numerically shift the value to only have the part in front of this separator. */
dTmp = floor(dValue / dScalingFactor);
/* Remove the larger stuff, and print the rest on this pass. */
dValue -= dTmp * dScalingFactor;
/* Get the stuff in front printed first. If it returns false, do not print leading zeros. */
if (print_groups(dTmp, iGroup+1))
/* Print with leading zeros. */
printf("%0*.0f", iGroupSize, dValue);
else
/* Print at least one character, without leading zeros. */
printf("%1.0f", dValue);
if (iGroup > 0)
fputs(localeconv()->mon_thousands_sep, stdout);
return true;
} /* print_groups() */
void print_money(double dValue)
{
/* Print the given double using local numeric separators. Includes two decimal places. */
/* no negatives should appear */
assert(dValue > 0.0);
/* insert the currency symbol for the locale */
if (localeconv()->p_cs_precedes)
{
fputs(localeconv()->currency_symbol, stdout);
if (localeconv()->p_sep_by_space)
fputs(" ", stdout);
}
if (!print_groups(dValue, 0))
/* If it returns false, nothing was printed. */
fputs("0", stdout);
/* Leave just the digits after the decimal point. */
dValue -= floor(dValue);
fputs(localeconv()->mon_decimal_point, stdout);
printf("%0*.0f", localeconv()->frac_digits, dValue*100.0);
if (!(localeconv()->p_cs_precedes))
{
if (localeconv()->p_sep_by_space)
fputs(" ", stdout);
fputs(localeconv()->currency_symbol, stdout);
}
} /* print_money() */
void print_cases(const int iSelected, const int iCaseIndex[])
{
int iLineLen, iCount;
puts("Available cases:");
iLineLen = 0;
for (iCount = 1; iCount <= CASE_COUNT; ++iCount)
{
if (iCount == iSelected)
; /* Do not print your case. */
else if (iCaseIndex[iCount-1] < 0)
; /* Do not print cases that have already been opened. */
else
{
printf("[%d]\t", iCount);
++iLineLen;
if (iLineLen > 8)
{
putchar('\n');
iLineLen = 0;
} /* if iLineLen */
} /* else */
} /* for iCount */
putchar('\n');
} /* print_cases() */
void print_board(const double dCases[])
{
int iCount;
/* Display the board. */
for (iCount = 0; iCount<(CASE_COUNT/2); ++iCount)
{
if (dCases[iCount] > 0.0)
print_money(dCases[iCount]);
putchar('\t');
if (dCases[iCount + (CASE_COUNT/2)] > 0.0)
print_money(dCases[iCount+(CASE_COUNT/2)]);
putchar('\n');
}
} /* print_board() */
double round_sig(const double dValue, const int iSignif, const char cDir)
{
/* Round the given double to the given number of significant digits. */
/* cDir may be one of "o" - round off, "u" - round up, "d" - round down. Anything else will be treated as a no-op. */
const int iPlaces = (int)(floor(log10(dValue)))+1;
double dReturn;
switch (cDir)
{
case 'u':
dReturn = ceil(dValue * pow(0.1, iPlaces-iSignif)) * pow(10.0, iPlaces-iSignif);
break;
case 'd':
dReturn = floor(dValue * pow(0.1, iPlaces-iSignif)) * pow(10.0, iPlaces-iSignif);
break;
case 'o':
dReturn = floor(dValue * pow(0.1, iPlaces-iSignif) + 0.5) * pow(10.0, iPlaces-iSignif);
break;
default:
dReturn = dValue;
break;
} /* switch cDir */
return dReturn;
} /* round_sig() */
void set_cases(int iCaseIndex[])
{
int iCount, iTmp, iSwap;
const unsigned int timer = (unsigned int)(time(NULL));
/* set up the randomizer, based on the time. */
srand(timer);
for (iCount = 0; iCount < CASE_COUNT; ++iCount)
iCaseIndex[iCount] = iCount;
/* mix up the order of the indexes. This way the cases stay in the same order for display purposes. */
for (iCount = 0; iCount < CASE_COUNT; ++iCount)
{
iTmp = rand() % CASE_COUNT;
if (iCount != iTmp)
{
iSwap = iCaseIndex[iCount];
iCaseIndex[iCount] = iCaseIndex[iTmp];
iCaseIndex[iTmp] = iSwap;
} /* if iCount != iTmp */
} /* for iCount */
} /* set_cases() */
int get_case(void)
{
int iSelected;
char sPrompt[80];
sprintf(sPrompt, "Which case do you want as your case? [1-%d] ", CASE_COUNT);
iSelected = get_integer(sPrompt, CASE_COUNT);
if (iSelected < 1)
return -1;
return iSelected;
} /* get_case() */
int last_round(const int iSelected, const int iCaseIndex[], const double dCases[])
{
int iCount;
double dTmp;
char cResult;
/* Last round */
for (iCount=0; iCount < CASE_COUNT; ++iCount)
{
if (iCount+1 == iSelected)
/* Skip the selected case */
continue;
else if (iCaseIndex[iCount] < 0)
/* Case already opened. */
continue;
else
/* Found the last case. */
break;
}
printf("There are only two cases left: yours (%d) and case number %d.\n", iSelected, iCount+1);
cResult = get_char("There is one last question. Do you want to switch cases? (Y/N) ", "yYnN");
if (cResult < 0)
return 2;
if (toupper(cResult) == 'Y')
{
dTmp = dCases[iCaseIndex[iCount]];
fputs("You have won the ", stdout);
print_money(dTmp);
printf(" that was in case %d.\n", iCount+1);
fputs("Your case contained ", stdout);
print_money(dCases[iCaseIndex[iSelected-1]]);
puts(".");
if (dTmp >= dCases[iCaseIndex[iSelected-1]]*10.0)
puts("You made a very good deal.");
else if (dTmp > dCases[iCaseIndex[iSelected-1]])
puts("You made a good deal.");
else
puts("You should not have switched cases.");
}
else
{
assert(toupper(cResult) == 'N');
dTmp = dCases[iCaseIndex[iCount]];
fputs("You have won the ", stdout);
print_money(dCases[iCaseIndex[iSelected-1]]);
puts(" that was in your case.");
printf("Case %d contained ", iCount+1);
print_money(dTmp);
puts(".");
if (dTmp <= dCases[iCaseIndex[iSelected-1]]*10.0)
puts("You made a very good deal.");
else if (dTmp < dCases[iCaseIndex[iSelected-1]])
puts("You made a good deal.");
else
puts("You should have switched cases.");
}
return 0;
} /* last_round() */
int banker_calls(unsigned int iRound, double dCases[], double *pdReceived)
{
/* Handle whether the banker called this round or not and what he offers. */
/* Return value is the number of turns until the next offer. If -1, then the offer was accepted. */
/* Returns the banker's offer in pdReceived, if accepted. */
int iCount, iTmp, iWaiting=0;
char cTmp;
double dTmp;
/* After which rounds does the banker give an offer? */
const unsigned int iBankerCalls[] = {6, 11, 15, 18, 20, 21, 22, 23, 24, 25};
for (iCount = 0; iCount<(sizeof(iBankerCalls)/sizeof(int)); ++iCount)
{
if (iRound > iBankerCalls[iCount])
/* Not found yet. */
continue;
else
{
iWaiting = iBankerCalls[iCount] - iRound;
break;
}
}
if (iWaiting > 0)
return iWaiting;
/* Banker is calling. */
/* Determine the arithmetic mean of the values. */
dTmp = 0.0;
iTmp = 0;
for (iCount = 0; iCount < CASE_COUNT; ++iCount)
{
if (dCases[iCount] > 0.0)
{
dTmp += dCases[iCount];
++iTmp;
}
}
dTmp /= iTmp;
/* Scale the offer based on the number of cases left.
* from 50% of the mean when all cases are available to 150% of the mean when only 3 cases are in play.
* based on 26 cases in play. This should encourage the player to stay in at the beginning of the game
* and more encouraged to take the offer later in the game.
*/
dTmp *= 75.0/46.0 - iTmp/23.0;
/* Round the value off to 3 significant figures. */
dTmp = round_sig(dTmp, 3, 'o');
fputs("The banker has called with an offer of ", stdout);
print_money(dTmp);
fputs(".\n", stdout);
cTmp = get_char("Deal(Y) or No Deal(N)? ", "YyNn");
if (cTmp < 0)
return 1;
else if (toupper(cTmp)=='Y')
{
fputs("You have won the ", stdout);
print_money(dTmp);
puts(" that was offered by the banker.");
if (pdReceived != NULL)
*pdReceived = dTmp;
return -1;
}
else
{
assert(toupper(cTmp)=='N');
/* Determine the new Waiting */
for (iCount = 0; iCount < (sizeof(iBankerCalls)/sizeof(int)); ++iCount)
{
if (iBankerCalls[iCount] == iRound)
{
/* This is the current one. Go to the next one. */
iWaiting = iBankerCalls[++iCount] - iRound;
}
}
return iWaiting;
} /* else: cTmp == 'N' */
} /* banker_calls() */
int main(void)
{
int iTmp;
unsigned int iRounds=0;
int iSelected = 0, iWaiting=6;
double dReceived = 0;
char sPrompt[120];
/* Where are the cases? Scramble the indexes, not the cases. */
int iCaseIndex[CASE_COUNT];
/* US Values */
double dCases[] = {0.01, 1.00, 5.00, 10.00, 25.00, 50.00, 75.00, 100.00, 200.00, 300.00, 400.00, 500.00, 750.00,
1000.00, 5000.00, 10000.00, 25000.00, 50000.00, 75000.00, 100000.00, 200000.00, 300000.00, 400000.00, 500000.00,
750000.00, 1000000.00};
assert((sizeof(dCases)/sizeof(double)) == CASE_COUNT);
/* use the local locale for all locale operations */
setlocale(LC_ALL, "");
setlocale(LC_NUMERIC, "");
setlocale(LC_MONETARY, "");
/* set up the case indexes, with randomized layout */
set_cases(iCaseIndex);
/* Make sure that the first case, the one with the lowest value, has the smallest value for the local currency. */
/* Because the case indexes have been scrambled, this is probably not case #1. */
dCases[0] = pow(0.1, (double)(localeconv()->frac_digits));
if (dCases[0] == 1.0)
dCases[1] = 2.0;
puts("Deal or No Deal");
printf("There are %d cases, each with a different value, from ", CASE_COUNT);
print_money(dCases[0]);
fputs("\nall the way up to ", stdout);
print_money(dCases[CASE_COUNT-1]);
puts(".");
iSelected = get_case();
if (iSelected < 0)
/* Some error occurred getting the case. */
return 1;
printf("You have selected case number %d. You can discover what value is in your\n", iSelected);
printf("case by revealing what values are in the other cases. The values in those\n");
printf("cases are not in your case.\n");
/* main loop of the game. */
while (true)
{
print_cases(iSelected, iCaseIndex);
if (iRounds == CASE_COUNT-2)
{
if (last_round(iSelected, iCaseIndex, dCases))
return 2;
else
/* Get out of the main loop */
break;
} /* if iRounds == CASE_COUNT-2 (last round) */
else
{
sprintf(sPrompt, "Please choose one of the remaining cases. You have to open %d\n"
"case%s before the next offer. ", iWaiting, (iWaiting==1?"":"s"));
while (true)
{
iTmp = get_integer(sPrompt, CASE_COUNT);
if (iTmp < 0)
return 2;
else if (iTmp == iSelected)
printf("%d is your case. Please choose a different case.\n", iSelected);
else if (iCaseIndex[iTmp-1] < 0)
printf("%d is has already been opened. Please choose a different case.\n", iTmp);
else
/* This is a valid number. */
break;
} /* while */
} /* else: iRounds < CASE_COUNT */
printf("Case %d had ", iTmp);
--iTmp;
print_money(dCases[iCaseIndex[iTmp]]);
puts(" so that value comes off the board.");
/* Clear the value */
dCases[iCaseIndex[iTmp]] = 0.0;
/* Reset the index to this case */
iCaseIndex[iTmp]=-1;
++iRounds;
assert(iRounds < CASE_COUNT);
iWaiting = banker_calls(iRounds, dCases, &dReceived);
if (iWaiting < 0)
{
fputs("Your case contained ", stdout);
print_money(dCases[iCaseIndex[iSelected-1]]);
puts(".");
if (dReceived >= dCases[iCaseIndex[iSelected-1]]*10.0)
puts("You made a very good deal.");
else if (dReceived > dCases[iCaseIndex[iSelected-1]])
puts("You made a good deal.");
else if (fabs(dReceived - dCases[iCaseIndex[iSelected-1]]) < 0.01 * dReceived)
puts("You made a fair deal.");
else
puts("That was not such a good deal for you.");
break;
}
else
print_board(dCases);
} /* while true */
puts("Press Enter to end the game.");
fflush(stdout);
getchar();
return 0;
} /* main() */