From 247cf85e573a42263041a9997adb1fe2082b1fff Mon Sep 17 00:00:00 2001 From: Fam Zheng Date: Tue, 7 Apr 2026 10:25:02 +0100 Subject: [PATCH] Refactor levels to YAML, remove icons, add vite-plugin-yaml --- frontend/package-lock.json | 125 ++++--- frontend/package.json | 1 + frontend/src/components/LevelComplete.vue | 2 +- frontend/src/lib/levels.js | 379 +--------------------- frontend/src/lib/levels/01.yaml | 48 +++ frontend/src/lib/levels/02.yaml | 44 +++ frontend/src/lib/levels/03.yaml | 52 +++ frontend/src/lib/levels/04.yaml | 47 +++ frontend/src/lib/levels/05.yaml | 57 ++++ frontend/src/lib/levels/06.yaml | 53 +++ frontend/src/lib/levels/07.yaml | 61 ++++ frontend/src/lib/levels/08.yaml | 77 +++++ frontend/src/lib/levels/09.yaml | 50 +++ frontend/src/lib/levels/10.yaml | 81 +++++ frontend/src/views/LevelSelectView.vue | 1 - frontend/src/views/LevelView.vue | 2 +- frontend/vite.config.js | 3 +- 17 files changed, 650 insertions(+), 433 deletions(-) create mode 100644 frontend/src/lib/levels/01.yaml create mode 100644 frontend/src/lib/levels/02.yaml create mode 100644 frontend/src/lib/levels/03.yaml create mode 100644 frontend/src/lib/levels/04.yaml create mode 100644 frontend/src/lib/levels/05.yaml create mode 100644 frontend/src/lib/levels/06.yaml create mode 100644 frontend/src/lib/levels/07.yaml create mode 100644 frontend/src/lib/levels/08.yaml create mode 100644 frontend/src/lib/levels/09.yaml create mode 100644 frontend/src/lib/levels/10.yaml 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 @@
← 返回
- {{ level.icon }} Level {{ level.id }} + Level {{ level.id }}

{{ level.title }}

diff --git a/frontend/vite.config.js b/frontend/vite.config.js index cbdec61..264012d 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,8 +1,9 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import yaml from '@modyfi/vite-plugin-yaml' export default defineConfig({ - plugins: [vue()], + plugins: [vue(), yaml()], server: { proxy: { '/api': 'http://localhost:8000'