本篇讲的是是怎样发牌,计算牌的准确位置,请看下面桌面:
发好牌后的样子:
自己家的牌位置很好计算:
假如牌的大小为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算法,因为牌都显示到桌面上了,要叫地主,什么样的牌能叫地主呢?这就要对牌进行分析!