7
\$\begingroup\$

The idea of the game is that the player and the computer take turns entering either 1, 2, or 3, and that number is added to a running total. Whoever goes over 21 loses. I need to develop a strategy that lets the computer always win. I have a strategy worked out, but I was wondering if there is a more efficient way to do it.

import java.util.Scanner;

public class Count21 {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        final int TWENTY_ONE = 21;
        int playerOneEntry;
        int computerEntry = 0;
        int total = 0;
        System.out.println("Instructions, two players take turns by entering 1, 2, or 3 \n"+ "which is then added to a running total. Whoever makes the score go over twenty one "+ "loses\n");

        while(total <= 21) {    
            System.out.println("Player, please enter 1, 2, or 3 >>> ");
            playerOneEntry = input.nextInt();
            while(playerOneEntry != 1 && playerOneEntry != 2 && playerOneEntry != 3) {
                System.out.println("Player, please enter 1, 2, or 3 >>> ");
                playerOneEntry = input.nextInt();
            }
            total += playerOneEntry;
            System.out.println("The total is " + total);
            if(total == 21) {
                System.out.println("Player Wins!");
                total = 25;
            }

            if(total < TWENTY_ONE) {
                switch (total) {
                    case 1:
                        computerEntry = 1;
                        break;
                    case 2:
                        computerEntry = 1;
                        break;
                    case 3:
                        computerEntry = 3;
                        break;
                    case 4:
                        computerEntry = 3;
                        break;
                    case 5:
                        computerEntry = 3;
                        break;
                    case 6:
                        computerEntry = 2;
                        break;
                    case 7:
                        computerEntry = 1;
                        break;
                    case 8:
                        computerEntry = 1;
                        break;
                    case 9:
                        computerEntry = 3;
                        break;
                    case 10:
                        computerEntry = 2;
                        break;
                    case 11:
                        computerEntry = 3;
                        break;
                    case 12:
                        computerEntry = 2;
                        break;
                    case 13:
                        computerEntry = 1;
                        break;
                    case 14:
                        computerEntry = 3;
                        break;
                    case 15:
                        computerEntry = 2;
                        break;
                    case 16:
                        computerEntry = 1;
                        break;
                    case 17:
                        computerEntry = 1;
                        break;
                    case 18:
                        computerEntry = 3;
                        break;
                    case 19:
                        computerEntry = 2;
                        break;
                    case 20:
                        computerEntry = 1;
                        break;
                    default:
                        computerEntry = 1;
                }
            }

            total += computerEntry;
            System.out.println("Computer entered " + computerEntry);
            System.out.println("The total is " + total);
            if(total == 21) {
                System.out.println();
                total = 25;
                System.out.println("Computer Wins!");
            }
            else {
                System.out.println("The total is " + total);
            }
        }   

    }
}
\$\endgroup\$
0

3 Answers 3

9
\$\begingroup\$

As far as I know, the game is whoever gets to 21 or higher loses, as I have implemented. But as you may have heard of a different version, I will not complain.

First, let's take a sample run:

Instructions, two players take turns by entering 1, 2, or 3 
which is then added to a running total. Whoever makes the score go over twenty one loses

Player, please enter 1, 2, or 3 >>> 
1
The total is 1
Computer entered 1
The total is 2
The total is 2
Player, please enter 1, 2, or 3 >>> 
3
The total is 5
Computer entered 3
The total is 8
The total is 8
Player, please enter 1, 2, or 3 >>> 
1
The total is 9
Computer entered 3
The total is 12
The total is 12
Player, please enter 1, 2, or 3 >>> 
1
The total is 13
Computer entered 1
The total is 14
The total is 14
Player, please enter 1, 2, or 3 >>> 
3
The total is 17
Computer entered 1
The total is 18
The total is 18
Player, please enter 1, 2, or 3 >>> 
3
The total is 21
Player Wins!
Computer entered 1
The total is 26
The total is 26

PLAYER WINS!?

Bugs

  1. Your computer lost. This is because... well... explanation later.
  2. The player wins, but the computer still plays a number.
  3. You print "the total is..." twice

Issues

  1. There is a memory leak, as you do not close the scanner.

Fixes

Bug #1 fix

The strategy that the computer uses is not the unbeatable. What is unbeatable is (for this):

Computer starts. Note that the list of numbers of the player's turn is all the possible numbers that the player can play.

Computer: 1
Player: 2, 3, 4
Computer: 5
Player: 6, 7, 8
Computer: 9
Player: 10, 11, 12
Computer: 13
Player: 14, 15, 16
Computer: 17
Player: 18, 19, 20
Computer: 21
Player: 22... LOST!

Why does this work?

Well, the computer has control over the whole game. No matter how hard the Player tries, the computer will always play a number which results in 21.

How did I think of this? Well, I already knew the unbeatable strategy for my version of 21, which was count all the multiples of 4 (4, 8, 12, 16, 20), and after 20, the other player would be forced to lose.

This strategy works because whatever the other player says, you can counter it with another number to put it back to a multiple of 4. For example, if the other player says 3 at the beginning, then the AI will counter with 1, therefore creating the result 4.

How does it work with your version? Well, your version has 22 as maximum, so all I did was shift the "control" numbers one up, to become 5, 9, 13, 17, 21.

Now, I'm not quite done yet, as the Player can start with 1 and the computer would lose, if the Player was smart, so I made the computer start with 1, which was all that was necessary to control the game.

Bug #2 fix

Check for player win.

Bug #3 fix

Don't print the total twice.

Issue #1 fix

Close the scanner.

Alternative Solution

As the answer of my question provides a very good alternate solution to my problem, it is much easier to modify it than try to review your code. Thank you to @200_success for the excellent answer.

The code below is copied directly from the answer:

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";
    }
}

The code below has been edited, due to the difference in the game:

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 AI(), new HumanPlayer(scanner, out) }; // only line needed to change, so that the order of the players are reversed
        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, 22); // One more here to change max
        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

Well, the AI needs to change, if the AI must be designed differently, right?

public class AI implements CursedNumberGame.Player {
    @Override
    public int play(int currentSum, int max, int avoid) {
        assert(max == 3);
        assert(avoid == 22); // change the number to avoid to 22, which is one more than 21
        return currentSum < 2 ? 1 : (5 - (currentSum % 5));
    }

    @Override
    public String toString() {
        return "The AI";
    }
}
\$\endgroup\$
7
  • \$\begingroup\$ Thank you for your help. The code worked. What I am wondering now is, what were the steps that you took to reach the conclusion that the computer had to start. What was your thought process? \$\endgroup\$
    – Jacob
    Dec 13, 2015 at 10:08
  • \$\begingroup\$ @Jacob will add that. \$\endgroup\$ Dec 13, 2015 at 16:36
  • \$\begingroup\$ So, to summarize. If the computer goes first, and uses the above strategy, it will always win. If the player goes first and starts with a "1", the player will always win. If, however, the player goes first and does NOT enter a "1" the computer can still win. Is this correct? \$\endgroup\$
    – Jacob
    Dec 14, 2015 at 13:11
  • \$\begingroup\$ @Jacob close. If the player starts with a 1 the player will not always win. It will win if it plays 5, 9, 13, and so on. \$\endgroup\$ Dec 14, 2015 at 19:14
  • 1
    \$\begingroup\$ Bug #4: The total is 21. Player Wins! Computer entered 1. The total is 26. Since when is 21 + 1 = 26 ? \$\endgroup\$ Jan 6, 2016 at 18:17
-1
\$\begingroup\$

You can have a Map<Integer, Integer> that will give you computer entry (value) for every total. that will save the long switch statement.

\$\endgroup\$
2
  • \$\begingroup\$ Adding the data to the map will take the same amount of lines :P \$\endgroup\$
    – gabbar0x
    Dec 10, 2015 at 13:43
  • \$\begingroup\$ Moderator's note: This answer was posted before the question was migrated to Code Review. \$\endgroup\$ Dec 12, 2015 at 1:01
-6
\$\begingroup\$

I'd use an array:

    static final int[] bestMove = {
        1,
        1,
        3,
        3,
        3,
        2,
        1,
        1,
        3,
        2,
        3,
        2,
        1,
        3,
        2,
        1,
        1,
        3,
        2,
        1,
        1,};
    // Replace your whole switch with this one statement.
    int best = bestMove[currentSum - 1];
\$\endgroup\$
1
  • \$\begingroup\$ Moderator's note: This answer was posted before the question was migrated to Code Review. \$\endgroup\$ Dec 12, 2015 at 1:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.