Explorar el Código

Merge branch 'master' of http://59.110.91.129:3000/zhangmengying/AIClass into wanzi

丸子 hace 6 meses
padre
commit
ffe83a5f12
Se han modificado 3 ficheros con 1139 adiciones y 1 borrados
  1. 1 0
      src/router/index.js
  2. 1 1
      src/views/block/Blockly2.vue
  3. 1137 0
      src/views/block/MapGame.vue

+ 1 - 0
src/router/index.js

@@ -82,6 +82,7 @@ const routes = [
   { path: '/blockly', component: () => import('../views/block/Blockly.vue') },
   // Blockly2
   { path: '/blockly2', component: () => import('../views/block/Blockly2.vue') },
+  { path: '/mapGame', component: () => import('../views/block/MapGame.vue') },
 ]
 const router = createRouter({
   history: createWebHistory(),

+ 1 - 1
src/views/block/Blockly2.vue

@@ -1294,7 +1294,7 @@ onMounted(() => {
       this.appendValueInput('COMMANDS')
           .setCheck('String')
           .appendField('命令(英文逗号分隔):');
-      this.appendDummyInput().appendField('格式示例: light=on,curtain=open,tv=on');
+      this.appendDummyInput().appendField('格式示例: light=true,curtain=true,tv=true');
       this.setInputsInline(false);
       this.setPreviousStatement(true, null);
       this.setNextStatement(true, null);

+ 1137 - 0
src/views/block/MapGame.vue

@@ -0,0 +1,1137 @@
+<template>
+  <div class="map-game-container">
+    <!-- 标题栏 -->
+    <div class="title-box">
+      <div class="box-icon" @click="navigateBack">
+        <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
+        地图游戏编程
+      </div>
+    </div>
+
+    <div class="content">
+      <!-- 地图显示区域 -->
+      <div class="map-section">
+        <h2>游戏地图</h2>
+        <div class="map-container">
+          <!-- 地图背景 -->
+          <div class="map-background">
+            <img :src="mapBackground" alt="地图背景" class="map-image" />
+
+            <!-- 可行走区域标记 -->
+            <div
+                v-for="(point, index) in walkablePoints"
+                :key="index"
+                class="walkable-point"
+                :style="getPointStyle(point)"
+            ></div>
+
+            <!-- 起点标记 -->
+            <div
+                class="start-point"
+                :style="getPointStyle(startPoint)"
+            >
+              <span>起点</span>
+            </div>
+
+            <!-- 终点标记 -->
+            <div
+                class="end-point"
+                :style="getPointStyle(endPoint)"
+            >
+              <span>终点</span>
+            </div>
+
+            <!-- 玩家角色 -->
+            <div
+                class="player"
+                :style="playerStyle"
+                :class="{ 'collision': isColliding, 'success': hasReachedEnd }"
+            ></div>
+          </div>
+
+          <!-- 游戏状态提示 -->
+          <div v-if="gameMessage" :class="['game-message', messageType]">
+            {{ gameMessage }}
+          </div>
+        </div>
+      </div>
+
+      <!-- Blockly工作区 -->
+      <div class="blockly-section">
+        <div class="toolbox-section">
+          <h2>工具箱</h2>
+          <div id="toolbox" style="display: none;">
+            <!-- 移动控制积木 -->
+            <category name="移动控制" colour="%{BKY_MOTION_HUE}">
+              <block type="move_forward"></block>
+              <block type="move_backward"></block>
+              <block type="turn_left"></block>
+              <block type="turn_right"></block>
+              <block type="turn_around"></block>
+            </category>
+
+            <!-- 逻辑控制积木 -->
+            <category name="逻辑" colour="%{BKY_LOGIC_HUE}">
+              <block type="controls_if"></block>
+              <block type="logic_compare"></block>
+              <block type="logic_operation"></block>
+              <block type="logic_negate"></block>
+              <block type="logic_boolean"></block>
+            </category>
+
+            <!-- 循环控制积木 -->
+            <category name="循环" colour="%{BKY_LOOPS_HUE}">
+              <block type="controls_repeat_ext">
+                <value name="TIMES">
+                  <shadow type="math_number">
+                    <field name="NUM">5</field>
+                  </shadow>
+                </value>
+              </block>
+              <block type="controls_whileUntil"></block>
+            </category>
+
+            <!-- 数学运算积木 -->
+            <category name="数学" colour="%{BKY_MATH_HUE}">
+              <block type="math_number"></block>
+              <block type="math_arithmetic"></block>
+              <block type="math_single"></block>
+            </category>
+
+            <!-- 文本输出积木 -->
+            <category name="文本" colour="%{BKY_TEXTS_HUE}">
+              <block type="text"></block>
+              <block type="text_length"></block>
+              <block type="text_print"></block>
+            </category>
+          </div>
+        </div>
+
+        <div class="workspace-section">
+          <h2>工作区</h2>
+          <div id="blocklyDiv"></div>
+          <div class="controls">
+            <button id="runCode" @click="runCode">运行代码</button>
+            <button @click="clearWorkspace">清空工作区</button>
+            <button @click="resetPlayer">重置玩家</button>
+          </div>
+        </div>
+
+        <div class="output-section">
+          <h2>输出</h2>
+          <div class="controls">
+            <button @click="clearOutput">清空输出</button>
+          </div>
+          <pre id="output">{{ output }}</pre>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, reactive, computed } from 'vue';
+import { useRouter } from 'vue-router';
+import { ArrowLeftBold } from '@element-plus/icons-vue';
+import * as Blockly from "blockly";
+import 'blockly/msg/zh-hans';
+import { javascriptGenerator } from "blockly/javascript";
+
+const router = useRouter();
+
+// 整合游戏状态
+const gameState = reactive({
+  // 地图配置
+  mapConfig: {
+    background: '@/assets/images/room.png',
+    tileSize: 50,
+    width: 10,
+    height: 8
+  },
+
+  // 玩家状态
+  player: {
+    position: { x: 1, y: 1 },
+    direction: 0, // 0=上, 1=右, 2=下, 3=左
+    isColliding: false,
+    hasReachedEnd: false,
+    collisionHappened: false // 标记是否已发生碰撞
+  },
+
+  // 游戏状态
+  status: {
+    message: '',
+    messageType: ''
+  },
+
+  // 地图数据
+  mapData: {
+    startPoint: { x: 1, y: 1 },
+    endPoint: { x: 8, y: 7 },
+    walkablePoints: [
+      { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 3, y: 1 }, { x: 4, y: 1 }, { x: 5, y: 1 },
+      { x: 1, y: 2 }, { x: 5, y: 2 },
+      { x: 1, y: 3 }, { x: 2, y: 3 }, { x: 3, y: 3 }, { x: 4, y: 3 }, { x: 5, y: 3 },
+      { x: 1, y: 4 }, { x: 3, y: 4 }, { x: 5, y: 4 },
+      { x: 1, y: 5 }, { x: 2, y: 5 }, { x: 3, y: 5 }, { x: 4, y: 5 }, { x: 5, y: 5 },
+      { x: 1, y: 6 }, { x: 5, y: 6 },
+      { x: 1, y: 7 }, { x: 2, y: 7 }, { x: 3, y: 7 }, { x: 4, y: 7 }, { x: 5, y: 7 },
+      { x: 6, y: 7 }, { x: 7, y: 7 }, { x: 8, y: 7 }
+    ]
+  }
+});
+
+// 计算属性 - 提高性能和可读性
+const mapBackground = computed(() => gameState.mapConfig.background);
+const tileSize = computed(() => gameState.mapConfig.tileSize);
+const walkablePoints = computed(() => gameState.mapData.walkablePoints);
+const startPoint = computed(() => gameState.mapData.startPoint);
+const endPoint = computed(() => gameState.mapData.endPoint);
+const playerPosition = computed(() => gameState.player.position);
+const playerDirection = computed(() => gameState.player.direction);
+const isColliding = computed(() => gameState.player.isColliding);
+const hasReachedEnd = computed(() => gameState.player.hasReachedEnd);
+const gameMessage = computed(() => gameState.status.message);
+const messageType = computed(() => gameState.status.messageType);
+
+// Blockly相关状态
+let workspace = null;
+const output = ref('// 输出将显示在这里\n');
+
+// 使用 Set 存储可行走点,提高查询效率
+let walkablePointsSet = new Set();
+
+// 初始化可行走点集合
+function initWalkablePointsSet() {
+  walkablePointsSet.clear();
+  gameState.mapData.walkablePoints.forEach(point => {
+    walkablePointsSet.add(`${point.x},${point.y}`);
+  });
+}
+
+// 优化后的可行走检查函数
+function isWalkable(x, y) {
+  return walkablePointsSet.has(`${x},${y}`);
+}
+
+// 计算点的样式
+function getPointStyle(point) {
+  return {
+    left: point.x * tileSize.value + 'px',
+    top: point.y * tileSize.value + 'px'
+  };
+}
+
+// 计算玩家样式
+const playerStyle = computed(() => ({
+  left: playerPosition.value.x * tileSize.value + 'px',
+  top: playerPosition.value.y * tileSize.value + 'px',
+  transform: `rotate(${playerDirection.value * 90}deg)`
+}));
+
+// 显示游戏消息
+function showGameMessage(message, type = 'info') {
+  gameState.status.message = message;
+  gameState.status.messageType = type;
+
+  // 3秒后自动清除消息
+  setTimeout(() => {
+    gameState.status.message = '';
+  }, 3000);
+}
+
+// 导航返回
+function navigateBack() {
+  router.back();
+}
+
+// 注册自定义积木
+function registerCustomBlocks() {
+  // 向前移动积木
+  Blockly.Blocks['move_forward'] = {
+    init: function() {
+      this.jsonInit({
+        "type": "move_forward",
+        "message0": "向前移动",
+        "previousStatement": null,
+        "nextStatement": null,
+        "colour": 230,
+        "tooltip": "控制角色向前移动一格",
+        "helpUrl": ""
+      });
+    }
+  };
+
+  // 向后移动积木
+  Blockly.Blocks['move_backward'] = {
+    init: function() {
+      this.jsonInit({
+        "type": "move_backward",
+        "message0": "向后移动",
+        "previousStatement": null,
+        "nextStatement": null,
+        "colour": 230,
+        "tooltip": "控制角色向后移动一格",
+        "helpUrl": ""
+      });
+    }
+  };
+
+  // 向左转积木
+  Blockly.Blocks['turn_left'] = {
+    init: function() {
+      this.jsonInit({
+        "type": "turn_left",
+        "message0": "向左转",
+        "previousStatement": null,
+        "nextStatement": null,
+        "colour": 230,
+        "tooltip": "控制角色向左转",
+        "helpUrl": ""
+      });
+    }
+  };
+
+  // 向右转积木
+  Blockly.Blocks['turn_right'] = {
+    init: function() {
+      this.jsonInit({
+        "type": "turn_right",
+        "message0": "向右转",
+        "previousStatement": null,
+        "nextStatement": null,
+        "colour": 230,
+        "tooltip": "控制角色向右转",
+        "helpUrl": ""
+      });
+    }
+  };
+
+  // 向后转积木
+  Blockly.Blocks['turn_around'] = {
+    init: function() {
+      this.jsonInit({
+        "type": "turn_around",
+        "message0": "向后转",
+        "previousStatement": null,
+        "nextStatement": null,
+        "colour": 230,
+        "tooltip": "控制角色向后转",
+        "helpUrl": ""
+      });
+    }
+  };
+}
+
+// 注册JavaScript生成器
+function registerJavaScriptGenerators() {
+  // 向前移动生成器
+  javascriptGenerator.forBlock['move_forward'] = function(block) {
+    return 'await moveForward();\n';
+  };
+
+  // 向后移动生成器
+  javascriptGenerator.forBlock['move_backward'] = function(block) {
+    return 'await moveBackward();\n';
+  };
+
+  // 向左转生成器
+  javascriptGenerator.forBlock['turn_left'] = function(block) {
+    return 'await turnLeft();\n';
+  };
+
+  // 向右转生成器
+  javascriptGenerator.forBlock['turn_right'] = function(block) {
+    return 'await turnRight();\n';
+  };
+
+  // 向后转生成器
+  javascriptGenerator.forBlock['turn_around'] = function(block) {
+    return 'await turnAround();\n';
+  };
+
+  // 为重复循环块注册自定义生成器,确保支持异步操作
+  javascriptGenerator.forBlock['controls_repeat_ext'] = function(block) {
+    // 获取循环次数 - 修改为使用 valueToCode 方法
+    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;
+  };
+
+  // 为while/until循环块注册自定义生成器
+  javascriptGenerator.forBlock['controls_whileUntil'] = function(block) {
+    const until = block.getFieldValue('MODE') === 'UNTIL';
+    const condition = javascriptGenerator.valueToCode(block, 'CONDITION',
+        javascriptGenerator.ORDER_NONE) || 'false';
+    const branch = javascriptGenerator.statementToCode(block, 'DO');
+
+    // 修复变量作用域问题,使用IIFE包装循环
+    let code = '(async function() {\n';
+    code += '  let loopCount = 0;\n';
+    code += until ? '  while (!((' + condition + ')) && loopCount < 100) {\n' :
+        '  while (((' + condition + ')) && loopCount < 100) {\n';
+    code += javascriptGenerator.prefixLines(branch, javascriptGenerator.INDENT + '  ');
+    code += '    loopCount++';
+    code += '    await new Promise(resolve => setTimeout(resolve, 10));\n'; // 防止UI阻塞
+    code += '  }\n';
+    code += '})();\n';
+
+    return code;
+  };
+
+  // 为text_print块添加生成器,用于调试
+  javascriptGenerator.forBlock['text_print'] = function(block) {
+    const msg = javascriptGenerator.valueToCode(block, 'TEXT', javascriptGenerator.ORDER_NONE) || '';
+    return `output.value += ${msg} + '\n';\n`;
+  };
+}
+
+// 初始化Blockly工作区
+function initBlockly() {
+  const toolbox = document.getElementById('toolbox');
+  workspace = Blockly.inject('blocklyDiv', {
+    toolbox: toolbox,
+    theme: Blockly.Themes.Classic,
+    grid: {
+      spacing: 20,
+      length: 3,
+      colour: '#ccc',
+      snap: true
+    }
+  });
+}
+
+// 平滑移动函数
+async function smoothMoveTo(targetX, targetY) {
+  const startX = playerPosition.value.x;
+  const startY = playerPosition.value.y;
+  const duration = 500; // 移动动画持续时间(毫秒)
+  const startTime = performance.now();
+
+  // 使用 requestAnimationFrame 实现平滑动画
+  return new Promise(resolve => {
+    function animate(currentTime) {
+      const elapsedTime = currentTime - startTime;
+
+      // 计算进度,确保不会超过1
+      const progress = Math.min(elapsedTime / duration, 1);
+
+      // 使用缓动函数使动画更自然
+      const easedProgress = progress * (2 - progress); // easeOutQuad 缓动
+
+      // 计算当前位置
+      const currentX = startX + (targetX - startX) * easedProgress;
+      const currentY = startY + (targetY - startY) * easedProgress;
+
+      // 更新玩家位置
+      gameState.player.position = { x: currentX, y: currentY };
+
+      // 如果动画未完成,继续下一帧
+      if (progress < 1) {
+        requestAnimationFrame(animate);
+      } else {
+        // 动画完成后解析Promise
+        resolve();
+      }
+    }
+
+    // 开始动画
+    requestAnimationFrame(animate);
+  });
+}
+
+// 获取方向文本描述
+function getDirectionText() {
+  const directions = ['上', '右', '下', '左'];
+  return directions[playerDirection.value];
+}
+
+// 移动函数(供生成的代码调用)
+window.moveForward = async function() {
+  // 如果已经发生过碰撞,不再执行任何移动
+  if (isColliding.value || gameState.player.collisionHappened) {
+    return;
+  }
+
+  let newX = playerPosition.value.x;
+  let newY = playerPosition.value.y;
+
+  // 根据当前方向计算新位置
+  switch(playerDirection.value) {
+    case 0: // 上
+      newY--;
+      break;
+    case 1: // 右
+      newX++;
+      break;
+    case 2: // 下
+      newY++;
+      break;
+    case 3: // 左
+      newX--;
+      break;
+  }
+
+  // 检查是否可以移动
+  if (isWalkable(newX, newY)) {
+    // 使用平滑移动动画
+    await smoothMoveTo(newX, newY);
+    output.value += `移动到: (${newX}, ${newY})\n`;
+
+    // 检查是否到达终点
+    if (newX === endPoint.value.x && newY === endPoint.value.y) {
+      gameState.player.hasReachedEnd = true;
+      showGameMessage('恭喜你到达终点!', 'success');
+      output.value += '恭喜你到达终点!\n';
+    }
+  } else {
+    // 发生碰撞
+    gameState.player.isColliding = true;
+    gameState.player.collisionHappened = true; // 标记已发生碰撞
+    showGameMessage('哎呀,撞到墙了!', 'error');
+    output.value += '碰撞!不能移动到这个位置\n';
+
+    // 立即中止整个代码执行
+    if (executionAbortController) {
+      executionAbortController.abort();
+    }
+    // 添加状态日志
+    console.log('碰撞发生,设置碰撞状态:', {
+      collisionHappened: gameState.player.collisionHappened,
+      isColliding: gameState.player.isColliding
+    });
+
+    // 1秒后取消碰撞状态
+    setTimeout(() => {
+      gameState.player.isColliding = false;
+    }, 1000);
+
+    // 添加碰撞延迟
+    await new Promise(resolve => setTimeout(resolve, 500));
+  }
+};
+
+window.moveBackward = async function() {
+  // 如果已经发生过碰撞,不再执行任何移动
+  if (isColliding.value || gameState.player.collisionHappened) {
+    return;
+  }
+
+  let newX = playerPosition.value.x;
+  let newY = playerPosition.value.y;
+
+  // 根据当前方向计算新位置(向后移动)
+  switch(playerDirection.value) {
+    case 0: // 上 -> 向下移动
+      newY++;
+      break;
+    case 1: // 右 -> 向左移动
+      newX--;
+      break;
+    case 2: // 下 -> 向上移动
+      newY--;
+      break;
+    case 3: // 左 -> 向右移动
+      newX++;
+      break;
+  }
+
+  // 检查是否可以移动
+  if (isWalkable(newX, newY)) {
+    // 使用平滑移动动画
+    await smoothMoveTo(newX, newY);
+    output.value += `向后移动到: (${newX}, ${newY})\n`;
+
+    // 检查是否到达终点
+    if (newX === endPoint.value.x && newY === endPoint.value.y) {
+      gameState.player.hasReachedEnd = true;
+      showGameMessage('恭喜你到达终点!', 'success');
+      output.value += '恭喜你到达终点!\n';
+    }
+  } else {
+    // 发生碰撞
+    gameState.player.isColliding = true;
+    gameState.player.collisionHappened = true; // 标记已发生碰撞
+    showGameMessage('哎呀,撞到墙了!', 'error');
+    output.value += '碰撞!不能移动到这个位置\n';
+
+    // 立即中止整个代码执行
+    if (executionAbortController) {
+      executionAbortController.abort();
+    }
+
+    // 1秒后取消碰撞状态
+    setTimeout(() => {
+      gameState.player.isColliding = false;
+    }, 1000);
+
+    // 添加碰撞延迟
+    await new Promise(resolve => setTimeout(resolve, 500));
+  }
+};
+
+window.turnLeft = async function() {
+  // 如果已经发生过碰撞,不再执行任何旋转
+  console.log('turnLeft',gameState.player.collisionHappened);
+  if (isColliding.value || gameState.player.collisionHappened) {
+    console.log('turnLeft: 检测到碰撞,停止旋转');
+    return;
+  }
+
+  // 向左转(逆时针旋转90度)
+  gameState.player.direction = (playerDirection.value - 1 + 4) % 4;
+  output.value += `向左转,当前方向: ${getDirectionText()}\n`;
+
+  // 添加旋转延迟
+  await new Promise(resolve => setTimeout(resolve, 300));
+  console.log('turnLeft222222222222',gameState.player.collisionHappened);
+};
+
+window.turnRight = async function() {
+  // 如果已经发生过碰撞,不再执行任何旋转
+  console.log('turnRight',gameState.player.collisionHappened);
+  if (isColliding.value || gameState.player.collisionHappened) {
+    console.log('turnRight: 检测到碰撞,停止旋转');
+    return;
+  }
+
+  // 向右转(顺时针旋转90度)
+  gameState.player.direction = (playerDirection.value + 1) % 4;
+  output.value += `向右转,当前方向: ${getDirectionText()}\n`;
+
+  // 添加旋转延迟
+  await new Promise(resolve => setTimeout(resolve, 300));
+  console.log('turnRight222222222222',gameState.player.collisionHappened);
+};
+
+window.turnAround = async function() {
+  // 如果已经发生过碰撞,不再执行任何旋转
+  if (isColliding.value || gameState.player.collisionHappened) {
+    console.log('turnRight: 检测到碰撞,停止旋转');
+    return;
+  }
+
+  // 向后转(旋转180度)
+  gameState.player.direction = (playerDirection.value + 2) % 4;
+  output.value += `向后转,当前方向: ${getDirectionText()}\n`;
+
+  // 添加旋转延迟
+  await new Promise(resolve => setTimeout(resolve, 500));
+};
+
+
+// 添加一个变量来跟踪当前执行的代码
+let currentExecutionPromise = null;
+let executionAbortController = null;
+// 运行代码
+const runCode = async () => {
+  try {
+    // 取消任何正在执行的代码
+    if (executionAbortController) {
+      executionAbortController.abort();
+    }
+
+    // 创建新的AbortController用于取消执行
+    executionAbortController = new AbortController();
+    const signal = executionAbortController.signal;
+
+    // 重置玩家状态
+    gameState.player.isColliding = false;
+    gameState.player.hasReachedEnd = false;
+    gameState.player.collisionHappened = false; // 重置碰撞标志
+
+    // 添加状态日志
+    console.log('执行开始,初始状态:', {
+      collisionHappened: gameState.player.collisionHappened,
+      isColliding: gameState.player.isColliding
+    });
+
+
+    // 初始化输出
+    output.value = '// 执行结果:\n';
+
+    // 确保生成器和工作区都存在
+    if (!javascriptGenerator || !workspace) {
+      throw new Error('生成器或工作区未正确初始化');
+    }
+
+    // 生成JavaScript代码
+    const code = javascriptGenerator.workspaceToCode(workspace);
+    output.value += '// 生成的代码:\n' + code + '\n\n// 执行过程:\n';
+
+    try {
+      // 增强的安全检查
+      const unsafePatterns = [
+        'eval(', 'Function(', 'document.write', 'window.location',
+        'document.createElement', 'XMLHttpRequest', 'fetch',
+        'setInterval', 'setTimeout', 'window.'
+      ];
+
+      const hasUnsafeCode = unsafePatterns.some(pattern => code.includes(pattern));
+
+      if (hasUnsafeCode) {
+        throw new Error('代码包含不安全的操作');
+      }
+
+      // 包装代码为异步函数执行,并设置超时保护
+      currentExecutionPromise = new Promise(async (resolve, reject) => {
+        try {
+          // 检查信号是否已中止
+          if (signal.aborted) {
+            throw new Error('执行已取消');
+          }
+
+          // 添加信号监听
+          signal.addEventListener('abort', () => {
+            reject(new Error('执行已取消'));
+          });
+
+          const wrappedCode = `(async () => { ${code} })()`;
+          await new Function(wrappedCode)();
+          resolve();
+        } catch (error) {
+          reject(error);
+        }
+      });
+
+      // 设置执行超时(例如10秒)
+      await Promise.race([
+        currentExecutionPromise,
+        new Promise((_, reject) => setTimeout(() => reject(new Error('代码执行超时')), 10000))
+      ]);
+
+      // 检查是否到达终点
+      if (!gameState.player.hasReachedEnd) {
+        showGameMessage('程序执行完毕,但未到达终点', 'info');
+        output.value += '程序执行完毕,但未到达终点\n';
+      }
+    } catch (error) {
+      // 捕获并显示执行错误
+      if (error.message !== '执行已取消') {
+        output.value += '\n// 执行错误: ' + error.message + '\n';
+        showGameMessage('代码执行错误: ' + error.message, 'error');
+      }
+    } finally {
+      // 清除当前执行的Promise引用
+      currentExecutionPromise = null;
+    }
+  } catch (error) {
+    output.value += '\n// 错误: ' + error.message + '\n';
+    showGameMessage('运行时错误: ' + error.message, 'error');
+  }
+};
+
+// 清空工作区
+const clearWorkspace = () => {
+  workspace.clear();
+  showGameMessage('工作区已清空', 'info');
+};
+
+// 清空输出
+const clearOutput = () => {
+  output.value = '// 输出将显示在这里\n';
+};
+
+// 重置玩家位置和状态
+const resetPlayer = () => {
+  // 取消任何正在执行的代码
+  if (executionAbortController) {
+    executionAbortController.abort();
+    executionAbortController = null;
+  }
+
+  if (currentExecutionPromise) {
+    currentExecutionPromise = null;
+  }
+
+  gameState.player.position = { ...startPoint.value };
+  gameState.player.direction = 0; // 重置为向上方向
+  gameState.player.isColliding = false;
+  gameState.player.hasReachedEnd = false;
+  gameState.player.collisionHappened = false; // 重置碰撞标志
+  showGameMessage('玩家已重置到起点', 'info');
+  output.value += '玩家已重置到起点\n';
+};
+
+// 组件挂载时初始化
+onMounted(() => {
+  // 初始化可行走点集合
+  initWalkablePointsSet();
+
+  registerCustomBlocks();
+  registerJavaScriptGenerators();
+  initBlockly();
+});
+
+// 组件卸载时清理
+onUnmounted(() => {
+  if (workspace) {
+    workspace.dispose();
+  }
+});
+</script>
+
+<style scoped lang="scss">
+@use "sass:math";
+
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+
+.map-game-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: transparent;
+  overflow-y: auto;
+}
+
+/* 自定义滚动条样式 */
+.map-game-container::-webkit-scrollbar {
+  width: rpx(2); /* 滚动条宽度 */
+}
+
+.map-game-container::-webkit-scrollbar-track {
+  background: #f1effd; /* 滚动条轨道背景色 */
+  border-radius: rpx(4);
+}
+
+.map-game-container::-webkit-scrollbar-thumb {
+  background: #e2ddfc; /* 滚动条滑块颜色 */
+  border-radius: rpx(4);
+}
+
+.map-game-container::-webkit-scrollbar-thumb:hover {
+  background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
+}
+
+.title-box {
+  position: relative;
+  top: 10px;
+  padding-left: 15px;
+  margin-bottom: 20px;
+  z-index: 10;
+}
+
+.box-icon {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 10px 20px;
+  background-color: rgba(255, 255, 255, 0.8);
+  border-radius: 30px;
+  backdrop-filter: blur(10px);
+  cursor: pointer;
+  transition: all 0.3s ease;
+  font-size: 16px;
+  color: #333;
+  font-weight: 500;
+  width: fit-content;
+}
+
+.box-icon:hover {
+  background-color: rgba(255, 255, 255, 0.9);
+  transform: translate(-3px);
+}
+
+.left-icon {
+  font-size: 18px;
+}
+
+.content {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20px;
+  padding: 20px;
+}
+
+/* 地图区域样式 */
+.map-section {
+  flex: 1;
+  min-width: 500px;
+  background: rgba(248, 249, 250, 0.82);
+  padding: 15px;
+  border-radius: 15px;
+}
+
+.map-section h2 {
+  margin-bottom: 15px;
+  color: #2c3e50;
+  border-bottom: 2px solid #3498db;
+  padding-bottom: 8px;
+}
+
+.map-container {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  min-height: 450px;
+}
+
+.map-background {
+  position: relative;
+  width: 500px;
+  height: 400px;
+  margin: 0 auto;
+  border: 2px solid #ddd;
+  border-radius: 8px;
+  overflow: hidden;
+  background-color: #f0f0f0;
+}
+
+.map-image {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  opacity: 0.7;
+}
+
+/* 可行走区域样式 */
+.walkable-point {
+  position: absolute;
+  width: 50px;
+  height: 50px;
+  background-color: rgba(52, 152, 219, 0.2);
+  border: 1px solid rgba(52, 152, 219, 0.5);
+  box-sizing: border-box;
+}
+
+/* 起点和终点样式 */
+.start-point,
+.end-point {
+  position: absolute;
+  width: 50px;
+  height: 50px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-weight: bold;
+  color: white;
+  text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.7);
+  border-radius: 5px;
+}
+
+.start-point {
+  background-color: rgba(46, 204, 113, 0.8);
+}
+
+.end-point {
+  background-color: rgba(231, 76, 60, 0.8);
+}
+
+/* 玩家样式 */
+.player {
+  position: absolute;
+  width: 40px;
+  height: 40px;
+  margin: 5px;
+  background-color: rgba(155, 89, 182, 0.9);
+  border-radius: 5px;
+  transition: all 0.3s ease;
+  z-index: 10;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  color: white;
+  font-weight: bold;
+  font-size: 14px;
+  /* 箭头指示器 */
+  &::after {
+    content: '▶';
+    font-size: 20px;
+  }
+}
+
+/* 碰撞动画 */
+.player.collision {
+  animation: collision 0.5s ease-in-out;
+  background-color: rgba(231, 76, 60, 0.9);
+}
+
+@keyframes collision {
+  0%, 100% { transform: translateX(0) rotate(var(--player-direction)); }
+  25% { transform: translateX(-5px) rotate(var(--player-direction)); }
+  75% { transform: translateX(5px) rotate(var(--player-direction)); }
+}
+
+/* 成功到达终点动画 */
+.player.success {
+  animation: success 1s ease-in-out;
+  background-color: rgba(46, 204, 113, 0.9);
+}
+
+@keyframes success {
+  0% { transform: scale(1) rotate(var(--player-direction)); }
+  50% { transform: scale(1.2) rotate(var(--player-direction)); }
+  100% { transform: scale(1) rotate(var(--player-direction)); }
+}
+
+/* 游戏消息样式 */
+.game-message {
+  position: absolute;
+  top: 20px;
+  left: 50%;
+  transform: translateX(-50%);
+  padding: 10px 20px;
+  border-radius: 5px;
+  font-weight: bold;
+  z-index: 20;
+  min-width: 200px;
+  text-align: center;
+}
+
+.game-message.success {
+  background-color: #d4edda;
+  color: #155724;
+  border: 1px solid #c3e6cb;
+}
+
+.game-message.error {
+  background-color: #f8d7da;
+  color: #721c24;
+  border: 1px solid #f5c6cb;
+}
+
+.game-message.info {
+  background-color: #d1ecf1;
+  color: #0c5460;
+  border: 1px solid #bee5eb;
+}
+
+/* Blockly区域样式 */
+.blockly-section {
+  flex: 1;
+  min-width: 600px;
+  display: flex;
+  flex-direction: column;
+  gap: 20px;
+}
+
+.toolbox-section {
+  background: rgba(248, 249, 250, 0.82);
+  padding: 15px;
+  border-radius: 15px;
+}
+
+.toolbox-section h2 {
+  margin-bottom: 15px;
+  color: #2c3e50;
+  border-bottom: 2px solid #3498db;
+  padding-bottom: 8px;
+}
+
+.workspace-section {
+  background: rgba(248, 249, 250, 0.82);
+  padding: 15px;
+  border-radius: 15px;
+  flex: 1;
+}
+
+.workspace-section h2 {
+  margin-bottom: 15px;
+  color: #2c3e50;
+  border-bottom: 2px solid #3498db;
+  padding-bottom: 8px;
+}
+
+#blocklyDiv {
+  height: 300px;
+  width: 100%;
+  background: #fff;
+  border: 1px solid #ddd;
+  border-radius: 8px;
+}
+
+.controls {
+  display: flex;
+  gap: 10px;
+  margin-top: 15px;
+  flex-wrap: wrap;
+}
+
+button {
+  padding: 10px 20px;
+  border: none;
+  border-radius: 5px;
+  background: #3498db;
+  color: #fff;
+  font-weight: 700;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+button:hover {
+  background: #2980b9;
+  transform: translateY(-2px);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
+}
+
+#runCode {
+  background: #e74c3c;
+}
+
+#runCode:hover {
+  background: #c0392b;
+}
+
+.output-section {
+  background: rgba(241, 248, 255, 0.84);
+  color: #000;
+  padding: 15px;
+  border-radius: 8px;
+  border: 1px solid #d1e7ff;
+}
+
+.output-section h2 {
+  margin-bottom: 15px;
+  color: #2c3e50;
+  border-bottom: 2px solid #3498db;
+  padding-bottom: 8px;
+}
+
+#output {
+  background: rgba(255, 255, 255, 0.5);
+  padding: 10px;
+  border-radius: 5px;
+  min-height: 100px;
+  max-height: 200px;
+  margin-top: 10px;
+  font-family: 'Courier New', monospace;
+  white-space: pre-wrap;
+  overflow-y: auto;
+  font-size: 12px;
+}
+
+/* 响应式布局 */
+@media (max-width: 1200px) {
+  .content {
+    flex-direction: column;
+  }
+
+  .map-section,
+  .blockly-section {
+    min-width: 100%;
+  }
+
+  .map-background {
+    width: 100%;
+    height: 400px;
+  }
+}
+</style>