PageSourceSearch

https://ttfdg.cn/assets/js/combination-generator.js

js ttfdg.cn collected 2026-10-02 01:20:51 UTC 15,173 bytes, 351 lines download raw bytes

1/**
2 * 游戏王实际组合生成器模块
3 * 功能:根据卡组信息和条件生成符合条件的实际组合
4 * 核心方法:generateCombinations() - 暴力枚举组合
5 * 依赖:ui-utils.js 提供的变量名转换功能
6 */
7(function (global) {
8    // 生成器状态变量
9    let generatorWorker = null;
10    let isGenerating = false;
11    let generationStartTime = 0;
12    let progressUpdateInterval = null;
13    let pauseTimes = [10, 30, 60, 90, 120, 180, 240, 300]; // 暂停时间节点(秒)
14    let nextPauseIndex = 0;
15    let foundCombinations = 0;
16
17    // 获取当前生成已用秒数
18    function getElapsedSeconds() {
19        return Math.floor((Date.now() - generationStartTime) / 1000);
20    }
21
22    // 生成组合主入口
23    function generateCombinations() {
24        if (isGenerating) {
25            alert("组合生成正在进行中,请稍后...");
26            return;
27        }
28        try {
29            generationStartTime = Date.now();
30            nextPauseIndex = 0;
31            foundCombinations = 0;
32
33            // 检查卡名重复
34            let cardNames = [];
35            let duplicateNames = new Set();
36            for (let i = 0; i < 52; i++) {
37                const name = document.getElementById(`cardName${i}`).value.trim();
38                if (name) { 
39                    if (cardNames.includes(name)) duplicateNames.add(name); 
40                    cardNames.push(name); 
41                }
42            }
43            if (duplicateNames.size > 0) {
44                throw new Error(`卡名重复:${Array.from(duplicateNames).join(', ')}`);
45            }
46
47            // 读取卡牌数量
48            const cardCounts = [];
49            for (let i = 0; i < 52; i++) {
50                cardCounts.push(parseInt(document.getElementById(`card${i}`).value) || 0);
51            }
52            const draws = parseInt(document.getElementById('draws').value);
53            const deckSize = parseInt(document.getElementById('total').value);
54            if (draws <= 0) throw new Error("抽卡数必须大于0");
55            if (deckSize <= 0) throw new Error("卡组中至少要有1张卡");
56            if (draws > deckSize) throw new Error("抽卡数不能超过卡组总数");
57
58            // 获取条件表达式
59            let condition = document.getElementById('condition').value.trim();
60            if (!condition) throw new Error("请输入逻辑判断条件");
61
62            // 卡名映射为变量名
63            const cardNameMap = {};
64            const sortedNames = [];
65            for (let i = 0; i < 52; i++) {
66                const name = document.getElementById(`cardName${i}`).value.trim();
67                if (name) { 
68                    cardNameMap[name] = global.UIUtils.getVarName(i); 
69                    sortedNames.push(name); 
70                }
71            }
72            sortedNames.sort((a, b) => b.length - a.length);
73            for (const name of sortedNames) {
74                const regex = new RegExp(global.UIUtils.escapeRegExp(name), 'g');
75                condition = condition.replace(regex, cardNameMap[name]);
76            }
77
78            // 获取生成选项
79            const combinationType = document.querySelector('input[name="combinationType"]:checked').value;
80            const combinationCount = parseInt(document.getElementById('combinationCount').value) || 10;
81
82            // 创建Web Worker执行组合生成
83            generatorWorker = new Worker(URL.createObjectURL(new Blob([`
84                // Worker内部:组合生成与条件验证
85                let deck = [];
86                let conditionFunc;
87                let combinationType;
88                let maxCombinations;
89                let draws;
90                // 已发现(并已发送给主线程)的组合去重集合;续算时保留并跳过,避免重复输出
91                let sentKeys = new Set();
92                let sentCount = 0;
93                let shouldStop = false;
94
95                function varToIndex(varName) {
96                    const lc = varName.toLowerCase();
97                    if (lc === 'true' || lc === 'false') return lc;
98                    if (lc.length === 1) { 
99                        let code = lc.charCodeAt(0) - 97; 
100                        if (code >= 0 && code < 26) return code; 
101                    }
102                    if (lc.length === 2 && lc[0] === 'a') { 
103                        let code = lc.charCodeAt(1) - 97; 
104                        if (code >= 0 && code < 26) return 26 + code; 
105                    }
106                    throw new Error("无效的卡名称: " + varName);
107                }
108
109                function initialize(cardCountsArr, condition, type, count, drawCount) {
110                    // 构建牌组数组
111                    deck = [];
112                    for (let i = 0; i < cardCountsArr.length; i++) {
113                        for (let j = 0; j < cardCountsArr[i]; j++) {
114                            deck.push(i);
115                        }
116                    }
117                    draws = drawCount;
118                    // 处理PROB函数
119                    const probMatches = condition.match(/PROB\((\\d+(?:\.\d+)?)\)/g) || [];
120                    const probValues = probMatches.map(m => parseFloat(m.match(/PROB\((\\d+(?:\.\d+)?)\)/)[1]));
121                    
122                    // 构建基础条件表达式
123                    const baseCondition = condition
124                        .replace(/PROB\((\\d+(?:\.\d+)?)\)/g, "__PROB__")
125                        .replace(/\\b([a-z]{1,2})\\b/g, function(m) {
126                            return (m === 'true' || m === 'false') ? m : "counts[" + varToIndex(m) + "]";
127                        });
128
129                    // 优化:把 eval 改为一次编译的 new Function
130                    // 每个 __PROB__ 占位符展开为真/假两种分支的表达式(存在任一分支满足即视为满足)
131                    function expandProbs(expr, index) {
132                        if (index >= probValues.length) return expr;
133                        const branchFalse = expandProbs(expr.replace('__PROB__', 'false'), index + 1);
134                        const branchTrue = expandProbs(expr.replace('__PROB__', 'true'), index + 1);
135                        return '(' + branchFalse + ') || (' + branchTrue + ')';
136                    }
137                    const expanded = expandProbs(baseCondition, 0);
138                    conditionFunc = new Function("counts", "return " + expanded);
139
140                    combinationType = type;
141                    maxCombinations = count;
142                }
143
144                // 递归组合枚举:从 deck 中选 draws 个位置(索引严格递增),计数去
144重,按条件过滤
145                // 续算(reset=false)时保留 sentKeys,重扫时跳过已发送组合,不重复输出
146                function generateUniqueCombinations() {
147                    const result = [];
148                    const currentCounts = Array(52).fill(0);
149
150                    function backtrack(start, remaining) {
151                        if (shouldStop) return;
152                        if (remaining === 0) {
153                            const key = currentCounts.join(',');
154                            if (!sentKeys.has(key)) {
155                                sentKeys.add(key);
156                                const isValid = conditionFunc(currentCounts);
157                                if ((combinationType === 'valid' && isValid) ||
158                                    (combinationType === 'invalid' && !isValid)) {
159                                    result.push(currentCounts.slice());
160                                    sentCount++;
161                                    postMessage({
162                                        type: 'combination',
163                                        counts: currentCounts.slice(),
164                                        totalFound: sentCount
165                                    });
166                                }
167                            }
168                            return;
169                        }
170                        for (let i = start; i < deck.length; i++) {
171                            currentCounts[deck[i]]++;
172                            backtrack(i + 1, remaining - 1);
173                            currentCounts[deck[i]]--;
174                            if (sentCount >= maxCombinations) { shouldStop = true; return; }
175                            if (shouldStop) return;
176                        }
177                    }
178
179                    backtrack(0, draws);
180                    return result;
181                }
182
183                onmessage = function(e) {
184                    if (e.data.type === 'start') {
185                        if (e.data.reset) {
186                            // 全新开始:清空已发送记录
187                            sentKeys = new Set();
188                            sentCount = 0;
189                            shouldStop = false;
190                            initialize(
191                                e.data.cardCounts,
192                                e.data.condition,
193                                e.data.combinationType,
194                                e.data.combinationCount,
195                                e.data.draws
196                            );
197                        } else {
198                            // 续算:保留 sentKeys/sentCount,重置停止标志,重扫跳过已发送组合
199                            shouldStop = false;
200                        }
201                        // 若已达到目标数量,无需继续枚举,直接完成
202                        if (sentCount >= maxCombinations) {
203                            postMessage({ type: 'complete', totalFound: sentCount });
204                            return;
205                        }
206                        const result = generateUniqueCombinations();
207                        if (!shouldStop) {
208                            postMessage({ type: 'complete', totalFound: sentCount });
209                        }
210                    } else if (e.data.type === 'stop') {
211                        shouldStop = true;
212                    }
213                };
214            `], { type: 'text/javascript' })));
215
216            generatorWorker.onmessage = function (e) {
217                if (e.data.type === 'combination') {
218                    foundCombinations = e.data.totalFound;
219                    // 将组合转换为可读格式并追加到结果文本框
220                    const combinationText = formatCombination(e.data.counts, cardNames);
221                    const resultsTextarea = document.getElementById('combinationResults');
222                    resultsTextarea.value += combinationText + '\n';
223                    resultsTextarea.scrollTop = resultsTextarea.scrollHeight;
224                } else if (e.data.type === 'complete') {
225                    finalizeGeneration();
226                }
227            };
228
229            // 启动进度更新定时器
230            progressUpdateInterval = setInterval(updateGenerationProgress, 1000);
231
232            // 启动生成器
233            generatorWorker.postMessage({
234                type: 'start',
235                reset: true,   // 全新开始:清空 Worker 内已发送组合记录
236                cardCounts,
237                cardNames,
238                draws,
239                condition,
240                combinationType,
241                combinationCount
242            });
243
244            isGenerating = true;
245            document.getElementById('combinationResults').value = '';
246        } catch (error) {
247            showGenerationError(error.message);
248        }
249    }
250
251    // 格式化组合为可读文本
252    function formatCombination(counts, cardNames) {
253        let result = [];
254        for (let i = 0; i < counts.length; i++) {
255            if (counts[i] > 0) {
256                const name = cardNames[i] || global.UIUtils.getVarName(i).toUpperCase() + '类卡';
257                result.push(`${name}×${counts[i]}`);
258            }
259        }
260        return result.join(' + ') || '空组合';
261    }
262
263    // 更新生成进度
264    function updateGenerationProgress() {
265        const elapsedSeconds = getElapsedSeconds();
266        document.getElementById('progressText').textContent =
267            `生成中: 已找到 ${foundCombinations} 组合  用时: ${elapsedSeconds}秒`;
268
269        // 检查是否需要暂停
270        if (nextPauseIndex < pauseTimes.length && elapsedSeconds >= pauseTimes[nextPauseIndex]) {
271            pauseGeneration();
272            nextPauseIndex++;
273        }
274    }
275
276    // 暂停生成并询问用户
277    function pauseGeneration() {
278        if (!isGenerating) return;
279        
280        generatorWorker.postMessage({ type: 'stop' });
281        clearInterval(progressUpdateInterval);
282        progressUpdateInterval = null;
283
284        const elapsedSeconds = getElapsedSeconds();
285        const shouldContinue = confirm(
286            `已生成 ${foundCombinations} 个组合,用时 ${elapsedSeconds} 秒。\n` +
287            `可能已无更多有效组合,是否继续生成?`
288        );
289
290        if (shouldContinue) {
291            // 续算:保留 Worker 内已发送组合记录,跳过已生成组合,继续枚举
292            progressUpdateInterval = setInterval(updateGenerationProgress, 1000);
293            generatorWorker.postMessage({ type: 'start', reset: false });
294        } else {
295            finalizeGeneration();
296        }
297    }
298
299    // 生成完成后清理
300    function finalizeGeneration() {
301        clearInterval(progressUpdateInterval);
302        progressUpdateInterval = null;
303        cleanupGeneration();
304
305        const elapsedSeconds = getElapsedSeconds();
306        const resultsTextarea = document.getElementById('combinationResults');
307        resultsTextarea.value += `\n生成完成: 共找到 ${foundCombinations} 组合  用时: ${elapsedSeconds}秒\n`;
308        document.getElementById('progressText').textContent = '生成完成';
309    }
310
311    // 显示生成错误
312    function showGenerationError(message) {
313        clearInterval(progressUpdateInterval);
314        progressUpdateInterval = null;
315        cleanupGeneration();
316
317        document.getElementById('combinationResults').value = '生成错误: ' + message;
318        document.getElementById('progressText').textContent = '生成错误';
319        alert('生成错误: ' + message);
320    }
321
322    // 清理生成状态
323    function cleanupGeneration() {
324        isGenerating = false;
325        if (generatorWorker) {
326            generatorWorker.terminate();
327            generatorWorker = null;
328        }
329    }
330
331    // 取消生成
332    function cancelGeneration() {
333        if (!isGenerating) return;
334        
335        generatorWorker.postMessage({ type: 'stop' });
336        clearInterval(progressUpdateInterval);
337        progressUpdateInterval = null;
338        cleanupGeneration();
339
340        const elapsedSeconds = getElapsedSeconds();
341        document.getElementById('progressText').textContent =
342            `生成已取消  用时: ${elapsedSeconds}秒`;
343        alert("生成已取消");
344    }
345
346    // 导出接口
347    global.CombinationGenerator = {
348        generateCombinations,
349        cancelGeneration
350    };
351})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.