以斗地主AI为例,探讨数值体系的设计和后期调整方案(二)

发表于2017-08-11
评论1 3.5k浏览


上一篇,我们谈到计算如何当前牌型的计分的问题,有前面的铺垫,终于走到本主题的核心部分了。查看上篇:以斗地主AI为例,探讨数值体系的设计和后期调整方案(一)

说点题外话,之前我发现,市面上成功项目的系统公开、技术公开还有一些,不过相关的数值体系公开真的少之又少。当时很是气愤,找一些特别想知道的信息怎么就那么难呢?

然而,做完这个AI设计第一版,测试效果还不错的时候。我突然觉得,整个代码公开是没所谓的(当然,写的太丑,没什么技术成分是关键),但是那些数字,每一个都是我玩几千把斗地主的心血,每改一个数字,都又几十把过去了。不舍得把他们交给别人(是嫁女儿的感觉么?)

但是,后来想通了,以后会写出更多、更完善、更有价值的数值设计的,现在自己看来的宝石,再未来的自己看来也只是一个路边的石头罢了。抛弃过去,才能早点走向未来。

 

这时候就离不开代码的展示了,整个体系核心数值部分基本都在这一段代码里面。

 

// 注:这是第一版的数值效果。可以说是凭感觉随手写出来的,反正当时想着先出来,再慢慢改嘛,不过实际效果,这个体系虽然不够完整,但足以作为一个判断依据了,代码如下。

// 把牌型算分,配合上面的找出牌型

func paixingscore(i []int) int {

         var score int

         if len(i) == 1 { //单牌的算分

                   switch i[0] {

                   case 3, 4, 5, 6:

                            score = -100

                   case 7, 8, 9, 10:

                            score = -80

                   case 11, 12:

                            score = -55

                   case 13:

                            score = -35

                  case 14:

                            score = -15

                   case 15:

                            score = 30

                   case 16:

                            score = 60

                   case 17:

                            score = 100

                   default:

                   }

         }

         if len(i) == 2 { //对子的算分

                   switch i[0] {

                   case 3, 4, 5, 6:

                            score = -95

                   case 7, 8, 9, 10:

                            score = -75

                   case 11, 12:

                            score = -55

                   case 13:

                            score = -25

                   case 14:

                            score = -10

                   case 15:

                            score = 60

                   default:

                   }

         }

         if len(i) == 3 && i[0] == 15 { //三张2的算分

                   score = 90

         }

         if len(i) == 4 && i[0] == i[3] { //炸弹的算分

                   score = 120

         }

         if (len(i) == 4 && i[0] != i[3]) || (len(i) == 5 && i[0] == i[1]) { //三带2或者三带1算分

                   switch i[0] {

                   case 3, 4, 5, 6:

                            score = -60

                   case 7, 8, 9, 10:

                            score = -35

                   case 11, 12, 13:

                            score = -10

                   case 14:

                            score = 25

                   default:

                   }

         }

         if len(i) == 5 && i[0] != i[1] { //顺子5个的算分

                   switch i[0] {

                   case 3, 4, 5, 6:

                            score = -40

                   case 7, 8:

                            score = -20

                   case 9:

                            score = -05

                   case 10:

                            score = 10

                   default:

                   }

         }

         return score

}

1.       这个的思路核心就是一张3(小牌)和一张大王(绝对大牌)是等值的。

(注:后来有改动)

并且以手上的牌能否获得收益为价值依据,例如一张A虽然很大可能能打出去,但是不太可能获得上手机会,所以价值是负的,我是大部分时候宁愿少这张牌的。

而三个A带一,则被打的几率很小,上手的几率很大,我可以接受手上多这张牌的,所以判断为正分。

2.       只简要的计算区段,不作明显的细分

(注:这样建模方便,但是最终肯定要没一张牌细分的,从实际效果看来,不细分还凑合)

3.       对子要比单牌稍强,实际战斗中,管上单牌的概率比管上对子的要高,但主要还是看大小。

4.       6张以上的牌型,基本认定为没机会管上,也不会被管的牌型,所以他们是0分,

(注:实际不仅仅能用0分来概括的,但大部分实战确实能简化)

5.       三带12),分值比单牌对子远远提升,而且涨幅很高,例如三个A已经从负分转为正分了。

6.       炸弹的分大约是大王的120%,不分炸弹大小。

(注:这个设置确实差不多了,炸弹吃大王确实是一个数值陷阱,让人觉得炸弹好厉害,但实际效果并不是那么强,高估炸弹很容易有不合理的分数判断)

 

 

// 这个是测试后,通过反馈的问题优化的函数

// 把牌型算分

func paixingscore(i []int) int {

       var score int

       if len(i) == 1 { //单牌的算分

                switch i[0] {

                case 3, 4, 5, 6:

                          score = -100

                case 7, 8, 9, 10:

                          score = -80

                case 11, 12:

                          score = -55

                case 13:

                          score = -35

                case 14:

                          score = -15

                case 15:

                          score = 35

                case 16:

                          score = 70

                case 17:

                          score = 110

                default:

                }

       }

       if len(i) == 2 { //对子的算分

                switch i[0] {

                case 3, 4, 5, 6:

                          score = -95

                case 7, 8, 9, 10:

                          score = -75

                case 11, 12:

                          score = -55

                case 13:

                          score = -25

                case 14:

                          score = -5

                case 15:

                          score = 70

                default:

                }

       }

       if len(i) == 3 && i[0] == 15 { //三张2的算分

                score = 105

       }

       if len(i) == 4 && i[0] == i[3] { //炸弹的算分

                score = 150

       }

       if (len(i) == 4 && i[0] != i[3]) || (len(i) == 5 && i[0] == i[1]) { //三带2或者三带1算分

                switch i[0] {

                case 3, 4, 5, 6:

                          score = -59

                case 7, 8, 9, 10:

                          score = -29

                case 11, 12, 13:

                          score = -9

                case 14:

                          score = 25

                default:

                }

       }

       if len(i) == 5 && i[0] != i[1] { //顺子5个的算分

                switch i[0] {

                case 3, 4, 5, 6:

                          score = -40

                case 7, 8:

                          score = -20

                case 9:

                          score = -5

                case 10:

                          score = 10

                default:

                }

       }

       return score

}

我们对比一下改变的数值。

1.       2、小王、大王的分值提高了。

原因:看起来都是一手牌换一手牌,打一个3,回一个王,又回到起点了。

但其实,大牌上手并不一定要出小牌,可能会出一些长牌,留小牌最后出。这样小牌的价值就可以忽略了。

也就是总体而言,能上手的牌的价值有额外加成。

同样的,2 小王 <大王,虽然很多时候2 小王也可以上手一次,但是没有大王那样一锤定音。

2.       2的价值因为单牌提高而提高了,因为对2拆开来也是很强大的。

但是对A的价值却逆增长,因为对A能称为大牌的时候,远比单张A多很多。而且拆对2很正常,拆对A是不够合理的。未来出牌的时候会有拆牌的需求出现,这时候就要在定分的时候调整了。

3.       三带12)的算分出现不整齐的数字(9),由于是第一版,所以数字都按爱好用510表示。

而三带1为什么要特殊出现9呢?

因为三带1在前期是小牌,被管上的可能性很大。

但是在后期,三带1就称为长牌了,很有可能称为值为0的牌。所以要做一个标记,让后期出牌能轻易的找出三带1作为清手牌的时机打出。

 

思考:这个策略最大的问题是什么?

缺陷1:没有动态调整,每个牌的价值,都会因为时间的推移变得不一样,例如大小王出完了,2的价值就提升了。应该要设计一个函数,通过计算各玩家出牌流程,然后调整手牌的价值。不过由于现在这个版本是没有记录功能的,所以动态调整将会在出牌(跟牌)的函数建立额外的判断条件。

缺陷2:手牌的价值,除了大小以外,还有牌型数的概念,即最少能出几次牌才能打完。例如到你出牌了,你宁愿剩下13,而不是一张A加一张2。而我把他们合在一起统计价值了。同上,如果有记录的话,把价值拆分,反而更好的调整两者的价值变化(注:大小价值变化慢,牌型数价值提升快)

 

结尾语,这一篇是大体的介绍了整体的策略变动和缺陷,下一篇将会讲解:

有分数了,但怎么利用,还需要添加哪些特殊变化的处理(策略)。

按这个策略实际打牌时会出现哪些问题(特殊情况)。

这些情况如何分析,如何处理。

 详见:以斗地主AI为例,探讨数值体系的设计和后期调整方案(三)


完整斗地主AI代码链接https://pan.baidu.com/s/1c3yuCI

 

本篇相关:findbest.go   API.go  fuzhufunc.go 

主要内容:判断最优牌型函数

 后端接口的API函数

 辅助函数(例如数组转换的函数)

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: