java控制台实现24点游戏

想着如何实现一个24点,游戏。然后完成找了,有很多。不过试了几个发现很多答案不是很全,对有些内容支持不是很友好。力扣平台上有一个判断24点是否有解,没有返回24点答案。那么就自己实现一个

1. 24点游戏说明

24点游戏,之前大家都有玩过。就是拿4张扑克,运用加减乘除,该等式等于24.例如[2, 3, 10, 6]4张扑克,那么可以 6*(10-(2*3))=24。

2. 实现原理

计算过程:
(1)从4张牌选出2张,有43种(第一次有4张扑克,可以任意选择一种,选第二张只剩下3张了)。这两张牌可以有4种运算(加减乘除)。那么一共有434
(2)接下来,将前面计算得到得值加入到剩下得牌。那么现在就是剩下3张牌。有3
2种选择牌,4种运算方式。那么一共有324
(3)再接下来,还剩下两张牌,两个数字有两种不同的顺序,4种运算2*4
(4)剩下一张牌,就是运算公式结果。查看是否等于24即可。

3.代码分析

  private List<String> getAns(int[] pokers) {
        ArrayList<NumObj> numObjs = new ArrayList<>();
        for (int card : pokers) {
            numObjs.add(new NumObj(card));
        }
        ArrayList<String> ans = new ArrayList<>();
        dfsJudge(numObjs, ans);
        return ans;
    }

    private void dfsJudge(List<NumObj> numObjs, List<String> ans) {
        if (numObjs.size() == 1) {
            NumObj curRes = numObjs.get(0);
            if (curRes.val > 0 && Math.abs(curRes.val - 24) <= 0.00001) {
                ans.add(curRes.cur.substring(1, curRes.cur.length() - 1));
            }
            return;
        }
        for (int i = 0; i < numObjs.size(); i++) {
            for (int j = i + 1; j < numObjs.size(); j++) {
                ArrayList<NumObj> copy = new ArrayList<>(numObjs);
                NumObj b = copy.remove(j), a = copy.remove(i);

                copy.add(performOperation(a, b, "+"));
                dfsJudge(copy, ans);

                copy.set(copy.size() - 1, performOperation(a, b, "-"));
                dfsJudge(copy, ans);

                copy.set(copy.size() - 1, performOperation(a, b, "*"));
                dfsJudge(copy, ans);

//                    除数不能为0
                copy.set(copy.size() - 1, performOperation(a, b, "/"));
                dfsJudge(copy, ans);


                copy.set(copy.size() - 1, performOperation(b, a, "-"));
                dfsJudge(copy, ans);


                copy.set(copy.size() - 1, performOperation(b, a, "/"));
                dfsJudge(copy, ans);
            }
        }
    }

    private NumObj performOperation(NumObj a, NumObj b, String operator) {
        NumObj res = new NumObj();
        switch (operator) {
            case "+":
                res.val = a.val + b.val;
                break;
            case "-":
                res.val = a.val - b.val;
                break;
            case "*":
                // 没有break语句,程序会继续执行下一个case.也就是会接着执行/,那么会不符合条件
                res.val = a.val * b.val;
                break;
            case "/":
                res.val = a.val / b.val;
                break;
            default:
                throw new RuntimeException("无效的操作符");
        }
        res.cur = "(" + a.cur + operator + b.cur + ")";
        return res;
    }
        class NumObj {
        double val;
        public String cur;

        NumObj() {
        }

        NumObj(int val) {
            this.val = val;
            this.cur = String.valueOf(val);
        }

    }

(1)这边使用double而不是int是方便处理使用到除法运算,不用处理小数。如果使用int 2/3=0,如果是6/(3/(2+10)),那么不好处理。还有除数不能为0,也需要判断。(Java 1/0和1/0.0结果是不一样小伙伴可以试着执行下结果
(2)使用NumObj 对象是为了存储前面使用得表达式,方便后面打印。
(3)a+b和b+a效果是一样的,为了减少计算加法和乘法只计算一遍。其实还有 如果a==b那么a-b和b-a效果是一样。
(4)这边留个小疑问,那么一共需要执行多少遍呢,这个实现方式时间复杂度
(5)以上实现方式是参考【详解】递归回溯,考察基本功 | 679. 24点游戏这位大神的求解和求助|如何将24点游戏所有答案都打出来

4.完整代码

1.24点代码

package demo.game;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;

import java.util.*;

/**
 * @author jisl on 2022/8/10 15:53
 * 24点
 */
public class TwentyFourGame {

    private final Random random = new Random();

    private ExpressionParser parser = new SpelExpressionParser();

    private int[] createNum() {
        int[] pokers = new int[4];
        for (int i = 0; i < pokers.length; i++) {
            pokers[i] = random.nextInt(13) + 1;
        }
        return pokers;
    }

    private int[] createPokers() {
        while (true) {
            final int[] pokers = createNum();
            if (judgePoint24(pokers)) {
                return pokers;
            }
        }
    }

    private boolean judgePoint24(int[] nums) {
        List<Double> list = new ArrayList<>(4);
        for (int num : nums) {
            list.add((double) num);
        }
        return solve(list);
    }

    boolean solve(List<Double> nums) {
        if (nums.size() == 1) {
            return Math.abs(nums.get(0) - 24) <= 0.00001;
        }
        for (int i = 0; i < nums.size(); i++) {
            for (int j = i + 1; j < nums.size(); j++) {
                List<Double> copy = new ArrayList<>(nums);
                double b = copy.remove(j), a = copy.remove(i);
                boolean valid = false;
                copy.add(a + b);
                valid |= solve(copy);
                copy.set(copy.size() - 1, a - b);
                valid |= solve(copy);
                copy.set(copy.size() - 1, a * b);
                valid |= solve(copy);
                copy.set(copy.size() - 1, a / b);
                valid |= solve(copy);
                copy.set(copy.size() - 1, b - a);
                valid |= solve(copy);
                copy.set(copy.size() - 1, b / a);
                valid |= solve(copy);
                if (valid) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 将所有的方式都计算一遍,符合条件的加入
     * 24点计算过程:(1)从4个数字选出2个数字,4*3=12种选择,可以有4种运算。得到的结果是变成1个新的数字
     * (2)前面运算得到,那么还有3个数字,3个数字选择两个有 3*2=6种选择,4种运算
     * (3)然后变成2个数字,两个数字有两种不同的顺序,4种运算
     * 一共有 12*4*6*4*2*4=9216种,实际乘法和加法两个效果一样,那么有 (12*4-12)*(6*4-6)*(2*4-2) 一共有3888种排列组合
     *
     * @param pokers pokers
     * @return java.util.List<java.lang.String>
     * @author jisl on 2022/10/30 13:31
     **/
    private List<String> getAns(int[] pokers) {
        ArrayList<NumObj> numObjs = new ArrayList<>();
        for (int card : pokers) {
            numObjs.add(new NumObj(card));
        }
        ArrayList<String> ans = new ArrayList<>();
        dfsJudge(numObjs, ans);
        return ans;
    }

    private void dfsJudge(List<NumObj> numObjs, List<String> ans) {
        if (numObjs.size() == 1) {
            NumObj curRes = numObjs.get(0);
            if (curRes.val > 0 && Math.abs(curRes.val - 24) <= 0.00001) {
                ans.add(curRes.cur.substring(1, curRes.cur.length() - 1));
            }
            return;
        }
        for (int i = 0; i < numObjs.size(); i++) {
            for (int j = i + 1; j < numObjs.size(); j++) {
                ArrayList<NumObj> copy = new ArrayList<>(numObjs);
                NumObj b = copy.remove(j), a = copy.remove(i);

                copy.add(performOperation(a, b, "+"));
                dfsJudge(copy, ans);

                copy.set(copy.size() - 1, performOperation(a, b, "-"));
                dfsJudge(copy, ans);

                copy.set(copy.size() - 1, performOperation(a, b, "*"));
                dfsJudge(copy, ans);

//                    除数不能为0
                copy.set(copy.size() - 1, performOperation(a, b, "/"));
                dfsJudge(copy, ans);


                copy.set(copy.size() - 1, performOperation(b, a, "-"));
                dfsJudge(copy, ans);


                copy.set(copy.size() - 1, performOperation(b, a, "/"));
                dfsJudge(copy, ans);
            }
        }
    }

    private NumObj performOperation(NumObj a, NumObj b, String operator) {
        NumObj res = new NumObj();
        switch (operator) {
            case "+":
                res.val = a.val + b.val;
                break;
            case "-":
                res.val = a.val - b.val;
                break;
            case "*":
                // 没有break语句,程序会继续执行下一个case.也就是会接着执行/,那么会不符合条件
                res.val = a.val * b.val;
                break;
            case "/":
                res.val = a.val / b.val;
                break;
            default:
                throw new RuntimeException("无效的操作符");
        }
        res.cur = "(" + a.cur + operator + b.cur + ")";
        return res;
    }

    private boolean isPoint24(String expressionString) {
        if ("q".equals(expressionString)) {
            return false;
        }
        try {
//            这边如果要使用/法,需要使用浮点数不然表达式正确会变成不正确。
            return Math.abs(MathExpressionCalculator.evaluateExpression(expressionString) - 24) < 0.00001;
        } catch (Exception e) {
            System.err.println(String.format("异常expressionString:%s,e:%s", expressionString, e.getMessage()));
            return false;
        }
    }

    public void start() {
        int total = 0;
        int score = 0;
        System.out.println("游戏开始");
        Scanner scanner = new Scanner(System.in);
        while (true) {
            total += 1;

            final int[] pokers = createPokers();
            System.out.println("当前的数字:" + Arrays.toString(pokers));
            System.out.println("请输入你的答案:");
            final String expressionString = scanner.nextLine();
            final boolean point24 = isPoint24(expressionString);
            if (point24) {
                score += 1;
                System.out.println("恭喜你答对");
            } else {
                System.err.println("不好意思,你答错了");
            }
            System.out.printf("题目数:%s,分数:%s,正确率:%.2f%n", total, score, 1.0 * score / total);
            if ("q".equals(expressionString) || !point24) {
                final List<String> ans = getAns(pokers);
                System.out.println("答案数:" + ans.size());
                System.out.println(ans);
            }
            System.out.println("============================");
        }
    }


    class NumObj {
        double val;
        public String cur;

        NumObj() {
        }

        NumObj(int val) {
            this.val = val;
            this.cur = String.valueOf(val);
        }

    }


    public static void main(String[] args) {
        final TwentyFourGame twentyFourGame = new TwentyFourGame();
        twentyFourGame.start();
    }

}

2.运算解析

表达式刚开始考虑使用spring的el表达式,不过它解析后是int类型,那么对于除法会出现错误。这边让chatgpt帮忙实现了一个。

package demo.game;

import java.util.Stack;

public class MathExpressionCalculator {
    public static void main(String[] args) {
        String expression = "3 + 4 * (2 - 1) / 5"; // 期望结果是 3.8
        double result = evaluateExpression(expression);
        System.out.println("结果: " + result);
    }

    public static double evaluateExpression(String expression) {
        // 移除空格
        expression = expression.replaceAll("\\s", "");

        Stack<Double> numbers = new Stack<>();
        Stack<Character> operators = new Stack<>();

        for (int i = 0; i < expression.length(); i++) {
            char c = expression.charAt(i);

            if (Character.isDigit(c)) {
                StringBuilder sb = new StringBuilder();
                while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
                    sb.append(expression.charAt(i));
                    i++;
                }
                i--; // 回退一步以处理下一个字符
                double num = Double.parseDouble(sb.toString());
                numbers.push(num);
            } else if (c == '(') {
                operators.push(c);
            } else if (c == ')') {
                while (!operators.isEmpty() && operators.peek() != '(') {
                    performOperation(numbers, operators);
                }
                operators.pop(); // 弹出 '('
            } else if (isOperator(c)) {
                while (!operators.isEmpty() && precedence(operators.peek()) >= precedence(c)) {
                    performOperation(numbers, operators);
                }
                operators.push(c);
            }
        }

        while (!operators.isEmpty()) {
            performOperation(numbers, operators);
        }

        return numbers.pop();
    }

    private static boolean isOperator(char c) {
        return c == '+' || c == '-' || c == '*' || c == '/';
    }

    private static int precedence(char operator) {
        if (operator == '+' || operator == '-') {
            return 1;
        } else if (operator == '*' || operator == '/') {
            return 2;
        }
        return 0;
    }

    private static void performOperation(Stack<Double> numbers, Stack<Character> operators) {
        double b = numbers.pop();
        double a = numbers.pop();
        char operator = operators.pop();

        double result = 0;
        switch (operator) {
            case '+':
                result = a + b;
                break;
            case '-':
                result = a - b;
                break;
            case '*':
                result = a * b;
                break;
            case '/':
                if (b != 0) {
                    result = a / b;
                } else {
                    throw new ArithmeticException("除数不能为零");
                }
                break;
        }
        numbers.push(result);
    }
}

5.结尾

这是以上的分享,主要实现难点是获取24点答案

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值