The Challenge
Create an implementation of "Rock - Paper - Scissors - Lizard - Spock".
The rules:
Scissors cuts paper, paper covers rock, rock crushes lizard, lizard poisons Spock, Spock smashes scissors, scissors decapitate lizard, lizard eats paper, paper disproves Spock, Spock vaporizes rock. And as it always has, rock crushes scissors. -- Dr. Sheldon Cooper
My approach
I wanted to make the game flexible and general. It should not be necessary to change much to create a "normal" Rock-Paper-Scissors implementation. It should also be possible to add elements such as Water balloon if you'd like. Technically, it should also be possible without too much effort to modify the elements at runtime (this is currently not supported in the below implementation, but there's not many changes needed to make it a reality).
Players. All players have a score that gets increased when they win. A player should have a method to return which item the player chooses, this implementation can vary (a human can write input, an AI can return something random, some other AIs should perhaps always choose SPOCK...)
Code
Because I am lazy, I have put all the classes/interfaces in the same file. Of course, all of them could be placed in their own files as public
. The code is stand-alone, just copy it and paste it to your favorite IDE (Eclipse) and run it as a JUnit test case.
interface IItem {
/**
* To allow configuration "from both sides", use an int instead of a boolean.<br>
* For example, if SCISSORS have -1 edge against ROCK, it will lose as long as ROCK doesn't have an edge below -1 against SCISSORS.<br>
* This way, it is possible to configure both "beats" and "gets beaten by". It is also possible to return a randomized value here to allow for more complex game styles.
*/
int edge(IItem opponent);
}
abstract class ItemPlayer {
public abstract IItem chooseOne(IItem[] possibles);
private int score = 0;
public void wonAGame() {
score++;
}
public int getScore() {
return score;
}
}
enum Items implements IItem {
SCISSORS, PAPER, ROCK, LIZARD, SPOCK;
private final Set<IItem> beats = new HashSet<IItem>();
void beat(IItem... items) {
this.beats.addAll(new HashSet<IItem>(Arrays.asList(items)));
}
@Override
public int edge(IItem opponent) {
return beats.contains(opponent) ? 1 : 0;
}
}
public class StackExchange {
private static final IItem NO_WINNER = null;
public static IItem fight(IItem first, IItem second) {
int firstEdge = first.edge(second) - second.edge(first);
if (firstEdge == 0)
return NO_WINNER;
return firstEdge > 0 ? first : second;
}
public static class AIInput extends ItemPlayer {
private final Random random = new Random();
@Override
public IItem chooseOne(IItem[] possibles) {
if (possibles.length == 0)
throw new IllegalArgumentException("Possibles needs to contain at least one element");
return possibles[random.nextInt(possibles.length)];
}
@Override
public String toString() {
return "AI";
}
}
public static class HumanInput extends ItemPlayer implements Closeable {
private final Scanner scanner;
public HumanInput() {
this.scanner = new Scanner(System.in);
}
@Override
public void close() {
this.scanner.close();
}
@Override
public IItem chooseOne(IItem[] possibles) {
if (possibles.length == 0)
throw new IllegalArgumentException("Possibles needs to contain at least one element");
do {
System.out.println("Choose one of the following: " + Arrays.toString(possibles));
String str = scanner.nextLine();
for (IItem item : possibles) {
if (item.toString().equals(str)) {
return item;
}
}
System.out.println("Incorrect input.");
}
while (true);
}
@Override
public String toString() {
return "Human";
}
}
@Before
public void setup() { // This configures what beats what.
Items.SCISSORS.beat(Items.PAPER);
Items.PAPER.beat(Items.ROCK);
Items.ROCK.beat(Items.LIZARD);
Items.LIZARD.beat(Items.SPOCK);
Items.SPOCK.beat(Items.SCISSORS);
Items.SCISSORS.beat(Items.LIZARD);
Items.LIZARD.beat(Items.PAPER);
Items.PAPER.beat(Items.SPOCK);
Items.SPOCK.beat(Items.ROCK);
Items.ROCK.beat(Items.SCISSORS);
}
@Test
public void assertions() {
assertEquals(NO_WINNER, fight(Items.SCISSORS, Items.SCISSORS));
assertEquals(Items.SPOCK, fight(Items.SPOCK, Items.SCISSORS));
assertTrue(Items.ROCK.edge(Items.SCISSORS) > 0);
assertTrue(Items.SCISSORS.edge(Items.ROCK) == 0);
assertEquals(Items.ROCK, fight(Items.SCISSORS, Items.ROCK));
}
@Test
public void challenge() {
HumanInput human = new HumanInput();
ItemPlayer comp = new AIInput();
final int GAMES = 42; // we play 42 games, just because it's 42 of course.
for (int i = 1; i <= GAMES; i++) {
System.out.println("Game " + i + " of " + GAMES);
// choose the items
IItem first = human.chooseOne(Items.values());
IItem second = comp.chooseOne(Items.values());
// determine which item wins
IItem fightResult = fight(first, second);
// show result
System.out.println(first + " vs. " + second + ": " + fightResult);
ItemPlayer winner = null;
if (fightResult == NO_WINNER)
System.out.println("Tie!");
else {
winner = fightResult == first ? human : comp;
winner.wonAGame();
System.out.println("Winner is: " + winner);
}
System.out.println("Score is now " + human.getScore() + " - " + comp.getScore());
System.out.println();
}
human.close();
}
}