Deal or No Deal

Nutster 0 Tallied Votes 327 Views Share

Recently, I saw somebody start a "Deal or No Deal" game in the Posting Games section. This got me thinking of writing a program to actually play the game based on the rules of the show. So I spent a couple of hours writing this in ANSI-C. I probably should have commented it more, but I did not use any wild in-code optimizations. Depending on your compiler/linker, you may have to include the math library during linking. This has been tested to work on gcc 4.6.3 on Ubuntu 12.04 and MS-Visual Studio 2008.

On Ubuntu: cc -O3 -ansi dond.c -lm -o dond
In MSVS: Create a new C console application. Add the file dond.c. Compile and run.
Have fun.

Please add any comments about this code and I will try to answer promptly.

/* dond.c
 * Deal or No Deal
 * Simulation of the game show.
 * by David Nuttall January 29, 2013
 */

#include <stdio.h>
#include <math.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <assert.h>

#define CASE_COUNT	26

void print_double(double dValue)
{
    /* Print the given double with commas as thousand seperators.  Includes two decimal places. */
    int iCount;
    int iPlaces = (int)(floor(log10(dValue)));
    double dTmp;

    if (dValue < 0.0)
    {
	putchar('-');
	dValue *= -1.0;
    }

    iPlaces -= iPlaces % 3;

    dTmp = floor(dValue * pow(0.1, iPlaces));
    printf("%1.0f", dTmp);
    dValue -= dTmp * pow(10.0, iPlaces);

    for (iCount=iPlaces-3; iCount >= 0; iCount -= 3)
    {
	putchar(',');
	dTmp = floor(dValue * pow(0.1, iCount));
	printf("%03.0f", dTmp);
	dValue -= dTmp * pow(10.0, iCount);
    }
    putchar('.');
    printf("%02.0f", dValue*100.0);
} /* print_double */

double round_sig(double dValue, int iSignif, 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 round off. */
    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':
	default:
	    dReturn = floor(dValue * pow(0.1, iPlaces-iSignif) + 0.5) * pow(10.0, iPlaces-iSignif);
	    break;
    } /* switch cDir */
    return dReturn;
} /* round_sig */

int main(void)
{
    unsigned char bContinue = 1;
    int iCount, iTmp, iRounds=0, iSwap;
    int iSelected = 0, iWaiting=6;
    double dTmp, dCaseValue;
    unsigned int iBankerCalls[] = {6, 11, 15, 18, 20, 21, 22, 23, 24, 25};
    int iCaseIndex[CASE_COUNT];
    double dCases[CASE_COUNT] = {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};
    char sInput[80];

    assert((sizeof(dCases)/sizeof(double)) == CASE_COUNT);

    srand(time(NULL));

    for (iCount = 0; iCount < CASE_COUNT; ++iCount)
	iCaseIndex[iCount] = iCount;

    for (iCount = 0; iCount < CASE_COUNT; ++iCount)
    {
	iTmp = rand() % CASE_COUNT;
	if (iCount != iTmp) 
	{
	    iSwap = iCaseIndex[iCount];
	    iCaseIndex[iCount] = iCaseIndex[iTmp];
	    iCaseIndex[iTmp] = iSwap;
	}
    }

    puts("Deal or No Deal");
    printf("There are %d cases, each with a different dollar amount, from $", CASE_COUNT);
    print_double(dCases[0]);
    fputs(" all the way up to $", stdout);
    print_double(dCases[CASE_COUNT-1]);
    puts(".");
    while (iSelected == 0)
    {
	printf("Which case do you want as your case? [1-%d] ", CASE_COUNT);
	if (fgets(sInput, 80, stdin) == NULL)
	    return 1;
	else if (feof(stdin))
	    return 1;
	switch (sscanf(sInput, "%i", &iSelected))
	{
	    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 (iSelected < 1)
		{
		    printf("%d is too small.  Choose a number at least 1.\n", iSelected);
		    iSelected = 0;
		    continue;
		}
		else if (iSelected > CASE_COUNT)
		{
		    printf("%d is too large.  Choose a number no more than %d.\n", iSelected, CASE_COUNT);
		    iSelected = 0;
		    continue;
		}
		/* 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 */

    printf("You have selected case number %d.  You can discover what value is in your case\n", iSelected);
    printf("by revealing what values are in the other cases.  The values in those cases are\n");
    printf("not in your case.\n");

    do
    {
	puts("Available cases:");
	iTmp = 0;
	for (iCount = 1; iCount <= CASE_COUNT; ++iCount)
	{
	    if (iCount == iSelected)
		; /* Do not print this case. */
	    else if (iCaseIndex[iCount-1] < 0)
		; /* Do not print cases that have already been opened. */
	    else
	    {
		printf("[%d]\t", iCount);
		++iTmp;
		if (iTmp > 8)
		{
		    putchar('\n');
		    iTmp = 0;
		}
	    }
	}
	putchar('\n');

	if (iRounds == CASE_COUNT-2)
	{
	    /* 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;
	    }
	    do
	    {
		printf("There are only two cases left: yours (%d) and case number %d.\n", iSelected, iCount+1);
		fputs("There is one last question.  Do you want to switch cases? (Y/N) ", 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]))
			++iTmp;
		    else if (toupper(sInput[iTmp])=='Y')
		    {
			dTmp = dCases[iCaseIndex[iCount]];
			fputs("You have won the $", stdout);
			print_double(dTmp);
			printf(" that was in case %d.\n", iCount+1);
			fputs("Your case contained $", stdout);
			print_double(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.");
			return 0;
		    }
		    else if (toupper(sInput[iTmp])=='N')
		    {
			dTmp = dCases[iCaseIndex[iCount]];
			fputs("You have won the $", stdout);
			print_double(dCases[iCaseIndex[iSelected-1]]);
			puts(" that was in your case.");
			printf("Case %d contained $", iCount+1);
			print_double(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;
		    }
		    else
		    {
			puts("Please enter Y or N.");
			continue;
		    }
		}
	    } while (strchr("YyNn", sInput[iTmp])==NULL);
	}
	else
	{
	    printf("Please choose one of the remaining cases.  You have to open %d case%s before the next offer.  ",
		    iWaiting, iWaiting==1?"":"s");
	    if (fgets(sInput, 80, stdin)==NULL)
		return 2;
	    else if (feof(stdin))
		return 2;
	    switch (sscanf(sInput, "%i", &iTmp))
	    {
		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 (iTmp < 1)
		    {
			printf("%d is too small.  Choose a number at least 1.\n", iTmp);
			continue;
		    }
		    else if (iTmp > CASE_COUNT)
		    {
			printf("%d is too large.  Choose a number no more than %d.\n", iTmp, CASE_COUNT);
			continue;
		    }
		    else if (iTmp == iSelected)
		    {
			printf("%d is your case.  Choose a different case.\n", iSelected);
			continue;
		    }
		    else if (iCaseIndex[iTmp-1] < 0)
		    {
			printf("%d is has already been opened.  Choose a different case.\n", iTmp);
			continue;
		    }
		    /* If it made it so far, then it is a good number. */
		    break;
		default:
		    /* Something else happened, just not sure what. */
		    puts("Something weird happened.  Please try again.");
		    continue;
	    } /* switch */
	}
	printf("Case %d had $", iTmp);
	print_double(dCases[iCaseIndex[iTmp-1]]);
	puts(" so that value comes off the board.");
	dCases[iCaseIndex[iTmp-1]] = 0.0;
	iCaseIndex[iTmp-1]=-1;
	++iRounds;
	assert(iRounds < CASE_COUNT);
	for (iCount = 0; iCount<(sizeof(iBankerCalls)/sizeof(int)); ++iCount)
	{
	    if (iRounds > iBankerCalls[iCount])
		/* Not found yet. */
		continue;
	    else
	    {
		iWaiting = iBankerCalls[iCount] - iRounds;
		break;
	    }
	}
	if (iRounds == iBankerCalls[iCount])
	{
	    /* Banker is calling. */
	    dTmp = 0.0;
	    iTmp = 0;
	    for (iCount = 0; iCount < CASE_COUNT; ++iCount)
	    {
		if (dCases[iCount] > 0.0)
		{
		    dTmp += dCases[iCount];
		    ++iTmp;
		}
	    }
	    dTmp /= iTmp;
	    /* Scale it 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.
	     */
	    dTmp *= 75.0/46.0 - iTmp/23.0;
	    /* Round it up a bit as an insentive. */
	    dTmp = round_sig(dTmp, 3, 'u');
	    do
	    {
		fputs("The banker has called with an offer of $", stdout);
		print_double(dTmp);
		fputs(".\nDeal(Y) or No Deal(N)? ", stdout);
		if (fgets(sInput, 80, stdin)==NULL)
		    return 1;
		else if (feof(stdin))
		    return 1;
		iTmp = 0;
		while (sInput[iTmp] != '\0')
		{
		    if (isspace(sInput[iTmp]))
			++iTmp;
		    else if (toupper(sInput[iTmp])=='Y')
		    {
			fputs("You have won the $", stdout);
			print_double(dTmp);
			puts(" that was offered by the banker.");
			fputs("Your case contained $", stdout);
			print_double(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 if (fabs(dTmp - dCases[iCaseIndex[iSelected-1]]) < 0.01 * dTmp)
			    puts("You made a fair deal.");
			else
			    puts("That was not such a good deal for you.");
			return 0;
		    }
		    else if (toupper(sInput[iTmp])=='N')
			break;
		    else
		    {
			puts("What was that?  Please answer Y or N.");
			break;
		    } /* else */
		} /* while */
	    } while(iTmp >= 80 || strchr("YyNn", sInput[iTmp]) == NULL);
	    for (iCount = 0; iCount < (sizeof(iBankerCalls)/sizeof(int)); ++iCount)
	    {
		if (iBankerCalls[iCount] == iRounds)
		{
		    /* This is the current one.  Go to the next one. */
		    iWaiting = iBankerCalls[++iCount] - iRounds;
		}
	    }
	} /* if Rounds == BankerCalls */
	/* Display the board. */
	for (iCount = 0; iCount<(CASE_COUNT/2); ++iCount)
	{
	    if (dCases[iCount] > 0.0)
	    {
		putchar('$');
		print_double(dCases[iCount]);
	    }
	    putchar('\t');
	    if (dCases[iCount + (CASE_COUNT/2)] > 0.0)
	    {
		putchar('$');
		print_double(dCases[iCount+(CASE_COUNT/2)]);
	    }
	    putchar('\n');
	}
    } while (bContinue);
    return 0;
}
deceptikon 1,790 Code Sniper Team Colleague Featured Poster

Let's start with your assumption about the formatting of a numeric value, which isn't safe these days. Consider the following function that's designed to be locale-friendly for the same task:

#include <ctype.h>
#include <locale.h>
#include <math.h>
#include <string.h>

char *dtoa(char buf[], double value, int precision, int show_sign)
{
    char *grouping = localeconv()->grouping;   /* Locale-specific grouping count/order */
    int group_size = *grouping;                /* Current working group total size */
    int group_len = 0;                         /* Current working group processed size */
    double ipart;                              /* Integral half of the value */
    double fpart = modf(fabs(value), &ipart);  /* Fractional half of the value */
    size_t last = 0;

    /* Set and skip any explicit sign */
    if (value < 0 || show_sign) {
        buf[last++] = (value < 0) ? '-' : '+';
    }

    /* Build the locale-friendly integer part */
    for (; fabs(ipart) > 1; ipart /= 10) {
        if (group_size && group_len++ == group_size) {
            /*
                The current working group has been completed. Apply
                a group separator and move to the next locale-specified 
                grouping. Repeat the last valid grouping if the locale
                doesn't specify any further groupings.
            */
            buf[last++] = *localeconv()->thousands_sep;

            if (*grouping && *++grouping) {
                group_size = *grouping;
            }

            /* Reset the group to 1 because a digit is still being processed */
            group_len = 1;
        }

        buf[last++] = (char)(fmod(ipart, 10) + '0');
    }

    if (last == 0 || (last == 1 && !isdigit(buf[0]))) {
        /* There weren't any integer part digits, force to 0 */
        buf[last++] = (char)((int)fmod(ipart, 10) + '0');
    }

    buf[last] = '\0'; /* Close the string for _strrev */

    _strrev(buf + (buf[0] == '-' || buf[0] == '+')); /* Reverse the integer part */
    strcat(buf, localeconv()->decimal_point);        /* Apply the radix */

    /* Build the fractional part with zero fill up to the  requested precision */
    for (last = strlen(buf); --precision >= 0; ++last, fpart *= 10) {
        buf[last] = (char)((int)fmod(fpart * 10, 10) + '0');
    }

    if (buf[last - 1] == *localeconv()->decimal_point) {
        /* The precision was zero, so the radix can be trimmed */
        --last;
    }

    buf[last] = '\0';

    return buf;
}

It does require that you use the standard locale functions to set a non-default locale for numeric grouping and separation, but it's much more useful in countries where three digit groups and a comma thousands separator (not to mention a dot for the radix) are not used:

int main()
{
    char buf[BUFSIZ];

    setlocale(LC_NUMERIC, "");

    printf("'%s'\n", dtoa(buf, 12345.67890, 2, 0));
}

srand(time(NULL));

I could be nitpicky about using time_t as an argument to srand(), but I won't because I still haven't seen any non-theoretical implementations where it fails.

iTmp = rand() % CASE_COUNT;

I could be nitpicky here too, because historically rand() has been pretty weak when lopping off the high order bits with the remainder operator. These days that weakness has been improved drastically, so you don't really see the uber predictable sequences anymore.

printf("Which case do you want as your case? [1-%d] ", CASE_COUNT);
if (fgets(sInput, 80, stdin) == NULL)

This one I will mention because it pops up occasionally. There's no guarantee that the prompt will be displayed before fgets() blocks for input, so you may find on some systems that the user will have no instructions on what to do. The only control you have in ensuring the prompt shows is printing a newline character at the end, or calling fflush(). For prompts the latter works better because it doesn't introduce a line break:

printf("Which case do you want as your case? [1-%d] ", CASE_COUNT);
fflush(stdout);

if (fgets(sInput, 80, stdin) == NULL)

Using continue instead of break in your input switch statement is a little unconventional. The switch is the last statement in the loop anyway, so there's no need to continue, just break out of the switch as usual and you'll get the same result with less eyebrow raising.

I'd consider modularizing that monolithic main() into multiple functions, but aside from that nothing really stands out after eyeballing th code. Nice work. :)

Nutster 58 Newbie Poster

My random number implementations in this program are not ment to be cryptographically secure. I just needed a way to get a relatively random sequence of cases. The exact implemenation was not that important, so long as a different sequence would be generated between runs. Besides, this is the way I have been using srand/rand for more than 20 years for a casual program like this. For more unpredictability, I tend to use something like the mt19937 pseudo-random generator, but I figured that would be overkill in this situation. I really only needed 26 values in the sequence.

Flushing the stdout stream looks like a good idea. My testing during development did not reveal any problems not flushing the stdout first. For added portability, the fflush should be included.

I did not think of internationalization for this small program but something along the lines of your function could be used to support it. I would definitely pass and check the buffer size to the function. The dtoa function as given could easily overflow the buffer and not know it. Also, I am using char (ASCII) and not wchar (Unicode). I do not plan on changing this, although the number adjustments could be a good idea.

I agree that main is a little big and could improve in maintainability by breaking the main function into some smaller functions and just calling them from within main. I will look at the program with these considerations and post the new version later.

Thank you for the critique.

deceptikon 1,790 Code Sniper Team Colleague Featured Poster

My random number implementations in this program are not ment to be cryptographically secure.

I wasn't talking about being cryptographically secure. In fact, being cryptographically secure when all you need is something a user would recognize as "random" would be unnecessarily inefficient.

As an example of using the low order bits to generate a random sequence, there have been implementations where rand() % 2 would produce a range of [0,1,0,1,0,1,0,1...] (just alternating 0 and 1). This is a highly predictable and nobody would call it random. The low order bits of the result were disturbingly non-random, so using modulo to extract just those bits was a poor solution.

I would definitely pass and check the buffer size to the function. The dtoa function as given could easily overflow the buffer and not know it.

That code was taken from one of my libraries where I control the buffer, so it's guaranteed to be large enough for any double value. I don't think overflow is a huge issue when the string representation is tightly bound to an upper limit, and there was little point in adding it for this example as it distracts from the topic, but as a general library you're correct that it should account for being called by a moron. ;)

Also, I am using char (ASCII) and not wchar (Unicode). I do not plan on changing this, although the number adjustments could be a good idea.

I didn't assume otherwise in my reply, what do you mean by "number adjustments"?

mvmalderen commented: Nothing would prevent a moron to pass a wrong buffer size though :D +0
Nutster 58 Newbie Poster

Number adjustments = Implementing locale-aware number printing routine.

deceptikon 1,790 Code Sniper Team Colleague Featured Poster

That has nothing to do with character sets. You can use ASCII exclusively and still confuse a user in Germany by displaying 1,234.56 instead of 1.234,56.

Nutster 58 Newbie Poster

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() */
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.