Refactor levels to YAML, remove icons, add vite-plugin-yaml

This commit is contained in:
2026-04-07 10:25:02 +01:00
parent bab0bb0c6b
commit 247cf85e57
17 changed files with 650 additions and 433 deletions

View File

@@ -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",

View File

@@ -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"

View File

@@ -5,7 +5,7 @@
<div class="card">
<div class="badge">&#127881;</div>
<h2>恭喜通关</h2>
<p class="lname">{{ level.icon }} {{ level.title }}</p>
<p class="lname">{{ level.title }}</p>
<div class="stars-row">
<span v-for="s in 3" :key="s"

View File

@@ -1,376 +1,5 @@
export const levels = [
// ===== Level 1: 认识寄存器 =====
{
id: 1,
title: '认识寄存器',
subtitle: '小机器人的记忆槽',
icon: '🤖',
description: '学习 MOV 指令给寄存器赋值',
tutorial: [
{
title: '什么是寄存器?',
text: 'CPU 是计算机的大脑,而**寄存器**是它手边的小抽屉 —— 速度最快的存储空间!我们的机器有 8 个寄存器:**R0** 到 **R7**。',
},
{
title: 'MOV 指令',
text: '`MOV` 把一个数字放进寄存器。注意数字前面要加 **#** 号,表示"这是一个数值"',
code: 'MOV R0, #42 ; 把 42 放进 R0\nMOV R1, #100 ; 把 100 放进 R1',
},
{
title: 'HLT 指令',
text: '程序最后要写 `HLT`halt = 停止),告诉机器"运行结束!"',
code: 'MOV R0, #42\nHLT',
},
],
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 寄存器\n; 提示:数字前面要加 # 号\n\n\nHLT',
showMemory: false,
},
const modules = import.meta.glob('./levels/*.yaml', { eager: true })
// ===== Level 2: 数据搬运工 =====
{
id: 2,
title: '数据搬运工',
subtitle: '寄存器之间的复制',
icon: '📦',
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\n; 把 R0 复制到 R1 和 R2\n\n\nHLT',
showMemory: false,
},
// ===== Level 3: 加减法 =====
{
id: 3,
title: '加减法',
subtitle: '三操作数的威力',
icon: '',
description: '学习 ADD 和 SUB 指令',
tutorial: [
{
title: 'ADD —— 加法(三操作数)',
text: 'ARM 风格的加法很酷:**三个操作数**!第一个放结果,后两个是被运算的值:',
code: 'ADD R2, R0, R1 ; R2 = R0 + R1\nADD R0, R0, #10 ; R0 = R0 + 10',
},
{
title: 'SUB —— 减法',
text: 'SUB 同理,也是三操作数:',
code: 'SUB R2, R0, R1 ; R2 = R0 - R1\nSUB 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\n; 计算 R0 + R1结果存入 R2\n\n\nHLT',
showMemory: false,
},
// ===== Level 4: 乘法与除法 =====
{
id: 4,
title: '乘法与除法',
subtitle: '更强的算术能力',
icon: '✖️',
description: '学习 MUL 和 DIV 指令',
tutorial: [
{
title: 'MUL —— 乘法',
text: 'MUL 也是三操作数:',
code: 'MOV R0, #6\nMOV R1, #7\nMUL R2, R0, R1 ; R2 = 6 × 7 = 42',
},
{
title: 'DIV —— 除法(取整)',
text: 'DIV 做整数除法(只留整数部分):',
code: 'MOV R0, #100\nMOV R1, #4\nDIV 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\n; 计算 100÷4 存入 R1\n\n\nHLT',
showMemory: false,
},
// ===== Level 5: 位运算魔法 =====
{
id: 5,
title: '位运算魔法',
subtitle: '0和1的秘密',
icon: '🔮',
description: '学习 AND、ORR、EOR、MVN 指令',
tutorial: [
{
title: '二进制世界',
text: '计算机内部用 **0** 和 **1** 存储一切。42 的二进制是 `00101010`。右边面板会显示每个寄存器的二进制值!',
},
{
title: 'AND —— 都是1才是1',
text: 'AND 逐位比较,两个都是 1 结果才是 1。可以用来"提取"某些位:',
code: '; 11111111 (255)\n; AND 00001111 (15)\n; = 00001111 (15)\nAND R0, R0, #15',
},
{
title: '其他位运算',
text: '**ORR** = 有一个1就是1 (OR)\n**EOR** = 不同才是1 (XOR)\n**MVN** = 全部翻转 (NOT)',
code: 'ORR R0, R0, #240 ; 设置高4位\nEOR R0, R0, #255 ; 翻转低8位\nMVN 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)\n; 用 AND 提取低4位\n\n\nHLT',
showMemory: false,
},
// ===== Level 6: 移位操作 =====
{
id: 6,
title: '移位操作',
subtitle: '位的舞蹈',
icon: '↔️',
description: '学习 LSL 和 LSR 指令',
tutorial: [
{
title: 'LSL —— 逻辑左移',
text: '所有位向左移右边补0。**左移1位 = 乘以2**左移3位 = 乘以8',
code: '; 5 = 00000101\nLSL R0, R0, #1 ; 00001010 = 10 (×2)\nLSL R0, R0, #1 ; 00010100 = 20 (×2)',
},
{
title: 'LSR —— 逻辑右移',
text: '所有位向右移左边补0。**右移1位 = 除以2**',
code: 'MOV R0, #40\nLSR R0, R0, #1 ; 20 (÷2)\nLSR R0, R0, #2 ; 5 (÷4)',
},
{
title: '程序员的技巧',
text: '在真实的 ARM 处理器中,移位比乘除快得多!`LSL R0, R0, #3` 比 `MUL R0, R0, #8` 高效。',
},
],
goal: 'R0 = **5**,只用**移位操作**把它变成 **40**40 = 5 × 8 = 5 ×',
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\n; 用 LSL 让 R0 变成 40不能用 MUL\n\n\nHLT',
showMemory: false,
},
// ===== Level 7: 内存读写 =====
{
id: 7,
title: '内存读写',
subtitle: '打开更大的空间',
icon: '💾',
description: '学习 LDR 和 STR 指令',
tutorial: [
{
title: '什么是内存?',
text: '寄存器只有8个太少了**内存**像一排256格的柜子每格有编号(0-255)。',
},
{
title: 'LDR —— 从内存读取',
text: '先把地址放进寄存器,再用 `LDR` 从那个地址读数据:',
code: 'MOV R1, #0 ; 地址 = 0\nLDR R0, [R1] ; R0 = 内存[0]',
},
{
title: 'STR —— 写入内存',
text: '`STR` 把寄存器的值写到内存:',
code: 'MOV R1, #5 ; 地址 = 5\nSTR R0, [R1] ; 内存[5] = R0',
},
{
title: '偏移寻址',
text: '还可以加偏移量:`[R1, #4]` 表示地址 R1+4',
code: 'MOV R1, #0\nLDR R0, [R1, #0] ; 内存[0]\nLDR 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\n; 计算它们的和,存入内存[2]\n;\n; 提示:先 MOV 一个地址到寄存器\n; 然后用 LDR/STR 读写内存\n\n\nHLT',
showMemory: true,
memoryRange: [0, 15],
},
// ===== Level 8: 比较与跳转 =====
{
id: 8,
title: '比较与跳转',
subtitle: '让程序会做决定',
icon: '🔀',
description: '学习 CMP 和条件分支指令',
tutorial: [
{
title: '到目前为止...',
text: '程序都是从头到尾顺序执行。但有了**分支**,程序就能做决定了!',
},
{
title: 'CMP —— 比较',
text: '`CMP` 比较两个值,记住比较结果(不会改变它们的值):',
code: 'CMP R0, #10 ; 比较 R0 和 10',
},
{
title: '条件分支',
text: '比较后用 **B** (Branch=分支) 跳转:',
code: 'BEQ label ; 等于则跳Equal\nBNE label ; 不等则跳Not Equal\nBGT label ; 大于则跳Greater Than\nBLT label ; 小于则跳Less Than\nB label ; 无条件跳',
},
{
title: '标签',
text: '**标签**是代码里的记号,分支指令跳到标签位置。标签后面加冒号:',
code: 'CMP R0, #10\nBGT big\nMOV R1, #0 ; R0 <= 10\nB done ; 跳过下面\nbig:\nMOV R1, #1 ; R0 > 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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 ×
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

View File

@@ -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]

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -20,7 +20,6 @@
@click="go(level)"
>
<div class="level-num">{{ level.id }}</div>
<div class="level-icon">{{ level.icon }}</div>
<h3>{{ level.title }}</h3>
<p class="card-subtitle">{{ level.subtitle }}</p>
<p class="card-desc">{{ level.description }}</p>

View File

@@ -3,7 +3,7 @@
<header class="level-header">
<router-link to="/levels" class="back-link">&larr; 返回</router-link>
<div class="hdr-center">
<span class="level-badge">{{ level.icon }} Level {{ level.id }}</span>
<span class="level-badge">Level {{ level.id }}</span>
<h1>{{ level.title }}</h1>
</div>
<div class="hdr-stars">

View File

@@ -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'