From 054765e9a9c4c852a80643e97ec915b1df447487 Mon Sep 17 00:00:00 2001 From: MR0TOM Date: Tue, 17 Mar 2026 11:03:28 +0000 Subject: [PATCH 1/2] Updated Documentation in README.md --- DIRECTORY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DIRECTORY.md b/DIRECTORY.md index b7b8aca45d..8046a4303e 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -322,8 +322,8 @@ * [TernarySearch](Search/TernarySearch.js) * [UnionFind](Search/UnionFind.js) * **Sliding-Windows** - * [MaxSumSubarrayFixed](Sliding-Windows/MaxSumSubarrayFixed.js) * [LongestSubarrayWithSumAtMost](Sliding-Windows/LongestSubarrayWithSumAtMost.js) + * [MaxSumSubarrayFixed](Sliding-Windows/MaxSumSubarrayFixed.js) * **Sorts** * [AlphaNumericalSort](Sorts/AlphaNumericalSort.js) * [BeadSort](Sorts/BeadSort.js) From e99488c81891a26a83482ed3d0e69488345cc0f2 Mon Sep 17 00:00:00 2001 From: Mr0Tom Date: Tue, 17 Mar 2026 19:58:34 +0800 Subject: [PATCH 2/2] GLM-5 --- 001game/algorithms/backtracking.js | 555 ++++++++++++++ 001game/algorithms/cipher.js | 451 +++++++++++ 001game/algorithms/dynamic-programming.js | 465 ++++++++++++ 001game/algorithms/graph.js | 405 ++++++++++ 001game/algorithms/math.js | 430 +++++++++++ 001game/algorithms/searching.js | 274 +++++++ 001game/algorithms/sorting.js | 438 +++++++++++ 001game/app.js | 474 ++++++++++++ 001game/index.html | 92 +++ 001game/styles.css | 884 ++++++++++++++++++++++ 10 files changed, 4468 insertions(+) create mode 100644 001game/algorithms/backtracking.js create mode 100644 001game/algorithms/cipher.js create mode 100644 001game/algorithms/dynamic-programming.js create mode 100644 001game/algorithms/graph.js create mode 100644 001game/algorithms/math.js create mode 100644 001game/algorithms/searching.js create mode 100644 001game/algorithms/sorting.js create mode 100644 001game/app.js create mode 100644 001game/index.html create mode 100644 001game/styles.css diff --git a/001game/algorithms/backtracking.js b/001game/algorithms/backtracking.js new file mode 100644 index 0000000000..43e181f535 --- /dev/null +++ b/001game/algorithms/backtracking.js @@ -0,0 +1,555 @@ +const BacktrackingAlgorithms = [ + { + id: 'n-queens', + name: 'N皇后问题', + description: '在 N×N 的棋盘上放置 N 个皇后,使其不能互相攻击(同行、同列、同对角线不能有两个皇后)。', + timeComplexity: 'O(N!)', + spaceComplexity: 'O(N)', + difficulty: 2, + init: function() { + this.n = 6; + this.board = Array(this.n).fill(null).map(() => Array(this.n).fill(0)); + this.renderBoard(); + }, + renderBoard: function() { + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+ ${this.board.flat().map((_, i) => { + const row = Math.floor(i / this.n); + const col = i % this.n; + const isLight = (row + col) % 2 === 0; + return `
`; + }).join('')} +
+ `; + }, + run: function() { + const n = this.n; + const board = Array(n).fill(null).map(() => Array(n).fill(0)); + const solutions = []; + + const isSafe = (row, col) => { + for (let i = 0; i < row; i++) { + if (board[i][col]) return false; + } + for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) { + if (board[i][j]) return false; + } + for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) { + if (board[i][j]) return false; + } + return true; + }; + + const solve = (row) => { + if (row === n) { + solutions.push(board.map(r => [...r])); + return true; + } + + for (let col = 0; col < n; col++) { + const currentRow = row; + const currentCol = col; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.querySelectorAll('.queen-cell').forEach(cell => { + cell.classList.remove('has-queen', 'conflict'); + }); + for (let r = 0; r < currentRow; r++) { + for (let c = 0; c < n; c++) { + if (board[r][c]) { + document.getElementById(`queen-${r}-${c}`).classList.add('has-queen'); + document.getElementById(`queen-${r}-${c}`).textContent = '♛'; + } + } + } + document.getElementById(`queen-${currentRow}-${currentCol}`).classList.add('has-queen'); + document.getElementById('step-info').textContent = + `尝试在第${currentRow}行第${currentCol}列放置皇后`; + } + }); + + if (isSafe(row, col)) { + board[row][col] = 1; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById(`queen-${currentRow}-${currentCol}`).textContent = '♛'; + document.getElementById('step-info').textContent = + `在(${currentRow}, ${currentCol})放置皇后成功`; + } + }); + + if (solve(row + 1)) { + return true; + } + + board[row][col] = 0; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById(`queen-${currentRow}-${currentCol}`).textContent = ''; + document.getElementById(`queen-${currentRow}-${currentCol}`).classList.add('conflict'); + document.getElementById('step-info').textContent = + `回溯: 移除(${currentRow}, ${currentCol})的皇后`; + } + }); + } else { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById(`queen-${currentRow}-${currentCol}`).classList.add('conflict'); + document.getElementById('step-info').textContent = + `位置(${currentRow}, ${currentCol})不安全,尝试下一个位置`; + } + }); + } + } + + return false; + }; + + solve(0); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `找到解决方案!成功放置${n}个皇后`; + } + }); + + runAnimation(); + }, + challenge: { + question: 'N皇后问题的时间复杂度是多少?', + options: ['O(N²)', 'O(N³)', 'O(N!)', 'O(2^N)'], + correct: 2 + } + }, + { + id: 'sudoku', + name: '数独求解', + description: '填充9×9的数独网格,使每行、每列、每个3×3宫格内的数字1-9各出现一次。', + timeComplexity: 'O(9^(n*n))', + spaceComplexity: 'O(n²)', + difficulty: 2, + init: function() { + this.board = [ + [5, 3, 0, 0, 7, 0, 0, 0, 0], + [6, 0, 0, 1, 9, 5, 0, 0, 0], + [0, 9, 8, 0, 0, 0, 0, 6, 0], + [8, 0, 0, 0, 6, 0, 0, 0, 3], + [4, 0, 0, 8, 0, 3, 0, 0, 1], + [7, 0, 0, 0, 2, 0, 0, 0, 6], + [0, 6, 0, 0, 0, 0, 2, 8, 0], + [0, 0, 0, 4, 1, 9, 0, 0, 5], + [0, 0, 0, 0, 8, 0, 0, 7, 9] + ]; + this.renderSudoku(); + }, + renderSudoku: function() { + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+ ${this.board.flat().map((val, i) => { + const row = Math.floor(i / 9); + const col = i % 9; + return `
${val || ''}
`; + }).join('')} +
+ `; + }, + run: function() { + const board = this.board.map(row => [...row]); + + const isValid = (row, col, num) => { + for (let i = 0; i < 9; i++) { + if (board[row][i] === num || board[i][col] === num) return false; + } + const boxRow = Math.floor(row / 3) * 3; + const boxCol = Math.floor(col / 3) * 3; + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + if (board[boxRow + i][boxCol + j] === num) return false; + } + } + return true; + }; + + const solve = () => { + for (let row = 0; row < 9; row++) { + for (let col = 0; col < 9; col++) { + if (board[row][col] === 0) { + for (let num = 1; num <= 9; num++) { + const currentRow = row; + const currentCol = col; + const currentNum = num; + + if (isValid(row, col, num)) { + board[row][col] = num; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const cell = document.getElementById(`sudoku-${currentRow}-${currentCol}`); + cell.textContent = currentNum; + cell.classList.add('solving'); + document.getElementById('step-info').textContent = + `尝试在(${currentRow}, ${currentCol})放置${currentNum}`; + } + }); + + if (solve()) return true; + + board[row][col] = 0; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const cell = document.getElementById(`sudoku-${currentRow}-${currentCol}`); + cell.textContent = ''; + cell.classList.remove('solving'); + document.getElementById('step-info').textContent = + `回溯: 清除(${currentRow}, ${currentCol})`; + } + }); + } + } + return false; + } + } + } + return true; + }; + + solve(); + + for (let row = 0; row < 9; row++) { + for (let col = 0; col < 9; col++) { + const currentRow = row; + const currentCol = col; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const cell = document.getElementById(`sudoku-${currentRow}-${currentCol}`); + cell.classList.remove('solving'); + cell.classList.add('solved'); + } + }); + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = '数独求解完成!'; + } + }); + + runAnimation(); + }, + challenge: { + question: '数独求解使用的算法思想是什么?', + options: ['贪心算法', '动态规划', '回溯算法', '分治算法'], + correct: 2 + } + }, + { + id: 'maze-solver', + name: '迷宫求解', + description: '给定一个迷宫,找到从起点到终点的路径。使用回溯算法探索所有可能的路径。', + timeComplexity: 'O(4^(m×n))', + spaceComplexity: 'O(m×n)', + difficulty: 2, + init: function() { + this.maze = [ + [0, 1, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 1, 0], + [1, 1, 1, 1, 1, 0, 1, 0], + [0, 0, 0, 0, 0, 0, 1, 0], + [0, 1, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 0] + ]; + this.start = { row: 0, col: 0 }; + this.end = { row: 7, col: 7 }; + this.renderMaze(); + }, + renderMaze: function() { + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+ ${this.maze.flat().map((val, i) => { + const row = Math.floor(i / 8); + const col = i % 8; + let className = val ? 'wall' : ''; + if (row === this.start.row && col === this.start.col) className = 'start'; + if (row === this.end.row && col === this.end.col) className = 'end'; + return `
`; + }).join('')} +
+ `; + }, + run: function() { + const maze = this.maze.map(row => [...row]); + const start = this.start; + const end = this.end; + const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; + const path = []; + + const solve = (row, col) => { + if (row === end.row && col === end.col) { + path.push([row, col]); + return true; + } + + if (row < 0 || row >= 8 || col < 0 || col >= 8 || maze[row][col] === 1 || maze[row][col] === 2) { + return false; + } + + maze[row][col] = 2; + path.push([row, col]); + + const currentRow = row; + const currentCol = col; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + if (!(currentRow === start.row && currentCol === start.col)) { + const cell = document.getElementById(`maze-${currentRow}-${currentCol}`); + cell.classList.add('current'); + setTimeout(() => cell.classList.replace('current', 'path'), 100); + } + document.getElementById('step-info').textContent = + `探索位置 (${currentRow}, ${currentCol})`; + } + }); + + for (const [dr, dc] of directions) { + if (solve(row + dr, col + dc)) { + return true; + } + } + + path.pop(); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const cell = document.getElementById(`maze-${currentRow}-${currentCol}`); + cell.classList.remove('path', 'current'); + document.getElementById('step-info').textContent = + `回溯: 离开位置 (${currentRow}, ${currentCol})`; + } + }); + + return false; + }; + + solve(start.row, start.col); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `找到路径!路径长度: ${path.length} 步`; + } + }); + + runAnimation(); + }, + challenge: { + question: '迷宫求解问题中,回溯算法的特点是什么?', + options: ['找到一条路径就停止', '找到所有可能的路径', '只找最短路径', '随机选择路径'], + correct: 0 + } + }, + { + id: 'generate-parentheses', + name: '括号生成', + description: '给定 n 对括号,生成所有有效的括号组合。', + timeComplexity: 'O(4^n / √n)', + spaceComplexity: 'O(n)', + difficulty: 2, + init: function() { + this.n = 3; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

生成 ${this.n} 对括号的所有有效组合

+
+
+
+ `; + }, + run: function() { + const n = this.n; + const results = []; + let currentStr = ''; + let depth = 0; + + const container = document.getElementById('parentheses-tree'); + const resultsContainer = document.getElementById('parentheses-results'); + + const generate = (open, close, str, level) => { + if (str.length === n * 2) { + results.push(str); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.style.cssText = 'background: var(--success-color); padding: 10px 15px; border-radius: 8px; animation: fadeIn 0.3s;'; + div.textContent = str; + resultsContainer.appendChild(div); + document.getElementById('step-info').textContent = `找到有效组合: ${str}`; + } + }); + return; + } + + const currentStr = str; + + if (open < n) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + container.innerHTML = `${' '.repeat(level)}${currentStr}(`; + document.getElementById('step-info').textContent = + `添加左括号: ${currentStr}(`; + } + }); + generate(open + 1, close, str + '(', level + 1); + } + + if (close < open) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + container.innerHTML = `${' '.repeat(level)}${currentStr})`; + document.getElementById('step-info').textContent = + `添加右括号: ${currentStr})`; + } + }); + generate(open, close + 1, str + ')', level + 1); + } + }; + + generate(0, 0, '', 0); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `共生成 ${results.length} 个有效括号组合: ${results.join(', ')}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '生成有效括号时,什么条件下可以添加右括号?', + options: ['任何时候都可以', '右括号数量小于n', '右括号数量小于左括号数量', '左括号数量大于0'], + correct: 2 + } + }, + { + id: 'permutations', + name: '全排列', + description: '给定一组不重复的数字,返回所有可能的排列组合。', + timeComplexity: 'O(n! × n)', + spaceComplexity: 'O(n)', + difficulty: 2, + init: function() { + this.nums = [1, 2, 3]; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

生成 [${this.nums.join(', ')}] 的所有排列

+
+
+
+ `; + }, + run: function() { + const nums = [...this.nums]; + const results = []; + const used = Array(nums.length).fill(false); + const current = []; + + const permute = () => { + if (current.length === nums.length) { + results.push([...current]); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const container = document.getElementById('perm-results'); + const div = document.createElement('div'); + div.style.cssText = 'background: var(--success-color); padding: 10px 15px; border-radius: 8px;'; + div.textContent = `[${current.join(', ')}]`; + container.appendChild(div); + document.getElementById('step-info').textContent = `找到排列: [${current.join(', ')}]`; + } + }); + return; + } + + for (let i = 0; i < nums.length; i++) { + if (used[i]) continue; + + used[i] = true; + current.push(nums[i]); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('perm-current').textContent = + `当前: [${current.join(', ')}]`; + document.getElementById('step-info').textContent = + `选择 ${nums[i]}, 当前排列: [${current.join(', ')}]`; + } + }); + + permute(); + + current.pop(); + used[i] = false; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `回溯: 移除 ${nums[i]}`; + } + }); + } + }; + + permute(); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `共生成 ${results.length} 个排列`; + } + }); + + runAnimation(); + }, + challenge: { + question: 'n个不同元素的排列数量是多少?', + options: ['n', 'n²', 'n!', '2^n'], + correct: 2 + } + } +]; diff --git a/001game/algorithms/cipher.js b/001game/algorithms/cipher.js new file mode 100644 index 0000000000..9e0f7cb526 --- /dev/null +++ b/001game/algorithms/cipher.js @@ -0,0 +1,451 @@ +const CipherAlgorithms = [ + { + id: 'caesar-cipher', + name: '凯撒密码', + description: '凯撒密码是一种替换加密技术,将字母表中的每个字母替换为固定位置后的字母。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(n)', + difficulty: 1, + init: function() { + this.text = 'HELLO WORLD'; + this.shift = 3; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

原文: ${this.text}

+

位移: ${this.shift}

+
+ ${'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(c => ` +
${c}
+ `).join('')} +
+
+
+ `; + }, + run: function() { + const text = this.text; + const shift = this.shift; + const container = document.getElementById('cipher-result'); + + let result = ''; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + const currentI = i; + + if (char === ' ') { + result += ' '; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.className = 'array-item'; + div.textContent = '␣'; + div.style.background = 'var(--bg-lighter)'; + container.appendChild(div); + document.getElementById('step-info').textContent = '空格保持不变'; + } + }); + } else { + const code = char.charCodeAt(0); + const shifted = ((code - 65 + shift) % 26) + 65; + const newChar = String.fromCharCode(shifted); + result += newChar; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.className = 'array-item'; + div.innerHTML = `
${char}
${newChar}`; + div.style.background = 'var(--primary-color)'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `${char} → ${newChar} (位移${shift}位)`; + } + }); + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `加密结果: ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '凯撒密码有多少种可能的密钥?', + options: ['1种', '25种', '26种', '无限种'], + correct: 1 + } + }, + { + id: 'morse-code', + name: '摩尔斯电码', + description: '摩尔斯电码使用点(.)和划(-)的组合来表示字母和数字。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(n)', + difficulty: 1, + init: function() { + this.text = 'SOS'; + this.morseCode = { + 'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.', + 'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..', + 'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.', + 'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-', + 'Y': '-.--', 'Z': '--..', '0': '-----', '1': '.----', '2': '..---', + '3': '...--', '4': '....-', '5': '.....', '6': '-....', '7': '--...', + '8': '---..', '9': '----.' + }; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

将 "${this.text}" 转换为摩尔斯电码

+
+
+
+ `; + }, + run: function() { + const text = this.text.toUpperCase(); + const morseCode = this.morseCode; + const container = document.getElementById('morse-result'); + const display = document.getElementById('morse-display'); + + let fullMorse = ''; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + const code = morseCode[char]; + fullMorse += code + ' '; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.style.cssText = 'background: var(--bg-lighter); padding: 15px 20px; border-radius: 8px; text-align: center;'; + div.innerHTML = ` +
${char}
+
${code}
+ `; + container.appendChild(div); + + display.textContent = fullMorse.trim(); + document.getElementById('step-info').textContent = + `${char} → ${code}`; + } + }); + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `摩尔斯电码: ${fullMorse.trim()}`; + } + }); + + runAnimation(); + }, + challenge: { + question: 'SOS 在摩尔斯电码中是什么?', + options: ['... --- ...', '.- .- .-', '-.. -.. -..', '. . .'], + correct: 0 + } + }, + { + id: 'vigenere-cipher', + name: '维吉尼亚密码', + description: '维吉尼亚密码使用关键词对明文进行多表替换加密,比凯撒密码更安全。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(n)', + difficulty: 2, + init: function() { + this.text = 'HELLO'; + this.key = 'KEY'; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

原文: ${this.text}

+

密钥: ${this.key}

+
+
+
+ `; + + this.renderTable(); + }, + renderTable: function() { + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + document.getElementById('vigenere-table').innerHTML = ` + + + + ${alphabet.split('').map(c => + `` + ).join('')} + + ${alphabet.split('').map((row, i) => ` + + + ${alphabet.split('').map((_, j) => { + const shifted = (i + j) % 26; + return ``; + }).join('')} + + `).join('')} +
${c}
${row}${alphabet[shifted]}
+ `; + }, + run: function() { + const text = this.text.toUpperCase(); + const key = this.key.toUpperCase(); + const container = document.getElementById('vigenere-result'); + + let result = ''; + + for (let i = 0; i < text.length; i++) { + const textChar = text[i]; + const keyChar = key[i % key.length]; + + const textCode = textChar.charCodeAt(0) - 65; + const keyCode = keyChar.charCodeAt(0) - 65; + const encryptedCode = (textCode + keyCode) % 26; + const encryptedChar = String.fromCharCode(encryptedCode + 65); + + result += encryptedChar; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.style.cssText = 'background: var(--bg-lighter); padding: 15px; border-radius: 8px; text-align: center;'; + div.innerHTML = ` +
+
+
原文
+
${textChar}
+
+
+
+
+
密钥
+
${keyChar}
+
+
=
+
+
密文
+
${encryptedChar}
+
+
+ `; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `${textChar}(${textCode}) + ${keyChar}(${keyCode}) = ${encryptedChar}(${encryptedCode})`; + } + }); + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `加密结果: ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '维吉尼亚密码相比凯撒密码的优势是什么?', + options: ['更快', '使用多表替换更安全', '更简单', '不需要密钥'], + correct: 1 + } + }, + { + id: 'rot13', + name: 'ROT13 加密', + description: 'ROT13 是凯撒密码的特例,位移固定为13位。加密和解密使用相同的操作。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(n)', + difficulty: 1, + init: function() { + this.text = 'HELLO WORLD'; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

原文: ${this.text}

+
+
+
原始字母表
+
+ ${'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map(c => + `
${c}
` + ).join('')} +
+
+
+
+
ROT13 字母表
+
+ ${'NOPQRSTUVWXYZABCDEFGHIJKLM'.split('').map(c => + `
${c}
` + ).join('')} +
+
+
+
+
+ `; + }, + run: function() { + const text = this.text; + const container = document.getElementById('rot13-result'); + + let result = ''; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + + if (char === ' ') { + result += ' '; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.className = 'array-item'; + div.textContent = '␣'; + div.style.background = 'var(--bg-lighter)'; + container.appendChild(div); + document.getElementById('step-info').textContent = '空格保持不变'; + } + }); + } else { + const code = char.charCodeAt(0); + const shifted = ((code - 65 + 13) % 26) + 65; + const newChar = String.fromCharCode(shifted); + result += newChar; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.className = 'array-item'; + div.innerHTML = `
${char}
${newChar}`; + div.style.background = 'var(--primary-color)'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `${char} → ${newChar}`; + } + }); + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `加密结果: ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: 'ROT13 的一个有趣特性是什么?', + options: ['只能加密不能解密', '加密和解密是同一操作', '需要两个密钥', '只能处理数字'], + correct: 1 + } + }, + { + id: 'atbash-cipher', + name: '埃特巴什密码', + description: '埃特巴什密码将字母表反转:A↔Z, B↔Y, C↔X,以此类推。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(n)', + difficulty: 1, + init: function() { + this.text = 'HELLO'; + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + this.reversed = alphabet.split('').reverse().join(''); + + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

原文: ${this.text}

+
+
+
原始
+
+ ${alphabet.split('').map(c => + `
${c}
` + ).join('')} +
+
+
+
+
反转
+
+ ${this.reversed.split('').map(c => + `
${c}
` + ).join('')} +
+
+
+
+
+ `; + }, + run: function() { + const text = this.text; + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const reversed = this.reversed; + const container = document.getElementById('atbash-result'); + + let result = ''; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + const index = alphabet.indexOf(char); + const newChar = reversed[index]; + result += newChar; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.className = 'array-item'; + div.innerHTML = `
${char}
${newChar}`; + div.style.background = 'var(--primary-color)'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `${char} (位置${index}) → ${newChar}`; + } + }); + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `加密结果: ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '埃特巴什密码的加密和解密有什么关系?', + options: ['完全不同的操作', '同一操作', '需要密钥', '只能加密'], + correct: 1 + } + } +]; diff --git a/001game/algorithms/dynamic-programming.js b/001game/algorithms/dynamic-programming.js new file mode 100644 index 0000000000..bc6a92cc4a --- /dev/null +++ b/001game/algorithms/dynamic-programming.js @@ -0,0 +1,465 @@ +const DPAlgorithms = [ + { + id: 'fibonacci', + name: '斐波那契数列', + description: '斐波那契数列定义为 F(n) = F(n-1) + F(n-2),使用动态规划可以避免重复计算。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(n) 或 O(1)', + difficulty: 1, + init: function() { + this.n = 15; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+ ${Array(this.n + 1).fill(0).map((_, i) => ` +
+
F(${i})
+
?
+
+ `).join('')} +
+ `; + }, + run: function() { + const n = this.n; + const dp = [0, 1]; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const box = document.getElementById('fib-0'); + box.classList.add('active'); + box.querySelector('.value').textContent = '0'; + document.getElementById('step-info').textContent = 'F(0) = 0'; + } + }); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const box = document.getElementById('fib-1'); + box.classList.add('active'); + box.querySelector('.value').textContent = '1'; + document.getElementById('step-info').textContent = 'F(1) = 1'; + } + }); + + for (let i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.querySelectorAll('.fib-box').forEach(b => b.classList.remove('active')); + + const prev1 = document.getElementById(`fib-${i - 1}`); + const prev2 = document.getElementById(`fib-${i - 2}`); + const current = document.getElementById(`fib-${i}`); + + prev1.style.background = 'var(--warning-color)'; + prev2.style.background = 'var(--secondary-color)'; + current.classList.add('active'); + current.querySelector('.value').textContent = dp[i]; + + document.getElementById('step-info').textContent = + `F(${i}) = F(${i - 1}) + F(${i - 2}) = ${dp[i - 1]} + ${dp[i - 2]} = ${dp[i]}`; + } + }); + } + + runAnimation(); + }, + challenge: { + question: '斐波那契数列的递归解法时间复杂度是多少?', + options: ['O(n)', 'O(n²)', 'O(2^n)', 'O(log n)'], + correct: 2 + } + }, + { + id: 'climbing-stairs', + name: '爬楼梯', + description: '有 n 级楼梯,每次可以爬 1 或 2 级,问有多少种方法爬到顶?', + timeComplexity: 'O(n)', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.n = 10; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

每次可以爬 1 或 2 级楼梯

+
+
+ ${Array(this.n + 1).fill(0).map((_, i) => ` +
+
第${i}级
+
?
+
+ `).join('')} +
+ `; + }, + run: function() { + const n = this.n; + const dp = [1, 1]; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const box = document.getElementById('stair-0'); + box.classList.add('active'); + box.querySelector('.value').textContent = '1'; + document.getElementById('step-info').textContent = '第0级: 1种方法(起点)'; + } + }); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const box = document.getElementById('stair-1'); + box.classList.add('active'); + box.querySelector('.value').textContent = '1'; + document.getElementById('step-info').textContent = '第1级: 1种方法(爬1级)'; + } + }); + + for (let i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.querySelectorAll('.fib-box').forEach(b => b.classList.remove('active')); + + const prev1 = document.getElementById(`stair-${i - 1}`); + const prev2 = document.getElementById(`stair-${i - 2}`); + const current = document.getElementById(`stair-${i}`); + + prev1.style.background = 'var(--warning-color)'; + prev2.style.background = 'var(--secondary-color)'; + current.classList.add('active'); + current.querySelector('.value').textContent = dp[i]; + + document.getElementById('step-info').textContent = + `第${i}级: 从第${i - 1}级爬1级 + 从第${i - 2}级爬2级 = ${dp[i - 1]} + ${dp[i - 2]} = ${dp[i]}种方法`; + } + }); + } + + runAnimation(); + }, + challenge: { + question: '爬楼梯问题与哪个经典问题等价?', + options: ['背包问题', '斐波那契数列', '最长公共子序列', '编辑距离'], + correct: 1 + } + }, + { + id: 'coin-change', + name: '零钱兑换', + description: '给定不同面额的硬币和一个总金额,计算可以凑成总金额的最少硬币数量。', + timeComplexity: 'O(amount × n)', + spaceComplexity: 'O(amount)', + difficulty: 2, + init: function() { + this.coins = [1, 2, 5]; + this.amount = 11; + + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

硬币面额: ${this.coins.join(', ')} | 目标金额: ${this.amount}

+
+
+ ${Array(this.amount + 1).fill(0).map((_, i) => ` +
+
金额${i}
+
${i === 0 ? '0' : '∞'}
+
+ `).join('')} +
+ `; + }, + run: function() { + const coins = this.coins; + const amount = this.amount; + const dp = Array(amount + 1).fill(Infinity); + dp[0] = 0; + + for (let i = 1; i <= amount; i++) { + for (const coin of coins) { + if (coin <= i && dp[i - coin] !== Infinity) { + const newCount = dp[i - coin] + 1; + if (newCount < dp[i]) { + dp[i] = newCount; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.querySelectorAll('.fib-box').forEach(b => b.classList.remove('active')); + + const current = document.getElementById(`coin-${i}`); + const prev = document.getElementById(`coin-${i - coin}`); + + prev.style.background = 'var(--secondary-color)'; + current.classList.add('active'); + current.querySelector('.value').textContent = dp[i]; + + document.getElementById('step-info').textContent = + `金额${i}: 使用${coin}面额硬币, 从金额${i - coin}转移, 需要${dp[i]}枚硬币`; + } + }); + } + } + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const result = dp[amount] === Infinity ? '无法兑换' : `${dp[amount]}枚硬币`; + document.getElementById('step-info').textContent = `最少需要 ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '零钱兑换问题属于哪类动态规划?', + options: ['线性DP', '区间DP', '背包问题', '树形DP'], + correct: 2 + } + }, + { + id: 'knapsack', + name: '0-1 背包问题', + description: '有 n 个物品,每个物品有重量和价值,在背包容量有限的情况下,选择物品使总价值最大。', + timeComplexity: 'O(n × W)', + spaceComplexity: 'O(n × W)', + difficulty: 2, + init: function() { + this.items = [ + { weight: 2, value: 3 }, + { weight: 3, value: 4 }, + { weight: 4, value: 5 }, + { weight: 5, value: 8 }, + { weight: 9, value: 10 } + ]; + this.capacity = 15; + + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

背包容量: ${this.capacity}

+
+ ${this.items.map((item, i) => ` +
+
物品${i + 1}
+
+ 重量:${item.weight} 价值:${item.value} +
+
+ `).join('')} +
+
+
+ `; + + this.renderDpTable(); + }, + renderDpTable: function() { + const n = this.items.length; + const w = this.capacity; + + document.getElementById('dp-table').innerHTML = ` + + + + ${Array(w + 1).fill(0).map((_, i) => + `` + ).join('')} + + ${Array(n + 1).fill(0).map((_, i) => ` + + + ${Array(w + 1).fill(0).map((_, j) => + `` + ).join('')} + + `).join('')} +
物品\\容量${i}
+ ${i === 0 ? '无' : `物品${i}`} + 0
+ `; + }, + run: function() { + const items = this.items; + const n = items.length; + const W = this.capacity; + const dp = Array(n + 1).fill(null).map(() => Array(W + 1).fill(0)); + + for (let i = 1; i <= n; i++) { + const { weight, value } = items[i - 1]; + + for (let w = 0; w <= W; w++) { + if (weight <= w) { + dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - weight] + value); + } else { + dp[i][w] = dp[i - 1][w]; + } + + const currentI = i; + const currentW = w; + const currentVal = dp[i][w]; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.querySelectorAll('#dp-table td').forEach(td => { + td.style.background = ''; + }); + + const cell = document.getElementById(`dp-${currentI}-${currentW}`); + cell.textContent = currentVal; + cell.style.background = 'var(--primary-color)'; + + if (weight <= currentW && dp[currentI - 1][currentW - weight] + value === currentVal) { + const prevCell = document.getElementById(`dp-${currentI - 1}-${currentW - weight}`); + if (prevCell) prevCell.style.background = 'var(--secondary-color)'; + } + + document.getElementById('step-info').textContent = + `考虑物品${currentI}(重${weight}, 值${value}), 容量${currentW}: 最大价值${currentVal}`; + } + }); + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `最大价值: ${dp[n][W]}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '0-1背包问题中,每个物品可以选择几次?', + options: ['0次', '1次', '无限次', '取决于容量'], + correct: 1 + } + }, + { + id: 'lcs', + name: '最长公共子序列', + description: '给定两个字符串,找出它们的最长公共子序列的长度。', + timeComplexity: 'O(m × n)', + spaceComplexity: 'O(m × n)', + difficulty: 2, + init: function() { + this.str1 = 'ABCBDAB'; + this.str2 = 'BDCABA'; + + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

字符串1: ${this.str1}

+

字符串2: ${this.str2}

+
+
+ `; + + this.renderLcsTable(); + }, + renderLcsTable: function() { + const m = this.str1.length; + const n = this.str2.length; + + document.getElementById('lcs-table').innerHTML = ` + + + + + ${this.str2.split('').map(c => + `` + ).join('')} + + + + ${Array(n + 1).fill(0).map((_, j) => + `` + ).join('')} + + ${this.str1.split('').map((c, i) => ` + + + ${Array(n + 1).fill(0).map((_, j) => + `` + ).join('')} + + `).join('')} +
${c}
0
${c}0
+ `; + }, + run: function() { + const str1 = this.str1; + const str2 = this.str2; + const m = str1.length; + const n = str2.length; + const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); + + for (let i = 1; i <= m; i++) { + for (let j = 1; j <= n; j++) { + if (str1[i - 1] === str2[j - 1]) { + dp[i][j] = dp[i - 1][j - 1] + 1; + } else { + dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); + } + + const currentI = i; + const currentJ = j; + const currentVal = dp[i][j]; + const match = str1[i - 1] === str2[j - 1]; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.querySelectorAll('#lcs-table td').forEach(td => { + td.style.background = ''; + }); + + const cell = document.getElementById(`lcs-${currentI}-${currentJ}`); + cell.textContent = currentVal; + cell.style.background = match ? 'var(--success-color)' : 'var(--primary-color)'; + + if (match) { + const prevCell = document.getElementById(`lcs-${currentI - 1}-${currentJ - 1}`); + if (prevCell) prevCell.style.background = 'var(--secondary-color)'; + } + + document.getElementById('step-info').textContent = + `比较 '${str1[currentI - 1]}' 和 '${str2[currentJ - 1]}': ${match ? '匹配!' : '不匹配'}, LCS长度: ${currentVal}`; + } + }); + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `最长公共子序列长度: ${dp[m][n]}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '最长公共子序列与最长公共子串的区别是什么?', + options: ['完全相同', '子序列可以不连续,子串必须连续', '子序列必须连续,子串可以不连续', '没有区别'], + correct: 1 + } + } +]; diff --git a/001game/algorithms/graph.js b/001game/algorithms/graph.js new file mode 100644 index 0000000000..860e498621 --- /dev/null +++ b/001game/algorithms/graph.js @@ -0,0 +1,405 @@ +const GraphAlgorithms = [ + { + id: 'bfs', + name: '广度优先搜索 (BFS)', + description: 'BFS 从起点开始,逐层向外扩展搜索,使用队列存储待访问节点。常用于寻找最短路径。', + timeComplexity: 'O(V + E)', + spaceComplexity: 'O(V)', + difficulty: 2, + init: function() { + this.grid = createGrid(10, 10); + this.start = { row: 0, col: 0 }; + this.end = { row: 9, col: 9 }; + + for (let i = 0; i < 20; i++) { + const row = Math.floor(Math.random() * 10); + const col = Math.floor(Math.random() * 10); + if (!(row === 0 && col === 0) && !(row === 9 && col === 9)) { + this.grid[row][col] = 1; + updateGridCell(row, col, 'wall'); + } + } + + updateGridCell(0, 0, 'start', 'S'); + updateGridCell(9, 9, 'end', 'E'); + }, + run: function() { + const grid = this.grid.map(row => [...row]); + const start = this.start; + const end = this.end; + const rows = 10, cols = 10; + const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; + + const queue = [[start.row, start.col, [[start.row, start.col]]]]; + const visited = new Set(); + visited.add(`${start.row},${start.col}`); + + while (queue.length > 0) { + const [row, col, path] = queue.shift(); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + if (!(row === start.row && col === start.col) && + !(row === end.row && col === end.col)) { + updateGridCell(row, col, 'visited'); + } + document.getElementById('step-info').textContent = + `访问节点 (${row}, ${col}), 队列长度: ${queue.length}`; + } + }); + + if (row === end.row && col === end.col) { + for (const [r, c] of path) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + if (!(r === start.row && c === start.col) && + !(r === end.row && c === end.col)) { + updateGridCell(r, c, 'path'); + } + } + }); + } + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `找到路径!路径长度: ${path.length}`; + } + }); + break; + } + + for (const [dr, dc] of directions) { + const newRow = row + dr; + const newCol = col + dc; + const key = `${newRow},${newCol}`; + + if (newRow >= 0 && newRow < rows && + newCol >= 0 && newCol < cols && + !visited.has(key) && grid[newRow][newCol] !== 1) { + visited.add(key); + queue.push([newRow, newCol, [...path, [newRow, newCol]]]); + } + } + } + + runAnimation(); + }, + challenge: { + question: 'BFS 使用什么数据结构来存储待访问节点?', + options: ['栈', '队列', '堆', '数组'], + correct: 1 + } + }, + { + id: 'dfs', + name: '深度优先搜索 (DFS)', + description: 'DFS 从起点开始,沿着一条路径尽可能深入,直到无法继续再回溯。使用栈或递归实现。', + timeComplexity: 'O(V + E)', + spaceComplexity: 'O(V)', + difficulty: 2, + init: function() { + this.grid = createGrid(10, 10); + this.start = { row: 0, col: 0 }; + this.end = { row: 9, col: 9 }; + + for (let i = 0; i < 20; i++) { + const row = Math.floor(Math.random() * 10); + const col = Math.floor(Math.random() * 10); + if (!(row === 0 && col === 0) && !(row === 9 && col === 9)) { + this.grid[row][col] = 1; + updateGridCell(row, col, 'wall'); + } + } + + updateGridCell(0, 0, 'start', 'S'); + updateGridCell(9, 9, 'end', 'E'); + }, + run: function() { + const grid = this.grid.map(row => [...row]); + const start = this.start; + const end = this.end; + const rows = 10, cols = 10; + const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; + + const stack = [[start.row, start.col, [[start.row, start.col]]]]; + const visited = new Set(); + + while (stack.length > 0) { + const [row, col, path] = stack.pop(); + const key = `${row},${col}`; + + if (visited.has(key)) continue; + visited.add(key); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + if (!(row === start.row && col === start.col) && + !(row === end.row && col === end.col)) { + updateGridCell(row, col, 'visited'); + } + document.getElementById('step-info').textContent = + `访问节点 (${row}, ${col}), 栈深度: ${stack.length}`; + } + }); + + if (row === end.row && col === end.col) { + for (const [r, c] of path) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + if (!(r === start.row && c === start.col) && + !(r === end.row && c === end.col)) { + updateGridCell(r, c, 'path'); + } + } + }); + } + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `找到路径!路径长度: ${path.length}`; + } + }); + break; + } + + for (const [dr, dc] of directions) { + const newRow = row + dr; + const newCol = col + dc; + const newKey = `${newRow},${newCol}`; + + if (newRow >= 0 && newRow < rows && + newCol >= 0 && newCol < cols && + !visited.has(newKey) && grid[newRow][newCol] !== 1) { + stack.push([newRow, newCol, [...path, [newRow, newCol]]]); + } + } + } + + runAnimation(); + }, + challenge: { + question: 'DFS 使用什么数据结构来存储待访问节点?', + options: ['栈', '队列', '堆', '链表'], + correct: 0 + } + }, + { + id: 'dijkstra', + name: 'Dijkstra 最短路径', + description: 'Dijkstra 算法用于计算带权图中单源最短路径,使用优先队列选择当前最短路径节点。', + timeComplexity: 'O((V + E) log V)', + spaceComplexity: 'O(V)', + difficulty: 3, + init: function() { + const container = document.getElementById('visualization-area'); + this.nodes = [ + { id: 0, x: 100, y: 100 }, + { id: 1, x: 250, y: 50 }, + { id: 2, x: 400, y: 100 }, + { id: 3, x: 150, y: 200 }, + { id: 4, x: 300, y: 200 }, + { id: 5, x: 450, y: 200 }, + { id: 6, x: 200, y: 300 }, + { id: 7, x: 400, y: 300 } + ]; + + this.edges = [ + { from: 0, to: 1, weight: 4 }, + { from: 0, to: 3, weight: 2 }, + { from: 1, to: 2, weight: 3 }, + { from: 1, to: 4, weight: 5 }, + { from: 2, to: 5, weight: 2 }, + { from: 3, to: 4, weight: 1 }, + { from: 3, to: 6, weight: 4 }, + { from: 4, to: 5, weight: 3 }, + { from: 4, to: 6, weight: 2 }, + { from: 5, to: 7, weight: 1 }, + { from: 6, to: 7, weight: 3 } + ]; + + container.innerHTML = ` +
+ + ${this.edges.map(e => { + const from = this.nodes[e.from]; + const to = this.nodes[e.to]; + return ` + + ${e.weight} + `; + }).join('')} + + ${this.nodes.map(n => ` +
+ ${n.id} +
+ `).join('')} +
+ `; + + document.getElementById('step-info').textContent = '从节点 0 开始寻找到其他节点的最短路径'; + }, + run: function() { + const n = this.nodes.length; + const adj = Array(n).fill(null).map(() => []); + + for (const edge of this.edges) { + adj[edge.from].push([edge.to, edge.weight]); + adj[edge.to].push([edge.from, edge.weight]); + } + + const dist = Array(n).fill(Infinity); + const visited = new Set(); + dist[0] = 0; + + for (let i = 0; i < n; i++) { + let minDist = Infinity; + let minNode = -1; + + for (let j = 0; j < n; j++) { + if (!visited.has(j) && dist[j] < minDist) { + minDist = dist[j]; + minNode = j; + } + } + + if (minNode === -1) break; + visited.add(minNode); + + const currentDist = dist[minNode]; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const node = document.getElementById(`node-${minNode}`); + node.classList.add('visited'); + document.getElementById('step-info').textContent = + `选择节点 ${minNode}, 当前距离: ${currentDist}`; + } + }); + + for (const [neighbor, weight] of adj[minNode]) { + if (!visited.has(neighbor)) { + const newDist = dist[minNode] + weight; + if (newDist < dist[neighbor]) { + dist[neighbor] = newDist; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `更新节点 ${neighbor} 的距离: ${newDist}`; + } + }); + } + } + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `最短距离: ${dist.map((d, i) => `到${i}: ${d}`).join(', ')}`; + } + }); + + runAnimation(); + }, + challenge: { + question: 'Dijkstra 算法可以处理负权边吗?', + options: ['可以', '不可以', '只能处理部分情况', '取决于实现'], + correct: 1 + } + }, + { + id: 'number-of-islands', + name: '岛屿数量', + description: '给定一个由 \'1\'(陆地)和 \'0\'(水)组成的二维网格,计算岛屿的数量。岛屿被水包围,由相邻的陆地连接而成。', + timeComplexity: 'O(m × n)', + spaceComplexity: 'O(m × n)', + difficulty: 2, + init: function() { + this.grid = []; + for (let i = 0; i < 8; i++) { + this.grid[i] = []; + for (let j = 0; j < 8; j++) { + this.grid[i][j] = Math.random() > 0.5 ? 1 : 0; + } + } + + createGrid(8, 8, this.grid); + + const cells = document.querySelectorAll('.grid-cell'); + cells.forEach((cell, i) => { + const row = Math.floor(i / 8); + const col = i % 8; + if (this.grid[row][col] === 1) { + cell.classList.add('wall'); + cell.textContent = ''; + } + }); + }, + run: function() { + const grid = this.grid.map(row => [...row]); + const rows = 8, cols = 8; + const directions = [[-1, 0], [1, 0], [0, -1], [0, 1]]; + let islandCount = 0; + + const dfs = (row, col, islandId) => { + if (row < 0 || row >= rows || col < 0 || col >= cols || grid[row][col] !== 1) { + return; + } + + grid[row][col] = 2; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + updateGridCell(row, col, 'visited', islandId); + document.getElementById('step-info').textContent = + `标记岛屿 ${islandId} 的陆地 (${row}, ${col})`; + } + }); + + for (const [dr, dc] of directions) { + dfs(row + dr, col + dc, islandId); + } + }; + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (grid[i][j] === 1) { + islandCount++; + dfs(i, j, islandCount); + } + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `总共发现 ${islandCount} 个岛屿!`; + } + }); + + runAnimation(); + }, + challenge: { + question: '解决岛屿数量问题通常使用什么算法?', + options: ['动态规划', '深度优先搜索', '贪心算法', '分治算法'], + correct: 1 + } + } +]; diff --git a/001game/algorithms/math.js b/001game/algorithms/math.js new file mode 100644 index 0000000000..3069aeaca4 --- /dev/null +++ b/001game/algorithms/math.js @@ -0,0 +1,430 @@ +const MathAlgorithms = [ + { + id: 'sieve-of-eratosthenes', + name: '埃拉托斯特尼筛法', + description: '用于找出小于等于 n 的所有素数。通过标记非素数的方式筛选。', + timeComplexity: 'O(n log log n)', + spaceComplexity: 'O(n)', + difficulty: 1, + init: function() { + this.n = 30; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

找出小于等于 ${this.n} 的所有素数

+
+
+ ${Array(this.n + 1).fill(0).map((_, i) => ` +
+ ${i} +
+ `).join('')} +
+ `; + + document.getElementById('prime-0').style.background = 'var(--danger-color)'; + document.getElementById('prime-1').style.background = 'var(--danger-color)'; + }, + run: function() { + const n = this.n; + const isPrime = Array(n + 1).fill(true); + isPrime[0] = isPrime[1] = false; + + for (let i = 2; i * i <= n; i++) { + if (isPrime[i]) { + const currentI = i; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById(`prime-${currentI}`).style.background = 'var(--secondary-color)'; + document.getElementById('step-info').textContent = + `发现素数 ${currentI},开始筛除其倍数`; + } + }); + + for (let j = i * i; j <= n; j += i) { + if (isPrime[j]) { + isPrime[j] = false; + const currentJ = j; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById(`prime-${currentJ}`).style.background = 'var(--danger-color)'; + document.getElementById('step-info').textContent = + `标记 ${currentJ} 为非素数 (${currentJ} = ${currentI} × ${currentJ / currentI})`; + } + }); + } + } + } + } + + const primes = []; + for (let i = 2; i <= n; i++) { + if (isPrime[i]) { + primes.push(i); + const currentI = i; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById(`prime-${currentI}`).style.background = 'var(--success-color)'; + } + }); + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `素数列表: [${primes.join(', ')}],共 ${primes.length} 个`; + } + }); + + runAnimation(); + }, + challenge: { + question: '埃拉托斯特尼筛法的时间复杂度是多少?', + options: ['O(n)', 'O(n log n)', 'O(n log log n)', 'O(n²)'], + correct: 2 + } + }, + { + id: 'gcd', + name: '最大公约数 (GCD)', + description: '使用欧几里得算法计算两个数的最大公约数。', + timeComplexity: 'O(log(min(a, b)))', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.a = 48; + this.b = 18; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

计算 GCD(${this.a}, ${this.b})

+
+
+ `; + }, + run: function() { + let a = this.a; + let b = this.b; + let step = 0; + + while (b !== 0) { + const currentA = a; + const currentB = b; + const quotient = Math.floor(a / b); + const remainder = a % b; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const container = document.getElementById('gcd-steps'); + const div = document.createElement('div'); + div.innerHTML = `步骤 ${step + 1}: GCD(${currentA}, ${currentB}) = GCD(${currentB}, ${remainder})
+ + ${currentA} = ${currentB} × ${quotient} + ${remainder} + `; + div.style.cssText = 'background: var(--bg-lighter); padding: 15px; border-radius: 8px; margin: 5px;'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `${currentA} ÷ ${currentB} = ${quotient} 余 ${remainder}`; + } + }); + + a = b; + b = remainder; + step++; + } + + const result = a; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const container = document.getElementById('gcd-steps'); + const div = document.createElement('div'); + div.innerHTML = `GCD(${this.a}, ${this.b}) = ${result}`; + div.style.cssText = 'background: var(--success-color); padding: 15px; border-radius: 8px; margin: 5px;'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `最大公约数是 ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '欧几里得算法基于什么原理?', + options: ['GCD(a, b) = GCD(a-b, b)', 'GCD(a, b) = GCD(b, a mod b)', 'GCD(a, b) = a × b', 'GCD(a, b) = a + b'], + correct: 1 + } + }, + { + id: 'factorial', + name: '阶乘计算', + description: '计算 n! = n × (n-1) × ... × 2 × 1。展示递归和迭代两种方式。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(n) 递归 / O(1) 迭代', + difficulty: 1, + init: function() { + this.n = 7; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

计算 ${this.n}!

+
+ ${Array(this.n).fill(0).map((_, i) => ` +
+
${i + 1}!
+
?
+
+ `).join('')} +
+
+
+ `; + }, + run: function() { + const n = this.n; + let result = 1; + + for (let i = 1; i <= n; i++) { + result *= i; + const currentI = i; + const currentResult = result; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.querySelectorAll('.array-item').forEach(item => { + item.style.background = ''; + }); + + const cell = document.getElementById(`fact-${currentI}`); + cell.style.background = 'var(--primary-color)'; + document.getElementById(`fact-val-${currentI}`).textContent = currentResult; + + const prev = currentI > 1 ? document.getElementById(`fact-${currentI - 1}`) : null; + if (prev) prev.style.background = 'var(--secondary-color)'; + + document.getElementById('step-info').textContent = + `${currentI}! = ${currentI} × ${currentResult / currentI} = ${currentResult}`; + } + }); + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('fact-result').innerHTML = + `${n}! = ${result}`; + document.getElementById('step-info').textContent = + `${n}! = ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '阶乘函数增长速度如何?', + options: ['线性增长', '指数增长', '比指数增长更快', '对数增长'], + correct: 2 + } + }, + { + id: 'power', + name: '快速幂运算', + description: '使用二进制分解快速计算 x^n,将时间复杂度从 O(n) 降低到 O(log n)。', + timeComplexity: 'O(log n)', + spaceComplexity: 'O(1)', + difficulty: 2, + init: function() { + this.base = 2; + this.exponent = 13; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

计算 ${this.base}^${this.exponent}

+
+
+ `; + }, + run: function() { + const base = this.base; + let exp = this.exponent; + let result = 1; + let currentBase = base; + let step = 0; + + const container = document.getElementById('power-steps'); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + container.innerHTML = `
初始: result = 1, base = ${base}, exp = ${exp}
`; + document.getElementById('step-info').textContent = + `开始计算 ${base}^${exp}`; + } + }); + + while (exp > 0) { + const currentExp = exp; + const currentResult = result; + const currentBaseVal = currentBase; + const isOdd = exp % 2 === 1; + + if (isOdd) { + result *= currentBase; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.innerHTML = `步骤 ${step + 1}: exp = ${currentExp} (奇数)
+ result = ${currentResult} × ${currentBaseVal} = ${result}`; + div.style.cssText = 'background: var(--warning-color); padding: 10px; border-radius: 8px; margin: 5px;'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `exp 是奇数,result = ${result}`; + } + }); + } else { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.innerHTML = `步骤 ${step + 1}: exp = ${currentExp} (偶数)
+ result 保持不变 = ${currentResult}`; + div.style.cssText = 'background: var(--bg-lighter); padding: 10px; border-radius: 8px; margin: 5px;'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `exp 是偶数,result 保持不变`; + } + }); + } + + currentBase *= currentBase; + exp = Math.floor(exp / 2); + step++; + + if (exp > 0) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.innerHTML = `base = ${currentBaseVal}² = ${currentBase}, exp = ${currentExp}/2 = ${exp}`; + div.style.cssText = 'color: var(--text-secondary); padding: 5px;'; + container.appendChild(div); + } + }); + } + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.innerHTML = `${base}^${this.exponent} = ${result}`; + div.style.cssText = 'background: var(--success-color); padding: 15px; border-radius: 8px; margin: 10px;'; + container.appendChild(div); + + document.getElementById('step-info').textContent = + `结果: ${base}^${this.exponent} = ${result}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '快速幂算法的时间复杂度是多少?', + options: ['O(n)', 'O(log n)', 'O(n²)', 'O(1)'], + correct: 1 + } + }, + { + id: 'collatz', + name: '考拉兹猜想', + description: '从任意正整数开始,如果是偶数则除以2,如果是奇数则乘3加1,最终会回到1。', + timeComplexity: '未知', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.n = 27; + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+

考拉兹序列 (从 ${this.n} 开始)

+
+
+ `; + }, + run: function() { + let n = this.n; + const sequence = [n]; + const container = document.getElementById('collatz-sequence'); + + while (n !== 1) { + const currentN = n; + + if (n % 2 === 0) { + n = n / 2; + } else { + n = 3 * n + 1; + } + + sequence.push(n); + + const nextN = n; + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const div = document.createElement('div'); + div.className = 'array-item'; + div.style.cssText = 'animation: fadeIn 0.3s;'; + div.textContent = nextN; + + if (nextN === 1) { + div.style.background = 'var(--success-color)'; + } else if (nextN % 2 === 0) { + div.style.background = 'var(--secondary-color)'; + } else { + div.style.background = 'var(--warning-color)'; + } + + container.appendChild(div); + + const operation = currentN % 2 === 0 + ? `${currentN} ÷ 2 = ${nextN}` + : `${currentN} × 3 + 1 = ${nextN}`; + document.getElementById('step-info').textContent = operation; + } + }); + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `序列长度: ${sequence.length}, 最大值: ${Math.max(...sequence)}`; + } + }); + + runAnimation(); + }, + challenge: { + question: '考拉兹猜想是否已被证明?', + options: ['已证明成立', '已证明不成立', '尚未被证明', '已被证伪'], + correct: 2 + } + } +]; diff --git a/001game/algorithms/searching.js b/001game/algorithms/searching.js new file mode 100644 index 0000000000..2a665784a7 --- /dev/null +++ b/001game/algorithms/searching.js @@ -0,0 +1,274 @@ +const SearchingAlgorithms = [ + { + id: 'linear-search', + name: '线性搜索', + description: '线性搜索从数组的第一个元素开始,逐个检查每个元素,直到找到目标值或遍历完整个数组。', + timeComplexity: 'O(n)', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.array = generateRandomArray(10, 50); + this.target = this.array[Math.floor(Math.random() * this.array.length)]; + createArrayDisplay(this.array); + document.getElementById('step-info').textContent = `查找目标: ${this.target}`; + }, + run: function() { + const arr = this.array; + const target = this.target; + + for (let i = 0; i < arr.length; i++) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items.forEach(item => item.classList.remove('highlight', 'searching', 'found')); + items[i].classList.add('searching'); + document.getElementById('step-info').textContent = `检查位置 ${i}: ${arr[i]} ${arr[i] === target ? '= 目标!' : '≠ 目标'}`; + } + }); + + if (arr[i] === target) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items[i].classList.remove('searching'); + items[i].classList.add('found'); + document.getElementById('step-info').textContent = `找到目标 ${target} 在位置 ${i}!`; + } + }); + break; + } + } + + runAnimation(); + }, + challenge: { + question: '线性搜索最坏情况下需要比较多少次?', + options: ['1次', 'n/2次', 'n次', 'log n次'], + correct: 2 + } + }, + { + id: 'binary-search', + name: '二分搜索', + description: '二分搜索在有序数组中查找目标值,每次将搜索范围缩小一半。', + timeComplexity: 'O(log n)', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.array = Array.from({ length: 15 }, (_, i) => (i + 1) * 3); + this.target = this.array[Math.floor(Math.random() * this.array.length)]; + createArrayDisplay(this.array); + document.getElementById('step-info').textContent = `查找目标: ${this.target}`; + }, + run: function() { + const arr = this.array; + const target = this.target; + let left = 0, right = arr.length - 1; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items.forEach(item => item.classList.remove('highlight', 'searching', 'found')); + for (let i = left; i <= right; i++) { + items[i].classList.add('highlight'); + } + items[mid].classList.remove('highlight'); + items[mid].classList.add('searching'); + document.getElementById('step-info').textContent = + `搜索范围: [${left}, ${right}], 中间位置: ${mid}, 值: ${arr[mid]}`; + } + }); + + if (arr[mid] === target) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items.forEach(item => item.classList.remove('highlight', 'searching')); + items[mid].classList.add('found'); + document.getElementById('step-info').textContent = `找到目标 ${target} 在位置 ${mid}!`; + } + }); + break; + } else if (arr[mid] < target) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `${arr[mid]} < ${target}, 在右半部分继续搜索`; + } + }); + left = mid + 1; + } else { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `${arr[mid]} > ${target}, 在左半部分继续搜索`; + } + }); + right = mid - 1; + } + } + + runAnimation(); + }, + challenge: { + question: '二分搜索的前提条件是什么?', + options: ['数组必须有序', '数组长度为偶数', '数组元素唯一', '数组元素为整数'], + correct: 0 + } + }, + { + id: 'jump-search', + name: '跳跃搜索', + description: '跳跃搜索在有序数组中按固定步长跳跃查找,找到范围后再进行线性搜索。', + timeComplexity: 'O(√n)', + spaceComplexity: 'O(1)', + difficulty: 2, + init: function() { + this.array = Array.from({ length: 20 }, (_, i) => (i + 1) * 2); + this.target = this.array[Math.floor(Math.random() * this.array.length)]; + createArrayDisplay(this.array); + document.getElementById('step-info').textContent = `查找目标: ${this.target}`; + }, + run: function() { + const arr = this.array; + const target = this.target; + const n = arr.length; + const step = Math.floor(Math.sqrt(n)); + let prev = 0; + let curr = step; + + while (curr < n && arr[curr] < target) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items.forEach(item => item.classList.remove('highlight', 'searching')); + items[curr].classList.add('searching'); + document.getElementById('step-info').textContent = + `跳跃到位置 ${curr}: ${arr[curr]} < ${target}, 继续跳跃`; + } + }); + prev = curr; + curr += step; + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + document.getElementById('step-info').textContent = + `目标可能在范围 [${prev}, ${Math.min(curr, n - 1)}] 内,开始线性搜索`; + } + }); + + for (let i = prev; i < Math.min(curr, n); i++) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items.forEach(item => item.classList.remove('searching')); + items[i].classList.add('searching'); + document.getElementById('step-info').textContent = + `检查位置 ${i}: ${arr[i]}`; + } + }); + + if (arr[i] === target) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items[i].classList.remove('searching'); + items[i].classList.add('found'); + document.getElementById('step-info').textContent = + `找到目标 ${target} 在位置 ${i}!`; + } + }); + break; + } + } + + runAnimation(); + }, + challenge: { + question: '跳跃搜索的最优步长是多少?', + options: ['n/2', '√n', 'log n', 'n/4'], + correct: 1 + } + }, + { + id: 'interpolation-search', + name: '插值搜索', + description: '插值搜索是二分搜索的改进版,根据目标值的大小估计其位置,适用于均匀分布的数据。', + timeComplexity: 'O(log log n)', + spaceComplexity: 'O(1)', + difficulty: 2, + init: function() { + this.array = Array.from({ length: 20 }, (_, i) => (i + 1) * 5); + this.target = this.array[Math.floor(Math.random() * this.array.length)]; + createArrayDisplay(this.array); + document.getElementById('step-info').textContent = `查找目标: ${this.target}`; + }, + run: function() { + const arr = this.array; + const target = this.target; + let left = 0, right = arr.length - 1; + + while (left <= right && target >= arr[left] && target <= arr[right]) { + const pos = left + Math.floor( + ((target - arr[left]) * (right - left)) / (arr[right] - arr[left]) + ); + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items.forEach(item => item.classList.remove('highlight', 'searching', 'found')); + for (let i = left; i <= right; i++) { + items[i].classList.add('highlight'); + } + items[pos].classList.remove('highlight'); + items[pos].classList.add('searching'); + document.getElementById('step-info').textContent = + `插值估计位置: ${pos}, 值: ${arr[pos]}`; + } + }); + + if (arr[pos] === target) { + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const items = document.querySelectorAll('.array-item'); + items.forEach(item => item.classList.remove('highlight', 'searching')); + items[pos].classList.add('found'); + document.getElementById('step-info').textContent = + `找到目标 ${target} 在位置 ${pos}!`; + } + }); + break; + } + + if (arr[pos] < target) { + left = pos + 1; + } else { + right = pos - 1; + } + } + + runAnimation(); + }, + challenge: { + question: '插值搜索在什么情况下性能最好?', + options: ['数据随机分布', '数据均匀分布', '数据完全逆序', '数据有重复'], + correct: 1 + } + } +]; diff --git a/001game/algorithms/sorting.js b/001game/algorithms/sorting.js new file mode 100644 index 0000000000..36704ee28b --- /dev/null +++ b/001game/algorithms/sorting.js @@ -0,0 +1,438 @@ +const SortingAlgorithms = [ + { + id: 'bubble-sort', + name: '冒泡排序', + description: '冒泡排序是一种简单的排序算法,它重复地遍历要排序的列表,比较相邻元素并交换顺序错误的元素。', + timeComplexity: 'O(n²)', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.array = generateRandomArray(15, 50); + createBars(this.array); + }, + run: function() { + const arr = [...this.array]; + const n = arr.length; + + for (let i = 0; i < n - 1; i++) { + for (let j = 0; j < n - i - 1; j++) { + GameState.animationSteps.push({ + type: 'compare', + indices: [j, j + 1], + values: [arr[j], arr[j + 1]] + }); + + if (arr[j] > arr[j + 1]) { + [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; + GameState.animationSteps.push({ + type: 'swap', + indices: [j, j + 1], + values: [arr[j], arr[j + 1]] + }); + } + } + GameState.animationSteps.push({ + type: 'sorted', + index: n - i - 1 + }); + } + GameState.animationSteps.push({ + type: 'sorted', + index: 0 + }); + + runAnimation(); + }, + challenge: { + question: '冒泡排序的时间复杂度是多少?', + options: ['O(n)', 'O(n²)', 'O(n log n)', 'O(log n)'], + correct: 1 + } + }, + { + id: 'selection-sort', + name: '选择排序', + description: '选择排序每次从未排序部分选择最小元素,放到已排序部分的末尾。', + timeComplexity: 'O(n²)', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.array = generateRandomArray(15, 50); + createBars(this.array); + }, + run: function() { + const arr = [...this.array]; + const n = arr.length; + + for (let i = 0; i < n - 1; i++) { + let minIdx = i; + + for (let j = i + 1; j < n; j++) { + GameState.animationSteps.push({ + type: 'compare', + indices: [minIdx, j], + values: [arr[minIdx], arr[j]] + }); + + if (arr[j] < arr[minIdx]) { + minIdx = j; + } + } + + if (minIdx !== i) { + [arr[i], arr[minIdx]] = [arr[minIdx], arr[i]]; + GameState.animationSteps.push({ + type: 'swap', + indices: [i, minIdx], + values: [arr[i], arr[minIdx]] + }); + } + + GameState.animationSteps.push({ + type: 'sorted', + index: i + }); + } + GameState.animationSteps.push({ + type: 'sorted', + index: n - 1 + }); + + runAnimation(); + }, + challenge: { + question: '选择排序是稳定的排序算法吗?', + options: ['是', '否', '取决于数据', '不确定'], + correct: 1 + } + }, + { + id: 'insertion-sort', + name: '插入排序', + description: '插入排序将数组分为已排序和未排序两部分,每次将未排序部分的第一个元素插入到已排序部分的正确位置。', + timeComplexity: 'O(n²)', + spaceComplexity: 'O(1)', + difficulty: 1, + init: function() { + this.array = generateRandomArray(15, 50); + createBars(this.array); + }, + run: function() { + const arr = [...this.array]; + const n = arr.length; + + for (let i = 1; i < n; i++) { + let key = arr[i]; + let j = i - 1; + + GameState.animationSteps.push({ + type: 'info', + message: `选择元素 ${key} 进行插入` + }); + + while (j >= 0 && arr[j] > key) { + GameState.animationSteps.push({ + type: 'compare', + indices: [j, j + 1], + values: [arr[j], key] + }); + + arr[j + 1] = arr[j]; + GameState.animationSteps.push({ + type: 'swap', + indices: [j, j + 1], + values: [arr[j], arr[j + 1]] + }); + j--; + } + + arr[j + 1] = key; + GameState.animationSteps.push({ + type: 'info', + message: `将 ${key} 插入到位置 ${j + 1}` + }); + } + + for (let i = 0; i < n; i++) { + GameState.animationSteps.push({ + type: 'sorted', + index: i + }); + } + + runAnimation(); + }, + challenge: { + question: '插入排序在什么情况下性能最好?', + options: ['数组完全逆序', '数组已经有序', '数组随机分布', '数组很大'], + correct: 1 + } + }, + { + id: 'quick-sort', + name: '快速排序', + description: '快速排序使用分治策略,选择一个基准元素,将数组分为小于和大于基准的两部分,然后递归排序。', + timeComplexity: 'O(n log n)', + spaceComplexity: 'O(log n)', + difficulty: 2, + init: function() { + this.array = generateRandomArray(15, 50); + createBars(this.array); + }, + run: function() { + const arr = [...this.array]; + const sortedIndices = new Set(); + + const quickSort = (low, high) => { + if (low < high) { + const pivotIdx = partition(low, high); + quickSort(low, pivotIdx - 1); + quickSort(pivotIdx + 1, high); + } else if (low === high) { + sortedIndices.add(low); + GameState.animationSteps.push({ + type: 'sorted', + index: low + }); + } + }; + + const partition = (low, high) => { + const pivot = arr[high]; + let i = low - 1; + + GameState.animationSteps.push({ + type: 'info', + message: `选择基准元素: ${pivot}` + }); + + for (let j = low; j < high; j++) { + GameState.animationSteps.push({ + type: 'compare', + indices: [j, high], + values: [arr[j], pivot] + }); + + if (arr[j] < pivot) { + i++; + [arr[i], arr[j]] = [arr[j], arr[i]]; + GameState.animationSteps.push({ + type: 'swap', + indices: [i, j], + values: [arr[i], arr[j]] + }); + } + } + + [arr[i + 1], arr[high]] = [arr[high], arr[i + 1]]; + GameState.animationSteps.push({ + type: 'swap', + indices: [i + 1, high], + values: [arr[i + 1], arr[high]] + }); + + sortedIndices.add(i + 1); + GameState.animationSteps.push({ + type: 'sorted', + index: i + 1 + }); + + return i + 1; + }; + + quickSort(0, arr.length - 1); + runAnimation(); + }, + challenge: { + question: '快速排序的平均时间复杂度是多少?', + options: ['O(n)', 'O(n²)', 'O(n log n)', 'O(log n)'], + correct: 2 + } + }, + { + id: 'merge-sort', + name: '归并排序', + description: '归并排序将数组分成两半,递归排序后再合并。是一种稳定的排序算法。', + timeComplexity: 'O(n log n)', + spaceComplexity: 'O(n)', + difficulty: 2, + init: function() { + this.array = generateRandomArray(15, 50); + createBars(this.array); + }, + run: function() { + const arr = [...this.array]; + + const mergeSort = (left, right) => { + if (left < right) { + const mid = Math.floor((left + right) / 2); + mergeSort(left, mid); + mergeSort(mid + 1, right); + merge(left, mid, right); + } + }; + + const merge = (left, mid, right) => { + const leftArr = arr.slice(left, mid + 1); + const rightArr = arr.slice(mid + 1, right + 1); + + let i = 0, j = 0, k = left; + + while (i < leftArr.length && j < rightArr.length) { + GameState.animationSteps.push({ + type: 'compare', + indices: [left + i, mid + 1 + j], + values: [leftArr[i], rightArr[j]] + }); + + if (leftArr[i] <= rightArr[j]) { + arr[k] = leftArr[i]; + i++; + } else { + arr[k] = rightArr[j]; + j++; + } + + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const bars = document.querySelectorAll('.bar'); + bars[k].style.height = `${arr[k] * 3}px`; + bars[k].querySelector('.bar-value').textContent = arr[k]; + } + }); + k++; + } + + while (i < leftArr.length) { + arr[k] = leftArr[i]; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const bars = document.querySelectorAll('.bar'); + bars[k].style.height = `${arr[k] * 3}px`; + bars[k].querySelector('.bar-value').textContent = arr[k]; + } + }); + i++; + k++; + } + + while (j < rightArr.length) { + arr[k] = rightArr[j]; + GameState.animationSteps.push({ + type: 'custom', + action: () => { + const bars = document.querySelectorAll('.bar'); + bars[k].style.height = `${arr[k] * 3}px`; + bars[k].querySelector('.bar-value').textContent = arr[k]; + } + }); + j++; + k++; + } + + for (let idx = left; idx <= right; idx++) { + GameState.animationSteps.push({ + type: 'sorted', + index: idx + }); + } + }; + + mergeSort(0, arr.length - 1); + runAnimation(); + }, + challenge: { + question: '归并排序是稳定的排序算法吗?', + options: ['是', '否', '取决于实现', '不确定'], + correct: 0 + } + }, + { + id: 'heap-sort', + name: '堆排序', + description: '堆排序利用堆这种数据结构来进行排序。首先构建最大堆,然后依次取出堆顶元素。', + timeComplexity: 'O(n log n)', + spaceComplexity: 'O(1)', + difficulty: 2, + init: function() { + this.array = generateRandomArray(15, 50); + createBars(this.array); + }, + run: function() { + const arr = [...this.array]; + const n = arr.length; + + const heapify = (size, root) => { + let largest = root; + const left = 2 * root + 1; + const right = 2 * root + 2; + + if (left < size) { + GameState.animationSteps.push({ + type: 'compare', + indices: [largest, left], + values: [arr[largest], arr[left]] + }); + if (arr[left] > arr[largest]) { + largest = left; + } + } + + if (right < size) { + GameState.animationSteps.push({ + type: 'compare', + indices: [largest, right], + values: [arr[largest], arr[right]] + }); + if (arr[right] > arr[largest]) { + largest = right; + } + } + + if (largest !== root) { + [arr[root], arr[largest]] = [arr[largest], arr[root]]; + GameState.animationSteps.push({ + type: 'swap', + indices: [root, largest], + values: [arr[root], arr[largest]] + }); + heapify(size, largest); + } + }; + + for (let i = Math.floor(n / 2) - 1; i >= 0; i--) { + heapify(n, i); + } + + for (let i = n - 1; i > 0; i--) { + [arr[0], arr[i]] = [arr[i], arr[0]]; + GameState.animationSteps.push({ + type: 'swap', + indices: [0, i], + values: [arr[0], arr[i]] + }); + + GameState.animationSteps.push({ + type: 'sorted', + index: i + }); + + heapify(i, 0); + } + + GameState.animationSteps.push({ + type: 'sorted', + index: 0 + }); + + runAnimation(); + }, + challenge: { + question: '堆排序使用的是什么数据结构?', + options: ['栈', '队列', '堆', '树'], + correct: 2 + } + } +]; diff --git a/001game/app.js b/001game/app.js new file mode 100644 index 0000000000..525c4a7a80 --- /dev/null +++ b/001game/app.js @@ -0,0 +1,474 @@ +const GameState = { + score: 0, + currentLevel: 1, + currentCategory: null, + currentAlgorithm: null, + isRunning: false, + isPaused: false, + speed: 1, + completedLevels: new Set(), + startTime: null, + animationSteps: [], + currentStep: 0, + maxBarValue: 100, + currentArray: [] +}; + +const speedLevels = [1, 2, 4, 8]; +let currentSpeedIndex = 0; + +const categories = [ + { + id: 'sorting', + name: '排序算法', + icon: '📊', + description: '学习各种经典排序算法的工作原理', + algorithms: [] + }, + { + id: 'searching', + name: '搜索算法', + icon: '🔍', + description: '掌握二分搜索、线性搜索等搜索技巧', + algorithms: [] + }, + { + id: 'graph', + name: '图算法', + icon: '🕸️', + description: '探索BFS、DFS、最短路径等图算法', + algorithms: [] + }, + { + id: 'dynamic-programming', + name: '动态规划', + icon: '📈', + description: '理解斐波那契、背包问题等经典DP问题', + algorithms: [] + }, + { + id: 'backtracking', + name: '回溯算法', + icon: '🔄', + description: '解决N皇后、数独、迷宫等问题', + algorithms: [] + }, + { + id: 'math', + name: '数学算法', + icon: '🔢', + description: '素数筛、最大公约数、阶乘等数学计算', + algorithms: [] + }, + { + id: 'cipher', + name: '密码算法', + icon: '🔐', + description: '凯撒密码、摩尔斯电码等加密解密', + algorithms: [] + } +]; + +function init() { + loadProgress(); + initializeAlgorithms(); + renderCategories(); + updateStats(); +} + +function initializeAlgorithms() { + if (typeof SortingAlgorithms !== 'undefined') { + categories.find(c => c.id === 'sorting').algorithms = SortingAlgorithms; + } + if (typeof SearchingAlgorithms !== 'undefined') { + categories.find(c => c.id === 'searching').algorithms = SearchingAlgorithms; + } + if (typeof GraphAlgorithms !== 'undefined') { + categories.find(c => c.id === 'graph').algorithms = GraphAlgorithms; + } + if (typeof DPAlgorithms !== 'undefined') { + categories.find(c => c.id === 'dynamic-programming').algorithms = DPAlgorithms; + } + if (typeof BacktrackingAlgorithms !== 'undefined') { + categories.find(c => c.id === 'backtracking').algorithms = BacktrackingAlgorithms; + } + if (typeof MathAlgorithms !== 'undefined') { + categories.find(c => c.id === 'math').algorithms = MathAlgorithms; + } + if (typeof CipherAlgorithms !== 'undefined') { + categories.find(c => c.id === 'cipher').algorithms = CipherAlgorithms; + } +} + +function loadProgress() { + const saved = localStorage.getItem('algorithmGameProgress'); + if (saved) { + const progress = JSON.parse(saved); + GameState.score = progress.score || 0; + GameState.completedLevels = new Set(progress.completedLevels || []); + } +} + +function saveProgress() { + localStorage.setItem('algorithmGameProgress', JSON.stringify({ + score: GameState.score, + completedLevels: Array.from(GameState.completedLevels) + })); +} + +function updateStats() { + document.getElementById('score').textContent = GameState.score; + document.getElementById('level').textContent = GameState.currentLevel; +} + +function showScreen(screenId) { + document.querySelectorAll('.screen').forEach(screen => { + screen.classList.remove('active'); + }); + document.getElementById(screenId).classList.add('active'); +} + +function showHome() { + showScreen('home-screen'); + renderCategories(); +} + +function showLevelSelect() { + if (GameState.currentCategory) { + showCategory(GameState.currentCategory.id); + } else { + showHome(); + } +} + +function renderCategories() { + const grid = document.getElementById('category-grid'); + grid.innerHTML = categories.map(category => { + const completedCount = category.algorithms.filter(a => + GameState.completedLevels.has(`${category.id}-${a.id}`) + ).length; + const progress = category.algorithms.length > 0 + ? (completedCount / category.algorithms.length) * 100 + : 0; + + return ` +
+
${category.icon}
+

${category.name}

+

${category.description}

+
+
+
+ ${completedCount}/${category.algorithms.length} 已完成 +
+ `; + }).join(''); +} + +function showCategory(categoryId) { + GameState.currentCategory = categories.find(c => c.id === categoryId); + document.getElementById('category-title').textContent = GameState.currentCategory.name; + renderLevels(); + showScreen('level-select'); +} + +function renderLevels() { + const grid = document.getElementById('level-grid'); + const algorithms = GameState.currentCategory.algorithms; + + grid.innerHTML = algorithms.map((algo, index) => { + const levelId = `${GameState.currentCategory.id}-${algo.id}`; + const isCompleted = GameState.completedLevels.has(levelId); + const isLocked = index > 0 && !GameState.completedLevels.has( + `${GameState.currentCategory.id}-${algorithms[index - 1].id}` + ); + + return ` +
+
${index + 1}
+
${algo.name}
+
${getDifficultyText(algo.difficulty)}
+ ${isCompleted ? '✓ 已完成' : ''} + ${isLocked ? '🔒 未解锁' : ''} +
+ `; + }).join(''); +} + +function getDifficultyText(difficulty) { + const texts = { + 1: '⭐ 简单', + 2: '⭐⭐ 中等', + 3: '⭐⭐⭐ 困难' + }; + return texts[difficulty] || '⭐ 简单'; +} + +function startGame(algorithmId) { + GameState.currentAlgorithm = GameState.currentCategory.algorithms.find( + a => a.id === algorithmId + ); + + if (!GameState.currentAlgorithm) return; + + document.getElementById('game-title').textContent = GameState.currentAlgorithm.name; + document.getElementById('algorithm-description').textContent = GameState.currentAlgorithm.description; + document.getElementById('time-complexity').textContent = GameState.currentAlgorithm.timeComplexity; + document.getElementById('space-complexity').textContent = GameState.currentAlgorithm.spaceComplexity; + + GameState.isRunning = false; + GameState.isPaused = false; + GameState.currentStep = 0; + GameState.animationSteps = []; + + document.getElementById('start-btn').textContent = '开始'; + document.getElementById('step-info').textContent = '点击"开始"按钮开始可视化'; + document.getElementById('challenge-area').classList.add('hidden'); + + if (GameState.currentAlgorithm.init) { + GameState.currentAlgorithm.init(); + } + + showScreen('game-screen'); +} + +function startVisualization() { + if (GameState.isRunning) { + GameState.isPaused = !GameState.isPaused; + document.getElementById('start-btn').textContent = GameState.isPaused ? '继续' : '暂停'; + if (!GameState.isPaused) { + runAnimation(); + } + return; + } + + GameState.isRunning = true; + GameState.isPaused = false; + GameState.startTime = Date.now(); + document.getElementById('start-btn').textContent = '暂停'; + + if (GameState.currentAlgorithm.run) { + GameState.currentAlgorithm.run(); + } +} + +function resetVisualization() { + GameState.isRunning = false; + GameState.isPaused = false; + GameState.currentStep = 0; + document.getElementById('start-btn').textContent = '开始'; + document.getElementById('step-info').textContent = '点击"开始"按钮开始可视化'; + document.getElementById('challenge-area').classList.add('hidden'); + + if (GameState.currentAlgorithm.init) { + GameState.currentAlgorithm.init(); + } +} + +function toggleSpeed() { + currentSpeedIndex = (currentSpeedIndex + 1) % speedLevels.length; + GameState.speed = speedLevels[currentSpeedIndex]; + document.getElementById('speed-btn').textContent = `速度: ${GameState.speed}x`; +} + +async function runAnimation() { + while (GameState.isRunning && !GameState.isPaused && GameState.currentStep < GameState.animationSteps.length) { + const step = GameState.animationSteps[GameState.currentStep]; + await executeStep(step); + GameState.currentStep++; + await sleep(getDelay()); + } + + if (GameState.currentStep >= GameState.animationSteps.length && GameState.isRunning) { + GameState.isRunning = false; + document.getElementById('start-btn').textContent = '完成'; + showChallenge(); + } +} + +function getDelay() { + return Math.max(50, 500 / GameState.speed); +} + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function executeStep(step) { + if (step.type === 'compare') { + document.getElementById('step-info').textContent = `比较位置 ${step.indices[0]} 和 ${step.indices[1]}: ${step.values[0]} vs ${step.values[1]}`; + highlightBars(step.indices, 'comparing'); + } else if (step.type === 'swap') { + document.getElementById('step-info').textContent = `交换位置 ${step.indices[0]} 和 ${step.indices[1]}`; + highlightBars(step.indices, 'swapping'); + swapBars(step.indices, step.values); + } else if (step.type === 'sorted') { + document.getElementById('step-info').textContent = `位置 ${step.index} 已排序`; + markBarSorted(step.index); + } else if (step.type === 'info') { + document.getElementById('step-info').textContent = step.message; + } else if (step.type === 'highlight') { + highlightBars(step.indices, step.className || 'current'); + } else if (step.type === 'custom') { + if (step.action) { + step.action(); + } + } +} + +function highlightBars(indices, className) { + const bars = document.querySelectorAll('.bar'); + bars.forEach((bar, i) => { + bar.classList.remove('comparing', 'swapping', 'current'); + if (indices.includes(i)) { + bar.classList.add(className); + } + }); +} + +function swapBars(indices, values) { + const bars = document.querySelectorAll('.bar'); + const maxVal = GameState.maxBarValue; + indices.forEach((index, i) => { + bars[index].style.height = `${(values[i] / maxVal) * 250}px`; + bars[index].querySelector('.bar-value').textContent = values[i]; + }); +} + +function markBarSorted(index) { + const bars = document.querySelectorAll('.bar'); + bars[index].classList.add('sorted'); +} + +function showChallenge() { + if (!GameState.currentAlgorithm.challenge) { + showResult(true); + return; + } + + const challenge = GameState.currentAlgorithm.challenge; + document.getElementById('challenge-question').textContent = challenge.question; + + const optionsContainer = document.getElementById('challenge-options'); + optionsContainer.innerHTML = challenge.options.map((option, index) => ` + + `).join(''); + + document.getElementById('challenge-area').classList.remove('hidden'); +} + +function checkAnswer(selected, correct) { + const buttons = document.querySelectorAll('.challenge-options button'); + buttons.forEach((btn, i) => { + btn.disabled = true; + if (i === correct) { + btn.classList.add('correct'); + } else if (i === selected && i !== correct) { + btn.classList.add('wrong'); + } + }); + + const isCorrect = selected === correct; + setTimeout(() => showResult(isCorrect), 1000); +} + +function showResult(success) { + const levelId = `${GameState.currentCategory.id}-${GameState.currentAlgorithm.id}`; + const isNewCompletion = success && !GameState.completedLevels.has(levelId); + + if (isNewCompletion) { + GameState.completedLevels.add(levelId); + const baseScore = GameState.currentAlgorithm.difficulty * 100; + const timeBonus = Math.max(0, Math.floor((300000 - (Date.now() - GameState.startTime)) / 1000)); + GameState.score += baseScore + timeBonus; + saveProgress(); + updateStats(); + } + + document.getElementById('result-title').textContent = success ? '🎉 恭喜完成!' : '💪 继续加油!'; + document.getElementById('result-message').textContent = success + ? '你成功掌握了这个算法!' + : '再试一次,你会做得更好!'; + + const completionTime = Math.floor((Date.now() - GameState.startTime) / 1000); + document.getElementById('completion-time').textContent = `${Math.floor(completionTime / 60)}分${completionTime % 60}秒`; + document.getElementById('earned-score').textContent = success ? `+${GameState.currentAlgorithm.difficulty * 100}` : '0'; + + const stars = success ? Math.min(3, Math.ceil(3 - (GameState.currentStep / GameState.animationSteps.length) * 2)) : 0; + const starsContainer = document.getElementById('stars'); + starsContainer.innerHTML = Array(3).fill(0).map((_, i) => + `` + ).join(''); + + showScreen('result-screen'); +} + +function retryLevel() { + startGame(GameState.currentAlgorithm.id); +} + +function nextLevel() { + const algorithms = GameState.currentCategory.algorithms; + const currentIndex = algorithms.findIndex(a => a.id === GameState.currentAlgorithm.id); + + if (currentIndex < algorithms.length - 1) { + startGame(algorithms[currentIndex + 1].id); + } else { + showCategory(GameState.currentCategory.id); + } +} + +function generateRandomArray(size = 20, max = 100) { + return Array.from({ length: size }, () => Math.floor(Math.random() * max) + 1); +} + +function createBars(array) { + const container = document.getElementById('visualization-area'); + const maxVal = Math.max(...array); + GameState.maxBarValue = maxVal; + GameState.currentArray = [...array]; + const barWidth = Math.max(20, Math.floor((container.clientWidth - array.length * 4) / array.length)); + + container.innerHTML = array.map((val, i) => ` +
+ ${val} +
+ `).join(''); +} + +function createArrayDisplay(array, highlightIndices = []) { + const container = document.getElementById('visualization-area'); + container.innerHTML = ` +
+ ${array.map((val, i) => ` +
${val}
+ `).join('')} +
+ `; +} + +function createGrid(rows, cols, grid = null) { + const container = document.getElementById('visualization-area'); + const displayGrid = grid || Array(rows).fill(null).map(() => Array(cols).fill(0)); + + container.innerHTML = ` +
+ ${displayGrid.flat().map((cell, i) => ` +
${cell || ''}
+ `).join('')} +
+ `; + + return displayGrid; +} + +function updateGridCell(row, col, className, value = '') { + const cell = document.querySelector(`.grid-cell[data-row="${row}"][data-col="${col}"]`); + if (cell) { + cell.className = `grid-cell ${className}`; + cell.textContent = value; + } +} + +document.addEventListener('DOMContentLoaded', init); diff --git a/001game/index.html b/001game/index.html new file mode 100644 index 0000000000..4dc8c74bba --- /dev/null +++ b/001game/index.html @@ -0,0 +1,92 @@ + + + + + + 算法可视化挑战 + + + +
+
+

🎮 算法可视化挑战

+
+ 得分: 0 + 关卡: 1 +
+
+ +
+
+
+

欢迎来到算法世界!

+

通过可视化学习各种经典算法,挑战你的算法思维!

+
+
+
+
+ +
+ +

+
+
+
+ +
+
+ +

+
+ + + +
+
+
+

+
+ 时间复杂度: + 空间复杂度: +
+
+
+
+ +
+ +
+
+

+
+

+
+

完成时间:

+

获得分数:

+
+
+ + + +
+
+
+
+
+ + + + + + + + + + + diff --git a/001game/styles.css b/001game/styles.css new file mode 100644 index 0000000000..80059844a0 --- /dev/null +++ b/001game/styles.css @@ -0,0 +1,884 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #6366f1; + --primary-dark: #4f46e5; + --secondary-color: #22d3ee; + --accent-color: #f472b6; + --success-color: #22c55e; + --warning-color: #f59e0b; + --danger-color: #ef4444; + --bg-dark: #0f172a; + --bg-card: #1e293b; + --bg-lighter: #334155; + --text-primary: #f8fafc; + --text-secondary: #94a3b8; + --border-color: #475569; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, var(--bg-dark) 0%, #1a1a2e 100%); + color: var(--text-primary); + min-height: 100vh; + overflow-x: hidden; +} + +#app { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 30px; + background: var(--bg-card); + border-radius: 16px; + margin-bottom: 30px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +.header h1 { + font-size: 1.8rem; + background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.stats { + display: flex; + gap: 30px; +} + +.stats span { + font-size: 1.1rem; + color: var(--text-secondary); +} + +.stats span span { + color: var(--secondary-color); + font-weight: bold; +} + +.screen { + display: none; + animation: fadeIn 0.3s ease; +} + +.screen.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(20px); } + to { opacity: 1; transform: translateY(0); } +} + +.welcome { + text-align: center; + padding: 40px; + background: var(--bg-card); + border-radius: 16px; + margin-bottom: 30px; +} + +.welcome h2 { + font-size: 2rem; + margin-bottom: 15px; + color: var(--text-primary); +} + +.welcome p { + color: var(--text-secondary); + font-size: 1.1rem; +} + +.category-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 20px; +} + +.category-card { + background: var(--bg-card); + border-radius: 16px; + padding: 25px; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid transparent; + position: relative; + overflow: hidden; +} + +.category-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 4px; + background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); + transform: scaleX(0); + transition: transform 0.3s ease; +} + +.category-card:hover { + transform: translateY(-5px); + border-color: var(--primary-color); + box-shadow: 0 10px 30px rgba(99, 102, 241, 0.2); +} + +.category-card:hover::before { + transform: scaleX(1); +} + +.category-icon { + font-size: 3rem; + margin-bottom: 15px; +} + +.category-card h3 { + font-size: 1.3rem; + margin-bottom: 10px; + color: var(--text-primary); +} + +.category-card p { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 15px; +} + +.category-progress { + height: 6px; + background: var(--bg-lighter); + border-radius: 3px; + overflow: hidden; +} + +.category-progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--primary-color), var(--secondary-color)); + transition: width 0.5s ease; +} + +.level-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 15px; + margin-top: 20px; +} + +.level-card { + background: var(--bg-card); + border-radius: 12px; + padding: 20px; + cursor: pointer; + transition: all 0.3s ease; + border: 2px solid transparent; + text-align: center; +} + +.level-card:hover { + border-color: var(--primary-color); + transform: scale(1.02); +} + +.level-card.completed { + border-color: var(--success-color); +} + +.level-card.locked { + opacity: 0.5; + cursor: not-allowed; +} + +.level-number { + font-size: 2rem; + font-weight: bold; + color: var(--primary-color); + margin-bottom: 10px; +} + +.level-card.completed .level-number { + color: var(--success-color); +} + +.level-name { + font-size: 1rem; + color: var(--text-primary); + margin-bottom: 5px; +} + +.level-difficulty { + font-size: 0.8rem; + color: var(--text-secondary); +} + +.back-btn { + background: transparent; + border: 2px solid var(--border-color); + color: var(--text-primary); + padding: 10px 20px; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + font-size: 1rem; +} + +.back-btn:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +.game-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + background: var(--bg-card); + border-radius: 12px; + margin-bottom: 20px; + flex-wrap: wrap; + gap: 15px; +} + +.game-header h2 { + color: var(--text-primary); +} + +.game-controls { + display: flex; + gap: 10px; +} + +.game-controls button { + padding: 10px 20px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s ease; +} + +#start-btn { + background: var(--success-color); + color: white; +} + +#start-btn:hover { + background: #16a34a; +} + +#reset-btn { + background: var(--warning-color); + color: white; +} + +#reset-btn:hover { + background: #d97706; +} + +#speed-btn { + background: var(--primary-color); + color: white; +} + +#speed-btn:hover { + background: var(--primary-dark); +} + +.game-info { + background: var(--bg-card); + padding: 20px; + border-radius: 12px; + margin-bottom: 20px; +} + +.game-info p { + color: var(--text-secondary); + margin-bottom: 10px; + line-height: 1.6; +} + +.complexity { + display: flex; + gap: 30px; + color: var(--text-secondary); +} + +.complexity span { + background: var(--bg-lighter); + padding: 8px 15px; + border-radius: 6px; +} + +.complexity span span { + color: var(--secondary-color); + font-weight: bold; +} + +.visualization-area { + background: var(--bg-card); + border-radius: 12px; + padding: 30px; + min-height: 400px; + display: flex; + align-items: flex-end; + justify-content: center; + gap: 4px; + flex-wrap: wrap; + align-content: flex-end; +} + +.bar { + background: linear-gradient(180deg, var(--primary-color), var(--primary-dark)); + border-radius: 4px 4px 0 0; + transition: height 0.1s ease, background 0.1s ease; + position: relative; +} + +.bar.comparing { + background: linear-gradient(180deg, var(--warning-color), #d97706); +} + +.bar.swapping { + background: linear-gradient(180deg, var(--danger-color), #dc2626); +} + +.bar.sorted { + background: linear-gradient(180deg, var(--success-color), #16a34a); +} + +.bar.current { + background: linear-gradient(180deg, var(--secondary-color), #06b6d4); +} + +.bar-value { + position: absolute; + top: -25px; + left: 50%; + transform: translateX(-50%); + font-size: 0.8rem; + color: var(--text-secondary); +} + +.game-footer { + margin-top: 20px; +} + +.step-info { + background: var(--bg-card); + padding: 15px 20px; + border-radius: 12px; + color: var(--text-secondary); + text-align: center; + font-size: 1rem; +} + +.challenge-area { + background: var(--bg-card); + padding: 25px; + border-radius: 12px; + margin-top: 20px; + text-align: center; +} + +.challenge-area.hidden { + display: none; +} + +.challenge-area p { + font-size: 1.1rem; + margin-bottom: 20px; + color: var(--text-primary); +} + +.challenge-options { + display: flex; + justify-content: center; + gap: 15px; + flex-wrap: wrap; +} + +.challenge-options button { + padding: 12px 25px; + background: var(--bg-lighter); + border: 2px solid var(--border-color); + color: var(--text-primary); + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s ease; +} + +.challenge-options button:hover { + border-color: var(--primary-color); + background: var(--primary-color); +} + +.challenge-options button.correct { + background: var(--success-color); + border-color: var(--success-color); +} + +.challenge-options button.wrong { + background: var(--danger-color); + border-color: var(--danger-color); +} + +.result-content { + background: var(--bg-card); + padding: 40px; + border-radius: 16px; + text-align: center; + max-width: 500px; + margin: 50px auto; +} + +.result-content h2 { + font-size: 2rem; + margin-bottom: 20px; + color: var(--text-primary); +} + +.stars { + font-size: 3rem; + margin-bottom: 20px; +} + +.star { + color: var(--bg-lighter); + margin: 0 5px; + transition: all 0.3s ease; +} + +.star.filled { + color: var(--warning-color); + text-shadow: 0 0 20px rgba(245, 158, 11, 0.5); +} + +.result-stats { + margin: 20px 0; + color: var(--text-secondary); +} + +.result-stats p { + margin: 10px 0; +} + +.result-buttons { + display: flex; + justify-content: center; + gap: 15px; + margin-top: 25px; + flex-wrap: wrap; +} + +.result-buttons button { + padding: 12px 25px; + border: none; + border-radius: 8px; + cursor: pointer; + font-size: 1rem; + transition: all 0.3s ease; + background: var(--primary-color); + color: white; +} + +.result-buttons button:hover { + background: var(--primary-dark); + transform: translateY(-2px); +} + +.array-container { + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + flex-wrap: wrap; +} + +.array-item { + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-lighter); + border-radius: 8px; + font-size: 1.2rem; + font-weight: bold; + color: var(--text-primary); + transition: all 0.3s ease; +} + +.array-item.highlight { + background: var(--primary-color); + transform: scale(1.1); +} + +.array-item.found { + background: var(--success-color); +} + +.array-item.searching { + background: var(--warning-color); +} + +.grid-container { + display: grid; + gap: 2px; + justify-content: center; +} + +.grid-cell { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-lighter); + border-radius: 4px; + font-weight: bold; + color: var(--text-primary); + transition: all 0.2s ease; +} + +.grid-cell.visited { + background: var(--primary-color); +} + +.grid-cell.path { + background: var(--success-color); +} + +.grid-cell.current { + background: var(--warning-color); +} + +.grid-cell.wall { + background: var(--danger-color); +} + +.grid-cell.start { + background: var(--secondary-color); +} + +.grid-cell.end { + background: var(--accent-color); +} + +.tree-container { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; +} + +.tree-node { + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + background: var(--primary-color); + border-radius: 50%; + font-weight: bold; + color: white; + margin: 5px; + transition: all 0.3s ease; +} + +.tree-node.highlight { + background: var(--warning-color); + transform: scale(1.2); +} + +.tree-level { + display: flex; + justify-content: center; + gap: 20px; + margin-bottom: 20px; +} + +.fibonacci-container { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: center; + padding: 20px; +} + +.fib-box { + padding: 15px 20px; + background: var(--bg-lighter); + border-radius: 8px; + text-align: center; + min-width: 80px; + transition: all 0.3s ease; +} + +.fib-box.active { + background: var(--primary-color); + transform: scale(1.1); +} + +.fib-box .index { + font-size: 0.8rem; + color: var(--text-secondary); +} + +.fib-box .value { + font-size: 1.5rem; + font-weight: bold; + color: var(--text-primary); +} + +.code-display { + background: #0d1117; + border-radius: 12px; + padding: 20px; + margin-top: 20px; + overflow-x: auto; +} + +.code-display pre { + color: #c9d1d9; + font-family: 'Consolas', 'Monaco', monospace; + font-size: 0.9rem; + line-height: 1.6; +} + +.code-line { + padding: 2px 10px; + border-left: 3px solid transparent; +} + +.code-line.highlight { + background: rgba(99, 102, 241, 0.2); + border-left-color: var(--primary-color); +} + +.graph-container { + position: relative; + width: 100%; + min-height: 400px; +} + +.graph-node { + position: absolute; + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + background: var(--primary-color); + border-radius: 50%; + font-weight: bold; + color: white; + transition: all 0.3s ease; + z-index: 2; +} + +.graph-node.visited { + background: var(--success-color); +} + +.graph-node.current { + background: var(--warning-color); +} + +.sudoku-grid { + display: grid; + grid-template-columns: repeat(9, 1fr); + gap: 2px; + background: var(--border-color); + padding: 4px; + border-radius: 8px; + max-width: 450px; + margin: 0 auto; +} + +.sudoku-cell { + width: 45px; + height: 45px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-lighter); + font-size: 1.2rem; + font-weight: bold; + color: var(--text-primary); + transition: all 0.2s ease; +} + +.sudoku-cell:nth-child(3n) { + border-right: 2px solid var(--border-color); +} + +.sudoku-cell:nth-child(n+19):nth-child(-n+27), +.sudoku-cell:nth-child(n+46):nth-child(-n+54) { + border-bottom: 2px solid var(--border-color); +} + +.sudoku-cell.given { + color: var(--text-secondary); +} + +.sudoku-cell.solving { + background: var(--primary-color); +} + +.sudoku-cell.solved { + background: var(--success-color); +} + +.nqueens-board { + display: grid; + gap: 2px; + background: var(--border-color); + padding: 4px; + border-radius: 8px; + max-width: 400px; + margin: 0 auto; +} + +.queen-cell { + width: 45px; + height: 45px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.8rem; + transition: all 0.2s ease; +} + +.queen-cell.light { + background: var(--bg-lighter); +} + +.queen-cell.dark { + background: var(--bg-card); +} + +.queen-cell.has-queen { + background: var(--warning-color); +} + +.queen-cell.conflict { + background: var(--danger-color); +} + +.maze-grid { + display: grid; + gap: 1px; + background: var(--border-color); + padding: 2px; + border-radius: 8px; + max-width: 500px; + margin: 0 auto; +} + +.maze-cell { + width: 25px; + height: 25px; + background: var(--bg-lighter); + transition: all 0.1s ease; +} + +.maze-cell.wall { + background: var(--bg-dark); +} + +.maze-cell.path { + background: var(--success-color); +} + +.maze-cell.current { + background: var(--warning-color); +} + +.maze-cell.start { + background: var(--secondary-color); +} + +.maze-cell.end { + background: var(--accent-color); +} + +@media (max-width: 768px) { + .header { + flex-direction: column; + gap: 15px; + text-align: center; + } + + .stats { + gap: 20px; + } + + .game-header { + flex-direction: column; + text-align: center; + } + + .game-controls { + width: 100%; + justify-content: center; + } + + .category-grid { + grid-template-columns: 1fr; + } + + .level-grid { + grid-template-columns: repeat(2, 1fr); + } + + .bar { + min-width: 15px; + } + + .sudoku-cell, + .queen-cell { + width: 35px; + height: 35px; + font-size: 1rem; + } + + .maze-cell { + width: 20px; + height: 20px; + } +} + +@media (max-width: 480px) { + .level-grid { + grid-template-columns: 1fr; + } + + .challenge-options { + flex-direction: column; + } + + .challenge-options button { + width: 100%; + } + + .result-buttons { + flex-direction: column; + } + + .result-buttons button { + width: 100%; + } +}