一、问题描述:
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文件中。
二、分析:
对于基本要求,我可以通过random函数来生成随机数;把生成的四个随机数存储在数组中;然后对数组进行全排列;对于每一种排列,分别进行加减乘除运算;看运算的结果是否为24;如果为24直接把式子输出出来。
对于提高要求,我可以设计一个game类来进行游戏,先对用户输入的数据进行检查。用户输入的表达式用java自带的JavaScript引擎去把字符串转换从语句,然后执行,计算出结果。
三、具体设计:
程序流程图:
代码如下:
package com.lyq.point;
import java.util.Random;
public class Point24 {
public int[] cards = new int[4]; //存储四张牌的数组
public char[] operators = new char[3]; //存放运算操作
public Point24() {
Random rd = new Random(); //随机产生四张牌
for(int i=0;i<4;i++) {
cards[i]=rd.nextInt(13)+1;
}
}
public void show() { //显示四张牌
System.out.println("你有四张牌,他们对应的值为:");
for(int i=0;i<4;i++) {
System.out.print(cards[i]+" ");
}
System.out.println("");
System.out.println("--------");
}
public void getOpertors(int[] Num){ //把操作符放入operators
for(int i=0;i<3;i++){
int op=Num[i];
switch (op) {
case 0: operators[i]='+'; break;
case 1: operators[i]='-'; break;
case 2: operators[i]='*'; break;
case 3: operators[i]='/'; break;
default: break;
}
}
}
public void Sort() { //对操作数进行全排序
int[] temp = new int[4];
for(int i=0;i<4;i++)
{
temp[i]=cards[i];
}
for(int i=0;i<4;i++) {
cards[0]=cards[i];
int j=(i+1)%4;
for(int k=0;k<3;){
if (j!=i) {
cards[1]=temp[j];
int e = (j+1)%4;
for(int h=0;h<2;)
{
if (e!=j&&e!=i) {
cards[2] = temp[e];
cards[3] = temp[6-i-j-e];
//System.out.println(cards[0]+" "+cards[1]+" "+cards[2]+" "+cards[3]);
this.opertor(); //排序完成后进行运算
h++;
}
e = (e+1)%4;
}
k++;
}
j = (j+1)%4;
}
}
}
public void opertor(){ //对数组中两两相邻元素进行运算
double sum=0;
for(int i=0;i<4;i++){
double sum1=count(cards[0],cards[1],i);
for(int j=0;j<4;j++){
double sum2=count(sum1,cards[2],j);
for(int k=0;k<4;k++){
sum=count(sum2,cards[3],k);
int[] symbolNum={i,j,k}; //记录运算的操作符
getOpertors(symbolNum);
if(sum==24){
System.out.println("["+"("+cards[0]+" "+operators[0]+" "+cards[1]+")"+" "+operators[1]+" "+cards[2]+"]"+" "+operators[2]+" "+cards[3]+"==24");
}
}
}
}
}
//对两个数据进行加减乘除运算
public static double count(double num1,double num2,int num){
double sum=0.0;
switch (num) {
case 0:
sum = num1+num2;
break;
case 1:
sum = num1-num2;
break;
case 2:
sum = num1*num2;
break;
case 3:
sum = num1/num2;
break;
}
return sum;
}
public void refresh() {
Random rd = new Random(); //随机产生四张牌
for(int i=0;i<4;i++) {
cards[i]=rd.nextInt(13)+1;
}
}
//用main函数进行测试
public static void main(String[] args) {
Point24 op = new Point24();
op.show();
System.out.println("可以算出24的表达式如下:");
op.Sort();
}
}
Game类
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Game {
public int life = 3;
public int score = 0;
public Game() { //构造函数
// TODO Auto-generated constructor stub
Point24 op = new Point24();
boolean flag = true;
while(flag)
{
System.out.println("当前你的生命值为:"+life+" ");
System.out.println("当前你的分数为: "+score+" ");
op.show();
System.out.println("请输入表达式:(输入时间为一分钟)");
String input = null;
Scanner sc = new Scanner(System.in);
input = sc.nextLine();
if(check(input)) {
score+=10;
System.out.println("答案正确");
}else {
life--;
System.out.println("答案错误");
if (life==0) {
System.out.println("抱歉,你的生命值已经耗尽");
System.out.println("请问是否要存储你本次的分数:y/n");
if (sc.nextLine()=="y") {
write(score+" "); //写入文件
}
flag = false;
}
}
sc.close();
op.refresh();
System.out.println("");
System.out.println("");
System.out.println("");
}
}
public void write(String str) { //把分数写入文件
BufferedWriter bd = null;
try {
bd = new BufferedWriter(new FileWriter("/Student/src/com/lyq/point/TopList.txt"));
bd.write(str);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
bd.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public boolean check(String str) { //判断输入表达式是否正确
boolean flag = false;
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
try {
Object result = engine.eval(str);
if (result.equals(24)) { //计算表达式是否等于24
flag = true;
}
} catch (ScriptException e) {
// TODO Auto-generated catch block
System.out.println("输入错误");
}
return flag;
}
public static void main(String[] args) {
new Game();
}
}
四、调试与结果
五、总结
通过这次作业,我知道了如何对数组进行全排列,这是一个不是很简单的过程,我自己写的这个算法十分复杂,有三个for循环嵌套,而且有大量的变量来控制这一个过程。我也了解到还有一些其它算法
递归算法:
这里以A{a,b,c}为例,来说明全排列的生成方法,对于这个集合,其包含3个元素,所有的排列情况有3!=6种,对于每一种排列,其第一个元素有3种选择a,b,c,对于第一个元素为a的排列,其第二个元素有2种选择b,c;第一个元素为b的排列,第二个元素也有2种选择a,c,……,依次类推,我们可以将集合的全排列与一棵多叉树对应;在此树中,每一个从树根到叶子节点的路径,就对应了集合A的一个排列。通过递归算法,可以避免多叉树的构建过程,直接生成集合A的全排列。
字典序:
全排列生成算法的一个重要思路,就是将集合A中的元素的排列,与某种顺序建立一一映射的关系,按照这种顺序,将集合的所有排列全部输出。这种顺序需要保证,既可以输出全部的排列,又不能重复输出某种排列,或者循环输出一部分排列。字典序就是用此种思想输出全排列的一种方式。这里以A{1,2,3,4}来说明用字典序输出全排列的方法。
首先,对于集合A的某种排列所形成的序列,字典序是比较序列大小的一种方式。以A{1,2,3,4}为例,其所形成的排列1234<1243,比较的方法是从前到后依次比较两个序列的对应元素,如果当前位置对应元素相同,则继续比较下一个位置,直到第一个元素不同的位置为止,元素值大的元素在字典序中就大于元素值小的元素。上面的a1[1…4]=1234和a2[1…4]=1243,对于i=1,i=2,两序列的对应元素相等,但是当i=2时,有a1[2]=3<a2[2]=4,所以1234<1243。
使用字典序输出全排列的思路是,首先输出字典序最小的排列,然后输出字典序次小的排列,……,最后输出字典序最大的排列。这里就涉及到一个问题,对于一个已知排列,如何求出其字典序中的下一个排列。这里给出算法。
对于排列a[1…n],找到所有满足a[k]<ak+1的k的最大值,如果这样的k不存在,则说明当前排列已经是a的所有排列中字典序最大者,所有排列输出完毕。
在a[k+1…n]中,寻找满足这样条件的元素l,使得在所有a[l]>a[k]的元素中,a[l]取得最小值。也就是说a[l]>a[k],但是小于所有其他大于a[k]的元素。
交换a[l]与a[k].
对于a[k+1…n],反转该区间内元素的顺序。也就是说a[k+1]与a[n]交换,a[k+2]与a[n-1]交换,……,这样就得到了a[1…n]在字典序中的下一个排列。
这里我们以排列a[1…8]=13876542为例,来解释一下上述算法。首先我们发现,1(38)76542,括号位置是第一处满足a[k]<a[k+1]的位置,此时k=2。所以我们在a[3…8]的区间内寻找比a[2]=3大的最小元素,找到a[7]=4满足条件,交换a[2]和a[7]得到新排列14876532,对于此排列的3~8区间,反转该区间的元素,将a[3]-a[8],a[4]-a[7],a[5]-a[6]分别交换,就得到了13876542字典序的下一个元素14235678。
通过这次作业,我还了解到了,如何在java中运行JavaScript代码的;即通过使用javax.script下的JavaScript引擎,来把字符串转化成JavaScript代码,并运行。