吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2495|回复: 14
收起左侧

[其他转载] 【AI修改】圈小猫作弊网页版

  [复制链接]
罗木魄 发表于 2026-4-21 12:50
本帖最后由 罗木魄 于 2026-4-21 13:01 编辑

https://www.52pojie.cn/thread-2103888-1-1.html此贴大佬的代码搜索深度只有12,且搜索效率不高
因此在此基础上,做了如下修改:
1.转换成了网页版,能方便更多人玩。
2.实现置换表和Alpha-Beta剪枝。这两项是博弈树搜索的标准配置,能安全地节省大量计算;并将搜索深度提高到20
3.只计算一个可行方案,减少等待时间。
注意以上修改过程中使用了DeepSeek
由于代码复制过程中可能存在一些问题,将小猫图标替换了,因此上传了网页附件
[HTML] 纯文本查看 复制代码
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>&#128049; 圈小猫 · 单方案快速求解 (修复作弊显示)</title>
    <style>
        * {
            box-sizing: border-box;
            font-family: system-ui, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
        }
        body {
            background: #f5f7fb;
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            margin: 20px;
        }
        .game-container {
            max-width: 1100px;
            width: 100%;
            background: white;
            border-radius: 28px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.08);
            padding: 24px;
        }
        .main-panel {
            display: flex;
            gap: 24px;
            flex-wrap: wrap;
        }
        .play-area {
            flex: 2;
            min-width: 500px;
        }
        .solution-panel {
            flex: 1.2;
            min-width: 280px;
            background: #fafbfc;
            border-radius: 20px;
            padding: 18px 16px;
            box-shadow: inset 0 1px 3px rgba(0,0,0,0.02), 0 4px 8px rgba(0,0,0,0.02);
        }
        .canvas-wrapper {
            background: #ffffff;
            border-radius: 24px;
            padding: 16px;
            box-shadow: 0 6px 14px rgba(0,0,0,0.03);
            border: 1px solid #e9ecf0;
        }
        canvas {
            display: block;
            margin: 0 auto;
            border-radius: 16px;
            background: #fefefe;
            cursor: pointer;
            touch-action: none;
            width: 100%;
            height: auto;
            border: 1px solid #dee2e6;
        }
        .button-bar {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            margin: 16px 0 8px;
        }
        .btn {
            background: white;
            border: 1px solid #d0d7de;
            border-radius: 40px;
            padding: 8px 16px;
            font-size: 14px;
            font-weight: 500;
            color: #1f2a3a;
            cursor: pointer;
            box-shadow: 0 2px 4px rgba(0,0,0,0.02);
            transition: all 0.15s;
            flex: 1 0 auto;
        }
        .btn-primary {
            background: #2c3e50;
            border-color: #1e2b37;
            color: white;
        }
        .btn-warning {
            background: #e67e22;
            border-color: #d35400;
            color: white;
        }
        .btn:disabled {
            opacity: 0.5;
            cursor: not-allowed;
            pointer-events: none;
        }
        .btn:hover:not(:disabled) {
            background: #f1f5f9;
            border-color: #9aa6b2;
        }
        .btn-primary:hover:not(:disabled) {
            background: #1e2b37;
        }
        .progress-row {
            display: flex;
            align-items: center;
            gap: 12px;
            margin: 10px 0;
        }
        .progress-bar-bg {
            flex: 1;
            height: 8px;
            background: #e2e8f0;
            border-radius: 20px;
            overflow: hidden;
        }
        .progress-bar-fill {
            height: 100%;
            width: 0%;
            background: #3498db;
            border-radius: 20px;
            transition: width 0.2s;
        }
        .info-text {
            font-size: 13px;
            color: #475569;
            margin: 8px 0;
        }
        .solution-header {
            display: flex;
            justify-content: space-between;
            align-items: baseline;
            margin-bottom: 12px;
        }
        .tree-container {
            background: white;
            border-radius: 16px;
            padding: 6px;
            max-height: 280px;
            overflow-y: auto;
            border: 1px solid #e2e8f0;
        }
        .solution-item {
            padding: 8px 12px;
            border-radius: 12px;
            margin: 4px 0;
            background: #ffffff;
            border: 1px solid #edf2f7;
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            transition: all 0.1s;
        }
        .solution-item.active {
            background: #dbeafe;
            border-color: #3b82f6;
        }
        .solution-stats {
            font-size: 13px;
            color: #64748b;
        }
        .detail-box {
            background: white;
            border-radius: 16px;
            padding: 14px;
            margin-top: 16px;
            border: 1px solid #e2e8f0;
            font-size: 13px;
            max-height: 180px;
            overflow-y: auto;
            white-space: pre-wrap;
            font-family: 'SF Mono', monospace;
        }
        .cheat-row {
            display: flex;
            align-items: center;
            gap: 8px;
            margin-top: 10px;
        }
        .status-badge {
            background: #ecfdf3;
            color: #067647;
            padding: 4px 12px;
            border-radius: 30px;
            font-size: 13px;
        }
        .flex-row {
            display: flex;
            align-items: center;
            gap: 10px;
            flex-wrap: wrap;
        }
        select, .sort-select {
            padding: 6px 12px;
            border-radius: 30px;
            border: 1px solid #d0d7de;
            background: white;
        }
        .footer-note {
            margin-top: 16px;
            color: #64748b;
            font-size: 13px;
            text-align: center;
        }
    </style>
</head>
<body>
<div class="game-container">
    <div class="main-panel">
        <!-- 左侧游戏区 -->
        <div class="play-area">
            <div class="canvas-wrapper">
                <canvas id="gameCanvas" width="580" height="540"></canvas>
            </div>
            <div class="button-bar">
                <button class="btn" id="resetBtn">&#128260; 重置</button>
                <button class="btn btn-primary" id="computeBtn">&#129504; 计算方案 (单解)</button>
                <button class="btn" id="stopBtn" disabled>&#9209;&#65039; 停止</button>
                <button class="btn" id="randomWallsBtn">&#127922; 生成墙壁</button>
                <button class="btn btn-warning" id="startGameBtn">&#127918; 开始游戏</button>
            </div>
            <div class="progress-row">
                <span id="progressLabel" style="min-width: 120px;">就绪</span>
                <div class="progress-bar-bg">
                    <div id="progressFill" class="progress-bar-fill" style="width:0%"></div>
                </div>
            </div>
            <div class="flex-row">
                <span class="info-text" id="infoMessage">&#10024; 点击圆圈设置墙壁 (最多8个)</span>
                <div class="cheat-row">
                    <input type="checkbox" id="cheatCheckbox"> 
                    <label for="cheatCheckbox" style="font-size:13px;">&#128065;&#65039; 显示方案步骤</label>
                </div>
            </div>
        </div>

        <!-- 右侧方案面板 -->
        <div class="solution-panel">
            <div class="solution-header">
                <strong>&#128203; 解决方案</strong>
                <span id="solutionCount" class="status-badge">0 个</span>
            </div>
            <div class="flex-row" style="margin-bottom: 10px;">
                <span style="font-size:13px;">排序:</span>
                <select id="sortSelect" class="sort-select" disabled>
                    <option value="steps" selected>步数 ↑</option>
                </select>
            </div>
            <div class="tree-container" id="solutionListContainer">
                <div style="padding:12px; text-align:center; color:#94a3b8;">暂无方案,点击“计算方案”</div>
            </div>
            <div class="detail-box" id="detailText">
                选择一个方案查看详情…
            </div>
            <div class="footer-note">
                &#9889; Alpha-Beta + 置换表 · 单解快速模式
            </div>
        </div>
    </div>
</div>
<script>
    (function(){
        "use strict";

        // ---------- 配置参数 ----------
        const WIDTH = 11;
        const HEIGHT = 11;
        const CELL_RADIUS = 18;
        const CELL_SPACING = 4;
        const DX = 2 * CELL_RADIUS + CELL_SPACING;
        const DY = (2 * CELL_RADIUS + CELL_SPACING) * 0.866;
        const CAT_START = [Math.floor(WIDTH/2), Math.floor(HEIGHT/2)];
        
        // 求解限制
        const MAX_DEPTH = 20;            // 最大搜索深度
        const MAX_CANDIDATES = 8;        // 每层候选墙壁数
        const MAX_TOTAL_TIME = 45;        // 秒

        // 置换表常量
        const EXACT = 0;
        const LOWERBOUND = 1;
        const UPPERBOUND = 2;

        // ---------- 全局状态 ----------
        let canvas = document.getElementById('gameCanvas');
        let ctx = canvas.getContext('2d');
        
        // 墙壁与模式
        let initialWalls = new Set();          // 字符串 "i,j"
        let wallClickOrder = [];               
        let gameMode = false;
        let gameWalls = new Set();
        let gameCatPos = null;
        let gameStep = 0;
        
        // 方案相关 (只保留一个方案)
        let currentSolution = null;             // 当前方案数组
        let currentSolutionWalls = new Set();
        let showSolutionSteps = true;           // 默认显示步骤序号
        let solutionOnBoard = [];               
        
        // 计算控制
        let computing = false;
        let stopRequested = false;
        let computeStartTime = 0;
        
        // AI 缓存与表
        const moveCache = new Map();
        const evalCache = new Map();
        const escapePathCache = new Map();
        const neighborsMap = new Map();
        
        // Alpha-Beta 专用表
        const transpositionTable = new Map();
        const historyTable = new Map();
        
        // DOM 元素
        const progressLabel = document.getElementById('progressLabel');
        const progressFill = document.getElementById('progressFill');
        const infoMessage = document.getElementById('infoMessage');
        const solutionCountSpan = document.getElementById('solutionCount');
        const solutionListDiv = document.getElementById('solutionListContainer');
        const detailText = document.getElementById('detailText');
        const resetBtn = document.getElementById('resetBtn');
        const computeBtn = document.getElementById('computeBtn');
        const stopBtn = document.getElementById('stopBtn');
        const randomWallsBtn = document.getElementById('randomWallsBtn');
        const startGameBtn = document.getElementById('startGameBtn');
        const cheatCheck = document.getElementById('cheatCheckbox');
        const sortSelect = document.getElementById('sortSelect');

        // 初始化邻居
        function computeNeighbors(i, j) {
            let neighbors;
            if (j % 2 === 0) {
                neighbors = [[i-1, j], [i-1, j-1], [i, j-1], [i+1, j], [i, j+1], [i-1, j+1]];
            } else {
                neighbors = [[i-1, j], [i, j-1], [i+1, j-1], [i+1, j], [i+1, j+1], [i, j+1]];
            }
            return neighbors.filter(([ni, nj]) => ni>=0 && ni<WIDTH && nj>=0 && nj<HEIGHT);
        }
        for (let j=0; j<HEIGHT; j++) {
            for (let i=0; i<WIDTH; i++) {
                neighborsMap.set(`${i},${j}`, computeNeighbors(i, j));
            }
        }
        function getNeighbors(i, j) {
            return neighborsMap.get(`${i},${j}`) || [];
        }

        // 工具函数
        function posKey(i,j) { return `${i},${j}`; }
        function parseKey(key) { return key.split(',').map(Number); }
        function isCatEscaped(pos) { return pos[0]===0 || pos[0]===WIDTH-1 || pos[1]===0 || pos[1]===HEIGHT-1; }
        function isCatCompletelyTrapped(wallsSet, catPos) {
            for (let [ni,nj] of getNeighbors(catPos[0], catPos[1])) 
                if (!wallsSet.has(posKey(ni,nj))) return false;
            return true;
        }

        // 绘图
        function getCellCenter(i, j) {
            const offset = (j % 2 === 1) ? CELL_RADIUS : 0;
            const x = CELL_RADIUS + CELL_SPACING + i * DX + offset;
            const y = CELL_RADIUS + CELL_SPACING + j * DY;
            return [x, y];
        }

        function drawBoard() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            for (let j=0; j<HEIGHT; j++) {
                for (let i=0; i<WIDTH; i++) {
                    const [x, y] = getCellCenter(i, j);
                    let color = '#E0E0E0';
                    const key = `${i},${j}`;
                    
                    if (gameMode) {
                        if (gameCatPos && i===gameCatPos[0] && j===gameCatPos[1]) color = '#FFB6C1';
                        else if (gameWalls.has(key)) color = '#333333';
                        // 游戏模式下也支持显示方案墙壁(作弊模式)
                        else if (showSolutionSteps && solutionOnBoard.some(p => p[0]===i && p[1]===j)) color = '#90EE90';
                    } else {
                        if (i===CAT_START[0] && j===CAT_START[1]) color = '#FFB6C1';
                        else if (initialWalls.has(key)) color = '#333333';
                        else if (currentSolutionWalls.has(key)) color = '#FF6B6B';
                        else if (showSolutionSteps && solutionOnBoard.some(p => p[0]===i && p[1]===j)) color = '#90EE90';
                    }
                    
                    ctx.beginPath();
                    ctx.arc(x, y, CELL_RADIUS*0.85, 0, 2*Math.PI);
                    ctx.fillStyle = color;
                    ctx.fill();
                    ctx.strokeStyle = '#555';
                    ctx.lineWidth = 1.5;
                    ctx.stroke();
                    
                    ctx.font = 'bold 16px "Segoe UI Emoji", "Apple Color Emoji", system-ui, sans-serif';
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    if (gameMode) {
                        if (gameCatPos && i===gameCatPos[0] && j===gameCatPos[1]) {
                            ctx.fillStyle = '#b45309';
                            ctx.fillText('&#128049;', x, y-1);
                        }
                    } else {
                        if (i===CAT_START[0] && j===CAT_START[1]) {
                            ctx.fillStyle = '#b45309';
                            ctx.fillText('&#128049;', x, y-1);
                        }
                    }
                    
                    // 显示步骤序号(游戏模式和非游戏模式均支持)
                    if (showSolutionSteps && solutionOnBoard.length) {
                        const idx = solutionOnBoard.findIndex(p => p[0]===i && p[1]===j);
                        if (idx !== -1) {
                            ctx.font = 'bold 12px system-ui';
                            ctx.fillStyle = '#1d4ed8';
                            ctx.fillText(idx+1, x, y-2);
                        }
                    }
                }
            }
        }

        // ---------- AI 核心函数 (保持不变) ----------
        function calcDistances(wallsSet) {
            const dist = Array(WIDTH).fill().map(() => Array(HEIGHT).fill(Infinity));
            const q = [];
            for (let i=0; i<WIDTH; i++) {
                for (let j=0; j<HEIGHT; j++) {
                    if ((i===0 || i===WIDTH-1 || j===0 || j===HEIGHT-1) && !wallsSet.has(posKey(i,j))) {
                        dist[i][j] = 0;
                        q.push([i,j]);
                    }
                }
            }
            while (q.length) {
                const [i,j] = q.shift();
                const cur = dist[i][j];
                for (let [ni,nj] of getNeighbors(i,j)) {
                    if (!wallsSet.has(posKey(ni,nj)) && dist[ni][nj] > cur+1) {
                        dist[ni][nj] = cur+1;
                        q.push([ni,nj]);
                    }
                }
            }
            return dist;
        }

        function getRoutesCount(wallsSet, i, j, dist, cache=new Map()) {
            const key = `${i},${j}`;
            if (cache.has(key)) return cache.get(key);
            if (wallsSet.has(key)) { cache.set(key,0); return 0; }
            if (i===0 || i===WIDTH-1 || j===0 || j===HEIGHT-1) { cache.set(key,1); return 1; }
            let count = 0;
            const curDist = dist[i][j];
            for (let [ni,nj] of getNeighbors(i,j)) {
                if (!wallsSet.has(posKey(ni,nj)) && dist[ni][nj] < curDist) {
                    count += getRoutesCount(wallsSet, ni, nj, dist, cache);
                }
            }
            cache.set(key, count);
            return count;
        }

        function getCatNextMove(wallsSet, catPos) {
            const cacheKey = JSON.stringify([...wallsSet].sort()) + '|' + catPos;
            if (moveCache.has(cacheKey)) return moveCache.get(cacheKey);
            if (isCatEscaped(catPos)) { moveCache.set(cacheKey, null); return null; }
            const dist = calcDistances(wallsSet);
            const [ci,cj] = catPos;
            if (dist[ci][cj] === Infinity) { moveCache.set(cacheKey, null); return null; }
            let bestMove = null, bestRoutes = -1;
            const currentDist = dist[ci][cj];
            const routesCache = new Map();
            for (let [ni,nj] of getNeighbors(ci,cj)) {
                if (!wallsSet.has(posKey(ni,nj)) && dist[ni][nj] < currentDist) {
                    const routes = getRoutesCount(wallsSet, ni, nj, dist, routesCache);
                    if (routes > bestRoutes) {
                        bestRoutes = routes;
                        bestMove = [ni,nj];
                    }
                }
            }
            moveCache.set(cacheKey, bestMove);
            return bestMove;
        }

        function heuristic(pos) {
            return Math.min(pos[0], WIDTH-1-pos[0], pos[1], HEIGHT-1-pos[1]);
        }

        function getEscapePath(wallsSet, catPos) {
            const key = JSON.stringify([...wallsSet])+'|'+catPos;
            if (escapePathCache.has(key)) return escapePathCache.get(key);
            const openSet = [[heuristic(catPos), catPos]];
            const cameFrom = new Map();
            const gScore = new Map(); gScore.set(catPos.toString(), 0);
            while (openSet.length) {
                openSet.sort((a,b) => a[0]-b[0]);
                const [f, cur] = openSet.shift();
                const [i,j] = cur;
                if (isCatEscaped(cur)) {
                    const path = [];
                    let p = cur;
                    while (cameFrom.has(p.toString())) {
                        path.push(p);
                        p = cameFrom.get(p.toString());
                    }
                    path.reverse();
                    escapePathCache.set(key, path);
                    return path;
                }
                for (let nb of getNeighbors(i,j)) {
                    if (wallsSet.has(posKey(...nb))) continue;
                    const tg = (gScore.get(cur.toString())||0) + 1;
                    const nbKey = nb.toString();
                    if (!gScore.has(nbKey) || tg < gScore.get(nbKey)) {
                        cameFrom.set(nbKey, cur);
                        gScore.set(nbKey, tg);
                        openSet.push([tg+heuristic(nb), nb]);
                    }
                }
            }
            escapePathCache.set(key, []);
            return [];
        }

        function quickEvaluate(wallsSet, catPos, wallPos) {
            const cacheKey = JSON.stringify([...wallsSet])+'|'+catPos+'|'+wallPos;
            if (evalCache.has(cacheKey)) return evalCache.get(cacheKey);
            let score = 0;
            const dist = Math.abs(wallPos[0]-catPos[0]) + Math.abs(wallPos[1]-catPos[1]);
            if (dist===1) score+=30; else if(dist===2) score+=20; else if(dist<=4) score+=15-dist*2;
            const path = getEscapePath(wallsSet, catPos);
            if (path.slice(0,3).some(p=>p[0]===wallPos[0]&&p[1]===wallPos[1])) score+=25;
            const testWalls = new Set(wallsSet); testWalls.add(posKey(...wallPos));
            const newPath = getEscapePath(testWalls, catPos);
            if (!newPath.length) score+=50;
            else if (path.length && newPath.length>path.length) score+=(newPath.length-path.length)*5;
            evalCache.set(cacheKey, score);
            return score;
        }

        function getCandidates(wallsSet, catPos, limit=8) {
            const strategic = new Set();
            for (let nb of getNeighbors(catPos[0],catPos[1])) if(!wallsSet.has(posKey(...nb))) strategic.add(posKey(...nb));
            const path = getEscapePath(wallsSet, catPos);
            path.slice(0,3).forEach(p=> strategic.add(posKey(...p)));
            for (let i=Math.max(0,catPos[0]-2); i<Math.min(WIDTH,catPos[0]+3); i++)
                for (let j=Math.max(0,catPos[1]-2); j<Math.min(HEIGHT,catPos[1]+3); j++)
                    if (!wallsSet.has(posKey(i,j)) && (i!==catPos[0]||j!==catPos[1])) strategic.add(posKey(i,j));
            const candidates = [];
            for (let key of strategic) {
                const pos = parseKey(key);
                candidates.push({pos, score: quickEvaluate(wallsSet, catPos, pos)});
            }
            candidates.sort((a,b)=>b.score-a.score);
            return candidates.slice(0, limit).map(c=>c.pos);
        }

        function hashState(wallsSet, catPos) {
            const wallsKey = Array.from(wallsSet).sort().join(';');
            return `${wallsKey}|${catPos[0]},${catPos[1]}`;
        }

        function evaluate(wallsSet, catPos, depthFromRoot) {
            if (isCatEscaped(catPos)) return -100000 + depthFromRoot;
            if (isCatCompletelyTrapped(wallsSet, catPos)) return 100000 - depthFromRoot;
            const dist = calcDistances(wallsSet);
            const [ci, cj] = catPos;
            const minDist = dist[ci][cj];
            if (minDist === Infinity) return 100000 - depthFromRoot;
            return -minDist * 10;
        }

        function alphaBeta(wallsSet, catPos, depth, alpha, beta, maxDepth, startTime) {
            if (Date.now() - startTime > MAX_TOTAL_TIME * 1000) {
                stopRequested = true;
                return 0;
            }
            if (stopRequested) return 0;

            const stateKey = hashState(wallsSet, catPos);
            
            const ttEntry = transpositionTable.get(stateKey);
            if (ttEntry && ttEntry.depth >= depth) {
                if (ttEntry.flag === EXACT) return ttEntry.score;
                if (ttEntry.flag === LOWERBOUND && ttEntry.score >= beta) return ttEntry.score;
                if (ttEntry.flag === UPPERBOUND && ttEntry.score <= alpha) return ttEntry.score;
            }
            
            if (isCatCompletelyTrapped(wallsSet, catPos)) return 100000 - (maxDepth - depth);
            if (isCatEscaped(catPos)) return -100000 + (maxDepth - depth);
            if (depth <= 0) return evaluate(wallsSet, catPos, maxDepth - depth);
            
            let candidates = getCandidates(wallsSet, catPos, MAX_CANDIDATES * 2);
            const ttBest = ttEntry ? ttEntry.bestMove : null;
            
            candidates.sort((a, b) => {
                if (ttBest && a[0]===ttBest[0] && a[1]===ttBest[1]) return -1;
                if (ttBest && b[0]===ttBest[0] && b[1]===ttBest[1]) return 1;
                const ha = historyTable.get(posKey(...a)) || 0;
                const hb = historyTable.get(posKey(...b)) || 0;
                return hb - ha;
            });
            candidates = candidates.slice(0, MAX_CANDIDATES);
            
            let bestScore = -Infinity;
            let bestMove = null;
            const originalAlpha = alpha;
            
            for (const wallPos of candidates) {
                const wallKey = posKey(...wallPos);
                if (wallsSet.has(wallKey)) continue;
                
                const newWalls = new Set(wallsSet);
                newWalls.add(wallKey);
                
                let nextCatPos = getCatNextMove(newWalls, catPos);
                let score;
                if (!nextCatPos) {
                    score = 100000 - (maxDepth - depth + 1);
                } else {
                    score = alphaBeta(newWalls, nextCatPos, depth - 1, alpha, beta, maxDepth, startTime);
                }
                
                if (stopRequested) return 0;
                
                if (score > bestScore) {
                    bestScore = score;
                    bestMove = wallPos;
                }
                if (bestScore > alpha) alpha = bestScore;
                if (alpha >= beta) {
                    const hist = historyTable.get(wallKey) || 0;
                    historyTable.set(wallKey, hist + depth * depth);
                    break;
                }
            }
            
            let flag;
            if (bestScore <= originalAlpha) flag = UPPERBOUND;
            else if (bestScore >= beta) flag = LOWERBOUND;
            else flag = EXACT;
            
            transpositionTable.set(stateKey, {
                score: bestScore,
                depth: depth,
                flag: flag,
                bestMove: bestMove
            });
            
            return bestScore;
        }

        function collectBestSequence(initialWalls, startCatPos, maxDepth, startTime) {
            const solution = [];
            let walls = new Set(initialWalls);
            let catPos = [...startCatPos];
            let depthLeft = maxDepth;
            
            while (depthLeft > 0 && !stopRequested) {
                if (isCatCompletelyTrapped(walls, catPos)) break;
                if (isCatEscaped(catPos)) return null;
                
                alphaBeta(walls, catPos, depthLeft, -Infinity, Infinity, depthLeft, startTime);
                const stateKey = hashState(walls, catPos);
                const ttEntry = transpositionTable.get(stateKey);
                
                let bestMove = ttEntry ? ttEntry.bestMove : null;
                if (!bestMove) {
                    let bestScore = -Infinity;
                    for (let i=0; i<WIDTH; i++) {
                        for (let j=0; j<HEIGHT; j++) {
                            if (walls.has(posKey(i,j)) || (i===catPos[0]&&j===catPos[1])) continue;
                            const score = quickEvaluate(walls, catPos, [i,j]);
                            if (score > bestScore) {
                                bestScore = score;
                                bestMove = [i,j];
                            }
                        }
                    }
                }
                if (!bestMove) break;
                
                solution.push(bestMove);
                walls.add(posKey(...bestMove));
                const nextCat = getCatNextMove(walls, catPos);
                if (!nextCat) break;
                catPos = nextCat;
                depthLeft--;
            }
            return solution;
        }

        function validateSolution(solution) {
            let walls = new Set(initialWalls);
            let cat = [...CAT_START];
            for (const wallPos of solution) {
                walls.add(posKey(...wallPos));
                if (isCatCompletelyTrapped(walls, cat)) return true;
                const next = getCatNextMove(walls, cat);
                if (!next) return true;
                if (isCatEscaped(next)) return false;
                cat = next;
            }
            return isCatCompletelyTrapped(walls, cat);
        }

        // ---------- 迭代加深搜索 (只找第一个有效解) ----------
        async function findFirstSolution() {
            stopRequested = false;
            const startTime = Date.now();
            
            transpositionTable.clear();
            historyTable.clear();
            
            for (let depth = 6; depth <= MAX_DEPTH; depth += 2) {
                if (stopRequested || (Date.now() - startTime) > MAX_TOTAL_TIME * 1000) break;
                
                updateProgress(`&#128269; 搜索深度 ${depth}...`, (depth - 6) / (MAX_DEPTH - 6));
                
                const solution = collectBestSequence(initialWalls, CAT_START, depth, startTime);
                
                if (stopRequested) break;
                
                if (solution && solution.length > 0 && validateSolution(solution)) {
                    return solution;
                }
                
                await new Promise(r => setTimeout(r, 0));
            }
            return null;
        }

        // ---------- UI 更新 (单方案显示) ----------
        function updateProgress(text, percent=null) {
            progressLabel.textContent = text;
            if (percent!==null) progressFill.style.width = Math.min(100, percent*100)+'%';
        }

        function displaySingleSolution(solution) {
            currentSolution = solution;
            currentSolutionWalls = new Set(solution.map(p => posKey(...p)));
            solutionOnBoard = [...solution];
            showSolutionSteps = true;
            cheatCheck.checked = true;
            
            // 更新列表区
            const eff = (1/solution.length).toFixed(3);
            solutionListDiv.innerHTML = `
                <div class="solution-item active" data-index="0">
                    <span>&#128204; 当前方案</span>
                    <span class="solution-stats">${solution.length}步 · ${eff}</span>
                </div>
            `;
            solutionCountSpan.textContent = '1 个';
            
            // 显示详情
            let details = `&#128216; 唯一方案 (${solution.length}步)\n${'═'.repeat(25)}\n`;
            let walls = new Set(initialWalls), cat = [...CAT_START];
            for (let step=0; step<solution.length; step++) {
                const w = solution[step];
                walls.add(posKey(...w));
                details += `第${step+1}步: 放墙 ${w[0]},${w[1]}`;
                if (isCatCompletelyTrapped(walls, cat)) { details += ` → &#127881; 猫被困!`; break; }
                const next = getCatNextMove(walls, cat);
                if (!next) { details += ` → &#127881; 猫无法移动`; break; }
                cat = next;
                details += ` → 猫移至 ${cat[0]},${cat[1]}`;
                if (isCatEscaped(cat)) { details += ` &#10060; 逃脱`; break; }
                details += '\n';
            }
            detailText.textContent = details;
            
            drawBoard();
        }

        function clearSolution() {
            currentSolution = null;
            currentSolutionWalls.clear();
            solutionOnBoard = [];
            solutionListDiv.innerHTML = '<div style="padding:12px; text-align:center; color:#94a3b8;">暂无方案</div>';
            solutionCountSpan.textContent = '0 个';
            detailText.textContent = '选择一个方案查看详情…';
            drawBoard();
        }

        // ---------- 事件处理 ----------
        function handleCanvasClick(e) {
            if (computing) { alert('计算中,请稍候'); return; }
            const rect = canvas.getBoundingClientRect();
            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            const clickX = (e.clientX - rect.left) * scaleX;
            const clickY = (e.clientY - rect.top) * scaleY;
            let minDist = Infinity, closest = null;
            for (let j=0; j<HEIGHT; j++) for (let i=0; i<WIDTH; i++) {
                const [cx, cy] = getCellCenter(i,j);
                const d = Math.hypot(clickX-cx, clickY-cy);
                if (d < minDist && d < CELL_RADIUS*1.2) { minDist = d; closest = [i,j]; }
            }
            if (!closest) return;
            const [i,j] = closest;
            if (gameMode) {
                if (gameCatPos && i===gameCatPos[0] && j===gameCatPos[1]) return;
                const key = posKey(i,j);
                if (!gameWalls.has(key)) {
                    gameWalls.add(key);
                    gameStep++;
                    drawBoard();
                    if (isCatCompletelyTrapped(gameWalls, gameCatPos)) { alert(`&#127881; 胜利!${gameStep}步困住小猫`); exitGameMode(); return; }
                    const nextCat = getCatNextMove(gameWalls, gameCatPos);
                    if (!nextCat) { alert(`&#127881; 胜利!小猫无法移动`); exitGameMode(); }
                    else if (isCatEscaped(nextCat)) { alert(`&#128575; 小猫逃脱了…`); exitGameMode(); }
                    else { gameCatPos = nextCat; drawBoard(); }
                }
                return;
            }
            if (i===CAT_START[0] && j===CAT_START[1]) return;
            const key = posKey(i,j);
            if (initialWalls.has(key)) {
                initialWalls.delete(key);
                wallClickOrder = wallClickOrder.filter(k => k!==key);
            } else {
                if (initialWalls.size >= 8) { alert('最多8个墙壁'); return; }
                initialWalls.add(key);
                wallClickOrder.push(key);
            }
            clearSolution();
            drawBoard();
        }

        function resetAll() {
            if (computing) return;
            initialWalls.clear(); wallClickOrder = [];
            clearSolution();
            gameMode = false; startGameBtn.textContent = '&#127918; 开始游戏';
            enableControls(true);
            drawBoard();
            infoMessage.textContent = '&#10024; 点击圆圈设置墙壁';
            cheatCheck.checked = true;
            showSolutionSteps = true;
            updateProgress('就绪', 0);
        }

        function generateRandomWalls() {
            if (computing||gameMode) return;
            if (wallClickOrder.length>=8) {
                const keep = wallClickOrder.slice(0,8);
                initialWalls = new Set(keep);
                wallClickOrder = keep;
            } else {
                const need = 8 - wallClickOrder.length;
                const available = [];
                for (let j=0;j<HEIGHT;j++) for(let i=0;i<WIDTH;i++) 
                    if(!(i===CAT_START[0]&&j===CAT_START[1]) && !initialWalls.has(posKey(i,j))) available.push(posKey(i,j));
                const shuffled = available.sort(()=>Math.random()-0.5);
                for (let i=0; i<need && i<shuffled.length; i++) wallClickOrder.push(shuffled[i]);
                initialWalls = new Set(wallClickOrder);
            }
            clearSolution();
            drawBoard();
        }

        async function startComputation() {
            if (!initialWalls.size) { alert('请先设置墙壁'); return; }
            if (computing) return;
            computing = true; stopRequested = false;
            computeStartTime = Date.now();
            stopBtn.disabled = false; computeBtn.disabled = true;
            updateProgress('Alpha-Beta 搜索中...', 0.05);
            moveCache.clear(); evalCache.clear(); escapePathCache.clear();
            
            try {
                const solution = await findFirstSolution();
                if (!stopRequested) {
                    if (solution) {
                        displaySingleSolution(solution);
                        updateProgress(`完成! 找到 ${solution.length} 步方案`, 1);
                        infoMessage.textContent = `&#9989; 已找到方案,共 ${solution.length} 步`;
                    } else {
                        updateProgress('未找到方案', 1);
                        infoMessage.textContent = '&#10060; 当前布局无解';
                        clearSolution();
                    }
                } else {
                    updateProgress('已停止');
                }
            } catch(e) { 
                alert('错误: '+e); 
                updateProgress('计算出错');
            }
            computing = false; stopBtn.disabled = true; computeBtn.disabled = false;
        }

        function stopComputation() { stopRequested = true; }

        function startGame() {
            if (computing) return;
            if (!initialWalls.size) { alert('请先设置墙壁'); return; }
            gameMode = true;
            gameWalls = new Set(initialWalls);
            gameCatPos = [...CAT_START];
            gameStep = 0;
            startGameBtn.textContent = '游戏中...';
            infoMessage.textContent = '&#127919; 点击放置墙壁,困住小猫!';
            enableControls(false);
            drawBoard();
        }

        function exitGameMode() {
            gameMode = false; startGameBtn.textContent = '&#127918; 开始游戏';
            enableControls(true);
            drawBoard();
            infoMessage.textContent = '&#10024; 点击圆圈设置墙壁';
        }

        function enableControls(enabled) {
            const btns = [resetBtn, computeBtn, randomWallsBtn];
            btns.forEach(b=> b.disabled = !enabled);
            stopBtn.disabled = true;
            startGameBtn.disabled = !enabled;
        }

        function toggleCheat() {
            showSolutionSteps = cheatCheck.checked;
            drawBoard();
        }

        // 绑定事件
        canvas.addEventListener('click', handleCanvasClick);
        resetBtn.addEventListener('click', resetAll);
        computeBtn.addEventListener('click', startComputation);
        stopBtn.addEventListener('click', stopComputation);
        randomWallsBtn.addEventListener('click', generateRandomWalls);
        startGameBtn.addEventListener('click', startGame);
        cheatCheck.addEventListener('change', toggleCheat);
        
        // 初始化
        cheatCheck.checked = true;
        showSolutionSteps = true;
        drawBoard();
        clearSolution();
    })();
</script>
</body>
</html>


抓小猫.zip

8.86 KB, 下载次数: 95, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 4吾爱币 +4 热心值 +4 收起 理由
CGR + 1 + 1 好玩好玩
Dreamguykyle + 1 + 1 热心回复!
Eurisia + 1 + 1 用心讨论,共获提升!
一生唯爱吾 + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| 罗木魄 发表于 2026-4-21 13:16

正好对BFS和A*算法感兴趣,就研究了一下。而且做成html也方便我自己在平板或者手机上玩,哈哈哈哈
不知道改成啥 发表于 2026-4-21 13:53
 楼主| 罗木魄 发表于 2026-4-21 14:17
gfy82 发表于 2026-4-21 16:42
这是什么工具,了解一下
神秘哥 发表于 2026-4-21 16:47
正好丢给学生玩
XTDR12 发表于 2026-4-21 17:23
感谢大佬分享
iawyxkdn8 发表于 2026-4-21 17:42
真能玩呀,!
dr-pan 发表于 2026-4-21 18:38
有啥意思,谢谢楼主分享
cr7890 发表于 2026-4-21 20:00
谢谢分享~
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-6 07:05

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表