点滴记录总结,一天一打鸡血。
2015年5月华为组织了一次软件精英挑战赛,赛题是德州扑克手牌AI~
环境及测试说明:http://pan.baidu.com/s/1dD2dL0P
最后一节吧,真心觉得贴代码没意思,反正自己不会去看代码了,只想把中间一些感触写下来,时间一长加上一忙,感觉找不到了。这时候的感触是,有货趁早写,别拿忙开脱,就是懒!这一节先别贴代码,来点鸡汤解渴先~
在能玩牌的基础上,想玩好可不太简单,特别是程序本身就是死的,这就需要策略具备一定灵活性,说到这就不由自主往机器学习上靠,其实没那么复杂,由于每次游戏有上限600局,加上决策时间有限500ms,使得机器学习在这里很难凑效的。但是,策略模型是要的,对手模型也是要的。这两个,很重要,你可以ifelse垒起来,尽管效率不太高;也可以基于一些比较成熟的算法模型,这样最好,但是需要理论基础了。
你的策略会综合很多指标,比如筹码数、玩家数、跟注额、座次顺序等,大部分玩家都是在这一点下足功夫了,因为这样简单高效。至于对手模型,我个人觉得这才是高端玩家的分水岭,像马云说的,其他不重要,了解人需求最重要,由于大部分玩家程序比较刻板,这给建立对手模型带来契机,当知道对手套路,加注弃牌收放自如。比赛中真看到的,有这样的人。赛后还调侃,打个牌都算,还怎么玩。。。
写到这打住吧,说说我的响应动作。在HOLD圈,一般不会出现随便跟注的情况,要么自己是大盲注(要咋唬),要么就是拿大牌,就加注,一般不会跟注,除非对手加注金额比较高。在之后的圈,就判断自己是否是咋唬,咋唬一套,非咋唬一套,总之只要自己加了,就不会轻易弃牌。设定加注满足一定条件就弃牌的策略,这样真的不好,初赛看这样的比赛真是虐心。。。
给出HOLD圈的正常代码吧:
char* HoldOne() {
float p = Uniform();
int card[2] = {0};
card[0] = gameInfo.card[0] % 14;
card[1] = gameInfo.card[1] % 14;
int pairs = 0;
if (card[0] == card[1])
pairs = card[0];
int len = strlen(opponent.bigid);
if (pairs >= 11) {
bigcall = 0;
if (gameInfo.total_call > 420)
return "check";
else if (gameInfo.total_call > 300)
return Raise(3);
else if (gameInfo.total_call > 120)
return Raise(4);
return Raise(8);
}
if (pairs >= 8) {
if (len > 0) {
bigcall = 1;
return Raise(2);
}
if (gameInfo.total_call < 125)
return Raise(3);
else if (gameInfo.total_call < 200)
return Raise(2);
return "check";
}
if (card[0] >=11 && card[1] >= 11) {
if (len > 0) {
bigcall = 1;
return Raise(2);
}
if (gameInfo.total_call < 125)
return Raise(3);
else if (gameInfo.total_call < 200)
return Raise(2);
return "check";
}
return FoldDecision();
}
最后有个FoldDecision(),这样写的好处之前说过,此外,还可以判断自己是否第一个出牌,第一个就可以让牌,没必要弃牌的。
下面给出RIVER圈的代码:
char* RiverOne(int detaCall) {
float p = Uniform();
int card[7] = {0};
card[0] = gameInfo.card[0] % 14;
card[1] = gameInfo.card[1] % 14;
card[2] = common_cards[0] % 14;
card[3] = common_cards[1] % 14;
card[4] = common_cards[2] % 14;
card[5] = common_cards[3] % 14;
card[6] = common_cards[4] % 14;
int max_card = Max(Max(Max(card[2], card[3]), Max(card[4], card[5])), card[6]);
if (cardType.my_type >= FOUR)//straight four
{
if (cardType.four.com > 0)
return ComFourDecision();
if (detaCall < 80)
return Raise(3);
else if (detaCall <= 200)
return Raise(2);
return "check";
}
else if (cardType.my_type == FULLHOUSE)//fullhouse
{
return FullHouseDecision(card, detaCall);
}
else if (cardType.my_type >= STRAIGHT) //s_card.flush
{
if (HasComPair(game_circle+2) >= 2) {
if (detaCall <= 120)
return "check";
return FoldDecision();
}
if (cardType.s_card.own == 5) {
if (cardType.s_card.com <= 3) {
if (detaCall < 80)
return Raise(3);
else if (detaCall <= 200)
return Raise(2);
return "check";
}
return "check";
}
if (cardType.f_card.own == 5) {
if (cardType.s_card.com <= 3) {
if (detaCall < 80)
return Raise(3);
else if (detaCall <= 200)
return Raise(2);
return "check";
}
return "check";
}
}
if (cardType.s_card.com >= 4 || cardType.f_card.com >= 4)
if (detaCall > 120)
return FoldDecision();
if (cardType.my_type == THREE) {//three
if (card[0] == cardType.three || card[1] == cardType.three) {
if (detaCall < 70)
return Raise(3);
else if (detaCall <= 120)
return Raise(2);
return "check";
}
if (card[0] >= 12 || card[1] >= 12) {
if (detaCall < 240)
return "check";
return FoldDecision();
}
if (detaCall > 100)
return FoldDecision();
return FoldDecision();
}
//under TwoPairDecision
return TwoPairDecision(card, detaCall, max_card);
return FoldDecision();
}
上面有个TwoPairDecision(card, detaCall, max_card);这样写的好处是提高代码复用率了,因为后面三个押注圈都会有这样的策略。