From d96c9789886f4f5c6f3f31b58cf2464fe375e368 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=8E=8B=E7=AB=8B=E5=B8=AE?= <3294713004@qq.com>
Date: Tue, 29 Apr 2025 00:48:25 +0800
Subject: [PATCH] =?UTF-8?q?feat(boards):=20python=5Fskulpt=5Fcar=E6=B7=BB?=
=?UTF-8?q?=E5=8A=A0=20`=E5=88=86=E6=AD=A5=E6=89=A7=E8=A1=8C`=20=E4=B8=8E?=
=?UTF-8?q?=20`=E5=85=B3=E5=8D=A1=E9=80=89=E6=8B=A9`?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../python_skulpt_car/others/nav-ext.js | 90 ++++++++++++++++++-
.../python_skulpt_car/others/py-engine.js | 60 +++++++++++++
.../python_skulpt_car/others/python-shell.js | 21 ++++-
.../others/skulpt/libs/bg_highlight.js | 35 ++++----
.../others/skulpt/libs/bg_nonehl.js | 2 -
.../python_skulpt_car/template.xml | 7 --
.../templates/html/level-selector.html | 13 +++
7 files changed, 199 insertions(+), 29 deletions(-)
create mode 100644 boards/default_src/python_skulpt_car/templates/html/level-selector.html
diff --git a/boards/default_src/python_skulpt_car/others/nav-ext.js b/boards/default_src/python_skulpt_car/others/nav-ext.js
index 52aabb33..53800d64 100644
--- a/boards/default_src/python_skulpt_car/others/nav-ext.js
+++ b/boards/default_src/python_skulpt_car/others/nav-ext.js
@@ -1,13 +1,64 @@
-import { app, Nav, Debug } from 'mixly';
+import $ from 'jquery';
import * as Blockly from 'blockly/core';
+import {
+ app,
+ Nav,
+ Debug,
+ HTMLTemplate,
+ Msg,
+ Workspace,
+ Storage
+} from 'mixly';
import PythonShell from './python-shell';
+import LEVEL_SELECTOR_TEMPLATE from '../templates/html/level-selector.html';
+
const NavExt = {};
+const LEVELS = [
+ `
+
+ `,
+ `
+
+ `,
+ `
+
+ `,
+ `
+
+ `,
+ `
+
+ `,
+ `
+
+ `,
+ `
+
+ `
+];
+NavExt.$shadow = $('
');
+NavExt.count = 0;
NavExt.init = function () {
PythonShell.init();
const nav = app.getNav();
+ nav.register({
+ icon: 'icon-play-circled',
+ title: '',
+ id: 'python-steprun-btn',
+ displayText: Blockly.Msg.MSG['step_run'],
+ preconditionFn: () => {
+ return true;
+ },
+ callback: () => {
+ PythonShell.steprun().catch(Debug.error);
+ },
+ scopeType: Nav.Scope.LEFT,
+ weight: 4
+ });
+
nav.register({
icon: 'icon-play-circled',
title: '',
@@ -20,7 +71,7 @@ NavExt.init = function () {
PythonShell.run().catch(Debug.error);
},
scopeType: Nav.Scope.LEFT,
- weight: 4
+ weight: 5
});
nav.register({
@@ -35,8 +86,41 @@ NavExt.init = function () {
PythonShell.stop().catch(Debug.error);
},
scopeType: Nav.Scope.LEFT,
- weight: 5
+ weight: 6
});
+
+ const template = new HTMLTemplate(LEVEL_SELECTOR_TEMPLATE);
+ const $selector = $(template.render());
+ nav.getBoardSelector().before($selector);
+ $selector.select2({
+ width: '90px',
+ minimumResultsForSearch: Infinity,
+ dropdownCssClass: `mixly-scrollbar mixly-${template.getId()}`,
+ dropdownAutoWidth: true,
+ placeholder: '',
+ language: Msg.nowLang
+ });
+ for (let i = 0; i < LEVELS.length; i++) {
+ const option = new Option(`关卡 ${i + 1}`, i);
+ $selector.append(option);
+ }
+ $selector.on('select2:select', (event) => {
+ const { data } = event.params;
+ const mainWorkspace = Workspace.getMain();
+ const editor = mainWorkspace.getEditorsManager().getActive();
+ editor.setValue(LEVELS[parseInt(data.id)], '.mix');
+ });
+ $selector.on('select2:opening', () => {
+ NavExt.count += 1;
+ $(document.body).append(NavExt.$shadow);
+ });
+ $selector.on('select2:closing', () => {
+ NavExt.count -= 1;
+ !NavExt.count && NavExt.$shadow.detach();
+ });
+ $selector.trigger('change');
+ Storage.board('mix', LEVELS[0]);
+ Storage.board('path', '');
}
export default NavExt;
\ No newline at end of file
diff --git a/boards/default_src/python_skulpt_car/others/py-engine.js b/boards/default_src/python_skulpt_car/others/py-engine.js
index 77337b81..3e68f79c 100644
--- a/boards/default_src/python_skulpt_car/others/py-engine.js
+++ b/boards/default_src/python_skulpt_car/others/py-engine.js
@@ -268,6 +268,53 @@ export default class PyEngine {
return true;
}
+ /**
+ * Runs the given python code, resetting the console and Trace Table.
+ * 分步调试代码
+ */
+ steprun(code) {
+ // Reset everything
+ this.reset();
+ if (code.indexOf('import blocklygame') !== -1
+ || code.indexOf('from blocklygame import') !== -1) {
+ PyGameZero.reset();
+ $(Sk.TurtleGraphics.target).empty();
+ }
+ //如果是第五关、第七关,则需要把检查循环次数的代码加进去
+ if (code.indexOf("settedMap(4") != -1 | code.indexOf("settedMap(6") != -1) {
+ if (code.indexOf("moveDirection") != -1) {//初始化的时候不加这行代码
+ code = code + "actor.isCirculationRight()\n"
+ }
+ }
+
+ //除了第六关,其他把检查是否成功代码加进去
+ if (code.indexOf("settedMap(5)") == -1) {
+ if (code.indexOf("moveDirection") != -1) {//初始化的时候不加这行代码
+ code = code + "actor.isSuccess()\n"
+ }
+ }
+ this.programStatus['running'] = true;
+ Sk.misceval.asyncToPromise(() => Sk.importMainWithBody("", false, code, true))
+ .then(() => {
+ // window.SPRITE.running = false;
+ this.programStatus['running'] = false;
+ this.#events_.run('finished');
+ })
+ .catch((error) => {
+ Debug.error(error);
+ // window.SPRITE.running = false;
+ this.programStatus['running'] = false;
+ this.#events_.run('error', error);
+ var original = prettyPrintError(error);
+ this.#events_.run('finished');
+ //hack for kill program with time limiterror
+ if (original.indexOf("TimeLimitError") !== -1) {
+ return;
+ }
+ this.executionEnd_();
+ });
+ }
+
/**
* Runs the given python code, resetting the console and Trace Table.
*/
@@ -308,6 +355,19 @@ export default class PyEngine {
}
}
}
+ //如果是第五关、第七关,则需要把检查循环次数的代码加进去
+ if (code.indexOf("settedMap(4") != -1 | code.indexOf("settedMap(6") != -1) {
+ if (code.indexOf("moveDirection") != -1) {//初始化的时候不加这行代码
+ code = code + "actor.isCirculationRight();\n"
+ }
+ }
+
+ //除了第六关,其他把检查是否成功代码加进去
+ if (code.indexOf("settedMap(5)") == -1) {
+ if (code.indexOf("moveDirection") != -1) {//初始化的时候不加这行代码
+ code = code + "actor.isSuccess();\n"
+ }
+ }
this.programStatus['running'] = true;
Sk.misceval.asyncToPromise(() => Sk.importMainWithBody("", false, code, true))
.then(() => {
diff --git a/boards/default_src/python_skulpt_car/others/python-shell.js b/boards/default_src/python_skulpt_car/others/python-shell.js
index 9a6c1c41..45b25764 100644
--- a/boards/default_src/python_skulpt_car/others/python-shell.js
+++ b/boards/default_src/python_skulpt_car/others/python-shell.js
@@ -12,6 +12,13 @@ class PythonShell {
this.pythonShell = new PythonShell();
}
+ this.steprun = function () {
+ const mainWorkspace = Workspace.getMain();
+ const editor = mainWorkspace.getEditorsManager().getActive();
+ const code = editor.getCode();
+ return this.pythonShell.steprun(code);
+ }
+
this.run = function () {
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
@@ -77,7 +84,6 @@ class PythonShell {
this.#statusBarTerminal_ = this.#statusBarsManager_.getStatusBarById('output');
this.#statusBarImage_ = this.#statusBarsManager_.getStatusBarById('images');
this.#pyEngine_ = new PyEngine({}, new MixpyProject());
- console.log(this.#statusBarImage_.getContent().children())
this.#pyEngine_.loadEngine(this.#statusBarImage_.getContent().children()[0]);
this.#addEventsListener_();
}
@@ -157,6 +163,19 @@ class PythonShell {
this.#enterInput_();
}
+ async steprun(code) {
+ await this.stop();
+ this.#statusBarsManager_.changeTo('output');
+ this.#statusBarsManager_.show();
+ this.#statusBarTerminal_.setValue(`${Msg.Lang['shell.running']}...\n`);
+ this.#running_ = true;
+ if (code.indexOf('import blocklygame') !== -1
+ || code.indexOf('from blocklygame import') !== -1) {
+ this.#statusBarsManager_.changeTo('images');
+ }
+ this.#pyEngine_.steprun(code);
+ }
+
async run(code) {
await this.stop();
this.#statusBarsManager_.changeTo('output');
diff --git a/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_highlight.js b/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_highlight.js
index 19afcf81..8c458557 100644
--- a/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_highlight.js
+++ b/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_highlight.js
@@ -2,7 +2,7 @@
var $builtinmodule = function (name) {
let mod= {__name__: new Sk.builtin.str("blocklygame")};
- var svg = d3.select('#blocklySVG').append('svg');
+ var svg = d3.select(Sk.TurtleGraphics.target).append('svg');
//其他变量设置
var map=//迷宫布局
@@ -63,7 +63,8 @@ var $builtinmodule = function (name) {
marker_num:0,
oil:1,//表示小车有充足的油量(为了适应教材而新增的变量)
traffic_light:22,//表示红绿灯为绿灯
- circulation_num:0//小车在赛道中循环的次数
+ circulation_num:0,//小车在赛道中循环的次数
+ invisible_mark:0//在地图中不可显示的标记数目,用于检测小车是否沿着特定路线走。规定不可见的标记点INVIMAKER=24
};
//迷宫变量
var maze_SQUARE_SIZE = 50;
@@ -84,7 +85,8 @@ var $builtinmodule = function (name) {
OPEN: 1,
START: 2,
FINISH: 3,
- AWARD:4//金币奖励
+ AWARD:4,//金币奖励
+ INVIMAKER:24
},
//迷宫部分参数指定
MAZE_WIDTH : maze_SQUARE_SIZE * maze_COLS,
@@ -92,7 +94,8 @@ var $builtinmodule = function (name) {
PATH_WIDTH : maze_SQUARE_SIZE / 3,
result : ResultType.UNSET,
finish : {x:0,y:0},
- type:1//类型为用户自定义的
+ type:1,//类型为用户自定义的
+ INVIMNUM : 0
};
//已经设置好的关卡的map
@@ -363,7 +366,7 @@ var $builtinmodule = function (name) {
* @param {number=} opt_angle Optional angle (in degrees) to rotate Pegman.
*/
var displayPegman = function(x, y, d, opt_angle) {
- var pegmanIcon = $('#pegman');
+ var pegmanIcon = $(Sk.TurtleGraphics.target).find('#pegman');
if(actor.type=='animate'){
if(maze.type==0){
pegmanIcon.attr('x', x * maze_SQUARE_SIZE - d * actor.width+ 1);
@@ -383,7 +386,7 @@ var $builtinmodule = function (name) {
pegmanIcon.attr('x', x * maze_SQUARE_SIZE + 1);
pegmanIcon.attr('y', maze_SQUARE_SIZE * (y + 0.5) - actor.height / 2 );
}
- var clipRect = $('#clipRect');
+ var clipRect = $(Sk.TurtleGraphics.target).find('#clipRect');
clipRect.attr('x', x * maze_SQUARE_SIZE + 1);
clipRect.attr('y', pegmanIcon.attr('y'));
};
@@ -391,7 +394,8 @@ var $builtinmodule = function (name) {
var initPegman=function(){
// Pegman's clipPath element, whose (x, y) is reset by Maze.displayPegman
svg.append('clipPath').attr('id','pegmanClipPath')
- d3.select("#pegmanClipPath").append('rect').attr('id','clipRect').attr('width', actor.width).attr('height', actor.height)
+ const elem = Sk.TurtleGraphics.target.querySelector("#pegmanClipPath");
+ d3.select(elem).append('rect').attr('id','clipRect').attr('width', actor.width).attr('height', actor.height)
if(actor.type=="animate"){
if(maze.type==0){
@@ -462,13 +466,13 @@ var $builtinmodule = function (name) {
var top = tile_SHAPES[tileShape][1];
// Tile's clipPath element.
svg.append('clipPath').attr('id','tileClipPath' + tileId)
- d3.select("#tileClipPath" + tileId).append('rect').attr('x', x * maze_SQUARE_SIZE).attr('y', y * maze_SQUARE_SIZE).attr('width', maze_SQUARE_SIZE).attr('height', maze_SQUARE_SIZE)
-
+ const elem = Sk.TurtleGraphics.target.querySelector("#tileClipPath" + tileId);
+ d3.select(elem).append('rect').attr('x', x * maze_SQUARE_SIZE).attr('y', y * maze_SQUARE_SIZE).attr('width', maze_SQUARE_SIZE).attr('height', maze_SQUARE_SIZE)
if(maze.type==0){//非用户自定义
// Tile sprite.
if ((map[y][x] != maze.SquareType.WALL) && (map[y][x] != maze.SquareType.OIL_STATION) && (map[y][x] != maze.SquareType.TRAFFIC_LIGHT)&& (map[y][x] != maze.SquareType.LIGHT_GREEN)&& (map[y][x] != maze.SquareType.LIGHT_RED)) {
svg.append('image').attr('x', x * maze_SQUARE_SIZE).attr('y', y * maze_SQUARE_SIZE).attr('width',maze_SQUARE_SIZE ).attr('height',maze_SQUARE_SIZE )
- .attr('clip-path', 'url(#tileClipPath' + tileId + ')').attr('xlink:href',maze.tiles)
+ .attr('clip-path', 'url(#tileClipPath' + tileId + ')').attr('xlink:href',maze.tiles);
tileId++;
}
}else{
@@ -519,7 +523,7 @@ var $builtinmodule = function (name) {
actor.y= y;
} else if (map[y][x] == maze.SquareType.FINISH) {
// Move the finish icon into position.
- var finishIcon = $('#finish');
+ var finishIcon = $(Sk.TurtleGraphics.target).find('#finish');
finishIcon.attr('x', maze_SQUARE_SIZE * (x + 0.5) -
finishIcon.attr('width') / 2);
finishIcon.attr('y', maze_SQUARE_SIZE * (y + 0.6) -
@@ -538,7 +542,7 @@ var $builtinmodule = function (name) {
svg.append('image').attr('id','finish').attr('width', maze_SQUARE_SIZE*0.8).attr('height', maze_SQUARE_SIZE*0.8).attr('xlink:href',maze.marker)
actor.x= x;
actor.y= y;
- var finishIcon = $('#finish');
+ var finishIcon = $(Sk.TurtleGraphics.target).find('#finish');
finishIcon.attr('x', maze_SQUARE_SIZE * x+5 );
finishIcon.attr('y', maze_SQUARE_SIZE * y+5);
maze.finish={x:x,y:y}
@@ -574,7 +578,6 @@ var $builtinmodule = function (name) {
return true
}else{
if(maze.mlevel==5 || maze.mlevel==7 ||maze.mlevel==6){
- console.log(11)
return true
}else{
if(actor.marker_num==maze_marker_num){
@@ -713,7 +716,7 @@ var $builtinmodule = function (name) {
var hasCoin=function(x , y) {
if(map[y][x]==maze.SquareType.AWARD){//如果此处是金币
setTimeout(function() {
- $('#coin'+y+x).remove()
+ $(Sk.TurtleGraphics.target).find('#coin'+y+x).remove()
}, actor.stepSpeed * 3)
map[y][x]=maze.SquareType.OPEN
actor.coin_point+=1
@@ -1147,7 +1150,8 @@ var $builtinmodule = function (name) {
map[actor.y][actor.x+1]=Math.random()>0.5? maze.SquareType.LIGHT_RED:maze.SquareType.LIGHT_GREEN;//随机刷新红绿灯的状态
actor.traffic_light=map[actor.y][actor.x+1];
if(actor.traffic_light==maze.SquareType.LIGHT_RED){//图像变为红灯
- d3.select("#lightgreen").remove();
+ const elem = Sk.TurtleGraphics.target.querySelector("#lightgreen");
+ d3.select(elem).remove();
svg.append('image').attr('id','lightred').attr('x',(actor.x+1) * maze_SQUARE_SIZE-5).attr('y',actor.y * maze_SQUARE_SIZE+5).attr('width',maze_SQUARE_SIZE).attr('height',maze_SQUARE_SIZE)
.attr('xlink:href','../common/js/skulpt_mixcar/pic/redlight.png')
}
@@ -1307,7 +1311,6 @@ var $builtinmodule = function (name) {
Sk.builtin.pyCheckArgs("isSuccess", arguments, 1,1);
return new Sk.misceval.promiseToSuspension(new Promise(function(resolve) {
var state=checkFinish()
- console.log(state)
if(state==true){
setTimeout(function() {
layer.alert("挑战成功!", { shade: false });
diff --git a/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_nonehl.js b/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_nonehl.js
index 4e0b5d35..18296d3f 100644
--- a/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_nonehl.js
+++ b/boards/default_src/python_skulpt_car/others/skulpt/libs/bg_nonehl.js
@@ -579,7 +579,6 @@ var $builtinmodule = function (name) {
return true
}else{
if(maze.mlevel==5 || maze.mlevel==7 ||maze.mlevel==6){
- console.log(11)
return true
}else{
if(actor.marker_num==maze_marker_num){
@@ -1172,7 +1171,6 @@ var $builtinmodule = function (name) {
Sk.builtin.pyCheckArgs("isSuccess", arguments, 1,1);
return new Sk.misceval.promiseToSuspension(new Promise(function(resolve) {
var state=checkFinish()
- console.log(state)
if(state==true){
setTimeout(function() {
layer.alert("挑战成功!", { shade: false });
diff --git a/boards/default_src/python_skulpt_car/template.xml b/boards/default_src/python_skulpt_car/template.xml
index b441fb93..ef5730bd 100644
--- a/boards/default_src/python_skulpt_car/template.xml
+++ b/boards/default_src/python_skulpt_car/template.xml
@@ -1166,13 +1166,6 @@
-
-
-
-
-
-
-
diff --git a/boards/default_src/python_skulpt_car/templates/html/level-selector.html b/boards/default_src/python_skulpt_car/templates/html/level-selector.html
new file mode 100644
index 00000000..df1ebd85
--- /dev/null
+++ b/boards/default_src/python_skulpt_car/templates/html/level-selector.html
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file