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

Game counting to 21

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

Problem

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

Solution

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

  • Your computer lost. This is because... well... explanation later.



  • The player wins, but the computer still plays a number.



  • You print "the total is..." twice



Issues

  • 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

Code Snippets

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

Context

StackExchange Code Review Q#113679, answer score: 9

Revisions (0)

No revisions yet.