7
\$\begingroup\$

I just went through a very easy online example creating a Rock-Paper-Scissors game, but it seemed like it was not a great use of computing power.

It seemed like I was writing out results for every possible outcome instead of declaring properties or attributes for variables that will dictate the results depending on the situation.

Could an index or something be used to set up the variables?

var rock = ["rock", "paper", "scissors"];
var paper = ["paper", "scissors", "rock"];
var scissors = ["scissors", "rock", "paper"];
    index           0         1        2

Player 1 could select a variable, then player 2 could select a string in the index, and the position of the selection in the index would determine the winner (0 = tie, 1 = p1Loss, 2 = p1Win). Is this possible?

I think the code in the example below would start to get messy and bloated if you were to expand on the choices/options. i.e. Instead of 3 options you have 9, any one of which will beat 4 options and loose to the other 4.

I guess I'm just wondering if there is a better way to do this that would scale better with larger numbers.

I've included the code from the example below:

var user1Choice = prompt("Do you choose rock, paper or scissors?");
var user2Choice = prompt("Do you choose rock, paper or scissors?");

var compare = function(choice1, choice2) {
    if (choice1 === choice2) {
        return "its a tie";
    }

    else if (choice1 === "rock") {

        if (choice2 === "scissors") {
            return "rock wins";
        }
        else {
            return "paper wins";
        }
    }

    else if (choice1 === "paper") {
        if (choice2 === "rock") {
            return "paper wins";
        }
        else {
            return "scissors wins";
        }
    }

    else if (choice1 === "scissors") {
        if (choice2 === "paper") {
            return "scissors wins";
        }
        else {
            return "paper wins";
        }
    }



}


compare(user1Choice, user2Choice)
\$\endgroup\$
  • 2
    \$\begingroup\$ One issue I have with this is the name choice. I would expect rock to represent a rock, whereas it really is an array containing "rock", "paper", and "scissors" and so is unintuitive. If you really hate conditionals, wrap your three subarrays into another array and then the result would be determined by results[player1][player2] \$\endgroup\$ – mleyfman Aug 5 '14 at 20:42
8
\$\begingroup\$

Your idea

Your idea is nice, but it will cause problems when extending the game. For example, we will add spock and lizard:

var spock = ["spock", "paper", "lizard", "scissors", "rock"];

Now, index 1 and 2 mean p1Loss, and index 3 and 4 mean p1Win. This can easily cause bugs in the future.

Maybe a better idea

You could instead maintain arrays that say what items beat. For example:

var spockBeats = ["scissor", "rock"];

Then, check at the beginning if the input of the users is equal, return "tie" if it is. If it is not, find the appropriate array for user1 (I would use a switch instead of many if-else) and see if it contains the choice of user2.

In this case, extending the game is easier. New item? Add a new array and a new switch option, and add the item to some of the other arrays.

In case you really want to extend the game (I mean like adding a couple dozen or more options), this approach too would become somewhat annoying. In that case you might want to read up on graph theory, as this is basically just a directed graph. I think that an adjacency matrix might work well then.

For rock, paper, scissor, the matrix would look like this (it contains duplicate information, but I don't think that memory is a concern and removing it would make it harder to use):

   R  P  S
R  0 -1  1  
P  1  0 -1
S -1  1  0

And you use it like this: Player1 plays paper, Player2 plays rock. Look in the paper row for the value under rock. it is 1 and player1 wins. -1 would mean they lose, and 0 means tie. For just rock, paper, scissors this is overkill, but for a lot more options this might work well.

Comments on current code

  • I would save the strings (rock, paper, scissors) in fields at the top of the file. It's an easy source of bugs because of typos
  • I would return which player won, not with what choice they won. Or both, but the current way seems just odd to me
\$\endgroup\$
  • \$\begingroup\$ +1 for the matrix. I just came up with this idea, than I saw it in your post ;) \$\endgroup\$ – Thomas Junk Aug 9 '14 at 19:04
  • \$\begingroup\$ This is awesome, I think this is what I was trying to do with the 3 index. Not sure how to set up matrices yet but I'm working on it. Great answer, thanks a lot. \$\endgroup\$ – Joseph James Frampton Aug 10 '14 at 5:28
3
\$\begingroup\$

Interesting puzzle. I came up with a solution that uses an array of objects that each contain a counters key. This way you can have an arbitrary amount of elements in the game, as the rock paper lizard spock example. :)

Here's the code and a live demo.

var objects = [{
  name: 'rock',
  counters: ['scissors']
}, {
  name: 'scissors',
  counters: ['paper']
}, {
  name: 'paper',
  counters: ['rock']
}];

var compare = function(playerOne, playerTwo) {
  var cmp = function(player) {
    return objects.filter(function(obj) {
      return obj.name === player;
    })[0];
  };

  if (playerOne === playerTwo) {
    return 'it\'s a tie!';
  } else if (cmp(playerOne).counters.indexOf(cmp(playerTwo).name) > -1) {
    return playerOne + ' wins';
  } else {
    return playerTwo + ' wins';
  } 
};

console.log(compare('paper', 'rock')); 
\$\endgroup\$
  • \$\begingroup\$ I really like this approach, thanks a lot Ben. \$\endgroup\$ – Joseph James Frampton Aug 10 '14 at 5:31
3
\$\begingroup\$

The most obvious way would be with a dictionary-like object for each winning combination:

var beats = {
    rock: ['scissors'],
    scissors: ['paper'],
    paper: ['rock']
};
function compare(choice1, choice2) {
    if (beats[choice1].indexOf(choice2) > -1) {
        return choice1 + ' wins';
    }
    if (beats[choice2].indexOf(choice1) > -1) {
        return choice2 + ' wins';
    }
    return "It's a draw";
}

Or make a second function to do the same thing.

function beats(choice1, choice2) {
    return (choice1 == 'rock' && choice2 == 'scissors') ||
           (choice1 == 'scissors' && choice2 == 'paper') ||
           (choice1 == 'paper' && choice2 == 'rock');
}
function compare(choice1, choice2) {
    if (beats(choice1, choice2)) {
        return choice1 + ' wins';
    }
    if (beats(choice2, choice1)) {
        return choice2 + ' wins';
    }
    return "It's a draw";
}
\$\endgroup\$
3
\$\begingroup\$

You can definitely use modular arithmetic on an ordered collection to determine the winner of Rock-Paper-Scissors. Ordering is determined by taking set differences between an elements winning conditions and the winning conditions of those losers. You can read about the full process here.. Once you have your ordered list, use modular arithmetic to find the distance between the two player's weapons, and return if the distance falls in the winner or loser range.

Note: I have no JS experience. Skeleton of the code taken from Ben in this post (Thanks Ben!) and like 10 mins of reading on w3schools.com. The modular arithmetic is there.

var RPS = RPS || {}
RPS.Weapons = Object.freeze({ 
  "Rock" : 0, "Paper" : 1, "Scissors" : 2 
});

var RPSLS = RPSLS || {}
RPSLS.Weapons = Object.freeze({ 
  "Rock"   : 0, "Spock"    : 1, "Paper" : 2,
  "Lizard" : 3, "Scissors" : 4 
});

var RPSLSBSWG = RPSLSBSWG || {}
RPSLSBSWG.Weapons = Object.freeze({ 
  "Rock"   : 0, "Spock"     : 1, "Batman"   : 2,
  "Glock"  : 3, "Paper"     : 4, "Lizard"   : 5,
  "Wizard" : 6, "Spiderman" : 7, "Scissors" : 8 
});

var compare = function(rules, playerOne, playerTwo) {
    if (playerOne === playerTwo) {
        return 'it\'s a tie!';
    }

    var size = Object.keys(rules).length;

    // Distance from playerTwo's weapon to playerOne's weapon.  
    // Think of the weapon ring being rotated such that playerTwo
    // is now at position 0.  There are N/2 elements to the right
    // that lead to loss conditions and the same number of elements
    // to the left that lead to win conditions.
    //     0  1  2  3  4
    //     D  L  L  W  W
    var resultDistance = (size + playerOne - playerTwo) % size;

    var LosingRange = Math.floor(size / 2);
    if (resultDistance <= LosingRange) {
        return 'Player one wins!';
    }
    return 'Player two wins!';
};

console.log(compare(RPSLSBSWG.Weapons,
                    RPSLSBSWG.Weapons.Rock,
                    RPSLSBSWG.Weapons.Scissors));
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.