11
\$\begingroup\$

The following is a program that lets a Human play "Rock Paper Scissors Lizard Spock" against the computer... (almost playable at: http://ideone.com/EBDlga)

Note I have posted a follow-up question incorporating the suggestions that have been made in answers and comments on this question.

Comments, critiques, suggestions are welcome and encouraged.

import java.util.Arrays;
import java.util.Random;
import java.util.Scanner;

/**
 * Rock Paper Scissors Lizard Spock
 * <p>
 * http://en.wikipedia.org/wiki/Rock-paper-scissors-lizard-Spock
 * <p>
 * Interface for a human to play against the computer.
 */
public class RPSLS {

    /**
     * Set up the rules for the game.
     * What are the moves, and what beats what!
     */
    public enum Move {
        Rock, Paper, Scissors, Lizard, Spock;

        static {
            Rock.willBeat(Scissors, Lizard);
            Paper.willBeat(Rock,Spock);
            Scissors.willBeat(Paper, Lizard);
            Lizard.willBeat(Spock,Paper);
            Spock.willBeat(Rock,Scissors);
        }

        // what will this move beat - populated in static initializer
        private Move[] ibeat;
        private void willBeat(Move...moves) {
            ibeat = moves;
        }

        /**
         * Return true if this Move will beat the supplied move
         * @param move the move we hope to beat
         * @return true if we beat that move.
         */
        public boolean beats(Move move) {
            // use binary search in case someone wants to set up crazy rules.
            return Arrays.binarySearch(ibeat, move) >= 0;
        }

    }    

    // This is a prompt that is set up just once per JVM
    private static final String MOVEPROMPT = buildOptions();
    private static String buildOptions() {
        StringBuilder sb = new StringBuilder();
        sb.append("     -> ");
        // go through the possible moves, and make a prompt string.
        for (Move m : Move.values()) {
            sb.append(m.ordinal() + 1).append(") ").append(m.name()).append(" ");
        }
        // include a quit option.
        sb.append(" q) Quit");
        return sb.toString();
    }

    /**
     * get some input from the human.
     * @param scanner What we read the input from.
     * @param prompt What we prompt the user for.
     * @param defval If the user just presses enter, what do we return.
     * @return the value the user entered (just the first char of it).
     */
    private static final char prompt(Scanner scanner, String prompt, char defval) {
        // prompt the user.
        System.out.print(prompt + ": ");
        // it would be nice to use a Console or something, but running from Eclipse there isn't one.
        String input = scanner.nextLine();
        if (input.isEmpty()) {
            return defval;
        }
        return input.charAt(0);
    }

    /**
     * Simple conditional that prompts the user to play again, and returns true if we should.
     * @param scanner the scanner to get the input from
     * @return true if the user wants to continue.
     */
    private static boolean playAgain(Scanner scanner) {
        return ('n' != prompt(scanner, "\nPlay Again (y/n)?", 'y'));
    }

    /**
     * Prompt the user for a move.
     * @param scanner The scanner to get the move from
     * @return the move the user wants to do.
     */
    private static Move getHumanMove(Scanner scanner) {
        // loop until we get some valid input.
        do {
            char val = prompt(scanner, MOVEPROMPT, 'q');
            if ('q' == val) {
                // user does not want to make a move... or just presses enter too fast.
                return null;
            }
            int num = (val - '0') - 1;
            if (num >= 0 && num < Move.values().length) {
                // we got valid input. Return.
                return Move.values()[num];
            }
            System.out.println("Invalid move " + val);
        } while (true);
    }

    /**
     * Run the game.... Good Luck!
     * @param args these are ignored.
     */
    public static void main(String[] args) {
        final Random rand = new Random();
        final Move[] moves = Move.values();
        final Scanner scanner = new Scanner(System.in);
        int htotal = 0;
        int ctotal = 0;
        do {
            System.out.println("\nBest of 3.... Go!");
            int hscore = 0;
            int cscore = 0;
            bestofthree: do {
                final Move computer = moves[rand.nextInt(moves.length)];
                final Move human = getHumanMove(scanner);
                if (human == null) {
                    System.out.println("Human quits Best-of-3...");
                    // quit the best-of-three loop
                    break bestofthree;
                }
                if (human == computer) {
                    System.out.printf("  DRAW... play again!! (%s same as %s)\n", human, computer);
                } else if (human.beats(computer)) {
                    hscore++;
                    System.out.printf("  HUMAN beats Computer (%s beats %s)\n", human, computer);
                } else {
                    cscore++;
                    System.out.printf("  COMPUTER beats Human (%s beats %s)\n", computer, human);
                }
                // play until someone scores 2....
            } while (hscore != 2 && cscore != 2);

            // track the total scores.
            if (hscore == 2) {
                htotal++;
            } else {
                // perhaps the human quit while ahead, computer wins that too.
                ctotal++;
            }

            String winner = hscore == 2 ? "Human" : "Computer";
            System.out.printf("\n %s\n **** %s wins Best-Of-Three (Human=%d, Computer=%d - game total is Human=%d Computer=%d)\n",
                    winner.toUpperCase(), winner, hscore, cscore, htotal, ctotal);

            // Shall we play again?
        } while (playAgain(scanner));

        System.out.printf("Thank you for playing. The final game score was Human=%d and Computer=%d\n", htotal, ctotal);

    }

}
\$\endgroup\$
  • 1
    \$\begingroup\$ Ha! Mine says "Spock vaporizes Rock. You lose!" +1 anyway :) \$\endgroup\$ – Mathieu Guindon Nov 30 '13 at 4:39
8
\$\begingroup\$
  • You seem to like static things, but this gives me a feeling that your code is more procedural-oriented than object-oriented.

  • Although not an issue in your actual implementation, I think willBeat should call Arrays.copyOf to create a copy of the input. If you would call someMove.willBeat(someArray) and then later change the indexes in someArray, you would change the rules of the game.

  • The documentation of Arrays.binarySearch states:

    The array must be sorted (as by the sort(byte[]) method) prior to making this call. If it is not sorted, the results are undefined

    Your code does not guarantee that the items are sorted. (It might work correctly anyway since your arrays only contains two items, but it still feels wrong) The values for Lizard are not sorted in the same way the others are.

  • You are using htotal and ctotal, hscore and cscore, Move computer and Move human. Assuming htotal stands for "Human Total", player classes would be useful here.

  • 2 and 3 are very Magic numbers.

  • The variable names htotal and hscore is a bit confusing.

  • Your main method is quite long and does a lot of things. Some of these things could be extracted into other methods/objects. Your main method currently is responsible for:

    • Keeping score
    • Keeping score (of a single "best of three")
    • Randomize a computer move
    • Determine who wins a game
  • I like your playAgain method and the way you are handling user input-output.

  • Overall, your code is not very abstracted. It seems to work, and it seems to do it's job well though.

\$\endgroup\$
  • \$\begingroup\$ Thanks... I think you are right on all points... (including procedural thinking) except the Arrays.copyOf()... that is all squarely contained within the enum. There's no way for (in a real situation where the Enum would be a separate file and not embedded) anything outside the Enum to mess with the arrays. \$\endgroup\$ – rolfl Nov 30 '13 at 18:46
  • \$\begingroup\$ @rolfl Yes, that's true. I was thinking ahead. As long as you don't pass an array and change the indexes in the array later, there is of course no harm done. \$\endgroup\$ – Simon Forsberg Nov 30 '13 at 19:18
2
\$\begingroup\$

I suggest that you get rid of most of the static methods, and then refactor this code using MVC design to allow the code to be the model of any type of view you desire, be it Swing, console, SWT, Android, or what have you.

\$\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.