1. 简介 这是一个基于Qt开发的单机版的斗地主小游戏。该项目一共涉及到的类有如下:
1.卡牌类
2.玩家类
玩家类(父类):Player
机器人玩家(子类):Robot
非机器人玩家(子类):UserPlayer
3.窗口类
游戏开始加载动画窗口:Loading
游戏主窗口:GamePanel
单张卡牌窗口:CardPanel
特效动画窗口:AnimationWindow
游戏窗口中的按钮窗口:ButtonGroup
游戏结束玩家的成绩窗口:EndingPanel
自定义按钮:MyButton
游戏分数面板窗口:ScorePanel
4.游戏策略类
出牌类:PlayHand
游戏策略类:Strategy
5.游戏控制类
6.线程类
机器人玩家抢地主:RobotGrapLord
机器人玩家出牌:RobotPlayHand
7.音频类
首先,创建一个项目,项目名为Landlords,再创建一个游戏主窗口类GamePanel
,继承的基类是QMainWindow
。该类也将作为斗地主小游戏的一个主窗口。
2. 单张卡牌类Card 单张卡牌类Card主要完成的是扑克牌中花色和点数的定义,以及一些操作符重载,以方便后序的开发中更加简便和高效。
单张卡牌类Card的创建:通过选择新建、c++、c++class、类名为Card
,基类为Custom,意思是自定义,不给Card类提供基类。
2.1 Card类的头文件 该头文件主要就是定义了卡牌花色和点数的枚举类,并通过有参构造来定义一张扑克牌。在这里为了后期的开发简便,事先定义了两个card类的排序函数,后期可以通过这两个函数实现对玩家手牌的排序。由于卡牌是存储在QSet容器里面的(定义在Cards类里面的),Qt中规定该容器里面存储的元素数据必须是可以分配和可以指定的类型,如果要存储一个自定义对象类型,需要提供其比较操作符函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 class Card { public : enum CardSuit { Suit_Begin, Diamond, Club, Heart, Spade, Suit_End }; enum CardPoint { Card_Begin, Card_3, Card_4, Card_5, Card_6, Card_7, Card_8, Card_9, Card_10, Card_J, Card_Q, Card_K, Card_A, Card_2, Card_SJ, Card_BJ, Card_End }; Card (); Card (CardPoint point, CardSuit suit); void setPoint (CardPoint point) ; void setSuit (CardSuit suit) ; CardPoint point () const ; CardSuit suit () const ; private : CardPoint m_point; CardSuit m_suit; }; bool lessSort (const Card& c1, const Card& c2) ; bool greaterSort (const Card& c1, const Card& c2) ; bool operator <(const Card& c1, const Card& c2); bool operator ==(const Card& left, const Card& right); uint qHash (const Card& card) ;using CardList = QVector<Card>;
2.2 Card类函数实现 这部分就是对单张卡牌Card类声明的函数进行实现,即卡牌进行构造,设置卡牌点数、花色,获取卡牌点数、花色等。也完成了一些Card类运算符的重载实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 Card::Card (){} Card::Card (Card::CardPoint point, Card::CardSuit suit) { setPoint (point); setSuit (suit); } void Card::setPoint (Card::CardPoint point) { m_point = point; } void Card::setSuit (Card::CardSuit suit) { m_suit = suit; } Card::CardPoint Card::point () const { return m_point; } Card::CardSuit Card::suit () const { return m_suit; } bool lessSort (const Card &c1, const Card &c2) { if (c1.point () == c2.point ()){ return c1.suit () < c2.suit (); }else { return c1.point () < c2.point (); } } bool greaterSort (const Card &c1, const Card &c2) { if (c1.point () == c2.point ()){ return c1.suit () > c2.suit (); }else { return c1.point () > c2.point (); } } bool operator ==(const Card& left, const Card& right){ return (left.point ()==right.point ()&&left.suit ()==right.suit ()); } uint qHash (const Card &card) { return card.point ()*100 +card.suit (); } bool operator <(const Card& c1, const Card& c2){ return lessSort (c1, c2); }
3. 多张卡牌类Cards 多张卡牌类Cards主要是在Card类的基础上,将对卡牌的一些功能操作进行完善和增添。
多张卡牌类Cards创建:通过选择新建、c++、c++class、类名为Cards
,基类为Custom,意思是自定义,不给Card类提供基类。
3.1 Cards类的头文件 该头文件主要定义了一个成员变量m_cards
,它是存储多张卡牌的的一个容器对象,以及定义了一系列的成员函数,如对卡牌的添加、删除,和获得m_cards
对象的属性,如几张卡牌,是否为空等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 class Cards { public : enum SortType {Asc,Desc,NoSort}; Cards (); Cards (const Card& card); void add (const Card& card) ; void add (const Cards& cards) ; void add (const QVector<Cards>& cards) ; Cards& operator <<(const Card& card); Cards& operator <<(const Cards& cards); void remove (const Card& card) ; void remove (const Cards& cards) ; void remove (const QVector<Cards>&cards) ; int cardCount () ; bool isEmpty () ; void clear () ; Card::CardPoint maxPoint () ; Card::CardPoint minPoint () ; int pointCount (Card::CardPoint point) ; bool contains (const Card& card) ; bool contains (const Cards& cards) ; Card takeRandomCard () ; CardList toCardList (SortType type = Desc) ; private : QSet<Card> m_cards; };
3.2 Cards类函数实现 这部分就是对多张卡牌Cards类声明的函数进行实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 Cards::Cards (){} Cards::Cards (const Card &card) { add (card); } void Cards::add (const Card &card) { m_cards.insert (card); } void Cards::add (const Cards &cards) { m_cards.unite (cards.m_cards); } void Cards::add (const QVector<Cards> &cards) { for (int i=0 ; i<cards.count (); ++i){ add (cards.at (i)); } } Cards &Cards::operator <<(const Card& card){ add (card); return *this ; } Cards &Cards::operator <<(const Cards& cards){ add (cards); return *this ; } void Cards::remove (const QVector<Cards> &cards) { for (int i=0 ; i<cards.size (); i++){ remove (cards.at (i)); } } void Cards::remove (const Card &card) { m_cards.remove (card); } void Cards::remove (const Cards &cards) { m_cards.subtract (cards.m_cards); } int Cards::cardCount () { return m_cards.size (); } bool Cards::isEmpty () { return m_cards.isEmpty (); } void Cards::clear () { m_cards.clear (); } Card::CardPoint Cards::maxPoint () { Card::CardPoint max = Card::Card_Begin; if (!m_cards.isEmpty ()){ for (auto it = m_cards.begin (); it!=m_cards.end (); it++){ if (it->point () > max){ max = it->point (); } } } return max; } Card::CardPoint Cards::minPoint () { Card::CardPoint min = Card::Card_End; if (!m_cards.isEmpty ()){ for (auto it = m_cards.begin (); it!=m_cards.end (); it++){ if (it->point () < min){ min = it->point (); } } } return min; } int Cards::pointCount (Card::CardPoint point) { int count = 0 ; for (auto it = m_cards.begin (); it!=m_cards.end (); it++){ if (it->point () == point){ count++; } } return count; } bool Cards::contains (const Card &card) { return m_cards.contains (card); } bool Cards::contains (const Cards &cards) { return m_cards.contains (cards.m_cards); } Card Cards::takeRandomCard () { int num = QRandomGenerator::global ()->bounded (m_cards.size ()); QSet<Card>::const_iterator it = m_cards.constBegin (); for (int i=0 ; i<num; i++,it++); Card card = *it; m_cards.erase (it); return card; } CardList Cards::toCardList (Cards::SortType type) { CardList list; for (auto it=m_cards.begin (); it!=m_cards.end (); it++){ list << *it; } if (type == Asc){ std::sort (list.begin (),list.end (),lessSort); }else if (type == Desc){ std::sort (list.begin (),list.end (),greaterSort); } return list; }
4. 卡牌窗口类CardPanel 因为每张卡牌在主界面中都是以窗口的形式出现,所以该卡牌窗口类CardPanel
相当于是对卡牌对象更充分的完善。
卡牌窗口类创建:通过选择新建、c++、c++class、类名为CardPanel
,基类为QWidget。
4.1 CardPanel类头文件 该头文件加载了每张卡牌的的图片,其定义了一系列的成员变量和成员函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class CardPanel : public QWidget { Q_OBJECT public : explicit CardPanel (QWidget *parent = nullptr ) ; void setImage (const QPixmap &front, const QPixmap &back) ; QPixmap getImage () ; void setFrontSide (bool flag) ; bool isFrontSide () ; void setSeclected (bool flag) ; bool isSelected () ; void setCard (Card& card) ; Card getCard () ; void setOwner (Player* player) ; Player* getOwner () ; void clicked () ; protected : void paintEvent (QPaintEvent *event) ; void mousePressEvent (QMouseEvent *event) ; signals: void cardSelected (Qt::MouseButton button) ; private : QPixmap m_front; QPixmap m_back; bool m_isfront=true ; bool m_isSelect=false ; Card m_card; Player* m_owner=nullptr ; };
4.2 CardPanel类函数实现 这部分就是卡牌窗口类CardPanel
里面声明的函数进行实现。可以通过该类成员获得卡牌窗口的一些属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 CardPanel::CardPanel (QWidget *parent) : QWidget (parent){} void CardPanel::setImage (const QPixmap &front, const QPixmap &back) { m_front = front; m_back = back; setFixedSize (m_front.size ()); update (); } QPixmap CardPanel::getImage () { return m_front; } void CardPanel::setFrontSide (bool flag) { m_isfront = flag; } bool CardPanel::isFrontSide () { return m_isfront; } void CardPanel::setSeclected (bool flag) { m_isSelect = flag; } bool CardPanel::isSelected () { return m_isSelect; } void CardPanel::setCard (Card &card) { m_card = card; } Card CardPanel::getCard () { return m_card; } void CardPanel::setOwner (Player *player) { m_owner = player; } Player *CardPanel::getOwner () { return m_owner; } void CardPanel::clicked () { emit cardSelected (Qt::LeftButton) ; } void CardPanel::paintEvent (QPaintEvent *event) { Q_UNUSED (event); QPainter p (this ) ; if (m_isfront){ p.drawPixmap (rect (),m_front); }else { p.drawPixmap (rect (),m_back); } } void CardPanel::mousePressEvent (QMouseEvent *event) { emit cardSelected (event->button()) ; }
5. 玩家类Player 该类是作为游戏里的三个玩家的基类,即两个机器人玩家类和用户玩家类。作为基类,它定义了一些共同的属性,提示为后序的开发提供了一些辅助函数,方便得到需要的属性内容。
玩家类创建:通过选择新建、c++、c++class、类名为Player
,基类为QObject。
5.1 Player类头文件 该头文件不仅定义了许多关于玩家的成员变量和成员函数,还定义了四个虚函数,通过多态的方式来实现不同机器人玩家的这个处理逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 class Player : public QObject { Q_OBJECT public : enum Role {Lord, Farmer}; enum Sex {Man, Woman}; enum Direction {Left, Right}; enum Type {Robot, User, UnKnow}; explicit Player (QObject *parent = nullptr ) ; explicit Player (QString name, QObject *parent = nullptr ) ; void setName (QString name) ; QString getName () ; void setRole (Role role) ; Role getRole () ; void setSex (Sex sex) ; Sex getSex () ; void setDirection (Direction direction) ; Direction getDirection () ; void setType (Type type) ; Type getType () ; void setScore (int score) ; int getScore () ; void setWin (bool flag) ; bool isWin () ; void setPrevPlayer (Player* player) ; void setNextPlayer (Player* player) ; Player* getPrevPlayer () ; Player* getNextPlayer () ; void grabLordBet (int point) ; void storeDispatchCard (Card& card) ; void storeDispatchCard (Cards& cards) ; Cards getCards () ; void clearCards () ; void playHand (Cards& cards) ; Player* getPendPlayer () ; Cards getPendCards () ; void storePendingInfo (Player* player, const Cards& cards) ; virtual void prepareCallLord () ; virtual void preparePlayHand () ; virtual void thinkCallLord () ; virtual void thinkPlayHand () ; signals: void notifyGrabLordBet (Player* player, int bet) ; void notifyPlayHand (Player* player, Cards& card) ; void notifyPickCards (Player* player, Cards& cards) ; protected : QString m_name; Role m_role; Sex m_sex; Direction m_direction; Type m_type; int m_score; bool m_isWin; Player* m_prev; Player* m_next; Cards m_cards; Cards m_pendCards; Player* m_pendPlayer = nullptr ; };
5.2 Player类函数实现 该玩家类Player实现了两种类的构造函数方法,同时实现了设置和获取玩家的相关属性。而虚函数在这里只需要定义出来即可,不需要实现,留给子类实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 Player::Player (QObject *parent) : QObject (parent) { m_score = 0 ; m_isWin = false ; m_pendPlayer = nullptr ; } Player::Player (QString name, QObject *parent) : Player (parent) { m_name = name; } void Player::setName (QString name) { m_name = name; } QString Player::getName () { return m_name; } void Player::setRole (Role role) { m_role = role; } Player::Role Player::getRole () { return m_role; } void Player::setSex (Player::Sex sex) { m_sex = sex; } Player::Sex Player::getSex () { return m_sex; } void Player::setDirection (Player::Direction direction) { m_direction = direction; } Player::Direction Player::getDirection () { return m_direction; } void Player::setType (Player::Type type) { m_type = type; } Player::Type Player::getType () { return m_type; } void Player::setScore (int score) { m_score = score; } int Player::getScore () { return m_score; } void Player::setWin (bool flag) { m_isWin = flag; } bool Player::isWin () { return m_isWin; } void Player::setPrevPlayer (Player *player) { m_prev = player; } void Player::setNextPlayer (Player *player) { m_next = player; } Player *Player::getPrevPlayer () { return m_prev; } Player *Player::getNextPlayer () { return m_next; } void Player::grabLordBet (int point) { emit notifyGrabLordBet (this , point) ; } void Player::storeDispatchCard (Card &card) { m_cards.add (card); Cards cs; cs.add (card); emit notifyPickCards (this , cs) ; } void Player::storeDispatchCard (Cards &cards) { m_cards.add (cards); emit notifyPickCards (this ,cards) ; } Cards Player::getCards () { return m_cards; } void Player::clearCards () { m_cards.clear (); } void Player::playHand (Cards &cards) { m_cards.remove (cards); emit notifyPlayHand (this , cards) ; } Player *Player::getPendPlayer () { return m_pendPlayer; } Cards Player::getPendCards () { return m_pendCards; } void Player::storePendingInfo (Player *player, const Cards &cards) { m_pendPlayer = player; m_pendCards = cards; } void Player::prepareCallLord () {}void Player::preparePlayHand () {}void Player::thinkCallLord () {}void Player::thinkPlayHand () {}
6. 机器人玩家类Robot 机器人玩家类Robot的基类是玩家类Player,它主要任务就是实现从父类继承下来的虚函数。
机器人玩家类创建:通过选择新建、c++、c++class、类名为Robot
,基类设为Custom,选择Player,勾选Include QObject和Add QOBJECT。创建好后,将基类改为Player类。
6.1 Robot类头文件 机器人玩家类Robot只需要实现从父类继承来的虚函数即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Robot : public Player { Q_OBJECT public : using Player::Player; explicit Robot (QObject *parent = nullptr ) ; void prepareCallLord () override ; void preparePlayHand () override ; void thinkCallLord () override ; void thinkPlayHand () override ; };
6.2 Robot类函数实现 这部分主要就是实现了准备叫地主、考虑叫地主、准备出牌和考虑出牌。
当是机器人玩家时,它会将从父类Player继承下来的虚函数进行重写。首先是在准备叫地主函数prepareCallLord()
中,它会创建一个叫地主的子线程类RobotGrapLord
(在后面),然后执行strat()
,它会启动子线程类里面的run()
函数,而run()
函数是睡眠了2s后,调用考虑叫地主函数thinkCallLord()
。机器人玩家通过计算手牌的权重来得出是否叫地主的决定。对于准备和考虑出牌过程和这个一模一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 Robot::Robot (QObject *parent):Player (parent) { m_type = Player::Robot; } void Robot::prepareCallLord () { RobotGrapLord* subThread = new RobotGrapLord (this ); connect (subThread, &RobotGrapLord::finished, this , [=](){ qDebug () <<"RobotGrapLord 子线程对象析构....." << ",Robot name: " << this ->getName (); subThread->deleteLater (); }); subThread->start (); } void Robot::preparePlayHand () { RobotPlayHand* subThread = new RobotPlayHand (this ); connect (subThread, &RobotGrapLord::finished, this , [=](){ qDebug () <<"RobotPlayHand 子线程对象析构....." << ",Robot name: " << this ->getName (); subThread->deleteLater (); }); subThread->start (); } void Robot::thinkCallLord () { int weight = 0 ; Strategy st (this , m_cards) ; weight += st.getRangeCards (Card::Card_SJ,Card::Card_BJ).cardCount ()*6 ; QVector<Cards>optSeq = st.pickOptimalSeqSingles (); weight += optSeq.size ()*5 ; QVector<Cards>bombs = st.findCardsByCount (4 ); weight += bombs.size ()*5 ; weight += m_cards.pointCount (Card::Card_2) * 3 ; Cards tmp = m_cards; tmp.remove (optSeq); tmp.remove (bombs); Cards card2 = st.getRangeCards (Card::Card_2,Card::Card_2); tmp.remove (card2); QVector<Cards>triples = Strategy (this ,tmp).findCardsByCount (3 ); weight += triples.size ()*4 ; tmp.remove (triples); QVector<Cards>pairs = Strategy (this ,tmp).findCardsByCount (2 ); weight += pairs.size ()*1 ; if (weight >= 22 ){ grabLordBet (3 ); }else if (weight<22 && weight>=18 ){ grabLordBet (2 ); }else if (weight<18 && weight>=10 ){ grabLordBet (1 ); }else { grabLordBet (0 ); } } void Robot::thinkPlayHand () { Strategy st (this , m_cards) ; Cards cs = st.makeStrategy (); qDebug () << "打出的牌数量:" <<cs.cardCount (); playHand (cs); }
7. 用户玩家类UserPlayer 用户玩家类UserPlayer只需要实现从基类Player继承下来的虚函数准备叫地主和准备出牌,且都不需要写太多程序操作,因为这些过程都是用户通过鼠标点击来完成的。
非机器人玩家类创建:通过选择新建、c++、c++class、类名为UserPlayer
,基类设为Custom,选择Player,勾选Include QObject和Add QOBJECT。创建好后,将其基类改为Player。
7.1 UserPlayer类头文件 该类只定义从基类继承下来的虚函数准备叫地主和准备出牌。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class UserPlayer : public Player { Q_OBJECT public : using Player::Player; explicit UserPlayer (QObject *parent = nullptr ) ; void prepareCallLord () override ; void preparePlayHand () override ; signals: void startCountDown () ; };
7.2 UserPlayer类函数实现 该部分实现的两个虚函数都不需要做太多操作,因为是用户玩家,这些过程都是通过鼠标完成。但在准备出牌函数中,发出了一个信号startCountDown
,表示从轮到用户玩家出牌开始就通知主窗口计时,当秒数从15变为0时,就默认用户玩家放弃出牌。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 UserPlayer::UserPlayer (QObject *parent):Player (parent) { m_type = Player::User; } void UserPlayer::prepareCallLord () {} void UserPlayer::preparePlayHand () { emit startCountDown () ; }
8. 叫地主线程类RobotGrapLord 叫地主线程类RobotGrapLord
是专为机器人玩家设计的类,就是负责模拟叫地主这一过程。
机器人玩家叫地主线程类创建:新建、c、c++、类名为RobotGrapLord
,基类为QObject,创建好后修改基类为QThread。
8.1 RobotGrapLord类头文件 叫地主线程类RobotGrapLord就定义了一个从基类继承下来的run()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class RobotGrapLord : public QThread{ Q_OBJECT public : explicit RobotGrapLord (Player* player, QObject *parent = nullptr ) ; protected : void run () ; signals: private : Player* m_player; };
8.2 RobotGrapLor类函数实现 该子线程就睡眠了2s,模拟机器人玩家考虑的这个过程,然后调用机器人玩家类的考虑叫地主函数。
1 2 3 4 5 6 7 8 9 10 RobotGrapLord::RobotGrapLord (Player* player, QObject *parent) : QThread (parent) { m_player= player; } void RobotGrapLord::run () { msleep (2000 ); m_player->thinkCallLord (); }
9. 出牌线程类RobotPlayHand 出牌线程类RobotPlayHand
是专为机器人玩家设计的类,就是负责模拟出牌这一过程。
机器人玩家出牌线程类创建:新建、c、c++、类名为RobotPlayHand
,基类为QObject,创建好后修改基类为QThread。
9.1 RobotPlayHand类头文件 出牌线程类RobotPlayHand就定义了一个从基类继承下来的run()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class RobotPlayHand : public QThread{ Q_OBJECT public : explicit RobotPlayHand (Player* player, QObject *parent = nullptr ) ; signals: protected : void run () override ; private : Player* m_player; };
9.2 RobotPlayHand类函数实现 该子线程就睡眠了2s,模拟机器人玩家考虑的这个过程,然后调用机器人玩家类的考虑出牌函数。
1 2 3 4 5 6 7 8 9 RobotPlayHand::RobotPlayHand (Player* player, QObject *parent) : QThread (parent) { m_player = player; } void RobotPlayHand::run () { msleep (2000 ); m_player->thinkPlayHand (); }
10. 玩家分数窗口类ScorePanel 分数窗口类ScorePanel就是负责显示各个玩家的分数,在该小项目中,有两处会使用该分数窗口。一个是主窗口的左上角会显示各个玩家的分数;还有一个是一局游戏结束后,在结束面板上显示各个玩家的分数。
10.1 制作分数面板窗口 创建子窗口:
1.游戏分数面板子窗口:通过选择新建、Qt、Qt设计师界面类、选择Widget类型的窗口(可以内嵌的,以没有边框的形式完美的附着在父窗口上面),类名为ScorePanel
。
2.将分数窗口添加到主窗口(右上方)
在主窗口拖入一个Widget子窗口,因为它是一个Widget类型,而不是分数面板的类型,所以就需要进行提升,即就是把父类变为子类。基于这个理论,可以发现分数面板类ScorePanel的基类是Widget类型,所以就可以将该Widget类提升为ScorePanel类型。这样就将Qt中的标准控件变为了自定义控件(把一个基类变成了子类类型)。
10.2 ScorePanel类头文件 分数窗口类ScorePanel就定义了一些设置属性的函数,如字体的大小和颜色,这样后期要使用分数窗口的时候,也可以根据环境来设置适合的字体和颜色。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 namespace Ui {class ScorePanel ;} class ScorePanel : public QWidget{ Q_OBJECT public : enum FontColor {Black, White,Red,Blue,Green}; explicit ScorePanel (QWidget *parent = nullptr ) ; ~ScorePanel (); void setScores (int left, int right, int user) ; void setMyFontSize (int point) ; void setMyFontColor (FontColor color) ; private : Ui::ScorePanel *ui; QVector<QLabel*> m_list; };
10.3 ScorePanel类函数实现 该部分就是实现了头文件定义的成员函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const QString MYCOLOR[] = {"black" , "white" , "red" , "blue" , "green" };ScorePanel::ScorePanel (QWidget *parent) : QWidget (parent), ui (new Ui::ScorePanel) { ui->setupUi (this ); m_list << ui->meScore << ui->leftScore << ui->rightScore << ui->meTitle << ui->leftTitle << ui->rightTitle << ui->score1 << ui->score2 << ui->score3; } ScorePanel::~ScorePanel () { delete ui; } void ScorePanel::setScores (int left, int right, int user) { ui->leftScore->setText (QString::number (left)); ui->rightScore->setText (QString::number (right)); ui->meScore->setText (QString::number (user)); } void ScorePanel::setMyFontSize (int point) { QFont font ("微软雅黑" , point, QFont::Bold) ; for (int i=0 ; i<m_list.size (); i++){ m_list[i]->setFont (font); } } void ScorePanel::setMyFontColor (ScorePanel::FontColor color) { QString style = QString ("QLabel{color:%1}" ).arg (MYCOLOR[color]); for (int i=0 ; i<m_list.size (); i++){ m_list[i]->setStyleSheet (style); } }
自定义按钮类MyButton主要就是对按钮进行美化,当鼠标经过按钮、按下按钮都加载显示不同的图片,起到一个有点击的效果。
11.1 自定义按钮类 创建自定义按钮类:
通过选择新建、c++、c++class、类名为MyButton
,基类设为QWidget。但创建好后,就将MyButton的基类QWidget修改为QPushButton。
不用带ui,该类只对按钮做美化,按钮上要放什么东西,不在考虑内。从QpushButton派生,然后基于QpushButton在按钮上做美化。
自定义按钮类MyButton
主要就是将从基类继承下来的函数进行定义,如鼠标按下、鼠标释放、鼠标进入和鼠标离开。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class MyButton : public QPushButton{ Q_OBJECT public : explicit MyButton (QWidget *parent = nullptr ) ; void setImage (QString normal, QString hover, QString pressed) ; protected : void mousePressEvent (QMouseEvent* ev) ; void mouseReleaseEvent (QMouseEvent* ev) ; void enterEvent (QEvent* ev) ; void leaveEvent (QEvent* ev) ; void paintEvent (QPaintEvent* ev) ; signals: private : QString m_normal; QString m_hover; QString m_pressed; QPixmap m_pixmap; };
这部分就是对自定义按钮类MyButton的头文件定义的函数进行实现,不同情况,加载不同图片,也显示不同图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 MyButton::MyButton (QWidget *parent) : QPushButton (parent){} void MyButton::setImage (QString normal, QString hover, QString pressed) { m_normal = normal; m_hover = hover; m_pressed = pressed; m_pixmap.load (m_normal); } void MyButton::mousePressEvent (QMouseEvent *ev) { if (ev->button () == Qt::LeftButton){ m_pixmap.load (m_pressed); update (); } QPushButton::mousePressEvent (ev); } void MyButton::mouseReleaseEvent (QMouseEvent *ev) { if (ev->button () == Qt::LeftButton){ m_pixmap.load (m_normal); update (); } QPushButton::mouseReleaseEvent (ev); } void MyButton::enterEvent (QEvent *ev) { Q_UNUSED (ev); m_pixmap.load (m_hover); update (); } void MyButton::leaveEvent (QEvent *ev) { Q_UNUSED (ev); m_pixmap.load (m_normal); update (); } void MyButton::paintEvent (QPaintEvent *ev) { Q_UNUSED (ev); QPainter p (this ) ; p.drawPixmap (rect (), m_pixmap); }
按钮组窗口类主要是为用户玩家服务的,因为开始游戏界面、抢地主界面、必须出牌界面和可放弃出牌界面都需要显示出不同的按钮,所以得在ui中使用Stacked Widget栈窗口,它可以容纳多张不同的窗口,然后通过函数调用切换即可。
12.1 创建按钮组窗口 1.窗口创建:
选择新建、Qt、Qt设计师界面类(是带ui界面的)、选择Widget类型的窗口,类名为ButtonGroup
。
该类是在ui界面上拖入了一个Stacked Widget栈窗口,在该栈窗口中建了5个页面。第1个页面是开始页面;第2个页面是必须出牌页面;第3个为可放弃出牌页面;第4个为叫地主页面;第5个是空白页面。并利用信号槽机制实现了connect操作,即按下按钮,就会触发响应的按钮信号,然后发送自定义信号。该类只负责发送信号即可,不用处理相关的操作。
2.对每个页面的按钮做美化
将Stacked Widget栈窗口中的5个窗口的按钮都提升为MyButton
类。然后基于MyButton
类对窗口所有按钮进行美化。
3.添加资源文件:选择新建、Qt、Qt Resource File、名称为res。
4.将按钮组窗口添加到主窗口(中下方)
在主窗口的下方拖入一个Widget窗口,将其提升为ButtonGroup类。因为主窗口最下方放的是扑克牌,所以得在最下面放一根弹簧将ButtonGroup窗口撑起来一点。然后在主窗口析构函数中,初始化按钮组,并设定刚开的页面为游戏开始页面的按钮组即可。
按钮组窗口类ButtonGroup就是定义了一个Page页的切换函数,通过传入的参数配置页枚举类的不同,会发出信号通知主窗口该显示哪个按钮组窗口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 namespace Ui {class ButtonGroup ;} class ButtonGroup : public QWidget{ Q_OBJECT public : enum Panel {Start, PlayCard, PassOrPlay,CallLord,Empty}; explicit ButtonGroup (QWidget *parent = nullptr ) ; ~ButtonGroup (); void initButtons () ; void selectPanel (Panel type, int bet = 0 ) ; signals: void startGame () ; void playHand () ; void pass () ; void betPoint (int bet) ; private : Ui::ButtonGroup *ui; };
这部分代码实现了头文件定义的函数,对每个按钮都加载了三种图片,即常规状态下、鼠标滑过状态下和点击按钮状态下。同时也通过信号槽机制,对每个按钮按下后,都会发出信号,触发对应的槽函数处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 ButtonGroup::ButtonGroup (QWidget *parent) : QWidget (parent), ui (new Ui::ButtonGroup) { ui->setupUi (this ); } ButtonGroup::~ButtonGroup () { delete ui; } void ButtonGroup::initButtons () { ui->start->setImage (":/images/start-1.png" , ":/images/start-3.png" , ":/images/start-2.png" ); ui->playCard->setImage (":/images/chupai_btn-1.png" , ":/images/chupai_btn-3.png" , ":/images/chupai_btn-2.png" ); ui->playCard1->setImage (":/images/chupai_btn-1.png" , ":/images/chupai_btn-3.png" , ":/images/chupai_btn-2.png" ); ui->pass->setImage (":/images/pass_btn-1.png" , ":/images/pass_btn-3.png" , ":/images/pass_btn-2.png" ); ui->giveup->setImage (":/images/buqiang-1.png" , ":/images/buqiang-3.png" , ":/images/buqiang-2.png" ); ui->oneScore->setImage (":/images/1fen-1.png" , ":/images/1fen-3.png" , ":/images/1fen-2.png" ); ui->twoScore->setImage (":/images/2fen-1.png" , ":/images/2fen-3.png" , ":/images/2fen-2.png" ); ui->threeScore->setImage (":/images/3fen-1.png" , ":/images/3fen-3.png" , ":/images/3fen-2.png" ); QVector<MyButton*>btns; btns << ui->start << ui->playCard << ui->playCard1 << ui->pass << ui->giveup << ui->oneScore << ui->twoScore << ui->threeScore; for (int i=0 ; i<btns.size (); i++){ btns[i]->setFixedSize (90 ,45 ); } connect (ui->start, &MyButton::clicked, this , &ButtonGroup::startGame); connect (ui->playCard, &MyButton::clicked, this , &ButtonGroup::playHand); connect (ui->playCard1, &MyButton::clicked, this , &ButtonGroup::playHand); connect (ui->pass, &MyButton::clicked, this , &ButtonGroup::pass); connect (ui->giveup, &MyButton::clicked, this , [=](){ emit betPoint (0 ); }); connect (ui->oneScore, &MyButton::clicked, this , [=](){ emit betPoint (1 ); }); connect (ui->twoScore, &MyButton::clicked, this , [=](){ emit betPoint (2 ); }); connect (ui->threeScore, &MyButton::clicked, this , [=](){ emit betPoint (3 ); }); } void ButtonGroup::selectPanel (ButtonGroup::Panel type, int bet) { ui->stackedWidget->setCurrentIndex (type); if (type != CallLord){ return ; } if (bet == 0 ){ ui->oneScore->setVisible (true ); ui->twoScore->setVisible (true ); ui->threeScore->setVisible (true ); }else if (bet == 1 ){ ui->oneScore->setVisible (false ); ui->twoScore->setVisible (true ); ui->threeScore->setVisible (true ); }else if (bet == 2 ){ ui->oneScore->setVisible (false ); ui->twoScore->setVisible (false ); ui->threeScore->setVisible (true ); } }
13. 游戏控制类GameControl 游戏控制类负责对游戏整个过程进行一个管理和控制,像游戏状态、玩家状态等都想要通过该类来进行一个维护。同时该类也会和主窗口类直接连接,在游戏控制过程中,每个玩家的变化、游戏的变化都需要通过信号的方式通知主窗口。
游戏控制类添加:通过选择新建、c++、c++class、类名为GameControl
,基类为QObject。
13.1 GameControl类头文件 游戏控制类GameControl
定义了枚举类游戏状态和玩家状态,当它们发送变化时,都会通过信号发送出去。同时,该头文件还定义了许多游戏过程函数,如发牌、叫地主、出牌等。而且有些后面开发会用到的信息都会记录保存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 struct BetRecord { BetRecord (){ reset (); } void reset () { player = nullptr ; bet = 0 ; times = 0 ; } Player* player; int bet; int times; }; class GameControl : public QObject{ Q_OBJECT public : enum GameStatus { DispatchCard, CallingLord, PlayingHand }; enum PlayerStatus { ThinkingForCallLord, ThinkingForPlayHand, Winning }; explicit GameControl (QObject *parent = nullptr ) ; void playerInit () ; Robot* getLeftRobot () ; Robot* getRightRobot () ; UserPlayer* getUserPlayer () ; void setCurrentPlayer (Player* player) ; Player *getCurrentPlayer () ; Player* getPendPlayer () ; Cards getPendCards () ; void initAllCards () ; Card takeOneCard () ; Cards getSurplusCards () ; void resetCardData () ; void startLordCard () ; void becomeLord (Player* player, int bet) ; void clearPlayerScore () ; int getPlayerMaxBet () ; void onGrabBet (Player* player, int bet) ; void onPlayHand (Player* player, Cards& card) ; signals: void playerStatusChanged (Player* player, PlayerStatus status) ; void notifyGrabLordBet (Player* player, int bet, bool flag) ; void gameStatusChanged (GameStatus status) ; void notifyPlayHand (Player* player, Cards& card) ; void pendingInfo (Player* player, Cards& card) ; private : Robot* m_robotLeft=nullptr ; Robot* m_robotRight=nullptr ; UserPlayer* m_user=nullptr ; Player* m_currPlayer=nullptr ; Player* m_pendPlayer=nullptr ; Cards m_pendCards; Cards m_allCards; BetRecord m_betRecord; int m_curBet = 0 ; };
13.2 GameControl类函数实现 这部分实现了头文件定义的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 GameControl::GameControl (QObject *parent) : QObject (parent){} void GameControl::playerInit () { m_robotLeft = new Robot ("机器人A" , this ); m_robotRight = new Robot ("机器人B" ,this ); m_user = new UserPlayer ("我自己" ,this ); m_robotLeft->setDirection (Player::Left); m_robotRight->setDirection (Player::Right); m_user->setDirection (Player::Right); Player::Sex sex; sex = (Player::Sex)QRandomGenerator::global ()->bounded (2 ); m_robotLeft->setSex (sex); sex = (Player::Sex)QRandomGenerator::global ()->bounded (2 ); m_robotRight->setSex (sex); sex = (Player::Sex)QRandomGenerator::global ()->bounded (2 ); m_user->setSex (sex); m_user->setPrevPlayer (m_robotLeft); m_user->setNextPlayer (m_robotRight); m_robotLeft->setPrevPlayer (m_robotRight); m_robotLeft->setNextPlayer (m_user); m_robotRight->setPrevPlayer (m_user); m_robotRight->setNextPlayer (m_robotLeft); m_currPlayer = m_user; connect (m_user, &UserPlayer::notifyGrabLordBet,this ,&GameControl::onGrabBet); connect (m_robotLeft, &UserPlayer::notifyGrabLordBet,this ,&GameControl::onGrabBet); connect (m_robotRight, &UserPlayer::notifyGrabLordBet,this ,&GameControl::onGrabBet); connect (this , &GameControl::pendingInfo, m_robotLeft, &Robot::storePendingInfo); connect (this , &GameControl::pendingInfo, m_robotRight, &Robot::storePendingInfo); connect (this , &GameControl::pendingInfo, m_user, &Robot::storePendingInfo); connect (m_robotLeft, &Robot::notifyPlayHand, this , &GameControl::onPlayHand); connect (m_robotRight, &Robot::notifyPlayHand, this , &GameControl::onPlayHand); connect (m_user, &Robot::notifyPlayHand, this , &GameControl::onPlayHand); } Robot *GameControl::getLeftRobot () { return m_robotLeft; } Robot *GameControl::getRightRobot () { return m_robotRight; } UserPlayer *GameControl::getUserPlayer () { return m_user; } void GameControl::setCurrentPlayer (Player *player) { m_currPlayer = player; } Player *GameControl::getCurrentPlayer () { return m_currPlayer; } Player *GameControl::getPendPlayer () { return m_pendPlayer; } Cards GameControl::getPendCards () { return m_pendCards; } void GameControl::initAllCards () { m_allCards.clear (); for (int p=Card::Card_Begin+1 ; p<Card::Card_SJ; p++){ for (int s=Card::Suit_Begin+1 ; s<Card::Suit_End; s++){ Card c ((Card::CardPoint)p, (Card::CardSuit)s) ; m_allCards.add (c); } } m_allCards.add (Card (Card::Card_SJ, Card::Suit_Begin)); m_allCards.add (Card (Card::Card_BJ, Card::Suit_Begin)); } Card GameControl::takeOneCard () { return m_allCards.takeRandomCard (); } Cards GameControl::getSurplusCards () { return m_allCards; } void GameControl::resetCardData () { initAllCards (); m_robotLeft->clearCards (); m_robotRight->clearCards (); m_user->clearCards (); m_pendPlayer = nullptr ; m_pendCards.clear (); } void GameControl::startLordCard () { m_currPlayer->prepareCallLord (); emit playerStatusChanged (m_currPlayer, ThinkingForCallLord) ; } void GameControl::becomeLord (Player *player, int bet) { m_curBet = bet; player->setRole (Player::Lord); player->getPrevPlayer ()->setRole (Player::Farmer); player->getNextPlayer ()->setRole (Player::Farmer); m_currPlayer = player; player->storeDispatchCard (m_allCards); QTimer::singleShot (1000 , this , [=](){ emit gameStatusChanged (PlayingHand); emit playerStatusChanged (player, ThinkingForPlayHand); m_currPlayer->preparePlayHand (); }); } void GameControl::clearPlayerScore () { m_robotLeft->setScore (0 ); m_robotRight->setScore (0 ); m_user->setScore (0 ); } int GameControl::getPlayerMaxBet () { return m_betRecord.bet; } void GameControl::onGrabBet (Player *player, int bet) { if (bet==0 || m_betRecord.bet>=bet){ emit notifyGrabLordBet (player, 0 , false ) ; }else if (bet>0 && m_betRecord.bet==0 ){ emit notifyGrabLordBet (player, bet, true ); }else { emit notifyGrabLordBet (player, bet, false ); } if (bet == 3 ){ becomeLord (player,bet); m_betRecord.reset (); return ; } if (m_betRecord.bet < bet){ m_betRecord.bet = bet; m_betRecord.player = player; } m_betRecord.times++; if (m_betRecord.times == 3 ){ if (m_betRecord.bet == 0 ){ emit gameStatusChanged (DispatchCard) ; }else { becomeLord (m_betRecord.player, m_betRecord.bet); } m_betRecord.reset (); return ; } m_currPlayer = player->getNextPlayer (); emit playerStatusChanged (m_currPlayer,ThinkingForCallLord) ; m_currPlayer->prepareCallLord (); } void GameControl::onPlayHand (Player *player, Cards &card) { emit notifyPlayHand (player, card) ; if (!card.isEmpty ()){ m_pendCards = card; m_pendPlayer = player; emit pendingInfo (player, card) ; } PlayHand::HandType type = PlayHand (card).getHandType (); if (type == PlayHand::Hand_Bomb || type == PlayHand::Hand_Bomb_Jokers){ m_curBet = m_curBet*2 ; } if (player->getCards ().isEmpty ()){ Player* prev = player->getPrevPlayer (); Player* next = player->getNextPlayer (); if (player->getRole () == Player::Lord){ player->setScore (player->getScore () + 2 *m_curBet); prev->setScore (prev->getScore () - m_curBet); next->setScore (next->getScore () - m_curBet); player->setWin (true ); prev->setWin (false ); next->setWin (false ); }else { player->setWin (true ); player->setScore (player->getScore () + m_curBet); if (prev->getRole () == Player::Lord){ prev->setScore (prev->getScore () - 2 *m_curBet); next->setScore (next->getScore () + m_curBet); prev->setWin (false ); next->setWin (true ); }else { next->setScore (next->getScore () - 2 *m_curBet); prev->setScore (prev->getScore () + m_curBet); prev->setWin (true ); next->setWin (false ); } } emit playerStatusChanged (player, GameControl::Winning) ; return ; } m_currPlayer = player->getNextPlayer (); m_currPlayer->preparePlayHand (); emit playerStatusChanged (m_currPlayer, GameControl::ThinkingForPlayHand) ; }
14. 出牌类PlayHand 该类主要是对一些牌进行分类和识别,得出调用者传入牌的一个牌型。
出牌类(策略):新建、c、c++、类名为PlayHand,基类为Custom。
14.1 PlayHand类头文件 出牌类PlayeHand定义了一个牌的类型枚举类,它里面都是斗地主小游戏里面所有的牌型。然后定义了对牌的分类函数和识别函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 class PlayHand { public : enum HandType { Hand_Unknown, Hand_Pass, Hand_Single, Hand_Pair, Hand_Triple, Hand_Triple_Single, Hand_Triple_Pair, Hand_Plane, Hand_Plane_Two_Single, Hand_Plane_Two_Pair, Hand_Seq_Pair, Hand_Seq_Single, Hand_Bomb, Hand_Bomb_Single, Hand_Bomb_Pair, Hand_Bomb_Two_Single, Hand_Bomb_Jokers, Hand_Bomb_Jokers_Single, Hand_Bomb_Jokers_Pair, Hand_Bomb_Jokers_Two_Single }; PlayHand (); explicit PlayHand (Cards& cards) ; PlayHand (HandType type, Card::CardPoint pt, int extra); HandType getHandType () ; Card::CardPoint getCardPoint () ; int getExtra () ; bool canBeat (const PlayHand& other) ; private : void classify (Cards& cards) ; void judgeCardType () ; bool isPass () ; bool isSingle () ; bool isPair () ; bool isTriple () ; bool isTripleSingle () ; bool isTriplePair () ; bool isPlane () ; bool isPlaneTwoSingle () ; bool isPlaneTwoPair () ; bool isSeqPair () ; bool isSeqSingle () ; bool isBomb () ; bool isBombSingle () ; bool isBombPair () ; bool isBombTwoSingle () ; bool isBombJokers () ; bool isBombJokersSingle () ; bool isBombJokersPair () ; bool isBombJokersTwoSingle () ; private : HandType m_type; Card::CardPoint m_pt; int m_extra; QVector<Card::CardPoint> m_oneCard; QVector<Card::CardPoint> m_twoCard; QVector<Card::CardPoint> m_threeCard; QVector<Card::CardPoint> m_fourCard; };
14.2 PlayHand类函数实现 该部分实现了头文件定义的函数,首先是通过函数classify()对传进来的牌进行分类,用4个容器进行存放,分别为1张相同点数的牌容器、2张相同点数的牌容器、3张相同点数的牌容器和4张相同点数的牌容器。然后通过judgeCardType()函数基于分类的4个容器来对牌进行分析。记录扑克牌的牌型、点数和附加类型(连对的对数和顺子的张数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 #include "playhand.h" PlayHand::PlayHand (){} PlayHand::PlayHand (Cards &cards) { classify (cards); judgeCardType (); } PlayHand::PlayHand (PlayHand::HandType type, Card::CardPoint pt, int extra) { m_type = type; m_pt = pt; m_extra = extra; } PlayHand::HandType PlayHand::getHandType () { return m_type; } Card::CardPoint PlayHand::getCardPoint () { return m_pt; } int PlayHand::getExtra () { return m_extra; } bool PlayHand::canBeat (const PlayHand &other) { if (m_type == Hand_Unknown){ return false ; } if (other.m_type == Hand_Pass){ return true ; } if (m_type == Hand_Bomb_Jokers){ return true ; } if (m_type==Hand_Bomb && other.m_type>=Hand_Single && other.m_type<=Hand_Seq_Single){ return true ; } if (m_type == other.m_type){ if (m_type == Hand_Seq_Pair || m_type == Hand_Seq_Single){ return m_pt>other.m_pt && m_extra == other.m_extra; }else { return m_pt > other.m_pt; } } return false ; } void PlayHand::classify (Cards &cards) { CardList list = cards.toCardList (); int cardRecord[Card::Card_End]; memset (cardRecord, 0 , sizeof (int )*Card::Card_End); for (int i=0 ; i<list.size (); i++){ Card c = list.at (i); cardRecord[c.point ()]++; } m_oneCard.clear (); m_twoCard.clear (); m_threeCard.clear (); m_fourCard.clear (); for (int i=0 ; i<Card::Card_End; i++){ if (cardRecord[i] == 1 ){ m_oneCard.push_back ((Card::CardPoint)i); }else if (cardRecord[i] == 2 ){ m_twoCard.push_back ((Card::CardPoint)i); }else if (cardRecord[i] == 3 ){ m_threeCard.push_back ((Card::CardPoint)i); }else if (cardRecord[i] == 4 ){ m_fourCard.push_back ((Card::CardPoint)i); } } } void PlayHand::judgeCardType () { m_type = Hand_Unknown; m_pt = Card::Card_Begin; m_extra = 0 ; if (isPass ()){ m_type = Hand_Pass; } if (isSingle ()){ m_type = Hand_Single; m_pt = m_oneCard[0 ]; }else if (isPair ()){ m_type= Hand_Pair; m_pt = m_twoCard[0 ]; }else if (isTriple ()){ m_type= Hand_Triple; m_pt = m_threeCard[0 ]; }else if (isTripleSingle ()){ m_type= Hand_Triple_Single; m_pt = m_threeCard[0 ]; }else if (isTriplePair ()){ m_type= Hand_Triple_Pair; m_pt = m_threeCard[0 ]; }else if (isPlane ()){ m_type= Hand_Plane; m_pt = m_threeCard[0 ]; }else if (isPlaneTwoSingle ()){ m_type= Hand_Plane_Two_Single; m_pt = m_threeCard[0 ]; }else if (isPlaneTwoPair ()){ m_type= Hand_Plane_Two_Pair; m_pt = m_threeCard[0 ]; }else if (isSeqPair ()){ m_type= Hand_Seq_Pair; m_pt = m_twoCard[0 ]; m_extra = m_twoCard.size (); }else if (isSeqSingle ()){ m_type= Hand_Seq_Single; m_pt = m_oneCard[0 ]; m_extra = m_oneCard.size (); }else if (isBomb ()){ m_type= Hand_Bomb; m_pt = m_fourCard[0 ]; }else if (isBombSingle ()){ m_type= Hand_Bomb_Single; m_pt = m_fourCard[0 ]; }else if (isBombPair ()){ m_type= Hand_Bomb_Pair; m_pt = m_fourCard[0 ]; }else if (isBombTwoSingle ()){ m_type= Hand_Bomb_Two_Single; m_pt = m_fourCard[0 ]; }else if (isBombJokers ()){ m_type= Hand_Bomb_Jokers; }else if (isBombJokersSingle ()){ m_type= Hand_Bomb_Jokers_Single; }else if (isBombJokersPair ()){ m_type= Hand_Bomb_Jokers_Pair; }else if (isBombJokersTwoSingle ()){ m_type= Hand_Bomb_Jokers_Two_Single; } } bool PlayHand::isPass () { if (m_oneCard.size ()==0 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ return true ; } return false ; } bool PlayHand::isSingle () { if (m_oneCard.size ()==1 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ return true ; } return false ; } bool PlayHand::isPair () { if (m_oneCard.isEmpty () && m_twoCard.size ()==1 && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ return true ; } return false ; } bool PlayHand::isTriple () { if (m_oneCard.isEmpty () && m_twoCard.isEmpty () && m_threeCard.size ()==1 && m_fourCard.isEmpty ()){ return true ; } return false ; } bool PlayHand::isTripleSingle () { if (m_oneCard.size ()==1 && m_twoCard.isEmpty () && m_threeCard.size ()==1 && m_fourCard.isEmpty ()){ return true ; } return false ; } bool PlayHand::isTriplePair () { if (m_oneCard.isEmpty () && m_twoCard.size ()==1 && m_threeCard.size ()==1 && m_fourCard.isEmpty ()){ return true ; } return false ; } bool PlayHand::isPlane () { if (m_oneCard.isEmpty () && m_twoCard.isEmpty () && m_threeCard.size ()==2 && m_fourCard.isEmpty ()){ std::sort (m_threeCard.begin (),m_threeCard.end ()); if (m_threeCard[1 ]-m_threeCard[0 ]==1 && m_threeCard[1 ]<Card::Card_2){ return true ; } } return false ; } bool PlayHand::isPlaneTwoSingle () { if (m_oneCard.size ()==2 && m_twoCard.isEmpty () && m_threeCard.size ()==2 && m_fourCard.isEmpty ()){ std::sort (m_threeCard.begin (),m_threeCard.end ()); std::sort (m_oneCard.begin (),m_oneCard.end ()); if (m_threeCard[1 ]-m_threeCard[0 ]==1 && m_threeCard[1 ]<Card::Card_2 && m_oneCard[0 ]!=Card::Card_SJ && m_oneCard[1 ]!=Card::Card_BJ){ return true ; } } return false ; } bool PlayHand::isPlaneTwoPair () { if (m_oneCard.isEmpty () && m_twoCard.size ()==2 && m_threeCard.size ()==2 && m_fourCard.isEmpty ()){ std::sort (m_threeCard.begin (),m_threeCard.end ()); if (m_threeCard[1 ]-m_threeCard[0 ]==1 && m_threeCard[1 ]<Card::Card_2){ return true ; } } return false ; } bool PlayHand::isSeqPair () { if (m_oneCard.isEmpty () && m_twoCard.size ()==3 && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ std::sort (m_twoCard.begin (),m_twoCard.end ()); if (m_twoCard.last ()-m_twoCard.first ()==(m_twoCard.size ()-1 ) && m_twoCard.first ()>=Card::Card_3 && m_twoCard.last ()<Card::Card_2){ return true ; } } return false ; } bool PlayHand::isSeqSingle () { if (m_oneCard.size ()>=5 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ std::sort (m_oneCard.begin (),m_oneCard.end ()); if (m_oneCard.last ()-m_oneCard.first ()==(m_oneCard.size ()-1 ) && m_oneCard.first ()>=Card::Card_3 && m_oneCard.last ()<Card::Card_2){ return true ; } } return false ; } bool PlayHand::isBomb () { if (m_oneCard.isEmpty () && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.size ()==1 ){ return true ; } return false ; } bool PlayHand::isBombSingle () { if (m_oneCard.size ()==1 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.size ()==1 ){ return true ; } return false ; } bool PlayHand::isBombPair () { if (m_oneCard.isEmpty () && m_twoCard.size ()==1 && m_threeCard.isEmpty () && m_fourCard.size ()==1 ){ return true ; } return false ; } bool PlayHand::isBombTwoSingle () { if (m_oneCard.size ()==2 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.size ()==1 ){ std::sort (m_oneCard.begin (), m_oneCard.end ()); if (m_oneCard.first ()!=Card::Card_SJ && m_oneCard.last ()!=Card::Card_BJ){ return true ; } } return false ; } bool PlayHand::isBombJokers () { if (m_oneCard.size ()==2 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ std::sort (m_oneCard.begin (), m_oneCard.end ()); if (m_oneCard.first ()==Card::Card_SJ && m_oneCard.last ()==Card::Card_BJ){ return true ; } } return false ; } bool PlayHand::isBombJokersSingle () { if (m_oneCard.size ()==3 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ std::sort (m_oneCard.begin (), m_oneCard.end ()); if (m_oneCard[1 ]==Card::Card_SJ && m_oneCard[2 ]==Card::Card_BJ){ return true ; } } return false ; } bool PlayHand::isBombJokersPair () { if (m_oneCard.size ()==2 && m_twoCard.size ()==1 && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ std::sort (m_oneCard.begin (), m_oneCard.end ()); if (m_oneCard[0 ]==Card::Card_SJ && m_oneCard[1 ]==Card::Card_BJ){ return true ; } } return false ; } bool PlayHand::isBombJokersTwoSingle () { if (m_oneCard.size ()==4 && m_twoCard.isEmpty () && m_threeCard.isEmpty () && m_fourCard.isEmpty ()){ std::sort (m_oneCard.begin (), m_oneCard.end ()); if (m_oneCard[2 ]==Card::Card_SJ && m_oneCard[3 ]==Card::Card_BJ){ return true ; } } return false ; }
15. 游戏策略类Strategy 游戏策略类主要是负责制定斗地主小游戏的规则,和模拟机器人玩家出牌的策略。
游戏策略类创建:选择新建、c、c++、类名为Strategy,基类为Custom。
15.1 Strategy类头文件 游戏策略类Strategy头文件定义的多数是一些游戏的规则,和对机器人玩家的出牌策略,机器人玩家会根据自己的手牌来判断该出什么牌,是否抢地主等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 class Strategy { public : Strategy (Player* player, const Cards& cards); Cards makeStrategy () ; Cards firstPlay () ; Cards getGreaterCards (PlayHand type) ; bool whetherToBeat (Cards& cs) ; Cards findSamePointCards (Card::CardPoint point, int count) ; QVector<Cards>findCardsByCount (int count); Cards getRangeCards (Card::CardPoint begin, Card::CardPoint end) ; QVector<Cards>findCardType (PlayHand hand, bool beat); void pickSeqSingles (QVector<QVector<Cards>>&allSeqRecord, const QVector<Cards>&seqSingle, const Cards& cards) ; QVector<Cards> pickOptimalSeqSingles () ; private : using function = Cards (Strategy::*)(Card::CardPoint point); struct CardInfo { Card::CardPoint begin; Card::CardPoint end; int extra; bool beat; int number; int base; function getSeq; }; QVector<Cards>getCards (Card::CardPoint point, int number); QVector<Cards>getTripleSingleOrPair (Card::CardPoint begin, PlayHand::HandType type); QVector<Cards>getPlane (Card::CardPoint begin); QVector<Cards>getPlane2SingleOr2Pair (Card::CardPoint begin, PlayHand::HandType type); QVector<Cards>getSepPairOrSeqSingle (CardInfo &info); Cards getBaseSeqPair (Card::CardPoint point) ; Cards getBaseSeqSingle (Card::CardPoint point) ; QVector<Cards>getBomb (Card::CardPoint begin); private : Player* m_player; Cards m_cards; };
15.2 Strategy类函数实现 这部分是对游戏策略类的头文件中定义的函数进行实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 Strategy::Strategy (Player *player, const Cards &cards): m_player (player), m_cards (cards) { } Cards Strategy::makeStrategy () { Player* pendPlayer = m_player->getPendPlayer (); Cards pendCards = m_player->getPendCards (); if (pendPlayer == m_player || pendPlayer == nullptr ){ return firstPlay (); }else { PlayHand type (pendCards); Cards beatCards = getGreaterCards (type); bool shouldBeat = whetherToBeat (beatCards); if (shouldBeat){ return beatCards; }else { return Cards (); } } return Cards (); } Cards Strategy::firstPlay () { PlayHand hand (m_cards) ; if (hand.getHandType () != PlayHand::Hand_Unknown){ return m_cards; } QVector<Cards> optimalSeq = pickOptimalSeqSingles (); if (!optimalSeq.isEmpty ()){ int baseNum = findCardsByCount (1 ).size (); Cards save = m_cards; save.remove (optimalSeq); int lastNum = Strategy (m_player,save).findCardsByCount (1 ).size (); if (baseNum > lastNum){ return optimalSeq[0 ]; } } bool hasPlane, hasTriple, hasPair; hasPair = hasTriple = hasPlane = false ; Cards backup = m_cards; QVector<Cards> bombArray = findCardType (PlayHand (PlayHand::Hand_Bomb, Card::Card_Begin,0 ),false ); backup.remove (bombArray); QVector<Cards>planeArray = Strategy (m_player,backup).findCardType (PlayHand (PlayHand::Hand_Plane,Card::Card_Begin,0 ),false ); if (!planeArray.isEmpty ()){ hasPlane = true ; backup.remove (planeArray); } QVector<Cards>seqTripleArray = Strategy (m_player,backup).findCardType (PlayHand (PlayHand::Hand_Triple,Card::Card_Begin,0 ),false ); if (!seqTripleArray.isEmpty ()){ hasTriple = true ; backup.remove (seqTripleArray); } QVector<Cards>seqPairArray = Strategy (m_player,backup).findCardType (PlayHand (PlayHand::Hand_Seq_Pair,Card::Card_Begin,0 ),false ); if (!seqPairArray.isEmpty ()){ hasPair = true ; backup.remove (seqPairArray); } if (hasPair){ Cards maxPair; for (int i=0 ; i<seqPairArray.size (); i++){ if (seqPairArray[i].cardCount () > maxPair.cardCount ()){ maxPair = seqPairArray[i]; } } return maxPair; } if (hasPlane){ bool twoPairFond = false ; QVector<Cards> pairArray; for (Card::CardPoint point=Card::Card_3; point<=Card::Card_10; point=Card::CardPoint (point+1 )){ Cards pair = Strategy (m_player,backup).findSamePointCards (point,2 ); if (!pair.isEmpty ()){ pairArray.push_back (pair); if (pairArray.size ()==2 ){ twoPairFond = true ; break ; } } } if (twoPairFond){ Cards tmp = planeArray[0 ]; tmp.add (pairArray); return tmp; }else { bool twoSingleFond = false ; QVector<Cards> singleArray; for (Card::CardPoint point=Card::Card_3; point<=Card::Card_10; point=Card::CardPoint (point+1 )){ if (backup.pointCount (point) == 1 ){ Cards single = Strategy (m_player,backup).findSamePointCards (point,1 ); if (!single.isEmpty ()){ singleArray.push_back (single); if (singleArray.size ()==2 ){ twoSingleFond = true ; break ; } } } } if (twoPairFond){ Cards tmp = planeArray[0 ]; tmp.add (singleArray); return tmp; }else { return planeArray[0 ]; } } } if (hasTriple){ if (PlayHand (seqTripleArray[0 ]).getCardPoint ()<Card::Card_A){ for (Card::CardPoint point=Card::Card_3; point<=Card::Card_A; point=Card::CardPoint (point+1 )){ int pointCount = backup.pointCount (point); if (pointCount == 1 ){ Cards single = Strategy (m_player, backup).findSamePointCards (point,1 ); Cards tmp = seqTripleArray[0 ]; tmp.add (single); return tmp; }else if (pointCount==2 ){ Cards pair = Strategy (m_player, backup).findSamePointCards (point,2 ); Cards tmp = seqTripleArray[0 ]; tmp.add (pair); return tmp; } } } return seqTripleArray[0 ]; } Player* nextPlayer = m_player->getNextPlayer (); if (nextPlayer->getCards ().cardCount ()==1 && m_player->getRole ()!=nextPlayer->getRole ()){ for (Card::CardPoint point = Card::CardPoint (Card::Card_End-1 ); point>=Card::Card_3; point=Card::CardPoint (point-1 )){ int pointCount = backup.pointCount (point); if (pointCount == 1 ){ Cards single = Strategy (m_player,backup).findSamePointCards (point,1 ); return single; }else if (pointCount == 2 ){ Cards pair = Strategy (m_player,backup).findSamePointCards (point,2 ); return pair; } } }else { for (Card::CardPoint point = Card::Card_3; point<Card::Card_End; point=Card::CardPoint (point+1 )){ int pointCount = backup.pointCount (point); if (pointCount == 1 ){ Cards single = Strategy (m_player,backup).findSamePointCards (point,1 ); return single; }else if (pointCount == 2 ){ Cards pair = Strategy (m_player,backup).findSamePointCards (point,2 ); return pair; } } } return Cards (); } Cards Strategy::getGreaterCards (PlayHand type) { Player* pendPlayer = m_player->getPendPlayer (); if (pendPlayer != nullptr && pendPlayer->getRole ()!=m_player->getRole () && pendPlayer->getCards ().cardCount ()<=3 ){ QVector<Cards> bombs = findCardsByCount (4 ); for (int i=0 ; i<bombs.size (); i++){ if (PlayHand (bombs[i]).canBeat (type)){ return bombs[i]; } } Cards sj = findSamePointCards (Card::Card_SJ,1 ); Cards bj = findSamePointCards (Card::Card_BJ,1 ); if (!sj.isEmpty () && !bj.isEmpty ()){ Cards jokers; jokers << sj << bj; return jokers; } } Player* nextPlayer = m_player->getNextPlayer (); Cards remain = m_cards; remain.remove (Strategy (m_player,remain).pickOptimalSeqSingles ()); auto beatCard = std::bind ([=](Cards &cards){ QVector<Cards>beatCardsArray = Strategy (m_player,cards).findCardType (type, true ); if (!beatCardsArray.isEmpty ()){ if (m_player->getRole ()!=nextPlayer->getRole () && nextPlayer->getCards ().cardCount ()<=2 ){ return beatCardsArray.back (); }else { return beatCardsArray.front (); } } return Cards (); }, std::placeholders::_1); Cards cs; if (!(cs=beatCard (remain)).isEmpty ()){ return cs; }else { if (!(cs=beatCard (m_cards)).isEmpty ()){ return cs; } } return Cards (); } bool Strategy::whetherToBeat (Cards &cs) { if (cs.isEmpty ()){ return false ; } Player* pendPlayer = m_player->getPendPlayer (); if (m_player->getRole () == pendPlayer->getRole ()){ Cards left = m_cards; left.remove (cs); if (PlayHand (left).getHandType ()!=PlayHand::Hand_Unknown){ return true ; } Card::CardPoint basePoint = PlayHand (cs).getCardPoint (); if (basePoint==Card::Card_2 || basePoint==Card::Card_SJ || basePoint==Card::Card_BJ){ return false ; } }else { PlayHand myHand (cs); if ((myHand.getHandType ()==PlayHand::Hand_Triple_Single || myHand.getHandType ()==PlayHand::Hand_Triple_Pair) && myHand.getCardPoint ()==Card::Card_2){ return false ; } if (myHand.getHandType ()==PlayHand::Hand_Pair && myHand.getCardPoint ()==Card::Card_2 && pendPlayer->getCards ().cardCount ()>=10 && m_player->getCards ().cardCount ()>=5 ){ return false ; } } return true ; } Cards Strategy::findSamePointCards (Card::CardPoint point, int count) { if (count<1 || count>4 ){ return Cards (); } if (point == Card::Card_SJ || point == Card::Card_BJ){ if (count>1 ){ return Cards (); } Card card; card.setPoint (point); card.setSuit (Card::Suit_Begin); if (m_cards.contains (card)){ Cards cards; cards.add (card); return cards; } return Cards (); } int findCount = 0 ; Cards findCards; for (int suit=Card::Suit_Begin+1 ; suit<Card::Suit_End; suit++){ Card card; card.setPoint (point); card.setSuit ((Card::CardSuit)suit); if (m_cards.contains (card)){ findCount++; findCards.add (card); if (findCount == count){ return findCards; } } } return Cards (); } QVector<Cards> Strategy::findCardsByCount (int count) { if (count<1 || count>4 ){ return QVector <Cards>(); } QVector<Cards>cardsArray; for (Card::CardPoint point=Card::Card_3; point<Card::Card_End; point=(Card::CardPoint)(point+1 )){ if (m_cards.pointCount (point)==count){ Cards cs; cs << findSamePointCards (point,count); cardsArray << cs; } } return cardsArray; } Cards Strategy::getRangeCards (Card::CardPoint begin, Card::CardPoint end) { Cards rangeCards; for (Card::CardPoint point=begin; point<end; point=(Card::CardPoint)(point+1 )){ int count = m_cards.pointCount (point); Cards cs = findSamePointCards (point, count); rangeCards << cs; } return rangeCards; } QVector<Cards> Strategy::findCardType (PlayHand hand, bool beat) { PlayHand::HandType type = hand.getHandType (); Card::CardPoint point = hand.getCardPoint (); int extra = hand.getExtra (); Card::CardPoint beginPoint = beat?Card::CardPoint (point+1 ):Card::Card_3; switch (type){ case PlayHand::Hand_Single: return getCards (beginPoint, 1 ); case PlayHand::Hand_Pair: return getCards (beginPoint, 2 ); case PlayHand::Hand_Triple: return getCards (beginPoint, 3 ); case PlayHand::Hand_Triple_Single: return getTripleSingleOrPair (beginPoint, PlayHand::Hand_Single); case PlayHand::Hand_Triple_Pair: return getTripleSingleOrPair (beginPoint, PlayHand::Hand_Pair); case PlayHand::Hand_Plane: return getPlane (beginPoint); case ::PlayHand::Hand_Plane_Two_Single: return getPlane2SingleOr2Pair (beginPoint, PlayHand::Hand_Single); case ::PlayHand::Hand_Plane_Two_Pair: return getPlane2SingleOr2Pair (beginPoint, PlayHand::Hand_Pair); case PlayHand::Hand_Seq_Pair: { CardInfo info; info.begin = beginPoint; info.end = Card::Card_Q; info.number = 2 ; info.base = 3 ; info.extra = extra; info.beat = beat; info.getSeq = &Strategy::getBaseSeqPair; return getSepPairOrSeqSingle (info); } case PlayHand::Hand_Seq_Single: { CardInfo info; info.begin = beginPoint; info.end = Card::Card_10; info.number = 1 ; info.base = 5 ; info.extra = extra; info.beat = beat; info.getSeq = &Strategy::getBaseSeqSingle; return getSepPairOrSeqSingle (info); } case PlayHand::Hand_Bomb: return getBomb (beginPoint); default : return QVector <Cards>(); } } void Strategy::pickSeqSingles (QVector<QVector<Cards> > &allSeqRecord, const QVector<Cards> &seqSingle, const Cards &cards) { QVector<Cards> allSeq = Strategy (m_player,cards).findCardType (PlayHand (PlayHand::Hand_Seq_Single,Card::Card_Begin,0 ),false ); if (allSeq.isEmpty ()){ allSeqRecord << seqSingle; }else { Cards saveCards = cards; for (int i=0 ; i<allSeq.size (); i++){ Cards aScheme = allSeq.at (i); Cards temp = saveCards; temp.remove (aScheme); QVector<Cards> seqArray = seqSingle; seqArray << aScheme; pickSeqSingles (allSeqRecord,seqArray,temp); } } } QVector<Cards> Strategy::pickOptimalSeqSingles () { QVector<QVector<Cards>>seqRecord; QVector<Cards> seqSingles; Cards save = m_cards; save.remove (findCardsByCount (4 )); save.remove (findCardsByCount (3 )); pickSeqSingles (seqRecord, seqSingles, save); if (seqRecord.isEmpty ()){ return QVector <Cards>(); } QMap<int ,int >seqMarks; for (int i=0 ; i<seqRecord.size ();i++){ Cards backupCards = m_cards; QVector<Cards>seqArray = seqRecord[i]; backupCards.remove (seqArray); QVector<Cards> singleArray = Strategy (m_player, backupCards).findCardsByCount (1 ); CardList cardList; for (int j=0 ; j<singleArray.size (); j++){ cardList << singleArray[j].toCardList (); } int mark = 0 ; for (int j=0 ; j<cardList.size (); j++){ mark += cardList[j].point () + 15 ; } seqMarks.insert (i,mark); } int value = 0 ; int comMark = 1000 ; auto it = seqMarks.constBegin (); for (; it!=seqMarks.constEnd (); it++){ if (it.value () < comMark){ comMark = it.value (); value = it.key (); } } return seqRecord[value]; } QVector<Cards> Strategy::getCards (Card::CardPoint point, int number) { QVector<Cards> findCardsArray; for (Card::CardPoint pt=point; pt<Card::Card_End; pt=(Card::CardPoint)(pt+1 )){ if (m_cards.pointCount (pt)==number){ Cards cs = findSamePointCards (pt, number); findCardsArray << cs; } } return findCardsArray; } QVector<Cards> Strategy::getTripleSingleOrPair (Card::CardPoint begin, PlayHand::HandType type) { QVector<Cards>findCardArray = getCards (begin,3 ); if (!findCardArray.isEmpty ()){ Cards remainCards = m_cards; remainCards.remove (findCardArray); Strategy st (m_player, remainCards) ; QVector<Cards>cardsArray = st.findCardType (PlayHand (type,Card::Card_Begin,0 ),false ); if (!cardsArray.isEmpty ()){ for (int i=0 ; i<findCardArray.size ();i++){ findCardArray[i].add (cardsArray.at (i)); } }else { findCardArray.clear (); } } return findCardArray; } QVector<Cards> Strategy::getPlane (Card::CardPoint begin) { QVector<Cards> findCardArray; for (Card::CardPoint point=begin; point<=Card::Card_K; point=(Card::CardPoint)(point+1 )){ Cards prevCards = findSamePointCards (point, 3 ); Cards nextCards = findSamePointCards ((Card::CardPoint)(point+1 ),3 ); if (!prevCards.isEmpty () && !nextCards.isEmpty ()){ Cards tmp; tmp << prevCards << nextCards; findCardArray << tmp; } } return findCardArray; } QVector<Cards> Strategy::getPlane2SingleOr2Pair (Card::CardPoint begin, PlayHand::HandType type) { QVector<Cards>findCardArray = getPlane (begin); if (!findCardArray.isEmpty ()){ Cards remainCards = m_cards; remainCards.remove (findCardArray); Strategy st (m_player, remainCards) ; QVector<Cards>cardsArray = st.findCardType (PlayHand (type, Card::Card_Begin, 0 ), false ); if (cardsArray.size ()>=2 ){ for (int i=0 ; i<findCardArray.size ();i++){ Cards tmp; tmp << cardsArray[0 ] << cardsArray[1 ]; findCardArray[i].add (tmp); } }else { findCardArray.clear (); } } return findCardArray; } QVector<Cards> Strategy::getSepPairOrSeqSingle (CardInfo &info) { QVector<Cards>findCardsArray; if (info.beat){ for (Card::CardPoint point=info.begin; point<=info.end; point=(Card::CardPoint)(point+1 )){ bool found = true ; Cards seqCards; for (int i=0 ; i<info.extra; i++){ Cards cards = findSamePointCards ((Card::CardPoint)(point+i),info.number); if (cards.isEmpty () || (point+info.extra>=Card::Card_2)){ found = false ; seqCards.clear (); break ; }else { seqCards << cards; } } if (found){ findCardsArray << seqCards; return findCardsArray; } } }else { for (Card::CardPoint point=info.begin; point<=info.end; point=(Card::CardPoint)(point+1 )){ Cards baseSeq = (this ->*info.getSeq)(point); if (baseSeq.isEmpty ()){ continue ; } findCardsArray << baseSeq; int followed = info.base; Cards alreadyFollowedCards; while (true ){ Card::CardPoint followedPoint = Card::CardPoint (point+followed); if (followedPoint >= Card::Card_2){ break ; } Cards follwedCards = findSamePointCards (followedPoint, info.number); if (follwedCards.isEmpty ()){ break ; }else { alreadyFollowedCards << follwedCards; Cards newSeq = baseSeq; newSeq << alreadyFollowedCards; findCardsArray << newSeq; followed++; } } } } return findCardsArray; } Cards Strategy::getBaseSeqPair (Card::CardPoint point) { Cards cards0 = findSamePointCards (point, 2 ); Cards cards1 = findSamePointCards ((Card::CardPoint)(point+1 ),2 ); Cards cards2 = findSamePointCards ((Card::CardPoint)(point+2 ),2 ); Cards baseSeq; if (!cards0.isEmpty () && !cards1.isEmpty () && !cards2.isEmpty ()){ baseSeq << cards0 << cards1 << cards2; } return baseSeq; } Cards Strategy::getBaseSeqSingle (Card::CardPoint point) { Cards cards0 = findSamePointCards (point, 1 ); Cards cards1 = findSamePointCards ((Card::CardPoint)(point+1 ),1 ); Cards cards2 = findSamePointCards ((Card::CardPoint)(point+2 ),1 ); Cards cards3 = findSamePointCards ((Card::CardPoint)(point+3 ),1 ); Cards cards4 = findSamePointCards ((Card::CardPoint)(point+4 ),1 ); Cards baseSeq; if (!cards0.isEmpty () && !cards1.isEmpty () && !cards2.isEmpty () && !cards3.isEmpty () && !cards4.isEmpty ()){ baseSeq << cards0 << cards1 << cards2 << cards3 << cards4; } return baseSeq; } QVector<Cards> Strategy::getBomb (Card::CardPoint begin) { QVector<Cards> findcardsArray; for (Card::CardPoint point=begin; point<Card::Card_End; point=(Card::CardPoint)(point+1 )){ Cards cs = findSamePointCards (point, 4 ); if (!cs.isEmpty ()){ findcardsArray << cs; } } return findcardsArray; }