Rock Paper Scissors

From CompSciWiki
Revision as of 15:35, 8 December 2011 by RyanV (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Back to the Program-A-Day homepage

Problem

Rock-Paper-Scissors is a game with two players with one in three chance of winning. The outcome of each round will result in the player to either win, lose, or draw. In this example, we're going to make a simulation of the game, Rock-Paper-Scissors, to play against the computer. During the execution of the program, the player may request a summary that prints the current score record. The score record includes the number of wins, loses, and draws, as well as the winning average, (in percentage to one decimal place) and the player's highest winning streak.

 

...By Students

"Whenever you are having trouble planning or solving a problem, writing a program, debugging a program, or just analyzing code, write/draw it out on paper. Step through each line of code so you know what you want the program to do, or what the code is doing. When you refer back to the code in your program, use print statements and test your variables to make sure you're getting the same results or the results that you should be getting. When you write or draw each step on paper (ex: arrays), it will give you a visual aid of what your program is doing. In the case you are fixing errors, you can compare and match your program with your diagrams to pinpoint where the errors occur."

Solution

Before we start programming, the first thing to do when solving a problem is understanding the problem, breaking it down, and planning out how to solve the smaller problems (divide and conquer).

Let's start off by going over the rules in Rock Paper Scissors. In Rock Paper Scissors, a player plays against an opponent where both of the players select either rock, paper, or scissors as their weapon. At the same time, both of the players draw their selected weapon. The result of each round will result in win, lose, or draw depending on the player and their opponent's selected weapon. Rock beats scissors, scissors beats paper, and paper beats rock.

Now that we understand how the game is played, we can start breaking the problem down and figure out how problems should be. In rock paper scissors there are two players, so in our program we need two variables (player and computer) to hold their chosen weapon. The player's choice will be obtained through the user's input and the opponent will be determined by the computer. Because we want the computer to generate a unique choice for each round, we will use a random number generator. Each number represents either rock, paper, or scissors. As you may have noticed, there are only three different choices so our number generator must generate one of three numbers.

 (int)(Math.random() * 3) 

Math.random() will generate a number between 0 and less than 1, if we multiply it by 3 we will get either 0, 1, or 2 values before the decimal. We can use 0, 1, and 2 to represent rock, paper, and scissors. We don't need the decimal value so we will cast it as an int and store it in an int variable to cut the decimal values off. This tells us that the variable computer should be an int type variable (as well as the variable player).


To make things easier to understand, use int variables to represent each of the three weapons by naming the variables with an assigned unique value.

 final int ROCK = 0;
final int PAPER = 1;
final int SCISSORS = 2; 

ROCK will be represented by 0, PAPER will be represented by 1, and SCISSORS will be represented by 2. Note that these constants should never change at any time during our program so set them to be final.

Because we want to be able to keep track of the number of wins, loses, draws, average, and win streak, the easiest way to do this is to use an array to store the result of each round. The easiest way to do this is using the same number representation method we used earlier to represent the results of each round, 1 for win, 2 for lose, and 3 for draw. So we'll use an int array rounds[] to store the result of each round. Keep in mind that when we use an array, we need to initialize a maximum size before we can use it. Pick a big random number as the array size (ex: 1000).

 final int MAX = 1000;

int [] rounds = new int [MAX]; 


Since this game is played with one or more rounds, it is a good idea to group the algorithm needed to process each round into a method so we can just simply call that function whenever a round is to be executed. Create the method in such a way that it will return the result of each round so we can store it in the rounds[] array. Win will be represented by the value 1, lose will be represent by the value 2, and draw will be represented by the value 3. So in other words, each time a player agrees to play another round, we will simply call the method playRound() that returns an int value to be stored in rounds[] array.

In general, you should have a visual idea that looks like,

 main method {

	loop (to get input from user) {
		if the player agrees to play another round {

			result[current round] = call playRound(); 

		}
	}
}

playRound() method {
	calculate win, lose, or tie;

	return an int value that represents win, lose, or draw (1,2, or 3 respectively);
} 

Now that we have the basic layout of our program, we can start filling in the details.


To get input from the user, we'll use JOptionPane's input dialog,

 input = JOptionPane.showInputDialog(null,"Message for the user goes here."); 

Since we want the user to play multiple rounds, we'll use a loop to continue getting input from the user. There are two cases that we have to consider for the loop to be true.

Case 1: Since we have an array storing the result of each round, we have a limit of the number of rounds there can be during each execution of the program (the size of the array). We can't store results exceeding the size of the array. The easiest way to do this is using a counter to keep track of each round and making sure the count does not exceed the max size of the array.

Case 2: If the number of rounds is represented by max size of the array, that means the user MUST play that many times. If the number is too small, the game will end too early when the user wants to keep playing. If the number is too big, the user will get frustrated, bored, and want the game to end. To solve this, we'll give the user the choice to quit the game at any time.

Since we are taking input from the user, we will let the user quit the game by entering the command "quit". The easiest way to do this is using a while loop and loop when the expression is true. When the user enters the command "quit", set the boolean expression to false to exit the loop.


Taking into consideration of case 1 and case 2, your statements should look like this,

 final int MAX = 1000;

String input;
int count = 0;
int [] rounds = new int [MAX];
boolean loop = true;

	:

while((count < MAX) && (loop)) {
	input = JOptionPane.showInputDialog(null,"Message for the user goes here.");

	if((input == null) || (input.equalsIgnoreCase("quit"))) {
		loop = false;
	}

		:
} 


By default, we'll initialize count = 0 so we can just use the count++ increment statement, and initialize boolean loop = true so the while loop will loop. The count variable will keep track of each round. The loop will loop as long as count is less than MAX (the size of our array), AND as long as the boolean loop is true. If the user enters "quit", the loop will terminate.


Note Note: Users may enter commands in upper case, lower case, or a mix of both. Use string function equalsIgnoreCase() to check the user's input regardless of their capitalization.

Note Note: JOptionPane's show input dialog function, there is an OK and CANCEL button. If the user clicks CANCEL, allow them to quit the program. We handle this matter similar to if the user enters the "quit" command, (if(input == null)).


Our next goal is to complete our if statement by implementing the different commands the user can enter. In general, the user can enter "Rock", "Paper", Scissors", "Summary", or "Quit" as valid commands.

If the player enters either "Rock", "Paper", or Scissors", then we want to process the game's round result. If a player enters "Rock", we want to set the variable player to the value that ROCK represents. Remember that the variable player is an int type, and we have ROCK, PAPER, SCISSORS represented by int values. This will allow us to assign player variable with the value representing ROCK, PAPER, or SCISSORS.

For example, if player enters "rock", player variable will hold the value 0.

 final int ROCK = 0;
final int PAPER = 1;
final int SCISSORS = 2;

	:

	:

if(input.equalsIgnoreCase("rock")) {
	player = ROCK;
		:

} 


Since the user has selected to play a round of the game, call the playRound() method to process the game. Because we have a legend of what ROCK, PAPER, and SCISSORS represent, we will pass these values into the method as well. The parameters are the variables player, ROCK, PAPER, and SCISSORS.The return value will be stored in rounds[] in the index position of count (the number of the current round).

 if(input.equalsIgnoreCase("rock")) {
	player = ROCK;
	rounds[count] = playRound(player,ROCK,PAPER,SCISSORS);
	count++;
} 


Remember to increment count by 1 so that the next round will not overwrite your previous values.

Repeat this step for user's input of "paper" or "scissors".

Once your done, piece it together with the other statements and you should have something like the following.

 if((input == null) || (input.equalsIgnoreCase("quit"))) {
	loop = false;
}
else if(input.equalsIgnoreCase("rock")) {
	player = ROCK;
	rounds[count] = playRound(player,ROCK,PAPER,SCISSORS);
	count++;
}
else if (input.equalsIgnoreCase("paper")) {
	player = PAPER;
	rounds[count] = playRound(player,ROCK,PAPER,SCISSORS);
	count++;
}
else if (input.equalsIgnoreCase("scissors")) {
	player = SCISSORS;
	rounds[count] = playRound(player,ROCK,PAPER,SCISSORS);
	count++;
}
else if (input.equalsIgnoreCase("summary")) {
	
	//do something for summary here.
		:
}
else {
	System.out.println("Invalid input.");
} 


When a user enters a command, it will either be "rock", "paper", "scissors", "summary, "quit", click on CANCEL, or enter something else. when the user enters something aside from these commands, inform the user of an invalid input.
Note Note: we only increment count whenever we process playRound() and get a result, so we have count++ only if players enter "rock", "paper", or "scissors". If the user enters anything other than these three choices, do nothing to count.

Side note: Although we have numbers representing rock, paper, and scissors to be used in our algorithm, we should keep the program's communication level with the user at the English language level.


Next we will implement our playRound() method, so we will come back to "summary" later. Complete the sections of code that you know for sure what you want the program to do and the related code to avoid confusion (and in case you forget to come back to it later).

Create your playRound() method. The parameters are int player, int ROCK, int PAPER, and int SCISSORS. The return value will be an int value which will either be 1 = win, 2 = lose, or 3 = draw. We will need an int variable that holds the result of the round to be returned (ex: int result).

As you may have noticed, we have all the elements we need to complete a round of rock paper scissors except for one, the computer’s selected weapon. We will generate the computer’s selection in the playRound() method because we are only using it in this method.

So far, your method should look something like this,

 public static int playRound(int player, int ROCK, int PAPER, int SCISSORS) {
	int computer;
	int result = 0;

	computer = (int)(Math.random() * 3);

		:
} 


The next thing we need to do is to determine if the player wins, loses, or ties, and assign the appropriate value to the result variable. Compare the player’s choice with the computer’s choice by using an if statement. For example, if player chooses ROCK and the computer chooses SCISSORS, then the player wins. We then assign the value 1 to the result variable (reminder, win = 1, lose = 2, and draw = 3).

Do this for all the possible outcomes (take some time to think about this and write it down).

Once you're done, your if statement should look something like this.

 if((player == ROCK) && (computer == SCISSORS)) {
	result = 1;
}
else if((player == ROCK) && (computer == PAPER)) {
	result = 2;
}
else if((player == PAPER) && (computer == ROCK)) {
	result = 1;
}
else if ((player == PAPER) && (computer == SCISSORS)) {
	result = 2;
}
else if ((player == SCISSORS) && (computer == PAPER)) {
	result = 1;
}
else if ((player == SCISSORS) && (computer == ROCK)) {
	result = 2;
}
else if (player == computer) {
	result = 3;
} 


There are nine possible comparisons and outcomes, but if you notice, three of the nine comparisons is ROCK == ROCK, PAPER == PAPER, and SCISSORS == SCISSORS. In this case, we can simplify it down to if player’s choice is equal to computer’s choice, then it’s the same. You should also add a print statements to notify the user what the outcome of the round is.

Once your if statement is complete, we have processed the necessary data that this method is intended to do. All we need to do is returning the value. Add your return statement, and then, your method is complete.

Your method should look something like this,

 public static int playRound(int player, int ROCK, int PAPER, int SCISSORS) {
	int computer;
	int result = 0;

	computer = (int)(Math.random() * 3);

	if((player == ROCK) && (computer == SCISSORS)) {
		result = 1;
		System.out.println("Computer chose scissors. You are victorious!");
	}
	else if((player == ROCK) && (computer == PAPER)) {
		result = 2;
		System.out.println("Computer chose paper. You have been defeated.");
	}
	else if((player == PAPER) && (computer == ROCK)) {
		result = 1;
		System.out.println("Computer chose rock. You are victorious!");
	}
	else if ((player == PAPER) && (computer == SCISSORS)) {
		result = 2;
		System.out.println("Computer chose scissors. You have been defeated.");
	}
	else if ((player == SCISSORS) && (computer == PAPER)) {
		result = 1;
		System.out.println("Computer chose paper. You are victorious!");
	}
	else if ((player == SCISSORS) && (computer == ROCK)) {
		result = 2;
		System.out.println("Computer chose rock. You have been defeated.");
	}
	else if (player == computer) {
		result = 3;
		System.out.println("Computer chose the same. It's a draw!");
	}

	return result;

} 


Now that your playRound() method is complete, your program now takes in input from the user, plays a round (or multiple rounds) of rock paper scissors, and stores the results of each round in an array.

Now all we have to do is creating the summary method. If the user enters "summary", then we want to display the user's current score record where we print out the number of wins, loses, draws, winning average, and win streak. This will be based on the array of results stored in the rounds[] array. If you notice, the summary method can be called at any time, and whenever it is called we don’t know how many elements are stored on the array so far. However, the count variable keeps track of how many rounds that have been played. In conclusion, we will need to pass the rounds[] array and the count variable to the summary method.

Go back to your main method and finish the statement for if the user enters the “summary” command by adding the method call statement. It should look something like this.

 :
else if (input.equalsIgnoreCase("summary")) {
		summary(rounds,count);
}
		: 

Now create the summary method itself. The parameters are int [] rounds and int count, and there are no return values. The first thing we want to do is determining the number of wins, loses, and draws where we will declare three new variables to hold these counts. (ex: int wins, int loses, and int draws). Next, we need to go through the array of results, and increment the count of the appropriate variables depending on the value stored (ex: if the result of the 10th round is 1, then increment the variable wins by 1).

The method so far should look something like this.

 int wins = 0;
int loses = 0;
int draws = 0;

for(int i = 0; i < count; i++) {
	if(rounds[i] == 1) {
		wins++;
	}
	else if(rounds[i] == 2) {
		loses++;
	}
	else if(rounds[i] == 3) {
		draws++;
	}
} 


Make sure you set your for loop’s expression to loop true when i < count because the current count value will be pointing to an empty spot in the array (where a new result will be stored). You should also initialize the three count variables to 0 so that when we loop through the array, we can simply increment the variables by 1 (ex: wins++).


The next thing we need to do is calculating the winning average, and creating a variable that stores the winning average. Just so that our summary method doesn’t get messy, we’ll create a new method (ex: getAverage()) to do the calculations and return the result to be stored in the winning average variable.

The winning average is calculated as the number of wins divided by the number of rounds played multiplied by 100. So the parameters are int wins and int count and the return value will be the calculated average. Note that the calculated average will be in decimals, so the return type variable and the variable that stores winning average should be double type variable. Also, create an average variable inside the getAverage method to hold your calculations.

Your method so far should look something like this.

 public static double getAverage(int wins, int count) {
	double average;
		:
} 


Now enter your formula average formula. Note that our values are integers and we’re storing the result in double. To solve this, cast your calculations with integers as doubles. It should look something like this.

 average = (double) wins / count;
average = average * 100; 


This will give us a value with two digits, and a bunch of digits after the decimal. We only want one decimal place, the easiest way to cut off the extra decimals is storing the double as an integer. But, we want 1 decimal place and we don’t want to cut all of it off. To solve this, multiply it by 10, cast it as an int and store it in a temporary integer variable. Then, convert it back into a double and divide it by 10.

Your average calculation should look something like this (where temp is an int variable).

 temp = (int) (average * 10);
average = (double) temp / 10; 

For example, if you had wins = 1 and count = 3.

 int wins = 1;
int count = 3;

average = (double) wins / count; //average = 0.3333333
average = average * 100; // average = 33.33333
temp = (int) (average * 10); // temp = 333
average = (double) temp / 10; // average = 33.3 


Now that our calculation is complete, return the average variable back to our previous method. Your method should look something like this,

 public static double getAverage(int wins, int count) {
	double average;
	int temp;

	average = (double) wins / count;
	average = average * 100;
	temp = (int) (average * 10);
	average = (double) temp / 10;

	return average;
} 


Go back to your summary method. The next thing we have to is calculating the best winning streak. We’ll create a new method (ex: winningStreak()) to do the calculations. To calculate the winning streak, we’ll need to go the array again. So the winningStreak method will have the parameters int [] rounds, int count, and the return value is an int. Make sure you create a variable to hold the returned value (ex: int winStreak).

Create your winningStreak method. To calculate the best winning streak, we need two variables (ex: int streak and int winStreak). The streak variable will keep count of the number of wins in a row. If the result is either a lose or draw then we reset streak to 0. You will want to initialize streak variable to 0 so that we can just increment the count as we go through the array. The winStreak variable will hold the largest number of wins in a row. Going further ahead, you will also want to initialize this variable to 0 (you will see why we do this later).

Your method so far should look something like this.

 public static int winningStreak(int [] rounds, int count) {
	int streak = 0;
	int winStreak = 0;
		:
} 


Next, write your for loop to loop through the array and find the results that are equal to wins. For every win that is found in the array, increment the streak count. If a lose or draw is found, reset the streak to 0. Make sure you have a separate if statement that will store the win streak whenever streak is larger than winStreak, then store the streak’s value into winStreak variable. This is why we initialize winStreak = 0 at the beginning, so winStreak has a default value to compare with.

Your algorithm should look something like this,

 for(int i = 0; i < count; i++) {
	if(rounds[i] == 1) {
		streak++;
	}
	else {
		streak = 0;
	}

	if(streak > winStreak) {
		winStreak = streak;
	}
} 


Now that your algorithm is complete, add your return statement to return winStreak (the variable that holds the largest number of consecutive wins). Your method should look something like this.

 public static int winningStreak(int [] rounds, int count) {
	int streak = 0;
	int winStreak = 0;

	for(int i = 0; i < count; i++) {
		if(rounds[i] == 1) {
			streak++;
		}
		else {
			streak = 0;
		}

		if(streak > winStreak) {
			winStreak = streak;
		}
	}

	return winStreak;
} 


Now that your done calculating the winning streak, go back to your summary method. The last thing you need to do is print out your results in the summary (the number of wins, loses, and etc) and you’re done!

Your summary method should look something like this.

 public static void summary(int [] rounds, int count) {
	int wins = 0;
	int loses = 0;
	int draws = 0;
	double average;
	int winStreak;

	for(int i = 0; i < count; i++) {
		if(rounds[i] == 1) {
			wins++;
		}
		else if(rounds[i] == 2) {
			loses++;
		}
		else if(rounds[i] == 3) {
			draws++;
		}
	}

	average = getAverage(wins,count);
	winStreak = winningStreak(rounds,count);

	System.out.println("===Summary===");
	System.out.println("");
	System.out.println("Total wins: " + wins);
	System.out.println("Total losses: " + loses);
	System.out.println("Total draws: " + draws);
	System.out.println("Winning Average: " + average + "%");
	System.out.println("Win Streak: " + winStreak);

} 


All your tasks are now complete. In this tutorial, I have walked you through techniques on how to solve problems, breaking down big problems and solving them step by step. This tutorial teaches you how to make a simple game in java and gives you another point of view on how to approach big problems. I have also covered a couple of different techniques on how to convert components that exist in reality into a programmable perspective, such as using integers to represent words or symbols (rock, paper, scissors) and getting the data you want from an array.

Code

Solution Code

Back to the Program-A-Day homepage