【脚本停止维护 有问题自行解决】宝可梦点击(Poke Clicker)辅助脚本 自动地牢/道馆模块

船新版本的策略算法,极大提高自动地牢/道馆效率

// ==UserScript==
// @name         【脚本停止维护 有问题自行解决】宝可梦点击(Poke Clicker)辅助脚本 自动地牢/道馆模块
// @namespace    PokeClickerHelper
// @version      1.0
// @description  船新版本的策略算法,极大提高自动地牢/道馆效率
// @author       DreamNya
// @match        https://www.pokeclicker.com
// @match        https://g8hh.github.io/pokeclicker/
// @match        https://pokeclicker.g8hh.com
// @match        https://yx.g8hh.com/pokeclicker/
// @match        https://dreamnya.github.io/pokeclicker/
// @icon         data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wAAAP8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/319hf99fYX/fX2F/319hf8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////////////////////////99fYX/fX2F/319hf8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////////////////wAAAP8AAAD/fX2F/319hf99fYX/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////8AAAD/AAAA/wAAAP99fYX/fX2F/wAAAP8AAAD/AAAA/319hf8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAP8AAAD/HBT//xwU//8AAAD//////319hf8AAAD/Dgim/w4Ipv8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAD/HBT//xwU//8cFP//HBT//wAAAP8AAAD/Dgim/w4Ipv8OCKb/Dgim/wAAAP8AAAAAAAAAAAAAAAAAAAAAAAAA/xwU//8cFP//HBT//xwU/44cFP//HBT//xwU//8cFP//Dgim/w4Ipv8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/HBT//xwU/47/////HBT/jhwU//8cFP//HBT//w4Ipv8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/xwU//8cFP//HBT/jhwU//8OCKb/Dgim/w4Ipv8OCKb/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/xwU//8OCKb/Dgim/w4Ipv8AAAD/AAAA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//8AAPw/AADwDwAA4AcAAOAHAADAAwAAwAMAAMADAADAAwAA4AcAAOAHAADwDwAA/D8AAP//AAD//wAA//8AAA==
// @grant        none
// @license      MIT
// @run-at       document-end
// ==/UserScript==
/* global $, PokeClickerHelper, MapHelper, player, App, GameConstants, DungeonRunner, DungeonBattle, GymRunner, Amount, RouteHelper */

if (typeof PokeClickerHelper == typeof void 0) {
    alert('宝可梦点击(Poke Clicker)辅助脚本 自动地牢/道馆模块加载失败\n\n未找到核心模块,需要先安装核心模块才可正常使用\n\n论坛主页:https://bbs.tampermonkey.net.cn/forum.php?mod=viewthread&tid=3842')
    window.open("https://bbs.tampermonkey.net.cn/forum.php?mod=viewthread&tid=3842")
    return
}



// UI相关
PokeClickerHelper.UIDOM.push(`
<div id="PokeClickerHelperDungeonGYM" class="custom-row">
    <div class="labelContainer">
        <label>自动地牢/道馆(-1为无限次):</label>
    </div>
    <div class="contentContainer ml-2">
        <div class="form-row">
            <select id="PokeClickerHelperDungeonGYMType" class="custom-select col-3" disabled data-save="false" title="脚本自动读取当前区域类型 无需手动切换">
                <option value="Dungeon">地牢</option>
                <option value="GYM">道馆</option>
                <option value="Route">野外</option>
            </select>
            <select id="PokeClickerHelperDungeonGYMClearType" class="custom-select col-4 ml-1" title="切换地牢不同挑战策略">
                <option value="OnlyBoss" title="自动计算避免普通战斗及不开宝箱的最短直奔Boss路线">速通不开</option>
                <option value="BossOpen" title="自动计算避免普通战斗及顺路开宝箱的最短直奔Boss路线">速通开箱</option>
                <option value="ClearOpen" title="清完所有普通战斗后先开宝箱再开始Boss战">全清开箱</option>
                <option value="OnlyClear" title="清完所有普通战斗后不开宝箱直接开始Boss战">全清不开</option>
            </select>
            <label class="form-check-label m-auto" title="直观显示脚本自动寻路效果"><input id="PokeClickerHelperShowMap" type="checkbox" value="false">显示地图</label>
        </div>
        <div class="form-row mt-1 mb-2">
            <input id="PokeClickerHelperDungeonGYMTimes" type="number" class="outline-dark form-control form-control-number col-4" placeholder="循环次数" title="地牢/道馆重复挑战次数(负数为无限次)">
            <select id="PokeClickerHelperDungeonGYMIndex" class="custom-select col-6 ml-1 opacity-25" data-save="false" title="自动读取当前地区可用道馆列表,如有多个道馆需要手动切换"><option value="0">道馆列表</option><option value="1" class="d-none"></option><option value="2" class="d-none"></option><option value="3" class="d-none"></option><option value="4" class="d-none"></option></select>
        </div>
        <button id="PokeClickerHelperToggleDungeonGYM" class="btn btn-sm btn-primary d-inline" data-save="false">开始</button>
        <select id="PokeClickerHelperDungeonCaughtType" class="custom-select col-5 ml-3" title="重复地牢直至当前所有可抓宝可梦全部符合条件(循环次数不能为0)" style="max-width: 43%;" data-save="false">
            <option value="none">无特殊选项</option>
            <option value="CaughtAllPokemon">抓齐普通</option>
            <option value="CaughtAllShinyPokemon">抓齐闪光</option>
            <option value="CaughtAllResistantPokemon">抓齐治愈</option>
        </select>
    </div>
</div>
<div class="mt-2 mb-1 border-top border-secondary"></div>
`)
const listener = () => {
    $("#PokeClickerHelperDungeonGYMType").on('change', changeListener)
    $("#PokeClickerHelperToggleDungeonGYM").on('click', DungeonGYMHelper.ToggleDungeonGYM)
}
PokeClickerHelper.UIlistener.push(listener);

//暴露对象方法到全局
const DungeonGYMHelper = {};
PokeClickerHelper.DungeonGYMHelper = DungeonGYMHelper;

//野外道路检测hook
const routeRep = [['App.game.gameState = GameConstants.GameState.fighting;', 'App.game.gameState = GameConstants.GameState.fighting;PokeClickerHelper.DungeonGYMHelper.mapMove();']];
PokeClickerHelper.HookFuc(MapHelper, 'moveToRoute', routeRep, 'route,region');

//城镇检测hook
const townRep = [['App.game.gameState = GameConstants.GameState.town;', 'App.game.gameState = GameConstants.GameState.town;PokeClickerHelper.DungeonGYMHelper.mapMove();']];
PokeClickerHelper.HookFuc(MapHelper, 'moveToTown', townRep, 'townName');

//道馆hook
const restartGYMBody = PokeClickerHelper.HookFucBody(restartGYM);
const gymLostRep = [['App.game.gameState = GameConstants.GameState.town;', restartGYMBody + 'App.game.gameState = GameConstants.GameState.town;']];
const gymWonRep = [['player.town(gym.parent);\n            App.game.gameState = GameConstants.GameState.town;\n', restartGYMBody + 'player.town(gym.parent);\n            App.game.gameState = GameConstants.GameState.town;\n']];
PokeClickerHelper.HookFuc(GymRunner, 'gymLost', gymLostRep, '');
PokeClickerHelper.HookFuc(GymRunner, 'gymWon', gymWonRep, 'gym');
function restartGYM() {
    if ($('#PokeClickerHelperToggleDungeonGYM').text() == '结束') {
        if ($('#PokeClickerHelperDungeonGYMTimes').val() != 0) {
            if (document.querySelector('#PokeClickerHelperDungeonGYMTimes').value > 0) document.querySelector('#PokeClickerHelperDungeonGYMTimes').value--
            this.startGym(this.gymObservable(), false, false)
            return
        }
        PokeClickerHelper.DungeonGYMHelper.ToggleDungeonGYM('', '剩余挑战次数为0')
    };
}

//移动hook
DungeonGYMHelper.mapMove = function () {
    const Type = document.querySelector("#PokeClickerHelperDungeonGYMType")
    const Helper = document.querySelector("#PokeClickerHelperToggleDungeonGYM").innerText
    const Times = document.querySelector("#PokeClickerHelperDungeonGYMTimes").value

    if (Helper == '开始') { //野外道路、城镇切换自动改变#PokeClickerHelperDungeonGYMType
        if (player.route() > 0) {
            changeValue(Type, "Route")
        } else {
            if (player.town().dungeon?.isUnlocked()) {
                changeValue(Type, "Dungeon")
            } else if (player.town().content.find(i => i.constructor.name == 'Gym' && i.isUnlocked())) {
                changeValue(Type, "GYM")
            } else {
                changeValue(Type, "Route")
            }
        }
    } else {
        if (Type.value == 'Dungeon') {//离开地牢
            DungeonGYMHelper.generator = void 0
            DungeonGYMHelper.isDungeon = false
            //dungeonEnd = new Date().getTime()
            //console.log('自动地牢耗时 ' + (dungeonEnd - dungeonStart))

            const DungeonCaughtType = document.querySelector('#PokeClickerHelperDungeonCaughtType').value
            if (Times > 0 && DungeonCaughtType == 'none') document.querySelector("#PokeClickerHelperDungeonGYMTimes").value--
        }
        if (Type.value == 'GYM') {
            DungeonGYMHelper.ToggleDungeonGYM('', '手动离开道馆')
            DungeonGYMHelper.mapMove()
        }
    }
};
PokeClickerHelper.initAfterList.add(() => { document.querySelector("#PokeClickerHelperDungeonGYMType").value = null })
PokeClickerHelper.initAfterList.add(DungeonGYMHelper.mapMove)

//赋值并触发事件监听
const changeValue = function (element, newValue) {
    const oldValue = element.value
    element.value = newValue
    if (oldValue != newValue || newValue == 'GYM') changeListener.call(element)
};

let init
//按钮点击事件
DungeonGYMHelper.ToggleDungeonGYM = (e, message = '') => {
    const type = $("#PokeClickerHelperDungeonGYMType").val()
    if ($('#PokeClickerHelperToggleDungeonGYM').text() == '开始') {
        $('#PokeClickerHelperToggleDungeonGYM').text('结束')
        if (type == 'Dungeon') (init = true, PokeClickerHelper.Worker.setInterval(DungeonGYMHelper.AutoDungeon, 50))
        if (type == 'GYM') DungeonGYMHelper.AutoGYM()
    } else {
        $('#PokeClickerHelperToggleDungeonGYM').text('开始')
        PokeClickerHelper.Worker.clearInterval(DungeonGYMHelper.AutoDungeon, 50)
        DungeonGYMHelper.generator = void 0
        DungeonGYMHelper.isDungeon = false
        PokeClickerHelper.Notify({ message: "自动地牢/道馆结束 " + message, timeout: 5000 }, true);
    }
}
//类型改变事件
const changeListener = function () {
    $("#PokeClickerHelperDungeonCaughtType").toggleClass('invisible', this.value != 'Dungeon')
    $("label:has(#PokeClickerHelperShowMap)").toggleClass('invisible', this.value != 'Dungeon')
    $("#PokeClickerHelperToggleDungeonGYM").attr('disabled', this.value == 'Route')
    $("#PokeClickerHelperDungeonGYMClearType").toggleClass('opacity-25', this.value != 'Dungeon')
    $("#PokeClickerHelperDungeonGYMIndex").toggleClass('opacity-25', this.value != 'GYM')
    $("#PokeClickerHelperDungeonGYMTimes").toggleClass('opacity-25', this.value == 'Route')
    if (this.value == 'GYM') {
        $(`#PokeClickerHelperDungeonGYMIndex [value]:not(:first)`).addClass('d-none')
        const GYMList = player.town().content.filter(i => i.constructor.name == 'Gym')
        for (let i = 0; i < GYMList.length; i++) {
            const GYM = GYMList[i]
            const select = $(`#PokeClickerHelperDungeonGYMIndex [value="${i}"]`)
            select.text(GYM.buttonText)
            if (i > 0) select.toggleClass('d-none', !GYM.isUnlocked())
        }
        $("#PokeClickerHelperDungeonGYMIndex").val('0')
    } else {
        $('#PokeClickerHelperDungeonGYMIndex option:first').text('道馆列表')
        $('#PokeClickerHelperDungeonGYMIndex option:not(:first)').addClass('d-none')
    }
}

//自动道馆
DungeonGYMHelper.AutoGYM = () => {
    if ($("#PokeClickerHelperDungeonGYMTimes").val() == 0) return DungeonGYMHelper.ToggleDungeonGYM('', '剩余挑战次数为0')
    const GYM = player.town().content.filter(i => i.constructor.name == 'Gym')[$("#PokeClickerHelperDungeonGYMIndex").val()]
    if (!GYM.isUnlocked()) return DungeonGYMHelper.ToggleDungeonGYM('', 'Error 道馆未解锁')
    if (document.querySelector("#PokeClickerHelperDungeonGYMTimes").value > 0) document.querySelector("#PokeClickerHelperDungeonGYMTimes").value--
    GymRunner.startGym(GYM)
};

//let dungeonStart
//let dungeonEnd

//地牢步进 Generator函数
function* step(route) {
    let retryTimes = 0
    for (let i = 0; i < route.length; i++) {
        const { x, y, value } = route[i]
        DungeonRunner.map.moveToCoordinates(x, y)
        const { x: px, y: py } = DungeonRunner.map.playerPosition()
        if (retryTimes < 10 && (px != x || py != y)) {
            route.splice(i + 1, 0, { x, y, value }) //如果没有到预期地点,在下一tick继续尝试,可能会不稳定导致卡死,增加10次重试上限
            console.log('自动地牢没有到达预期地点,在下一tick继续尝试', route)
            retryTimes++
        }
        if (value == 5) {
            DungeonRunner.nextFloor()
            if (DungeonRunner.map.playerPosition().floor != 1) {
                route.splice(i + 1, 0, { x, y, value }) //如果没有到预期地点,在下一tick继续尝试,可能会不稳定导致卡死,增加10次重试上限
                console.log('自动地牢没有到达第二层,在下一tick继续尝试', route)
                retryTimes++
            }
        }
        if (value == 3) DungeonRunner.openChest()
        if (value != 4) yield value
    }
    DungeonRunner.startBossFight()
    return 'Boss'
}

const DungeonCaughtTypeObj = {};
DungeonCaughtTypeObj.none = () => false;
DungeonCaughtTypeObj.CaughtAllPokemon = (dungeon) => DungeonRunner.dungeonCompleted(dungeon, false);
DungeonCaughtTypeObj.CaughtAllShinyPokemon = (dungeon) => DungeonRunner.dungeonCompleted(dungeon, true);
DungeonCaughtTypeObj.CaughtAllResistantPokemon = (dungeon) => RouteHelper.minPokerus(dungeon.allAvailablePokemon()) == 3;

//自动地牢 委托到Worker中执行,this实际指向PokeClickerHelper.Worker,因此不用function+this 改用箭头函数
DungeonGYMHelper.AutoDungeon = () => {
    if ($("#PokeClickerHelperDungeonGYMTimes").val() == 0) return DungeonGYMHelper.ToggleDungeonGYM('', '剩余挑战次数为0')
    if (init && App.game.gameState !== GameConstants.GameState.town) return DungeonGYMHelper.ToggleDungeonGYM('', '首次挑战需要在地牢外开始')
    init = false
    //初始化进入地牢
    if (!DungeonGYMHelper.isDungeon) {
        if ($('#dungeonMap').length > 0) return
        let dungeon = player.town().dungeon
        if (App.game.gameState === GameConstants.GameState.town && dungeon?.isUnlocked()) {
            if (dungeon.tokenCost > App.game.wallet.currencies[GameConstants.Currency.dungeonToken]()) return DungeonGYMHelper.ToggleDungeonGYM('', '地牢币不足')

            const DungeonCaughtType = document.querySelector('#PokeClickerHelperDungeonCaughtType').value
            if (DungeonCaughtTypeObj[DungeonCaughtType](dungeon)) return DungeonGYMHelper.ToggleDungeonGYM('', '捕捉到全部符合条件宝可梦')

            DungeonRunner.dungeonCompleted(player.town().dungeon, true)
            DungeonGYMHelper.isDungeon = true
            //dungeonStart = new Date().getTime()
            DungeonRunner.initializeDungeon(dungeon)
            PokeClickerHelper.Notify({ message: "自动地牢开始", timeout: 1000 });
        } else {
            DungeonGYMHelper.ToggleDungeonGYM('', '当前区域无可进入地牢')
        }
        return
    }

    //进入地牢
    if ($('#dungeonMap').length == 0) return
    if (DungeonRunner.fighting() || DungeonBattle.catching() || DungeonGYMHelper.generator == 'done') return
    //DungeonGYMHelper[$("#PokeClickerHelperDungeonGYMClearType").val()]()

    if (DungeonGYMHelper.generator == void 0) {
        if ($("#PokeClickerHelperShowMap").prop('checked')) DungeonRunner.map.showAllTiles()
        let map = DungeonRunner.map.board().map(i => i.map(i => i.map(i => i.type())))
        let calc
        let type = $("#PokeClickerHelperDungeonGYMClearType").val()
        //读取内存地图格式化自动寻最短路线并生成Generator步进函数
        switch (type) {
            case 'OnlyBoss': calc = map.flatMap(i => JSON.parse(JSON.stringify(DungeonGYMHelper.calcBoss(i, false)))); break//深拷贝 便于闭包垃圾回收?
            case 'BossOpen': calc = map.flatMap(i => JSON.parse(JSON.stringify(DungeonGYMHelper.calcBoss(i, true)))); break
            case 'ClearOpen': calc = map.flatMap(i => JSON.parse(JSON.stringify(DungeonGYMHelper.calcClear(i, true)))); break
            case 'OnlyClear': calc = map.flatMap(i => JSON.parse(JSON.stringify(DungeonGYMHelper.calcClear(i, false)))); break
        }
        DungeonGYMHelper.generator = step(calc)//直奔BOSS 只找尽量避免BOSS的最短路线 暂不考虑顺路开箱
    }
    //步进
    let stepResult = DungeonGYMHelper.generator.next()
    //console.log(stepResult)
    if (stepResult.done) DungeonGYMHelper.generator = 'done'
}

//计算全清路线
DungeonGYMHelper.calcClear = function (m, openChest) {
    const record = m.map((i, y) => i.map((m, x) => ({ x: x, y: y, value: m })));
    //console.log(record)
    let flat = record.flatMap((i, index) => {
        const r = Math.floor(index / 2) == index / 2
        const n = i.map(i => i)
        return r ? n : n.reverse()
    })
    const startIndex = flat.findIndex(i => i.value == 1) //兼容傻逼360 不支持findLastIndex
    const end = flat.find(i => i.value > 3)
    let route = flat.slice(startIndex + 1).concat(flat.slice(0, startIndex).reverse()).map(({ ...i }) => {
        if (i.value > 2) i.value = 0//路过BOSS、宝箱、楼梯视为普通道路
        return i
    })
    if (openChest) route = route.concat(flat.filter(i => i.value == 3))
    route.push(end)
    return route
}

//计算直奔Boss最短路线
//《关于A*算法不能穿墙、dijkstra算法看不懂,只能被迫自己编算法,结果被迫编了3天才编出来这档事》 我还是太菜了……
DungeonGYMHelper.calcBoss = function (m, openChest) {
    //获取可通过的周围四个点
    function getChild({ x, y }) {
        return [[x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1]].filter(([x, y]) => { return (x < size && y < size && x >= 0 && y >= 0 && !(x == end.x && y == end.y)) }).map(([X, Y]) => flat.find(({ x, y }) => x == X && y == Y))
    }
    //尝试通路
    function applyChild(parent) {
        const child = getChild(parent)
        const newChild = child.filter(c => {
            const newCost = c.cost + parent.totalCost //子点总消耗=子点消耗+父点总消耗,只取总消耗最少的点作为最终路径点
            const newLength = 1 + parent.totalLength //子点总步长=1+父点总步长,总消耗相同时只取总步长最少的点作为最终路径点
            if (c.parent.size == 0) {
                c.parent.add(parent)
                c.totalCost = newCost
                c.totalLength = newLength
            } else if (c.parent.has(parent) && newCost >= c.totalCost) {
                return false //终止走回路
            } else {
                if (c.totalCost == newCost) {
                    if (c.totalLength > newLength) {
                        c.totalLegnth = newLength
                        c.parent = new Set([parent])
                    } else if (c.totalLength == newLength) {
                        c.parent.add(parent)
                    } else {
                        return false
                    }
                } else if (c.totalCost > newCost) {
                    c.totalCost = newCost
                    c.totalLength = newLength
                    c.parent = new Set([parent])
                } else {
                    return false //终止消耗过高
                }
            }
            return true
        })
        return newChild
    }

    //回溯路径 结果逆推顺序
    function getRoute({ x, y, value, parent }, result) {
        parent = [...parent].filter(({ x, y }) => !result.find(({ x: X, y: Y }) => x == X && y == Y))//不走回路
        if (!(start.x == x && start.y == y)) {
            result.push({ x, y, value })
            parent.forEach(_parent => getRoute(_parent, [...result]))
        } else {
            routes.push(result)
        }
    }

    function getPoint({ x, y, value }) {
        return { x, y, value }
    }

    const startTime = Date.now()
    const size = m.length;
    const record = m.map((i, y) => i.map((m, x) => ({ x: x, y: y, value: m, cost: (m == 2) * 1, totalCost: 0, totalLength: 0, parent: new Set() })));
    const flat = record.flat();
    const start = flat.find(i => i.value == 1);
    const end = flat.find(i => i.value > 3); //4为BOSS 5为楼梯

    let result = applyChild(start);
    let times = 1
    //循环遍历通路
    while (result.length > 0) {
        result = result.map(c => applyChild(c)).flat()
        times++
    }
    //console.log(`直奔BOSS 遍历路径计算完毕 花费${Date.now() - startTime}ms 长度${size} 循环计算${times}次`)
    //遍历结果
    result = getChild(end).sort((a, b) => a.totalCost - b.totalCost)
    result = result.filter(i => i.totalCost == result[0].totalCost)
    let routes = []
    result.forEach(i => { getRoute(i, [getPoint(end)]) })

    //最终结果,[步数,战斗次数,宝箱数,正序路径]
    routes = routes.map(i => ({ length: i.length, battle: i.filter(i => i.value == 2).length, chest: i.filter(i => i.value == 3).length, route: i.reverse() }))
    //排序战斗次数优先、其次步数
    routes.sort((a, b) => a.length - b.length).sort((a, b) => a.battle - b.battle)
    //console.log(`直奔BOSS 回溯路径计算完毕 花费${Date.now() - startTime}ms 长度${size} 循环计算${times}次`)
    //console.log('直奔BOSS 路径长度:' + routes[0].length + ' 战斗次数:' + routes[0].battle)
    //返回最优结果

    if (openChest) {
        //排序战斗次数优先、其次宝箱、最后步数
        return routes.sort((a, b) => b.chest - a.chest).sort((a, b) => a.battle - b.battle)[0].route
    } else {
        //将宝箱视为通路
        return routes[0].route.map(i => {
            if (i.value == 3) i.value = 0
            return i
        })
    }
}