「こおりのぬけみち」をコンピュータで解く

2016/11/03

Tweet

こおりのぬけみち

「こおりのぬけみち」は,ポケモン金銀シリーズに出てきた迷路で,小学生だった筆者は解けなかった. この迷路については,ニコニコ大百科の当該項目 を参照してほしい. そして操作の再現ページ から遊んでほしい.

こおりのぬけみちの画像
こおりのぬけみちの画像

コンピュータで解いてみた

あれから10年以上がたち,筆者はアルゴリズムを学んだので,今回はコンピュータの力で「こおりのぬけみち」を解く. 以下がそのウェブページである.ちなみに自分で迷路を変更できるようにもしてみた.

アルゴリズム

本当はもっとまじめに幅優先探索しようかと思ったけど,面倒なので深さ優先探索した. あと若干雑である (番兵の存在,ゴールの向こうが壁であることを仮定など). ソースコードは github に置いておくので,幅優先探索を実装する課題としても良いかもしれない.

const dy = [-1, 0, 1,  0];
const dx = [ 0, 1, 0, -1];
function solve(map){
    let alreadyVisited = new Set();
    function serializePoint(y, x){
        return x + "," + y;
    }
    let route = [];
    function dfs(y, x){
        // ゴールを発見したら,例外で脱出する
        if(map[y][x] == GOAL){
            throw new Route(route);
        }
        // もうそれより先にいってたら,中断
        if(alreadyVisited.has(serializePoint(y, x))){
            return;
        }
        alreadyVisited.add(serializePoint(y, x));
        // 4方向試す
        for(var i=0; i<4; i++){
            // 氷に乗っていけるところまでいってみる
            const ddy = dy[i];
            const ddx = dx[i];
            let ny = y;
            let nx = x;
            // 岩にあたるまでまっすぐ.(本当は g とかも考慮しないといけんね.)
            while(map[ny][nx] != WALL){
                ny += ddy;
                nx += ddx;
            }
            // いきすぎなのでもどる
            ny -= ddy;
            nx -= ddx;
            // 動けたらそれより先に dfs
            if(!(y == ny && x == nx)){
                // あとで表示するように今の場所をいれる
                route.push([ny, nx]);
                dfs(ny, nx);
                // どうやらこれより先には答はなかったみたいなので
                // 今の場所を破棄
                route.pop();
            }
        }
    }
    function findStart(){
        let ret = [];
        for(let y=0; y<map.length; y++){
            for(let x=0; x<map[y].length; x++){
                if(map[y][x] == START){
                    ret.push([y, x]);
                }
            }
        }
        return ret;
    }
    try {
        // スタートは複数あるかも?
        for(let start of findStart()){
            route = [start];
            dfs(start[0], start[1]);
        }
    } catch(e) { // found
        if(e instanceof Route){
            console.log(e.route);
            console.log("found");
            return e.route;
        }
        throw(e);
    }
    return null;
}