4

我在做什么

我正在使用 Blockly 构建一个海龟图形应用程序。用户可以从块构建代码,然后 Blockly 引擎生成 JS 代码,将其绘制到画布上。

我的问题是什么

Blockly 引擎生成 JS 代码,但将其作为字符串返回,我必须将eval()其绘制到画布上。

我可以更改块的代码以生成不同的输出,但重要的是要使其尽可能简单,因为用户可以阅读块输入背后的实际代码。所以我不想把它搞砸。

我想做什么

我可以完全控制原子操作(goturn等),所以我想在函数的开头插入一小段代码,这会延迟函数其余部分的执行。就像是:

function go(dir, dist) {
  // wait here a little

  // do the drawing
}

我认为它应该是同步的,它可以保持执行流程的延迟。我尝试使用setTimeout(async,fail),a promise(fail),循环中的时间戳检查(fail)。

在JS中甚至可能吗?

4

3 回答 3

3

您不能让代码同步等待。您唯一会得到的是一个冻结的浏览器窗口。

您需要的是使用js 解释器而不是 eval。通过这种方式,您可以暂停执行、播放动画、突出显示当前正在执行的块等……本教程有许多示例可以帮助您入门。这是一个基于JS 解释器示例的工作代码:

var workspace = Blockly.inject("editor-div", {
  toolbox: document.getElementById('toolbox')
});

Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
Blockly.JavaScript.addReservedWords('highlightBlock');

Blockly.JavaScript['text_print'] = function(block) {
  var argument0 = Blockly.JavaScript.valueToCode(
    block, 'TEXT',
    Blockly.JavaScript.ORDER_FUNCTION_CALL
  ) || '\'\'';
  return "print(" + argument0 + ');\n';
};

function run() {
  var code = Blockly.JavaScript.workspaceToCode(workspace);
  var running = false;

  workspace.traceOn(true);
  workspace.highlightBlock(null);

  var lastBlockToHighlight = null;
  var myInterpreter = new Interpreter(code, (interpreter, scope) => {
    interpreter.setProperty(
      scope, 'highlightBlock',
      interpreter.createNativeFunction(id => {
        id = id ? id.toString() : '';
        running = false;
        workspace.highlightBlock(lastBlockToHighlight);
        lastBlockToHighlight = id;
      })
    );
    interpreter.setProperty(
      scope, 'print',
      interpreter.createNativeFunction(val => {
        val = val ? val.toString() : '';
        console.log(val);
      })
    );
  });

  var intervalId = setInterval(() => {
    running = true;
    while (running) {
      if (!myInterpreter.step()) {
        workspace.highlightBlock(lastBlockToHighlight);
        clearInterval(intervalId);
        return;
      }
    }
  }, 500);
}
#editor-div {
  width: 500px;
  height: 150px;
}
<script src="https://rawgit.com/google/blockly/master/blockly_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/blocks_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/javascript_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/msg/js/en.js"></script>
<script src="https://rawgit.com/NeilFraser/JS-Interpreter/master/acorn_interpreter.js"></script>

<xml id="toolbox" style="display: none">
  <block type="text"></block>
  <block type="text_print"></block>
  <block type="controls_repeat_ext"></block>
 <block type="math_number"></block>
</xml>

<div>
  <button id="run-code" onclick="run()">run</button>
</div>
<div id="editor-div"></div>

编辑

添加变量running来控制解释器。现在它会单步执行,直到running变量设置为 false,因此running = falsehighlightBlock 函数内的语句本质上是一个断点。

编辑

引入lastBlockToHighlight变量来延迟高亮,所以最新的运行语句被高亮,而不是下一个。不幸的是,JavaScript 代码生成器没有STATEMENT_SUFFIX类似于STATEMENT_PREFIX.

于 2016-08-21T23:05:34.180 回答
2

最近我发布了一个库,可以让你与 blockly 进行异步交互,我为类似的游戏设计了这个库。事实上,在文档中您可以找到一个重制迷宫游戏的游戏演示。该库被称为blockly-gamepad,我希望它是您正在寻找的。


这是演示的 GIF。

演示


这个怎么运作

与正常使用 blockly 相比,这是一种不同且简化的方法。

首先,您必须定义 (请参阅如何在文档中定义它们)
您不必定义any code generator,所有与代码生成有关的事情都由库执行。

在此处输入图像描述


每个块生成一个请求

// the request
{ method: 'TURN', args: ['RIGHT'] }


当一个块被执行时,相应的请求被传递给你的游戏

class Game{
    manageRequests(request){
        // requests are passed here
        if(request.method == 'TURN')
            // animate your sprite
            turn(request.args)
    }
}


您可以使用承诺来管理异步动画,就像您的情况一样。

class Game{
    async manageRequests(request){
        if(request.method == 'TURN')
            await turn(request.args)
    }
}


块和您的游戏之间的链接由游戏手柄管理。

let gamepad = new Blockly.Gamepad(),
    game = new Game()

// requests will be passed here
gamepad.setGame(game, game.manageRequest)


游戏手柄提供了一些方法来管理块执行和请求生成

// load the code from the blocks in the workspace
gamepad.load()
// reset the code loaded previously
gamepad.reset()

// the blocks are executed one after the other
gamepad.play() 
// play in reverse
gamepad.play(true)
// the blocks execution is paused
gamepad.pause()
// toggle play
gamepad.togglePlay()

// load the next request 
gamepad.forward()
// load the prior request
gamepad.backward()

// use a block as a breakpoint and play until it is reached
gamepad.debug(id)

您可以在此处阅读完整的文档。


编辑:我更新了库的名称,现在称为blockly-gamepad

于 2019-07-12T16:35:43.560 回答
1

如果我理解你!

您可以构建一个新类来处理go(dir, dist)函数的执行,并覆盖 go 函数以在执行器中创建新的go

function GoExecutor(){

    var executeArray = [];     // Store go methods that waiting for execute
    var isRunning = false;     // Handle looper function

    // start runner function
    var run = function(){
        if(isRunning)
            return;
        isRunning = true;
        runner();
    }

    // looper for executeArray
    var runner = function(){
        if(executeArray.length == 0){
            isRunning = false;
            return;
        }

        // pop the first inserted params 
        var currentExec = executeArray.shift(0);

        // wait delay miliseconds
        setTimeout(function(){
            // execute the original go function
            originalGoFunction(currentExec.dir, currentExec.dist);

            // after finish drawing loop on the next execute method
            runner();
        }, currentExec.delay);

    }
    this.push = function(dir, dist){
        executeArray.push([dir,dist]);
        run();
    }
}

// GoExecutor instance
var goExec = new GoExecutor();

// Override go function
var originalGoFunction = go;
var go = function (dir, dist, delay){
    goExec.push({"dir":dir, "dist":dist, "delay":delay});
}

编辑1:

现在您必须使用您的函数和参数调用callWithDelay,执行程序将通过将参数应用于指定函数来处理此调用。

function GoExecutor(){

    var executeArray = [];     // Store go methods that waiting for execute
    var isRunning = false;     // Handle looper function

    // start runner function
    var run = function(){
        if(isRunning)
            return;
        isRunning = true;
        runner();
    }

    // looper for executeArray
    var runner = function(){
        if(executeArray.length == 0){
            isRunning = false;
            return;
        }

        // pop the first inserted params 
        var currentExec = executeArray.shift(0);

        // wait delay miliseconds
        setTimeout(function(){
            // execute the original go function
            currentExec.funcNam.apply(currentExec.funcNam, currentExec.arrayParams);

            // after finish drawing loop on the next execute method
            runner();
        }, currentExec.delay);

    }
    this.push = function(dir, dist){
        executeArray.push([dir,dist]);
        run();
    }
}

// GoExecutor instance
var goExec = new GoExecutor();

var callWithDelay = function (func, arrayParams, delay){
    goExec.push({"func": func, "arrayParams":arrayParams, "delay":delay});
}
于 2016-08-21T22:53:11.010 回答