Pārlūkot izejas kodu

blockly编程课
1、blockly新增想前后移动多次(参数)
2、加回加成python代码功能

liyanbo 4 mēneši atpakaļ
vecāks
revīzija
19c0608d80
2 mainītis faili ar 248 papildinājumiem un 104 dzēšanām
  1. 146 23
      src/api/blockly/blockly.js
  2. 102 81
      src/components/blockly/MapGame.vue

+ 146 - 23
src/api/blockly/blockly.js

@@ -1,6 +1,7 @@
 import * as Blockly from "blockly";
 import 'blockly/msg/zh-hans';
 import { javascriptGenerator } from "blockly/javascript";
+import { pythonGenerator } from "blockly/python";
 
 /**
  * 定义所有可用的自定义积木配置
@@ -25,6 +26,34 @@ const availableBlocks = {
     },
     isGeneral: true // 标记为通用积木
   },
+  move_forward_param: {
+    jsonConfig: {
+      "type": "move_forward_param",
+      "message0": "向前移动 %1 次",
+      "args0": [
+        {
+          "type": "field_number",
+          "name": "PARAM",
+          "value": 1,
+          "min": 1,
+          "max": 10,
+          "precision": 1
+        }
+      ],
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 230,
+      "tooltip": "控制角色向前移动指定次数",
+      "helpUrl": "",
+      "icon": {
+        "src": "▶",
+        "width": 16,
+        "height": 16,
+        "alt": "向前移动多次"
+      }
+    },
+    isGeneral: true // 标记为通用积木
+  },
   move_backward: {
     jsonConfig: {
       "type": "move_backward",
@@ -43,6 +72,34 @@ const availableBlocks = {
     },
     isGeneral: true
   },
+  move_backward_param: {
+    jsonConfig: {
+      "type": "move_backward_param",
+      "message0": "向后移动 %1 次",
+      "args0": [
+        {
+          "type": "field_number",
+          "name": "PARAM",
+          "value": 1,
+          "min": 1,
+          "max": 10,
+          "precision": 1
+        }
+      ],
+      "previousStatement": null,
+      "nextStatement": null,
+      "colour": 230,
+      "tooltip": "控制角色向后移动指定次数",
+      "helpUrl": "",
+      "icon": {
+        "src": "▶",
+        "width": 16,
+        "height": 16,
+        "alt": "向后移动多次"
+      }
+    },
+    isGeneral: true
+  },
   turn_left: {
     jsonConfig: {
       "type": "turn_left",
@@ -209,9 +266,17 @@ const availableGenerators = {
   move_forward: function(block) {
     return 'await moveForward();\n';
   },
+  move_forward_param: function(block) {
+    const stepCount = block.getFieldValue('PARAM');
+    return `await moveForward(${stepCount});\n`;
+  },
   move_backward: function(block) {
     return 'await moveBackward();\n';
   },
+  move_backward_param: function(block) {
+    const stepCount = block.getFieldValue('PARAM');
+    return `await moveBackward(${stepCount});\n`;
+  },
   turn_left: function(block) {
     return 'await turnLeft();\n';
   },
@@ -240,6 +305,53 @@ const availableGenerators = {
   }
 };
 
+/**
+ * 定义所有可用的Python生成器配置
+ */
+const availablePythonGenerators = {
+  // 通用生成器 - 始终可用
+  move_forward: function(block) {
+    return 'move_forward()\n';
+  },
+  move_forward_param: function(block) {
+    const times = block.getFieldValue('PARAM');
+    return `move_forward(${times})\n`;
+  },
+  move_backward: function(block) {
+    return 'move_backward()\n';
+  },
+  move_backward_param: function(block) {
+    const times = block.getFieldValue('PARAM');
+    return `move_backward(${times})\n`;
+  },
+  turn_left: function(block) {
+    return 'turn_left()\n';
+  },
+  turn_right: function(block) {
+    return 'turn_right()\n';
+  },
+  turn_around: function(block) {
+    return 'turn_around()\n';
+  },
+  pickup_item: function(block) {
+    return 'pickup_item()\n';
+  },
+  use_item: function(block) {
+    return 'use_item()\n';
+  },
+  // 特殊生成器 - 可根据配置动态加载
+  pause: function(block) {
+    const seconds = block.getFieldValue('SECONDS');
+    return `pause(${seconds})\n`;
+  },
+  play_sound: function(block) {
+    return 'play_sound()\n';
+  },
+  construct: function(block) {
+    return 'construct()\n';
+  }
+};
+
 /**
  * 注册自定义Blockly积木
  * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
@@ -346,27 +458,6 @@ export function registerJavaScriptGenerators(blocklySpecialBlocks = []) {
     }
   }
 
-  // 为重复循环块注册自定义生成器,确保支持异步操作(始终可用)
-  javascriptGenerator.forBlock['controls_repeat_ext'] = function(block) {
-    const repeats = javascriptGenerator.valueToCode(block, 'TIMES', javascriptGenerator.ORDER_ATOMIC) || '0';
-
-    // 确保获取到的是数字类型,如果是字符串需要转换
-    const safeRepeats = `(function() {
-      const num = Number(${repeats});
-      return isNaN(num) ? 0 : Math.max(0, Math.floor(num));
-    })()`;
-
-    // 获取循环体代码
-    const branch = javascriptGenerator.statementToCode(block, 'DO');
-
-    // 生成支持异步的循环代码
-    let code = `for (let i = 0; i < ${safeRepeats}; i++) {\n`;
-    code += javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT);
-    code += '}\n';
-
-    return code;
-  };
-
   // 为text_print块添加生成器,用于调试(始终可用)
   javascriptGenerator.forBlock['text_print'] = function(block) {
     const msg = javascriptGenerator.valueToCode(block, 'TEXT', javascriptGenerator.ORDER_NONE) || '';
@@ -374,6 +465,29 @@ export function registerJavaScriptGenerators(blocklySpecialBlocks = []) {
   };
 }
 
+/**
+ * 注册Python生成器
+ * @param {Array<string>} blocklySpecialBlocks - 特殊积木方法标识集合
+ */
+export function registerPythonGenerators(blocklySpecialBlocks = []) {
+  // 确保blocklySpecialBlocks始终是数组
+  blocklySpecialBlocks = Array.isArray(blocklySpecialBlocks) ? blocklySpecialBlocks : [];
+  // 遍历所有可用Python生成器
+  for (const [blockName, generatorFunction] of Object.entries(availablePythonGenerators)) {
+    // 检查对应的积木是否为通用积木或允许的特殊积木
+    const blockConfig = availableBlocks[blockName];
+    if (blockConfig && (blockConfig.isGeneral || blocklySpecialBlocks.includes(blockName))) {
+      pythonGenerator.forBlock[blockName] = generatorFunction;
+    }
+  }
+
+  // 为text_print块添加生成器,用于调试(始终可用)
+  pythonGenerator.forBlock['text_print'] = function(block) {
+    const msg = pythonGenerator.valueToCode(block, 'TEXT', pythonGenerator.ORDER_NONE) || '';
+    return msg;
+  };
+}
+
 /**
  * 初始化Blockly工作区
  * @param {Object} options - 初始化选项
@@ -431,16 +545,25 @@ export function getAllBlocksConfig() {
 }
 
 /**
- * 获取所有可用的生成器配置
- * @returns {Object} 所有可用生成器的配置对象
+ * 获取所有可用的JavaScript生成器配置
+ * @returns {Object} 所有可用JavaScript生成器的配置对象
  */
 export function getAllGeneratorsConfig() {
   return {...availableGenerators};
 }
 
+/**
+ * 获取所有可用的Python生成器配置
+ * @returns {Object} 所有可用Python生成器的配置对象
+ */
+export function getAllPythonGeneratorsConfig() {
+  return {...availablePythonGenerators};
+}
+
 export default {
   registerCustomBlocks,
   registerJavaScriptGenerators,
+  registerPythonGenerators,
   setupBlocklyChineseLocale,
   initBlockly,
   getAllBlocksConfig,

+ 102 - 81
src/components/blockly/MapGame.vue

@@ -81,6 +81,7 @@
       <div class="workspace-section">
         <div class="controls">
           <button id="runCode" @click="runCode" :disabled="isRunning">运行代码</button>
+<!--          <button @click="generatePythonCode">生成Python代码</button>-->
           <button @click="resetPlayer" >重置玩家</button>
           <button @click="clearWorkspace">清空工作区</button>
         </div>
@@ -94,6 +95,7 @@
 <script setup>
 import {ref, onMounted, onUnmounted, reactive, computed, nextTick, watch, defineEmits} from 'vue';
 import { javascriptGenerator } from "blockly/javascript";
+import { pythonGenerator } from "blockly/python";
 import playerImage from '@/assets/images/blockly/user.png';
 
 // 导入音频文件
@@ -102,7 +104,7 @@ import failureMp3 from '@/assets/music/blockly/failure.MP3';
 import errorMp3 from '@/assets/music/blockly/error.MP3'
 
 // 游戏接口数据
-import { registerCustomBlocks, registerJavaScriptGenerators, initBlockly } from '@/api/blockly/blockly.js';
+import { registerCustomBlocks, registerJavaScriptGenerators, registerPythonGenerators, initBlockly } from '@/api/blockly/blockly.js';
 import {playMp3} from "@/api/blockly/music.js";
 
 // 定义emits
@@ -249,6 +251,9 @@ const mapContainerDimensions = ref({ width: 0, height: 0 });
 // 用于控制运行/重置按钮状态
 const isRunning = ref(false);
 
+// 存储生成的Python代码
+const generatedPythonCode = ref('');
+
 // 暂停模块-倒计时相关状态
 const showCountdown = ref(false);
 const countdownValue = ref(0);
@@ -407,6 +412,9 @@ onMounted(async () => {
   // 注册JavaScript生成器
   registerJavaScriptGenerators(props.blocklySpecialBlocks);
 
+  // 注册Python生成器
+  registerPythonGenerators(props.blocklySpecialBlocks);
+
   // 动态生成工具箱XML
   const toolboxContainer = document.getElementById('toolbox');
   toolboxContainer.innerHTML = generateToolboxXml();
@@ -432,6 +440,8 @@ function generateToolboxXml() {
       <block type="turn_left"></block>
       <block type="turn_right"></block>
       <block type="turn_around"></block>
+      <block type="move_forward_param"></block>
+      <block type="move_backward_param"></block>
     </category>
     <category name="功能" colour="120">
       <block type="pickup_item"></block>
@@ -752,6 +762,25 @@ const clearWorkspace = () => {
   showGameMessage('工作区已清空', 'info');
 };
 
+// 生成Python代码
+const generatePythonCode = () => {
+  if (!workspace) {
+    return;
+  }
+  try {
+    // 生成Python代码
+    const pythonCode = pythonGenerator.workspaceToCode(workspace);
+    // 存储到常量中
+    generatedPythonCode.value = pythonCode;
+    console.log('生成的Python代码:', pythonCode);
+    // 可以添加提示信息
+    showGameMessage('Python代码生成成功!', 'success');
+  } catch (error) {
+    console.error('生成Python代码时出错:', error);
+    showGameMessage('生成Python代码失败', 'error');
+  }
+};
+
 // 统一处理撞到墙时的停止逻辑
 async function handleWallCollision(endMsg = CONFIG.TIPS.NO_ENTRY) {
   // 设置碰撞状态
@@ -790,75 +819,47 @@ function showGameMessage(message, type = 'info', duration = CONFIG.DELAY.MESSAGE
 //================积木组件方法=====================
 
 // 向前移动
-window.moveForward = async function() {
+window.moveForward = async function(stepCount = 1) {
   if (shouldStopExecution || isColliding.value || isSliding.value) {
     return;
   }
 
-  let newX = playerPosition.value.x;
-  let newY = playerPosition.value.y;
-
-  // 向前移动
-  switch(playerDirection.value) {
-    case CONFIG.GAME.DIRECTIONS.UP: newY--; break;
-    case CONFIG.GAME.DIRECTIONS.RIGHT: newX++; break;
-    case CONFIG.GAME.DIRECTIONS.DOWN: newY++; break;
-    case CONFIG.GAME.DIRECTIONS.LEFT: newX--; break;
-  }
-
-  // 检查是否可以移动
-  if (walkablePointsMap.has(`${newX},${newY}`)) {
-
-    // 处理方块类型逻辑
-    await switchMapType(0);
-
-    // 使用平滑移动动画
-    await smoothMoveTo(newX, newY);
-
-    // 处理方块类型逻辑
-    await switchMapType(1, 0);
+  for (let i = 1; i <= stepCount; i++) {
+    let newX = playerPosition.value.x;
+    let newY = playerPosition.value.y;
+    // 向前移动
+    switch(playerDirection.value) {
+      case CONFIG.GAME.DIRECTIONS.UP: newY--; break;
+      case CONFIG.GAME.DIRECTIONS.RIGHT: newX++; break;
+      case CONFIG.GAME.DIRECTIONS.DOWN: newY++; break;
+      case CONFIG.GAME.DIRECTIONS.LEFT: newX--; break;
+    }
 
-    await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
-  } else {
-    // 发生碰撞 使用统一的碰撞处理方法
-    await handleWallCollision();
+    console.log(`第${i}步`,newX, newY);
+    await moveStep(newX, newY);
   }
 };
 
 // 向后移动
-window.moveBackward = async function() {
+window.moveBackward = async function(stepCount = 1) {
   // 如果已经发生过碰撞,不再执行任何移动
   if (shouldStopExecution || isColliding.value || isSliding.value) {
     return;
   }
 
-  let newX = playerPosition.value.x;
-  let newY = playerPosition.value.y;
-
-  // 根据当前方向计算新位置(向后移动)
-  switch(playerDirection.value) {
-    case CONFIG.GAME.DIRECTIONS.UP: newY++; break;
-    case CONFIG.GAME.DIRECTIONS.RIGHT: newX--; break;
-    case CONFIG.GAME.DIRECTIONS.DOWN: newY--; break;
-    case CONFIG.GAME.DIRECTIONS.LEFT: newX++; break;
-  }
-
-  // 检查是否可以移动
-  if (walkablePointsMap.has(`${newX},${newY}`)) {
-
-    // 处理方块类型逻辑
-    await switchMapType(0);
+  for (let i = 1; i <= stepCount; i++) {
+    let newX = playerPosition.value.x;
+    let newY = playerPosition.value.y;
 
-    // 使用平滑移动动画
-    await smoothMoveTo(newX, newY);
-
-    // 处理方块类型逻辑
-    await switchMapType(1, 1);
+    // 根据当前方向计算新位置(向后移动)
+    switch(playerDirection.value) {
+      case CONFIG.GAME.DIRECTIONS.UP: newY--; break;
+      case CONFIG.GAME.DIRECTIONS.RIGHT: newX--; break;
+      case CONFIG.GAME.DIRECTIONS.DOWN: newY++; break;
+      case CONFIG.GAME.DIRECTIONS.LEFT: newX++; break;
+    }
 
-    await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
-  } else {
-    // 发生碰撞 使用统一的碰撞处理方法
-    await handleWallCollision();
+    await moveStep(newX, newY, 1);
   }
 };
 
@@ -1180,8 +1181,6 @@ window.construct = async function() {
   processingSpecialTasksDisappearing()
 };
 
-
-
 //校验是否到达终点
 window.isFinish = async function() {
   // 如果已经发生过碰撞,不再执行任何检查
@@ -1224,6 +1223,28 @@ window.isFinish = async function() {
 
 //================特殊组件积木逻辑=====================
 
+//移动逻辑处理(前后通用)
+async function moveStep(newX, newY, moveDirection = 0){
+
+  // 检查是否可以移动
+  if (walkablePointsMap.has(`${newX},${newY}`)) {
+
+    // 处理方块类型逻辑
+    await switchMapType(0);
+
+    // 使用平滑移动动画
+    await smoothMoveTo(newX, newY);
+
+    // 处理方块类型逻辑
+    await switchMapType(1, moveDirection);
+
+    await new Promise(resolve => setTimeout(resolve, CONFIG.DELAY.ACTION_DELAY));
+  } else {
+    // 发生碰撞 使用统一的碰撞处理方法
+    await handleWallCollision();
+  }
+}
+
 // 处理地图类型逻辑
 async function switchMapType(type, moveDirection = 0) {
   //取人物当前位置
@@ -1266,31 +1287,6 @@ async function switchMapType(type, moveDirection = 0) {
   }
 }
 
-// 特殊任务处理消失(不需要物品)
-function processingSpecialTasksDisappearing(errorTip = CONFIG.TIPS.ERROR_ERROR) {
-  let x = playerPosition.value.x;
-  let y = playerPosition.value.y;
-  let tileMap = walkablePointsMap.get(`${x},${y}`);
-
-  if (tileMap && tileMap.type === BLOCKLY_MAP_TYPE_DICT.TASK) {
-    const pointIndex = gameState.mapData.walkablePoints.findIndex(
-        p => p.x === x && p.y === y
-    );
-    if (pointIndex !== -1) {
-      const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
-      updatedPoint.img = updatedPoint.endImg;
-      updatedPoint.status = true;
-      gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
-      walkablePointsMap.set(`${x},${y}`, updatedPoint);
-    }
-    showGameMessage(tileMap.finishedTip || CONFIG.TIPS.USE_ITEM_SUCCESS, 'success');
-    return true;
-  }else{
-    showGameMessage(errorTip, 'info');
-    return false;
-  }
-}
-
 // 平滑移动函数
 async function smoothMoveTo(targetX, targetY) {
   const startX = playerPosition.value.x;
@@ -1333,6 +1329,31 @@ async function smoothMoveTo(targetX, targetY) {
   });
 }
 
+// 特殊任务处理消失(不需要物品)
+function processingSpecialTasksDisappearing(errorTip = CONFIG.TIPS.ERROR_ERROR) {
+  let x = playerPosition.value.x;
+  let y = playerPosition.value.y;
+  let tileMap = walkablePointsMap.get(`${x},${y}`);
+
+  if (tileMap && tileMap.type === BLOCKLY_MAP_TYPE_DICT.TASK) {
+    const pointIndex = gameState.mapData.walkablePoints.findIndex(
+        p => p.x === x && p.y === y
+    );
+    if (pointIndex !== -1) {
+      const updatedPoint = { ...gameState.mapData.walkablePoints[pointIndex] };
+      updatedPoint.img = updatedPoint.endImg;
+      updatedPoint.status = true;
+      gameState.mapData.walkablePoints.splice(pointIndex, 1, updatedPoint);
+      walkablePointsMap.set(`${x},${y}`, updatedPoint);
+    }
+    showGameMessage(tileMap.finishedTip || CONFIG.TIPS.USE_ITEM_SUCCESS, 'success');
+    return true;
+  }else{
+    showGameMessage(errorTip, 'info');
+    return false;
+  }
+}
+
 // 处理冰块滑行逻辑
 async function slidingLogic(moveDirection = 0) {
   if (shouldStopExecution || isColliding.value) {