C++开发斗地主(QT)第三篇之动画发牌与位置计算

本篇讲的是是怎样发牌,计算牌的准确位置,请看下面桌面:

发好牌后的样子:

 

自己家的牌位置很好计算:

假如牌的大小为122*150,数量为n,每张牌的间隔是40,窗口的宽为width(),高为height()按从左到右的排列,后面盖住前面。则起点位置为:x=(width()-((n-1)*40+122))/2

y=height()-150-60.

(n-1)*40是除最后一张牌的宽度,再加上最后一张全部显示的宽度122,就是所有牌的宽度。

左右两边的位置比较麻烦,请看下图:(我用的五笔打字,下图有错字,如遇到错字不要笑话)

由于发牌的时候,牌始终在中间显示,而且又有角度,Y在变,X也在变。以右边为例,当X高于中心点O的时候,X不断减小;当X大于O的时候,X在不断增加。右边的牌是从下往上发的,我们不但要求出Y的起始点,还要知道X的变化。假如有n张牌,中心一张的牌的位置为n/2,x偏移量为7,Y的偏移量为-11,牌的大小为61*75,则i张牌的X位置为:

x=1000+(n/2-i)*7

y=height()-(height()-(n-1)*11-75)/2-60;

 x,y为牌的坐标。

下面给出主窗体MainWindow的源码

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "classes/cardai.h"
#include "vector"
using namespase std;
class CardWidget;
//主窗口继承QMainWindow窗口类,也可以继承QWidget,程序中的所有控件都是代码创建的
class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    //这是牌计算控制类,第一篇讲的
    CardGame *cardGame;
    //开始游戏函数,用于每次调用,打完牌都要调用
    void startGame();
    //显示牌发牌 出牌过程中不断调用更新牌的位置 参数seatId 为(0,1,2) 哪个人的位置
    void showCards(int seatId);
protected:
    //重写绘制函数,绘制背景
    virtual void paintEvent(QPaintEvent *event);
public slots:
    void slot_start(int);
private:
    //所有牌控件列表
    vector<CardWidget*> allCards;
    //每个人的牌
    vector<vector<CardWidget*>> playerCards;
    //底牌
    vector<CardWidget*> bottomCards;
};
#endif // MAINWINDOW_H

上面就是主窗口的头文件,暂时先定义这些;

下在看源MainWindow.cpp文件

#include "mainwindow.h"
#include "qpropertyanimation.h"
#include "qdebug.h"
#include "QEventLoop"
#include "cardwidget.h"
#include "qevent.h"
#include "qtimer.h"
#include "qpainter.h"
#include "qpixmap.h"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    //设置所有控件的字体
    setStyleSheet("QWidget{font-family:方正粗圆简体;}");
    //窗口标题
    setWindowTitle("MM斗地主");
    //创建游戏算法控制对象
    cardGame=new CardGame();
    //设置窗口大小不可变(最大和最小都一样)
    setMinimumSize(1280,720);
    setMaximumSize(1280,720);
    //创建三张底牌
    for(int i=0;i<3;i++)
    {
        CardWidget* card=new CardWidget(0,0,true,this);
        //设置牌的绝对位置和大小,通过背景图片计算来的
        card->setGeometry(520+(i)*90,120,61,75);
        //暂时隐藏
        card->hide();
        //存入底牌数组
        bottomCards.push_back(card);
        //顺便创建一个人牌数组,因为这里刚好三次,也是三个人,就放这里了。
        playerCards.push_back(vector<CardWidget*>());
    }
    
    
}

//重绘背景函数比例简单
void MainWindow::paintEvent(QPaintEvent *event)
{
    //定义画板为本背景
    QPainter painter(this);
     //导入图像
    QPixmap pix(":/images/bg.png");
    //绘到窗口中,rect()是窗口大小,位置是 0,0,width(),height()
    painter.drawPixmap(rect(),pix);
}
//开始游戏 一些初始化和发牌
void MainWindow::startGame()
{
    //隐藏三张底牌
    foreach(CardWidget *card,bottomCards)
    {
        card->hide();
    }
    //删除所有牌(UI)
    foreach(CardWidget *card,allCards)
    {
        delete  card;
        card=nullptr;
    }
    allCards.clear();
    //清除每个人的牌
    for (int i=0;i<3;i++)
    {
       playerCards[i].clear();
    }
    //播放开始音乐
    playSound("start.mp3");
    //主游戏类初始化 主要是一些变量
    cardGame->init();
    //获取一副随机打乱的牌
    vector<int> CardValueArray=cardGame->getCards();
       //随机切牌
    srand(time(0));
    //获取1-55之间随机数
    int seat=rand()%55;
    //获取首个发牌位置0-自己 1-右边 2-左边,以便确定谁先叫地主
    int startSeat=getCardValue(seat)%3;//获取牌值后除以3取余数
    cardGame->curSeatId=startSeat;
    int count=0;
    vector<int> tmpCards;
    //按切牌的位置重新排列牌
    while(count<54)
    {
        tmpCards.push_back(CardValueArray.at(seat));
        seat++;
        if(seat>53)
        {
            seat=0;
        }
        count++;
    }
    allCards.clear();
    //把牌绘到桌面上,只绘背面,准备发牌
    for(int j=tmpCards.size()-1;j>=0;j--)
    {
        CardWidget *card=new CardWidget(tmpCards.at(j),0,false,this);
        //牌的位置
        card->setGeometry(580,300,96,120);
        //显示
        card->show();
        //置顶显示
        card->raise();
        //存入数组
        allCards.push_back(card);
    }


    //发牌
    for(int j=allCards.size()-1;j>=0;j--)
    {
        //QT动画类,动画类型为"geometry",即控件的位置和大小
        QPropertyAnimation* animation= new QPropertyAnimation( allCards[j],"geometry");
        //整个动画时间
        animation->setDuration(100);
        //设置动画的开始位置,也就是控件当前的位置
        animation->setStartValue(allCards[j]->geometry());
        //定义一个阻塞,等动画完成后,再进行下一个动画,回为QT动画是多线程的,所有牌会一下子一起发了。
        QEventLoop loop1;
        //对最后三张牌做动画处理,放到上面等待谁叫地主
        if(j==2)//倒数第三张牌
        {

            animation->setDuration(500);
            animation->setEndValue(QRect(520,120,61,75));
            //开始动画
            animation->start();
            //定义一个动画结束信号,如果动画结束,阻塞也结束(相当于VS中的消息)
            connect(animation,SIGNAL(finished()),&loop1,SLOT(quit()));
            //开始执行阻塞
            loop1.exec();
            //更新 并返回循环
            allCards[j]->update();
            continue;
        }
        if(j==1)//倒数第二张牌
        {
            animation->setDuration(500);
            animation->setEndValue(QRect(610,120,61,75));
            animation->start();
            connect(animation,SIGNAL(finished()),&loop1,SLOT(quit()));
            loop1.exec();
            allCards[j]->update();
            continue;
        }
        if(j==0)//倒数第一张牌
        {
            animation->setDuration(500);
            animation->setEndValue(QRect(700,120,61,75));
            animation->start();
            connect(animation,SIGNAL(finished()),&loop1,SLOT(quit()));
            loop1.exec();
            allCards[j]->update();
            continue;
        }

        //创建发牌动画 设置三个人的动画结束位置
        if(startSeat%3==2)//左边玩家
        {

            animation->setEndValue(QRect(allCards[j]->x()-300,allCards[j]->y(),61,75));
        }
        else if(startSeat%3==1)//右边玩家
        {

            animation->setEndValue(QRect(allCards[j]->x()+300,allCards[j]->y(),61,75));
        }
        else if(startSeat%3==0)//自己的牌
        {
            animation->setEndValue(QRect(allCards[j]->x(),allCards[j]->y()+200,122,150));
        }
        //开始
        animation->start();
        //等待动画完成
        connect(animation,SIGNAL(finished()),&loop1,SLOT(quit()));
        loop1.exec();

       
        if(startSeat%3==2)//左边玩家
        {
            allCards[j]->angle=CarcAngle;//系张牌水平转120度,以便和桌子平行
            allCards[j]->update();
            allCards[j]->raise();
            playerCards[2].push_back(allCards[j]);
            showCards(2);
            cardGame->seatHands[2]->theCards.push_back(allCards[j]->value);
        }
        else if(startSeat%3==1)//右边玩家
        {
            allCards[j]->raise();
            allCards[j]->angle=-CarcAngle;//系张牌水平转负120度,以便和桌子平行
            allCards[j]->update();
            playerCards[1].push_back(allCards[j]);
            cardGame->seatHands[1]->theCards.push_back(allCards[j]->value);
            showCards(1);
        }
        else if(startSeat%3==0)//自己的牌
        {
            allCards[j]->raise();//控件置顶显示
            playerCards[0].push_back(allCards[j]);
            allCards[j]->isShow=true;//显示正面
            cardGame->seatHands[0]->theCards.push_back(allCards[j]->value);
            //更新所有牌位置
            showCards(0);
        }
        startSeat++;
        //添加发牌音效
        // PlaySound *playsound=new PlaySound("sounds/211.mp3");
        // playsound->start();
    }
    for(int i=0;i<3;i++)
    {   
        //对牌控制类中的牌排序(由小到大(3-17))
        cardGame->seatHands[i]->sort();
        m_haveCardList[i]->setText(QString::number(cardGame->seatHands[i]->theCards.size()));
        //对三个人的桌面牌按从大到小排序,打牌习惯问题
        for(int j=0;j<cardGame->seatHands[i]->theCards.size();j++)
        {
            playerCards[i][j]->value=cardGame->seatHands[i]->theCards.at(cardGame->seatHands[i]->theCards.size()-1-j);
            playerCards[i][j]->raise();
            playerCards[i][j]->update();
        }
    }
  }
//显示牌函数,参数为哪个人(0,1,2)
void MainWindow::showCards(int index)
{
    //获取牌的张数
    int n= playerCards[index].size();
    if(n==0)
        return;
    //自己的牌左边(X)位置计算
    int left=(width()-(n-1)*40-playerCards[index][0]->width())/2;
    //自己的牌上面(Y)位置
    int top=height()-playerCards[index][0]->height()-70;
    //下家Y起始位置
    int topSpace1=height()-(height()-(n-1)*11-playerCards[index][0]->height())/2-60;
    //上家Y起始位置
    int topSpace2=(height()-(n-1)*11-playerCards[index][0]->height())/2+20;
    
    for(int i=0;i<n;i++)
    {
        //playerList[index][i]->isShow=true;
        // playerList[index][i]->update();
        if(index==0)
        {
            //自己的牌 只需更新X位置从左到右
            playerCards[index][i]->move(left+i*40,top);
        }
        else if(index==1)
        {
            //下家牌,X Y都要更新,从下到上方向
            playerCards[index][i]->move(1000+((n/2)-i)*7,topSpace1-i*11);
        }
        else
        {    
             //上家牌,X Y都要更新,从上到下方向
            playerCards[index][i]->move(210+((n/2)-i)*7,topSpace2+i*11);
        }
        //牌置顶
        playerCards[index][i]->raise();
        //如果牌是选中状态,保持选中状态
        if(playerCards[index][i]->isClick)
        {
            playerCards[index][i]->setClick(true);
        }
    }
}

 

CardGame.h

 

//游戏主控制类
class CardGame
{
public:
    //当前位置0-自己 1-右边 2-左边
    int     curSeatId;
    ///一副牌
    vector<int> 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<std::string, OneHand> * powerOfCards;

public:
    void    init();
    //获取一副打乱的扑克
    vector<int> getCards();
};

CardGame.cpp源码如下

//游戏初始化
void CardGame::init()
{
    powerOfCards        =nullptr;
    powerOfCards = new unordered_map<std::string, OneHand>();
    OneHand hand;
    hand.totalPower = 0.0f;
    (*powerOfCards)[""] = hand;
    m_times=0;
    passtimes=0;
    curSeatId    = -1;
    isRobotMode=true;
    landlordId=-1;
    multiple=-1;
    winId=-1;
    currCardNode=CardNode();
    memset(playedCards,0,sizeof(playedCards));
    state=-1;
    allCards=getCards();
    std::vector<int> cards;
    //初始化三个选手类
    for (int i=0; i<kMaxPlayers; ++i)
    {
        seatHands[i] = new LordCards(this,i,cards);
    }
}
//获取一副随机牌
vector<int> CardGame::getCards()
{
    vector<int> cards;
    for(int i=1;i<=54;i++)
    {
        cards.push_back(i);
    }
    srand(uint32_t(time(nullptr)));
    //洗三次
    for(int n=0;n<3;n++)
    {
        for (uint32_t i=0;i<54;i++)
        {
            //随机交换
            swap(cards[i], cards[rand()%54]);
        }
    }
    return  cards;
}

本篇讲的是牌的生成与发送到指定位置,相关介绍与分析都放到源码中,其它的没有过多介绍。

下面是动画演示视频:

C++开发斗地主源码之发牌视频

 

 从下一篇介绍,将介绍斗地主AI算法,因为牌都显示到桌面上了,要叫地主,什么样的牌能叫地主呢?这就要对牌进行分析!

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++写的基于MFC界面的斗地主游戏源码,内含详细注释,附带了简单的AI出牌规则,放出来供大家参考交流。vs2010编写,vs2015测试可用,理论上vs05及以上都可正常编译运行。 void Judge::MainFlow() { switch(DataCenter::Instance().GetPlayState()) { case EM_LandHolderBorn_PlayState: { //先检查是否已经问完了 //遍历玩家检查是否已经询问过了,如果已经都问过了,则设置叫分最高的为地主 BOOL bAllAsked = TRUE;//是否已经询问完了 vector & vecPlayer = DataCenter::Instance().GetPlayerList(); for (UINT i = 0; i m_nCurHighstScore) { m_nCurHighstScore = vecPlayer[i].GetLandOwerScore(); m_pToBeLandOwer = &vecPlayer;[i]; } if (vecPlayer[i].GetLandOwerScore() SetLandOwer(TRUE); } //然后根据情况执行询问流程 //如果地主已经产生,则跳入下一阶段 if (NULL != DataCenter::Instance().GetLandOwner()) { m_pCurPlayer = NULL; DataCenter::Instance().SetPlayState(EM_WaitPlayer_PlayState); MainFlow(); return; } //如果当前player为空,设置当前player为地主牌得主 if (m_pCurPlayer == NULL) { m_pCurPlayer = DataCenter::Instance().GetLandOwnerCardHolder(); } //对当前玩家执行地主问询 ASSERT(m_pCurPlayer); m_pCurPlayer->ExcuteCallLandOwer(); } break; case EM_WaitPlayer_PlayState: { //如果游戏已经结束,则执行结束逻辑 BOOL bLandOwerWin = FALSE; if (DataCenter::Instance().IsOver(bLandOwerWin)) { if (bLandOwerWin) { AfxMessageBox(_T("地主赢了!")); } else { AfxMessageBox(_T("佃户赢了!")); } DataCenter::Instance().SetPlayState(EM_WaitToStart_PlayState); //将所有玩家明牌 DataCenter::Instance().ShowAllPlayerCard(); RefreshView(); return; } //如果是出牌阶段而当前player为空,设置当前player为地主,并发予底牌 if (m_pCurPlayer == NULL) { m_pCurPlayer = DataCenter::Instance().GetLandOwner(); DataCenter::Instance().SendOutBottomCard(); RefreshView(); } ASSERT(m_pCurPlayer); m_pCurPlayer->ExcuteCallCardPlay(); } break; } } void Judge::CurPlayerCallScore(int nScore) { if (m_pCurPlayer == NULL) { ASSERT(FALSE); return; } //将玩家选择的分数设置给玩家 m_pCurPlayer->SetLandOwerScore(nScore); //如果当前玩家为空,直接返回 if(m_pCurPlayer == NULL) { return; } if (nScore == 3) { //如果玩家叫了三分,直接设为地主 m_pCurPlayer->SetLandOwer(TRUE); } else { //玩家叫的不是三分,则记下玩家叫的分数 m_pCurPlayer->SetLandOwerScore(nScore); } if (nScore == 0) { CString strWord; strWord.Format(_T("不叫")); m_pCurPlayer->Say(strWord); } else { CString strWord; strWord.Format(_T("%d分"), nScore); m_pCurPlayer->Say(strWord); } //玩家叫分后隐藏叫地主按钮 Judge::Instance().ShowCallLandOwerBtn(FALSE); //切换到下一个玩家,流程继续 SwitchToNextPlayer(); MainFlow(); }
棋牌游戏测试面试题包括以下内容: 1. 棋牌游戏算法-牌分类 2. 棋牌游戏算法-找出一副牌里的顺子 这些问题涉及到了游戏规则和算法的知识。在测试中,面试官可能会要求你解释这些算法的原理并给出相应的实现代码。 其他可能涉及到的面试题目包括: - Cocos Creator的使用 - Lua的特点和绑定 - Cocos2d-x的新特性 - 网络协议和通信 - 内存管理机制 - C++中的堆和栈 - 虚函数和纯虚函数 - 数据结构和算法的时间复杂度 这些问题主要考察了面试者对Cocos2d-x游戏引擎以及相关编程语言和算法的理解和掌握程度。 在面试前,你可以通过学习和实践来提高自己在这些方面的知识水平,以便更好地应对面试挑战。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [面试题..](https://blog.csdn.net/u013321328/article/details/90602947)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [棋牌游戏测试用例怎么写?我敢打赌你绝对不知道](https://blog.csdn.net/MXB1220/article/details/129145963)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值