10
\$\begingroup\$

I made a Powerball lottery simulator in C. I chose C because it is fast.

Here's how it plays:

  • The user plays one ticket every day.
  • The user can choose to play the same ticket every day or a different ticket every day.
  • The numbers on each ticket are a "quick pick" -- that is, randomly generated.
  • The program also generates a random winning drawing every day.
  • The program stops when a user-decided number of balls on that day's ticket match with the same number of balls in the drawing.
  • The user decides whether the powerballs (the 6th balls) must match before the program stops.
  • The program ends with telling the user how many tickets they bought, how many years and days they played, and how much they won.

My main problem is that when I run this to not stop until all 6 balls match, I am waiting for days (real life days) without a match. I comment out the printf() lines in main() that show the numbers for better efficiency, but I still have yet to get a match on all six numbers.

What else can I do to improve efficiency?

Here's the project on GitHub if anyone wants to try it out.

main.c:

/* *** This program simulates a person playing the powerball
   *** everyday and stops once a certain number of balls
   *** match. Ther user decides how many balls should match
   *** before stopping. */

/* DEFINTIONS:
   redball and powerball refer to the same thing
*/

#include <stdio.h>
#include <stdlib.h>
/* for rand(), srand() */
#include <stdbool.h>
#include <time.h>
/* for time(), to seed srand() */

/* functions to get user input for program options */
#include "options.h"

/* functions to do the calculation */
#include "calc.h"

/* functions for output */
#include "output.h"

int main(void)
{
    /* customer ticket, powerball drawing */
    int ticket[6] = {0};
    int draw[6] = {0};

    /* how many in-program days have we been playing? */
    int days = 0;
    int years = 0;

    /* how much did the player win? */
    int winnings = 0;

    /* Intializes random number generator */
    srand(time(NULL));

    /* do the powerballs match? */
    bool powerballsMatch = false;

    /* how many elements are equal */
    int equalElements = 0;

    /* how many equal elements to stop at */
    int stopAtThisManyMatches = 0;

    /* use new ticket everyday? */
    bool newTicketEveryday = false;

    /* do the powerballs have to match? */
    bool mustPowerballsMatch = false;

    /* ask how many numbers to match */
    printf("Stop after how many numbers match?: ");
    scanf("%d", &stopAtThisManyMatches);

    /* ask if powerballs must match */
    mustPowerballsMatch = askIfPowerballsShouldMatch(); 

    /* ask if ticket should be new everday */
    newTicketEveryday = getTicketOption();

    /* very input input for stopAtThisManyMatches */
    if (stopAtThisManyMatches < 1 || stopAtThisManyMatches > 6)
    {
        printf("Number must be between 1 - 6.\n");
        return 0;
    }

    printf("\n"); // new line

    /* if the powerballs don't have to match */
    if (mustPowerballsMatch == false)
    {
        while (equalElements < stopAtThisManyMatches)
        { 
            /* randomize ticket, draw */

            /* If the user wants to user the same ticket every day,
       * I will only randomize the array ticket[] on the first day. */
      if (newTicketEveryday == false)
            {   
                /* only randomize array on first day */ 
                if (days == 0) randomizeArray(ticket);
            }

            /* If the user want a different ticket every day */
            else randomizeArray(ticket);
            randomizeArray(draw);

            /* see if powerballs match */
            powerballsMatch = checkPowerballs(ticket, draw);

            /* compare the arrays */
            equalElements = compareArrays(ticket, draw, powerballsMatch);   

            /* print the arrays */
            printArray(ticket);
            printArray(draw);

            /* print a space */
            printf("\n");

            days++; 
        }
    }

    /* if powerballs must match */
    else if (mustPowerballsMatch == true)
    {
        while (equalElements < stopAtThisManyMatches || powerballsMatch == false)
        { 
            /* randomize ticket, draw */

            /* If the user wants to user the same ticket every day,
       * I will only randomize the array ticket[] on the first day. */
      if (newTicketEveryday == false)
            {   
                /* only randomize array on first day */ 
                if (days == 0) randomizeArray(ticket);
            }
            else randomizeArray(ticket);
            randomizeArray(draw);

            /* see if powerballs match */
            powerballsMatch = checkPowerballs(ticket, draw);

            /* compare the arrays */
            equalElements = compareArrays(ticket, draw, powerballsMatch);   

            /* print the arrays */
            printArray(ticket);
            printArray(draw);

            /* print a space */
            printf("\n");
            days++; 
        }
    }

    /* Tell the user have many days they have played. */

    /* Tell the user have many days they have played. */
    printf("You bought %d tickets.\n", days);

    if (days <= 365)
    {
        printf("You have played for %d days.\n", days);
        printf("You spent $%d.\n", days * 2);
    }

    else 
    {
        years = days / 365;
        days = days - (years * 365);
        printf("You have played for %d years and %d days.\n", years, days);             
        printf("You spent $%d.\n", ((years * 365) + days) * 2);
    }

    printf("\n");

    /* do the powerballs match */
    if (powerballsMatch == false)
    {
        printf("Powerballs do not match.\n");
    }

    else if (powerballsMatch == true)
    {
        printf("Powerballs match.\n");
    }   

    winnings = amountWon(equalElements, powerballsMatch);

    if (winnings == 999) printf("You won the jackpot.\n");
    else printf("You won $%d.\n", winnings);

    return 0;
}

calc.c:

#include <stdbool.h>
/* for type bool */
#include <stdlib.h>
/* for rand() */

/* checks if powerballs match */
bool checkPowerballs(int array1[], int array2[])
{
    /* If the powerball of the user's ticket (array1[5])
   * matches ( == ) the powerball of the drawing (array[5]),
   * then return true. */
    if (array1[5] == array2[5]) return true;
    else return false;
}

/* This function gets random numbers for an array in
 * the range [1, 69] for first five numbers and [0, 26] */
void randomizeArray(int array[])
{
  /* make the first five elements of the array */
  /* a random number between 1 and 69 */
  array[0] = (rand() % 69) + 1;

  /* ___________________________________________ */

  array[1] = (rand() % 69) + 1;

  /* make sure the same number isn't drawn again*/
  while (array[1] == array[0])
  {
    array[1] = (rand() % 69) + 1;
  }

  /* ___________________________________________ */

  array[2] = (rand() % 69) + 1;

  /* make sure the same number isn't drawn again*/
  while (array[2] == array[1] || array[2] == array[0])
  {
    array[2] = (rand() % 69) + 1;
  }

  /* ___________________________________________ */

  array[3] = (rand() % 69) + 1;

  /* make sure the same number isn't drawn again*/
  while (array[3] == array[2] || array[3] == array[1] || array[3] == array[0])
  {
    array[3] = (rand() % 69) + 1;
  }

  /* ___________________________________________ */

  array[4] = (rand() % 69) + 1;
  /* make sure the same number isn't drawn again*/
  while (array[4] == array[3] || array[4] == array[2] || array[4] == array[1] || array[4] == array[0])
  {
    array[4] = (rand() % 69) + 1;
  }

  /* ___________________________________________ */

  /* make the last element the powerball - it can be */
  /* 1 to 26 */
  array[5] = (rand() % 26) + 1;

  /* ___________________________________________ */

  return;
}

  /* returns how many elements are equal */
int compareArrays(int array1[], int array2[], bool powerballsMatch)
{
  /* index */
  int i, j;

  /* how many equal elements */
  int equalElements = 0;

  for (i = 0; i < 4; i++)
  {
    for (j = 0; j < 4; j++)
    {
      if (array1[i] == array2[j])
      {
        equalElements++;
      }
    }
  }

  /* if the powerballs match, add 1 to equalElements */
  if (powerballsMatch == true) equalElements++;

  return equalElements;
}

/* This function returns how much the player won. */
int amountWon(int equalElements, bool powerballsMatch)
{
  int winnings = 0;

  /* one number mathces */
  if (equalElements == 1 && powerballsMatch == false) winnings = 0;
  /* and powerball matches */
  else if (equalElements == 1 && powerballsMatch == true) winnings = 4;

  /* two numbers mathces */
  else if (equalElements == 2 && powerballsMatch == false) winnings = 0;
  /* and powerball matches */
  else if (equalElements == 2 && powerballsMatch == true) winnings = 4;

  /* three numbers mathces */
  else if (equalElements == 3 && powerballsMatch == false) winnings = 7;
  /* and powerball matches */
  else if (equalElements == 3 && powerballsMatch == true) winnings = 7;

  /* four numbers mathces */
  else if (equalElements == 4 && powerballsMatch == false) winnings = 100;
  /* and powerball matches */
  else if (equalElements == 4 && powerballsMatch == true) winnings = 100;

  /* five numbers mathces */
  else if (equalElements == 5 && powerballsMatch == false) winnings = 1000000;
  /* and powerball matches */
  else if (equalElements == 5 && powerballsMatch == true) winnings = 50000;

  /* six number match */
  /* if six numbers, match, powerball has to match */
  else if (equalElements == 6) winnings = 999;

  else winnings = 0;

  return winnings;
}

calc.h:

#include <stdlib.h>
#include <time.h>
#include <stdbool.h>

/* a function to get random numbers for ticket and powerball */
void randomizeArray(int array[]);

/* compare the arrays to see if they are equal */
int compareArrays(int array1[], int array2[], bool powerballsMatch);

/* how much has player won */
int amountWon(int equalElements, bool powerballsMatch);

/* check if powerballs match */
bool checkPowerballs(int array1[], int array2[]);

options.c:

#include <stdio.h>
#include <stdbool.h>
/* for type bool */

/* do powerballs have to match */
bool askIfPowerballsShouldMatch(void)
{
  int answer = false;

  /* ask the user if powerballs must match */
    printf("\n"); // new line
  printf("Must powerballs match?\n");
  printf("Type 1 for yes or 2 for no: ");

  /* get the answer */
  scanf("%d", &answer);

  /* return result */
  if (answer == 1) return true;
  else if (answer == 2) return false;
  /* return false in case they type something else */
  return true;
}

/* new ticket or same ticket */
bool getTicketOption(void)
{
  int answer = false;

  /* ask the user if powerballs must match */
    printf("\n"); // new line
  printf("Use new ticket everyday or same ticket everyday?\n");
  printf("Type 1 for new ticket or 2 for same tikcet: ");

  /* get the answer */
  scanf("%d", &answer);

  /* return result */
  if (answer == 1) return true;
  else if (answer == 2) return false;
  /* return false in case they type something else */
    return true;
}

options.h:

#include <stdio.h>
#include <stdbool.h>
/* for type bool */
/* ask the user for options */

/* do powerballs have to match */
bool askIfPowerballsShouldMatch(void);

/* Does the user want to use the same ticket
 * everyday or have a new ticket generated every
 * day? */
bool getTicketOption(void);

output.c:

#include <stdio.h>
/* for printf() */

/* print the array */
void printArray(int array[])
{
  printf("%d %d %d %d %d %d\n", array[0], array[1], array[2], array[3], array[4], array[5]);

  return;
}

output.h

/* print an array */
void printArray(int array[]);

makefile:

all:
    gcc -Wall main.c options.c calc.c output.c -o powerballSim

test:
    clear
    gcc -Wall main.c options.c calc.c output.c -o powerballSim
    ./powerballSim

clean:
rm powerballSim
\$\endgroup\$
7
\$\begingroup\$

I choose C because it is fast.

You should think about your priorities for the code. The first priority should be correctness, and C is a terrible choice for that goal, since a bug in one part of the program (especially buffer overflows) can influence other totally unrelated parts in unpredictable ways. If you prefer to have working code over fast code, you should look for a language that checks array boundaries and invalid pointers.

Choosing a "fast" programming language is useless when you don't tell the compiler that you want fast code. By default, C compilers produce slow code. (I'll come back to this topic later, in the Makefile section.)

Your code has lots of typos. Run it through a spell checker to catch the obvious ones at least.

Why do you say "redball and powerball refer to the same thing", when the word "redball" doesn't appear in the code that follows? That's unnecessary. Remove that comment.

Many of your comments are redundant. Remove all comments that only repeat what the code already says.

The code variable == false is usually written as !variable.

The code variable == true is usually written as variable.

Fix the indentation of the code. The then branch of each if statement must start at the same column as the corresponding else branch. There are programs that do the indentation for you, such as GNU indent. Use these tools.

Don't lie in comments. "print a space" is not what printf("\n") does, since a space is commonly thought of as being a horizontal space, not a vertical space.

Better yet: remove all the comments that describe code, only keep those that explain the variables further. Your code is structured so well, and the variables and functions have so good names that you don't need any comments at all.

In calc.c, in the function compareArrays, you have an off-by-one error. Since array[5] is the powerball, array[0..4] should be the normal numbers. You only compare array[0..3] because the conditions say i < 4.

The function randomizeArray contains lots of duplicated code. Since you already know how to use for loops and functions, write a function bool arrayContains(const int haystack, size_t haystackSize, int needle). You can then use this function to regenerate array[i] randomly while arrayContains(array, i, array[i]).

A return; that doesn't return any value is useless at the end of a function.

Remove the redundant if conditions from amountWon. A sentence like whether the powerballsMatch or not, you win 100 can be shortened to you win 100.

In calc.h, there is no need to #include <time.h> since the function declarations in that file do not use the time at all. The #include is only necessary in the corresponding calc.c file, if at all.

In options.c, for each call to scanf you must check the return value to see how many variables have been read successfully. You must only access those variables which have definitely been read. Typically, the code looks like if (scanf("%d", &var) == 1) { ... }.

From a usability point of view, it is confusing for the user to type 1 when they mean yes. Make them type the obvious y instead of 1. You can read this using scanf("%c", &answer), declaring char answer.

The line int answer = false is wrong, since false is not an int, at least not colloquially. The C compilers only accept this code for historic reasons.

In the Makefile, you should include the options -Os and -Wextra.

\$\endgroup\$
  • \$\begingroup\$ Thank you for your input. I will revise my code with your suggestions in mind. \$\endgroup\$ – user91656 Apr 12 '17 at 21:31
  • \$\begingroup\$ What kind of programs should I use C for in the future? \$\endgroup\$ – user91656 Apr 13 '17 at 0:40
  • \$\begingroup\$ You can use the C programming language for almost everything that isn't the web. I was developed specifically for operating system development. The Unix operating system was written in C, Microsoft Windows was originally written in C and then moved to C++. The current use of C is for embedded systems programming. The C programming language is sometimes thought of as a high level assembly language. \$\endgroup\$ – pacmaninbw Apr 13 '17 at 1:42
5
\$\begingroup\$

Interesting problem you addressed there. Definitely a nice question.

Other than Optimization and the number of comments I will address things that @RolandIllig didn't address.

Optimizing for Performance

The C programming language can definitely generate fast code, but as Roland Illig indicated the -O switch needs to be used to generate the fastest code, the -O3 switch will generate the fastest code.

Also as Roland Illig indicated, optimization should always be the last thing considered. These are two articles on when to optimize the code are good references ACM The Fallacy of Premature Optimization and Premature Optimization. The primary concern is to write robust code that is easy to debug and modify. Once an application is release for use by others there will always be feature requests so easing any future modifications is the second most important thing to consider. The most important thing to consider is writing robust correct code. Make sure that any input is checked for errors, provide easy to understand error messages, make sure to handle any possible errors, especially user errors.

When the application or program takes noticeable time, or uses too much memory then it can and probably should be optimized. When the code is optimized, the first thing one should do is profile the code to see where bottle necks are. If the code spends 90% of it's time in one particular function, that function should be optimized.

Reduce the Complexity of All Tasks in a Program

It is easier to write and debug software when the complexity of of each and every task is reduced to an atomic level. The Single Responsibility Principal is an excellent software design principal to follow, it states: module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

Any function that is 157 lines long is TOO complex. The main() function is 157 lines long, without the unnecessary comments (as Roland Illig pointed out) the main function is still 128 lines long. Almost all functions should fit in a single editing screen of whatever editing tool is being used, my arbitrary limit is about 40 lines. The main() function should be broken up into multiple sub-functions that are all atomic.

The main() function should not contain the actual implementation of the algorithm, that should be one of the sub-functions. What the main() function should contain is a function that gets the user options, a function that sets up the necessary conditions using the user options, a function that implements the algorithm, and a function that cleans up after the algorithm is finished.

The proper definition of main()

In the code the main function is defined as int main(void) { }

This is actually an error, whether the arguments are used or not, main is always passed arguments from the operating system, the definition of main should always be:

int main(int argc, const char * argv[]) { }

The variable argc is the argument count and will always be >= 1, the variable argv is an array of strings. There will always be at least one string in argv, the first argument is the name of the program. the variable argv is used to pass command line switches such as -v, -h, -p into the program.

Rather than return 0; at the end of the main function, there are two macros defined in stdlib.h that might be more useful, EXIT_SUCCESS and EXIT_FAILURE. A good programing practice might be to define a status variable at the begining of main() and return that variable where ever the program exits:

int main(int argc, const char * argv[])
{
    int exitStatus = EXIT_SUCCESS;

    ... 

    if (errorOccurred) {
        exitStatus = EXIT_FAILURE;
    }

    ...

    return exitStatus;
}

Magic Numbers

There are at least three numeric constants used in the main that should Symbolic Constants, this would make the code more readable and understandable. It's almost obvious that 365 is the number of days in a year, but it is not obvious what 2 or 999 are. I'm guessing that 2 represents the cost of a powerball ticket, and that 999 is just some arbitrary number.

To create numeric constants in C use #define to create a macro:

#define DAYS_IN_A_YEAR 365
#define POWERBALL_TICKET_PRICE 2
#define MAX_WINNINGS 999

Note: In the specific usage of these numbers there are errors, every four years there are 366 days in a year, the price of a powerball ticket can change. Why 999?

Also what if the winnings were greater than 999?

In the following code 5 is another magic number

bool checkPowerballs(int array1[], int array2[])
{
    /* If the powerball of the user's ticket (array1[5])
     * matches ( == ) the powerball of the drawing (array[5]),
     * then return true. */
    if (array1[5] == array2[5]) return true;
    else return false;
}

Why 5?

Symbolic constants are used because you might not be the one who alters the code, or you may come back in 5 years to change the code.

Unnecessary Code

In options.c the functions askIfPowerballsShouldMatch() and getTicketOption() have the following code:

/* return result */
if (answer == 1) return true;
else if (answer == 2) return false;
/* return false in case they type something else */
return true;

In both cases this can be shortened to:

return (answer == 1);

since (answer == 1) is a Boolean condition.

The second return true; is unnecessary since it is an unreachable statement.

\$\endgroup\$
  • 2
    \$\begingroup\$ You're not quite right about the signature of main. According to ISO/IEC 9899:201x, section 5.1.2.2.1 'Program startup': "[main function] shall be defined with a return type of int and with no parameters [...] or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared)". \$\endgroup\$ – kyrill Apr 13 '17 at 22:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.