Problem Solving: Rock, Paper, Scissors (revisited)
Learn to make a rock, paper, scissors program.
We'll cover the following
Rock, paper, scissors tournament
In the previous chapter, we created a program for this game without using functions. We’ll implement it using functions in this lesson to show the difference between the two implementations. Ultimately, we’ll see how neat and efficient the code becomes with functions!
Previous implementation
In this game, two players individually select “rock,” “paper,” or “scissors” by typing r
for rock, p
for paper, or s
for scissors. The winner is the one whose choice dominates the other. We won’t discuss the game’s rules because we already saw them in this lesson.
The program that we wrote initially in our previous lesson is given below. We have to refresh it.
#include <stdio.h> #include <termios.h> #include <unistd.h> int getch ( void ) { int ch; struct termios oldt, newt; tcgetattr ( STDIN_FILENO, &oldt ); newt.c_cc[VMIN] = 1; newt.c_cc[VTIME] = 0; // newt = oldt; // newt.c_lflag &= ~( ICANON | ECHO ); tcsetattr ( STDIN_FILENO, TCSANOW, &newt ); ch = getchar(); tcsetattr ( STDIN_FILENO, TCSANOW, &oldt ); return ch; }
Look at the above implementation and think about the possible functions we need to make and the parameters needed for those functions.
Implementation using functions
So let’s start with the implementation of the rock, paper, scissors game using functions.
We first decide which modules we’ll need to solve this problem. First, we want a module that calculates who wins the game. It should take the inputs entered by the user, perform calculations/comparisons, and return an integer representing which player won (1
or 2
) or if the game is drawn (0
in that case). So, we create a function called whoWins()
which we can call in our main()
function and write its prototype before main()
starts.
Second, we need a function to display which player won or if the game was drawn. So, we create a function called gameMsg()
. Since this function does not need to return anything and just needs to print, its return type would be void
. We’ll pass it an int
input parameter, based on which it will print the corresponding message. We add its prototype before main()
and then call it within the main()
function.
//function prototypesint whoWins(char sym1, char sym2);void gameMsg(int M);int main(){char sym1;char sym2;cout << "(R)ock, (P)aper, or (S)cissors:" << endl;sym1 = getch(); // getch() is a built in function (provided in conio.h)cout << "*" << endl;sym2 = getch(); // it is a built in function (provided in conio.h)cout << "*" << endl;//calling the functionint winner = whoWins(sym1, sym2);gameMsg(winner);return 0;}
Note that getch()
is another function (module) that performs a specific task for us. With getch()
, when the user enters any character, the control returns to the program without displaying anything on the console, and the function call is replaced by the entered character.
In the code snippet above, we write the following:
int winner = whoWins(sym1, sym2);
gameMsg(winner);
Can we rewrite the above lines to the lines given below?
int winner = whoWins(sym1, sym2);
int message = gameMsg(winner);
Yes
No
Now, let’s write the implementation of the whoWins
function.
int whoWins(char sym1, char sym2){/*return 0 - if drawreturn 1 - if P1 winsreturn 2 - if P2 wins*/if (sym1 == sym2){return 0; //exits the function}//player 1 winsif ((sym1 == 'p' && sym2 == 'r') ||(sym1 == 'r' && sym2 == 's') ||(sym1 == 's' && sym2 == 'p')){return 1;}//player 2 winsif((sym2 == 'p' && sym2 == 'r') ||(sym2 == 'r' && sym2 == 's') ||(sym2 == 's' && sym2 == 'p')){return 2;}}
We create a whoWins()
function that deals with the three cases of either player winning or the game being drawn. As we can see, the return type of whoWins()
is int
because it returns 0
, 1
, or 2
if the game is drawn, or player 1
or player 2
wins respectively.
We passed two char
input parameters (the symbols which the players choose) into the function.
We first check whether the game is drawn. If it is, we exit the function. We use the if
conditions for both player 1
and player 2
to see which one wins.
Though, if we notice, we actually don’t need to use if
conditions for player 2
. As our code runs sequentially and the two conditions (game drawn or player 1
winning) have already been checked, we know the third possibility is only of player 2
winning. So, we can write our code like this:
int whoWins(char sym1, char sym2){/*return 0 - if drawreturn 1 - if P1 winsreturn 2 - if P2 wins*/if (sym1 == sym2){return 0; //exits the function}//player 1 winsif ((sym1 == 'p' && sym2 == 'r') ||(sym1 == 'r' && sym2 == 's') ||(sym1 == 's' && sym2 == 'p')){return 1; // exits the function}//player 2 winsreturn 2; // exits the function}}
We now need to create a function, gameMsg()
, that displays the output message. Let’s use switch
statements to print messages.
void gameMsg(int m){switch(m){case 0:cout << "The game is drawn" << endl;break;case 1:cout << "Player 1 wins" << endl;break;case 2:cout << "Player 2 wins" << endl;break;}}
The whoWins()
function computes who the winner is and returns an integer back to int main()
and saves to an integer winner
. The variable winner
is then passed to gameMsg()
which uses switch
conditions to display who the winner is depending on the integer passed to it. As this function does not return any value, the control is returned back to int main()
after displaying the outcome. Upon reaching return 0
, the program is exited.
Playground for your implementation
We are done with our program. Now rewrite (or at least copy) the code inside the playground below and click “Run”!
Instruction: Add the respective code in the playground below and execute the code step-by-step to see how it is executed.
#include <stdio.h> #include <termios.h> #include <unistd.h> int getch ( void ) { int ch; struct termios oldt, newt; tcgetattr ( STDIN_FILENO, &oldt ); newt.c_cc[VMIN] = 1; newt.c_cc[VTIME] = 0; // newt = oldt; // newt.c_lflag &= ~( ICANON | ECHO ); tcsetattr ( STDIN_FILENO, TCSANOW, &newt ); ch = getchar(); tcsetattr ( STDIN_FILENO, TCSANOW, &oldt ); return ch; }
Task 1: Refining the implementation using functions
There are some cases that we still haven’t handled in our code above. We need to write a function for when the player enters invalid inputs. We also need to change the above code so that the two players can play the game multiple times in a series.
We need to write a function for when the player enters the invalid inputs. Also, what if the player enters R
, S
, or P
instead of small r
, s
, or p
? We need our program to accept both small and capital valid alphabets.
So, let’s make a function called isValidSym()
with a bool
return type and a char
input parameter.
bool isValidSym(char sym){if(sym == 'r' || sym == 'R' || sym == 'p' || sym == 'P' || sym == 's' || sym == 'S')return true;return false;}
The above can also be written as follows:
bool isValidSym(char sym){return (sym == 'r' || sym == 'R' || sym == 'p' || sym == 'P' || sym == 's' || sym == 'S');}
Depending on what the sym
is (whether it is true with R
, S
, P
, r
, s
, or p
, or false with some other alphabet), the return
statement simply returns true
or false
.
To ensure our program takes both small and capital valid alphabets, we have created the isCapital()
function that returns true
when the parameter passed is capital.
bool isCapital(char sym){return (sym >= 'A' && sym <= 'Z');}
We call the isCapital()
function in another function, toLower()
, to convert the capital letter to a small letter.
char toLower(char sym){if (isCapital(sym))sym = sym + 32;return sym;}
Let’s call the isValidSym()
function inside a do-while
loop for both sym1
and sym2
to ensure that the input entered is valid and the program is not executed until both users have entered the valid inputs.
Look at the highlighted lines below to see what and where we have updated the code.
..// write function prototypes here// Add appropriate prototypes hereint main(){char sym1;char sym2;cout << "(R)ock, (P)aper, or (S)cissors:" << endl;do{sym1 = getch();}while (isValidSym(sym1) == false);// taking the first player symbol repeatedly until, correct symbol is selectedcout << "*" << endl; // displaying the * instead of the symboldo{sym2 = getch();}while (isValidSym(sym2) == false);// taking the second player symbol repeatedly until, correct symbol is selectedcout << "*" << endl; // displaying the * instead of the symbolsym1 = toLower(sym1);sym2 = toLower(sym2);//call the functions hereint winner = whoWins(sym1, sym2);gameMsg(winner);return 0;}...bool isValidSym(char sym){return (sym == 'r' || sym == 'R' || sym == 'p' || sym == 'P' || sym == 's' || sym == 'S');}bool isCapital(char sym){return (sym >= 'A' && sym <= 'Z');}char toLower(char sym){if (isCapital(sym))sym = sym + 32;return sym;}
Instruction: Add the relevant code in the playground above.
Task 2: Converting the game to a tournament
We can ask the users to choose the number of games they want to play and, at the end, we can display how many times each player won.
For this, we need a choice
variable that can be used to ask if players want to continue or stop the series and also a do-while
loop, which operates on this choice
variable.
...int main(){char sym1;char sym2;char choice;do{cout << "(R)ock, (P)aper, or (S)cissors:" << endl;...cout << "press (c)ontinue or (s)top \n";cin >> choice;}while(choice=='c');return 0;}...
We have used a do-while
loop which operates till the user wants to continue the series by entering c
. If the user enters the choice c
, then the program continues; otherwise, the program stops and terminates.
Next, to display how many times each player won, we modify the code as follows:
...int main(){char sym1;char sym2;int player1_win_count=0;int player2_win_count=0;char choice;do{cout << "(R)ock, (P)aper, or (S)cissors:" << endl;...//call the functions hereint winner = whoWins(char sym1, char sym2);if(winner==1)player1_win_count++;if(winner==2)player2_win_count++;gameMsg(winner);cout << "press (c)ontinue or (s)top \n";cin >> choice;}while(choice=='c');if (player1_win_count>player2_win_count){cout<<"Final Result \n";cout << "Congratulations! Player one won with " << player1_win_count << " games." << endl;}else if (player1_win_count<player2_win_count){cout<<"\nFinal Result \n";cout << "Congratulations! Player two won with " << player2_win_count << " games." << endl;}else{cout << "The game is drawn." << endl;}return 0;}...
Instruction: Add this enhancement of the tournament in your playground above.
Exercise
Add the following three instructions in your implementation playground above:
-
Change the code above so that, instead of converting
sym1
andsym2
to small alphabets using thetoLower()
function, we can convert them to capital alphabets by replacing thetoLower()
with thetoUpper()
function. -
Note that taking input for each user and displaying
'*'
as a player’s move is done twice, along with its validity checking. Convert it into a function and call it in yourmain()
function. -
Displaying the tournament winner should be done in a separate function, which needs to be called after the tournament
do-while()
loop.
Complete implementation of rock, paper, scissors (tournament version)
We recommend that you look at the solution below only after you have implemented the complete code on your own.
Our final program looks like this:
#include <stdio.h> #include <termios.h> #include <unistd.h> int getch ( void ) { int ch; struct termios oldt, newt; tcgetattr ( STDIN_FILENO, &oldt ); newt.c_cc[VMIN] = 1; newt.c_cc[VTIME] = 0; // newt = oldt; // newt.c_lflag &= ~( ICANON | ECHO ); tcsetattr ( STDIN_FILENO, TCSANOW, &newt ); ch = getchar(); tcsetattr ( STDIN_FILENO, TCSANOW, &oldt ); return ch; }