拉马车游戏规则及实现
题目描述
小时候,我们可能玩过一种叫做"拉马车"的纸牌游戏。这个游戏规则简单,却非常吸引小朋友。
游戏规则简述
假设有两个小朋友,分别是 A 和 B。游戏开始时,他们各自持有一组随机的纸牌序列。游戏从 A 方开始,A 和 B 双方轮流出牌。
初始牌序列
- A 方:
[K, 8, X, K, A, 2, A, 9, 5, A]
- B 方:
[2, 7, K, 5, J, 5, Q, 6, K, 4]
其中 X
表示 "10",我们忽略纸牌的花色。
出牌规则
- 当轮到某一方出牌时,他从自己的纸牌队列的头部拿走一张,放到桌上,并且压在最上面一张纸牌上(如果有的话)。
- 如果出的牌与桌上已有的牌相同,那么可以将包括这张牌在内的,以及两张相同牌之间的所有纸牌赢回,放入自己牌的队尾。放入牌的顺序是与桌上的顺序相反的。
- 赢牌的一方继续出牌。
- 当某一方出掉手里最后一张牌,但无法从桌面上赢取牌时,游戏结束。
示例游戏过程
- A 出 K, B 出 2, A 出 8, B 出 7, A 出 X, 此时桌上的序列为:
K, 2, 8, 7, X
- B 出 K,与桌上的 K 相同,赢回
K, 2, 8, 7, X
,此时双方手里牌为:- A 方:
[K, A, 2, A, 9, 5, A]
- B 方:
[5, J, 5, Q, 6, K, 4, K, X, 7, 8, 2, K]
- A 方:
- B 出 5, A 出 K, B 出 J, A 出 A, B 出 5 赢牌,桌上序列为:
5, K, J, A, 5
- 最后双方手里牌为:
- A 方:
[2, A, 9, 5, A]
- B 方:
[Q, 6, K, 4, K, X, 7, 8, 2, K, 5, A, J, K, 5]
- A 方:
输入输出描述
输入描述
输入为两行,两个字符串,分别表示 A、B 双方初始手里的牌序列。输入的字符串长度不超过 30。
输出描述
输出为一行,一个字符串,表示 A 先出牌,最后赢的一方手里的牌序。如果游戏无法结束,输出 -1
。
输入输出样例
输入
96J5A898QA
6278A7Q973
输出
2J9A7QA6Q6889977
这个输出表示在游戏结束时,B 方手里的牌序为 2J9A7QA6Q6889977
。
用C++写的答案:
出牌操作频繁设置函数op
op函数步骤:
1.设置结束条件,玩家的牌长度=0
2.设置赢牌标识符=true
3.取玩家的第一张牌
4.根据是否赢牌,选择不同操作
如果出的牌在出牌串里有,即赢牌了,则
-
- 1.添加出的牌到自己的末尾
-
- 2.把出牌串中从下标为0到出牌字符的下标i的牌都放到自己的末尾
-
- 3.从出牌串中移除那些赢的牌
如果没有赢牌
1.把出的牌放到z串的开头
2.设置赢牌标识符=false
主函数:
1.输入两个串A,B
2.设置A,B赢牌的标识符
无限循环游戏:
3.如果A赢牌了,则
1.A出牌,并用flagA记录A是否赢牌
2.设置结束条件,A长度=0,输出B串
3.设置flagB!=flagA,让两者不冲突
B赢牌相同操作
cpp
#include <iostream>//C++都需要导入的库
#include <string>//因为会用到字符串操作函数,导入string库
using namespace std; //用名字空间避免同名函数误用
string A,B,C;//声明三个字符串A,B,C分别放A的牌,B的牌,桌上的牌
/**
* 函数用于处理玩家出牌的逻辑
* @param x 正在操作的玩家手中的牌
* @param z 桌面上的牌
* @return 操作是否成功,如果成功则返回true,表示牌被成功放置或赢取,否则返回false
*/
bool op(string &x, string &z) {
//bool返回true表示牌成功放置,如果没有牌,返回false
//string x表示玩家目前要出的牌,加&表示需要对原字符串进行操作
//string z表示目前桌面上出掉的牌,因为出牌和赢牌,桌面上出的牌会发生改变,所以也加&
//先设置结束条件
// 如果玩家手中没有牌,即字符串长度为0,则无法出牌,返回false
if (x.length() == 0) return false;
// ans来标记出牌操作是否成功,初始化操作成功的标志为true
bool ans = true;
//要出牌,先获取玩家手中最顶端的牌
char front = x[0];
// 在桌面上的牌即z字符串 中查找是否有与 玩家即将出的牌相同的牌
long i=z.find(front);
// 看已经出的牌z中如果有玩家要出的牌,那么玩家赢牌,赢牌条件
if(i!=string::npos) {
//如果i不等于 没有找到返回的string::npos(这里没有前面的std,是因为前面using namespace std)
//- A 方: `[K, 8, X, K, A, 2, A, 9, 5, A]`
// B 方: `[2, 7, K, 5, J, 5, Q, 6, K, 4]`
//例:A 出 K, B 出 2, A 出 8, B 出 7, A 出 X,,此时桌上的序列为: 'X,7,8,2,K'
//B 出 K,与桌上的 K 相同,即赢牌
//1.把要出的牌放在自己牌的末尾,即B=[ 5, J, 5, Q, 6, K, 4,K]
x.insert(x.end(), front);
// 将玩家手中的这张牌添加到自己牌的末尾
//字符串插入insert,位置x.end()在x串的最后,牌为front
//2.把出牌序列'X,7,8,2,K'中的从下标为0开始到找到K的下标的牌从x串的末尾放入X中,赢回 `K, 2, 8, 7, X`,倒着放进去,此时双方手里牌为:
// - A 方: `[K, A, 2, A, 9, 5, A]`
//- B 方: `[5, J, 5, Q, 6, K, 4, K, X, 7, 8, 2, K]`
// 将桌面上从0到找到的相同牌之间的所有牌添加到玩家牌的末尾
//因为每次出牌都放在出牌串z的开头,所以赢牌是从下标为0开始到找到的子字符的下标
for (int j = 0; j <= i; ++j) {
x.insert(x.end(), z[j]);
}
// 3.从桌面上移除这些牌
z.erase(0, i + 1);
//移除函数,0表示开始位置,i+1表示要移除的个数,因为0-i有i+1个字符
} else {
// 如果没有找到相同的牌,将这张牌放在桌面上
//在z串的开始放入玩家要出的牌,即出的每张牌都是放在出牌串z的开头
z.insert(z.begin(), front);
// 操作不成功,设置标志为false,没有赢牌
ans = false;
}
// 从玩家手中移除已经出掉的牌,即在x串开头的牌
x.erase(x.begin());
//赢牌同样需要删掉第一张牌,因为赢牌后第一张牌已经收回x串中,此时的第一张牌应该已经出掉了并赢回牌,不是有效牌了,需要删掉
// 返回操作是否成功的标志,表示是否赢牌
return ans;
}
// 主函数入口
int main(int argc, const char *argv[]) {
//常用写法
// 从标准输入读取玩家A和B的牌
cin >> A >> B;
// 初始化标志,表示玩家A能出牌
bool flagA = true;
// 初始化标志,表示玩家B能出牌,初始为false,因为游戏从玩家A开始
bool flagB = false;
// 使用无限循环来持续游戏,直到游戏结束
while (1) {
// 如果玩家A能出牌
if (flagA) {
// 调用op函数处理玩家A的出牌逻辑,A出牌,C是出牌字符串
//如果A赢牌了,返回true,赢牌的继续出
//如果A没有赢牌,返回flase
flagA = op(A, C);
// 如果玩家A没有牌了,输出玩家B的牌,游戏结束
if (A.length() == 0) {
cout << B << endl;
break;
}
// 设置玩家B为下一个出牌的玩家
//如果A赢牌了,flagA=true,那B还是不能出牌。flagB=false
//如果A没有赢牌,flagA=false,那B可以出牌了,flagB=true
flagB = !flagA;
}
// 如果玩家B能出牌
if (flagB) {
// 调用op函数处理玩家B的出牌逻辑
flagB = op(B, C);
// 如果玩家B没有牌了,输出玩家A的牌,游戏结束
if (B.length() == 0) {
cout << A << endl;
break;
}
// 设置玩家A为下一个出牌的玩家
flagA = !flagB;
}
}
// 程序结束,返回0
return 0;
}
我写的C语言代码:
问题:
1.如果是频繁的操作用函数写
2.如果涉及标识符,多个最好设置对应的标识符
3.会无限循环的写while(1)
4.逻辑没弄清楚,赢牌后原本出的牌也一样放到末尾
c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
//输入A,B的初始牌序
char A[100],B[100];
char chupai[300]={0};
//错误: 出牌队列里面的数应该初始化为0,因为出牌队列不像A,B队列会直接输入一串字符
int a,b=-1;
int flag=0;
//设置队列存储目前的已经出的排序
int top=0,rear=0;
//输入一串字符%s
scanf("%s",A);
scanf("%s",B);
//注意:scanf输入两个字符串,用空格隔开即可
//A,B轮流出牌,放入队列中
//for(int i=0;i<200;i++)
//{
//错误;一直循环,用while即可
while(1){
//出牌是把出牌方的队列头部牌拿出一张 放入队列中
//赢牌的一方继续出牌
if(flag==0){//A赢
//出牌操作频繁,可以设置函数写
a++;//A出牌
chupai[rear]=A[a];//把出的牌放入队列中
rear++;
b++;//B出牌
flag=1;//表示B现在出牌
//注意:flag设置有问题,一次来回是A,B的话,那只有B赢了,flag才可以=1
chupai[rear]=B[b];//把B出的牌放入队列中
rear++;
} else{
b++;//B出牌
flag=1;//表示B现在出牌
chupai[rear]=B[b];//把B出的牌放入队列中
rear++;
a++;//A出牌
chupai[rear]=A[a];//把出的牌放入队列中
rear++;
}
for(int j=0;j<rear+1;j++)
//如果出现有牌跟 已经出的纸牌序列其中一张牌相同,则把包括K在内及两个数之间的纸牌都拿回来逆序放入队尾
{
if(chupai[rear]==chupai[j])
{
for(int g=rear;g<=j;g++)
{
if(flag==0)//A赢
{
a++;//把队列中的牌放入A中
A[a]=chupai[g];
}else{
b++;//把队列中的牌放入A中
B[b]=chupai[g];
}
}
}
}
// if(a==-1)
// {
// puts(B);//输出 计算游戏结束时,赢的一方手里的牌序
// }
// if(b==-1)
// {
// puts(A);
// }
//注意:对测字符串长度的函数不熟
//可以用字符长度来看字符是否为空
}
return -1;
}
补充:字符串查找函数
命名空间里的string类的find方法
**用处:**找子字符串在字符串中的位置(下标从0开始)
- 找到子字符串:返回子字符串在字符串中的位置
- 没找到子字符串:返回 std::string::npos(一般用long类型)
例:
c
z="Hellow";
front="l";
z.find(front);//在Hellow找到l,下标从0开始,在下标2处找到"l",返回2
front="f";//在Hellow里找不到f,返回std::string::npos
z.find(front);