diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 6f68680..0105a2c 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -8,6 +8,7 @@
"name": "simpleasm",
"version": "1.0.0",
"dependencies": {
+ "@modyfi/vite-plugin-yaml": "^1.1.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
@@ -66,7 +67,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"aix"
@@ -82,7 +82,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -98,7 +97,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -114,7 +112,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -130,7 +127,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -146,7 +142,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -162,7 +157,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -178,7 +172,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -194,7 +187,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -210,7 +202,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -226,7 +217,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -242,7 +232,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -258,7 +247,6 @@
"cpu": [
"mips64el"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -274,7 +262,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -290,7 +277,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -306,7 +292,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -322,7 +307,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -338,7 +322,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"netbsd"
@@ -354,7 +337,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -370,7 +352,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"sunos"
@@ -386,7 +367,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -402,7 +382,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -418,7 +397,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -432,6 +410,40 @@
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
},
+ "node_modules/@modyfi/vite-plugin-yaml": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@modyfi/vite-plugin-yaml/-/vite-plugin-yaml-1.1.1.tgz",
+ "integrity": "sha512-rEbfFNlMGLKpAYs2RsfLAhxCHFa6M4QKHHk0A4EYcCJAUwFtFO6qiEdLjUGUTtnRUxAC7GxxCa+ZbeUILSDvqQ==",
+ "dependencies": {
+ "@rollup/pluginutils": "5.1.0",
+ "js-yaml": "4.1.0",
+ "tosource": "2.0.0-alpha.3"
+ },
+ "peerDependencies": {
+ "vite": ">=3.2.7"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
+ "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
@@ -439,7 +451,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -452,7 +463,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"android"
@@ -465,7 +475,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -478,7 +487,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"darwin"
@@ -491,7 +499,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -504,7 +511,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"freebsd"
@@ -517,7 +523,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -530,7 +535,6 @@
"cpu": [
"arm"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -543,7 +547,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -556,7 +559,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -569,7 +571,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -582,7 +583,6 @@
"cpu": [
"loong64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -595,7 +595,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -608,7 +607,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -621,7 +619,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -634,7 +631,6 @@
"cpu": [
"riscv64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -647,7 +643,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -660,7 +655,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -673,7 +667,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"linux"
@@ -686,7 +679,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"openbsd"
@@ -699,7 +691,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"openharmony"
@@ -712,7 +703,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -725,7 +715,6 @@
"cpu": [
"ia32"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -738,7 +727,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -751,7 +739,6 @@
"cpu": [
"x64"
],
- "dev": true,
"optional": true,
"os": [
"win32"
@@ -760,8 +747,7 @@
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
- "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
- "dev": true
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.4",
@@ -872,6 +858,11 @@
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz",
"integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg=="
},
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
@@ -892,7 +883,6 @@
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
- "dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
@@ -935,7 +925,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
@@ -945,6 +934,17 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -975,6 +975,17 @@
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
"node_modules/pinia": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
@@ -1027,7 +1038,6 @@
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
"integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
- "dev": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -1075,11 +1085,18 @@
"node": ">=0.10.0"
}
},
+ "node_modules/tosource": {
+ "version": "2.0.0-alpha.3",
+ "resolved": "https://registry.npmjs.org/tosource/-/tosource-2.0.0-alpha.3.tgz",
+ "integrity": "sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/vite": {
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
- "dev": true,
"dependencies": {
"esbuild": "^0.21.3",
"postcss": "^8.4.43",
diff --git a/frontend/package.json b/frontend/package.json
index 707e91a..dc09618 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -8,6 +8,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@modyfi/vite-plugin-yaml": "^1.1.1",
"pinia": "^2.1.7",
"vue": "^3.4.21",
"vue-router": "^4.3.0"
diff --git a/frontend/src/components/LevelComplete.vue b/frontend/src/components/LevelComplete.vue
index d5f18a8..f31bd1b 100644
--- a/frontend/src/components/LevelComplete.vue
+++ b/frontend/src/components/LevelComplete.vue
@@ -5,7 +5,7 @@
🎉
恭喜通关!
-
{{ level.icon }} {{ level.title }}
+
{{ level.title }}
10\ndone:\nHLT',
- },
- ],
- goal: 'R0=**15**。如果 R0 > 10 则 R1 = **1**;否则 R1 = **0**',
- initialState: { registers: { R0: 15 } },
- testCases: [
- { init: { registers: { R0: 15 } }, expected: { registers: { R1: 1 } } },
- { init: { registers: { R0: 5 } }, expected: { registers: { R1: 0 } } },
- { init: { registers: { R0: 10 } }, expected: { registers: { R1: 0 } } },
- ],
- hints: [
- '先设 R1=#0(默认),再比较 R0 和 10',
- '如果 R0 > 10,跳到标签把 R1 改成 1',
- '答案:MOV R1, #0 / CMP R0, #10 / BLE done / MOV R1, #1 / done: HLT',
- ],
- starThresholds: [5, 7, 9],
- starterCode: '; 如果 R0 > 10,则 R1 = 1\n; 否则 R1 = 0\n\n\nHLT',
- showMemory: false,
- },
-
- // ===== Level 9: 循环 =====
- {
- id: 9,
- title: '循环',
- subtitle: '重复的力量',
- icon: '🔄',
- description: '用分支指令创建循环',
- tutorial: [
- {
- title: '什么是循环?',
- text: '循环让一段代码**反复执行**。在汇编中,循环就是**跳回前面的标签**!',
- },
- {
- title: '循环结构',
- text: '①初始化 ②做事 ③更新计数器 ④判断+跳回:',
- code: 'MOV R4, #0 ; ① 初始化\nloop: ; 循环开始\n ADD R4, R4, #1 ; ②③ 计数+1\n CMP R4, #5 ; ④ 到5了吗?\n BLE loop ; 没到就跳回\nHLT',
- },
- {
- title: '注意!',
- text: '忘了更新计数器 = **死循环**(别担心,超过10000步会自动停止)。',
- },
- ],
- goal: '计算 **1+2+3+...+10** 的和存入 **R0**(答案是55)',
- initialState: {},
- testCases: [{ init: {}, expected: { registers: { R0: 55 } } }],
- hints: [
- 'R0 累加结果,R4 做计数器(1到10)',
- '循环体:ADD R0, R0, R4 / ADD R4, R4, #1 / CMP R4, #10 / BLE loop',
- '完整:MOV R0, #0 / MOV R4, #1 / loop: ADD R0, R0, R4 / ADD R4, R4, #1 / CMP R4, #10 / BLE loop / HLT',
- ],
- starThresholds: [7, 9, 12],
- starterCode: '; 计算 1+2+3+...+10\n; 结果存入 R0\n;\n; 提示:用一个寄存器做计数器\n\n\nHLT',
- showMemory: false,
- },
-
- // ===== Level 10: 终极挑战 =====
- {
- id: 10,
- title: '终极挑战',
- subtitle: '寻找最大值',
- icon: '🏆',
- description: '综合运用所有技能!',
- tutorial: [
- {
- title: '最后一关!',
- text: '你已经学会了寄存器、算术、位运算、内存、分支和循环。现在把**所有技能**结合起来!',
- },
- {
- title: '挑战说明',
- text: '内存地址 0-4 存了5个数字。你要找到**最大值**和它的**位置**。需要:循环 + 内存读取 + 比较分支。',
- },
- {
- title: '解题思路',
- text: '1. 假设第一个数最大(R0=内存[0],R1=位置0)\n2. 循环检查剩余的数\n3. 如果发现更大的,更新最大值和位置\n4. 直到检查完全部5个数',
- code: '; 伪代码:\n; R0 = max = mem[0]\n; R1 = maxIdx = 0\n; for R4 = 1 to 4:\n; R5 = mem[R4]\n; if R5 > R0: R0=R5, R1=R4',
- },
- ],
- goal: '内存[0..4] 有5个数,找出**最大值**存入 **R0**,其**位置**存入 **R1**',
- initialState: { memory: { 0: 5, 1: 3, 2: 8, 3: 1, 4: 7 } },
- testCases: [
- {
- init: { memory: { 0: 5, 1: 3, 2: 8, 3: 1, 4: 7 } },
- expected: { registers: { R0: 8, R1: 2 } },
- },
- {
- init: { memory: { 0: 1, 1: 9, 2: 4, 3: 9, 4: 2 } },
- expected: { registers: { R0: 9, R1: 1 } },
- },
- ],
- hints: [
- 'R0=最大值, R1=位置, R4=循环计数器, R5=当前值, R3=基地址',
- '用 LDR R5, [R3, R4] 不行的话,可以用 R3 当地址:MOV R3, R4 / LDR R5, [R3]',
- '循环体:把 R4 当地址读内存 → CMP R5, R0 → BLE skip → 更新 R0,R1 → skip: ADD R4, R4, #1 → CMP R4, #5 → BLT loop',
- ],
- starThresholds: [12, 15, 20],
- starterCode: '; 内存[0..4] = [5, 3, 8, 1, 7]\n; 找最大值存入 R0,位置存入 R1\n;\n; 提示:用 R4 做循环变量\n; 用 MOV + LDR 读取内存\n\n\nHLT',
- showMemory: true,
- memoryRange: [0, 15],
- },
-]
+export const levels = Object.values(modules)
+ .map(m => m.default)
+ .sort((a, b) => a.id - b.id)
diff --git a/frontend/src/lib/levels/01.yaml b/frontend/src/lib/levels/01.yaml
new file mode 100644
index 0000000..4056f29
--- /dev/null
+++ b/frontend/src/lib/levels/01.yaml
@@ -0,0 +1,48 @@
+id: 1
+title: 认识寄存器
+subtitle: 小机器人的记忆槽
+description: 学习 MOV 指令给寄存器赋值
+
+tutorial:
+ - title: 什么是寄存器?
+ text: >
+ CPU 是计算机的大脑,而**寄存器**是它手边的小抽屉 ——
+ 速度最快的存储空间!我们的机器有 8 个寄存器:**R0** 到 **R7**。
+ - title: MOV 指令
+ text: >
+ `MOV` 把一个数字放进寄存器。注意数字前面要加 **#** 号,表示"这是一个数值":
+ code: |
+ MOV R0, #42 ; 把 42 放进 R0
+ MOV R1, #100 ; 把 100 放进 R1
+ - title: HLT 指令
+ text: >
+ 程序最后要写 `HLT`(halt = 停止),告诉机器"运行结束!"
+ code: |
+ MOV R0, #42
+ HLT
+
+goal: 把数字 **42** 放进 **R0** 寄存器
+
+initialState: {}
+
+testCases:
+ - init: {}
+ expected:
+ registers:
+ R0: 42
+
+hints:
+ - "MOV 的格式:MOV 寄存器, #数字"
+ - "试试:MOV R0, #???"
+ - "答案:MOV R0, #42 然后 HLT"
+
+starThresholds: [2, 3, 5]
+
+starterCode: |
+ ; 把 42 放进 R0 寄存器
+ ; 提示:数字前面要加 # 号
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/02.yaml b/frontend/src/lib/levels/02.yaml
new file mode 100644
index 0000000..57f9d54
--- /dev/null
+++ b/frontend/src/lib/levels/02.yaml
@@ -0,0 +1,44 @@
+id: 2
+title: 数据搬运工
+subtitle: 寄存器之间的复制
+description: 学习在寄存器之间复制数据
+
+tutorial:
+ - title: 寄存器间复制
+ text: >
+ MOV 也能把一个寄存器的值**复制**到另一个(这时不需要 # 号):
+ code: |
+ MOV R1, R0 ; 把 R0 的值复制到 R1
+ - title: 复制,不是移动!
+ text: >
+ 虽然叫 "MOV"(移动),但其实是**复制**。执行后 R0 的值不变,R1 变成和 R0 一样。
+
+goal: R0 已经有值 **7**,把它复制到 **R1** 和 **R2**
+
+initialState:
+ registers:
+ R0: 7
+
+testCases:
+ - init: {}
+ expected:
+ registers:
+ R0: 7
+ R1: 7
+ R2: 7
+
+hints:
+ - "MOV 寄存器, 寄存器 —— 把右边复制到左边"
+ - "MOV R1, R0 可以把 R0 复制到 R1"
+ - "答案:MOV R1, R0 / MOV R2, R0 / HLT"
+
+starThresholds: [3, 4, 6]
+
+starterCode: |
+ ; R0 = 7
+ ; 把 R0 复制到 R1 和 R2
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/03.yaml b/frontend/src/lib/levels/03.yaml
new file mode 100644
index 0000000..8f4d9ee
--- /dev/null
+++ b/frontend/src/lib/levels/03.yaml
@@ -0,0 +1,52 @@
+id: 3
+title: 加减法
+subtitle: 三操作数的威力
+description: 学习 ADD 和 SUB 指令
+
+tutorial:
+ - title: ADD —— 加法(三操作数)
+ text: >
+ ARM 风格的加法很酷:**三个操作数**!第一个放结果,后两个是被运算的值:
+ code: |
+ ADD R2, R0, R1 ; R2 = R0 + R1
+ ADD R0, R0, #10 ; R0 = R0 + 10
+ - title: SUB —— 减法
+ text: >
+ SUB 同理,也是三操作数:
+ code: |
+ SUB R2, R0, R1 ; R2 = R0 - R1
+ SUB R0, R0, #5 ; R0 = R0 - 5
+ - title: 好处
+ text: >
+ 三操作数的好处:可以直接把结果放到新的寄存器,**不用先复制**!
+
+goal: R0=**15**,R1=**27**,计算 R0+R1 存入 **R2**(R0和R1不变)
+
+initialState:
+ registers:
+ R0: 15
+ R1: 27
+
+testCases:
+ - init: {}
+ expected:
+ registers:
+ R0: 15
+ R1: 27
+ R2: 42
+
+hints:
+ - "ADD 第一个参数放结果,后两个参数相加"
+ - "ADD R2, R0, R1 —— 结果存入 R2"
+ - "答案:ADD R2, R0, R1 / HLT"
+
+starThresholds: [2, 3, 5]
+
+starterCode: |
+ ; R0=15, R1=27
+ ; 计算 R0 + R1,结果存入 R2
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/04.yaml b/frontend/src/lib/levels/04.yaml
new file mode 100644
index 0000000..33e4268
--- /dev/null
+++ b/frontend/src/lib/levels/04.yaml
@@ -0,0 +1,47 @@
+id: 4
+title: 乘法与除法
+subtitle: 更强的算术能力
+description: 学习 MUL 和 DIV 指令
+
+tutorial:
+ - title: MUL —— 乘法
+ text: >
+ MUL 也是三操作数:
+ code: |
+ MOV R0, #6
+ MOV R1, #7
+ MUL R2, R0, R1 ; R2 = 6 × 7 = 42
+ - title: DIV —— 除法(取整)
+ text: >
+ DIV 做整数除法(只留整数部分):
+ code: |
+ MOV R0, #100
+ MOV R1, #4
+ DIV R2, R0, R1 ; R2 = 100 ÷ 4 = 25
+
+goal: 计算 **6 × 7** 存入 R0,**100 ÷ 4** 存入 R1
+
+initialState: {}
+
+testCases:
+ - init: {}
+ expected:
+ registers:
+ R0: 42
+ R1: 25
+
+hints:
+ - "先 MOV 数字到寄存器,再 MUL/DIV"
+ - "MUL R0, R2, R3 可以把 R2×R3 的结果放到 R0"
+ - "答案:MOV R2, #6 / MOV R3, #7 / MUL R0, R2, R3 / MOV R2, #100 / MOV R3, #4 / DIV R1, R2, R3 / HLT"
+
+starThresholds: [7, 9, 12]
+
+starterCode: |
+ ; 计算 6×7 存入 R0
+ ; 计算 100÷4 存入 R1
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/05.yaml b/frontend/src/lib/levels/05.yaml
new file mode 100644
index 0000000..7eba1b6
--- /dev/null
+++ b/frontend/src/lib/levels/05.yaml
@@ -0,0 +1,57 @@
+id: 5
+title: 位运算魔法
+subtitle: 0和1的秘密
+description: 学习 AND、ORR、EOR、MVN 指令
+
+tutorial:
+ - title: 二进制世界
+ text: >
+ 计算机内部用 **0** 和 **1** 存储一切。42 的二进制是 `00101010`。
+ 右边面板会显示每个寄存器的二进制值!
+ - title: AND —— 都是1才是1
+ text: >
+ AND 逐位比较,两个都是 1 结果才是 1。可以用来"提取"某些位:
+ code: |
+ ; 11111111 (255)
+ ; AND 00001111 (15)
+ ; = 00001111 (15)
+ AND R0, R0, #15
+ - title: 其他位运算
+ text: >
+ **ORR** = 有一个1就是1 (OR)
+
+ **EOR** = 不同才是1 (XOR)
+
+ **MVN** = 全部翻转 (NOT)
+ code: |
+ ORR R0, R0, #240 ; 设置高4位
+ EOR R0, R0, #255 ; 翻转低8位
+ MVN R0, R0 ; 翻转所有位
+
+goal: R0 = **255** (二进制 11111111),用 AND 提取**低4位**,使 R0 变成 **15**
+
+initialState:
+ registers:
+ R0: 255
+
+testCases:
+ - init: {}
+ expected:
+ registers:
+ R0: 15
+
+hints:
+ - "AND 用来保留某些位,把其他位清零"
+ - "低4位的掩码是 15(二进制 00001111)"
+ - "答案:AND R0, R0, #15 / HLT"
+
+starThresholds: [2, 3, 5]
+
+starterCode: |
+ ; R0 = 255 (二进制 11111111)
+ ; 用 AND 提取低4位
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/06.yaml b/frontend/src/lib/levels/06.yaml
new file mode 100644
index 0000000..e7e8079
--- /dev/null
+++ b/frontend/src/lib/levels/06.yaml
@@ -0,0 +1,53 @@
+id: 6
+title: 移位操作
+subtitle: 位的舞蹈
+description: 学习 LSL 和 LSR 指令
+
+tutorial:
+ - title: LSL —— 逻辑左移
+ text: >
+ 所有位向左移,右边补0。**左移1位 = 乘以2**,左移3位 = 乘以8:
+ code: |
+ ; 5 = 00000101
+ LSL R0, R0, #1 ; 00001010 = 10 (×2)
+ LSL R0, R0, #1 ; 00010100 = 20 (×2)
+ - title: LSR —— 逻辑右移
+ text: >
+ 所有位向右移,左边补0。**右移1位 = 除以2**:
+ code: |
+ MOV R0, #40
+ LSR R0, R0, #1 ; 20 (÷2)
+ LSR R0, R0, #2 ; 5 (÷4)
+ - title: 程序员的技巧
+ text: >
+ 在真实的 ARM 处理器中,移位比乘除快得多!
+ `LSL R0, R0, #3` 比 `MUL R0, R0, #8` 高效。
+
+goal: R0 = **5**,只用**移位操作**把它变成 **40**(40 = 5 × 8 = 5 × 2³)
+
+initialState:
+ registers:
+ R0: 5
+
+testCases:
+ - init: {}
+ expected:
+ registers:
+ R0: 40
+
+hints:
+ - "8 = 2³,乘以8就是左移3位"
+ - "LSL R0, R0, #3"
+ - "就这一条指令!"
+
+starThresholds: [2, 3, 5]
+blockedOps: [MUL, DIV]
+
+starterCode: |
+ ; R0 = 5
+ ; 用 LSL 让 R0 变成 40(不能用 MUL)
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/07.yaml b/frontend/src/lib/levels/07.yaml
new file mode 100644
index 0000000..ae3970f
--- /dev/null
+++ b/frontend/src/lib/levels/07.yaml
@@ -0,0 +1,61 @@
+id: 7
+title: 内存读写
+subtitle: 打开更大的空间
+description: 学习 LDR 和 STR 指令
+
+tutorial:
+ - title: 什么是内存?
+ text: >
+ 寄存器只有8个,太少了!**内存**像一排256格的柜子,每格有编号(0-255)。
+ - title: LDR —— 从内存读取
+ text: >
+ 先把地址放进寄存器,再用 `LDR` 从那个地址读数据:
+ code: |
+ MOV R1, #0 ; 地址 = 0
+ LDR R0, [R1] ; R0 = 内存[0]
+ - title: STR —— 写入内存
+ text: >
+ `STR` 把寄存器的值写到内存:
+ code: |
+ MOV R1, #5 ; 地址 = 5
+ STR R0, [R1] ; 内存[5] = R0
+ - title: 偏移寻址
+ text: >
+ 还可以加偏移量:`[R1, #4]` 表示地址 R1+4:
+ code: |
+ MOV R1, #0
+ LDR R0, [R1, #0] ; 内存[0]
+ LDR R2, [R1, #1] ; 内存[1]
+
+goal: 内存[0]=**10**,内存[1]=**20**,计算它们的和存入 **内存[2]**
+
+initialState:
+ memory:
+ 0: 10
+ 1: 20
+
+testCases:
+ - init: {}
+ expected:
+ memory:
+ 2: 30
+
+hints:
+ - "先用 LDR 把内存值读到寄存器,算完用 STR 写回"
+ - "MOV R3, #0 设基地址,LDR R0, [R3, #0] 读第一个值"
+ - "答案:MOV R3, #0 / LDR R0, [R3, #0] / LDR R1, [R3, #1] / ADD R2, R0, R1 / STR R2, [R3, #2] / HLT"
+
+starThresholds: [6, 8, 10]
+
+starterCode: |
+ ; 内存[0]=10, 内存[1]=20
+ ; 计算它们的和,存入内存[2]
+ ;
+ ; 提示:先 MOV 一个地址到寄存器
+ ; 然后用 LDR/STR 读写内存
+
+
+ HLT
+
+showMemory: true
+memoryRange: [0, 15]
diff --git a/frontend/src/lib/levels/08.yaml b/frontend/src/lib/levels/08.yaml
new file mode 100644
index 0000000..bc968dd
--- /dev/null
+++ b/frontend/src/lib/levels/08.yaml
@@ -0,0 +1,77 @@
+id: 8
+title: 比较与跳转
+subtitle: 让程序会做决定
+description: 学习 CMP 和条件分支指令
+
+tutorial:
+ - title: 到目前为止...
+ text: >
+ 程序都是从头到尾顺序执行。但有了**分支**,程序就能做决定了!
+ - title: CMP —— 比较
+ text: >
+ `CMP` 比较两个值,记住比较结果(不会改变它们的值):
+ code: |
+ CMP R0, #10 ; 比较 R0 和 10
+ - title: 条件分支
+ text: >
+ 比较后用 **B** (Branch=分支) 跳转:
+ code: |
+ BEQ label ; 等于则跳(Equal)
+ BNE label ; 不等则跳(Not Equal)
+ BGT label ; 大于则跳(Greater Than)
+ BLT label ; 小于则跳(Less Than)
+ B label ; 无条件跳
+ - title: 标签
+ text: >
+ **标签**是代码里的记号,分支指令跳到标签位置。标签后面加冒号:
+ code: |
+ CMP R0, #10
+ BGT big
+ MOV R1, #0 ; R0 <= 10
+ B done ; 跳过下面
+ big:
+ MOV R1, #1 ; R0 > 10
+ done:
+ HLT
+
+goal: R0=**15**。如果 R0 > 10 则 R1 = **1**;否则 R1 = **0**
+
+initialState:
+ registers:
+ R0: 15
+
+testCases:
+ - init:
+ registers:
+ R0: 15
+ expected:
+ registers:
+ R1: 1
+ - init:
+ registers:
+ R0: 5
+ expected:
+ registers:
+ R1: 0
+ - init:
+ registers:
+ R0: 10
+ expected:
+ registers:
+ R1: 0
+
+hints:
+ - "先设 R1=#0(默认),再比较 R0 和 10"
+ - "如果 R0 > 10,跳到标签把 R1 改成 1"
+ - "答案:MOV R1, #0 / CMP R0, #10 / BLE done / MOV R1, #1 / done: HLT"
+
+starThresholds: [5, 7, 9]
+
+starterCode: |
+ ; 如果 R0 > 10,则 R1 = 1
+ ; 否则 R1 = 0
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/09.yaml b/frontend/src/lib/levels/09.yaml
new file mode 100644
index 0000000..26ee9d0
--- /dev/null
+++ b/frontend/src/lib/levels/09.yaml
@@ -0,0 +1,50 @@
+id: 9
+title: 循环
+subtitle: 重复的力量
+description: 用分支指令创建循环
+
+tutorial:
+ - title: 什么是循环?
+ text: >
+ 循环让一段代码**反复执行**。在汇编中,循环就是**跳回前面的标签**!
+ - title: 循环结构
+ text: >
+ ①初始化 ②做事 ③更新计数器 ④判断+跳回:
+ code: |
+ MOV R4, #0 ; ① 初始化
+ loop: ; 循环开始
+ ADD R4, R4, #1 ; ②③ 计数+1
+ CMP R4, #5 ; ④ 到5了吗?
+ BLE loop ; 没到就跳回
+ HLT
+ - title: 注意!
+ text: >
+ 忘了更新计数器 = **死循环**(别担心,超过10000步会自动停止)。
+
+goal: 计算 **1+2+3+...+10** 的和存入 **R0**(答案是55)
+
+initialState: {}
+
+testCases:
+ - init: {}
+ expected:
+ registers:
+ R0: 55
+
+hints:
+ - "R0 累加结果,R4 做计数器(1到10)"
+ - "循环体:ADD R0, R0, R4 / ADD R4, R4, #1 / CMP R4, #10 / BLE loop"
+ - "完整:MOV R0, #0 / MOV R4, #1 / loop: ADD R0, R0, R4 / ADD R4, R4, #1 / CMP R4, #10 / BLE loop / HLT"
+
+starThresholds: [7, 9, 12]
+
+starterCode: |
+ ; 计算 1+2+3+...+10
+ ; 结果存入 R0
+ ;
+ ; 提示:用一个寄存器做计数器
+
+
+ HLT
+
+showMemory: false
diff --git a/frontend/src/lib/levels/10.yaml b/frontend/src/lib/levels/10.yaml
new file mode 100644
index 0000000..74fea42
--- /dev/null
+++ b/frontend/src/lib/levels/10.yaml
@@ -0,0 +1,81 @@
+id: 10
+title: 终极挑战
+subtitle: 寻找最大值
+description: 综合运用所有技能!
+
+tutorial:
+ - title: 最后一关!
+ text: >
+ 你已经学会了寄存器、算术、位运算、内存、分支和循环。
+ 现在把**所有技能**结合起来!
+ - title: 挑战说明
+ text: >
+ 内存地址 0-4 存了5个数字。你要找到**最大值**和它的**位置**。
+ 需要:循环 + 内存读取 + 比较分支。
+ - title: 解题思路
+ text: |
+ 1. 假设第一个数最大(R0=内存[0],R1=位置0)
+ 2. 循环检查剩余的数
+ 3. 如果发现更大的,更新最大值和位置
+ 4. 直到检查完全部5个数
+ code: |
+ ; 伪代码:
+ ; R0 = max = mem[0]
+ ; R1 = maxIdx = 0
+ ; for R4 = 1 to 4:
+ ; R5 = mem[R4]
+ ; if R5 > R0: R0=R5, R1=R4
+
+goal: 内存[0..4] 有5个数,找出**最大值**存入 **R0**,其**位置**存入 **R1**
+
+initialState:
+ memory:
+ 0: 5
+ 1: 3
+ 2: 8
+ 3: 1
+ 4: 7
+
+testCases:
+ - init:
+ memory:
+ 0: 5
+ 1: 3
+ 2: 8
+ 3: 1
+ 4: 7
+ expected:
+ registers:
+ R0: 8
+ R1: 2
+ - init:
+ memory:
+ 0: 1
+ 1: 9
+ 2: 4
+ 3: 9
+ 4: 2
+ expected:
+ registers:
+ R0: 9
+ R1: 1
+
+hints:
+ - "R0=最大值, R1=位置, R4=循环计数器, R5=当前值"
+ - "用 MOV R3, R4 / LDR R5, [R3] 来读取 mem[R4]"
+ - "循环体:MOV R3, R4 / LDR R5, [R3] / CMP R5, R0 / BLE skip / MOV R0, R5 / MOV R1, R4 / skip: ADD R4, R4, #1 / CMP R4, #5 / BLT loop"
+
+starThresholds: [12, 15, 20]
+
+starterCode: |
+ ; 内存[0..4] = [5, 3, 8, 1, 7]
+ ; 找最大值存入 R0,位置存入 R1
+ ;
+ ; 提示:用 R4 做循环变量
+ ; 用 MOV + LDR 读取内存
+
+
+ HLT
+
+showMemory: true
+memoryRange: [0, 15]
diff --git a/frontend/src/views/LevelSelectView.vue b/frontend/src/views/LevelSelectView.vue
index ca830cf..b5b5edd 100644
--- a/frontend/src/views/LevelSelectView.vue
+++ b/frontend/src/views/LevelSelectView.vue
@@ -20,7 +20,6 @@
@click="go(level)"
>
{{ level.id }}
- {{ level.icon }}
{{ level.title }}
{{ level.subtitle }}
{{ level.description }}
diff --git a/frontend/src/views/LevelView.vue b/frontend/src/views/LevelView.vue
index 857ef59..310a5bb 100644
--- a/frontend/src/views/LevelView.vue
+++ b/frontend/src/views/LevelView.vue
@@ -3,7 +3,7 @@