程序设计作业之24点游戏问题
1、题目
从扑克中每次取出4张牌。使用加减乘除,第一个能得出24者为赢。(其中,J代表11,Q代表12,K代表13,A代表1),按照要求编程解决24点游戏。
要求:用户初始生命值为一给定值(比如3),初始分数为0。随机生成4个代表扑克牌牌面的数字或字母,由用户输入包含这4个数字或字母的运算表达式(可包含括号),如果表达式计算结果为24则代表用户赢了此局。
1. 程序风格良好(使用自定义注释模板)
2.使用计时器要求用户在规定时间内输入表达式,如果规定时间内运算正确则加分,超时或运算错误则进入下一题并减少生命值(不扣分)。
3.所有成绩均可记录在TopList.txt文件中。
根据题目可知该问题的关键点在于对表达式的处理,表达式包括数字和操作符。对于操作符则要有优先级的判断,然后再进行计算,因此可以利用栈来完成这个游戏。利用两个栈,一个存放操作数,一个存放运算符,通过出栈的操作进行运算来解决该问题。
2、算法设计
对表达式的处理用到中缀表达式的知识,需要建立栈,用栈来储存表达式。
以下为对栈的定义及操作。
typedef struct
{
char op;
int inputprecedence;
int stackprecedence;
}DataType1;
template <class T> //定义模板类SeqStack
class SeqStack
{
public:
SeqStack( ) ; //构造函数,栈的初始化
~SeqStack( ); //析构函数
void Push(T x); //将元素x入栈
T Pop( ); //将栈顶元素弹出
T GetTop( ); //取栈顶元素(并不删除)
bool Empty( ); //判断栈是否为空
private:
T data[StackSize]; //存放栈元素的数组
int top; //栈顶指针,指示栈顶元素在数组中的下标
};
3、以下为程序源代码
#include"stdio.h"
#include"iostream"
#include"fstream"
#include"stdlib.h"
#include"time.h"
using namespace std;
class Player//玩家类
{
private:
int life;//生命值
int score;//分数
public:
Player(); //构造函数,初始化生命值和分数
int play();
int lifelose();
int showlife();
int showscore();
int scoreadd();
};
int Player::showscore()//显示当前分数
{return score;}
int Player::showlife()//显示当前生命值
{ return life;}
int Player::lifelose()//生命值减1
{
life--;
return life;}
int Player::scoreadd()//分数减1
{ return score++;}
Player::Player()
{
life=3;//初始化生命值为3,分数为0
score=0;
}
const int StackSize=10;
typedef struct
{
char op;
int inputprecedence;
int stackprecedence;
}DataType1;
template <class T> //定义模板类SeqStack
class SeqStack
{
public:
SeqStack( ) ; //构造函数,栈的初始化
~SeqStack( ); //析构函数
void Push(T x); //将元素x入栈
T Pop( ); //将栈顶元素弹出
T GetTop( ); //取栈顶元素(并不删除)
bool Empty( ); //判断栈是否为空
private:
T data[StackSize]; //存放栈元素的数组
int top; //栈顶指针,指示栈顶元素在数组中的下标
};
template <class T>
SeqStack<T>::SeqStack( )//栈的初始化
{ top=-1;}
template <class T>
SeqStack<T>::~SeqStack( ){} //释放栈所占用的存储空间
template <class T>
void SeqStack<T>::Push(T x)//在栈顶插入一个元素x
{
if (top== StackSize-1) throw "上溢";//如果插入不成功,抛出异常
top++;
data[top]=x;
}
template <class T>
T SeqStack<T>::GetTop( )//读取当前的栈顶元素
{
if (top!=-1)
return data[top];
}
template <class T>
bool SeqStack<T>::Empty( )//判断栈是否为空
{
if(top==-1) return 1;
else return 0;
}
template <class T>//删除栈顶元素
T SeqStack<T>::Pop( )
{
T x;
if (top==-1) throw "下溢";
x=data[top--];
return x;
}
DataType1 MathOptr(char ch) //为不同的运算符赋值优先级
{
DataType1 optr;
optr.op=ch;
switch(optr.op)
{
case'+':
case'-': optr.inputprecedence=1;
optr.stackprecedence=1;
break;
case'*':
case'/': optr.inputprecedence=2;
optr.stackprecedence=2;
break;
case'(': optr.inputprecedence=3;
optr.stackprecedence=-1;
break;
case')': optr.inputprecedence=0;
optr.stackprecedence=0;
break;
}
return(optr);
}
int isoptr(char ch)//判断输入的字符是否为算术运算符
{
if(ch=='+'||ch=='-'||ch=='*'||ch=='/')
return(1);
return(0);
}
template <class T>
void Evaluate(SeqStack<T>*OpndStack,DataType1 optr)//计算某一符号的表达式的值,结果进数据栈;
{
T opnd1,opnd2;
opnd1=OpndStack->Pop();
opnd2=OpndStack->Pop();
switch(optr.op)
{
case'+':OpndStack->Push(opnd2+opnd1);
break;
case'-':OpndStack->Push(opnd2-opnd1);
break;
case'*':OpndStack->Push(opnd2*opnd1);
break;
case'/':OpndStack->Push(opnd2/opnd1);
break;
}
}
int Infix(char *str)//中缀表达式的求值
{
int i,k;
char ch,numstr[10];
float opnd;
DataType1 optr,temp;
SeqStack<float> OpndStack;//运算对象栈
SeqStack<DataType1> OptrStack;//运算符栈
k=0;
ch=str[k];
while(ch!='=')
if(isdigit(ch)||ch=='.')//数据处理
{
for(i=0;isdigit(ch)||ch=='.';i++)
{
numstr[i]=ch;
k++;
ch=str[k];
}
numstr[i]='\0';
opnd=atof(numstr);//把字符串转化为浮点数
OpndStack.Push(opnd);//操作数进栈
}
else//运算符处理
{
optr=MathOptr(ch);//ch的优先级赋值
if(ch=='(')
{
OptrStack.Push(optr);
k++;
ch=str[k];
}
else if(isoptr(ch))
{
while(!OptrStack.Empty () && OptrStack.GetTop().stackprecedence>=optr.inputprecedence)
Evaluate(&OpndStack,OptrStack.Pop());
OptrStack.Push(optr);
k++;
ch=str[k];
}
else if(ch==')')
{
while(!OptrStack.Empty () && OptrStack.GetTop().op!='(')
Evaluate(&OpndStack,OptrStack.Pop());
if(!OptrStack.Empty ())
temp=OptrStack.Pop();
k++;
ch=str[k];
}
}//表达式结束
while(!OptrStack.Empty ())
Evaluate(&OpndStack,OptrStack.Pop());
opnd=OpndStack.Pop();
return opnd;
}
int play()
{
Player p;
int arr[4],i,n=0,result,l,s=0,j=1;
char str[50];
char c='y';
clock_t start, finish;
double duration;
unsigned int seed; /*申明初始化器的种子,注意是unsigned int 型的*/
cout<<"游戏开始,您的初始生命值为3!"<<endl;
while(c=='y')
{
printf("请随机输入一个正整数作为随机数种子: \n");
scanf("%u",&seed);
srand(seed);
for(i=0;i<4;i++)
{
arr[i]=rand()%14;
}
cout<<"系统随机产生的四个数为:";
for(i=0;i<4;i++)
{
cout<<arr[i]<<" ";
}
cout<<endl<<"请用这四个数输入包含这4个数字的运算表达式(可包含括号,以'='结束)!"<<endl;
fflush(stdin);
start=clock();
gets(str);//从stdio流中读取字符串,直至接受到换行符或EOF时停止,
finish=clock();
duration=(finish-start)/CLOCKS_PER_SEC;
if(duration>20)
{
cout<<"对不起,您超时了,生命值减1!"<<endl;
l=p.lifelose();
cout<<"您当前生命值为:"<<l<<endl;
}
else result=Infix(str);//中缀表达式的求值
if(result==24)
{ cout<<"恭喜您计算正确!"<<endl;
p.scoreadd();
s=p.showscore();
cout<<"您当前的分数为:"<<s<<endl;
}
else {
cout<<"对不起,结果错误,生命值减1!"<<endl;
l=p.lifelose();
cout<<"您当前生命值为:"<<l<<endl;
cout<<"您当前的分数为:"<<s<<endl;
}
ofstream f("TopList.txt", ios::app);//打开文件,没有则创建该文件
f <<"第"<<j<<"轮分数为 "<< s<<endl; //写入数据
f.close();//关闭文件
if(p.showlife()==0)
{
cout<<"您的生命值为零,游戏结束!"<<endl;
return 0;
}
cout<<"是否继续下一轮(输入'y'(继续)or'n'(退出))";
cin>>c;
if(c=='n') break;
j++;
}
}
void help()
{
cout<<"1.系统会随机生成4个代表扑克牌牌面的数字"<<endl;
cout<<"您输入包含这4个数字或字母的运算表达式(可包含括号)"<<endl;
cout<<"3.如果表达式计算结果为24则代表您赢了此局。"<<endl;
cout<<"4.当输入表达式的时间超过20秒时会失去一点生命生命"<<endl;
cout<<"5.当生命值消耗完时游戏结束"<<endl;
}
void main()
{
int i=1;
char s='y';
while(s=='y')
{
cout<<"请选择游戏菜单!"<<endl;
cout<<"1.开始游戏"<<endl;
cout<<"2.游戏帮助"<<endl;
cout<<"3.结束游戏!"<<endl;
cin>>i;
switch(i)
{
case 1: play(); break;
case 2: help(); break;
case 3: exit(0);
}
cout<<"是否开始新的一局游戏(输入'y'(继续)or'n'(退出游戏)";
cin>>s;
}
}
4、结果截图
游戏界面
系统随机输出一组数,玩家根据系统输出的数字,进行组合输入一个表达式,如果表达式结果不等于24则给出提示错误并且生命值减1,如果输入表达式的时间过长大于20秒则给出时间过长提示,生命值再次减1.
当输入表达式能够计算出24时,系统给出提示,并且分数加1,继续询问玩家是否进行下一轮。
经过多次进局游戏后,可以看到文件中写入了每一轮的分数,如果玩家得到分数,则分数累计到1.
4、总结
这次作业的难度较大,特别是要用到栈的内容。通过这次作业熟悉了对栈的建立及操作,如初始化栈、删除栈元素、读取栈元素等,这些知识都是数据结构中很重要的知识,通过这次练习更加建立了我对栈的认识,对栈的知识还要多加熟悉,希望以后也能够进行运用并处理实际问题。其次是我的文件读写能力增加了一些,比起刚开始做作业有了很大提升,已经能够对文件的打开方式,写入文件方式较熟练运用了。最后是对类的知识的巩固,在这次作业中,用到了类模板的知识,这部分内容较难理解,但方便解决问题,因此对于这部分内容接下来也要再次进行复习运用。