feat(boards): python_skulpt_car添加 分步执行关卡选择

This commit is contained in:
王立帮
2025-04-29 00:48:25 +08:00
parent 25998b66df
commit d96c978988
7 changed files with 199 additions and 29 deletions

View File

@@ -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 = [
`<xml>
<block type="initSettedMap_1"></block>
</xml>`,
`<xml>
<block type="initSettedMap_2"></block>
</xml>`,
`<xml>
<block type="initSettedMap_3"></block>
</xml>`,
`<xml>
<block type="initSettedMap_4"></block>
</xml>`,
`<xml>
<block type="initSettedMap_5"></block>
</xml>`,
`<xml>
<block type="initSettedMap_6"></block>
</xml>`,
`<xml>
<block type="initSettedMap_7"></block>
</xml>`
];
NavExt.$shadow = $('<div style="position:absolute;z-index:1000;width:100%;background:transparent;bottom:0px;top:var(--nav-height);"></div>');
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;

View File

@@ -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("<stdin>", 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("<stdin>", false, code, true))
.then(() => {

View File

@@ -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');

View File

@@ -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 });

View File

@@ -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 });

View File

@@ -1166,13 +1166,6 @@
<!-- 游戏:需要添加 -->
<category id="catGame" name="game" colour="270">
<block type="initSettedMap_1"></block>
<block type="initSettedMap_2"></block>
<block type="initSettedMap_3"></block>
<block type="initSettedMap_4"></block>
<block type="initSettedMap_5"></block>
<block type="initSettedMap_6"></block>
<block type="initSettedMap_7"></block>
<block type="move_direction_steps">
<value name="times">
<shadow type="math_number">

View File

@@ -0,0 +1,13 @@
<style>
.select2-dropdown.mixly-{{d.mId}} {
width: 100% !important;
transform: translate(calc(-50% + 45px), calc((var(--nav-height) - var(--nav-select-height)) / 2));
}
body > .select2-container:has(.mixly-{{d.mId}}) {
animation-duration: 0.3s;
animation-fill-mode: both;
animation-name: layui-upbit;
}
</style>
<select m-id={{d.mId}}></select>