问题描述
24点游戏是经典的纸牌益智游戏。
常见游戏规则:
从扑克中每次取出4张牌。使用加减乘除,第一个能得出24者为赢。(其中,J代表11,Q代表12,K代表13,A代表1),按照要求编程解决24点游戏。
基本要求: 随机生成4个代表扑克牌牌面的数字字母,程序自动列出所有可能算出24的表达式,用擅长的语言(C/C++/Java或其他均可)实现程序解决问题。
1.程序风格良好(使用自定义注释模板)
2.列出表达式无重复。
提高要求:用户初始生命值为一给定值(比如3),初始分数为0。随机生成4个代表扑克牌牌面的数字或字母,由用户输入包含这4个数字或字母的运算表达式(可包含括号),如果表达式计算结果为24则代表用户赢了此局。
1. 程序风格良好(使用自定义注释模板)
2.使用计时器要求用户在规定时间内输入表达式,如果规定时间内运算正确则加分,超时或运算错误则进入下一题并减少生命值(不扣分)。
3.所有成绩均可记录在TopList.txt文件中。
算法实现
2.1 基本要求设计思路
大体是思路上采用穷举法,即列举出四个数字的所有排列组合情况,以及所有运算符的排列组合情况。每种数字组合都默认优先级从第一位开始,相当与第一位和第二位现进行运算,其运算结果在于第三位进行运算,最后上面运算结果在与第四位进行运算。即满足((a#b)#c)#c。
利用二维数组operator[24][4]列举出24种数字的组合情况,利用for循环结构循环查找每一个数字组合中符合最后结果等于24的运算符组合方式。实现这一要求,需要利用利用四个for循环。
2.2 提高要求设计思路
提高要求大体思路是:用户根据随机生成的四个属列出中缀表达式,根据用户输入的中缀表达式(含括号)转化为后缀表达式。利用中缀表达式求出最终值的大小。判断最终值大小是否为24。
中缀表达式转化为后缀表达式是实践核心算法。利用栈堆实现这一功能。把输入的中缀表达式转成后缀表达式,利用正则表达式来处理数字,然后利用栈来得出结果。转化的思路如下:
(1)如果遇到操作数,直接输出,添加到后缀表达式之后;
(2)如果遇到运算符(+ - * /)且此时栈为空,将运算符入栈;
(3)如果遇到运算符(+ - * /)且此时栈为不为空,先弹出优先级高与或等于扫描到的运算符的运算符,弹出后的运算符写入后缀表达式之后,并将扫描得到运算符压入栈中;
(4)如果遇到括号(“(”)直接压入栈中;
(5)如果遇到括号(“)”)弹出最近一个左括号及其上面的所有运算符,并顺序地写入后缀表达式中。
最终将栈中的元素依次出栈,输出。
计时器的处理上,调用System.currentTimeMillis()获取当前的时间,定义两个变量starttime和endtime计算出输入表达式的值是否控制在1分钟内。
流程图
源码
package game24;
import java.util.Random;
public class Game24
{
public float[] Generate4Num()//随机产生四个数,即为四个纸牌
{
Random ran =new Random();
float[] num =new float[4];
//依次产生这四个数
for(int i = 0 ;i<num.length ;i++ )
{
int k =ran.nextInt(13)+1;
num[i] = (float)(k);
}
return num;
}
public void print4Num(float num[]) //输出随机产生的四个数
{
System.out.print("随机产生的这四张牌为: ");
for(int i = 0 ; i<num.length ;i++)
{
if(num[i]==1.0|num[i]==11.0|num[i]==12.0|num[i]==13.0)
{
switch((int)num[i])
{
case 1: System.out.print("A(1) ");break;
case 11:System.out.print("J(11) ");break;
case 12:System.out.print("Q(12) ");break;
case 13:System.out.print("K(13) ");break;
default:break;
}
}
else
{
int k =(int) num[i];
System.out.print( k + " " );
}
}
System.out.println();
}
//获取四个数字进行循环判断符合的表达式
public String game24(float num[])
{
//将数组中四个值分别赋值给a\b\c\d
float a = num[0];
float b = num[1];
float c = num[2];
float d = num[3];
//操作数数组,存放四个操作数(4个数的排列组合情况共有24种)
float operator[][] = {{a,b,c,d},{a,b,d,c},{a,c,b,d},{a,c,d,b},{a,d,b,c},{a,d,c,b}
,{b,a,c,d},{b,a,d,c},{b,c,a,d},{b,c,d,a},{b,d,a,c},{b,d,c,a}
,{c,a,b,d},{c,a,b,b},{c,b,a,d},{c,b,d,a},{c,d,a,b},{c,d,b,a}
,{d,a,b,c},{d,a,c,b},{d,b,a,c},{d,b,c,a},{d,c,a,b},{d,c,b,a}};
//float operator[][] = new float[24][4];
char []symbol= {'+','-','*','/'};//运算数组,存放四个运算符号即加减乘除
//依次循环这24种数字情况
for(int i = 0 ;i < 24 ; i++)
{ //依次循环这所有运算符号可能的排列组合方式 ,共有4*4*4=64种
for(int k1 = 0 ; k1 < 4 ;k1++)
{
for(int k2 = 0 ; k2 < 4 ;k2++)
{
for(int k3 = 0 ; k3 < 4 ; k3++)
{
//计算前两个数
float m1 = fun(operator[i][0],operator[i][1],symbol[k1]);
//计算前两个数的运算结果结果和第三个数
float m2 = fun(m1,operator[i][2],symbol[k2]);
//
float m3 = fun(m2,operator[i][3],symbol[k3]);
//如果最后运算结果为24的话,输出运算过程
if(m3 == 24.0 )
{
// System.out.print(operator[i][0]+symbol[k1]+operator[i][1]+"="+m1+" ");
// System.out.print(m1+symbol[k2]+operator[i][2]+"="+m2+" ");
// System.out.print(m2+symbol[k3]+operator[i][3]+"="+m3+" ");
System.out.println("(("+operator[i][0]+symbol[k1]+operator[i][1]
+")"+symbol[k2]+operator[i][2]+")"
+symbol[k3]+operator[i][3]);
}
}
}
}
}
System.out.println("输出1完毕");
return null;
}
//依据传来的参数执行相应的加减乘除运算
public float fun(float f, float g, char c) {
// TODO 自动生成的方法存根
switch(c)
{
case '+':return f+g;
case '-':return f-g;
case '*':return f*g;
case '/':return f/g;
default:break;
}
return 0;
}
}
package game24;
import java.util.Scanner;
public class Test01 {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Game24 game = new Game24();
float[] num =new float[4];
num=game.Generate4Num();
game.print4Num(num);
//float[] num = {11,2,3,4};
game.game24(num);
}
}
基本功能运行结果
package game24;
/**
* 测试二
* 完成提高要求:模拟游戏
* 用户输入随机生成的含四个数字的中缀表达式,判断返回的表达式的值是否为24
*/
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
public class Test02 {
public static float lifevalue = 3; //初始化生命值为3.0
public static float score = 0; //初始化分数为0.0
public static void main(String[] args) {
System.out.println("-------提高要求----------");
boolean flag = true ; //中间变量,控制游戏进程
float res = 0; //表达式计算结果
while(lifevalue>0) //生命值还存在
{
System.out.println("开始(1)还是退出(2)?");
Scanner scan = new Scanner(System.in);
int m = scan.nextInt();
switch(m)
{
case 1:gameplay();break; //游戏开始
case 2:{ //游戏中途退出
System.out.println("当前生命值为"+lifevalue);
System.out.println("当前分数为"+score);
String s1 = "分数为"+Float.toString(score);
try {
filepreserve(s1);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return;
}
default:break;
}
}
//生命值为0,游戏结束
System.out.println("Game over!");
System.out.println("当前分数为"+score);
//将结果输入文件中
String s = "分数为"+Float.toString(score);
try {
filepreserve(s);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
private static void filepreserve(String s) throws IOException {
// TODO 自动生成的方法存根
byte[] bs =s.getBytes();//定义字节数组当作缓冲区
FileOutputStream file = new FileOutputStream("TopList.txt",true);
try {
file.write(bs);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
file.close();
}
public static void gameplay()
{
long starttime = 0; //开始时间
long endtime = 0; //结束时间
long runtime; //输入表达式时间
//定义game对象,随机生成4个数字并输出
Game24 game = new Game24();
float[] num =new float[4];
num=game.Generate4Num();
game.print4Num(num);
System.out.println("请输入表达式");
//获取开始的时间
starttime =System.currentTimeMillis();
Scanner in=new Scanner(System.in);
//定义midToBack类对象 计算中缀表达式转化为后缀表达式的值
midToBack mtb=new midToBack();
//返回计算后的值
float result = mtb.calculate();
//获取输入结束的时间
endtime =System.currentTimeMillis();
//获取输入的时间
runtime = endtime - starttime;
//判断是否超时输入
if(runtime <1000*60 )
{
//没有超时输入,判断表达式值是否为24
if(result==24.0)
{
System.out.println("回答正确!!!");
score++;
System.out.println("当前生命值为"+lifevalue);
System.out.println("当前分数为"+score);
}
else
{
System.out.println("回答错误!!!");
lifevalue--;
System.out.println("当前生命值为"+lifevalue);
System.out.println("当前分数为"+score);
}
}
else
{
System.out.println("输入超时");
lifevalue--;
System.out.println("当前生命值为"+lifevalue);
System.out.println("当前分数为"+score);
}
}
}
package game24;
/**
* 该类实现把输入的中缀表达式转成后缀表达式,利用正则表达式来处理数字,然后利用栈来得出结果
* 思路:
* (1)如果遇到操作数,直接输出,添加到后缀表达式之后;
(2)如果遇到运算符(+ - * /)且此时栈为空,将运算符入栈;
(3)如果遇到运算符(+ - * /)且此时栈为不为空,先弹出优先级高与或等于扫描到的运算符的运算符,弹出后的运算符写入后缀表达式之后,并将扫描得到运算符压入栈中;
(4)如果遇到括号(“(”)直接压入栈中;
(5)如果遇到括号(“)”)弹出最近一个左括号及其上面的所有运算符,并顺序地写入后缀表达式中。
*/
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Scanner;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class midToBack {
public float calculate() {
// TODO Auto-generated method stub
Scanner in=new Scanner(System.in);
//获取输入的中缀表达式字符串
String s=in.nextLine();
//计算表达之结果值
double res = 0;
try {
//定义存放后缀表达式数字运算符值集合List容器
List<Object> list = trans(s);
//定义存放运算符的栈堆
Stack<Double> result = new Stack<Double>();
//计算表达式的值
res=f(list,result);
System.out.println(res);
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("表达式不合法!");
}
//将计算后的值返回
return (float) res;
}
//计算转化后的后缀表达式的值
public double f(List<Object> list, Stack<Double> result) {
// TODO Auto-generated method stub
Iterator it=list.iterator();
while(it.hasNext()){
String m=it.next().toString();
if (m.equals("+")||m.equals("-")||m.equals("*")||m.equals("/")) {
double b=result.pop();
double a=result.pop();
double v=g(a,b,m);
result.push(v);
}else {
result.push(Double.valueOf(m));
}
}
return(result.pop());
}
public double g(double a, double b, String m) {
// TODO Auto-generated method stub
double v=0;
switch (m)
{
case "+":
v=a+b;
break;
case "-":
v=a-b;
break;
case "*":
v=a*b;
break;
case "/":
v=a/b;
break;
}
return v;
}
public List<Object> trans(String s) {
// TODO Auto-generated method stub
Stack<Character> op=new Stack<Character>();
ArrayList<Object> list=new ArrayList<Object>();
Pattern P=Pattern.compile("[0-9]+(\\.[0-9]+)?"); //正则表达式来处理带小数点的数字
int i=0;
while(i<s.length()){
char c=s.charAt(i);
if (c>='0'&&c<='9') {
String s1=s.substring(i);
Matcher m =P.matcher(s1);
if (m.find()) { //取匹配到的第一个数字
s1=m.group();
list.add(s1);
}
i=i+s1.length();
continue;
}else if (c=='(') {
op.push(c);
}else if (c==')') {
char p=op.pop();
while(p!='('){
list.add(p);
p=op.pop();
}
}else if (c=='+'||c=='-') {
while(!op.isEmpty()&&(op.peek()=='+'||op.peek()=='-'||
op.peek()=='*'||op.peek()=='/')){
list.add(op.pop());
}
op.push(c);
}else if (c=='*'||c=='/') {
while(!op.isEmpty()&&(op.peek()=='*'||op.peek()=='/')){
list.add(op.pop());
}
op.push(c);
}
i++;
}
while(!op.isEmpty()){
list.add(op.pop());
}
return list;
}
}
实践心得
通过本次实践,有了以下的体会:
(1)列出所有的表达式采用穷举法,即列出24种数字的排列组合,64中符号的自由组合。虽然可以通过多个for循环实现这一功能,不过也出现了一些重复的表达式。
(2)实现提高要求上,利用栈完成中缀表达式转化为后缀表达式后求值的思路。该部分代码借鉴自网络。这部分是用Java中的集合栈堆的知识,这部分最近正好要开始学这部分,利用此次机会了解这部分的知识。
(3)了解了一些Java语法知识上的本质。关于时间上的出来一开始是像采用Date类,后来通过了解到了Date实质还是调用了System中system.currentTimeMill()方法进行时间的获取(从1970到目前时刻的毫秒数)有了这个基础,计算出用户输入的时间就迎刃而解了。
(4)借此借鉴的源码(中缀表达式转化为后缀表达式),以此为案例学习了集合List,Stack栈堆的方法操作。如:pop()出栈并返回元素值。