资讯专栏INFORMATION COLUMN

华容道游戏(下)

BicycleWarrior / 797人阅读

摘要:华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。

此为《算法的乐趣》读书笔记,我用javascript(ES6)重新实现算法。

华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。我尽量用面向对象的思想来进行封装,整个过程将分成几个部分记录下来,今天是最后一部分,棋局的广度搜索。
广度搜索

棋局的搜索空间是一个树状关系空间,广度优先搜索能够首先找到最优解,因为首先找到的解深度是最浅的。

游戏定义

我们对算法的要求是:给定一个华容道游戏的开局布局,可以得到这个开局的所有解决方法以及相应的武将移动步骤,要求算法具有能用性,能处理任何一种开局的华容道游戏。

class HrdGame{
    constructor(caoIdx, heroes){                
        this.caoIdx = caoIdx;                        //曹操在武将列表中的序号
        var startState = new HrdGameState();         //新建开局棋局
        startState.initState(heroes)                 //开局棋局初始化
        this.states = []                             //存储所有棋局状态,广度搜索的状态空间
        this.zhash = {}                              //棋局及其镜像哈希,判重空间
        this.result = 0;                             //解的总数
        addNewStatePattern(this,startState)          //开局处理,相当于游戏初始化
    }
}
算法思路及代码

游戏的求解过程就是棋局的搜索过程,每移动一个棋子就会生成一个新的棋局,对每一个棋局我们都要生成其所有的后续棋局。结束条件:在生成每一个新棋局时,判断是否为解,是则该状态终止;另一方面,每一个棋局若其后续棋局数为空,也自动终止。

解的判定
function isEscaped(game, gameState){            //曹操的位置到达(1,3)
    return (gameState.heroes[game.caoIdx -1].left == CAO_ESCAPE_LEFT) && (gameState.heroes[game.caoIdx - 1].top == CAO_ESCAPE_TOP)
}
棋局搜索
function resolveGame(game)                                     //广度搜索主函数
{
    let index = 0;
    while(index < game.states.length){
        gameState = game.states[index];                        //依次选定棋局状态
        if(isEscaped(game, gameState)){                        //找到解,输出
            game.result++;
            console.log("result:"+game.result+" step--"+gameState.step+" index:"+index)
        }
        else{
            searchNewGameStates(game, gameState);             //选定棋局搜索所有新棋局
        }
        index++;
    }
    return (game.result > 0);
}
搜索新棋局

武将移动产生新棋局。

function searchNewGameStates(game, gameState)                   //搜索新棋局
{
    for(let i = 0; i < gameState.heroes.length; i++)            //遍历武将
    {
        for(let j = 0; j < MAX_MOVE_DIRECTION; j++)             //遍历所有方向
        {
            trySearchHeroNewState(game, gameState, i, j);       //移动武将产生新棋局
        }
    }
}
新棋局生成

根据华容道规则,对一个武将棋子连续移动只算一步,因此在每一步移动成功后,需要继续对该棋子尝试移动,但是移动的方向有限制,不能向原方向移动。

function trySearchHeroNewState(game, gameState, heroIdx, dirIdx)
{
    let newState = moveHeroToNewState(gameState, heroIdx, dirIdx);    //新棋局产生
    if(newState) {
        if(addNewStatePattern(game, newState))                        //处理新棋局,判重,添加到状态链中
        {
             /*尝试连续移动,根据华容道游戏规则,连续的移动也只算一步*/
            tryHeroContinueMove(game, newState, heroIdx, dirIdx);
            return;
        }
    }
}
移动武将
function moveHeroToNewState(gameState, heroIdx, dirIdx)
{
    if(canHeroMove(gameState, heroIdx, dirIdx))                //能够移动
    {
        var newState = new HrdGameState();                     //新建棋局
        if(newState)
        {
            copyGameState(gameState, newState);                //用父棋局初始化新棋局
            var hero = newState.heroes[heroIdx];               //取得武将
            const dir = DIRECTION[dirIdx];                     //取得方向

            clearPosition(newState, hero.type, hero.left, hero.top); //清除父棋局信息
            takePosition(newState, heroIdx, hero.type, hero.left + dir[0], hero.top + dir[1]);            //新棋局数据生成
            hero.left = hero.left + dir[0];                    //武将新位置设定
            hero.top = hero.top + dir[1];

            newState.step = gameState.step + 1;                //移动步数加一
            newState.parent = gameState;                       //形成因果链
            newState.move.heroIdx = heroIdx;                   //记录移动方法
            newState.move.dirIdx = dirIdx;
            return newState;                                   //返回新棋局
        }
    }
    return null;
}
处理棋局
function addNewStatePattern(game, gameState)
{
    var l2rHash = getZobristHash(zobHash, gameState);            //计算棋局哈希值
    if(!game.zhash[l2rHash])                                     //棋局不存在
    {
        game.zhash[l2rHash] = l2rHash;                           //棋局哈希存储
        var r2lHash = getMirrorZobristHash(zobHash, gameState);
        game.zhash[r2lHash] = r2lHash;                           //棋局镜像哈希存储
        game.states.push(gameState);                             //棋局存储

        return true;
    }

    return false;                                                //棋局已经存在,忽略
}
开局及解 横刀立马

定义:

var hs =[new Warrior(WARRIOR_TYPE.HT_VBAR,0,0),          
          new Warrior(WARRIOR_TYPE.HT_BOX,1,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,2),
          new Warrior(WARRIOR_TYPE.HT_HBAR,1,2),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,2),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,0,4),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,3,4)
]

四个解:

result:1 step--81 index:11930
result:2 step--85 index:12123
result:3 step--98 index:12337
result:4 step--101 index:12348
指挥若定

定义:

var hs = [new Warrior(WARRIOR_TYPE.HT_VBAR,0,0),          //构建武将列表,初始棋局
          new Warrior(WARRIOR_TYPE.HT_BOX,1,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,0),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,0,2),
          new Warrior(WARRIOR_TYPE.HT_HBAR,1,2),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,3,2),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,3)
]

四个解:

result:1 step--73 index:11391
result:2 step--84 index:12207
result:3 step--86 index:12263
result:4 step--89 index:12306
兵分三路

定义:

var hs = [new Warrior(WARRIOR_TYPE.HT_BLOCK,0,0),          //构建武将列表,初始棋局
          new Warrior(WARRIOR_TYPE.HT_BOX,1,0),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,3,0),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,1),
          new Warrior(WARRIOR_TYPE.HT_HBAR,1,2),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,1),
          new Warrior(WARRIOR_TYPE.HT_VBAR,0,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,1,3),
          new Warrior(WARRIOR_TYPE.HT_BLOCK,2,3),
          new Warrior(WARRIOR_TYPE.HT_VBAR,3,3)
]

四个解:

result:1 step--74 index:7767
result:2 step--80 index:9212
result:3 step--94 index:10921
result:4 step--97 index:11157
完整代码

文中是主要代码分析,完整代码托管在开源中国,其中的hyd.js即华容道解法。

https://gitee.com/zhoutk/test
小结

终于完成了,其中遇到一个坑,就是zobrist的空间问题,《算法的乐趣》书中是说用32位整数,但其提供的源码是左移15位,我觉得也应该够,就用了15位整数。结果搜索不到解,各种调试、跟踪,感觉哪哪都是对的,曹操就是下不来,郁闷一晚。突然想到会不会是zobrist空间太小,若空间太小,新的棋局会与旧棋局冲突,这样应该会导致很多状态被忽略。清晨起床一试,爽!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/79353.html

相关文章

  • 容道游戏(中)

    摘要:棋类游戏一般需要行数列数状态数个状态,以华容道为例,需要为个状态分配编码值。对于华容道游戏来说,这种左右镜像的情况对于滑动棋子寻求结果的影响是一样的。 此为《算法的乐趣》读书笔记,我用javascript(ES6)重新实现算法。 华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。我尽量用面向对象的思想来进行封装,整个过程将分成几...

    Jason 评论0 收藏0
  • 容道游戏(上)

    摘要:华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。华容道游戏介绍华容道游戏据说来源于三国故事诸葛亮智算华容,关云长义释曹操。 此为《算法的乐趣》读书笔记,我用javascript(ES6)重新实现算法。 华容道游戏看似简单,但求解需要设计的数据结构比较复杂,还牵涉到棋类游戏的棋局判断,所以整个过程还是挺费劲的。我尽量用面向对象的...

    xi4oh4o 评论0 收藏0
  • 用React写一个数字容道,你需要知道的秘密

    摘要:还在上班很无聊数字华容道畅玩地址开发源码地址这个叫前言年末了。光随机生成一个乱序数列是不够的,还得保证这个数列的逆序数为偶数,嗦嘎。所以,我们直接将交换的次数,记为数列逆序数个数,就达到了想要的效果。 还在上班?很无聊?数字华容道畅玩地址 开发源码地址 这个叫前言 年末了。哦,不,要过年了。以前只能一路站到公司的我,今早居然是坐着过来的。新的一年,总要学一个新东西来迎接新的未来吧,所以...

    Jason 评论0 收藏0
  • 一只青蛙跳

    这是一个关于独立钻石的游戏 独立钻石起源于法国,是一种风靡世界的益智游戏与中国发明的华容道、匈牙利人发明的魔方, 并称为智力游戏界的三大不可思议它类似于跳棋,但不能走步,只能跳。走棋时棋子跳过相邻的棋子到空位上,并把跳过的棋子吃掉。棋子可以沿棋盘的格线横跳、纵跳,但不能斜跳 了解更多 游戏截图 showImg(https://segmentfault.com/img/bVAdq3); 游戏地址 游...

    邹强 评论0 收藏0
  • UCloud携手广东移动、中兴通讯国内率先5G云游戏测试

    摘要:近日,优刻得科技股份有限公司以下简称联合中国移动广东公司中兴通讯股份有限公司,以及云游戏合作伙伴北京念力科技有限公司,进行了实验室环境下云游戏体验测试。近日,优刻得科技股份有限公司(以下简称UCloud)联合中国移动广东公司、中兴通讯股份有限公司,以及云游戏合作伙伴北京念力科技有限公司,进行了5G实验室环境下云游戏体验测试。这项测试将为云游戏在5G网络环境下的发展提供实验室参考,为5G技术的...

    wing324 评论0 收藏0

发表评论

0条评论

BicycleWarrior

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<