深搜+评估+特例优化
(在积分赛的表现大概处于20名左右的位置。)现在的天梯排名10名左右,不是该版本。
如果可以用机器学习的方法优化参数就好了,虽然开发了对战平台,但并没有用于炼丹。后附实现代码,对战平台及算法报告pdf。
建议先看一看算法报告!
算法报告链接:https://pan.baidu.com/s/119b3MlZfZjXgkZB5cO3A5Q 密码:d1tg
具体算法如下:
// 斗地主(FightTheLandlord)程序
// 深搜策略
// 最后更新于2018-6-3
// 作者:dreamingshao基于zhouhy框架下开发
// 游戏信息:http://www.botzone.org/games#FightTheLandlord
#include <iostream>
#include <set>
#include <string>
#include <cassert>
#include <cstring> // 注意memset是cstring里的
#include <algorithm>
#include <ctime>
#include <cstdlib>
#include <exception>
#include "jsoncpp/json.h" // 在平台上,C++编译时默认包含此库
using std::cout;
using std::endl;
using std::vector;
using std::sort;
using std::unique;
using std::set;
using std::string;
using std::time;
using std::srand;
using std::rand;
using std::exception;
constexpr int PLAYER_COUNT = 3;
enum class CardComboType
{
PASS, // 过
SINGLE, // 单张
PAIR, // 对子
STRAIGHT, // 顺子
STRAIGHT2, // 双顺
TRIPLET, // 三条
TRIPLET1, // 三带一
TRIPLET2, // 三带二
BOMB, // 炸弹
QUADRUPLE2, // 四带二(只)
QUADRUPLE4, // 四带二(对)
PLANE, // 飞机
PLANE1, // 飞机带小翼
PLANE2, // 飞机带大翼
SSHUTTLE, // 航天飞机
SSHUTTLE2, // 航天飞机带小翼
SSHUTTLE4, // 航天飞机带大翼
ROCKET, // 火箭
INVALID // 非法牌型
};
int cardComboScores[] = {
0, // 过
1, // 单张
2, // 对子
6, // 顺子
6, // 双顺
4, // 三条
4, // 三带一
4, // 三带二
10, // 炸弹
8, // 四带二(只)
8, // 四带二(对)
8, // 飞机
8, // 飞机带小翼
8, // 飞机带大翼
10, // 航天飞机(需要特判:二连为10分,多连为20分)
10, // 航天飞机带小翼
10, // 航天飞机带大翼
16, // 火箭
0 // 非法牌型
};
#ifndef _BOTZONE_ONLINE
string cardComboStrings[] = {
"PASS",
"SINGLE",
"PAIR",
"STRAIGHT",
"STRAIGHT2",
"TRIPLET",
"TRIPLET1",
"TRIPLET2",
"BOMB",
"QUADRUPLE2",
"QUADRUPLE4",
"PLANE",
"PLANE1",
"PLANE2",
"SSHUTTLE",
"SSHUTTLE2",
"SSHUTTLE4",
"ROCKET",
"INVALID"
};
#endif
// 用0~53这54个整数表示唯一的一张牌
using Card = short;
constexpr Card card_joker = 52;
constexpr Card card_JOKER = 53;
// 除了用0~53这54个整数表示唯一的牌,
// 这里还用另一种序号表示牌的大小(不管花色),以便比较,称作等级(Level)
// 对应关系如下:
// 3 4 5 6 7 8 9 10 J Q K A 2 小王 大王
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
using Level = short;
constexpr Level MAX_LEVEL = 15;
constexpr Level MAX_STRAIGHT_LEVEL = 11;
constexpr Level level_joker = 13;
constexpr Level level_JOKER = 14;
/**
* 将Card变成Level
*/
constexpr Level card2level(Card card)
{
return card / 4 + card / 53;
}
// 牌的组合,用于计算牌型
struct CardCombo
{
// 表示同等级的牌有多少张
// 会按个数从大到小、等级从大到小排序
struct CardPack
{
Level level;
short count;
bool operator< (const CardPack& b) const
{
if (count == b.count)
return level > b.level;
return count > b.count;
}
};
vector<Card> cards; // 原始的牌,未排序
vector<CardPack> packs; // 按数目和大小排序的牌种
CardComboType comboType; // 算出的牌型
Level comboLevel = 0; // 算出的大小序
/**
* 检查个数最多的CardPack递减了几个
*/
int findMaxSeq() const
{
for (unsigned c = 1; c < packs.size(); c++)
if (packs[c].count != packs[0].count ||
packs[c].level != packs[c - 1].level - 1)
return c;
return packs.size();
}
/**
* 这个牌型最后算总分的时候的权重
*/
int getWeight() const
{
if (comboType == CardComboType::SSHUTTLE ||
comboType == CardComboType::SSHUTTLE2 ||
comboType == CardComboType::SSHUTTLE4)
return cardComboScores[(int)comboType] + (findMaxSeq() > 2) * 10;
return cardComboScores[(int)comboType];
}
// 创建一个空牌组
CardCombo() : comboType(CardComboType::PASS) {}
/**
* 通过Card(即short)类型的迭代器创建一个牌型
* 并计算出牌型和大小序等
* 假设输入没有重复数字(即重复的Card)
*/
template <typename CARD_ITERATOR>
CardCombo(CARD_ITERATOR begin, CARD_ITERATOR end)
{
// 特判:空
if (begin == end)
{
comboType = CardComboType::PASS;
return;
}
// 每种牌有多少个
short counts[MAX_LEVEL + 1] = {};
// 同种牌的张数(有多少个单张、对子、三条、四条)
short countOfCount[5] = {};
cards = vector<Card>(begin, end);
for (Card c : cards)
counts[card2level(c)]++;
for (Level l = 0; l <= MAX_LEVEL; l++)
if (counts[l])
{
packs.push_back(CardPack{ l, counts[l] });
countOfCount[counts[l]]++;
}
sort(packs.begin(), packs.end());
// 用最多的那种牌总是可以比较大小的
comboLevel = packs[0].level;
// 计算牌型
// 按照 同种牌的张数 有几种 进行分类
vector<int> kindOfCountOfCount;
for (int i = 0; i <= 4; i++)
if (countOfCount[i])
kindOfCountOfCount.push_back(i);
sort(kindOfCountOfCount.begin(), kindOfCountOfCount.end());
int curr, lesser;
switch (kindOfCountOfCount.size())
{
case 1: // 只有一类牌
curr = countOfCount[kindOfCountOfCount[0]];
switch (kindOfCountOfCount[0])
{
case 1:
// 只有若干单张
if (curr == 1)
{
comboType = CardComboType::SINGLE;
return;
}
if (curr == 2 && packs[1].level == level_joker)
{
comboType = CardComboType::ROCKET;
return;
}
if (curr >= 5 && findMaxSeq() == curr &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::STRAIGHT;
return;
}
break;
case 2:
// 只有若干对子
if (curr == 1)
{
comboType = CardComboType::PAIR;
return;
}
if (curr >= 3 && findMaxSeq() == curr &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::STRAIGHT2;
return;
}
break;
case 3:
// 只有若干三条
if (curr == 1)
{
comboType = CardComboType::TRIPLET;
return;
}
if (findMaxSeq() == curr &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::PLANE;
return;
}
break;
case 4:
// 只有若干四条
if (curr == 1)
{
comboType = CardComboType::BOMB;
return;
}
if (findMaxSeq() == curr &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::SSHUTTLE;
return;
}
}
break;
case 2: // 有两类牌
curr = countOfCount[kindOfCountOfCount[1]];
lesser = countOfCount[kindOfCountOfCount[0]];
if (kindOfCountOfCount[1] == 3)
{
// 三条带?
if (kindOfCountOfCount[0] == 1)
{
// 三带一
if (curr == 1 && lesser == 1)
{
comboType = CardComboType::TRIPLET1;
return;
}
if (findMaxSeq() == curr && lesser == curr &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::PLANE1;
return;
}
}
if (kindOfCountOfCount[0] == 2)
{
// 三带二
if (curr == 1 && lesser == 1)
{
comboType = CardComboType::TRIPLET2;
return;
}
if (findMaxSeq() == curr && lesser == curr &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::PLANE2;
return;
}
}
}
if (kindOfCountOfCount[1] == 4)
{
// 四条带?
if (kindOfCountOfCount[0] == 1)
{
// 四条带两只 * n
if (curr == 1 && lesser == 2)
{
comboType = CardComboType::QUADRUPLE2;
return;
}
if (findMaxSeq() == curr && lesser == curr * 2 &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::SSHUTTLE2;
return;
}
}
if (kindOfCountOfCount[0] == 2)
{
// 四条带两对 * n
if (curr == 1 && lesser == 2)
{
comboType = CardComboType::QUADRUPLE4;
return;
}
if (findMaxSeq() == curr && lesser == curr * 2 &&
packs.begin()->level <= MAX_STRAIGHT_LEVEL)
{
comboType = CardComboType::SSHUTTLE4;
return;
}
}
}
}
comboType = CardComboType::INVALID;
}
/**
* 判断指定牌组能否大过当前牌组(这个函数不考虑过牌的情况!)
*/
bool canBeBeatenBy(const CardCombo& b) const
{
if (comboType == CardComboType::INVALID || b.comboType == CardComboType::INVALID)
return false;
if (b.comboType == CardComboType::ROCKET)
return true;
if (b.comboType == CardComboType::BOMB)
switch (comboType)
{
case CardComboType::ROCKET:
return false;
case CardComboType::BOMB:
return b.comboLevel > comboLevel;
default:
return true;
}
return b.comboType == comboType && b.cards.size() == cards.size() && b.comboLevel > comboLevel;
}
/**
* 从指定手牌中寻找第一个能大过当前牌组的牌组
* 如果随便出的话只出第一张
* 如果不存在则返回一个PASS的牌组
*/
template <typename CARD_ITERATOR>
CardCombo findFirstValid(CARD_ITERATOR begin, CARD_ITERATOR end) const
{
if (comboType == CardComboType::PASS) // 如果不需要大过谁,只需要随便出
{
CARD_ITERATOR second = begin;
second++;
return CardCombo(begin, second); // 那么就出第一张牌……
}
// 然后先看一下是不是火箭,是的话就过
if (comboType == CardComboType::ROCKET)
return CardCombo();
// 现在打算从手牌中凑出同牌型的牌
auto deck = vector<Card>(begin, end); // 手牌
short counts[MAX_LEVEL + 1] = {};
unsigned short kindCount = 0;
// 先数一下手牌里每种牌有多少个
for (Card c : deck)
counts[card2level(c)]++;
// 手牌如果不够用,直接不用凑了,看看能不能炸吧
if (deck.size() < cards.size())
goto failure;
// 再数一下手牌里有多少种牌
for (short c : counts)
if (c)
kindCount++;
// 否则不断增大当前牌组的主牌,看看能不能找到匹配的牌组
{
// 开始增大主牌
int mainPackCount = findMaxSeq();
bool isSequential =
comboType == CardComboType::STRAIGHT ||
comboType == CardComboType::STRAIGHT2 ||
comboType == CardCom