一本通网站1132:石头剪子布
【题目描述】
石头剪子布,是一种猜拳游戏。起源于中国,然后传到日本、朝鲜等地,随着亚欧贸易的不断发展它传到了欧洲,到了近现代逐渐风靡世界。
简单明了的规则,使得石头剪子布没有任何规则漏洞可钻,单次玩法比拼运气,多回合玩法比拼心理博弈,使得石头剪子布这个古老的游戏
同时用于“意外”与“技术”两种特性,深受世界人民喜爱。
游戏规则:石头打剪刀,布包石头,剪刀剪布。
现在,需要你写一个程序来判断石头剪子布游戏的结果。
【输入】
第一行是一个整数N,表示一共进行了N次游戏。1 ≤ N ≤ 100。
接下来N行的每一行包括两个字符串,表示游戏参与者Player1,Player2的选择(石头、剪子或者是布):
S1 S2
字符串之间以空格隔开S1,S2只可能取值在{"Rock", "Scissors", "Paper"}(大小写敏感)中。
【输出】
输出包括N行,每一行对应一个胜利者(Player1或者Player2),或者游戏出现平局,则输出Tie。
【输入样例】
3
Rock Scissors
Paper Paper
Rock Paper
【输出样例】
Player1
Tie
Player2
分析:这个题目难度不大,完全可以暴力解决,如果完全无脑算法也还可以接受,最多9种对决情况。暴力列出9种对决情况的算法在这里就不啰嗦了,如果从结题判断,那最终只有三种结果,故可以暴力列举三种结果,当然,每一种结果仍然可能包含多种情况。在这三种情况中,平局最简单——两个选手出相同手势(其实这里面包含了三种情况,但判断语句都一样),再列出一种,另一种直接else就好。比如,列举Player1胜Player2,那就需要列出三种对决的完整情形。可得到如下代码:
//1132:石头剪子布第一版本:完整暴力列举
#include<iostream>
using namespace std;
int main()
{
int n;
string p1,p2;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p1>>p2;
if(p1=="Rock"&&p2=="Scissors"||p1=="Scissors"&&p2=="Paper"||p1=="Paper"&&p2=="Rock")
{
cout<<"Player1\n";
}
else if(p1==p2)
{
cout<<"Tie\n";
}
else cout<<"Player2\n";
}
return 0;
}
在第一版本里可以做一点小优化,我们注意到三个单词的首字母不用,故我们也可以直接用首字母判断即可,这样可避免字母太多,出现拼写错误。于是有了第二版本:
//1132:石头剪子布第二版本-部分暴力列举
#include<iostream>
using namespace std;
int main()
{
int n;
char p1[10],p2[10];
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p1>>p2;
if(p1[0]=='R'&&p2[0]=='S'||p1[0]=='S'&&p2[0]=='P'||p1[0]=='P'&&p2[0]=='R')
{
cout<<"Player1\n";
}
else if(p1[0]==p2[0])
{
cout<<"Tie\n";
}
else cout<<"Player2\n";
}
return 0;
}
对于C++编程,我们还要学会模块化编程思想,即可以把一些特定的功能编成一个函数,这些可以提高代码的利用率,也可以增加程序的可读性和减少程序维护代价。对本题而言,我们完全可以把判断过程写成一个函数,这样可以大大减少主程序的复杂度,特别是在以后程序变得更加复杂后,模块化是必然趋势。于得得到第三版本:
//1132:石头剪子布第三版本-普通函数
#include<iostream>
using namespace std;
string rsp(string p1,string p2)
{
string rerult;
if(p1[0]=='R'&&p2[0]=='S'||p1[0]=='S'&&p2[0]=='P'||p1[0]=='P'&&p2[0]=='R')
{
rerult="Player1\n";
}
else if(p1==p2)
{
rerult="Tie\n";
}
else rerult="Player2\n";
return rerult;
}
int main()
{
int n;
string p1,p2;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p1>>p2;
cout<<rsp(p1,p2);
}
return 0;
}
如果,我们对这一个问题再做一个更深入的思考,不难发现,P1胜P2的情况很有规律性,如果把石头、剪刀、布放在一个圆圈上,按照一定的方向便是胜/败的判断依据。这里的“圆圈”实质上可以理解为周期性出现。最终结果只有三种,那我们可以想到任何一个整数除以3的余数也只有三种情况(0、1、2),那是否可以考虑对石头、剪刀、布数字化,依次赋值为1,2,3。那我们不难得出:p1胜p2<==>p1的对应值-p2对应值=-1或2,从余数角度完全可以是同一个。为了解决负数问题,我们先加一个3,再对3取余数,那-1和2是等价的了,都将转换成2。至此,程序便可以修改为第四版本:
//1132:石头剪子布第四版本-赋值函数
#include<iostream>
using namespace std;
int rsp(string s)
{
if(s[0]=='R') return 1;
if(s[0]=='S')return 2;
return 3;
}
int main()
{
int n;
string p1,p2;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p1>>p2;
if(p1==p2)cout<<"Tie\n";
else if((rsp(p2)-rsp(p1)+3)%3==1)cout<<"Player1\n";
else cout<<"Player2\n";
}
return 0;
}
再对照上例中(rsp(p2)-rsp(p1)+3)%3这一个表达式可能出现的值为0、1、2,而我们最终结果也正好是三种,那我们可以把三种结果存入数组,用(rsp(p2)-rsp(p1)+3)%3做下标,直接调用结果,于是便有第五版本:
//1132:石头剪子布第五版本-数组函数
#include<iostream>
using namespace std;
int rsp(string s)
{
if(s[0]=='R') return 1;
if(s[0]=='S')return 2;
return 3;
}
int main()
{
int n;
string p1,p2,rerult[3]={"Tie\n","Player1\n","Player2\n"};
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p1>>p2;
cout<<rerult[(rsp(p2)-rsp(p1)+3)%3];
}
return 0;
}
当然,随着我们学习的深入,我们发现,“数字化”的想法已经不是我们首创,这些想法C++已经都想到了,并作出了很系统的模板封装好,我们只需直接调用便是——那就是功能强大的STL库函数。在这个题中的自编函数可用map来解决,看第六版本:
//1132:石头剪子布第六版本-STL库函数
#include<iostream>
#include<map>
using namespace std;
int main()
{
int n;
string p1,p2;
string rerult[3]={"Tie\n","Player1\n","Player2\n"};;
map<string,int> rsp;
rsp["Rock"]=1;
rsp["Scissors"]=2;
rsp["Paper"]=3;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>p1>>p2;
cout<<rerult[(rsp[p2]-rsp[p1]+3)%3];
}
return 0;
}
写这么多,主要想让新学者体验一个程序的修改过程、优化过程,高手们也请多多指教,以便让我和新手们一起学学。