Greasy Fork

来自缓存

Greasy Fork is available in English.

Gooboo辅助 (v4.8.5 画廊贪婪消除版)

全自动画廊策略:修复普通消除急躁引爆问题(现会贪婪吸附全图同色凑极大团)、修复特殊方块洗牌发呆Bug、全局死局自救

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Gooboo辅助 (v4.8.5 画廊贪婪消除版)
// @license      MIT
// @namespace    http://tampermonkey.net/
// @homepage     http://greasyfork.icu/zh-CN/scripts/481441-gooboo辅助
// @version      4.8.5
// @description  全自动画廊策略:修复普通消除急躁引爆问题(现会贪婪吸附全图同色凑极大团)、修复特殊方块洗牌发呆Bug、全局死局自救
// @author       jasmineamber & Gemini
// @match        https://www.ggpop.com/gooboo/
// @icon         https://www.google.com/s2/favicons?sz=64&domain=github.io
// @require      https://cdn.jsdelivr.net/npm/[email protected]/bignumber.min.js
// @require      https://openuserjs.org/src/libs/sizzle/GM_config.js
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM_registerMenuCommand
// @grant        GM_unregisterMenuCommand
// @grant        GM_notification
// ==/UserScript==

(async function() {
    'use strict';

    let gmc = new GM_config(
        {
            'id': 'gooboo',
            'css': '#gooboo_section_0  { display: flex; flex-flow: row wrap } #gooboo_section_0 .config_var {  } .section_header, .section_desc { flex: 1 100% }',
            'title': 'Gooboo辅助设置',
            'fields':
            {
                'ignore_skills': { 'type': 'hidden', 'default': '', 'section': ['部落', '忽略技能列表'] },
                'ignore_mdi-knife-military': { 'label': '匕首', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-medical-bag': { 'label': '衬衫', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-flare': { 'label': '守护天使', 'type': 'checkbox', 'default': true, 'labelPos': 'right' },
                'ignore_mdi-bone': { 'label': '一杯牛奶', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-octagram-outline': { 'label': '星盾', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-sword': { 'label': '长剑', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-shoe-cleat': { 'label': '靴子', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-clover': { 'label': '三叶草', 'type': 'checkbox', 'default': true, 'labelPos': 'right' },
                'ignore_mdi-stomach': { 'label': '肝脏', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-fire': { 'label': '火球', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-campfire': { 'label': '营火', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-snowflake': { 'label': '雪花', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-emoticon-devil': { 'label': '压迫者', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-laser-pointer': { 'label': '腐败的眼睛', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-shimmer': { 'label': '巫师帽', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-pentagram': { 'label': '红色杖', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-timer': { 'label': '坏了的秒表', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-pillar': { 'label': '大理石柱', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-looks': { 'label': '彩虹之杖', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-bottle-tonic-skull': { 'label': '毒素', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
                'ignore_mdi-water-opacity': { 'label': '净化泉', 'type': 'checkbox', 'default': false, 'labelPos': 'right' },
            },
            'events': {
                'save': function () { GM_notification({title: GM_info.script.name, text: `保存成功`, timeout: 3500}); }
            }
        });

    var menu_ALL = [
        ['menu_study_math', '[学校]数学-学习', '[学校]数学-学习', false, math, '1'],
        ['menu_study_literature', '[学校]文学-学习', '[学校]文学-学习', false, literature, '2'],
        ['menu_study_history', '[学校]历史-学习', '[学校]历史-学习', false, history, '3'],
        ['menu_exam_math', '[学校]数学-考试', '[学校]学习数学-考试', "other", exam_math, '4'],
        ['menu_exam_literature', '[学校]文学-考试', '[学校]文学-考试', "other", exam_literature, '5'],
        ['menu_exam_history', '[学校]历史-考试', '[学校]历史-考试', "other", exam_history, '6'],
        ['menu_auto_skill', '[部落]使用技能', '[部落]使用技能', false, auto_skill, 'a'],
        ['menu_auto_harvest', '[农场]循环播种', '[农场]循环播种', false, auto_harvest, 'z'],
        ['menu_auto_water', '[农场]自动浇水', '[农场]自动浇水', false, auto_water, 'w'],
        ['menu_auto_shape', '[画廊]自动形状', '[画廊]自动形状', false, auto_shape, 's'],
        ['menu_auto_shape_water', '[组合]自动形状+浇水', '[组合]自动形状+浇水', false, auto_shape_water, 'c'],
    ], menu_ID = [];

    registerMenuCommand();

    async function registerMenuCommand() {
        for (let i = 0; i < menu_ID.length; i++){
            GM_unregisterMenuCommand(menu_ID[i]);
            await sleep(100)
        }
        menu_ID[0] = GM_registerMenuCommand(`ℹ️ 设置`, function() {gmc.open()})
        await sleep(100)
        for (let i = 0;i < menu_ALL.length; i++){
            let icon = '✅'
            if (menu_ALL[i][3] == 'other') icon = 'ℹ️';
            menu_ID[i + 1] = GM_registerMenuCommand(`${menu_ALL[i][3]?icon:'❌'} ${menu_ALL[i][1]}`, async function(){
                if (!(menu_ALL[i][3] == 'other')) {
                    menu_ALL[i][3] = !menu_ALL[i][3]
                    await menu_switch(`${menu_ALL[i][3]}`,`${menu_ALL[i][0]}`,`${menu_ALL[i][2]}`);
                }
                await menu_ALL[i][4]()
            }, menu_ALL[i][5]);
            await sleep(100)
        }
    }

    async function menu_switch(menu_status, Name, Tips, Title=GM_info.script.name) {
        if (menu_status == 'true'){
            GM_notification({title: Title, text: `已开启 ${Tips} 功能`, timeout: 3500});
        }else{
            GM_notification({title: Title, text: `已停止 ${Tips} 功能`, timeout: 3500});
        }
        await registerMenuCommand();
    };

    function get_menu_info(menuName) { return menu_ALL.find(m => m[0] == menuName); }
    function get_menu_value(menuName) { return get_menu_info(menuName)[3]; }
    BigNumber.config({ EXPONENTIAL_AT: 1e+9 })
    function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

    // --- 学校功能 ---
    async function auto_calc() {
        let id; let question = null
        id = setInterval(function() {
            let answer = ""; let next_question = document.querySelector(".question-text")
            if (next_question == null) { clearInterval(id); return }
            if (question === next_question.innerText) return
            question = next_question.innerText
            let input = document.querySelector("#answer-input-math"); input.value = ""; input.dispatchEvent(new Event("input"))
            if(question.indexOf("^") > 0){ answer = Math.pow(question.split("^")[0], question.split("^")[1]) }
            else if (question.startsWith("√")) { answer = Math.sqrt((eval(question.replace("√", "")))) }
            else if (question.indexOf("e") > 0) {
                let x = BigNumber(question.split(" ")[0]), y = BigNumber(question.split(" ")[2]);
                answer = question.indexOf(" + ") > 0 ? x.plus(y).toString() : x.minus(y).toString()
            } else { answer = eval(question) }
            input.value = answer; input.dispatchEvent(new Event("input"))
            let btn = [...document.querySelectorAll(".v-btn__content")].find(item=>item.innerText === "答题"); btn.click()
        }, 200)
    }

    function auto_writing() {
        let id; id = setInterval(function() {
            let input = document.querySelector(".answer-input input"); let text = ''; let nodes = document.querySelector(".question-text .mx-2")
            if (nodes == null) { clearInterval(id); return }
            nodes = nodes.querySelectorAll("span"); for (let i of nodes) text += i.innerText
            input.value = text; input.dispatchEvent(new Event('input'))
        }, 200)
    }

    function fill_history(years) {
        let id; id = setInterval(function() {
            let dom = [...document.querySelectorAll("span")].find(item => item.innerText === "年份 ???")
            if (dom == null) { clearInterval(id); return }
            let icon = Array.from(dom.parentNode.querySelector(".v-icon").classList).filter(x => x.startsWith("mdi-"))[0]
            let input = document.querySelector(".answer-input input")
            if (input == null){ clearInterval(id); return }
            input.value = years[icon]; input.dispatchEvent(new Event('input'))
            let btn = [...document.querySelectorAll(".v-btn__content")].find(item => item.innerText === "答题"); btn.click()
        }, 200)
    }

    function get_history_year() {
        let doms = document.querySelectorAll(".v-main__wrap .rounded"); let year = {}
        for (let i = 0; i < doms.length; i++) {
            let dom = doms[i], icon = Array.from(dom.querySelector(".v-icon").classList).filter(x => x.startsWith("mdi-"))[0]
            year[icon] = dom.querySelector("span").innerText.match(/\d+/g)[0]
        }
        return year
    }

    let id_study
    async function math(is_first=true) {
        let menu_name = "menu_study_math"; let menu_info = get_menu_info(menu_name)
        if (!get_menu_value(menu_name)) { clearInterval(id_study); return }
        let target = [...document.querySelectorAll(".v-card__title")].find(item => item.innerText === "数学")
        if (!target) { if (is_first) { alert("请解锁后再使用"); menu_info[3] = !menu_info[3]; menu_switch(`${menu_info[3]}`,`${menu_info[0]}`,`${menu_info[2]}`); } return }
        let btn_study = [...target.parentNode.querySelectorAll(".v-btn__content")].find(item => item.innerText === "学习")
        if (btn_study) { btn_study.click(); await sleep(2000); auto_calc() }
        clearInterval(id_study); id_study = setInterval(function(){math(false)}, 5000)
    }

    async function exam_math() {
        let target = [...document.querySelectorAll(".v-card__title")].find(item => item.innerText === "数学")
        if (!target) { alert("请解锁后再使用"); return }
        let ticket = document.querySelector(".mdi-ticket-account").nextElementSibling.querySelector(".v-progress-linear__content span").innerText
        if (ticket === "0") { alert("考试次数不足"); return }
        let btn_exam = [...target.parentNode.querySelectorAll(".v-btn__content")].find(item => item.innerText === "参加考试")
        if (btn_exam) { btn_exam.click(); await sleep(2000); auto_calc() }
    }

    async function literature(is_first=true) {
        let menu_name = "menu_study_literature"; let menu_info = get_menu_info(menu_name)
        if (!get_menu_value(menu_name)) { clearInterval(id_study); return }
        let target = [...document.querySelectorAll(".v-card__title")].find(item => item.innerText === "文学")
        if (!target) { if (is_first) { alert("请解锁后再使用"); menu_info[3] = !menu_info[3]; menu_switch(`${menu_info[3]}`,`${menu_info[0]}`,`${menu_info[2]}`); } return }
        let btn_study = [...target.parentNode.querySelectorAll(".v-btn__content")].find(item => item.innerText === "学习")
        if (btn_study) { btn_study.click(); await sleep(2000); auto_writing() }
        clearInterval(id_study); id_study = setInterval(function(){literature(false)}, 5000)
    }

    async function exam_literature() {
        let target = [...document.querySelectorAll(".v-card__title")].find(item => item.innerText === "文学")
        if (!target) { alert("请解锁后再使用"); return }
        let ticket = document.querySelector(".mdi-ticket-account").nextElementSibling.querySelector(".v-progress-linear__content span").innerText
        if (ticket === "0") { alert("考试次数不足"); return }
        let btn_exam = [...target.parentNode.querySelectorAll(".v-btn__content")].find(item => item.innerText === "参加考试")
        if (btn_exam) { btn_exam.click(); await sleep(2000); auto_writing() }
    }

    async function history(is_first=true) {
        let menu_name = "menu_study_history"; let menu_info = get_menu_info(menu_name)
        if (!get_menu_value(menu_name)) { clearInterval(id_study); return }
        let target = [...document.querySelectorAll(".v-card__title")].find(item => item.innerText === "历史")
        if (!target) { if (is_first) { alert("请解锁后再使用"); menu_info[3] = !menu_info[3]; menu_switch(`${menu_info[3]}`,`${menu_info[0]}`,`${menu_info[2]}`); } return }
        let btn_study = [...target.parentNode.querySelectorAll(".v-btn__content")].find(item => item.innerText === "学习")
        if (btn_study) { btn_study.click(); await sleep(2000); let btn_start = [...document.querySelectorAll(".v-btn__content")].find(item => item.innerText === "答题"); btn_start.click(); await sleep(1000); fill_history(get_history_year()) }
        clearInterval(id_study); id_study = setInterval(function(){history(false)}, 5000)
    }

    async function exam_history(is_first=true) {
        let target = [...document.querySelectorAll(".v-card__title")].find(item => item.innerText === "历史")
        if (!target) { alert("请解锁后再使用"); return }
        let ticket = document.querySelector(".mdi-ticket-account").nextElementSibling.querySelector(".v-progress-linear__content span").innerText
        if (ticket === "0") { alert("考试次数不足"); return }
        let btn_exam = [...target.parentNode.querySelectorAll(".v-btn__content")].find(item => item.innerText === "参加考试")
        if (btn_exam) { btn_exam.click(); await sleep(2000); let btn = [...document.querySelectorAll(".v-btn__content")].find(item=>item.innerText === "答题"); btn.click(); await sleep(1000); fill_history(get_history_year()) }
    }

    // --- 部落功能 ---
    let id_skill
    async function auto_skill() {
        let menu_name = "menu_auto_skill"
        if (!get_menu_value(menu_name)) { clearInterval(id_skill); return }
        let skills_info = [
            {"name": "匕首", "icon": "mdi-knife-military"}, {"name": "衬衫", "icon": "mdi-medical-bag"}, {"name": "守护天使", "icon": "mdi-flare"},
            {"name": "一杯牛奶", "icon": "mdi-bone"}, {"name": "星盾", "icon": "mdi-octagram-outline"}, {"name": "长剑", "icon": "mdi-sword"},
            {"name": "靴子", "icon": "mdi-shoe-cleat"}, {"name": "三叶草", "icon": "mdi-clover"}, {"name": "肝脏", "icon": "mdi-stomach"},
            {"name": "火球", "icon": "mdi-fire"}, {"name": "营火", "icon": "mdi-campfire"}, {"name": "雪花", "icon": "mdi-snowflake"},
            {"name": "压迫者", "icon": "mdi-emoticon-devil"}, {"name": "肉盾", "icon": "mdi-octagram-outline"}, {"name": "腐败的眼睛", "icon": "mdi-laser-pointer"},
            {"name": "巫师帽", "icon": "mdi-shimmer"}, {"name": "红色杖", "icon": "mdi-pentagram"}, {"name": "坏了的秒表", "icon": "mdi-timer"},
            {"name": "大理石柱", "icon": "mdi-pillar"}, {"name": "彩虹之杖", "icon": "mdi-looks"}, {"name": "毒素", "icon": "mdi-bottle-tonic-skull"},
            {"name": "净化泉", "icon": "mdi-water-opacity"},
        ]
        id_skill = setInterval(async function() {
            let player = [...document.querySelectorAll("div")].find(item => item.innerText === "玩家");
            if (player == null) return
            let skill_bar = player.parentNode.previousElementSibling
            let skills = skill_bar.querySelectorAll(".v-icon");
            for (let skill of [...skills]) {
                if (skills_info.find(item => skill.classList.contains(item.icon) && gmc.get(`ignore_${item.icon}`))) continue
                skill.click()
                await sleep(100)
            }
        }, 1000)
    }

    // --- 农场功能 ---
    let id_harvest
    async function auto_harvest() {
        let menu_name = "menu_auto_harvest"
        if (!get_menu_value(menu_name)) { clearInterval(id_harvest); return }
        id_harvest = setInterval(function() {
            let btn_seed = document.querySelector(".mdi-seed")
            let btn_refresh = document.querySelector(".mdi-refresh");
            if (btn_refresh == null || btn_seed == null) return
            btn_refresh.click()
        }, 1000)
    }

    let id_water
    async function auto_water() {
        let val1 = get_menu_value("menu_auto_water"); let val2 = get_menu_value("menu_auto_shape_water");
        if (!val1 && !val2) { clearInterval(id_water); return }
        clearInterval(id_water);
        id_water = setInterval(function() {
            let icons = document.querySelectorAll('.care-icon:not(.farm-care-hidden)');
            icons.forEach(icon => {
                let tile = icon.parentElement;
                if (tile) { tile.dispatchEvent(new MouseEvent('mouseenter')); tile.dispatchEvent(new MouseEvent('mouseleave')); }
            });
        }, 1000)
    }

    // ==========================================
    // 画廊自动消除核心算法 (V4.8.5 贪婪消除版)
    // ==========================================
    let id_shape;
    const SPECIAL_TYPES = ['bomb', 'chest', 'accelerator', 'sparkles', 'dice', 'hourglass'];

    const getSortedShapesDesc = (counts) => {
        return Object.keys(counts).sort((a, b) => {
            if (counts[b] !== counts[a]) return counts[b] - counts[a];
            return a.localeCompare(b);
        });
    };

    const getSortedShapesAsc = (counts) => {
        return Object.keys(counts).sort((a, b) => {
            if (counts[a] !== counts[b]) return counts[a] - counts[b];
            return a.localeCompare(b);
        });
    };

    const moveToTarget = (store, grid, from, to, lockedCells) => {
        if (from.x === to.x && from.y === to.y) return false;
        let queue = [[from]];
        let visited = new Set([`${from.x},${from.y}`]);
        let nextStep = null;
        
        while(queue.length > 0) {
            let path = queue.shift();
            let curr = path[path.length - 1];
            
            if (curr.x === to.x && curr.y === to.y) {
                nextStep = path[1];
                break;
            }
            
            const dirs = [[0,1], [0,-1], [1,0], [-1,0]];
            for (let d of dirs) {
                let nx = curr.x + d[0], ny = curr.y + d[1];
                if (ny >= 0 && ny < grid.length && nx >= 0 && nx < grid[0].length) {
                    let key = `${nx},${ny}`;
                    if (!visited.has(key) && !lockedCells.has(key)) {
                        visited.add(key);
                        queue.push([...path, {x: nx, y: ny}]);
                    }
                }
            }
        }
        
        if (nextStep) {
            if (store.getters['currency/canAfford']({gallery_motivation: 1})) {
                store.dispatch('gallery/switchShape', { fromX: from.x, fromY: from.y, toX: nextStep.x, toY: nextStep.y });
                return true;
            }
        }
        return false;
    };

    const findReachable = (grid, startSpot, lockedCells, predicate) => {
        let queue = [startSpot];
        let visited = new Set([`${startSpot.x},${startSpot.y}`]);
        
        while (queue.length > 0) {
            let curr = queue.shift();
            if (predicate(grid[curr.y][curr.x], curr.x, curr.y)) return curr;
            const dirs = [[0,1], [0,-1], [1,0], [-1,0]];
            for (let d of dirs) {
                let nx = curr.x + d[0], ny = curr.y + d[1];
                if (ny >= 0 && ny < grid.length && nx >= 0 && nx < grid[0].length) {
                    let key = `${nx},${ny}`;
                    if (!visited.has(key) && !lockedCells.has(key)) {
                        visited.add(key);
                        queue.push({x: nx, y: ny});
                    }
                }
            }
        }
        return null;
    };

    const findClusterSafe = (grid, startX, startY, shapeType, lockedCells) => {
        let queue = [{x: startX, y: startY}];
        let visited = new Set([`${startX},${startY}`]);
        let cluster = [{x: startX, y: startY}];
        while (queue.length > 0) {
            let curr = queue.shift();
            const dirs = [[0,1], [0,-1], [1,0], [-1,0]];
            for (let d of dirs) {
                let nx = curr.x + d[0], ny = curr.y + d[1];
                if (ny >= 0 && ny < grid.length && nx >= 0 && nx < grid[0].length) {
                    let key = `${nx},${ny}`;
                    if (!visited.has(key) && !lockedCells.has(key) && grid[ny][nx] === shapeType) {
                        visited.add(key);
                        cluster.push({x: nx, y: ny});
                        queue.push({x: nx, y: ny});
                    }
                }
            }
        }
        return cluster;
    };

    const getSafeClusters = (grid, shapeType, lockedCells) => {
        let cells = [];
        grid.forEach((row, y) => {
            row.forEach((type, x) => {
                if (type === shapeType && !lockedCells.has(`${x},${y}`)) cells.push({x, y});
            });
        });
        let visited = new Set();
        let clusters = [];
        for (let c of cells) {
            let key = `${c.x},${c.y}`;
            if (!visited.has(key)) {
                let cluster = findClusterSafe(grid, c.x, c.y, shapeType, lockedCells);
                cluster.forEach(i => visited.add(`${i.x},${i.y}`));
                clusters.push(cluster);
            }
        }
        clusters.sort((a,b) => b.length - a.length);
        return clusters;
    };

    // 【核心大改】:贪婪消除逻辑,必定全图吸附凑大团后再引爆
    const executeNormalElimination = (store, grid, shapeType, lockedCells, isUnlockedNormal) => {
        let safeClusters = getSafeClusters(grid, shapeType, lockedCells);
        if (safeClusters.length === 0) return false;
        
        let main = safeClusters[0];

        // 找寻所有不在主星团内的散落孤儿
        let isolated = [];
        grid.forEach((row, y) => {
            row.forEach((type, x) => {
                if (type === shapeType && !lockedCells.has(`${x},${y}`) && !main.some(m => m.x===x && m.y===y)) {
                    isolated.push({x, y});
                }
            });
        });

        // 步骤1:如果有散落孤儿,坚决优先拉取吸附它们!
        if (isolated.length > 0) {
            let neighbors = [];
            main.forEach(m => {
                [[0,1], [0,-1], [1,0], [-1,0]].forEach(d => {
                    let nx = m.x+d[0], ny = m.y+d[1];
                    if (ny>=0 && ny<grid.length && nx>=0 && nx<grid[0].length) {
                        // 找主团边缘的外接空位(必须是普通其他形状,不能是墙)
                        if (!lockedCells.has(`${nx},${ny}`) && grid[ny][nx] !== shapeType && isUnlockedNormal(grid[ny][nx])) {
                            neighbors.push({x: nx, y: ny});
                        }
                    }
                })
            });

            if (neighbors.length > 0) {
                // 尝试将最近的散件拉到边缘空位上
                for (let nei of neighbors) {
                    let iso = findReachable(grid, nei, lockedCells, (type, x, y) => type === shapeType && !main.some(m => m.x === x && m.y === y) && !lockedCells.has(`${x},${y}`));
                    if (iso) {
                        if (moveToTarget(store, grid, iso, nei, lockedCells)) return true; // 拉取成功,此帧结束,下帧继续贪婪吸附
                    }
                }
            }
        }

        // 步骤2:只有在以下两种情况下才引爆:
        // 1. isolated.length === 0 (全图同色全部凑齐了!)
        // 2. 有孤儿但被死路挡住(墙)拉不过来了。
        // 这时只要团块数量 >= 5,就可以享受满额收益一口气引爆。
        if (main.length >= 5) {
            store.dispatch('gallery/clickShape', {x: main[0].x, y: main[0].y});
            return true;
        }

        return false;
    };

    async function auto_shape() {
        let val1 = get_menu_value("menu_auto_shape");
        let val2 = get_menu_value("menu_auto_shape_water");
        if (!val1 && !val2) {
            clearInterval(id_shape)
            return
        }
        
        clearInterval(id_shape);
        const getStore = () => document.querySelector('.v-application')?.__vue__?.$store;

        id_shape = setInterval(async function() {
            const store = getStore();
            if (!store || !store.state.gallery || !store.state.gallery.shapeGrid) return;
            const grid = store.state.gallery.shapeGrid;
            const shapeInfo = store.state.gallery.shape;
            const motivation = store.getters['currency/value']('gallery_motivation');

            if (motivation < 1) return;

            let specials = { chest: null, accelerator: null, bomb: null, sparkles: null, dice: null, hourglass: null };
            let normalCounts = {};

            const isUnlockedNormal = (type) => {
                const isSpec = shapeInfo[type]?.isSpecial || SPECIAL_TYPES.includes(type);
                return shapeInfo[type] && shapeInfo[type].unlocked && !isSpec;
            };

            grid.forEach((row, y) => {
                row.forEach((type, x) => {
                    if (shapeInfo[type]?.isSpecial || SPECIAL_TYPES.includes(type)) {
                        specials[type] = {x, y};
                    } else if (isUnlockedNormal(type)) {
                        normalCounts[type] = (normalCounts[type] || 0) + 1;
                    }
                });
            });

            // 沙漏绝对锁死,作为物理墙壁
            let lockedCells = new Set();
            if (specials.hourglass) lockedCells.add(`${specials.hourglass.x},${specials.hourglass.y}`);

            const handleDeadlock = () => {
                if (specials.dice) { store.dispatch('gallery/clickShape', {x: specials.dice.x, y: specials.dice.y}); return true; }
                if (specials.hourglass) { store.dispatch('gallery/clickShape', {x: specials.hourglass.x, y: specials.hourglass.y}); return true; }
                if (motivation >= 35) { store.dispatch('gallery/buyShapeReroll'); return true; }
                return false;
            };

            // -----------------------------------------------------
            // 独立状态机 1. 闪耀 (Sparkles)
            // -----------------------------------------------------
            if (specials.sparkles) {
                let sp = specials.sparkles;
                let cx = 4, cy = 2;
                if (sp.x !== cx || sp.y !== cy) { moveToTarget(store, grid, sp, {x: cx, y: cy}, lockedCells); return; }
                lockedCells.add(`${cx},${cy}`);

                let top4 = getSortedShapesDesc(normalCounts).slice(0, 4);
                if (top4.length < 4) { store.dispatch('gallery/clickShape', {x: cx, y: cy}); return; }

                let seedSpots = [{x:4, y:1}, {x:4, y:3}, {x:3, y:2}, {x:5, y:2}];
                let seedsPlaced = true;
                
                for (let i=0; i<top4.length; i++) {
                    let shape = top4[i];
                    let spot = seedSpots[i];
                    if (grid[spot.y][spot.x] !== shape) {
                        seedsPlaced = false;
                        let bestCell = findReachable(grid, spot, lockedCells, (type, x, y) => type === shape && !lockedCells.has(`${x},${y}`));
                        if (bestCell && moveToTarget(store, grid, bestCell, spot, lockedCells)) return;
                    } else {
                        lockedCells.add(`${spot.x},${spot.y}`);
                    }
                }

                if (!seedsPlaced) { store.dispatch('gallery/clickShape', {x: cx, y: cy}); return; }

                let allConnected = true;
                let wandererMoved = false;

                for (let i=0; i<top4.length; i++) {
                    let shape = top4[i];
                    let seedSpot = seedSpots[i];
                    let rootSystem = findClusterSafe(grid, seedSpot.x, seedSpot.y, shape, new Set()); 
                    let rootSet = new Set(rootSystem.map(r => `${r.x},${r.y}`));
                    rootSystem.forEach(r => lockedCells.add(`${r.x},${r.y}`));

                    let wanderersExists = false;
                    grid.forEach((row, y) => { row.forEach((type, x) => { if (type === shape && !rootSet.has(`${x},${y}`)) wanderersExists = true; }); });

                    if (wanderersExists) {
                        allConnected = false;
                        let rootEdges = [];
                        rootSystem.forEach(r => {
                            [[0,1],[0,-1],[1,0],[-1,0]].forEach(d => {
                                let nx = r.x+d[0], ny = r.y+d[1];
                                if (ny>=0 && ny<grid.length && nx>=0 && nx<grid[0].length) {
                                    if (!lockedCells.has(`${nx},${ny}`) && isUnlockedNormal(grid[ny][nx])) rootEdges.push({x: nx, y: ny});
                                }
                            });
                        });

                        for (let e of rootEdges) {
                            let w = findReachable(grid, e, lockedCells, (type, x, y) => type === shape && !rootSet.has(`${x},${y}`) && !lockedCells.has(`${x},${y}`));
                            if (w && moveToTarget(store, grid, w, e, lockedCells)) {
                                wandererMoved = true;
                                break;
                            }
                        }
                    }
                    if (wandererMoved) break;
                }

                if (allConnected || (!wandererMoved && seedsPlaced)) { store.dispatch('gallery/clickShape', {x: cx, y: cy}); }
                return;
            }

            // -----------------------------------------------------
            // 独立状态机 2. 宝箱 (Chest)
            // -----------------------------------------------------
            if (specials.chest) {
                let chest = specials.chest;
                let cx = 4, cy = 2;
                if (chest.x !== cx || chest.y !== cy) { moveToTarget(store, grid, chest, {x: cx, y: cy}, lockedCells); return; }
                lockedCells.add(`${cx},${cy}`);
                let spots = [[-1,-1],[0,-1],[1,-1],[-2,0],[-1,0],[1,0],[2,0],[-1,1],[0,1],[1,1]].map(d => ({x: cx+d[0], y: cy+d[1]}));

                let globalUnlockedCount = Object.keys(shapeInfo).filter(k => shapeInfo[k].unlocked && !shapeInfo[k].isSpecial && !SPECIAL_TYPES.includes(k)).length;

                if (globalUnlockedCount >= 10) {
                    let usedTypes = new Set();
                    let emptySpots = [];
                    spots.forEach(spot => {
                        let cell = grid[spot.y][spot.x];
                        if (isUnlockedNormal(cell) && !usedTypes.has(cell)) {
                            usedTypes.add(cell);
                            lockedCells.add(`${spot.x},${spot.y}`);
                        } else { emptySpots.push(spot); }
                    });

                    if (emptySpots.length === 0) { store.dispatch('gallery/clickShape', {x: cx, y: cy}); return; }

                    let availableTypes = getSortedShapesDesc(normalCounts).filter(k => !usedTypes.has(k));
                    if (availableTypes.length > 0) {
                        let targetSpot = emptySpots[0];
                        let moved = false;
                        for (let tShape of availableTypes) {
                            let bCell = findReachable(grid, targetSpot, lockedCells, (type, x, y) => type === tShape && !lockedCells.has(`${x},${y}`));
                            if (bCell && moveToTarget(store, grid, bCell, targetSpot, lockedCells)) { moved = true; break; }
                        }
                        if (!moved) {
                            let validMost = getSortedShapesDesc(normalCounts).filter(k => normalCounts[k] >= 5);
                            for (let vShape of validMost) {
                                if (executeNormalElimination(store, grid, vShape, lockedCells, isUnlockedNormal)) return;
                            }
                            handleDeadlock();
                        }
                    } else {
                        let validMost = getSortedShapesDesc(normalCounts).filter(k => normalCounts[k] >= 5);
                        for (let vShape of validMost) {
                            if (executeNormalElimination(store, grid, vShape, lockedCells, isUnlockedNormal)) return;
                        }
                        handleDeadlock();
                    }
                } else {
                    let emptySpots = [];
                    spots.forEach(spot => {
                        let cell = grid[spot.y][spot.x];
                        if (isUnlockedNormal(cell)) { lockedCells.add(`${spot.x},${spot.y}`); } else { emptySpots.push(spot); }
                    });

                    if (emptySpots.length === 0) { store.dispatch('gallery/clickShape', {x: cx, y: cy}); return; }
                    let targetSpot = emptySpots[0];
                    let bestCell = findReachable(grid, targetSpot, lockedCells, (type, x, y) => isUnlockedNormal(type) && !lockedCells.has(`${x},${y}`));
                    if (bestCell) moveToTarget(store, grid, bestCell, targetSpot, lockedCells);
                    else handleDeadlock();
                }
                return;
            }

            // -----------------------------------------------------
            // 独立状态机 3. 加速器 (Accelerator)
            // -----------------------------------------------------
            if (specials.accelerator) {
                let acc = specials.accelerator;
                let cx = 4, cy = 2;
                if (acc.x !== cx || acc.y !== cy) { moveToTarget(store, grid, acc, {x: cx, y: cy}, lockedCells); return; }
                lockedCells.add(`${cx},${cy}`);
                let spots = [[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]].map(d => ({x: cx+d[0], y: cy+d[1]}));

                let bigBrother = getSortedShapesDesc(normalCounts)[0];
                let bbCount = normalCounts[bigBrother] || 0;
                
                let filledSpots = 0;
                let emptySpots = [];
                spots.forEach(spot => {
                    if (grid[spot.y][spot.x] === bigBrother) {
                        lockedCells.add(`${spot.x},${spot.y}`);
                        filledSpots++;
                    } else { emptySpots.push(spot); }
                });

                if (filledSpots === 8) {
                    if (motivation >= 100) store.dispatch('gallery/clickShape', {x: cx, y: cy});
                    return;
                }

                let availableBB = bbCount - filledSpots;
                if (availableBB > 0) {
                    let targetSpot = emptySpots[0];
                    let bestCell = findReachable(grid, targetSpot, lockedCells, (type, x, y) => type === bigBrother && !lockedCells.has(`${x},${y}`));
                    if (bestCell) {
                        moveToTarget(store, grid, bestCell, targetSpot, lockedCells); return;
                    }
                }
                
                let validLeast = getSortedShapesAsc(normalCounts)
                    .filter(k => k !== bigBrother)
                    .filter(k => normalCounts[k] >= 5);
                
                for (let vShape of validLeast) {
                    if (executeNormalElimination(store, grid, vShape, lockedCells, isUnlockedNormal)) return;
                }
                
                handleDeadlock();
                return;
            }

            // -----------------------------------------------------
            // 独立状态机 4. 炸弹 (Bomb)
            // -----------------------------------------------------
            if (specials.bomb) {
                let bx = specials.bomb.x, by = specials.bomb.y;
                lockedCells.add(`${bx},${by}`);
                
                let crossSpots = [];
                for (let i = 0; i < grid[0].length; i++) if(i !== bx) crossSpots.push({x: i, y: by});
                for (let i = 0; i < grid.length; i++) if(i !== by) crossSpots.push({x: bx, y: i});

                let emptySpots = [];
                crossSpots.forEach(spot => {
                    let cell = grid[spot.y][spot.x];
                    if (isUnlockedNormal(cell)) {
                        lockedCells.add(`${spot.x},${spot.y}`);
                    } else { emptySpots.push(spot); }
                });

                if (emptySpots.length === 0) { store.dispatch('gallery/clickShape', {x: bx, y: by}); return; }

                let targetSpot = emptySpots[0];
                let bestCell = findReachable(grid, targetSpot, lockedCells, (type, x, y) => isUnlockedNormal(type) && !lockedCells.has(`${x},${y}`) && x !== bx && y !== by);
                if (bestCell) moveToTarget(store, grid, bestCell, targetSpot, lockedCells);
                else handleDeadlock();
                return;
            }

            // -----------------------------------------------------
            // 基础贪婪消除 (寻找最多数量,全图聚拢)
            // -----------------------------------------------------
            let anyNormalEliminated = false;
            let validNormals = getSortedShapesDesc(normalCounts).filter(k => normalCounts[k] >= 5);
            for (let shape of validNormals) {
                // 会卡在这里不断执行拉拽,直到拉不动才会发生点爆
                if (executeNormalElimination(store, grid, shape, lockedCells, isUnlockedNormal)) {
                    anyNormalEliminated = true;
                    break;
                }
            }
            if (anyNormalEliminated) return;

            // 死局自救
            handleDeadlock();

        }, 200)
    }

    async function auto_shape_water() {
        auto_water();
        auto_shape();
    }
})();