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