Today I saw an extremely interesting question on the Rock, Paper, Scissors, Lizard and Spock game. I've decided to take my swing at it and I've written the following OOP implementation:
function RockPaperScissors(name, hard_action, soft_action) {
this.name = (name + '').toLowerCase();
this.hard_action = hard_action + '';
this.soft_action = (soft_action || hard_action) + '';
this.strengths = {};
this.weaknesses = {};
}
RockPaperScissors.prototype = {
getName: function(){
return this.name;
},
addStrength: function(strength) {
if( !(strength instanceof RockPaperScissors) ) {
throw new TypeError('A strength must be an instance of RockPaperScissors');
}
this.strengths[strength.getName()] = strength;
},
getStrengths: function() {
var strengths = {};
for(var k in this.strengths)
{
strengths[k] = this.strengths[k];
}
return strengths;
},
addWeakness: function(weakness, soft) {
if( !(weakness instanceof RockPaperScissors) ) {
throw new TypeError('A weakness must be an instance of RockPaperScissors');
}
this.weaknesses[weakness.getName()] = {
enemy: weakness,
soft: !!soft
};
},
getWeaknesses: function() {
var weaknesses = {};
for(var k in this.weaknesses)
{
weaknesses[k] = this.weaknesses[k];
}
return weaknesses;
},
getAction(soft){
return soft? this.soft_action : this.hard_action;
},
winsTo: function(enemy) {
if( !(enemy instanceof RockPaperScissors) ) {
throw new TypeError('An enemy must be an instance of RockPaperScissors');
}
return (enemy.getName() in this.strengths);
}
};
function RockPaperScissorsGame(){
this.score = {
won: 0,
lost: 0,
tied: 0
};
this.games = 0;
}
RockPaperScissorsGame.prototype = {
match: function(user, enemy){
if( !(user instanceof RockPaperScissors) ) {
throw new TypeError('The user must be an instance of RockPaperScissors');
}
if( !(enemy instanceof RockPaperScissors) ) {
throw new TypeError('The enemy must be an instance of RockPaperScissors');
}
this.games++;
if( user == enemy ) {
this.score.tied++;
return 'Tie';
}
var userName = user.getName();
var enemyName = enemy.getName();
if( user.winsTo(enemy) ) {
this.score.won++;
var enemyWeakness = enemy.getWeaknesses()[userName] || {enemy: enemy, soft: false};
return userName + ' ' + user.getAction(enemyWeakness.soft) + ' ' + enemyName;
} else {
this.score.lost++;
var userWeakness = user.getWeaknesses()[enemyName] || {enemy: enemy, soft: false};
return enemyName + ' ' + enemy.getAction(userWeakness.soft) + ' ' + userName;
}
},
getMatches: function(){
return this.games;
},
getWins: function(){
return this.won;
},
getLosses: function(){
return this.lost;
},
getTies: function(){
return this.tied;
}
};
So far so good. The setup is minimal, allows to extend it to as many as you want (you can add a tornado, a hurricane, a thunder .... anything!).
Here, there's the concept of hard and soft actions. Soft actions may be, for example, the paper wrapping the rock. Hard actions are, for example, the scissor beheading the lizard. You can define if a weakness is soft or hard.
To make it easier, I've also implemented a way to have multiple games and easily start a new one.
It is a good idea, but I'm not 100% happy with its quality. There's something that makes me uneasy.
In terms of overall quality and OOP, what am I doing wrong? What readability improvements can be made?