#include <bits/stdc++.h>
#define PLAYERCOUNT 3//三个玩家
#define CARDSCOUNT 54//牌数
#define CURRENTPLAYER 0
#define VALUECOUNT 17//15种牌+空格+末尾结束'\0'
#define ERROR -1
using namespace std;
const char toFigure[] = "34567890JQKA 2YZ";//牌,Y为小王,Z为大王
//将A和2分隔开,如顺子等出牌类型不允许用2和大小王,同时支持读入空格
enum COLOR { //枚举花色显示ASCII: 3~6
HEART = 3, //红桃
DIAMOND,//方片
CLUB, //梅花
SPADE //黑桃
};
class Card;//单牌参数
class CardsType;//出牌类型
class CardGroup;//出牌牌组
class Player;//玩家手牌
class Landlords;//地主
class LastCards;//电脑出牌相关操作
bool makeChoice(string tip);//玩家决定是否抢地主
bool cmp(Card *a, Card *b);//比较点牌大小
class Card {//记录单张牌的类
public://声明公有成员
char figure;//发牌的字母
COLOR color;//定义一个枚举类型color
int value;//单牌权重
Card(char _figure, COLOR _color) {//赋值函数,赋值单张牌的大小和花色
figure = _figure;
color = _color;
value = calValue();//记录该牌顺序编号,编号越大权重越大
}
int calValue() {
for (int i = 0; toFigure[i]; i++) {//'\0'结束比较
if (toFigure[i] == figure) {//发的牌在15种牌中有
return i;//返回该牌顺序编号
}
}
return ERROR;//找完了15种牌也没有,返回错误
}
void print() {//输出该牌
assert(value != ERROR);//如果未报错,程序正常运行,否则终止程序
if (figure == 'Z') {
cout << "ZZ";
} else if (figure == 'Y') {
cout << "YY";
} else {
cout << figure;//输出牌号
}
cout << ' ';
}
};
class CardsType {//记录出牌类型
public:
//为了规范查找对应牌的方法
//统一为3个参数cnt1、isContinuous、cnt2
int typeId;//出牌类型编号
string typeStr;//出牌类型中文表示
int cnt1, cnt2;
//几带几,cnt1记录主牌最小单位,如三带一==3,单牌==1.对子==2,王炸例外
//cnt2记录带的牌最小单位(如四带两张单牌为1,炸弹带对子为2)
bool isContinuous;
//是否为可改变出牌数类型出牌(如顺子、连对、飞机),是为TRUE
CardsType() {
typeId = ERROR;
}
bool operator ==(const CardsType &other)const {
return this->typeId == other.typeId;//比较CardsType类中typeId
}
void init(char *_typeStr, int _typeId, int _cnt1, bool _isContinuous, int _cnt2) {
//赋值函数,将出牌的参数一次性赋值
cnt1 = _cnt1;
isContinuous = _isContinuous;
cnt2 = _cnt2;
typeStr = _typeStr;
typeId = _typeId;
}
};
class CardGroup {//判断出牌类型等
public:
vector<Card *> cards;//定义card类型指针数组cards,用于记录出牌
CardsType type;//定义CardsType类的type
void calType() {//判断出牌类型函数
int i, n = cards.size();//出牌数
//init(typeStr,typeId,cnt1,isContinuous,cnt2)
if (n == 0) {//没牌了,不出
type.init("不出", 14, 0, 0, 0);//不出为第15种出牌类型(typeId==14)
return;
}
if (n == 2 && cards[0]->value == 15 && cards[1]->value == 14) {//手上剩两张王
type.init("王炸", 0, 0, 0, 0);//王炸为第一种出牌类型(typeId==0)
return;
}
//统计同点数牌有多少张
int cntFlag[VALUECOUNT] = {0};//定义17个计数器(15种类型的单牌)
for (i = 0; i < n; i++) {
cntFlag[cards[i]->value]++;//编号为value的牌数量++
}
//统计点数最多和最少的牌
int maxCnt = 0, minCnt = 4;//最多4张同点数牌,如此初始化minCnt计数器
for (i = 0; i < VALUECOUNT; i++) {
//开始遍历数组找出出的牌中同点数牌最多的
if (maxCnt < cntFlag[i]) {
maxCnt = cntFlag[i];
}
if (cntFlag[i] && minCnt > cntFlag[i]) {
//同点数牌数量不为0,且大于minCnt计数器
minCnt = cntFlag[i];
}
}
if (n == 4 && maxCnt == 4) {//剩4张牌,且为炸弹
type.init("炸弹", 1, 4, 0, 0);
return;
}
if (n == 1) {
type.init("单牌", 2, 1, 0, 0);
return;
}
if (n == 2 && maxCnt == 2) {
type.init("对子", 3, 2, 0, 0);
//第4种出牌类型,出2+0张牌,不是可改变出牌数类型
return;
}
if (n == 3 && maxCnt == 3) {
type.init("三张 ", 4, 3, 0, 0);
return;
}
if (n == 4 && maxCnt == 3) {
type.init("三带一", 5, 3, 0, 1);
return;
}
if (n == 5 && maxCnt == 3 && minCnt == 2) {
type.init("三带一对", 6, 3, 0, 2);
return;
}
if (n == 6 && maxCnt == 4) {//出6张牌,其中有四张相同,一定为4带2
type.init("四带二", 7, 4, 0, 1);
return;
}
if (n == 8 && maxCnt == 4 && minCnt == 2) {//炸弹带对子
type.init("四带二", 8, 4, 0, 2);
return;
}
if (n >= 5 && maxCnt == 1 && cards[0]->value == cards[n - 1]->value + n - 1) {
//判断为顺子,出牌数==最大一张牌编号-最小张牌编号+1
type.init("顺子", 9, 1, 1, 0);
return;
}
if (n >= 6 && maxCnt == 2 && minCnt == 2 && cards[0]->value == cards[n - 1]->value + n / 2 - 1) {
type.init("连对", 10, 2, 1, 0);
return;
}
//以下单独判断飞机
int fjCnt;//统计连续且大于三张的牌,附近count
for (i = 0; i < VALUECOUNT && cntFlag[i] < 3; i++);
//跳过非三张相同牌
for (fjCnt = 0; i < VALUECOUNT && cntFlag[i] >= 3; i++, fjCnt++);
//记录飞机里有几个三连
if (fjCnt > 1) {//三连牌数大于1才算飞机
if (n == fjCnt * 3)//飞机不带
type.init("飞机", 11, 3, 1, 0);
else if (n == fjCnt * 4)//飞机带单牌,eg.999888777345
type.init("飞机", 12, 3, 1, 1);
else if (n == fjCnt * 5 && minCnt == 2)//飞机带对子
type.init("飞机", 13, 3, 1, 2);
}
}
void init(string inputStr, vector<Card *> &cardsHolded) {
//玩家输入字符串(出牌),目前手牌(数组记录)
this->cards.clear();//先释放指针
if (inputStr == "N") {
this->calType();//判断出牌类型
return;
}
int i, j;
//输入合法性判断
for (i = 0; i < inputStr.size(); i++) {//遍历出牌
bool find = false;
for (j = 0; toFigure[j]; j++) {
if (inputStr[i] == toFigure[j]) {//查找有无该牌种
find = true;
break;
}
}
if (find == false) {
//输入字符不在toFigure中
return;
}
}
//查找手中有没有这些牌
int visitFlag[20] = {0};//记录手牌中存在的出牌(实际为bool类型数组)
//最多出10连对,即20张牌
for (i = 0; i < inputStr.size(); i++) {
Card *find = NULL;//指针记录出牌对应手牌地址
for (j = 0; j < cardsHolded.size(); j++) {//遍历手牌
if (!visitFlag[j] && cardsHolded[j]->figure == inputStr[i]) {
//如果手牌中存在该牌牌,且该牌未被记录
visitFlag[j] = 1;//存在该牌,赋值为TRUE
find = cardsHolded[j];//指针赋值
break;
}
}
if (find) {//找到不为空指针
this->cards.push_back(find);//
} else {
cout << inputStr[i];//没找到的牌
cout << "没有找到\t";
this->cards.clear();//释放指针
return;
}
}//end for(i=0;i<inputStr.size();i++)
this->arrange();//整理玩家出牌
}
void init(vector<Card *> newCards) {
this->cards = newCards;
this->arrange();//整理出牌牌组,见下方
}
bool isCanBeat(CardGroup &cardGroup) {//cardGroup为上家出牌
//判断出牌能否打过上家,能则为TRUE
if (cardGroup.type.typeStr == "王炸") {
return false;//上家出王炸,打不过
} else if (this->type.typeStr == "王炸") {
return true;//我方出王炸,打得过
} else if (cardGroup.type == this->type && this->type.typeStr == "炸弹") {
//都出炸弹
return value() > cardGroup.value();//判断谁的炸弹大
} else if (cardGroup.type.typeStr == "炸弹") {
return false;//上家炸弹,我方非王炸/炸弹,打不过
} else if (this->type.typeStr == "炸弹") {
return true;//上家非炸弹,我方炸弹,TRUE
} else if (cardGroup.type == this->type && this->cards.size() == cardGroup.cards.size()) {
//出同类型牌且牌数相同(比如334455和556677)
return this->value() > cardGroup.value();
} else {
return false;
}
}
int value() {//计算牌组权值
int i;
if (type.typeStr == "三带一" || type.typeStr == "三带一对" || type.typeStr == "飞机") {
for (i = 2; i < cards.size(); i++) {
if (cards[i]->value == cards[i - 2]->value) {
//由于不确定cards中主牌位置,故先判断找出三连牌。如34445,66888
//三带几出牌类型以主牌权重为牌组权值,飞机以最小三连为主牌权值。
return cards[i]->value;//返回主牌编号
}
}
}
if (type.typeStr == "四带二") {
for (i = 3; i < cards.size(); i++) {
if (cards[i]->value == cards[i - 3]->value) {
return cards[i]->value;
//四带二以主牌权重为牌组权重
}
}
}
return cards[0]->value;
}
void arrange() {
//整理工作:排序,然后计算出牌类型
sort(this->cards.begin(), this->cards.end(), cmp);
this->calType();
}
};
class LastCards {//电脑出牌相关操作
static LastCards *lastCards;//定义电脑出牌牌组指针
public:
Player *player;
CardGroup cardGroup;
static LastCards *inst() {//单例模式
if (lastCards == NULL) {
lastCards = new LastCards();
}
return lastCards;
}
vector<Card *> findCanBeatFrom(vector<Card *> &cardsHolded) {
//查找能打得过的牌
int i, j, k, n = cardsHolded.size(), m = cardGroup.cards.size();
string typeStr = cardGroup.type.typeStr;
//强制规定电脑本次出牌类型为上家(玩家/机器人1号)
vector<Card *> ret;//定义一个空指针便于在打不过时“不出”
if (typeStr == "王炸" || n < m) {
//打不过,返回空数组指针
return ret;
}
int value = cardGroup.value();//记录上家出牌牌组权值
//以下统计各点牌出现的次数
int cntFlag[VALUECOUNT] = {0};//初始化各点牌为0张
for (i = 0; i < n; i++) {
cntFlag[cardsHolded[i]->value]++;//统计各点牌数
}
int continuousCount = 1;//便于记录要出几连对/顺/飞机
if (cardGroup.type.isContinuous) {
//如果为可增出牌类型(连对、顺子等)
continuousCount = m / (cardGroup.type.cnt1 + cardGroup.type.cnt2);
}//m为上家出牌总数
bool findFirstFigure;
//是否找到第一张符合出牌类型且大于上家出牌权值的主牌
//cout<<"continuousCount="<<continuousCount<<endl;
for (i = value + 1; i < VALUECOUNT; i++) {
//从15种牌的上家主牌开始对比寻找
findFirstFigure = true;
for (j = 0; j < continuousCount; j++) {
if (cntFlag[i - j] < cardGroup.type.cnt1) {
//如果牌值大于等于上家但是数量不够凑成类型,false
findFirstFigure = false;
break;
}
}
//记录电脑出牌
if (findFirstFigure) {
ret.clear();//释放空指针
int firstFigure = i;//第一张要出的牌
//cout<<"查找"<<cardGroup.type.cnt1<<"个"<<firstFigure+3<<endl;
for (k = 0, j = 0; k < cardsHolded.size() && j < continuousCount; k++) {
if (cardsHolded[k]->value == firstFigure - j) {//
for (int kk = 0; j >= 0 && kk < cardGroup.type.cnt1; kk++) {
ret.push_back(cardsHolded[k + kk]);
}
j++;
}
}
if (cardGroup.type.cnt2 > 0) {//如果带了牌
int SecondFigures[5];
//记录第二出牌牌组(最多带5种牌)
int SecondCount = continuousCount;
//初始化默认带的牌数量等于主牌种类数量
if (cardGroup.type.typeStr == "四带二")
SecondCount = 2;//四带二例外
bool findSecondFigure = true;
//判断能否满足带牌条件,几乎同上
for (j = 0, k = -1; j < SecondCount && findSecondFigure; j++) {
findSecondFigure = false;
for (k++; k < VALUECOUNT; k++) {
SecondFigures[j] = k;
if (cntFlag[k] >= cardGroup.type.cnt2 && cntFlag[k] < cardGroup.type.cnt1) {
findSecondFigure = true;
break;
}
}
}
if (findSecondFigure) {//确认找到能出的副牌
//cout<<"查找SecondFigure "<<cardGroup.type.cnt2<<"个"<<SecondFigures[0]+3<<endl;
//cout<<"SecondCount= "<<SecondCount<<endl;
//for(i=0;i<SecondCount;i++)cout<<"SecondFigures["<<i<<"]="<<SecondFigures[i]<<endl;
for (i = 0; i < SecondCount; i++) {
for (j = 0; j < cardsHolded.size();) {
if (cardsHolded[j]->value == SecondFigures[i]) {
for (k = 0; k < cardGroup.type.cnt2; k++) {
//cout<<"添加"<<cardsHolded[j]->value+3<<endl;
ret.push_back(cardsHolded[j + k]);
}
do {
j++;
} while (j < cardsHolded.size() && cardsHolded[j]->value == SecondFigures[i]);
} else {
j++;
}
}
}
return ret;
//将做好的出牌牌组返回,否则返回空
}//if(findSecondFigure)
}//end if(cardGroup.type.cnt2>0)
else {
return ret;//没带牌则直接返回空
}
}//end if(findFirstFigure)先有主牌才用找副牌
}//end for(i=value+1;i<VALUECOUNT;i++)
ret.clear();//释放牌组养成习惯
//没牌打得过时查找有没有炸弹
if (typeStr != "炸弹") {
for (i = cardsHolded.size() - 1; i >= 3; i--) {
if (cardsHolded[i]->value == cardsHolded[i - 3]->value) {
//如果找到炸弹(四连牌)
for (j = 0; j < 4; j++) {
ret.push_back(cardsHolded[i - j]);
}
return ret;
}
}
}
//寻找王炸保底
bool Y=0,Z=0;
for(i = cardsHolded.size() - 1; i >=0; i--){
if(cardsHolded[i]->value==14){
Y=1;
}else if(cardsHolded[i]->value==15){
Z=1;
}
}
i = cardsHolded.size() - 1;
if(Y==1&&Z==1){
for(j=0;j<2;j++){
ret.push_back(cardsHolded[i-j]);
}
}
return ret;
}//end vector<Card*> findCanBeatFrom()
};
LastCards *LastCards::lastCards = NULL;
class Player {//整理牌组和输出部分
public:
string name;
vector<Card *> cards;
void arrange() {
sort(cards.begin(), cards.end(), cmp);
}
void print() {//输出手牌
cout << this->name << ":\t";
for (int i = 0; i < cards.size(); i++) {
cards[i]->print();
}
cout << "[" << cards.size() << "]\n";//牌数
}
vector<Card *> tip() {
//提示功能,使自己最小一张连最长
CardGroup ret;
string temp;
int j, k, m = cards.size();
for (j = 0; j < m; j++) {
temp = "";
for (k = j; k < m; k++) {
temp += cards[k]->figure;
}
ret.init(temp, cards);
if (ret.type.typeId != ERROR) {
return ret.cards;
}
}
ret.cards.clear();
return ret.cards;
}
void chupai(CardGroup &cardGroup) {
//出牌
cout << this->name << ":\t";
cout << cardGroup.type.typeStr << ' ';
for (int i = 0; i < cardGroup.cards.size(); i++) {
cardGroup.cards[i]->print();
this->cards.erase(find(this->cards.begin(), this->cards.end(), cardGroup.cards[i]));
}
cout << "\t[" << this->cards.size() << "]\n";
if (cardGroup.type.typeStr != "不出") {
//记录到 LastCards 中
LastCards::inst()->player = this;
LastCards::inst()->cardGroup.init(cardGroup.cards);
}
}
};
class Landlords {//玩家操作及初始化部分
Player *player[PLAYERCOUNT];//创建三个玩家class Player
bool finished, youWin, landlordWin;
//finished游戏是否结束,landlord地主
int landlordIndex;
Card *cards[CARDSCOUNT];//创建54张牌牌组
public:
Landlords() {
int i, j, k;
for (i = 0; i < PLAYERCOUNT; i++) {
this->player[i] = new Player();
}
//54张牌初始化
for (k = i = 0; i < 14; i++) {
if (toFigure[i] == ' ') {//遇到中间的空格跳过
continue;
}
for (COLOR color = HEART; color <= SPADE; color = (COLOR)(color + 1)) {
this->cards[k++] = new Card(toFigure[i], color);
//赋予花色
}
}
this->cards[k++] = new Card('Y', SPADE);
this->cards[k] = new Card('Z', HEART);
}
~Landlords() {
for (int i = 0; i < PLAYERCOUNT; i++) {
delete this->player[i];
}
for (int i = 0; i < CARDSCOUNT; i++) {
delete this->cards[i];
}
}
void init() {
player[CURRENTPLAYER]->name = "Bice";
player[1]->name = "玩家2";
player[2]->name = "玩家3";
finished = false;
youWin = false;
landlordWin = false;
//抢地主
landlordIndex = ERROR;
while (landlordIndex == ERROR) {
srand((int)time(0));//随机函数
shuffle();//洗牌发牌
landlordIndex = chooseLandlord();//记录地主的玩家编号
}
cout << player[landlordIndex]->name << "\t成为地主\n\n";
this->add3Cards();//地主加三张牌
LastCards::inst()->player = player[landlordIndex];
//地主牌赋给玩家指针
}
void startGame() {
string inputSrt;//记录玩家输入字符串
CardGroup inputCards;
for (int iTurns = landlordIndex; !finished; iTurns++) {
//iTurns一轮内轮到玩家次数,从地主开始出牌
if (iTurns >= PLAYERCOUNT) {//三人走完
iTurns = 0;//再次地主出牌
}
if (iTurns == CURRENTPLAYER) {//轮到玩家
cout << endl;
player[iTurns]->print();
cout << "输入提示:Z=大王 Y=小王 0=10 输入可无序 :=不出 例如:JKQ0A9\n请出牌:\t";
do {
cin >> inputSrt;//读牌
inputCards.init(inputSrt, player[iTurns]->cards);
} while (check(&inputCards) == false);//检查输入直到正确出牌
} else {
if (player[iTurns] == LastCards::inst()->player) {
//若是上次出牌的是自己,启用提示功能
inputCards.init(player[iTurns]->tip());
} else {
//查找能打得过上家的牌
inputCards.init(LastCards::inst()->findCanBeatFrom(player[iTurns]->cards));
}
}
player[iTurns]->chupai(inputCards);//出牌
if (player[iTurns]->cards.size() == 0) {
//玩家手中没牌了,游戏结束
finished = true;
landlordWin = iTurns == landlordIndex;
if (landlordWin) {//地主赢了
youWin = landlordIndex == CURRENTPLAYER;//'我'是地主,youwin==1 or==0
} else {//农民赢了
youWin = landlordIndex != CURRENTPLAYER;
}
}
}
cout << "\n_________________________ " << (youWin ? "You Win!" : "You Lose!") << " _________________________\n\n";
}
void add3Cards() {
cout << "地主3张牌:\t";
for (int i = PLAYERCOUNT * 17; i < CARDSCOUNT; i++) {
this->cards[i]->print();//输出地主的三张牌
player[landlordIndex]->cards.push_back(cards[i]);
}
cout << endl;
player[landlordIndex]->arrange();
}
int chooseLandlord() {//抢地主函数
cout << "\n_________________________ 抢地主 _________________________\n\n";
int first = -1, last, cnt = 0, i, j = rand() % PLAYERCOUNT;
bool decision;
for (i = 0; i < PLAYERCOUNT; i++, j == 2 ? j = 0 : j++) {
if (j == CURRENTPLAYER) {
decision = makeChoice("是否抢地主?(Y=抢/N=不抢):");
} else {
decision = rand() % 2;//随机两个人机是否抢地主
}
if (decision) {
cnt++;
last = j;
if (first == -1) {
first = j;
}
cout << this->player[j]->name << "\t抢地主\n";
} else {
cout << this->player[j]->name << "\t没有抢\n";
}
}
if (cnt == 0) {
cout << "没人抢,重新发牌\n";
return ERROR;
}
if (cnt == 1) {
//第一轮只有一人抢地主
return first;
} else {
//最后一次争抢
if (first == CURRENTPLAYER) {
decision = makeChoice("是否抢地主?(Y=抢/N=不抢):");
} else {
decision = rand() % 2;
}
if (decision) {
cout << this->player[first]->name << "\t抢地主\n";
return first;
} else {
cout << this->player[first]->name << "\t没有抢\n";
return last;
}
}
}
void shuffle() {
//洗牌
int i, j, k;
for (i = 0; i < CARDSCOUNT; i++) {
swap(this->cards[i], this->cards[rand() % CARDSCOUNT]);
}
//分牌
for (k = i = 0; i < PLAYERCOUNT; i++) {
this->player[i]->cards.clear();
for (j = 0; j < 17; j++) {
this->player[i]->cards.push_back(this->cards[k++]);
}
this->player[i]->arrange();//整理
}
this->player[0]->print();//显示玩家抽到的牌
}
bool check(CardGroup *cardGroup) {//出牌错误时反馈
if (cardGroup->type.typeId == ERROR) {
cout << "出牌错误,重新输入\n";
return false;
} else if (cardGroup->type.typeStr == "不出") {
return true;
} else if (LastCards::inst()->player != player[CURRENTPLAYER] && !cardGroup->isCanBeat(LastCards::inst()->cardGroup)) {
cout << "打不过,重新输入\n";
return false;
} else {
return true;
}
}
};
bool makeChoice(string tip) {
cout << tip;
string input;
cin >> input;
return input == "Y" || input == "y";//大小写宽容
}
bool cmp(Card *a, Card *b) {
//比较两张牌大小
if (a->value == b->value) {
return a->color > b->color;//同点数按花色排序
} else {
return a->value > b->value;
}
}
int main() {
Landlords *landlords = new Landlords();
do {
landlords->init();//发牌、抢地主
landlords->startGame();//游戏开始
} while (makeChoice("\n是否继续游戏?(Y=继续/N=结束): "));
delete landlords;//释放缓存
return 0;
}
Last modification:November 17, 2023
© Allow specification reprint