牌型权重
给定一组手牌,怎么计算它的权重呢?我们可以把手牌切分成不同的牌型,一次出牌只能出一个牌型(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顺子,为什么把它排在第一,这排是按什么算法排的,下一篇介绍:怎样列出所有能出的牌型和该出哪个牌型最对整个牌布局最好,也就是出哪个牌型对整个牌的权重最高!