blockly.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. import * as Blockly from "blockly";
  2. import 'blockly/msg/zh-hans';
  3. import {javascriptGenerator} from "blockly/javascript";
  4. import {pythonGenerator} from "blockly/python";
  5. // 导入图片资源
  6. import about_turnImage from '@/assets/images/blockly/component/about_turn.png';
  7. import move_backwardImage from '@/assets/images/blockly/component/move_backward.png';
  8. import move_forwardImage from '@/assets/images/blockly/component/move_forward.png';
  9. import pick_upImage from '@/assets/images/blockly/component/pick_up.png';
  10. import turn_leftImage from '@/assets/images/blockly/component/turn_left.png';
  11. import turn_rightImage from '@/assets/images/blockly/component/turn_right.png';
  12. import useImage from '@/assets/images/blockly/component/use.png';
  13. import pauseImage from '@/assets/images/blockly/component/pause.png';
  14. import play_soundImage from '@/assets/images/blockly/component/sound.png';
  15. import constructImage from '@/assets/images/blockly/component/construct.png';
  16. import lightImage from '@/assets/images/blockly/component/light.png';
  17. import repeatImage from '@/assets/images/blockly/component/repeat.png';
  18. import ifImage from '@/assets/images/blockly/component/if.png';
  19. import ifElseImage from '@/assets/images/blockly/component/if_else.png';
  20. import lightRedImage from '@/assets/images/blockly/light/red.png';
  21. import lightYellowImage from '@/assets/images/blockly/light/yellow.png';
  22. import lightGreenImage from '@/assets/images/blockly/light/green.png';
  23. import {DICT_TYPE} from '@/utils/dictUtils.js';
  24. // 安全地获取和解析字典数据
  25. const getDictDataFromStorage = (dictType) => {
  26. try {
  27. const data = localStorage.getItem(dictType);
  28. return data ? JSON.parse(data) : [];
  29. } catch (error) {
  30. console.error(`解析${dictType}字典数据失败:`, error);
  31. return [];
  32. }
  33. };
  34. // 地图元素类型字典
  35. const savedMapTypeDict = getDictDataFromStorage(DICT_TYPE.AI_BLOCKLY_MAP_TYPE);
  36. const BLOCKLY_MAP_TYPE_DICT = savedMapTypeDict.reduce((acc, item) => {
  37. acc[item.value.toUpperCase()] = item.value;
  38. return acc;
  39. }, {});
  40. // 特殊积木字典
  41. const blocklyMapSpecialDict = getDictDataFromStorage(DICT_TYPE.BLOCKLY_MAP_SPECIAL);
  42. const BLOCKLY_MAP_SPECIAL_DICT = blocklyMapSpecialDict.reduce((acc, item) => {
  43. acc[item.value.toUpperCase()] = item.value;
  44. return acc;
  45. }, {});
  46. // 地图标记字典
  47. const blocklyMapMarkDict = getDictDataFromStorage(DICT_TYPE.BLOCKLY_MAP_MARK);
  48. const BLOCKLY_MAP_MARK_DICT = blocklyMapMarkDict.reduce((acc, item) => {
  49. acc[item.value.toUpperCase()] = {
  50. value: item.value,
  51. label: item.label,
  52. img: item.remark
  53. };
  54. return acc;
  55. }, {});
  56. // 灯光颜色字典
  57. const BLOCKLY_LIGHT_COLOR_DICT = [
  58. {
  59. label: '红色',
  60. color: [233, 77, 88],
  61. img: lightRedImage,
  62. },
  63. {
  64. label: '黄色',
  65. color: [233, 211, 36],
  66. img: lightYellowImage,
  67. },
  68. {
  69. label: '绿色',
  70. color: [118, 187, 45],
  71. img: lightGreenImage,
  72. },
  73. ];
  74. /**
  75. * 定义所有可用的自定义积木配置
  76. */
  77. const availableBlocks = {
  78. // 通用积木 - 始终可用(移动控制)
  79. move_forward: {
  80. jsonConfig: {
  81. "type": "move_forward",
  82. "message0": "%1 向前移动",
  83. "args0": [
  84. {
  85. "type": "field_image",
  86. "src": move_forwardImage,
  87. "width": 20,
  88. "height": 20,
  89. "alt": "向前移动"
  90. }
  91. ],
  92. "previousStatement": null,
  93. "nextStatement": null,
  94. "colour": 230,
  95. "tooltip": "控制角色向前移动一格",
  96. "helpUrl": ""
  97. },
  98. isGeneral: true // 标记为通用积木
  99. },
  100. move_forward_param: {
  101. jsonConfig: {
  102. "type": "move_forward_param",
  103. "message0": "%1 向前移动 %2 次",
  104. "args0": [
  105. {
  106. "type": "field_image",
  107. "src": move_forwardImage,
  108. "width": 20,
  109. "height": 20,
  110. "alt": "向前移动多次"
  111. },
  112. {
  113. "type": "field_number",
  114. "name": "PARAM",
  115. "value": 1,
  116. "min": 1,
  117. "max": 10,
  118. "precision": 1
  119. }
  120. ],
  121. "previousStatement": null,
  122. "nextStatement": null,
  123. "colour": 230,
  124. "tooltip": "控制角色向前移动指定次数",
  125. "helpUrl": ""
  126. },
  127. isGeneral: true // 标记为通用积木
  128. },
  129. move_backward: {
  130. jsonConfig: {
  131. "type": "move_backward",
  132. "message0": "%1 向后移动",
  133. "args0": [
  134. {
  135. "type": "field_image",
  136. "src": move_backwardImage,
  137. "width": 20,
  138. "height": 20,
  139. "alt": "向后移动"
  140. }
  141. ],
  142. "previousStatement": null,
  143. "nextStatement": null,
  144. "colour": 230,
  145. "tooltip": "控制角色向后移动一格",
  146. "helpUrl": ""
  147. },
  148. isGeneral: true
  149. },
  150. move_backward_param: {
  151. jsonConfig: {
  152. "type": "move_backward_param",
  153. "message0": "%1 向后移动 %2 次",
  154. "args0": [
  155. {
  156. "type": "field_image",
  157. "src": move_backwardImage,
  158. "width": 20,
  159. "height": 20,
  160. "alt": "向后移动多次"
  161. },
  162. {
  163. "type": "field_number",
  164. "name": "PARAM",
  165. "value": 1,
  166. "min": 1,
  167. "max": 10,
  168. "precision": 1
  169. }
  170. ],
  171. "previousStatement": null,
  172. "nextStatement": null,
  173. "colour": 230,
  174. "tooltip": "控制角色向后移动指定次数",
  175. "helpUrl": ""
  176. },
  177. isGeneral: true
  178. },
  179. turn_left: {
  180. jsonConfig: {
  181. "type": "turn_left",
  182. "message0": "%1 向左转",
  183. "args0": [
  184. {
  185. "type": "field_image",
  186. "src": turn_leftImage,
  187. "width": 20,
  188. "height": 20,
  189. "alt": "向左转"
  190. }
  191. ],
  192. "previousStatement": null,
  193. "nextStatement": null,
  194. "colour": 230,
  195. "tooltip": "控制角色向左转",
  196. "helpUrl": ""
  197. },
  198. isGeneral: true
  199. },
  200. turn_right: {
  201. jsonConfig: {
  202. "type": "turn_right",
  203. "message0": "%1 向右转",
  204. "args0": [
  205. {
  206. "type": "field_image",
  207. "src": turn_rightImage,
  208. "width": 20,
  209. "height": 20,
  210. "alt": "向右转"
  211. }
  212. ],
  213. "previousStatement": null,
  214. "nextStatement": null,
  215. "colour": 230,
  216. "tooltip": "控制角色向右转",
  217. "helpUrl": ""
  218. },
  219. isGeneral: true
  220. },
  221. turn_around: {
  222. jsonConfig: {
  223. "type": "turn_around",
  224. "message0": "%1 向后转",
  225. "args0": [
  226. {
  227. "type": "field_image",
  228. "src": about_turnImage,
  229. "width": 20,
  230. "height": 20,
  231. "alt": "向后转"
  232. }
  233. ],
  234. "previousStatement": null,
  235. "nextStatement": null,
  236. "colour": 230,
  237. "tooltip": "控制角色向后转",
  238. "helpUrl": ""
  239. },
  240. isGeneral: true
  241. },
  242. // 通用积木 - 始终可用(功能积木)
  243. pickup_item: {
  244. jsonConfig: {
  245. "type": "pickup_item",
  246. "message0": "%1 拾取物品",
  247. "args0": [
  248. {
  249. "type": "field_image",
  250. "src": pick_upImage,
  251. "width": 20,
  252. "height": 20,
  253. "alt": "拾取物品"
  254. }
  255. ],
  256. "previousStatement": null,
  257. "nextStatement": null,
  258. "colour": 30,
  259. "tooltip": "尝试拾取当前位置的物品",
  260. "helpUrl": ""
  261. },
  262. isGeneral: true
  263. },
  264. use_item: {
  265. jsonConfig: {
  266. "type": "use_item",
  267. "message0": "%1 使用物品",
  268. "args0": [
  269. {
  270. "type": "field_image",
  271. "src": useImage,
  272. "width": 20,
  273. "height": 20,
  274. "alt": "使用物品"
  275. }
  276. ],
  277. "previousStatement": null,
  278. "nextStatement": null,
  279. "colour": 30,
  280. "tooltip": "在当前位置使用物品",
  281. "helpUrl": ""
  282. },
  283. isGeneral: true
  284. },
  285. // 逻辑积木 - 自定义如果否则带图标
  286. controls_if: {
  287. jsonConfig: {
  288. "type": "controls_if",
  289. "message0": "%1 如果 %2",
  290. "args0": [
  291. {
  292. "type": "field_image",
  293. "src": ifImage,
  294. "width": 20,
  295. "height": 20,
  296. "alt": "条件"
  297. },
  298. {
  299. "type": "input_value",
  300. "name": "IF0",
  301. "check": "Boolean"
  302. }
  303. ],
  304. "message1": "执行%1",
  305. "args1": [
  306. {
  307. "type": "input_statement",
  308. "name": "DO0"
  309. }
  310. ],
  311. "previousStatement": null,
  312. "nextStatement": null,
  313. "colour": 210,
  314. "tooltip": "如果条件为真,则执行相应的代码。",
  315. "helpUrl": "",
  316. "mutator": "controls_if_mutator"
  317. },
  318. isGeneral: true // 标记为通用积木
  319. },
  320. // 循环积木 - 自定义重复执行带图标
  321. controls_repeat_ext: {
  322. jsonConfig: {
  323. "type": "controls_repeat_ext",
  324. "message0": "%1 重复 %2 次",
  325. "args0": [
  326. {
  327. "type": "field_image",
  328. "src": repeatImage,
  329. "width": 25,
  330. "height": 25,
  331. "alt": "循环"
  332. },
  333. {
  334. "type": "field_number",
  335. "name": "TIMES",
  336. "value": 1,
  337. "min": 1,
  338. "max": 100
  339. }
  340. ],
  341. "message1": "执行 %1",
  342. "args1": [
  343. {
  344. "type": "input_statement",
  345. "name": "DO"
  346. }
  347. ],
  348. "previousStatement": null,
  349. "nextStatement": null,
  350. "colour": "#5BA55B",
  351. "tooltip": "重复执行内部代码指定次数",
  352. "helpUrl": ""
  353. },
  354. isGeneral: true // 标记为通用积木
  355. },
  356. // 循环积木 - while循环
  357. controls_whileUntil: {
  358. jsonConfig: {
  359. "type": "controls_whileUntil",
  360. "message0": "%1 %2 %3",
  361. "args0": [
  362. {
  363. "type": "field_image",
  364. "src": repeatImage,
  365. "width": 25,
  366. "height": 25,
  367. "alt": "循环"
  368. },
  369. {
  370. "type": "field_dropdown",
  371. "name": "MODE",
  372. "options": [
  373. ["当", "WHILE"],
  374. ["直到", "UNTIL"]
  375. ]
  376. },
  377. {
  378. "type": "input_value",
  379. "name": "CONDITION",
  380. "check": "Boolean"
  381. }
  382. ],
  383. "message1": "执行 %1",
  384. "args1": [
  385. {
  386. "type": "input_statement",
  387. "name": "DO"
  388. }
  389. ],
  390. "previousStatement": null,
  391. "nextStatement": null,
  392. "colour": "#5BA55B",
  393. "tooltip": "当条件为真时重复执行内部代码",
  394. "helpUrl": ""
  395. },
  396. isGeneral: true // 标记为通用积木
  397. },
  398. // 当经过某个形状的积木(可作为参数使用的value类型)
  399. when_passed: {
  400. jsonConfig: {
  401. "type": "when_passed",
  402. "message0": "当经过 : %1",
  403. "args0": [
  404. {
  405. "type": "field_dropdown",
  406. "name": "SHAPE",
  407. "options": Object.values(BLOCKLY_MAP_MARK_DICT).map(shape => [
  408. {
  409. "src": shape.img,
  410. "width": 30,
  411. "height": 30,
  412. "alt": shape.label
  413. },
  414. shape.value
  415. ])
  416. }
  417. ],
  418. "output": "Boolean",
  419. "colour": 30,
  420. "tooltip": "检查角色是否经过指定形状,可作为参数使用",
  421. "helpUrl": ""
  422. },
  423. isGeneral: true
  424. },
  425. // 是否到达终点积木
  426. is_at_end: {
  427. jsonConfig: {
  428. "type": "is_at_end",
  429. "message0": "到达终点",
  430. "output": "Boolean",
  431. "colour": 10,
  432. "tooltip": "检查角色是否到达终点位置",
  433. "helpUrl": ""
  434. },
  435. isGeneral: true
  436. },
  437. // 特殊积木 - 可根据配置动态加载
  438. pause: {
  439. jsonConfig: {
  440. "type": "pause",
  441. "message0": "%1 暂停 %2 秒",
  442. "args0": [
  443. {
  444. "type": "field_image",
  445. "src": pauseImage,
  446. "width": 20,
  447. "height": 20,
  448. "alt": "暂停"
  449. },
  450. {
  451. "type": "field_number",
  452. "name": "SECONDS",
  453. "value": 1,
  454. "min": 1,
  455. "max": 10,
  456. "precision": 1
  457. }
  458. ],
  459. "previousStatement": null,
  460. "nextStatement": null,
  461. "colour": 0,
  462. "tooltip": "让角色在原地停留指定的秒数",
  463. "helpUrl": ""
  464. },
  465. isGeneral: false
  466. },
  467. play_sound: {
  468. jsonConfig: {
  469. "type": "play_sound",
  470. "message0": "%1 声音: %2",
  471. "args0": [
  472. {
  473. "type": "field_image",
  474. "src": play_soundImage,
  475. "width": 20,
  476. "height": 20,
  477. "alt": "声音"
  478. },
  479. {
  480. "type": "field_dropdown",
  481. "name": "SOUND",
  482. "options": [
  483. ["打招呼", "打招呼"],
  484. ["铃铛", "铃铛"],
  485. ]
  486. }
  487. ],
  488. "previousStatement": null,
  489. "nextStatement": null,
  490. "colour": 160,
  491. "tooltip": "播放指定的声音特效",
  492. "helpUrl": ""
  493. },
  494. isGeneral: false
  495. },
  496. construct: {
  497. jsonConfig: {
  498. "type": "construct",
  499. "message0": "%1 修建",
  500. "args0": [
  501. {
  502. "type": "field_image",
  503. "src": constructImage,
  504. "width": 20,
  505. "height": 20,
  506. "alt": "修建"
  507. }
  508. ],
  509. "previousStatement": null,
  510. "nextStatement": null,
  511. "colour": 160,
  512. "tooltip": "修建处理",
  513. "helpUrl": ""
  514. },
  515. isGeneral: false
  516. },
  517. light: {
  518. jsonConfig: {
  519. "type": "light",
  520. "message0": "%1 灯光 %2",
  521. "args0": [
  522. {
  523. "type": "field_image",
  524. "src": lightImage,
  525. "width": 25,
  526. "height": 25,
  527. "alt": "灯光"
  528. },
  529. {
  530. "type": "field_dropdown",
  531. "name": "COLOR",
  532. "options": BLOCKLY_LIGHT_COLOR_DICT.map(item => [
  533. {
  534. "src": item.img,
  535. "width": 20,
  536. "height": 20,
  537. "alt": item.label
  538. },
  539. item.label
  540. ])
  541. }
  542. ],
  543. "previousStatement": null,
  544. "nextStatement": null,
  545. "colour": 30,
  546. "tooltip": "在当前位置产生灯光效果",
  547. "helpUrl": ""
  548. },
  549. isGeneral: false
  550. }
  551. };
  552. /**
  553. * 定义所有可用的JavaScript生成器配置
  554. */
  555. const availableGenerators = {
  556. // 通用生成器 - 始终可用
  557. move_forward: function(block) {
  558. return 'await moveForward();\n';
  559. },
  560. move_forward_param: function(block) {
  561. const stepCount = block.getFieldValue('PARAM');
  562. return `await moveForward(${stepCount});\n`;
  563. },
  564. move_backward: function(block) {
  565. return 'await moveBackward();\n';
  566. },
  567. move_backward_param: function(block) {
  568. const stepCount = block.getFieldValue('PARAM');
  569. return `await moveBackward(${stepCount});\n`;
  570. },
  571. turn_left: function(block) {
  572. return 'await turnLeft();\n';
  573. },
  574. turn_right: function(block) {
  575. return 'await turnRight();\n';
  576. },
  577. turn_around: function(block) {
  578. return 'await turnAround();\n';
  579. },
  580. pickup_item: function(block) {
  581. return 'await pickupItem();\n';
  582. },
  583. use_item: function(block) {
  584. return 'await useItem();\n';
  585. },
  586. when_passed: function(block) {
  587. const shape = block.getFieldValue('SHAPE');
  588. return [`whenPassed('${shape}')`, javascriptGenerator.ORDER_FUNCTION_CALL];
  589. },
  590. is_at_end: function(block) {
  591. return [`isAtEnd()`, javascriptGenerator.ORDER_FUNCTION_CALL];
  592. },
  593. // 条件判断生成器 - 使用Blockly默认实现方式确保兼容性
  594. controls_if: function(block) {
  595. let code = '';
  596. let branchCode, conditionCode;
  597. let n = 0;
  598. // 生成if条件
  599. if (block.getInput('IF0')) {
  600. conditionCode = javascriptGenerator.valueToCode(block, 'IF0', javascriptGenerator.ORDER_NONE) || 'false';
  601. branchCode = javascriptGenerator.statementToCode(block, 'DO0');
  602. code += `if (${conditionCode}) {
  603. ${javascriptGenerator.prefixLines(branchCode, javascriptGenerator.INDENT)}}
  604. `;
  605. n = 1;
  606. }
  607. // 生成else if条件
  608. for (; block.getInput('IF' + n); n++) {
  609. conditionCode = javascriptGenerator.valueToCode(block, 'IF' + n, javascriptGenerator.ORDER_NONE) || 'false';
  610. branchCode = javascriptGenerator.statementToCode(block, 'DO' + n);
  611. code += `else if (${conditionCode}) {
  612. ${javascriptGenerator.prefixLines(branchCode, javascriptGenerator.INDENT)}}
  613. `;
  614. }
  615. // 生成else条件
  616. if (block.getInput('ELSE')) {
  617. const elseCode = javascriptGenerator.statementToCode(block, 'ELSE');
  618. code += `else {
  619. ${javascriptGenerator.prefixLines(elseCode, javascriptGenerator.INDENT)}}
  620. `;
  621. }
  622. return code;
  623. },
  624. controls_repeat_ext: function(block) {
  625. const repeats = block.getFieldValue('TIMES');
  626. const branch = javascriptGenerator.statementToCode(block, 'DO');
  627. const code = `for (let i = 0; i < ${repeats}; i++) {\n${javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT)}}\n`;
  628. return code;
  629. },
  630. controls_whileUntil: function(block) {
  631. const until = block.getFieldValue('MODE') === 'UNTIL';
  632. const condition = javascriptGenerator.valueToCode(block, 'CONDITION', javascriptGenerator.ORDER_NONE) || 'false';
  633. const branch = javascriptGenerator.statementToCode(block, 'DO');
  634. const code = until ? `while (!((${condition})) && loopCount < 100) {\n${javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT)}loopCount++;\n}` : `while ((${condition}) && loopCount < 100) {\n${javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT)}loopCount++;\n}`;
  635. return code;
  636. },
  637. // 特殊生成器 - 可根据配置动态加载
  638. pause: function(block) {
  639. const seconds = block.getFieldValue('SECONDS');
  640. return `await pause(${seconds});\n`;
  641. },
  642. play_sound: function(block) {
  643. const sound = block.getFieldValue('SOUND');
  644. return `await playSound('${sound}');
  645. `;
  646. },
  647. construct: function(block) {
  648. return 'await construct();\n';
  649. },
  650. light: function(block) {
  651. const color = block.getFieldValue('COLOR');
  652. return `await light('${color}');
  653. `;
  654. }
  655. };
  656. /**
  657. * 定义所有可用的Python生成器配置
  658. */
  659. const availablePythonGenerators = {
  660. // 通用生成器 - 始终可用
  661. move_forward: function(block) {
  662. return 'move_forward()\n';
  663. },
  664. move_forward_param: function(block) {
  665. const stepCount = block.getFieldValue('PARAM');
  666. return `move_forward(${stepCount})\n`;
  667. },
  668. move_backward: function(block) {
  669. return 'move_backward()\n';
  670. },
  671. move_backward_param: function(block) {
  672. const stepCount = block.getFieldValue('PARAM');
  673. return `move_backward(${stepCount})\n`;
  674. },
  675. turn_left: function(block) {
  676. return 'turn_left()\n';
  677. },
  678. turn_right: function(block) {
  679. return 'turn_right()\n';
  680. },
  681. turn_around: function(block) {
  682. return 'turn_around()\n';
  683. },
  684. pickup_item: function(block) {
  685. return 'pickup_item()\n';
  686. },
  687. use_item: function(block) {
  688. return 'use_item()\n';
  689. },
  690. when_passed: function(block) {
  691. const shape = block.getFieldValue('SHAPE');
  692. return [`when_passed('${shape}')`, pythonGenerator.ORDER_FUNCTION_CALL];
  693. },
  694. is_at_end: function(block) {
  695. return [`is_at_end()`, pythonGenerator.ORDER_FUNCTION_CALL];
  696. },
  697. // 条件判断生成器
  698. controls_if: function(block) {
  699. const n = block.elseifCount_;
  700. let code = '';
  701. let branchCode = '';
  702. let branchCondition = '';
  703. // 生成if条件
  704. branchCondition = pythonGenerator.valueToCode(block, 'IF0', pythonGenerator.ORDER_NONE) || 'False';
  705. branchCode = pythonGenerator.statementToCode(block, 'DO0');
  706. code += `if ${branchCondition}:
  707. ${pythonGenerator.prefixLines(branchCode, pythonGenerator.INDENT)}
  708. `;
  709. // 生成else if条件
  710. for (let i = 1; i <= n; i++) {
  711. branchCondition = pythonGenerator.valueToCode(block, 'IF' + i, pythonGenerator.ORDER_NONE) || 'False';
  712. branchCode = pythonGenerator.statementToCode(block, 'DO' + i);
  713. code += `elif ${branchCondition}:
  714. ${pythonGenerator.prefixLines(branchCode, pythonGenerator.INDENT)}
  715. `;
  716. }
  717. debugger
  718. // 生成else条件
  719. const elseCode = pythonGenerator.statementToCode(block, 'ELSE');
  720. if (elseCode) {
  721. code += `else:
  722. ${pythonGenerator.prefixLines(elseCode, pythonGenerator.INDENT)}
  723. `;
  724. }
  725. return code;
  726. },
  727. controls_repeat_ext: function(block) {
  728. const repeats = block.getFieldValue('TIMES');
  729. const branch = pythonGenerator.statementToCode(block, 'DO');
  730. const code = `for i in range(${repeats}):\n${pythonGenerator.prefixLines(branch, pythonGenerator.INDENT)}\n`;
  731. return code;
  732. },
  733. controls_whileUntil: function(block) {
  734. const until = block.getFieldValue('MODE') === 'UNTIL';
  735. const condition = pythonGenerator.valueToCode(block, 'CONDITION', pythonGenerator.ORDER_NONE) || 'False';
  736. const branch = pythonGenerator.statementToCode(block, 'DO');
  737. const code = until ? `while not (${condition}) and loop_count < 100:\n${pythonGenerator.prefixLines(branch, pythonGenerator.INDENT)}loop_count += 1\n` : `while (${condition}) and loop_count < 100:\n${pythonGenerator.prefixLines(branch, pythonGenerator.INDENT)}loop_count += 1\n`;
  738. return code;
  739. },
  740. // 特殊生成器 - 可根据配置动态加载
  741. pause: function(block) {
  742. const seconds = block.getFieldValue('SECONDS');
  743. return `pause(${seconds})\n`;
  744. },
  745. play_sound: function(block) {
  746. const sound = block.getFieldValue('SOUND');
  747. return `play_sound('${sound}')
  748. `;
  749. },
  750. construct: function(block) {
  751. return 'construct()\n';
  752. },
  753. light: function(block) {
  754. const color = block.getFieldValue('COLOR');
  755. return `light('${color}')
  756. `;
  757. }
  758. };
  759. /**
  760. * 注册自定义Blockly积木
  761. * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
  762. */
  763. export function registerCustomBlocks(blocklySpecialBlocks = []) {
  764. // 确保blocklySpecialBlocks始终是数组
  765. blocklySpecialBlocks = Array.isArray(blocklySpecialBlocks) ? blocklySpecialBlocks : [];
  766. // 遍历所有可用积木
  767. for (const [blockName, blockConfig] of Object.entries(availableBlocks)) {
  768. // 如果是通用积木,或者是允许的特殊积木,则注册
  769. if (blockConfig.isGeneral || blocklySpecialBlocks.includes(blockName)) {
  770. Blockly.Blocks[blockName] = {
  771. init: function() {
  772. this.jsonInit(blockConfig.jsonConfig);
  773. }
  774. };
  775. }
  776. }
  777. }
  778. /**
  779. * 设置Blockly中文本地化
  780. */
  781. export function setupBlocklyChineseLocale() {
  782. // 使用扩展方式覆盖默认的英文文本为中文
  783. const locale = {
  784. // 逻辑积木中文配置
  785. CONTROLS_IF_MSG_IF: "如果",
  786. CONTROLS_IF_MSG_ELSEIF: "否则如果",
  787. CONTROLS_IF_MSG_ELSE: "否则",
  788. CONTROLS_IF_MSG_THEN: "执行",
  789. CONTROLS_IF_MSG_DO: "执行",
  790. CONTROLS_IF_TOOLTIP_1: "如果条件为真,则执行相应的代码。",
  791. CONTROLS_IF_TOOLTIP_2: "如果条件为真,则执行相应的代码;否则执行其他代码。",
  792. CONTROLS_IF_TOOLTIP_3: "如果条件为真,则执行相应的代码;否则检查其他条件。",
  793. CONTROLS_IF_TOOLTIP_4: "如果条件为真,则执行相应的代码;否则检查其他条件,如果都不满足则执行最后代码。",
  794. // 控制块的弹出菜单文本
  795. CONTROLS_IF_IF: "如果",
  796. CONTROLS_IF_ELSEIF: "否则如果",
  797. CONTROLS_IF_ELSE: "否则",
  798. CONTROLS_IF_IF_TITLE: "如果",
  799. CONTROLS_IF_IF_TITLE_IF: "如果",
  800. CONTROLS_IF_ELSEIF_TITLE_ELSEIF: "否则如果",
  801. CONTROLS_IF_ELSE_TITLE: "否则",
  802. CONTROLS_IF_ELSE_TITLE_ELSE: "否则",
  803. // 条件块的提示文本和帮助URL
  804. CONTROLS_IF_HELPURL: "条件语句帮助",
  805. CONTROLS_IF_TOOLTIP_IF: "添加或删除条件。",
  806. CONTROLS_IF_TOOLTIP_ELSE: "添加或删除否则块。",
  807. // 添加设置按钮相关的中文配置
  808. CONTROLS_IF_ELSEIF_TOOLTIP: "否则如果条件为真,则执行相应的代码。",
  809. CONTROLS_IF_ELSE_TOOLTIP: "否则执行相应的代码。",
  810. // 比较运算符中文配置
  811. LOGIC_COMPARE_EQ: "等于",
  812. LOGIC_COMPARE_NEQ: "不等于",
  813. LOGIC_COMPARE_LT: "小于",
  814. LOGIC_COMPARE_LTE: "小于等于",
  815. LOGIC_COMPARE_GT: "大于",
  816. LOGIC_COMPARE_GTE: "大于等于",
  817. LOGIC_COMPARE_TOOLTIP_EQ: "比较两个值是否相等。",
  818. LOGIC_COMPARE_TOOLTIP_NEQ: "比较两个值是否不相等。",
  819. LOGIC_COMPARE_TOOLTIP_LT: "比较第一个值是否小于第二个值。",
  820. LOGIC_COMPARE_TOOLTIP_LTE: "比较第一个值是否小于或等于第二个值。",
  821. LOGIC_COMPARE_TOOLTIP_GT: "比较第一个值是否大于第二个值。",
  822. LOGIC_COMPARE_TOOLTIP_GTE: "比较第一个值是否大于或等于第二个值。",
  823. // 逻辑运算中文配置
  824. LOGIC_OPERATION_AND: "且",
  825. LOGIC_OPERATION_OR: "或",
  826. LOGIC_OPERATION_TOOLTIP_AND: "如果两个条件都为真,则结果为真。",
  827. LOGIC_OPERATION_TOOLTIP_OR: "如果任一条件为真,则结果为真。",
  828. // 布尔值配置
  829. LOGIC_BOOLEAN_TRUE: "真",
  830. LOGIC_BOOLEAN_FALSE: "假",
  831. LOGIC_BOOLEAN_TOOLTIP: "返回一个布尔值:真或假。",
  832. // 循环积木中文配置
  833. CONTROLS_REPEAT_TITLE: "重复执行%1次",
  834. CONTROLS_REPEAT_TOOLTIP: "重复执行内部代码指定次数。",
  835. CONTROLS_REPEAT_MSG_DO: "执行",
  836. CONTROLS_REPEAT_INPUT_DO: "执行",
  837. CONTROLS_WHILEUNTIL_MSG_DO: "执行",
  838. CONTROLS_WHILEUNTIL_INPUT_DO: "执行",
  839. CONTROLS_FOR_MSG_DO: "执行",
  840. CONTROLS_FOR_INPUT_DO: "执行",
  841. CONTROLS_FOR_EACH_MSG_DO: "执行",
  842. CONTROLS_FOR_EACH_INPUT_DO: "执行",
  843. // 数学积木中文配置
  844. MATH_NUMBER_TOOLTIP: "一个数字。在编辑器中双击以更改。",
  845. MATH_ADDITION_SYMBOL: "加",
  846. MATH_SUBTRACTION_SYMBOL: "减",
  847. MATH_MULTIPLICATION_SYMBOL: "乘",
  848. MATH_DIVISION_SYMBOL: "除",
  849. MATH_ARITHMETIC_TOOLTIP_ADD: "返回两个数的和。",
  850. MATH_ARITHMETIC_TOOLTIP_SUBTRACT: "返回第一个数减去第二个数的差。",
  851. MATH_ARITHMETIC_TOOLTIP_MULTIPLY: "返回两个数的积。",
  852. MATH_ARITHMETIC_TOOLTIP_DIVIDE: "返回第一个数除以第二个数的商。"
  853. };
  854. // 使用Object.assign来合并配置,避免直接修改导入对象
  855. Object.assign(Blockly.Msg, locale);
  856. }
  857. /**
  858. * 注册JavaScript生成器
  859. * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
  860. */
  861. export function registerJavaScriptGenerators(blocklySpecialBlocks = []) {
  862. // 确保blocklySpecialBlocks始终是数组
  863. blocklySpecialBlocks = Array.isArray(blocklySpecialBlocks) ? blocklySpecialBlocks : [];
  864. // 遍历所有可用生成器
  865. for (const [blockName, generatorFunc] of Object.entries(availableGenerators)) {
  866. // 如果是通用生成器,或者是允许的特殊生成器对应的生成器,则注册
  867. if (availableBlocks[blockName].isGeneral || blocklySpecialBlocks.includes(blockName)) {
  868. javascriptGenerator.forBlock[blockName] = generatorFunc;
  869. }
  870. }
  871. // 为text_print块添加生成器,用于调试(始终可用)
  872. javascriptGenerator.forBlock['text_print'] = function(block) {
  873. const msg = javascriptGenerator.valueToCode(block, 'TEXT', javascriptGenerator.ORDER_NONE) || '';
  874. return msg;
  875. };
  876. }
  877. /**
  878. * 注册Python生成器
  879. * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
  880. */
  881. export function registerPythonGenerators(blocklySpecialBlocks = []) {
  882. // 确保blocklySpecialBlocks始终是数组
  883. blocklySpecialBlocks = Array.isArray(blocklySpecialBlocks) ? blocklySpecialBlocks : [];
  884. // 遍历所有可用生成器
  885. for (const [blockName, generatorFunc] of Object.entries(availablePythonGenerators)) {
  886. // 如果是通用生成器,或者是允许的特殊生成器对应的生成器,则注册
  887. if (availableBlocks[blockName].isGeneral || blocklySpecialBlocks.includes(blockName)) {
  888. pythonGenerator.forBlock[blockName] = generatorFunc;
  889. }
  890. }
  891. // 为text_print块添加生成器,用于调试(始终可用)
  892. pythonGenerator.forBlock['text_print'] = function(block) {
  893. const msg = pythonGenerator.valueToCode(block, 'TEXT', pythonGenerator.ORDER_NONE) || '';
  894. return msg;
  895. };
  896. }
  897. /**
  898. * 初始化Blockly工作区
  899. * @param {HTMLElement} blocklyDiv - Blockly容器元素
  900. * @param {HTMLElement} toolbox - 工具箱元素
  901. * @returns {Blockly.WorkspaceSvg} - Blockly工作区实例
  902. */
  903. export function initBlockly(options = {}) {
  904. // 应用中文配置
  905. setupBlocklyChineseLocale();
  906. const defaultOptions = {
  907. toolbox: document.getElementById('toolbox'),
  908. collapse: false,
  909. comments: true,
  910. disable: false, // 设为false以允许编辑
  911. maxBlocks: Infinity,
  912. trashcan: true,
  913. horizontalLayout: false,
  914. toolboxPosition: 'start',
  915. toolboxCollapse: false, // 设置工具箱默认展开
  916. toolboxAlwaysExpanded: true, // 确保点击类别后保持展开状态
  917. css: true,
  918. media: 'https://unpkg.com/blockly/media/',
  919. rtl: false,
  920. scrollbars: true,
  921. sounds: false, // 禁用声音以提高性能
  922. oneBasedIndex: true,
  923. grid: {
  924. spacing: 20,
  925. length: 3,
  926. colour: "#ccc",
  927. snap: true
  928. },
  929. zoom: {
  930. controls: true,
  931. wheel: true,
  932. startScale: 1.0,
  933. maxScale: 3,
  934. minScale: 0.3,
  935. scaleSpeed: 1.2
  936. }
  937. };
  938. // 合并默认选项和用户提供的选项
  939. const finalOptions = { ...defaultOptions, ...options };
  940. return Blockly.inject('blocklyDiv', finalOptions);
  941. }
  942. /**
  943. * 获取所有可用的积木配置
  944. * @returns {Object} 所有可用积木的配置对象
  945. */
  946. export function getAllBlocksConfig() {
  947. return {...availableBlocks};
  948. }
  949. /**
  950. * 获取所有可用的JavaScript生成器配置
  951. * @returns {Object} 所有可用JavaScript生成器的配置对象
  952. */
  953. export function getAllGeneratorsConfig() {
  954. return {...availableGenerators};
  955. }
  956. /**
  957. * 获取所有可用的Python生成器配置
  958. * @returns {Object} 所有可用Python生成器的配置对象
  959. */
  960. export function getAllPythonGeneratorsConfig() {
  961. return {...availablePythonGenerators};
  962. }
  963. // 导出字典常量
  964. export { BLOCKLY_MAP_TYPE_DICT, BLOCKLY_MAP_MARK_DICT, BLOCKLY_MAP_SPECIAL_DICT,
  965. BLOCKLY_LIGHT_COLOR_DICT };
  966. export default {
  967. registerCustomBlocks,
  968. registerJavaScriptGenerators,
  969. registerPythonGenerators,
  970. setupBlocklyChineseLocale,
  971. initBlockly,
  972. getAllBlocksConfig,
  973. getAllGeneratorsConfig,
  974. BLOCKLY_MAP_TYPE_DICT,
  975. BLOCKLY_MAP_MARK_DICT,
  976. BLOCKLY_MAP_SPECIAL_DICT
  977. };