HNU程序设计 作业训练四 斯诺克台球

【问题描述】

斯诺克台球是一项古老而又时尚的运动,使用长方形球桌,台面四角以及两长边中心位置各有一个球袋,使用的球分为1个白球,15个红球和6个彩球共22个球。

其中母球(白球)1只,目标球21只。目标球中:红球15只各1分、黄球1只2分、绿球1只3分、咖啡球1只4分、蓝球1只5分、粉球1只6分、黑球1只7分。

选手需要使用球杆撞击母球去击打目标球来完成得分,每局开始时总是先从红球开始。击球顺序为先打进红球(每次击打允许多个红球同时落袋),然后必须任意指定一个目标彩球击打,如果该彩球被打进(打进后需要再摆回),然后接着击打红球,直到红球全部落袋,然后以黄、绿、咖啡、蓝、粉红、黑的顺序逐个击球(不再摆回),最后以得分高者为胜。任何时候红球落袋都不再摆回,任何时候因犯规导致彩球落袋,彩球必须摆回。

斯诺克比赛由双方轮流击打,必须击打合规的目标球,打进则本方得到相应的分数并继续击打,未打进或犯规轮换为对方击打,未打进不得分,犯规将进行罚分处理。

犯规规则如下:

1.     当击打目标球时,如果先击打到或同时击打到一个或多个其他颜色的球,或者有其他颜色的球落袋,或者打空(未击打到任何球),则视为犯规。此时需要比较目标球的分值和与本犯规相关的其他颜色的球的分值,取其中最高的分值,如果该分值小于4,则对方加4分,否则对方加该分值。

2.     当击打红球落袋后,继续击打任意彩球时打空,即未打击到任何球,对方加4分。

相比正式的斯诺克比赛,本问题对规则进行了简化,任何时候都可以结束比赛并计算比赛结果,不考虑白球落袋的情况。

信息化时代的智能台球桌能自动记录实际比赛时的击打记录,并传送到后台,但该记录仅仅是流水记录,并且无参赛选手的任何信息,需要你编程计算每场比赛的比分,同时需要计算单杆100分及以上的情况(单杆得分是指选手一次连续击打所得分数之和)。

【输入形式】

输入第一行为正整数t (t≤100),表示有t组测试数据,每组数据代表一局比赛。

在输入中,球的颜色表示为:

               r-红色球 y-黄色球 g-绿色球 c-咖啡色球 b-蓝色球 p-粉红球 B-黑色球

接下来的每组数据包括若干行,每一行为一次击打的结果,为智能球桌记录下来的流水记录,每组数据最后一行为-1,表示每组数据的结束。

流水记录包含用空格分隔的2个部分:

首先撞到的球 落袋球及数量

第一部分“首先撞到的球”为一个字符串,可以是“rygcbpB”中1个或多个字符组合(可能有多个字符“r”),或为字符串“NULL”。为“NULL”时,第二部分必为空,表示该次击打未撞击到任何球也没有任何球落袋。当红球落袋后继续击打任意彩球时,该部分为“ygcbpB”中的任意单个字符时都认为是合规的目标球。

第二部分“落袋球及数量”为一个字符串,例如“r2gb”,代表本次击打有两个红球落袋,以及绿球和篮球落袋,红色球r后面有数字(大于0小于16),表示红球的落袋数,其他彩球后无数字。该部分可以为空,表示本次击打无球落袋。

比赛在A与B之间进行,每局比赛总是由A先开球。

【输出形式】

输出为t+1行,前t行每行输出用冒号分隔的两个整数,表示每局比赛A与B之间的比分;最后一行输出用冒号分隔的两个整数,表示t局比赛之后A与B之间获得的单杆100分及以上的次数之比(单杆得分是指选手一次连续击打所得分数之和)。

【样例输入】

3
r r1
B
r r2
c c
r r1
b g
-1
rp r1
r br2B
NULL
r r12
y y
g p
-1
rr r3
NULL
r r1
yg y
-1

【样例输出】

6:7
13:24
7:5
0:0

【样例说明】

第一局比赛:

A击打红球,打进1个红球,得1分,比分为 1:0

A继续击打任意彩球,打到黑球,未打进,不得分,比分为1:0

轮换为B击打红球,打进两个红球,得2分,比分为1:2

B继续击打任意彩球,打到咖啡球,打进咖啡球,咖啡球摆回,得4分,比分为1:6

B继续击打红球,打进一个红球,得1分,比分为1:7

B继续击打任意彩球,打到蓝球,打进绿球,犯规,取分值最大者蓝球,绿球摆回,对方加5分,比分为6:7

-1比赛结束

第二局比赛:

A击打红球,首先打到红球和粉球,犯规,打进1个红球和咖啡球,犯规,咖啡球摆回,取分值最大的粉球,对方加6分,比分为0:6

B击打红球,首先打到红球,打进蓝球、2个红球和黑球,犯规,蓝球和黑球摆回,取分值最大的黑球,对方加7分,比分为7:6

A击打红球,未打到任何球,犯规,对方加4分,比分为7:10

B击打红球,打到红球,打进12个红球,加12分,比分为7:22

B击打任意彩球,打到黄球,打进黄球,黄球摆回,得2分,比分为7:24

B击打黄球,打到绿球,打进粉球,犯规,粉球摆回,对方加6分,比分为13:24

-1比赛结束

第三局比赛:

A击打红球,打到2个红球,打进3个红球,加3分,比分为3:0

A击打任意彩球,打空,未打到任何球,对方加4分,比分为3:4

B击打红球,打到1个红球,打进1个红球,加1分,比分为3:5

B击打任意彩球,打到黄球和绿球,打进黄球,犯规,黄球摆回,取分值最高的绿球,绿球分值小于4,对方加4分,比分为7:5 

-1比赛结束

3局比赛中无人单杆得分过100,最后一行输出0:0

【解题思路】

这是我写出来的第一个大模拟,发个博客纪念一下。

题目涉及到的情况还是比较多的,需要耐心看题。有以下几种情况需要特别注意:

(1) 选手在击球之后是有可能没办法将任何球击落入袋的,如样例输入的第三行就只有一个B,意思是选手击打黑球之后并没有任何球入袋。

(2) 选手如果击空(NULL)之后,是需要换人上场的。

(3) 对于红球,不管选手是否犯规,只要入袋,则不被拿回,所以我们还需要特别判断击入袋中的球是否有红球,如果有,我们要将击入袋的红球一并减掉。

(4) 选手如果成功击打红球,此时若恰好将红球全部击打入袋,即红球数量为0,此时仍需进行一次自由击打彩球,而后再进行顺序击打彩球。

【代码分析】

使用string来存储输入的比赛记录,将所有比赛记录放入list中来维护(这里换成queue更好),而后再来一步一步判断比赛情况。

涉及到A和B交替发球,所以这里使用一个 ret 变量来判断是A起手发球还是B起手发球,这样做可以有效避免代码冗长的问题。

play函数是用来模拟选手发球的情况,按照题目意思,选手先打红球,若此时选手犯规,则换人;若不犯规,则自由击打彩球,若犯规,则换人;若不犯规,判断红球个数,若红球为0,则顺序击打彩球,若红球不为0,则继续击打红球(代码当中使用了goto语句来处理这个问题)

play_color函数是用来模拟顺序击打彩球的情况,使用 list 来维护彩球,实际上可以使用queue,毕竟先进先出。

check函数用来判断是否犯规(或者理解为判断是否换人),根据题目意思,进行相对应的犯规判断即可,要注意的是对之前在【解题思路】里面说过的特殊情况要做特别判断。

还有一个需要注意的地方就是函数在编写的时候注意进行截断,我一开始有一个地方忘记做截断导致多判断了几次。还有就是要注意初始化,进行数据存储的数据结构在使用之前要清空。

【代码实现】

#include<iostream>
#include<string>
#include<list>
using namespace std;
int n,ret=1,red=15,scoreA=0,scoreB=0,hitA=0,hitB=0,loneA=0,loneB=0;
//ret=1,A起手,scoreAB是AB的比赛分数,hitAB是单杆分数,loneAB是单杆超100分次数
list<string> l; //存储比赛记录
list<char> color_l; //彩球击打顺序
void init_list(list<char> &color_l) {
	//黄、绿、咖啡、蓝、粉红、黑
	color_l.push_back('y');
	color_l.push_back('g');
	color_l.push_back('c');
	color_l.push_back('b');
	color_l.push_back('p');
	color_l.push_back('B');
}
int get_ballScore(char ball) {
	switch(ball) {
		case 'r':{
			return 1;
			break;
		}
		case 'y':{
			return 2;
			break;
		}
		case 'g':{
			return 3;
			break;
		}
		case 'c':{
			return 4;
			break;
		}
		case 'b':{
			return 5;
			break;
		}
		case 'p':{
			return 6;
			break;
		}
		case 'B':{
			return 7;
			break;
		}
	}
}
bool check(string s, char goal) {    //比赛记录,目标球
	if(s=="NULL") { //打空直接犯规
		if(ret==1) {    //记录分数,给对方加分
			scoreB+=(get_ballScore(goal)>4?get_ballScore(goal):4);
		}
		else if(ret==-1) {
			scoreA+=(get_ballScore(goal)>4?get_ballScore(goal):4);
		}
		return true;
	}
	string temp=s+" ",left="",right=""; //left为击球情况,right为落球情况
	int pos=temp.find(" ");
	left=temp.substr(0,pos);
	if(pos!=s.size()) { //打中球但是落空的情况要特判
		right=s.substr(pos+1);
	}
	int num=0;
 	if(right!="") { //先处理红球落袋的情况,不管是否犯规,红球一落袋,就不能放回
 		for(int i=0; i<right.size(); i++) {
 			if(right[i]=='r') {
 				for(int j=i+1; j<right.size(); j++) {
 					if(!(right[j]>='0'&&right[j]<='9')) {
 						break;
					 }
					 num=num*10+right[j]-'0';
				 }
				 red-=num;
				 break;
			 }
		 }
	 }
	
	bool flag=false;    //判断是否有别的球被击打或者落袋
	
	for(int i=0; i<left.size(); i++) {	//先来判断击打情况中是否有别的球
		if(!(left[i]>='0'&&left[i]<='9')&&left[i]!=goal) {	//有别的球
			flag=true;
		}
	}
	
	if(right!="") { //再判断落袋情况中是否有别的球
		for(int i=0; i<right.size(); i++) {
			if(!(right[i]>='0'&&right[i]<='9')&&right[i]!=goal) {
				flag=true;
			}
		}
	}

	if(flag) {
		int tempScore=0;
		for(int j=0; j<s.size(); j++) { //		?红球是算1分还是算num分?
			if(!((s[j]>='0'&&s[j]<='9')||s[j]==' ')) {
				if(get_ballScore(s[j])>tempScore) {
					tempScore=get_ballScore(s[j]);
				}
			}
		}
		if(ret==1) {    //记录分数,给对方加分
			scoreB+=(tempScore>4?tempScore:4);
		}
		else if(ret==-1) {
			scoreA+=(tempScore>4?tempScore:4);
		}
		return true;
	}
	
	if(right=="") { //未打进最终还是要换人
		return true;
	}
	
	return false;
}
void getScore(string s, char goal) {
	//优先判定不犯规但是没有球落袋的情况
	string temp=s+" ",left="",right=""; //left为击球情况,right为落球情况
	int pos=temp.find(" ");
	left=temp.substr(0,pos);
	if(pos!=s.size()) { //打中球但是落空的情况要特判
		right=s.substr(pos+1);
	}
	if(right=="") {
		return; //没有球落袋,不做任何处理
	}
	int sum=0;
	if(goal=='r') {
		for(int i=1; i<right.size(); i++) {
			sum=sum*10+right[i]-'0';
		}
	}
	else {
  		sum=get_ballScore(goal);
	}
	//总分进行累加
	if(ret==1) {
		scoreA+=sum;
		hitA+=sum;
	}
	else if(ret==-1) {
		scoreB+=sum;
		hitB+=sum;
	}
}
void change(int Ret) {
	if(Ret==1) {    //从A换到B
		if(hitA>=100) {
			loneA++;
		}
		hitA=0;
	}
	else if(Ret==-1) {  //从B换到A
		if(hitB>=100) {
			loneB++;
		}
		hitB=0;
	}
}
void play_color(int* Ret) {
	if(l.empty()) {
		return;
	}
	string s=l.front();
	l.pop_front();
	if(check(s,color_l.front())) {  //犯规了就换人
		change(ret);    //计算单杆得分
		*Ret=-*Ret;
		play_color(&ret);
		return;
	}
	getScore(s,color_l.front());    //自己得分
	color_l.pop_front();    //没有犯规就弹出一个彩球
	play_color(&ret);
}
void play(int* Ret) {
	if(l.empty()) { //比赛打完了
		return;
	}
	if(red==0) {
		play_color(&ret);
		return;
	}
AGAIN:
	string s=l.front(); //拿取第一条比赛记录
	l.pop_front();
	//打红球,先确定是否犯规
	if(check(s,'r')) {
		change(ret);    //计算单杆得分
		*Ret=-*Ret;   //换人
		play(&ret);
		return;
	}
	getScore(s,'r');    //自己得分
	if(l.empty()) {
		return;
	}
	s=l.front();
	l.pop_front();	//接下来打任意彩球,先判定是否犯规
	if(s=="NULL") { //打红球后任意彩球打空,对方加4分
		if(*Ret==1) {
			scoreB+=4;
		}
		else if(*Ret==-1) {
			scoreA+=4;
		}
		change(ret);    //计算单杆得分
		*Ret=-*Ret;
		play(&ret);
		return;
	}
	if(check(s,s[0])) { //把第一个打到的彩球作为目标球
		change(ret);    //计算单杆得分
		*Ret=-*Ret;
		play(&ret);
		return;
	}
	getScore(s,s[0]);
	if(l.empty()) {
		return;
	}
	if(red==0) {
		play_color(&ret);
		return;
	}
	//如果红球还有剩余,则重复上述过程
	goto AGAIN;

}
int main() {
	string s="";
	cin>>n;
	cin.get();
	for(int i=0; i<n; i++) {
		l.clear();
		color_l.clear();
		init_list(color_l);     //注意初始化
		red=15; //红球一共有15个
		scoreA=scoreB=hitA=hitB=0;    //A与B开始得分为0
		ret=1; //A先起手
		while(getline(cin,s)) {
			if(s=="-1") {
				break;
			}
			l.push_back(s); //对比赛信息进行记录
		}
		//对储存好的比赛记录进行得分模拟
		play(&ret);
		if(hitA>=100) {
			loneA++;
		}
		if(hitB>=100) {
			loneB++;
		}
		cout<<scoreA<<":"<<scoreB<<endl;
	}
	cout<<loneA<<":"<<loneB<<endl;
	return 0;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值