Hi!
Just came across someone working on the RPS thing and thought to myself, let’s have some fun and turn this into something more elegant.
Then I wanted to share the solution, since it massively reduces the standard code: As a bonus after completing the task. Cleanup and optimization: Let’s reduce the problems to something we can better deal with.
The most obvious pitfall of the if/else attempt is the immense redundancy. The good news is: We don’t need to bother comparing the different results to each other, that can be reduced to a simple equals (==) check.
Now, everybody knows RPS kind of works in a circle.
Rock is beaten by paper, is beaten by scissors, is again beaten by rock, and it repeats.
So we can imagine the whole thing as a circle. After 2, it begins with 0 again.
And we notice, that a player wins if his value is exactly one higher than his opponent’s. (who beats 2 then? Let’s imagine the numbers just wrapping around: 3 becomes 0.)
Now: How can we switch between numbers and names for our moves? We can just use an Array
, containing the names:
var choices = ["rock", "paper", "scissors"];
Now when we call choices[0]
, we get “rock”, choices[1]
is “paper”, choices[2]
is “scissors”.
Similarly, we can use choices.indexOf("rock")
to get 0, and so on.
Thus, we can switch between numbers and names.
And how do we compare those more efficiently?
Let’s look at how we check for the player’s inputs first.
Player (Human):
var userChoice = function () {
var choice = prompt("Do you choose rock, paper or scissors?");
if (choice !== "rock" && choice !== "paper" && choice !== "scissors") {
return userChoice(); // In case the input was invalid, check again and return THAT result instead.
} else {
return choice; // Otherwise, return the valid input
}
};
And Player2 (Machine):
var computerChoice = Math.round(Math.random() * 2);
Math.random()
creates a random value between 0 and 1. If we want a random value between 0 and 2 (for out possible indices), we can simply multiply that by 2.
Now, we would get some pretty awful floating point numbers, so we have to round. That gives us either 0, 1 or 2.
We can later use those to refer to our move names in the table.
We’ve established that a wins if his value is equal to b + 1.
So we could ask
if(a = b+1)
to find out if a won. That would work.
Only, what if b is actually 2? then b+1 would equal 3, which can never be reached, since the index of 3 does not exist in our array.
So we have to wrap b+1 around, if necessary. We COULD to that with another IF, but let’s not. Let’s do this inline, the neat way.
We can use the Modulo operator (%) to get the remainder of b+1 divided by 3. This means, in this case, b+1 will remain b+1, unless b+1 == 3
, because then we will have no remainder (3/3 = 1, no remainder) - so 2+1 will be 0 again.
This way, we’ve created a way to wrap the number around between 0 and 2. (This also works for ANY other integer, not just for 3)
Now, we won’t ever get a number higher than 2:
if(a = (b+1) % 3)
And it will definitely tell us if a won.
Now we just need to combine that with a check to see if a is actually equal to b (is it a draw?) If not, check who won.
If it wasn’t a, then it must have been b.
Let’s make a function out of this.
We will call it using the player’s pick first (which is entered as a string), and the computer’s pick second (which will be an integer: 0, 1 or 2) - like so:
compare(userChoice(), computerChoice);
We use the return value of the user function directly.
And this shall be our function:
function compare(a, b) {
var w = choices[b]; // let w be our winner; start off with b's move being the winner by default, to save space
if (choices.indexOf(a) == b) { // is it a draw?
w = "draw";
} else if (choices.indexOf(a) == (b + 1) % 3) { // If a's number is in fact exactly one higher than b's, a wins.
w = a;
}
console.log(a + " vs. " + choices[b] + ": " + w + " won!"); // tell us who won
}
We already have the player’s move’s name and the computer’s number in the function, as a
and b
respectively.
To get the player’s “number”, we use choices.indexOf(a)
. to get the computer’s move’s name, we use choices[b]
.
And really… that’s it. let’s look at our whole code:
var choices = ["rock", "paper", "scissors"];
var userChoice = function () {
var choice = prompt("Do you choose rock, paper or scissors?");
if (choice !== "rock" && choice !== "paper" && choice !== "scissors") {
return userChoice();
} else {
return choice;
}
};
var computerChoice = Math.round(Math.random() * 2);
function compare(a, b) {
var w = choices[b];
if (choices.indexOf(a) == b) {
w = "draw";
} else if (choices.indexOf(a) == (b + 1) % 3) {
w = a;
}
console.log(a + " vs. " + choices[b] + ": " + w + " won!");
}
compare(userChoice(), computerChoice); // actually call the function in the end
And see it working here:
http://jsfiddle.net/svArtist/12nLz7nb/7/
This is a lot shorter than most attempts, and should give you some helpful ideas and practices for the future.
I hope this is useful for many people out there, I thought it couldn’t hurt to share my findings.