此游戏的目标是不说数字21。

规则:


您只能说连续于前一个所说数字的数字。
您一次可以说1、2或3个数字。

例如:


播放器1说1
播放器2说2 ,3
播放器1表示4、5、6
播放器2表示7、8
依此类推...


您将在玩AI,您将开始。尽力而为,但是无论努力如何,都会输!

我将亲自给能够找到一种以50 rep赏金击败AI(即程序中的错误)的人。

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.println("The goal of this game is to not say the number 21.\n\n" + "Rules:\n"
                + "1. You must only say numbers consecutive to the previous said number.\n"
                + "2. You may say 1, 2, or 3 numbers at a time.\n\n" + "For example:\n\n" + "\tPlayer 1 says 1\n"
                + "\tPlayer 2 says 2, 3\n" + "\tPlayer 1 says 4, 5, 6\n" + "\tPlayer 2 says 7, 8\n"
                + "\tAnd so on...\n\n"
                + "You will be playing AI, and you will start. Try your best, but no matter how hard you try, you will lose!\n");
    }

    private int[] getNumbers(Scanner scanner, int previousNumber) {
        System.out
                .println("Enter your numbers, separated by commas only (e.g. \"1,2,3\"), or enter \"help\" for help.\n"
                        + "Note that if you enter more than three numbers, this program will ignore the ones after the third one.");
        while (true) {
            String input = scanner.nextLine().trim();
            if (input.equals(HELP)) {
                displayHelp();
                return null;
            } else {
                if (NUMBER_LIST_PATTERN.matcher(input).matches()) {
                    int[] result = toIntArray(input);
                    if (result != null && result[0] == previousNumber + 1) {
                        return result;
                    }
                }
                System.out.println("Oops, illegal input. Try again:");
            }
        }
    }

    private int[] toIntArray(String input) {
        String[] array = input.split(",");
        int length = array.length;
        int[] result = new int[length > MAX_NUMS_PER_TURN ? MAX_NUMS_PER_TURN : length];
        for (int i = 0; i < length && i < MAX_NUMS_PER_TURN; i++) {
            result[i] = Integer.parseInt(array[i]);
            if (i != 0 && result[i] != result[i - 1] + 1) {
                return null;
            }
        }
        return result;
    }

    private boolean contains(int[] numbers, int number) {
        for (int num : numbers) {
            if (num == number) {
                return true;
            }
        }
        return false;
    }

    private int printAINumbers(int[] nextNumbers) {
        System.out.print("The AI says: ");
        for (int number : nextNumbers) {
            System.out.print(number + "  ");
        }
        System.out.println();
        return nextNumbers[nextNumbers.length - 1];
    }

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

}


现在是揭示秘密的部分...

AI.java

public class AI {

    public int[] getNextNumbers(int lastNumber) {
        if (lastNumber % 4 == 0) {
            return new int[] {lastNumber + 1};
        }
        return list(lastNumber + 1, 4 * ((int) (lastNumber / 4) + 1));
    }

    private int[] list(int x, int y) {
        int[] result = new int[y - x + 1];
        for (int i = 0; x + i <= y; i++) {
            result[i] = x + i;
        }
        return result;
    }

}


如果您不明白为什么会这样,那么AI所做的就是算到4的下一个倍数。为什么这项工作?好吧,这就是发生的事情:

Player: 1, [2], [3]
AI:     [2], [3], 4
Player: 5, [6], [7]
AI:     [6], [7], 8
Player: 9, [10], [11]
AI:     [10], [11], 12
Player: 13, [14], [15]
AI:     [14], [15], 16
Player: 17, [18], [19]
AI:     [18], [19], 20
Player: 21


方括号中的任何内容,如果玩家未说,将由AI讲。无论您如何玩,AI都会赢(只要它开始就可以)。

关注点:


我在面向对象操作方面做得好吗?
我的变量名好吗?
和往常一样,还有其他吗?


#1 楼

这是一个两人游戏。 AI和人类玩家都应实现一个通用的Player接口。例如,这会使将其转换成人类游戏变得容易。

输入验证部分发生在getNumbers()(正则表达式匹配和连续数字检查)中,部分发生在toIntArray()(另一个连续数字)中检查)。如果验证都发生在同一个地方,那就太好了。

等效的游戏只会要求每个玩家选择1、2或3来添加到当前总和中。这样可以简化解析过程,并且无需传递int[]数组。如果您想恢复原始行为,可以在遵循以下界面的前提下进行恢复。

我不喜欢startGame()可以玩多个游戏的方式。我会考虑“再次播放?”循环并提示。

使用Scanner时,最好在遇到EOF的情况下捕获NoSuchElementException

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