7
\$\begingroup\$

I wrote a little Javascript rock/paper/scissors/lizard/spock game and when the user clicks the button to make their choice from the five options, I choose the computer's randomly and then compare the two to find a winner and output the result. It works, but I have the feeling that the findWinner function can be a lot shorter or more elegant. I'm trying to improve my Javascript and jQuery and was hoping for someone to help me with next steps here:.

function findWinner(choiceA,choiceB) {
  if (choiceA == choiceB) {
    return "It's a tie!"; 
  }

  if (choiceA == 'Rock') {
    if (choiceB == 'Lizard' || choiceB == 'Scissors') {
      return "You win!";
    }
    else if (choiceB == 'Spock' || choiceB == 'Paper') {
      return choiceB;
    }
  }
  else if (choiceA == 'Paper') {
    if (choiceB == 'Rock' || choiceB == 'Spock') {
      return "You win!";
    }
    else if (choiceB == 'Scissors' || choiceB == 'Lizard') {
      return "Computer wins :( "; 
    }
  }
  else if (choiceA == 'Scissors') {
    if (choiceB == 'Paper' || choiceB == 'Lizard') {
      return "You win!";
    }
    else if (choiceB == 'Spock' || choiceB == 'Rock') {
      return "Computer wins :( "; 
    }
  }
  else if (choiceA == 'Lizard') {
    if (choiceB == 'Paper' || choiceB == 'Spock') {
      return "You win!";
    }
    else if (choiceB == 'Rock' || choiceB == 'Scissors') {
      return "Computer wins :( "; 
    }
  }
  else if (choiceA == 'Spock') {
    if (choiceB == 'Rock' || choiceB == 'Scissor') {
      return "You win!";
    }
    else if (choiceB == 'Paper' || choiceB == 'Lizard') {
      return "Computer wins :( "; 
    }
  }
}

This just seems very repetitive, but I'm not sure where to go next with it.

\$\endgroup\$
13
\$\begingroup\$

Write down the rules in an object. If an object contains a key, it means it can beat it.

Also, you're writing a javascript game, not jQuery. jQuery is a javascript library.

var rules = {
  rock: {
    scissors: true,
    lizard: true
  },
  scissors: {
    paper: true,
    lizard: true
  },
  lizard: {
    paper: true,
    spock: true
  },
  spock: {
    rock: true,
    scissors: true
  },
  paper: {
    rock: true,
    spock: true
  }
};

function play(opt1, opt2) {
  if (opt1 === opt2) {
    return "Draw";
  }

  if (rules[opt1][opt2]) {
    return "You win";
  }

  return "You loose";
}

console.log(play("rock", "scissors"));
console.log(play("paper", "paper"));
console.log(play("lizard", "spock"));

\$\endgroup\$
  • 1
    \$\begingroup\$ It would be better to have the value as an array rather than object. No need for true or false \$\endgroup\$ – Carl Markham Sep 4 '16 at 20:33
  • 3
    \$\begingroup\$ With an array you'll have to do an indexOf kind of check, with an object it's an instant return. But for an array with only 2 values, there's probably no difference. \$\endgroup\$ – user3297291 Sep 4 '16 at 20:34
  • \$\begingroup\$ How would indexOf not be an instant return? \$\endgroup\$ – Carl Markham Sep 4 '16 at 20:35
  • \$\begingroup\$ What I meant is that indexOf loops over the array, which takes more time. With an object, you don't need a loop. \$\endgroup\$ – user3297291 Sep 4 '16 at 20:36
  • \$\begingroup\$ I see what you mean, but the time difference is marginal at best. Not saying this is a bad answer, the more different aproaches the better \$\endgroup\$ – Carl Markham Sep 4 '16 at 20:38
4
\$\begingroup\$

You can use an object. The following function uses the Array.prototype.includes function for checking existence of the choiceB in the arrays, if the item exists in the array the user wins, otherwise the winner is computer.

var items = {
  'Rock': ['Lizard', 'Scissors'],
  'Paper': ['Spock', 'Rock'],
  'Scissors': ['Paper', 'Lizard'],
  'Lizard': ['Paper', 'Spock'],
  'Spock': ['Rock', 'Scissors']
};

function findWinner(choiceA, choiceB) {
  if (choiceA === choiceB) return "It's a tie!";
  return items[choiceA].includes(choiceB) ? 'You win!' : 'Computer wins!';
}
\$\endgroup\$
2
\$\begingroup\$

Yes, it can be much shorter!

function game(you, enemy){
  val = function(x){return (parseInt(x,36)%46305)%11;} 
  sum = (val(enemy)-val(you)+5)%5;
  return (sum==0)?"tie":(sum==1||sum==3)?"win":"loss"; 
}

How does it work?

if we look at the wikipedia picture for the game we can see that we have five states, and that the rules are symmetric. Surely, you must see the pattern!

  • First, we translate the States into coordinates on the pentagram.

  • Then, we bruteforce a salt (parseInt(x,36)%46305)%11;) that translates the human input into workable coordinates (1, 2, 3, 4, 5).

  • Finally we determine the distance between the coordinates and what it means. If they're on the same spot, we have a tie. if they're one or three spaces ahead, (going clockwise), you win. Otherwise you lose.

function game(you, enemy){
  val = function(x){return (parseInt(x,36)%46305)%11;} //hash into coordinates
  sum = (val(enemy)-val(you)+5)%5; // determine the distance
  return (sum==0)?"tie":(sum==1||sum==3)?"win":"loss"; // determine the outcome 
}

/*
* TEST
*/

console.log(game("paper","paper"));
console.log(game("paper","rock"));
console.log(game("paper","lizard"));
console.log(game("paper","spock"));
console.log(game("paper","scissors"));
console.log("");
console.log(game("rock","paper"));
console.log(game("rock","rock"));
console.log(game("rock","lizard"));
console.log(game("rock","spock"));
console.log(game("rock","scissors"));

A solution like this has the advantage that it will give cheaters a bad time if the rules are supposed to be hidden! (in the general case. here, probably not very important)

EDIT:

While we're at it, I might as well show you a quick and dirty compression, for reference.

num = function(text,s) { return (parseInt(text,36)%s)%11; } //solve for s
crit = function(s) { return num("scissors",s)+ //build target environment
  num("paper",s)*10+
  num("rock",s)*100+
  num("lizard",s)*1000+
  num("spock",s)*10000; }
brut = function(){for(i=1;i<100000;i++){if(crit(i)==54321){return(i);}}}
console.log(brut());

History:

If I had come up with this four years earlier, I could have won the The International Obfuscated C Code Contest ;D

It turns out that Adrian Cable came up with an almost identical method in 2013 which won him a spot in the IOCCC then. He even uses the same two stage double modulo method.

\$\endgroup\$
  • 1
    \$\begingroup\$ Since you have control over the input, this is overkill, but enormously clever \$\endgroup\$ – Tibrogargan Sep 5 '16 at 2:11
1
\$\begingroup\$

This is really just a variation on @user3297291's answer that provides the winning mechanism as well as just determining the victor

var rules =
	{ rock:     { scissors: 'crushes', lizard: 'crushes'     }
	, paper:    {     rock: 'covers',   spock: 'disproves'   }
	, scissors: {    paper: 'cuts',    lizard: 'decapitates' }
	, lizard:   {    spock: 'poisons',  paper: 'eats'        }
	, spock:    { scissors: 'smashes',   rock: 'vaporizes'   }
	};

var actors = [];
for (var property in rules) {
    rules.hasOwnProperty(property) && actors.push(property);
}

var iterations = 3;
function test() {
	for(var i = 0; i < iterations; i++) {
		var a = b = actors[Math.floor(Math.random() * actors.length)];
		while (b == a) b = actors[Math.floor(Math.random() * actors.length)];
		var winner = rules[a][b]? a : b;
		var loser = winner == a? b : a;
		console.log(winner+" "+rules[winner][loser]+" "+loser);
	}
}

function init() {
	document.getElementById('button').addEventListener( 'click', function() { iterations = parseInt(document.getElementById('iterations').value); test(); }, false );
}

document.addEventListener( "DOMContentLoaded", init, false );
<input id="iterations" size="2" value="3">
<button id="button">test</button>

\$\endgroup\$

Your Answer

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