Subscribe to Martyr2's Programming Underground        RSS Feed
-----

Slot Machine Example in C++

Icon 3 Comments
I was bored and that can be a dangerous thing. Like doodling on the phone book while you are talking on the phone, I doodle code while answering questions on DIC. Yeah, it means I have no life and yes it means I was born a coder. During this little doodle I decided to make a slot machine. But not your standard slot machine per say, but one designed a little bit more like the real thing. Sure it could have been done a little more simpler and not even using a Wheel class at all, but what fun is that? In this entry I show the creation of a slot machine from a bit more of a mechanical aspect than a purely computerized one. It should provide a small sampling of classes and how they can represent real life machines. We cover it all right here on the Programming Underground!

<Kris Allen (American Idol) singing "Apologize" but from the perspective of a n00b asking for help with a sloppy for loop>

So as I have already said, this little project was just something to play around with. It turned out kinda nice, so I thought I would share it. But what did I mean about it being mechanical in nature? Well, if you have ever played a real slot machine, not the digital ones they have in casinos now, you would see a metal case with a series of wheels. Typically it would be three wheels with pictures on them. When you put your money in and pull the handle the wheels would be set into motion. They would spin and then the first wheel would stop, followed by the second and then the third. After they have all stopped, the winnings are determined and you are paid out in coinage or credits.

I thought, why not be a bit mechanical in this slot machine design and create the wheels as a class called "Wheel" and give it the ability to spin independently of the other wheels? Have the wheel keep track of which picture (or in our case number) is flying by and report the results to the actual slot machine class. I could have done this mechanism without the need of a wheel at all and instead load up an array and have it randomly pick a number from the wheel. Little slimmer, little more efficient but wouldn't show much programming theory.

What do we gain by recreating these Wheel classes and spinning them independently? Well, you gain a slight bit of flexibility. Independently we are able to control the speed of the spinning if we wanted to, we are able to grasp the idea of the wheel as a concept in our mind and manipulate it. We could easily built in features like if the wheel lands on a certain number it will adjust itself. Like some slots in Vegas, if you land on lets say a rocket in the center line, the machine would see the rocket and correct the wheel to spin backwards 1 spot (in the direction of the rocket as if the rocket was controlling the wheel). We could spin one wheel one way and another wheel another. We could inherit from that wheel and create a specialized wheel that does a slew of new different behaviors. All encapsulated into one solid object making the actual Machine class oblivious to the trickery of the wheel itself... encapsulation at its finest!

The machine class we create will contain 3 pointers. Each to one of the wheels. The machine itself will be in charge of a few different tasks. Taking money, issuing and removing credits, determining when to spin, telling each of the wheels to spin and checking our winnings based on some chart we create. It has enough on its plate than worrying about the wheels and reading their values.

So lets start with our Wheel class and its declaration/implementation...

wheel.h

#include <time.h>
using namespace std;

class Wheel {
public:
	Wheel();
	void spin();
	int* read();

private:
	int slots[9];
	int internalPtr;
};


// Constructor
Wheel::Wheel() {
	srand((int)time(NULL));

	internalPtr = (rand() % 9);

	for (int i = 0; i < 9; i++) {
		slots[i] = i + 1;
	}
}


// Spin the internal pointer around spins times.
void Wheel::spin() {
	int spins = (rand() % 50) + 10;

	for (int i = 0; i < spins; i++) {
		internalPtr++;

		if (internalPtr > 8) { internalPtr = 0; }
	}
}


// Read the wheel for its values and return 3 values in an array
int* Wheel::read() {
	int prev = internalPtr - 1;
	int next = internalPtr + 1;

	if (prev < 0) { prev = 8; }
	if (next > 8) { next = 0; }

	int* values = new int[3];

	// Get values from wheel for pointer and before/after pointer.
	values[0] = slots[prev];
	values[1] = slots[internalPtr];
	values[2] = slots[next];

	return values;
}
	



As you can see the wheel itself is not a difficult concept to envision. The bulk of the work is in the read() method. Here we simply read the values from our internal array of integers (the values on the wheel) and return those values as an array of the three integers... representing the visible column. This column will then be loaded into our 2-Dimensional Array back in the Machine class. The 2D array represents the view or screen by which the user sees the results. Remember that the user never gets to see the entire wheel. Only the 3 consecutive values on the face of the wheel.

Here is how it may look in the real world. We have our machine with the three wheels and our 2D array called "Screen" which acts as our viewing window. Each wheel will report its values and those values will be put into the screen...

Posted Image

Below is our machine class...

machine.h

#include "wheel.h"

// Class declaration for our slot machine.
class Machine {
public:
	enum Image {
		orange = 1,
		watermelon,
		luckyseven,
		lemon,
		bar,
		doublebar,
		triplebar,
		cherry,
		plum
	};

	Machine();
	~Machine();
	void spin();
	void bet(int);
	void insertcoin();
	void insertbill(double);
	void printscreen();


private:
	void loadscreen(int, int*);
	void checkwinnings();
	int checkline(int line[3]);
	int credits;
	int betAmount;
	int screen[3][3];

	Wheel *wheels[3];
};


// Constructor
Machine::Machine() {
	credits = 0;
	betAmount = 0;

	wheels[0] = new Wheel();
	wheels[1] = new Wheel();
	wheels[2] = new Wheel();

	// Initialize screen
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			screen[i][j] = 0;
		}
	}
}


// Destructor
Machine::~Machine() {
	delete(wheels[0]);
	delete(wheels[1]);
	delete(wheels[2]);
}


// Spins the wheels and calls the loading of each wheel's results.
// Then calls to check for winnings.
void Machine::spin() {
	if (betAmount > 0) {
		wheels[0]->spin();
		wheels[1]->spin();
		wheels[2]->spin();

		int *column1 = wheels[0]->read();
		int *column2 = wheels[1]->read();
		int *column3 = wheels[2]->read();


		loadscreen(0, column1);
		loadscreen(1, column2);
		loadscreen(2, column3);

		printscreen();
		checkwinnings();

		betAmount = 0;


		delete [] column1;
		delete [] column2;
		delete [] column3;

		column1 = column2 = column3 = NULL;

	}
	else { cout << "Please make a bet before spinning." << endl; }
}


// Loads a wheel into the respective column of the screen
void Machine::loadscreen(int col, int *wheelcolumn) {
	for (int i = 0; i < 3; i++) {
		screen[i][col] = wheelcolumn[i];
	}
}


// Simply prints the screen
void Machine::printscreen() {
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 3; j++) {
			cout << screen[i][j] << " ";
		}
		cout << endl;
	}
}


// Bet function takes the number of lines the user wants to bet on (3 horizontal, 2 diagonals)
// and checks if they have enough credits to bet that much.
void Machine::bet(int lines) {
	if ((lines < 1) || (lines > 5)) {
		cout << "You may only bet between 1 and 5 lines. Please try again." << endl;
	}
	else if ((credits - lines) < 0) {
		cout << "You have " << credits << " credits available. Try adding more money." << endl;
	}
	else {
		betAmount = lines;
		credits -= betAmount;
	}
}


// Adds a credit
void Machine::insertcoin() {
	credits++;

	cout << "You now have " << credits << " credits." << endl;
}


// Adds multiple credits based on the bill denomination.
void Machine::insertbill(double billAmount) {
	if ((billAmount >= 1.00) && (billAmount <= 20.00)) {
		int newcredits = (int)(billAmount / .25);
		credits += newcredits;
		
		cout << "You now have " << credits << " credits." << endl;
	}
}


// Checks lines for wins and adds credits earned.
void Machine::checkwinnings() {
	int lineValues[3];
	int winnings = 0;

	// Check line middle line
	if (betAmount >= 1) {
		for (int i = 0; i < 3; i++) {
			lineValues[i] = screen[1][i];
		}

		winnings += checkline(lineValues);
	}

	// Check line top line
	if (betAmount >= 2) {
		for (int i = 0; i < 3; i++) {
			lineValues[i] = screen[0][i];
		}

		winnings += checkline(lineValues);
	}

	// Check bottom line
	if (betAmount >= 3) {
		for (int i = 0; i < 3; i++) {
			lineValues[i] = screen[2][i];
		}

		winnings += checkline(lineValues);
	}

	// Check left to right diagonal
	if (betAmount >= 4) {
		for (int i = 0; i < 3; i++) {
			lineValues[i] = screen[i][i];
		}

		winnings += checkline(lineValues);
	}

	// Check right to left diagonal
	if (betAmount == 5) {
		for (int i = 2; i >= 0; i--) {
			lineValues[i] = screen[i][i];
		}

		winnings += checkline(lineValues);
	}	

	if (winnings > 0) {
		cout << "You won " << winnings << " credits!" << endl;
		credits += winnings;
	}
	else {
		cout << "Sorry, you did not win anything." << endl;
	}

	cout << "You have " << credits << " credits left." << endl << endl;
}


// Determines the winning sequences. 
// 3 of a kind wins something
// Two cherries and something else wins as does two luckysevens and something else.
int Machine::checkline(int line[3]) {
	if (line[0] == this->luckyseven && line[1] == this->luckyseven && line[2] == this->luckyseven) { return 1000; }
	if (line[0] == this->watermelon && line[1] == this->watermelon && line[2] == this->watermelon) { return 800; }
	if (line[0] == this->triplebar && line[1] == this->triplebar && line[2] == this->triplebar) { return 600; }
	if (line[0] == this->doublebar && line[1] == this->doublebar && line[2] == this->doublebar) { return 500; }
	if (line[0] == this->bar && line[1] == this->bar && line[2] == this->bar) { return 400; }
	if (line[0] == this->cherry && line[1] == this->cherry && line[2] == this->cherry) { return 375; }
	if (line[0] == this->orange && line[1] == this->orange && line[2] == this->orange) { return 350; }
	if (line[0] == this->plum && line[1] == this->plum && line[2] == this->plum) { return 300; }
	if (line[0] == this->luckyseven && line[1] == this->luckyseven) { return 200; }
	if (line[0] == this->cherry && line[1] == this->cherry) { return 50; }
	if (line[0] == this->lemon && line[1] == this->lemon && line[2] == this->lemon) { return 5; }


	return 0;
}



This looks like a lot of code but really it is not if you look at each function. Most of them are very very simple to understand. We have a spin method which essentially spins each of the wheels, reads their values back from the Wheel class into a pointer (representing each column), then they are loaded into the 2D array one column at a time (our view screen), printed for the user to see the results and lastly the winnings are checked. The checkwinnings() method determines which rows to check based on the amount of the bet. If they chose 1 line, it checks for winning combinations on the middle row only. If they choose 2 lines, it checks the middle and top lines, 3 line bet checks all three horizontal rows, 4 line bet checks the first diagonal as well and 5 line bet checks both diagonals in addition to the lines.

How does it check the lines? Well each line is given to the checkline() helper function which compares the 3 values of the line against an enumerated type of various symbols. Here we are just assigning a symbol against each numbered value to help the programmer determine which numbers correspond to which winning combos. For instance, luckyseven represents the number 3 in the enumeration. So if it runs across a line with 3 number 3s, then it knows it hit the grand jackpot and credits the player 1000. This method makes things easy because if we ever wanted to change the win patterns later, we could change the enum and checkline method to do so. We could also build in multiple types of symbols and even let the user choose what slot machine game they want to go by. It becomes very flexible and is a testament to great design!

Lastly we can put some tests together just to show some the various aspects of how this thing works and how the programmer can use the classes...

slotmachine.cpp

#include <iostream>
#include "machine.h"
using namespace std;

int main() {

	// Create our slot machine
	Machine *slotmachine = new Machine();

	// Insert a five
	slotmachine->insertbill(5.00);

	// Plus a coin for good luck
	slotmachine->insertcoin();

	// Go for it all and bet five lines
	// Then spin!
	slotmachine->bet(5);
	slotmachine->spin();

	// Win or lose, lets bet five lines again
	// Then spin!
	slotmachine->bet(5);
	slotmachine->spin();


	// Ok, we had enough of the slot machine.
	delete(slotmachine);

	return 0;
}



This simply inserts a 5 dollar bill and a coin for good luck. Then bets 5 lines and spins. Despite the outcome we go and bet five lines again and spin once more. Hopefully we win something this time around! But either way, those are the classes for you and I hope you like them. As always, all code here on the Programming Underground is in the public domain and free for the taking (just don't cause a mess in isle 3, I am tired of running out there for cleanup). Thanks for stopping by and reading my blog. :)

If you want more blog entries like this, check out the official blog over on The Coders Lexicon. There you will find more code, more guides and more resources for programmers of all skill levels!

3 Comments On This Entry

Page 1 of 1

paperclipmuffin Icon

04 October 2009 - 03:20 AM
Wow, cool!
0

Lillefix Icon

11 October 2009 - 01:46 PM
Nice work, I was just wondering about one thing, this part:
// Check left to right diagonal
	if (betAmount >= 4) {
		for (int i = 0; i < 3; i++) {
			lineValues[i] = screen[i][i];
		}

		winnings += checkline(lineValues);
	}

	// Check right to left diagonal
	if (betAmount == 5) {
		for (int i = 2; i >= 0; i--) {
			lineValues[i] = screen[i][i];
		}

		winnings += checkline(lineValues);
	}


Will first check the line from top-left to bottom-right and then the line from bottom-right to top-left.
Wouldn't it make more sense to first check top-left to bottom-right and then bottom-left to top-right given that
all the current winning will not differ if the line is read forwards or backwards?

You could for example do this by changing the second
lineValues[i] = screen[i][i]; to lineValues[i] = screen[i][2-i];
0

Martyr2 Icon

11 October 2009 - 04:12 PM
Sure, I mean you could check them in any order or sequence you like. It really is a preference thing. The real trick here is to load a single row for comparison. Heck if I wanted to check 10 lines (all winning lines and their reverse) I could with little modification. Just the introduction of alternate line checking loops. The way I did it was just a quick example. But you are right, I mean you could check whatever lines in whatever order you wanted really. :)
0
Page 1 of 1

August 2014

S M T W T F S
     12
3456789
10111213141516
17181920212223
242526272829 30
31