java实现24点游戏

游戏规则

从扑克中每次取出4张牌。使用加减乘除,第一个能得出24者为赢。(其中,J代表11,Q代表12,K代表13,A代表1),按照要求编程解决24点游戏。

基本要求

  • 随机生成4个代表扑克牌牌面的数字字母,程序自动列出所有可能算出24的表达式
  • 列出表达式无重复
  • 用户初始生命值为一给定值(比如3),初始分数为0。随机生成4个代表扑克牌牌面的数字或字母,由用户输入包含这4个数字或字母的运算表达式(可包含括号),如果表达式计算结果为24则代表用户赢了此局。
  • 使用计时器要求用户在规定时间内输入表达式,如果规定时间内运算正确则加分,超时或运算错误则进入下一题并减少生命值(不扣分)。
  • 所有成绩均可记录在TopList.txt文件中。

解表达式基本思路

采用穷举法列举每一种存在的可能,接着判断是否值为24,如果等于24,则将表达式存入Set集合,最终遍历Set集合即可得到所有表达式。具体思路如下:

  1. 采用随机数生成4个符合要求的数据,假设四个数据为n1,n2,n3,n4 。
  2. 把数据相互组合可以得到如下组合:n1和n2 ,n1和n3,n1和n4,n2和n3,n2和n4,n3和n4
  3. 将上面的组合进行各种可能的运算例如:n1+n2,n1-n2,n2-n1,n1*n2,n1/n2,n2/n1等等。
  4. 把上面组合计算出来的结果存入进对应的数组中例如:组合相加的结果存入add数组,相减的结果存入sub数组……最终将这些数组存入一个list集合中,目的是为了方便通过循环遍历出每一种组合。
  5. 通过循环去遍历每一种组合,把这些组合在一起进行相加,相减等运算,记录结果为24的表达式。在这里需要注意的是,因为数组得值为两个数字的运算结果,所以需要根据当前循环变量的值和list集合以及数组存入数据的顺序去把表达式格式化成四个数字组成的表达式,否则表达式只有两个数字。
  • 需要注意的是:在遍历集合的过程中,由于集合中存入的数组的数据为两个数据组合的形式,所以遍历是只需要控制好下标,使的每一个表达式中只有n1,n2,n3,n4这四个数据,而不会出现类似于n1,n2,n1,n3 这种组合的方式。
  • 具体的流程图入下图所示:
    java实现24点游戏

玩家运算基本思路

在java中使用Timer和Canender即可实现定时的功能,而判断表达式是否正确,在java中使用Script引擎调用eval方法即可判断表达式的值是否正确,具体思路如下:

  1. 初始化生命值,和分数
  2. 由系统给出四个数字
  3. 玩家给出答案
  4. 判断给出的答案中的数字和系统给出的数字是否吻合,如果吻合进入5,否则,生命值减一。
  5. 借助Timer类的schedule()判断是否超时,如果没有超时,进入6,否则,生命值减一。
  6. 借助script引擎的eval()方法可以判断表达式是否正确,如果正确,分数加一,否则,生命值减一。
    当生命值大于0时执行2,3,4,5,6操做,否则将当前分数写入文件。
  • 流程图如下图所示
    java实现24点游戏

编码

Game类,主要实现由系统随机生成数据,并计算可能的表达式
为了避免除零异常采用了float类型的数组进行存储

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Scanner;
import java.util.Set;

/**
 * 
 * Title: Test Description: 1. 采用随机数生成4个符合要求的数据,假设四个数据为n1,n2,n3,n4 。
 * 2.把数据相互组合可以得到如下组合:n1和n2 ,n1和n3,n1和n4,n2和n3,n2和n4。
 * 3.将上面的组合进行各种可能的运算例如:n1+n2,n1-n2,n2-n1,n1*n2,n1/n2,n2/n1等等。 
 * 4.把上面组合计算出来的结果存入进对应的数组中例如:组合相加的结果存入add数组,相减的结果存入sub数组……最终将这些数组存入一个list集合中,目的是为了方便通过循环遍历出每一种组合。
 * 5.通过循环去遍历每一种组合,把这些组合在一起进行相加,相减等运算,记录结果为24的表达式
 * @author jianglei
 */
public class Game {
	static Set<String> set = new HashSet<String>();// 使用Set存储算式表达式可以避免有重复的结果
	float[] data = new float[4];// 存放数据的集合
	float[] add = new float[6];// 存放组合相加的结果的集合
	float[] sub1 = new float[6];// 存放相减的结果(两个数相减的结果存在两种可能)
	float[] sub2 = new float[6];// 存放相减的结果
	float[] mul = new float[6];// 存放相乘
	float[] div1 = new float[6];// 存放相除(两个数相除的结果存在两种可能)
	float[] div2 = new float[6];// 存放相除
	List<float[]> list = new ArrayList<float[]>();// 存放组合结果运算的集合

	/**
	 * 
	 * Title: combine
	 * </p>
	 * Description: 该方法用来获取每两个数字的组合组合, 用1代表第一个数字,2代表第二个数字....共有六种组合方式,分别如下:
	 * 1和2,1和3,1和 4,2和 3,2和 4,3和 4 在上面的组合中,每一种组合都对应着 加减乘除 这四种运算
	 * 将上述的组合的每种计算结果存入上面声明的集合
	 */
	public void conbine(float n1, float n2, float n3, float n4) {
		add[0] = n1 + n2;
		add[1] = n1 + n3;
		add[2] = n1 + n4;
		add[3] = n2 + n3;
		add[4] = n2 + n4;
		add[5] = n3 + n4;

		sub1[0] = n1 - n2;
		sub1[1] = n1 - n3;
		sub1[2] = n1 - n4;
		sub1[3] = n2 - n3;
		sub1[4] = n2 - n4;
		sub1[5] = n3 - n4;
		sub1[5] = n3 + n4;

		sub2[0] = n2 - n1;
		sub2[1] = n3 - n1;
		sub2[2] = n4 - n1;
		sub2[3] = n3 - n2;
		sub2[4] = n4 - n2;
		sub2[5] = n4 - n3;

		mul[0] = n2 * n1;
		mul[1] = n3 * n1;
		mul[2] = n4 * n1;
		mul[3] = n3 * n2;
		mul[4] = n4 * n2;
		mul[5] = n4 * n3;

		div1[0] = n1 / n2;
		div1[1] = n1 / n3;
		div1[2] = n1 / n4;
		div1[3] = n2 / n3;
		div1[4] = n2 / n4;
		div1[5] = n3 / n4;

		div2[0] = n2 / n1;
		div2[1] = n3 / n1;
		div2[2] = n4 / n1;
		div2[3] = n3 / n2;
		div2[4] = n4 / n2;
		div2[5] = n4 / n3;

		list.add(add);// 把各种组合加入到list集合中,方便通过循环来遍历每一种组合方式
		list.add(sub1);
		list.add(sub2);
		list.add(mul);
		list.add(div1);
		list.add(div2);
	}

	public void getData() {
		Random r = new Random();
		// 获取1——13的的数字的集合
		data[0] = r.nextInt(12) + 1;
		data[1] = r.nextInt(12) + 1;
		data[2] = r.nextInt(12) + 1;
		data[3] = r.nextInt(12) + 1;
		System.out.print("四个数字为:");
		for (float f : data)
			switch ((int) f) {// 将11,12,13,1变成J,Q,K,A
			case 1:
				System.out.print("A" + " ");
				break;
			case 11:
				System.out.print("J" + " ");
				break;
			case 12:
				System.out.print("O" + " ");
				break;
			case 13:
				System.out.print("K" + " ");
				break;
			default:
				System.out.print((int) f + " ");
				break;
			}
		System.out.println();
		boolean flag = false;// 通过该变量去判断是否存在表达式
		conbine(data[0], data[1], data[2], data[3]);
		for (int a = 0; a < 3; a++) {// 有种组合方式,分别遍历每一种组合方法
			for (int b = 0; b < 6; b++) {// 穷举每一个组合和他们之间的运算
				for (int c = 0; c < 6; c++) {// 判断每一种组合的每一种运算结果是否等于24
					if ((list.get(b)[a] + list.get(c)[5 - a]) == 24) {
						DataFormat.judge(a, b, c, data, "+", set);
						flag = true;
					}
					// 减法
					if ((list.get(b)[a] - list.get(c)[5 - a]) == 24) {
						DataFormat.judge(a, b, c, data, "-", set);
					}
					if ((list.get(b)[5 - a] - list.get(c)[a]) == 24) {
						DataFormat.judge(a, b, c, data, "-", set);
						flag = true;
					}
					// 乘法
					if ((list.get(b)[a] * list.get(c)[5 - a]) == 24) {
						DataFormat.judge(a, b, c, data, "*", set);
						flag = true;
					}
					// 除法
					if ((list.get(b)[a] / list.get(c)[5 - a]) == 24) {
						DataFormat.judge(a, b, c, data, "/", set);
						flag = true;
					}
					if ((list.get(b)[5 - a] / list.get(c)[a]) == 24) {
						DataFormat.judge(a, b, c, data, "/", set);
						flag = true;
					}
				}
			}
		}
		if (!flag)
			System.out.println("没有表达式满足条件");
	}

AutoGame类主要实现由系统给定数值,用户输入表达式来根据结过进行计分,并将分数写入文件

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;
import java.util.Scanner;
import java.util.Timer;
import java.util.TimerTask;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class AutoGame {
	static boolean flag = true;//该变量标识是否超时
	static int life = 3;//初始化生命值
	static int score=0;//初始化分数

	/**
	 *开始游戏的方法,该方法通过当前生命值判断是否结束 
	 */
	public void start() throws IOException {
		Calendar date = Calendar.getInstance();//实例化Calendar对象
		while(life>0) {//当生命值大于0才会进行
			flag=true;//初始化分数标记
			date.setTime(new Date());
			date.add(Calendar.SECOND, 20);//设置限定时间
			Timer timer = new Timer();
			//当达到限定时间将会执行run()方法,即把全局变量flag变为false
			timer.schedule(new RemindTask(), date.getTime());
			int answer = answer();
			switch(answer) {
			case -1:
				System.out.println("表达式错误!!    当前生命值为"+life+" 分数为:"+score);
				break;
			case -2:
				System.out.println("输入超时!!    当前生命值为"+life+" 分数为:"+score);
				break;
			case -3:
				System.out.println("结果错误!!    当前生命值为"+life+" 分数为:"+score);
				break;
			case 1:	
				System.out.println("正确,得到1分奖励!!    当前生命值为"+life+" 分数为:"+score);
				break;
			}
			System.out.println("----------");
		}
		System.out.println("游戏结束……分数为: "+score);//循环结束也就是生命值为0,打印游戏结束
		saveScore(score);//将玩家当前分数存入文件
		return;
	}
/**
 * 
 * Title: getData</p>  
 * Description:给定任意的数据   
 * @return
 */
	private float[] getData() {
		float[] data = new float[4];
		Random r = new Random();//随机生成四个数据存入数组中
		data[0] = r.nextInt(12) + 1;
		data[1] = r.nextInt(12) + 1;
		data[2] = r.nextInt(12) + 1;
		data[3] = r.nextInt(12) + 1;
		System.out.print("四个数字为:");
		for (float f : data)
			switch ((int) f) {
			case 1:
				System.out.print("A" + " ");
				break;
			case 11:
				System.out.print("J" + " ");
				break;
			case 12:
				System.out.print("O" + " ");
				break;
			case 13:
				System.out.print("K" + " ");
				break;
			default:
				System.out.print((int) f + " ");
				break;
			}
		System.out.println("请开始作答,时间20秒");
		return data;
	}

	/**
	 * 
	 * Title: answer</p>  
	 * Description:根据用户输入返回false或true
	 * 1.输入不含给定值,返回FALSE
	 * 2.输入超时,返回false
	 * 3.输入表达式的值不为24,返回false
	 * 否则,返回true   
	 * @return -1代表输入的表达式与系统给出的数字不吻合
	 * @return -2代表用户计算结果超时
	 * @return -3代表结果错误
	 * @return 1代表用户计算正确
	 */
	public int answer() {
		Scanner sc = new Scanner(System.in);
		float[] data = getData();//获取给定的数据
		//获取script引擎,调用eval()方法来判断表达式是否正确
		ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
		ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn");
		String exper = sc.next();
		try {
			String res = String.valueOf(scriptEngine.eval(exper));
			for (int i = 0; i < data.length; i++)
				if (!exper.contains(data[i] + "")) {//输入的表达式不含给定的值
					life--;
					return -1;
				}
			if(!flag) {//判断超时
				life--;
				return -2;
			}
			if (res.equals("24")) {//回答正確并且没有超时
				score++;//分数加一
				return 1;
			}
			life--;
		} catch (ScriptException e) {
			System.out.println("表达式输入不合法");
		}
		return -3;

	}
	/**
	 * Title: saveScore</p>  
	 * Description:   该方法表示将玩家当前分数存入TopList.txt文件
	 * @param score 需要存入的分数
	 * @throws IOException
	 */
	public static void saveScore(int score) throws IOException {
		FileOutputStream fos = new FileOutputStream("e:/TopList.txt");
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		bos.write((score+"").getBytes());//把分数写入文件
		bos.close();
		
	}
	public static void main(String[] args) throws IOException {
		saveScore(1);
	}
}
/**
 * 
* Title: RemindTask
* Description:该TimerTask并且重写run()可以实现在指定的定时时间执行run方法的内容
* @author jianglei  
 */
class RemindTask extends TimerTask {

	@Override
	public void run() {
		AutoGame.flag = false;//当超时会把全局变量flag变为false
	}

}

DataFormat类根据a b c是三个循环变量将两个组合数字的运算格式化成四个数字的表达式
由于代码比较长,在这里只留一种情况的,其余的情况与它类似

import java.util.Set;
/**
 * 
* Title: GetFoamat
* Description:   该类通过在循环中的a b c的值判断出当前所参与运算的值与参加运算的符号,假设四个值为n0,n1,n2,n3
* 形参 a、b、c为循环变量,形参n数组为参与运算的四个数字,形参op表示当前进行的运算类型,形参set为存储结果的容器

 */
public class DataFormat {
	public static void  judge(int a,int b,int c,float[]n,String op,Set<String> set) {
		StringBuilder sb = new StringBuilder();
		if(a==0) {//n0和n1 与n2和n3之间的运算
			if(b==0) {//n0+n1
				if(c==0) {//n2+n3
					sb.append("(").append(n[0]).append("+").append(n[1]).append(")").append(op).append("(").append(n[2]).append("+").append(n[3]).append(")").append("=24");
				}if(c==1) {//n2-n3
					sb.append("(").append(n[0]).append("+").append(n[1]).append(")").append(op).append("(").append(n[2]).append("-").append(n[3]).append(")").append("=24");
				}if(c==2) {//n3-n2
					sb.append("(").append(n[0]).append("+").append(n[1]).append(")").append(op).append("(").append(n[3]).append("-").append(n[2]).append(")").append("=24");
				}if(c==3) {// n2*n3
					sb.append("(").append(n[0]).append("+").append(n[1]).append(")").append(op).append("(").append(n[2]).append("*").append(n[3]).append(")").append("=24");
				}if(c==4) {// n2/n3
					sb.append("(").append(n[0]).append("+").append(n[1]).append(")").append(op).append("(").append(n[2]).append("/").append(n[3]).append(")").append("=24");
				}if(c==5) {// n3/n2
					sb.append("(").append(n[0]).append("+").append(n[1]).append(")").append(op).append("(").append(n[3]).append("/").append(n[2]).append(")").append("=24");
				}
				//将运算表达式存入容器
				set.add(sb.toString());
				//将sb清空
				sb = new StringBuilder();
			}if(b==1) {//n0-n1
			//由于这种代码篇幅比较长,在这里省略了,思路和上面的一致
			}

测试

import java.io.IOException;
import java.util.Scanner;

public class Test {
	public static void main(String[] args) throws IOException {
		Scanner sc = new Scanner(System.in);
		Game t = new Game();
		System.out.println("----------");
		System.out.println("1.随机生成数判断");
		System.out.println("2.用户输入运算式");
		System.out.println("请选择");
		char op = sc.next().charAt(0);
		switch (op) {
		case '1':
			t.getData();
			for (String str : t.set) {
				System.out.println(str.replaceAll(".0", ""));
			}
			break;
		case '2':
			AutoGame a = new AutoGame();
			a.start();
			break;
		default:
			System.out.println("输入错误");
		}
	}
}

运行结果

java实现24点游戏
java实现24点游戏
java实现24点游戏
以下用户输入表达式的运行结果
测试过程中在该情况下的测试数据是由自己给出的为1 1 12 12这四个数字
java实现24点游戏
分数存入文件
java实现24点游戏