C语言之斗地主游戏

embedded/2025/1/21 15:34:26/


🌟 嗨,我是LucianaiB!

🌍 总有人间一两风,填我十万八千梦。

🚀 路漫漫其修远兮,吾将上下而求索。


C语言之斗地主游戏

目录

  1. 程序概述
  2. 程序设计
    1. Card类
    2. CardGroup类
    3. Player类
    4. LastCards类
    5. Landlords类
  3. 游戏流程
  4. 特点与功能
    1. 牌型判断
    2. AI提示
    3. 交互性
    4. 随机性
  5. 测试与运行
  6. 总体评价
  7. 附录代码

本文介绍了一个基于C++实现的简单斗地主游戏程序。该程序模拟了斗地主的基本规则和流程,包括发牌、抢地主、出牌以及胜负判定等功能。程序的核心由多个类组成,包括Card(表示单张牌)、CardGroup(表示一组牌)、Player(表示玩家)、LastCards(用于记录上家出的牌)和Landlords(用于管理整个游戏流程)。

程序设计

程序通过Card类定义了牌的基本属性,如牌面值、花色和显示方法。CardGroup类用于管理一组牌,并提供牌型判断和大小比较的功能。Player类则管理玩家手中的牌,并提供出牌和提示功能。LastCards类用于记录上家出的牌,并提供查找可打得过的牌的功能。Landlords类作为游戏的核心,负责初始化游戏、发牌、抢地主、轮流出牌以及判断游戏结束等逻辑。

游戏流程

游戏开始时,程序通过洗牌和分牌将54张牌随机分配给三个玩家,并通过抢地主环节确定地主玩家。地主玩家获得额外的三张底牌后,游戏正式开始。玩家按顺序出牌,每次出牌需要比上家的牌大,或者选择不出。当某个玩家手中的牌全部出完时,游戏结束,程序会判断地主是否获胜。

特点与功能
  1. 牌型判断:程序能够准确判断牌型,如单牌、对子、三带一、炸弹等,并根据牌型和牌的大小进行比较。
  2. AI提示:非地主玩家在出牌时,程序会自动提示可打得过的牌,提高了游戏的流畅性。
  3. 交互性:程序通过控制台输入输出与用户交互,用户可以选择是否抢地主、出牌以及是否继续游戏
  4. 随机性:通过随机洗牌和抢地主环节,增加了游戏的趣味性和不确定性。
测试与运行

程序在测试中表现出了良好的稳定性和正确性。玩家可以自由选择出牌策略,程序会根据规则判断出牌是否合法,并在游戏结束后输出胜负结果。用户可以通过简单的命令行交互体验完整的斗地主游戏流程。
在这里插入图片描述

总体评价

本文介绍了一个基于C++实现的斗地主游戏程序,模拟了斗地主的基本规则和流程,包括发牌、抢地主、出牌和胜负判定等功能。程序的核心由多个类组成,如Card、CardGroup、Player、LastCards和Landlords,通过面向对象编程的方式实现了游戏的完整逻辑。

在程序设计中,Card类用于定义单张牌的基本属性,如牌面值、花色和显示方法。CardGroup类管理一组牌,并提供牌型判断和大小比较的功能。Player类管理玩家手中的牌,并提供出牌和提示功能。LastCards类用于记录上家出的牌,并提供查找可打得过的牌的功能。Landlords类作为游戏的核心,负责初始化游戏、发牌、抢地主、轮流出牌以及判断游戏结束等逻辑。

游戏流程从洗牌和分牌开始,通过抢地主环节确定地主玩家。地主获得额外三张底牌后,游戏正式开始。玩家按顺序出牌,每次出牌需要比上家的牌大,或者选择不出。当某个玩家手中的牌全部出完时,游戏结束,程序会判断地主是否获胜。
该程序具有以下特点:

牌型判断:能够准确判断牌型,如单牌、对子、三带一、炸弹等,并根据牌型和牌的大小进行比较。

AI提示:非地主玩家在出牌时,程序会自动提示可打得过的牌,提高了游戏的流畅性。

交互性:通过控制台输入输出与用户交互,用户可以选择是否抢地主、出牌以及是否继续游戏

随机性:通过随机洗牌和抢地主环节,增加了游戏的趣味性和不确定性。

在测试中,程序表现出良好的稳定性和正确性,用户可以通过简单的命令行交互体验完整的斗地主游戏流程。该程序不仅实现了斗地主的基本规则,还通过类的设计和封装展示了面向对象编程的思想。代码结构清晰,易于理解和扩展,适合用于学习和研究C++编程以及游戏开发。

附录代码:
#include <iostream>
#include<vector>
#include<assert.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<time.h>using namespace std;#define PLAYERCOUNT 3
#define CARDSCOUNT 54
#define CURRENTPLAYER 0
#define VALUECOUNT 17
#define ERROR -1
using namespace std;
int scnt=0;
const char toFigure[]="34567890JQKA 2YZ";enum COLOR{  //花色显示ASCII: 3~6
eHEART=3,//红桃
eDIAMOND=4,//方片
eCLUB=5,   //草花
eSPADE=6   //黑桃
};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;
int value;
Card(char _figure,COLOR _color){figure=_figure;color=_color;value=calValue();
}
int calValue(){for(int i=0;toFigure[i];i++){if(toFigure[i]==figure){return i;}}return ERROR;
}void print() {assert(value != ERROR);switch (color) {case eHEART:cout << "♥"; // 红桃break;case eDIAMOND:cout << "♦"; // 方片break;case eCLUB:cout << "♣"; // 草花break;case eSPADE:cout << "♠"; // 黑桃break;default:cout << "?"; // 未知花色break;}cout << figure << ' ';}
};
class CardsType{
public:
//为了规范查找对应牌的方法
//统一为3个参数cnt1、isContinuous、cnt2
int typeId;
string typeStr;
int cnt1,cnt2;
bool isContinuous;
CardsType(){typeId=ERROR;
}
bool operator ==(const CardsType& other)const{return this->typeId==other.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;
CardsType type;
void calType(){int i,n=cards.size();//init(typeStr,typeId,cnt1,isContinuous,cnt2)if(n==0){type.init("不出",14,0,0,0);return;}if(n==2&&cards[0]->value==15&&cards[1]->value==14){type.init("王炸",0,0,0,0);return;}//统计同点数牌有多少张int cntFlag[VALUECOUNT]={0};for(i=0;i<n;i++){cntFlag[cards[i]->value]++;}//统计点数最多和最少的牌int maxCnt=0,minCnt=4;for(i=0;i<VALUECOUNT;i++){if(maxCnt<cntFlag[i]){maxCnt=cntFlag[i];}if(cntFlag[i]&&minCnt>cntFlag[i]){minCnt=cntFlag[i];}}if(n==4&&maxCnt==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);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){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){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;//统计连续且大于3三张的牌for(i=0;i<VALUECOUNT &&cntFlag[i]<3;i++);for(fjCnt=0;i<VALUECOUNT &&cntFlag[i]>=3;i++,fjCnt++);if(fjCnt>1){if(n==fjCnt*3)type.init("飞机",11,3,1,0);else if(n==fjCnt*4)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};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;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){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;}else if(cardGroup.type==this->type &&this->cards.size()==cardGroup.cards.size()){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){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;vector<Card*> ret;if(typeStr=="王炸"||n<m){//打不过,返回空数组return ret;}int value=cardGroup.value();//统计各点牌出现的次数int cntFlag[VALUECOUNT]={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);}bool findFirstFigure;//cout<<"continuousCount="<<continuousCount<<endl;for(i=value+1;i<VALUECOUNT;i++){findFirstFigure=true;for(j=0;j<continuousCount;j++){if(cntFlag[i-j]<cardGroup.type.cnt1){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];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]);}break;}}}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];
bool finished,youWin,landlordWin;
int landlordIndex;
Card *cards[CARDSCOUNT];
public:
Landlords(){int i=0,j=0,k=0;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=eHEART;color<=eSPADE;color=(COLOR)(color+1)){this->cards[k++]=new Card(toFigure[i],color);}}this->cards[k++]=new Card('Y',eSPADE);this->cards[k]=new Card('Z',eHEART);
}
~Landlords(){for(int i=0;i<PLAYERCOUNT;i++){delete this->player[i];}for(int i1=0;i1<CARDSCOUNT;i1++){delete this->cards[i1];}
}
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++){if(iTurns>=PLAYERCOUNT){iTurns=0;}if(iTurns==CURRENTPLAYER){cout<<endl;player[iTurns]->print();cout<<"输入提示:Z=大王 Y=小王 0=10 N=不要 输入可无序 例如: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;}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[i]->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;}
}
};
int main(){
Landlords *landlords=new Landlords();
do{landlords->init();//发牌、抢地主landlords->startGame();//游戏开始
}while(makeChoice("\n是否继续游戏?(Y=继续/N=结束): "));
delete landlords;
return 0;
}
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;
}
}

嗨,我是[LucianaiB](https://lucianaib.blog.csdn.net/ “LucianaiB”)。如果你觉得我的分享有价值,不妨通过以下方式表达你的支持:👍 点赞来表达你的喜爱,📁 关注以获取我的最新消息,💬 评论与我交流你的见解。我会继续努力,为你带来更多精彩和实用的内容。

点击这里👉[LucianaiB](https://lucianaib.blog.csdn.net/ “LucianaiB”) ,获取最新动态,⚡️ 让信息传递更加迅速。


http://www.ppmy.cn/embedded/155799.html

相关文章

在 Ubuntu 上安装 MySQL 的详细指南

在Ubuntu环境中安装 mysql-server 以及 MySQL 开发包&#xff08;包括头文件和动态库文件&#xff09;&#xff0c;并处理最新版本MySQL初始自动生成的用户名和密码&#xff0c;可以通过官方的APT包管理器轻松完成。以下是详细的步骤指南&#xff0c;包括从官方仓库和MySQL官方…

闪豆多平台视频批量下载器

1. 视频链接获取与解析 首先&#xff0c;在哔哩哔哩网页中随意点击一个视频&#xff0c;比如你最近迷上了一个UP主的美食制作视频&#xff0c;想要下载下来慢慢学。点击视频后&#xff0c;复制视频页面的链接。复制完成后&#xff0c;不要急着关闭浏览器&#xff0c;因为接下来…

Vue进阶之旅:组件通信与高级用法深度剖析(组件通信进阶用法)

Vue进阶之旅&#xff1a;组件通信与高级用法深度剖析 文章目录 Vue进阶之旅&#xff1a;组件通信与高级用法深度剖析一、组件的三大组成部分二、组件通信&#xff08;一&#xff09;父子关系通信&#xff08;二&#xff09;非父子关系通信 三、综合案例&#xff1a;小黑记事本&…

uniapp button 去除边框

在找去除边框的办法时试了好久 css里设置了 border: none; /* 去掉边框 */outline: none; /* 确保点击时不出现轮廓 */压根不行&#xff0c;按钮还是浮在页面上有明显轮廓 最后看到了大佬的文章 https://www.cnblogs.com/menxiaojin/p/13752916.html button::after{border: no…

从零开始:Spring Boot核心概念与架构解析

引言 在当今的Java开发领域&#xff0c;Spring Boot已经成为构建企业级应用的首选框架之一。它以其简洁、高效、易于上手的特点&#xff0c;极大地简化了Spring应用的开发过程。本文将从Spring Boot的核心概念入手&#xff0c;深入解析其架构设计和运行原理&#xff0c;帮助读…

vue+高德API搭建前端环境页面

一、模板部分&#xff08;<template>&#xff09; html <template><div class"page-container"><div id"container"></div></div> </template>这部分使用 Vue 的模板语法&#xff0c;定义了组件的 HTML 结构。…

大数据-260 实时数仓 - 项目背景与需求 实时数仓架构 需求分析 技术选型 逻辑架构

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; H…

网络编程套接字-上篇

目录 ⏳初始TCP和UDP &#x1f4dd;面经:TCP和UDP的区别 &#x1f9f8;UDP和TCP API的使用 Socket对象 客户端 vs 服务端 常⻅的客⼾端服务端模型 请求 vs 响应 UDP数据报套接字编程 API介绍 DatagramSocket DatagramPacket &#x1f441;‍&#x1f5e8;代码示例(…