HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaModerate

21: The number with the curse

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
withnumberthecurse

Problem

The goal of this game is to not say the number 21.

Rules:

  • You must only say numbers consecutive to the previous said number.



  • You may say 1, 2, or 3 numbers at a time.



For example:

Player 1 says 1

Player 2 says 2, 3

Player 1 says 4, 5, 6

Player 2 says 7, 8

And so on...

You will be playing AI, and you will start. Try your best, but no matter how hard you try, you will lose!

I will personally give the one who can find a way to beat the AI (i.e. a bug in the program) with a 50 rep bounty.

Main.java

public class Main {

    private static final int END = 21;

    public static void main(String[] args) {
        Game game = new Game(END);
        game.startGame();
    }

}


Game.java

```
import java.util.Scanner;
import java.util.regex.Pattern;

public class Game {

private static final Pattern NUMBER_LIST_PATTERN = Pattern.compile("(\\d+,)*\\d+");
private static final String HELP = "help";
private static final int MAX_NUMS_PER_TURN = 3;

private final int end;

public Game(int end) {
if (end % 4 != 1) {
throw new IllegalArgumentException("Nice try, this number will fail the AI.");
}
this.end = end;
}

public void startGame() {
Scanner scanner = new Scanner(System.in);
AI impossibleComputer = new AI();
displayHelp();
do {
boolean hasLost = false;
int previousNumber = 0;
do {
int[] numbers = getNumbers(scanner, previousNumber);
if (contains(numbers, end)) {
System.out.println("You lose!");
hasLost = true;
} else {
previousNumber = printAINumbers(impossibleComputer.getNextNumbers(numbers[numbers.length - 1]));
}
} while (!hasLost);

} while (doAgain(scanner));
System.out.println("Thanks for playing!");
}

private void displayHelp() {
System.out.printl

Solution

It's a two-player game. Both the AI and the human player should implement a common Player interface. That would make it easy to convert it into a human-human game, for example.

Input validation happens partly in getNumbers() (the regex match and a consecutive numbers check) and partly in toIntArray() (another consecutive numbers check). It would be nice if the validation all happened in the same place.

An equivalent game would just ask each player to pick 1, 2, or 3 to be added to the current sum. That would simplify the parsing and eliminate the need to pass int[] arrays. If you want to reinstate the original behaviour, you can do so while still adhering to the interfaces laid out below.

I'm not a fan of the way startGame() can play multiple games. I'd factor out the "Play again?" loop and prompt.

When using a Scanner, it would be good to catch NoSuchElementException in case EOF is encountered.

CursedNumberGame.java

import java.io.PrintStream;
import java.util.NoSuchElementException;
import java.util.Scanner;

public class CursedNumberGame {

    public interface Player {
        /**
         * Given the parameters of the game (max and avoid), and the
         * current sum, chooses a number between 1 and max inclusive.
         */
        int play(int currentSum, int max, int avoid);
    }

    private final int maxPerTurn, avoid;

    public CursedNumberGame(int maxPerTurn, int avoid) {
        this.maxPerTurn = maxPerTurn;
        this.avoid = avoid;
    }

    public static void displayHelp(PrintStream out) {
        out.println("The goal of this game is to stay below the number 21.\n\n"
                + "At each turn, a player chooses either \"1\", \"2\", or \"3\".\n"
                + "That number will be added to the current number.\n\n"
                + "You will be playing AI, and you will start. Try your best, but no matter how\n"
                + "hard you try, you will lose!");
    }

    public void run(Scanner scanner, PrintStream out) {
        displayHelp(out);

        Player[] players = new Player[] { new HumanPlayer(scanner, out), new AI() };
        int p, sum;
        for (p = 0, sum = 0; sum < this.avoid; p = 1 - p) {
            int choice = players[p].play(sum, this.maxPerTurn, this.avoid);
            out.printf("%s played %d.  The sum is now %d.\n", players[p], choice, sum + choice);
            sum += choice;
        }
        out.printf("%s lost!\n", players[1 - p]);
    }

    private static boolean doAgain(Scanner scanner, PrintStream out) {
        out.print("Do you want to play again? ");
        while (true) {
            char in = Character.toUpperCase(scanner.nextLine().charAt(0));
            if (in == 'Y') {
                return true;
            } else if (in == 'N') {
                return false;
            }
            out.print("Oops, that was not valid input. Try again: ");
        }
    }

    public static void main(String[] args) {
        CursedNumberGame game = new CursedNumberGame(3, 21);
        try (Scanner scanner = new Scanner(System.in)) {
            do {
                game.run(scanner, System.out);
            } while (doAgain(scanner, System.out));
        } catch (NoSuchElementException eof) {
        }
        System.out.println("Thanks for playing!");
    }
}


AI.java

public class AI implements CursedNumberGame.Player {
    @Override
    public int play(int currentSum, int max, int avoid) {
        assert(max == 3);
        assert(avoid == 21);
        assert(4 - (currentSum % 4) < max);
        return 4 - (currentSum % 4);
    }

    @Override
    public String toString() {
        return "The AI";
    }
}


HumanPlayer.java

import java.util.Scanner;
import java.io.PrintStream;

public class HumanPlayer implements CursedNumberGame.Player {
    private static final String HELP = "help";

    private final Scanner in;
    private final PrintStream out;

    public HumanPlayer(Scanner in, PrintStream out) {
        this.in = in;
        this.out = out;
    }

    @Override
    public int play(int currentSum, int max, int avoid) {
        out.printf("\nEnter a number from 1 to %d inclusive: ", max);
        do {
            String input = in.nextLine();
            if (HELP.equals(input)) {
                CursedNumberGame.displayHelp(out);
                continue;
            } else try {
                int n = Integer.parseInt(input);
                if (0 < n && n <= max) {
                    return n;
                }
            } catch (NumberFormatException notAnInt) {
            }
            out.print("Oops, illegal input. Try again: ");
        } while (true);
    }

    @Override
    public String toString() {
        return "You";
    }
}

Code Snippets

import java.io.PrintStream;
import java.util.NoSuchElementException;
import java.util.Scanner;

public class CursedNumberGame {

    public interface Player {
        /**
         * Given the parameters of the game (max and avoid), and the
         * current sum, chooses a number between 1 and max inclusive.
         */
        int play(int currentSum, int max, int avoid);
    }

    private final int maxPerTurn, avoid;

    public CursedNumberGame(int maxPerTurn, int avoid) {
        this.maxPerTurn = maxPerTurn;
        this.avoid = avoid;
    }

    public static void displayHelp(PrintStream out) {
        out.println("The goal of this game is to stay below the number 21.\n\n"
                + "At each turn, a player chooses either \"1\", \"2\", or \"3\".\n"
                + "That number will be added to the current number.\n\n"
                + "You will be playing AI, and you will start. Try your best, but no matter how\n"
                + "hard you try, you will lose!");
    }

    public void run(Scanner scanner, PrintStream out) {
        displayHelp(out);

        Player[] players = new Player[] { new HumanPlayer(scanner, out), new AI() };
        int p, sum;
        for (p = 0, sum = 0; sum < this.avoid; p = 1 - p) {
            int choice = players[p].play(sum, this.maxPerTurn, this.avoid);
            out.printf("%s played %d.  The sum is now %d.\n", players[p], choice, sum + choice);
            sum += choice;
        }
        out.printf("%s lost!\n", players[1 - p]);
    }

    private static boolean doAgain(Scanner scanner, PrintStream out) {
        out.print("Do you want to play again? ");
        while (true) {
            char in = Character.toUpperCase(scanner.nextLine().charAt(0));
            if (in == 'Y') {
                return true;
            } else if (in == 'N') {
                return false;
            }
            out.print("Oops, that was not valid input. Try again: ");
        }
    }

    public static void main(String[] args) {
        CursedNumberGame game = new CursedNumberGame(3, 21);
        try (Scanner scanner = new Scanner(System.in)) {
            do {
                game.run(scanner, System.out);
            } while (doAgain(scanner, System.out));
        } catch (NoSuchElementException eof) {
        }
        System.out.println("Thanks for playing!");
    }
}
public class AI implements CursedNumberGame.Player {
    @Override
    public int play(int currentSum, int max, int avoid) {
        assert(max == 3);
        assert(avoid == 21);
        assert(4 - (currentSum % 4) < max);
        return 4 - (currentSum % 4);
    }

    @Override
    public String toString() {
        return "The AI";
    }
}
import java.util.Scanner;
import java.io.PrintStream;

public class HumanPlayer implements CursedNumberGame.Player {
    private static final String HELP = "help";

    private final Scanner in;
    private final PrintStream out;

    public HumanPlayer(Scanner in, PrintStream out) {
        this.in = in;
        this.out = out;
    }

    @Override
    public int play(int currentSum, int max, int avoid) {
        out.printf("\nEnter a number from 1 to %d inclusive: ", max);
        do {
            String input = in.nextLine();
            if (HELP.equals(input)) {
                CursedNumberGame.displayHelp(out);
                continue;
            } else try {
                int n = Integer.parseInt(input);
                if (0 < n && n <= max) {
                    return n;
                }
            } catch (NumberFormatException notAnInt) {
            }
            out.print("Oops, illegal input. Try again: ");
        } while (true);
    }

    @Override
    public String toString() {
        return "You";
    }
}

Context

StackExchange Code Review Q#111862, answer score: 12

Revisions (0)

No revisions yet.