More efficient choice comparison for Rock Paper Scissors

You can also use an array to check the winner. Order the array so that the winner is always on the right side. Then compare if the machine's choise is the one next to user's choise, like so:

var weapons = ['paper', 'scissors', 'rock'],
  user = 'scissors',
  machine = 'paper',
  uIdx = weapons.indexOf(user),
  mIdx = weapons.indexOf(machine),
  winner;
if (uIdx !== mIdx) {
  winner = (mIdx === (uIdx + 1) % 3) ? 'machine' : 'user';
} else {
  winner = 'tie';
}

console.log(winner);

A fiddle to play with.

The modulo operator makes the magic at the end of the array. If user has chosen "rock", the next to it would be undefined, but the modulo operator of 3 % 3 returns 0, hence "paper" is compared to "rock".


I removed some of your variables and combined some, just to make it shorter. I also got rid of the bulk of the if/else since it's not really needed here. For more info on how a switch works, check out https://javascript.info/switch.

I also changed up your choices so that you can add multiple win or loss conditions for each choice, in case you wanted to upgrade to Rock,Paper,Scissors,Lizard,Spock ( https://www.youtube.com/watch?v=cSLeBKT7-s ).

// Set up our various choices, how they rank, and their action (can also be array if desired).
const choices = {
    Rock     : { win:["Scissors"]        , action:"beats" } ,
    Paper    : { win:["Rock"]            , action:"beats"  } ,
    Scissors : { win:["Paper"]           , action:"cuts" } ,
    Spock    : { win:["Rock","Scissors"] , action:"beats" }
} ;

// Use the keys in choices as our selectable items.
const weapons = Object.keys(choices) ;

// Our basic intro.
const rps = prompt("Welcome to Rock, Paper, Scissors. Would you like to play?" + '\n' + "If you do, enter number 1." + '\n' + "If you don't, enter number 2.");

// Set the computer choice.
const chosenOne = weapons[Math.floor(Math.random()*3)];

// This is an example of your switch.
switch (rps) {
  case "1" : // Since we used text input, we have to evaluate for a text "number".
      alert("Remember:" + '\n' + " - Rock beats the scissors" + '\n' + " - Paper beats the rock" + '\n' + " - The scissors cut the paper");

     // Make your choice.
     let weapon = prompt("Make your choice:" + '\n' + weapons, "");
     // Is our choice valid?
     if ( !weapons.includes(weapon) ) { 
         alert("Invalid choice. Closing Game."); break; 
     } else {
         alert("You chose: " + weapon + '\n' + "The computer chose: " + chosenOne);
     }
     // Did I win?
     alert( compareRPS(weapon,chosenOne) ) ;     
  break ; // This will break out of the switch. Otherwise will fall through to next case.

  case "2":    
    alert("Thanks for visiting! See you later.");
  break ;

  default :
    alert("Invalid option. Closing game.");       
  // No break needed here since this is the end of the switch.
}

// I broke the check-you-vs-cpu functionality out into its own function.
function compareRPS(youC,cpuC) {
    if ( youC === cpuC ) {
      return "It's a tie! Try again to win." ;
    }
    if (choices[youC].win.includes(cpuC)) {
      return "You won! " + youC + " " + choices[youC].action + " " + cpuC + "." ;
    } else {
      return "You lost! " + cpuC + " " + choices[cpuC].action + " " + youC + "." ;
    }
}

NOTE: I also switch between const and let. See https://codeburst.io/part-2-var-vs-const-vs-let-69ea73fe76c1 for differences. I mostly use const to indicate a variable I won't change and let to be one that I can (within its proper scope). There's also var, but I didn't need it here.


You can define an object that define if your move is weak or strong against another. Example:

const myChoice = 'Rock'
const enemyChoice = 'Scissors' 

const weapons = {
   Rock: {weakTo: 'Paper', strongTo: 'Scissors'},
   Paper: {weakTo: 'Scissors', strongTo: 'Rock'},
   Scissors: {weakTo: 'Rock', strongTo: 'Paper'}
}

if (weapons[myChoice].strongTo === enemyChoice) {
    // I won
    return;
}

if (weapons[myChoice].weakTo === enemyChoice) {
    // I Lost
    return;
}

// tie

Quite late to the party, but here's a slightly different way to the other answers in here. I've included the answer in TypeScript, but you can obviously remove the types if you like.

type Weapon = "Rock" | "Paper" | "Scissors";
type Result = "Player" | "Computer" | "Draw";
type GameLogic = Record<Weapon, Array<Weapon>>;

// Map that tracks what each weapon beats.
const outcomes: GameLogic = {
  Rock: ["Scissors"],
  Paper: ["Rock"],
  Scissors: ["Paper"]
};

const determineWinner = (playerOneWeapon: Weapon, playerTwoWeapon: Weapon): Result => {
  if (playerOneWeapon === playerTwoWeapon) {
    return "Draw";
  }

  return outcomes[playerOneWeapon].includes(playerTwoWeapon)
    ? "Player One"
    : "Player Two";
}

This implementation has the ability to scale nicely, too. As you can add extra weapons into the mix and the implementation of determineWinner doesn't change - by adding to the Weapon type and the outcomes map:

type Weapon = "Rock" | "Paper" | "Scissors" | "Lizard" | "Spock";
const outcomes: GameLogic = {
  Rock: ["Scissors", "Lizard"],
  Paper: ["Rock", "Spock"],
  Scissors: ["Paper", "Lizard"],
  Lizard: ["Spock", "Paper"],
  Spock: ["Rock", "Scissors"]
};

We can now support a game where each weapon beats each weapon exactly twice and each weapon loses to a weapon exactly twice (as opposed to the classic variation where everything beats one weapon and loses to one weapon).

Tags:

Javascript