C++开发斗地主(QT)第一篇之数据结构 您所在的位置:网站首页 小程序的斗地主头像怎么换 C++开发斗地主(QT)第一篇之数据结构

C++开发斗地主(QT)第一篇之数据结构

2024-07-16 02:55| 来源: 网络整理| 查看: 265

斗地主,是一种在中国流行的纸牌游戏。游戏最少由3个玩家进行,用一副54张牌(连鬼牌),其中一方为地主,其余两家为另一方,双方对战,先出完牌的一方获胜。如今已风靡整个中国,并流行于互联网上!

从今天开始,我将会一步步详细讲解单机斗地主游戏开发过程。该游戏以C++为主,QT做为界面(包括显示 动画 声音等)的Window平台,没有用到第三方面库如CoCo2d 等,至于为什么要用QT,主要是自己得心应手(QT动画 透明 图像处理太简单了),比VS简单,而且还能跨平台,本游戏在最后开源。网上的斗地主算法 源码很多,如果写的不好,欢迎批评 探讨。为了提起大家的兴趣,我给我做好的游戏功能和界面简单给大家看一下:

该游戏的大部分资源都是人网上下载的,如有侵权,请及时通知,我将会删除。本软件主要算法用的是 宽立斗地主AI设计与实现

我在此基础上用QT在Windows上进行了详细开发,主要是为了研究算法,我在此基础上对算法做了适当修改。

游戏主要功能:音效开关  机器人模式  记牌器窗口 退出 功能按键,还有出牌控制 音效 报警等,以后会更加完善,加一些设置,如声音大小  游戏难度 人物选取 等功能。

现在开始来讲游戏的开发过程:

一、数据结构,任何软件开发都离不开数据结构和数据处理。

1.牌型枚举(CardTypes):

//牌型枚举 enum CardTypes { Error_Card,//错误出牌 Single_Card,//单牌 Double_Card,//对子 Three_Card,//三张 ThreeOne_Card,//三带一 ThreeTwo_Card,//三代二 Line_Card,//单顺 Double_Line_Card,//连对 Plane_Card,//飞机(两个三张连) Plane_TwoSingle_Card,//飞机带俩单 Plane_TwoDouble_Card,//飞机带两对 Four_TwoSingle_Card,//四带俩单 Four_TwoDouble_Card,//四带两对 Bomb_Card,//炸弹 Rocket_Card//王炸(火箭) };  

我给他们定义了15种牌型:

⒈错误牌型:不能出的牌型

⒉单牌:例一张A

⒊对子:例AA

⒋三张:AAA

⒌三带一:AAAB

⒍三带二:AAABB

⒎单顺:也叫连子3,4,5,6,7,8,9...A一直到A为止

⒏连对:33,44,55,66

⒐飞机:333,444或444,555,666

⒑飞机带单:33344458

⒒飞机带双:3334447799

⒓四带单:88889J 四张可以带2单

⒔四带双:88883377 四张可以带2个对子

⒕炸弹:7777

⒖王炸:又称火箭 大小王一起出,是最大牌型

牌型的大小:

火箭是最大的牌。

炸弹,除火箭和比自己大的炸弹外,比其它牌型都大。

对一般牌型而言,只有当牌型相同和总张数相同的牌,才可比较大小。

其中像三带一、三带二、飞机带翅膀等组合牌型,只要比较其牌数最多牌值就行。只有比当前出的牌(场牌)大的牌才能出。

2.牌值枚举(CardValue):

//牌值枚举 enum CardValue{ // 以下为牌的面值,从3开始 kCard_ValueLeast = 2, kCard_Value3 = 3, kCard_Value4 = 4, kCard_Value5 = 5, kCard_Value6 = 6, kCard_Value7 = 7, kCard_Value8 = 8, kCard_Value9 = 9, kCard_ValueT = 10, kCard_ValueJ = 11,//J kCard_ValueQ = 12,//Q kCard_ValueK = 13,//K kCard_ValueA = 14,//A kCard_Value2 = 15,//2 kCard_ValueJoker1 = 16,//小王 kCard_ValueJoker2 = 17,//大王 kCard_ValueMax = 18, kCard_TableMax = 20, kCard_KindMax = 5, // 特殊牌值 kCard_Joker1 = 53, kCard_Joker2 = 54, kCard_Flower = 55, kCardMask_CardValue = 0x00ff, // 牌的面值 kCardMask_AnyMatch = 0x0100, // 任意配 kMaxCardNum = 56, kMaxPlayers = 3, // 牌型定义 kCardType_Single = 1, // 单纯类型, seriaNum == 1 kCardType_Serial = 2, // 单顺, 双顺, 三顺(飞机), 4顺 kCardType_Rocket = 3, // 火箭(大小王) CarcAngle = 130 //牌的角度,另外两家的牌是倾斜的 };

 

我把牌分成了54种类,即牌面大小(3,4,5,6,7,8,9,10,J(11),Q(12),K(13),A(14),2(15))13种,和四种花色(梅花,方块,黑桃,红桃),再加上小王(16),大王(17)。13*4+2=54;

3.牌型结构体( CardNode):

//cardType是牌型,只有三种,王炸,单纯,连续; //value 是牌型的值,单纯类型为牌的面值,连续类型为起始牌的面值,相同牌型以此比较大小; //mainNum是主牌张数,比如三带二和飞机里mainNum=3, 连对时, mainNum=2; //seralNum是连续张数,seralNum=1是单纯牌型,顺子时seralNum>=5; //subNum是副牌数目,三带一和四带二时subNum=1,三带二和四带两对时,subNum=2; //cards是牌型里包括的牌的牌值,比如三带一时,可能就是[3, 16, 42, 4], 连对时,可能就是 [3, 16, 4, 17, 5, 18, 6, 19]等等 //aggregate是权重,根据不同的情况求出权重,再按照权重排序所有牌型。可以是本牌型的权重,也可以是手牌里除了本牌型外剩下所有牌加在一起的权重。 struct CardNode { int32_t cardType : 4; int32_t mainNum : 4; int32_t value : 10; int32_t seralNum : 10; int32_t subNum : 4; float aggregate; std::vector cards; public: CardNode(); CardNode(int type, int val, int mainN, int len, int sub); CardNode(const CardNode &other); CardNode & operator = (const CardNode &other); bool isValidNode() const; void resetNode(); int getTopValue() const; int getMaxCapacity() const; void fillJokers() ; void merge(const CardNode & other); bool isRocket() const; bool isBomb() const; bool isExactLessThan(const CardNode & other) const; bool isStrictLessThan(const CardNode &other) const; float getPower() const; bool operator 这里先对几个定义的数据变量时行解释一下,构造函数不用讲,其它函数后其用到的时候再讲

cardType是牌型,只有三种,王炸,单纯,连续;value 是牌型的值(3-17),单纯类型为牌的面值,连续类型为起始牌的面值,相同牌型以此比较大小;mainNum是主牌张数,比如三带二和飞机里mainNum=3, 连对时, mainNum=2;seralNum是连续张数,seralNum=1是单纯牌型,顺子时seralNum>=5;subNum是副牌数目,三带一和四带二时subNum=1,三带二和四带两对时,subNum=2;cards是牌型里包括的牌的牌值(1-54),比如三带一时,可能就是[3, 16, 42, 4], 连对时,可能就是 [3, 16, 4, 17, 5, 18, 6, 19]等等aggregate是权重,根据不同的情况求出权重,再按照权重排序所有牌型。可以是本牌型的权重,也可以是手牌里除了本牌型外剩下所有牌加在一起的权重。

这里要注意的是 value 的是牌的面值:3-17,cards里的值是(1-54);在调用的时候要用一个转换函数,获取牌的面值:

//获取牌的面值 int getCardValue(int card) { //55为花牌,本软件中没有用到 if (v == kCard_Flower) { return kCard_ValueMax; } //为53即为小王 if (v == kCard_Joker1) { return kCard_ValueJoker1; } //为54即为大王 if (v == kCard_Joker2) { return kCard_ValueJoker2; } int t = v % 13; //小于3为 A 2 if (t  

4.手牌的结构体(OneHand):

//手牌结构体 struct OneHand { float totalPower;//权重 int handNum;//值 CardNode bestNode;//牌型(里面是哪些牌组成) public: OneHand():bestNode() { totalPower = kMinPowerValue; handNum = 0; } };

 

5.游戏主控制类( CardGame):

//游戏主控件类 class CardGame { public: //当前位置0-自己 1-右边(下家) 2-左边(上家) int curSeatId; ///一副牌 vector allCards; //过牌次数 int passtimes; //叫地主的次数 int m_times; //自己是否机器出牌 bool isRobotMode=false; //是否开启音效 bool isSound=true; //谁是地主 int landlordId; //翻倍数 取决于叫的倍数和炸弹 int multiple; //当前状态 0-发牌 1-叫分 2-出牌 int state; //胜利id int winId; //底分 int scroe=1000; //当前上家最大的牌,也就是自己要打他的牌 CardNode currCardNode; //选手金币输赢统计,初始化100000 int handsScore[kMaxPlayers]={100000,100000,100000}; //选手名字,完全是瞎写 哈哈 string handName[3]={"Keepmoving","Lily","AngelaBaby"}; int minScore=50;//最低底分 int maxScore=5000;//最高底分 //记录打的牌 int playedCards[kCard_TableMax]; //选手牌数组 LordCards * seatHands[kMaxPlayers]; unordered_map * powerOfCards; public: //初始化 void init(); //获取一副打乱的扑克 vector getCards(); };

 

主游戏类中主要存储一些游戏的常规变量,对游戏进制操控,分析比如记录谁是地主 底分是多少 翻几倍 打牌人出了什么牌等....

6.本游戏中最重点的类( LordCards),也是最复杂 难理解的一个类 暂时不用想太多,后面慢慢分析。

//主牌类 class LordCards { public: static int getMinSerialLength(int mainNum); static int getMaxSubNum(int mainNum); static int getDupSubNum(int mainNum); static int getCardSuit(int card); static int getCardValue(int v); static bool updateHandForNode(OneHand & best, OneHand &left, CardNode & node, bool isTrim); public: LordCards(class CardGame * game,int id, const std::vector&vec); LordCards(class CardGame * game, int id,int cards[], int num); ~LordCards(); LordCards & operator = (const LordCards & other); void assign(class CardGame * game, const std::vector&vec); void assign(class CardGame * game, int cards[], int num); public: float winRateIfLord(); bool bigEnough(); std::vector removeSubset(const std::vector & subset); int scanToTable(); public: std::string getKey(bool checkFlower, int &leastValue, int &maxCount); bool containsFlower(int value, int num); bool collectNode(CardNode & one, int value, int num); OneHand calcPowerByRemoveNode(const CardNode & node); void checkRocket (const std::string & key, OneHand & hand); void checkBomb4 (const std::string & key, OneHand & hand, int top); void checkSerial (const std::string & key, OneHand & hand, int top, int mainNum, int len, int subNum); void checkSub (const std::string & key, OneHand & hand, int mainNum, int subNum, int poss); OneHand calcPowerValue_noFlower(); OneHand calcPowerValue_expandAny(int countAny, int cardIndex); OneHand calcPowerValue(bool checkFlower=false); //打出牌 void playcards(CardNode cards); CardNode typeAndValueFind(); public: void collectAllNodes(std::set &possNodes, CardNode & node, int dup); void sortByFactorInNodes(std::vector &allNodes, const CardNode & other, bool isDirect); void getGreaterNodes_expandAny(int countAny, int cardIndex, std::set &possNodes, const CardNode &other); void getGreaterNodes_possNode(std::set &possNodes, const CardNode &other); std::vector getNodesGreaterThan(const CardNode & node); //选择最好的出牌 CardNode getBestCardNode(CardNode simple=CardNode()); void getGreaterNodes_simple(std::set &possNodes, const CardNode &other); int get_GroupData(); public: class CardGame * theGame; //位置ID int id; CardNode curretCardNode; std::vector theCards; //叫地主的倍(1 2 3) int multiple=-1; //一共打了几手牌,用于算“春天” int playTimes=0; //权重 int cardWeight; //排序 void sort(); std::vector m_fillCards[kCard_TableMax]; //kCard_KindMax表示牌的面值大小 //kCard_KindMax //0数组表示每张牌的数量 //1表示单张序列,顺子 值>4表示顺子 //2表示对子 值>1表示边对 //3三条 值为>1表示飞机 //4炸弹 int cardsTable[kCard_KindMax][kCard_TableMax]; // 保存每牌面值的数目,比如A的牌有几张 };

 

这个类里面的函数相当多,是一个比较复杂的类,他是一个 AI 计算,它能列出所有牌型,一把牌能分几次出,怎么出最合理,怎么打容易胜利 等等...

本节内容就简单讲到这,没有实际结合 ,都是抽象的东西,下节将讲解与QT相结合,和UI一起分析 讲解更容易理解。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有