诈金花是很多男人最爱的卡牌游戏 , 每当你拿到三张牌的时候, 生活重新充满了期待和鸟语花香. 那么我们如果判断手中的牌在所有可能出现的牌中占据的百分比位置呢.
这是最终效果:
这是更多的结果:
在此做些简单的说明:
- 炸弹(有些地方叫豹子) > 同花顺 > 同花 > 顺子 > 对子 > 散牌
- 同类型的组合先比较最大的牌的点数. 然后是第二大的点数, 然后是第三大的点数, 最后才是比较花色,同样也是从最大牌开始
- 最大的顺子是A K Q , 最小的顺子是 A 2 3 , 同花顺也是同样道理
- 对子先比较的成对的点数,其次是散牌的点数,然后是成对的花色,最后是散牌的花色
- 这两个百分比的意思是这样的, 第一个百分比是指这三张牌的组合在所有可能的组合中所超过的比例. 第二个百分比的意思是实际牌局中, 他超过的所有不含有这三张牌之外的所有场景的个数. 这两个百分比相差很小.
- 主要的判断逻辑在Hand类中,界面代码非常简单 , 一共六个文件,可以直接编译运行
cpp
//Hand.h
#ifndef CARD_H
#define CARD_H
#include <QDebug>
#include <qglobal.h>
class Hand{
public:
//最小的点是Ace , 对应的点数是4, 5 ,6 ,7
//最大的点是K , 对应的点数是52,53,54,55
enum class BaseType{
Junk5 = 0,//最大点数是5的散牌
Junk6,Junk7,Junk8,Junk9,Junk10,JunkJ,JunkQ,JunkK,JunkA,
Pair , Sequence,Suit,Flush,Bomb
};
Hand(char card1,char card2 ,char card3);
Hand(const Hand& that);
Hand& operator=(const Hand& that) = delete;
bool operator<(const Hand& that)const;
//这手牌的战斗值,最小的牌是1,最大的牌是22100
int value()const;
//是炸弹
bool isBomb()const;
//是同花顺
bool isFlush()const;
//是同花
bool isSuitedOnly()const;
//是顺子
bool isSequenceOnly()const;
//是对子
bool isPair()const;
//是垃圾牌
bool isJunk()const;
QString toString()const; //不重要
QString card1()const; //不重要
char card1Color()const; //不重要
QString card2()const; //不重要
char card2Color()const; //不重要
QString card3()const; //不重要
char card3Color()const; //不重要
bool clash(const Hand* that)const; //不重要
private:
BaseType baseType()const;
int sumColor()const; //三张牌的花色值的简单相加
char pairPoint()const;// 在对子中成对的点数
char singlePoint()const;//在对子中,散牌的点数
char pairColorValue()const;//对子的花色带来的加分,0到5之间
int fragmentValue(BaseType t)const;//基本类型确定后的剩余分数
int baseValue(BaseType t)const;//基本类型的加分
bool isSuited()const; //是同花 包含同花顺
bool isSequence()const; //是顺子 包含同花顺
QString strPoint(char c)const;//不重要
QString strColor(char c)const;//不重要
void sortCard(char* arr)const;//不重要
char a;
char b;
char c;
};
#endif // CARD_H
cpp
//Hand.cpp
#include "Hand.h"
int Hand::sumColor()const{
return a%4 + b%4 + c%4;
}
char Hand::pairPoint()const{// 在对子中成对的点数
if(a/4 == b/4) return a;
return b;
}
char Hand::singlePoint()const{//在对子中,散牌的点数
if(a/4 == b/4) return c;
return a;
}
char Hand::pairColorValue()const{//对子的花色带来的加分,0到5之间
char max,min;
if(a/4 == b/4){
max = b%4;
min = a%4;
}
else{
max = c%4;
min = b%4;
}
if(max == 3){
if(2 == min) return 5;
if(1 == min) return 4;
if(0 == min) return 3;
}
else if(2 == max){
if(1 == min) return 2;
if(0 == min) return 1;
}
return 0;
}
int Hand::fragmentValue(BaseType t)const{
int ret = 0;
static const char colorCombinationValueList[4][4][4] = {
{{0, 1, 2, 3} , {4, 5, 6,7} , {8,9,10,11} , {12,13,14,15}},
{{16,17,18,19} , {20,0, 21,22} , {23,24,25,26} , {27,28,29,30}},
{{31,32,33,34} , {35,36,37,38} , {39,40,0 ,41} , {42,43,44,45}},
{{46,47,48,49} , {50,51,52,53} , {54,55,56,57} , {58,59,60,0 }}
};
static const char midPointListA[14] = {0,0,0,0,0,2,5,9,14,20,27,35,44,54};
static const char midPointList[13] = {0,0,0,0,1,3,6,10,15,21,28,36,45};
if(t == BaseType::Bomb){
if(a/4 == 1){
return 48 + sumColor() - 2;
}
else{
return (a/4-2) * 4 + sumColor() - 2;
}
}
else if(t == BaseType::Flush){
if(a/4 == 1 && c/4 == 13){//Q K A
return 44 + sumColor() / 3 + 1;
}
else{
return (a/4 - 1)*4 + sumColor() /3 + 1;
}
}
else if(t == BaseType::Suit){
if(a/4 == 1){
ret += 840;//(12*11*10/6-10)*4 Ace带来的加分
ret += midPointListA[c/4] * 4; //第二大的牌带来的加分
ret += (b/4-2)*4; //最小牌带来的加分
ret += a%4+1; //花色带来的加分
}
else {
const auto max = c/4;
ret += ((max-2)*(max-3)*(max-4)/6-(max-4))*4; //最大牌带来的加分 624(K) 448 308 200 120 64 28 8(6)
const auto mid = b/4;
const auto min = a/4;
ret += midPointList[mid] * 4; //第二大的牌带来的加分
ret += (min - 2) * 4;
ret += a%4 + 1;
}
}
else if(t == BaseType::Sequence){//共720个情况; 4*4*4*12-48
if(a/4 == 1 && c/4 == 13){//Q K A
ret += 660;//组合带来的加分
ret += colorCombinationValueList[a%4][c%4][b%4];
}
else{
ret += (c/4-3) * 60;
ret += colorCombinationValueList[c%4][b%4][a%4];
}
}
else if(t == BaseType::Pair){//6 * 48 * 13
const auto pair = pairPoint();
const auto single = singlePoint();
if(pair/4 == 1){//A A ?
ret += 6*48*12;//对子点数带来的分数
ret += (single/4-2)*24;//散牌点数带来的分数
}
else{
ret += 6*48*(pair/4-2); //对子点数带来的分数
if(single/4 == 1){//Ace是散牌
ret += 11*24;//散牌点数带来的分数
}
else{
if(single / 4 < pair /4)
ret += (single/4-2)*24;
else
ret += (single/4-3)*24;
}
}
ret += pairColorValue() * 4;//对子花色带来的分数
ret += single%4 + 1;//散牌的花色带来的分数
}
else if(t == BaseType::JunkA){//3840;// ((14-2)*(14-3)/2-2)*(4*4*4-4)
auto mid = c/4;
auto min = b/4;
ret += midPointListA[mid] * 60;//中间点数带来的加分
ret += (min-2)*60;//最小点数带来的加分
ret += colorCombinationValueList[a%4][c%4][b%4];
}
else {//3240; (11*10/2-1)*(4*4*4-4)
auto mid = b/4;
auto min = a/4;
ret += midPointList[mid] * 60;
ret += (min-2) * 60;
ret += colorCombinationValueList[c%4][b%4][a%4];
}
return ret;
}
Hand::BaseType Hand::baseType()const{
if(isBomb()) return BaseType::Bomb;
if(isFlush()) return BaseType::Flush;
if(isSuitedOnly()) return BaseType::Suit;
if(isSequenceOnly()) return BaseType::Sequence;
if(isPair()) return BaseType::Pair;
if(isJunk()){
if(a/4 == 1 ) return BaseType::JunkA;
if(c/4 == 13) return BaseType::JunkK;
if(c/4 == 12) return BaseType::JunkQ;
if(c/4 == 11) return BaseType::JunkJ;
if(c/4 == 10) return BaseType::Junk10;
if(c/4 == 9 ) return BaseType::Junk9;
if(c/4 == 8 ) return BaseType::Junk8;
if(c/4 == 7 ) return BaseType::Junk7;
if(c/4 == 6 ) return BaseType::Junk6;
if(c/4 == 5 ) return BaseType::Junk5;
Q_ASSERT(c/4 > 4);
}
Q_ASSERT(false);
}
Hand::Hand(char card1,char card2 ,char card3){
Q_ASSERT(card1 != card2 && card1 != card3 && card2 != card3);
Q_ASSERT(card1>=4 && card1<56 && card2>=4 && card2<56 && card3>=4 && card3<56);
char arr[3] = {card1,card2,card3};
std::sort(arr,arr+3);
a = arr[0];//min
b = arr[1];
c = arr[2];//max
}
Hand::Hand(const Hand& that):a(that.a),b(that.b),c(that.c){}
bool Hand::operator<(const Hand& that)const{
return value() < that.value();
}
int Hand::value()const{
const auto type = baseType();
const int base = baseValue(type);
const int fragment = fragmentValue(type);
return base + fragment;
}
bool Hand::isBomb()const{//炸弹
return a/4 == c/4;
}
bool Hand::isFlush()const{//同花顺
return isSuited() && isSequence();
}
bool Hand::isSuitedOnly()const{//只是同花
return isSuited() && !isSequence();
}
bool Hand::isSequenceOnly()const{//只是顺子
return isSequence() && !isSuited();
}
bool Hand::isPair()const{//对子
return (a/4 == b/4 || b/4 == c/4) && a/4 != c/4;
}
bool Hand::isJunk()const{//散牌
return !isBomb() && !isSuited() && !isSequence() && !isPair();
}
QString Hand::toString()const{
return card1() + "-" + card1Color() + " , " +
card2() + "-" + card2Color() + " , " +
card3() + "-" + card3Color();
}
void Hand::sortCard(char* arr)const{
auto _a = a;
auto _b = b;
auto _c = c;
if(_a/4 == 1) _a += 52;
if(_b/4 == 1) _b += 52;
if(_c/4 == 1) _c += 52;
arr[0] = _a;
arr[1] = _b;
arr[2] = _c;
std::sort(arr,arr+3,[](char _1,char _2){
return _1 > _2;
});
}
static const char* strList[15] = {
"","","2","3","4","5","6","7","8","9","10","J","Q","K","A"
};
QString Hand::card1()const{
char arr[3];
sortCard(arr);
return strList[arr[0] / 4];
}
char Hand::card1Color()const{
char arr[3];
sortCard(arr);
return arr[0] % 4;
}
QString Hand::card2()const{
char arr[3];
sortCard(arr);
return strList[arr[1] / 4];
}
char Hand::card2Color()const{
char arr[3];
sortCard(arr);
return arr[1] % 4;
}
QString Hand::card3()const{
char arr[3];
sortCard(arr);
return strList[arr[2] / 4];
}
char Hand::card3Color()const{
char arr[3];
sortCard(arr);
return arr[2] % 4;
}
bool Hand::clash(const Hand* that)const{
if(a == that->a || a == that->b || a == that->c ||
b == that->a || b == that->b || b == that->c ||
c == that->a || c == that->b || c == that->c) return true;
return false;
}
int Hand::baseValue(Hand::BaseType t)const{
static constexpr int mNodeList[15] = {//每种场景的数量
120,300,540,840,1200,1620,2100,2640,3240,3840,3744, 720, 1096, 48, 52
//5 6 7 8 9 10 J Q K A 对子 顺子 同花 同花顺 炸弹
};
int idx = (int)t;
int ret = 0;
for(int i = 0;i < idx;++i) ret += mNodeList[i];
return ret;
}
bool Hand::isSuited()const{//同花
return a%4 == b%4 && a%4 == c%4;
}
bool Hand::isSequence()const{//顺子
if(a/4 == 1){
if(b/4 == 2 && c/4 == 3) return true;
if(b/4 == 12 && c/4 == 13) return true;
return false;
}
return (b/4-a/4)==1 && (c/4-b/4)==1;
}
QString Hand::strPoint(char c)const{
const auto p = c / 4;
if(p == 1) return "Ace";
if(p == 11) return "J";
if(p == 12) return "Q";
if(p == 13) return "K";
return QString::number(p);
}
QString Hand::strColor(char c)const{
const auto spade = c % 4;
if(spade == 0) return "0";
if(spade == 1) return "1";
if(spade == 2) return "2";
if(spade == 3) return "3";
return "null";
}
cpp
//CardWidget.h
#ifndef CARDWIDGET_H
#define CARDWIDGET_H
#include <QWidget>
class CardWidget : public QWidget
{
Q_OBJECT
public:
explicit CardWidget(char card,QWidget *parent = nullptr);
inline void recordOriginalPos(const QPointF& p){ mOriginalPos = p; }
inline QPointF originalPos()const{return mOriginalPos;}
const char mCard;
private:
QPointF mOriginalPos;
protected:
void mousePressEvent(QMouseEvent*);
void paintEvent(QPaintEvent* e);
signals:
void cardClicked(CardWidget*);
public slots:
};
#endif // CARDWIDGET_H
cpp
//CardWidget.cpp
#include "CardWidget.h"
#include <QPainter>
#include <QDebug>
CardWidget::CardWidget(char c,QWidget *parent) : QWidget(parent),mCard(c) {}
void CardWidget::mousePressEvent(QMouseEvent*){
emit cardClicked(this);
}
void CardWidget::paintEvent(QPaintEvent* e){
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(Qt::NoPen);
painter.setBrush(QBrush("lightgray"));
painter.drawRoundedRect(rect(),8,8);
static const QFont font("Microsoft YaHei",16,2);
static const QFont fontIcon("Microsoft YaHei",32,2);
static const char* pointList[14] = {"","A","2","3","4","5","6","7","8","9","10","J","Q","K"};
static const char* colorList[4] = {"♦","♣","♥","♠"};
static const QPen penList[4] = {QPen("lightpink") , QPen("hotpink") , QPen("orangered") ,QPen("red")};
const qreal x = width();
const qreal y = height();
painter.setFont(font);
painter.setPen("black");
painter.drawText(QRectF(0,0,x*0.5,x/2),Qt::AlignCenter,pointList[mCard/4]);
painter.setFont(fontIcon);
painter.setPen(penList[mCard%4]);
painter.drawText(QRect(0,x/2,x,y-x/2),Qt::AlignCenter,colorList[mCard%4]);
}
cpp
//DemoCardTable.h
#ifndef DEMOCARDTABLE_H
#define DEMOCARDTABLE_H
#include <QWidget>
#include "CardWidget.h"
#include <QLabel>
#include "Hand.h"
class DemoCardTable : public QWidget
{
Q_OBJECT
public:
explicit DemoCardTable(QWidget *parent = nullptr);
private:
QLabel* mInfo;
QList<CardWidget*> mSelectList;
QList<Hand*> mHandList;
void moveCardWithAnimation(CardWidget* w,const QPointF& start,const QPointF& end);
signals:
public slots:
void onCardClicked(CardWidget*);
};
#endif // DEMOCARDTABLE_H
cpp
//DemoCardTable.cpp
#include "DemoCardTable.h"
#include <QPropertyAnimation>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
DemoCardTable t;
t.setWindowTitle("炸金花牌面值分析");
t.show();
return a.exec();
}
const int gCardW = 50;
const int gCardH = 75;
const int gCardGap = 4;
static bool compareHands(const Hand* a,const Hand* b){
return a->value() < b->value();
}
DemoCardTable::DemoCardTable(QWidget *parent) : QWidget(parent)
{
for(char i = 0;i < 4;++i){
for(char j = 1;j < 14;++j){
auto* card = new CardWidget(j*4+i,this);
connect(card,&CardWidget::cardClicked,this,&DemoCardTable::onCardClicked);
card->setFixedSize(gCardW,gCardH);
QPointF pos( (j-1)*(gCardW+gCardGap),(i+1)*(gCardH+gCardGap));
card->recordOriginalPos(pos);
card->move(pos.toPoint());
}
}
mInfo = new QLabel(this);
mInfo->setFixedSize(10*(gCardW+gCardGap)-gCardGap,gCardH);
mInfo->setFont(QFont("Microsoft YaHei",16,2));
mInfo->setWordWrap(true);
mInfo->move(3*(gCardW+gCardGap),0);
setFixedSize(13*(gCardW+gCardGap)-gCardGap,5*(gCardH+gCardGap));
for(char a = 55;a>=6;--a){
for(char b = a-1;b >= 5;--b){
for(char c = b-1;c >= 4;--c){
mHandList.push_back(new Hand(a,b,c));
}
}
}
std::sort(mHandList.begin(),mHandList.end(),compareHands);
}
void DemoCardTable::moveCardWithAnimation(CardWidget* w,const QPointF& start,const QPointF& end){
auto* anim = new QPropertyAnimation(w,"pos");
anim->setStartValue(start);
anim->setEndValue(end);
anim->setDuration(400);
connect(anim,&QPropertyAnimation::finished,anim,&QObject::deleteLater);
anim->start();
}
void DemoCardTable::onCardClicked(CardWidget* w){
if(!w) return;
if(mSelectList.size() >= 3) {
//reset pos
for(int i = 0;i < mSelectList.size();++i){
auto* card = mSelectList[i];
if(!card) continue;
moveCardWithAnimation(card,card->pos(),card->originalPos());
}
//clear record
mSelectList.clear();
mInfo->clear();
}
//move card
int x = mSelectList.size() * (gCardW+gCardGap);
moveCardWithAnimation(w,w->originalPos(),QPointF(x,0));
//record
mSelectList.push_back(w);
if(mSelectList.size() == 3){
//calculate
Hand hand(mSelectList[0]->mCard,mSelectList[1]->mCard,mSelectList[2]->mCard);
const int v =hand.value();
const int index = v-1;
float percent = index / 22100.0*100;
int cnt1 = 0;
int cnt2 = 0;
for(int i = 0;i < index;++i){
if(mHandList[i]->clash(&hand)) cnt1++;
}
for(int i = index+1;i< 22100;++i){
if(mHandList[i]->clash(&hand)) cnt2++;
}
float percent2 = (index - cnt1)*1.0/(22100-cnt1-cnt2-1)*100;
QString info = "当前牌面值超过了 " + QString::number(percent,'g',5) + "% 的理论场景\n"+
"实际牌局中,它超过了 " + QString::number(percent2,'g',5) +"% 的场景";
mInfo->setText(info);
}
}