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;
}
The rock, paper, scissors game

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.

Press + to interact
//function prototypes
int 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 function
int 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.

Q

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);
A)

Yes

B)

No

Now, let’s write the implementation of the whoWins function.

Press + to interact
int whoWins(char sym1, char sym2)
{
/*
return 0 - if draw
return 1 - if P1 wins
return 2 - if P2 wins
*/
if (sym1 == sym2)
{
return 0; //exits the function
}
//player 1 wins
if ((sym1 == 'p' && sym2 == 'r') ||
(sym1 == 'r' && sym2 == 's') ||
(sym1 == 's' && sym2 == 'p'))
{
return 1;
}
//player 2 wins
if((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:

Press + to interact
int whoWins(char sym1, char sym2)
{
/*
return 0 - if draw
return 1 - if P1 wins
return 2 - if P2 wins
*/
if (sym1 == sym2)
{
return 0; //exits the function
}
//player 1 wins
if ((sym1 == 'p' && sym2 == 'r') ||
(sym1 == 'r' && sym2 == 's') ||
(sym1 == 's' && sym2 == 'p'))
{
return 1; // exits the function
}
//player 2 wins
return 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.

Press + to interact
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;
}
The rock, paper, scissors game


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.

Press + to interact
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:

Press + to interact
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.

Press + to interact
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.

Press + to interact
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.

Press + to interact
.
.
// write function prototypes here
// Add appropriate prototypes here
int 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 selected
cout << "*" << endl; // displaying the * instead of the symbol
do
{
sym2 = getch();
}
while (isValidSym(sym2) == false);
// taking the second player symbol repeatedly until, correct symbol is selected
cout << "*" << endl; // displaying the * instead of the symbol
sym1 = toLower(sym1);
sym2 = toLower(sym2);
//call the functions here
int 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.

Press + to interact
...
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:

Press + to interact
...
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 here
int 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:

  1. Change the code above so that, instead of converting sym1 and sym2 to small alphabets using the toLower() function, we can convert them to capital alphabets by replacing the toLower() with the toUpper() function.

  2. 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 your main() function.

  3. 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;
}
Complete implementation of the rock, paper, scissors game