*文本为上一篇博客http://blog.csdn.net/sm9sun/article/details/77898734的部分追加优化
上一篇博客已经实现了基本的出牌逻辑,大部分情况能够给出正确的策略选择,但经过了一些测试,仍发现了几个严重的问题:
问题一:当手牌无闲牌时,偶尔会将完整的一组牌拆开打出。例如:二万、四万、七万、八万、三筒、五筒、一条、二条、三条、九条、九条
可能会打出一条。
发生该问题的原因是当计算needhun时,打出任意一张牌返回的结果是一样的(打二万和一条所需要的混牌数都是一样)。并且三张牌组又有一万(19优先策略)。故两张牌组战胜了三张牌组。其实这种问题只是不仅仅是两牌和三牌的关系,更能延伸出当手上没有单一的闲牌且needhun值相等时,当面对拆牌的情况,AI该如何抉择。按照我们大部分打牌的逻辑,如果该牌和手上其他的牌可以关联,那么我们会尽量的先留着,因为这种牌扩展性极强。所以在needhun值相等的情况下,我们还要参考下当前手牌有多少张牌和这张牌有关系,这样不但可以解决上述的两张牌组战胜了三张牌组的问题,也可以优先时牌变的紧凑。因为紧凑的牌型容易演变出听口多的结构。
解决方案:将原有的is_nexus方法(判断是否为单一)改成计算该牌和手牌有关系的总数
//返回单排和手牌有关系的个数
exports.has_nexus = function (i, arr) {
if (i > 26) {
return arr[i];
}
else if (i % 9 == 8) {
return arr[i] + arr[i - 1] + arr[i - 2];
}
else if (i % 9 == 7) {
return arr[i] + arr[i - 1] + arr[i - 2] + arr[i + 1];
}
else if (i % 9 == 0) {
return arr[i] + arr[i + 1] + arr[i + 2];
}
else if (i % 9 == 1) {
return arr[i] + arr[i + 1] + arr[i + 2] + arr[i - 1];
}
else {
return arr[i] + arr[i + 1] + arr[i + 2] + arr[i - 1] + arr[i - 2];
}
}
else if (needhun == ret_needhun)
{
if (nexus < ret_nexus) {
ret_nexus = nexus;
ret_pai = list[k];
}
else if (nexus == ret_nexus) {
if (list[k] > 26)//风牌优先打
{
ret_pai = list[k];
}
if ((list[k] % 9 < 1 || list[k] % 9 > 7) && ret_pai <= 26)//边牌优先打
{
ret_pai = list[k];
}
}
}
问题二:只考虑听牌数并非最佳策略甚至造成死听
这个问题上篇博客已经说了,当可以听牌时我们可以选择听口或者剩余牌多等不同的策略,若选择听口多则会造成报个死听的可能。
故新增选择听牌剩余牌最多的逻辑:
exports.GetTingPaiCount = function(Tinglist, holds, game_RemainMap)
{
var RemainMap = [];
if (game_RemainMap == null)//若参数为空,即无视出牌情况只考虑自身手牌,默认都为4个计算
{
for (var i = 0; i < 34; i++)
{
RemainMap[i] = 4;
}
}
else
{
RemainMap = game_RemainMap.concat();
}
for (var i = 0; i < holds.length;i++)
{
RemainMap[holds[i]]--;
}
var TingPaiCount = 0;
for (var i = 0; i < Tinglist.length;i++)
{
TingPaiCount += RemainMap[Tinglist[i]];
}
return TingPaiCount;
}
将原来的Tinglist比对改为TingPaiCount比对即可
var TingPaiCount = exports.GetTingPaiCount(Tinglist, list, RemainMap);
if (TingPaiCount > 0)//至少有得胡 如果胡的牌都没了就换牌吧
{
//听牌数比对,也可以按其他方式比对,比如所听的牌接下来的剩余牌
if (ret_tingpaicount < TingPaiCount) {
ret_tingpaicount = TingPaiCount;
ret_needhun = 1;
ret_pai = list[k];
}
}
else if (ret_tingpaicount == 0)
上个版本的一些BUG(前篇博客已经改正):
1.偶尔出现第一张牌总是优先打出的BUG
一开始发现这个BUG时我是懵逼的,经过了好久才找到问题。其实这是一个语法上的BUG,之前调用get_needhun_for_hu时为了把赖子先摘出来,将其置为了0,由于JS数组传递是引用,导致了后面按赖子为0算了,这样当手牌有赖子时,后面的返回结果都大于正确值。
解决方法:运算结束后还原数组,或用新数组。
2.出牌函数循环计算ret_needhun不正确
初始化最大值过小,我们计算needhun时一张废牌是会需要2张混牌的 故0xf(16)在极端的情况下并不满足最大值(东南西北中发白各一个就是14张了)。
解决方法:初始化最大值0x1a(26)
修改后的出牌方法完整代码:
exports.GetRobotChupai = function (list, special, hun, RemainMap) {
if (hun == null) {
hun = -1;
}
var arr = [];
var Tingobj = [];
for (var i = 0; i < special.mj_count; i++) {
arr[i] = 0;
}
for (var j = 0; j < list.length; j++) {
Tingobj[j] = {};
if (arr[list[j]] == null) {
arr[list[j]] = 1;
}
else {
arr[list[j]]++;
}
}
var ret_needhun = 0x1a;
var ret_pai = list[0];
var ret_tinglist = [];
var ret_nexus = 0xff;
var ret_tingpaicount = 0;
for (var k = 0; k < list.length; k++) {
if (list[k] == hun)
{
continue;
}
arr[list[k]]--;
var Tinglist = new Array();
var canting = exports.CanTingPai(arr, hun, Tinglist, special);
var TingPaiCount = exports.GetTingPaiCount(Tinglist, list, RemainMap);
if (TingPaiCount > 0)//至少有得胡 如果胡的牌都没了就换牌吧
{
//听牌数比对,也可以按其他方式比对,比如所听的牌接下来的剩余牌
if (ret_tingpaicount < TingPaiCount) {
ret_tingpaicount = TingPaiCount;
ret_needhun = 1;
ret_pai = list[k];
}
}
else if (ret_tingpaicount == 0) {
var needhun = get_needhun_for_hu(arr, hun, special);
var nexus = exports.has_nexus(list[k], arr);
if (needhun < ret_needhun)//判断此张牌需要的混牌数
{
ret_needhun = needhun;
ret_pai = list[k];
ret_nexus = nexus;
}
else if (needhun == ret_needhun)
{
if (nexus < ret_nexus) {
ret_nexus = nexus;
ret_pai = list[k];
}
else if (nexus == ret_nexus) {
if (list[k] > 26)//风牌优先打
{
ret_pai = list[k];
}
else if (list[k] % 9 < 1 || list[k] % 9 > 7)//边牌优先打
{
ret_pai = list[k];
}
else if (arr[list[k] + 1] == 0 && arr[list[k] - 1]==0)//主要针对夹,优先拆
{
ret_pai = list[k];
}
}
}
}
arr[list[k]]++;
}
return ret_pai;
}