浏览代码

blockly编程游戏
1、人物图片
2、经过终点就会触发终点状态,应该是最终停落在终点后才会触发状态和动效
3、在移动过程中点击运行和重置需要立刻马上清除所有状态然后再继续执行逻辑

liyanbo 5 月之前
父节点
当前提交
f1eca2f57c
共有 5 个文件被更改,包括 123 次插入267 次删除
  1. 50 0
      src/api/ai/mapgame/index.ts
  2. 二进制
      src/assets/images/blockly/mapGame2.png
  3. 二进制
      src/assets/images/blockly/user.png
  4. 0 113
      src/views/block/Blockly.vue
  5. 73 154
      src/views/block/MapGame.vue

+ 50 - 0
src/api/ai/mapgame/index.ts

@@ -0,0 +1,50 @@
+import request from '@/config/axios'
+
+// AI-blockly地图编程游戏 VO
+export interface MapGameVO {
+  id: number // 主键
+  name: string // 名称
+  info: string // 简介
+  userImage: string // 人物图标
+  userPosition: string // 人物初始坐标
+  mapStartPoint: string // 地图开始坐标
+  mapBackground: string // 地图背景图
+  mapEndPoint: string // 地图结束坐标
+  mapWalkablePoints: string // 地图可行走坐标
+  jsonData: string // json数据
+  sort: number // 排序
+  status: number // 状态
+}
+
+// AI-blockly地图编程游戏 API
+export const MapGameApi = {
+  // 查询AI-blockly地图编程游戏分页
+  getMapGamePage: async (params: any) => {
+    return await request.get({ url: `/ai/map-game/page`, params })
+  },
+
+  // 查询AI-blockly地图编程游戏详情
+  getMapGame: async (id: number) => {
+    return await request.get({ url: `/ai/map-game/get?id=` + id })
+  },
+
+  // 新增AI-blockly地图编程游戏
+  createMapGame: async (data: MapGameVO) => {
+    return await request.post({ url: `/ai/map-game/create`, data })
+  },
+
+  // 修改AI-blockly地图编程游戏
+  updateMapGame: async (data: MapGameVO) => {
+    return await request.put({ url: `/ai/map-game/update`, data })
+  },
+
+  // 删除AI-blockly地图编程游戏
+  deleteMapGame: async (id: number) => {
+    return await request.delete({ url: `/ai/map-game/delete?id=` + id })
+  },
+
+  // 导出AI-blockly地图编程游戏 Excel
+  exportMapGame: async (params) => {
+    return await request.download({ url: `/ai/map-game/export-excel`, params })
+  }
+}

二进制
src/assets/images/blockly/mapGame2.png


二进制
src/assets/images/blockly/user.png


+ 0 - 113
src/views/block/Blockly.vue

@@ -307,120 +307,7 @@ const jsonDataString = computed({
   }
 });
 
-// 响应式变量
-// const jsonData = ref({
-//   "blocks": {
-//     "languageVersion": 0,
-//     "blocks": [
-//       {
-//         "type": "variables_set",
-//         "id": "kM:Fgf:wd4U3Z$j0x8oK",
-//         "x": 90,
-//         "y": 130,
-//         "fields": {
-//           "VAR": {
-//             "id": "MHW(ZbOKhL!/An`5N@6`"
-//           }
-//         },
-//         "inputs": {
-//           "VALUE": {
-//             "block": {
-//               "type": "ai_voice_input",
-//               "id": "l5E=g|1L+4hThQ8v})lQ",
-//               "fields": {
-//                 "LANGUAGE": "zh-CN"
-//               },
-//               "inputs": {
-//                 "PROMPT": {
-//                   "block": {
-//                     "type": "text",
-//                     "id": "Q*n.c_)@7j^E2=s5/X!n",
-//                     "fields": {
-//                       "TEXT": "请发言:"
-//                     }
-//                   }
-//                 }
-//               }
-//             }
-//           }
-//         },
-//         "next": {
-//           "block": {
-//             "type": "variables_set",
-//             "id": "]g.xbBe.i=a9B*Kfw@|`",
-//             "fields": {
-//               "VAR": {
-//                 "id": "zn.7{ZqbUaH1?P,R05hF"
-//               }
-//             },
-//             "inputs": {
-//               "VALUE": {
-//                 "block": {
-//                   "type": "ai_text_to_text",
-//                   "id": "R$h+R!6#@+4=+WX1*nvh",
-//                   "inputs": {
-//                     "PROMPT": {
-//                       "block": {
-//                         "type": "variables_get",
-//                         "id": "h$S$nt)3VU.=nX*W-mo~",
-//                         "fields": {
-//                           "VAR": {
-//                             "id": "MHW(ZbOKhL!/An`5N@6`"
-//                           }
-//                         }
-//                       }
-//                     },
-//                     "提示词": {
-//                       "block": {
-//                         "type": "text",
-//                         "id": "7k%sgLP?i]e[,m^49P++",
-//                         "fields": {
-//                           "TEXT": "请只回复我指定格式:白,100,热闹"
-//                         }
-//                       }
-//                     }
-//                   }
-//                 }
-//               }
-//             },
-//             "next": {
-//               "block": {
-//                 "type": "ai_smart_lamp_single_param",
-//                 "id": "!.0;Ktwm+Z?o8_9FRa}G",
-//                 "inputs": {
-//                   "PARAMS": {
-//                     "block": {
-//                       "type": "variables_get",
-//                       "id": "d{cIJ-kEFFQcn~%A,g@g",
-//                       "fields": {
-//                         "VAR": {
-//                           "id": "zn.7{ZqbUaH1?P,R05hF"
-//                         }
-//                       }
-//                     }
-//                   }
-//                 }
-//               }
-//             }
-//           }
-//         }
-//       }
-//     ]
-//   },
-//   "variables": [
-//     {
-//       "name": "inputText",
-//       "id": "MHW(ZbOKhL!/An`5N@6`"
-//     },
-//     {
-//       "name": "lampConfig",
-//       "id": "zn.7{ZqbUaH1?P,R05hF"
-//     }
-//   ]
-// });
-
 //输出结果
-
 const output = ref('');
 const statusMessage = ref('');
 const statusType = ref('');

+ 73 - 154
src/views/block/MapGame.vue

@@ -25,22 +25,6 @@
                 :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"
@@ -116,12 +100,14 @@
 
 <script setup>
 import { ref, onMounted, onUnmounted, reactive, computed } from 'vue';
-import { useRouter, useRoute } from 'vue-router'; 
+import { useRouter, useRoute } 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";
-import mapBackgroundImage from '@/assets/images/blockly/mapGame.png';
+import mapBackgroundImage from '@/assets/images/blockly/mapGame2.png';
+import playerImage from '@/assets/images/blockly/user.png';
+
 
 const router = useRouter();
 const route = useRoute();
@@ -143,7 +129,7 @@ const gameState = reactive({
     // 地图背景图片路径
     background: mapBackgroundImage,
     // 每个瓦片的尺寸(像素)
-    tileSize: 167,
+    tileSize: 110,
   },
 
   // 玩家相关状态
@@ -151,7 +137,7 @@ const gameState = reactive({
     // 玩家当前位置坐标
     position: { x: 2, y: 2 },
     // 玩家当前朝向:0=上, 1=右, 2=下, 3=左
-    direction: 1,
+    direction: 0,
     // 是否正在发生碰撞
     isColliding: false,
     // 是否已到达终点
@@ -171,7 +157,7 @@ const gameState = reactive({
     // 游戏起点位置
     startPoint: { x: 2, y: 2 },
     // 游戏终点位置
-    endPoint: { x: 3, y: 3 },
+    endPoint: { x: 3, y: 2 },
     // 地图上所有可行走的点坐标集合
     walkablePoints: [
       { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 3, y: 1 },
@@ -194,9 +180,10 @@ const hasReachedEnd = computed(() => gameState.player.hasReachedEnd);
 const gameMessage = computed(() => gameState.status.message);
 const messageType = computed(() => gameState.status.messageType);
 
+const playerImageSrc = computed(() => playerImage);
+
 // Blockly相关状态
 let workspace = null;
-const output = ref('// 输出将显示在这里\n');
 
 // 使用 Set 存储可行走点,提高查询效率
 let walkablePointsSet = new Set();
@@ -225,21 +212,16 @@ function getPointStyle(point) {
 }
 
 // 计算玩家样式
-const playerStyle = computed(() => {
-  // 调整旋转角度,使direction=1(右)时图片显示为正的
-  // 原来的角度计算:0=上(0度), 1=右(90度), 2=下(180度), 3=左(270度)
-  // 调整后的角度计算:0=上(270度), 1=右(0度), 2=下(90度), 3=左(180度)
-  const adjustedRotation = (playerDirection.value - 1 + 4) % 4 * 90;
-  return {
-    left: playerPosition.value.x * tileSize.value - tileSize.value + 'px',
-    top: playerPosition.value.y * tileSize.value - tileSize.value + 'px',
-    transform: `rotate(${adjustedRotation}deg)`,
-    '--player-rotation': `${adjustedRotation}deg`,
-    width: (tileSize.value * 0.8) + 'px',
-    height: (tileSize.value * 0.8) + 'px',
-    margin: (tileSize.value * 0.1) + 'px'
-  };
-});
+const playerStyle = computed(() => ({
+  left: playerPosition.value.x * tileSize.value - tileSize.value + 'px',
+  top: playerPosition.value.y * tileSize.value - tileSize.value + 'px',
+  transform: `rotate(${playerDirection.value * 90}deg)`,
+  '--player-rotation': `${playerDirection.value * 90}deg`,
+  '--player-image': `url(${playerImageSrc.value})`,
+  width: (tileSize.value * 0.8) + 'px',
+  height: (tileSize.value * 0.8) + 'px',
+  margin: (tileSize.value * 0.1) + 'px'
+}));
 
 // 显示游戏消息
 function showGameMessage(message, type = 'info') {
@@ -257,7 +239,6 @@ function navigateBack() {
   router.back();
 }
 
-
 // 注册自定义积木
 function registerCustomBlocks() {
   // 向前移动积木
@@ -409,7 +390,7 @@ function registerJavaScriptGenerators() {
   // 为text_print块添加生成器,用于调试
   javascriptGenerator.forBlock['text_print'] = function(block) {
     const msg = javascriptGenerator.valueToCode(block, 'TEXT', javascriptGenerator.ORDER_NONE) || '';
-    return `output.value += ${msg} + '\n';\n`;
+    return msg;
   };
 }
 
@@ -487,22 +468,14 @@ async function smoothMoveTo(targetX, targetY) {
   });
 }
 
-// 获取方向文本描述
-function getDirectionText() {
-  const directions = ['上', '右', '下', '左'];
-  return directions[playerDirection.value];
-}
-
 // 创建通用的移动函数
 async function move(direction) {
-  // direction: 1 表示向前,-1 表示向后
   if (isColliding.value) {
     return;
   }
 
   let newX = playerPosition.value.x;
   let newY = playerPosition.value.y;
-  let moveType = direction === 1 ? '移动' : '向后移动';
 
   // 根据当前方向和移动类型计算新位置
   if (direction === 1) {
@@ -527,22 +500,10 @@ async function move(direction) {
   if (isWalkable(newX, newY)) {
     // 使用平滑移动动画
     await smoothMoveTo(newX, newY);
-    output.value += `${moveType}到: (${newX}, ${newY})\n`;
-
-    // 检查是否到达终点 - 确保在动画完全完成后才触发终点效果
-    if (Math.abs(playerPosition.value.x - endPoint.value.x) < 0.01 && Math.abs(playerPosition.value.y - endPoint.value.y) < 0.01) {
-      // 确保玩家精确停在终点位置
-      gameState.player.position = { x: endPoint.value.x, y: endPoint.value.y };
-      gameState.player.hasReachedEnd = true;
-      gameState.player.direction = 1;
-      showGameMessage('恭喜你到达终点!', 'success');
-      output.value += '恭喜你到达终点!';
-    }
   } else {
     // 发生碰撞
     gameState.player.isColliding = true;
     showGameMessage('哎呀,撞到墙了!', 'error');
-    output.value += '碰撞!不能移动到这个位置';
 
     // 立即中止整个代码执行
     if (executionAbortController) {
@@ -556,7 +517,7 @@ async function move(direction) {
     }, 1000);
 
     // 添加碰撞延迟
-    await new Promise(resolve => setTimeout(resolve, 500));
+    await new Promise(resolve => setTimeout(resolve, 300));
   }
 }
 
@@ -577,7 +538,7 @@ window.turnLeft = async function() {
 
   // 向左转(逆时针旋转90度)
   gameState.player.direction = (playerDirection.value - 1 + 4) % 4;
-  output.value += `向左转,当前方向: ${getDirectionText()}\n`;
+  console.log('向左转', playerDirection.value, gameState.player.direction);
 
   // 添加旋转延迟
   await new Promise(resolve => setTimeout(resolve, 300));
@@ -591,7 +552,7 @@ window.turnRight = async function() {
 
   // 向右转(顺时针旋转90度)
   gameState.player.direction = (playerDirection.value + 1) % 4;
-  output.value += `向右转,当前方向: ${getDirectionText()}\n`;
+  console.log('向右转', playerDirection.value, gameState.player.direction);
 
   // 添加旋转延迟
   await new Promise(resolve => setTimeout(resolve, 300));
@@ -605,12 +566,24 @@ window.turnAround = async function() {
 
   // 向后转(旋转180度)
   gameState.player.direction = (playerDirection.value + 2) % 4;
-  output.value += `向后转,当前方向: ${getDirectionText()}\n`;
 
   // 添加旋转延迟
   await new Promise(resolve => setTimeout(resolve, 500));
 };
 
+//校验是否到达终点
+window.isFinish = async function() {
+  // 如果已经发生过碰撞,不再执行任何旋转
+  if (isColliding.value) {
+    return;
+  }
+
+  if (gameState.player.position.x === endPoint.value.x && gameState.player.position.y === endPoint.value.y) {
+    gameState.player.hasReachedEnd = true;
+    showGameMessage('恭喜你到达终点!', 'success');
+  }
+};
+
 // 添加一个变量来跟踪当前执行的代码
 let currentExecutionPromise = null;
 let executionAbortController = null;
@@ -618,22 +591,19 @@ let executionAbortController = null;
 const runCode = async () => {
   try {
     await resetPlayer();
+    await new Promise(resolve => setTimeout(resolve, 300));
 
     // 创建新的AbortController用于取消执行
     executionAbortController = new AbortController();
     const signal = executionAbortController.signal;
 
-    // 初始化输出
-    output.value = '// 执行结果:\n';
-
     // 确保生成器和工作区都存在
     if (!javascriptGenerator || !workspace) {
       throw new Error('生成器或工作区未正确初始化');
     }
 
     // 生成JavaScript代码
-    const code = javascriptGenerator.workspaceToCode(workspace);
-    output.value += '// 生成的代码:\n' + code + '\n\n// 执行过程:\n';
+    const code = javascriptGenerator.workspaceToCode(workspace) + "await isFinish();";
 
     try {
       // 增强的安全检查
@@ -676,7 +646,6 @@ const runCode = async () => {
       // 捕获并显示执行错误
       if (error.message !== '执行已取消') {
         const errorMsg = error.message || '未知错误';
-        output.value += `\n// 执行错误: ${errorMsg}\n`;
         showGameMessage(`代码执行错误: ${errorMsg}`, 'error');
         console.error('代码执行错误:', error);
       }
@@ -685,7 +654,6 @@ const runCode = async () => {
       currentExecutionPromise = null;
     }
   } catch (error) {
-    output.value += `\n// 错误: ${error.message || '未知错误'}\n`;
     showGameMessage(`运行时错误: ${error.message || '未知错误'}`, 'error');
     console.error('运行时错误:', error);
   }
@@ -710,11 +678,9 @@ const resetPlayer = () => {
   }
 
   gameState.player.position = { ...startPoint.value };
-  gameState.player.direction = 1; // 重置为向上方向
+  gameState.player.direction = 0; // 重置为向上方向
   gameState.player.isColliding = false; //碰撞标志
   gameState.player.hasReachedEnd = false;
-  showGameMessage('玩家已重置到起点', 'info');
-  output.value += '玩家已重置到起点\n';
 };
 
 // 组件挂载时初始化
@@ -833,71 +799,53 @@ onUnmounted(() => {
 }
 
 .map-container {
-  position: relative;
-  width: 100%;
-  height: 100%;
-  min-height: 450px;
+  //position: relative;
+  //width: 100%;
+  //height: 100%  ;
+  //display: flex;
+  //justify-content: center;
+  //align-items: center;
 }
 
 .map-background {
   position: relative;
-  width: 500px;
-  height: 500px;
-  margin: 0 auto;
-  border: 2px solid #ddd;
-  border-radius: 8px;
-  overflow: hidden;
-  background-color: #f0f0f0;
+  width: 100%;
+  height: 100%;
+  background-size: cover;
+  background-position: center;
+  background-repeat: no-repeat;
+  //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;
-  //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;
-  //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);
+  position: absolute;
+  background-color: rgba(52, 152, 219, 0.2);
+  border: 1px solid rgba(52, 152, 219, 0.5);
+  box-sizing: border-box;
 }
 
 /* 玩家样式 */
 .player {
   position: absolute;
-  background-image: url('@/assets/images/xiaozhi2.png');
+  background-image: var(--player-image);
   background-size: contain;
   background-repeat: no-repeat;
   background-position: center;
   border-radius: 5px;
   transition: all 0.3s ease;
   z-index: 10;
-  /* 初始旋转调整,使图片朝右时显示为正的 */
-  transform-origin: center;
 }
 
 /* 碰撞动画 */
@@ -919,17 +867,17 @@ onUnmounted(() => {
 }
 
 @keyframes success {
-  0% { transform: rotate(0deg) scale(1); }
-  10% { transform: rotate(0deg) scale(1.2) translateX(-5px) translateY(-5px); }
-  20% { transform: rotate(0deg) scale(1.3) translateX(5px) translateY(5px); }
-  30% { transform: rotate(0deg) scale(1.2) translateX(-5px) translateY(-5px); }
-  40% { transform: rotate(0deg) scale(1.3) translateX(5px) translateY(5px); }
-  50% { transform: rotate(0deg) scale(1.4) translateX(0) translateY(0); }
-  60% { transform: rotate(0deg) scale(1.3) translateX(-3px) translateY(-3px); }
-  70% { transform: rotate(0deg) scale(1.2) translateX(3px) translateY(3px); }
-  80% { transform: rotate(0deg) scale(1.3) translateX(-3px) translateY(-3px); }
-  90% { transform: rotate(0deg) scale(1.2) translateX(3px) translateY(3px); }
-  100% { transform: rotate(0deg) scale(1); }
+  0% { transform: rotate(var(--player-rotation)) scale(1); }
+  10% { transform: rotate(var(--player-rotation)) scale(1.2) translateX(-5px) translateY(-5px); }
+  20% { transform: rotate(var(--player-rotation)) scale(1.3) translateX(5px) translateY(5px); }
+  30% { transform: rotate(var(--player-rotation)) scale(1.2) translateX(-5px) translateY(-5px); }
+  40% { transform: rotate(var(--player-rotation)) scale(1.3) translateX(5px) translateY(5px); }
+  50% { transform: rotate(var(--player-rotation)) scale(1.4) translateX(0) translateY(0); }
+  60% { transform: rotate(var(--player-rotation)) scale(1.3) translateX(-3px) translateY(-3px); }
+  70% { transform: rotate(var(--player-rotation)) scale(1.2) translateX(3px) translateY(3px); }
+  80% { transform: rotate(var(--player-rotation)) scale(1.3) translateX(-3px) translateY(-3px); }
+  90% { transform: rotate(var(--player-rotation)) scale(1.2) translateX(3px) translateY(3px); }
+  100% { transform: rotate(var(--player-rotation)) scale(1); }
 }
 
 /* 游戏消息样式 */
@@ -982,8 +930,7 @@ onUnmounted(() => {
 // 合并重复的区块标题样式
 .map-section h2,
 .toolbox-section h2,
-.workspace-section h2,
-.output-section h2 {
+.workspace-section h2 {
   margin-bottom: 15px;
   color: #2c3e50;
   border-bottom: 2px solid #3498db;
@@ -1047,34 +994,6 @@ button: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 {