C++开发斗地主(QT)第五篇之牌型权重

 

牌型权重

给定一组手牌,怎么计算它的权重呢?我们可以把手牌切分成不同的牌型,一次出牌只能出一个牌型(CardNode),手上有多少个Node就是手数。 我们可以对每种牌型,同一个牌型的每个面值,设置不同的权重。权重可以是正值,也可以是负值。

斗地主以跑牌成功为胜利,手上多一张牌,就多一分负担(。 我们可以考虑以厌恶度来衡量每张牌型的权重。如果出完手上的牌,那么手数为0,权重设为0;以此为基础,面值越小,权重越低,每多一手数就再降低一定的权重。//牌型结构体变量的定义
 

// 权重计算
#define kOneHandPower (-150) //是每多一手数,就需要降低的权重,
#define kPowerUnit (-100)//是基础厌恶值, 我们通过对不同的牌型,设置不同的bad度,最后得出牌型的权重:
#define kMinPowerValue (-1000000000.0f)//最小权重值

版型CardNode.h头文件:

#define kOneHandPower (-150) //是每多一手数,就需要降低的权重,
#define kPowerUnit (-100)//是基础厌恶值, 我们通过对不同的牌型,设置不同的bad度,最后得出牌型的权重:
#define kMinPowerValue (-1000000000.0f)//最小权重值

//合并两个vector
template <typename Ele>
void mergeTwoVectors(std::vector<Ele> & dest, const std::vector<Ele> &src) {
    dest.insert(dest.end(), src.begin(), src.end());
}

//牌型结构体
struct CardNode
{
    int32_t cardType;//牌型三种: 1 不是连续的牌,包括三条带  ,2 连续的牌 包括顺子  双顺 飞机 , 3 火箭 也就是双王
    int32_t value ;//牌的值是指单纯类型为牌的面值,连续类型为起始牌的面值,相同牌型以此比较大小;
    int32_t mainNum; //主牌张数
    int32_t seralNum;//连续张数
    int32_t subNum;//副牌张数(三条 四条所带的牌)
    float aggregate;//权重
    std::vector<int> cards;//所有的牌
public:
    CardNode();
    CardNode(int type, int val, int mainN, int len, int sub);
    CardNode(const CardNode &other);
    //赋值
    CardNode & operator = (const CardNode &other);
    bool isValidNode() const;
    //重置为0
    void resetNode();
    //获取最大牌值
    int getTopValue() const;
    //把版型变为王炸
    void fillJokers() ;
    //合并牌型的牌
    void merge(const CardNode & other);
    //是否火箭
    bool isRocket() const;
    //是否炸弹
    bool isBomb() const;
    //比较 是否比Other牌型小
    bool isExactLessThan(const CardNode & other) const;
    //比较 是否比Other牌型小
    bool isStrictLessThan(const CardNode &other) const;
    //获取牌型的权重
    float getPower() const;
    //比较 是否小于
    bool operator < (const CardNode & other) const;
    //是否相等
    bool isEqualTo(const CardNode & other) const;
    //获取牌的花色和值字符串
    std::string description() const ;
};

 

版型CardNode.cpp文件:

 

CardNode::CardNode() {
    resetNode();
}

CardNode::CardNode(int type, int val, int mainN, int len, int sub) {
    cardType = type;
    value = val;
    mainNum = mainN;
    seralNum = len;
    subNum = sub;
    aggregate = 0.0f;
    cards.clear();
}

CardNode::CardNode(const CardNode &other) {
    cardType = other.cardType;
    mainNum = other.mainNum;
    value = other.value;
    seralNum = other.seralNum;
    subNum = other.subNum;
    aggregate = other.aggregate;
    cards = other.cards;
}

CardNode & CardNode::operator = (const CardNode &other) {
    cardType = other.cardType;
    mainNum = other.mainNum;
    value = other.value;
    seralNum = other.seralNum;
    subNum = other.subNum;
    aggregate = other.aggregate;
    cards = other.cards;
    return *this;
}
//是否是一种牌型
bool CardNode::isValidNode() const {
    return mainNum != 0;
}

void CardNode::resetNode() {
    cardType = 0;
    value = 0;
    mainNum = 0;
    seralNum = 0;
    subNum = 0;
    aggregate = 0.0f;
    cards.clear();
}
//获取最大一张牌的值
int CardNode::getTopValue() const {
    int top = value;
    //顺子 或 连对 或飞机
    if (cardType == kCardType_Serial) {
        //牌的起始值+连续长度-1
        top = value + seralNum - 1;
    }
    return top;
}

//把大小王放到牌中
void CardNode::fillJokers() {
    cards.clear();

    if (cardType == kCardType_Rocket) {
        cards.push_back(kCard_Joker1);
        cards.push_back(kCard_Joker2);
    } else {
        assert(false);
    }
}
//是否火箭
bool CardNode::isRocket() const {
    return cardType == kCardType_Rocket;
}

bool CardNode::isBomb() const {
    return (seralNum==1 && mainNum >= 4 && subNum == 0);
}


// 比较是否比同一个牌型other小
bool CardNode::isExactLessThan(const CardNode & other) const {
    if (!isValidNode()) {
        return true;
    }
    return (cardType == other.cardType && mainNum == other.mainNum
            && subNum == other.subNum && seralNum == other.seralNum
            && getTopValue() < other.getTopValue());
}

// 比较炸弹是否比other小
bool CardNode::isStrictLessThan(const CardNode &other) const {
    if (!isValidNode())
        return true;

    if (isRocket()) {//自己是火箭最大
        return false;
    }
    if (other.isRocket()) {//它是火箭,肯定比它小
        return true;
    }
    if (isBomb() && other.isBomb()) {
        return getTopValue() < other.getTopValue();
    } else if (isBomb()) {
        return false;
    } else if (other.isBomb()) {
        return true;
    }

    return isExactLessThan(other);
}

 //合并两个型中的牌
void CardNode::merge(const CardNode & other) {
    mergeTwoVectors(cards, other.cards);
}

// 计算当前牌型的权重,全部是预估的;应该用AI来估算会更准确
//会返回每个节点的权重。对于某些大牌,权重为正,剩下的牌型,越厌恶的权重越小
float CardNode::getPower() const {
    float bad  = 0.0f;
    //牌型为火箭
    if (cardType == kCardType_Rocket)
    {
        bad = -8.0f; // -1.5 * 4.2f
    }
    else
    {
        //最高值为 牌值大小*2+连续张数 再除以2
        //假如对2的最高值为(15+15+1)/2=15.5 对3的最高值为: (3+3+1)/2=3.5
        //334455这样的牌型值为:(3+3+3)/2=4.5
        float top = ( value + value + seralNum)/2.0f;
        if (mainNum == 4)//如果为炸弹
        {
            if (subNum)//四张带了牌,权重变低
            {
                bad = -1.5 * 3.0f + 0.003 * (kCard_Value2 - top) + (seralNum > 1 ? seralNum : 0) * 0.002 - subNum * 0.002;
            }
            else if (value == kCard_Value2)//四张2
            {
                bad = -1.5f * 3.1f;
            }
            else //其它的四张
            {
                bad = -1.5f * 4.0f + 0.175 * (kCard_Value2 - top) + (seralNum > 1 ? seralNum : 0) * 0.002;
            }
        }
        else if (mainNum == 3)//三张
        {
            bad = 0.433  + 0.02 * (kCard_Value2 - top) + (seralNum > 1 ? seralNum : 0)  * 0.02 - subNum * 0.01;
        }
        else if (mainNum == 2) //对子
        {
            bad = 0.437  + 0.015 * (kCard_Value2 - top) + (seralNum > 2 ? seralNum : 0) * 0.02;
        }
        else  // 单牌
        {
            bad = 0.435  + 0.0151 * (kCard_Value2 - top) + (seralNum > 4 ? seralNum : 0) * 0.02;
        }
    }
    float ret = kOneHandPower + kPowerUnit * bad;
    return ret;
}


//比较牌是否比Other小
bool CardNode::operator < (const CardNode & other) const {
    if (mainNum != other.mainNum) {
        return mainNum > other.mainNum;
    }

    if (value != other.value) {
        return value < other.value;
    }

    if (cardType != other.cardType) {
        return cardType < other.cardType;
    }

    if (seralNum != other.seralNum) {
        return seralNum < other.seralNum;
    }

    if (cards.size() != other.cards.size()) {
        return cards.size() < other.cards.size();
    }

    for (int i=0; i<cards.size(); ++i) {
        if (cards[i] != other.cards[i]) {
            return cards[i] < other.cards[i];
        }
    }
    return false;
}
//比较牌是否和Other相等
bool CardNode::isEqualTo(const CardNode & other) const {
    if (isRocket() && other.isRocket()) {
        return true;
    }
    if (mainNum == other.mainNum && value == other.value
            && seralNum == other.seralNum && subNum == other.subNum) {
        return cards == other.cards;
    } else {
        return false;
    }
}


static const char * cardSuits[] = {"♣️", "♦️", "♥️", "♠️"};
static const char * cardValues[] = {"", "1", "2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A", "2"};
//获取牌的花色和值字符串
std::string CardNode::description() const {
    std::string ret = "";

    for (int i=0; i<cards.size(); ++i) {
        if (cards[i] == kCard_Flower) {
            ret += "🌹 ";
        } else if (cards[i] == kCard_Joker1) {
            ret += "jj ";
        } else if (cards[i] == kCard_Joker2) {
            ret += "JJ ";
        } else {
            ret += cardSuits[LordCards::getCardSuit(cards[i])];
            ret += cardValues[LordCards::getCardValue(cards[i])];
            ret += " ";
        }
    }

    return ret;
}

// 计算当前牌型的权重,全部是预估的;应该用AI来估算会更准确
float CardNode::getPower() const {
float bad = 0.0f;
//牌型为火箭
if (cardType == kCardType_Rocket) {
bad = -8.0f; // -1.5 * 4.2f
} else {
  //最高值为 牌值大小*2+连续张数 再除以2
  //假如对2的最高值为(15+15+1)/2=15.5 对3的最高值为: (3+3+1)/2=3.5
//334455这样的牌型值为:(3+3+3)/2=4.5
float top = ( value + value + seralNum)/2.0f;
if (mainNum == 4)//如果为炸弹
{
if (subNum) {//四张带了牌,权重变低
bad = -1.5 * 3.0f + 0.003 * (kCard_Value2 - top) + (seralNum > 1 ? seralNum : 0) * 0.002 - subNum * 0.002;
} else if (value == kCard_Value2) { //四张2
bad = -1.5f * 3.1f;
} else {//其它的四张
bad = -1.5f * 4.0f + 0.175 * (kCard_Value2 - top) + (seralNum > 1 ? seralNum : 0) * 0.002;
}
} else if (mainNum == 3) {//三张
bad = 0.433 + 0.02 * (kCard_Value2 - top) + (seralNum > 1 ? seralNum : 0) * 0.02 - subNum * 0.01;
} else if (mainNum == 2) {//对子
bad = 0.437 + 0.015 * (kCard_Value2 - top) + (seralNum > 2 ? seralNum : 0) * 0.02;
} else { // 单牌
bad = 0.435 + 0.0151 * (kCard_Value2 - top) + (seralNum > 4 ? seralNum : 0) * 0.02;
}
}
float ret = kOneHandPower + kPowerUnit * bad;
return ret;
}

CardNode.getPower() 会返回每个节点的权重。对于某些大牌,权重为正,剩下的牌型,越厌恶的权重越小。

下面通过实例让你明白什么是权重

假如抓到这样一手牌

我列出所有能出的牌型(21种)的权重,其中火箭的权重达到650,其它都是负值,值的大小是由kOneHandPower (-150)和#kPowerUnit (-100)。决定 的,可以改变它们的值来改变权重的值,这里只注重大小,不注重正负!

0 "(7,6,5,4,3,)" "牌型:2,起始牌的值:3,主牌张数:1,连续张数:5,副牌数目:0,牌的权重:-217.845"

1 "(6,)" "牌型:1,起始牌的值:6,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-206.335"

2 "(14,14,13,13,12,12,)" "牌型:2,起始牌的值:12,主牌张数:2,连续张数:3,副牌数目:0,牌的权重:-201.95"

3 "(10,10,)" "牌型:1,起始牌的值:10,主牌张数:2,连续张数:1,副牌数目:0,牌的权重:-200.45"

4 "(11,)" "牌型:1,起始牌的值:11,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-198.785"

5 "(10,)" "牌型:1,起始牌的值:10,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-200.295"

6 "(14,13,12,11,10,)" "牌型:2,起始牌的值:10,主牌张数:1,连续张数:5,副牌数目:0,牌的权重:-207.275"

7 "(12,12,)" "牌型:1,起始牌的值:12,主牌张数:2,连续张数:1,副牌数目:0,牌的权重:-197.45"

8 "(13,13,)" "牌型:1,起始牌的值:13,主牌张数:2,连续张数:1,副牌数目:0,牌的权重:-195.95"

9 "(14,14,)" "牌型:1,起始牌的值:14,主牌张数:2,连续张数:1,副牌数目:0,牌的权重:-194.45"

10 "(12,)" "牌型:1,起始牌的值:12,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-197.275"

11 "(13,)" "牌型:1,起始牌的值:13,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-195.765"

12 "(14,)" "牌型:1,起始牌的值:14,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-194.255"

13 "(3,)" "牌型:1,起始牌的值:3,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-210.865"

14 "(4,)" "牌型:1,起始牌的值:4,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-209.355"

15 "(5,)" "牌型:1,起始牌的值:5,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-207.845"

16 "(6,6,)" "牌型:1,起始牌的值:6,主牌张数:2,连续张数:1,副牌数目:0,牌的权重:-206.45"

17 "(7,)" "牌型:1,起始牌的值:7,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-204.825"

18 "(17,16,)" "牌型:3,起始牌的值:16,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:650"

19 "(16,)" "牌型:1,起始牌的值:16,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-191.235"

20 "(17,)" "牌型:1,起始牌的值:17,主牌张数:1,连续张数:1,副牌数目:0,牌的权重:-189.725"

看上面的列表,可以看出排在第一是34567顺子,为什么把它排在第一,这排是按什么算法排的,下一篇介绍:怎样列出所有能出的牌型和该出哪个牌型最对整个牌布局最好,也就是出哪个牌型对整个牌的权重最高!

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值