I feel the need... the need for speed (C++)

Speedo

Esse Quam Videri
Joined
May 29, 2003
Messages
4,891
Location
NC USA
I'm doing this program for a friend of mine... It's simulating playing the powerball lottery, but lets you either play until you win the jackpot or just win a single prize.

Since the play until jackpot option can literally run for days, I'd like to squeeze every bit of speed out of the code that I can. Any of you more god-like coders have suggestions for improvements? 'Preciate it ;)

DL: http://home.directus.net/jrc748/lottery.cpp

Code:
/* lottery.cpp
** Written for DevC++ 4.9.9.2
** by Michael Crawford
*/

#include <cstdlib>
#include <iostream>
#include <conio.h>
#include <ctime>

using std::cout;
using std::cin;
using std::endl;

const short LOTTO_SIZE = 6;		// size of arrays for tracking picks

// used to store the text output shown when the player wins
const char* const winTxt[] = { "Win!!!! - Jackpot!!!!",		// 5 + PB
							   "Win!!!! - $200,000!!",		// 5
							   "Win!!!! - $10,000!!",		// 4 + PB
							   "Win!!!! - $100!",			// 4, 3 + PB
							   "Win!!!! - $7!",				// 3, 2 + PB
							   "Win!!!! - $4!",				// 1 + PB
							   "Win!!!! - $3!" };			// PB

struct pbSet
{
		float alone;
		float match1;
		float match2;
		float match3;
		float match4;
		float match5;
};

// used to keep tally of wins if playing until jackpot
struct winCount
{
	pbSet powerball;
	float match3;
	float match4;
	float match5;
} winnings = {0, 0, 0, 0, 0, 0, 0, 0, 0};	// counter to track the winnings

// tracks the elapsed time while the game runs
class timer
{
public:
	timer( ) : start(std::clock( )){ }		// default constructor starts timer
	~timer( );								// destructor ends timer, prints time elapsed
private:
	std::clock_t start;
};

int menu( );
/* Main menu function
** Outputs menu options and returns the player's choice
** 1 = show instructions
** 2 = play standard game
** 3 = play until jackpot is won
** 4 = exit
*/

void game(const bool& type);
/* Main game function
** boolean argument determines the type of game
*/

short* makePicks(const short& size);
/* Prompts the user to input their choices
** checks that those choices are valid, then
** returns a pointer to the array of picks
*/

short* makePicksRand(const short& size);
/* Selects random numbers, checks them for validity
** then returns pointer to the array
*/

bool validate(const short& num, const short* picks);
/* Checks the passed array for validity.
** see instructions for the criteria to determine
** if a number is valid
*/

bool checkWin(const short* player, const short* random, const bool& type);
/* Checks the users selections against the random selections
** if standard gametype, returns true whenever a match is found
** if jackpot gametype, returns true *only* when all numbers match
*/

int main( )
{
	cout.setf(std::ios::fixed);		// Remove counter's E notation
	cout.precision(0);				// Don't display decimal
	srand(time(0));					// Seed RNG

	bool gameType = false;
	// true - jackpot game
	// false - standard game

	while(true)
	{
		switch(menu( ))	// Display menu & get users selection
		{
			case 1:		// Show instructions
				cout << "You will be asked to pick a total of 6 numbers.\n"
					 << "The first five numbers must be a value between 1 and 55, and you may not repeat\n"
					 << "a number that you have already picked.  The last number, the powerball, must be\n"
					 << "between 1 and 42, and may repeat a previously picked value.\n"
					 << "All picks must be whole number values.\n\n"
					 << "There are 9 possible winning combinations:\n"
					 << "5 matches + powerball  - Jackpot\n"
					 << "5 matches              - $200,000\n"
					 << "4 matches + powerball  - $10,000\n"
					 << "4 matches              - $100\n"
					 << "3 matches + powerball  - $100\n"
					 << "3 matches              - $7\n"
					 << "2 matches + powerball  - $7\n"
					 << "1 match + powerball    - $4\n"
					 << "Powerball only         - $3\n"
					 << "Press any key to return to the main menu\n\n";
				_getch( );
				break;
			case 2:		// standard game
				gameType = false;
				game(gameType);
				break;
			case 3:		// jackpot game
				gameType = true;
				game(gameType);
				break;
			case 4:		// exit
				delete [] winTxt;
				return 0;
				break;
			default:	// Invalid choice
				cout << "Invalid menu choice.\n\n";
				break;
		}
	}
}

timer::~timer( )
{
	float seconds = (static_cast<float>(std::clock( ) - start) / CLOCKS_PER_SEC),
	      days = seconds / 86400.0f,
		  hours = seconds / 3600.0f,
		  minutes = seconds / 60.0f;
	while (hours >= 24.0f)
		hours--;
	while (minutes >= 60.0f)
		minutes--;
	while (seconds >= 60.0f)
		seconds--;
	cout << "Elapsed time ";
	if (minutes >= 1.0f)
	{
		if (days >= 1.0f)
		{
			if (hours >= 1.0f)
				cout << hours << " hours, ";
			cout << days << " days, ";
		}
		cout << minutes << " minutes, ";
	}
	cout.precision(6);
	cout << seconds << " seconds.\n";
	cout.precision(0);
} 

int menu( )
{
	short temp = 0;
	cout << endl
		 << "1. View instructions\n"
		 << "2. Play standard game\n"
		 << "3. Play until jackpot\n"
		 << "4. Exit\n";
	cin >> temp;
	cout << endl;
	if (! cin)
	{
		cin.clear( );
		cin.ignore( );
		return 0;
	}
	else
		return temp;
}

void game(const bool& type)
{
	short *playerPicks,									// numbers chosen by the player
		  *randPicks;									// random numbers (winning picks)
	float counter = 1.0f;								// counter of number of tries
	bool win = false;									// tracks win condition
	playerPicks = makePicks(LOTTO_SIZE);
	cout << endl
		 << "You've chosen: ";
	for (short i = 0; i < LOTTO_SIZE; i++)
		cout << playerPicks[i] << " ";
	cout << endl
		 << "Press any key to begin playing.\n";
	_getch( );
	{	// begin section to be timed
		timer stopwatch;	// start the timer
		while (! win)
		{
			randPicks = makePicksRand(LOTTO_SIZE);
			cout << "Try " << counter << ": ";
			for (short i = 0; i < LOTTO_SIZE; i++)
				cout << randPicks[i] << " ";
			cout << endl;
			win = checkWin(playerPicks, randPicks, type);
			counter++;
			delete [] randPicks;
		}
	}	// end section to be timed, timer ends via destructor
	cout << endl;
	if (type)
		cout << "Before you hit the Jackpot, you also collected the following wins:\n"
			 << "Powerball only         -  " << winnings.powerball.alone << endl
			 << "Powerball + 1 match    -  " << winnings.powerball.match1 << endl
			 << "Powerball + 2 matches  -  " << winnings.powerball.match2 << endl
			 << "3 matches              -  " << winnings.match3 << endl
			 << "Powerball + 3 matches  -  " << winnings.powerball.match3 << endl
			 << "4 matches              -  " << winnings.match4 << endl
			 << "Powerball + 4 matches  -  " << winnings.powerball.match4 << endl
			 << "5 matches              -  " << winnings.match5 << endl
			 << endl;
	cout << "Press any key to return to the main menu.\n";
	_getch( );
	delete [] playerPicks;
}

short* makePicks(const short& size)
{
	short *temp = new short[size];
	bool repeat = false;	
	cout << "Pick your first 5 numbers.  Choices must be from 1-55, and may not repeat\n";
	for (short i = 0; i < LOTTO_SIZE;) // increment counter manually, later
	{
		if ((i == 5) && (! repeat))
		{
			cout << "Now, pick your powerball number.  Choice must be from 1-42, and may\n"
				 << "repeat any previous pick.\n";
			repeat = true;
		}
		cout << "Pick " << (i + 1) << ": ";
		cin >> temp[i];
		if (! cin)
		{
			cin.clear( );
			cin.ignore( );
			cout << "Invalid input.\n";
		}
		else
		{
			if (validate(i, temp))
				i++;
			else
				cout << "Pick " << (i + 1) << " conflicts with a previous choice or is invalid.\n";
		}
	}
	return temp;
}

short* makePicksRand(const short& size)
{
	short *temp = new short[size];
	for (short i = 0; i < LOTTO_SIZE;)		// will increment counter manually
	{
		if (i == 5)
			temp[i] = (rand( ) % 42) + 1;
		else
			temp[i] = (rand( ) % 55) + 1;
		if (validate(i, temp))
			i++;
	}
	return temp;
}

bool validate(const short& num, const short* picks)
{
	if (num == 5)	// when checking the last number (powerball)
	{
		if ((picks[num] < 1) || (picks[num] > 42))
			return false;
		else
			return true;
	}
	else			// checks all other numbers
	{
		if ((picks[num] > 55) || (picks[num] < 1))
			return false;
		else if (num > 0)
			for (short i = 0; i <= num; i++)
				if (picks[i] == picks[i + 1])
					return false;
		return true;
	}
}

bool checkWin(const short* player, const short* random, const bool& type)
{
	bool pbMatch = false;
	short matches = 0;
	for (short i = 0; i < LOTTO_SIZE; i++)
	{
		if (player[i] == random[i])
		{
			if (i == 5)
				pbMatch = true;
			else
				matches++;
		}
	}
	if (pbMatch)
		switch (matches)
		{
		case 0:		// 3
			cout << winTxt[6] << endl;
			if (type)
			{
				winnings.powerball.alone++;
				return false;
			}
			else 
				return true;
			break;
		case 1:		// 4
			cout << winTxt[5] << endl;
			if (type)
			{
				winnings.powerball.match1++;
				return false;
			}
			else
				return true;
			break;
		case 2:		// 7
			cout << winTxt[4] << endl;
			if (type)
			{
				winnings.powerball.match2++;
				return false;
			}
			else
				return true;
			break;
		case 3:		// 100
			cout << winTxt[3] << endl;
			if (type)
			{
				winnings.powerball.match3++;
				return false;
			}
			else
				return true;
			break;
		case 4:		// 10k
			cout << winTxt[2] << endl;
			if (type)
			{
				winnings.powerball.match4++;
				return false;
			}
			else
				return true;
			break;
		case 5:		// jackpot
			cout << winTxt[0] << endl;
			return true;
		}
	else
		switch (matches)
		{
		case 3:		// 7
			cout << winTxt[4] << endl;
			if (type)
			{
				winnings.match3++;
				return false;
			}
			else
				return true;
			break;
		case 4:		// 100
			cout << winTxt[3] << endl;
			if (type)
			{
				winnings.match4++;
				return false;
			}
			else
				return true;
			break;
		case 5:		// 200k
			cout << winTxt[1] << endl;
			if (type)
			{
				winnings.match5++;
				return false;
			}
			else
				return true;
			break;
		}
	return false;
}
 
Padma said:
But can have unintended consequences. ;)

(Caveat: I haven't looked at his code, so I don't know if it would be a problem here, or not.)
Only if the return value is actually used. for loop counters and such, ++i is slightly faster.

Unless the compiler optemises away the return value, in which case it does not matter.
 
Padma said:
But can have unintended consequences. ;)

(Caveat: I haven't looked at his code, so I don't know if it would be a problem here, or not.)
I know the difference between prefix and postfix increment and decrement ;)

I'll look through it and try those tonight, I don't think there will be problems anywhere.

It would be a lot faster just to keep generating 2 sets of numbers until they match up... ie. without any function calls

You could do this with a couple lines of code.

True, but that would make it a bit more difficult to check the validity of the numbers, and he wants to count the number of "wins" you tally before you hit the jackpot.

At the moment the program will run through 2312.4 "tries" per second on my comp (AthlonX2 4400), which isn't too bad IMO.
 
One unrelated problem you have is that you define LOTTO_SIZE as 6, but hardcode 5 to be one less then LOTTO_SIZE. If you were to change LOTTO_SIZE your program would not work right.

One other thing I suggest is instead of allocating and deleteing randPicks every cycle, make it a stack array defined outside your loop.

And my experience has been that generally the most time consuming process is actually printing to the console. Don't print anything durring the process.
 
One other thing I suggest is instead of allocating and deleteing randPicks every cycle, make it a stack array defined outside your loop.

Hmm, I did that originally because I remember reading somewhere that working with pointers was faster than arrays. In the context of having to allocate and delete every loop though, you're probably right.
 
Working with pointers is faster than indexing arrays, because everytime you index an array (Array), you are actually doing addition(*(Array+i)). Thus if you instead of of using indexing, you increment a pointer, it will be faster. Which by the way is another way to optimize your program.
 
Er, because I don't know assembler and don't feel like learning?

This is interesting though - going to prefix increments netted an 8% increase, to 2,514.9 loops/sec.

Then, though, I made it so that the console was updated every 15 seconds, which netted a 55,290% speed increase :eek: We're now at 1,390,497.6 loops/sec.
 
I'm actually more surprised about the 8% increase with prefix form.

So what are the actual odds of hitting the jackpot?

You can still try to use pointers instead of indexing arrays. For example by passing a pointer to validate(), instead of an array and index. This will mean that you need to determine the last element pointer runtime, another reason to make the randPicks Array static.
 
http://www.powerball.com/powerball/pb_prizes.asp

Code:
5 of 5 + Powerball:  Grand Prize 1 in 146,107,962.00 
5 of 5:                 $200,000 1 in 3,563,608.83 
4 of 5 + Powerball:      $10,000 1 in 584,431.85 
4 of 5:                     $100 1 in 14,254.44 
3 of 5 + Powerball:         $100 1 in 11,927.18 
3 of 5:                       $7 1 in 290.91 
2 of 5 + Powerball:           $7 1 in 745.45 
1 of 5 + Poweball:            $4 1 in 126.88 
Powerball only:               $3 1 in 68.96
 
This is where you start hating the RNG. In theory it should only take 150 million or so attempts to pick (jackpot) match, but I started the program last night before I went to bed and it's now at 45 billion tries, with no match.
 
Speedo said:
This is where you start hating the RNG. In theory it should only take 150 million or so attempts to pick (jackpot) match, but I started the program last night before I went to bed and it's now at 45 billion tries, with no match.

Play around with the RNG seeds
 
It's probably because there is a relitively small finite number of random numbers that is generated by windows for this purpose. I don't think there's much you can do. Changeing the seed will only change where the sequence starts, As far as I know.
 
Back
Top Bottom