题目
设计一个带有记分功能的24点游戏(简易版本,只使用1-10之间的数)
要求:游戏开始,程序随机自动生成四张牌(以1-10之间的数值代替),游戏者要求尽快给出表达式,只能使用 + - × % 运算。可以使用(),不涉及小数运算。比如机器给出四个数为3、3、6、2,游戏者输入表达式:(3+3-2)×6,程序检查运算成功,根据要求3计算时间。如果给出的四个数不能得出24,如:1、2、1、3,那么游戏者必须输入NO,要求计算机进入下一题,但此次如果是正确判断一样记分。如此玩3次,计算总分。任何一次在要求时间内不能给出表达式或NO,那么游戏此轮结束。
分析题目
本题有三个难点:
- 是本题的核心问题,如何判断随机给的四个数能否算出24;
- 题目要求如果可以算出24,则要输入表达式,也就是要求中缀表达式转为后缀表达式然后再求值;
- 限时输入,如果到一定时间还没输入成功则该回合得0分;
思路
首先利用结构体,注意初始化:
struct data
{
int n; //存数
char c; //存符号
data()
{
n = -1;
c = '#';
}
};
接着逐一突破难点。首先是第一个问题,这里打算用递归解决:
bool count24(int n)
{
int i, j;
double p, q;
if (n == 1)
{
if (fabs(a[0] - 24) < 1e-3)
return true;
else
return false;
}
for (i = 0; i < n - 1; i++)
{
for (j = i + 1; j < n; j++)
{
p = a[i]; //p记录第一位
q = a[j]; //q记录第二位
a[j] = a[n - 1]; //将最后一位前移
//开始执行递归
a[i] = p + q; //首位将p,q相加,再不停递归相加
if (count24(n - 1))
return true;
//注意相减时出现负数的情况
a[i] = p - q;
if (count24(n - 1))
return true;
a[i] = q - p;
if (count24(n - 1))
return true;
a[i] = p * q;
if (count24(n - 1))
return true;
//注意分母为0的情况
if (q != 0)
{
a[i] = p / q;
if (count24(n - 1))
return true;
}
if (p != 0)
{
a[i] = q / p;
if (count24(n - 1))
return true;
}
a[i] = p;
a[j] = q;
}
}
return false;
}
举例说明递归:
- 第一次[a,b,c,d]传入,变成[c,d,X],X是a和b的和,差,积,商(4种情况)
- 第二次[c,d,X]传入,变成[X,Y],Y是c和d的和,差,积,商(4种情况)
- 第三次[X,Y]传入,变成[Z],Z是X和Y的和,差,积,商(4种情况)
- 最后判断Z是不是24
接下来解决手动输入中缀表达式转为后缀,然后求值。这需要使用栈来辅助解决。首先要明白中缀转后缀的规则:
从左到右扫描字符串
- 如果遇到操作数,我们就直接将其输出。
- 如果遇到操作符,当栈为空直接进栈;不为空,判断栈顶元素操作符优先级是否比当前操作符小,如果比当前操作符小则直接把当前操作符进栈;比当前操作符大则栈顶元素循环弹栈,直到栈顶元素操作符优先级比当前操作符小
- 遇到左括号时我们也将其放入栈中。
- 如果遇到一个右括号,则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止。注意,左括号只弹出并不输出。
- 如果我们读到了输入的末尾,则将栈中所有元素依次弹出。
按照这个规则配合STL即可将元素提取出,代码如下:
mp['+'] = mp['-'] = 1; //利用散列表模拟运算符号的优先级
mp['*'] = mp['/'] = 2;
vector<char> rpn; //后缀数组
void transform(string s)
{
rpn.clear();
stack<data> stk;
for (int i = 0; i < s.size(); i++)
{
//如果是数字则直接入栈
if (i < s.size() && s[i] >= '0' && s[i] <= '9')
{
data node;
node.n = s[i] - '0';
//因为可能存在10这种两位数,所以要往前判断是否为数字
int rec = i + 1, flag = 0;
while (rec < s.size() && s[rec] >= '0' && s[rec] <= '9')
{
flag = 1;
node.n = node.n * 10 + s[rec] - '0';
rec++;
}
if (flag) //如果前一位也是数字,则i要往前自增一位
++i;
rpn.push_back(node);
}
else if (s[i] == '(') //左括号直接入栈
{
data node;
node.c = s[i];
stk.push(node);
}
else if (s[i] == ')') //右括号则循环弹栈,直到遇到左括号
{
while (stk.top().c != '(')
{
rpn.push_back(stk.top());
stk.pop();
}
stk.pop(); //注意把左括号也弹掉
}
else //否则是符号
{
//如果栈顶符号优先级大于等于当前符号优先级,则弹栈
while (!stk.empty() && mp[stk.top().c] >= mp[s[i]])
{
rpn.push_back(stk.top());
stk.pop();
}
data node;
node.c = s[i];
stk.push(node);
}
}
while (!stk.empty()) //最后如果栈不为空,则全部输出
{
rpn.push_back(stk.top());
stk.pop();
}
}
由中缀转为的后缀表达式已经全部放入vector容器中,接着只要将后缀表达式求值就行了。这里同样使用了一个栈,从头遍历vector,当是数字时入栈,当是符号时将栈顶两个元素弹出,进行该符号的运算再入栈。代码如下:
int count(char c, int a, int b)
{
switch (c)
{
case '+':
return a + b;
break;
case '-':
return a - b;
break;
case '*':
return a * b;
break;
case '/':
return a / b;
break;
default:
cout << "Error\n";
break;
}
}
bool calculate(vector<data> vec)
{
stack<int> cal;
for (int i = 0; i < vec.size(); i++)
{
if (vec[i].n != -1) //不为-1说明是数字,入栈
cal.push(vec[i].n);
else if (vec[i].c != '#') //不是#说明是符号
{
//注意符号运算的先后关系
int b = cal.top();
cal.pop();
int a = cal.top();
cal.pop();
cal.push(count(vec[i].c, a, b));
}
}
return cal.top() == 24;
}
最后是计时功能,要求超过计时时间则该回合结束,这里使用了clock()函数:
int flag_in = 1;
float sec1 = 60.0;
clock_t delay1 = sec1 * CLOCKS_PER_SEC; //delay1表示现实生活中的60秒
clock_t start = clock(); //start表示此时的时间
while (1)
{
//clock()会不停更新当前的时间,所以当和start相差60秒时则退出循环
if(clock() - start >= delay1)
break;
if (kbhit()) //kbhit()函数当键盘有输入时候返回true,否则返回false
{
flag_in = 0;
break;
}
}
if (flag_in) //如果while循环结束后flag_in仍是1,则说明超时
{
cout << "\n已超时,该回合0分!\n";
continue;
}
完整代码如下:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <string>
#include <map>
#include <vector>
#include <ctime>
#include <time.h>
#include <Windows.h>
#include <stack>
#include <conio.h>
using namespace std;
struct data
{
int n; //存数
char c; //存符号
data()
{
n = -1;
c = '#';
}
};
double a[4];
vector<data> rpn;
map<char, int> mp;
string change(string);
void transform(string);
bool calculate(vector<data>);
int count(char c, int a, int b);
bool count24(int n);
int main()
{
int cnt = 3, score = 0, rounds = 1;
mp['+'] = mp['-'] = 1;
mp['*'] = mp['/'] = 2;
srand((unsigned)time(NULL)); //生成随机数的种子
while (cnt--)
{
cout << "******************回合" << rounds++ << "******************" << endl;
for (int i = 0; i < 4; i++)
a[i] = (rand() % 10) + 1;
cout << "随机生成4个数字:";
for (int i = 0; i < 4; i++)
{
Sleep(500); //Sleep()函数模拟停顿
cout << a[i];
if (i != 3)
cout << ' ';
}
Sleep(500);
cout << endl;
cout << "是否能算出24(限时60秒)?.........[Yes]\\[No]:";
string choose;
int flag_in = 1;
float sec1 = 60.0;
clock_t delay1 = sec1 * CLOCKS_PER_SEC;
clock_t start = clock();
while (1)
{
if(clock() - start >= delay1)
break;
if (kbhit())
{
flag_in = 0;
break;
}
}
if (flag_in)
{
cout << "\n已超时,该回合0分!\n";
continue;
}
cin >> choose;
choose = change(choose);
if (choose == "yes" && count24(4))
{
cout << "请输入您的表达式(限时30秒):";
int chance = 3, timeout = 1;
string ans;
clock_t start_ = clock();
float sec2 = 30.0;
clock_t delay2 = sec2 * CLOCKS_PER_SEC;
while (1)
{
if(clock() - start_ >= delay2)
break;
if (kbhit())
{
timeout = 0;
break;
}
}
if (timeout)
{
cout << "\n已超时,本轮的0分!\n";
continue;
}
cin >> ans;
transform(ans);
while (!calculate(rpn))
{
if (chance == 0)
break;
else
{
cout << "表达式错误!剩余尝试次数:"<< --chance << endl;
cout << "请输入您的表达式:";
cin >> ans;
transform(ans);
}
}
if (chance == 0)
{
cout << "尝试次数已用尽,该回合得5分\n" << endl;
score += 5;
}
else
{
cout << "表达式正确,得10分!\n" << endl;
score += 10;
}
}
else if (choose == "yes" && !count24(4))
cout << "错误!该回合得0分\n";
else if (choose == "no" && !count24(4))
{
cout << "正确!得10分!\n";
score += 10;
}
else if (choose == "no" && count24(4))
cout << "错误!该回合得0分\n";
cout << endl;
if (cnt == 4)
break;
}
cout << "************************\n";
cout << "您的最终得分为:" << score << "分" << endl;
cout << "************************\n";
system("pause");
return 0;
}
string change(string s)
{
string ans;
for (int i = 0; i < s.size(); i++)
ans += tolower(s[i]);
return ans;
}
int count(char c, int a, int b)
{
switch (c)
{
case '+':
return a + b;
break;
case '-':
return a - b;
break;
case '*':
return a * b;
break;
case '/':
return a / b;
break;
default:
cout << "Error\n";
break;
}
}
bool count24(int n)
{
int i, j;
double p, q;
if (n == 1)
{
if (fabs(a[0] - 24) < 1e-3)
return true;
else
return false;
}
for (i = 0; i < n - 1; i++)
{
for (j = i + 1; j < n; j++)
{
p = a[i];
q = a[j];
a[j] = a[n - 1];
a[i] = p + q;
if (count24(n - 1))
return true;
a[i] = p - q;
if (count24(n - 1))
return true;
a[i] = q - p;
if (count24(n - 1))
return true;
a[i] = p * q;
if (count24(n - 1))
return true;
if (q != 0)
{
a[i] = p / q;
if (count24(n - 1))
return true;
}
if (p != 0)
{
a[i] = q / p;
if (count24(n - 1))
return true;
}
a[i] = p;
a[j] = q;
}
}
return false;
}
void transform(string s)
{
rpn.clear();
stack<data> stk;
for (int i = 0; i < s.size(); i++)
{
if (i < s.size() && s[i] >= '0' && s[i] <= '9')
{
data node;
node.n = s[i] - '0';
int rec = i + 1, flag = 0;
while (rec < s.size() && s[rec] >= '0' && s[rec] <= '9')
{
flag = 1;
node.n = node.n * 10 + s[rec] - '0';
rec++;
}
if (flag)
++i;
rpn.push_back(node);
}
else if (s[i] == '(')
{
data node;
node.c = s[i];
stk.push(node);
}
else if (s[i] == ')')
{
while (stk.top().c != '(')
{
rpn.push_back(stk.top());
stk.pop();
}
stk.pop();
}
else
{
while (!stk.empty() && mp[stk.top().c] >= mp[s[i]])
{
rpn.push_back(stk.top());
stk.pop();
}
data node;
node.c = s[i];
stk.push(node);
}
}
while (!stk.empty())
{
rpn.push_back(stk.top());
stk.pop();
}
}
bool calculate(vector<data> vec)
{
stack<int> cal;
for (int i = 0; i < vec.size(); i++)
{
if (vec[i].n != -1)
cal.push(vec[i].n);
else if (vec[i].c != '#')
{
int b = cal.top();
cal.pop();
int a = cal.top();
cal.pop();
cal.push(count(vec[i].c, a, b));
}
}
return cal.top() == 24;
}